Lineup a 1-D peak : scan detector v motor#

From APS Python Training for Bluesky Data Acquisition.

Objective

Line up a motor m1 to a simulated diffraction peak noisy. Also monitor a simulated temperature signal in a separate stream.

About the noisy peak#

In this lesson, alignment to a narrow diffraction peak is using capabilities provided by the ophyd package. The simulation consists of a simulated motor and simulated noisy detector.

The noisy detector is configured to describe a narrow diffraction peak with Gaussian profile based on the value of the motor position. The peak is centered randomly somewhere between motor values -1 and +1. The width is less than 0.05 in the same units. The peak intensity is expected to be approximately 100,000 (counts/sec are typical units).

The noisy peak simulation (computed using EPICS PV gp:userCalc1) has these parameters:

parameter

description

peak shape

Lorentzian as function of m1.position

center

randomly-placed between -1 .. +1

width

randomly-selected, at most 0.15 (motor units)

scale (height)

randomly-selected, between 10,000 .. 100,000 (counts)

counting noise

randomly-selected, between .01 .. .09 of scale (counts)

The calculation updates noisy when a new m1.position (gp:m1.RBV) value is reported to gp:userCalc1.

Since this detector is actually an EPICS calculation using the swait record, the concept of counting time was not built into the simulation.

Since we know the peak is somewhere between -1 .. +1, we can limit the scan range. To ensure the scan crosses the peak, we increase the scan range a bit, to -2 .. 2.

Normally, we’d simulate a more narrow diffraction peak but the resolution of these motors is the limiting factor.

Start the instrument package#

Our instrument package is in the bluesky subdirectory here so we add that to the search path before importing it.

[1]:
import pathlib, sys
sys.path.append(str(pathlib.Path.home() / "bluesky"))
from instrument.collection import *
/home/prjemian/bluesky/instrument/_iconfig.py
Activating auto-logging. Current session state plus future input saved.
Filename       : /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/docs/source/howto/.logs/ipython_console.log
Mode           : rotate
Output logging : True
Raw input log  : False
Timestamping   : True
State          : active
I Thu-17:00:57 - ############################################################ startup
I Thu-17:00:57 - logging started
I Thu-17:00:57 - logging level = 10
I Thu-17:00:57 - /home/prjemian/bluesky/instrument/session_logs.py
I Thu-17:00:57 - /home/prjemian/bluesky/instrument/collection.py
I Thu-17:00:57 - CONDA_PREFIX = /home/prjemian/.conda/envs/bluesky_2023_2
Exception reporting mode: Minimal
I Thu-17:00:57 - xmode exception level: 'Minimal'
I Thu-17:00:57 - /home/prjemian/bluesky/instrument/mpl/notebook.py
I Thu-17:00:57 - #### Bluesky Framework ####
I Thu-17:00:57 - /home/prjemian/bluesky/instrument/framework/check_python.py
I Thu-17:00:57 - /home/prjemian/bluesky/instrument/framework/check_bluesky.py
I Thu-17:00:57 - /home/prjemian/bluesky/instrument/framework/initialize.py
I Thu-17:00:57 - RunEngine metadata saved in directory: /home/prjemian/Bluesky_RunEngine_md
I Thu-17:00:57 - using databroker catalog 'training'
I Thu-17:00:57 - using ophyd control layer: pyepics
I Thu-17:00:57 - /home/prjemian/bluesky/instrument/framework/metadata.py
I Thu-17:00:57 - /home/prjemian/bluesky/instrument/epics_signal_config.py
I Thu-17:00:57 - Using RunEngine metadata for scan_id
I Thu-17:00:58 - #### Devices ####
I Thu-17:00:58 - /home/prjemian/bluesky/instrument/devices/area_detector.py
I Thu-17:00:58 - /home/prjemian/bluesky/instrument/devices/calculation_records.py
I Thu-17:01:00 - /home/prjemian/bluesky/instrument/devices/fourc_diffractometer.py
I Thu-17:01:00 - /home/prjemian/bluesky/instrument/devices/ioc_stats.py
I Thu-17:01:00 - /home/prjemian/bluesky/instrument/devices/kohzu_monochromator.py
I Thu-17:01:00 - /home/prjemian/bluesky/instrument/devices/motors.py
I Thu-17:01:00 - /home/prjemian/bluesky/instrument/devices/noisy_detector.py
I Thu-17:01:01 - /home/prjemian/bluesky/instrument/devices/scaler.py
I Thu-17:01:01 - /home/prjemian/bluesky/instrument/devices/shutter_simulator.py
I Thu-17:01:01 - /home/prjemian/bluesky/instrument/devices/simulated_fourc.py
I Thu-17:01:02 - /home/prjemian/bluesky/instrument/devices/simulated_kappa.py
I Thu-17:01:02 - /home/prjemian/bluesky/instrument/devices/slits.py
I Thu-17:01:02 - /home/prjemian/bluesky/instrument/devices/sixc_diffractometer.py
I Thu-17:01:02 - /home/prjemian/bluesky/instrument/devices/temperature_signal.py
I Thu-17:01:02 - #### Callbacks ####
I Thu-17:01:02 - /home/prjemian/bluesky/instrument/callbacks/spec_data_file_writer.py
I Thu-17:01:02 - #### Plans ####
I Thu-17:01:02 - /home/prjemian/bluesky/instrument/plans/lup_plan.py
I Thu-17:01:02 - /home/prjemian/bluesky/instrument/plans/peak_finder_example.py
I Thu-17:01:02 - /home/prjemian/bluesky/instrument/utils/image_analysis.py
I Thu-17:01:02 - #### Utilities ####
I Thu-17:01:02 - writing to SPEC file: /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/docs/source/howto/20230413-170102.dat
I Thu-17:01:02 -    >>>>   Using default SPEC file name   <<<<
I Thu-17:01:02 -    file will be created when bluesky ends its next scan
I Thu-17:01:02 -    to change SPEC file, use command:   newSpecFile('title')
I Thu-17:01:02 - #### Startup is complete. ####

Let’s also monitor the simulated temperature during these scans.

[2]:
sd.monitors.append(temperature)

2-pass scan#

Find the peak of noisy v. m1 in the range of +/- 2. This is a 2 scan procedure. First scan passes through the full range. Second scan is centered on the peak and width of the first scan.

We know the peak of the simulated noisy detector is positioned somewhere between -1 to +1. Overscan that range to find both sides of the peak.

The plan used here comes from our instrument package, instrument/plans/peak_finder_example.py. It’s a custom plan that, among other activities, calls the standard bluesky plan for a scan relative to the current position. The bp.rel_scan() is called twice in this custom plan.

[3]:
RE(two_pass_scan(md={"motive": "two_pass_scan"}))


Transient Scan ID: 937     Time: 2023-04-13 17:01:15
Persistent Unique Scan ID: 'd3d48c63-299b-4105-b71c-d7f76d384610'
New stream: 'label_start_motor'
New stream: 'temperature_monitor'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         m1 |      noisy |
+-----------+------------+------------+------------+
|         1 | 17:01:18.2 |    -2.0000 |  157.59212 |
|         2 | 17:01:18.7 |    -1.8182 |  198.25932 |
|         3 | 17:01:19.2 |    -1.6364 |  247.01842 |
|         4 | 17:01:19.7 |    -1.4546 |  338.79833 |
|         5 | 17:01:20.2 |    -1.2727 |  463.20949 |
|         6 | 17:01:20.7 |    -1.0909 |  664.81482 |
|         7 | 17:01:21.2 |    -0.9091 | 1029.39208 |
|         8 | 17:01:21.7 |    -0.7273 | 1876.34456 |
|         9 | 17:01:22.2 |    -0.5454 | 4261.73803 |
|        10 | 17:01:22.7 |    -0.3636 | 16701.77848 |
|        11 | 17:01:23.2 |    -0.1818 | 82311.60621 |
|        12 | 17:01:23.7 |     0.0000 | 10604.00156 |
|        13 | 17:01:24.2 |     0.1818 | 3347.12140 |
|        14 | 17:01:24.7 |     0.3636 | 1547.41780 |
|        15 | 17:01:25.2 |     0.5454 |  901.41541 |
|        16 | 17:01:25.7 |     0.7273 |  586.56298 |
|        17 | 17:01:26.2 |     0.9091 |  404.69955 |
|        18 | 17:01:26.7 |     1.0909 |  309.28576 |
|        19 | 17:01:27.2 |     1.2727 |  239.25274 |
|        20 | 17:01:27.7 |     1.4546 |  188.78387 |
|        21 | 17:01:28.2 |     1.6364 |  155.08798 |
|        22 | 17:01:28.7 |     1.8182 |  128.29261 |
|        23 | 17:01:29.2 |     2.0000 |  107.78115 |
+-----------+------------+------------+------------+
generator rel_scan ['d3d48c63'] (scan num: 937)


Transient Scan ID: 938     Time: 2023-04-13 17:01:32
Persistent Unique Scan ID: '773ec7fe-cee1-42c5-9f9f-a71e5fbbba74'
New stream: 'label_start_motor'
New stream: 'temperature_monitor'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         m1 |      noisy |
+-----------+------------+------------+------------+
|         1 | 17:01:33.0 |    -0.4484 | 7904.46212 |
|         2 | 17:01:33.1 |    -0.4246 | 9524.20099 |
|         3 | 17:01:33.3 |    -0.4008 | 11769.89338 |
|         4 | 17:01:33.5 |    -0.3770 | 14749.11678 |
|         5 | 17:01:33.7 |    -0.3532 | 19493.29960 |
|         6 | 17:01:33.9 |    -0.3294 | 25858.24056 |
|         7 | 17:01:34.2 |    -0.3056 | 34818.95318 |
|         8 | 17:01:34.3 |    -0.2818 | 47345.90678 |
|         9 | 17:01:34.6 |    -0.2580 | 62548.96813 |
|        10 | 17:01:34.8 |    -0.2343 | 80809.72645 |
|        11 | 17:01:35.0 |    -0.2105 | 90355.73829 |
|        12 | 17:01:35.2 |    -0.1867 | 84500.97257 |
|        13 | 17:01:35.4 |    -0.1629 | 68930.86411 |
|        14 | 17:01:35.5 |    -0.1391 | 48903.47788 |
|        15 | 17:01:35.7 |    -0.1153 | 36644.26066 |
|        16 | 17:01:35.9 |    -0.0915 | 27533.02424 |
|        17 | 17:01:36.2 |    -0.0677 | 20668.16407 |
|        18 | 17:01:36.3 |    -0.0439 | 16073.17176 |
|        19 | 17:01:36.5 |    -0.0201 | 12742.72471 |
|        20 | 17:01:36.7 |     0.0037 | 10125.59796 |
|        21 | 17:01:37.0 |     0.0275 | 8580.01046 |
|        22 | 17:01:37.2 |     0.0513 | 6969.20370 |
|        23 | 17:01:37.3 |     0.0751 | 5982.47230 |
+-----------+------------+------------+------------+
generator rel_scan ['773ec7fe'] (scan num: 938)
[3]:
('d3d48c63-299b-4105-b71c-d7f76d384610',
 '773ec7fe-cee1-42c5-9f9f-a71e5fbbba74')
../_images/howto__lineup_1d_peak_5_2.png
../_images/howto__lineup_1d_peak_5_3.png

Multi-pass scan#

Scan up to 4 times across the peak (in the same direction) starting with range of -2 .. +2, then reducing the range of the next scan based on the measured FWHM and centering on the measured center position (centroid).

After all scans are complete, report the computed peak centroid and FWHM for each scan in a table. The motor will be positioned at the last centroid after the last scan.

The findpeak_multipass is also from our insturment package. It calls bp.rel_scan() inside a loop for the desired number of iterations.

[4]:
RE(findpeak_multipass(md={"motive": "findpeak_multipass"}))
print(f"{m1.name} now at {m1.position:.4f}")


Transient Scan ID: 939     Time: 2023-04-13 17:01:55
Persistent Unique Scan ID: '841f1016-f954-4f9d-9f1f-984417c01d69'
New stream: 'label_start_motor'
New stream: 'temperature_monitor'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         m1 |      noisy |
+-----------+------------+------------+------------+
|         1 | 17:01:57.7 |    -2.1000 |  141.70330 |
|         2 | 17:01:58.2 |    -1.9091 |  180.10604 |
|         3 | 17:01:58.7 |    -1.7182 |  220.01310 |
|         4 | 17:01:59.2 |    -1.5273 |  297.27020 |
|         5 | 17:01:59.7 |    -1.3364 |  399.82872 |
|         6 | 17:02:00.2 |    -1.1455 |  580.57415 |
|         7 | 17:02:00.7 |    -0.9546 |  925.49621 |
|         8 | 17:02:01.2 |    -0.7636 | 1594.46591 |
|         9 | 17:02:01.7 |    -0.5727 | 3625.38025 |
|        10 | 17:02:02.2 |    -0.3818 | 14003.98883 |
|        11 | 17:02:02.7 |    -0.1909 | 87128.55953 |
|        12 | 17:02:03.2 |     0.0000 | 10781.74360 |
|        13 | 17:02:03.7 |     0.1909 | 3151.01669 |
|        14 | 17:02:04.2 |     0.3818 | 1474.58822 |
|        15 | 17:02:04.7 |     0.5727 |  860.73083 |
|        16 | 17:02:05.2 |     0.7636 |  557.77404 |
|        17 | 17:02:05.7 |     0.9546 |  386.27086 |
|        18 | 17:02:06.2 |     1.1455 |  278.80271 |
|        19 | 17:02:06.7 |     1.3364 |  210.42662 |
|        20 | 17:02:07.2 |     1.5273 |  175.16166 |
|        21 | 17:02:07.7 |     1.7182 |  137.03429 |
|        22 | 17:02:08.2 |     1.9091 |  115.49049 |
|        23 | 17:02:08.7 |     2.1000 |   96.98126 |
+-----------+------------+------------+------------+
generator rel_scan ['841f1016'] (scan num: 939)


Transient Scan ID: 940     Time: 2023-04-13 17:02:11
Persistent Unique Scan ID: '9a544ec5-36b1-43ec-8364-2daccf62ab4f'
New stream: 'label_start_motor'
New stream: 'temperature_monitor'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         m1 |      noisy |
+-----------+------------+------------+------------+
|         1 | 17:02:12.5 |    -0.4602 | 7228.56248 |
|         2 | 17:02:12.7 |    -0.4360 | 8710.12161 |
|         3 | 17:02:12.9 |    -0.4117 | 10910.88638 |
|         4 | 17:02:13.1 |    -0.3874 | 13381.24817 |
|         5 | 17:02:13.4 |    -0.3632 | 17270.12555 |
|         6 | 17:02:13.7 |    -0.3389 | 22573.96343 |
|         7 | 17:02:14.0 |    -0.3146 | 30758.95023 |
|         8 | 17:02:14.3 |    -0.2904 | 41349.69255 |
|         9 | 17:02:14.5 |    -0.2661 | 57975.44508 |
|        10 | 17:02:14.7 |    -0.2418 | 74679.24117 |
|        11 | 17:02:14.9 |    -0.2176 | 91613.35872 |
|        12 | 17:02:15.1 |    -0.1933 | 86654.05662 |
|        13 | 17:02:15.3 |    -0.1691 | 74681.03816 |
|        14 | 17:02:15.5 |    -0.1448 | 52992.45140 |
|        15 | 17:02:15.7 |    -0.1205 | 38576.68641 |
|        16 | 17:02:15.9 |    -0.0963 | 29362.35466 |
|        17 | 17:02:16.1 |    -0.0720 | 22195.94876 |
|        18 | 17:02:16.3 |    -0.0477 | 16870.37138 |
|        19 | 17:02:16.5 |    -0.0235 | 12863.75950 |
|        20 | 17:02:16.7 |     0.0008 | 10872.18860 |
|        21 | 17:02:16.9 |     0.0251 | 8739.44338 |
|        22 | 17:02:17.1 |     0.0493 | 7125.04495 |
|        23 | 17:02:17.4 |     0.0736 | 6019.72705 |
+-----------+------------+------------+------------+
generator rel_scan ['9a544ec5'] (scan num: 940)


Transient Scan ID: 941     Time: 2023-04-13 17:02:18
Persistent Unique Scan ID: '2f671579-4ca2-499f-b6d6-61f9e981ee43'
New stream: 'label_start_motor'
New stream: 'temperature_monitor'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         m1 |      noisy |
+-----------+------------+------------+------------+
|         1 | 17:02:19.0 |    -0.3787 | 14399.84182 |
|         2 | 17:02:19.1 |    -0.3632 | 17536.27942 |
|         3 | 17:02:19.3 |    -0.3478 | 20784.29981 |
|         4 | 17:02:19.5 |    -0.3323 | 24533.51947 |
|         5 | 17:02:19.7 |    -0.3168 | 29518.74018 |
|         6 | 17:02:19.9 |    -0.3014 | 34729.46943 |
|         7 | 17:02:20.2 |    -0.2859 | 42741.67328 |
|         8 | 17:02:20.5 |    -0.2705 | 54270.10986 |
|         9 | 17:02:20.7 |    -0.2550 | 67470.90664 |
|        10 | 17:02:20.9 |    -0.2395 | 79750.20383 |
|        11 | 17:02:21.1 |    -0.2241 | 88410.00755 |
|        12 | 17:02:21.3 |    -0.2086 | 94729.82146 |
|        13 | 17:02:21.5 |    -0.1932 | 90374.69658 |
|        14 | 17:02:21.7 |    -0.1777 | 77382.72138 |
|        15 | 17:02:21.9 |    -0.1622 | 69850.84615 |
|        16 | 17:02:22.1 |    -0.1468 | 55975.70555 |
|        17 | 17:02:22.3 |    -0.1313 | 44631.50376 |
|        18 | 17:02:22.5 |    -0.1159 | 38118.60161 |
|        19 | 17:02:22.7 |    -0.1004 | 30890.33051 |
|        20 | 17:02:22.9 |    -0.0849 | 24882.10213 |
|        21 | 17:02:23.1 |    -0.0695 | 20989.25822 |
|        22 | 17:02:23.4 |    -0.0540 | 18319.92818 |
|        23 | 17:02:23.5 |    -0.0386 | 15567.73558 |
+-----------+------------+------------+------------+
generator rel_scan ['2f671579'] (scan num: 941)


Transient Scan ID: 942     Time: 2023-04-13 17:02:24
Persistent Unique Scan ID: 'ac664dc7-504f-4425-8fe4-489adbae7fdd'
New stream: 'label_start_motor'
New stream: 'temperature_monitor'
New stream: 'primary'
+-----------+------------+------------+------------+
|   seq_num |       time |         m1 |      noisy |
+-----------+------------+------------+------------+
|         1 | 17:02:24.8 |    -0.3578 | 18569.97283 |
|         2 | 17:02:25.1 |    -0.3441 | 21728.44437 |
|         3 | 17:02:25.3 |    -0.3305 | 25226.32175 |
|         4 | 17:02:25.5 |    -0.3168 | 28941.51480 |
|         5 | 17:02:25.7 |    -0.3031 | 34314.24493 |
|         6 | 17:02:25.9 |    -0.2895 | 40531.42738 |
|         7 | 17:02:26.1 |    -0.2758 | 49028.60617 |
|         8 | 17:02:26.2 |    -0.2621 | 60024.30969 |
|         9 | 17:02:26.5 |    -0.2485 | 68583.11363 |
|        10 | 17:02:26.7 |    -0.2348 | 83210.76279 |
|        11 | 17:02:26.9 |    -0.2211 | 88905.13543 |
|        12 | 17:02:27.1 |    -0.2075 | 91254.82153 |
|        13 | 17:02:27.3 |    -0.1938 | 91395.55793 |
|        14 | 17:02:27.5 |    -0.1802 | 80290.89715 |
|        15 | 17:02:27.8 |    -0.1665 | 71926.05508 |
|        16 | 17:02:28.0 |    -0.1528 | 60632.25445 |
|        17 | 17:02:28.2 |    -0.1392 | 50935.77565 |
|        18 | 17:02:28.4 |    -0.1255 | 41194.65347 |
|        19 | 17:02:28.6 |    -0.1118 | 35045.20416 |
|        20 | 17:02:28.8 |    -0.0982 | 30070.29286 |
|        21 | 17:02:29.0 |    -0.0845 | 25524.63241 |
|        22 | 17:02:29.2 |    -0.0708 | 21599.11847 |
|        23 | 17:02:29.4 |    -0.0572 | 18357.45299 |
+-----------+------------+------------+------------+
generator rel_scan ['ac664dc7'] (scan num: 942)
I Thu-17:02:30 - iterative results:
======= ==================== ===================
scan_id center               FWHM
======= ==================== ===================
939     -0.19330745175234704 0.2224228216416789
940     -0.20861123128447245 0.14171756691442283
941     -0.20747604249899584 0.12525759324435487
942     -0.20662333630889465 0.12382359689785855
======= ==================== ===================

m1 now at -0.2066
../_images/howto__lineup_1d_peak_7_3.png
../_images/howto__lineup_1d_peak_7_4.png

Show how to re-plot the data from the catalog cat. We’ll show the plots of the last three runs. The measured data is always in the primary stream.

[5]:
cat[-3].primary.read().plot.scatter(x="m1", y="noisy", marker="x")
cat[-2].primary.read().plot.scatter(x="m1", y="noisy", marker="+")
cat[-1].primary.read().plot.scatter(x="m1", y="noisy", marker="o")
[5]:
<matplotlib.collections.PathCollection at 0x7f9c327f8d60>
../_images/howto__lineup_1d_peak_9_1.png

Plot the monitored temperature v time. (See the table of data printed during the scan, the temperature_monitor stream was listed near the top of the table.)

[6]:
# temperature_monitor
cat[-4].temperature_monitor.read().plot.scatter(x="time", y="temperature", marker="*")
cat[-3].temperature_monitor.read().plot.scatter(x="time", y="temperature", marker="x")
cat[-2].temperature_monitor.read().plot.scatter(x="time", y="temperature", marker="+")
cat[-1].temperature_monitor.read().plot.scatter(x="time", y="temperature", marker="o")
[6]:
<matplotlib.collections.PathCollection at 0x7f9c32681450>
../_images/howto__lineup_1d_peak_11_1.png