class ImagePlugin(plugins.ImagePlugin, PluginBase): """ Image plugin with a couple of the signals spoofed. To set ImagePlugin to return images using the array_data signal, override the _image method to be a method that returns the desired image """ plugin_type = Component(FakeSignal, value="NDPluginStdArrays") array_data = Component(FakeSignal, value=np.zeros((256, 256))) def __init__(self, prefix, *args, **kwargs): super().__init__(prefix, *args, **kwargs) # Spoof the different components self.array_data._get_readback = lambda: self._image().flatten() self.ndimensions._get_readback = lambda: len(self.array_size.get()) self.array_size.height._get_readback = lambda: self._get_shape()[0] self.array_size.width._get_readback = lambda: self._get_shape()[1] self.array_size.depth._get_readback = lambda: self._get_shape()[2] def _get_shape(self): image_shape = self._image().shape pad_zeros = [0] * (3 - len(image_shape)) return [*image_shape, *pad_zeros] def _image(self): return np.zeros((256, 256))
class SimDevice(Device): """ Class to house components and methods common to all simulated devices. """ sim_x = Component(FakeSignal, value=0) sim_y = Component(FakeSignal, value=0) sim_z = Component(FakeSignal, value=0)
def create_attr(self, attr_name): try: cls, suffix, kwargs = self.defn[attr_name] inst = Component(cls, suffix, **kwargs) except ValueError: cls, kwargs = self.defn[attr_name] inst = Component(cls, **kwargs) inst.attr = attr_name return inst
class EpicsRecordInputFields(Device): """ some fields common to EPICS input records """ input_link = Component(EpicsSignal, ".INP") raw_value = Component(EpicsSignal, ".RVAL") final_value = Component(EpicsSignal, ".VAL") # will ignore simulation mode fields @property def value(self): return self.final_value.value
class StateRecordPositioner(StatePositioner): """ A `StatePositioner` for an EPICS states record. The `states_list` must match the order of the EPICS enum. """ state = Component(EpicsSignal, '', write_pv=':GO') readback = FormattedComponent(EpicsSignalRO, '{self._readback}') _default_read_attrs = ['state', 'readback'] def __init__(self, prefix, *, name, **kwargs): some_state = self.states_list[0] self._readback = '{}:{}_CALC.A'.format(prefix, some_state) super().__init__(prefix, name=name, **kwargs) self._has_subscribed_readback = False def subscribe(self, cb, event_type=None, run=True): """ Subcribe a callback to be run on specific events. See the ``opyhd`` documentation for more information. """ cid = super().subscribe(cb, event_type=event_type, run=run) if (event_type == self.SUB_READBACK and not self._has_subscribed_readback): self.readback.subscribe(self._run_sub_readback, run=False) self._has_subscribed_readback = True return cid def _run_sub_readback(self, *args, **kwargs): kwargs.pop('sub_type') kwargs.pop('obj') self._run_subs(sub_type=self.SUB_READBACK, obj=self, **kwargs)
class PVStatePositioner(StatePositioner): """ A `StatePositioner` that combines a set of PVs into a single state. The user can provide state logic and a move method if desired. %s Attributes ---------- _state_logic: ``dict`` Information dictionaries for each state of the following form: .. code:: { "signal_name": { 0: "OUT", 1: "IN", 2: "Unknown", 3: "defer" } } The dictionary defines the relevant signal names and how to interpret each of the states. These states will be evaluated in the dict's order, which may matter if ``_state_logic_mode == 'FIRST'``. This is for cases where the logic is simple. If there are more complex requirements, replace the `state` component. _state_logic_mode: ``str``, ``'ALL'`` or ``'FIRST'`` This should be ``ALL`` (default) if the pvs need to agree for a valid state. You can set this to 'FIRST' to instead use the first state found while traversing the state_logic tree. This means an earlier state definition can mask a later state definition. """ __doc__ = __doc__ % basic_positioner_init state = Component(PVStateSignal) _state_logic = {} _state_logic_mode = 'ALL' def __init__(self, prefix, *, name, **kwargs): if self._state_logic and not self.states_list: self.states_list = [] for state_mapping in self._state_logic.values(): for state_name in state_mapping.values(): if state_name not in (self._unknown, 'defer'): if state_name not in self.states_list: self.states_list.append(state_name) super().__init__(prefix, name=name, **kwargs) def _do_move(self, state): raise NotImplementedError(('Class must implement a _do_move method or ' 'override the move and set methods'))
class SimDetector(detectors.DetectorBase): """ Generic simulated detector that has image, stats and cam components. """ cam = Component(CamBase, ":") image = Component(ImagePlugin, ":IMAGE:", read_attrs=["array_data"]) stats = Component(StatsPlugin, ":Stats:", read_attrs=['centroid', 'mean_value']) def __init__(self, prefix, read_attrs=None, *args, **kwargs): if read_attrs is None: read_attrs = ["cam", "image", "stats"] super().__init__(prefix, read_attrs=read_attrs, *args, **kwargs) def centroid_x(self): return self.stats.centroid.x.value def centroid_y(self): return self.stats.centroid.y.value @property def centroid(self): return (self.centroid_x, self.centroid_y)
class EpicsRecordOutputFields(Device): """ some fields common to EPICS output records """ output_link = Component(EpicsSignal, ".OUT") raw_value = Component(EpicsSignal, ".RVAL") output_value = Component(EpicsSignal, ".OVAL") readback_value = Component(EpicsSignalRO, ".RBV") desired_output_location = Component(EpicsSignal, ".DOL") output_mode_select = Component(EpicsSignal, ".OMSL") desired_value = Component(EpicsSignal, ".VAL") # will ignore simulation mode fields @property def value(self): return self.desired_value.value
class PulnixDetector(DetectorBase): cam = Component(PulnixCam, ":")
class AsynRecord(EpicsRecordDeviceCommonAll): """ EPICS asyn record support in ophyd :see: https://epics.anl.gov/modules/soft/asyn/R4-36/asynRecord.html :see: https://github.com/epics-modules/asyn :see: https://epics.anl.gov/modules/soft/asyn/ """ ascii_output = Component(EpicsSignal, ".AOUT") binary_output = Component(EpicsSignal, ".BOUT") binary_output_maxlength = Component(EpicsSignal, ".BMAX") end_of_message_reason = Component(EpicsSignalRO, ".EOMR") input_format = Component(EpicsSignalRO, ".IFMT") interface = Component(EpicsSignal, ".IFACE") number_bytes_actually_read = Component(EpicsSignalRO, ".NRRD") number_bytes_actually_written = Component(EpicsSignalRO, ".NAWT") number_bytes_to_read = Component(EpicsSignal, ".NORD") number_bytes_to_write = Component(EpicsSignal, ".NOWT") octet_is_valid = Component(EpicsSignalRO, ".OCTETIV") output_format = Component(EpicsSignalRO, ".OFMT") terminator_input = Component(EpicsSignal, ".IEOS") terminator_output = Component(EpicsSignal, ".OEOS") timeout = Component(EpicsSignal, ".TMOT") transaction_mode = Component(EpicsSignal, ".TMOD") translated_input = Component(EpicsSignal, ".TINP")
class Layer(Device): potato = Component(Device)
class CamBase(cam.CamBase): # Shared among all cams and plugins array_counter = Component(FakeSignal, value=0) array_rate = Component(FakeSignal, value=0) asyn_io = Component(FakeSignal, value=0) nd_attributes_file = Component(FakeSignal, value=0) pool_alloc_buffers = Component(FakeSignal, value=0) pool_free_buffers = Component(FakeSignal, value=0) pool_max_buffers = Component(FakeSignal, value=0) pool_max_mem = Component(FakeSignal, value=0) pool_used_buffers = Component(FakeSignal, value=0) pool_used_mem = Component(FakeSignal, value=0) port_name = Component(FakeSignal, value='CAM') # Cam-specific acquire = Component(FakeSignal, value=0) acquire_period = Component(FakeSignal, value=0) acquire_time = Component(FakeSignal, value=0) array_callbacks = Component(FakeSignal, value=0) array_size = DynamicDeviceComponent( ad_group(FakeSignal, (('array_size_x'), ('array_size_y'), ('array_size_z')), value=0), doc='Size of the array in the XYZ dimensions') array_size_bytes = Component(FakeSignal, value=0) bin_x = Component(FakeSignal, value=0) bin_y = Component(FakeSignal, value=0) color_mode = Component(FakeSignal, value=0) data_type = Component(FakeSignal, value=0) detector_state = Component(FakeSignal, value=0) frame_type = Component(FakeSignal, value=0) gain = Component(FakeSignal, value=0) image_mode = Component(FakeSignal, value=0) manufacturer = Component(FakeSignal, value=0) max_size = DynamicDeviceComponent( ad_group(FakeSignal, (('max_size_x'), ('max_size_y')), value=0), doc='Maximum sensor size in the XY directions') min_x = Component(FakeSignal, value=0) min_y = Component(FakeSignal, value=0) model = Component(FakeSignal, value=0) num_exposures = Component(FakeSignal, value=0) num_exposures_counter = Component(FakeSignal, value=0) num_images = Component(FakeSignal, value=0) num_images_counter = Component(FakeSignal, value=0) read_status = Component(FakeSignal, value=0) reverse = DynamicDeviceComponent( ad_group(FakeSignal, (('reverse_x'), ('reverse_y')), value=0)) shutter_close_delay = Component(FakeSignal, value=0) shutter_close_epics = Component(FakeSignal, value=0) shutter_control = Component(FakeSignal, value=0) shutter_control_epics = Component(FakeSignal, value=0) shutter_fanout = Component(FakeSignal, value=0) shutter_mode = Component(FakeSignal, value=0) shutter_open_delay = Component(FakeSignal, value=0) shutter_open_epics = Component(FakeSignal, value=0) shutter_status_epics = Component(FakeSignal, value=0) shutter_status = Component(FakeSignal, value=0) size = DynamicDeviceComponent( ad_group(FakeSignal, (('size_x'), ('size_y')), value=0)) status_message = Component(FakeSignal, value=0) string_from_server = Component(FakeSignal, value=0) string_to_server = Component(FakeSignal, value=0) temperature = Component(FakeSignal, value=0) temperature_actual = Component(FakeSignal, value=0) time_remaining = Component(FakeSignal, value=0) trigger_mode = Component(FakeSignal, value=0) # Extra resolution = DynamicDeviceComponent( ad_group(FakeSignal, (('resolution_x'), ('resolution_y')), value=0))
class EpicsRecordFloatFields(Device): """ some fields common to EPICS records supporting floating point values """ units = Component(EpicsSignal, ".EGU") precision = Component(EpicsSignal, ".PREC") monitor_deadband = Component(EpicsSignal, ".MDEL") # upper and lower display limits for the VAL, CVAL, HIHI, HIGH, LOW, and LOLO fields high_operating_range = Component(EpicsSignal, ".HOPR") low_operating_range = Component(EpicsSignal, ".LOPR") hihi_alarm_limit = Component(EpicsSignal, ".HIHI") high_alarm_limit = Component(EpicsSignal, ".HIGH") low_alarm_limit = Component(EpicsSignal, ".LOW") lolo_alarm_limit = Component(EpicsSignal, ".LOLO") hihi_alarm_severity = Component(EpicsSignal, ".HHSV") high_alarm_severity = Component(EpicsSignal, ".HSV") low_alarm_severity = Component(EpicsSignal, ".LSV") lolo_alarm_severity = Component(EpicsSignal, ".LLSV") alarm_hysteresis = Component(EpicsSignal, ".HYST")
class EpicsRecordDeviceCommonAll(Device): """ Many of the fields common to all EPICS records Some fields are not included because they are not interesting to an EPICS client or are already provided in other support. """ description = Component(EpicsSignal, ".DESC") processing_active = Component(EpicsSignalRO, ".PACT") scanning_rate = Component(EpicsSignal, ".SCAN") disable_value = Component(EpicsSignal, ".DISV") scan_disable_input_link_value = Component(EpicsSignal, ".DISA") scan_disable_value_input_link = Component(EpicsSignal, ".SDIS") process_record = Component(EpicsSignal, ".PROC") forward_link = Component(EpicsSignal, ".FLNK") trace_processing = Component(EpicsSignal, ".TPRO") device_type = Component(EpicsSignalRO, ".DTYP") alarm_status = Component(EpicsSignalRO, ".STAT") alarm_severity = Component(EpicsSignalRO, ".SEVR") new_alarm_status = Component(EpicsSignalRO, ".NSTA") new_alarm_severity = Component(EpicsSignalRO, ".NSEV") disable_alarm_severity = Component(EpicsSignal, ".DISS")
class NormalDevice(Device): apples = Component(Device) bananas = Component(Device, lazy=True) oranges = Component(Signal) veggies = Component(Layer)
class DetectorBase(detectors.DetectorBase): cam = Component(FakeSignal, ":")
class PluginBase(plugins.PluginBase): """ PluginBase but with components initialized to be empty signals. """ array_counter = Component(FakeSignal, value=0) array_rate = Component(FakeSignal, value=0) asyn_io = Component(FakeSignal, value=0) nd_attributes_file = Component(FakeSignal, value=0) pool_alloc_buffers = Component(FakeSignal, value=0) pool_free_buffers = Component(FakeSignal, value=0) pool_max_buffers = Component(FakeSignal, value=0) pool_max_mem = Component(FakeSignal, value=0) pool_used_buffers = Component(FakeSignal, value=0) pool_used_mem = Component(FakeSignal, value=0) port_name = Component(FakeSignal, value=0) width = Component(FakeSignal, value=0) height = Component(FakeSignal, value=0) depth = Component(FakeSignal, value=0) array_size = DynamicDeviceComponent(ad_group(FakeSignal, (('height'), ('width'), ('depth')), value=0), doc='The array size') bayer_pattern = Component(FakeSignal, value=0) blocking_callbacks = Component(FakeSignal, value=0) color_mode = Component(FakeSignal, value=0) data_type = Component(FakeSignal, value=0) dim0_sa = Component(FakeSignal, value=0) dim1_sa = Component(FakeSignal, value=0) dim2_sa = Component(FakeSignal, value=0) dim_sa = DynamicDeviceComponent(ad_group(FakeSignal, (('dim0'), ('dim1'), ('dim2')), value=0), doc='Dimension sub-arrays') dimensions = Component(FakeSignal, value=0) dropped_arrays = Component(FakeSignal, value=0) enable = Component(FakeSignal, value=0) min_callback_time = Component(FakeSignal, value=0) nd_array_address = Component(FakeSignal, value=0) nd_array_port = Component(FakeSignal, value=0) ndimensions = Component(FakeSignal, value=0) plugin_type = Component(FakeSignal, value="TEST", use_string=True) queue_free = Component(FakeSignal, value=0) queue_free_low = Component(FakeSignal, value=0) queue_size = Component(FakeSignal, value=0) queue_use = Component(FakeSignal, value=0) queue_use_high = Component(FakeSignal, value=0) queue_use_hihi = Component(FakeSignal, value=0) time_stamp = Component(FakeSignal, value=0) unique_id = Component(FakeSignal, value=0)
class busyRecord(Device): state = Component(EpicsSignal, "") output_link = Component(EpicsSignal, ".OUT") forward_link = Component(EpicsSignal, ".FLNK")
class StatsPlugin(plugins.StatsPlugin, PluginBase): """ StatsPlugin but with components instantiated to be empty signals. To override centroid values, patch methods: _get_readback_centroid_x - Centroid x _get_readback_centroid_y - Centroid y This will guarantee that returned centroid will always be an int. """ plugin_type = Component(FakeSignal, value='NDPluginStats') bgd_width = Component(FakeSignal, value=0) centroid_threshold = Component(FakeSignal, value=0) centroid = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='The centroid XY') compute_centroid = Component(FakeSignal, value=0) compute_histogram = Component(FakeSignal, value=0) compute_profiles = Component(FakeSignal, value=0) compute_statistics = Component(FakeSignal, value=0) cursor = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='The cursor XY') hist_entropy = Component(FakeSignal, value=0) hist_max = Component(FakeSignal, value=0) hist_min = Component(FakeSignal, value=0) hist_size = Component(FakeSignal, value=0) histogram = Component(FakeSignal, value=0) max_size = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='The maximum size in XY') max_value = Component(FakeSignal, value=0) max_xy = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Maximum in XY') mean_value = Component(FakeSignal, value=0) min_value = Component(FakeSignal, value=0) min_xy = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Minimum in XY') net = Component(FakeSignal, value=0) profile_average = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Profile average in XY') profile_centroid = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Profile centroid in XY') profile_cursor = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Profile cursor in XY') profile_size = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Profile size in XY') profile_threshold = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Profile threshold in XY') set_xhopr = Component(FakeSignal, value=0) set_yhopr = Component(FakeSignal, value=0) sigma_xy = Component(FakeSignal, value=0) sigma_x = Component(FakeSignal, value=0) sigma_y = Component(FakeSignal, value=0) sigma = Component(FakeSignal, value=0) ts_acquiring = Component(FakeSignal, value=0) ts_centroid = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Time series centroid in XY') ts_control = Component(FakeSignal, value=0) ts_current_point = Component(FakeSignal, value=0) ts_max_value = Component(FakeSignal, value=0) ts_max = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Time series maximum in XY') ts_mean_value = Component(FakeSignal, value=0) ts_min_value = Component(FakeSignal, value=0) ts_min = DynamicDeviceComponent(ad_group(FakeSignal, (('x'), ('y')), value=0), doc='Time series minimum in XY') ts_net = Component(FakeSignal, value=0) ts_num_points = Component(FakeSignal, value=0) ts_read = Component(FakeSignal, value=0) ts_sigma = Component(FakeSignal, value=0) ts_sigma_x = Component(FakeSignal, value=0) ts_sigma_xy = Component(FakeSignal, value=0) ts_sigma_y = Component(FakeSignal, value=0) ts_total = Component(FakeSignal, value=0) total = Component(FakeSignal, value=0) def __init__(self, prefix, *, noise_x=False, noise_y=False, noise_func_x=None, noise_func_y=None, noise_type_x="uni", noise_type_y="uni", noise_args_x=(), noise_args_y=(), noise_kwargs_x={}, noise_kwargs_y={}, **kwargs): super().__init__(prefix, **kwargs) self.noise_x = noise_x self.noise_y = noise_y self.noise_type_x = noise_type_x self.noise_type_y = noise_type_y self.noise_func_x = noise_func_x or ( lambda: self._int_noise_func(self.centroid.x)) self.noise_func_y = noise_func_y or ( lambda: self._int_noise_func(self.centroid.y)) self.noise_args_x = noise_args_x self.noise_args_y = noise_args_y self.noise_kwargs_x = noise_kwargs_x self.noise_kwargs_y = noise_kwargs_y # Override the default centroid calculator to always output ints self.centroid.x._get_readback = lambda **kwargs: int( np.round(self._get_readback_centroid_x())) self.centroid.y._get_readback = lambda **kwargs: int( np.round(self._get_readback_centroid_y())) def _get_readback_centroid_x(self, **kwargs): return self.centroid.x._raw_readback def _get_readback_centroid_y(self, **kwargs): return self.centroid.y._raw_readback def _int_noise_func(self, sig): if sig.noise_type == "uni": sig._check_args_uni() return int(np.round(sig.noise_uni())) elif self.noise_type == "norm": sig._check_args_norm() return int(np.round(sig.noise_norm())) else: raise ValueError("Invalid noise type. Must be 'uni' or 'norm'") @property def noise_x(self): return self.centroid.x.noise @noise_x.setter def noise_x(self, val): self.centroid.x.noise = bool(val) @property def noise_y(self): return self.centroid.y.noise @noise_y.setter def noise_y(self, val): self.centroid.y.noise = bool(val) @property def noise_func_x(self): return self.centroid.x.noise_func() @noise_func_x.setter def noise_func_x(self, val): self.centroid.x.noise_func = val @property def noise_func_y(self): return self.centroid.y.noise_func() @noise_func_y.setter def noise_func_y(self, val): self.centroid.y.noise_func = val @property def noise_type_x(self): return self.centroid.x.noise_type @noise_type_x.setter def noise_type_x(self, val): self.centroid.x.noise_type = val @property def noise_type_y(self): return self.centroid.y.noise_type @noise_type_y.setter def noise_type_y(self, val): self.centroid.y.noise_type = val @property def noise_args_x(self): return self.centroid.x.noise_args @noise_args_x.setter def noise_args_x(self, val): self.centroid.x.noise_args = val @property def noise_args_y(self): return self.centroid.y.noise_args @noise_args_y.setter def noise_args_y(self, val): self.centroid.y.noise_args = val @property def noise_kwargs_x(self): return self.centroid.x.noise_kwargs @noise_kwargs_x.setter def noise_kwargs_x(self, val): self.centroid.x.noise_kwargs = val @property def noise_kwargs_y(self): return self.centroid.y.noise_kwargs @noise_kwargs_y.setter def noise_kwargs_y(self, val): self.centroid.y.noise_kwargs = val
class EpidRecord(EpicsRecordFloatFields, EpicsRecordDeviceCommonAll): """ EPICS epid record support in ophyd :see: https://epics.anl.gov/bcda/synApps/std/epidRecord.html """ controlled_value_link = Component(EpicsSignal, ".INP") controlled_value = Component(EpicsSignalRO, ".CVAL") readback_trigger_link = Component(EpicsSignal, ".TRIG") readback_trigger_link_value = Component(EpicsSignal, ".TVAL") setpoint_location = Component(EpicsSignal, ".STPL") setpoint_mode_select = Component(EpicsSignal, ".SMSL") output_location = Component(EpicsSignal, ".OUTL") feedback_on = Component(EpicsSignal, ".FBON") proportional_gain = Component(EpicsSignal, ".KP") integral_gain = Component(EpicsSignal, ".KI") derivative_gain = Component(EpicsSignal, ".KD") following_error = Component(EpicsSignalRO, ".ERR") output_value = Component(EpicsSignalRO, ".OVAL") calculated_P = Component(EpicsSignalRO, ".P") calculated_I = Component(EpicsSignal, ".I") calculated_D = Component(EpicsSignalRO, ".D") clock_ticks = Component(EpicsSignalRO, ".CT") time_difference = Component(EpicsSignal, ".DT") minimum_delta_time = Component(EpicsSignal, ".MDT") # limits imposed by the record support: # .LOPR <= .OVAL <= .HOPR # .LOPR <= .I <= .HOPR high_limit = Component(EpicsSignal, ".DRVH") low_limit = Component(EpicsSignal, ".DRVL") @property def value(self): return self.output_value.value