dls-dodal 1.32.0__py3-none-any.whl → 1.34.1__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.
- {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/METADATA +3 -3
- {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/RECORD +53 -43
- {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/WHEEL +1 -1
- dodal/__init__.py +8 -0
- dodal/_version.py +2 -2
- dodal/beamline_specific_utils/i03.py +6 -2
- dodal/beamlines/__init__.py +2 -3
- dodal/beamlines/b01_1.py +77 -0
- dodal/beamlines/i03.py +41 -9
- dodal/beamlines/i04.py +26 -4
- dodal/beamlines/i10.py +257 -0
- dodal/beamlines/i22.py +1 -2
- dodal/beamlines/i24.py +7 -7
- dodal/beamlines/p38.py +1 -2
- dodal/common/signal_utils.py +53 -0
- dodal/common/types.py +2 -7
- dodal/devices/aperturescatterguard.py +12 -15
- dodal/devices/apple2_undulator.py +602 -0
- dodal/devices/areadetector/plugins/CAM.py +31 -0
- dodal/devices/areadetector/plugins/MJPG.py +51 -106
- dodal/devices/backlight.py +7 -6
- dodal/devices/diamond_filter.py +47 -0
- dodal/devices/eiger.py +6 -2
- dodal/devices/eiger_odin.py +48 -39
- dodal/devices/focusing_mirror.py +14 -8
- dodal/devices/i10/i10_apple2.py +398 -0
- dodal/devices/i10/i10_setting_data.py +7 -0
- dodal/devices/i22/dcm.py +7 -8
- dodal/devices/i24/dual_backlight.py +5 -5
- dodal/devices/oav/oav_calculations.py +22 -0
- dodal/devices/oav/oav_detector.py +118 -97
- dodal/devices/oav/oav_parameters.py +50 -104
- dodal/devices/oav/oav_to_redis_forwarder.py +75 -34
- dodal/devices/oav/{grid_overlay.py → snapshots/grid_overlay.py} +0 -43
- dodal/devices/oav/snapshots/snapshot_with_beam_centre.py +64 -0
- dodal/devices/oav/snapshots/snapshot_with_grid.py +57 -0
- dodal/devices/oav/utils.py +26 -25
- dodal/devices/pgm.py +41 -0
- dodal/devices/qbpm.py +18 -0
- dodal/devices/robot.py +2 -2
- dodal/devices/smargon.py +2 -2
- dodal/devices/tetramm.py +2 -2
- dodal/devices/undulator.py +2 -1
- dodal/devices/util/adjuster_plans.py +1 -1
- dodal/devices/util/lookup_tables.py +4 -5
- dodal/devices/zebra.py +5 -2
- dodal/devices/zocalo/zocalo_results.py +13 -10
- dodal/plans/data_session_metadata.py +2 -2
- dodal/plans/motor_util_plans.py +11 -9
- dodal/utils.py +7 -0
- dodal/beamlines/i04_1.py +0 -140
- dodal/devices/oav/oav_errors.py +0 -35
- {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/LICENSE +0 -0
- {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import csv
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from bluesky.protocols import Movable
|
|
9
|
+
from ophyd_async.core import (
|
|
10
|
+
AsyncStatus,
|
|
11
|
+
HintedSignal,
|
|
12
|
+
StandardReadable,
|
|
13
|
+
soft_signal_r_and_setter,
|
|
14
|
+
soft_signal_rw,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from dodal.devices.apple2_undulator import (
|
|
18
|
+
Apple2,
|
|
19
|
+
Apple2Val,
|
|
20
|
+
Lookuptable,
|
|
21
|
+
UndulatorGap,
|
|
22
|
+
UndulatorJawPhase,
|
|
23
|
+
UndulatorPhaseAxes,
|
|
24
|
+
)
|
|
25
|
+
from dodal.devices.pgm import PGM
|
|
26
|
+
from dodal.log import LOGGER
|
|
27
|
+
|
|
28
|
+
ROW_PHASE_MOTOR_TOLERANCE = 0.004
|
|
29
|
+
MAXIMUM_ROW_PHASE_MOTOR_POSITION = 24.0
|
|
30
|
+
MAXIMUM_GAP_MOTOR_POSITION = 100
|
|
31
|
+
DEFAULT_JAW_PHASE_POLY_PARAMS = [1.0 / 7.5, -120.0 / 7.5]
|
|
32
|
+
ALPHA_OFFSET = 180
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# data class to store the lookup table configuration that is use in convert_csv_to_lookup
|
|
36
|
+
@dataclass
|
|
37
|
+
class LookupPath:
|
|
38
|
+
Gap: Path
|
|
39
|
+
Phase: Path
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class LookupTableConfig:
|
|
44
|
+
path: LookupPath
|
|
45
|
+
source: tuple[str, str]
|
|
46
|
+
mode: str | None
|
|
47
|
+
min_energy: str | None
|
|
48
|
+
max_energy: str | None
|
|
49
|
+
poly_deg: list | None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class I10Apple2(Apple2):
|
|
53
|
+
"""
|
|
54
|
+
I10Apple2 is the i10 version of Apple2 ID.
|
|
55
|
+
The set and update_lookuptable should be the only part that is I10 specific.
|
|
56
|
+
|
|
57
|
+
A pair of look up tables are needed to provide the conversion
|
|
58
|
+
between motor position and energy.
|
|
59
|
+
Set is in energy(eV).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
id_gap: UndulatorGap,
|
|
65
|
+
id_phase: UndulatorPhaseAxes,
|
|
66
|
+
id_jaw_phase: UndulatorJawPhase,
|
|
67
|
+
energy_gap_table_path: Path,
|
|
68
|
+
energy_phase_table_path: Path,
|
|
69
|
+
source: tuple[str, str],
|
|
70
|
+
prefix: str = "",
|
|
71
|
+
mode: str = "Mode",
|
|
72
|
+
min_energy: str = "MinEnergy",
|
|
73
|
+
max_energy: str = "MaxEnergy",
|
|
74
|
+
poly_deg: list | None = None,
|
|
75
|
+
name: str = "",
|
|
76
|
+
) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
id_gap:
|
|
81
|
+
An UndulatorGap device.
|
|
82
|
+
id_phase:
|
|
83
|
+
An UndulatorPhaseAxes device.
|
|
84
|
+
energy_gap_table_path:
|
|
85
|
+
The path to id gap look up table.
|
|
86
|
+
energy_phase_table_path:
|
|
87
|
+
The path to id phase look up table.
|
|
88
|
+
source:
|
|
89
|
+
The column name and the name of the source in look up table. e.g. ("source", "idu")
|
|
90
|
+
mode:
|
|
91
|
+
The column name of the mode in look up table.
|
|
92
|
+
min_energy:
|
|
93
|
+
The column name that contain the maximum energy in look up table.
|
|
94
|
+
max_energy:
|
|
95
|
+
The column name that contain the maximum energy in look up table.
|
|
96
|
+
poly_deg:
|
|
97
|
+
The column names for the parameters for the energy conversion polynomial, starting with the least significant.
|
|
98
|
+
prefix:
|
|
99
|
+
Not in use but needed for device_instantiation.
|
|
100
|
+
Name:
|
|
101
|
+
Name of the device
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# A dataclass contains the path to the look up table and the expected column names.
|
|
105
|
+
self.lookup_table_config = LookupTableConfig(
|
|
106
|
+
path=LookupPath(Gap=energy_gap_table_path, Phase=energy_phase_table_path),
|
|
107
|
+
source=source,
|
|
108
|
+
mode=mode,
|
|
109
|
+
min_energy=min_energy,
|
|
110
|
+
max_energy=max_energy,
|
|
111
|
+
poly_deg=poly_deg,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
super().__init__(
|
|
115
|
+
id_gap=id_gap,
|
|
116
|
+
id_phase=id_phase,
|
|
117
|
+
prefix=prefix,
|
|
118
|
+
name=name,
|
|
119
|
+
)
|
|
120
|
+
with self.add_children_as_readables():
|
|
121
|
+
self.id_jaw_phase = id_jaw_phase
|
|
122
|
+
|
|
123
|
+
@AsyncStatus.wrap
|
|
124
|
+
async def set(self, value: float) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Check polarisation state and use it together with the energy(value)
|
|
127
|
+
to calculate the required gap and phases before setting it.
|
|
128
|
+
"""
|
|
129
|
+
if self.pol is None:
|
|
130
|
+
LOGGER.warning("Polarisation not set attempting to read from hardware")
|
|
131
|
+
pol, phase = await self.determinePhaseFromHardware()
|
|
132
|
+
if pol is None:
|
|
133
|
+
raise ValueError(f"Pol is not set for {self.name}")
|
|
134
|
+
self.pol = pol
|
|
135
|
+
|
|
136
|
+
self._polarisation_set(self.pol)
|
|
137
|
+
gap, phase = self._get_id_gap_phase(value)
|
|
138
|
+
phase3 = phase * (-1 if self.pol == "la" else (1))
|
|
139
|
+
id_set_val = Apple2Val(
|
|
140
|
+
top_outer=str(phase),
|
|
141
|
+
top_inner="0.0",
|
|
142
|
+
btm_inner=str(phase3),
|
|
143
|
+
btm_outer="0.0",
|
|
144
|
+
gap=str(gap),
|
|
145
|
+
)
|
|
146
|
+
LOGGER.info(f"Setting polarisation to {self.pol}, with {id_set_val}")
|
|
147
|
+
await self._set(value=id_set_val, energy=value)
|
|
148
|
+
if self.pol != "la":
|
|
149
|
+
await self.id_jaw_phase.set(0)
|
|
150
|
+
await self.id_jaw_phase.set_move.set(1)
|
|
151
|
+
|
|
152
|
+
def update_lookuptable(self):
|
|
153
|
+
"""
|
|
154
|
+
Update the stored lookup tabled from file.
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
LOGGER.info("Updating lookup dictionary from file.")
|
|
158
|
+
for key, path in self.lookup_table_config.path.__dict__.items():
|
|
159
|
+
if path.exists():
|
|
160
|
+
self.lookup_tables[key] = convert_csv_to_lookup(
|
|
161
|
+
file=path,
|
|
162
|
+
source=self.lookup_table_config.source,
|
|
163
|
+
mode=self.lookup_table_config.mode,
|
|
164
|
+
min_energy=self.lookup_table_config.min_energy,
|
|
165
|
+
max_energy=self.lookup_table_config.max_energy,
|
|
166
|
+
poly_deg=self.lookup_table_config.poly_deg,
|
|
167
|
+
)
|
|
168
|
+
# ensure the importing lookup table is the correct format
|
|
169
|
+
Lookuptable.model_validate(self.lookup_tables[key])
|
|
170
|
+
else:
|
|
171
|
+
raise FileNotFoundError(f"{key} look up table is not in path: {path}")
|
|
172
|
+
|
|
173
|
+
self._available_pol = list(self.lookup_tables["Gap"].keys())
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class I10Apple2PGM(StandardReadable, Movable):
|
|
177
|
+
"""
|
|
178
|
+
Compound device to set both ID and PGM energy at the sample time,poly_deg
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def __init__(
|
|
183
|
+
self, id: I10Apple2, pgm: PGM, prefix: str = "", name: str = ""
|
|
184
|
+
) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
id:
|
|
189
|
+
An Apple2 device.
|
|
190
|
+
pgm:
|
|
191
|
+
A PGM/mono device.
|
|
192
|
+
prefix:
|
|
193
|
+
Not in use but needed for device_instantiation.
|
|
194
|
+
name:
|
|
195
|
+
New device name.
|
|
196
|
+
"""
|
|
197
|
+
super().__init__(name=name)
|
|
198
|
+
with self.add_children_as_readables():
|
|
199
|
+
self.id = id
|
|
200
|
+
self.pgm = pgm
|
|
201
|
+
with self.add_children_as_readables(HintedSignal):
|
|
202
|
+
self.energy_offset = soft_signal_rw(float, initial_value=0)
|
|
203
|
+
|
|
204
|
+
@AsyncStatus.wrap
|
|
205
|
+
async def set(self, value: float) -> None:
|
|
206
|
+
LOGGER.info(f"Moving f{self.name} energy to {value}.")
|
|
207
|
+
await asyncio.gather(
|
|
208
|
+
self.id.set(value=value + await self.energy_offset.get_value()),
|
|
209
|
+
self.pgm.energy.set(value),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class I10Apple2Pol(StandardReadable, Movable):
|
|
214
|
+
"""
|
|
215
|
+
Compound device to set polorisation of ID.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
def __init__(self, id: I10Apple2, prefix: str = "", name: str = "") -> None:
|
|
219
|
+
"""
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
id:
|
|
223
|
+
An I10Apple2 device.
|
|
224
|
+
prefix:
|
|
225
|
+
Not in use but needed for device_instantiation.
|
|
226
|
+
name:
|
|
227
|
+
New device name.
|
|
228
|
+
"""
|
|
229
|
+
super().__init__(name=name)
|
|
230
|
+
with self.add_children_as_readables():
|
|
231
|
+
self.id = id
|
|
232
|
+
|
|
233
|
+
@AsyncStatus.wrap
|
|
234
|
+
async def set(self, value: str) -> None:
|
|
235
|
+
self.id.pol = value # change polarisation.
|
|
236
|
+
LOGGER.info(f"Changing f{self.name} polarisation to {value}.")
|
|
237
|
+
await self.id.set(
|
|
238
|
+
await self.id.energy.get_value()
|
|
239
|
+
) # Move id to new polarisation
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class LinearArbitraryAngle(StandardReadable, Movable):
|
|
243
|
+
"""
|
|
244
|
+
Device to set polorisation angle of the ID. Linear Arbitrary Angle (laa)
|
|
245
|
+
is the direction of the magnetic field which can be change by varying the jaw_phase
|
|
246
|
+
in (linear arbitrary (la) mode,
|
|
247
|
+
The angle of 0 is equivalent to linear horizontal "lh" (sigma) and
|
|
248
|
+
90 is linear vertical "lv" (pi).
|
|
249
|
+
This device require a jaw_phase to angle conversion which is done via a polynomial.
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def __init__(
|
|
253
|
+
self,
|
|
254
|
+
id: I10Apple2,
|
|
255
|
+
prefix: str = "",
|
|
256
|
+
name: str = "",
|
|
257
|
+
jaw_phase_limit: float = 12.0,
|
|
258
|
+
jaw_phase_poly_param: list[float] = DEFAULT_JAW_PHASE_POLY_PARAMS,
|
|
259
|
+
angle_threshold_deg=30.0,
|
|
260
|
+
) -> None:
|
|
261
|
+
"""
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
id: I10Apple2
|
|
265
|
+
An I10Apple2 device.
|
|
266
|
+
prefix: str
|
|
267
|
+
Not in use but needed for device_instantiation.
|
|
268
|
+
name: str
|
|
269
|
+
New device name.
|
|
270
|
+
jaw_phase_limit: float
|
|
271
|
+
The maximum allowed jaw_phase movement.
|
|
272
|
+
jaw_phase_poly_param: list
|
|
273
|
+
polynomial parameters highest power first.
|
|
274
|
+
"""
|
|
275
|
+
super().__init__(name=name)
|
|
276
|
+
with self.add_children_as_readables():
|
|
277
|
+
self.id = id
|
|
278
|
+
self.jaw_phase_from_angle = np.poly1d(jaw_phase_poly_param)
|
|
279
|
+
self.angle_threshold_deg = angle_threshold_deg
|
|
280
|
+
self.jaw_phase_limit = jaw_phase_limit
|
|
281
|
+
with self.add_children_as_readables(HintedSignal):
|
|
282
|
+
self.angle, self._angle_set = soft_signal_r_and_setter(
|
|
283
|
+
float, initial_value=None
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
@AsyncStatus.wrap
|
|
287
|
+
async def set(self, value: float) -> None:
|
|
288
|
+
pol = self.id.pol
|
|
289
|
+
if pol != "la":
|
|
290
|
+
raise RuntimeError(
|
|
291
|
+
f"Angle control is not available in polarisation {pol} with {self.id.name}"
|
|
292
|
+
)
|
|
293
|
+
# Moving to real angle which is 210 to 30.
|
|
294
|
+
alpha_real = value if value > self.angle_threshold_deg else value + ALPHA_OFFSET
|
|
295
|
+
jaw_phase = self.jaw_phase_from_angle(alpha_real)
|
|
296
|
+
if abs(jaw_phase) > self.jaw_phase_limit:
|
|
297
|
+
raise RuntimeError(
|
|
298
|
+
f"jaw_phase position for angle ({value}) is outside permitted range"
|
|
299
|
+
f" [-{self.jaw_phase_limit}, {self.jaw_phase_limit}]"
|
|
300
|
+
)
|
|
301
|
+
await self.id.id_jaw_phase.set(jaw_phase)
|
|
302
|
+
self._angle_set(value)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def convert_csv_to_lookup(
|
|
306
|
+
file: str,
|
|
307
|
+
source: tuple[str, str],
|
|
308
|
+
mode: str | None = "Mode",
|
|
309
|
+
min_energy: str | None = "MinEnergy",
|
|
310
|
+
max_energy: str | None = "MaxEnergy",
|
|
311
|
+
poly_deg: list | None = None,
|
|
312
|
+
) -> dict[str | None, dict[str, dict[str, dict[str, Any]]]]:
|
|
313
|
+
"""
|
|
314
|
+
Convert csv to a dictionary that can be read by Apple2 ID device.
|
|
315
|
+
|
|
316
|
+
Parameters
|
|
317
|
+
-----------
|
|
318
|
+
file: str
|
|
319
|
+
File path.
|
|
320
|
+
source: tuple[str, str]
|
|
321
|
+
Tuple(column name, source name)
|
|
322
|
+
e.g. ("Source", "idu").
|
|
323
|
+
mode: str = "Mode"
|
|
324
|
+
Column name for the available modes, "lv","lh","pc","nc" etc
|
|
325
|
+
min_energy: str = "MinEnergy":
|
|
326
|
+
Column name for min energy for the polynomial.
|
|
327
|
+
max_energy: str = "MaxEnergy",
|
|
328
|
+
Column name for max energy for the polynomial.
|
|
329
|
+
poly_deg: list | None = None,
|
|
330
|
+
Column names for the parameters for the polynomial, starting with the least significant.
|
|
331
|
+
|
|
332
|
+
return
|
|
333
|
+
------
|
|
334
|
+
return a dictionary that conform to Apple2 lookup table format:
|
|
335
|
+
|
|
336
|
+
{mode: {'Energies': {Any: {'Low': float,
|
|
337
|
+
'High': float,
|
|
338
|
+
'Poly':np.poly1d
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
'Limit': {'Minimum': float,
|
|
342
|
+
'Maximum': float
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
"""
|
|
347
|
+
if poly_deg is None:
|
|
348
|
+
poly_deg = [
|
|
349
|
+
"7th-order",
|
|
350
|
+
"6th-order",
|
|
351
|
+
"5th-order",
|
|
352
|
+
"4th-order",
|
|
353
|
+
"3rd-order",
|
|
354
|
+
"2nd-order",
|
|
355
|
+
"1st-order",
|
|
356
|
+
"b",
|
|
357
|
+
]
|
|
358
|
+
look_up_table = {}
|
|
359
|
+
pol = []
|
|
360
|
+
|
|
361
|
+
def data2dict(row) -> None:
|
|
362
|
+
# logic for the conversion for each row of data.
|
|
363
|
+
if row[mode] not in pol:
|
|
364
|
+
pol.append(row[mode])
|
|
365
|
+
look_up_table[row[mode]] = {}
|
|
366
|
+
look_up_table[row[mode]] = {
|
|
367
|
+
"Energies": {},
|
|
368
|
+
"Limit": {
|
|
369
|
+
"Minimum": float(row[min_energy]),
|
|
370
|
+
"Maximum": float(row[max_energy]),
|
|
371
|
+
},
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
# create polynomial object for energy to gap/phase
|
|
375
|
+
cof = [float(row[x]) for x in poly_deg]
|
|
376
|
+
poly = np.poly1d(cof)
|
|
377
|
+
|
|
378
|
+
look_up_table[row[mode]]["Energies"][row[min_energy]] = {
|
|
379
|
+
"Low": float(row[min_energy]),
|
|
380
|
+
"High": float(row[max_energy]),
|
|
381
|
+
"Poly": poly,
|
|
382
|
+
}
|
|
383
|
+
look_up_table[row[mode]]["Limit"]["Minimum"] = min(
|
|
384
|
+
look_up_table[row[mode]]["Limit"]["Minimum"], float(row[min_energy])
|
|
385
|
+
)
|
|
386
|
+
look_up_table[row[mode]]["Limit"]["Maximum"] = max(
|
|
387
|
+
look_up_table[row[mode]]["Limit"]["Maximum"], float(row[max_energy])
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
with open(file, newline="") as csvfile:
|
|
391
|
+
reader = csv.DictReader(csvfile)
|
|
392
|
+
for row in reader:
|
|
393
|
+
# If there are multiple source only convert requested.
|
|
394
|
+
if row[source[0]] == source[1]:
|
|
395
|
+
data2dict(row=row)
|
|
396
|
+
if not look_up_table:
|
|
397
|
+
raise RuntimeError(f"Unable to convert lookup table:/n/t{file}")
|
|
398
|
+
return look_up_table
|
dodal/devices/i22/dcm.py
CHANGED
|
@@ -39,7 +39,6 @@ class DoubleCrystalMonochromator(StandardReadable):
|
|
|
39
39
|
|
|
40
40
|
def __init__(
|
|
41
41
|
self,
|
|
42
|
-
motion_prefix: str,
|
|
43
42
|
temperature_prefix: str,
|
|
44
43
|
crystal_1_metadata: CrystalMetadata | None = None,
|
|
45
44
|
crystal_2_metadata: CrystalMetadata | None = None,
|
|
@@ -48,13 +47,13 @@ class DoubleCrystalMonochromator(StandardReadable):
|
|
|
48
47
|
) -> None:
|
|
49
48
|
with self.add_children_as_readables():
|
|
50
49
|
# Positionable Parameters
|
|
51
|
-
self.bragg = Motor(
|
|
52
|
-
self.offset = Motor(
|
|
53
|
-
self.perp = Motor(
|
|
54
|
-
self.energy = Motor(
|
|
55
|
-
self.crystal_1_roll = Motor(
|
|
56
|
-
self.crystal_2_roll = Motor(
|
|
57
|
-
self.crystal_2_pitch = Motor(
|
|
50
|
+
self.bragg = Motor(prefix + "BRAGG")
|
|
51
|
+
self.offset = Motor(prefix + "OFFSET")
|
|
52
|
+
self.perp = Motor(prefix + "PERP")
|
|
53
|
+
self.energy = Motor(prefix + "ENERGY")
|
|
54
|
+
self.crystal_1_roll = Motor(prefix + "XTAL1:ROLL")
|
|
55
|
+
self.crystal_2_roll = Motor(prefix + "XTAL2:ROLL")
|
|
56
|
+
self.crystal_2_pitch = Motor(prefix + "XTAL2:PITCH")
|
|
58
57
|
|
|
59
58
|
# Temperatures
|
|
60
59
|
self.backplate_temp = epics_signal_r(float, temperature_prefix + "PT100-7")
|
|
@@ -26,8 +26,8 @@ class BacklightPositioner(StandardReadable):
|
|
|
26
26
|
super().__init__(name)
|
|
27
27
|
|
|
28
28
|
@AsyncStatus.wrap
|
|
29
|
-
async def set(self,
|
|
30
|
-
await self.pos_level.set(
|
|
29
|
+
async def set(self, value: BacklightPositions):
|
|
30
|
+
await self.pos_level.set(value, wait=True)
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class DualBacklight(StandardReadable):
|
|
@@ -53,9 +53,9 @@ class DualBacklight(StandardReadable):
|
|
|
53
53
|
super().__init__(name)
|
|
54
54
|
|
|
55
55
|
@AsyncStatus.wrap
|
|
56
|
-
async def set(self,
|
|
57
|
-
await self.backlight_position.set(
|
|
58
|
-
if
|
|
56
|
+
async def set(self, value: BacklightPositions):
|
|
57
|
+
await self.backlight_position.set(value)
|
|
58
|
+
if value == BacklightPositions.OUT:
|
|
59
59
|
await self.backlight_state.set(LEDStatus.OFF, wait=True)
|
|
60
60
|
else:
|
|
61
61
|
await self.backlight_state.set(LEDStatus.ON, wait=True)
|
|
@@ -37,3 +37,25 @@ def camera_coordinates_to_xyz(
|
|
|
37
37
|
|
|
38
38
|
z = vertical * sine
|
|
39
39
|
return np.array([x, y, z], dtype=np.float64)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def calculate_beam_distance(
|
|
43
|
+
beam_centre: tuple[int, int],
|
|
44
|
+
horizontal_pixels: int,
|
|
45
|
+
vertical_pixels: int,
|
|
46
|
+
) -> tuple[int, int]:
|
|
47
|
+
"""
|
|
48
|
+
Calculates the distance between the beam centre and the given (horizontal, vertical).
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
horizontal_pixels (int): The x (camera coordinates) value in pixels.
|
|
52
|
+
vertical_pixels (int): The y (camera coordinates) value in pixels.
|
|
53
|
+
Returns:
|
|
54
|
+
The distance between the beam centre and the (horizontal, vertical) point in pixels as a tuple
|
|
55
|
+
(horizontal_distance, vertical_distance).
|
|
56
|
+
"""
|
|
57
|
+
beam_x, beam_y = beam_centre
|
|
58
|
+
return (
|
|
59
|
+
beam_x - horizontal_pixels,
|
|
60
|
+
beam_y - vertical_pixels,
|
|
61
|
+
)
|