dls-dodal 1.36.0__py3-none-any.whl → 1.36.1a0__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 (37) hide show
  1. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/METADATA +33 -33
  2. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/RECORD +37 -33
  3. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/b01_1.py +16 -31
  6. dodal/beamlines/i22.py +124 -265
  7. dodal/beamlines/i24.py +56 -7
  8. dodal/beamlines/p38.py +16 -1
  9. dodal/beamlines/p99.py +22 -53
  10. dodal/beamlines/training_rig.py +16 -26
  11. dodal/cli.py +54 -8
  12. dodal/common/beamlines/beamline_utils.py +32 -2
  13. dodal/common/beamlines/device_helpers.py +2 -0
  14. dodal/devices/aperture.py +7 -0
  15. dodal/devices/aperturescatterguard.py +195 -79
  16. dodal/devices/dcm.py +5 -4
  17. dodal/devices/fast_grid_scan.py +21 -46
  18. dodal/devices/focusing_mirror.py +8 -3
  19. dodal/devices/i24/beam_center.py +12 -0
  20. dodal/devices/i24/focus_mirrors.py +60 -0
  21. dodal/devices/i24/pilatus_metadata.py +44 -0
  22. dodal/devices/linkam3.py +1 -1
  23. dodal/devices/motors.py +14 -10
  24. dodal/devices/oav/oav_detector.py +2 -2
  25. dodal/devices/oav/pin_image_recognition/__init__.py +4 -5
  26. dodal/devices/oav/utils.py +1 -0
  27. dodal/devices/p99/sample_stage.py +12 -16
  28. dodal/devices/pressure_jump_cell.py +299 -0
  29. dodal/devices/robot.py +1 -1
  30. dodal/devices/tetramm.py +1 -1
  31. dodal/devices/undulator.py +4 -1
  32. dodal/devices/undulator_dcm.py +3 -19
  33. dodal/devices/zocalo/zocalo_results.py +7 -7
  34. dodal/utils.py +151 -2
  35. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/LICENSE +0 -0
  36. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/entry_points.txt +0 -0
  37. {dls_dodal-1.36.0.dist-info → dls_dodal-1.36.1a0.dist-info}/top_level.txt +0 -0
@@ -31,12 +31,12 @@ from dodal.parameters.experiment_parameter_base import AbstractExperimentWithBea
31
31
  @dataclass
32
32
  class GridAxis:
33
33
  start: float
34
- step_size: float
34
+ step_size_mm: float
35
35
  full_steps: int
36
36
 
37
37
  def steps_to_motor_position(self, steps):
38
38
  """Gives the motor position based on steps, where steps are 0 indexed"""
39
- return self.start + self.step_size * steps
39
+ return self.start + self.step_size_mm * steps
40
40
 
41
41
  @property
42
42
  def end(self):
@@ -62,44 +62,29 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
62
62
  x_steps: int = 1
63
63
  y_steps: int = 1
64
64
  z_steps: int = 0
65
- x_step_size: float = 0.1
66
- y_step_size: float = 0.1
67
- z_step_size: float = 0.1
68
- x_start: float = 0.1
69
- y1_start: float = 0.1
70
- y2_start: float = 0.1
71
- z1_start: float = 0.1
72
- z2_start: float = 0.1
65
+ x_step_size_mm: float = 0.1
66
+ y_step_size_mm: float = 0.1
67
+ z_step_size_mm: float = 0.1
68
+ x_start_mm: float = 0.1
69
+ y1_start_mm: float = 0.1
70
+ y2_start_mm: float = 0.1
71
+ z1_start_mm: float = 0.1
72
+ z2_start_mm: float = 0.1
73
73
 
74
74
  # Whether to set the stub offsets after centering
75
75
  set_stub_offsets: bool = False
76
76
 
77
- def get_param_positions(self) -> dict:
78
- return {
79
- "x_steps": self.x_steps,
80
- "y_steps": self.y_steps,
81
- "z_steps": self.z_steps,
82
- "x_step_size": self.x_step_size,
83
- "y_step_size": self.y_step_size,
84
- "z_step_size": self.z_step_size,
85
- "x_start": self.x_start,
86
- "y1_start": self.y1_start,
87
- "y2_start": self.y2_start,
88
- "z1_start": self.z1_start,
89
- "z2_start": self.z2_start,
90
- }
91
-
92
77
  @property
93
78
  def x_axis(self) -> GridAxis:
94
- return GridAxis(self.x_start, self.x_step_size, self.x_steps)
79
+ return GridAxis(self.x_start_mm, self.x_step_size_mm, self.x_steps)
95
80
 
96
81
  @property
97
82
  def y_axis(self) -> GridAxis:
98
- return GridAxis(self.y1_start, self.y_step_size, self.y_steps)
83
+ return GridAxis(self.y1_start_mm, self.y_step_size_mm, self.y_steps)
99
84
 
100
85
  @property
101
86
  def z_axis(self) -> GridAxis:
102
- return GridAxis(self.z2_start, self.z_step_size, self.z_steps)
87
+ return GridAxis(self.z2_start_mm, self.z_step_size_mm, self.z_steps)
103
88
 
104
89
  def get_num_images(self):
105
90
  return self.x_steps * (self.y_steps + self.z_steps)
@@ -140,11 +125,6 @@ class ZebraGridScanParams(GridScanParamsCommon):
140
125
 
141
126
  dwell_time_ms: float = 10
142
127
 
143
- def get_param_positions(self):
144
- param_positions = super().get_param_positions()
145
- param_positions["dwell_time_ms"] = self.dwell_time_ms
146
- return param_positions
147
-
148
128
  @field_validator("dwell_time_ms")
149
129
  @classmethod
150
130
  def non_integer_dwell_time(cls, dwell_time_ms: float) -> float:
@@ -166,11 +146,6 @@ class PandAGridScanParams(GridScanParamsCommon):
166
146
 
167
147
  run_up_distance_mm: float = 0.17
168
148
 
169
- def get_param_positions(self):
170
- param_positions = super().get_param_positions()
171
- param_positions["run_up_distance_mm"] = self.run_up_distance_mm
172
- return param_positions
173
-
174
149
 
175
150
  class MotionProgram(Device):
176
151
  def __init__(self, prefix: str, name: str = "") -> None:
@@ -241,14 +216,14 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
241
216
  "x_steps": self.x_steps,
242
217
  "y_steps": self.y_steps,
243
218
  "z_steps": self.z_steps,
244
- "x_step_size": self.x_step_size,
245
- "y_step_size": self.y_step_size,
246
- "z_step_size": self.z_step_size,
247
- "x_start": self.x_start,
248
- "y1_start": self.y1_start,
249
- "y2_start": self.y2_start,
250
- "z1_start": self.z1_start,
251
- "z2_start": self.z2_start,
219
+ "x_step_size_mm": self.x_step_size,
220
+ "y_step_size_mm": self.y_step_size,
221
+ "z_step_size_mm": self.z_step_size,
222
+ "x_start_mm": self.x_start,
223
+ "y1_start_mm": self.y1_start,
224
+ "y2_start_mm": self.y2_start,
225
+ "z1_start_mm": self.z1_start,
226
+ "z2_start_mm": self.z2_start,
252
227
  }
253
228
  super().__init__(name)
254
229
 
@@ -130,7 +130,12 @@ class FocusingMirror(StandardReadable):
130
130
  """Focusing Mirror"""
131
131
 
132
132
  def __init__(
133
- self, name, prefix, bragg_to_lat_lut_path=None, x_suffix="X", y_suffix="Y"
133
+ self,
134
+ prefix: str,
135
+ name: str = "",
136
+ bragg_to_lat_lut_path: str | None = None,
137
+ x_suffix: str = "X",
138
+ y_suffix: str = "Y",
134
139
  ):
135
140
  self.bragg_to_lat_lookup_table_path = bragg_to_lat_lut_path
136
141
  self.yaw_mrad = Motor(prefix + "YAW")
@@ -161,12 +166,12 @@ class FocusingMirrorWithStripes(FocusingMirror):
161
166
  """A focusing mirror where the stripe material can be changed. This is usually done
162
167
  based on the energy of the beamline."""
163
168
 
164
- def __init__(self, name, prefix, *args, **kwargs):
169
+ def __init__(self, prefix: str, name: str = "", *args, **kwargs):
165
170
  self.stripe = epics_signal_rw(MirrorStripe, prefix + "STRP:DVAL")
166
171
  # apply the current set stripe setting
167
172
  self.apply_stripe = epics_signal_x(prefix + "CHANGE.PROC")
168
173
 
169
- super().__init__(name, prefix, *args, **kwargs)
174
+ super().__init__(prefix, name, *args, **kwargs)
170
175
 
171
176
  def energy_to_stripe(self, energy_kev) -> MirrorStripe:
172
177
  # In future, this should be configurable per-mirror
@@ -0,0 +1,12 @@
1
+ """A small temporary device to get the beam center positions from \
2
+ eiger or pilatus detector on i24"""
3
+
4
+ from ophyd_async.core import StandardReadable
5
+ from ophyd_async.epics.core import epics_signal_rw
6
+
7
+
8
+ class DetectorBeamCenter(StandardReadable):
9
+ def __init__(self, prefix: str, name: str = "") -> None:
10
+ self.beam_x = epics_signal_rw(float, prefix + "BeamX")
11
+ self.beam_y = epics_signal_rw(float, prefix + "BeamY")
12
+ super().__init__(name)
@@ -0,0 +1,60 @@
1
+ from ophyd_async.core import StandardReadable, StrictEnum
2
+ from ophyd_async.epics.core import epics_signal_rw
3
+
4
+ from dodal.common.signal_utils import create_hardware_backed_soft_signal
5
+
6
+
7
+ class HFocusMode(StrictEnum):
8
+ focus10 = "HMFMfocus10"
9
+ focus20d = "HMFMfocus20d"
10
+ focus30d = "HMFMfocus30d"
11
+ focus50d = "HMFMfocus50d"
12
+ focus1050d = "HMFMfocus1030d"
13
+ focus3010d = "HMFMfocus3010d"
14
+
15
+
16
+ class VFocusMode(StrictEnum):
17
+ focus10 = "VMFMfocus10"
18
+ focus20d = "VMFMfocus20d"
19
+ focus30d = "VMFMfocus30d"
20
+ focus50d = "VMFMfocus50d"
21
+ focus1030d = "VMFMfocus1030d"
22
+ focus3010d = "VMFMfocus3010d"
23
+
24
+
25
+ BEAM_SIZES = {
26
+ "focus10": [7, 7],
27
+ "focus20d": [20, 20],
28
+ "focus30d": [30, 30],
29
+ "focus50d": [50, 50],
30
+ "focus1030d": [10, 30],
31
+ "focus3010d": [30, 10],
32
+ }
33
+
34
+
35
+ class FocusMirrorsMode(StandardReadable):
36
+ """A small device to read the focus mode and work out the beam size."""
37
+
38
+ def __init__(self, prefix: str, name: str = "") -> None:
39
+ self.horizontal = epics_signal_rw(HFocusMode, prefix + "G1:TARGETAPPLY")
40
+ self.vertical = epics_signal_rw(VFocusMode, prefix + "G0:TARGETAPPLY")
41
+
42
+ with self.add_children_as_readables():
43
+ self.beam_size_x = create_hardware_backed_soft_signal(
44
+ int, self._get_beam_size_x, units="um"
45
+ )
46
+ self.beam_size_y = create_hardware_backed_soft_signal(
47
+ int, self._get_beam_size_y, units="um"
48
+ )
49
+
50
+ super().__init__(name)
51
+
52
+ async def _get_beam_size_x(self) -> int:
53
+ h_mode = await self.horizontal.get_value()
54
+ beam_x = BEAM_SIZES[h_mode.removeprefix("HMFM")][0]
55
+ return beam_x
56
+
57
+ async def _get_beam_size_y(self) -> int:
58
+ v_mode = await self.vertical.get_value()
59
+ beam_y = BEAM_SIZES[v_mode.removeprefix("VMFM")][1]
60
+ return beam_y
@@ -0,0 +1,44 @@
1
+ """A small temporary device to set and read the filename template from the pilatus"""
2
+
3
+ import re
4
+
5
+ from ophyd_async.core import StandardReadable
6
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
7
+
8
+ from dodal.common.signal_utils import create_hardware_backed_soft_signal
9
+
10
+
11
+ class PilatusMetadata(StandardReadable):
12
+ def __init__(self, prefix: str, name: str = "") -> None:
13
+ self.filename = epics_signal_rw(str, prefix + "cam1:FileName")
14
+ self.template = epics_signal_r(str, prefix + "cam1:FileTemplate_RBV")
15
+ self.filenumber = epics_signal_r(int, prefix + "cam1:FileNumber_RBV")
16
+ with self.add_children_as_readables():
17
+ self.filename_template = create_hardware_backed_soft_signal(
18
+ str, self._get_full_filename_template
19
+ )
20
+ super().__init__(name)
21
+
22
+ async def _get_full_filename_template(self) -> str:
23
+ """
24
+ Get the template file path by querying the detector PVs.
25
+ Mirror the construction that the PPU does.
26
+
27
+ Returns: A template string, with the image numbers replaced with '#'
28
+ """
29
+ filename = await self.filename.get_value()
30
+ filename_template = await self.template.get_value()
31
+ file_number = await self.filenumber.get_value()
32
+ # Exploit fact that passing negative numbers will put the - before the 0's
33
+ expected_filename = str(
34
+ filename_template % (filename, f"{file_number:05d}_", -9)
35
+ )
36
+ # Now, find the -09 part of this
37
+ numberpart = re.search(r"(-0+9)", expected_filename)
38
+ assert numberpart is not None
39
+ template_fill = "#" * len(numberpart.group(0))
40
+ return (
41
+ expected_filename[: numberpart.start()]
42
+ + template_fill
43
+ + expected_filename[numberpart.end() :]
44
+ )
dodal/devices/linkam3.py CHANGED
@@ -33,7 +33,7 @@ class Linkam3(StandardReadable):
33
33
  tolerance: float = 0.5
34
34
  settle_time: int = 0
35
35
 
36
- def __init__(self, prefix: str, name: str):
36
+ def __init__(self, prefix: str, name: str = ""):
37
37
  self.temp = epics_signal_r(float, prefix + "TEMP:")
38
38
  self.dsc = epics_signal_r(float, prefix + "DSC:")
39
39
  self.start_heat = epics_signal_rw(bool, prefix + "STARTHEAT:")
dodal/devices/motors.py CHANGED
@@ -1,8 +1,8 @@
1
- from ophyd_async.core import Device
1
+ from ophyd_async.core import StandardReadable
2
2
  from ophyd_async.epics.motor import Motor
3
3
 
4
4
 
5
- class XYZPositioner(Device):
5
+ class XYZPositioner(StandardReadable):
6
6
  """
7
7
 
8
8
  Standard ophyd_async xyz motor stage, by combining 3 Motors,
@@ -15,7 +15,7 @@ class XYZPositioner(Device):
15
15
  name:
16
16
  name for the stage.
17
17
  infix:
18
- EPICS PV, default is the ["X", "Y", "Z"].
18
+ EPICS PV, default is the ("X", "Y", "Z").
19
19
  Notes
20
20
  -----
21
21
  Example usage::
@@ -23,14 +23,18 @@ class XYZPositioner(Device):
23
23
  xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:")
24
24
  Or::
25
25
  with DeviceCollector():
26
- xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:", suffix = ["A", "B", "C"])
26
+ xyz_stage = XYZPositioner("BLXX-MO-STAGE-XX:", infix = ("A", "B", "C"))
27
27
 
28
28
  """
29
29
 
30
- def __init__(self, prefix: str, name: str, infix: list[str] | None = None):
31
- if infix is None:
32
- infix = ["X", "Y", "Z"]
33
- self.x = Motor(prefix + infix[0])
34
- self.y = Motor(prefix + infix[1])
35
- self.z = Motor(prefix + infix[2])
30
+ def __init__(
31
+ self,
32
+ prefix: str,
33
+ name: str = "",
34
+ infix: tuple[str, str, str] = ("X", "Y", "Z"),
35
+ ):
36
+ 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])
36
40
  super().__init__(name=name)
@@ -40,15 +40,15 @@ class ZoomController(StandardReadable):
40
40
  """
41
41
 
42
42
  def __init__(self, prefix: str, name: str = "") -> None:
43
- super().__init__(name=name)
44
43
  self.percentage = epics_signal_rw(float, f"{prefix}ZOOMPOSCMD")
45
44
 
46
45
  # Level is the string description of the zoom level e.g. "1.0x" or "1.0"
47
46
  self.level = epics_signal_rw(str, f"{prefix}MP:SELECT")
47
+ super().__init__(name=name)
48
48
 
49
49
  async def _get_allowed_zoom_levels(self) -> list:
50
50
  zoom_levels = await self.level.describe()
51
- return zoom_levels["level"]["choices"] # type: ignore
51
+ return zoom_levels[self.level.name]["choices"] # type: ignore
52
52
 
53
53
  @AsyncStatus.wrap
54
54
  async def set(self, level_to_set: str):
@@ -2,7 +2,6 @@ import asyncio
2
2
  import time
3
3
 
4
4
  import numpy as np
5
- from numpy.typing import NDArray
6
5
  from ophyd_async.core import (
7
6
  Array1D,
8
7
  AsyncStatus,
@@ -57,12 +56,12 @@ class PinTipDetection(StandardReadable):
57
56
  Tip, name="triggered_tip"
58
57
  )
59
58
  self.triggered_top_edge, self._top_edge_setter = soft_signal_r_and_setter(
60
- NDArray[np.uint32], name="triggered_top_edge"
59
+ Array1D[np.int32], name="triggered_top_edge"
61
60
  )
62
61
  self.triggered_bottom_edge, self._bottom_edge_setter = soft_signal_r_and_setter(
63
- NDArray[np.uint32], name="triggered_bottom_edge"
62
+ Array1D[np.int32], name="triggered_bottom_edge"
64
63
  )
65
- self.array_data = epics_signal_r(NDArray[np.uint8], f"pva://{prefix}PVA:ARRAY")
64
+ self.array_data = epics_signal_r(Array1D[np.uint8], f"pva://{prefix}PVA:ARRAY")
66
65
 
67
66
  # Soft parameters for pin-tip detection.
68
67
  self.preprocess_operation = soft_signal_rw(int, 10, name="preprocess")
@@ -101,7 +100,7 @@ class PinTipDetection(StandardReadable):
101
100
  self._bottom_edge_setter(results.edge_bottom)
102
101
 
103
102
  async def _get_tip_and_edge_data(
104
- self, array_data: NDArray[np.uint8]
103
+ self, array_data: Array1D[np.uint8]
105
104
  ) -> SampleLocation:
106
105
  """
107
106
  Gets the location of the pin tip and the top and bottom edges.
@@ -87,6 +87,7 @@ def calculate_x_y_z_of_pixel(
87
87
  beam_centre: tuple[int, int],
88
88
  microns_per_pixel: tuple[float, float],
89
89
  ) -> np.ndarray:
90
+ """Get the x, y, z position of a pixel in mm"""
90
91
  beam_distance_px: Pixel = calculate_beam_distance(beam_centre, *pixel)
91
92
 
92
93
  return current_x_y_z + camera_coordinates_to_xyz(
@@ -1,18 +1,13 @@
1
- from ophyd_async.core import Device, SubsetEnum
2
- from ophyd_async.epics.core import epics_signal_rw
1
+ from ophyd_async.core import StandardReadable, SubsetEnum
2
+ from ophyd_async.epics.core import epics_signal_rw, epics_signal_rw_rbv
3
3
 
4
4
 
5
- class SampleAngleStage(Device):
6
- def __init__(self, prefix: str, name: str):
7
- self.theta = epics_signal_rw(
8
- float, prefix + "WRITETHETA:RBV", prefix + "WRITETHETA"
9
- )
10
- self.roll = epics_signal_rw(
11
- float, prefix + "WRITEROLL:RBV", prefix + "WRITEROLL"
12
- )
13
- self.pitch = epics_signal_rw(
14
- float, prefix + "WRITEPITCH:RBV", prefix + "WRITEPITCH"
15
- )
5
+ class SampleAngleStage(StandardReadable):
6
+ def __init__(self, prefix: str, name: str = ""):
7
+ with self.add_children_as_readables():
8
+ self.theta = epics_signal_rw_rbv(float, prefix + "WRITETHETA", ":RBV")
9
+ self.roll = epics_signal_rw_rbv(float, prefix + "WRITEROLL", ":RBV")
10
+ self.pitch = epics_signal_rw_rbv(float, prefix + "WRITEPITCH", ":RBV")
16
11
  super().__init__(name=name)
17
12
 
18
13
 
@@ -35,7 +30,8 @@ class p99StageSelections(SubsetEnum):
35
30
  User = "User"
36
31
 
37
32
 
38
- class FilterMotor(Device):
39
- def __init__(self, prefix: str, name: str):
40
- self.user_setpoint = epics_signal_rw(p99StageSelections, prefix)
33
+ class FilterMotor(StandardReadable):
34
+ def __init__(self, prefix: str, name: str = ""):
35
+ with self.add_children_as_readables():
36
+ self.user_setpoint = epics_signal_rw(p99StageSelections, prefix)
41
37
  super().__init__(name=name)