nbs-bl 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nbs_bl/__init__.py +15 -0
- nbs_bl/beamline.py +450 -0
- nbs_bl/configuration.py +838 -0
- nbs_bl/detectors.py +89 -0
- nbs_bl/devices/__init__.py +12 -0
- nbs_bl/devices/detectors.py +154 -0
- nbs_bl/devices/motors.py +242 -0
- nbs_bl/devices/sampleholders.py +360 -0
- nbs_bl/devices/shutters.py +120 -0
- nbs_bl/devices/slits.py +51 -0
- nbs_bl/gGrEqns.py +171 -0
- nbs_bl/geometry/__init__.py +0 -0
- nbs_bl/geometry/affine.py +197 -0
- nbs_bl/geometry/bars.py +189 -0
- nbs_bl/geometry/frames.py +534 -0
- nbs_bl/geometry/linalg.py +138 -0
- nbs_bl/geometry/polygons.py +56 -0
- nbs_bl/help.py +126 -0
- nbs_bl/hw.py +270 -0
- nbs_bl/load.py +113 -0
- nbs_bl/motors.py +19 -0
- nbs_bl/planStatus.py +5 -0
- nbs_bl/plans/__init__.py +8 -0
- nbs_bl/plans/batches.py +174 -0
- nbs_bl/plans/conditions.py +77 -0
- nbs_bl/plans/flyscan_base.py +180 -0
- nbs_bl/plans/groups.py +55 -0
- nbs_bl/plans/maximizers.py +423 -0
- nbs_bl/plans/metaplans.py +179 -0
- nbs_bl/plans/plan_stubs.py +246 -0
- nbs_bl/plans/preprocessors.py +160 -0
- nbs_bl/plans/scan_base.py +58 -0
- nbs_bl/plans/scan_decorators.py +524 -0
- nbs_bl/plans/scans.py +145 -0
- nbs_bl/plans/suspenders.py +87 -0
- nbs_bl/plans/time_estimation.py +168 -0
- nbs_bl/plans/xas.py +123 -0
- nbs_bl/printing.py +221 -0
- nbs_bl/qt/models/beamline.py +11 -0
- nbs_bl/qt/models/energy.py +53 -0
- nbs_bl/qt/widgets/energy.py +225 -0
- nbs_bl/queueserver.py +249 -0
- nbs_bl/redisDevice.py +96 -0
- nbs_bl/run_engine.py +63 -0
- nbs_bl/samples.py +130 -0
- nbs_bl/settings.py +68 -0
- nbs_bl/shutters.py +39 -0
- nbs_bl/sim/__init__.py +2 -0
- nbs_bl/sim/config/polphase.nc +0 -0
- nbs_bl/sim/energy.py +403 -0
- nbs_bl/sim/manipulator.py +14 -0
- nbs_bl/sim/utils.py +36 -0
- nbs_bl/startup.py +27 -0
- nbs_bl/status.py +114 -0
- nbs_bl/tests/__init__.py +0 -0
- nbs_bl/tests/modify_regions.py +160 -0
- nbs_bl/tests/test_frames.py +99 -0
- nbs_bl/tests/test_panels.py +69 -0
- nbs_bl/utils.py +235 -0
- nbs_bl-0.2.0.dist-info/METADATA +71 -0
- nbs_bl-0.2.0.dist-info/RECORD +64 -0
- nbs_bl-0.2.0.dist-info/WHEEL +4 -0
- nbs_bl-0.2.0.dist-info/entry_points.txt +2 -0
- nbs_bl-0.2.0.dist-info/licenses/LICENSE +13 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from ..beamline import GLOBAL_BEAMLINE
|
|
3
|
+
from ..detectors import (
|
|
4
|
+
activate_detector,
|
|
5
|
+
deactivate_detector,
|
|
6
|
+
activate_detector_set,
|
|
7
|
+
)
|
|
8
|
+
from ..utils import merge_func
|
|
9
|
+
from .plan_stubs import (
|
|
10
|
+
set_exposure,
|
|
11
|
+
sampleholder_set_sample,
|
|
12
|
+
sampleholder_move_sample,
|
|
13
|
+
update_plan_status,
|
|
14
|
+
clear_plan_status,
|
|
15
|
+
)
|
|
16
|
+
from bluesky.plan_stubs import mv, trigger_and_read, declare_stream
|
|
17
|
+
from bluesky.utils import separate_devices
|
|
18
|
+
from bluesky.preprocessors import stage_wrapper, plan_mutator, set_run_key_wrapper
|
|
19
|
+
from .preprocessors import wrap_metadata, plan_status_decorator
|
|
20
|
+
from .suspenders import dynamic_suspenders
|
|
21
|
+
from .groups import repeat
|
|
22
|
+
|
|
23
|
+
# from ..settings import settings
|
|
24
|
+
from typing import Optional
|
|
25
|
+
from importlib.metadata import entry_points
|
|
26
|
+
from functools import reduce
|
|
27
|
+
from datetime import datetime
|
|
28
|
+
from bluesky_queueserver import parameter_annotation_decorator
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def wrap_scantype(scantype):
|
|
32
|
+
def decorator(func):
|
|
33
|
+
return wrap_metadata({"scantype": scantype})(func)
|
|
34
|
+
|
|
35
|
+
return decorator
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _beamline_setup(func):
|
|
39
|
+
blconf = GLOBAL_BEAMLINE.config.get("configuration", {})
|
|
40
|
+
if blconf.get("has_slits", False):
|
|
41
|
+
func = _slit_setup(func)
|
|
42
|
+
if blconf.get("has_motorized_samples", False):
|
|
43
|
+
func = _sample_setup_with_move(func)
|
|
44
|
+
else:
|
|
45
|
+
func = _sample_setup_no_move(func)
|
|
46
|
+
func = _eref_setup(func)
|
|
47
|
+
func = _energy_setup(func)
|
|
48
|
+
return func
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _eref_setup(func):
|
|
52
|
+
@merge_func(func)
|
|
53
|
+
def _inner(
|
|
54
|
+
*args, eref_sample: Optional[str] = None, md: Optional[dict] = None, **kwargs
|
|
55
|
+
):
|
|
56
|
+
"""
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
eref_sample : str, optional
|
|
60
|
+
The energy reference sample. If given, the selected reference sample is set
|
|
61
|
+
"""
|
|
62
|
+
md = md or {}
|
|
63
|
+
_md = {}
|
|
64
|
+
blconf = GLOBAL_BEAMLINE.config.get("configuration", {})
|
|
65
|
+
if eref_sample is not None and blconf.get("has_motorized_eref", False):
|
|
66
|
+
yield from update_plan_status("moving energy reference sample")
|
|
67
|
+
yield from sampleholder_move_sample(
|
|
68
|
+
GLOBAL_BEAMLINE.reference_sampleholder, eref_sample
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
_md.update({"reference_sample": eref_sample})
|
|
72
|
+
_md.update(md)
|
|
73
|
+
return (yield from func(*args, md=_md, **kwargs))
|
|
74
|
+
|
|
75
|
+
return _inner
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _sample_setup_with_move(func):
|
|
79
|
+
@merge_func(func)
|
|
80
|
+
def _inner(
|
|
81
|
+
*args,
|
|
82
|
+
sample: Optional[str] = None,
|
|
83
|
+
sample_position: Optional[dict] = {},
|
|
84
|
+
**kwargs,
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
sample : str, optional
|
|
90
|
+
The sample id. If given, the selected sample metadata is set
|
|
91
|
+
sample_position: dict, optional
|
|
92
|
+
A dictionary of positions relative to the sample center. Parameters not given will be assumed to
|
|
93
|
+
be the default for the sampleholder (typically moving the sample into the beam at a typical angle)
|
|
94
|
+
"""
|
|
95
|
+
if sample is not None:
|
|
96
|
+
yield from update_plan_status("moving_sample")
|
|
97
|
+
yield from sampleholder_move_sample(
|
|
98
|
+
GLOBAL_BEAMLINE.primary_sampleholder, sample, **sample_position
|
|
99
|
+
)
|
|
100
|
+
return (yield from func(*args, **kwargs))
|
|
101
|
+
|
|
102
|
+
return _inner
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _sample_setup_no_move(func):
|
|
106
|
+
@merge_func(func)
|
|
107
|
+
def _inner(*args, sample=None, **kwargs):
|
|
108
|
+
"""
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
sample : str, optional
|
|
112
|
+
The sample id. If given, the selected sample metadata is set, but the sample is not moved
|
|
113
|
+
"""
|
|
114
|
+
if sample is not None:
|
|
115
|
+
yield from sampleholder_set_sample(
|
|
116
|
+
GLOBAL_BEAMLINE.primary_sampleholder, sample
|
|
117
|
+
)
|
|
118
|
+
return (yield from func(*args, **kwargs))
|
|
119
|
+
|
|
120
|
+
return _inner
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _slit_setup(func):
|
|
124
|
+
@merge_func(func)
|
|
125
|
+
def _inner(*args, eslit: Optional[float] = None, **kwargs):
|
|
126
|
+
"""
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
eslit : float, optional
|
|
130
|
+
If not None, will set the beamline exit slit prior to the plan start.
|
|
131
|
+
"""
|
|
132
|
+
if eslit is not None:
|
|
133
|
+
yield from update_plan_status("setting exit slit")
|
|
134
|
+
yield from mv(GLOBAL_BEAMLINE.slits, eslit)
|
|
135
|
+
return (yield from func(*args, **kwargs))
|
|
136
|
+
|
|
137
|
+
return _inner
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _energy_setup(func):
|
|
141
|
+
@merge_func(func)
|
|
142
|
+
def _inner(
|
|
143
|
+
*args,
|
|
144
|
+
energy: Optional[float] = None,
|
|
145
|
+
polarization: Optional[float] = None,
|
|
146
|
+
**kwargs,
|
|
147
|
+
):
|
|
148
|
+
"""
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
energy : float, optional
|
|
152
|
+
If not None, will set the beamline energy prior to the plan start.
|
|
153
|
+
"""
|
|
154
|
+
print("Energy Setup Decorator")
|
|
155
|
+
|
|
156
|
+
if energy is not None:
|
|
157
|
+
print("Setting up energy")
|
|
158
|
+
yield from update_plan_status("setting up energy")
|
|
159
|
+
yield from mv(GLOBAL_BEAMLINE.energy, energy)
|
|
160
|
+
if polarization is not None and hasattr(GLOBAL_BEAMLINE, "polarization"):
|
|
161
|
+
yield from update_plan_status("setting up polarization")
|
|
162
|
+
yield from mv(GLOBAL_BEAMLINE.polarization, polarization)
|
|
163
|
+
return (yield from func(*args, **kwargs))
|
|
164
|
+
|
|
165
|
+
return _inner
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def staged_baseline_wrapper(plan, devices, name="staged_baseline"):
|
|
169
|
+
"""
|
|
170
|
+
Preprocessor that records a baseline of all `devices` after `open_run`
|
|
171
|
+
|
|
172
|
+
The readings are designated for a separate event stream named 'baseline' by
|
|
173
|
+
default.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
plan : iterable or iterator
|
|
178
|
+
a generator, list, or similar containing `Msg` objects
|
|
179
|
+
devices : collection
|
|
180
|
+
collection of Devices to read
|
|
181
|
+
If None, the plan passes through unchanged.
|
|
182
|
+
name : string, optional
|
|
183
|
+
name for event stream; by default, 'baseline'
|
|
184
|
+
|
|
185
|
+
Yields
|
|
186
|
+
------
|
|
187
|
+
msg : Msg
|
|
188
|
+
messages from plan, with 'set' messages inserted
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
def head():
|
|
192
|
+
yield from declare_stream(*devices, name=name)
|
|
193
|
+
yield from trigger_and_read(devices, name=name)
|
|
194
|
+
|
|
195
|
+
def tail():
|
|
196
|
+
yield from trigger_and_read(devices, name=name)
|
|
197
|
+
|
|
198
|
+
def insert_baseline(msg):
|
|
199
|
+
if msg.command == "open_run":
|
|
200
|
+
if msg.run is not None:
|
|
201
|
+
return None, set_run_key_wrapper(head(), msg.run)
|
|
202
|
+
else:
|
|
203
|
+
return None, head()
|
|
204
|
+
|
|
205
|
+
elif msg.command == "close_run":
|
|
206
|
+
if msg.run is not None:
|
|
207
|
+
return set_run_key_wrapper(tail(), msg.run), None
|
|
208
|
+
else:
|
|
209
|
+
return tail(), None
|
|
210
|
+
|
|
211
|
+
return None, None
|
|
212
|
+
|
|
213
|
+
if not devices:
|
|
214
|
+
# no-op
|
|
215
|
+
return (yield from plan)
|
|
216
|
+
else:
|
|
217
|
+
return (yield from plan_mutator(plan, insert_baseline))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _nbs_setup_detectors_with_baseline(func):
|
|
221
|
+
@parameter_annotation_decorator({"parameters": {"dwell": {"min": 0}}})
|
|
222
|
+
@merge_func(func, ["detectors"])
|
|
223
|
+
def _inner(*args, extra_dets=[], dwell: Optional[float] = None, **kwargs):
|
|
224
|
+
"""
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
extra_dets : list, optional
|
|
228
|
+
A list of extra detectors to be activated for the scan, by default [].
|
|
229
|
+
dwell : float, optional
|
|
230
|
+
The exposure time in seconds for all detectors, by default None.
|
|
231
|
+
If None, do not set any exposure time, and assume that detectors are already set
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
# for det in extra_dets:
|
|
235
|
+
# activate_detector(det)
|
|
236
|
+
print("Detector Setup Decorator with Baseline")
|
|
237
|
+
if dwell is not None:
|
|
238
|
+
yield from set_exposure(dwell, extra_dets=extra_dets)
|
|
239
|
+
|
|
240
|
+
all_dets = separate_devices(GLOBAL_BEAMLINE.detectors.active + extra_dets)
|
|
241
|
+
# We need to stage only the baseline devices that are not already in the list of detectors
|
|
242
|
+
# In order to avoid double staging
|
|
243
|
+
if hasattr(GLOBAL_BEAMLINE, "staged_baseline"):
|
|
244
|
+
staged_baseline_devices = separate_devices(
|
|
245
|
+
GLOBAL_BEAMLINE.staged_baseline.values()
|
|
246
|
+
)
|
|
247
|
+
devices_to_stage = [
|
|
248
|
+
dev for dev in staged_baseline_devices if dev not in all_dets
|
|
249
|
+
]
|
|
250
|
+
else:
|
|
251
|
+
staged_baseline_devices = []
|
|
252
|
+
devices_to_stage = []
|
|
253
|
+
|
|
254
|
+
def inner():
|
|
255
|
+
yield from func(all_dets, *args, **kwargs)
|
|
256
|
+
|
|
257
|
+
ret = yield from stage_wrapper(
|
|
258
|
+
staged_baseline_wrapper(inner(), staged_baseline_devices), devices_to_stage
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# for det in extra_dets:
|
|
262
|
+
# deactivate_detector(det)
|
|
263
|
+
|
|
264
|
+
return ret
|
|
265
|
+
|
|
266
|
+
return _inner
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _nbs_setup_detectors(func):
|
|
270
|
+
@parameter_annotation_decorator({"parameters": {"dwell": {"min": 0}}})
|
|
271
|
+
@merge_func(func, ["detectors"])
|
|
272
|
+
def _inner(*args, extra_dets=[], dwell: Optional[float] = None, **kwargs):
|
|
273
|
+
"""
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
extra_dets : list, optional
|
|
277
|
+
A list of extra detectors to be activated for the scan, by default [].
|
|
278
|
+
dwell : float, optional
|
|
279
|
+
The exposure time in seconds for all detectors, by default None.
|
|
280
|
+
If None, do not set any exposure time, and assume that detectors are already set
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
# for det in extra_dets:
|
|
284
|
+
# activate_detector(det)
|
|
285
|
+
print("Detector Setup Decorator")
|
|
286
|
+
if dwell is not None:
|
|
287
|
+
yield from set_exposure(dwell, extra_dets=extra_dets)
|
|
288
|
+
all_dets = GLOBAL_BEAMLINE.detectors.active + extra_dets
|
|
289
|
+
ret = yield from func(all_dets, *args, **kwargs)
|
|
290
|
+
|
|
291
|
+
# for det in extra_dets:
|
|
292
|
+
# deactivate_detector(det)
|
|
293
|
+
|
|
294
|
+
return ret
|
|
295
|
+
|
|
296
|
+
return _inner
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _nbs_add_plot_md(func):
|
|
300
|
+
@merge_func(func)
|
|
301
|
+
def _inner(*args, md: Optional[dict] = None, plot_detectors: list = None, **kwargs):
|
|
302
|
+
md = md or {}
|
|
303
|
+
plot_hints = {}
|
|
304
|
+
if plot_detectors is not None:
|
|
305
|
+
activate_detector_set(plot_detectors)
|
|
306
|
+
plot_hints = GLOBAL_BEAMLINE.detectors.get_plot_hints()
|
|
307
|
+
_md = {"plot_hints": plot_hints}
|
|
308
|
+
_md.update(md)
|
|
309
|
+
return (yield from func(*args, md=_md, **kwargs))
|
|
310
|
+
|
|
311
|
+
return _inner
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _nbs_add_sample_md(func):
|
|
315
|
+
@merge_func(func)
|
|
316
|
+
def _inner(*args, md: Optional[dict] = None, **kwargs):
|
|
317
|
+
"""
|
|
318
|
+
Sample information is automatically added to the run md
|
|
319
|
+
"""
|
|
320
|
+
md = md or {}
|
|
321
|
+
if hasattr(GLOBAL_BEAMLINE, "current_sample"):
|
|
322
|
+
_md = {
|
|
323
|
+
"sample_name": GLOBAL_BEAMLINE.current_sample.get("name", ""),
|
|
324
|
+
"sample_id": GLOBAL_BEAMLINE.current_sample.get("sample_id", ""),
|
|
325
|
+
"sample_desc": GLOBAL_BEAMLINE.current_sample.get("description", ""),
|
|
326
|
+
"sample_set": GLOBAL_BEAMLINE.current_sample.get("group", ""),
|
|
327
|
+
"sample_info": {
|
|
328
|
+
k: v
|
|
329
|
+
for k, v in GLOBAL_BEAMLINE.current_sample.items()
|
|
330
|
+
if k not in ["name", "sample_id", "description", "group"]
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
_md.update(md)
|
|
334
|
+
return (yield from func(*args, md=_md, **kwargs))
|
|
335
|
+
else:
|
|
336
|
+
return (yield from func(*args, md=md, **kwargs))
|
|
337
|
+
|
|
338
|
+
return _inner
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _nbs_add_comment(func):
|
|
342
|
+
@merge_func(func)
|
|
343
|
+
def _inner(
|
|
344
|
+
*args,
|
|
345
|
+
md: Optional[dict] = None,
|
|
346
|
+
comment: Optional[str] = None,
|
|
347
|
+
group_name: Optional[str] = None,
|
|
348
|
+
**kwargs,
|
|
349
|
+
):
|
|
350
|
+
"""
|
|
351
|
+
Parameters
|
|
352
|
+
----------
|
|
353
|
+
comment : str, optional
|
|
354
|
+
A comment that will be added into the run metadata. If not provided, no comment will be added.
|
|
355
|
+
group_name : str, optional
|
|
356
|
+
A group name label that will be added into the run metadata.
|
|
357
|
+
"""
|
|
358
|
+
md = md or {}
|
|
359
|
+
if comment is not None:
|
|
360
|
+
_md = {"comment": comment}
|
|
361
|
+
else:
|
|
362
|
+
_md = {}
|
|
363
|
+
if group_name is not None:
|
|
364
|
+
_md["group_name"] = group_name
|
|
365
|
+
_md.update(md)
|
|
366
|
+
return (yield from func(*args, md=_md, **kwargs))
|
|
367
|
+
|
|
368
|
+
return _inner
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def nbs_base_scan_decorator(func):
|
|
372
|
+
@repeat
|
|
373
|
+
@plan_status_decorator
|
|
374
|
+
@_nbs_add_plan_args
|
|
375
|
+
@_beamline_setup
|
|
376
|
+
@_nbs_setup_detectors
|
|
377
|
+
@_nbs_add_sample_md
|
|
378
|
+
@_nbs_add_plot_md
|
|
379
|
+
@_nbs_add_comment
|
|
380
|
+
@merge_func(func)
|
|
381
|
+
def _inner(*args, **kwargs):
|
|
382
|
+
return (yield from func(*args, **kwargs))
|
|
383
|
+
|
|
384
|
+
return _inner
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _nbs_add_plan_args(func):
|
|
388
|
+
@merge_func(func)
|
|
389
|
+
def _inner(*args, md: Optional[dict] = None, **kwargs):
|
|
390
|
+
md = md or {}
|
|
391
|
+
_md = {}
|
|
392
|
+
_md.update(md)
|
|
393
|
+
_md.update(
|
|
394
|
+
{
|
|
395
|
+
"plan_passed_name": func.__name__,
|
|
396
|
+
# Don't have a way to normalize args and kwargs, copy.deepcopy is dangerous
|
|
397
|
+
# "plan_passed_args": args,
|
|
398
|
+
# "plan_passed_kwargs": kwargs,
|
|
399
|
+
"time_human": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
400
|
+
}
|
|
401
|
+
)
|
|
402
|
+
return (yield from func(*args, md=_md, **kwargs))
|
|
403
|
+
|
|
404
|
+
return _inner
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
"""
|
|
408
|
+
This was too clever, and was a mistake
|
|
409
|
+
def nbs_add_bl_prefix(func):
|
|
410
|
+
base_name = func.__name__
|
|
411
|
+
plan_name = f"{settings.beamline_prefix}_{base_name}"
|
|
412
|
+
func.__name__ = plan_name
|
|
413
|
+
return func
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def wrap_plan_name(func):
|
|
418
|
+
return wrap_metadata({"plan_name": func.__name__})(func)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def nbs_builtin_scan_wrapper(func):
|
|
422
|
+
"""
|
|
423
|
+
Designed to wrap bluesky built-in scans to produce an nbs version
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
@wrap_plan_name
|
|
427
|
+
@nbs_base_scan_decorator
|
|
428
|
+
@merge_func(func)
|
|
429
|
+
def _inner(*args, **kwargs):
|
|
430
|
+
print("Running ", func.__name__)
|
|
431
|
+
return (yield from func(*args, **kwargs))
|
|
432
|
+
|
|
433
|
+
# _inner = wrap_metadata({"plan_name": plan_name})(nbs_base_scan_decorator(func))
|
|
434
|
+
|
|
435
|
+
d = f"""Modifies {func.__name__} to automatically fill
|
|
436
|
+
dets with global active beamline detectors.
|
|
437
|
+
Other detectors may be added on the fly via extra_dets
|
|
438
|
+
---------------------------------------------------------
|
|
439
|
+
"""
|
|
440
|
+
|
|
441
|
+
_inner.__doc__ = d + _inner.__doc__
|
|
442
|
+
return _inner
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def get_decorator_entrypoints():
|
|
446
|
+
"""
|
|
447
|
+
Get decorator entrypoints from beamline configuration.
|
|
448
|
+
|
|
449
|
+
Returns
|
|
450
|
+
-------
|
|
451
|
+
list
|
|
452
|
+
List of decorator functions loaded from entrypoints
|
|
453
|
+
"""
|
|
454
|
+
config = GLOBAL_BEAMLINE.config
|
|
455
|
+
decorator_entrypoints = config.get("settings", {}).get("plan_decorators", [])
|
|
456
|
+
|
|
457
|
+
decorators = []
|
|
458
|
+
if decorator_entrypoints:
|
|
459
|
+
eps = entry_points()
|
|
460
|
+
for ep_name in decorator_entrypoints:
|
|
461
|
+
try:
|
|
462
|
+
# Look for entrypoint in nbs_bl.plan_decorators group
|
|
463
|
+
matches = eps.select(group="nbs_bl.plan_decorators", name=ep_name)
|
|
464
|
+
for match in matches:
|
|
465
|
+
decorator = match.load()
|
|
466
|
+
decorators.append(decorator)
|
|
467
|
+
except Exception as e:
|
|
468
|
+
print(f"Failed to load decorator {ep_name}: {e}")
|
|
469
|
+
|
|
470
|
+
return decorators
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def dynamic_scan_wrapper(func, func_name=None):
|
|
474
|
+
"""
|
|
475
|
+
A flexible wrapper for Bluesky scans that incorporates base NBS functionality
|
|
476
|
+
and additional decorators from entrypoints.
|
|
477
|
+
|
|
478
|
+
Parameters
|
|
479
|
+
----------
|
|
480
|
+
func : callable
|
|
481
|
+
The scan function to wrap
|
|
482
|
+
|
|
483
|
+
Returns
|
|
484
|
+
-------
|
|
485
|
+
callable
|
|
486
|
+
Wrapped scan function with all decorators applied
|
|
487
|
+
"""
|
|
488
|
+
# Get base decorators
|
|
489
|
+
base_decorators = [
|
|
490
|
+
merge_func(func, use_func_name=False),
|
|
491
|
+
dynamic_suspenders,
|
|
492
|
+
nbs_base_scan_decorator,
|
|
493
|
+
]
|
|
494
|
+
|
|
495
|
+
# Get additional decorators from entrypoints
|
|
496
|
+
additional_decorators = get_decorator_entrypoints()
|
|
497
|
+
|
|
498
|
+
# Combine all decorators
|
|
499
|
+
all_decorators = base_decorators + additional_decorators + [wrap_plan_name]
|
|
500
|
+
|
|
501
|
+
func_name = func_name or func.__name__
|
|
502
|
+
|
|
503
|
+
# Apply decorators in order
|
|
504
|
+
def _inner(*args, **kwargs):
|
|
505
|
+
print("Running ", func_name)
|
|
506
|
+
return (yield from func(*args, **kwargs))
|
|
507
|
+
|
|
508
|
+
if func_name is not None:
|
|
509
|
+
_inner.__name__ = func_name
|
|
510
|
+
# Apply all decorators in sequence
|
|
511
|
+
wrapped = reduce(lambda f, dec: dec(f), all_decorators, _inner)
|
|
512
|
+
|
|
513
|
+
# Add documentation
|
|
514
|
+
d = f"""Wraps {func.__name__} with additional arguments and default detectors.
|
|
515
|
+
|
|
516
|
+
Other detectors may be added on the fly via extra_dets
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
Additional decorators loaded:
|
|
520
|
+
{[d.__name__ for d in additional_decorators]}
|
|
521
|
+
"""
|
|
522
|
+
wrapped.__doc__ = d + (wrapped.__doc__ or "")
|
|
523
|
+
|
|
524
|
+
return wrapped
|
nbs_bl/plans/scans.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from .scan_decorators import dynamic_scan_wrapper
|
|
2
|
+
from .scan_base import _make_gscan_points
|
|
3
|
+
from .flyscan_base import fly_scan
|
|
4
|
+
from ..help import add_to_scan_list, add_to_plan_time_dict
|
|
5
|
+
from ..utils import merge_func
|
|
6
|
+
from ..beamline import GLOBAL_BEAMLINE as bl
|
|
7
|
+
from .plan_stubs import update_plan_status
|
|
8
|
+
|
|
9
|
+
import bluesky.plans as bp
|
|
10
|
+
from bluesky.plan_stubs import mv
|
|
11
|
+
|
|
12
|
+
_scan_list = [
|
|
13
|
+
bp.count,
|
|
14
|
+
bp.scan,
|
|
15
|
+
bp.rel_scan,
|
|
16
|
+
bp.list_scan,
|
|
17
|
+
bp.rel_list_scan,
|
|
18
|
+
bp.list_grid_scan,
|
|
19
|
+
bp.rel_list_grid_scan,
|
|
20
|
+
bp.log_scan,
|
|
21
|
+
bp.rel_log_scan,
|
|
22
|
+
bp.grid_scan,
|
|
23
|
+
bp.rel_grid_scan,
|
|
24
|
+
bp.scan_nd,
|
|
25
|
+
bp.spiral,
|
|
26
|
+
bp.spiral_fermat,
|
|
27
|
+
bp.spiral_square,
|
|
28
|
+
bp.rel_spiral,
|
|
29
|
+
bp.rel_spiral_fermat,
|
|
30
|
+
bp.rel_spiral_square,
|
|
31
|
+
fly_scan,
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
for _scan in _scan_list:
|
|
35
|
+
_fixedname = f"nbs_{_scan.__name__}"
|
|
36
|
+
_newscan = dynamic_scan_wrapper(_scan, func_name=_fixedname)
|
|
37
|
+
# _newscan.__name__ = _fixedname
|
|
38
|
+
globals()[_fixedname] = _newscan
|
|
39
|
+
add_to_scan_list(_newscan)
|
|
40
|
+
|
|
41
|
+
add_to_plan_time_dict(
|
|
42
|
+
nbs_count, "generic_estimate", fixed=0, overhead=0.05, dwell="dwell", points="num"
|
|
43
|
+
)
|
|
44
|
+
for _scan in [
|
|
45
|
+
bp.scan,
|
|
46
|
+
nbs_scan,
|
|
47
|
+
bp.rel_scan,
|
|
48
|
+
nbs_rel_scan,
|
|
49
|
+
bp.log_scan,
|
|
50
|
+
nbs_log_scan,
|
|
51
|
+
bp.rel_log_scan,
|
|
52
|
+
nbs_rel_log_scan,
|
|
53
|
+
]:
|
|
54
|
+
add_to_plan_time_dict(
|
|
55
|
+
_scan, "generic_estimate", fixed=5, overhead=0.5, dwell="dwell", points="num"
|
|
56
|
+
)
|
|
57
|
+
for _scan in [bp.list_scan, nbs_list_scan, bp.rel_list_scan, nbs_rel_list_scan]:
|
|
58
|
+
add_to_plan_time_dict(
|
|
59
|
+
_scan, "list_scan_estimate", fixed=5, overhead=0.5, dwell="dwell"
|
|
60
|
+
)
|
|
61
|
+
for _scan in [bp.grid_scan, nbs_grid_scan, bp.rel_grid_scan, nbs_rel_grid_scan]:
|
|
62
|
+
add_to_plan_time_dict(
|
|
63
|
+
_scan, "grid_scan_estimate", fixed=5, overhead=0.5, dwell="dwell"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
for _scan in [
|
|
67
|
+
bp.list_grid_scan,
|
|
68
|
+
nbs_list_grid_scan,
|
|
69
|
+
bp.rel_list_grid_scan,
|
|
70
|
+
nbs_rel_list_grid_scan,
|
|
71
|
+
]:
|
|
72
|
+
add_to_plan_time_dict(
|
|
73
|
+
_scan, "list_grid_scan_estimate", fixed=5, overhead=0.5, dwell="dwell"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@add_to_scan_list
|
|
78
|
+
@merge_func(
|
|
79
|
+
nbs_list_scan,
|
|
80
|
+
omit_params=["points"],
|
|
81
|
+
exclude_wrapper_args=False,
|
|
82
|
+
use_func_name=False,
|
|
83
|
+
)
|
|
84
|
+
def nbs_gscan(motor, start: float, *args, extra_dets=[], shift: float = 0, **kwargs):
|
|
85
|
+
"""A variable step scan of a motor and the default detectors.
|
|
86
|
+
|
|
87
|
+
Other detectors may be added via extra_dets
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
motor : Ophyd Device
|
|
92
|
+
The motor object to scan
|
|
93
|
+
start : float
|
|
94
|
+
Starting position for the scan
|
|
95
|
+
step : float, optional
|
|
96
|
+
Step size for first region
|
|
97
|
+
stop : float, optional
|
|
98
|
+
First stopping position
|
|
99
|
+
step2 : float, optional
|
|
100
|
+
Step size for second region. Additional stop/step pairs can be provided
|
|
101
|
+
stop2 : float, optional
|
|
102
|
+
Second stopping position. Additional stop/step pairs can be provided
|
|
103
|
+
*args : float, optional
|
|
104
|
+
Additional stop/step pairs can be provided
|
|
105
|
+
extra_dets : list
|
|
106
|
+
A list of detectors to add for just this scan
|
|
107
|
+
shift : float
|
|
108
|
+
A value to shift all start/stop positions by
|
|
109
|
+
|
|
110
|
+
Examples
|
|
111
|
+
--------
|
|
112
|
+
# Scan from 270 to 280 in steps of 1
|
|
113
|
+
nbs_gscan(energy, 270, 1, 280)
|
|
114
|
+
|
|
115
|
+
# Scan from 270 to 280 in steps of 1, then from 280 to 290 in steps of 0.25
|
|
116
|
+
nbs_gscan(energy, 270, 1, 280, 0.25, 290)
|
|
117
|
+
|
|
118
|
+
# Scan with multiple regions of different step sizes
|
|
119
|
+
nbs_gscan(energy, 270, 1, 280, 0.5, 285, 0.1, 290)
|
|
120
|
+
"""
|
|
121
|
+
points = _make_gscan_points(start, *args, shift=shift)
|
|
122
|
+
# Move motor to start position first
|
|
123
|
+
yield from update_plan_status(
|
|
124
|
+
f"moving motor {motor.name} to start position {points[0]}"
|
|
125
|
+
)
|
|
126
|
+
yield from mv(motor, points[0])
|
|
127
|
+
return (yield from nbs_list_scan(motor, points, extra_dets=extra_dets, **kwargs))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@add_to_scan_list
|
|
131
|
+
@merge_func(nbs_gscan, use_func_name=False, omit_params=["motor"])
|
|
132
|
+
def nbs_energy_scan(*args, **kwargs):
|
|
133
|
+
"""A variable step scan of the energy and the default detectors.
|
|
134
|
+
|
|
135
|
+
The energy motor is automatically included.Other detectors may be added via extra_dets.
|
|
136
|
+
"""
|
|
137
|
+
motor = bl.energy
|
|
138
|
+
return (yield from nbs_gscan(motor, *args, **kwargs))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
for _scan in [nbs_gscan, nbs_energy_scan]:
|
|
142
|
+
add_to_plan_time_dict(_scan, "gscan_estimate", fixed=5, overhead=0.5, dwell="dwell")
|
|
143
|
+
|
|
144
|
+
for _scan in [fly_scan, nbs_fly_scan]:
|
|
145
|
+
add_to_plan_time_dict(_scan, "fly_scan_estimate", fixed=5)
|