Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Toggle shuttercount #2234

Merged
merged 7 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#2230: Create checkbox that can be toggled on or off to apply ShutterCount correction to ToF data within the spectrum viewer window
123 changes: 86 additions & 37 deletions mantidimaging/gui/ui/spectrum_viewer.ui
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>1001</width>
<height>766</height>
<height>908</height>
</rect>
</property>
<property name="windowTitle">
Expand Down Expand Up @@ -80,7 +80,7 @@
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
<height>10</height>
</size>
</property>
</spacer>
Expand Down Expand Up @@ -154,7 +154,56 @@
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="normalise_ShutterCount_CheckBox">
<property name="text">
<string>ShutterCount Correction</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="shuttercountErrorIcon">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="ShuttercountSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
Expand Down Expand Up @@ -407,38 +456,38 @@
<property name="title">
<string>Time of Flight Properties</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Flight path:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="flightPathSpinBox">
<property name="suffix">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Time delay: </string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="timeDelaySpinBox">
<property name="suffix">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Flight path:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="flightPathSpinBox">
<property name="suffix">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Time delay: </string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="timeDelaySpinBox">
<property name="suffix">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
Expand All @@ -447,7 +496,7 @@
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
<height>20</height>
</size>
</property>
</spacer>
Expand Down Expand Up @@ -486,4 +535,4 @@
</customwidgets>
<resources/>
<connections/>
</ui>
</ui>
2 changes: 2 additions & 0 deletions mantidimaging/gui/windows/main/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ def load_shuttercounts_dialog(self):
QMessageBox.information(
self, "Load complete", f"{selected_file} was loaded as a shutter count file into "
f"{stack_to_add_shuttercounts_to}.")
if self.spectrum_viewer:
self.spectrum_viewer.handle_shuttercount_change()

def load_180_deg_dialog(self):
dataset_selector = DatasetSelectorDialog(main_window=self,
Expand Down
16 changes: 15 additions & 1 deletion mantidimaging/gui/windows/spectrum_viewer/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,17 @@ def normalise_issue(self) -> str:
return "Stack shapes must match"
return ""

def shuttercount_issue(self) -> str:
"""
Return an error message if there is an issue with the shutter count data.
"""
assert self._stack and self._normalise_stack
if not self._stack.shutter_count_file or not self._normalise_stack.shutter_count_file:
return "Need 2 selected ShutterCount stacks"
if self._stack.shutter_count_file.data == self._normalise_stack.shutter_count_file.data:
return "Need 2 different ShutterCount stacks"
return ""

def get_spectrum(self,
roi: str | SensibleROI,
mode: SpecType,
Expand Down Expand Up @@ -250,7 +261,10 @@ def get_shuttercount_normalised_correction_parameter(self) -> float:
open_shuttercount = self.get_stack_shuttercounts(self._normalise_stack)
if sample_shuttercount is None or open_shuttercount is None:
return 1.0 # No shutter count data available so no correction needed
normalised_shuttercounts = sample_shuttercount / open_shuttercount
normalised_shuttercounts = np.divide(sample_shuttercount.astype(np.float32),
open_shuttercount.astype(np.float32),
out=np.ones_like(sample_shuttercount, dtype=np.float32),
where=open_shuttercount != 0)
JackEAllen marked this conversation as resolved.
Show resolved Hide resolved
return normalised_shuttercounts[0]

def get_stack_shuttercounts(self, stack: ImageStack | None) -> np.ndarray | None:
Expand Down
42 changes: 36 additions & 6 deletions mantidimaging/gui/windows/spectrum_viewer/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def handle_sample_change(self, uuid: UUID | None) -> None:
self.do_add_roi()
self.add_rits_roi()
self.view.set_normalise_error(self.model.normalise_issue())
self.set_shuttercount_error()
self.show_new_sample()
self.view.on_visibility_change()

Expand All @@ -138,6 +139,7 @@ def handle_normalise_stack_change(self, normalise_uuid: UUID | None) -> None:
return
self.model.set_normalise_stack(self.main_window.get_stack(normalise_uuid))
self.view.set_normalise_error(self.model.normalise_issue())
self.set_shuttercount_error()
if self.view.normalisation_enabled():
self.redraw_all_rois()

Expand Down Expand Up @@ -192,7 +194,11 @@ def handle_roi_moved(self, force_new_spectrums: bool = False) -> None:
roi = self.view.spectrum_widget.get_roi(name)
if force_new_spectrums or roi != self.model.get_roi(name):
self.model.set_roi(name, roi)
self.view.set_spectrum(name, self.model.get_spectrum(name, self.spectrum_mode))
self.view.set_spectrum(
name,
self.model.get_spectrum(name,
self.spectrum_mode,
normalise_with_shuttercount=self.view.shuttercount_norm_enabled()))

def handle_roi_clicked(self, roi: SpectrumROI) -> None:
if not roi.name == ROI_RITS:
Expand All @@ -204,15 +210,23 @@ def redraw_spectrum(self, name: str) -> None:
"""
Redraw the spectrum with the given name
"""
self.view.set_spectrum(name, self.model.get_spectrum(name, self.spectrum_mode))
self.view.set_spectrum(
name,
self.model.get_spectrum(name,
self.spectrum_mode,
normalise_with_shuttercount=self.view.shuttercount_norm_enabled()))

def redraw_all_rois(self) -> None:
"""
Redraw all ROIs and spectrum plots
"""
for name in self.model.get_list_of_roi_names():
self.model.set_roi(name, self.view.spectrum_widget.get_roi(name))
self.view.set_spectrum(name, self.model.get_spectrum(name, self.spectrum_mode))
self.view.set_spectrum(
name,
self.model.get_spectrum(name,
self.spectrum_mode,
normalise_with_shuttercount=self.view.shuttercount_norm_enabled()))

def handle_button_enabled(self) -> None:
"""
Expand All @@ -225,6 +239,9 @@ def handle_button_enabled(self) -> None:
self.view.exportButton.setEnabled(has_stack and normalisation_no_error)
self.view.exportButtonRITS.setEnabled(has_stack and normalisation_on and normalisation_no_error)
self.view.addBtn.setEnabled(has_stack)
self.view.normalise_ShutterCount_CheckBox.setEnabled(has_stack and normalisation_on and normalisation_no_error)
if not self.view.normalise_ShutterCount_CheckBox.isEnabled():
self.view.normalise_ShutterCount_CheckBox.setChecked(False)

def handle_export_csv(self) -> None:
path = self.view.get_csv_filename()
Expand Down Expand Up @@ -277,6 +294,16 @@ def handle_enable_normalised(self, enabled: bool) -> None:
self.redraw_all_rois()
self.view.display_normalise_error()

def set_shuttercount_error(self, enabled: bool = False) -> None:
"""
Set ShutterCount error message when a valid normalisation stack has been selected and
shuttercount correction has been toggled on and redraw all ROIs to ensure the spectrum
is updated when ShutterCount correction is toggled between enabled and disabled states.
"""
if self.spectrum_mode is SpecType.SAMPLE_NORMED:
self.view.set_shuttercount_error(self.model.shuttercount_issue() if enabled else "")
self.redraw_all_rois()

def get_roi_names(self) -> list:
"""
Return a list of ROI names
Expand All @@ -292,7 +319,8 @@ def do_add_roi(self) -> None:
roi_name = self.model.roi_name_generator()
self.model.set_new_roi(roi_name)
self.view.spectrum_widget.add_roi(self.model.get_roi(roi_name), roi_name)
self.view.set_spectrum(roi_name, self.model.get_spectrum(roi_name, self.spectrum_mode))
self.view.set_spectrum(
roi_name, self.model.get_spectrum(roi_name, self.spectrum_mode, self.view.shuttercount_norm_enabled()))
self.view.auto_range_image()
self.do_add_roi_to_table(roi_name)

Expand All @@ -312,7 +340,8 @@ def add_rits_roi(self) -> None:
roi_name = ROI_RITS
self.model.set_new_roi(roi_name)
self.view.spectrum_widget.add_roi(self.model.get_roi(roi_name), roi_name)
self.view.set_spectrum(roi_name, self.model.get_spectrum(roi_name, self.spectrum_mode))
self.view.set_spectrum(
roi_name, self.model.get_spectrum(roi_name, self.spectrum_mode, self.view.shuttercount_norm_enabled()))
self.view.set_roi_alpha(0, ROI_RITS)

def do_add_roi_to_table(self, roi_name: str) -> None:
Expand Down Expand Up @@ -348,7 +377,8 @@ def do_remove_roi(self, roi_name: str | None = None) -> None:
self.model.remove_all_roi()
else:
self.view.spectrum_widget.remove_roi(roi_name)
self.view.set_spectrum(roi_name, self.model.get_spectrum(roi_name, self.spectrum_mode))
self.view.set_spectrum(
roi_name, self.model.get_spectrum(roi_name, self.spectrum_mode, self.view.shuttercount_norm_enabled()))
self.model.remove_roi(roi_name)

def handle_export_tab_change(self, index: int) -> None:
Expand Down
15 changes: 12 additions & 3 deletions mantidimaging/gui/windows/spectrum_viewer/test/presenter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from unittest import mock

import numpy as np
from PyQt5.QtWidgets import QPushButton, QActionGroup, QGroupBox, QAction
from PyQt5.QtWidgets import QPushButton, QActionGroup, QGroupBox, QAction, QCheckBox
from parameterized import parameterized

from mantidimaging.core.data.dataset import StrictDataset, MixedDataset
Expand Down Expand Up @@ -36,6 +36,7 @@ def setUp(self) -> None:
roi_dict=mock_spectrum_roi_dict)
self.view.exportButton = mock.create_autospec(QPushButton)
self.view.exportButtonRITS = mock.create_autospec(QPushButton)
self.view.normalise_ShutterCount_CheckBox = mock.create_autospec(QCheckBox)
self.view.addBtn = mock.create_autospec(QPushButton)
self.view.tof_mode_select_group = mock.create_autospec(QActionGroup)
self.view.tofPropertiesGroupBox = mock.create_autospec(QGroupBox)
Expand Down Expand Up @@ -134,6 +135,7 @@ def test_WHEN_no_stack_THEN_buttons_disabled(self, has_stack):
self.view.exportButton.setEnabled.assert_called_once_with(False)
self.view.exportButtonRITS.setEnabled.assert_called_once_with(False)
self.view.addBtn.setEnabled.assert_called_once_with(False)
self.view.normalise_ShutterCount_CheckBox.setEnabled.assert_called_once_with(False)

@mock.patch("mantidimaging.gui.windows.spectrum_viewer.model.SpectrumViewerWindowModel.has_stack")
def test_WHEN_has_stack_no_norm_THEN_buttons_set(self, has_stack):
Expand All @@ -143,17 +145,21 @@ def test_WHEN_has_stack_no_norm_THEN_buttons_set(self, has_stack):
self.view.exportButton.setEnabled.assert_called_once_with(True)
self.view.exportButtonRITS.setEnabled.assert_called_once_with(False) # RITS export needs norm
self.view.addBtn.setEnabled.assert_called_once_with(True)
self.view.normalise_ShutterCount_CheckBox.setEnabled.assert_called_once_with(False) # Shuttercount needs norm

@mock.patch("mantidimaging.gui.windows.spectrum_viewer.model.SpectrumViewerWindowModel.has_stack")
@mock.patch("mantidimaging.gui.windows.spectrum_viewer.model.SpectrumViewerWindowModel.normalise_issue")
def test_WHEN_has_stack_has_good_norm_THEN_buttons_set(self, normalise_issue, has_stack):
@mock.patch("mantidimaging.gui.windows.spectrum_viewer.model.SpectrumViewerWindowModel.shuttercount_issue")
def test_WHEN_has_stack_has_good_norm_THEN_buttons_set(self, shuttercount_issue, normalise_issue, has_stack):
has_stack.return_value = True
normalise_issue.return_value = ""
shuttercount_issue.return_value = ""
self.view.normalisation_enabled.return_value = True
self.presenter.handle_button_enabled()
self.view.exportButton.setEnabled.assert_called_once_with(True)
self.view.exportButtonRITS.setEnabled.assert_called_once_with(True)
self.view.addBtn.setEnabled.assert_called_once_with(True)
self.view.normalise_ShutterCount_CheckBox.setEnabled.assert_called_once_with(True)

@mock.patch("mantidimaging.gui.windows.spectrum_viewer.model.SpectrumViewerWindowModel.has_stack")
@mock.patch("mantidimaging.gui.windows.spectrum_viewer.model.SpectrumViewerWindowModel.normalise_issue")
Expand All @@ -165,6 +171,7 @@ def test_WHEN_has_stack_has_bad_norm_THEN_buttons_set(self, normalise_issue, has
self.view.exportButton.setEnabled.assert_called_once_with(False)
self.view.exportButtonRITS.setEnabled.assert_called_once_with(False)
self.view.addBtn.setEnabled.assert_called_once_with(True)
self.view.normalise_ShutterCount_CheckBox.setEnabled.assert_called_once_with(False)

def test_WHEN_show_sample_call_THEN_add_range_set(self):
self.presenter.model.set_stack(generate_images([10, 5, 5]))
Expand All @@ -191,10 +198,12 @@ def test_handle_export_csv_none(self, mock_save_csv: mock.Mock):
mock_save_csv.assert_not_called()

@parameterized.expand(["/fake/path", "/fake/path.csv"])
@mock.patch("mantidimaging.gui.windows.spectrum_viewer.model.SpectrumViewerWindowModel.shuttercount_issue")
@mock.patch("mantidimaging.gui.windows.spectrum_viewer.model.SpectrumViewerWindowModel.save_csv")
def test_handle_export_csv(self, path_name: str, mock_save_csv: mock.Mock):
def test_handle_export_csv(self, path_name: str, mock_save_csv: mock.Mock, mock_shuttercount_issue):
self.view.get_csv_filename = mock.Mock(return_value=Path(path_name))
self.view.shuttercount_norm_enabled.return_value = False
mock_shuttercount_issue.return_value = "Something wrong"

self.presenter.model.set_stack(generate_images())

Expand Down
Loading