refactor: refacoring of falcon sitoro

This commit is contained in:
appel_c 2023-11-09 10:59:35 +01:00
parent 38db08c877
commit 97b6111832

View File

@ -7,13 +7,13 @@ from bec_lib.core.devicemanager import DeviceStatus
from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV, Component as Cpt from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV, Component as Cpt
from ophyd.mca import EpicsMCARecord from ophyd.mca import EpicsMCARecord
from ophyd import DetectorBase, Device from ophyd import Device
from ophyd import ADComponent as ADCpt
from bec_lib.core.file_utils import FileWriterMixin from bec_lib.core.file_utils import FileWriterMixin
from bec_lib.core import MessageEndpoints, BECMessage from bec_lib.core import MessageEndpoints, BECMessage
from bec_lib.core import bec_logger from bec_lib.core import bec_logger
from bec_lib.core.bec_service import SERVICE_CONFIG
from ophyd_devices.epics.devices.bec_scaninfo_mixin import BecScaninfoMixin from ophyd_devices.epics.devices.bec_scaninfo_mixin import BecScaninfoMixin
from ophyd_devices.utils import bec_utils from ophyd_devices.utils import bec_utils
@ -42,13 +42,34 @@ class DeviceClassInitError(FalconError):
pass pass
class DetectorState(int, enum.Enum): class DetectorState(enum.IntEnum):
"""Detector states for Falcon detector""" """Detector states for Falcon detector"""
DONE = 0 DONE = 0
ACQUIRING = 1 ACQUIRING = 1
class TriggerSource(enum.IntEnum):
"""Trigger source for Falcon detector
Translates setttings for PV:pixel_advance_mode
"""
USER = 0
GATE = 1
SYNC = 2
class MappingSource(enum.IntEnum):
"""Mapping source for Falcon detector
Translates setttings for PV:collect_mode
"""
SPECTRUM = 0
MAPPING = 1
class EpicsDXPFalcon(Device): class EpicsDXPFalcon(Device):
"""DXP parameters for Falcon detector """DXP parameters for Falcon detector
@ -221,16 +242,35 @@ class FalconCsaxs(Device):
def _default_parameter(self) -> None: def _default_parameter(self) -> None:
"""Set default parameters for Falcon """Set default parameters for Falcon
readout (float): readout time in seconds readout (float): readout time in seconds
_value_pixel_per_buffer (int): number of spectra in buffer of Falcon Sitoro""" _value_pixel_per_buffer (int): number of spectra in buffer of Falcon Sitoro
self.readout = 1e-3 """
self._value_pixel_per_buffer = 20 # 16 self._value_pixel_per_buffer = 20
self._stop_det() self._update_readout_time()
self._stop_file_writer()
def _update_readout_time(self) -> None:
readout_time = (
self.scaninfo.readout_time
if hasattr(self.scaninfo, "readout_time")
else self.readout_time_min
)
self.readout_time = max(readout_time, self.readout_time_min)
def _stop_det(self) -> None: def _stop_det(self) -> None:
""" "Stop detector""" """ "Stop detector"""
self.stop_all.put(1) self.stop_all.put(1)
self.erase_all.put(1) self.erase_all.put(1)
det_ctrl = self.state.read()[self.state.name]["value"]
timer = 0
while True:
det_ctrl = self.state.read()[self.state.name]["value"]
if det_ctrl == DetectorState.DONE:
break
if self._stopped == True:
break
time.sleep(0.01)
timer += 0.01
if timer > 5:
raise FalconTimeoutError("Failed to stop the detector. IOC did not update.")
def _stop_file_writer(self) -> None: def _stop_file_writer(self) -> None:
""" "Stop the file writer""" """ "Stop the file writer"""
@ -240,13 +280,16 @@ class FalconCsaxs(Device):
"""Initialize file writer for Falcon. """Initialize file writer for Falcon.
This includes setting variables for the HDF5 plugin (EPICS) that is used to write the data. This includes setting variables for the HDF5 plugin (EPICS) that is used to write the data.
""" """
self.hdf5.enable.put(1) # EnableCallbacks self.hdf5.enable.put(1)
self.hdf5.xml_file_name.put("layout.xml") # Points to hardcopy of HDF5 Layout xml file # file location of h5 layout for cSAXS
self.hdf5.lazy_open.put( self.hdf5.xml_file_name.put("layout.xml")
1 # Potentially not needed, means a temp data file is created first, could be 0
) # Potentially not needed, means a temp data file is created first, could be 0 self.hdf5.lazy_open.put(1)
self.hdf5.temp_suffix.put("") # -> To be checked how to add FilePlugin_V22+ self.hdf5.temp_suffix.put("")
self.hdf5.queue_size.put(2000) # size of queue for spectra in the buffer # size of queue for number of spectra allowed in the buffer, if too small at high throughput, data is lost
self.hdf5.queue_size.put(2000)
# Segmentation into Spectra within EPICS, 1 is activate, 0 is deactivate
self.nd_array_mode.put(1)
def _init_detector(self) -> None: def _init_detector(self) -> None:
"""Initialize Falcon detector. """Initialize Falcon detector.
@ -254,14 +297,33 @@ class FalconCsaxs(Device):
Parameters here affect the triggering, gating etc. Parameters here affect the triggering, gating etc.
This includes also the readout chunk size and whether data is segmented into spectra in EPICS. This includes also the readout chunk size and whether data is segmented into spectra in EPICS.
""" """
self.collect_mode.put(1) # 1 MCA Mapping, 0 MCA Spectrum self._stop_det()
self._stop_file_writer()
self._set_trigger(
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
)
self.preset_mode.put(1) # 1 Realtime self.preset_mode.put(1) # 1 Realtime
self.input_logic_polarity.put(0) # 0 Normal, 1 Inverted self.input_logic_polarity.put(0) # 0 Normal, 1 Inverted
self.pixel_advance_mode.put(1) # 0 User, 1 Gate, 2 Sync
self.ignore_gate.put(0) # 1 Yes, 0 No
self.auto_pixels_per_buffer.put(0) # 0 Manual 1 Auto self.auto_pixels_per_buffer.put(0) # 0 Manual 1 Auto
self.pixels_per_buffer.put(self._value_pixel_per_buffer) # self.pixels_per_buffer.put(self._value_pixel_per_buffer) #
self.nd_array_mode.put(1) # Segmentation happens in EPICS
def _set_trigger(
self, mapping_mode: MappingSource, trigger_source: TriggerSource, ignore_gate: int = 0
) -> None:
"""
Set triggering mode for detector
Args:
mapping_mode (MappingSource): Mapping mode for the detector
trigger_source (TriggerSource): Trigger source for the detector, pixel_advance_signal
ignore_gate (int): Ignore gate from TTL signal; defaults to 0
"""
mapping = int(mapping_mode)
trigger = trigger_source
self.collect_mode.put(mapping)
self.pixel_advance_mode.put(trigger)
self.ignore_gate.put(ignore_gate)
def stage(self) -> List[object]: def stage(self) -> List[object]:
"""Stage command, called from BEC in preparation of a scan. """Stage command, called from BEC in preparation of a scan.
@ -280,24 +342,26 @@ class FalconCsaxs(Device):
""" """
self._stopped = False self._stopped = False
self.scaninfo.load_scan_metadata() self.scaninfo.load_scan_metadata()
self.mokev = self.device_manager.devices.mokev.obj.read()[
self.device_manager.devices.mokev.name
]["value"]
self._prep_file_writer() self._prep_file_writer()
self._prep_det() self._prep_det()
state = False state = False
self._publish_file_location(done=state, successful=state) self._publish_file_location(done=state)
self._arm_acquisition() self._arm_acquisition()
return super().stage() return super().stage()
def _prep_det(self) -> None: def _prep_det(self) -> None:
"""Prepare detector for acquisition""" """Prepare detector for acquisition"""
self.collect_mode.put(1) self._set_trigger(
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
)
self.preset_real.put(self.scaninfo.exp_time) self.preset_real.put(self.scaninfo.exp_time)
self.pixels_per_run.put(int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger)) self.pixels_per_run.put(int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger))
def _prep_file_writer(self) -> None: def _prep_file_writer(self) -> None:
"""Prepare filewriting from HDF5 plugin""" """Prepare filewriting from HDF5 plugin
#TODO check these settings together with Controls put vs set
"""
self.filepath = self.filewriter.compile_full_filename( self.filepath = self.filewriter.compile_full_filename(
self.scaninfo.scan_number, f"{self.name}.h5", 1000, 5, True self.scaninfo.scan_number, f"{self.name}.h5", 1000, 5, True
) )
@ -307,33 +371,50 @@ class FalconCsaxs(Device):
self.hdf5.file_template.put(f"%s%s") self.hdf5.file_template.put(f"%s%s")
self.hdf5.num_capture.put(int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger)) self.hdf5.num_capture.put(int(self.scaninfo.num_points * self.scaninfo.frames_per_trigger))
self.hdf5.file_write_mode.put(2) self.hdf5.file_write_mode.put(2)
# Reset spectrum counter in filewriter, used for indexing & identifying missing triggers
self.hdf5.array_counter.put(0) self.hdf5.array_counter.put(0)
# Start file writing
self.hdf5.capture.put(1) self.hdf5.capture.put(1)
def _publish_file_location(self, done=False, successful=False) -> None: def _publish_file_location(self, done: bool = False, successful: bool = False) -> None:
"""Publish the filepath to REDIS """Publish the filepath to REDIS.
First msg for file writer and the second one for other listeners (e.g. radial integ) We publish two events here:
- file_event: event for the filewriter
- public_file: event for any secondary service (e.g. radial integ code)
Args:
done (bool): True if scan is finished
successful (bool): True if scan was successful
""" """
pipe = self._producer.pipeline() pipe = self._producer.pipeline()
msg = BECMessage.FileMessage(file_path=self.filepath, done=done, successful=successful) if successful is None:
msg = BECMessage.FileMessage(file_path=self.filepath, done=done)
else:
msg = BECMessage.FileMessage(file_path=self.filepath, done=done, successful=successful)
self._producer.set_and_publish( self._producer.set_and_publish(
MessageEndpoints.public_file(self.scaninfo.scanID, self.name), msg.dumps(), pipe=pipe MessageEndpoints.public_file(self.scaninfo.scanID, self.name), msg.dumps(), pipe=pipe
) )
self._producer.set_and_publish( self._producer.set_and_publish(
MessageEndpoints.file_event(self.name), msg.dumps(), pip=pipe MessageEndpoints.file_event(self.name), msg.dumps(), pipe=pipe
) )
pipe.execute() pipe.execute()
def _arm_acquisition(self) -> None: def _arm_acquisition(self) -> None:
"""Arm Falcon detector for acquisition""" """Arm Falcon detector for acquisition"""
timer = 0
self.start_all.put(1) self.start_all.put(1)
while True: while True:
det_ctrl = self.state.read()[self.state.name]["value"] det_ctrl = self.state.read()[self.state.name]["value"]
if det_ctrl == int(DetectorState.ACQUIRING): if det_ctrl == DetectorState.ACQUIRING:
break break
if self._stopped == True: if self._stopped == True:
break break
time.sleep(0.005) time.sleep(0.01)
timer += 0.01
if timer > 5:
self.stop()
raise FalconTimeoutError("Failed to arm the acquisition. IOC did not update.")
# TODO function for abstract class? # TODO function for abstract class?
def trigger(self) -> DeviceStatus: def trigger(self) -> DeviceStatus:
@ -343,7 +424,10 @@ class FalconCsaxs(Device):
# TODO function for abstract class? # TODO function for abstract class?
def _on_trigger(self): def _on_trigger(self):
"""Specify action that should be taken upon trigger signal.""" """Specify action that should be taken upon trigger signal.
At cSAXS with DDGs triggering the devices, we do nothing upon the trigger signal
"""
pass pass
def unstage(self) -> List[object]: def unstage(self) -> List[object]:
@ -397,6 +481,8 @@ class FalconCsaxs(Device):
time.sleep(sleep_time) time.sleep(sleep_time)
timer += sleep_time timer += sleep_time
if timer > timeout: if timer > timeout:
# self._stop_det()
# self._stop_file_writer()
logger.info( logger.info(
f"Falcon missed a trigger: received trigger {received_frames}, send data {written_frames} from total_frames {total_frames}" f"Falcon missed a trigger: received trigger {received_frames}, send data {written_frames} from total_frames {total_frames}"
) )