Perform a simple scan with a data table and plot

Problem

Move a motor in equally spaced steps, stopping at each position to acquire a reading from one or more detectors.

Note

Interested in a different kind of scan? A simple “count”? A more complex Fermat spiral? In the solution below, replace scan with one of these.

Approach

Set up a table and a plot, create a plan, and execute it. View the live stream of data on the table and the plot while it is collected.

Example Solution

As an example, we can use a simulated motor and detector. You can substitute any real motors or detectors that may already be defined in your configuration.

from bluesky.examples import motor, det

We will import a plan, which generates instructions to be executed. We’ll also import LiveTable and LivePlot. Typically, this is done as part your configuration (your IPython profile). It happens automatically at startup and need not be typed every time.

from bluesky.plans import scan
from bluesky.callbacks import LiveTable, LivePlot

First, set up a table and a plot. The table takes a list of field names to show. The plot takes a field name for y followed (optionally) by a field name for x. (If only y is provided, it plots y against point number.)

In [1]: subs = [LiveTable(['motor', 'det']), LivePlot('det', 'motor')]

You can name the variable anything you like; we call it subs, short for “subscriptions.” They are functions that “subscribe” to the live stream of data from the scan.

We’ll scan motor from -10 to 10 inclusive, sampling 15 equally-spaced points, and reading detector det at each point. We specify this like so:

scan([det], motor, -10, 10, 15)

Notice that det is in square brackets because scan expects list of one or more detectors.

Finally, this command tells the RunEngine, RE, to execute our scan and send the data to subs.

In [2]: RE(scan([det], motor, -10, 10, 15), subs)
+-----------+------------+------------+------------+
|   seq_num |       time |      motor |        det |
+-----------+------------+------------+------------+
|         1 | 17:04:06.4 |     -10.00 |       0.00 |
|         2 | 17:04:06.5 |      -8.57 |       0.00 |
|         3 | 17:04:06.5 |      -7.14 |       0.00 |
|         4 | 17:04:06.6 |      -5.71 |       0.00 |
|         5 | 17:04:06.6 |      -4.29 |       0.00 |
|         6 | 17:04:06.7 |      -2.86 |       0.02 |
|         7 | 17:04:06.7 |      -1.43 |       0.36 |
|         8 | 17:04:06.8 |       0.00 |       1.00 |
|         9 | 17:04:06.8 |       1.43 |       0.36 |
|        10 | 17:04:06.9 |       2.86 |       0.02 |
|        11 | 17:04:06.9 |       4.29 |       0.00 |
|        12 | 17:04:07.0 |       5.71 |       0.00 |
|        13 | 17:04:07.0 |       7.14 |       0.00 |
|        14 | 17:04:07.1 |       8.57 |       0.00 |
|        15 | 17:04:07.1 |      10.00 |       0.00 |
+-----------+------------+------------+------------+
generator scan ['94f215'] (scan num: 1)
Out[2]: ['94f2156b-fcde-4136-b449-e7cc44f97450']

This table prints line by line while the scan is executed. The bottom of the table shows additional information, the data type and name of the plan, the unique ID of the run, and an integer scan ID.

Note

Since other users can reset the scan ID to 1, you should not rely on it as a long-term reference to your data. Scribble down the first 5 or 6 characters of the unique ID instead.

While the table prints line by line, points appear on a plot one at a time. This is the final result:

../_images/simple-scan1.png

Let’s look again at the output from our command. As shown above, it was

['73b23bd3-54ca-40a4-9a5f-46486e8f3a91']

It looks like gibberish, but it’s useful: it is a list of the unique ID(s) of the run(s) generated by the plan, and it allows usage like this:

run_ids = RE(scan([det], motor, -10, 10, 15), subs)  # assign ids to a variable
some_function_that_analyzes_or_exports_data(run_ids)