Beispiel #1
0
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()
Beispiel #3
0
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
Beispiel #5
0
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")
Beispiel #7
0
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
Beispiel #8
0
 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
Beispiel #9
0
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))
Beispiel #10
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
Beispiel #11
0
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
Beispiel #12
0
 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()
Beispiel #13
0
 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)
Beispiel #14
0

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
Beispiel #15
0
 def reset():
     for obj, time in original_times.items():
         yield from mv(obj.count_time, time)
Beispiel #16
0
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)
Beispiel #17
0
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')
Beispiel #19
0
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")
Beispiel #21
0
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
Beispiel #22
0
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
Beispiel #23
0
 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)
Beispiel #24
0
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])