def _collect_img(exposure, dark_sub_bool, sample_md, tag, RE_instance, *, calibrant=None, detector=None): """helper function to collect image and return it""" # grab beamtime object linked to run_engine bto = RE_instance.beamtime plan = ScanPlan(bto, ct, exposure).factory() sample_md.update(bto) if tag == "calib": # instantiate Calibrant class calibrant_obj = Calibration(calibrant=calibrant).calibrant if calibrant_obj is None: raise xpdAcqException("Invalid calibrant") dSpacing = calibrant_obj.dSpacing if not dSpacing: raise xpdAcqException("empty dSpacing from calibrant") sample_md.update({"dSpacing": dSpacing, "detector": detector}) plan = bpp.msg_mutator(plan, _inject_calibration_tag) # collect image uid = RE_instance(sample_md, plan) """
def inject_metadata(plan: typing.Generator, metadata: dict) -> typing.Generator: """Inject the metadata into a plan.""" def _inject_metadata(msg: Msg): """Inject metadata in the start of the run.""" if msg.command == "open_run": msg.kwargs.update(**metadata) return msg plan1 = bpp.msg_mutator(plan, _inject_metadata) return plan1
def test_simple_replace(): new_cmd = 'replaced' def change_command(msg): return msg._replace(command=new_cmd) num = 10 msgs = EchoRE(msg_mutator(echo_plan(num=num), change_command)) _verify_msg_seq(msgs, m_len=num, cmd_sq=[new_cmd] * num, args_sq=[()] * num, kwargs_sq=[{}] * num)
def test_simple_replace(): new_cmd = 'replaced' def change_command(msg): return msg._replace(command=new_cmd) num = 10 msgs = EchoRE(msg_mutator(echo_plan(num=num), change_command)) _verify_msg_seq(msgs, m_len=num, cmd_sq=[new_cmd]*num, args_sq=[()]*num, kwargs_sq=[{}]*num)
def inner(): # Run plan (stripping open/close run messages) yield from msg_mutator(plan, block_run_control) # Yield result of Lorentz model logger.debug(model.result.fit_report()) max_position = model.result.values['center'] # Check that the estimated position is reasonable if not bounds[0] < max_position < bounds[1]: raise ValueError("Predicted maximum position of {} is outside the " "bounds {}".format(max_position, bounds)) # Order move to maximum position logger.debug("Travelling to maximum of Lorentz at %s", max_position) yield from abs_set(motor, model.result.values['center'], wait=True)
def test_msg_mutator_skip(): def skipper(msg): if msg.command == 'SKIP': return None return msg def skip_plan(): for c in 'abcd': yield Msg(c, None) yield Msg('SKIP', None) pln = msg_mutator(skip_plan(), skipper) msgs = EchoRE(pln) _verify_msg_seq(msgs, m_len=4, cmd_sq='abcd', args_sq=[()]*4, kwargs_sq=[{}]*4)
def test_msg_mutator_skip(): def skipper(msg): if msg.command == 'SKIP': return None return msg def skip_plan(): for c in 'abcd': yield Msg(c, None) yield Msg('SKIP', None) pln = msg_mutator(skip_plan(), skipper) msgs = EchoRE(pln) _verify_msg_seq(msgs, m_len=4, cmd_sq='abcd', args_sq=[()] * 4, kwargs_sq=[{}] * 4)
def _collect_img(exposure, dark_sub_bool, sample_md, RE_instance, *, calibrant=None, detector=None): """helper function to collect image and return it""" # grab beamtime object linked to run_engine bto = RE_instance.beamtime plan = ScanPlan(bto, ct, exposure).factory() sample_md.update(bto) dSpacing = find_dspacing(calibrant) sample_md.update({"dSpacing": dSpacing, "detector": detector}) plan = bpp.msg_mutator(plan, _inject_calibration_tag) # collect image RE_instance(sample_md, plan)
def _collect_img(exposure, dark_sub_bool, sample_md, tag, RE_instance, *, calibrant=None, detector=None): """helper function to collect image and return it""" # grab beamtime object linked to run_engine bto = RE_instance.beamtime plan = ScanPlan(bto, ct, exposure).factory() sample_md.update(bto) if tag == "calib": # instantiate Calibrant class calibrant_obj = Calibration(calibrant=calibrant).calibrant if calibrant_obj is None: raise xpdAcqException("Invalid calibrant") dSpacing = calibrant_obj.dSpacing if not dSpacing: raise xpdAcqException("empty dSpacing from calibrant") sample_md.update({"dSpacing": dSpacing, "detector": detector}) plan = bpp.msg_mutator(plan, _inject_calibration_tag) # collect image uid = RE_instance(sample_md, plan) # last one must be light db = xpd_configuration["db"] light_header = db[uid[-1]] dark_uid = light_header["start"].get("sc_dk_field_uid") dark_header = db[dark_uid] dark_img = dark_header.data(glbl["image_field"]) dark_img = np.asarray(next(dark_img)).squeeze() img = light_header.data(glbl["image_field"]) img = np.asarray(next(img)).squeeze() if dark_sub_bool: img -= dark_img # FIXME: filename template from xpdAn fn_template = "from_calib_func_{}.poni".format(_timestampstr(time.time())) return img, fn_template
def translate_to_plan(self, plan, sample): """Translate a plan input into a generator Parameters ---------- sample : list of int or dict-like Sample metadata. If a beamtime object is linked, an integer will be interpreted as the index appears in the ``bt.list()`` method, corresponding metadata will be passed. A customized dict can also be passed as the sample metadata. plan : list of int or generator Scan plan. If a beamtime object is linked, an integer will be interpreted as the index appears in the ``bt.list()`` method, corresponding scan plan will be A generator or that yields ``Msg`` objects (or an iterable that returns such a generator) can also be passed. Returns ------- plan : generator The generator of messages for the plan """ if isinstance(plan, list): plan = [self.translate_to_plan(p, s) for p, s in zip(plan, sample)] # If a plan is given as a int, look in up in the global registry. else: if isinstance(plan, int): try: plan = list(self.beamtime.scanplans.values())[plan] except IndexError: print("WARNING: hmm, there is no scanplan with index `{}`" ", please do `bt.list()` to check if it exists yet". format(plan)) return # If the plan is an xpdAcq 'ScanPlan', make the actual plan. if isinstance(plan, ScanPlan): plan = plan.factory() mm = _sample_injector_factory(sample) plan = bpp.msg_mutator(plan, mm) return plan
def __call__(self, sample, plan, subs=None, *, verify_write=False, dark_strategy=periodic_dark, robot=False, **metadata_kw): """ Execute a plan Any keyword arguments other than those listed below will be interpreted as metadata and recorded with the run. Parameters ---------- sample : int or dict-like or list of int or dict-like Sample metadata. If a beamtime object is linked, an integer will be interpreted as the index appears in the ``bt.list()`` method, corresponding metadata will be passed. A customized dict can also be passed as the sample metadata. plan : int or generator or list of int or generator Scan plan. If a beamtime object is linked, an integer will be interpreted as the index appears in the ``bt.list()`` method, corresponding scan plan will be A generator or that yields ``Msg`` objects (or an iterable that returns such a generator) can also be passed. subs: callable, list, or dict, optional Temporary subscriptions (a.k.a. callbacks) to be used on this run. Default to None. For convenience, any of the following are accepted: * a callable, which will be subscribed to 'all' * a list of callables, which again will be subscribed to 'all' * a dictionary, mapping specific subscriptions to callables or lists of callables; valid keys are {'all', 'start', 'stop', 'event', 'descriptor'} verify_write: bool, optional Double check if the data have been written into database. In general data is written in a lossless fashion at the NSLS-II. Therefore, False by default. dark_strategy: callable, optional. Protocol of taking dark frame during experiment. Default to the logic of matching dark frame and light frame with the sample exposure time and frame rate. Details can be found at ``http://xpdacq.github.io/xpdAcq/usb_Running.html#automated-dark-collection`` robot: bool, optional If true run the scan as a robot scan, defaults to False metadata_kw: Extra keyword arguments for specifying metadata in the run time. If the extra metdata has the same key as the ``sample``, ``ValueError`` will be raised. Returns ------- uids : list list of uids (i.e. RunStart Document uids) of run(s) """ if self.md.get("robot", None) is not None: raise RuntimeError("Robot must be specified at call time, not in" "global metadata") if robot: metadata_kw.update(robot=True) # The CustomizedRunEngine knows about a Beamtime object, and it # interprets integers for 'sample' as indexes into the Beamtime's # lists of Samples from all its Experiments. # Turn everything into lists sample, plan = self._normalize_sample_plan(sample, plan) # Turn ints into actual samples sample = self.translate_to_sample(sample) if robot: print("This is the current experimental plan:") print("Sample Name: Sample Position") for s, p in [ (k, [o[1] for o in v]) for k, v in groupby(zip(sample, plan), key=lambda x: x[0]) ]: print( s["sample_name"], ":", self._beamtime.robot_info[s["sa_uid"]], ) for pp in p: # Check if this is a registered scanplan if isinstance(pp, int): print( indent( "{}".format( list( self.beamtime.scanplans.values())[pp]), "\t", )) else: print("This scan is not a registered scanplan so no " "summary") ip = input("Is this ok? [y]/n") if ip.lower() == "n": return # Turn ints into generators plan = self.translate_to_plan(plan, sample) # Collect the plans by contiguous samples and chain them sample, plan = zip( *[(k, pchain(*[o[1] for o in v])) for k, v in groupby(zip(sample, plan), key=lambda x: x[0])]) # Make the complete plan by chaining the chained plans total_plan = [] for s, p in zip(sample, plan): if robot: # If robot scan inject the needed md into the sample s.update(self._beamtime.robot_info[s["sa_uid"]]) total_plan.append(robot_wrapper(p, s)) else: total_plan.append(p) plan = pchain(*total_plan) _subs = normalize_subs_input(subs) if verify_write: _subs.update({"stop": verify_files_saved}) if self._beamtime and self._beamtime.get("bt_wavelength") is None: print("WARNING: there is no wavelength information in current" "beamtime object, scan will keep going....") if glbl["shutter_control"]: # Alter the plan to incorporate dark frames. # only works if user allows shutter control if glbl["auto_dark"]: plan = dark_strategy(plan) plan = bpp.msg_mutator(plan, _inject_qualified_dark_frame_uid) # force to close shutter after scan plan = bpp.finalize_wrapper( plan, bps.abs_set( xpd_configuration["shutter"], XPD_SHUTTER_CONF["close"], wait=True, ), ) # Load calibration file if glbl["auto_load_calib"]: plan = bpp.msg_mutator(plan, _inject_calibration_md) # Insert xpdacq md version plan = bpp.msg_mutator(plan, _inject_xpdacq_md_version) # Insert analysis stage tag plan = bpp.msg_mutator(plan, _inject_analysis_stage) # Insert filter metadata plan = bpp.msg_mutator(plan, _inject_filter_positions) # Execute return super().__call__(plan, subs, **metadata_kw)
def xpdacq_composer(beamtime: Beamtime, sample: typing.Union[int, dict, typing.List[int]], plan: typing.Union[int, Generator, typing.List[int], typing.Generator], *, robot: bool = False, shutter_control: typing.Tuple[Device, typing.Any] = None, dark_strategy: typing.Callable = None, auto_load_calib: bool = False) -> typing.Generator: """Create a list of plans for an xpd experiment. Used in `~xpdacq.xpdacq.CumstomeizedRunEngine.__call__`. Parameters ---------- beamtime : The beamtime object. sample : If a beamtime object is linked, an integer will be interpreted as the index appears in the ``bt.list()`` method, corresponding metadata will be passed. A customized dict can also be passed as the sample metadata. plan : Scan plan. If a beamtime object is linked, an integer will be interpreted as the index appears in the ``bt.list()`` method, corresponding scan plan will be A generator or that yields ``Msg`` objects (or an iterable that returns such a generator) can also be passed. robot : If True, the plan is meant to be using robot. shutter_control : A tuple of the shutter device and its close state. The shutter will be closed after the whole scan. If None, shutter won't be controlled and no dark will be taken. dark_strategy : The strategy how to take the dark frame. auto_load_calib : If True, the calibration meta-data will be injected into the run. Else, do nothing. Returns ------- grand_plan : The grand plan to be run by the RunEngine. """ # check wavelength warn_wavelength(beamtime) # noramlize the sample and plan to two lists with the same length lst_sample, lst_plan = _normalize_sample_plan(sample, plan) # Turn ints into actual sample dictionary lst_metadata = [translate_to_sample(beamtime, s) for s in lst_sample] # Turn ints into bluesky generators lst_bp_plan = [translate_to_plan(beamtime, p) for p in lst_plan] # Make the complete plan by chaining the chained plans if robot: lst_bp_plan = gen_robot_plans(beamtime, lst_metadata, lst_bp_plan) # shutter control and dark if shutter_control: shutter, close_state = shutter_control # Alter the plan to incorporate dark frames. if dark_strategy: lst_bp_plan = [dark_strategy(p) for p in lst_bp_plan] lst_bp_plan = [ bpp.msg_mutator(p, _inject_qualified_dark_frame_uid) for p in lst_bp_plan ] # force to close shutter after scan lst_bp_plan = [ close_shutter_at_last(p, shutter, close_state) for p in lst_bp_plan ] # Load calibration file if auto_load_calib: lst_bp_plan = [ bpp.msg_mutator(p, _inject_calibration_md) for p in lst_bp_plan ] # Insert xpdacq md version lst_bp_plan = [ bpp.msg_mutator(p, _inject_xpdacq_md_version) for p in lst_bp_plan ] # Insert analysis stage tag lst_bp_plan = [ bpp.msg_mutator(p, _inject_analysis_stage) for p in lst_bp_plan ] # Insert filter metadata lst_bp_plan = [ bpp.msg_mutator(p, _inject_filter_positions) for p in lst_bp_plan ] # Inject the sample metadata lst_bp_plan = [ inject_metadata(p, s) for p, s in zip(lst_bp_plan, lst_metadata) ] return pchain(*lst_bp_plan)