Example #1
0
    def __call__(self, *args, subs=None, sub_factories=None, **kwargs):
        scan_kwargs = dict()
        # Any kwargs valid for the scan go to the scan, not the RE.
        for k, v in kwargs.copy().items():
            if k in self.params:
                scan_kwargs[k] = kwargs.pop(k)
        from bluesky.global_state import gs

        RE_params = list(signature(gs.RE.__call__).parameters.keys())
        if set(RE_params) & set(self.params):
            raise AssertionError("The names of the scan's arguments clash "
                                 "the RunEngine arguments. Use different "
                                 "names. Avoid: {0}".format(RE_params))

        self.scan = self.scan_class(gs.DETS, *args, **scan_kwargs)
        # Combine subs passed as args and subs set up in subs attribute.
        _subs = defaultdict(list)
        _update_lists(_subs, normalize_subs_input(subs))
        _update_lists(_subs, normalize_subs_input(self.subs))
        # Create a sub from each sub_factory.
        _update_lists(_subs, _run_factories(sub_factories, self.scan))
        _update_lists(_subs, _run_factories(self.sub_factories, self.scan))

        # Set up scan attributes.
        self.scan.configuration = self.configuration
        self.scan.flyers = self.flyers

        # Any remainging kwargs go the RE. To be safe, no args are passed
        # to RE; RE args effectively become keyword-only arguments.
        return gs.RE(self.scan, _subs, **kwargs)
Example #2
0
    def __call__(self, *args, subs=None, sub_factories=None, **kwargs):
        scan_kwargs = dict()
        # Any kwargs valid for the scan go to the scan, not the RE.
        for k, v in kwargs.copy().items():
            if k in self.params:
                scan_kwargs[k] = kwargs.pop(k)
        from bluesky.global_state import gs

        RE_params = list(signature(gs.RE.__call__).parameters.keys())
        if set(RE_params) & set(self.params):
            raise AssertionError("The names of the scan's arguments clash "
                                 "the RunEngine arguments. Use different "
                                 "names. Avoid: {0}".format(RE_params))

        self.scan = self.scan_class(gs.DETS, *args, **scan_kwargs)
        # Combine subs passed as args and subs set up in subs attribute.
        _subs = defaultdict(list)
        _update_lists(_subs, normalize_subs_input(subs))
        _update_lists(_subs, normalize_subs_input(self.subs))
        # Create a sub from each sub_factory.
        _update_lists(_subs, _run_factories(sub_factories, self.scan))
        _update_lists(_subs, _run_factories(self.sub_factories, self.scan))

        # Set up scan attributes.
        self.scan.configuration = self.configuration
        self.scan.flyers = self.flyers

        # Any remainging kwargs go the RE. To be safe, no args are passed
        # to RE; RE args effectively become keyword-only arguments.
        return gs.RE(self.scan, _subs, **kwargs)
Example #3
0
def construct_subs(plan_name, **kwargs):
    '''Construct the subscriptions functions from factories.


    This is a small dependency injection tool to construct subscriptions
    based on `plan_name` by consulting `gs.SUB_FACTORIES` and combining
    `'common'` and `plan_name` lists.

    The values in `**kwargs` are injected into the factories as needed
    by consulting the signature.

    Parameters
    ----------
    plan_name : str
        The key used to get the subscription factories

    Returns
    -------
    subs : dict
       A dictionary suitable for handing to the RE or to the sub_wrapper
       plan.
    '''
    factories = gs.SUB_FACTORIES.get('common', [])
    factories.extend(gs.SUB_FACTORIES.get(plan_name, []))

    kwargs.setdefault('gs', gs)
    ret = normalize_subs_input(None)
    for factory in factories:
        try:
            inp = get_factory_input(factory)
        except InvalidFactory as e:
            warnings.warn('factory {fn} could not be run due to {e!r}'.format(
                fn=factory.__name__, e=e))
        else:
            fact_kwargs = {}
            missing_kwargs = set()
            for k in inp.req:
                try:
                    fact_kwargs[k] = kwargs[k]
                except KeyError:
                    missing_kwargs.add(k)
            if missing_kwargs:
                warnings.warn('The factory {fn} could not be run due to '
                              'missing '
                              'required input: {missing!r}'.format(
                                  fn=factory.__name__, missing=missing_kwargs))
                continue
            for k in inp.opt:
                try:
                    fact_kwargs[k] = kwargs[k]
                except KeyError:
                    pass

            update_sub_lists(ret,
                             normalize_subs_input(factory(**fact_kwargs)))

    return ret
Example #4
0
def construct_subs(plan_name, **kwargs):
    '''Construct the subscriptions functions from factories.


    This is a small dependency injection tool to construct subscriptions
    based on `plan_name` by consulting `gs.SUB_FACTORIES` and combining
    `'common'` and `plan_name` lists.

    The values in `**kwargs` are injected into the factories as needed
    by consulting the signature.

    Parameters
    ----------
    plan_name : str
        The key used to get the subscription factories

    Returns
    -------
    subs : dict
       A dictionary suitable for handing to the RE or to the sub_wrapper
       plan.
    '''
    factories = gs.SUB_FACTORIES.get('common', [])
    factories.extend(gs.SUB_FACTORIES.get(plan_name, []))

    kwargs.setdefault('gs', gs)
    ret = normalize_subs_input(None)
    for factory in factories:
        try:
            inp = get_factory_input(factory)
        except InvalidFactory as e:
            warnings.warn('factory {fn} could not be run due to {e!r}'.format(
                fn=factory.__name__, e=e))
        else:
            fact_kwargs = {}
            missing_kwargs = set()
            for k in inp.req:
                try:
                    fact_kwargs[k] = kwargs[k]
                except KeyError:
                    missing_kwargs.add(k)
            if missing_kwargs:
                warnings.warn('The factory {fn} could not be run due to '
                              'missing '
                              'required input: {missing!r}'.format(
                                  fn=factory.__name__, missing=missing_kwargs))
                continue
            for k in inp.opt:
                try:
                    fact_kwargs[k] = kwargs[k]
                except KeyError:
                    pass

            update_sub_lists(ret,
                             normalize_subs_input(factory(**fact_kwargs)))

    return ret
Example #5
0
def _run_factories(factories, scan):
    factories = normalize_subs_input(factories)
    out = {k: list(filterfalse(lambda x: x is None,
                               (sf(scan) for sf in v)))
           for k, v in factories.items()}
    gs._SECRET_STASH = out
    return out
Example #6
0
def _run_factories(factories, scan):
    '''Run sub factory functions for a scan

    Factory functions should return lists, which will be added onto the
    subscription key (e.g., 'all' or 'start') specified in the factory
    definition.

    If the factory function returns None, the list will not be modified.
    '''
    factories = normalize_subs_input(factories)
    out = {k: list(filterfalse(lambda x: x is None,
                               (sf(scan) for sf in v)))
           for k, v in factories.items()}
    gs._SECRET_STASH = out
    return out
Example #7
0
def _run_factories(factories, scan):
    '''Run sub factory functions for a scan

    Factory functions should return lists, which will be added onto the
    subscription key (e.g., 'all' or 'start') specified in the factory
    definition.

    If the factory function returns None, the list will not be modified.
    '''
    factories = normalize_subs_input(factories)
    out = {
        k: list(filterfalse(lambda x: x is None, (sf(scan) for sf in v)))
        for k, v in factories.items()
    }
    gs._SECRET_STASH = out
    return out
Example #8
0
    def __call__(self, sample, plan, subs=None, *,
                 verify_write=False, dark_strategy=periodic_dark,
                 raise_if_interrupted=False, **metadata_kw):
        # 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.

        # deprecated from v0.5 release
        #if getattr(glbl, 'collection', None) is None:
        #    raise RuntimeError("No collection has been linked to current "
        #                       "experiment yet.\nPlease do\n"
        #                       ">>> open_collection(<collection_name>)\n"
        #                       "before you run any prun")

        if isinstance(sample, int):
            try:
                sample = self.beamtime.samples[sample]
            except IndexError:
                print("WARNING: hmm, there is no sample with index `{}`"
                      ", please do `bt.list()` to check if it exists yet"
                      .format(sample))
                return
        # If a plan is given as a string, look in up in the global registry.
        if isinstance(plan, int):
            try:
                plan = self.beamtime.scanplans[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()
        _subs = normalize_subs_input(subs)
        if verify_write:
            _subs.update({'stop': verify_files_saved})
        # No keys in metadata_kw are allows to collide with sample keys.
        if set(sample) & set(metadata_kw):
            raise ValueError("These keys in metadata_kw are illegal "
                             "because they are always in sample: "
                             "{}".format(set(sample) & set(metadata_kw)))
        if self._beamtime.get('bt_wavelength') is None:
            print("WARNING: there is no wavelength information in current"
                  "beamtime object, scan will keep going....")
        metadata_kw.update(sample)
        sh = glbl.shutter
        # force to open shutter before scan and close it after
        if glbl.shutter_control:
            plan = bp.pchain(bp.abs_set(sh, 1), plan, bp.abs_set(sh, 0))
        # Alter the plan to incorporate dark frames.
        if glbl.auto_dark:
            plan = dark_strategy(plan)
            plan = bp.msg_mutator(plan, _inject_qualified_dark_frame_uid)
        # Load calibration file
        if glbl.auto_load_calib:
            plan = bp.msg_mutator(plan, _inject_calibration_md)
        # Execute
        super().__call__(plan, subs,
                         raise_if_interrupted=raise_if_interrupted,
                         **metadata_kw)

        # deprecated from v0.5 release
        # insert collection
        #_insert_collection(glbl.collection_name, glbl.collection,
        #                   self._run_start_uids)

        return self._run_start_uids
Example #9
0
    def __call__(self,
                 sample,
                 plan,
                 subs=None,
                 *,
                 verify_write=False,
                 dark_strategy=periodic_dark,
                 raise_if_interrupted=False,
                 **metadata_kw):
        # 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.

        # deprecated from v0.5 release
        #if getattr(glbl, 'collection', None) is None:
        #    raise RuntimeError("No collection has been linked to current "
        #                       "experiment yet.\nPlease do\n"
        #                       ">>> open_collection(<collection_name>)\n"
        #                       "before you run any xrun")

        if isinstance(sample, int):
            try:
                sample = self.beamtime.samples[sample]
            except IndexError:
                print(
                    "WARNING: hmm, there is no sample with index `{}`"
                    ", please do `bt.list()` to check if it exists yet".format(
                        sample))
                return
        # If a plan is given as a string, look in up in the global registry.
        if isinstance(plan, int):
            try:
                plan = self.beamtime.scanplans[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()
        _subs = normalize_subs_input(subs)
        if verify_write:
            _subs.update({'stop': verify_files_saved})
        # No keys in metadata_kw are allows to collide with sample keys.
        if set(sample) & set(metadata_kw):
            raise ValueError("These keys in metadata_kw are illegal "
                             "because they are always in sample: "
                             "{}".format(set(sample) & set(metadata_kw)))
        if self._beamtime.get('bt_wavelength') is None:
            print("WARNING: there is no wavelength information in current"
                  "beamtime object, scan will keep going....")
        metadata_kw.update(sample)
        sh = glbl.shutter

        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 = bp.msg_mutator(plan, _inject_qualified_dark_frame_uid)
            # force to close shutter after scan
            plan = bp.finalize_wrapper(plan, bp.abs_set(sh, 0, wait=True))

        # Load calibration file
        if glbl.auto_load_calib:
            plan = bp.msg_mutator(plan, _inject_calibration_md)

        # Insert glbl mask
        plan = bp.msg_mutator(plan, _inject_mask)

        # Execute
        return super().__call__(plan,
                                subs,
                                raise_if_interrupted=raise_if_interrupted,
                                **metadata_kw)
Example #10
0
    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)
Example #11
0
    def __call__(self,
                 sample: typing.Union[int, str, dict, list, tuple],
                 plan: typing.Union[int, str, typing.Generator, ScanPlan, list,
                                    tuple],
                 subs: typing.Union[typing.Callable, dict, list] = None,
                 *,
                 verify_write: bool = False,
                 dark_strategy: typing.Callable = periodic_dark,
                 robot: bool = False,
                 ask_before_run: bool = False,
                 **metadata_kw):
        """
        Execute a plan.

        The call is basically the same as the RE. The only difference is that the plan will be mutated and
        preprocessed to incorpate sample meta-data, dark fram stragy and so on. 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")
        dark_strategy = dark_strategy if glbl["auto_dark"] else None
        shutter_control = (
            xpd_configuration["shutter"],
            XPD_SHUTTER_CONF["close"]) if glbl["shutter_control"] else None
        # 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
        grand_plan = xpdacq_composer(self.beamtime,
                                     sample,
                                     plan,
                                     robot=robot,
                                     shutter_control=shutter_control,
                                     dark_strategy=dark_strategy,
                                     auto_load_calib=glbl['auto_load_calib'])
        # normalize the subs
        _subs = normalize_subs_input(subs) if subs else {}
        # verify writing files
        if verify_write:
            _subs.update({"stop": verify_files_saved})
        if robot:
            metadata_kw.update({'robot': True})
        if ask_before_run:
            ip = input("Is this ok? [y]/n")
            if ip.lower() == "n":
                return
        return super(CustomizedRunEngine,
                     self).__call__(grand_plan, _subs, **metadata_kw)