diff --git a/src/cditools/screens.py b/src/cditools/screens.py index 7fcd69b..58706e4 100644 --- a/src/cditools/screens.py +++ b/src/cditools/screens.py @@ -1,13 +1,14 @@ from __future__ import annotations +from collections import OrderedDict import numpy as np +import time as ttime from ophyd import ( CamBase, DerivedSignal, Device, EpicsMotor, EpicsSignal, - ImagePlugin, ProsilicaDetector, ProsilicaDetectorCam, ROIPlugin, @@ -19,8 +20,10 @@ ROIStatPlugin_V35, StatsPlugin, TransformPlugin, + HDF5Plugin_V34 as HDF5Plugin ) from ophyd.areadetector.trigger_mixins import SingleTrigger +from ophyd.areadetector.filestore_mixins import FileStoreHDF5IterativeWrite from ophyd.device import DynamicDeviceComponent @@ -48,7 +51,53 @@ def set_from_epics(self): cpt.item.name = f"{root.name}_{epics_name}_{cpt.dotted_name}" +class HDF5PluginWithFileStore(HDF5Plugin, FileStoreHDF5IterativeWrite): + """Add this as a component to detectors that write HDF5s.""" + + def warmup(self): + """ + This is vendored from ophyd (https://github.com/bluesky/ophyd/blob/master/ophyd/areadetector/plugins.py) + to fix the non-existent "Internal" trigger mode that is hard-coded there: + + In [13]: cam6.stage() + An exception has occurred, use '%tb verbose' to see the full traceback. + UnprimedPlugin: The plugin hdf5 on the area detector with name cam6 has not been primed. + + See /home/xf08bm/bluesky-files/log/bluesky/bluesky.log for the full traceback. + + In [14]: cam6.hdf5.warmup() + An exception has occurred, use '%tb verbose' to see the full traceback. + ValueError: invalid literal for int() with base 0: b'Internal' + + See /home/xf08bm/bluesky-files/log/bluesky/bluesky.log for the full traceback. + """ + self.enable.set(1).wait() + sigs = OrderedDict([(self.parent.cam.array_callbacks, 1), + (self.parent.cam.image_mode, 'Single'), + (self.parent.cam.trigger_mode, 'Fixed Rate'), # updated here "Internal" -> "Fixed Rate" + # just in case tha acquisition time is set very long... + (self.parent.cam.acquire_time, 1), + (self.parent.cam.acquire_period, 1), + (self.parent.cam.acquire, 1)]) + + original_vals = {sig: sig.get() for sig in sigs} + + for sig, val in sigs.items(): + ttime.sleep(0.1) # abundance of caution + sig.set(val).wait() + + ttime.sleep(2) # wait for acquisition + + for sig, val in reversed(list(original_vals.items())): + ttime.sleep(0.1) + sig.set(val).wait() + + class ProsilicaCamBase(ProsilicaDetector): + hdf5 = Cpt(HDF5PluginWithFileStore, + suffix="HDF1:", + write_path_template="/tmp", + root="/nsls2/data/cdi/proposals") wait_for_plugins = Cpt(EpicsSignal, "WaitForPlugins", string=True, kind="hinted") cam = Cpt(ProsilicaDetectorCam, "cam1:") stats1 = Cpt(StatsPlugin, "Stats1:") @@ -75,6 +124,18 @@ def default_plugin_graph( ) -> dict[PluginBase, CamBase | PluginBase] | None: return self._default_plugin_graph + @property + def root_path_str(self): + return f"{self.hdf5.root}/{self._md['cycle']}/{self._md['data_session']}/assets/{self._asset_path}" + + @property + def _asset_path(self): + # cam names are 'cam_{hutch}{num}' + # need to split, slice off hutch, and join on a dash for directory + base, num = self.name.split("_")[0:2] + num = num[1:] + return f"{base}-{num}" + def _stage_plugin_graph(self, plugin_graph: dict[PluginBase, CamBase | PluginBase]): for target, source in plugin_graph.items(): self.stage_sigs[target.nd_array_port] = source.port_name.get() @@ -104,10 +165,6 @@ def __init__(self, *args, **kwargs): self.roistat1: self.cam, } - def stage(self): - return super().stage() - - class ScreenState(DerivedSignal): def __init__(self, *args, in_position=0.0, out_position=25.0, **kwargs): super().__init__(*args, **kwargs)