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
nbs_bl/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """
2
+ Why do I have anything at all in __init__?
3
+ from importlib.metadata import version, PackageNotFoundError
4
+ from os.path import join, exists
5
+ from .configuration import loadDeviceConfig
6
+
7
+ try:
8
+ __version__ = version("sst_funcs")
9
+ except PackageNotFoundError:
10
+ # package is not installed
11
+ pass
12
+
13
+
14
+ """
15
+ # ip.user_ns['RE'] = RE
nbs_bl/beamline.py ADDED
@@ -0,0 +1,450 @@
1
+ from bluesky.preprocessors import SupplementalData
2
+ from .queueserver import GLOBAL_USER_STATUS
3
+ from .status import StatusDict
4
+ from .hw import HardwareGroup, DetectorGroup, loadDevices
5
+ from nbs_core.autoload import instantiateOphyd, _find_deferred_devices
6
+
7
+ import IPython
8
+
9
+
10
+ class BeamlineModel:
11
+ default_groups = [
12
+ "shutters",
13
+ "gatevalves",
14
+ "apertures",
15
+ "pinholes",
16
+ "gauges",
17
+ "motors",
18
+ "detectors",
19
+ "manipulators",
20
+ "mirrors",
21
+ "controllers",
22
+ "vacuum",
23
+ "misc",
24
+ ]
25
+
26
+ default_roles = [
27
+ "beam_current",
28
+ "beam_status",
29
+ "default_shutter",
30
+ "energy",
31
+ "intensity_detector",
32
+ "primary_sampleholder",
33
+ "reference_sampleholder",
34
+ "slits",
35
+ ]
36
+
37
+ reserved = [
38
+ "current_sample",
39
+ "samples",
40
+ "config",
41
+ "groups",
42
+ "roles",
43
+ "supplemental_data",
44
+ "devices",
45
+ "redis",
46
+ "RE",
47
+ "md",
48
+ ]
49
+
50
+ def __init__(self, *args, **kwargs):
51
+ """
52
+ Creates an empty BeamlineModel.
53
+
54
+ The model is a pure data structure. Use BeamlineInitializer
55
+ to initialize it from configuration files.
56
+ """
57
+ print("Initializing beamline model")
58
+ self.reset()
59
+
60
+ def add_device(self, device_name, device_info):
61
+ """
62
+ Add a device to the beamline model.
63
+
64
+ Parameters
65
+ ----------
66
+ device_name : str
67
+ Name of the device
68
+ device_info : dict
69
+ Dictionary containing the following keys:
70
+ - device: The device object
71
+ - loaded: Whether the device is loaded
72
+ - groups: List of groups the device is in
73
+ - roles: List of roles the device is in
74
+ """
75
+ # print(f"Adding device {device_name} to beamline model: {device_info}")
76
+ self._add_device_to_modes(device_name, device_info)
77
+ if not device_info["loaded"]:
78
+ self._deferred_devices.add(device_name)
79
+ self._deferred_config[device_name] = device_info["config"]
80
+ return
81
+
82
+ self.devices[device_name] = device_info["device"]
83
+ self._add_device_to_groups(device_name, device_info)
84
+ self._set_device_roles(device_name, device_info)
85
+
86
+ def _add_device_to_modes(self, device_name, device_info):
87
+ modes = device_info["config"].get("_modes", [])
88
+ for mode in modes:
89
+ if mode in self.modes:
90
+ self.modes[mode].append(device_name)
91
+ else:
92
+ self.modes[mode] = [device_name]
93
+
94
+ def _set_device_roles(self, device_name, device_info):
95
+ """
96
+ Handle role assignment and special device setup.
97
+
98
+ Parameters
99
+ ----------
100
+ role : str
101
+ Role name to configure
102
+ key : str
103
+ Device key to assign to the role
104
+ """
105
+ roles = device_info["roles"]
106
+ for role in roles:
107
+ if role in self.reserved:
108
+ raise KeyError(f"Key {role} is reserved, use a different role name")
109
+ if role != "":
110
+ if role not in self.roles:
111
+ self.roles.append(role)
112
+ print(f"Setting {role} to {device_name}")
113
+ setattr(self, role, self.devices[device_name])
114
+
115
+ def handle_special_devices(self):
116
+ """
117
+ Handle special device setup, particularly sampleholders.
118
+
119
+ Parameters
120
+ ----------
121
+ roles : dict
122
+ Dictionary mapping role names to device names
123
+ """
124
+ self._setup_sampleholder(
125
+ self.primary_sampleholder,
126
+ "GLOBAL_SAMPLES",
127
+ "GLOBAL_SELECTED",
128
+ is_primary=True,
129
+ )
130
+ if "reference_sampleholder" in self.roles:
131
+ self._setup_sampleholder(
132
+ self.reference_sampleholder,
133
+ "REFERENCE_SAMPLES",
134
+ "REFERENCE_SELECTED",
135
+ is_primary=False,
136
+ )
137
+
138
+ def _setup_sampleholder(self, holder, samples_key, current_key, is_primary=False):
139
+ """
140
+ Set up a sampleholder with Redis data.
141
+
142
+ Parameters
143
+ ----------
144
+ holder : object
145
+ The sampleholder device to set up
146
+ samples_key : str
147
+ Redis key for samples data
148
+ current_key : str
149
+ Redis key for current sample data
150
+ is_primary : bool, optional
151
+ Whether this is the primary sampleholder
152
+ """
153
+ if holder is None:
154
+ return
155
+
156
+ tmp_samples = GLOBAL_USER_STATUS.request_status_dict(
157
+ samples_key, use_redis=True
158
+ )
159
+ tmp_samples.update(holder.samples)
160
+ holder.samples = tmp_samples
161
+
162
+ tmp_current = GLOBAL_USER_STATUS.request_status_dict(
163
+ current_key, use_redis=True
164
+ )
165
+ tmp_current.update(holder.current_sample)
166
+ holder.current_sample = tmp_current
167
+
168
+ if is_primary:
169
+ self.samples = holder.samples
170
+ self.current_sample = holder.current_sample
171
+
172
+ try:
173
+ holder.reload_sample_frames()
174
+ except Exception as e:
175
+ print(f"Error reloading sample frames for primary sampleholder: {e}")
176
+
177
+ def activate_mode(self, modes):
178
+ if not isinstance(modes, (list, tuple)):
179
+ modes = [modes]
180
+ all_devices = set(self.devices.keys())
181
+ devices_to_defer = set()
182
+ devices_to_load = set()
183
+ for mode, mode_devices in self.modes.items():
184
+ if mode in modes:
185
+ devices_to_load.update(mode_devices)
186
+ else:
187
+ devices_to_defer.update(mode_devices)
188
+ devices_to_defer.difference_update(devices_to_load)
189
+ devices_to_defer &= all_devices
190
+ devices_to_load.difference_update(all_devices)
191
+
192
+ for device_name in devices_to_defer:
193
+ self.defer_device(device_name)
194
+ for device_name in devices_to_load:
195
+ self.load_deferred_device(device_name)
196
+
197
+ def deactivate_mode(self, modes):
198
+ if not isinstance(modes, (list, tuple)):
199
+ modes = [modes]
200
+ devices_to_defer = set()
201
+ for mode in modes:
202
+ if mode in self.modes:
203
+ for device_name in self.modes[mode]:
204
+ devices_to_defer.add(device_name)
205
+ for device_name in devices_to_defer:
206
+ self.defer_device(device_name)
207
+
208
+ def load_deferred_device(self, device_name, ns=None):
209
+ """
210
+ Load a specific deferred device and its dependencies.
211
+ If an alias is requested, loads its root device.
212
+
213
+ Parameters
214
+ ----------
215
+ device_name : str
216
+ Name of the device to load
217
+
218
+ Returns
219
+ -------
220
+ object
221
+ The loaded device
222
+
223
+ Raises
224
+ ------
225
+ KeyError
226
+ If the device is not in the deferred devices list
227
+ RuntimeError
228
+ If loading the device fails
229
+ """
230
+ if device_name not in self._deferred_devices:
231
+ raise KeyError(f"Device {device_name} is not in deferred devices")
232
+
233
+ # If it's an alias, get and load the root device
234
+ config = self._deferred_config.get(device_name, {})
235
+ if isinstance(config, dict) and "_alias" in config:
236
+ root_device = config["_alias"].split(".")[0]
237
+ if root_device != device_name: # Prevent recursion
238
+ return self.load_deferred_device(root_device, ns)
239
+
240
+ # Create config with just this device and its dependencies
241
+ self._deferred_config[device_name]["_defer_loading"] = False
242
+
243
+ try:
244
+ devices = loadDevices(
245
+ self._deferred_config,
246
+ namespace=ns,
247
+ mode=None,
248
+ )
249
+ for device_name, device_info in devices.items():
250
+ self.add_device(device_name, device_info)
251
+
252
+ for device_name in devices:
253
+ self._deferred_config.pop(device_name, None)
254
+ self._deferred_devices.discard(device_name)
255
+
256
+ return self.devices.get(device_name)
257
+ except Exception as e:
258
+ raise RuntimeError(f"Failed to load device {device_name}: {e}") from e
259
+
260
+
261
+ def get_device(self, device_name, get_subdevice=True):
262
+ """
263
+ If get_subdevice, follow dotted device names and return the deepest device.
264
+ If False, follow the parents and return the overall parent device
265
+ """
266
+ device_parts = device_name.split(".")
267
+ device = self.devices[device_parts[0]]
268
+ if get_subdevice:
269
+ for subdev in device_parts[1:]:
270
+ device = getattr(device, subdev)
271
+ else:
272
+ while device.parent is not None:
273
+ device = device.parent
274
+ return device
275
+
276
+ def add_to_baseline(self, device_or_name, only_subdevice=False):
277
+ if isinstance(device_or_name, str):
278
+ device = self.get_device(device_or_name, only_subdevice)
279
+ else:
280
+ device = device_or_name
281
+ if device not in self.supplemental_data.baseline:
282
+ self.supplemental_data.baseline.append(device)
283
+
284
+ def _add_device_to_groups(self, device_key, device_info):
285
+ groups = device_info["groups"]
286
+ # print(f"Adding device {device_key} to groups: {groups}")
287
+ for groupname in groups:
288
+ if groupname in self.reserved:
289
+ raise KeyError(f"Key {groupname} is reserved, use a different group name")
290
+ if groupname not in self.groups:
291
+ self.groups.append(groupname)
292
+ setattr(self, groupname, HardwareGroup(groupname))
293
+ group = getattr(self, groupname)
294
+ group.add(device_key, device_info["device"], **device_info["config"])
295
+ # print(f"Setting {groupname}[{device_key}]")
296
+
297
+ def get_deferred_devices(self):
298
+ """Return set of currently deferred devices."""
299
+ return self._deferred_devices.copy()
300
+
301
+ def is_device_deferred(self, device_name):
302
+ """Check if a device is currently deferred."""
303
+ return device_name in self._deferred_devices
304
+
305
+ def defer_device(self, device_name):
306
+ """
307
+ Move a loaded device to deferred state.
308
+
309
+ Parameters
310
+ ----------
311
+ device_name : str
312
+ Name of the device to defer
313
+
314
+ Raises
315
+ ------
316
+ KeyError
317
+ If the device is not loaded or already deferred
318
+ RuntimeError
319
+ If the device cannot be deferred
320
+ """
321
+ ip = IPython.get_ipython()
322
+
323
+ if device_name in self._deferred_devices:
324
+ print(
325
+ f"Device {device_name} is already deferred, continuing to check aliased devices"
326
+ )
327
+
328
+ # Get device's configuration
329
+ device_config = self.config["devices"].get(device_name)
330
+ if not device_config:
331
+ raise RuntimeError(f"No configuration found for device {device_name}")
332
+
333
+ # Update configuration to defer loading
334
+ device_config["_defer_loading"] = True
335
+
336
+ deferred_devices, _, deferred_config = _find_deferred_devices(
337
+ self.config["devices"]
338
+ )
339
+
340
+ self._deferred_config.update(deferred_config)
341
+ self._deferred_devices.update(deferred_devices)
342
+
343
+ # Remove from groups
344
+ for newly_deferred in deferred_devices:
345
+ if newly_deferred not in self.devices:
346
+ continue
347
+ for group in self.groups:
348
+ group_obj = getattr(self, group)
349
+ if newly_deferred in group_obj.devices:
350
+ group_obj.remove(newly_deferred)
351
+
352
+ # Remove from roles
353
+ for role in self.roles:
354
+ if hasattr(self, role) and getattr(self, role) == self.devices.get(
355
+ newly_deferred, None
356
+ ):
357
+ setattr(self, role, None)
358
+
359
+ # Remove from baseline if present
360
+ device = self.devices.get(newly_deferred, None)
361
+ if device != None and device in self.supplemental_data.baseline:
362
+ self.supplemental_data.baseline.remove(device)
363
+
364
+ # Remove from devices registry
365
+ if device != None:
366
+ self.devices.pop(newly_deferred)
367
+ del device
368
+ ip.user_global_ns.pop(newly_deferred, None)
369
+ return device_name
370
+
371
+ def __getitem__(self, key):
372
+ """Allow dictionary-like access to devices."""
373
+ return self.devices[key]
374
+
375
+ def __setitem__(self, key, value):
376
+ """Allow dictionary-like setting of devices."""
377
+ self.devices[key] = value
378
+
379
+ def reset(self):
380
+ """
381
+ Reset the beamline model to an uninitialized state.
382
+
383
+ Clears all devices, configuration, settings, and other initialized
384
+ data while preserving the structure. Useful for debugging and testing.
385
+ """
386
+
387
+ self.initialized = False
388
+ self.supplemental_data = SupplementalData()
389
+ self.md = {}
390
+ self.RE = None
391
+ self.settings = StatusDict()
392
+ self.devices = StatusDict()
393
+ self.config = {}
394
+ self.plan_status = {}
395
+ self._deferred_config = {}
396
+ self._deferred_devices = set()
397
+
398
+ self.groups = list(self.default_groups)
399
+ self.roles = list(self.default_roles)
400
+
401
+ self.modes = {}
402
+ if hasattr(self, "samples"):
403
+ delattr(self, "samples")
404
+ if hasattr(self, "current_sample"):
405
+ delattr(self, "current_sample")
406
+ if hasattr(self, "redis_settings"):
407
+ delattr(self, "redis_settings")
408
+
409
+
410
+ def _initialize_groups(self):
411
+ print("Initializing groups")
412
+ for group in self.default_groups:
413
+ setattr(self, group, HardwareGroup(group))
414
+
415
+ for role in self.default_roles:
416
+ setattr(self, role, None)
417
+
418
+ self.detectors = DetectorGroup("detectors")
419
+ self.motors = HardwareGroup("motors")
420
+
421
+
422
+
423
+ def reset_old(self):
424
+ self.supplemental_data = SupplementalData()
425
+ self.md = {}
426
+ self.RE = None
427
+ self.settings = StatusDict()
428
+ self.devices = StatusDict()
429
+ self.energy = None
430
+ self.primary_sampleholder = None
431
+ self.default_shutter = None
432
+ self.config = {}
433
+ self.groups = list(self.default_groups)
434
+ self.roles = list(self.default_roles)
435
+ self.detectors = DetectorGroup("detectors")
436
+ self.motors = HardwareGroup("motors")
437
+ self.plan_status = {}
438
+
439
+ self._deferred_config = {}
440
+ self._deferred_devices = set()
441
+
442
+ for group in self.default_groups:
443
+ if not hasattr(self, group):
444
+ setattr(self, group, HardwareGroup(group))
445
+
446
+ for role in self.default_roles:
447
+ if not hasattr(self, role):
448
+ setattr(self, role, None)
449
+
450
+ GLOBAL_BEAMLINE = BeamlineModel()