How to use NeXus/HDF5 Templates#
Show how to use templates to write powder diffraction data using the NeXus NXmonopd application definition. Templates are used to:
make links from existing fields or groups to new locations
create new groups as directed
create constants for attributes or fields
Overview#
This powder diffraction experiment positions a sample on a rotation stage (called th
, or \(\theta\)) and a detector at some distance on a second rotation axis (called tth
, or \(2\theta\)). The experiment moves \(2\theta\) through an angular range while synchronizing \(\theta\) to half of the \(2\theta\) value. The intensity of incident (I0
) and scattered radiation (sensor
) are sampled at discrete angular intervals. The I0
detector is positioned before the
sample. It does not move during the experiment. The sensor
detector is mounted on a long arm projecting from the \(2\theta\) rotation.
Build the powder diffraction instrument#
We simulate a powder diffraction instrument with a monochromatic source with a Bluesky hardware setup similar to the guide to output scans(s) to a NeXus HDF5 file. These are the modifications of that setup:
Continue to use EPICS PVs for the simulation
Change
motor
totth
(\(2\theta\))Add
th
(\(\theta\)) motorChange sensor to a simulated Lorentzian
based on
tth
readback valuecalculation will re-compute as motor position changes
center: randomly placed between 3 .. 8 degrees
width: random between 0.01 .. 0.51 degrees
height: random (~10,000)
Add
I0
signalConstant value:
100,000
[1]:
%matplotlib inline
import random
from apstools.synApps import setup_lorentzian_swait
from apstools.synApps import setup_random_number_swait
from apstools.synApps import SwaitRecord
from bluesky import RunEngine
from bluesky import plans as bp
from bluesky import plan_stubs as bps
from bluesky.callbacks.best_effort import BestEffortCallback
from matplotlib import pyplot as plt
from ophyd import EpicsMotor
from ophyd import EpicsSignalRO
from ophyd import Signal
import databroker
# bluesky-level
best_effort_callback = BestEffortCallback()
cat = databroker.temp().v2
plt.ion() # enables matplotlib graphics
RE = RunEngine({})
RE.subscribe(cat.v1.insert)
RE.subscribe(best_effort_callback) # LivePlot & LiveTable
IOC = "gp:"
# ophyd-level
th = EpicsMotor(f"{IOC}m9", name="th")
tth = EpicsMotor(f"{IOC}m10", name="tth")
calc10 = SwaitRecord(f"{IOC}userCalc10", name="calc10")
I0 = Signal(name="I0", value=100_000)
sensor = EpicsSignalRO(calc10.calculated_value.pvname, name="sensor")
# set up calc10, updating at 10Hz
calc10.wait_for_connection()
tth.wait_for_connection()
setup_lorentzian_swait(
calc10,
tth.user_readback,
center=3 + 5 * random.random(),
width=0.01 + 0.5 * random.random(),
scale=10_000 * (0.98 + 0.04 * random.random()),
noise=0.02
)
# wait for the other connections
th.wait_for_connection()
sensor.wait_for_connection()
Write data to a NeXus/HDF5 file#
Use the NXWriter()
from apstools
. Choose where to write the HDF5 file.
[2]:
from apstools.callbacks import NXWriter
import json
import pathlib
h5_file = pathlib.Path("/tmp/nxwriter.h5")
nxwriter = NXWriter()
RE.subscribe(nxwriter.receiver)
nxwriter.file_name = str(h5_file)
nxwriter.warn_on_missing_content = False
Use the NXmonopd application definition#
Steps to configure the NXWriter to write raw data using the NeXus NXmonopd powder diffraction application definition.
Review the definition. Note what are the required groups and fields.
Write them as comments into a prototype of the template structure to be used.
Collect data from a \(2\theta:\theta\) scan.
Examine the HDF5 file.
Create templates to connect existing data with NXmonopd definition.
Add constants and attributes as appropriate.
Collect data again with revised template and compare with the NXmonopd definition.
Decisions when making the template.#
To write data according to the NXmonopd definition, we must make some decisions about linking to existing data and creating any new required structures.
First decision is where to write the structure of the application definition ( whether or not to use a NXsubentry group). The NXsubentry group is used for multi-modal experiments (such as SAXS/WAXS). Here, we are only writing raw monochromatic powder diffraction data. We do not need to use a NXsubentry group. We will write the structure into the NXentry group.
A NeXus file that uses an application definition needs a definition
field with the name of the application definition. In this case, the name is "NXmonopd"
. We write this with a constant template:
["/entry/definition=", "NXmonopd"]
The NXmonopd definition requires the NXentry group contain these fields: title
and start_time
. These fields are already provided by NXWriter()
. Nothing more needs to be done.
NXmonopd requires a NXcrystal group with a wavelength
field. This is a simulation, we can use any wavelength. It is convenient here to pass the wavelength as run metadata. (If it was available as an ophyd signal, it could be recorded in the baseline
stream.) We’ll call it mono_wavelength
in the metadata. NXWriter()
writes this metadata to the HDF5 address: /entry/instrument/bluesky/metadata/mono_wavelength
The NXcrystal group is not created in the standard NXWriter()
file. We can create it and give it the name of crystal
by using a class path: crystal:NXcrystal
which specifies both the group name and the NeXus base class for the new group. The next template connects the run metadata wavelength
with the location required by NXmonopd (note that the name of the target field does not have to be the same as the source):
[
"/entry/instrument/bluesky/metadata/mono_wavelength",
"/entry/instrument/crystal:NXcrystal/wavelength"
]
A NXdetector group is required with fields polar_angle
and data
. These two fields correspond to tth
and sensor
. We use a class path for NXdetector in case the group does not already exist. These templates provide the links:
[
"/entry/instrument/bluesky/streams/primary/tth/value",
"/entry/instrument/detector:NXdetector/polar_angle"
],
[
"/entry/instrument/bluesky/streams/primary/sensor/value",
"/entry/instrument/detector:NXdetector/data"
],
A sample name is required in a NXsample group. We’ll give the sample name in the metadata, then link it with a template:
[
"/entry/instrument/bluesky/metadata/sample_name",
"/entry/instrument/sample:NXsample/name"
]
The NXmonopd definition is unclear whether or not a rotation_angle
field is required. The text says it is optional but the presence of "(required)"
means it must be provided. We’ll set it to a constant of zero:
["/entry/sample:NXsample/rotation_angle=", 0],
A NXmonitor group is required with fields mode
, preset
, and integral
. This simulation uses a constant monitor count (I0
), not a counting time. The preset
field is the number of counts required for I0
at each collection. These are the templates for the NXmonitor group:
["/entry/monitor:NXmonitor/mode=", "monitor"],
["/entry/monitor:NXmonitor/preset=", I0.get()],
[
"/entry/instrument/bluesky/streams/primary/I0/value",
"/entry/monitor:NXmonitor/integral"
],
The NeXus NXmonitor group acts like a NXdata group. We add a @signal="integral"
attribute that names integral
as the default field in that group to be plotted. This is not required but is of great assistance when using visualization software such as NeXPy.
["/entry/monitor:NXmonitor/@signal", "integral"],
The NXmonopd definition specifies a DATA:NXdata
group. The NXWriter()
creates a data:NXdata
group and links all the detector and motor fields into this group. Since NXmonopd requres some different names, we choose to create an additional NXdata group. In a NeXus definition, a field name that is given in all upper case, such as DATA
means that the name can be chosen by us. We’ll use monopd
as this group’s name. The @signal
attribute names the ordinate (\(y\))
axis to be plotted, the @axes
attribute names any abcissae (\(x\)). In this case, we only use \(2\theta\) (mapped to polar_angle
) as the \(x\) axis, as customary for powder diffraction data.
["/entry/instrument/bluesky/streams/primary/tth/value", "/entry/monopd:NXdata/polar_angle"],
["/entry/instrument/bluesky/streams/primary/sensor/value", "/entry/monopd:NXdata/data"],
["/entry/monopd/@signal", "data"],
["/entry/monopd/@axes", ["polar_angle", ]],
Finally, we change the default data group to point to our new monopd
group:
["/entry/@default", "monopd"],
[3]:
templates = [
["/entry/definition=", "NXmonopd"], # satisfy the NXmonopd definnition
# /entry/title already defined
# /entry/start_time already defined
# /entry/instrument/source/type already defined
# /entry/instrument/source/name already defined
# /entry/instrument/source/probe already defined
["/entry/instrument/bluesky/metadata/mono_wavelength", "/entry/instrument/crystal:NXcrystal/wavelength"],
["/entry/instrument/bluesky/streams/primary/tth/value", "/entry/instrument/detector:NXdetector/polar_angle"],
["/entry/instrument/bluesky/streams/primary/sensor/value", "/entry/instrument/detector:NXdetector/data"],
["/entry/instrument/bluesky/metadata/sample_name", "/entry/sample:NXsample/name"],
["/entry/sample:NXsample/rotation_angle=", 0], # sample has not been rotated
["/entry/monitor:NXmonitor/mode=", "monitor"], # in this simulation
["/entry/monitor:NXmonitor/preset=", I0.get()], # constant
["/entry/instrument/bluesky/streams/primary/I0/value", "/entry/monitor:NXmonitor/integral"],
["/entry/instrument/bluesky/streams/primary/tth/value", "/entry/monopd:NXdata/polar_angle"],
["/entry/instrument/bluesky/streams/primary/sensor/value", "/entry/monopd:NXdata/data"],
["/entry/monopd/@signal", "data"],
["/entry/monopd/@axes", ["polar_angle", ]],
["/entry/@default", "monopd"], # change the default plot group
["/entry/monitor:NXmonitor/@signal", "integral"], # in NXmonitor, same as for NXdata groups
]
[4]:
md = {
"title": "Demonstrate NeXus/HDF5 template support",
nxwriter.template_key: json.dumps(templates),
"mono_wavelength": 1.0, # simulation
"sample_name": "simulation",
}
Run a \(2\theta:\theta\) scan#
Make a bluesky plan
Run the plan with the
RE
, steppingtth
across the range where the Lorentzian peak is expected.Print a table of the data as it is measured.
Plot the
sensor
andI0
signals, separately.
Verify the NeXus/HDF5 file has been saved.
[5]:
def th2th(detectors, tth, th, start, finish, num, md={}):
# Since x2x is a relative scan, first move to the absolute start positions.
yield from bps.mv(tth, start, th, start/2)
yield from bp.x2x_scan(detectors, tth, th, 0, finish-start, num, md=md)
print(f"{h5_file.exists()=} {h5_file=}")
RE(th2th([sensor, I0], tth, th, 2, 10, 11, md=md))
Transient Scan ID: 1 Time: 2023-12-02 13:15:51
Persistent Unique Scan ID: '90acae1f-2475-406a-b86f-05b7ca2bb7d2'
New stream: 'primary'
+-----------+------------+------------+------------+------------+------------+
| seq_num | time | tth | th | sensor | I0 |
+-----------+------------+------------+------------+------------+------------+
| 1 | 13:15:51.7 | 2.0000 | 1.0000 | 0.00000 | 100000 |
| 2 | 13:15:52.8 | 2.8000 | 1.4000 | 182.83233 | 100000 |
| 3 | 13:15:53.9 | 3.6000 | 1.8000 | 390.07416 | 100000 |
| 4 | 13:15:55.0 | 4.4000 | 2.2000 | 1233.82536 | 100000 |
| 5 | 13:15:56.1 | 5.2000 | 2.6000 | 8820.36874 | 100000 |
| 6 | 13:15:57.3 | 6.0000 | 3.0000 | 1987.91262 | 100000 |
| 7 | 13:15:58.5 | 6.8000 | 3.4000 | 503.62617 | 100000 |
| 8 | 13:15:59.8 | 7.6000 | 3.8000 | 222.62671 | 100000 |
| 9 | 13:16:00.9 | 8.4000 | 4.2000 | 122.16422 | 100000 |
| 10 | 13:16:02.1 | 9.2000 | 4.6000 | 77.89379 | 100000 |
| 11 | 13:16:03.3 | 10.0000 | 5.0000 | 53.29990 | 100000 |
+-----------+------------+------------+------------+------------+------------+
generator x2x_scan ['90acae1f'] (scan num: 1)
h5_file.exists()=True h5_file=PosixPath('/tmp/nxwriter.h5')
[5]:
('90acae1f-2475-406a-b86f-05b7ca2bb7d2',)
Look at the NeXus/HDF5 NXmonopd file#
[6]:
from apstools.utils import unix
for line in unix(f"punx tree {nxwriter.file_name}"):
print(line.decode().strip())
!!! WARNING: this program is not ready for distribution.
/tmp/nxwriter.h5 : NeXus data file
@HDF5_Version = "1.14.0"
@NeXus_release = "v2020.1"
@creator = "NXWriter"
@default = "entry"
@file_name = "/tmp/nxwriter.h5"
@file_time = "2023-12-02T13:16:03.536788"
@h5py_version = "3.9.0"
entry:NXentry
@NX_class = "NXentry"
@default = "monopd"
@target = "/entry"
definition:NX_CHAR = b'NXmonopd'
@target = "/entry/definition"
duration:NX_FLOAT64[] =
@units = "s"
end_time:NX_CHAR = b'2023-12-02T13:16:03.365098'
entry_identifier --> /entry/instrument/bluesky/metadata/run_start_uid
plan_name --> /entry/instrument/bluesky/metadata/plan_name
program_name:NX_CHAR = b'bluesky'
start_time:NX_CHAR = b'2023-12-02T13:15:51.630091'
title:NX_CHAR = b'x2x_scan-S0001-90acae1'
data:NXdata
@NX_class = "NXdata"
@axes = ["tth", "th"]
@signal = "sensor"
@target = "/entry/data"
EPOCH --> /entry/instrument/bluesky/streams/primary/tth_user_setpoint/time
I0 --> /entry/instrument/bluesky/streams/primary/I0/value
sensor --> /entry/instrument/bluesky/streams/primary/sensor/value
th --> /entry/instrument/bluesky/streams/primary/th/value
th_user_setpoint --> /entry/instrument/bluesky/streams/primary/th_user_setpoint/value
tth --> /entry/instrument/bluesky/streams/primary/tth/value
tth_user_setpoint --> /entry/instrument/bluesky/streams/primary/tth_user_setpoint/value
instrument:NXinstrument
@NX_class = "NXinstrument"
@target = "/entry/instrument"
bluesky:NXnote
@NX_class = "NXnote"
@target = "/entry/instrument/bluesky"
plan_name --> /entry/instrument/bluesky/metadata/plan_name
uid --> /entry/instrument/bluesky/metadata/run_start_uid
metadata:NXnote
@NX_class = "NXnote"
@target = "/entry/instrument/bluesky/metadata"
detectors:NX_CHAR = b'- sensor\n- I0\n'
@target = "/entry/instrument/bluesky/metadata/detectors"
@text_format = "yaml"
hints:NX_CHAR = b'dimensions:\n- !!python/tuple\n - - tth\n - th\n - primary\n'
@target = "/entry/instrument/bluesky/metadata/hints"
@text_format = "yaml"
mono_wavelength:NX_FLOAT64[] =
@target = "/entry/instrument/bluesky/metadata/mono_wavelength"
motors:NX_CHAR = b'!!python/tuple\n- tth\n- th\n'
@target = "/entry/instrument/bluesky/metadata/motors"
@text_format = "yaml"
num_intervals:NX_INT64[] =
@target = "/entry/instrument/bluesky/metadata/num_intervals"
num_points:NX_INT64[] =
@target = "/entry/instrument/bluesky/metadata/num_points"
nxwriter_template:NX_CHAR = b'[["/entry/definition=", "NXmonopd"], ["/entry/instrument/bluesky/metadata/mono_wavelength", "/entry/instrument/crystal:NXcrystal/wavelength"], ["/entry/instrument/bluesky/streams/primary/tth/value", "/entry/instrument/detector:NXdetector/polar_angle"], ["/entry/instrument/bluesky/streams/primary/sensor/value", "/entry/instrument/detector:NXdetector/data"], ["/entry/instrument/bluesky/metadata/sample_name", "/entry/sample:NXsample/name"], ["/entry/sample:NXsample/rotation_angle=", 0], ["/entry/monitor:NXmonitor/mode=", "monitor"], ["/entry/monitor:NXmonitor/preset=", 100000], ["/entry/instrument/bluesky/streams/primary/I0/value", "/entry/monitor:NXmonitor/integral"], ["/entry/instrument/bluesky/streams/primary/tth/value", "/entry/monopd:NXdata/polar_angle"], ["/entry/instrument/bluesky/streams/primary/sensor/value", "/entry/monopd:NXdata/data"], ["/entry/monopd/@signal", "data"], ["/entry/monopd/@axes", ["polar_angle"]], ["/entry/@default", "monopd"], ["/entry/monitor:NXmonitor/@signal", "integral"]]'
@target = "/entry/instrument/bluesky/metadata/nxwriter_template"
plan_args:NX_CHAR = b"detectors:\n- EpicsSignalRO(read_pv='gp:userCalc10.VAL', name='sensor', value=0.0, timestamp=1701544551.572926,\n auto_monitor=True, string=False)\n- Signal(name='I0', value=100000, timestamp=1701544551.4371457)\nmotor1: tth\nmotor2: th\nnum: 11\nper_step: None\nstart: 0\nstop: 8\n"
@target = "/entry/instrument/bluesky/metadata/plan_args"
@text_format = "yaml"
plan_name:NX_CHAR = b'x2x_scan'
@target = "/entry/instrument/bluesky/metadata/plan_name"
plan_pattern:NX_CHAR = b'inner_product'
@target = "/entry/instrument/bluesky/metadata/plan_pattern"
plan_pattern_args:NX_CHAR = b"args:\n- EpicsMotor(prefix='gp:m10', name='tth', settle_time=0.0, timeout=None, read_attrs=['user_readback',\n 'user_setpoint'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity',\n 'acceleration', 'motor_egu'])\n- 0\n- 8\n- EpicsMotor(prefix='gp:m9', name='th', settle_time=0.0, timeout=None, read_attrs=['user_readback',\n 'user_setpoint'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity',\n 'acceleration', 'motor_egu'])\n- 0.0\n- 4.0\nnum: 11\n"
@target = "/entry/instrument/bluesky/metadata/plan_pattern_args"
@text_format = "yaml"
plan_pattern_module:NX_CHAR = b'bluesky.plan_patterns'
@target = "/entry/instrument/bluesky/metadata/plan_pattern_module"
plan_type:NX_CHAR = b'generator'
@target = "/entry/instrument/bluesky/metadata/plan_type"
run_start_uid:NX_CHAR = b'90acae1f-2475-406a-b86f-05b7ca2bb7d2'
@long_name = "bluesky run uid"
@target = "/entry/instrument/bluesky/metadata/run_start_uid"
sample_name:NX_CHAR = b'simulation'
@target = "/entry/instrument/bluesky/metadata/sample_name"
title:NX_CHAR = b'Demonstrate NeXus/HDF5 template support'
@target = "/entry/instrument/bluesky/metadata/title"
versions:NX_CHAR = b'bluesky: 1.10.0\nophyd: 1.9.0\n'
@target = "/entry/instrument/bluesky/metadata/versions"
@text_format = "yaml"
streams:NXnote
@NX_class = "NXnote"
@target = "/entry/instrument/bluesky/streams"
primary:NXnote
@NX_class = "NXnote"
@target = "/entry/instrument/bluesky/streams/primary"
@uid = "175a60a2-aec7-4307-bd24-ffe206ffc3ae"
I0:NXdata
@NX_class = "NXdata"
@axes = ["time"]
@signal = "value"
@signal_type = "detector"
@target = "/entry/instrument/bluesky/streams/primary/I0"
EPOCH:NX_FLOAT64[11] = [1701544551.4371457, 1701544551.4371457, 1701544551.4371457, '...', 1701544551.4371457]
@long_name = "epoch time (s)"
@target = "/entry/instrument/bluesky/streams/primary/I0/EPOCH"
@units = "s"
time:NX_FLOAT64[11] = [0.0, 0.0, 0.0, '...', 0.0]
@long_name = "time since first data (s)"
@start_time = 1701544551.4371457
@start_time_iso = "2023-12-02T13:15:51.437146"
@target = "/entry/instrument/bluesky/streams/primary/I0/time"
@units = "s"
value:NX_INT64[11] = [100000, 100000, 100000, '...', 100000]
@long_name = "I0"
@lower_ctrl_limit = ""
@precision = 0
@signal_type = "detector"
@source = "SIM:I0"
@target = "/entry/instrument/bluesky/streams/primary/I0/value"
@units = ""
@upper_ctrl_limit = ""
sensor:NXdata
@NX_class = "NXdata"
@axes = ["time"]
@signal = "value"
@signal_type = "detector"
@target = "/entry/instrument/bluesky/streams/primary/sensor"
EPOCH:NX_FLOAT64[11] = [1701544551.572926, 1701544552.871271, 1701544553.974374, '...', 1701544563.299544]
@long_name = "epoch time (s)"
@target = "/entry/instrument/bluesky/streams/primary/sensor/EPOCH"
@units = "s"
time:NX_FLOAT64[11] = [0.0, 1.2983448505401611, 2.4014480113983154, '...', 11.72661805152893]
@long_name = "time since first data (s)"
@start_time = 1701544551.572926
@start_time_iso = "2023-12-02T13:15:51.572926"
@target = "/entry/instrument/bluesky/streams/primary/sensor/time"
@units = "s"
value:NX_FLOAT64[11] = [0.0, 182.83233136804841, 390.0741582405076, '...', 53.2999002115527]
@long_name = "sensor"
@lower_ctrl_limit = 0.0
@precision = 5
@signal_type = "detector"
@source = "PV:gp:userCalc10.VAL"
@target = "/entry/instrument/bluesky/streams/primary/sensor/value"
@units = ""
@upper_ctrl_limit = 0.0
th:NXdata
@NX_class = "NXdata"
@axes = ["time"]
@signal = "value"
@signal_type = "positioner"
@target = "/entry/instrument/bluesky/streams/primary/th"
EPOCH:NX_FLOAT64[11] = [1701543761.161202, 1701544552.469743, 1701544553.573023, '...', 1701544562.898525]
@long_name = "epoch time (s)"
@target = "/entry/instrument/bluesky/streams/primary/th/EPOCH"
@units = "s"
time:NX_FLOAT64[11] = [0.0, 791.308541059494, 792.4118211269379, '...', 801.7373230457306]
@long_name = "time since first data (s)"
@start_time = 1701543761.161202
@start_time_iso = "2023-12-02T13:02:41.161202"
@target = "/entry/instrument/bluesky/streams/primary/th/time"
@units = "s"
value:NX_FLOAT64[11] = [1.0, 1.4000000000000001, 1.8, '...', 5.0]
@long_name = "th"
@lower_ctrl_limit = -1000.0
@precision = 4
@signal_type = "positioner"
@source = "PV:gp:m9.RBV"
@target = "/entry/instrument/bluesky/streams/primary/th/value"
@units = "degrees"
@upper_ctrl_limit = 1000.0
th_user_setpoint:NXdata
@NX_class = "NXdata"
@axes = ["time"]
@signal = "value"
@signal_type = "other"
@target = "/entry/instrument/bluesky/streams/primary/th_user_setpoint"
EPOCH:NX_FLOAT64[11] = [1701544551.636257, 1701544551.786764, 1701544552.965123, '...', 1701544562.2169707]
@long_name = "epoch time (s)"
@target = "/entry/instrument/bluesky/streams/primary/th_user_setpoint/EPOCH"
@units = "s"
time:NX_FLOAT64[11] = [0.0, 0.15050697326660156, 1.3288660049438477, '...', 10.580713748931885]
@long_name = "time since first data (s)"
@start_time = 1701544551.636257
@start_time_iso = "2023-12-02T13:15:51.636257"
@target = "/entry/instrument/bluesky/streams/primary/th_user_setpoint/time"
@units = "s"
value:NX_FLOAT64[11] = [1.0, 1.4, 1.8, '...', 5.0]
@long_name = "th_user_setpoint"
@lower_ctrl_limit = -1000.0
@precision = 4
@signal_type = "other"
@source = "PV:gp:m9.VAL"
@target = "/entry/instrument/bluesky/streams/primary/th_user_setpoint/value"
@units = "degrees"
@upper_ctrl_limit = 1000.0
tth:NXdata
@NX_class = "NXdata"
@axes = ["time"]
@signal = "value"
@signal_type = "positioner"
@target = "/entry/instrument/bluesky/streams/primary/tth"
EPOCH:NX_FLOAT64[11] = [1701543761.160919, 1701544552.871001, 1701544553.974129, '...', 1701544563.299386]
@long_name = "epoch time (s)"
@target = "/entry/instrument/bluesky/streams/primary/tth/EPOCH"
@units = "s"
time:NX_FLOAT64[11] = [0.0, 791.7100820541382, 792.8132100105286, '...', 802.1384670734406]
@long_name = "time since first data (s)"
@start_time = 1701543761.160919
@start_time_iso = "2023-12-02T13:02:41.160919"
@target = "/entry/instrument/bluesky/streams/primary/tth/time"
@units = "s"
value:NX_FLOAT64[11] = [2.0, 2.8000000000000003, 3.6, '...', 10.0]
@long_name = "tth"
@lower_ctrl_limit = -1000.0
@precision = 4
@signal_type = "positioner"
@source = "PV:gp:m10.RBV"
@target = "/entry/instrument/bluesky/streams/primary/tth/value"
@units = "degrees"
@upper_ctrl_limit = 1000.0
tth_user_setpoint:NXdata
@NX_class = "NXdata"
@axes = ["time"]
@signal = "value"
@signal_type = "other"
@target = "/entry/instrument/bluesky/streams/primary/tth_user_setpoint"
EPOCH:NX_FLOAT64[11] = [1701544551.6354487, 1701544551.785843, 1701544552.964422, '...', 1701544562.215509]
@long_name = "epoch time (s)"
@target = "/entry/instrument/bluesky/streams/primary/tth_user_setpoint/EPOCH"
@units = "s"
time:NX_FLOAT64[11] = [0.0, 0.15039420127868652, 1.3289732933044434, '...', 10.580060243606567]
@long_name = "time since first data (s)"
@start_time = 1701544551.6354487
@start_time_iso = "2023-12-02T13:15:51.635449"
@target = "/entry/instrument/bluesky/streams/primary/tth_user_setpoint/time"
@units = "s"
value:NX_FLOAT64[11] = [2.0, 2.8, 3.6, '...', 10.0]
@long_name = "tth_user_setpoint"
@lower_ctrl_limit = -1000.0
@precision = 4
@signal_type = "other"
@source = "PV:gp:m10.VAL"
@target = "/entry/instrument/bluesky/streams/primary/tth_user_setpoint/value"
@units = "degrees"
@upper_ctrl_limit = 1000.0
crystal:NXcrystal
@NX_class = "NXcrystal"
@target = "/entry/instrument/crystal"
wavelength --> /entry/instrument/bluesky/metadata/mono_wavelength
detector:NXdetector
@NX_class = "NXdetector"
@target = "/entry/instrument/detector"
data --> /entry/instrument/bluesky/streams/primary/sensor/value
polar_angle --> /entry/instrument/bluesky/streams/primary/tth/value
detectors:NXnote
@NX_class = "NXnote"
@target = "/entry/instrument/detectors"
I0:NXdetector
@NX_class = "NXdetector"
@target = "/entry/instrument/detectors/I0"
data --> /entry/instrument/bluesky/streams/primary/I0
sensor:NXdetector
@NX_class = "NXdetector"
@target = "/entry/instrument/detectors/sensor"
data --> /entry/instrument/bluesky/streams/primary/sensor
positioners:NXnote
@NX_class = "NXnote"
@target = "/entry/instrument/positioners"
th:NXpositioner
@NX_class = "NXpositioner"
@target = "/entry/instrument/positioners/th"
value --> /entry/instrument/bluesky/streams/primary/th
tth:NXpositioner
@NX_class = "NXpositioner"
@target = "/entry/instrument/positioners/tth"
value --> /entry/instrument/bluesky/streams/primary/tth
source:NXsource
@NX_class = "NXsource"
@target = "/entry/instrument/source"
name:NX_CHAR = b'Bluesky framework'
@short_name = "bluesky"
probe:NX_CHAR = b'x-ray'
type:NX_CHAR = b'Synchrotron X-ray Source'
monitor:NXmonitor
@NX_class = "NXmonitor"
@signal = "integral"
@target = "/entry/monitor"
integral --> /entry/instrument/bluesky/streams/primary/I0/value
mode:NX_CHAR = b'monitor'
@target = "/entry/monitor/mode"
preset:NX_INT64 = 100000
@target = "/entry/monitor/preset"
monopd:NXdata
@NX_class = "NXdata"
@axes = ["polar_angle"]
@signal = "data"
@target = "/entry/monopd"
data --> /entry/instrument/bluesky/streams/primary/sensor/value
polar_angle --> /entry/instrument/bluesky/streams/primary/tth/value
sample:NXsample
@NX_class = "NXsample"
@target = "/entry/sample"
name --> /entry/instrument/bluesky/metadata/sample_name
rotation_angle:NX_INT64 = 0
@target = "/entry/sample/rotation_angle"