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.
Files changed (64) hide show
  1. nbs_bl/__init__.py +15 -0
  2. nbs_bl/beamline.py +450 -0
  3. nbs_bl/configuration.py +838 -0
  4. nbs_bl/detectors.py +89 -0
  5. nbs_bl/devices/__init__.py +12 -0
  6. nbs_bl/devices/detectors.py +154 -0
  7. nbs_bl/devices/motors.py +242 -0
  8. nbs_bl/devices/sampleholders.py +360 -0
  9. nbs_bl/devices/shutters.py +120 -0
  10. nbs_bl/devices/slits.py +51 -0
  11. nbs_bl/gGrEqns.py +171 -0
  12. nbs_bl/geometry/__init__.py +0 -0
  13. nbs_bl/geometry/affine.py +197 -0
  14. nbs_bl/geometry/bars.py +189 -0
  15. nbs_bl/geometry/frames.py +534 -0
  16. nbs_bl/geometry/linalg.py +138 -0
  17. nbs_bl/geometry/polygons.py +56 -0
  18. nbs_bl/help.py +126 -0
  19. nbs_bl/hw.py +270 -0
  20. nbs_bl/load.py +113 -0
  21. nbs_bl/motors.py +19 -0
  22. nbs_bl/planStatus.py +5 -0
  23. nbs_bl/plans/__init__.py +8 -0
  24. nbs_bl/plans/batches.py +174 -0
  25. nbs_bl/plans/conditions.py +77 -0
  26. nbs_bl/plans/flyscan_base.py +180 -0
  27. nbs_bl/plans/groups.py +55 -0
  28. nbs_bl/plans/maximizers.py +423 -0
  29. nbs_bl/plans/metaplans.py +179 -0
  30. nbs_bl/plans/plan_stubs.py +246 -0
  31. nbs_bl/plans/preprocessors.py +160 -0
  32. nbs_bl/plans/scan_base.py +58 -0
  33. nbs_bl/plans/scan_decorators.py +524 -0
  34. nbs_bl/plans/scans.py +145 -0
  35. nbs_bl/plans/suspenders.py +87 -0
  36. nbs_bl/plans/time_estimation.py +168 -0
  37. nbs_bl/plans/xas.py +123 -0
  38. nbs_bl/printing.py +221 -0
  39. nbs_bl/qt/models/beamline.py +11 -0
  40. nbs_bl/qt/models/energy.py +53 -0
  41. nbs_bl/qt/widgets/energy.py +225 -0
  42. nbs_bl/queueserver.py +249 -0
  43. nbs_bl/redisDevice.py +96 -0
  44. nbs_bl/run_engine.py +63 -0
  45. nbs_bl/samples.py +130 -0
  46. nbs_bl/settings.py +68 -0
  47. nbs_bl/shutters.py +39 -0
  48. nbs_bl/sim/__init__.py +2 -0
  49. nbs_bl/sim/config/polphase.nc +0 -0
  50. nbs_bl/sim/energy.py +403 -0
  51. nbs_bl/sim/manipulator.py +14 -0
  52. nbs_bl/sim/utils.py +36 -0
  53. nbs_bl/startup.py +27 -0
  54. nbs_bl/status.py +114 -0
  55. nbs_bl/tests/__init__.py +0 -0
  56. nbs_bl/tests/modify_regions.py +160 -0
  57. nbs_bl/tests/test_frames.py +99 -0
  58. nbs_bl/tests/test_panels.py +69 -0
  59. nbs_bl/utils.py +235 -0
  60. nbs_bl-0.2.0.dist-info/METADATA +71 -0
  61. nbs_bl-0.2.0.dist-info/RECORD +64 -0
  62. nbs_bl-0.2.0.dist-info/WHEEL +4 -0
  63. nbs_bl-0.2.0.dist-info/entry_points.txt +2 -0
  64. 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)