Example #1
0
def macro_sweep_test(target):
    logging.info(
        'macro_sweep_test initiated with target {:0.4f}'.format(target))
    RE = RunEngine({})
    bec = BestEffortCallback()
    RE.subscribe(bec)
    RE.waiting_hook = ProgressBarManager()
    RE(run_wrapper(rel_smooth_sweep_test(tst_23, target)))
Example #2
0
def pbar_manager_for_notebook(delay_draw: float = 0.2) -> ProgressBarManager:
    """
    Helper method for generating managers when using notebooks.

    Returns
    -------
    ProgressBarManager
        A manager that creates progress bars for Jupuyter notebooks
    """

    return ProgressBarManager(
        partial(NotebookProgressBar, delay_draw=delay_draw))
Example #3
0
def macro_RSXS_smooth_sweep(stroke_height,
                            stroke_spacing,
                            n_strokes,
                            both_directions=True):
    """
    macro_RSXS_smooth_sweep

    This method wraps up the bluesky/ophyd codeand allows users to drive
    the LU20 experiment with minimal code overhead. It contains the following
    bluesky plan.

    This bluesky plan moves a 2-axis actuator across multiple traversals of a
    sample. The plan traverses the entirety of the stroke_height (y-axis) and
    after each traversal, steps in the x-axis by the stroke_spacing.It may be
    configured to scan in only a single direction and shutter the beam for the
    opposite direction. This removes the shutter at the beginning of the plan
    and reinserts it at the end. At the end of the plan, the sample is moved to
    its original y-axis position but with an x-axis posiiton ready for the next
    run. For more details about the path, see the documentation of the
    xy_sequencer, the method that generates the sample's path. 

    Parameters 
    ----------
    stroke_height : float
        Vertical distance (y-axs) of each stroke.

    stroke_spacing : float
        Horizontal distance between individual strokes.
    
    n_strokes : int
        Number of strokes to complete.

    both_directions : bool, optional
        Defaults to True. If this value is true the beam will be scanned across
        the sample while moving in both vertical directions. If false, the beam
        is only scanned in a single direction.
    """

    RE = RunEngine({})
    bec = BestEffortCallback()
    RE.subscribe(bec)
    RE.waiting_hook = ProgressBarManager()
    RE(
        run_wrapper(
            rel_smooth_sweep(mot_x=rsxs_sample_x,
                             mot_y=rsxs_sample_y,
                             shutter=shutter,
                             stroke_height=stroke_height,
                             stroke_spacing=stroke_spacing,
                             n_strokes=n_strokes,
                             both_directions=both_directions)))
def test_mv_progress(RE, hw):
    motor1 = hw.motor1
    motor2 = hw.motor2

    RE.waiting_hook = ProgressBarManager()

    # moving time > delay_draw
    motor1.delay = 0.5
    motor1.delay = 0.5
    RE(mv(motor1, 0, motor2, 0))

    # moving time < delay_draw
    motor1.delay = 0.01
    motor1.delay = 0.01
    RE(mv(motor1, 0, motor2, 0))
Example #5
0
def test_mv_progress(fresh_RE):
    RE = fresh_RE
    RE.waiting_hook = ProgressBarManager()
    motor1 = Mover('motor1', OrderedDict([('motor1', lambda x: x),
                                        ('motor1_setpoint', lambda x: x)]),
                {'x': 0})
    motor2 = Mover('motor2', OrderedDict([('motor2', lambda x: x),
                                        ('motor2_setpoint', lambda x: x)]),
                {'x': 0})

    assert RE.waiting_hook.delay_draw == 0.2

    # moving time > delay_draw
    motor1._fake_sleep = 0.5
    motor1._fake_sleep = 0.5
    RE(mv(motor1, 0, motor2, 0))

    # moving time < delay_draw
    motor1._fake_sleep = 0.01
    motor1._fake_sleep = 0.01
    RE(mv(motor1, 0, motor2, 0))
Example #6
0
# keep track of callback subscriptions
callback_db = {}

# set up databroker
import databroker
db = databroker.Broker.named('mongoCat')
callback_db['Broker'] = RE.subscribe(db.insert)

# Set up SupplementalData.
from bluesky import SupplementalData
sd = SupplementalData()
RE.preprocessors.append(sd)

# Add a progress bar.
from bluesky.utils import ProgressBarManager
pbar_manager = ProgressBarManager()
RE.waiting_hook = pbar_manager

# Register bluesky IPython magics.
from IPython import get_ipython
from bluesky.magics import BlueskyMagics
get_ipython().register_magics(BlueskyMagics)
get_ipython().magic("automagic 0") # Turn off automagic

# Set up the BestEffortCallback.
from bluesky.callbacks.best_effort import BestEffortCallback
bec = BestEffortCallback()
callback_db['BestEffortCallback'] = RE.subscribe(bec)
peaks = bec.peaks  # just as alias for less typing
bec.disable_baseline()
Example #7
0
def macro_VT50_smooth_sweep(short_edge_end,
                            long_edge_end,
                            n_strokes,
                            scalar=1.0,
                            min_base=.05,
                            min_v=.07,
                            both_directions=True):
    """
    macro_RSXS_smooth_sweep

    This method wraps up the bluesky/ophyd codeand allows users to drive
    the LT00 experiment with minimal code overhead. It contains the following
    bluesky plan.

    This bluesky plan moves a 3-axis actuator across multiple traversals of a
    sample. The plan traverses the space enclosed in a plane defined by the
    motors' starting location and the two positions short_edge_end and
    long_edge_end. 
    

    Parameters 
    ----------
    short_edge_end : tuple or np.array
        3-length (x,y,z) iteralbe specifying the end location of the short
        steps.
    
    long_edge_end : tuple or np.array
        3-length (x,y,z) iteralbe specifying the end location of the long
        sweep.
    
    n_strokes : int
        Number of strokes to complete.

    scalar : float
        Scale the motor velocities by this factor. Defaults to 1.0.

    min_base : float
        Set motor's Base velocity. Larger numbers means the motor accelerates
        and decelerates more quickly. This value must be less than min_v.
        Values larger than 2 are not recommended.

    min_v : float 
        Set the motor's minimum velocity. Must be larger than min_base. Larger
        numbers means the motor accelerates and decelerates more quickly.
        Values larger than 2 are not recommended. 
    """

    RE = RunEngine({})
    bec = BestEffortCallback()
    RE.subscribe(bec)
    RE.waiting_hook = ProgressBarManager()
    RE(
        run_wrapper(
            rel_smooth_sweep(mot_x=sample_x,
                             mot_y=sample_y,
                             mot_z=sample_z,
                             shutter=shutter,
                             short_edge_end=short_edge_end,
                             long_edge_end=long_edge_end,
                             n_strokes=n_strokes,
                             scalar=scalar,
                             min_base=min_base,
                             min_v=min_v)))
Example #8
0
get_ipython().magic("matplotlib qt")

# Create a databroker backed by temporary files
db = Broker.named("mycat")


def spy(name, doc):
    pass


# Insert all metadata/data captured into db.
RE.subscribe(db.insert)
RE.subscribe(spy)

# Make a progress bar
RE.waiting_hook = ProgressBarManager()

# Running in simulation mode?
SIM_MODE = True

with SignalCollector(), NamedDevices(), TmpFilenameScheme():
    # All Signals with a sim:// prefix or without a prefix will come from this provider
    if SIM_MODE:
        sim = SignalCollector.add_provider(sim=SimProvider(), set_default=True)
    else:
        # Do something like this here
        # ca = SignalCollector.add_provider(ca=CAProvider(), set_default=True)
        pass
    # A PMAC has a trajectory scan interface and 16 Co-ordinate systems
    # which may have motors in them
    pmac1 = pmac.PMAC("BLxxI-MO-PMAC-01:")
Example #9
0
def configure_base(user_ns,
                   broker_name,
                   *,
                   bec=True,
                   epics_context=True,
                   magics=True,
                   mpl=True,
                   ophyd_logging=True,
                   pbar=True):
    """
    Perform base setup and instantiation of important objects.

    This factory function instantiates the following and adds them to the
    namespace:

    * ``RE`` -- a RunEngine
    * ``db`` -- a Broker (from "databroker"), subscribe to ``RE``
    * ``bec`` -- a BestEffortCallback, subscribed to ``RE``
    * ``peaks`` -- an alias for ``bec.peaks``
    * ``sd`` -- a SupplementalData preprocessor, added to ``RE.preprocessors``
    * ``pbar_maanger`` -- a ProgressBarManager, set as the ``RE.waiting_hook``

    And it performs some low-level configuration:

    * creates a context in ophyd's control layer (``ophyd.setup_ophyd()``)
    * turns out interactive plotting (``matplotlib.pyplot.ion()``)
    * bridges the RunEngine and Qt event loops
      (``bluesky.utils.install_kicker()``)
    * logs ERROR-level log message from ophyd to the standard out

    Parameters
    ----------
    user_ns: dict
        a namespace --- for example, ``get_ipython().user_ns``
    broker_name : Union[str, Broker]
        Name of databroker configuration or a Broker instance.
    bec : boolean, optional
        True by default. Set False to skip BestEffortCallback.
    epics_context : boolean, optional
        True by default. Set False to skip ``setup_ophyd()``.
    magics : boolean, optional
        True by default. Set False to skip registration of custom IPython
        magics.
    mpl : boolean, optional
        True by default. Set False to skip matplotlib ``ion()`` at event-loop
        bridging.
    ophyd_logging : boolean, optional
        True by default. Set False to skip ERROR-level log configuration for
        ophyd.
    pbar : boolean, optional
        True by default. Set false to skip ProgressBarManager.

    Returns
    -------
    names : list
        list of names added to the namespace

    Examples
    --------
    Configure IPython for CHX.

    >>>> configure_base(get_ipython().user_ns, 'chx');
    """
    ns = {}  # We will update user_ns with this at the end.

    # Test if we are in Jupyter or IPython:
    in_jupyter = user_ns['get_ipython']().has_trait('kernel')

    # Set up a RunEngine and use metadata backed by a sqlite file.
    from bluesky import RunEngine
    from bluesky.utils import get_history
    # if RunEngine already defined grab it
    # useful when users make their own custom RunEngine
    if 'RE' in user_ns:
        RE = user_ns['RE']
    else:
        RE = RunEngine(get_history())
        ns['RE'] = RE

    # Set up SupplementalData.
    # (This is a no-op until devices are added to it,
    # so there is no need to provide a 'skip_sd' switch.)
    from bluesky import SupplementalData
    sd = SupplementalData()
    RE.preprocessors.append(sd)
    ns['sd'] = sd

    if isinstance(broker_name, str):
        # Set up a Broker.
        from databroker import Broker
        db = Broker.named(broker_name)
        ns['db'] = db
    else:
        db = broker_name

    RE.subscribe(db.insert)

    if pbar and not in_jupyter:
        # Add a progress bar.
        from bluesky.utils import ProgressBarManager
        pbar_manager = ProgressBarManager()
        RE.waiting_hook = pbar_manager
        ns['pbar_manager'] = pbar_manager

    if magics:
        # Register bluesky IPython magics.
        from bluesky.magics import BlueskyMagics
        get_ipython().register_magics(BlueskyMagics)

    if bec:
        # Set up the BestEffortCallback.
        from bluesky.callbacks.best_effort import BestEffortCallback
        _bec = BestEffortCallback()
        bec = _bec
        RE.subscribe(_bec)
        if in_jupyter:
            _bec.disable_plots()
        ns['bec'] = _bec
        ns['peaks'] = _bec.peaks  # just as alias for less typing

    if mpl:
        # Import matplotlib and put it in interactive mode.
        import matplotlib.pyplot as plt
        ns['plt'] = plt
        plt.ion()

        # Commented to allow more intelligent setting of kickers (for Jupyter and IPython):
        ## Make plots update live while scans run.
        # from bluesky.utils import install_kicker
        # install_kicker()

        # Make plots update live while scans run.
        if in_jupyter:
            from bluesky.utils import install_nb_kicker
            install_nb_kicker()
        else:
            from bluesky.utils import install_qt_kicker
            install_qt_kicker()

    if not ophyd_logging:
        # Turn on error-level logging, particularly useful for knowing when
        # pyepics callbacks fail.
        import logging
        import ophyd.ophydobj
        ch = logging.StreamHandler()
        ch.setLevel(logging.ERROR)
        ophyd.ophydobj.logger.addHandler(ch)

    # convenience imports
    # some of the * imports are for 'back-compatibility' of a sort -- we have
    # taught BL staff to expect LiveTable and LivePlot etc. to be in their
    # namespace
    import numpy as np
    ns['np'] = np

    import bluesky.callbacks
    ns['bc'] = bluesky.callbacks
    import_star(bluesky.callbacks, ns)

    import bluesky.plans
    ns['bp'] = bluesky.plans
    import_star(bluesky.plans, ns)

    import bluesky.plan_stubs
    ns['bps'] = bluesky.plan_stubs
    import_star(bluesky.plan_stubs, ns)
    # special-case the commonly-used mv / mvr and its aliases mov / movr4
    ns['mv'] = bluesky.plan_stubs.mv
    ns['mvr'] = bluesky.plan_stubs.mvr
    ns['mov'] = bluesky.plan_stubs.mov
    ns['movr'] = bluesky.plan_stubs.movr

    import bluesky.preprocessors
    ns['bpp'] = bluesky.preprocessors

    import bluesky.callbacks.broker
    import_star(bluesky.callbacks.broker, ns)
    import bluesky.simulators
    import_star(bluesky.simulators, ns)

    user_ns.update(ns)
    return list(ns)
Example #10
0
class BlueskyMagics(Magics, metaclass=MetaclassForClassProperties):
    """
    IPython magics for bluesky.

    To install:

    >>> ip = get_ipython()
    >>> ip.register_magics(BlueskyMagics)

    Optionally configure default detectors and positioners by setting
    the class attributes:

    * ``BlueskyMagics.detectors``
    * ``BlueskyMagics.positioners``

    For more advanced configuration, access the magic's RunEngine instance and
    ProgressBarManager instance:

    * ``BlueskyMagics.RE``
    * ``BlueskyMagics.pbar_manager``
    """
    RE = RunEngine({}, loop=asyncio.new_event_loop())
    pbar_manager = ProgressBarManager()

    def _ensure_idle(self):
        if self.RE.state != 'idle':
            print('The RunEngine invoked by magics cannot be resumed.')
            print('Aborting...')
            self.RE.abort()

    @line_magic
    def mov(self, line):
        if len(line.split()) % 2 != 0:
            raise TypeError("Wrong parameters. Expected: "
                            "%mov motor position (or several pairs like that)")
        args = []
        for motor, pos in partition(2, line.split()):
            args.append(eval(motor, self.shell.user_ns))
            args.append(eval(pos, self.shell.user_ns))
        plan = bps.mv(*args)
        self.RE.waiting_hook = self.pbar_manager
        try:
            self.RE(plan)
        except RunEngineInterrupted:
            ...
        self.RE.waiting_hook = None
        self._ensure_idle()
        return None

    @line_magic
    def movr(self, line):
        if len(line.split()) % 2 != 0:
            raise TypeError("Wrong parameters. Expected: "
                            "%mov motor position (or several pairs like that)")
        args = []
        for motor, pos in partition(2, line.split()):
            args.append(eval(motor, self.shell.user_ns))
            args.append(eval(pos, self.shell.user_ns))
        plan = bps.mvr(*args)
        self.RE.waiting_hook = self.pbar_manager
        try:
            self.RE(plan)
        except RunEngineInterrupted:
            ...
        self.RE.waiting_hook = None
        self._ensure_idle()
        return None

    @line_magic
    def ct(self, line):
        # If the deprecated BlueskyMagics.detectors list is non-empty, it has
        # been configured by the user, and we must revert to the old behavior.
        if type(self).detectors:
            if line.strip():
                dets = eval(line, self.shell.user_ns)
            else:
                dets = type(self).detectors
        else:
            # new behaviour
            devices_dict = get_labeled_devices(user_ns=self.shell.user_ns)
            if line.strip():
                if '[' in line or ']' in line:
                    raise ValueError("It looks like you entered a list like "
                                     "`%ct [motors, detectors]` "
                                     "Magics work a bit differently than "
                                     "normal Python. Enter "
                                     "*space-separated* labels like "
                                     "`%ct motors detectors`.")
                # User has provided a white list of labels like
                # %ct label1 label2
                labels = line.strip().split()
            else:
                labels = ['detectors']
            dets = []
            for label in labels:
                dets.extend(obj for _, obj in devices_dict.get(label, []))
        plan = bp.count(dets)
        print("[This data will not be saved. "
              "Use the RunEngine to collect data.]")
        try:
            self.RE(plan, _ct_callback)
        except RunEngineInterrupted:
            ...
        self._ensure_idle()
        return None

    FMT_PREC = 6

    @line_magic
    def wa(self, line):
        "List positioner info. 'wa' stands for 'where all'."
        # If the deprecated BlueskyMagics.positioners list is non-empty, it has
        # been configured by the user, and we must revert to the old behavior.
        if type(self).positioners:
            if line.strip():
                positioners = eval(line, self.shell.user_ns)
            else:
                positioners = type(self).positioners
            if len(positioners) > 0:
                _print_positioners(positioners, precision=self.FMT_PREC)
        else:
            # new behaviour
            devices_dict = get_labeled_devices(user_ns=self.shell.user_ns)
            if line.strip():
                if '[' in line or ']' in line:
                    raise ValueError("It looks like you entered a list like "
                                     "`%wa [motors, detectors]` "
                                     "Magics work a bit differently than "
                                     "normal Python. Enter "
                                     "*space-separated* labels like "
                                     "`%wa motors detectors`.")
                # User has provided a white list of labels like
                # %wa label1 label2
                labels = line.strip().split()
            else:
                # Show all labels.
                labels = list(devices_dict.keys())
            for label in labels:
                print(label)
                try:
                    devices = devices_dict[label]
                    all_children = [(k, getattr(obj, k)) for _, obj in devices
                                    for k in getattr(obj, 'read_attrs', [])]
                except KeyError:
                    print('<no matches for this label>')
                    continue
                # Search devices and all their children for positioners.
                positioners = [
                    dev for _, dev in devices + all_children
                    if is_positioner(dev)
                ]
                if positioners:
                    _print_positioners(positioners,
                                       precision=self.FMT_PREC,
                                       prefix=" " * 2)
                    print()  # blank line
                # Just display the top-level devices in the namespace (no
                # children).
                _print_devices(devices, prefix=" " * 2)
                print()  # blank line
Example #11
0
class BlueskyMagics(Magics):
    """
    IPython magics for bluesky.

    To install:

    >>> ip = get_ipython()
    >>> ip.register_magics(BlueskyMagics)

    Optionally configure default detectors and positioners by setting
    the class attributes:

    * ``BlueskyMagics.detectors``
    * ``BlueskyMagics.positioners``

    For more advanced configuration, access the magic's RunEngine instance and
    ProgressBarManager instance:

    * ``BlueskyMagics.RE``
    * ``BlueskyMagics.pbar_manager``
    """
    RE = RunEngine({}, loop=asyncio.new_event_loop())
    pbar_manager = ProgressBarManager()

    def _ensure_idle(self):
        if self.RE.state != 'idle':
            print('The RunEngine invoked by magics cannot be resumed.')
            print('Aborting...')
            self.RE.abort()

    @line_magic
    def mov(self, line):
        if len(line.split()) % 2 != 0:
            raise TypeError("Wrong parameters. Expected: "
                            "%mov motor position (or several pairs like that)")
        args = []
        for motor, pos in partition(2, line.split()):
            args.append(eval(motor, self.shell.user_ns))
            args.append(eval(pos, self.shell.user_ns))
        plan = bps.mv(*args)
        self.RE.waiting_hook = self.pbar_manager
        try:
            self.RE(plan)
        except RunEngineInterrupted:
            ...
        self.RE.waiting_hook = None
        self._ensure_idle()
        return None

    @line_magic
    def movr(self, line):
        if len(line.split()) % 2 != 0:
            raise TypeError("Wrong parameters. Expected: "
                            "%mov motor position (or several pairs like that)")
        args = []
        for motor, pos in partition(2, line.split()):
            args.append(eval(motor, self.shell.user_ns))
            args.append(eval(pos, self.shell.user_ns))
        plan = bps.mvr(*args)
        self.RE.waiting_hook = self.pbar_manager
        try:
            self.RE(plan)
        except RunEngineInterrupted:
            ...
        self.RE.waiting_hook = None
        self._ensure_idle()
        return None

    detectors = []

    @line_magic
    def ct(self, line):
        if line.strip():
            dets = eval(line, self.shell.user_ns)
        else:
            dets = self.detectors
        plan = bp.count(dets)
        print("[This data will not be saved. "
              "Use the RunEngine to collect data.]")
        try:
            self.RE(plan, _ct_callback)
        except RunEngineInterrupted:
            ...
        self._ensure_idle()
        return None

    positioners = []
    FMT_PREC = 6

    @line_magic
    def wa(self, line):
        "List positioner info. 'wa' stands for 'where all'."
        if line.strip():
            positioners = eval(line, self.shell.user_ns)
        else:
            positioners = self.positioners
        positioners = sorted(set(positioners), key=attrgetter('name'))
        values = []
        for p in positioners:
            try:
                values.append(p.position)
            except Exception as exc:
                values.append(exc)

        headers = ['Positioner', 'Value', 'Low Limit', 'High Limit', 'Offset']
        LINE_FMT = '{: <30} {: <11} {: <11} {: <11} {: <11}'
        lines = []
        lines.append(LINE_FMT.format(*headers))
        for p, v in zip(positioners, values):
            if not isinstance(v, Exception):
                try:
                    prec = p.precision
                except Exception:
                    prec = self.FMT_PREC
                value = np.round(v, decimals=prec)
                try:
                    low_limit, high_limit = p.limits
                except Exception as exc:
                    low_limit = high_limit = exc.__class__.__name__
                else:
                    low_limit = np.round(low_limit, decimals=prec)
                    high_limit = np.round(high_limit, decimals=prec)
                try:
                    offset = p.user_offset.get()
                except Exception as exc:
                    offset = exc.__class__.__name__
                else:
                    offset = np.round(offset, decimals=prec)
            else:
                value = v.__class__.__name__  # e.g. 'DisconnectedError'
                low_limit = high_limit = offset = ''

            lines.append(LINE_FMT.format(p.name, value, low_limit, high_limit,
                                         offset))
        print('\n'.join(lines))
Example #12
0
def configure_base(
    user_ns,
    broker_name,
    *,
    bec=True,
    epics_context=False,
    magics=True,
    mpl=True,
    configure_logging=True,
    pbar=True,
    ipython_exc_logging=True,
):
    """
    Perform base setup and instantiation of important objects.

    This factory function instantiates essential objects to data collection
    environments at NSLS-II and adds them to the current namespace. In some
    cases (documented below), it will check whether certain variables already
    exist in the user name space, and will avoid creating them if so. The
    following are added:

    * ``RE`` -- a RunEngine
        This is created only if an ``RE`` instance does not currently exist in
        the namespace.
    * ``db`` -- a Broker (from "databroker"), subscribe to ``RE``
    * ``bec`` -- a BestEffortCallback, subscribed to ``RE``
    * ``peaks`` -- an alias for ``bec.peaks``
    * ``sd`` -- a SupplementalData preprocessor, added to ``RE.preprocessors``
    * ``pbar_maanger`` -- a ProgressBarManager, set as the ``RE.waiting_hook``

    And it performs some low-level configuration:

    * creates a context in ophyd's control layer (``ophyd.setup_ophyd()``)
    * turns on interactive plotting (``matplotlib.pyplot.ion()``)
    * bridges the RunEngine and Qt event loops
      (``bluesky.utils.install_kicker()``)
    * logs ERROR-level log message from ophyd to the standard out

    Parameters
    ----------
    user_ns: dict
        a namespace --- for example, ``get_ipython().user_ns``
    broker_name : Union[str, Broker]
        Name of databroker configuration or a Broker instance.
    bec : boolean, optional
        True by default. Set False to skip BestEffortCallback.
    epics_context : boolean, optional
        True by default. Set False to skip ``setup_ophyd()``.
    magics : boolean, optional
        True by default. Set False to skip registration of custom IPython
        magics.
    mpl : boolean, optional
        True by default. Set False to skip matplotlib ``ion()`` at event-loop
        bridging.
    configure_logging : boolean, optional
        True by default. Set False to skip INFO-level logging to
        /var/logs/bluesky/bluesky.log.
    pbar : boolean, optional
        True by default. Set false to skip ProgressBarManager.
    ipython_exc_logging : boolean, optional
        True by default. Exception stack traces will be written to IPython
        log file when IPython logging is enabled.

    Returns
    -------
    names : list
        list of names added to the namespace

    Examples
    --------
    Configure IPython for CHX.

    >>>> configure_base(get_ipython().user_ns, 'chx');
    """
    ns = {}  # We will update user_ns with this at the end.
    # Protect against double-subscription.
    SENTINEL = "__nslsii_configure_base_has_been_run"
    if user_ns.get(SENTINEL):
        raise RuntimeError(
            "configure_base should only be called once per process.")
    ns[SENTINEL] = True
    # Set up a RunEngine and use metadata backed by files on disk.
    from bluesky import RunEngine, __version__ as bluesky_version

    if LooseVersion(bluesky_version) >= LooseVersion("1.6.0"):
        # current approach using PersistentDict
        from bluesky.utils import PersistentDict

        directory = os.path.expanduser("~/.config/bluesky/md")
        os.makedirs(directory, exist_ok=True)
        md = PersistentDict(directory)
    else:
        # legacy approach using HistoryDict
        from bluesky.utils import get_history

        md = get_history()
    # if RunEngine already defined grab it
    # useful when users make their own custom RunEngine
    if "RE" in user_ns:
        RE = user_ns["RE"]
    else:
        RE = RunEngine(md)
        ns["RE"] = RE

    # Set up SupplementalData.
    # (This is a no-op until devices are added to it,
    # so there is no need to provide a 'skip_sd' switch.)
    from bluesky import SupplementalData

    sd = SupplementalData()
    RE.preprocessors.append(sd)
    ns["sd"] = sd

    if isinstance(broker_name, str):
        # Set up a Broker.
        from databroker import Broker

        db = Broker.named(broker_name)
        ns["db"] = db
    else:
        db = broker_name

    RE.subscribe(db.insert)

    if pbar:
        # Add a progress bar.
        from bluesky.utils import ProgressBarManager

        pbar_manager = ProgressBarManager()
        RE.waiting_hook = pbar_manager
        ns["pbar_manager"] = pbar_manager

    if magics:
        # Register bluesky IPython magics.
        from bluesky.magics import BlueskyMagics

        get_ipython().register_magics(BlueskyMagics)

    if bec:
        # Set up the BestEffortCallback.
        from bluesky.callbacks.best_effort import BestEffortCallback

        _bec = BestEffortCallback()
        RE.subscribe(_bec)
        ns["bec"] = _bec
        ns["peaks"] = _bec.peaks  # just as alias for less typing

    if mpl:
        # Import matplotlib and put it in interactive mode.
        import matplotlib.pyplot as plt

        ns["plt"] = plt
        plt.ion()

        # Make plots update live while scans run.
        if LooseVersion(bluesky_version) < LooseVersion("1.6.0"):
            from bluesky.utils import install_kicker

            install_kicker()

    if epics_context:
        # Create a context in the underlying EPICS client.
        from ophyd import setup_ophyd

        setup_ophyd()

    if configure_logging:
        configure_bluesky_logging(ipython=get_ipython())

    if ipython_exc_logging:
        # IPython logging must be enabled separately
        from nslsii.common.ipynb.logutils import log_exception

        configure_ipython_exc_logging(exception_logger=log_exception,
                                      ipython=get_ipython())

    # always configure %xmode minimal
    get_ipython().magic("xmode minimal")

    # convenience imports
    # some of the * imports are for 'back-compatibility' of a sort -- we have
    # taught BL staff to expect LiveTable and LivePlot etc. to be in their
    # namespace
    import numpy as np

    ns["np"] = np

    import bluesky.callbacks

    ns["bc"] = bluesky.callbacks
    import_star(bluesky.callbacks, ns)

    import bluesky.plans

    ns["bp"] = bluesky.plans
    import_star(bluesky.plans, ns)

    import bluesky.plan_stubs

    ns["bps"] = bluesky.plan_stubs
    import_star(bluesky.plan_stubs, ns)
    # special-case the commonly-used mv / mvr and its aliases mov / movr4
    ns["mv"] = bluesky.plan_stubs.mv
    ns["mvr"] = bluesky.plan_stubs.mvr
    ns["mov"] = bluesky.plan_stubs.mov
    ns["movr"] = bluesky.plan_stubs.movr

    import bluesky.preprocessors

    ns["bpp"] = bluesky.preprocessors

    import bluesky.callbacks.broker

    import_star(bluesky.callbacks.broker, ns)

    import bluesky.simulators

    import_star(bluesky.simulators, ns)

    user_ns.update(ns)
    return list(ns)