diff --git a/extra_foam/special_suite/config.py b/extra_foam/special_suite/config.py index 9b39054f7..deb4099f9 100644 --- a/extra_foam/special_suite/config.py +++ b/extra_foam/special_suite/config.py @@ -67,6 +67,6 @@ def topics(self): _MAX_N_GOTTHARD_PULSES = 120 GOTTHARD_DEVICE = { - "MID": "MID_EXP_DES/DET/GOTTHARD_RECEIVER:daqOutput", + "MID": "MID_EXP_DES/DET/GOTTHARD_RECEIVER:output", "SCS": "SCS_PAM_XOX/DET/GOTTHARD_RECEIVER1:daqOutput", } diff --git a/extra_foam/special_suite/gotthard_pump_probe_w.py b/extra_foam/special_suite/gotthard_pump_probe_w.py index 53ab60b06..808de1129 100644 --- a/extra_foam/special_suite/gotthard_pump_probe_w.py +++ b/extra_foam/special_suite/gotthard_pump_probe_w.py @@ -7,6 +7,8 @@ Copyright (C) European X-Ray Free-Electron Laser Facility GmbH. All rights reserved. """ +import numpy as np + from PyQt5.QtCore import Qt from PyQt5.QtGui import QIntValidator from PyQt5.QtWidgets import QSplitter @@ -94,8 +96,10 @@ def __init__(self, *, parent=None): def updateF(self, data): """Override.""" - self._mean.setData(data['vfom_mean']) - self._mean_ma.setData(data['vfom_ma_mean']) + vfom_mean = data['vfom_mean'] + x = np.arange(len(vfom_mean)) + self._mean.setData(x, vfom_mean) + self._mean_ma.setData(x, data['vfom_ma_mean']) class GotthardPpFomPulsePlot(PlotWidgetF): @@ -128,8 +132,10 @@ def updateF(self, data): self._idx = idx self._updateTitle() - self._poi.setData(data['vfom'][idx]) - self._poi_ma.setData(data['vfom_ma'][idx]) + vfom = data['vfom'][idx] + x = np.arange(len(vfom)) + self._poi.setData(x, vfom) + self._poi_ma.setData(x, data['vfom_ma'][idx]) class GotthardPpRawPulsePlot(PlotWidgetF): @@ -161,8 +167,10 @@ def updateF(self, data): self._idx = idx self._updateTitle() - self._on.setData(data['raw'][data['on_slicer']][idx]) - self._off.setData(data['raw'][data['off_slicer']][idx]) + on = data['raw'][data['on_slicer']][idx] + x = np.arange(len(on)) + self._on.setData(x, on) + self._off.setData(x, data['raw'][data['off_slicer']][idx]) class GotthardPpDarkPulsePlot(PlotWidgetF): @@ -192,7 +200,9 @@ def updateF(self, data): self._idx = idx self._updateTitle() - self._plot.setData(data['raw'][data['dark_slicer']][idx]) + raw = data['raw'][data['dark_slicer']][idx] + x = np.arange(len(raw)) + self._plot.setData(x, raw) class GotthardPpImageView(ImageViewF): diff --git a/extra_foam/special_suite/gotthard_w.py b/extra_foam/special_suite/gotthard_w.py index dd21d5814..3de0fbfa5 100644 --- a/extra_foam/special_suite/gotthard_w.py +++ b/extra_foam/special_suite/gotthard_w.py @@ -9,6 +9,8 @@ """ from string import Template +import numpy as np + from PyQt5.QtCore import Qt from PyQt5.QtGui import QDoubleValidator, QIntValidator from PyQt5.QtWidgets import QCheckBox, QSplitter @@ -112,16 +114,19 @@ def __init__(self, *, parent=None): def updateF(self, data): """Override.""" + spectrum = data['spectrum_mean'] + spectrum_ma = data['spectrum_ma_mean'] + x = data["x"] if x is None: - self._mean.setData(data['spectrum_mean']) - self._mean_ma.setData(data['spectrum_ma_mean']) self.setLabel('bottom', "Pixel") + x = np.arange(len(spectrum)) else: - self._mean.setData(x, data['spectrum_mean']) - self._mean_ma.setData(x, data['spectrum_ma_mean']) self.setLabel('bottom', "eV") + self._mean.setData(x, spectrum) + self._mean_ma.setData(x, spectrum_ma) + class GotthardPulsePlot(PlotWidgetF): """GotthardPulsePlot class. @@ -152,16 +157,19 @@ def updateF(self, data): self._idx = idx self._updateTitle() + spectrum = data['spectrum'][idx] + spectrum_ma = data['spectrum_ma'][idx] + x = data["x"] if x is None: - self._poi.setData(data['spectrum'][idx]) - self._poi_ma.setData(data['spectrum_ma'][idx]) self.setLabel('bottom', "Pixel") + x = np.arange(len(spectrum)) else: - self._poi.setData(x, data['spectrum'][idx]) - self._poi_ma.setData(x, data['spectrum_ma'][idx]) self.setLabel('bottom', "eV") + self._poi.setData(x, spectrum) + self._poi_ma.setData(x, spectrum_ma) + class GotthardImageView(ImageViewF): """GotthardImageView class. diff --git a/extra_foam/special_suite/tests/__init__.py b/extra_foam/special_suite/tests/__init__.py index a2cfd0aba..2baeaad7b 100644 --- a/extra_foam/special_suite/tests/__init__.py +++ b/extra_foam/special_suite/tests/__init__.py @@ -3,6 +3,8 @@ from extra_foam.gui.plot_widgets import TimedPlotWidgetF, TimedImageViewF +from extra_foam.special_suite import logger, mkQApp + class _SpecialSuiteWindowTestBase(unittest.TestCase): @staticmethod @@ -12,15 +14,22 @@ def data4visualization(): def _check_update_plots(self): win = self._win worker = win._worker_st - worker._output_st.put_pop(self.data4visualization()) - - win.updateWidgetsST() - for widget in win._plot_widgets_st: - if isinstance(widget, TimedPlotWidgetF): - widget.refresh() - for widget in win._image_views_st: - if isinstance(widget, TimedImageViewF): - widget.refresh() + + with self.assertLogs(logger, level="ERROR") as cm: + logger.error("dummy") # workaround + + win.updateWidgetsST() # with empty data + + worker._output_st.put_pop(self.data4visualization()) + win.updateWidgetsST() + for widget in win._plot_widgets_st: + if isinstance(widget, TimedPlotWidgetF): + widget.refresh() + for widget in win._image_views_st: + if isinstance(widget, TimedImageViewF): + widget.refresh() + + self.assertEqual(1, len(cm.output)) class _SpecialSuiteProcessorTestBase: diff --git a/extra_foam/special_suite/tests/test_camview.py b/extra_foam/special_suite/tests/test_camview.py index c2b4094d7..87798639f 100644 --- a/extra_foam/special_suite/tests/test_camview.py +++ b/extra_foam/special_suite/tests/test_camview.py @@ -17,12 +17,15 @@ CamViewWindow, CameraView, CameraViewRoiHist ) +from . import _SpecialSuiteWindowTestBase, _SpecialSuiteProcessorTestBase + + app = mkQApp() -logger.setLevel('CRITICAL') +logger.setLevel('INFO') -class TestCamView(unittest.TestCase): +class TestCamViewWindow(_SpecialSuiteWindowTestBase): @classmethod def setUpClass(cls): cls._win = CamViewWindow('SCS') @@ -32,6 +35,14 @@ def tearDownClass(cls): # explicitly close the MainGUI to avoid error in GuiLogger cls._win.close() + @staticmethod + def data4visualization(): + """Override.""" + return { + "displayed": np.arange(20).reshape(4, 5), + "roi_hist": (np.arange(4), np.arange(4), 1, 2, 3) + } + def testWindow(self): win = self._win @@ -43,7 +54,7 @@ def testWindow(self): self.assertEqual(1, counter[CameraView]) self.assertEqual(1, counter[CameraViewRoiHist]) - win.updateWidgetsST() + self._check_update_plots() def testCtrl(self): from extra_foam.special_suite.cam_view_w import ( @@ -99,7 +110,7 @@ def testCtrl(self): self.assertEqual(999, proc._n_bins) -class TestCamViewProcessor(_RawDataMixin): +class TestCamViewProcessor(_RawDataMixin, _SpecialSuiteProcessorTestBase): @pytest.fixture(autouse=True) def setUp(self): self._proc = CamViewProcessor(object(), object()) @@ -208,6 +219,7 @@ def testProcessing(self, subtract_dark): # 1st train processed = proc.process(self._get_data(12345)) + self._check_processed_data_structure(processed) np.testing.assert_array_almost_equal(imgdata_gt, processed["displayed"]) # 2nd train @@ -222,3 +234,8 @@ def testProcessing(self, subtract_dark): # reset proc.reset() assert proc._raw_ma is None + + def _check_processed_data_structure(self, ret): + """Override.""" + data_gt = TestCamViewWindow.data4visualization().keys() + assert set(ret.keys()) == set(data_gt) diff --git a/extra_foam/special_suite/tests/test_gotthard.py b/extra_foam/special_suite/tests/test_gotthard.py index 02a978d86..5e646d04e 100644 --- a/extra_foam/special_suite/tests/test_gotthard.py +++ b/extra_foam/special_suite/tests/test_gotthard.py @@ -21,12 +21,14 @@ ProcessingError ) +from . import _SpecialSuiteWindowTestBase, _SpecialSuiteProcessorTestBase + app = mkQApp() -logger.setLevel('CRITICAL') +logger.setLevel('INFO') -class TestGotthard(unittest.TestCase): +class TestGotthardWindow(_SpecialSuiteWindowTestBase): @classmethod def setUpClass(cls): cls._win = GotthardWindow('MID') @@ -36,6 +38,19 @@ def tearDownClass(cls): # explicitly close the MainGUI to avoid error in GuiLogger cls._win.close() + @staticmethod + def data4visualization(n_pulses=4): + """Override.""" + return { + "x": None, + "spectrum": np.arange(10 * n_pulses).reshape(n_pulses, 10), + "spectrum_ma": np.arange(10 * n_pulses).reshape(n_pulses, 10), + "spectrum_mean": np.arange(10), + "spectrum_ma_mean": np.arange(10), + "poi_index": 0, + "hist": (np.arange(5), np.arange(5), 1, 1, 1), + } + def testWindow(self): win = self._win @@ -49,7 +64,7 @@ def testWindow(self): self.assertEqual(1, counter[GotthardPulsePlot]) self.assertEqual(1, counter[GotthardHist]) - win.updateWidgetsST() + self._check_update_plots() def testCtrl(self): from extra_foam.special_suite.gotthard_w import _DEFAULT_N_BINS, _DEFAULT_BIN_RANGE @@ -135,7 +150,7 @@ def testCtrl(self): self.assertTrue(proc._hist_over_ma) -class TestGotthardProcessor(_RawDataMixin): +class TestGotthardProcessor(_RawDataMixin, _SpecialSuiteProcessorTestBase): @pytest.fixture(autouse=True) def setUp(self): self._proc = GotthardProcessor(object(), object()) @@ -269,6 +284,7 @@ def testProcessing(self, subtract_dark): # 1st train processed = proc.process(self._get_data(12345)) + self._check_processed_data_structure(processed) assert 1 == processed["poi_index"] np.testing.assert_array_almost_equal(adc_gt, processed["spectrum"]) np.testing.assert_array_almost_equal(adc_gt, processed["spectrum_ma"]) @@ -329,3 +345,8 @@ def testRemoveDark(self): proc.onRemoveDark() assert proc._dark_ma is None assert proc._dark_mean_ma is None + + def _check_processed_data_structure(self, ret): + """Override.""" + data_gt = TestGotthardWindow.data4visualization().keys() + assert set(ret.keys()) == set(data_gt) diff --git a/extra_foam/special_suite/tests/test_gotthard_pump_probe.py b/extra_foam/special_suite/tests/test_gotthard_pump_probe.py index 8cbde77f0..e9910bbbf 100644 --- a/extra_foam/special_suite/tests/test_gotthard_pump_probe.py +++ b/extra_foam/special_suite/tests/test_gotthard_pump_probe.py @@ -18,12 +18,14 @@ ) from extra_foam.special_suite.special_analysis_base import ProcessingError +from . import _SpecialSuiteWindowTestBase, _SpecialSuiteProcessorTestBase + app = mkQApp() -logger.setLevel('CRITICAL') +logger.setLevel('INFO') -class TestGotthardPumpProbe(unittest.TestCase): +class TestGotthardPpWindow(_SpecialSuiteWindowTestBase): @classmethod def setUpClass(cls): cls._win = GotthardPumpProbeWindow('SCS') @@ -33,6 +35,23 @@ def tearDownClass(cls): # explicitly close the MainGUI to avoid error in GuiLogger cls._win.close() + @staticmethod + def data4visualization(n_pulses=5, n_on=2, n_off=2): + """Override.""" + return { + "raw": np.arange(8*n_pulses).reshape(n_pulses, 8), + "corrected": np.arange(8 * n_pulses).reshape(n_pulses, 8), + "on_slicer": slice(0, n_on), + "off_slicer": slice(n_on, n_on + n_off), + "dark_slicer": slice(n_on + n_off, None), + "poi_index": n_on - 1, + "dark_poi_index": 0, + "vfom": np.arange(40).reshape(5, 8), + "vfom_ma": np.arange(40).reshape(5, 8), + "vfom_mean": np.arange(8), + "vfom_ma_mean": np.arange(8), + } + def testWindow(self): win = self._win @@ -47,7 +66,7 @@ def testWindow(self): self.assertEqual(1, counter[GotthardPpRawPulsePlot]) self.assertEqual(1, counter[GotthardPpDarkPulsePlot]) - win.updateWidgetsST() + self._check_update_plots() def testCtrl(self): win = self._win @@ -103,7 +122,7 @@ def testCtrl(self): self.assertEqual(9, proc.__class__._vfom_ma.window) -class TestGotthardPpProcessor(_RawDataMixin): +class TestGotthardPpProcessor(_RawDataMixin, _SpecialSuiteProcessorTestBase): @pytest.fixture(autouse=True) def setUp(self): self._proc = GotthardPpProcessor(object(), object()) @@ -153,9 +172,15 @@ def testProcessing(self): adc_gt = self._adc.astype(_PIXEL_DTYPE) processed = proc.process(self._get_data(12345)) + self._check_processed_data_structure(processed) np.testing.assert_array_almost_equal(adc_gt, processed["raw"]) corrected_gt = adc_gt - np.mean(adc_gt[proc._dark_slicer], axis=0) np.testing.assert_array_almost_equal(corrected_gt, processed["corrected"]) vfom_gt = corrected_gt[proc._on_slicer] - corrected_gt[proc._off_slicer] np.testing.assert_array_almost_equal(vfom_gt, processed["vfom"]) + + def _check_processed_data_structure(self, ret): + """Override.""" + data_gt = TestGotthardPpWindow.data4visualization().keys() + assert set(ret.keys()) == set(data_gt) diff --git a/extra_foam/special_suite/tests/test_module_scan.py b/extra_foam/special_suite/tests/test_module_scan.py index fbac2941c..bc6909eaa 100644 --- a/extra_foam/special_suite/tests/test_module_scan.py +++ b/extra_foam/special_suite/tests/test_module_scan.py @@ -12,13 +12,14 @@ ModuleScanWindow ) +from . import _SpecialSuiteWindowTestBase, _SpecialSuiteProcessorTestBase app = mkQApp() -logger.setLevel('CRITICAL') +logger.setLevel('INFO') -class TestModuleScan(unittest.TestCase): +class TestModuleScan(_SpecialSuiteWindowTestBase): @classmethod def setUpClass(cls): cls._win = ModuleScanWindow('DET') diff --git a/extra_foam/special_suite/tests/test_multicamview.py b/extra_foam/special_suite/tests/test_multicamview.py index 402125eb0..fecb1b1ce 100644 --- a/extra_foam/special_suite/tests/test_multicamview.py +++ b/extra_foam/special_suite/tests/test_multicamview.py @@ -16,12 +16,14 @@ MultiCamViewWindow, CameraView ) +from . import _SpecialSuiteWindowTestBase, _SpecialSuiteProcessorTestBase + app = mkQApp() -logger.setLevel('CRITICAL') +logger.setLevel('INFO') -class TestMultiCamView(unittest.TestCase): +class TestMultiCamViewWindow(_SpecialSuiteWindowTestBase): @classmethod def setUpClass(cls): cls._win = MultiCamViewWindow('SCS') @@ -31,6 +33,14 @@ def tearDownClass(cls): # explicitly close the MainGUI to avoid error in GuiLogger cls._win.close() + @staticmethod + def data4visualization(): + """Override.""" + return { + "channels": {0: "camera1", 1: None, 2: None, 3: "camera2"}, + "images": {0: np.ones((4, 5)), 1: None, 2: None, 3: np.ones((5, 6))} + } + def testWindow(self): win = self._win @@ -41,7 +51,7 @@ def testWindow(self): self.assertEqual(4, counter[CameraView]) - win.updateWidgetsST() + self._check_update_plots() def testCtrl(self): @@ -67,7 +77,7 @@ def testCtrl(self): self.assertEqual(f"new/property{i}", proc._properties[i]) -class TestMultiCamViewProcessor(_RawDataMixin): +class TestMultiCamViewProcessor(_RawDataMixin, _SpecialSuiteProcessorTestBase): @pytest.fixture(autouse=True) def setUp(self): self._proc = MultiCamViewProcessor(object(), object()) @@ -89,6 +99,8 @@ def testProcessing(self): }) processed = proc.process(data) + self._check_processed_data_structure(processed) + for i, gt in enumerate(proc._output_channels): assert gt == processed["channels"][i] @@ -97,3 +109,8 @@ def testProcessing(self): np.testing.assert_array_equal(np.ones((3, 3)), processed["images"][2]) np.testing.assert_array_equal(np.ones((4, 4)), processed["images"][3]) assert np.float32 == processed["images"][3].dtype + + def _check_processed_data_structure(self, ret): + """Override.""" + data_gt = TestMultiCamViewWindow.data4visualization().keys() + assert set(ret.keys()) == set(data_gt) diff --git a/extra_foam/special_suite/tests/test_trxas.py b/extra_foam/special_suite/tests/test_trxas.py index e966e9631..b1e079539 100644 --- a/extra_foam/special_suite/tests/test_trxas.py +++ b/extra_foam/special_suite/tests/test_trxas.py @@ -20,7 +20,7 @@ app = mkQApp() -logger.setLevel('CRITICAL') +logger.setLevel('INFO') class TestTrXasWindow(_SpecialSuiteWindowTestBase): diff --git a/extra_foam/special_suite/tests/test_xas_tim.py b/extra_foam/special_suite/tests/test_xas_tim.py index f93adb677..84d89fa8a 100644 --- a/extra_foam/special_suite/tests/test_xas_tim.py +++ b/extra_foam/special_suite/tests/test_xas_tim.py @@ -23,7 +23,7 @@ app = mkQApp() -logger.setLevel('CRITICAL') +logger.setLevel('INFO') class TestXasTimWindow(_SpecialSuiteWindowTestBase): @@ -42,11 +42,11 @@ def data4visualization(n_trains=10, n_pulses_per_train=10, n_bins=6): """Override.""" return { "xgm_intensity": np.arange(n_pulses_per_train), - "digitizer_apds": [np.arange(n_pulses_per_train)] * 4, + "digitizer_apds": [np.arange(n_pulses_per_train)] * 3 + [None], "energy_scan": (np.arange(n_trains), np.arange(n_trains)), "correlation_length": 20, "i0": np.arange(n_trains * n_pulses_per_train), - "i1": [np.arange(n_trains * n_pulses_per_train)] * 4, + "i1": [np.arange(n_trains * n_pulses_per_train)] * 3 + [None], "spectra": ([np.arange(n_bins)] * 5, np.arange(n_bins), np.arange(n_bins)), } diff --git a/extra_foam/special_suite/tests/test_xas_tim_xmcd.py b/extra_foam/special_suite/tests/test_xas_tim_xmcd.py index fb6e96b69..1271779e9 100644 --- a/extra_foam/special_suite/tests/test_xas_tim_xmcd.py +++ b/extra_foam/special_suite/tests/test_xas_tim_xmcd.py @@ -23,7 +23,7 @@ app = mkQApp() -logger.setLevel('CRITICAL') +logger.setLevel('INFO') class TestXasTimXmcdWindow(_SpecialSuiteWindowTestBase): @@ -42,12 +42,12 @@ def data4visualization(n_trains=10, n_pulses_per_train=10, n_bins=6): stats.append(np.arange(n_bins)) return { "xgm_intensity": np.arange(n_pulses_per_train), - "digitizer_apds": [np.arange(n_pulses_per_train)] * 4, + "digitizer_apds": [np.arange(n_pulses_per_train)] * 3 + [None], "energy_scan": (np.arange(n_trains), np.arange(n_trains)), "current_scan": (np.arange(n_trains), np.arange(n_trains)), "correlation_length": 20, "i0": np.arange(n_trains * n_pulses_per_train), - "i1": [np.arange(n_trains * n_pulses_per_train)] * 4, + "i1": [np.arange(n_trains * n_pulses_per_train)] * 3 + [None], "spectra": (stats, np.arange(n_bins), np.arange(n_bins)), } diff --git a/extra_foam/special_suite/xas_tim_w.py b/extra_foam/special_suite/xas_tim_w.py index c945720ca..ce4c15f64 100644 --- a/extra_foam/special_suite/xas_tim_w.py +++ b/extra_foam/special_suite/xas_tim_w.py @@ -9,6 +9,8 @@ """ from functools import partial +import numpy as np + from PyQt5.QtCore import Qt from PyQt5.QtGui import QDoubleValidator, QIntValidator from PyQt5.QtWidgets import ( @@ -145,7 +147,9 @@ def __init__(self, *, parent=None): def updateF(self, data): """Override.""" - self._plot.setData(data['xgm_intensity']) + i = data['xgm_intensity'] + x = np.arange(len(i)) + self._plot.setData(x, i) class XasTimDigitizerPulsePlot(PlotWidgetF): @@ -171,7 +175,11 @@ def __init__(self, *, parent=None): def updateF(self, data): """Override.""" for p, apd in zip(self._plots, data['digitizer_apds']): - p.setData(apd) + if apd is None: + p.setData([], []) + else: + x = np.arange(len(apd)) + p.setData(x, apd) class XasTimMonoScanPlot(TimedPlotWidgetF):