dls-dodal 1.59.1__py3-none-any.whl → 1.61.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.
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/METADATA +2 -3
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/RECORD +46 -25
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/i15.py +242 -0
- dodal/beamlines/i15_1.py +156 -0
- dodal/beamlines/i19_1.py +3 -1
- dodal/beamlines/i19_2.py +1 -1
- dodal/devices/apple2_undulator.py +85 -52
- dodal/devices/areadetector/__init__.py +0 -0
- dodal/devices/areadetector/plugins/__init__.py +0 -0
- dodal/devices/attenuator/__init__.py +0 -0
- dodal/devices/electron_analyser/abstract/__init__.py +2 -2
- dodal/devices/electron_analyser/abstract/base_detector.py +13 -26
- dodal/devices/electron_analyser/abstract/base_driver_io.py +5 -4
- dodal/devices/electron_analyser/abstract/base_region.py +28 -13
- dodal/devices/electron_analyser/detector.py +19 -31
- dodal/devices/electron_analyser/specs/driver_io.py +0 -1
- dodal/devices/electron_analyser/vgscienta/driver_io.py +0 -1
- dodal/devices/fast_grid_scan.py +14 -11
- dodal/devices/i04/murko_results.py +24 -12
- dodal/devices/i10/i10_apple2.py +15 -15
- dodal/devices/i10/rasor/__init__.py +0 -0
- dodal/devices/i11/__init__.py +0 -0
- dodal/devices/i15/__init__.py +0 -0
- dodal/devices/i15/dcm.py +77 -0
- dodal/devices/i15/focussing_mirror.py +55 -0
- dodal/devices/i15/jack.py +31 -0
- dodal/devices/i15/laue.py +14 -0
- dodal/devices/i15/motors.py +27 -0
- dodal/devices/i15/multilayer_mirror.py +21 -0
- dodal/devices/i15/rail.py +13 -0
- dodal/devices/i18/__init__.py +0 -0
- dodal/devices/i22/__init__.py +0 -0
- dodal/devices/i24/commissioning_jungfrau.py +9 -1
- dodal/devices/motors.py +52 -1
- dodal/devices/mx_phase1/__init__.py +0 -0
- dodal/devices/oav/snapshots/__init__.py +0 -0
- dodal/devices/slits.py +18 -0
- dodal/devices/v2f.py +7 -7
- dodal/devices/xspress3/__init__.py +0 -0
- dodal/parameters/__init__.py +0 -0
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/top_level.txt +0 -0
|
@@ -32,6 +32,7 @@ class MurkoMetadata(TypedDict):
|
|
|
32
32
|
sample_id: str
|
|
33
33
|
omega_angle: float
|
|
34
34
|
uuid: str
|
|
35
|
+
used_for_centring: bool | None
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
class Coord(Enum):
|
|
@@ -47,6 +48,7 @@ class MurkoResult:
|
|
|
47
48
|
y_dist_mm: float
|
|
48
49
|
omega: float
|
|
49
50
|
uuid: str
|
|
51
|
+
metadata: MurkoMetadata
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
class NoResultsFound(ValueError):
|
|
@@ -101,7 +103,7 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
101
103
|
|
|
102
104
|
def _reset(self):
|
|
103
105
|
self._last_omega = 0
|
|
104
|
-
self.
|
|
106
|
+
self._results: list[MurkoResult] = []
|
|
105
107
|
|
|
106
108
|
@AsyncStatus.wrap
|
|
107
109
|
async def stage(self):
|
|
@@ -126,17 +128,17 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
126
128
|
continue
|
|
127
129
|
await self.process_batch(message, sample_id)
|
|
128
130
|
|
|
129
|
-
if not self.
|
|
131
|
+
if not self._results:
|
|
130
132
|
raise NoResultsFound("No results retrieved from Murko")
|
|
131
133
|
|
|
132
|
-
for result in self.
|
|
134
|
+
for result in self._results:
|
|
133
135
|
LOGGER.debug(result)
|
|
134
136
|
|
|
135
|
-
self.filter_outliers()
|
|
137
|
+
filtered_results = self.filter_outliers()
|
|
136
138
|
|
|
137
|
-
x_dists_mm = [result.x_dist_mm for result in
|
|
138
|
-
y_dists_mm = [result.y_dist_mm for result in
|
|
139
|
-
omegas = [result.omega for result in
|
|
139
|
+
x_dists_mm = [result.x_dist_mm for result in filtered_results]
|
|
140
|
+
y_dists_mm = [result.y_dist_mm for result in filtered_results]
|
|
141
|
+
omegas = [result.omega for result in filtered_results]
|
|
140
142
|
|
|
141
143
|
LOGGER.info(f"Using average of x beam distances: {x_dists_mm}")
|
|
142
144
|
avg_x = float(np.mean(x_dists_mm))
|
|
@@ -147,6 +149,11 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
147
149
|
self._y_mm_setter(-best_y)
|
|
148
150
|
self._z_mm_setter(-best_z)
|
|
149
151
|
|
|
152
|
+
for result in self._results:
|
|
153
|
+
await self.redis_client.hset( # type: ignore
|
|
154
|
+
f"murko:{sample_id}:metadata", result.uuid, json.dumps(result.metadata)
|
|
155
|
+
)
|
|
156
|
+
|
|
150
157
|
async def process_batch(self, message: dict | None, sample_id: str):
|
|
151
158
|
if message and message["type"] == "message":
|
|
152
159
|
batch_results: list[dict] = pickle.loads(message["data"])
|
|
@@ -186,13 +193,14 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
186
193
|
centre_px[0],
|
|
187
194
|
centre_px[1],
|
|
188
195
|
)
|
|
189
|
-
self.
|
|
196
|
+
self._results.append(
|
|
190
197
|
MurkoResult(
|
|
191
198
|
centre_px=centre_px,
|
|
192
199
|
x_dist_mm=beam_dist_px[0] * metadata["microns_per_x_pixel"] / 1000,
|
|
193
200
|
y_dist_mm=beam_dist_px[1] * metadata["microns_per_y_pixel"] / 1000,
|
|
194
201
|
omega=omega,
|
|
195
202
|
uuid=metadata["uuid"],
|
|
203
|
+
metadata=metadata,
|
|
196
204
|
)
|
|
197
205
|
)
|
|
198
206
|
self._last_omega = omega
|
|
@@ -203,8 +211,8 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
203
211
|
meaning that by keeping only a percentage of the results with the smallest X we
|
|
204
212
|
remove many of the outliers.
|
|
205
213
|
"""
|
|
206
|
-
LOGGER.info(f"Number of results before filtering: {len(self.
|
|
207
|
-
sorted_results = sorted(self.
|
|
214
|
+
LOGGER.info(f"Number of results before filtering: {len(self._results)}")
|
|
215
|
+
sorted_results = sorted(self._results, key=lambda item: item.centre_px[0])
|
|
208
216
|
|
|
209
217
|
worst_results = [
|
|
210
218
|
r.uuid for r in sorted_results[-self.NUMBER_OF_WRONG_RESULTS_TO_LOG :]
|
|
@@ -214,9 +222,13 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
214
222
|
f"Worst {self.NUMBER_OF_WRONG_RESULTS_TO_LOG} murko results were {worst_results}"
|
|
215
223
|
)
|
|
216
224
|
cutoff = max(1, int(len(sorted_results) * self.PERCENTAGE_TO_USE / 100))
|
|
225
|
+
for i, result in enumerate(sorted_results):
|
|
226
|
+
result.metadata["used_for_centring"] = i < cutoff
|
|
227
|
+
|
|
217
228
|
smallest_x = sorted_results[:cutoff]
|
|
218
|
-
|
|
219
|
-
LOGGER.info(f"Number of results after filtering: {len(
|
|
229
|
+
|
|
230
|
+
LOGGER.info(f"Number of results after filtering: {len(smallest_x)}")
|
|
231
|
+
return smallest_x
|
|
220
232
|
|
|
221
233
|
|
|
222
234
|
def get_yz_least_squares(vertical_dists: list, omegas: list) -> tuple[float, float]:
|
dodal/devices/i10/i10_apple2.py
CHANGED
|
@@ -19,10 +19,9 @@ from ophyd_async.core import (
|
|
|
19
19
|
)
|
|
20
20
|
from pydantic import BaseModel, ConfigDict, RootModel
|
|
21
21
|
|
|
22
|
-
from dodal.
|
|
23
|
-
|
|
24
|
-
from ..apple2_undulator import (
|
|
22
|
+
from dodal.devices.apple2_undulator import (
|
|
25
23
|
Apple2,
|
|
24
|
+
Apple2Motors,
|
|
26
25
|
Apple2Val,
|
|
27
26
|
EnergyMotorConvertor,
|
|
28
27
|
Pol,
|
|
@@ -30,6 +29,8 @@ from ..apple2_undulator import (
|
|
|
30
29
|
UndulatorJawPhase,
|
|
31
30
|
UndulatorPhaseAxes,
|
|
32
31
|
)
|
|
32
|
+
from dodal.log import LOGGER
|
|
33
|
+
|
|
33
34
|
from ..pgm import PGM
|
|
34
35
|
|
|
35
36
|
ROW_PHASE_MOTOR_TOLERANCE = 0.004
|
|
@@ -359,14 +360,15 @@ class I10Apple2(Apple2):
|
|
|
359
360
|
|
|
360
361
|
with self.add_children_as_readables():
|
|
361
362
|
super().__init__(
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
363
|
+
apple2_motors=Apple2Motors(
|
|
364
|
+
id_gap=UndulatorGap(prefix=prefix),
|
|
365
|
+
id_phase=UndulatorPhaseAxes(
|
|
366
|
+
prefix=prefix,
|
|
367
|
+
top_outer="RPQ1",
|
|
368
|
+
top_inner="RPQ2",
|
|
369
|
+
btm_inner="RPQ3",
|
|
370
|
+
btm_outer="RPQ4",
|
|
371
|
+
),
|
|
370
372
|
),
|
|
371
373
|
energy_motor_convertor=energy_motor_convertor,
|
|
372
374
|
name=name,
|
|
@@ -376,8 +378,7 @@ class I10Apple2(Apple2):
|
|
|
376
378
|
move_pv="RPQ1",
|
|
377
379
|
)
|
|
378
380
|
|
|
379
|
-
|
|
380
|
-
async def set(self, value: float) -> None:
|
|
381
|
+
async def _set(self, value: float) -> None:
|
|
381
382
|
"""
|
|
382
383
|
Check polarisation state and use it together with the energy(value)
|
|
383
384
|
to calculate the required gap and phases before setting it.
|
|
@@ -408,11 +409,10 @@ class I10Apple2(Apple2):
|
|
|
408
409
|
)
|
|
409
410
|
|
|
410
411
|
LOGGER.info(f"Setting polarisation to {pol}, with values: {id_set_val}")
|
|
411
|
-
await self.
|
|
412
|
+
await self.motors.set(id_motor_values=id_set_val)
|
|
412
413
|
if pol != Pol.LA:
|
|
413
414
|
await self.id_jaw_phase.set(0)
|
|
414
415
|
await self.id_jaw_phase.set_move.set(1)
|
|
415
|
-
LOGGER.info(f"Energy set to {value} eV successfully.")
|
|
416
416
|
|
|
417
417
|
|
|
418
418
|
class EnergySetter(StandardReadable, Movable[float]):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
dodal/devices/i15/dcm.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing import Generic, TypeVar
|
|
2
|
+
|
|
3
|
+
from ophyd_async.core import StandardReadable
|
|
4
|
+
from ophyd_async.epics.motor import Motor
|
|
5
|
+
|
|
6
|
+
from dodal.devices.common_dcm import (
|
|
7
|
+
StationaryCrystal,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ThetaYCrystal(StationaryCrystal):
|
|
12
|
+
def __init__(self, prefix):
|
|
13
|
+
with self.add_children_as_readables():
|
|
14
|
+
self.theta = Motor(prefix + "THETA")
|
|
15
|
+
self.y = Motor(prefix + "Y")
|
|
16
|
+
super().__init__(prefix)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ThetaRollYZCrystal(ThetaYCrystal):
|
|
20
|
+
def __init__(self, prefix):
|
|
21
|
+
with self.add_children_as_readables():
|
|
22
|
+
self.roll = Motor(prefix + "ROLL")
|
|
23
|
+
self.z = Motor(prefix + "Z")
|
|
24
|
+
super().__init__(prefix)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Xtal_1 = TypeVar("Xtal_1", bound=StationaryCrystal)
|
|
28
|
+
Xtal_2 = TypeVar("Xtal_2", bound=StationaryCrystal)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DualCrystalMonoSimple(StandardReadable, Generic[Xtal_1, Xtal_2]):
|
|
32
|
+
"""
|
|
33
|
+
Device for simple double crystal monochromators (DCM), which only allow energy of the beam to be selected.
|
|
34
|
+
|
|
35
|
+
Features common across all DCM's should include virtual motors to set energy/wavelength and contain two crystals,
|
|
36
|
+
each of which can be movable. Some DCM's contain crystals with roll motors, and some contain crystals with roll and pitch motors.
|
|
37
|
+
This base device accounts for all combinations of this.
|
|
38
|
+
|
|
39
|
+
This device is more able to act as a parent for beamline-specific DCM's, in which any other missing signals can be added,
|
|
40
|
+
as it doesn't assume WAVELENGTH, BRAGG and OFFSET are available for all DCM deivces, as BaseDCM does.
|
|
41
|
+
|
|
42
|
+
Bluesky plans using DCM's should be typed to specify which types of crystals are required. For example, a plan
|
|
43
|
+
which only requires one crystal which can roll should be typed 'def my_plan(dcm: BaseDCM[RollCrystal, StationaryCrystal])`
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self, prefix: str, xtal_1: type[Xtal_1], xtal_2: type[Xtal_2], name: str = ""
|
|
48
|
+
) -> None:
|
|
49
|
+
with self.add_children_as_readables():
|
|
50
|
+
# Virtual motor PV's which set the physical motors so that the DCM produces requested
|
|
51
|
+
# wavelength/energy
|
|
52
|
+
self.energy_in_kev = Motor(prefix + "ENERGY")
|
|
53
|
+
self._make_crystals(prefix, xtal_1, xtal_2)
|
|
54
|
+
|
|
55
|
+
super().__init__(name)
|
|
56
|
+
|
|
57
|
+
# Prefix convention is different depending on whether there are one or two controllable crystals
|
|
58
|
+
def _make_crystals(self, prefix: str, xtal_1: type[Xtal_1], xtal_2: type[Xtal_2]):
|
|
59
|
+
if StationaryCrystal not in [xtal_1, xtal_2]:
|
|
60
|
+
self.xtal_1 = xtal_1(f"{prefix}XTAL1:")
|
|
61
|
+
self.xtal_2 = xtal_2(f"{prefix}XTAL2:")
|
|
62
|
+
else:
|
|
63
|
+
self.xtal_1 = xtal_1(prefix)
|
|
64
|
+
self.xtal_2 = xtal_2(prefix)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DCM(DualCrystalMonoSimple[ThetaRollYZCrystal, ThetaYCrystal]):
|
|
68
|
+
"""
|
|
69
|
+
A double crystal monocromator device, used to select the beam energy.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
73
|
+
with self.add_children_as_readables():
|
|
74
|
+
self.calibrated_energy_in_kev = Motor(prefix + "CAL")
|
|
75
|
+
self.x1 = Motor(prefix + "X1")
|
|
76
|
+
|
|
77
|
+
super().__init__(prefix, ThetaRollYZCrystal, ThetaYCrystal, name)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FocusingMirrorBase(StandardReadable):
|
|
6
|
+
"""Focusing Mirror with curve, ellip & pitch"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
9
|
+
with self.add_children_as_readables():
|
|
10
|
+
self.curve = Motor(prefix + "CURVE")
|
|
11
|
+
self.ellipticity = Motor(prefix + "ELLIP")
|
|
12
|
+
self.pitch = Motor(prefix + "PITCH")
|
|
13
|
+
|
|
14
|
+
super().__init__(name)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FocusingMirrorHorizontal(FocusingMirrorBase):
|
|
18
|
+
"""Focusing Mirror with curve, ellip, pitch & X"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
21
|
+
with self.add_children_as_readables():
|
|
22
|
+
self.x = Motor(prefix + "X")
|
|
23
|
+
|
|
24
|
+
super().__init__(prefix, name)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FocusingMirrorVertical(FocusingMirrorBase):
|
|
28
|
+
"""Focusing Mirror with curve, ellip, pitch & Y"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
31
|
+
with self.add_children_as_readables():
|
|
32
|
+
self.y = Motor(prefix + "Y")
|
|
33
|
+
|
|
34
|
+
super().__init__(prefix, name)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FocusingMirror(FocusingMirrorBase):
|
|
38
|
+
"""Focusing Mirror with curve, ellip, pitch, yaw, X & Y"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
41
|
+
with self.add_children_as_readables():
|
|
42
|
+
self.yaw = Motor(prefix + "YAW")
|
|
43
|
+
self.x = Motor(prefix + "X")
|
|
44
|
+
self.y = Motor(prefix + "Y")
|
|
45
|
+
|
|
46
|
+
super().__init__(prefix, name)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class FocusingMirrorWithRoll(FocusingMirror):
|
|
50
|
+
"""Focusing Mirror with curve, ellip, pitch, roll, yaw, X & Y"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, prefix: str, name: str = "") -> None:
|
|
53
|
+
with self.add_children_as_readables():
|
|
54
|
+
self.roll = Motor(prefix + "ROLL")
|
|
55
|
+
super().__init__(prefix, name)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class JackX(StandardReadable):
|
|
6
|
+
"""Focusing Mirror"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
9
|
+
with self.add_children_as_readables():
|
|
10
|
+
self.rotation = Motor(prefix + "Ry")
|
|
11
|
+
self.transx = Motor(prefix + "X")
|
|
12
|
+
self.y1 = Motor(prefix + "Y1")
|
|
13
|
+
self.y2 = Motor(prefix + "Y2")
|
|
14
|
+
self.y3 = Motor(prefix + "Y3")
|
|
15
|
+
|
|
16
|
+
super().__init__(name)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class JackY(StandardReadable):
|
|
20
|
+
"""Focusing Mirror"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
23
|
+
with self.add_children_as_readables():
|
|
24
|
+
self.j1 = Motor(prefix + "J1")
|
|
25
|
+
self.j2 = Motor(prefix + "J2")
|
|
26
|
+
self.j3 = Motor(prefix + "J3")
|
|
27
|
+
self.pitch = Motor(prefix + "PITCH")
|
|
28
|
+
self.roll = Motor(prefix + "ROLL")
|
|
29
|
+
self.y = Motor(prefix + "Y")
|
|
30
|
+
|
|
31
|
+
super().__init__(name)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class LaueMonochrometer(StandardReadable):
|
|
6
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
7
|
+
with self.add_children_as_readables():
|
|
8
|
+
self.bend = Motor(prefix + "BENDER")
|
|
9
|
+
self.bragg = Motor(prefix + "PITCH")
|
|
10
|
+
self.roll = Motor(prefix + "ROLL")
|
|
11
|
+
self.yaw = Motor(prefix + "YAW")
|
|
12
|
+
self.y = Motor(prefix + "Y")
|
|
13
|
+
|
|
14
|
+
super().__init__(name)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from ophyd_async.epics.motor import Motor
|
|
2
|
+
|
|
3
|
+
from dodal.devices.motors import Stage
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UpstreamDownstreamPair(Stage):
|
|
7
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
8
|
+
with self.add_children_as_readables():
|
|
9
|
+
self.upstream = Motor(prefix + "US")
|
|
10
|
+
self.downstream = Motor(prefix + "DS")
|
|
11
|
+
super().__init__(name=name)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NumberedTripleAxisStage(Stage):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
prefix: str,
|
|
18
|
+
name: str = "",
|
|
19
|
+
axis1_infix: str = "AXIS1",
|
|
20
|
+
axis2_infix: str = "AXIS2",
|
|
21
|
+
axis3_infix: str = "AXIS3",
|
|
22
|
+
):
|
|
23
|
+
with self.add_children_as_readables():
|
|
24
|
+
self.axis1 = Motor(prefix + axis1_infix)
|
|
25
|
+
self.axis2 = Motor(prefix + axis2_infix)
|
|
26
|
+
self.axis3 = Motor(prefix + axis3_infix)
|
|
27
|
+
super().__init__(name=name)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MultiLayerMirror(StandardReadable):
|
|
6
|
+
"""Multilayer Mirror"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
9
|
+
with self.add_children_as_readables():
|
|
10
|
+
self.ds_x = Motor(prefix + "X2")
|
|
11
|
+
self.ds_y = Motor(prefix + "J3")
|
|
12
|
+
self.ib_y = Motor(prefix + "J1")
|
|
13
|
+
self.ob_y = Motor(prefix + "J2")
|
|
14
|
+
self.pitch = Motor(prefix + "PITCH")
|
|
15
|
+
self.roll = Motor(prefix + "ROLL")
|
|
16
|
+
self.us_x = Motor(prefix + "X1")
|
|
17
|
+
self.x = Motor(prefix + "X")
|
|
18
|
+
self.y = Motor(prefix + "Y")
|
|
19
|
+
self.yaw = Motor(prefix + "YAW")
|
|
20
|
+
|
|
21
|
+
super().__init__(name)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Rail(StandardReadable):
|
|
6
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
7
|
+
with self.add_children_as_readables():
|
|
8
|
+
self.pitch = Motor(prefix + "PITCH")
|
|
9
|
+
self.y = Motor(prefix + "Y")
|
|
10
|
+
self.y1 = Motor(prefix + "Y1")
|
|
11
|
+
self.y2 = Motor(prefix + "Y2")
|
|
12
|
+
|
|
13
|
+
super().__init__(name)
|
|
File without changes
|
|
File without changes
|
|
@@ -5,11 +5,13 @@ from pathlib import Path
|
|
|
5
5
|
from bluesky.protocols import StreamAsset
|
|
6
6
|
from event_model import DataKey # type: ignore
|
|
7
7
|
from ophyd_async.core import (
|
|
8
|
+
AsyncStatus,
|
|
8
9
|
AutoIncrementingPathProvider,
|
|
9
10
|
DetectorWriter,
|
|
10
11
|
StandardDetector,
|
|
11
12
|
StandardReadable,
|
|
12
13
|
StaticPathProvider,
|
|
14
|
+
TriggerInfo,
|
|
13
15
|
observe_value,
|
|
14
16
|
wait_for_value,
|
|
15
17
|
)
|
|
@@ -40,6 +42,7 @@ class JunfrauCommissioningWriter(DetectorWriter, StandardReadable):
|
|
|
40
42
|
self.file_name = epics_signal_rw_rbv(str, f"{prefix}FileName")
|
|
41
43
|
self.file_path = epics_signal_rw_rbv(str, f"{prefix}FilePath")
|
|
42
44
|
self.writer_ready = epics_signal_r(int, f"{prefix}Ready_RBV")
|
|
45
|
+
self.expected_frames = epics_signal_rw(int, f"{prefix}NumCapture")
|
|
43
46
|
super().__init__(name)
|
|
44
47
|
|
|
45
48
|
async def open(self, name: str, exposures_per_event: int = 1) -> dict[str, DataKey]:
|
|
@@ -80,7 +83,7 @@ class JunfrauCommissioningWriter(DetectorWriter, StandardReadable):
|
|
|
80
83
|
async def observe_indices_written(
|
|
81
84
|
self, timeout: float
|
|
82
85
|
) -> AsyncGenerator[int, None]:
|
|
83
|
-
timeout = timeout *
|
|
86
|
+
timeout = timeout * 4 # This filewriter is very slow
|
|
84
87
|
async for num_captured in observe_value(self.frame_counter, timeout):
|
|
85
88
|
yield num_captured // (self._exposures_per_event)
|
|
86
89
|
|
|
@@ -112,3 +115,8 @@ class CommissioningJungfrau(
|
|
|
112
115
|
writer = JunfrauCommissioningWriter(writer_prefix, path_provider)
|
|
113
116
|
controller = JungfrauController(self.drv)
|
|
114
117
|
super().__init__(controller, writer, name=name)
|
|
118
|
+
|
|
119
|
+
@AsyncStatus.wrap
|
|
120
|
+
async def prepare(self, value: TriggerInfo) -> None:
|
|
121
|
+
await super().prepare(value)
|
|
122
|
+
await self._writer.expected_frames.set(value.total_number_of_exposures)
|
dodal/devices/motors.py
CHANGED
|
@@ -70,13 +70,27 @@ class XYZThetaStage(XYZStage):
|
|
|
70
70
|
x_infix: str = _X,
|
|
71
71
|
y_infix: str = _Y,
|
|
72
72
|
z_infix: str = _Z,
|
|
73
|
-
theta_infix: str =
|
|
73
|
+
theta_infix: str = "THETA",
|
|
74
74
|
) -> None:
|
|
75
75
|
with self.add_children_as_readables():
|
|
76
76
|
self.theta = Motor(prefix + theta_infix)
|
|
77
77
|
super().__init__(prefix, name, x_infix, y_infix, z_infix)
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
class XYPhiStage(XYStage):
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
prefix: str,
|
|
84
|
+
x_infix: str = _X,
|
|
85
|
+
y_infix: str = _Y,
|
|
86
|
+
phi_infix: str = "PHI",
|
|
87
|
+
name: str = "",
|
|
88
|
+
) -> None:
|
|
89
|
+
with self.add_children_as_readables():
|
|
90
|
+
self.phi = Motor(prefix + phi_infix)
|
|
91
|
+
super().__init__(prefix, name, x_infix, y_infix)
|
|
92
|
+
|
|
93
|
+
|
|
80
94
|
class XYPitchStage(XYStage):
|
|
81
95
|
def __init__(
|
|
82
96
|
self,
|
|
@@ -91,6 +105,23 @@ class XYPitchStage(XYStage):
|
|
|
91
105
|
super().__init__(prefix, name, x_infix, y_infix)
|
|
92
106
|
|
|
93
107
|
|
|
108
|
+
class XYZPitchYawStage(XYZStage):
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
prefix: str,
|
|
112
|
+
name: str = "",
|
|
113
|
+
x_infix: str = _X,
|
|
114
|
+
y_infix: str = _Y,
|
|
115
|
+
z_infix: str = _Z,
|
|
116
|
+
pitch_infix="PITCH",
|
|
117
|
+
yaw_infix="YAW",
|
|
118
|
+
):
|
|
119
|
+
with self.add_children_as_readables():
|
|
120
|
+
self.pitch = Motor(prefix + pitch_infix)
|
|
121
|
+
self.yaw = Motor(prefix + yaw_infix)
|
|
122
|
+
super().__init__(prefix, name, x_infix, y_infix, z_infix)
|
|
123
|
+
|
|
124
|
+
|
|
94
125
|
class XYZPitchYawRollStage(XYZStage):
|
|
95
126
|
def __init__(
|
|
96
127
|
self,
|
|
@@ -136,6 +167,26 @@ class SixAxisGonio(XYZStage):
|
|
|
136
167
|
)
|
|
137
168
|
|
|
138
169
|
|
|
170
|
+
class SixAxisGonioKappaPhi(XYZStage):
|
|
171
|
+
def __init__(
|
|
172
|
+
self,
|
|
173
|
+
prefix: str,
|
|
174
|
+
name: str = "",
|
|
175
|
+
x_infix: str = _X,
|
|
176
|
+
y_infix: str = _Y,
|
|
177
|
+
z_infix: str = _Z,
|
|
178
|
+
kappa_infix: str = "KAPPA",
|
|
179
|
+
phi_infix: str = "PHI",
|
|
180
|
+
):
|
|
181
|
+
"""Six-axis goniometer with a standard xyz stage and two axes of rotation:
|
|
182
|
+
kappa and phi.
|
|
183
|
+
"""
|
|
184
|
+
with self.add_children_as_readables():
|
|
185
|
+
self.kappa = Motor(prefix + kappa_infix)
|
|
186
|
+
self.phi = Motor(prefix + phi_infix)
|
|
187
|
+
super().__init__(prefix, name, x_infix, y_infix, z_infix)
|
|
188
|
+
|
|
189
|
+
|
|
139
190
|
class YZStage(Stage):
|
|
140
191
|
def __init__(
|
|
141
192
|
self, prefix: str, name: str = "", y_infix: str = _Y, z_infix: str = _Z
|
|
File without changes
|
|
File without changes
|
dodal/devices/slits.py
CHANGED
|
@@ -37,3 +37,21 @@ class Slits(MinimalSlits):
|
|
|
37
37
|
self.x_centre = Motor(prefix + x_centre)
|
|
38
38
|
self.y_centre = Motor(prefix + y_centre)
|
|
39
39
|
super().__init__(prefix=prefix, x_gap=x_gap, y_gap=y_gap, name=name)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SlitsY(StandardReadable):
|
|
43
|
+
"""
|
|
44
|
+
Representation of a 2-blade slits.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
prefix: str,
|
|
50
|
+
y_gap: str = "Y:SIZE",
|
|
51
|
+
y_centre: str = "Y:CENTRE",
|
|
52
|
+
name: str = "",
|
|
53
|
+
) -> None:
|
|
54
|
+
with self.add_children_as_readables():
|
|
55
|
+
self.y_gap = Motor(prefix + y_gap)
|
|
56
|
+
self.y_centre = Motor(prefix + y_centre)
|
|
57
|
+
super().__init__(name=name)
|
dodal/devices/v2f.py
CHANGED
|
@@ -10,13 +10,13 @@ class V2FGain(StrictEnum):
|
|
|
10
10
|
LOW_NOISE7 = "10^7 low noise"
|
|
11
11
|
LOW_NOISE8 = "10^8 low noise"
|
|
12
12
|
LOW_NOISE9 = "10^9 low noise"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
HIGH_SPEED5 = "10^5 high speed"
|
|
14
|
+
HIGH_SPEED6 = "10^6 high speed"
|
|
15
|
+
HIGH_SPEED7 = "10^7 high speed"
|
|
16
|
+
HIGH_SPEED8 = "10^8 high speed"
|
|
17
|
+
HIGH_SPEED9 = "10^9 high speed"
|
|
18
|
+
HIGH_SPEED10 = "10^10 high spd"
|
|
19
|
+
HIGH_SPEED11 = "10^11 high spd"
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class QDV2F(StandardReadable):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|