def check_value(self, value): """ Raise an exception if the motor cannot move to value. The full list of checks done in this method: - Check if the value is within the soft limits of the motor. - Check if the motor is disabled ('.DISP' field). Raises ------ MotorDisabledError If the motor is passed any motion command when disabled. ~ophyd.utils.errors.LimitError When the provided value is outside the range of the low and high limits. """ # First check that the user has returned a valid EPICS value. It will # not consult the limits of the PV itself because limits=False super().check_value(value) # Find the soft limit values from EPICS records and check that this # command will be accepted by the motor if any(self.limits): if not (self.low_limit <= value <= self.high_limit): raise LimitError("Value {} outside of range: [{}, {}]".format( value, self.low_limit, self.high_limit)) # Find the value for the disabled attribute if self.disabled.get() == 1: raise MotorDisabledError("Motor is not enabled. Motion requests " "ignored")
def check_value(self, position): """ Checks to make sure the inputted value is valid. Parameters ---------- position : float Position to check for validity Raises ------ ValueError If position is None, NaN or Inf LimitError If the position is outside the soft limits. """ # Check for invalid positions if position is None or np.isnan(position) or np.isinf(position): raise ValueError("Invalid value inputted: '{0}'".format(position)) # Check if it is within the soft limits if not (self.low_limit <= position <= self.high_limit): err_str = "Requested value {0} outside of range: [{1}, {2}]" logger.warn( err_str.format(position, self.low_limit, self.high_limit)) raise LimitError(err_str)
def _validate_motor_limits(motor, start, stop, k): # 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 check_value(self, value): """ Check if the value is within the soft limits of the motor. Raises ------ ValueError """ # First check that the user has returned a valid EPICS value. It will # not consult the limits of the PV itself because limits=False super().check_value(value) # Find the soft limit values from EPICS records and check that this # command will be accepted by the motor low_limit, high_limit = self.limits if not (low_limit <= value <= high_limit): raise LimitError("Value {} outside of range: [{}, {}]" .format(value, low_limit, high_limit))
def check_value(self, value, retries=5): """ Check if the value is within the soft limits of the motor. Raises ------ ValueError """ if value is None: raise ValueError('Cannot write None to epics PVs') for i in range(retries): try: low_limit, high_limit = self.limits if not (low_limit <= value <= high_limit): raise LimitError( "Value {} outside of range: [{}, {}]".format( value, low_limit, high_limit)) return except TypeError: logger.warning("Failed to get limits, retrying...") if i == retries - 1: raise
def check_value(self, value): """ Check if the motor is disabled Check if the value is within the soft limits of the motor. Raises ------ Exception If the motor is passed any motion command when disabled ValueError When the provided value is outside the range of the low and high limits """ # First check that the user has returned a valid EPICS value. It will # not consult the limits of the PV itself because limits=False super().check_value(value) # Find the value for the disabled attribute if self.disabled.value == 1: raise Exception("Motor is not enabled. Motion requests " "ignored") if self.motor_spg.value in [0, 'Stop']: raise Exception("Motor is stopped. Motion requests " "ignored until motor is set to 'Go'") if self.motor_spg.value in [1, 'Pause']: raise Exception("Motor is paused. If a move is set, motion " "will resume when motor is set to 'Go'") # Find the soft limit values from EPICS records and check that this # command will be accepted by the motor low_limit, high_limit = self.limits if not (low_limit <= value <= high_limit): raise LimitError("Value {} outside of range: [{}, {}]".format( value, low_limit, high_limit))
def xy_fly( scan_title, *, beamline_operator, dwell_time, xstart, xstop, xstep_size, ystart, ystop, ystep_size=None, xspress3=None, ): """Do a x-y fly scan. The x-motor is the 'fast' direction. Parameters ---------- scan_title : str A name for the scan. beamline_operator : str The individual responsible for this scan. Appears in output directory path. dwell_time : float Target time is s on each pixel xstart, xstop : float The start and stop values in the fast direction in mm xstep_size : xstep_size is step of x movement ystart, ystop : float The start and stop values in the slow direction in mm ystep_size : ystep_size use xstep_size if it isn't passed in scan_title : str Title of scan, required. """ xy_fly_stage = xy_stage _validate_motor_limits(xy_fly_stage.x, xstart, xstop, "x") _validate_motor_limits(xy_fly_stage.y, ystart, ystop, "y") ystep_size = ystep_size if ystep_size is not None else xstep_size assert dwell_time > 0, f"dwell_time ({dwell_time}) must be more than 0" assert xstep_size > 0, f"xstep_size ({xstep_size}) must be more than 0" assert ystep_size > 0, f"ystep_size ({ystep_size}) must be more than 0" ret = yield from bps.read(xy_fly_stage.x.mres) # (in mm) xmres = ret[ xy_fly_stage.x.mres.name]["value"] if ret is not None else 0.0003125 ret = yield from bps.read(xy_fly_stage.y.mres) # (in mm) ymres = ret[ xy_fly_stage.y.mres.name]["value"] if ret is not None else 0.0003125 prescale = int(np.floor((xstep_size / (5 * xmres)))) a_xstep_size = prescale * (5 * xmres) a_ystep_size = int(np.floor((ystep_size / (ymres)))) * ymres num_xpixels = int(np.floor((xstop - xstart) / a_xstep_size)) num_ypixels = int(np.floor((ystop - ystart) / a_ystep_size)) yield from bps.mv( x_centers, a_xstep_size / 2 + xstart + np.arange(num_xpixels) * a_xstep_size) # SRX original roi_key = getattr(xs.channel1.rois, roi_name).value.name roi_livegrid_key = xs.channel1.rois.roi01.value.name fig = plt.figure("xs") fig.clf() roi_livegrid = LiveGrid( (num_ypixels + 1, num_xpixels + 1), roi_livegrid_key, clim=None, cmap="inferno", xlabel="x (mm)", ylabel="y (mm)", extent=[xstart, xstop, ystart, ystop], x_positive="right", y_positive="down", ax=fig.gca(), ) flyspeed = a_xstep_size / dwell_time # this is in mm/s try: xy_fly_stage.x.velocity.check_value(flyspeed) except LimitError as e: raise LimitError(f"You requested a range of {xstop - xstart} with " f"{num_xpixels} pixels and a dwell time of " f"{dwell_time}. This requires a " f"motor velocity of {flyspeed} which " "is out of range.") from e # set up delta-tau trigger to fast motor for v in ["p1600=0", "p1607=1", "p1600=1"]: yield from bps.mv(dtt, v) yield from bps.sleep(0.1) # TODO make this a message? sclr.set_mode("flying") # poke the struck settings yield from bps.mv(sclr.mcas.prescale, prescale) yield from bps.mv(sclr.mcas.nuse, num_xpixels) if xspress3 is not None: yield from bps.mv(xs.external_trig, True) yield from bps.mv(xspress3.total_points, num_xpixels) yield from bps.mv(xspress3.hdf5.num_capture, num_xpixels) yield from bps.mv(xspress3.settings.num_images, num_xpixels) @bpp.reset_positions_decorator([xy_fly_stage.x, xy_fly_stage.y]) @bpp.subs_decorator({"all": [roi_livegrid]}) @bpp.monitor_during_decorator([xs.channel1.rois.roi01.value]) @bpp.stage_decorator([sclr]) @bpp.baseline_decorator([mono, xy_fly_stage]) # TODO put is other meta data @bpp.run_decorator( md={ "scan_title": scan_title, "operator": beamline_operator, "user_input": { "dwell_time": dwell_time, "xstart": xstart, "xstop": xstop, "xstep_size": xstep_size, "ystart": ystart, "ystep_size": ystep_size, }, "derived_input": { "actual_ystep_size": a_ystep_size, "actual_xstep_size": a_xstep_size, "fly_velocity": flyspeed, "xpixels": num_xpixels, "ypixels": num_ypixels, "prescale": prescale, }, }) def fly_body(): yield from bps.mv(xy_fly_stage.x, xstart, xy_fly_stage.y, ystart) @bpp.stage_decorator([x for x in [xspress3] if x is not None]) def fly_row(): # go to start of row target_y = ystart + y * a_ystep_size yield from bps.mv(xy_fly_stage.x, xstart, xy_fly_stage.y, target_y) yield from bps.mv(y_centers, np.ones(num_xpixels) * target_y) # set the fly speed ret = yield from bps.read(xy_fly_stage.z.user_readback) # (in mm) zpos = (ret[xy_fly_stage.z.user_readback.name]["value"] if ret is not None else 0) yield from bps.mov(z_centers, np.ones(num_xpixels) * zpos) yield from bps.mv(xy_fly_stage.x.velocity, flyspeed) yield from bps.trigger_and_read([xy_fly_stage], name="row_ends") for v in ["p1600=0", "p1600=1"]: yield from bps.mv(dtt, v) yield from bps.sleep(0.1) # arm the struck yield from bps.trigger(sclr, group=f"fly_row_{y}") # maybe start the xspress3 if xspress3 is not None: yield from bps.trigger(xspress3, group=f"fly_row_{y}") yield from bps.sleep(0.1) # fly the motor yield from bps.abs_set(xy_fly_stage.x, xstop + a_xstep_size, group=f"fly_row_{y}") yield from bps.wait(group=f"fly_row_{y}") yield from bps.trigger_and_read([xy_fly_stage], name="row_ends") yield from bps.mv(xy_fly_stage.x.velocity, 5.0) yield from bps.sleep(0.1) # read and save the struck yield from bps.create(name="primary") # yield from bps.read(sclr) yield from bps.read(mono) yield from bps.read(x_centers) yield from bps.read(y_centers) yield from bps.read(z_centers) yield from bps.read(xy_fly_stage.y) yield from bps.read(xy_fly_stage.z) # 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.mv(xspress3.fly_next, True) yield from fly_row() yield from fly_body() # save the start document to a file for the benefit of the user start = db[-1].start dt = datetime.datetime.fromtimestamp(start["time"]) filepath = os.path.expanduser( f"~/Users/Data/{start['operator']}/{dt.date().isoformat()}/xy_fly/" f"{start['scan_title']}-{start['scan_id']}-{start['operator']}-{dt.time().isoformat()}.log" ) os.makedirs(os.path.dirname(filepath), exist_ok=True) with open(filepath, "wt") as output_file: output_file.write(pprint.pformat(start))
def xy_fly(scan_title, *, dwell_time, xstart, xstop, xstep_size, ystart, ystop, ystep_size=None, xspress3=None): """Do a x-y fly scan. The x-motor is the 'fast' direction. Parameters ---------- dwell_time : float Target time is s on each pixel xstart, xstop : float The start and stop values in the fast direction in mm xstep_size : xstep_size is step of x movement ystart, ystop : float The start and stop values in the slow direction in mm ystep_size : ystep_size use xstep_size if it isn't passed in scan_title : str Title of scan, required. """ xy_fly_stage = xy_stage _validate_motor_limits(xy_fly_stage.x, xstart, xstop, 'x') _validate_motor_limits(xy_fly_stage.y, ystart, ystop, 'y') ystep_size = ystep_size if ystep_size is not None else xstep_size assert dwell_time > 0, f'dwell_time ({dwell_time}) must be more than 0' assert xstep_size > 0, f'xstep_size ({xstep_size}) must be more than 0' assert ystep_size > 0, f'ystep_size ({ystep_size}) must be more than 0' ret = yield from bps.read(xy_fly_stage.x.mres) # (in mm) xmres = (ret[xy_fly_stage.x.mres.name]['value'] if ret is not None else .0003125) ret = yield from bps.read(xy_fly_stage.y.mres) # (in mm) ymres = (ret[xy_fly_stage.y.mres.name]['value'] if ret is not None else .0003125) prescale = int(np.floor((xstep_size / (5 * xmres)))) a_xstep_size = prescale * (5 * xmres) a_ystep_size = int(np.floor((ystep_size / (ymres)))) * ymres num_xpixels = int(np.floor((xstop - xstart) / a_xstep_size)) num_ypixels = int(np.floor((ystop - ystart) / a_ystep_size)) flyspeed = a_xstep_size / dwell_time # this is in mm/s try: xy_fly_stage.x.velocity.check_value(flyspeed) except LimitError as e: raise LimitError(f'You requested a range of {xstop - xstart} with ' f'{num_xpixels} pixels and a dwell time of ' f'{dwell_time}. This requires a ' f'motor velocity of {flyspeed} which ' 'is out of range.') from e # set up delta-tau trigger to fast motor for v in ['p1600=0', 'p1607=1', 'p1600=1']: yield from bps.mv(dtt, v) yield from bps.sleep(0.1) # TODO make this a message? sclr.set_mode('flying') # poke the struck settings yield from bps.mv(sclr.mcas.prescale, prescale) yield from bps.mv(sclr.mcas.nuse, num_xpixels) if xspress3 is not None: yield from bps.mov(xs.external_trig, True) yield from mov(xspress3.total_points, num_xpixels) yield from mov(xspress3.hdf5.num_capture, num_xpixels) yield from mov(xspress3.settings.num_images, num_xpixels) @bpp.reset_positions_decorator([xy_fly_stage.x, xy_fly_stage.y]) @bpp.stage_decorator([sclr]) @bpp.baseline_decorator([mono, xy_fly_stage]) # TODO put is other meta data @bpp.run_decorator(md={'scan_title': scan_title}) def fly_body(): yield from bps.mv(xy_fly_stage.x, xstart, xy_fly_stage.y, ystart) @bpp.stage_decorator([x for x in [xspress3] if x is not None]) def fly_row(): # go to start of row yield from bps.mv(xy_fly_stage.x, xstart, xy_fly_stage.y, ystart + y * ystep_size) # set the fly speed yield from bps.mv(xy_fly_stage.x.velocity, flyspeed) yield from bps.trigger_and_read([xy_fly_stage], name='row_ends') for v in ['p1600=0', 'p1600=1']: yield from bps.mv(dtt, v) yield from bps.sleep(0.1) # arm the struck yield from bps.trigger(sclr, group=f'fly_row_{y}') # maybe start the xspress3 if xspress3 is not None: yield from bps.trigger(xspress3, group=f'fly_row_{y}') yield from bps.sleep(0.1) # fly the motor yield from bps.abs_set(xy_fly_stage.x, xstop + a_xstep_size, group=f'fly_row_{y}') yield from bps.wait(group=f'fly_row_{y}') yield from bps.trigger_and_read([xy_fly_stage], name='row_ends') yield from bps.mv(xy_fly_stage.x.velocity, 5.0) yield from bps.sleep(.1) # read and save the struck yield from bps.create(name='primary') 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()