def test_bluesky_magics(pln, line, magic, fresh_RE): RE = fresh_RE # Build a FakeIPython instance to use the magics with. dets = [det] ip = FakeIPython({'motor1': motor1, 'motor2': motor2, 'dets': dets}) sm = BlueskyMagics(ip) sm.detectors = default_dets # Spy on all msgs processed by RE. msgs = [] def collect(msg): msgs.append(msg) RE.msg_hook = collect sm.RE.msg_hook = collect # Test magics cause the RunEngine to execute the messages we expect. RE(bp.mv(motor1, 10, motor2, 10)) # ensure known initial state RE(pln) expected = msgs.copy() msgs.clear() RE(bp.mv(motor1, 10, motor2, 10)) # ensure known initial state getattr(sm, magic)(line) actual = msgs.copy() msgs.clear() compare_msgs(actual, expected)
def hdcm_rock(hdcm_p_range=0.03, hdcm_p_points=51): """ Scan HDCM crystal 2 pitch to maximize flux on BPM1 Optional arguments: hdcm_p_range: HDCM rocking curve range [mrad]. Default 0.03 mrad hdcm_p_points: HDCM rocking curve points. Default 51 Example: RE(hdcm_rock()) RE(hdcm_rock(hdcm_p_range=0.035, hdcm_p_points=71)) """ energy = get_energy() LUT = {m: [epics.caget(LUT_fmt.format(m.name, axis)) for axis in 'XY'] for m in (hdcm.p, )} # Lookup Table def lut(motor): return motor, np.interp(energy, *LUT[motor]) yield from bp.mv( *lut(hdcm.p) # Set Pitch interpolated position ) # Setup plots ax1 = plt.subplot(111) ax1.grid(True) plt.tight_layout() # Decorate find_peaks to play along with our plot and plot the peak location def find_peak_inner(detector, motor, start, stop, num, ax): det_name = detector.name+'_sum_all' mot_name = motor.name+'_user_setpoint' @bp.subs_decorator(LivePlot(det_name, mot_name, ax=ax)) def inner(): peak_x, peak_y = yield from find_peak(detector, motor, start, stop, num) ax.plot([peak_x], [peak_y], 'or') return peak_x, peak_y return inner() # Scan DCM Pitch peak_x, peak_y = yield from find_peak_inner(bpm1, hdcm.p, -hdcm_p_range, hdcm_p_range, hdcm_p_points, ax1) yield from bp.mv(hdcm.p, peak_x) plt.close()
def parse_and_execute(hhm, sample_stage, batch, plans_dict): tm = trajectory_manager(hhm) for ii in range(batch.rowCount()): experiment = batch.item(ii) print(experiment.item_type) repeat = experiment.repeat print(repeat) for jj in range(experiment.rowCount()): sample = experiment.child(jj) print(' ' + sample.name) print(' ' + str(sample.x)) print(' ' + str(sample.y)) yield from mv(sample_stage.x, sample.x, sample_stage.y, sample.y) for kk in range(sample.rowCount()): scan = sample.child(kk) traj_index = scan.trajectory print(' ' + scan.scan_type) plan = plans_dict[scan.scan_type] kwargs = { 'name': sample.name, 'comment': '', 'delay': 0, 'n_cycles': repeat } tm.init(traj_index + 1) yield from plan(**kwargs)
def test_mv(): # special-case mv because the group is not configurable actual = list(mv(det1, 1, det2, 2)) expected = [Msg("set", det1, 1, group=None), Msg("set", det2, 2, group=None), Msg("wait", None, group=None)] strip_group(actual) strip_group(expected) assert actual == expected
def homs_recovery(*, detectors, motors, goals, detector_fields, index, sim=False, **kwargs): """ Plan to recover the homs system should something go wrong. Is passed arguments as defined in iterwalk. """ # Interpret args as homs mirrors and areadetector pims # Take the active mirror/pim pair mirror = motors[index] yag = detectors[index] sig_threshold = 0.1 if sim: # The fake mirror should not try to return to nominal # This lets us test the plan sig = yag.detector.stats2.centroid.x else: sig = yag.detector.stats2.centroid.y # The real mirror should try to return to nominal first logger.info("Try recovering to nominal first...") try: nominal = mirror.nominal_position except AttributeError: nominal = None # Explicitly check again in case mirror.nominal_position is None if nominal is None: logger.warning("No nominal position configured, skipping...") else: yield from mv(mirror, nominal) if sig.value > sig_threshold: logger.info("We have beam at the nominal position.") return True else: logger.info("We do not have beam at the nominal position.") # Do the shorter move first dist_to_high = abs(mirror.position - mirror.high_limit) dist_to_low = abs(mirror.position - mirror.low_limit) if dist_to_high < dist_to_low: dir_initial = 1 else: dir_initial = -1 # Call the threshold recovery ok = yield from recover_threshold(sig, sig_threshold, mirror, dir_initial, timeout=60, try_reverse=True, off_limit=0.001, has_stop=False) # Pass the return value back out return ok
def inner(): # Prepare TIFF plugin if filepath is not None and filename is not None: fp = filepath if fp[-1] != '/': fp+= '/' print("Saving files as", "".join((fp, filename, "_XXX.tif"))) print("First file number:", cam_8.tiff.file_number.get()) yield from bp.mv( tiff.enable, 1, tiff.auto_increment, 1, tiff.file_path, fp, tiff.file_name, filename, tiff.file_template, "%s%s_%3.3d.tif", tiff.file_write_mode, 1, # Capture mode tiff.num_capture, steps, ) # Prepare statistics plugin yield from bp.mv( stats.enable, 1, stats.compute_centroid, 1 ) # Prepare Camera yield from bp.mv(cam.acquire, 0) # Stop camera... yield from bp.sleep(.5) # ...and wait for the pipeline to empty. yield from bp.mv( cam.trigger_mode, "Sync In 1", # External Trigger cam.array_counter, 0, ) yield from bp.abs_set(cam.acquire, 1) # wait=False yield from bp.abs_set(tiff.capture, 1) # Move to the starting positions yield from bp.mv( slt_gap, gap, # Move gap to desired position slt_ctr, start - move_slack, # Move slits to the beginning of the motion stats.ts_control, "Erase/Start", # Prepare statistics Time Series ) # Set Slits Center velocity for the scan yield from bp.mv(slt_ctr.velocity, speed) # Go yield from bp.kickoff(flyer, wait=True) st = yield from bp.complete(flyer) yield from bp.abs_set(slt_ctr, end + move_slack) while not st.done: yield from bp.collect(flyer, stream=True) yield from bp.sleep(0.2) yield from bp.sleep(1) yield from bp.collect(flyer, stream=True) yield from bp.mv(stats.ts_control, "Stop")
def test_mv(): # special-case mv because the group is not configurable actual = list(mv(det1, 1, det2, 2)) expected = [Msg('set', det1, 1, group=None), Msg('set', det2, 2, group=None), Msg('wait', None, group=None)] strip_group(actual) strip_group(expected) assert actual == expected
def insert_set(msg): obj = msg.obj if obj is not None and obj not in devices_seen: devices_seen.add(obj) if hasattr(obj, 'count_time'): # TODO Do this with a 'read' Msg once reads can be # marked as belonging to a different event stream (or no # event stream. original_times[obj] = obj.count_time.get() # TODO do this with configure return pchain(mv(obj.count_time, time), single_gen(msg)), None return None, None
def test_mv_progress(fresh_RE): RE = fresh_RE RE.waiting_hook = ProgressBarManager() motor1 = Mover('motor1', OrderedDict([('motor1', lambda x: x), ('motor1_setpoint', lambda x: x)]), {'x': 0}) motor2 = Mover('motor2', OrderedDict([('motor2', lambda x: x), ('motor2_setpoint', lambda x: x)]), {'x': 0}) assert RE.waiting_hook.delay_draw == 0.2 # moving time > delay_draw motor1._fake_sleep = 0.5 motor1._fake_sleep = 0.5 RE(mv(motor1, 0, motor2, 0)) # moving time < delay_draw motor1._fake_sleep = 0.01 motor1._fake_sleep = 0.01 RE(mv(motor1, 0, motor2, 0))
def mov(self, line): if len(line.split()) % 2 != 0: raise TypeError("Wrong parameters. Expected: " "%mov motor position (or several pairs like that)") args = [] for motor, pos in partition(2, line.split()): args.append(eval(motor, self.shell.user_ns)) args.append(eval(pos, self.shell.user_ns)) plan = bp.mv(*args) self.RE.waiting_hook = self.pbar_manager self.RE(plan) self.RE.waiting_hook = None self._ensure_idle() return None
def test_mv(): # special-case mv because the group is not configurable # move motors first to ensure that movement is absolute, not relative motor1.set(10) motor2.set(10) actual = list(mv(motor1, 1, motor2, 2)) expected = [ Msg('set', motor1, 1, group=None), Msg('set', motor2, 2, group=None), Msg('wait', None, group=None) ] strip_group(actual) strip_group(expected) assert actual == expected
def dull_scan(mot, count, sig=None, sleep_time=0): if sig: thread = threading.Thread(target=sig_sequence, args=(sig, )) thread.start() for i in range(count): yield from checkpoint() try: yield from mv(mot, i) except: pass # make every step take 1s extra yield from sleep(sleep_time) yield from checkpoint() yield from create() yield from read(mot) yield from save() yield from checkpoint()
def gradient_step(): logger.debug("Using gradient of {} for naive step..." "".format(gradient)) #Take a quick measurement avgs = yield from measure_average([detector, motor] + system, filters=filters, num=average, delay=delay, drop_missing=drop_missing) #Extract centroid and position center, pos = avgs[target_fields[0]], avgs[target_fields[1]] #Calculate corresponding intercept intercept = center - gradient*pos #Calculate best step on first guess of line next_pos = (target - intercept)/gradient logger.debug("Predicting position using line y = {}*x + {}" "".format(gradient, intercept)) #Move to position yield from mv(motor, next_pos)
def compare_msgs(actual, expected): for a, e in zip(actual, expected): # Strip off randomized stuff that cannot be compared. a.kwargs.pop('group', None) e.kwargs.pop('group', None) assert a == e dets = [det] default_dets = [det1, det2] @pytest.mark.parametrize('pln,magic,line', [ (bp.mv(motor1, 2), 'mov', 'motor1 2'), (bp.mv(motor1, 2, motor2, 3), 'mov', 'motor1 2 motor2 3'), (bp.mvr(motor1, 2), 'movr', 'motor1 2'), (bp.mvr(motor1, 2, motor2, 3), 'movr', 'motor1 2 motor2 3'), (bp.count(dets), 'ct', 'dets'), (bp.count(default_dets), 'ct', ''), ]) def test_bluesky_magics(pln, line, magic, fresh_RE): RE = fresh_RE # Build a FakeIPython instance to use the magics with. dets = [det] ip = FakeIPython({'motor1': motor1, 'motor2': motor2, 'dets': dets}) sm = BlueskyMagics(ip) sm.detectors = default_dets
def reset(): for obj, time in original_times.items(): yield from mv(obj.count_time, time)
def test(p_o='83.7681090000'): # yield from bp.mv(PGM.Grating_lines, p_o) yield from bp.mv(PGM.Mirror_Pitch_off, p_o)
def test(g_o = 300, p_o = 83.7681090000): yield from bp.mv(PGM.Grating_lines, g_o) yield from bp.mv(PGM.Mirror_Pitch_off, p_o)
def set_energy(energy, hdcm_p_range=0.03, hdcm_p_points=51): """ Sets undulator, HDCM, HFM and KB settings for a certain energy energy: Photon energy [eV] Optional arguments: hdcm_p_range: HDCM rocking curve range [mrad]. Default 0.03 mrad hdcm_p_points: HDCM rocking curve points. Default 51 Lookup tables and variables are set in a settings notebook: settings/set_energy setup FMX.ipynb Example: RE(set_energy(12660)) RE(set_energy(7110, hdcm_p_range=0.035, hdcm_p_points=71)) """ # MF 20180331: List lacked hdcm.r. Added by hand. Consider using LUT_valid here (set above). # Order is also different, probably irrelevant LUT = {m: [epics.caget(LUT_fmt.format(m.name, axis)) for axis in 'XY'] for m in (ivu_gap, hdcm.g, hdcm.r, hdcm.p, hfm.y, hfm.x, hfm.pitch, kbm.hy, kbm.vx)} LUT_offset = [epics.caget(LUT_fmt.format('ivu_gap_off', axis)) for axis in 'XY'] LGP = {m: epics.caget(LGP_fmt.format(m.name)) for m in (kbm.hp, kbm.hx, kbm.vp, kbm.vy)} # Open HHL Slits yield from bp.mv( slits1.x_gap, 3000, slits1.y_gap, 2000 ) # Lookup Table def lut(motor): return motor, np.interp(energy, *LUT[motor]) # Last Good Position def lgp(motor): return motor, LGP[motor] yield from bp.mv( *lut(ivu_gap), # Set IVU Gap interpolated position hdcm.e, energy, # Set Bragg Energy pseudomotor *lut(hdcm.g), # Set DCM Gap interpolated position *lut(hdcm.r), # Set DCM Roll interpolated position # MF 20180331 *lut(hdcm.p), # Set Pitch interpolated position # Set HFM from interpolated positions *lut(hfm.x), *lut(hfm.y), *lut(hfm.pitch), # Set KB from interpolated positions *lut(kbm.vx), *lut(kbm.hy), # Set KB from known good setpoints *lgp(kbm.vy), *lgp(kbm.vp), *lgp(kbm.hx), *lgp(kbm.hp) ) # Setup plots ax1 = plt.subplot(311) ax1.grid(True) ax2 = plt.subplot(312) ax2.grid(True) ax3 = plt.subplot(313) plt.tight_layout() # Decorate find_peaks to play along with our plot and plot the peak location def find_peak_inner(detector, motor, start, stop, num, ax): det_name = detector.name+'_sum_all' mot_name = motor.name+'_setpoint' if motor is ivu_gap else motor.name+'_user_setpoint' # Prevent going below the lower limit or above the high limit if motor is ivu_gap: step_size = (stop - start) / (num - 1) while motor.setpoint.value + start < motor.low_limit: start += 5*step_size stop += 5*step_size while motor.setpoint.value + stop > motor.high_limit: start -= 5*step_size stop -= 5*step_size @bp.subs_decorator(LivePlot(det_name, mot_name, ax=ax)) def inner(): peak_x, peak_y = yield from find_peak(detector, motor, start, stop, num) ax.plot([peak_x], [peak_y], 'or') return peak_x, peak_y return inner() # Scan DCM Pitch peak_x, peak_y = yield from find_peak_inner(bpm1, hdcm.p, -hdcm_p_range, hdcm_p_range, hdcm_p_points, ax1) yield from bp.mv(hdcm.p, peak_x) # Scan IVU Gap peak_x, peak_y = yield from find_peak_inner(bpm1, ivu_gap, -.1, .1, 41, ax2) yield from bp.mv(ivu_gap, peak_x + np.interp(energy, *LUT_offset)) # Get image prefix = 'XF:17IDA-BI:FMX{FS:2-Cam:1}image1:' image = epics.caget(prefix+'ArrayData') width = epics.caget(prefix+'ArraySize0_RBV') height = epics.caget(prefix+'ArraySize1_RBV') ax3.imshow(image.reshape(height, width), cmap='jet')
def statTramp( dets, exposure, Tstart, Tstop, Tstep, sample_mapping, *, bt=None ): """ Parameters: ----------- sample_mapping : dict {'sample_ind': croysta_motor_pos} """ pe1c, = dets # setting up area_detector (num_frame, acq_time, computed_exposure) = yield from _configure_area_det( exposure ) area_det = xpd_configuration["area_det"] temp_controller = xpd_configuration["temp_controller"] stat_motor = xpd_configuration["stat_motor"] ring_current = xpd_configuration["ring_current"] # compute Nsteps (Nsteps, computed_step_size) = _nstep(Tstart, Tstop, Tstep) # stat_list _sorted_mapping = sorted(sample_mapping.items(), key=lambda x: x[1]) sp_uid = str(uuid.uuid4()) xpdacq_md = { "sp_time_per_frame": acq_time, "sp_num_frames": num_frame, "sp_requested_exposure": exposure, "sp_computed_exposure": computed_exposure, "sp_type": "statTramp", "sp_startingT": Tstart, "sp_endingT": Tstop, "sp_requested_Tstep": Tstep, "sp_computed_Tstep": computed_step_size, "sp_Nsteps": Nsteps, "sp_uid": sp_uid, "sp_plan_name": "statTramp", } # plan uids = {k: [] for k in sample_mapping.keys()} yield from bp.mv(temp_controller, Tstart) for t in np.linspace(Tstart, Tstop, Nsteps): yield from bp.mv(temp_controller, t) for s, pos in _sorted_mapping: # sample ind yield from bp.mv(stat_motor, pos) # update md md = list(bt.samples.values())[int(s)] _md = ChainMap(md, xpdacq_md) plan = bp.count( [temp_controller, stat_motor, ring_current] + dets, md=_md ) plan = bp.subs_wrapper( plan, LiveTable( [area_det, temp_controller, stat_motor, ring_current] ), ) # plan = bp.baseline_wrapper(plan, [temp_controller, # stat_motor, # ring_current]) uid = yield from plan if uid is not None: from xpdan.data_reduction import save_last_tiff save_last_tiff() uids[s].append(uid) for s, uid_list in uids.items(): from databroker import db import pandas as pd hdrs = db[uid_list] dfs = [db.get_table(h, stream_name="primary") for h in hdrs] df = pd.concat(dfs) fn_md = list(bt.samples.keys())[int(s)] fn_md = "_".join([fn_md, sp_uid]) + ".csv" fn = os.path.join(glbl["tiff_base"], fn_md) df.to_csv(fn) return uids
def inner(): # Prepare Camera yield from bp.mv(cam.acquire, 0) # Stop camera... yield from bp.sleep(.5) # ...and wait for the pipeline to empty. yield from bp.mv( cam.trigger_mode, "Sync In 1", # External Trigger cam.array_counter, 0, ) if use_roi4: yield from bp.mv( cam.min_x, roi.min_xyz.min_x.get(), cam.min_y, roi.min_xyz.min_y.get(), cam.size.size_x, roi.size.x.get(), cam.size.size_y, roi.size.y.get() ) # Prepare TIFF Plugin yield from bp.mv( tiff.file_write_mode, "Stream", tiff.num_capture, steps, tiff.auto_save, 1, tiff.auto_increment, 1, tiff.file_path, folder, tiff.file_name, filename, tiff.file_template, "%s%s_%d.tif", tiff.file_number, 1, tiff.enable, 1) yield from bp.abs_set(tiff.capture, 1) yield from bp.abs_set(cam.acquire, 1) # wait=False # Move to the starting positions yield from bp.mv( gonio.py, start_y - slack_y, gonio.pz, start_z - slack_z, ) # Set velocity for the scan yield from bp.mv( gonio.py.velocity, speed_y, gonio.pz.velocity, speed_z ) # Arm Zebra yield from bp.abs_set(zebra.pos_capt.arm.arm, 1) # Wait Zebra armed while not zebra2.download_status.get(): time.sleep(0.1) # Go yield from bp.mv( gonio.py, end_y + slack_y, gonio.pz, end_z + slack_z ) yield from abs_set(tiff.capture, 0) print(f"{cam.array_counter.get()} images captured")
def fitwalk(detectors, motor, models, target, naive_step=None, average=120, filters=None, drop_missing=True, tolerance=10, delay=None, max_steps=10): """ Parameters ---------- detectors : list List of detectors to read at each step of walk motor : ``ophyd.Object`` Ophyd object that supports the set and wait. This should have a one to one relationship with the independent variable in the model you plan to optimize models : list List of models to evaluate during the walk target : float Desired end position for the walk naive_step : bluesky.plan, optional Plan to execute when there is not an accurate enough model available. By default this is ``mv(0.01)`` average : int, optional Number of readings to take and average at each event. Models are allowed to take a subset of each reading and average, however if the two settings will create an average over multiple steps in the walk the :attr:`.LiveBuild.average` setting is automatically updated. For example, if the walk is told to average over 10 events, your model can either average over 10, 5, 2, or 1 shots. filters : dict, optional Key, callable pairs of event keys and single input functions that evaluate to True or False. For more infromation see :meth:`.apply_filters` drop_missing : bool, optional Choice to include events where event keys are missing tolerance : float, optional Maximum distance from target considered successful delay : float, optional Mininum time between consecutive readings max_steps : int, optional Maximum number of steps the scan will attempt before faulting """ #Check all models are fitting the same key if len(set([model.y for model in models])) > 1: raise RuntimeError("Provided models must predict "\ "the same dependent variable.") #Prepare model callbacks for model in models: #Modify averaging if average % model.average != 0: logger.warning("Model {} was set to an incompatible averaging " "setting, changing setting to {}".format(model.name, average)) model.average = average #Subscribe callbacks yield Msg('subscribe', None, 'all', model) #Target field target_field = models[0].y #Install filters filters = filters or {} [m.install_filters(filters) for m in models] #Link motor to independent variables detectors.insert(1, motor) field_names = list(set(var for model in models for var in model.independent_vars.values())) motors = dict((key, motor) for key in field_names if key in motor.read_attrs) #Initialize variables steps = 0 if not naive_step: def naive_step(): return (yield from rel_set(motor, 0.01, wait=True)) #Measurement method def model_measure(): #Take average measurement avg = yield from measure_average(detectors, num=average, delay=delay, drop_missing=drop_missing, filters=filters) #Save current target position last_shot = avg.pop(target_field) logger.debug("Averaged data yielded {} is at {}" "".format(target_field, last_shot)) #Rank models based on accuracy of fit model_ranking = rank_models(models, last_shot, **avg) #Determine if any models are accurate enough if len(model_ranking): model = model_ranking[0] else: model = None return avg, last_shot, model #Make first measurements averaged_data, last_shot, accurate_model = yield from model_measure() #Begin walk while not np.isclose(last_shot, target, atol=tolerance): #Log error if not steps: logger.info("Initial error before fitwalk is {}" "".format(int(target-last_shot))) else: logger.info("fitwalk is reporting an error {} of after step #{}"\ "".format(int(target-last_shot), steps)) #Break on maximum step count if steps >= max_steps: raise RuntimeError("fitwalk failed to converge after {} steps"\ "".format(steps)) #Use naive step plan if no model is accurate enough #or we have not made a step yet if not accurate_model or steps==0: logger.info("No model yielded accurate prediction, "\ "using naive plan") yield from naive_step() else: logger.info("Using model {} to determine next step."\ "".format(accurate_model.name)) #Calculate estimate of next step from accurate model fixed_motors = dict((key, averaged_data[key]) for key in field_names if key not in motors.keys()) #Try and step off model prediction try: estimates = accurate_model.backsolve(target, **fixed_motors) #Report model faults except Exception as e: logger.warning("Accurate model {} was unable to backsolve " "for target {}".format(accurate_model.name, target)) logger.warning(e) #Reuse naive step logger.info("Reusing naive step due to lack of accurate model") yield from naive_step() else: #Move system to match estimate for param, pos in estimates.items(): #Watch for NaN if pd.isnull(pos) or np.isinf(pos): raise RuntimeError("Invalid position return by fit") #Attempt to move try: logger.info("Adjusting motor {} to position {:.1f}"\ "".format(motor.name, pos)) yield from mv(motor, pos) except KeyboardInterrupt as e: logger.debug("No motor found to adjust variable {}"\ "".format(e)) #Count our steps steps += 1 #Take a new measurement logger.debug("Resampling after successfull move") averaged_data, last_shot, accurate_model = yield from model_measure() #Report a succesfull run logger.info("Succesfully walked to value {} (target={}) after {} steps."\ "".format(int(last_shot), target, steps)) return last_shot, accurate_model
def walk_to_pixel(detector, motor, target, filters=None, start=None, gradient=None, models=[], target_fields=['centroid_x', 'alpha'], first_step=1., tolerance=20, system=None, average=1, delay=None, max_steps=None, drop_missing=True): """ Step a motor until a specific threshold is reached on the detector This function assumes a linear relationship between the position of the given motor and the position on the YAG. While at the onset of the plan, we don't know anything about the physical setup of the system, we can track the steps as they happen and use our prior attempts to inform future ones. The first step of the plan makes a move out into the unknown parameter space of the model. Using the two data points of the initial centroid and the result of our first step we can form a coarse model by simply drawing a line through each point. A new step is calculated based on this rudimentary model, and the centroid is measured again now at a third point. As we gather more data points on successive attempts at alignment our linear fit improves. The iteration stops when the algorithm has centered the beam within the specified tolerance. There are ways to seed the walk with the known information to make the first step the algorithm takes more fruitful. The most naive is to give it a logical first step size that will keep the object you are trying to center within the image. However, in some cases we may know enough to have a reasonable first guess at the relationship between pitch and centroid. In this case the algorithm accepts the :param:`.gradient` parameter that is then used to calculate the optimal first step. Parameters ---------- detector : :class:`.BeamDetector` YAG to make measure beam centroid motor : :class:`.FlatMirror` Mirror to adjust pitch mechanism target : int Target pixel for beam centroid start : float Starting position for pitch mechanism first_step : float, optional Initial step to attempt gradient : float, optional Assume an initial gradient for the relationship between pitch and beam center target_fields : iterable, optional (detector, motor) fields to average and calculate line of best fit models : list, optional Additional models to include in the :func:`.fitwalk` system : list, optional Extra detectors to include in the datastream as we measure the average tolerance : int, optional Number of pixels the final centroid position is allowed to differ from the target average : int, optional Number of images to average together for each step along the scan max_steps : int, optional Limit the number of steps the walk will take before exiting """ #Prepend field names target_fields = [field_prepend(fld, obj) for (fld, obj) in zip(target_fields,[detector, motor])] system = system or list() average = average or 1 #Travel to starting position if start: yield from mv(motor, start) else: start = motor.position #Create initial step plan if gradient: #Seed the fit with our estimate init_guess = {'slope' : gradient} #Take a quick measurement def gradient_step(): logger.debug("Using gradient of {} for naive step..." "".format(gradient)) #Take a quick measurement avgs = yield from measure_average([detector, motor] + system, filters=filters, num=average, delay=delay, drop_missing=drop_missing) #Extract centroid and position center, pos = avgs[target_fields[0]], avgs[target_fields[1]] #Calculate corresponding intercept intercept = center - gradient*pos #Calculate best step on first guess of line next_pos = (target - intercept)/gradient logger.debug("Predicting position using line y = {}*x + {}" "".format(gradient, intercept)) #Move to position yield from mv(motor, next_pos) naive_step = gradient_step else: init_guess = dict() def naive_step(): return (yield from rel_set(motor, first_step, wait=True)) #Create fitting callback fit = LinearFit(target_fields[0], target_fields[1], init_guess=init_guess, average=average, name='Linear') #Fitwalk last_shot, accurate_model = yield from fitwalk([detector]+system, motor, [fit]+models, target, naive_step=naive_step, average=average, filters=filters, tolerance=tolerance, delay=delay, drop_missing=drop_missing, max_steps=max_steps) #Report if we did not need a model if not accurate_model: logger.info("Reached target without use of model") return last_shot, accurate_model
def basic_plan(self,filename, x,y, sample_stage_x=None, sample_stage_y=None, plan=None, **kwargs): assert sample_stage_x, "Set sample stage x" assert sample_stage_y, "Set sample stage y" assert plan, "Set plan to be executed" yield from mv(sample_stage_x, x, sample_stage_y, y) yield from plan(filename, **kwargs)
def iterwalk(detectors, motors, goals, starts=None, first_steps=1, gradients=None, detector_fields='centroid_x', motor_fields='alpha', tolerances=20, system=None, averages=1, overshoot=0, max_walks=None, timeout=None, recovery_plan=None, filters=None): """ Iteratively adjust a system of detectors and motors where each motor primarily affects the reading of a single detector but also affects the other axes parasitically. This is a Bluesky plan, but it does not have start run or end run decorators so it can be included inside of other plans. It yields a checkpoint message before adjusting the detector positions and before executing a walk substep. All list arguments that expect one entry per detector must be the same length as the detectors list. If any optional list arguments are provided as single values instead of as iterables, iterwalk will interpret it as "use this value for every detector". Parameters ---------- detectors: list of objects These are your axes of motion, which must implement both the Bluesky reader interface and the setter interface. The set method must accept the "IN" and "OUT" arguments to move the device in and out. It is assumed that detectors earlier in the list block detectors later in the list. motors: list of objects These are your axes of motion, which must implement both the Bluesky reader interface and the setter interface. goals: list of numbers These are the scalar readbacks expected from the detectors at the desired positions. starts: list of numbers, optional If provided, these are the nominal positions to move the motors to before starting to align. first_steps: list of numbers, optional. This is how far the motors will be moved in an initial probe step as we gauge the detector's response. This argument is ignored if 'gradients' is provided. gradients: list of numbers, optional If provided, this is a guess at the ratio between motor move and change in detector readback. This will be used to make a very good first guess at the first step towards the goal pixel. This should be in units of detector/motor detector_fields: list of strings, optional For each detector, this is the field we're walking to. motor_fields: list of strings, optional For each motor, this is the field we're using along with the detector_field to build our linear regression. This should be the readback with nominally the same value as the position field. tolerances: list of numbers, optional If our detector readbacks are within these tolerances of the goal positions, we'll treat the goal as reached. system: list of readable objects, optional Other system parameters that we'd like to read during the walk. averages: list of numbers, optional For each detector, this is the number of shots to average before settling on a reading. overshoot: number, optional The percent to overshoot at each goal step. For these parasitic systems, over or undershooting can allow convergence to occur faster. An overshoot of 0 is no overshoot, an overshoot of 0.1 is a 10% overshoot, an overshoot of -0.2 is a 20% undershoot, etc. max_walks: int, optional The number of sets of walks to try before giving up on the alignment. E.g. if max_walks is 3, we'll move each motor/detector pair 3 times in series before giving up. timeout: number, optional The maximum time to allow for the alignment before aborting. The timeout is checked after each walk step. recovery_plan: plan, optional A backup plan to run when no there is no readback on the detectors. This plan should expect a complete description of the situation in the form of all arguments given to iterwalk, except for recovery_plan. Note that these arguments are listified before being passed to recovery_plan. It will also be passed 'index', which is the index in all of the list arguments that is currently active. Also note that there is no requirement to use all of the provided arguments. A good practice is to have recovery_plan accept and ignore **kwargs while explicitly including desired keywords to use. filters: list of dictionaries Each entry in this list should be a valid input to the filters argument in the lower functions, such as walk_to_pixel and measure_average. """ num = len(detectors) # Listify most optional arguments goals = as_list(goals, num) starts = as_list(starts, num) first_steps = as_list(first_steps, num, float) gradients = as_list(gradients, num) detector_fields = as_list(detector_fields, num) motor_fields = as_list(motor_fields, num) tolerances = as_list(tolerances, num) system = as_list(system) averages = as_list(averages, num) filters = as_list(filters, num) logger.debug("iterwalk aligning %s to %s on %s", motors, goals, detectors) # Debug counters mirror_walks = 0 yag_cycles = 0 recoveries = 0 # Set up end conditions n_steps = 0 start_time = time.time() models = [None] * num finished = [False] * num done_pos = [0] * num while True: index = 0 while index < num: try: # Before each walk, check the global timeout. if timeout is not None and time.time() - start_time > timeout: raise RuntimeError("Iterwalk has timed out after %s s", time.time() - start_time) logger.debug("putting imager in") ok = (yield from prep_img_motors(index, detectors, timeout=15)) yag_cycles += 1 # Be loud if the yags fail to move! Operator should know! if not ok: err = "Detector motion timed out!" logger.error(err) raise RuntimeError(err) # Choose a start position for the first move if it was given if n_steps == 0 and starts[index] is not None: firstpos = starts[index] else: firstpos = None # Give higher-level a chance to recover or suspend yield from checkpoint() # Set up the system to not include the redundant objects full_system = copy(system) try: full_system.remove(motors[index]) except ValueError: pass try: full_system.remove(detectors[index]) except ValueError: pass # Set flag for needing recovery before walking recover_pre_walk = True original_position = motors[index].position # Check if we're already done logger.debug("measure_average on det=%s, mot=%s, sys=%s", detectors[index], motors[index], full_system) avgs = (yield from measure_average( [detectors[index], motors[index]] + full_system, num=averages[index], filters=filters[index])) pos = avgs[field_prepend(detector_fields[index], detectors[index])] logger.debug("recieved %s from measure_average on %s", pos, detectors[index]) if abs(pos - goals[index]) < tolerances[index]: logger.info("Beam was aligned on %s without a move", detectors[index]) finished[index] = True done_pos[index] = pos if all(finished): logger.debug("beam aligned on all yags") break # Increment index before restarting loop index += 1 continue else: # If any of the detectors were wrong, reset finished flags logger.debug("reset alignment flags before move") finished = [False] * num # Modify goal to use overshoot if index == 0: goal = goals[index] else: goal = (goals[index] - pos) * (1 + overshoot) + pos # Clear flag for needing recovery before walking recover_pre_walk = False # Core walk logger.info(('Starting walk from {} to {} on {} using {}' ''.format(pos, goal, detectors[index].name, motors[index].name))) pos, models[index] = (yield from walk_to_pixel( detectors[index], motors[index], goal, filters=filters[index], start=firstpos, gradient=gradients[index], target_fields=[ detector_fields[index], motor_fields[index] ], first_step=first_steps[index], tolerance=tolerances[index], system=full_system, average=averages[index], max_steps=10)) if models[index]: try: gradients[index] = models[index].result.values['slope'] logger.debug( "Found equation of ({}, {}) between " "linear fit of {} to {}" "".format(gradients[index], models[index].result.values['intercept'], motors[index].name, detectors[index].name)) except Exception as e: logger.warning(e) logger.warning("Unable to find gradient of " "linear fit of {} to {}" "".format(motors[index].name, detectors[index].name)) logger.debug("Walk reached pos %s on %s", pos, detectors[index].name) mirror_walks += 1 # Be loud if the walk fails to reach the pixel! if abs(pos - goal) > tolerances[index]: err = "walk_to_pixel failed to reach the goal" logger.error(err) raise RuntimeError(err) finished[index] = True done_pos[index] = pos # Increment index before restarting loop index += 1 except FilterCountError as err: if recovery_plan is None: logger.error("No recovery plan, not attempting to recover") raise # If we had to recover before walk_to_pixel, # get a fallback position for if the recovery fails if recover_pre_walk: try: fallback_pos = motors[index].nominal_position except AttributeError: fallback_pos = None # Explicitly check again in case nominal_position is None if fallback_pos is None: fallback_pos = motors[index].position else: # If we are recovering after walk_to_pixel, don't bother # with the recovery plan. Just move back and adjust # parameters. logger.info(("Bad state reached during walk_to_pixel. " "Undoing walk_to_pixel...")) yield from mv(motors[index], original_position) # Reset the finished flag finished = [False] * num # Cut our step parameters in half, because they were # probably too big logger.info("Lowering initial step parameters...") if gradients[index] is not None: gradients[index] = gradients[index] * 2 if first_steps[index] is not None: first_steps[index] = first_steps[index] / -2 continue ok = yield from recovery_plan(detectors=detectors, motors=motors, goals=goals, starts=starts, first_steps=first_steps, gradients=gradients, detector_fields=detector_fields, motor_fields=motor_fields, tolerances=tolerances, system=system, averages=averages, overshoot=overshoot, max_walks=max_walks, timeout=timeout, filters=filters, index=index) # Reset the finished tag because we moved something finished = [False] * num recoveries += 1 # If recovery failed, move to nominal and switch to next device if not ok: logger.info(("Recover failed, using fallback pos and " "trying next device alignment.")) yield from mv(motors[index], fallback_pos) index += 1 # Try again continue if all(finished): break # After each set of walks, check if we've exceeded max_walks n_steps += 1 if max_walks is not None and n_steps > max_walks: logger.info("Iterwalk has reached the max_walks limit") break logger.info(("Finished in %.2fs after %s mirror walks, %s yag cycles, " "and %s recoveries"), time.time() - start_time, mirror_walks, yag_cycles, recoveries) logger.info("Aligned to %s", done_pos) logger.info("Goals were %s", goals) logger.info("Deltas are %s", [d - g for g, d in zip(goals, done_pos)]) logger.info("Mirror positions are %s", [m.position for m in motors])