def test_mutator_exceptions(): handled = False def base_plan(): yield Msg('foo') yield Msg('bar') def failing_plan(): nonlocal handled handled = False yield Msg('pre') try: yield Msg('FAIL') except EchoException: handled = True raise def test_mutator(msg): if msg.command == 'bar': return (failing_plan(), single_message_gen(Msg('foo'))) return None, None # check generator exit behavior plan = plan_mutator(base_plan(), test_mutator) next(plan) plan.close() # check exception fall through plan = plan_mutator(base_plan(), test_mutator) with pytest.raises(EchoException): EchoRE(plan, debug=True) assert handled
def test_mutator_exceptions(): handled = False def base_plan(): yield Msg('foo') yield Msg('bar') def failing_plan(): nonlocal handled handled = False yield Msg('pre') try: yield Msg('FAIL') except EchoException: handled = True raise def test_mutator(msg): if msg.command == 'bar': return ( failing_plan(), single_message_gen(Msg('foo')) ) return None, None # check generator exit behavior plan = plan_mutator(base_plan(), test_mutator) next(plan) plan.close() # check exception fall through plan = plan_mutator(base_plan(), test_mutator) with pytest.raises(EchoException): EchoRE(plan, debug=True) assert handled
def test_simple_mutator(): _mut_active = True pre_count = 3 post_count = 5 pre_cmd = 'pre' post_cmd = 'post' def test_mutator(msg): nonlocal _mut_active if _mut_active: _mut_active = False return (pchain(echo_plan(num=pre_count, command=pre_cmd), single_message_gen(msg)), echo_plan(num=post_count, command=post_cmd)) return None, None num = 5 cmd = 'echo' plan = plan_mutator(echo_plan(command=cmd, num=num), test_mutator) msgs = EchoRE(plan) total = num + pre_count + post_count cmd_sq = ([pre_cmd] * pre_count + [cmd] + [post_cmd] * post_count + [cmd] * (num - 1)) _verify_msg_seq(msgs, m_len=total, cmd_sq=cmd_sq, args_sq=[()] * total, kwargs_sq=[{}] * total)
def test_plan_mutator_exception_propogation(): class ExpectedException(Exception): pass num = 5 cmd1 = 'echo1' cmd2 = 'echo2' def bad_tail(): yield Msg('one_tail', None) raise ExpectedException('this is a test') def sarfing_plan(): try: yield from echo_plan(command=cmd1, num=num) except ExpectedException: print('CAUGHT IT') _mut_active = True def test_mutator(msg): nonlocal _mut_active if _mut_active: _mut_active = False return (pchain(echo_plan(num=2, command=cmd2), single_message_gen(msg)), bad_tail()) return None, None plan = plan_mutator(sarfing_plan(), test_mutator) EchoRE(plan, debug=True)
def test_exception_in_pre_with_tail(): class SnowFlake(Exception): ... def bad_pre(): yield Msg('pre_bad', None) raise SnowFlake('this one') def good_post(): yield Msg('good_post', None) def test_mutator(msg): if msg.command == 'TARGET': return bad_pre(), good_post() return None, None def testing_plan(): yield Msg('a', None) yield Msg('b', None) try: yield Msg('TARGET', None) except SnowFlake: pass yield Msg('b', None) yield Msg('a', None) plan = plan_mutator(testing_plan(), test_mutator) msgs = EchoRE(plan, debug=True) _verify_msg_seq(msgs, m_len=5, cmd_sq=['a', 'b', 'pre_bad', 'b', 'a'], args_sq=[()] * 5, kwargs_sq=[{}] * 5)
def test_plan_mutator_returns(): def testing_plan(): yield Msg('a', None) yield Msg('TARGET', None) yield Msg('b', None) return 'foobar' def outer_plan(pln): ret = (yield from pln) assert ret == 'foobar' return ret def tail_plan(): yield Msg('A', None) return 'baz' def test_mutator(msg): def pre_plan(): yield Msg('pre', None) yield msg if msg.command == 'TARGET': return pre_plan(), tail_plan() return None, None plan = plan_mutator(testing_plan(), test_mutator) msgs = EchoRE(plan) _verify_msg_seq(msgs, m_len=5, cmd_sq=['a', 'pre', 'TARGET', 'A', 'b'], args_sq=[()] * 5, kwargs_sq=[{}] * 5)
def test_plan_mutator_returns(): def testing_plan(): yield Msg('a', None) yield Msg('TARGET', None) yield Msg('b', None) return 'foobar' def outer_plan(pln): ret = (yield from pln) assert ret == 'foobar' return ret def tail_plan(): yield Msg('A', None) return 'baz' def test_mutator(msg): def pre_plan(): yield Msg('pre', None) yield msg if msg.command == 'TARGET': return pre_plan(), tail_plan() return None, None plan = plan_mutator(testing_plan(), test_mutator) msgs = EchoRE(plan) _verify_msg_seq(msgs, m_len=5, cmd_sq=['a', 'pre', 'TARGET', 'A', 'b'], args_sq=[()]*5, kwargs_sq=[{}]*5)
def test_exception_in_pre_with_tail(): class SnowFlake(Exception): ... def bad_pre(): yield Msg('pre_bad', None) raise SnowFlake('this one') def good_post(): yield Msg('good_post', None) def test_mutator(msg): if msg.command == 'TARGET': return bad_pre(), good_post() return None, None def testing_plan(): yield Msg('a', None) yield Msg('b', None) try: yield Msg('TARGET', None) except SnowFlake: pass yield Msg('b', None) yield Msg('a', None) plan = plan_mutator(testing_plan(), test_mutator) msgs = EchoRE(plan, debug=True) _verify_msg_seq(msgs, m_len=5, cmd_sq=['a', 'b', 'pre_bad', 'b', 'a'], args_sq=[()]*5, kwargs_sq=[{}]*5)
def test_simple_mutator(): _mut_active = True pre_count = 3 post_count = 5 pre_cmd = 'pre' post_cmd = 'post' def test_mutator(msg): nonlocal _mut_active if _mut_active: _mut_active = False return (pchain(echo_plan(num=pre_count, command=pre_cmd), single_message_gen(msg)), echo_plan(num=post_count, command=post_cmd)) return None, None num = 5 cmd = 'echo' plan = plan_mutator(echo_plan(command=cmd, num=num), test_mutator) msgs = EchoRE(plan) total = num + pre_count + post_count cmd_sq = ([pre_cmd]*pre_count + [cmd] + [post_cmd]*post_count + [cmd]*(num-1)) _verify_msg_seq(msgs, m_len=total, cmd_sq=cmd_sq, args_sq=[()]*total, kwargs_sq=[{}]*total)
def periodic_dark(plan): """ a plan wrapper that takes a plan and inserts `take_dark` The `take_dark` plan is inserted on the fly before the beginning of any new run after a period of time defined by glbl['dk_window'] has passed. """ need_dark = True def insert_take_dark(msg): now = time.time() nonlocal need_dark qualified_dark_uid = _validate_dark(expire_time=glbl["dk_window"]) area_det = xpd_configuration["area_det"] if (not need_dark) and (not qualified_dark_uid): need_dark = True if (need_dark and (not qualified_dark_uid) and msg.command == "open_run" and ("dark_frame" not in msg.kwargs)): # We are about to start a new 'run' (e.g., a count or a scan). # Insert a dark frame run first. need_dark = False # Annoying detail: the detector was probably already staged. # Unstage it (if it wasn't staged, nothing will happen) and # then take_dark() and then re-stage it. return ( bpp.pchain( bps.unstage(area_det), take_dark(), bps.stage(area_det), bpp.single_gen(msg), bps.abs_set( xpd_configuration.get("shutter"), XPD_SHUTTER_CONF["open"], wait=True, ), bps.sleep(glbl['shutter_sleep']), ), None, ) elif msg.command == "open_run" and "dark_frame" not in msg.kwargs: return ( bpp.pchain( bpp.single_gen(msg), bps.abs_set( xpd_configuration.get("shutter"), XPD_SHUTTER_CONF["open"], wait=True, ), bps.sleep(glbl['shutter_sleep'])), None, ) else: # do nothing if (not need_dark) return None, None return (yield from bpp.plan_mutator(plan, insert_take_dark))
def test_base_exception(): class SnowFlake(Exception): ... def null_mutator(msg): return None, None def test_plan(): yield Msg('a', None) raise SnowFlake('this one') pln = plan_mutator(test_plan(), null_mutator) try: EchoRE(pln) except SnowFlake as ex: assert ex.args[0] == 'this one'
def test_insert_after(): def target(): yield Msg('a', None) ret = yield Msg('TARGET', None) yield Msg('b', None) assert ret is not None assert ret.command == 'TARGET' return ret def insert_after(msg): if msg.command == 'TARGET': def post(): yield Msg('post', None) return None, post() else: return None, None EchoRE(plan_mutator(target(), insert_after))
def test_insert_after(): def target(): yield Msg('a', None) ret = yield Msg('TARGET', None) yield Msg('b', None) assert ret is not None assert ret.command == 'TARGET' return ret def insert_after(msg): if msg.command == 'TARGET': def post(): yield Msg('post', None) return None, post() else: return None, None ret = EchoRE(plan_mutator(target(), insert_after))
def test_insert_before(): def target(): yield Msg('a', None) ret = yield Msg('TARGET', None) yield Msg('b', None) assert ret.command == 'TARGET' return ret return ret def insert_before(msg): if msg.command == 'TARGET': def pre(): yield Msg('pre', None) ret = yield msg assert ret is not None assert ret.command == 'TARGET' return ret return pre(), None else: return None, None EchoRE(plan_mutator(target(), insert_before))
def test_insert_before(): def target(): yield Msg('a', None) ret = yield Msg('TARGET', None) yield Msg('b', None) assert ret.command == 'TARGET' return ret return ret def insert_before(msg): if msg.command == 'TARGET': def pre(): yield Msg('pre', None) ret = yield msg assert ret is not None assert ret.command == 'TARGET' return ret return pre(), None else: return None, None ret = EchoRE(plan_mutator(target(), insert_before))
def tseries_gas_plan(detectors, gas_in, exp_time, delay=1, num_exp=1, rga_masses=default_mass_list): """ tseries-type scan with rga gas reading Parameters ---------- detectors: list List of detectors will be triggered and recored. gas_in : string e.g., 'He', default is 'He' These gas must be in `gas.gas_list` but they may be in any order. exp_time : float, optional exposure time in seconds num_exp : integer, optional number of exposures delay : float, optional delay between exposures in seconds rga_masses: list, optional a list of rga masses appearing in a live table Example: >>> plan = tseries_gas_plan([pe1c, rga], 'He', 5, 10, 2) >>> xrun(<sample ind>, plan) """ ## configure hints on gas device configure_gas_mass_hint(rga_masses) ## switch gas yield from set_gas(gas_in) # configure the exposure time first (num_frame, acq_time, computed_exposure) = yield from _configure_area_det(exp_time) real_delay = max(0, delay - computed_exposure) period = max(computed_exposure, real_delay + computed_exposure) print('INFO: requested delay = {}s -> computed delay = {}s'.format( delay, real_delay)) print('INFO: nominal period (neglecting readout overheads) of {} s'.format( period)) xpdacq_md = { 'sp_time_per_frame': acq_time, 'sp_num_frames': num_frame, 'sp_requested_exposure': exp_time, 'sp_computed_exposure': computed_exposure, 'sp_plan_name': 'tseries' } plan = bp.count(detectors, num_exp, delay, md=xpdacq_md) plan = bpp.subs_wrapper(plan, LiveTable(detectors)) def inner_shutter_control(msg): if msg.command == 'trigger': def inner(): yield from open_shutter_stub() yield msg return inner(), None elif msg.command == 'save': return None, close_shutter_stub() else: return None, None plan = bpp.plan_mutator(plan, inner_shutter_control) yield from plan
def flash_step(VIT_table, total_exposure, md, *, dets=None, delay=1, mm_mode='Current', per_step=bps.trigger_and_read, control_shutter=True): """ Run a step-series of current/voltage. The current/voltage profile will look something like: ┌┐_┌┐_┌┐_ Parameters ---------- VIT_table : pd.DataFrame The required columns are {"I", "V", "t"} which are the current, voltage, and hold time respectively. total_exposure : float The total exposure time for the detector. This is set via _configure_area_detector which is implicitly coupled to xpd_configuration['area_det'] md : dict The metadata to put into the runstart. Will have some defaults added dets : List[OphydObj], optional The detectors to trigger at each point. If None, defaults to:: [xpd_configuration['area_det']] delay : float, optional The time lag between subsequent data acquisition mm_mode : {'Current', 'Voltage'}, optional The thing to measure from the Keithly multimeter. per_step : Callable[List[OphydObj], Optional[str]] -> Generator[Msg], optional The inner-most data acquisition loop. This plan will be repeated as many times as possible (with the target *delay* between starting the plan. If the plan take longer than *delay* to run it will immediately be restarted. control_shutter : bool, optional If the plan should try to open and close the shutter defaults to True """ if total_exposure > delay: raise RuntimeError(f"You asked for total_exposure={total_exposure} " f"with a delay of delay={delay} which is less ") if dets is None: dets = [xpd_configuration['area_det']] all_dets = dets + [flash_power, eurotherm] req_cols = ['I', 'V', 't'] if not all(k in VIT_table for k in req_cols): raise ValueError(f"input table must have {req_cols}") monitor_during = yield from _setup_mm(mm_mode) VIT_table = pd.DataFrame(VIT_table) # TODO put in default meta-data md.setdefault('hints', {}) md['hints'].setdefault('dimensions', [(('time', ), 'primary')]) md['plan_name'] = 'flash_step' md['plan_args'] = { 'VIT_table': {k: v.values for k, v in VIT_table.items()}, 'delay': delay, 'total_exposure': total_exposure } md['detectors'] = [det.name for det in dets] @subs_decorator(bec) # paranoia to be very sure we turn the PSU off @finalize_decorator(lambda: bps.mov(flash_power.enabled, 0)) # this arms the detectors @stage_decorator(all_dets) # this sets up the monitors of the multi-meter @monitor_during_decorator(monitor_during) # this opens the run and puts the meta-data in it @run_decorator(md=md) def flash_step_field_inner(): # set everything to zero at the top yield from bps.mv(flash_power.current_sp, 0, flash_power.voltage_sp, 0) # put in "Duty Cycle" mode so current changes immediately yield from bps.mv(flash_power.mode, 'Duty-Cycle') # take a measurement on the way in yield from per_step(all_dets, 'primary') # turn it on! yield from bps.mv(flash_power.enabled, 1) last_I = last_V = np.nan for _, row in VIT_table.iterrows(): tau = row['t'] exposure_count = int(max(1, tau // delay)) print('initial per step call') yield from per_step(all_dets, 'primary') next_I = row['I'] next_V = row['V'] print('set IV') if next_I != last_I: yield from bps.mv(flash_power.current_sp, next_I) last_I = next_I if next_V != last_V: yield from bps.mv(flash_power.voltage_sp, next_V) last_V = next_V print('finsh setting IV') deadline = time.monotonic() + tau yield from _inner_loop(all_dets, exposure_count, delay, deadline, per_step, 'primary') print('finished inner loop call') print('final shot') # take a measurement on the way out yield from per_step(all_dets, 'primary') print('turning off') # turn it off! # there are several other places we turn this off, but better safe yield from bps.mv(flash_power.enabled, 0) # HACK to get at global state yield from _configure_area_det(total_exposure) plan = flash_step_field_inner() if control_shutter: return (yield from bpp.plan_mutator(plan, inner_shutter_control)) else: return (yield from plan)
def ttseries(dets, temp_setpoint, exposure, delay, num, auto_shutter=True, manual_set=False): """ Set a target temperature. Make time series scan with area detector during the ramping and holding. Since abs_set is used, please do not set the temperature through CSstudio when the plan is running. Parameters ---------- dets : list list of 'readable' objects. default to area detector linked to xpdAcq. temp_setpoint: float A temperature set point. If None, do not set the temperature. exposure : float The exposure time at each reading from area detector in seconds delay : float The period of time between the starting points of two consecutive readings from area detector in seconds num : int The total number of readings auto_shutter: bool Option on whether delegates shutter control to ``xpdAcq``. If True, following behavior will take place: `` open shutter - collect data - close shutter `` To make shutter stay open during ``tseries`` scan, pass ``False`` to this argument. See ``Notes`` below for more detailed information. manual_set : bool Option on whether to manual set the temperature set point outside the plan. If True, no temperature will be set in plan. Examples -------- To see which area detector and shutter will be used, type the following commands: >>> xpd_configuration['area_det'] >>> xpd_configuration['shutter'] >>> xpd_configuration['temp_controller'] To override default behavior and keep the shutter open throughout scan, create ScanPlan with following syntax: >>> ScanPlan(bt, ttseries, 300, 10, 20, 10, False) """ area_det = xpd_configuration["area_det"] temp_controller = xpd_configuration["temp_controller"] md = { "sp_type": "ttseries", "sp_uid": str(uuid.uuid4()), "sp_plan_name": "ttseries", "temp_setpoint": temp_setpoint } # calculate number of frames exposure_md = tl.calc_exposure(area_det, exposure) md.update(exposure_md) # calculate the real delay and period computed_exposure = exposure_md.get("sp_computed_exposure") delay_md = tl.calc_delay(delay, computed_exposure, num) md.update(delay_md) # make the count plan real_delay = delay_md.get('sp_computed_delay') plan = count([area_det, temp_controller], num, real_delay, md=md) plan = subs_wrapper(plan, LiveTable([temp_controller])) # open and close the shutter for each count if auto_shutter: plan = plan_mutator(plan, tl.inner_shutter_control) # yield messages yield from tl.configure_area_det(area_det, md) if not manual_set: yield from abs_set(temp_controller, temp_setpoint, wait=False) yield from plan
def daq_step_plan(): return (yield from bpp.plan_mutator(plan, daq_mutator))
def tseries(dets, exposure, delay, num, auto_shutter=True): """ time series scan with area detector. Parameters ---------- dets : list list of 'readable' objects. default to area detector linked to xpdAcq. exposure : float exposure time at each reading from area detector in seconds delay : float delay between two consecutive readings from area detector in seconds num : int total number of readings auto_shutter: bool, optional Option on whether delegates shutter control to ``xpdAcq``. If True, following behavior will take place: `` open shutter - collect data - close shutter `` To make shutter stay open during ``tseries`` scan, pass ``False`` to this argument. See ``Notes`` below for more detailed information. Notes ----- To see which area detector and shutter will be used, type the following commands: >>> xpd_configuration['area_det'] >>> xpd_configuration['shutter'] To override default behavior and keep the shutter open throughout scan , create ScanPlan with following syntax: >>> ScanPlan(bt, tseries, 10, 5, 10, False) """ pe1c, = dets md = {} # setting up area_detector area_det = xpd_configuration["area_det"] (num_frame, acq_time, computed_exposure) = yield from _configure_area_det( exposure ) real_delay = max(0, delay - computed_exposure) period = max(computed_exposure, real_delay + computed_exposure) print( "INFO: requested delay = {}s -> computed delay = {}s".format( delay, real_delay ) ) print( "INFO: nominal period (neglecting readout overheads) of {} s".format( period ) ) # update md _md = ChainMap( md, { "sp_time_per_frame": acq_time, "sp_num_frames": num_frame, "sp_requested_exposure": exposure, "sp_computed_exposure": computed_exposure, "sp_requested_delay": delay, "sp_requested_num": num, "sp_type": "tseries", # need a name that shows all parameters values # 'sp_name': 'tseries_<exposure_time>', "sp_uid": str(uuid.uuid4()), "sp_plan_name": "tseries", }, ) plan = bp.count([area_det], num, delay, md=_md) plan = bpp.subs_wrapper(plan, LiveTable([])) def inner_shutter_control(msg): if msg.command == "trigger": def inner(): yield from open_shutter_stub() yield msg return inner(), None elif msg.command == "save": return None, close_shutter_stub() else: return None, None if auto_shutter: plan = bpp.plan_mutator(plan, inner_shutter_control) yield from plan
def flash_ramp(start_I, stop_I, ramp_rate, voltage, total_exposure, md, *, dets=None, delay=1, mm_mode='Current', hold_time=0, per_step=bps.trigger_and_read, control_shutter=True): """ Run a current ramp The current profile will look something like: /| Parameters ---------- start_I, stop_I : float The start and end points of the current ramp In mA ramp_rate : float The rate of current change. In mA/min voltage : float The voltage limit through the current ramp. total_exposure : float The total exposure time for the detector. This is set via _configure_area_detector which is implicitly coupled to xpd_configuration['area_det'] md : dict The metadata to put into the runstart. Will have some defaults added dets : List[OphydObj], optional The detectors to trigger at each point. If None, defaults to:: [xpd_configuration['area_det']] delay : float, optional The time lag between subsequent data acquisition mm_mode : {'Current', 'Voltage'}, optional The thing to measure from the Keithly multimeter. hold_time : float, optional How long to hold at the top of the ramp, defalts to 0 per_step : Callable[List[OphydObj], Optional[str]] -> Generator[Msg], optional The inner-most data acquisition loop. This plan will be repeated as many times as possible (with the target *delay* between starting the plan. If the plan take longer than *delay* to run it will immediately be restarted. control_shutter : bool, optional If the plan should try to open and close the shutter defaults to True """ if dets is None: dets = [xpd_configuration['area_det']] if total_exposure > delay: raise RuntimeError(f"You asked for total_exposure={total_exposure} " f"with a delay of delay={delay} which is less ") # mA -> A start_I = start_I / 1000 stop_I = stop_I / 1000 if stop_I < start_I: raise ValueError("IOC can not ramp backwards") fudge_factor = 1 ramp_rate *= fudge_factor all_dets = dets + [flash_power, eurotherm] monitor_during = yield from _setup_mm(mm_mode) expected_time = abs( (stop_I - start_I) / (ramp_rate / (fudge_factor * 60 * 1000))) exposure_count = int(max(1, expected_time // delay)) md.setdefault('hints', {}) md['hints'].setdefault('dimensions', [(('time', ), 'primary')]) md['plan_name'] = 'flash_ramp' md['plan_args'] = { 'start_I': start_I, 'stop_I': stop_I, 'ramp_rate': ramp_rate, 'voltage': voltage, 'delay': delay, 'total_exposure': total_exposure } md['detectors'] = [det.name for det in dets] @subs_decorator(bec) # paranoia to be very sure we turn the PSU off @finalize_decorator(lambda: bps.mov(flash_power.enabled, 0)) # this arms the detectors @stage_decorator(all_dets) # this sets up the monitors of the multi-meter @monitor_during_decorator(monitor_during) # this opens the run and puts the meta-data in it @run_decorator(md=md) def flash_ramp_inner(): # set everything to zero at the top yield from bps.mv(flash_power.current_sp, 0, flash_power.voltage_sp, 0, flash_power.ramp_rate, ramp_rate) # put in "Duty Cycle" mode so current changes immediately yield from bps.mv(flash_power.mode, 'Duty-Cycle') # take one shot on the way in yield from per_step(all_dets, 'primary') # turn it on! yield from bps.mv(flash_power.enabled, 1) # TODO # what voltage limit to start with ?! yield from bps.mv(flash_power.current_sp, start_I, flash_power.voltage_sp, voltage) # put in "Current Ramp" to start the ramp yield from bps.mv(flash_power.mode, 'Current Ramping') # set the target to let it go gid = short_uid() yield from bps.abs_set(flash_power.current_sp, stop_I, group=gid) yield from bps.mv(flash_power.voltage_sp, voltage) yield from _inner_loop(all_dets, exposure_count, delay, time.monotonic() + expected_time * 1.1, per_step, 'primary', done_signal=flash_power.ramp_done) if hold_time > 0: yield from _inner_loop(all_dets, int(max(1, hold_time // delay)), delay, time.monotonic() + hold_time, per_step, 'primary') # take one shot on the way out yield from per_step(all_dets, 'primary') yield from bps.wait(gid) # turn it off! # there are several other places we turn this off, but better safe yield from bps.mv(flash_power.enabled, 0) # HACK to get at global state yield from _configure_area_det(total_exposure) plan = flash_ramp_inner() if control_shutter: return (yield from bpp.plan_mutator(plan, inner_shutter_control)) else: return (yield from plan)
def autoplan(bt: Beamtime, sample_index, plan_index, wait_time=30., auto_shutter=False): """ Yield messages to count the predefined measurement plan on the a list of samples on a sample rack. It requires the following information to be added for each sample. position_x The x position of the sample in mm. position_y The y position of the sample in mm. wait_time The waiting time between the end of the former plan and the start of the latter plan in second. Parameters ---------- bt: Beamtime The Beamtime instance that contains the sample information. sample_index : List[int] A list of the sample index in the BeamTime instance. plan_index: List[int] A list of the plan index in the BeamTime instance. wait_time : float Waiting time before conduct plan for each sample in second. auto_shutter : bool Whether to mutate the plan with inner_shutter_control. Yields ------ Msg Messages of the plan. Examples -------- Add position controller to the xpd_configuration. >>> xpd_configuration["posx_controller"] = Grid_X >>> xpd_configuration["posy_controller"] = Grid_Y Register the scan plan to the beamtime. >>> ScanPlan(bt, ct, 30) Add the information of 'position_x', 'position_y' and 'wait_time' to the excel and import. Automatically conduct the scan plan for sample No.0 and No.1 >>> plan = autoplan(bt, [0, 1]) >>> xrun({}, plan) """ posx_controller = xpd_configuration["posx_controller"] posy_controller = xpd_configuration["posy_controller"] for sample_ind, plan_ind in zip(sample_index, plan_index): sample = translate_to_sample(bt, int(sample_ind)) posx = mg.get_from_sample(sample, "position_x") posy = mg.get_from_sample(sample, "position_y") count_plan = mg.translate_to_plan(bt, int(plan_ind), sample) if auto_shutter: count_plan = plan_mutator(count_plan, inner_shutter_control) if posx and posy and count_plan: yield from checkpoint() print(f"INFO: Move to x: {posx}") yield from mv(posx_controller, float(posx)) yield from checkpoint() print(f"INFO: Move to y: {posy}") yield from mv(posy_controller, float(posy)) yield from checkpoint() yield from wait() print(f"INFO: Wait for {wait_time} s") yield from sleep(float(wait_time)) yield from checkpoint() yield from count_plan