dls-dodal 1.50.0__py3-none-any.whl → 1.52.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 (83) hide show
  1. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/METADATA +5 -5
  2. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/RECORD +76 -68
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/adsim.py +5 -3
  5. dodal/beamlines/b01_1.py +41 -5
  6. dodal/beamlines/b07.py +13 -2
  7. dodal/beamlines/b07_1.py +13 -2
  8. dodal/beamlines/b16.py +8 -4
  9. dodal/beamlines/b21.py +148 -0
  10. dodal/beamlines/i03.py +10 -12
  11. dodal/beamlines/i04.py +7 -7
  12. dodal/beamlines/i09.py +25 -2
  13. dodal/beamlines/i09_1.py +13 -2
  14. dodal/beamlines/i09_2.py +24 -0
  15. dodal/beamlines/i10.py +5 -6
  16. dodal/beamlines/i13_1.py +5 -5
  17. dodal/beamlines/i18.py +5 -6
  18. dodal/beamlines/i22.py +18 -1
  19. dodal/beamlines/i24.py +5 -5
  20. dodal/beamlines/p45.py +4 -3
  21. dodal/beamlines/p60.py +21 -2
  22. dodal/beamlines/p99.py +19 -5
  23. dodal/beamlines/training_rig.py +3 -3
  24. dodal/common/beamlines/beamline_utils.py +5 -2
  25. dodal/common/device_utils.py +45 -0
  26. dodal/devices/aithre_lasershaping/goniometer.py +4 -5
  27. dodal/devices/aperture.py +4 -7
  28. dodal/devices/aperturescatterguard.py +2 -2
  29. dodal/devices/attenuator/attenuator.py +5 -3
  30. dodal/devices/b07/__init__.py +3 -0
  31. dodal/devices/b07/enums.py +24 -0
  32. dodal/devices/b07_1/__init__.py +3 -0
  33. dodal/devices/b07_1/enums.py +18 -0
  34. dodal/devices/detector/detector_motion.py +19 -17
  35. dodal/devices/electron_analyser/abstract/__init__.py +4 -0
  36. dodal/devices/electron_analyser/abstract/base_driver_io.py +44 -28
  37. dodal/devices/electron_analyser/abstract/base_region.py +20 -7
  38. dodal/devices/electron_analyser/detector.py +3 -13
  39. dodal/devices/electron_analyser/specs/detector.py +24 -4
  40. dodal/devices/electron_analyser/specs/driver_io.py +20 -5
  41. dodal/devices/electron_analyser/specs/region.py +9 -5
  42. dodal/devices/electron_analyser/types.py +21 -5
  43. dodal/devices/electron_analyser/vgscienta/detector.py +22 -7
  44. dodal/devices/electron_analyser/vgscienta/driver_io.py +16 -8
  45. dodal/devices/electron_analyser/vgscienta/region.py +11 -6
  46. dodal/devices/fast_grid_scan.py +1 -2
  47. dodal/devices/i04/constants.py +1 -1
  48. dodal/devices/i09/__init__.py +4 -0
  49. dodal/devices/i09/dcm.py +26 -0
  50. dodal/devices/i09/enums.py +15 -0
  51. dodal/devices/i09_1/__init__.py +3 -0
  52. dodal/devices/i09_1/enums.py +19 -0
  53. dodal/devices/i10/mirrors.py +4 -6
  54. dodal/devices/i10/rasor/rasor_motors.py +0 -14
  55. dodal/devices/i19/beamstop.py +3 -7
  56. dodal/devices/i24/aperture.py +4 -6
  57. dodal/devices/i24/beamstop.py +5 -8
  58. dodal/devices/i24/pmac.py +4 -8
  59. dodal/devices/linkam3.py +25 -81
  60. dodal/devices/motors.py +92 -35
  61. dodal/devices/oav/pin_image_recognition/__init__.py +11 -14
  62. dodal/devices/p45.py +0 -12
  63. dodal/devices/p60/__init__.py +4 -0
  64. dodal/devices/p60/enums.py +10 -0
  65. dodal/devices/p60/lab_xray_source.py +21 -0
  66. dodal/devices/pgm.py +1 -1
  67. dodal/devices/robot.py +11 -7
  68. dodal/devices/smargon.py +8 -9
  69. dodal/devices/tetramm.py +134 -150
  70. dodal/devices/xbpm_feedback.py +6 -3
  71. dodal/devices/zocalo/zocalo_results.py +27 -78
  72. dodal/plans/configure_arm_trigger_and_disarm_detector.py +7 -5
  73. dodal/devices/adsim.py +0 -13
  74. dodal/devices/i18/table.py +0 -14
  75. dodal/devices/i18/thor_labs_stage.py +0 -12
  76. dodal/devices/i24/i24_detector_motion.py +0 -12
  77. dodal/devices/scatterguard.py +0 -11
  78. dodal/devices/training_rig/__init__.py +0 -0
  79. dodal/devices/training_rig/sample_stage.py +0 -10
  80. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/WHEEL +0 -0
  81. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/entry_points.txt +0 -0
  82. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/licenses/LICENSE +0 -0
  83. {dls_dodal-1.50.0.dist-info → dls_dodal-1.52.0.dist-info}/top_level.txt +0 -0
dodal/devices/motors.py CHANGED
@@ -1,61 +1,118 @@
1
+ from abc import ABC
2
+
1
3
  from ophyd_async.core import StandardReadable
2
4
  from ophyd_async.epics.motor import Motor
3
5
 
6
+ _X, _Y, _Z = "X", "Y", "Z"
4
7
 
5
- class XYZPositioner(StandardReadable):
6
- """
7
8
 
8
- Standard ophyd_async xyz motor stage, by combining 3 Motors,
9
- with added infix for extra flexibliy to allow different axes other than x,y,z.
9
+ class Stage(StandardReadable, ABC):
10
+ """
11
+ For these devices, the following co-ordinates are typical but not enforced:
12
+ - z is horizontal & parallel to the direction of beam travel
13
+ - y is vertical and antiparallel to the force of gravity
14
+ - x is the cross product of y🞬z
10
15
 
11
16
  Parameters
12
17
  ----------
13
18
  prefix:
14
- EPICS PV (Common part up to and including :).
19
+ Common part of the EPICS PV for all motors, including ":".
15
20
  name:
16
- name for the stage.
17
- infix:
18
- EPICS PV, default is the ("X", "Y", "Z").
19
- Notes
20
- -----
21
- Example usage::
22
- async with init_devices():
23
- xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:")
24
- Or::
25
- with init_devices():
26
- xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:", infix = ("A", "B", "C"))
27
-
21
+ Name of the stage, each child motor will be named "{name}-{field_name}"
22
+ *_infix:
23
+ Infix between the common prefix and the EPICS motor record fields for the field.
28
24
  """
29
25
 
26
+ ...
27
+
28
+
29
+ class XThetaStage(Stage):
30
+ def __init__(
31
+ self, prefix: str, name: str = "", x_infix: str = _X, theta_infix: str = "A"
32
+ ):
33
+ with self.add_children_as_readables():
34
+ self.x = Motor(prefix + x_infix)
35
+ self.theta = Motor(prefix + theta_infix)
36
+ super().__init__(name=name)
37
+
38
+
39
+ class XYStage(Stage):
40
+ def __init__(
41
+ self, prefix: str, name: str = "", x_infix: str = _X, y_infix: str = _Y
42
+ ):
43
+ with self.add_children_as_readables():
44
+ self.x = Motor(prefix + x_infix)
45
+ self.y = Motor(prefix + y_infix)
46
+ super().__init__(name=name)
47
+
48
+
49
+ class XYZStage(XYStage):
30
50
  def __init__(
31
51
  self,
32
52
  prefix: str,
33
53
  name: str = "",
34
- infix: tuple[str, str, str] = ("X", "Y", "Z"),
54
+ x_infix: str = _X,
55
+ y_infix: str = _Y,
56
+ z_infix: str = _Z,
35
57
  ):
36
58
  with self.add_children_as_readables():
37
- self.x = Motor(prefix + infix[0])
38
- self.y = Motor(prefix + infix[1])
39
- self.z = Motor(prefix + infix[2])
40
- super().__init__(name=name)
59
+ self.z = Motor(prefix + z_infix)
60
+ super().__init__(prefix, name, x_infix, y_infix)
41
61
 
42
62
 
43
- class SixAxisGonio(XYZPositioner):
63
+ class XYZThetaStage(XYZStage):
44
64
  def __init__(
45
65
  self,
46
66
  prefix: str,
47
67
  name: str = "",
48
- infix: tuple[str, str, str, str, str, str] = (
49
- "X",
50
- "Y",
51
- "Z",
52
- "KAPPA",
53
- "PHI",
54
- "OMEGA",
55
- ),
68
+ x_infix: str = _X,
69
+ y_infix: str = _Y,
70
+ z_infix: str = _Z,
71
+ theta_infix: str = _Z,
72
+ ) -> None:
73
+ with self.add_children_as_readables():
74
+ self.theta = Motor(prefix + theta_infix)
75
+ super().__init__(prefix, name, x_infix, y_infix, z_infix)
76
+
77
+
78
+ class XYPitchStage(XYStage):
79
+ def __init__(
80
+ self,
81
+ prefix: str,
82
+ x_infix: str = _X,
83
+ y_infix: str = _Y,
84
+ pitch_infix: str = "PITCH",
85
+ name: str = "",
86
+ ) -> None:
87
+ with self.add_children_as_readables():
88
+ self.pitch = Motor(prefix + pitch_infix)
89
+ super().__init__(prefix, name, x_infix, y_infix)
90
+
91
+
92
+ class SixAxisGonio(XYZStage):
93
+ def __init__(
94
+ self,
95
+ prefix: str,
96
+ name: str = "",
97
+ x_infix: str = _X,
98
+ y_infix: str = _Y,
99
+ z_infix: str = _Z,
100
+ kappa_infix: str = "KAPPA",
101
+ phi_infix: str = "PHI",
102
+ omega_infix: str = "OMEGA",
56
103
  ):
57
104
  with self.add_children_as_readables():
58
- self.kappa = Motor(prefix + infix[3])
59
- self.phi = Motor(prefix + infix[4])
60
- self.omega = Motor(prefix + infix[5])
61
- super().__init__(name=name, prefix=prefix, infix=infix[0:3])
105
+ self.kappa = Motor(prefix + kappa_infix)
106
+ self.phi = Motor(prefix + phi_infix)
107
+ self.omega = Motor(prefix + omega_infix)
108
+ super().__init__(prefix, name, x_infix, y_infix, z_infix)
109
+
110
+
111
+ class YZStage(Stage):
112
+ def __init__(
113
+ self, prefix: str, name: str = "", y_infix: str = _Y, z_infix: str = _Z
114
+ ) -> None:
115
+ with self.add_children_as_readables():
116
+ self.y = Motor(prefix + y_infix)
117
+ self.z = Motor(prefix + z_infix)
118
+ super().__init__(name)
@@ -141,14 +141,16 @@ class PinTipDetection(StandardReadable):
141
141
 
142
142
  @AsyncStatus.wrap
143
143
  async def trigger(self):
144
- async def _set_triggered_tip():
145
- """Monitors the camera data and updates the triggered_tip signal.
146
-
147
- If a tip is found it will update the signal and stop monitoring
148
- If no tip is found it will retry with the next monitored value
149
- This loop will serve as a good example of using 'observe_value' in the ophyd_async documentation
150
- """
151
- async for value in observe_value(self.array_data):
144
+ """Monitors the camera data and updates the triggered_tip signal.
145
+
146
+ * If a tip is found it will update the signal and stop monitoring
147
+ * If no tip is found it will retry with the next monitored value, if this
148
+ continues for {validity_timeout} seconds it will timeout.
149
+ """
150
+ try:
151
+ async for value in observe_value(
152
+ self.array_data, done_timeout=await self.validity_timeout.get_value()
153
+ ):
152
154
  try:
153
155
  location = await self._get_tip_and_edge_data(value)
154
156
  self._set_triggered_values(location)
@@ -157,12 +159,7 @@ class PinTipDetection(StandardReadable):
157
159
  f"Failed to detect pin-tip location, will retry with next image: {e}"
158
160
  )
159
161
  else:
160
- return
161
-
162
- try:
163
- await asyncio.wait_for(
164
- _set_triggered_tip(), timeout=await self.validity_timeout.get_value()
165
- )
162
+ break
166
163
  except asyncio.exceptions.TimeoutError:
167
164
  LOGGER.error(
168
165
  f"No tip found in {await self.validity_timeout.get_value()} seconds."
dodal/devices/p45.py CHANGED
@@ -41,15 +41,3 @@ class TomoStageWithStretchAndSkew(StandardReadable):
41
41
  self.y = SampleY(prefix)
42
42
  self.theta = SampleTheta(prefix)
43
43
  super().__init__(name)
44
-
45
-
46
- class Choppers(StandardReadable):
47
- """
48
- Grouping for the P45 chopper motors
49
- """
50
-
51
- def __init__(self, prefix: str, name: str = ""):
52
- with self.add_children_as_readables():
53
- self.x = Motor(prefix + "ENDAT")
54
- self.y = Motor(prefix + "BISS")
55
- super().__init__(name)
@@ -0,0 +1,4 @@
1
+ from .enums import LensMode
2
+ from .lab_xray_source import LabXraySource, LabXraySourceReadable
3
+
4
+ __all__ = ["LensMode", "LabXraySource", "LabXraySourceReadable"]
@@ -0,0 +1,10 @@
1
+ from ophyd_async.core import StrictEnum
2
+
3
+
4
+ class LensMode(StrictEnum):
5
+ TRANSMISSION = "Transmission"
6
+ ANGULAR14 = "Angular14"
7
+ ANGULAR7NF = "Angular7NF"
8
+ ANGULAR30 = "Angular30"
9
+ ANGULAR30_SMALLSPOT = "Angular30_SmallSpot"
10
+ ANGULAR14_SMALLSPOT = "Angular14_SmallSpot"
@@ -0,0 +1,21 @@
1
+ from ophyd_async.core import (
2
+ StandardReadable,
3
+ StrictEnum,
4
+ soft_signal_r_and_setter,
5
+ )
6
+
7
+
8
+ class LabXraySource(StrictEnum):
9
+ AL_KALPHA = 1486.6
10
+ MG_KALPHA = 1253.6
11
+
12
+
13
+ class LabXraySourceReadable(StandardReadable):
14
+ """Simple device to get the laboratory x-ray tube energy reading"""
15
+
16
+ def __init__(self, xraysource: LabXraySource, name: str = "") -> None:
17
+ with self.add_children_as_readables():
18
+ self.energy_ev, _ = soft_signal_r_and_setter(
19
+ float, initial_value=xraysource.value, units="eV"
20
+ )
21
+ super().__init__(name)
dodal/devices/pgm.py CHANGED
@@ -16,7 +16,7 @@ class PGM(StandardReadable):
16
16
  self,
17
17
  prefix: str,
18
18
  grating: type[StrictEnum],
19
- gratingPv: str,
19
+ gratingPv: str = "GRATINGSELECT:SELECT",
20
20
  name: str = "",
21
21
  ) -> None:
22
22
  """
dodal/devices/robot.py CHANGED
@@ -7,6 +7,7 @@ from ophyd_async.core import (
7
7
  AsyncStatus,
8
8
  Device,
9
9
  StandardReadable,
10
+ StandardReadableFormat,
10
11
  StrictEnum,
11
12
  set_and_wait_for_value,
12
13
  wait_for_value,
@@ -74,18 +75,21 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
74
75
  LOAD_TOLERANCE_MM = 0.02
75
76
 
76
77
  def __init__(self, name: str, prefix: str) -> None:
77
- self.barcode = epics_signal_r(str, prefix + "BARCODE")
78
- self.gonio_pin_sensor = epics_signal_r(PinMounted, prefix + "PIN_MOUNTED")
78
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
79
+ self.barcode = epics_signal_r(str, prefix + "BARCODE")
80
+ self.gonio_pin_sensor = epics_signal_r(PinMounted, prefix + "PIN_MOUNTED")
81
+
82
+ self.current_puck = epics_signal_r(float, prefix + "CURRENT_PUCK_RBV")
83
+ self.current_pin = epics_signal_r(float, prefix + "CURRENT_PIN_RBV")
79
84
 
80
85
  self.next_pin = epics_signal_rw_rbv(float, prefix + "NEXT_PIN")
81
86
  self.next_puck = epics_signal_rw_rbv(float, prefix + "NEXT_PUCK")
82
- self.current_puck = epics_signal_r(float, prefix + "CURRENT_PUCK_RBV")
83
- self.current_pin = epics_signal_r(float, prefix + "CURRENT_PIN_RBV")
84
87
 
85
- self.next_sample_id = epics_signal_rw_rbv(int, prefix + "NEXT_ID")
86
88
  self.sample_id = epics_signal_r(int, prefix + "CURRENT_ID_RBV")
89
+ self.next_sample_id = epics_signal_rw_rbv(int, prefix + "NEXT_ID")
87
90
 
88
91
  self.load = epics_signal_x(prefix + "LOAD.PROC")
92
+ self.unload = epics_signal_x(prefix + "UNLD.PROC")
89
93
  self.program_running = epics_signal_r(bool, prefix + "PROGRAM_RUNNING")
90
94
  self.program_name = epics_signal_r(str, prefix + "PROGRAM_NAME")
91
95
 
@@ -93,7 +97,7 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
93
97
  self.controller_error = ErrorStatus(prefix + "CNTL")
94
98
 
95
99
  self.reset = epics_signal_x(prefix + "RESET.PROC")
96
- self.stop = epics_signal_x(prefix + "ABORT.PROC")
100
+ self.abort = epics_signal_x(prefix + "ABORT.PROC")
97
101
  self.init = epics_signal_x(prefix + "INIT.PROC")
98
102
  self.soak = epics_signal_x(prefix + "SOAK.PROC")
99
103
  self.home = epics_signal_x(prefix + "GOHM.PROC")
@@ -173,7 +177,7 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
173
177
  self._load_pin_and_puck(value),
174
178
  timeout=self.LOAD_TIMEOUT + self.NOT_BUSY_TIMEOUT,
175
179
  )
176
- except (asyncio.TimeoutError, TimeoutError) as e:
180
+ except TimeoutError as e:
177
181
  # Will only need to catch asyncio.TimeoutError after https://github.com/bluesky/ophyd-async/issues/572
178
182
  await self.prog_error.raise_if_error(e)
179
183
  await self.controller_error.raise_if_error(e)
dodal/devices/smargon.py CHANGED
@@ -3,7 +3,7 @@ from collections.abc import Collection, Generator
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from math import isclose
6
- from typing import TypedDict, cast
6
+ from typing import NotRequired, TypedDict, cast
7
7
 
8
8
  from bluesky import plan_stubs as bps
9
9
  from bluesky.protocols import Movable
@@ -11,14 +11,13 @@ from bluesky.utils import Msg
11
11
  from ophyd_async.core import (
12
12
  AsyncStatus,
13
13
  Device,
14
- StandardReadable,
15
14
  StrictEnum,
16
15
  wait_for_value,
17
16
  )
18
17
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
19
18
  from ophyd_async.epics.motor import Motor
20
- from typing_extensions import NotRequired
21
19
 
20
+ from dodal.devices.motors import XYZStage
22
21
  from dodal.devices.util.epics_util import SetWhenEnabled
23
22
 
24
23
 
@@ -116,7 +115,7 @@ class CombinedMove(TypedDict):
116
115
  chi: NotRequired[float | None]
117
116
 
118
117
 
119
- class Smargon(StandardReadable, Movable):
118
+ class Smargon(XYZStage, Movable):
120
119
  """
121
120
  Real motors added to allow stops following pin load (e.g. real_x1.stop() )
122
121
  X1 and X2 real motors provide compound chi motion as well as the compound X travel,
@@ -126,9 +125,6 @@ class Smargon(StandardReadable, Movable):
126
125
 
127
126
  def __init__(self, prefix: str = "", name: str = ""):
128
127
  with self.add_children_as_readables():
129
- self.x = Motor(prefix + "X")
130
- self.y = Motor(prefix + "Y")
131
- self.z = Motor(prefix + "Z")
132
128
  self.chi = Motor(prefix + "CHI")
133
129
  self.phi = Motor(prefix + "PHI")
134
130
  self.omega = Motor(prefix + "OMEGA")
@@ -143,7 +139,7 @@ class Smargon(StandardReadable, Movable):
143
139
 
144
140
  self.defer_move = epics_signal_rw(DeferMoves, prefix + "CS1:DeferMoves")
145
141
 
146
- super().__init__(name)
142
+ super().__init__(prefix, name)
147
143
 
148
144
  def get_xyz_limits(self) -> Generator[Msg, None, XYZLimits]:
149
145
  """Obtain a plan stub that returns the smargon XYZ axis limits
@@ -171,6 +167,9 @@ class Smargon(StandardReadable, Movable):
171
167
  for k, v in value.items():
172
168
  if v is not None:
173
169
  tasks.append(getattr(self, k).set(v))
174
- await asyncio.gather(*tasks)
175
170
  finally:
176
171
  await self.defer_move.set(DeferMoves.OFF)
172
+ # The set() coroutines will not complete until after defer moves has been
173
+ # switched back off so we cannot wait for them until this point.
174
+ # see https://github.com/DiamondLightSource/dodal/issues/1315
175
+ await asyncio.gather(*tasks)