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,56 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .linalg import vec_len
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module that computes areas, distances, and inclusion in polygons
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def triarea(p1, p2, p3):
|
|
10
|
+
n1 = p1 - p2
|
|
11
|
+
n2 = p3 - p2
|
|
12
|
+
return 0.5*(n1[0]*n2[1] - n1[1]*n2[0])
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def getPointAreas(p, *args):
|
|
16
|
+
areas = []
|
|
17
|
+
for n in range(len(args)):
|
|
18
|
+
areas.append(triarea(p, args[n-1], args[n]))
|
|
19
|
+
return areas
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def distFromTri(p, a, b):
|
|
23
|
+
area = np.abs(triarea(p, a, b))
|
|
24
|
+
d = vec_len(a - b)
|
|
25
|
+
s1 = vec_len(a - p)
|
|
26
|
+
s2 = vec_len(b - p)
|
|
27
|
+
if np.isclose(d, 0):
|
|
28
|
+
return min(s1, s2)
|
|
29
|
+
elif s1**2 > d**2 + s2**2:
|
|
30
|
+
return s2
|
|
31
|
+
elif s2**2 > d**2 + s1**2:
|
|
32
|
+
return s1
|
|
33
|
+
else:
|
|
34
|
+
return 2.0*area/d
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def getMinDist(p, *args):
|
|
38
|
+
distances = []
|
|
39
|
+
for n in range(len(args)):
|
|
40
|
+
distances.append(distFromTri(p, args[n-1], args[n]))
|
|
41
|
+
return np.min(distances)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def prunePoints(*args):
|
|
45
|
+
pruned = []
|
|
46
|
+
for n in range(len(args)):
|
|
47
|
+
if not np.all(np.isclose(args[n-1] - args[n], 0.0)):
|
|
48
|
+
pruned.append(args[n])
|
|
49
|
+
return pruned
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def isInPoly(p, *args):
|
|
53
|
+
# Works for convex polygons only!
|
|
54
|
+
polyPoints = prunePoints(*args)
|
|
55
|
+
areas = np.array(getPointAreas(p, *polyPoints))
|
|
56
|
+
return (np.all(areas < 0) or np.all(areas > 0))
|
nbs_bl/help.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from .status import StatusList
|
|
2
|
+
from .queueserver import GLOBAL_USER_STATUS
|
|
3
|
+
from .printing import boxed_text
|
|
4
|
+
|
|
5
|
+
GLOBAL_HELP_DICTIONARY = {
|
|
6
|
+
"functions": {},
|
|
7
|
+
"plans": {},
|
|
8
|
+
"scans": {},
|
|
9
|
+
"xas": {},
|
|
10
|
+
"conditions": {},
|
|
11
|
+
}
|
|
12
|
+
GLOBAL_IMPORT_DICTIONARY = {}
|
|
13
|
+
|
|
14
|
+
# Request status lists from the global manager
|
|
15
|
+
GLOBAL_PLAN_LIST = GLOBAL_USER_STATUS.request_status_list("PLAN_LIST", use_redis=True)
|
|
16
|
+
GLOBAL_SCAN_LIST = GLOBAL_USER_STATUS.request_status_list("SCAN_LIST", use_redis=True)
|
|
17
|
+
GLOBAL_CONDITION_LIST = GLOBAL_USER_STATUS.request_status_list(
|
|
18
|
+
"CONDITION_LIST", use_redis=True
|
|
19
|
+
)
|
|
20
|
+
GLOBAL_PLAN_TIME_DICT = GLOBAL_USER_STATUS.request_status_dict(
|
|
21
|
+
"PLAN_TIME_DICT", use_redis=True
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _add_to_import_list(f, help_section):
|
|
26
|
+
"""
|
|
27
|
+
A function decorator that will add the function to the built-in list
|
|
28
|
+
"""
|
|
29
|
+
key = f.__name__
|
|
30
|
+
doc = f.__doc__ if f.__doc__ is not None else "No Docstring yet!"
|
|
31
|
+
if help_section not in GLOBAL_HELP_DICTIONARY:
|
|
32
|
+
GLOBAL_HELP_DICTIONARY[help_section] = {}
|
|
33
|
+
GLOBAL_HELP_DICTIONARY[help_section][key] = doc.lstrip()
|
|
34
|
+
GLOBAL_IMPORT_DICTIONARY[key] = f
|
|
35
|
+
return key
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def add_to_func_list(f):
|
|
39
|
+
"""
|
|
40
|
+
A function decorator that will add the function to the built-in list
|
|
41
|
+
"""
|
|
42
|
+
_add_to_import_list(f, "functions")
|
|
43
|
+
return f
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def add_to_plan_list(f):
|
|
47
|
+
"""
|
|
48
|
+
A function decorator that will add the plan to the built-in list
|
|
49
|
+
"""
|
|
50
|
+
key = _add_to_import_list(f, "plans")
|
|
51
|
+
GLOBAL_PLAN_LIST.append(key)
|
|
52
|
+
return f
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def add_to_scan_list(f):
|
|
56
|
+
"""
|
|
57
|
+
A function decorator that will add the plan to the built-in list
|
|
58
|
+
"""
|
|
59
|
+
key = _add_to_import_list(f, "scans")
|
|
60
|
+
GLOBAL_SCAN_LIST.append(key)
|
|
61
|
+
return f
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def add_to_condition_list(f):
|
|
65
|
+
"""
|
|
66
|
+
A function decorator that will add the condition to the built-in list
|
|
67
|
+
"""
|
|
68
|
+
key = _add_to_import_list(f, "conditions")
|
|
69
|
+
GLOBAL_CONDITION_LIST.append(key)
|
|
70
|
+
return f
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def add_to_plan_time_dict(
|
|
74
|
+
f,
|
|
75
|
+
estimator="generic_estimate",
|
|
76
|
+
fixed=0,
|
|
77
|
+
overhead=0.5,
|
|
78
|
+
dwell="dwell",
|
|
79
|
+
points=None,
|
|
80
|
+
reset=0,
|
|
81
|
+
**kwargs,
|
|
82
|
+
):
|
|
83
|
+
key = f.__name__
|
|
84
|
+
if key not in GLOBAL_PLAN_TIME_DICT:
|
|
85
|
+
GLOBAL_PLAN_TIME_DICT[key] = {}
|
|
86
|
+
GLOBAL_PLAN_TIME_DICT[key]["estimator"] = estimator
|
|
87
|
+
GLOBAL_PLAN_TIME_DICT[key]["fixed"] = fixed
|
|
88
|
+
GLOBAL_PLAN_TIME_DICT[key]["overhead"] = overhead
|
|
89
|
+
GLOBAL_PLAN_TIME_DICT[key]["dwell"] = dwell
|
|
90
|
+
GLOBAL_PLAN_TIME_DICT[key]["points"] = points
|
|
91
|
+
GLOBAL_PLAN_TIME_DICT[key]["reset"] = reset
|
|
92
|
+
GLOBAL_PLAN_TIME_DICT[key].update(kwargs)
|
|
93
|
+
return f
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@add_to_func_list
|
|
97
|
+
def print_builtins(sections=None):
|
|
98
|
+
"""Prints a list of built-in functions for ucal"""
|
|
99
|
+
|
|
100
|
+
if sections is None:
|
|
101
|
+
sections = sorted(GLOBAL_HELP_DICTIONARY.keys())
|
|
102
|
+
if type(sections) is str:
|
|
103
|
+
sections = [sections]
|
|
104
|
+
for key in sections:
|
|
105
|
+
textList = []
|
|
106
|
+
section = f"{key.capitalize()}"
|
|
107
|
+
if key == "xas":
|
|
108
|
+
for f in sorted(GLOBAL_HELP_DICTIONARY[key].keys()):
|
|
109
|
+
doc = GLOBAL_IMPORT_DICTIONARY[f]._short_doc
|
|
110
|
+
textList.append(f"{f}: {doc}")
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
for f in sorted(GLOBAL_HELP_DICTIONARY[key].keys()):
|
|
114
|
+
doc = GLOBAL_HELP_DICTIONARY[key][f].split("\n")[0]
|
|
115
|
+
textList.append(f"{f}: {doc}")
|
|
116
|
+
boxed_text(section, textList, "white", width=100)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@add_to_func_list
|
|
120
|
+
def sst_help():
|
|
121
|
+
print(
|
|
122
|
+
"Welcome to SST. For a list of loaded functions and plans, call print_builtins() \n"
|
|
123
|
+
'To print the docstring for any of the built-in functions, use the built-in python "?"'
|
|
124
|
+
" command with the name of the desired function. \n I.e, typing activate_detector? will "
|
|
125
|
+
'print the help text for the "activate_detector" function'
|
|
126
|
+
)
|
nbs_bl/hw.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from .queueserver import GLOBAL_USER_STATUS
|
|
2
|
+
from .printing import boxed_text
|
|
3
|
+
from nbs_core.autoload import loadFromConfig as _loadFromConfig
|
|
4
|
+
from nbs_core.autoload import instantiateOphyd, _find_deferred_devices, getMaxLoadPass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def loadDevices(device_config, namespace=None, mode=None):
|
|
8
|
+
max_load_pass = getMaxLoadPass(device_config)
|
|
9
|
+
|
|
10
|
+
all_devices = {}
|
|
11
|
+
all_groups = {}
|
|
12
|
+
all_roles = {}
|
|
13
|
+
deferred_config = {}
|
|
14
|
+
deferred_devices = set()
|
|
15
|
+
for pass_num in range(1, max_load_pass + 1):
|
|
16
|
+
print(f" Load pass {pass_num}/{max_load_pass}")
|
|
17
|
+
|
|
18
|
+
_, _, _deferred_config = _find_deferred_devices(device_config, mode=mode)
|
|
19
|
+
|
|
20
|
+
devices, groups, roles = loadFromConfig(
|
|
21
|
+
device_config,
|
|
22
|
+
instantiateOphyd,
|
|
23
|
+
alias=True,
|
|
24
|
+
namespace=namespace,
|
|
25
|
+
load_pass=pass_num,
|
|
26
|
+
mode=mode,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
deferred_config.update(_deferred_config)
|
|
31
|
+
deferred_devices.update(_deferred_config.keys())
|
|
32
|
+
|
|
33
|
+
for device_name in devices:
|
|
34
|
+
deferred_config.pop(device_name, None)
|
|
35
|
+
deferred_devices.discard(device_name)
|
|
36
|
+
for group, group_devices in groups.items():
|
|
37
|
+
if group in all_groups:
|
|
38
|
+
all_groups[group] = all_groups[group] + group_devices
|
|
39
|
+
else:
|
|
40
|
+
all_groups[group] = group_devices
|
|
41
|
+
all_devices.update(devices)
|
|
42
|
+
all_roles.update(roles)
|
|
43
|
+
device_dict = {key: {"device": device, "loaded": True, "groups": [], "roles": [], "config": device_config.get(key, {})} for key, device in all_devices.items()}
|
|
44
|
+
for group, group_devices in all_groups.items():
|
|
45
|
+
for device in group_devices:
|
|
46
|
+
if device in device_dict:
|
|
47
|
+
device_dict[device]["groups"].append(group)
|
|
48
|
+
else:
|
|
49
|
+
device_dict[device] = {"device": None, "groups": [group], "loaded": False, "roles": [], "config": device_config.get(device, {})}
|
|
50
|
+
|
|
51
|
+
for role, device in all_roles.items():
|
|
52
|
+
if device in device_dict:
|
|
53
|
+
device_dict[device]["roles"].append(role)
|
|
54
|
+
else:
|
|
55
|
+
device_dict[device] = {"device": None, "roles": [role], "loaded": False, "groups": [], "config": device_config.get(device, {})}
|
|
56
|
+
for device_name, dconf in deferred_config.items():
|
|
57
|
+
dinfo = {"loaded": False, "config": dconf}
|
|
58
|
+
device_dict[device_name] = dinfo
|
|
59
|
+
|
|
60
|
+
return device_dict
|
|
61
|
+
|
|
62
|
+
def loadFromConfig(
|
|
63
|
+
config,
|
|
64
|
+
instantiateDevice,
|
|
65
|
+
alias=True,
|
|
66
|
+
namespace=None,
|
|
67
|
+
load_pass="auto",
|
|
68
|
+
filter_deferred=True,
|
|
69
|
+
mode=None,
|
|
70
|
+
):
|
|
71
|
+
devices, groups, roles = _loadFromConfig(
|
|
72
|
+
config, instantiateDevice, alias, namespace, load_pass, filter_deferred, mode=mode
|
|
73
|
+
)
|
|
74
|
+
for key, device in devices.items():
|
|
75
|
+
globals()[key] = device
|
|
76
|
+
return devices, groups, roles
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class HardwareGroup:
|
|
80
|
+
def __init__(self, name="", use_redis=False):
|
|
81
|
+
self.groupname = name
|
|
82
|
+
self.devices = GLOBAL_USER_STATUS.request_status_dict(
|
|
83
|
+
f"{self.groupname.upper()}", use_redis=False
|
|
84
|
+
)
|
|
85
|
+
self.descriptions = GLOBAL_USER_STATUS.request_status_dict(
|
|
86
|
+
f"{self.groupname.upper()}_DESCRIPTIONS", use_redis=use_redis
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def values(self):
|
|
90
|
+
return self.devices.values()
|
|
91
|
+
|
|
92
|
+
def items(self):
|
|
93
|
+
return self.devices.items()
|
|
94
|
+
|
|
95
|
+
def get_key(self, device_or_key):
|
|
96
|
+
if isinstance(device_or_key, str):
|
|
97
|
+
if device_or_key in self.devices:
|
|
98
|
+
return device_or_key
|
|
99
|
+
else:
|
|
100
|
+
raise KeyError(f"Device {device_or_key} not found")
|
|
101
|
+
else:
|
|
102
|
+
for k, v in self.devices.items():
|
|
103
|
+
if v == device_or_key:
|
|
104
|
+
return k
|
|
105
|
+
raise KeyError(f"Device {device_or_key} not found")
|
|
106
|
+
|
|
107
|
+
def get(self, device_or_key):
|
|
108
|
+
key = self.get_key(device_or_key)
|
|
109
|
+
return self.devices[key]
|
|
110
|
+
|
|
111
|
+
def add(self, key, device, description="", **kwargs):
|
|
112
|
+
self.devices[key] = device
|
|
113
|
+
has_subdevices = False
|
|
114
|
+
if hasattr(device, "real_positioners"):
|
|
115
|
+
try:
|
|
116
|
+
for k2 in device.real_positioners._fields:
|
|
117
|
+
self.descriptions[f"{key}.{k2}"] = getattr(
|
|
118
|
+
device.real_positioners, k2
|
|
119
|
+
).name
|
|
120
|
+
has_subdevices = True
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print(f"Error getting real positioners for {key}: {e}")
|
|
123
|
+
if hasattr(device, "pseudo_positioners"):
|
|
124
|
+
try:
|
|
125
|
+
for k2 in device.pseudo_positioners._fields:
|
|
126
|
+
self.descriptions[f"{key}.{k2}"] = getattr(
|
|
127
|
+
device.pseudo_positioners, k2
|
|
128
|
+
).name
|
|
129
|
+
has_subdevices = True
|
|
130
|
+
except Exception as e:
|
|
131
|
+
print(f"Error getting pseudo positioners for {key}: {e}")
|
|
132
|
+
if hasattr(device, "position_axes"):
|
|
133
|
+
try:
|
|
134
|
+
for k2 in device.position_axes:
|
|
135
|
+
self.descriptions[f"{key}.{k2.attr_name}"] = k2.attr_name
|
|
136
|
+
has_subdevices = True
|
|
137
|
+
except Exception as e:
|
|
138
|
+
print(f"Error getting position axes for {key}: {e}")
|
|
139
|
+
if not has_subdevices:
|
|
140
|
+
self.descriptions[key] = description
|
|
141
|
+
|
|
142
|
+
def remove(self, device_or_key):
|
|
143
|
+
key = self.get_key(device_or_key)
|
|
144
|
+
self.devices.pop(key, None)
|
|
145
|
+
self.descriptions.pop(key, None)
|
|
146
|
+
for dkey in list(self.descriptions.keys()):
|
|
147
|
+
if dkey.split(".")[0] == key:
|
|
148
|
+
self.descriptions.pop(dkey, None)
|
|
149
|
+
|
|
150
|
+
def describe(self, verbose=True, textFunction=None):
|
|
151
|
+
title = self.groupname
|
|
152
|
+
text = []
|
|
153
|
+
for key, device in self.devices.items():
|
|
154
|
+
name = device.name
|
|
155
|
+
if textFunction is None:
|
|
156
|
+
text.append(f"Key: {key}; Name: {name}")
|
|
157
|
+
else:
|
|
158
|
+
text.append(textFunction(self, key, device))
|
|
159
|
+
if verbose:
|
|
160
|
+
text.append(f" {self.descriptions[key]}")
|
|
161
|
+
boxed_text(title, text, "white")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class DetectorGroup(HardwareGroup):
|
|
165
|
+
def __init__(self, name="detectors", use_redis=False):
|
|
166
|
+
super().__init__(name=name, use_redis=use_redis)
|
|
167
|
+
self.status = GLOBAL_USER_STATUS.request_status_dict(
|
|
168
|
+
f"{self.groupname.upper()}_STATUS", use_redis=use_redis
|
|
169
|
+
)
|
|
170
|
+
self.thresholds = GLOBAL_USER_STATUS.request_status_dict(
|
|
171
|
+
f"{self.groupname.upper()}_THRESHOLDS", use_redis=use_redis
|
|
172
|
+
)
|
|
173
|
+
self.active = GLOBAL_USER_STATUS.request_status_list(
|
|
174
|
+
f"{self.groupname.upper()}_ACTIVE", use_redis=False
|
|
175
|
+
)
|
|
176
|
+
self.detector_sets = GLOBAL_USER_STATUS.request_status_dict(
|
|
177
|
+
f"{self.groupname.upper()}_SETS", use_redis=use_redis
|
|
178
|
+
)
|
|
179
|
+
self.roles = {}
|
|
180
|
+
|
|
181
|
+
def add(
|
|
182
|
+
self,
|
|
183
|
+
key,
|
|
184
|
+
device,
|
|
185
|
+
description="",
|
|
186
|
+
activate=True,
|
|
187
|
+
sets=None,
|
|
188
|
+
threshold=None,
|
|
189
|
+
**kwargs,
|
|
190
|
+
):
|
|
191
|
+
super().add(key, device, description)
|
|
192
|
+
self.status[key] = "inactive"
|
|
193
|
+
if activate:
|
|
194
|
+
self.activate(key)
|
|
195
|
+
if sets is not None:
|
|
196
|
+
for set_name in sets:
|
|
197
|
+
self.add_to_set(device, set_name)
|
|
198
|
+
if threshold is not None:
|
|
199
|
+
self.thresholds[key] = threshold
|
|
200
|
+
|
|
201
|
+
def remove(self, device_or_key):
|
|
202
|
+
self.deactivate(device_or_key)
|
|
203
|
+
key = self.get_key(device_or_key)
|
|
204
|
+
super().remove(device_or_key)
|
|
205
|
+
self.status.pop(key, None)
|
|
206
|
+
self.thresholds.pop(key, None)
|
|
207
|
+
|
|
208
|
+
def activate(self, device_or_key, role=None):
|
|
209
|
+
key = self.get_key(device_or_key)
|
|
210
|
+
detector = self.devices[key]
|
|
211
|
+
if self.status[key] == "disabled":
|
|
212
|
+
print(f"Detector {key} is disabled and will not be activated")
|
|
213
|
+
return
|
|
214
|
+
else:
|
|
215
|
+
self.status[key] = "active"
|
|
216
|
+
if detector not in self.active:
|
|
217
|
+
self.active.append(detector)
|
|
218
|
+
if role is not None:
|
|
219
|
+
self.add_role(key, role)
|
|
220
|
+
|
|
221
|
+
def deactivate(self, device_or_key):
|
|
222
|
+
key = self.get_key(device_or_key)
|
|
223
|
+
device = self.get(device_or_key)
|
|
224
|
+
if device in self.active:
|
|
225
|
+
idx = self.active.index(device)
|
|
226
|
+
self.active.pop(idx)
|
|
227
|
+
if self.status[key] != "disabled":
|
|
228
|
+
self.status[key] = "inactive"
|
|
229
|
+
|
|
230
|
+
def activate_set(self, set_name):
|
|
231
|
+
for key in self.detector_sets.get(set_name, []):
|
|
232
|
+
self.activate(key)
|
|
233
|
+
|
|
234
|
+
def deactivate_set(self, set_name):
|
|
235
|
+
for key in self.detector_sets.get(set_name, []):
|
|
236
|
+
self.deactivate(key)
|
|
237
|
+
|
|
238
|
+
def add_set(self, set_name, set_keys):
|
|
239
|
+
self.detector_sets[set_name] = set_keys
|
|
240
|
+
|
|
241
|
+
def set_role(self, key, role):
|
|
242
|
+
self.roles[key] = role
|
|
243
|
+
|
|
244
|
+
def save_active_as_set(self, set_name):
|
|
245
|
+
set_keys = [self.get_key(device) for device in self.active]
|
|
246
|
+
self.add_set(set_name, set_keys)
|
|
247
|
+
|
|
248
|
+
def enable(self, device_or_key, activate=True):
|
|
249
|
+
key = self.get_key(device_or_key)
|
|
250
|
+
self.status[key] = "inactive"
|
|
251
|
+
if activate:
|
|
252
|
+
self.activate(device_or_key)
|
|
253
|
+
|
|
254
|
+
def disable(self, device_or_key):
|
|
255
|
+
key = self.get_key(device_or_key)
|
|
256
|
+
self.deactivate(device_or_key)
|
|
257
|
+
self.status[key] = "disabled"
|
|
258
|
+
|
|
259
|
+
def get_plot_hints(self):
|
|
260
|
+
plot_hints = {}
|
|
261
|
+
for detector in self.active:
|
|
262
|
+
key = self.get_key(detector)
|
|
263
|
+
role = self.roles.get(key, "auxiliary")
|
|
264
|
+
if role not in plot_hints:
|
|
265
|
+
plot_hints[role] = []
|
|
266
|
+
if hasattr(detector, "get_plot_hints"):
|
|
267
|
+
plot_hints[role] += detector.get_plot_hints()
|
|
268
|
+
else:
|
|
269
|
+
plot_hints[role].append(detector.name)
|
|
270
|
+
return plot_hints
|
nbs_bl/load.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from importlib import import_module
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
import tomllib
|
|
6
|
+
except ModuleNotFoundError:
|
|
7
|
+
import tomli as tomllib
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def loadConfigDB(filename):
|
|
11
|
+
"""
|
|
12
|
+
Load configuration from a TOML file.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
filename : str
|
|
17
|
+
The path to the TOML file containing the configuration.
|
|
18
|
+
|
|
19
|
+
Returns
|
|
20
|
+
-------
|
|
21
|
+
dict
|
|
22
|
+
The configuration loaded from the TOML file.
|
|
23
|
+
"""
|
|
24
|
+
with open(filename, "rb") as f:
|
|
25
|
+
db = tomllib.load(f)
|
|
26
|
+
return db
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def simpleResolver(fullclassname):
|
|
30
|
+
"""
|
|
31
|
+
Resolve a full class name to a class object.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
fullclassname : str
|
|
36
|
+
The full class name to resolve.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
type
|
|
41
|
+
The class object resolved from the full class name.
|
|
42
|
+
"""
|
|
43
|
+
class_name = fullclassname.split(".")[-1]
|
|
44
|
+
module_name = ".".join(fullclassname.split(".")[:-1])
|
|
45
|
+
module = import_module(module_name)
|
|
46
|
+
cls = getattr(module, class_name)
|
|
47
|
+
return cls
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def instantiateDevice(device_key, info, cls=None, namespace=None):
|
|
51
|
+
"""
|
|
52
|
+
Instantiate a device with given information.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
device_key : str
|
|
57
|
+
The key identifying the device.
|
|
58
|
+
info : dict
|
|
59
|
+
The information dictionary for the device.
|
|
60
|
+
cls : type, optional
|
|
61
|
+
The class to instantiate the device with. If not provided, it will be resolved from the info dictionary.
|
|
62
|
+
namespace : dict, optional
|
|
63
|
+
The namespace to add the instantiated device to.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
object
|
|
68
|
+
The instantiated device.
|
|
69
|
+
"""
|
|
70
|
+
device_info = deepcopy(info)
|
|
71
|
+
if cls is not None:
|
|
72
|
+
device_info.pop("_target", None)
|
|
73
|
+
elif device_info.get("_target", None) is not None:
|
|
74
|
+
cls = simpleResolver(device_info.pop("_target"))
|
|
75
|
+
else:
|
|
76
|
+
raise KeyError("Could not find '_target' in {}".format(device_info))
|
|
77
|
+
|
|
78
|
+
popkeys = [key for key in device_info if key.startswith("_")]
|
|
79
|
+
for key in popkeys:
|
|
80
|
+
device_info.pop(key)
|
|
81
|
+
|
|
82
|
+
name = device_info.pop("name", device_key)
|
|
83
|
+
prefix = device_info.pop("prefix", "")
|
|
84
|
+
add_to_namespace = device_info.pop("_add_to_ns", True)
|
|
85
|
+
device = cls(prefix, name=name, **device_info)
|
|
86
|
+
|
|
87
|
+
if add_to_namespace and namespace is not None:
|
|
88
|
+
namespace[device_key] = device
|
|
89
|
+
return device
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def loadDeviceConfig(filename, namespace=None):
|
|
93
|
+
"""
|
|
94
|
+
Load device configuration from a file and instantiate devices.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
filename : str
|
|
99
|
+
The path to the file containing the device configuration.
|
|
100
|
+
namespace : dict, optional
|
|
101
|
+
The namespace to add the instantiated devices to.
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
dict
|
|
106
|
+
A dictionary of instantiated devices.
|
|
107
|
+
"""
|
|
108
|
+
db = loadConfigDB(filename)
|
|
109
|
+
device_dict = {}
|
|
110
|
+
for key, info in db.items():
|
|
111
|
+
device = instantiateDevice(key, info, namespace=namespace)
|
|
112
|
+
device_dict[key] = device
|
|
113
|
+
return device_dict
|
nbs_bl/motors.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ophyd.utils.errors import DisconnectedError
|
|
2
|
+
from .beamline import GLOBAL_BEAMLINE
|
|
3
|
+
from .help import add_to_func_list
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@add_to_func_list
|
|
7
|
+
def list_motors(verbose=False):
|
|
8
|
+
"""List the most important motors and their current positions"""
|
|
9
|
+
|
|
10
|
+
def textFunction(motors, key, device):
|
|
11
|
+
name = device.name
|
|
12
|
+
try:
|
|
13
|
+
position = device.position
|
|
14
|
+
except DisconnectedError:
|
|
15
|
+
position = "disconnected"
|
|
16
|
+
text = f"{name}: {position}"
|
|
17
|
+
return text
|
|
18
|
+
|
|
19
|
+
GLOBAL_BEAMLINE.motors.describe(verbose, textFunction)
|
nbs_bl/planStatus.py
ADDED