def test_standalone_signals(): # A object's kind only matters when we read its _parent_. It affects # whether its parent recursively calls read() and/or read_configuration(). # When we call read() and/or read_configuration() on the object itself, # directly, and its 'kind' setting does not affects its behavior. normal_sig = Signal(name='normal_sig', value=3, kind=Kind.normal) assert normal_sig.kind == Kind.normal assert normal_sig.name not in normal_sig.hints['fields'] assert 'normal_sig' in normal_sig.read() assert 'normal_sig' in normal_sig.read_configuration() hinted_sig = Signal(name='hinted_sig', value=3) assert hinted_sig.kind == Kind.hinted assert hinted_sig.name in hinted_sig.hints['fields'] assert 'hinted_sig' in hinted_sig.read() assert 'hinted_sig' in hinted_sig.read_configuration() # Same with a sig set up this way config_sig = Signal(name='config_sig', value=5, kind=Kind.config) assert config_sig.kind == Kind.config assert config_sig.name not in config_sig.hints['fields'] assert 'config_sig' in config_sig.read() assert 'config_sig' in config_sig.read_configuration() # Same with a sig set up this way omitted_sig = Signal(name='omitted_sig', value=5, kind=Kind.omitted) assert omitted_sig.kind == Kind.omitted assert omitted_sig.name not in omitted_sig.hints['fields'] assert 'omitted_sig' in omitted_sig.read() assert 'omitted_sig' in omitted_sig.read_configuration()
def test_signal_connection(qapp): # Create a signal and attach our listener sig = Signal(name='my_signal', value=1) register_signal(sig) widget = WritableWidget() listener = widget.channels()[0] sig_conn = SignalConnection(listener, 'my_signal') sig_conn.add_listener(listener) # Check that our widget receives the initial value qapp.processEvents() assert widget._write_access assert widget._connected assert widget.value == 1 # Check that we can push values back to the signal which in turn causes the # internal value at the widget to update widget.send_value_signal[int].emit(2) qapp.processEvents() qapp.processEvents() # Must be called twice. Multiple rounds of signals assert sig.get() == 2 assert widget.value == 2 # Try changing types qapp.processEvents() qapp.processEvents() # Must be called twice. Multiple rounds of signals sig_conn.remove_listener(listener) # Check that our signal is disconnected completely and maintains the same # value as the signal updates in the background sig.put(3) qapp.processEvents() assert widget.value == 2 widget.send_value_signal.emit(1) qapp.processEvents() assert sig.get() == 3
def test_array_signal_send_value(qapp, qtbot): sig = Signal(name='my_array', value=np.ones(4)) register_signal(sig) widget = PyDMLineEdit() qtbot.addWidget(widget) widget.channel = 'sig://my_array' qapp.processEvents() assert all(widget.value == np.ones(4))
def test_array_signal_put_value(qapp, qtbot): sig = Signal(name='my_array_write', value=np.ones(4)) register_signal(sig) widget = PyDMLineEdit() qtbot.addWidget(widget) widget.channel = 'sig://my_array_write' widget.send_value_signal[np.ndarray].emit(np.zeros(4)) qapp.processEvents() assert all(sig.value == np.zeros(4))
def test_evil_table_names(RE): from ophyd import Signal sigs = [ Signal(value=0, name="a:b"), Signal(value=0, name="a,b"), Signal(value=0, name="a'b"), Signal(value=0, name="🐍"), ] table = LiveTable([s.name for s in sigs], min_width=5, extra_pad=2, separator_lines=False) with _print_redirect() as fout: print() # get a blank line in camptured output RE(bpp.subs_wrapper(bp.count(sigs, num=2), table)) reference = """ +------------+--------------+--------+--------+--------+--------+ | seq_num | time | a:b | a,b | a'b | 🐍 | +------------+--------------+--------+--------+--------+--------+ | 1 | 12:47:09.7 | 0 | 0 | 0 | 0 | | 2 | 12:47:09.7 | 0 | 0 | 0 | 0 | +------------+--------------+--------+--------+--------+--------+""" _compare_tables(fout, reference)
def test_plot_ints(RE): from ophyd import Signal from bluesky.callbacks.best_effort import BestEffortCallback from bluesky.plans import count import bluesky.plan_stubs as bps bec = BestEffortCallback() RE.subscribe(bec) s = Signal(name='s') RE(bps.mov(s, int(0))) assert s.describe()['s']['dtype'] == 'integer' s.kind = 'hinted' with pytest.warns(None) as record: RE(count([s], num=35)) assert len(record) == 0
def mono_vernier_scan( *, ev_bounds=None, urad_bounds=None, duration, fake_mono=False, fake_vernier=False, fake_daq=False, ): """ WARNING: this scan CANNOT be safely inspected! Only pass into RE! Parameters ---------- ev_bounds: list of numbers, keyword-only Upper and lower bounds of the scan in ev. Provide either ev_bounds or urad_bounds, but not both. urad_bounds: list of numbers, keyword-only Upper and lower bounds of the scan in mono grating urad. Provide either ev_bounds or urad_bounds, but not both. duration: number, required keyword-only Duration of the scan in seconds. """ setup_scan_devices() if fake_mono: mono_grating = FixSlowMotor(name='fake_mono') else: mono_grating = scan_devices.mono_grating if fake_vernier: vernier = Signal(name='fake_vernier') else: vernier = scan_devices.energy_req if fake_daq: inner_scan = interpolation_mono_vernier_duration_scan else: inner_scan = daq_interpolation_mono_vernier_duration_scan return (yield from inner_scan( mono_grating, vernier, scan_devices.config, ev_bounds=ev_bounds, urad_bounds=urad_bounds, duration=duration ) )
def test_signal_connection(qapp, qtbot): # Create a signal and attach our listener sig = Signal(name='my_signal', value=1) register_signal(sig) widget = PyDMLineEdit() qtbot.addWidget(widget) widget.channel = 'sig://my_signal' listener = widget.channels()[0] # If PyDMChannel can not connect, we need to connect it ourselves # In PyDM > 1.5.0 this will not be neccesary as the widget will be # connected after we set the channel name if not hasattr(listener, 'connect'): pydm.utilities.establish_widget_connections(widget) # Check that our widget receives the initial value qapp.processEvents() assert widget._write_access assert widget._connected assert widget.value == 1 # Check that we can push values back to the signal which in turn causes the # internal value at the widget to update widget.send_value_signal[int].emit(2) qapp.processEvents() qapp.processEvents() # Must be called twice. Multiple rounds of signals assert sig.get() == 2 assert widget.value == 2 # Try changing types qapp.processEvents() qapp.processEvents() # Must be called twice. Multiple rounds of signals # In PyDM > 1.5.0 we will not need the application to disconnect the # widget, but until then we have to check for the attribute if hasattr(listener, 'disconnect'): listener.disconnect() else: qapp.close_widget_connections(widget) # Check that our signal is disconnected completely and maintains the same # value as the signal updates in the background sig.put(3) qapp.processEvents() assert widget.value == 2 widget.send_value_signal.emit(1) qapp.processEvents() assert sig.get() == 3
def documentation_run(words, md=None, stream=None): """ Save text as a bluesky run. """ text = Signal(value=words, name="text") stream = stream or "primary" _md = dict( purpose=f"save text as bluesky run", plan_name="documentation_run", ) _md.update(md or {}) bec.disable_plots() bec.disable_table() uid = yield from bps.open_run(md=_md) yield from bps.create(stream) yield from bps.read(text) yield from bps.save() yield from bps.close_run() bec.enable_table() bec.enable_plots() return uid
def gisaxs_scan(dets=[pil300KW, pil1M], trajectory='None', measurement_time=1, number_images=1, user_name='GF', md=None): #Pull out the motor names from a cycler #ToDo: This can be improved motor_names = [] for trajs in trajectory: for traj in trajs.items(): if traj[0].name not in motor_names: motor_names.append(traj[0].name) # Check if what is planned is doable try: motor_names [det for det in dets] except: raise Exception('Motors or detectors not known') # Fixed values. If this values change over a scan, possibility to transform in ophyd signal and record over a scan base_md = { 'plan_name': 'gi_scan', 'geometry': 'reflection', 'detectors': [det.name for det in dets], 'user_name': user_name, 'motor_scanned': motor_names, 'exposure_time': measurement_time, 'number_image': number_images, } base_md.update(md or {}) all_detectors = dets # Start a list off all the detector to trigger all_detectors.append(xbpm2) # add all the values to track for analysis all_detectors.append(xbpm3) # add all the values to track for analysis all_detectors.append( ring.current) # add all the values to track for analysis sd.baseline = [] # Initializatin of the baseline SMI.update_md() # update metadata from the beamline # Update metadata for the detectors if 'pil300KW' in [det.name for det in dets]: all_detectors.append( waxs) # Record the position of the WAXS arc and beamstop sd.baseline.append(smi_waxs_detector ) # Load metadata of WAXS detector in the baseline if 'pil1M' in [det.name for det in dets]: SMI_SAXS_Det() # Update metadata from the beamline # Nothing added as detector so far since the saxs detector is not moved but anything could be implemented sd.baseline.append(smi_saxs_detector ) # Load metadata of SAXS detector in the baseline if 'rayonix' in [det.name for det in dets]: print('no metadata for the rayonix yet') # Update metadata for motors not used in baseline and add the motor as detector if so all_detectors.append( piezo) if 'piezo' in motor_names else sd.baseline.append(piezo) all_detectors.append( stage) if 'stage' in motor_names else sd.baseline.append(stage) all_detectors.append(prs) if 'prs' in motor_names else sd.baseline.append( prs) all_detectors.append( energy) if 'energy' in motor_names else sd.baseline.append(energy) all_detectors.append( waxs) if 'waxs' in motor_names else sd.baseline.append(waxs) all_detectors.append(ls) if 'ls' in motor_names else sd.baseline.append(ls) ''' if 'piezo' in [motor_names]: all_detectors.append(piezo) else: sd.baseline.append(piezo) if 'stage' in [motor_names]: all_detectors.append(stage) else: sd.baseline.append(stage) if 'prs' in [motor_names]: all_detectors.append(prs) else: sd.baseline.append(prs) if 'energy' in [motor_names]: all_detectors.append(energy) else: sd.baseline.append(energy) if 'waxs' in [motor_names]: all_detectors.append(waxs) else: sd.baseline.append(waxs) ''' sample_na = Signal(name='sample_name', value='test') all_detectors.append(sample_na) #Set exposure time det_exposure_time(measurement_time, number_images * measurement_time) bec.disable_plots() yield from bp.scan_nd(all_detectors, trajectory, md=base_md) bec.enable_plots() print('GISAXS scan with metadata done')
def scan(dets=[pil300KW, pil1M], trajectory='None', measurement_time=1, number_images=1, user_name='GF', plan_name='gisaxs_scan', md=None): ''' Read a trajectory of motors and acquire data at the each position of the trajectory. Data will be save as a databroker document. :param dets: list of SMI detector which will acquire data: pil300KW, pil1M, rayonix :param trajectory: a cycle trjactory of motor and ophyd signal :param measurement_time: (integer) exposure time for 1 image :param number_images: (integer) number of images :param user_name: (string) user name :param plan_name: (string) plan name that will be used for the analysis :param md: extra metadata if needed ''' #Pull out the motor names from a cycler motor_names = [] for trajs in trajectory: for traj in trajs.items(): if traj[0].name not in motor_names: motor_names.append(traj[0].name) # Check if what is planned is doable try: motor_names [det for det in dets] except: raise Exception('Motors or detectors not known') # Fixed values. If this values change over a scan, possibility to transform in ophyd signal and record over a scan geometry = 'reflection' if 'gisaxs' in plan_name or 'giwaxs' in plan_name else 'transmission' base_md = { 'plan_name': plan_name, 'geometry': geometry, 'detectors': [det.name for det in dets], 'user_name': user_name, 'motor_scanned': motor_names, 'exposure_time': measurement_time, 'number_image': number_images, } base_md.update(md or {}) all_detectors = dets # Start a list off all the detector to trigger all_detectors.append(xbpm2) # add all the values to track for analysis all_detectors.append(xbpm3) # add all the values to track for analysis all_detectors.append( ring.current) # add all the values to track for analysis sd.baseline = [] # Initializatin of the baseline SMI.update_md() # update metadata from the beamline # Update metadata for the detectors if 'pil300KW' in [det.name for det in dets]: all_detectors.append( waxs) # Record the position of the WAXS arc and beamstop sd.baseline.append(smi_waxs_detector ) # Load metadata of WAXS detector in the baseline if 'pil1M' in [det.name for det in dets]: SMI_SAXS_Det() # Update metadata from the beamline sd.baseline.append(smi_saxs_detector ) # Load metadata of SAXS detector in the baseline if 'rayonix' in [det.name for det in dets]: print('no metadata for the rayonix yet') # Update metadata for motors not used in baseline and add the motor as detector if so all_detectors.append( piezo) if 'piezo' in motor_names else sd.baseline.append(piezo) all_detectors.append( stage) if 'stage' in motor_names else sd.baseline.append(stage) all_detectors.append(prs) if 'prs' in motor_names else sd.baseline.append( prs) all_detectors.append( energy) if 'energy' in motor_names else sd.baseline.append(energy) all_detectors.append( waxs) if 'waxs' in motor_names else sd.baseline.append(waxs) all_detectors.append(ls) if 'ls' in motor_names else sd.baseline.append(ls) #That is not needed because sample name should be in the motor list sample_na = Signal(name='sample_name', value='test') all_detectors.append(sample_na) #Set exposure time det_exposure_time(measurement_time, number_images * measurement_time) bec.disable_plots() yield from bp.scan_nd(all_detectors, trajectory, md=base_md) bec.enable_plots() print('Scan with metadata done')
def test_set_signal_to_None(): s = Signal(value='0', name='bob') set_and_wait(s, None, timeout=1)
# resume 100s after current > 10 mA logger.info("Installing suspender for low APS current.") suspend_APS_current = bluesky.suspenders.SuspendFloor(aps.current, 2, resume_thresh=10, sleep=100) RE.install_suspender(suspend_APS_current) # remove comment if likely to use this suspender (issue #170) # suspend_FE_shutter = bluesky.suspenders.SuspendFloor(FE_shutter.pss_state, 1) # RE.install_suspender(suspend_FE_shutter) logger.info(f"mono shutter connected = {mono_shutter.pss_state.connected}") # remove comment if likely to use this suspender (issue #170) # suspend_mono_shutter = bluesky.suspenders.SuspendFloor(mono_shutter.pss_state, 1) logger.info( "Defining suspend_BeamInHutch. Install/remove in scan plans as desired." ) suspend_BeamInHutch = bluesky.suspenders.SuspendBoolLow(BeamInHutch) # be more judicious when to use this suspender (only within scan plans) -- see #180 # RE.install_suspender(suspend_BeamInHutch) # logger.info("BeamInHutch suspender installed") else: # simulators _simulated_beam_in_hutch = Signal(name="_simulated_beam_in_hutch") suspend_BeamInHutch = bluesky.suspenders.SuspendBoolHigh( _simulated_beam_in_hutch) # RE.install_suspender(suspend_BeamInHutch)
# blow up on inverted values assert start < stop, (f"start ({start}) must be smaller than " f"stop ({stop}) for {k}") limits = motor.limits if any(not (limits[0] < v < limits[1]) for v in (start, stop)): raise LimitError(f"your requested {k} values are out of limits for " "the motor " f"{limits[0]} < ({start}, {stop}) < {limits[1]}") def _get_v_with_dflt(sig, dflt): ret = yield from bps.read(sig) return ret[sig.name]["value"] if ret is not None else dflt x_centers = Signal(value=[], name="x_centers", kind="normal") x_centers.tolerance = 1e-15 y_centers = Signal(value=[], name="y_centers", kind="normal") y_centers.tolerance = 1e-15 z_centers = Signal(value=[], name="z_centers", kind="normal") z_centers.tolerance = 1e-15 def xy_fly( scan_title, *, beamline_operator, dwell_time, xstart, xstop, xstep_size,
def test_strings(): sig = Signal(name='sig', kind='normal') assert sig.kind == Kind.normal
import threading import numpy import numpy as np from ophyd import Component, Device, Signal, DeviceStatus from bluesky.plan_stubs import mv from .generate_data import generate_measured_image, SHAPE sample_selector = Signal(value=0, name="sample_selector") class TimerStatus(DeviceStatus): """Simulate the time it takes for a detector to acquire an image.""" def __init__(self, device, delay): super().__init__(device) self.delay = delay # for introspection purposes threading.Timer(delay, self.set_finished).start() class DiffractionDetector(Device): # exposure_time = Component(Signal, value=1) image = Component(Signal, value=numpy.zeros(SHAPE)) signal_to_noise = Component(Signal, value=0) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.delay = 2 # simulated exposure time delay def trigger(self): "Generate a simulated reading with noise for the current sample."
yield from bps.read(sclr) # and maybe the xspress3 if xspress3 is not None: yield from bps.read(xspress3) yield from bps.save() for y in range(num_ypixels): if xspress3 is not None: yield from bps.mov(xspress3.fly_next, True) yield from fly_row() yield from fly_body() E_centers = Signal(value=[], name='E_centers', kind='normal') E_centers.tolerance = 1e-15 def E_fly(scan_title, *, start, stop, step_size, num_scans): _validate_motor_limits(mono.energy, start, stop, 'E') assert step_size > 0, f'step_size ({step_size}) must be more than 0' assert num_scans > 0, f'num_scans ({num_scans}) must be more than 0' e_back = yield from _get_v_with_dflt(mono.e_back, 1977.04) energy_cal = yield from _get_v_with_dflt(mono.cal, 0.40118) def _linear_to_energy(linear): linear = np.asarray(linear) return (e_back / np.sin( np.deg2rad(45) + 0.5 * np.arctan((28.2474 - linear) / 35.02333) +
move_motors usaxs_slit """.split() from ..session_logs import logger logger.info(__file__) from ..framework import sd from apstools.utils import pairwise from bluesky import plan_stubs as bps from ophyd import Component, EpicsMotor, EpicsSignal, MotorBundle, Signal import ophyd guard_h_size = Signal(name="guard_h_size", value=0.5, labels=["terms",]) guard_v_size = Signal(name="guard_v_size", value=0.5, labels=["terms",]) def move_motors(*args): """ move one or more motors at the same time, returns when all moves are done move_motors(m1, 0) move_motors(m2, 0, m3, 0, m4, 0) """ status = [] for m, v in pairwise(args): status.append(m.move(v, wait=False)) for st in status:
class PSO_Device(Device): """ Operate the motion trajectory controls of an Aerotech Ensemble controller note: PSO means Position Synchronized Output (Aerotech's term) USAGE: pso1 = PSO_Device("2bmb:PSOFly1:", name="pso1") # # ... configure the pso1 object # pso1.set("taxi") # or pso1.taxi() interactively pso1.set("fly") # or pso1.fly() interactively # in a plan, use this instead yield from abs_set(pso1, "taxi", wait=True) yield from abs_set(pso1, "fly", wait=True) """ # TODO: this might fit the ophyd "Flyer" API slew_speed = Component(EpicsSignal, "slewSpeed.VAL") scan_control = Component(EpicsSignal, "scanControl.VAL", string=True) start_pos = Component(EpicsSignal, "startPos.VAL") end_pos = Component(EpicsSignal, "endPos.VAL") scan_delta = Component(EpicsSignal, "scanDelta.VAL") pso_taxi = Component(EpicsSignal, "taxi.VAL", put_complete=True) pso_fly = Component(EpicsSignal, "fly.VAL", put_complete=True) busy = Signal(value=False, name="busy") # def __init__(self, motor, detector): # self.motor = motor # self.detector = detector # # self.return_position = self.motor.position # self.poll_delay_s = 0.05 # self.num_spins = 1 # # self._completion_status = None # self._data = deque() def taxi(self): """ request move to taxi position, interactive use Since ``pso_taxi`` has the ``put_complete=True`` attribute, this will block until the move is complete. (note: ``2bmb:PSOFly1:taxi.RTYP`` is a ``busy`` record.) """ # TODO: verify that this blocks until complete self.pso_taxi.put("Taxi") def fly(self): """ request fly scan to start, interactive use Since ``pso_fly`` has the ``put_complete=True`` attribute, this will block until the move is complete. """ # TODO: verify that this blocks until complete self.pso_fly.put("Fly") def set(self, value): # interface for BlueSky plans """value is either Taxi, Fly, or Return""" # allowed = "Taxi Fly Return".split() allowed = "Taxi Fly".split() if str(value).lower() not in list(map(str.lower, allowed)): msg = "value should be one of: " + " | ".join(allowed) msg + " received " + str(value) raise ValueError(msg) if self.busy.value: raise RuntimeError("shutter is operating") status = DeviceStatus(self) def action(): """the real action of ``set()`` is here""" if str(value).lower() == "taxi": self.taxi() elif str(value).lower() == "fly": self.fly() # elif str(value).lower() == "return": # self.motor.move(self.return_position) def run_and_wait(): """handle the ``action()`` in a thread""" self.busy.put(True) action() self.busy.put(False) status._finished(success=True) threading.Thread(target=run_and_wait, daemon=True).start() return status
def test_array_into_softsignal(): data = np.array([1, 2, 3]) s = Signal(name='np.array') set_and_wait(s, data) assert np.all(s.get() == data)
def sim_signal(): sim_sig = Signal(name='tst_this_2') sim_sig.put(3.14) register_signal(sim_sig) return sim_sig
from ophyd import (EpicsSignal, Signal) from ophyd.utils.epics_pvs import record_field def callback(sub_type=None, timestamp=None, value=None, **kwargs): logger.info('[callback] [%s] (type=%s) value=%s', timestamp, sub_type, value) # Test that the monitor dispatcher works (you cannot use channel access in # callbacks without it) logger.info('[callback] caget=%s', rw_signal.get()) logger = config.logger motor_record = config.motor_recs[0] val = record_field(motor_record, 'VAL') rbv = record_field(motor_record, 'RBV') rw_signal = EpicsSignal(rbv, write_pv=val) rw_signal.subscribe(callback, event_type=rw_signal.SUB_VALUE) rw_signal.subscribe(callback, event_type=rw_signal.SUB_SETPOINT) rw_signal.value = 2 time.sleep(1.) rw_signal.value = 1 time.sleep(1.) # You can also create a Python Signal: sig = Signal(name='testing', value=10) logger.info('Python signal: %s', sig)