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,246 @@
1
+ from ..beamline import GLOBAL_BEAMLINE
2
+ from ..help import add_to_plan_list, add_to_condition_list
3
+ import warnings
4
+ from bluesky import Msg
5
+ from bluesky.plan_stubs import rd, sleep, mv
6
+ import time
7
+ from typing import Optional
8
+ from bluesky.protocols import Readable
9
+ import typing
10
+ from .conditions import is_signal_below, is_signal_equals, is_signal_above
11
+
12
+ GLOBAL_EXPOSURE_TIME = 1.0
13
+
14
+
15
+ def update_plan_status(status):
16
+ ret = yield Msg("update_plan_status", None, status)
17
+ return ret
18
+
19
+
20
+ def clear_plan_status():
21
+ ret = yield Msg("clear_plan_status")
22
+ return ret
23
+
24
+
25
+ def call_obj(obj, method, *args, **kwargs):
26
+ ret = yield Msg("call_obj", obj, *args, method=method, **kwargs)
27
+ return ret
28
+
29
+
30
+ def sampleholder_move_sample_old(sampleholder, sample_id=None, **position):
31
+ """
32
+ Set and move a sample. Dangerous! Sample moves really need to go through
33
+ Bluesky's mv plan, or else they won't be properly pause-able
34
+ """
35
+ yield from call_obj(sampleholder, "move_sample", sample_id, **position)
36
+
37
+
38
+ def sampleholder_move_sample(sampleholder, sample_id=None, **position):
39
+ """
40
+ Set and move a sample.
41
+ """
42
+ positions = yield from call_obj(
43
+ sampleholder, "get_sample_position", sample_id, **position
44
+ )
45
+ print(f"Moving {sampleholder.name} to {positions}")
46
+ yield from mv(sampleholder, positions)
47
+ print("Done Moving ", sampleholder.name)
48
+
49
+
50
+ def sampleholder_set_sample(sampleholder, sample_id):
51
+ """
52
+ Set a sample without moving it
53
+ """
54
+ yield from call_obj(sampleholder, "set_sample", sample_id)
55
+
56
+
57
+ @add_to_plan_list
58
+ def set_exposure(time: Optional[float] = None, extra_dets=[]):
59
+ """Sets the exposure time for all active detectors"""
60
+ global GLOBAL_EXPOSURE_TIME
61
+ if time is not None:
62
+ GLOBAL_EXPOSURE_TIME = time
63
+
64
+ all_dets = GLOBAL_BEAMLINE.detectors.active + extra_dets
65
+ for d in all_dets:
66
+ try:
67
+ if hasattr(d, "set_exposure"):
68
+ yield from call_obj(d, "set_exposure", GLOBAL_EXPOSURE_TIME)
69
+ except RuntimeError as ex:
70
+ warnings.warn(repr(ex), RuntimeWarning)
71
+
72
+
73
+ @add_to_plan_list
74
+ def set_roi(label, llim, ulim):
75
+ for d in GLOBAL_BEAMLINE.detectors.active:
76
+ try:
77
+ if hasattr(d, "set_roi"):
78
+ yield from call_obj(d, "set_roi", label, llim, ulim)
79
+ except RuntimeError as ex:
80
+ warnings.warn(repr(ex), RuntimeWarning)
81
+
82
+
83
+ @add_to_plan_list
84
+ def clear_all_rois():
85
+ for d in GLOBAL_BEAMLINE.detectors.active:
86
+ try:
87
+ if hasattr(d, "clear_all_rois"):
88
+ yield from call_obj(d, "clear_all_rois")
89
+ except RuntimeError as ex:
90
+ warnings.warn(repr(ex), RuntimeWarning)
91
+
92
+
93
+ @add_to_plan_list
94
+ def clear_one_roi(label):
95
+ for d in GLOBAL_BEAMLINE.detectors.active:
96
+ try:
97
+ if hasattr(d, "clear_roi"):
98
+ yield from call_obj(d, "clear_roi", label)
99
+ except RuntimeError as ex:
100
+ warnings.warn(repr(ex), RuntimeWarning)
101
+
102
+
103
+ @add_to_plan_list
104
+ def wait_for_condition(
105
+ condition,
106
+ condition_args: typing.List = None,
107
+ condition_kwargs: typing.Dict = None,
108
+ timeout: Optional[float] = None,
109
+ sleep_time: float = 10,
110
+ ):
111
+ """
112
+ Wait for a condition function to return True.
113
+
114
+ Parameters
115
+ ----------
116
+ condition : str or callable
117
+ A condition function that returns a boolean.
118
+ condition_args : list, optional
119
+ Arguments for the condition function.
120
+ condition_kwargs : dict, optional
121
+ Keyword arguments for the condition function.
122
+ timeout : Optional[float], optional
123
+ Maximum time to wait in seconds. If None, wait indefinitely.
124
+ sleep_time : float, optional
125
+ Time to sleep between checks in seconds. Default is 10.
126
+
127
+ Returns
128
+ -------
129
+ bool
130
+ True when condition returns True.
131
+
132
+ Raises
133
+ ------
134
+ TimeoutError
135
+ If timeout is reached before condition is met.
136
+ """
137
+ if condition_args is None:
138
+ condition_args = []
139
+ if condition_kwargs is None:
140
+ condition_kwargs = {}
141
+
142
+ start_time = time.time()
143
+ while True:
144
+ if timeout is not None and (time.time() - start_time > timeout):
145
+ raise TimeoutError
146
+ result = yield from condition(*condition_args, **condition_kwargs)
147
+ if result:
148
+ return True
149
+ else:
150
+ yield from sleep(sleep_time)
151
+
152
+
153
+ def wait_for_signal_below(
154
+ sig: Readable, val: float, timeout: Optional[float] = None, sleep_time: float = 10
155
+ ):
156
+ """
157
+ Wait for a readable object to go below a threshold value.
158
+
159
+ Parameters
160
+ ----------
161
+ sig : Readable
162
+ Any object that implements the Readable protocol (can be read with rd()).
163
+ val : float
164
+ The target value to wait for.
165
+ timeout : Optional[float], optional
166
+ Maximum time to wait in seconds. If None, wait indefinitely.
167
+ sleep_time : float, optional
168
+ Time to sleep between checks in seconds. Default is 10.
169
+
170
+ Returns
171
+ -------
172
+ bool
173
+ True when signal goes below threshold.
174
+
175
+ Raises
176
+ ------
177
+ TimeoutError
178
+ If timeout is reached before condition is met.
179
+ """
180
+ return wait_for_condition(
181
+ is_signal_below, [sig, val], timeout=timeout, sleep_time=sleep_time
182
+ )
183
+
184
+
185
+ def wait_for_signal_equals(
186
+ sig: Readable, val: float, timeout: Optional[float] = None, sleep_time: float = 10
187
+ ):
188
+ """
189
+ Wait for a readable object to equal a target value.
190
+
191
+ Parameters
192
+ ----------
193
+ sig : Readable
194
+ Any object that implements the Readable protocol (can be read with rd()).
195
+ val : float
196
+ The target value to wait for.
197
+ timeout : Optional[float], optional
198
+ Maximum time to wait in seconds. If None, wait indefinitely.
199
+ sleep_time : float, optional
200
+ Time to sleep between checks in seconds. Default is 10.
201
+
202
+ Returns
203
+ -------
204
+ bool
205
+ True when signal equals target value.
206
+
207
+ Raises
208
+ ------
209
+ TimeoutError
210
+ If timeout is reached before condition is met.
211
+ """
212
+ return wait_for_condition(
213
+ is_signal_equals, [sig, val], timeout=timeout, sleep_time=sleep_time
214
+ )
215
+
216
+
217
+ def wait_for_signal_above(
218
+ sig: Readable, val: float, timeout: Optional[float] = None, sleep_time: float = 10
219
+ ):
220
+ """
221
+ Wait for a readable object to go above a threshold value.
222
+
223
+ Parameters
224
+ ----------
225
+ sig : Readable
226
+ Any object that implements the Readable protocol (can be read with rd()).
227
+ val : float
228
+ The target value to wait for.
229
+ timeout : Optional[float], optional
230
+ Maximum time to wait in seconds. If None, wait indefinitely.
231
+ sleep_time : float, optional
232
+ Time to sleep between checks in seconds. Default is 10.
233
+
234
+ Returns
235
+ -------
236
+ bool
237
+ True when signal goes above threshold.
238
+
239
+ Raises
240
+ ------
241
+ TimeoutError
242
+ If timeout is reached before condition is met.
243
+ """
244
+ return wait_for_condition(
245
+ is_signal_above, [sig, val], timeout=timeout, sleep_time=sleep_time
246
+ )
@@ -0,0 +1,160 @@
1
+ from bluesky.plan_stubs import open_run, close_run
2
+ from bluesky.utils import RunEngineControlException, make_decorator
3
+ from bluesky.preprocessors import contingency_wrapper
4
+ from bluesky.preprocessors import plan_mutator, ensure_generator, single_gen
5
+ from bluesky import Msg
6
+ from functools import wraps
7
+ from ..utils import merge_func
8
+ from copy import deepcopy
9
+ import inspect
10
+ import uuid
11
+ from typing import Optional
12
+
13
+
14
+ def sanitize(arg):
15
+ # storable_types = (int, float, str, bool)
16
+ if type(arg) == list:
17
+ _arg = []
18
+ for thing in arg:
19
+ try:
20
+ _arg.append(thing.name)
21
+ except Exception:
22
+ _arg.append(thing)
23
+ return _arg
24
+ else:
25
+ if hasattr(arg, "name"):
26
+ return arg.name
27
+ else:
28
+ return arg
29
+
30
+
31
+ def plan_md_decorator(plan_function):
32
+ @wraps(plan_function)
33
+ def _inner(*args, md: Optional[dict] = None, plan_level=0, **kwargs):
34
+ plan_name = plan_function.__name__
35
+ md = md or {}
36
+ _md = {}
37
+ arguments = {}
38
+ s = inspect.signature(plan_function)
39
+ for p in s.parameters.values():
40
+ if p.default is not s.empty:
41
+ arguments[p.name] = p.default
42
+
43
+ for a, n in zip(args, plan_function.__code__.co_varnames):
44
+ arguments[n] = sanitize(a)
45
+ for k, v in kwargs.items():
46
+ arguments[k] = sanitize(v)
47
+ if "md" in arguments:
48
+ del arguments["md"]
49
+
50
+ _md["plan_history"] = []
51
+ if plan_level == 0:
52
+ _md["master_plan"] = plan_name
53
+ _md["batch_uid"] = str(uuid.uuid4())
54
+ _md.update(deepcopy(md))
55
+ _md["plan_history"].append(
56
+ {"plan_name": plan_name, "arguments": arguments, "plan_level": plan_level}
57
+ )
58
+ return plan_function(*args, md=_md, plan_level=plan_level + 1, **kwargs)
59
+
60
+ return _inner
61
+
62
+
63
+ def wrap_metadata(param):
64
+ def decorator(func):
65
+ @merge_func(func)
66
+ def inner(*args, md: Optional[dict] = None, **kwargs):
67
+ md = md or {}
68
+ _md = {}
69
+ _md.update(param)
70
+ _md.update(md)
71
+ return (yield from func(*args, md=_md, **kwargs))
72
+
73
+ return inner
74
+
75
+ return decorator
76
+
77
+
78
+ def run_return_wrapper(plan, *, md: Optional[dict] = None):
79
+ """Enclose in 'open_run' and 'close_run' messages.
80
+
81
+ Parameters
82
+ ----------
83
+ plan : iterable or iterator
84
+ a generator, list, or similar containing `Msg` objects
85
+ md : dict, optional
86
+ metadata to be passed into the 'open_run' message
87
+ """
88
+ yield from open_run(md)
89
+
90
+ def except_plan(e):
91
+ if isinstance(e, RunEngineControlException):
92
+ yield from close_run(exit_status=e.exit_status)
93
+ else:
94
+ yield from close_run(exit_status="fail", reason=str(e))
95
+
96
+ return (
97
+ yield from contingency_wrapper(
98
+ plan, except_plan=except_plan, else_plan=close_run
99
+ )
100
+ )
101
+
102
+
103
+ run_return_decorator = make_decorator(run_return_wrapper)
104
+
105
+
106
+ def plan_status_decorator(func):
107
+ """
108
+ Update plan status, and clear after plan completion
109
+
110
+ Parameters
111
+ ----------
112
+ plan : iterable or iterator
113
+ a generator, list, or similar containing `Msg` objects
114
+ status : str
115
+ the status to update to
116
+
117
+ Yields
118
+ ------
119
+ msg : Msg
120
+ messages from plan with 'update_plan_status', and 'clear_plan_status' messages inserted
121
+
122
+ """
123
+
124
+ @merge_func(func)
125
+ def _inner(*args, md: Optional[dict] = None, **kwargs):
126
+ # Get plan name from metadata if available
127
+ md = md or {}
128
+ plan_name = md.get("plan_name", func.__name__) if md else func.__name__
129
+ status = "Running " + plan_name
130
+ print(f"Plan status decorator: {status}")
131
+ update_msgs = [Msg("update_plan_status", None, status)]
132
+ clear_msgs = [Msg("clear_plan_status")]
133
+
134
+ def insert_after_open(msg):
135
+ if msg.command == "open_run":
136
+
137
+ def new_gen():
138
+ yield from ensure_generator(update_msgs)
139
+
140
+ return single_gen(msg), new_gen()
141
+ else:
142
+ return None, None
143
+
144
+ def insert_before_close(msg):
145
+ if msg.command == "close_run":
146
+
147
+ def new_gen():
148
+ yield from ensure_generator(clear_msgs)
149
+ yield msg
150
+
151
+ return new_gen(), None
152
+ else:
153
+ return None, None
154
+
155
+ # Apply nested mutations.
156
+ plan1 = plan_mutator(func(*args, md=md, **kwargs), insert_after_open)
157
+ plan2 = plan_mutator(plan1, insert_before_close)
158
+ return (yield from plan2)
159
+
160
+ return _inner
@@ -0,0 +1,58 @@
1
+ def _make_gscan_points(*args, shift: float = 0):
2
+ """
3
+ Generate a sequence of energy scan points from a variable-length parameter list.
4
+
5
+ Parameters should be passed in the following format:
6
+ (estart1, delta1, estop1, delta2, estop2, ...)
7
+
8
+ - Each segment defines a range starting from the given estart and ending at the estop,
9
+ using the specified delta (step size).
10
+ - Delta values must be positive. The function automatically determines the direction
11
+ (increasing or decreasing) based on the estart and estop values.
12
+ - A single energy value can be passed to return a single-point list.
13
+ - Segments can be non-monotonic — i.e., you can mix increasing and decreasing ranges.
14
+ - An optional `shift` parameter can be used to apply a constant offset to all values.
15
+
16
+ Examples:
17
+ _make_gscan_points(250)
18
+ ➜ [250.0]
19
+
20
+ _make_gscan_points(250, 5, 260)
21
+ ➜ [250.0, 255.0, 260.0]
22
+
23
+ _make_gscan_points(264, 2, 260, 5, 250)
24
+ ➜ [264.0, 262.0, 260.0, 255.0, 250.0]
25
+
26
+ Raises:
27
+ TypeError if the number of arguments is incorrect
28
+ ValueError if any delta is zero
29
+ """
30
+
31
+ if len(args) == 1:
32
+ return [float(args[0]) + shift]
33
+ if (len(args) - 1) % 2 != 0:
34
+ raise TypeError(
35
+ "gscan received an even number of arguments. Either a stop or a step-size is missing. Expected format: (estart1, delta1, estop1, delta2, estop2, ...)"
36
+ )
37
+ points = []
38
+ for i in range(1, len(args) - 1, 2):
39
+ estart = float(args[i - 1]) + shift
40
+ delta = abs(args[i]) ## Ensures delta is positive in case users input negative delta for reverse energy list.
41
+ estop = float(args[i + 1]) + shift
42
+
43
+ if delta == 0:
44
+ raise ValueError("Step size (delta) cannot be zero.")
45
+
46
+ step = delta if estop > estart else -delta
47
+
48
+ if not points or points[-1] != estart:
49
+ points.append(estart)
50
+
51
+ next_point = estart + step
52
+ while (step > 0 and next_point < estop - step / 2.0) or (step < 0 and next_point > estop - step / 2.0):
53
+ points.append(next_point)
54
+ next_point += step
55
+
56
+ points.append(estop)
57
+
58
+ return points