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

Enter Time of Flight user properties in Spectrum Viewer #2159

Merged
merged 9 commits into from
Apr 23, 2024
32 changes: 28 additions & 4 deletions mantidimaging/core/utility/unit_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,42 @@ class UnitConversion:
planck_h: float = 6.62606896e-34 # [JHz-1]
angstrom: float = 1e-10 # [m]
mega_electro_volt: float = 1.60217662e-19 / 1e6
target_to_camera_dist: float = 56 # [m]
data_offset: float = 0 # [us]
MikeSullivan7 marked this conversation as resolved.
Show resolved Hide resolved
tof_data_to_convert: np.ndarray
velocity: np.ndarray

def __init__(self, data_to_convert: np.ndarray, target_to_camera_dist: float = 56) -> None:
self.tof_data_to_convert = data_to_convert
self.target_to_camera_dist = target_to_camera_dist
self.velocity = self.target_to_camera_dist / self.tof_data_to_convert
def __init__(self, data_to_convert: np.ndarray | None = None) -> None:
if data_to_convert is not None:
self.set_data_to_convert(data_to_convert)

def tof_seconds_to_wavelength(self) -> np.ndarray:
self.check_data()
wavelength = self.planck_h / (self.neutron_mass * self.velocity)
wavelength_angstroms = wavelength / self.angstrom
return wavelength_angstroms

def tof_seconds_to_energy(self) -> np.ndarray:
self.check_data()
energy = self.neutron_mass * self.velocity / 2
energy_evs = energy / self.mega_electro_volt
return energy_evs

def tof_seconds_to_us(self) -> np.ndarray:
self.check_data()
return (self.tof_data_to_convert + self.data_offset) * 1e6

def set_target_to_camera_dist(self, target_to_camera_dist: float) -> None:
self.target_to_camera_dist = target_to_camera_dist
MikeSullivan7 marked this conversation as resolved.
Show resolved Hide resolved

def set_data_to_convert(self, data_to_convert: np.ndarray) -> None:
self.tof_data_to_convert = data_to_convert

def check_data(self) -> None:
if self.tof_data_to_convert is None:
raise TypeError("Data is not present")
else:
self.velocity = self.target_to_camera_dist / (self.tof_data_to_convert + self.data_offset)

def set_data_offset(self, data_offset: float) -> None:
self.data_offset = data_offset * 1e-6
MikeSullivan7 marked this conversation as resolved.
Show resolved Hide resolved
77 changes: 74 additions & 3 deletions mantidimaging/gui/ui/spectrum_viewer.ui
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>905</width>
<height>752</height>
<width>1001</width>
<height>766</height>
</rect>
</property>
<property name="windowTitle">
Expand Down Expand Up @@ -391,7 +391,78 @@
</widget>
</item>
<item>
<spacer name="PropertiesSpacer">
<widget class="QGroupBox" name="tofPropertiesGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>84</height>
</size>
</property>
<property name="title">
<string>Time of Flight Properties</string>
</property>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>10</x>
<y>31</y>
<width>101</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Flight path:</string>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>10</x>
<y>50</y>
<width>91</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>Time delay: </string>
</property>
</widget>
<widget class="QDoubleSpinBox" name="flightPathSpinBox">
<property name="geometry">
<rect>
<x>80</x>
<y>30</y>
<width>211</width>
<height>19</height>
</rect>
</property>
<property name="suffix">
<string/>
</property>
</widget>
<widget class="QDoubleSpinBox" name="timeDelaySpinBox">
<property name="geometry">
<rect>
<x>80</x>
<y>50</y>
<width>211</width>
<height>19</height>
</rect>
</property>
<property name="suffix">
<string/>
</property>
</widget>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
Expand Down
10 changes: 6 additions & 4 deletions mantidimaging/gui/windows/spectrum_viewer/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ def __init__(self, presenter: SpectrumViewerWindowPresenter):
else:
self.tof_mode = ToFUnitMode.WAVELENGTH

self.units = UnitConversion()
MikeSullivan7 marked this conversation as resolved.
Show resolved Hide resolved

def roi_name_generator(self) -> str:
"""
Returns a new Unique ID for newly created ROIs
Expand Down Expand Up @@ -470,12 +472,12 @@ def set_relevant_tof_units(self) -> None:
self.tof_range = (0, self._stack.data.shape[0] - 1)
self.tof_data = np.arange(self.tof_range[0], self.tof_range[1] + 1)
else:
units = UnitConversion(self.tof_data)
self.units.set_data_to_convert(self.tof_data)
if self.tof_mode == ToFUnitMode.TOF_US:
self.tof_data = self.tof_data * 1e6
self.tof_data = self.units.tof_seconds_to_us()
elif self.tof_mode == ToFUnitMode.WAVELENGTH:
self.tof_data = units.tof_seconds_to_wavelength()
self.tof_data = self.units.tof_seconds_to_wavelength()
elif self.tof_mode == ToFUnitMode.ENERGY:
self.tof_data = units.tof_seconds_to_energy()
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)
24 changes: 22 additions & 2 deletions mantidimaging/gui/windows/spectrum_viewer/presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ def handle_sample_change(self, uuid: UUID | None) -> None:
self.view.on_visibility_change()

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):
Expand Down Expand Up @@ -349,11 +355,25 @@ 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()
self.view.spectrum_widget.spectrum_plot_widget.set_tof_axis_label(
self.view.allowed_modes[selected_mode]["label"])
tof_axis_label = self.view.allowed_modes[selected_mode]["label"]
self.view.spectrum_widget.spectrum_plot_widget.set_tof_axis_label(tof_axis_label)
self.refresh_spectrum_plot()

def refresh_spectrum_plot(self) -> None:
self.view.spectrum_widget.spectrum.clearPlots()
self.view.spectrum_widget.spectrum.update()
self.view.show_visible_spectrums()
self.view.spectrum_widget.spectrum_plot_widget.add_range(*self.model.tof_plot_range)
self.view.spectrum_widget.spectrum_plot_widget.set_image_index_range_label(*self.model.tof_range)
self.view.auto_range_image()

def handle_flight_path_change(self) -> None:
self.model.units.set_target_to_camera_dist(self.view.flightPathSpinBox.value())
self.model.set_relevant_tof_units()
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.set_data_offset(self.view.timeDelaySpinBox.value())
self.model.set_relevant_tof_units()
self.refresh_spectrum_plot()
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pathlib import Path
from unittest import mock

from PyQt5.QtWidgets import QPushButton, QActionGroup
from PyQt5.QtWidgets import QPushButton, QActionGroup, QGroupBox
from parameterized import parameterized

from mantidimaging.core.data.dataset import StrictDataset, MixedDataset
Expand Down Expand Up @@ -35,7 +35,7 @@ def setUp(self) -> None:
self.view.exportButtonRITS = mock.create_autospec(QPushButton)
self.view.addBtn = mock.create_autospec(QPushButton)
self.view.tof_mode_select_group = mock.create_autospec(QActionGroup)
self.view.allowed_modes = mock.create_autospec(dict)
self.view.tofPropertiesGroupBox = mock.create_autospec(QGroupBox)
self.presenter = SpectrumViewerWindowPresenter(self.view, self.main_window)

def test_get_dataset_id_for_stack_no_stack_id(self):
Expand Down
16 changes: 15 additions & 1 deletion mantidimaging/gui/windows/spectrum_viewer/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QCheckBox, QVBoxLayout, QFileDialog, QPushButton, QLabel, QAbstractItemView, QHeaderView, \
QTabWidget, QComboBox, QSpinBox, QTableWidget, QTableWidgetItem, QGroupBox, QActionGroup, QAction
QTabWidget, QComboBox, QSpinBox, QTableWidget, QTableWidgetItem, QGroupBox, QActionGroup, QAction, QDoubleSpinBox
from PyQt5.QtCore import QSignalBlocker, Qt

from mantidimaging.core.utility import finder
Expand Down Expand Up @@ -58,6 +58,10 @@ class SpectrumViewerWindowView(BaseMainWindowView):

number_roi_properties_procced: int = 0

tofPropertiesGroupBox: QGroupBox
flightPathSpinBox: QDoubleSpinBox
timeDelaySpinBox: QDoubleSpinBox

def __init__(self, main_window: MainWindowView):
super().__init__(None, 'gui/ui/spectrum_viewer.ui')

Expand Down Expand Up @@ -200,6 +204,16 @@ def __init__(self, main_window: MainWindowView):
self.current_roi = self.last_clicked_roi = self.roi_table_model.roi_names()[0]
self.set_roi_properties()

self.flightPathSpinBox.setMinimum(0)
self.flightPathSpinBox.setMaximum(1e10)
self.flightPathSpinBox.setValue(56)
self.flightPathSpinBox.setSuffix(" m")
self.timeDelaySpinBox.setMaximum(1e10)
self.timeDelaySpinBox.setSuffix(" \u03BCs")

self.flightPathSpinBox.valueChanged.connect(self.presenter.handle_flight_path_change)
self.timeDelaySpinBox.valueChanged.connect(self.presenter.handle_time_delay_change)

def on_row_change(item, _) -> None:
"""
Handle cell change in table view and update selected ROI and
Expand Down
Loading