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

Unit tests for Time of Flight user properties in Spectrum Viewer #2180

Merged
merged 6 commits into from
May 9, 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
1 change: 1 addition & 0 deletions docs/release_notes/next/dev-2177-spectrum-viewer-ToF-tests
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#2177: Added unit tests for the Time of Flight user properties in the Spectrum Viewer
45 changes: 38 additions & 7 deletions mantidimaging/gui/windows/spectrum_viewer/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import csv
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, TypedDict

import numpy as np
from math import ceil
Expand Down Expand Up @@ -52,6 +52,31 @@ def get_by_value(cls, value: str) -> ErrorMode:
raise ValueError(f"Unknown error mode: {value}")


class AllowedModesTypedDict(TypedDict):
mode: ToFUnitMode
label: str


allowed_modes: dict[str, AllowedModesTypedDict] = {
"Image Index": {
"mode": ToFUnitMode.IMAGE_NUMBER,
"label": "Image index"
},
"Wavelength": {
"mode": ToFUnitMode.WAVELENGTH,
"label": "Neutron Wavelength (\u212B)"
},
"Energy": {
"mode": ToFUnitMode.ENERGY,
"label": "Neutron Energy (MeV)"
},
"Time of Flight (\u03BCs)": {
"mode": ToFUnitMode.TOF_US,
"label": "Time of Flight (\u03BCs)"
}
}


class SpectrumViewerWindowModel:
"""
The model for the spectrum viewer window.
Expand All @@ -75,12 +100,6 @@ def __init__(self, presenter: SpectrumViewerWindowPresenter):
self._roi_ranges = {}
self.special_roi_list = [ROI_ALL]

self.tof_data = self.get_stack_time_of_flight()
if self.tof_data is None:
self.tof_mode = ToFUnitMode.IMAGE_NUMBER
else:
self.tof_mode = ToFUnitMode.WAVELENGTH

self.units = UnitConversion()

def roi_name_generator(self) -> str:
Expand Down Expand Up @@ -481,3 +500,15 @@ def set_relevant_tof_units(self) -> None:
self.tof_data = self.units.tof_seconds_to_energy()
self.tof_plot_range = (self.tof_data.min(), self.tof_data.max())
self.tof_range = (0, self.tof_data.size)

def set_tof_unit_mode_for_stack(self) -> None:
if self.get_stack_time_of_flight() is None or self.tof_data is None:
self.tof_mode = ToFUnitMode.IMAGE_NUMBER
self.presenter.change_selected_menu_option("Image Index")
elif self.tof_mode == ToFUnitMode.ENERGY:
self.presenter.change_selected_menu_option("Energy")
elif self.tof_mode == ToFUnitMode.TOF_US:
self.presenter.change_selected_menu_option("Time of Flight (\u03BCs)")
else:
self.tof_mode = ToFUnitMode.WAVELENGTH
self.presenter.change_selected_menu_option("Wavelength")
38 changes: 22 additions & 16 deletions mantidimaging/gui/windows/spectrum_viewer/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from mantidimaging.gui.dialogs.async_task import start_async_task_view, TaskWorkerThread
from mantidimaging.gui.mvp_base import BasePresenter
from mantidimaging.gui.windows.spectrum_viewer.model import SpectrumViewerWindowModel, SpecType, ROI_RITS, ErrorMode, \
ToFUnitMode
ToFUnitMode, allowed_modes

if TYPE_CHECKING:
from mantidimaging.gui.windows.spectrum_viewer.view import SpectrumViewerWindowView # pragma: no cover
Expand Down Expand Up @@ -67,7 +67,10 @@ def handle_stack_changed(self) -> None:
except RuntimeError:
norm_stack = None
self.model.set_normalise_stack(norm_stack)

self.model.set_tof_unit_mode_for_stack()
self.reset_units_menu()

self.handle_tof_unit_change()
self.show_new_sample()
self.redraw_all_rois()
Expand All @@ -92,7 +95,9 @@ def handle_sample_change(self, uuid: UUID | None) -> None:
return

self.model.set_stack(self.main_window.get_stack(uuid))
self.model.set_tof_unit_mode_for_stack()
self.reset_units_menu()

self.handle_tof_unit_change()
normalise_uuid = self.view.get_normalise_stack()
if normalise_uuid is not None:
Expand All @@ -112,20 +117,12 @@ def reset_units_menu(self):
if self.model.tof_data is None:
self.view.tof_mode_select_group.setEnabled(False)
self.view.tofPropertiesGroupBox.setEnabled(False)
else:
self.view.tof_mode_select_group.setEnabled(True)
self.view.tofPropertiesGroupBox.setEnabled(True)
self.model.tof_mode = ToFUnitMode.IMAGE_NUMBER
for action in self.view.tof_mode_select_group.actions():
with QSignalBlocker(action):
if action.objectName() == 'Image Index':
action.setChecked(True)
else:
action.setChecked(False)
if self.model.tof_data is None:
self.model.tof_mode = ToFUnitMode.IMAGE_NUMBER
self.change_selected_menu_option("Image Index")
self.view.tof_mode_select_group.setEnabled(False)
else:
self.view.tof_mode_select_group.setEnabled(True)
self.view.tofPropertiesGroupBox.setEnabled(True)

def handle_normalise_stack_change(self, normalise_uuid: UUID | None) -> None:
if normalise_uuid == self.current_norm_stack_uuid:
Expand Down Expand Up @@ -352,13 +349,15 @@ def handle_export_tab_change(self, index: int) -> None:
self.view.on_visibility_change()

def handle_tof_unit_change(self) -> None:
selected_mode = self.view.tof_mode_select_group.checkedAction().text()
self.model.tof_mode = self.view.allowed_modes[selected_mode]["mode"]
self.model.set_relevant_tof_units()
tof_axis_label = self.view.allowed_modes[selected_mode]["label"]
tof_axis_label = allowed_modes[self.view.tof_units_mode]["label"]
self.view.spectrum_widget.spectrum_plot_widget.set_tof_axis_label(tof_axis_label)
self.refresh_spectrum_plot()

def handle_tof_unit_change_via_menu(self) -> None:
self.model.tof_mode = allowed_modes[self.view.tof_units_mode]["mode"]
self.handle_tof_unit_change()

def refresh_spectrum_plot(self) -> None:
self.view.spectrum_widget.spectrum.clearPlots()
self.view.spectrum_widget.spectrum.update()
Expand All @@ -373,7 +372,14 @@ def handle_flight_path_change(self) -> None:
self.refresh_spectrum_plot()

def handle_time_delay_change(self) -> None:
self.model.tof_data = self.model.get_stack_time_of_flight()
self.model.units.data_offset = self.view.timeDelaySpinBox.value() * 1e-6
self.model.set_relevant_tof_units()
self.refresh_spectrum_plot()

def change_selected_menu_option(self, opt):
for action in self.view.tof_mode_select_group.actions():
with QSignalBlocker(action):
if action.objectName() == opt:
action.setChecked(True)
else:
action.setChecked(False)
36 changes: 35 additions & 1 deletion mantidimaging/gui/windows/spectrum_viewer/test/presenter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from mantidimaging.core.data.dataset import StrictDataset, MixedDataset
from mantidimaging.gui.windows.main import MainWindowView
from mantidimaging.gui.windows.spectrum_viewer import SpectrumViewerWindowView, SpectrumViewerWindowPresenter
from mantidimaging.gui.windows.spectrum_viewer.model import ErrorMode
from mantidimaging.gui.windows.spectrum_viewer.model import ErrorMode, ToFUnitMode
from mantidimaging.gui.windows.spectrum_viewer.spectrum_widget import SpectrumWidget, SpectrumPlotWidget
from mantidimaging.test_helpers import mock_versions, start_qapplication
from mantidimaging.test_helpers.unit_test_helper import generate_images
Expand Down Expand Up @@ -173,6 +173,7 @@ def test_gui_changes_tof_range(self):
image_stack = generate_images([30, 11, 12])
new_tof_range = (10, 20)
self.presenter.model.set_stack(image_stack)
self.presenter.model.tof_mode = ToFUnitMode.IMAGE_NUMBER
self.presenter.handle_range_slide_moved(new_tof_range)

self.assertEqual(self.presenter.model.tof_range, new_tof_range)
Expand Down Expand Up @@ -271,3 +272,36 @@ def test_WHEN_do_remove_roi_called_with_no_arguments_THEN_all_rois_removed(self)
self.assertEqual(["all", "roi", "roi_1", "roi_2"], self.presenter.model.get_list_of_roi_names())
self.presenter.do_remove_roi()
self.assertEqual([], self.presenter.model.get_list_of_roi_names())

@parameterized.expand([("Image Index", ToFUnitMode.IMAGE_NUMBER), ("Wavelength", ToFUnitMode.WAVELENGTH),
("Energy", ToFUnitMode.ENERGY), ("Time of Flight (\u03BCs)", ToFUnitMode.TOF_US)])
def test_WHEN_tof_unit_selected_THEN_model_mode_changes(self, mode_text, expected_mode):
self.view.tof_units_mode = mode_text
self.presenter.refresh_spectrum_plot = mock.Mock()
self.presenter.handle_tof_unit_change_via_menu()
self.assertEqual(self.presenter.model.tof_mode, expected_mode)

@mock.patch("mantidimaging.gui.windows.spectrum_viewer.model.SpectrumViewerWindowModel.get_stack_time_of_flight")
def test_WHEN_no_spectrum_data_THEN_mode_is_image_index(self, get_stack_time_of_flight):
self.presenter.model.set_stack(generate_images())
self.presenter.get_dataset_id_for_stack = mock.Mock(return_value=uuid.uuid4())
self.presenter.main_window.get_stack = mock.Mock(return_value=generate_images())
get_stack_time_of_flight.return_value = None
self.view.tof_units_mode = "Wavelength"
self.presenter.refresh_spectrum_plot = mock.Mock()
self.presenter.handle_sample_change(uuid.uuid4())
self.assertEqual(self.presenter.model.tof_mode, ToFUnitMode.IMAGE_NUMBER)

def test_WHEN_tof_flight_path_changed_THEN_unit_conversion_flight_path_set(self):
self.view.flightPathSpinBox = mock.Mock()
self.view.flightPathSpinBox.value.return_value = 10
self.presenter.refresh_spectrum_plot = mock.Mock()
self.presenter.handle_flight_path_change()
self.assertEqual(self.presenter.model.units.target_to_camera_dist, 10)

def test_WHEN_tof_delay_changed_THEN_unit_conversion_delay_set(self):
self.view.timeDelaySpinBox = mock.Mock()
self.view.timeDelaySpinBox.value.return_value = 400
self.presenter.refresh_spectrum_plot = mock.Mock()
self.presenter.handle_time_delay_change()
self.assertEqual(self.presenter.model.units.data_offset, 400 * 1e-6)
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,7 @@ def test_WHEN_rename_roi_called_with_default_roi_THEN_roi_name_not_changed(self)
self.assertIn("roi_1", self.spectrum_widget.roi_dict.keys())
self.spectrum_widget.rename_roi("roi_1", "roi")
self.assertIn("roi_1", self.spectrum_widget.roi_dict)

def test_WHEN_tof_axis_label_changed_THEN_axis_label_set(self):
self.spectrum_plot_widget.set_tof_axis_label("test")
self.assertEqual(self.spectrum_plot_widget.spectrum.getAxis('bottom').labelText, "test")
35 changes: 8 additions & 27 deletions mantidimaging/gui/windows/spectrum_viewer/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, TypedDict
from typing import TYPE_CHECKING

from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QCheckBox, QVBoxLayout, QFileDialog, QPushButton, QLabel, QAbstractItemView, QHeaderView, \
Expand All @@ -13,7 +13,7 @@
from mantidimaging.core.utility import finder
from mantidimaging.gui.mvp_base import BaseMainWindowView
from mantidimaging.gui.widgets.dataset_selector import DatasetSelectorWidgetView
from .model import ROI_RITS, ToFUnitMode
from .model import ROI_RITS, allowed_modes
from .presenter import SpectrumViewerWindowPresenter, ExportMode
from mantidimaging.gui.widgets import RemovableRowTableView
from .spectrum_widget import SpectrumWidget
Expand All @@ -27,11 +27,6 @@
from uuid import UUID


class AllowedModesTypedDict(TypedDict):
mode: ToFUnitMode
label: str


class SpectrumViewerWindowView(BaseMainWindowView):
tableView: RemovableRowTableView
sampleStackSelector: DatasetSelectorWidgetView
Expand Down Expand Up @@ -94,30 +89,12 @@ def __init__(self, main_window: MainWindowView):
self.units_menu = self.spectrum_right_click_menu.addMenu("Units")
self.tof_mode_select_group = QActionGroup(self)

self.allowed_modes: dict[str, AllowedModesTypedDict] = {
"Image Index": {
"mode": ToFUnitMode.IMAGE_NUMBER,
"label": "Image index"
},
"Wavelength": {
"mode": ToFUnitMode.WAVELENGTH,
"label": "Neutron Wavelength (\u212B)"
},
"Energy": {
"mode": ToFUnitMode.ENERGY,
"label": "Neutron Energy (MeV)"
},
"Time of Flight (\u03BCs)": {
"mode": ToFUnitMode.TOF_US,
"label": "Time of Flight (\u03BCs)"
}
}
for mode in self.allowed_modes.keys():
for mode in allowed_modes.keys():
action = QAction(mode, self.tof_mode_select_group)
action.setCheckable(True)
action.setObjectName(mode)
self.units_menu.addAction(action)
action.triggered.connect(self.presenter.handle_tof_unit_change)
action.triggered.connect(self.presenter.handle_tof_unit_change_via_menu)
if mode == "Image Index":
action.setChecked(True)
if self.presenter.model.tof_data is None:
Expand Down Expand Up @@ -527,6 +504,10 @@ def set_binning_visibility(self) -> None:
self.bin_step_label.setHidden(hide_binning)
self.bin_step_spinBox.setHidden(hide_binning)

@property
def tof_units_mode(self) -> str:
return self.tof_mode_select_group.checkedAction().text()

def set_roi_properties(self) -> None:
if self.presenter.export_mode == ExportMode.IMAGE_MODE:
self.current_roi = ROI_RITS
Expand Down