Пример #1
0
def grid_fly(flyer, y, ystart, ystop, ynum, x, xstart, xstop, xnum):
    generator = CompoundGenerator(
        generators=[
            LineGenerator(y.name, "mm", ystart, ystop, ynum),
            LineGenerator(x.name, "mm", xstart, xstop, xnum),
        ],
        duration=0.1,
    )
    mapping.configure(dict(generator=generator))
    md = dict(
        hints=dict(
            gridding="rectilinear",
            dimensions=[([y.name], "primary"), ([x.name], "primary")],
        ),
        shape=(ynum, xnum),
        extents=([ystart, ystop], [xstart, xstop]),
    )
    uid = yield from bps.open_run(md)
    yield from bps.kickoff(flyer, wait=True)
    yield from bps.collect(flyer, stream=True)
    yield from bps.checkpoint()
    yield from bps.complete(flyer, group="flyer")
    for _ in range(int(ynum * xnum * 0.1)):
        yield from bps.sleep(1)
        yield from bps.collect(flyer, stream=True)
        yield from bps.checkpoint()
    yield from bps.wait(group="flyer")
    yield from bps.collect(flyer, stream=True)
    yield from bps.close_run()
    return uid
Пример #2
0
def move_and_do_one(bt: Beamtime,
                    sample_ind: tp.Union[int, str],
                    plan_ind: tp.Union[int, str, tp.Generator],
                    wait_time: float = 0.,
                    sample_x: str = "sample_x",
                    sample_y: str = "sample_y",
                    x_controller: str = "x_controller",
                    y_controller: str = "y_controller") -> tp.Generator:
    """Move to the sample and conduct the plan."""
    sample = translate_to_sample(bt, sample_ind)
    plan = translate_to_plan(bt, plan_ind, sample)
    xc = xpd_configuration[x_controller]
    yc = xpd_configuration[y_controller]
    x = float(get_from_sample(sample, sample_x))
    y = float(get_from_sample(sample, sample_y))
    yield from bps.checkpoint()
    print("Start moving to sample {} at ({}, {}).".format(sample_ind, x, y))
    yield from bps.mv(xc, x, yc, y)
    print("Finish. ")
    yield from bps.checkpoint()
    print("Start sleeping for {} s.".format(wait_time))
    yield from bps.sleep(wait_time)
    print("Wake up.")
    yield from bps.checkpoint()
    print("Start plan {} for sample {}".format(plan_ind, sample_ind))
    yield from plan
    print("Finish.")
Пример #3
0
def cryostat_plan(bt: object, temp_motor: object, temperatures: List[float], posi_motor: object,
                  positions: List[float],
                  samples: List[int], exposures: List[float], temp_to_power: dict = None):
    """
    The scanplan of cryostat measurement.

    Parameters
    ----------
        bt : beamtime object
            The beamtime object.

        temp_motor : motor object
            The controller of temperature.

        temperatures : List[float]
            A list of temperature setpoints.

        posi_motor : motor object
            The controller of positions.

        positions : List[float]
            A list of positions.

        samples : List[int]
            A list of index of samples in bt.

        exposures : List[float]
            A list of exposure time.

        temp_to_power : dict
            A mapping from temperature range to power. The range is open at left and close at right. If None,
            default setting (see function 'get_heater_range') is used. Default None.

    Yields
    ------
        Message of the plan
    """
    samples = translate_to_sample(bt, samples)
    for temperature in temperatures:
        yield from set_power(temp_motor, temperature, temp_to_power)
        yield from checkpoint()
        yield from mv(temp_motor, temperature)
        yield from checkpoint()
        if not (len(positions) == len(samples) and len(samples) == len(exposures)):
            raise ValueError("Unmatched length of positions, samples and exposures: "
                             f"{len(positions)}, {len(samples)}, {len(exposures)}.")
        for position, sample, exposure in zip(positions, samples, exposures):
            yield from mv(posi_motor, position)
            yield from checkpoint()
            yield from config_det_and_count([temp_motor, posi_motor], sample, exposure)
            yield from checkpoint()
Пример #4
0
def my_move_per_step(step: dict, pos_cache: dict):
    yield from bps.checkpoint()
    for motor, pos in step.items():
        if pos == pos_cache[motor]:
            continue
        yield from bps.mv(motor, pos)
        pos_cache[motor] = pos
Пример #5
0
 def simple_plan():
     nonlocal flag
     try:
         yield from checkpoint()
         yield Msg('pause')
     except excp:
         flag = True
Пример #6
0
        def inner_per_step(detectors, motor, step):
            # Set a checkpoint in case the scan is interrupted
            yield from checkpoint()
            """
            # Notify the user where we are trying to move to
            goal_sample = inner_motor.position + inner_step_size
            goal_index = inner_motor.locate_1d(goal_sample)
            logger.info('Inner Step: Moving {0} to {1} (sample {2})'.format(
                inner_motor.name, goal_index, goal_sample))
            """

            # Move the motor to the inputted step
            yield from rel_set(inner_motor, inner_step_size, wait=True)

            if use_sequencer:
                # # Start and wait for the sequencer
                logger.info('Inner Step: Starting the sequencer')
                yield from abs_set(sequencer, 1, wait=True)

            # Wait the specified amount of time
            if wait:
                logger.info(
                    "Inner Step: Waiting for {0} second(s)...".format(wait))
                time.sleep(wait)

            # Fill the dataframe
            scan_positions.append((
                outer_motor.position,
                #inner_motor.chip,
                inner_motor.position,
                #*inner_motor.index,
                #*inner_motor.coordinates
            ))
Пример #7
0
def close_shutter_stub():
    """simple function to return a generator that yields messages to
    close the shutter"""
    yield from bps.abs_set(
        xpd_configuration["shutter"], XPD_SHUTTER_CONF["close"], wait=True
    )
    yield from bps.checkpoint()
def motor_dark_step(detectors, motor, step):
    """
    Take darks while moving motors, wait for all to be finished before 
    taking light
    """

    yield from bps.checkpoint()
    print('l120')
    # close the shutter
    #yield from _close_shutter_stub() COMMENTED OUT (dark per light)
    print('l123')
    # move motors don't wait yet.
    # Note for Soham: use `group=None` to ramp temp after dark collected
    # (Broken and replaced below) yield from bps.abs_set(motor, step, group="dark_motor")
    yield from bps.abs_set(motor, step, group="dark_motor")
    print('l127')
    # take dark (this has an internal wait on the readback)
    #yield from bps.trigger_and_read(list(detectors), name="dark") COMMENTED OUT (dark per light)
    print('l130')
    # (Broken) now wait for the motors to be done too
    yield from bps.wait(group="dark_motor")
    print('l133')
    # open shutter
    yield from open_shutter_stub()
    print('l136')
    # take data
    yield from bps.trigger_and_read(list(detectors) + [motor])
    print('l139')
Пример #9
0
def open_shutter_stub():
    """simple function to return a generator that yields messages to
    open the shutter"""
    yield from bps.abs_set(
        xpd_configuration["shutter"], XPD_SHUTTER_CONF["open"], wait=True
    )
    yield from bps.sleep(glbl["shutter_sleep"])
    yield from bps.checkpoint()
Пример #10
0
def xpdacq_trigger_and_read(detectors: list,
                            name: str = "primary") -> typing.Generator:
    """Open shutter, wait, trigger and read detectors, close shutter."""
    yield from bps.checkpoint()
    yield from xb.open_shutter_stub()
    yield from bps.sleep(xb.glbl["shutter_sleep"])
    yield from bps.trigger_and_read(detectors, name=name)
    yield from xb.close_shutter_stub()
Пример #11
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()
Пример #12
0
 def measure(detectors, motor, step):
     # Perform step
     logger.debug("Measuring average at step %s ...", step)
     yield from checkpoint()
     yield from abs_set(motor, step, wait=True)
     # Measure the average
     return (yield from measure_average([motor, detector],
                                        num=average,
                                        filters=filters))
Пример #13
0
 def move():
     yield from checkpoint()
     grp = short_uid('set')
     for motor, pos in step.items():
         if pos == pos_cache[motor]:
             # This step does not move this motor.
             continue
         yield from abs_set(motor, pos, group=grp)
         pos_cache[motor] = pos
     yield from wait(group=grp)
Пример #14
0
 def move():
     yield from checkpoint()
     grp = short_uid("set")
     for motor, pos in step.items():
         if pos == pos_cache[motor]:
             # This step does not move this motor.
             continue
         yield from abs_set(motor, pos, group=grp)
         pos_cache[motor] = pos
     yield from wait(group=grp)
Пример #15
0
    def one_1d_step(detectors, motor, step):
        """
        Inner loop of a 1D step scan

        This is the default function for ``per_step`` param in 1D plans.
        """
        yield from checkpoint()
        yield from abs_set(motor, step, wait=True)
        yield from trigger_and_read(list(detectors) + [motor], name="dark")
        return (yield from trigger_and_read(list(detectors) + [motor]))
Пример #16
0
    def plan():
        while True:
            for step in np.linspace(start, stop, num):
                yield from bps.abs_set(motor, step, wait=True)
                yield from bps.trigger_and_read(list(detectors) + [motor])
                yield from bps.checkpoint()

            err = errorbar(lf.result, 'sigma')
            if err < err_thresh:
                break
Пример #17
0
def shutter_step(detectors, motor, step):
    """ customized step to ensure shutter is open before
    reading at each motor point and close shutter after reading
    """
    yield from bps.checkpoint()
    yield from bps.abs_set(motor, step, wait=True)
    yield from open_shutter_stub()
    yield from bps.sleep(glbl["shutter_sleep"])
    yield from bps.trigger_and_read(list(detectors) + [motor])
    yield from close_shutter_stub()
Пример #18
0
def custom_step(detectors, motor, step):
    """
    Inner loop of a 1D step scan

    This is the default function for ``per_step`` param in 1D plans.
    """
    yield from checkpoint()
    print('Set attenuator to {}'.format(step))
    yield from pause()
    yield from abs_set(motor, step, wait=True)
    return (yield from trigger_and_read(list(detectors) + [motor]))
Пример #19
0
def robot_wrapper(plan, sample):
    """Wrap a plan in load/unload messages.
    Parameters
    ----------
    plan : a bluesky plan
    sample : dict
        must contain 'position'; optionally also 'geometry'
    Example
    -------
    >>> plan = count([pe1c])
    >>> new_plan = robot_wrapper(plan, {'position': 1})
    """
    yield from bps.checkpoint()
    yield from load_sample(sample['robot_identifier'],
                           sample.get('robot_geometry', None))
    yield from bps.checkpoint()
    yield from plan
    yield from bps.checkpoint()
    yield from unload_sample()
    yield from bps.checkpoint()
Пример #20
0
 def per_step(detectors, motor, step):
     # Perform step
     yield from checkpoint()
     logger.debug("Measuring average at step {0} ...".format(step))
     yield from abs_set(motor, step, wait=True)
     # Measure the average
     reads = (yield from measure_average(all_devices, num=average,
                                         filters=filters, *args, **kwargs))
     # Fill the dataframe at this step with the centroid difference
     for fld in all_fields:
         df.loc[step, fld] = reads[fld]
Пример #21
0
 def per_step(detectors, step: dict, pos_cache):
     """ customized step to ensure shutter is open before
     reading at each motor point and close shutter after reading
     """
     yield from bps.checkpoint()
     for motor, pos in step.items():
         yield from bps.mv(motor, pos)
     yield from bps.sleep(wait)
     yield from open_shutter_stub()
     yield from bps.sleep(glbl["shutter_sleep"])
     yield from bps.trigger_and_read(list(detectors) + list(step.keys()))
     yield from close_shutter_stub()
Пример #22
0
        def move_to_start_fly():
            "See http://nsls-ii.github.io/bluesky/plans.html#the-per-step-hook"
            # row_str = short_uid('row')
            # yield from abs_set(xmotor, row_start, group=row_str)
            # yield from one_1d_step([temp_nanoKB], motor, step)
            # yield from bps.wait(group=row_str)

            row_str = short_uid('row')
            yield from bps.checkpoint()
            yield from bps.abs_set(xmotor, row_start, group=row_str)
            yield from bps.abs_set(motor, step, group=row_str)
            yield from bps.wait(group=row_str)
            yield from bps.trigger_and_read([temp_nanoKB, motor])
Пример #23
0
def _shutter_step(detectors, motor, step):
    """ customized step to ensure shutter is open before
    reading at each motor point and close shutter after reading
    """
    yield from bps.checkpoint()
    yield from bps.abs_set(motor, step, wait=True)
    yield from bps.abs_set(
        xpd_configuration["shutter"], XPD_SHUTTER_CONF["open"], wait=True
    )
    yield from bps.sleep(glbl['shutter_sleep'])
    yield from bps.trigger_and_read(list(detectors) + [motor])
    yield from bps.abs_set(
        xpd_configuration["shutter"], XPD_SHUTTER_CONF["close"], wait=True
    )
Пример #24
0
    def outer_per_step(detectors, motor, step):
        # Set a checkpoint in case the scan is interrupted
        yield from checkpoint()

        # Move the monochrometer to the inputted energy
        logger.info('Outer Step: Moving {0} to {1}'.format(
            outer_motor.name, step))
        yield from abs_set(outer_motor, step, wait=True)

        # Define what we will do at every motor step
        def inner_per_step(detectors, motor, step):
            # Set a checkpoint in case the scan is interrupted
            yield from checkpoint()
            """
            # Notify the user where we are trying to move to
            goal_sample = inner_motor.position + inner_step_size
            goal_index = inner_motor.locate_1d(goal_sample)
            logger.info('Inner Step: Moving {0} to {1} (sample {2})'.format(
                inner_motor.name, goal_index, goal_sample))
            """

            # Move the motor to the inputted step
            yield from rel_set(inner_motor, inner_step_size, wait=True)

            if use_sequencer:
                # # Start and wait for the sequencer
                logger.info('Inner Step: Starting the sequencer')
                yield from abs_set(sequencer, 1, wait=True)

            # Wait the specified amount of time
            if wait:
                logger.info(
                    "Inner Step: Waiting for {0} second(s)...".format(wait))
                time.sleep(wait)

            # Fill the dataframe
            scan_positions.append((
                outer_motor.position,
                #inner_motor.chip,
                inner_motor.position,
                #*inner_motor.index,
                #*inner_motor.coordinates
            ))

        # Define the larger inner scan as a list_scan. We cannot use
        # rel_list_scan because it includes the reset_positions_decorator,
        # which we do not want to do
        yield from stub_wrapper(
            list_scan([], inner_motor, inner_steps, per_step=inner_per_step))
Пример #25
0
        def move_to_start_fly():
            "See http://nsls-ii.github.io/bluesky/plans.html#the-per-step-hook"
            # row_str = short_uid('row')
            # yield from abs_set(xmotor, row_start, group=row_str)
            # yield from one_1d_step([temp_nanoKB], motor, step)
            # yield from bps.wait(group=row_str)

            print(f"Start moving to beginning of the row")
            row_str = short_uid('row')
            yield from bps.checkpoint()
            yield from bps.abs_set(xmotor, row_start, group=row_str)
            yield from bps.abs_set(motor, step, group=row_str)
            yield from bps.wait(group=row_str)
            # yield from bps.trigger_and_read([temp_nanoKB, motor])  ## Uncomment this
            print(f"Finished moving to the beginning of the row")
            print(f"Fast axis: {xmotor.read()} Slow axis: {motor.read()}")
Пример #26
0
def solve_stub(detectors, motor, step, log=None):
    '''

    This stub should be a bit more generic
    '''
    bk_dev = detectors[0]

    yield from bps.checkpoint()
    yield from bps.mv(bk_dev.status, SolverState.unknown)

    def step_stub(detectors, motor, x):
        yield from bps.checkpoint()
        yield from bps.mv(motor, x)
        all_dev = list(detectors) + [motor]
        r = (yield from bps.trigger_and_read(all_dev))
        return r

    def cb(val):
        cmd = partial(step_stub, detectors, motor, val)
        r = bridge.submit(cmd)
        val = r[motor.readback.name]['value']
        return val - step

    bridge = setup_bridge()

    def run_solver():
        a, b = -10, 10
        r = brentq(cb, a, b)
        bridge.stopDelegation()
        return r

    yield from bps.mv(bk_dev.status, SolverState.searching)
    yield from bps.mv(bk_dev.target, step)

    thread = threading.Thread(target=run_solver, name='run_solver')
    thread.start()

    try:
        (yield from bridge_plan_stub(bridge, log=log))
    except Exception:
        yield from bps.mv(bk_dev.status, SolverState.failed)
    else:
        yield from bps.mv(bk_dev.status, SolverState.finished)

    thread.join()
    del thread
Пример #27
0
def setup_plan(detectors, motors, *args, log=None, **kwargs):
    '''retrieve the actual status

    Reads the detectors and returns the state

    Args:
        detectors: detectors to read from

    Motors are passed for convenience for a user that intends to
    implement his own plan.
    '''
    if log is None:
        log = logger

    yield from bps.checkpoint()
    log.info(f'Reading detectors {detectors}')
    r = (yield from bps.trigger_and_read(detectors))
    log.info(f'setup returned {r}')
    return r
Пример #28
0
def custom_step(detectors, motor, step):
    """
    Inner loop of a 1D step scan
    Modified the default function for ``per_step`` param in 1D plans.
    Add a pause to adjust the attenuator
    """
    yield from checkpoint()
    print('Set attenuator to {}'.format(step))

    # adjust DMM range
    if step < 12:
        yield from abs_set(dmm.volt_range_dc, 10)
    elif step < 30:
        yield from abs_set(dmm.volt_range_dc, 1)
    elif step >= 30:
        yield from abs_set(dmm.volt_range_dc, 0.1)

    yield from pause()

    yield from abs_set(motor, step, wait=True)
    return (yield from trigger_and_read(list(detectors) + [motor]))
Пример #29
0
        def fly_once(y):
            # for y in range(num_scans):
            # go to start of row

            yield from bps.checkpoint()
            yield from bps.mv(mono.linear.velocity, 1)
            yield from bps.mv(mono.linear, l_start)

            # set the fly speed
            yield from bps.mv(mono.linear.velocity, flyspeed)

            yield from bps.trigger_and_read([mono], 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_energy_{y}")
            if xspress3 is not None:
                yield from bps.trigger(xspress3, group=f"fly_energy_{y}")

            # fly the motor
            yield from bps.abs_set(mono.linear,
                                   l_stop + a_l_step_size,
                                   group=f"fly_energy_{y}")
            yield from bps.wait(group=f"fly_energy_{y}")

            yield from bps.trigger_and_read([mono], name="row_ends")

            yield from bps.mv(mono.linear.velocity, flyspeed)
            # hard coded to let the sclr count its fingers and toes
            yield from bps.sleep(0.1)
            # read and save the struck
            yield from bps.create(name="primary")
            yield from bps.read(sclr)
            if xspress3 is not None:
                yield from bps.read(xspress3)

            yield from bps.save()
Пример #30
0
def step_scan(experiment, cfg_tomo):
    """
    Collect projects with step motion
    """
    # unpack devices
    det = experiment.det
    tomostage = experiment.tomostage

    yield from bps.mv(det.hdf1.nd_array_port, 'PROC1')
    yield from bps.mv(det.tiff1.nd_array_port, 'PROC1')
    yield from bps.mv(det.proc1.enable, 1)
    yield from bps.mv(det.proc1.reset_filter, 1)
    yield from bps.mv(det.proc1.num_filter, cfg_tomo['n_frames'])

    angs = np.arange(
        cfg_tomo['omega_start'],
        cfg_tomo['omega_end'] + cfg_tomo['omega_step'] / 2,
        cfg_tomo['omega_step'],
    )
    for ang in angs:
        yield from bps.checkpoint()
        yield from bps.mv(tomostage.preci, ang)
        yield from bps.trigger_and_read([det])
Пример #31
0
    def scan_closure():
        # -------------------
        # collect white field
        # -------------------
        # 1-1 monitor shutter status, auto-puase scan if beam is lost
        yield from bps.open_run()
        yield from bps.mv(A_shutter, 'open')
        yield from bps.install_suspender(suspend_A_shutter)

        #1-1.5 configure output plugins     edited by Jason 07/19/2019
        for me in [det.tiff1, det.hdf1]:
            yield from bps.mv(me.file_path, fp)
            yield from bps.mv(me.file_name, fn)
            yield from bps.mv(me.file_write_mode, 2)
            yield from bps.mv(me.num_capture, total_images)
            yield from bps.mv(
                me.file_template,
                ".".join([r"%s%s_%06d", config['output']['type'].lower()]))

        if config['output']['type'] in ['tif', 'tiff']:
            yield from bps.mv(det.tiff1.enable, 1)
            yield from bps.mv(det.tiff1.capture, 1)
            yield from bps.mv(det.hdf1.enable, 0)
        elif config['output']['type'] in ['hdf', 'hdf1', 'hdf5']:
            yield from bps.mv(det.tiff1.enable, 0)
            yield from bps.mv(det.hdf1.enable, 1)
            yield from bps.mv(det.hdf1.capture, 1)
        else:
            raise ValueError(f"Unsupported output type {output_dict['type']}")

        # 1-2 move sample out of the way
        initial_samx = samX.position
        initial_samy = samY.position
        initial_preci = preci.position
        dx = config['tomo']['sample_out_position']['samX']
        dy = config['tomo']['sample_out_position']['samY']
        r = config['tomo']['sample_out_position']['preci']
        yield from bps.mv(samX, initial_samx + dx)
        yield from bps.mv(samY, initial_samy + dy)
        yield from bps.mv(preci, r)

        # 1-2.5 set frame type for an organized HDF5 archive
        yield from bps.mv(det.cam.frame_type, 0)

        # 1-3 collect front white field images
        yield from bps.mv(det.hdf1.nd_array_port, 'PROC1')
        yield from bps.mv(det.tiff1.nd_array_port, 'PROC1')
        yield from bps.mv(det.proc1.enable, 1)
        yield from bps.mv(det.proc1.reset_filter, 1)
        yield from bps.mv(det.proc1.num_filter, n_frames)
        yield from bps.mv(det.cam.trigger_mode, "Internal")
        yield from bps.mv(det.cam.image_mode, "Multiple")
        yield from bps.mv(det.cam.num_images, n_frames * n_white)
        yield from bps.mv(det.cam.acquire_time, acquire_time)
        yield from bps.mv(det.cam.acquire_period, acquire_period)
        yield from bps.trigger_and_read([det])

        # 1-4 move sample back
        yield from bps.mv(samX, initial_samx)
        yield from bps.mv(samY, initial_samy)
        #yield from bps.mv(preci, initial_preci)

        # -------------------
        # collect projections
        # -------------------
        # 1-5 set frame type for an organized HDF5 archive
        yield from bps.mv(det.cam.frame_type, 1)
        # 1-6 step and fly scan are differnt
        if config['tomo']['type'].lower() == 'step':
            yield from bps.mv(det.proc1.reset_filter, 1)
            yield from bps.mv(det.cam.num_images, n_frames)
            # 1-6 collect projections
            for ang in angs:
                yield from bps.checkpoint()
                yield from bps.mv(preci, ang)
                yield from bps.trigger_and_read([det])
        elif config['tomo']['type'].lower() == 'fly':
            yield from bps.mv(det.proc1.num_filter, 1)
            yield from bps.mv(det.hdf1.nd_array_port, 'PG1')
            yield from bps.mv(det.tiff1.nd_array_port, 'PG1')
            yield from bps.mv(
                psofly.start,
                config['tomo']['omega_start'],
                psofly.end,
                config['tomo']['omega_end'],
                psofly.scan_delta,
                abs(config['tomo']['omega_step']),
                psofly.slew_speed,
                slew_speed,
            )
            # taxi
            yield from bps.mv(psofly.taxi, "Taxi")
            # setup detector to overlap for fly scan
            yield from bps.mv(
                det.cam.num_images,
                n_projections,
                det.cam.trigger_mode,
                "Overlapped",
            )
            # start the fly scan
            print("before trigger")
            yield from bps.trigger(det, group='trigger')
            print("waiting for trigger")
            # yield from bps.wait(group='trigger')
            print("before plan()")
            try:
                yield from psofly.plan()
            except NotEnoughTriggers as err:
                reason = (
                    f"{err.expected:.0f} were expected but {err.actual:.0f} were received."
                )
                yield from bps.close_run('fail', reason=reason)
                return  # short-circuit

            # fly scan finished. switch image port and trigger_mode back
            yield from bps.mv(det.cam.trigger_mode, "Internal")
            yield from bps.mv(det.hdf1.nd_array_port, 'PROC1')
            yield from bps.mv(det.tiff1.nd_array_port, 'PROC1')
        else:
            raise ValueError(f"Unknown scan type: {config['tomo']['type']}")

        # ------------------
        # collect back white
        # ------------------
        # 1-7 move the sample out of the way
        # NOTE:
        # this will return ALL motors to starting positions, we need a
        # smart way to calculate a shorter trajectory to move sample
        # out of way
        yield from bps.mv(preci, r)
        yield from bps.mv(samX, initial_samx + dx)
        yield from bps.mv(samY, initial_samy + dy)

        # 1-7.5 set frame type for an organized HDF5 archive
        yield from bps.mv(det.cam.frame_type, 2)

        # 1-8 take the back white
        yield from bps.mv(det.proc1.num_filter, n_frames)
        yield from bps.mv(det.cam.num_images, n_frames * n_white)
        yield from bps.trigger_and_read([det])

        # 1-9 move sample back
        yield from bps.mv(samX, initial_samx)
        yield from bps.mv(samY, initial_samy)

        # -----------------
        # collect back dark
        # -----------------
        # 1-10 close the shutter
        yield from bps.remove_suspender(suspend_A_shutter)
        yield from bps.mv(A_shutter, "close")

        # 1-10.5 set frame type for an organized HDF5 archive
        yield from bps.mv(det.cam.frame_type, 3)

        # 1-11 collect the back dark
        yield from bps.mv(det.cam.num_images, n_frames * n_dark)
        yield from bps.trigger_and_read([det])
        yield from bps.close_run('success')