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.
Files changed (46) hide show
  1. {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/METADATA +2 -3
  2. {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/RECORD +46 -25
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/__init__.py +1 -0
  5. dodal/beamlines/i15.py +242 -0
  6. dodal/beamlines/i15_1.py +156 -0
  7. dodal/beamlines/i19_1.py +3 -1
  8. dodal/beamlines/i19_2.py +1 -1
  9. dodal/devices/apple2_undulator.py +85 -52
  10. dodal/devices/areadetector/__init__.py +0 -0
  11. dodal/devices/areadetector/plugins/__init__.py +0 -0
  12. dodal/devices/attenuator/__init__.py +0 -0
  13. dodal/devices/electron_analyser/abstract/__init__.py +2 -2
  14. dodal/devices/electron_analyser/abstract/base_detector.py +13 -26
  15. dodal/devices/electron_analyser/abstract/base_driver_io.py +5 -4
  16. dodal/devices/electron_analyser/abstract/base_region.py +28 -13
  17. dodal/devices/electron_analyser/detector.py +19 -31
  18. dodal/devices/electron_analyser/specs/driver_io.py +0 -1
  19. dodal/devices/electron_analyser/vgscienta/driver_io.py +0 -1
  20. dodal/devices/fast_grid_scan.py +14 -11
  21. dodal/devices/i04/murko_results.py +24 -12
  22. dodal/devices/i10/i10_apple2.py +15 -15
  23. dodal/devices/i10/rasor/__init__.py +0 -0
  24. dodal/devices/i11/__init__.py +0 -0
  25. dodal/devices/i15/__init__.py +0 -0
  26. dodal/devices/i15/dcm.py +77 -0
  27. dodal/devices/i15/focussing_mirror.py +55 -0
  28. dodal/devices/i15/jack.py +31 -0
  29. dodal/devices/i15/laue.py +14 -0
  30. dodal/devices/i15/motors.py +27 -0
  31. dodal/devices/i15/multilayer_mirror.py +21 -0
  32. dodal/devices/i15/rail.py +13 -0
  33. dodal/devices/i18/__init__.py +0 -0
  34. dodal/devices/i22/__init__.py +0 -0
  35. dodal/devices/i24/commissioning_jungfrau.py +9 -1
  36. dodal/devices/motors.py +52 -1
  37. dodal/devices/mx_phase1/__init__.py +0 -0
  38. dodal/devices/oav/snapshots/__init__.py +0 -0
  39. dodal/devices/slits.py +18 -0
  40. dodal/devices/v2f.py +7 -7
  41. dodal/devices/xspress3/__init__.py +0 -0
  42. dodal/parameters/__init__.py +0 -0
  43. {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/WHEEL +0 -0
  44. {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/entry_points.txt +0 -0
  45. {dls_dodal-1.59.1.dist-info → dls_dodal-1.61.0.dist-info}/licenses/LICENSE +0 -0
  46. {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.results: list[MurkoResult] = []
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.results:
131
+ if not self._results:
130
132
  raise NoResultsFound("No results retrieved from Murko")
131
133
 
132
- for result in self.results:
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 self.results]
138
- y_dists_mm = [result.y_dist_mm for result in self.results]
139
- omegas = [result.omega for result in self.results]
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.results.append(
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.results)}")
207
- sorted_results = sorted(self.results, key=lambda item: item.centre_px[0])
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
- self.results = smallest_x
219
- LOGGER.info(f"Number of results after filtering: {len(self.results)}")
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]:
@@ -19,10 +19,9 @@ from ophyd_async.core import (
19
19
  )
20
20
  from pydantic import BaseModel, ConfigDict, RootModel
21
21
 
22
- from dodal.log import LOGGER
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
- id_gap=UndulatorGap(name="id_gap", prefix=prefix),
363
- id_phase=UndulatorPhaseAxes(
364
- name="id_phase",
365
- prefix=prefix,
366
- top_outer="RPQ1",
367
- top_inner="RPQ2",
368
- btm_inner="RPQ3",
369
- btm_outer="RPQ4",
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
- @AsyncStatus.wrap
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._set(value=id_set_val, energy=value)
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
@@ -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 * 2 # This filewriter is slow
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 = _Z,
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
- HIGHSPEED5 = "10^5 high speed"
14
- HIGHSPEED6 = "10^6 high speed"
15
- HIGHSPEED7 = "10^7 high speed"
16
- HIGHSPEED8 = "10^8 high speed"
17
- HIGHSPEED9 = "10^9 high speed"
18
- HIGHSPEED10 = "10^10 high spd"
19
- HIGHSPEED11 = "10^11 high spd"
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