From 8ad3eb29b79a0a8a742d1bc319cfedf60fcc150f Mon Sep 17 00:00:00 2001 From: appel_c Date: Tue, 5 Sep 2023 00:24:04 +0200 Subject: [PATCH] fix: working mcs readout --- .../epics/devices/DelayGeneratorDG645.py | 75 +++++++++++++----- .../epics/devices/bec_scaninfo_mixin.py | 8 +- ophyd_devices/epics/devices/eiger9m_csaxs.py | 34 ++++++--- ophyd_devices/epics/devices/mcs_csaxs.py | 76 +++++++++---------- 4 files changed, 124 insertions(+), 69 deletions(-) diff --git a/ophyd_devices/epics/devices/DelayGeneratorDG645.py b/ophyd_devices/epics/devices/DelayGeneratorDG645.py index 27bd113..224f773 100644 --- a/ophyd_devices/epics/devices/DelayGeneratorDG645.py +++ b/ophyd_devices/epics/devices/DelayGeneratorDG645.py @@ -61,7 +61,9 @@ class DummyPositioner(PVPositioner): setpoint = Component(EpicsSignal, "DelayAO", put_complete=True, kind=Kind.config) readback = Component(EpicsSignalRO, "DelayAI", kind=Kind.config) done = Component(Signal, value=1) - reference = Component(EpicsSignal, "ReferenceMO", put_complete=True, kind=Kind.config) + reference = Component( + EpicsSignal, "ReferenceMO", put_complete=True, kind=Kind.config + ) class DelayPair(PseudoPositioner): @@ -91,12 +93,16 @@ class DelayPair(PseudoPositioner): @pseudo_position_argument def forward(self, pseudo_pos): """Run a forward (pseudo -> real) calculation""" - return self.RealPosition(ch1=pseudo_pos.delay, ch2=pseudo_pos.delay + pseudo_pos.width) + return self.RealPosition( + ch1=pseudo_pos.delay, ch2=pseudo_pos.delay + pseudo_pos.width + ) @real_position_argument def inverse(self, real_pos): """Run an inverse (real -> pseudo) calculation""" - return self.PseudoPosition(delay=real_pos.ch1, width=real_pos.ch2 - real_pos.ch1) + return self.PseudoPosition( + delay=real_pos.ch1, width=real_pos.ch2 - real_pos.ch1 + ) class TriggerSource(int, enum.Enum): @@ -141,8 +147,12 @@ class DelayGeneratorDG645(Device): "reload_config", ] - trigger_burst_readout = Component(EpicsSignal, "EventStatusLI.PROC", name="read_burst_state") - burst_cycle_finished = Component(EpicsSignalRO, "EventStatusMBBID.B3", name="read_burst_state") + trigger_burst_readout = Component( + EpicsSignal, "EventStatusLI.PROC", name="read_burst_state" + ) + burst_cycle_finished = Component( + EpicsSignalRO, "EventStatusMBBID.B3", name="read_burst_state" + ) status = Component(EpicsSignalRO, "StatusSI", name="status") clear_error = Component(EpicsSignal, "StatusClearBO", name="clear_error") @@ -189,7 +199,9 @@ class DelayGeneratorDG645(Device): name="trigger_rate", kind=Kind.config, ) - trigger_shot = Component(EpicsSignal, "TriggerDelayBO", name="trigger_shot", kind="config") + trigger_shot = Component( + EpicsSignal, "TriggerDelayBO", name="trigger_shot", kind="config" + ) # Burst mode burstMode = Component( EpicsSignal, @@ -337,7 +349,7 @@ class DelayGeneratorDG645(Device): f"{name}_delay_burst": 0, f"{name}_delta_width": 0, f"{name}_additional_triggers": 0, - f"{name}_polarity": 1, + f"{name}_polarity": [1, 1, 1, 1, 1], f"{name}_amplitude": 4.5, f"{name}_offset": 0, f"{name}_thres_trig_level": 2.5, @@ -346,7 +358,10 @@ class DelayGeneratorDG645(Device): f"{name}_set_trigger_source": "SINGLE_SHOT", } if ddg_config is not None: - [self.ddg_config.update({f"{name}_{key}": value}) for key, value in ddg_config.items()] + [ + self.ddg_config.update({f"{name}_{key}": value}) + for key, value in ddg_config.items() + ] super().__init__( prefix=prefix, name=name, @@ -357,7 +372,9 @@ class DelayGeneratorDG645(Device): **kwargs, ) if device_manager is None and not sim_mode: - raise DDGError("Add DeviceManager to initialization or init with sim_mode=True") + raise DDGError( + "Add DeviceManager to initialization or init with sim_mode=True" + ) self.device_manager = device_manager if not sim_mode: self._producer = self.device_manager.producer @@ -430,7 +447,10 @@ class DelayGeneratorDG645(Device): self.set_channels( "reference", 0, - [f"channel{self._all_delay_pairs[ii]}.ch1" for ii in range(len(self._all_delay_pairs))], + [ + f"channel{self._all_delay_pairs[ii]}.ch1" + for ii in range(len(self._all_delay_pairs)) + ], ) for ii in range(len(self._all_delay_pairs)): self.set_channels( @@ -448,7 +468,12 @@ class DelayGeneratorDG645(Device): """ while True: self.trigger_burst_readout.set(1) - if self.burst_cycle_finished.read()[self.burst_cycle_finished.name]["value"] == 1: + if ( + self.burst_cycle_finished.read()[self.burst_cycle_finished.name][ + "value" + ] + == 1 + ): self._acquisition_done = True return if self._stopped == True: @@ -474,7 +499,9 @@ class DelayGeneratorDG645(Device): ) total_exposure = exp_time delay_burst = self.delay_burst.get() - self.burst_enable(num_burst_cycle, delay_burst, total_exposure, config="first") + self.burst_enable( + num_burst_cycle, delay_burst, total_exposure, config="first" + ) self.set_channels("delay", 0) # Set burst length to half of the experimental time! self.set_channels("width", exp_time) @@ -482,9 +509,13 @@ class DelayGeneratorDG645(Device): exp_time = self.delta_width.get() + self.scaninfo.exp_time total_exposure = exp_time + self.scaninfo.readout_time delay_burst = self.delay_burst.get() - num_burst_cycle = self.scaninfo.num_frames + self.additional_triggers.get() + num_burst_cycle = ( + self.scaninfo.num_frames + self.additional_triggers.get() + ) # set parameters in DDG - self.burst_enable(num_burst_cycle, delay_burst, total_exposure, config="first") + self.burst_enable( + num_burst_cycle, delay_burst, total_exposure, config="first" + ) self.set_channels("delay", 0) # Set burst length to half of the experimental time! self.set_channels("width", exp_time) @@ -503,7 +534,9 @@ class DelayGeneratorDG645(Device): # self.additional_triggers should be 0 for self.set_high_on_exposure or remove here fully.. num_burst_cycle = 1 + self.additional_triggers.get() # set parameters in DDG - self.burst_enable(num_burst_cycle, delay_burst, total_exposure, config="first") + self.burst_enable( + num_burst_cycle, delay_burst, total_exposure, config="first" + ) self.set_channels("delay", 0.0) # Set burst length to half of the experimental time! self.set_channels("width", exp_time) @@ -513,9 +546,13 @@ class DelayGeneratorDG645(Device): exp_time = self.delta_width.get() + self.scaninfo.exp_time total_exposure = exp_time + self.scaninfo.readout_time delay_burst = self.delay_burst.get() - num_burst_cycle = self.scaninfo.num_frames + self.additional_triggers.get() + num_burst_cycle = ( + self.scaninfo.num_frames + self.additional_triggers.get() + ) # set parameters in DDG - self.burst_enable(num_burst_cycle, delay_burst, total_exposure, config="first") + self.burst_enable( + num_burst_cycle, delay_burst, total_exposure, config="first" + ) self.set_channels("delay", 0.0) # Set burst length to half of the experimental time! self.set_channels("width", exp_time) @@ -540,7 +577,9 @@ class DelayGeneratorDG645(Device): def trigger(self) -> None: # if self.scaninfo.scan_type == "step": - if self.source.read()[self.source.name]["value"] == int(TriggerSource.SINGLE_SHOT): + if self.source.read()[self.source.name]["value"] == int( + TriggerSource.SINGLE_SHOT + ): self.trigger_shot.set(1).wait() super().trigger() diff --git a/ophyd_devices/epics/devices/bec_scaninfo_mixin.py b/ophyd_devices/epics/devices/bec_scaninfo_mixin.py index c5f8b65..179da3d 100644 --- a/ophyd_devices/epics/devices/bec_scaninfo_mixin.py +++ b/ophyd_devices/epics/devices/bec_scaninfo_mixin.py @@ -4,7 +4,9 @@ from bec_lib.core import DeviceManagerBase, BECMessage, MessageEndpoints class BecScaninfoMixin: - def __init__(self, device_manager: DeviceManagerBase = None, sim_mode=False) -> None: + def __init__( + self, device_manager: DeviceManagerBase = None, sim_mode=False + ) -> None: self.device_manager = device_manager self.sim_mode = sim_mode self.scan_msg = None @@ -18,6 +20,7 @@ class BecScaninfoMixin: "readout_time": 2e-3, "scan_type": "fly", "num_lines": 10, + "frames_per_trigger": 1, } def get_bec_info_msg(self) -> None: @@ -54,7 +57,8 @@ class BecScaninfoMixin: self.scan_number = scan_msg.content["info"]["scan_number"] self.exp_time = scan_msg.content["info"]["exp_time"] self.num_frames = ( - scan_msg.content["info"]["num_points"] * scan_msg.content["info"]["frames_per_trigger"] + scan_msg.content["info"]["num_points"] + * scan_msg.content["info"]["frames_per_trigger"] ) self.scan_type = scan_msg.content["info"].get("scan_type", "step") self.readout_time = scan_msg.content["info"]["readout_time"] diff --git a/ophyd_devices/epics/devices/eiger9m_csaxs.py b/ophyd_devices/epics/devices/eiger9m_csaxs.py index c283665..f5a6293 100644 --- a/ophyd_devices/epics/devices/eiger9m_csaxs.py +++ b/ophyd_devices/epics/devices/eiger9m_csaxs.py @@ -126,7 +126,9 @@ class Eiger9mCsaxs(DetectorBase): **kwargs, ) if device_manager is None and not sim_mode: - raise EigerError("Add DeviceManager to initialization or init with sim_mode=True") + raise EigerError( + "Add DeviceManager to initialization or init with sim_mode=True" + ) self.name = name self.wait_for_connection() # Make sure to be connected before talking to PVs @@ -140,7 +142,9 @@ class Eiger9mCsaxs(DetectorBase): # TODO self.filepath = "" self.scaninfo.username = "e21206" - self.service_cfg = {"base_path": f"/sls/X12SA/data/{self.scaninfo.username}/Data10/"} + self.service_cfg = { + "base_path": f"/sls/X12SA/data/{self.scaninfo.username}/Data10/" + } self.filewriter = FileWriterMixin(self.service_cfg) self.reduce_readout = 1e-3 # 3 ms self.triggermode = 0 # 0 : internal, scan must set this if hardware triggered @@ -168,7 +172,9 @@ class Eiger9mCsaxs(DetectorBase): f"Type of new value {type(value)}:{value} does not match old value {type(old_value)}:{old_value}" ) cfg.update({cfg_key: value}) - logger.info(f"Updated std_daq config for key {cfg_key} from {old_value} to {value}") + logger.info( + f"Updated std_daq config for key {cfg_key} from {old_value} to {value}" + ) def _init_standard_daq(self) -> None: self.std_rest_server_url = "http://xbl-daq-29:5000" @@ -180,9 +186,12 @@ class Eiger9mCsaxs(DetectorBase): time.sleep(0.1) timeout = timeout + 0.1 if timeout > 2: - raise EigerError( - f"Std client not in READY state, returns: {self.std_client.get_status()}" - ) + if not self.std_client.get_status()["state"]: + raise EigerError( + f"Std client not in READY state, returns: {self.std_client.get_status()}" + ) + else: + return def _prep_det(self) -> None: self._set_det_threshold() @@ -198,7 +207,9 @@ class Eiger9mCsaxs(DetectorBase): energy = self.cam.beam_energy.read()[self.cam.beam_energy.name]["value"] if setp_energy != energy: self.cam.beam_energy.set(setp_energy) # .wait() - threshold = self.cam.threshold_energy.read()[self.cam.threshold_energy.name]["value"] + threshold = self.cam.threshold_energy.read()[self.cam.threshold_energy.name][ + "value" + ] if not np.isclose(setp_energy / 2, threshold, rtol=0.05): self.cam.threshold_energy.set(setp_energy / 2) # .wait() @@ -225,6 +236,7 @@ class Eiger9mCsaxs(DetectorBase): self.filepath = self.filewriter.compile_full_filename( self.scaninfo.scan_number, "eiger.h5", 1000, 5, True ) + # self._close_file_writer() logger.info(f" std_daq output filepath {self.filepath}") self.std_client.start_writer_async( {"output_file": self.filepath, "n_images": self.scaninfo.num_frames} @@ -259,7 +271,9 @@ class Eiger9mCsaxs(DetectorBase): self.arm_acquisition() logger.info("Waiting for detector to be armed") while True: - det_ctrl = self.cam.detector_state.read()[self.cam.detector_state.name]["value"] + det_ctrl = self.cam.detector_state.read()[self.cam.detector_state.name][ + "value" + ] if det_ctrl == int(DetectorState.RUNNING): break time.sleep(0.005) @@ -286,7 +300,9 @@ class Eiger9mCsaxs(DetectorBase): # Message to BEC state = True - msg = BECMessage.FileMessage(file_path=self.filepath, done=True, successful=state) + msg = BECMessage.FileMessage( + file_path=self.filepath, done=True, successful=state + ) self._producer.set_and_publish( MessageEndpoints.public_file(self.scaninfo.scanID, self.name), msg.dumps(), diff --git a/ophyd_devices/epics/devices/mcs_csaxs.py b/ophyd_devices/epics/devices/mcs_csaxs.py index 9b8bc43..59f1147 100644 --- a/ophyd_devices/epics/devices/mcs_csaxs.py +++ b/ophyd_devices/epics/devices/mcs_csaxs.py @@ -152,7 +152,10 @@ class McsCsaxs(SIS38XX): f"{name}_num_lines": 1, } if mcs_config is not None: - [self.mcs_config.update({f"{name}_{key}": value}) for key, value in mcs_config.items()] + [ + self.mcs_config.update({f"{name}_{key}": value}) + for key, value in mcs_config.items() + ] super().__init__( prefix=prefix, @@ -165,7 +168,9 @@ class McsCsaxs(SIS38XX): ) if device_manager is None and not sim_mode: - raise MCSError("Add DeviceManager to initialization or init with sim_mode=True") + raise MCSError( + "Add DeviceManager to initialization or init with sim_mode=True" + ) self.name = name self._stream_ttl = 1800 @@ -182,7 +187,9 @@ class McsCsaxs(SIS38XX): self.scaninfo = BecScaninfoMixin(device_manager, sim_mode) # TODO self.scaninfo.username = "e21206" - self.service_cfg = {"base_path": f"/sls/X12SA/data/{self.scaninfo.username}/Data10/"} + self.service_cfg = { + "base_path": f"/sls/X12SA/data/{self.scaninfo.username}/Data10/" + } self.filewriter = FileWriterMixin(self.service_cfg) self._stopped = False self._acquisition_done = False @@ -203,8 +210,11 @@ class McsCsaxs(SIS38XX): self.mux_output.set(5) self._set_trigger(TriggerSource.MODE3) self.input_polarity.set(0) + self.output_polarity.set(1) self.count_on_start.set(0) - self.mca_names = [signal for signal in self.component_names if signal.startswith("mca")] + self.mca_names = [ + signal for signal in self.component_names if signal.startswith("mca") + ] self.mca_data = defaultdict(lambda: []) for mca in self.mca_names: signal = getattr(self, mca) @@ -212,24 +222,28 @@ class McsCsaxs(SIS38XX): self._counter = 0 def _on_mca_data(self, *args, obj=None, **kwargs) -> None: - self.mca_data[obj.attr_name] = kwargs["value"] + if not isinstance(kwargs["value"], (list, np.ndarray)): + return + self.mca_data[obj.attr_name] = kwargs["value"][1:] if len(self.mca_names) != len(self.mca_data): return - ref_entry = self.mca_data[self.mca_names[0]] - if not ref_entry: - self.mca_data = defaultdict(lambda: []) - return - if isinstance(ref_entry, list) and not ref_entry: - return + # ref_entry = self.mca_data[self.mca_names[0]] + # if not ref_entry: + # self.mca_data = defaultdict(lambda: []) + # return + # if isinstance(ref_entry, list) and (ref_entry > 0): + # return self._updated = True - self.counter += 1 - if self.counter == self.num_lines.get(): + self._counter += 1 + if self._counter == self.num_lines.get(): self._acquisition_done = True self._send_data_to_bec() self.stop_all.put(1, use_complete=False) self._send_data_to_bec() self.erase_all.set(1) + # TODO how to make card wait for sure! + self._counter = 0 return self.erase_start.set(1) self._send_data_to_bec() @@ -245,6 +259,7 @@ class McsCsaxs(SIS38XX): "num_lines": self.num_lines.get(), } ) + logger.info(f"{self.mca_data}") msg = BECMessage.DeviceMessage( signals=dict(self.mca_data), metadata=self.scaninfo.scan_msg.metadata ).dumps() @@ -262,9 +277,11 @@ class McsCsaxs(SIS38XX): def _set_acquisition_params(self) -> None: n_points = self.scaninfo.num_frames / int(self.num_lines.get()) - if n_points > 10000: - raise MCSError(f"Requested number of points {n_points} exceeds hardware limit of 10000") - self.num_use_all.set(n_points) + if n_points > 9999: + raise MCSError( + f"Requested number of points {n_points+1} exceeds hardware limit of 10000 (n+1)" + ) + self.num_use_all.set(n_points + 1) self.preset_real.set(0) def _set_trigger(self, trigger_source: TriggerSource) -> None: @@ -284,29 +301,6 @@ class McsCsaxs(SIS38XX): def _force_readout_mcs_card(self) -> None: self.read_all.put(1, use_complete=False) - # TODO does not work anymore with new mca signals - # def readout_data(self) -> List[List]: - # """Manual readout of mca slots, returns list of lists""" - # self._force_readout_mcs_card() - # readback = [] - # for ii in range(1, int(self.mux_output.read()[self.mux_output.name]["value"]) + 1): - # readback.append(self._readout_mca_channels(ii)) - # return readback - - # def _readout_mca_channels(self, num: int) -> List: - # """readout of single mca channel""" - # signal = f"mca{num}" - # if signal in self.component_names: - # readback = f"{getattr(self, signal).name}_spectrum" - # return getattr(self, signal).read()[readback]["value"] - - # def _start_readout_loop(self) -> None: - # # stop acquisition and clean up data - # self.stop_all.set(1) - # self.erase_all.set(1) - # self._acquisition_done = True - # self._updated = False - def stage(self) -> List[object]: """stage the detector and file writer""" self.scaninfo.load_scan_metadata() @@ -348,7 +342,7 @@ class McsCsaxs(SIS38XX): Start: start_all Erase/Start: erase_start """ - self.counter = 0 + self._counter = 0 self.erase_start.set(1) # self.start_all.set(1) @@ -360,6 +354,7 @@ class McsCsaxs(SIS38XX): # self.erase_all.set(1) self._stopped = True self._acquisition_done = True + self._counter = 0 super().stop(success=success) @@ -367,3 +362,4 @@ class McsCsaxs(SIS38XX): if __name__ == "__main__": mcs = McsCsaxs(name="mcs", prefix="X12SA-MCS:", sim_mode=True) mcs.stage() + mcs.unstage()