IPython ‘Magics’ [Experimental]

Warning

This section covers an experimental feature of bluesky. It may be altered or removed in the future.

What Are ‘Magics’?

IPython is an interactive Python interpreter designed for and by scientists. It includes a feature called “magics” — convenience commands that aren’t part of the Python language itself. For example, %history is a magic:

In [1]: a = 1

In [2]: b = 2

In [3]: %history
a = 1
b = 2
%history

The IPython documentation documents the complete list of built-in magics and, further, how to define custom magics.

Bluesky Magics

Bundled with bluesky are some IPython magics. They are intended for maintenance tasks or casual sanity checks. Intentionally, none of the magics save data; for that you should use the RunEngine and plans.

To use the magics, first register them with IPython:

In [4]: from bluesky.magics import BlueskyMagics

In [5]: get_ipython().register_magics(BlueskyMagics)

For this example we’ll use some simulated hardware.

In [6]: from ophyd.sim import motor1, motor2

Moving a Motor

Suppose you want to move a motor interactively. You can use the %mov magic:

In [7]: %mov motor1 42

Where motor1 refers to the actual ophyd object itself. This is equivanent to:

from bluesky.plan_stubs import mv

RE(mv(motor1, 42))

but less to type. There is also a %movr magic for “relative move”. They can move multiple devices in parallel like so:

In [8]: %mov motor1 -3 motor2 3

Note: Magics has changed from version v1.3.0 onwards. The previous method will be described in the next section.

Taking a reading using %ct (Post v1.3.0)

Before we may make use of the power of magics for counting, we must “label” this hardware. To add a label, we must give hardware a labels={'mylabel'} keyword argument. For example, here we initialize five simulated signals: two motors, a shutter motor, an area detector and a point detector:

In [9]: import numpy as np

In [10]: from ophyd.sim import SynAxis, SynSignal

In [11]: motor1 = SynAxis(name='motor1', labels={'motors', 'scan_motors'})

In [12]: motor2 = SynAxis(name='motor2', labels={'motors', 'scan_motors'})

In [13]: shutter_motor = SynAxis(name='shutter_motor', labels={'motors', 'shutter_motors'})

# create a fake area detector that returns a 2x2 array
In [14]: area_detector = SynSignal(func=lambda: np.random.random((2, 2)),
   ....:                           name='adet1', labels={'detectors', 'area_detectors'})
   ....: 

In [15]: point_detector = SynSignal(func=lambda: np.random.random((1,)),
   ....:                            name='pointdet1', labels={'detectors', 'point_detectors'})
   ....: 

Now we have detectors and motors, with proper labels.

Now suppose you want to take a quick reading of some devices and print the results to the screen without saving them or doing any fancy processing. Use the %ct magic:

In [16]: %ct area_detectors
[This data will not be saved. Use the RunEngine to collect data.]
adet1                          [[0.12863167 0.41513498]
 [0.32127453 0.18288686]]

Where the names after count are a list of whitespace separated labels. In this case, only area_detector will be counted.

Running %ct without arguments looks for the detectors label by default:

In [17]: %ct
[This data will not be saved. Use the RunEngine to collect data.]
adet1                          [[0.02266023 0.71185793]
 [0.77089951 0.2637542 ]]
det                            3.726653172078671e-06
det1                           7.614989872356314e-08
det2                           1.2130613194252668
pointdet1                      [0.67355025]

In this case, we count both on the area detector and the point detector.

Aside on the automagic feature in IPython

If IPython’s ‘automagic’ feature is enabled, IPython will even let you drop the % as long as the meaning is unambiguous:

In [18]: ct
[This data will not be saved. Use the RunEngine to collect data.]
adet1                          [[0.58453971 0.81334754]
 [0.31308556 0.57760651]]
det                            3.726653172078671e-06
det1                           7.614989872356314e-08
det2                           1.2130613194252668
pointdet1                      [0.48505754]

In [19]: ct = 3  # Now ct is a variable so automagic will not work...

In [20]: ct
Out[20]: 3

# ... but the magic still works.
In [21]: %ct
[This data will not be saved. Use the RunEngine to collect data.]
adet1                          [[0.88809859 0.83198707]
 [0.01396825 0.63369614]]
det                            3.726653172078671e-06
det1                           7.614989872356314e-08
det2                           1.2130613194252668
pointdet1                      [0.57421545]

For what it’s worth, we recommend disabling ‘automagic’. The % is useful for flagging what follows as magical, non-Python code.

Listing available motors using %wa (Post v1.3.0)

Finally, the %wa magic displays the a list of labeled devices.

In [22]: %wa scan_motors
scan_motors
  Positioner                     Value       Low Limit   High Limit  Offset     
  motor1                         0           AttributeError AttributeError AttributeError
  motor2                         0           AttributeError AttributeError AttributeError

  Local variable name                    Ophyd name (to be recorded as metadata)
  motor1                                 motor1                                
  motor2                                 motor2                                

will display all motors used for a scan. If blank, will print all labeled devices.

In [23]: %wa
motors
  Positioner                     Value       Low Limit   High Limit  Offset     
  motor                          5.0         AttributeError AttributeError AttributeError
  motor1                         0           AttributeError AttributeError AttributeError
  motor2                         0           AttributeError AttributeError AttributeError
  shutter_motor                  0           AttributeError AttributeError AttributeError

  Local variable name                    Ophyd name (to be recorded as metadata)
  motor                                  motor                                 
  motor1                                 motor1                                
  motor2                                 motor2                                
  shutter_motor                          shutter_motor                         

detectors
  Local variable name                    Ophyd name (to be recorded as metadata)
  area_detector                          adet1                                 
  det                                    det                                   
  det1                                   det1                                  
  det2                                   det2                                  
  point_detector                         pointdet1                             

scan_motors
  Positioner                     Value       Low Limit   High Limit  Offset     
  motor1                         0           AttributeError AttributeError AttributeError
  motor2                         0           AttributeError AttributeError AttributeError

  Local variable name                    Ophyd name (to be recorded as metadata)
  motor1                                 motor1                                
  motor2                                 motor2                                

shutter_motors
  Positioner                     Value       Low Limit   High Limit  Offset     
  shutter_motor                  0           AttributeError AttributeError AttributeError

  Local variable name                    Ophyd name (to be recorded as metadata)
  shutter_motor                          shutter_motor                         

area_detectors
  Local variable name                    Ophyd name (to be recorded as metadata)
  area_detector                          adet1                                 

point_detectors
  Local variable name                    Ophyd name (to be recorded as metadata)
  point_detector                         pointdet1                             

Note: It is possible to give a device more than one label. Thus it is possible to have the same device in more than one list when calling %wa. It is up to the user to decide whether they want overlapping labels or not.

Comparison with SPEC

The names of these magics, and the order of the parameters they take, are meant to feel familiar to users of SPEC.

Again, they must be registered with IPython before they can be used:

from bluesky.magics import BlueskyMagics
get_ipython().register_magics(BlueskyMagics)

Taking a reading using %ct (Pre v1.3.0)

Previously, you could set a default list of detectors and them use %ct without any parameters. This behaviour is deprecated. Do not use this:

In [24]: BlueskyMagics.detectors = [area_detector, point_detector]

In [25]: %ct
[This data will not be saved. Use the RunEngine to collect data.]
adet1                          [[0.97838333 0.40968088]
 [0.26235569 0.09169873]]
pointdet1                      [0.3095653]

This is no longer supported.

Listing available motors using %wa (Pre v1.3.0)

Previously, it was possible to supply a list of motors. This feature is also deprecated. Do not use this:

In [26]: BlueskyMagics.positioners = [motor1, motor2]

In [27]: %wa
Positioner                     Value       Low Limit   High Limit  Offset     
motor1                         0           AttributeError AttributeError AttributeError
motor2                         0           AttributeError AttributeError AttributeError
Magic Plan Invoked
%mov mv()
%movr mvr()
%ct count()
%wa (“where all”) Survey positioners*