diff --git a/bliss/controllers/mosca/base.py b/bliss/controllers/mosca/base.py index 46d480e4c98e57e9e0b1174ffe214c4247f9f006..457029004d0f292a09af7ef3d96380e159ba5a12 100644 --- a/bliss/controllers/mosca/base.py +++ b/bliss/controllers/mosca/base.py @@ -914,17 +914,15 @@ class McaController(CounterContainer): self._detector_model = None self._number_channels = None self._spectrum_size = None - self._settings = OrderedHashObjSetting(f"{self._name}_ctrl_settings") - self._load_settings() - - self._masterCC = None + self._masterCC = McaCounterController(self.name, self) self._calcroiCC = None self._sumroiCC = None self.initialize() def __info__(self): + self._check_server_has_restarted() txt = f"=== MCA controller: {self.config['tango_name']} ===\n" txt += f" detector name: {self._detector_name}\n" txt += f" detector model: {self._detector_model}\n" @@ -946,12 +944,12 @@ class McaController(CounterContainer): self._build_channels_mapping() def _create_counters(self): - for cc in [self._masterCC, self._calcroiCC, self._sumroiCC]: + for cc in [self._calcroiCC, self._sumroiCC]: if cc is not None: cc._global_map_unregister() # === instantiations order matters! - self._masterCC = McaCounterController(self.name, self) + self._masterCC._counters.clear() SpectrumCounter("spectrum", self._masterCC) for label_index, label in enumerate(self.hardware.metadata_labels): if label not in ["chnum", "deadtime_correction"]: @@ -969,6 +967,9 @@ class McaController(CounterContainer): self._rois = ROIManager(self) # ================================== + def _get_default_chain_counter_controller(self): + return self._masterCC + def _build_channels_mapping(self): """Build mapping between channels aliases and corresponding data indexes""" self._chan2index = {} @@ -1001,6 +1002,8 @@ class McaController(CounterContainer): if server_start_timestamp is not None: log_warning(self, "re-initializing because server has been restarted") self.initialize() + return True + return False def _update_global_map_calccounters(self, value): # enable / disable calc_counters in GlobalMap (and therefore MeasurementGroups) @@ -1011,6 +1014,7 @@ class McaController(CounterContainer): global_map.unregister(cnt) def initialize(self): + self._load_settings() self._get_hardware_info() self._create_counters() self._update_global_map_calccounters(self._calcroiCC.calc_formula) diff --git a/bliss/controllers/mosca/calccounters.py b/bliss/controllers/mosca/calccounters.py index e929a8fe8f0192764670ccda37a9d61f5fa0a0b3..4b1df6b4e1e89df3fd23d2dfffb671b295c94356 100644 --- a/bliss/controllers/mosca/calccounters.py +++ b/bliss/controllers/mosca/calccounters.py @@ -32,8 +32,9 @@ class CalcROICounterController(CalcCounterController): ) self._external_counters = config.get("external_counters", {}) self._inputs_dict_stat = {} - self._inputs_dict_roi = {} self._inputs_dict_external = {} + self._inputs_dict_roi = {} + super().__init__(name, config) @property @@ -60,44 +61,30 @@ class CalcROICounterController(CalcCounterController): ) def build_counters(self, config): - self._tags = {} - # add mca stat counters as inputs + # superseded by update_counters + pass + + def update_stat_inputs(self): self._inputs_dict_stat = {} for cnt in self._mca._masterCC.counters: if isinstance(cnt, StatCounter): self._tags[cnt.name] = cnt.name self._inputs_dict_stat[cnt.name] = cnt - # add external counters (declared in config) as inputs - self.update_external_inputs() - def update_external_inputs(self): + # remove current keys from tags + for cnt in self._inputs_dict_external.values(): + self._tags.pop(cnt.name, None) + self._inputs_dict_external = {} for tag, cnt in self._external_counters.items(): self._tags[cnt.name] = tag if tag in self.calc_formula: self._inputs_dict_external[tag] = cnt - def update_counters(self): - """update inputs and outputs depending on RoiCounters declared in associated self._mca._masterCC""" - - # WARNING: a CalcCounterController registers its COUNTERS (not itself, unlike the CounterController class) - for cntout in self._output_counters: - global_map.unregister(cntout) - + def update_rois_inputs_and_output_counters(self): self._inputs_dict_roi = {} self._output_counters = [] - self._counters = {} - self._tags = {} - - # rebuild outputs - self.build_output_counters() - - # WARNING: a CalcCounterController registers its COUNTERS (not itself, unlike the CounterController class) - for cntout in self._output_counters: - global_map.register(cntout, parents_list=["counters"]) - - def build_output_counters(self): for cnt in self._mca._masterCC.counters: if isinstance(cnt, ROICounter): # only consider single channel rois (i.e. not summed by mosca) @@ -110,8 +97,28 @@ class CalcROICounterController(CalcCounterController): self._tags[cntout_name] = cntout_name self._output_counters.append(cntout) - # WARNING: a CalcCounterController registers its COUNTERS (not itself, unlike the CounterController class) - global_map.register(cntout, parents_list=["counters"]) + if self.calc_formula: + # WARNING: a CalcCounterController registers its COUNTERS (not itself, unlike the CounterController class) + global_map.register(cntout, parents_list=["counters"]) + + def update_counters(self): + """update inputs and outputs depending on RoiCounters declared in associated self._mca._masterCC""" + + # WARNING: a CalcCounterController registers its COUNTERS (not itself, unlike the CounterController class) + for cntout in self._output_counters: + global_map.unregister(cntout) + + self._counters = {} + self._tags = {} + + # add mca stat counters as inputs + self.update_stat_inputs() + + # add external counters (declared in config) as inputs + self.update_external_inputs() + + # add rois inputs and build outputs + self.update_rois_inputs_and_output_counters() @property def calc_formula(self): @@ -183,7 +190,23 @@ class SumCalcROICounterController(CalcCounterController): return self._tags def build_counters(self, config=None): + # superseded by update_counters + pass + + def update_counters(self): + # clear previous calc counters + # WARNING: a CalcCounterController registers its COUNTERS (not itself, unlike the CounterController class) + for cntout in self._output_counters: + global_map.unregister(cntout) + + self._input_counters = [] + self._output_counters = [] + self._counters = {} + self._tags = {} + self._out2in = {} + + # rebuild counters # list rois that should be summed tmp_out2in = {} for cntin in self._calcroicc.outputs: @@ -203,25 +226,9 @@ class SumCalcROICounterController(CalcCounterController): self._input_counters.append(cntin) self._out2in[cntout_name].append(cntin.name) - def update_counters(self): - # clear previous calc counters - - # WARNING: a CalcCounterController registers its COUNTERS (not itself, unlike the CounterController class) - for cntout in self._output_counters: - global_map.unregister(cntout) - - self._input_counters = [] - self._output_counters = [] - self._counters = {} - self._tags = {} - self._out2in = {} - - # rebuild counters - self.build_counters() - - # WARNING: a CalcCounterController registers its COUNTERS (not itself, unlike the CounterController class) - for cntout in self._output_counters: - global_map.register(cntout, parents_list=["counters"]) + if self._calcroicc.calc_formula: + # WARNING: a CalcCounterController registers its COUNTERS (not itself, unlike the CounterController class) + global_map.register(cntout, parents_list=["counters"]) def calc_function(self, input_dict): output_dict = {} diff --git a/bliss/scanning/toolbox.py b/bliss/scanning/toolbox.py index 42266ded6c0e1fff4fadf438c8511b6f32c181a5..2b9d41c3ddeb77b262907aed29d933ba931b93df 100644 --- a/bliss/scanning/toolbox.py +++ b/bliss/scanning/toolbox.py @@ -16,7 +16,6 @@ from bliss.common.measurementgroup import ( ) from bliss.common.counter import CalcCounter, Counter from bliss.controllers.counter import CounterController, CalcCounterController -from bliss.controllers.bliss_controller import BlissController from bliss.scanning.chain import AcquisitionChain from bliss.scanning.acquisition.timer import SoftwareTimerMaster @@ -309,11 +308,11 @@ class DefaultAcquisitionChain: master = device_settings.get("master") if master is not None: - if isinstance(master, BlissController): + if hasattr(master, "_get_default_chain_counter_controller"): master = master._get_default_chain_counter_controller() elif not isinstance(master, CounterController): - msg = "default_chain config: master object must be a CounterController or a BlissController," + msg = "default_chain config: master object must be a CounterController or implement '_get_default_chain_counter_controller'," msg += f" instead received '{master.name}' of type {type(master)} !" raise ValueError(msg) @@ -322,10 +321,10 @@ class DefaultAcquisitionChain: controller = device._counter_controller elif isinstance(device, CounterController): controller = device - elif isinstance(device, BlissController): + elif hasattr(device, "_get_default_chain_counter_controller"): controller = device._get_default_chain_counter_controller() else: - msg = "default_chain config: device object must be a CounterController, a Counter or a BlissController" + msg = "default_chain config: device object must be a CounterController, a Counter or implement '_get_default_chain_counter_controller'" msg += f" instead received '{device.name}' of type {type(device)} !" raise ValueError(msg) diff --git a/tests/controllers_sw/test_mca_simulator.py b/tests/controllers_sw/test_mca_simulator.py index 9763ef76af5292d481ba57bd3a3ea60e5c54d3d6..5994bfa59ab3019f7f625b990f2abceaa2c2c1d6 100644 --- a/tests/controllers_sw/test_mca_simulator.py +++ b/tests/controllers_sw/test_mca_simulator.py @@ -8,6 +8,7 @@ import numpy from bliss import global_map from bliss.common.scans import loopscan +from tests.conftest import mosca_simulator_context def test_mca_simulator(default_session, mosca_simulator): @@ -186,3 +187,53 @@ def test_mca_simulator(default_session, mosca_simulator): assert sumname not in [x.name for x in mca._sumroiCC.outputs] assert sumname not in mca._sumroiCC._counters assert sumname not in gm_cntnames + + +def test_server_restart(default_session): + + mca = None + maseterCC = None + + with mosca_simulator_context("mosca_simulator", "id00/mosca/simulator"): + mca = default_session.config.get("mca_sim") + assert mca._check_server_has_restarted() is False + maseterCC = mca._masterCC + + with mosca_simulator_context("mosca_simulator", "id00/mosca/simulator"): + assert mca._check_server_has_restarted() is True + assert maseterCC is mca._masterCC + + +def test_issue_4223(default_session, mosca_simulator): + + mca = default_session.config.get("mca_sim") + mca._set_number_of_channels(2) + mca.rois.clear() + + mca.calc_formula = "roi" + mca.rois["r1"] = 100, 300 + gm_cntnames = [x.name for x in global_map.get_counters_iter()] + assert "r1_corr_det00" in gm_cntnames + assert "r1_corr_det01" in gm_cntnames + assert "r1_sum" in gm_cntnames + + mca.calc_formula = None + mca.rois["r2"] = 100, 300 + gm_cntnames = [x.name for x in global_map.get_counters_iter()] + assert "r1_corr_det00" not in gm_cntnames + assert "r1_corr_det01" not in gm_cntnames + assert "r2_corr_det00" not in gm_cntnames + assert "r2_corr_det01" not in gm_cntnames + assert "r2_corr_det01" not in gm_cntnames + assert "r1_sum" not in gm_cntnames + assert "r2_sum" not in gm_cntnames + + mca.calc_formula = "roi" + gm_cntnames = [x.name for x in global_map.get_counters_iter()] + assert "r1_corr_det00" in gm_cntnames + assert "r1_corr_det01" in gm_cntnames + assert "r2_corr_det00" in gm_cntnames + assert "r2_corr_det01" in gm_cntnames + assert "r2_corr_det01" in gm_cntnames + assert "r1_sum" in gm_cntnames + assert "r2_sum" in gm_cntnames diff --git a/tests/test_default_scan_chain.py b/tests/test_default_scan_chain.py index dc97851d10cd1bbad570aa8b10e56fa7cd6c83a9..c44a06a65fd01a20d53e34f02312a43da22bd87b 100644 --- a/tests/test_default_scan_chain.py +++ b/tests/test_default_scan_chain.py @@ -5,6 +5,7 @@ # Copyright (c) Beamline Control Unit, ESRF # Distributed under the GNU LGPLv3. See LICENSE for more info. +import pytest import gevent from bliss.common.scans import DEFAULT_CHAIN, loopscan from bliss.scanning.acquisition.lima import LimaAcquisitionMaster @@ -423,13 +424,10 @@ def test_default_chain_with_bad_master(default_session, lima_simulator): }, ] - try: + with pytest.raises(ValueError) as exc: DEFAULT_CHAIN.set_settings(defch_settings) - assert False # it should have failed the line above - except ValueError as exc: - assert "must be a CounterController" in str(exc) - finally: - DEFAULT_CHAIN.set_settings([]) + assert "must be a CounterController" in str(exc) + DEFAULT_CHAIN.clear() def test_default_chain_with_bad_device(default_session, lima_simulator): @@ -460,13 +458,10 @@ def test_default_chain_with_bad_device(default_session, lima_simulator): } ] - try: + with pytest.raises(ValueError) as exc: DEFAULT_CHAIN.set_settings(defch_settings) - assert False # it should have failed the line above - except ValueError as exc: - assert "must be a CounterController, a Counter or a BlissController" in str(exc) - finally: - DEFAULT_CHAIN.set_settings([]) + assert "must be a CounterController" in str(exc) + DEFAULT_CHAIN.clear() def test_default_chain_with_mca_defaults_parameters(default_session, lima_simulator):