From 206ad5b41fd17ddd8540cdb77f8911cfb23fd250 Mon Sep 17 00:00:00 2001 From: payno <henri.payno@gmail.com> Date: Fri, 13 Dec 2024 14:58:27 +0100 Subject: [PATCH] NXtomoeditor: add handling of source / sample distance Rename some 'distance' to smaple- detector distance --- src/tomwer/gui/edit/nxtomoeditor.py | 82 ++++++++++++++----- src/tomwer/gui/edit/tests/test_nx_editor.py | 35 ++++++-- .../test/test_nx_tomo_metadata_viewer.py | 4 +- src/tomwer/tasks/edit/nxtomoeditor.py | 15 ++++ .../widgets/edit/tests/test_nxtomo_editor.py | 5 +- 5 files changed, 110 insertions(+), 31 deletions(-) diff --git a/src/tomwer/gui/edit/nxtomoeditor.py b/src/tomwer/gui/edit/nxtomoeditor.py index dc1a338009..d5e4fce156 100644 --- a/src/tomwer/gui/edit/nxtomoeditor.py +++ b/src/tomwer/gui/edit/nxtomoeditor.py @@ -114,8 +114,26 @@ class NXtomoEditor(qt.QWidget): self._energyLockerLB.setMaximumSize(30, 30) self._lockerPBs.append(self._energyLockerLB) self._tree.setItemWidget(self._energyQTWI, 2, self._energyLockerLB) + # 1.1 source + self._sourceQTWI = qt.QTreeWidgetItem(self._instrumentQTWI) + self._sourceQTWI.setText(0, "source") + self._sourceSampleDistanceQTWI = qt.QTreeWidgetItem(self._sourceQTWI) + self._sourceSampleDistanceQTWI.setText(0, "distance") + + self._sourceSampleDistanceMetricEntry = MetricEntry("", parent=self) + self._sourceSampleDistanceMetricEntry.layout().setContentsMargins(2, 2, 2, 2) + self._tree.setItemWidget( + self._sourceSampleDistanceQTWI, 1, self._sourceSampleDistanceMetricEntry + ) + self._editableWidgets.append(self._sourceSampleDistanceMetricEntry) + self._sourceSampleDistanceLB = PadlockButton(self) + self._sourceSampleDistanceLB.setMaximumSize(30, 30) + self._lockerPBs.append(self._sourceSampleDistanceLB) + self._tree.setItemWidget( + self._sourceSampleDistanceQTWI, 2, self._sourceSampleDistanceLB + ) - # 1.1 detector + # 1.2 detector self._detectorQTWI = qt.QTreeWidgetItem(self._instrumentQTWI) self._detectorQTWI.setText(0, "detector") ## pixel size @@ -141,19 +159,21 @@ class NXtomoEditor(qt.QWidget): self._lockerPBs.append(self._yPixelSizeLB) self._tree.setItemWidget(self._yPixelSizeQTWI, 2, self._yPixelSizeLB) - ## distance + ## sample - detector distance self._sampleDetectorDistanceQTWI = qt.QTreeWidgetItem(self._detectorQTWI) self._sampleDetectorDistanceQTWI.setText(0, "distance") - self._distanceMetricEntry = MetricEntry("", parent=self) - self._distanceMetricEntry.layout().setContentsMargins(2, 2, 2, 2) + self._sampleDetectorDistanceMetricEntry = MetricEntry("", parent=self) + self._sampleDetectorDistanceMetricEntry.layout().setContentsMargins(2, 2, 2, 2) + self._tree.setItemWidget( + self._sampleDetectorDistanceQTWI, 1, self._sampleDetectorDistanceMetricEntry + ) + self._editableWidgets.append(self._sampleDetectorDistanceMetricEntry) + self._sampleDetectorDistanceLB = PadlockButton(self) + self._sampleDetectorDistanceLB.setMaximumSize(30, 30) + self._lockerPBs.append(self._sampleDetectorDistanceLB) self._tree.setItemWidget( - self._sampleDetectorDistanceQTWI, 1, self._distanceMetricEntry + self._sampleDetectorDistanceQTWI, 2, self._sampleDetectorDistanceLB ) - self._editableWidgets.append(self._distanceMetricEntry) - self._distanceLB = PadlockButton(self) - self._distanceLB.setMaximumSize(30, 30) - self._lockerPBs.append(self._distanceLB) - self._tree.setItemWidget(self._sampleDetectorDistanceQTWI, 2, self._distanceLB) ## field of view self._fieldOfViewQTWI = qt.QTreeWidgetItem(self._detectorQTWI) @@ -210,6 +230,7 @@ class NXtomoEditor(qt.QWidget): self._sampleQTWI.setExpanded(True) self._beamQTWI.setExpanded(True) self._detectorQTWI.setExpanded(True) + self._sourceQTWI.setExpanded(True) self.hideLockers(hide_lockers) # connect signal / slot @@ -219,8 +240,14 @@ class NXtomoEditor(qt.QWidget): self._xPixelSizeLB.toggled.connect(self._editingFinished) self._yPixelSizeMetricEntry.editingFinished.connect(self._editingFinished) self._yPixelSizeLB.toggled.connect(self._editingFinished) - self._distanceMetricEntry.editingFinished.connect(self._editingFinished) - self._distanceLB.toggled.connect(self._editingFinished) + self._sampleDetectorDistanceMetricEntry.editingFinished.connect( + self._editingFinished + ) + self._sampleDetectorDistanceLB.toggled.connect(self._editingFinished) + self._sourceSampleDistanceMetricEntry.editingFinished.connect( + self._editingFinished + ) + self._sourceSampleDistanceLB.toggled.connect(self._editingFinished) self._fieldOfViewCB.currentIndexChanged.connect(self._editingFinished) self._fieldOfViewLB.toggled.connect(self._editingFinished) self._xFlippedCB.toggled.connect(self._editingFinished) @@ -244,7 +271,8 @@ class NXtomoEditor(qt.QWidget): "pixel size": self._updatePixelSize, "frame flips": self._updateFlipped, "field of view": self._updateFieldOfView, - "sample-detector distance": self._updateDistance, + "sample-detector distance": self._updateSampleDetectorDistance, + "source-sample distance": self._updateSourceSampleDistance, }.items(): try: fct(scan=scan) @@ -325,10 +353,16 @@ class NXtomoEditor(qt.QWidget): if (not self._yFlippedLB.isLocked()) and flip_ud is not None: self._yFlippedCB.setChecked(flip_ud) - def _updateDistance(self, scan: NXtomoScan) -> None: - if not self._distanceLB.isLocked(): + def _updateSampleDetectorDistance(self, scan: NXtomoScan) -> None: + if not self._sampleDetectorDistanceLB.isLocked(): # if in ''auto mode: we want to overwrite the NXtomo existing value by the one of the GUI - self._distanceMetricEntry.setValue(scan.sample_detector_distance) + self._sampleDetectorDistanceMetricEntry.setValue( + scan.sample_detector_distance + ) + + def _updateSourceSampleDistance(self, scan: NXtomoScan) -> None: + if not self._sourceSampleDistanceLB.isLocked(): + self._sourceSampleDistanceMetricEntry.setValue(scan.source_sample_distance) def _updateEnergy(self, scan: NXtomoScan) -> None: assert isinstance(scan, NXtomoScan) @@ -400,8 +434,12 @@ class NXtomoEditor(qt.QWidget): self._yPixelSizeLB.isLocked(), ), NXtomoEditorKeys.SAMPLE_DETECTOR_DISTANCE: ( - self._distanceMetricEntry.getValue(), - self._distanceLB.isLocked(), + self._sampleDetectorDistanceMetricEntry.getValue(), + self._sampleDetectorDistanceLB.isLocked(), + ), + NXtomoEditorKeys.SOURCE_SAMPLE_DISTANCE: ( + self._sourceSampleDistanceMetricEntry.getValue(), + self._sourceSampleDistanceLB.isLocked(), ), NXtomoEditorKeys.FIELD_OF_VIEW: ( self._fieldOfViewCB.currentText(), @@ -441,9 +479,15 @@ class NXtomoEditor(qt.QWidget): detector_sample_distance = config.get("instrument.detector.distance", None) if detector_sample_distance is not None: detector_sample_distance, distance_locked = detector_sample_distance - self._distanceMetricEntry.setValue(detector_sample_distance) + self._sampleDetectorDistanceMetricEntry.setValue(detector_sample_distance) self._sampleDetectorDistanceLB.setLock(distance_locked) + source_sample_distance = config.get("instrument.source.distance", None) + if source_sample_distance is not None: + source_sample_distance, distance_locked = source_sample_distance + self._sourceSampleDistanceMetricEntry.setValue(source_sample_distance) + self._sourceSampleDistanceLB.setLock(distance_locked) + field_of_view = config.get("instrument.detector.field_of_view", None) if field_of_view is not None: field_of_view, field_of_view_locked = field_of_view diff --git a/src/tomwer/gui/edit/tests/test_nx_editor.py b/src/tomwer/gui/edit/tests/test_nx_editor.py index ade4095c4c..71688de70c 100644 --- a/src/tomwer/gui/edit/tests/test_nx_editor.py +++ b/src/tomwer/gui/edit/tests/test_nx_editor.py @@ -26,7 +26,8 @@ from tomwer.tests.conftest import qtapp # noqa F401 @pytest.mark.parametrize("x_pixel_size", (None, 0.12)) @pytest.mark.parametrize("y_pixel_size", (None, 0.0065)) @pytest.mark.parametrize("field_of_view", FOV.values()) -@pytest.mark.parametrize("distance", (None, 1.2)) +@pytest.mark.parametrize("sample_detector_distance", (None, 1.2)) +@pytest.mark.parametrize("source_sample_distance", (None, 30.2)) @pytest.mark.parametrize("energy", (None, 23.5)) @pytest.mark.parametrize("x_flipped", (True, False)) @pytest.mark.parametrize("y_flipped", (True, False)) @@ -38,7 +39,8 @@ def test_nx_editor( x_pixel_size, y_pixel_size, field_of_view, - distance, + sample_detector_distance, + source_sample_distance, energy, x_flipped, y_flipped, @@ -50,12 +52,13 @@ def test_nx_editor( nx_tomo.instrument.detector.x_pixel_size = x_pixel_size nx_tomo.instrument.detector.y_pixel_size = y_pixel_size nx_tomo.instrument.detector.field_of_view = field_of_view - nx_tomo.instrument.detector.distance = distance + nx_tomo.instrument.detector.distance = sample_detector_distance nx_tomo.energy = energy nx_tomo.sample.x_translation = x_translation nx_tomo.sample.z_translation = z_translation nx_tomo.instrument.detector.image_key_control = [ImageKey.PROJECTION.value] * 12 nx_tomo.instrument.detector.data = numpy.empty(shape=(12, 10, 10)) + nx_tomo.instrument.source.distance = source_sample_distance nx_tomo.sample.rotation_angle = numpy.linspace(0, 20, num=12) nx_tomo.instrument.detector.transformations.add_transformation( @@ -89,8 +92,14 @@ def test_nx_editor( assert check_metric(y_pixel_size, widget._yPixelSizeMetricEntry.getValue()) assert widget._yPixelSizeMetricEntry._qcbUnit.currentText() == "m" - assert check_metric(distance, widget._distanceMetricEntry.getValue()) - assert widget._distanceMetricEntry._qcbUnit.currentText() == "m" + assert check_metric( + sample_detector_distance, widget._sampleDetectorDistanceMetricEntry.getValue() + ) + assert widget._sampleDetectorDistanceMetricEntry._qcbUnit.currentText() == "m" + assert check_metric( + source_sample_distance, widget._sourceSampleDistanceMetricEntry.getValue() + ) + assert widget._sourceSampleDistanceMetricEntry._qcbUnit.currentText() == "m" assert field_of_view == widget._fieldOfViewCB.currentText() assert x_flipped == widget._xFlippedCB.isChecked() @@ -120,7 +129,8 @@ def test_nx_editor( widget._energyEntry.setText("23.789") widget._xPixelSizeMetricEntry.setUnit("nm") widget._yPixelSizeMetricEntry.setValue(2.1e-7) - widget._distanceMetricEntry.setValue("unknown") + widget._sampleDetectorDistanceMetricEntry.setValue("unknown") + widget._sourceSampleDistanceMetricEntry.setValue("unknown") widget._fieldOfViewCB.setCurrentText(FOV.HALF.value) widget._xFlippedCB.setChecked(not x_flipped) widget._xTranslationQLE.setValue(1.8) @@ -204,6 +214,7 @@ def test_nx_editor_lock( nx_tomo_1.instrument.detector.image_key_control = [ImageKey.PROJECTION.value] * 12 nx_tomo_1.instrument.detector.data = numpy.empty(shape=(12, 10, 10)) nx_tomo_1.sample.rotation_angle = numpy.linspace(0, 20, num=12) + nx_tomo_1.instrument.source.distance = 1.1 file_path = os.path.join(tmp_path, "nxtomo.nx") entry = "entry0000" @@ -229,6 +240,7 @@ def test_nx_editor_lock( nx_tomo_2.instrument.detector.image_key_control = [ImageKey.PROJECTION.value] * 12 nx_tomo_2.instrument.detector.data = numpy.empty(shape=(12, 10, 10)) nx_tomo_2.sample.rotation_angle = numpy.linspace(0, 20, num=12) + nx_tomo_2.instrument.source.distance = 6.02 file_path = os.path.join(tmp_path, "nxtomo.nx") entry = "entry0001" @@ -251,7 +263,8 @@ def test_nx_editor_lock( assert widget._energyEntry.getValue() == 5.9 assert widget._xPixelSizeMetricEntry.getValue() == 0.023 assert widget._yPixelSizeMetricEntry.getValue() == 0.025 - assert widget._distanceMetricEntry.getValue() == 2.4 + assert widget._sampleDetectorDistanceMetricEntry.getValue() == 2.4 + assert widget._sourceSampleDistanceMetricEntry.getValue() == 1.1 assert widget._fieldOfViewCB.currentText() == "Full" assert not widget._xFlippedCB.isChecked() assert widget._yFlippedCB.isChecked() @@ -286,6 +299,10 @@ def test_nx_editor_lock( overwrite_nx_tomo.instrument.detector.distance.value == nx_tomo_1.instrument.detector.distance.value ) + assert ( + overwrite_nx_tomo.instrument.source.distance.value + == nx_tomo_1.instrument.source.distance.value + ) assert ( overwrite_nx_tomo.instrument.detector.x_flipped == nx_tomo_1.instrument.detector.x_flipped @@ -306,6 +323,7 @@ def test_nx_editor_lock( "instrument.detector.y_flipped": (True, True), "sample.x_translation": (None,), "sample.z_translation": (None,), + "instrument.source.distance": (1.1, True), } for lockerButton in widget._lockerPBs: @@ -321,6 +339,7 @@ def test_nx_editor_lock( "instrument.detector.y_flipped": (True, False), "sample.x_translation": (None,), "sample.z_translation": (None,), + "instrument.source.distance": (1.1, False), } @@ -361,7 +380,7 @@ def test_nxtomo_editor_with_missing_paths( widget.setScan(scan=scan) - widget._distanceMetricEntry.setValue(0.05) + widget._sampleDetectorDistanceMetricEntry.setValue(0.05) widget._energyEntry.setValue(50) widget._xPixelSizeMetricEntry.setValue(0.02) widget._yPixelSizeMetricEntry.setValue(0.03) diff --git a/src/tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py b/src/tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py index c03a4ec35f..998ef0e68d 100644 --- a/src/tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +++ b/src/tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py @@ -54,8 +54,8 @@ def test_nx_editor( assert check_metric(2.5e-6, widget._yPixelSizeMetricEntry.getValue()) assert widget._yPixelSizeMetricEntry._qcbUnit.currentText() == "m" - assert check_metric(59, widget._distanceMetricEntry.getValue()) - assert widget._distanceMetricEntry._qcbUnit.currentText() == "m" + assert check_metric(59, widget._sampleDetectorDistanceMetricEntry.getValue()) + assert widget._sampleDetectorDistanceMetricEntry._qcbUnit.currentText() == "m" assert "Half" == widget._fieldOfViewCB.currentText() assert widget._xFlippedCB.isChecked() diff --git a/src/tomwer/tasks/edit/nxtomoeditor.py b/src/tomwer/tasks/edit/nxtomoeditor.py index bd4c30aaae..b99489fe98 100644 --- a/src/tomwer/tasks/edit/nxtomoeditor.py +++ b/src/tomwer/tasks/edit/nxtomoeditor.py @@ -36,6 +36,7 @@ class NXtomoEditorKeys: X_PIXEL_SIZE = "instrument.detector.x_pixel_size" Y_PIXEL_SIZE = "instrument.detector.y_pixel_size" SAMPLE_DETECTOR_DISTANCE = "instrument.detector.distance" + SOURCE_SAMPLE_DISTANCE = "instrument.source.distance" FIELD_OF_VIEW = "instrument.detector.field_of_view" X_FLIPPED = "instrument.detector.x_flipped" Y_FLIPPED = "instrument.detector.y_flipped" @@ -272,6 +273,20 @@ class NXtomoEditorTask( name="sample detector distance", n_value=1, ), + # source / sample distance + NXtomoEditorKeys.SOURCE_SAMPLE_DISTANCE: _EditorFieldInfo( + nexus_path="/".join( + [ + nexus_paths.INSTRUMENT_PATH, + nexus_paths.nx_instrument_paths.SOURCE, + nexus_paths.nx_source_paths.DISTANCE, + ] + ), + expected_type=float, + units="m", + name="source sample distance", + n_value=1, + ), # overwrite FOV NXtomoEditorKeys.FIELD_OF_VIEW: _EditorFieldInfo( nexus_path=nexus_paths.FOV_PATH, diff --git a/src/tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_nxtomo_editor.py b/src/tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_nxtomo_editor.py index cb79ae76eb..547083afa5 100644 --- a/src/tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_nxtomo_editor.py +++ b/src/tomwer/tests/orangecontrib/tomwer/widgets/edit/tests/test_nxtomo_editor.py @@ -29,6 +29,7 @@ def getDefaultConfig() -> dict: NXtomoEditorKeys.Y_FLIPPED: (False, False), NXtomoEditorKeys.X_TRANSLATION: (0.0,), NXtomoEditorKeys.Z_TRANSLATION: (0.0,), + NXtomoEditorKeys.SOURCE_SAMPLE_DISTANCE: (1.3, True), } @@ -50,10 +51,10 @@ def test_NXtomoEditorOW( signal_listener = SignalListener() window.sigScanReady.connect(signal_listener) # set up the widget to define and lock distance, energy and x pixel size - distance_widget = window.widget.mainWidget._distanceMetricEntry + distance_widget = window.widget.mainWidget._sampleDetectorDistanceMetricEntry distance_widget.setValue(0.6) distance_widget.setUnit("mm") - distance_locker = window.widget.mainWidget._distanceLB + distance_locker = window.widget.mainWidget._sampleDetectorDistanceLB distance_locker.setLock(True) energy_widget = window.widget.mainWidget._energyEntry energy_widget.setValue(88.058) -- GitLab