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.
Files changed (55) hide show
  1. {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/METADATA +3 -3
  2. {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/RECORD +53 -43
  3. {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/WHEEL +1 -1
  4. dodal/__init__.py +8 -0
  5. dodal/_version.py +2 -2
  6. dodal/beamline_specific_utils/i03.py +6 -2
  7. dodal/beamlines/__init__.py +2 -3
  8. dodal/beamlines/b01_1.py +77 -0
  9. dodal/beamlines/i03.py +41 -9
  10. dodal/beamlines/i04.py +26 -4
  11. dodal/beamlines/i10.py +257 -0
  12. dodal/beamlines/i22.py +1 -2
  13. dodal/beamlines/i24.py +7 -7
  14. dodal/beamlines/p38.py +1 -2
  15. dodal/common/signal_utils.py +53 -0
  16. dodal/common/types.py +2 -7
  17. dodal/devices/aperturescatterguard.py +12 -15
  18. dodal/devices/apple2_undulator.py +602 -0
  19. dodal/devices/areadetector/plugins/CAM.py +31 -0
  20. dodal/devices/areadetector/plugins/MJPG.py +51 -106
  21. dodal/devices/backlight.py +7 -6
  22. dodal/devices/diamond_filter.py +47 -0
  23. dodal/devices/eiger.py +6 -2
  24. dodal/devices/eiger_odin.py +48 -39
  25. dodal/devices/focusing_mirror.py +14 -8
  26. dodal/devices/i10/i10_apple2.py +398 -0
  27. dodal/devices/i10/i10_setting_data.py +7 -0
  28. dodal/devices/i22/dcm.py +7 -8
  29. dodal/devices/i24/dual_backlight.py +5 -5
  30. dodal/devices/oav/oav_calculations.py +22 -0
  31. dodal/devices/oav/oav_detector.py +118 -97
  32. dodal/devices/oav/oav_parameters.py +50 -104
  33. dodal/devices/oav/oav_to_redis_forwarder.py +75 -34
  34. dodal/devices/oav/{grid_overlay.py → snapshots/grid_overlay.py} +0 -43
  35. dodal/devices/oav/snapshots/snapshot_with_beam_centre.py +64 -0
  36. dodal/devices/oav/snapshots/snapshot_with_grid.py +57 -0
  37. dodal/devices/oav/utils.py +26 -25
  38. dodal/devices/pgm.py +41 -0
  39. dodal/devices/qbpm.py +18 -0
  40. dodal/devices/robot.py +2 -2
  41. dodal/devices/smargon.py +2 -2
  42. dodal/devices/tetramm.py +2 -2
  43. dodal/devices/undulator.py +2 -1
  44. dodal/devices/util/adjuster_plans.py +1 -1
  45. dodal/devices/util/lookup_tables.py +4 -5
  46. dodal/devices/zebra.py +5 -2
  47. dodal/devices/zocalo/zocalo_results.py +13 -10
  48. dodal/plans/data_session_metadata.py +2 -2
  49. dodal/plans/motor_util_plans.py +11 -9
  50. dodal/utils.py +7 -0
  51. dodal/beamlines/i04_1.py +0 -140
  52. dodal/devices/oav/oav_errors.py +0 -35
  53. {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/LICENSE +0 -0
  54. {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/entry_points.txt +0 -0
  55. {dls_dodal-1.32.0.dist-info → dls_dodal-1.34.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,64 @@
1
+ from ophyd_async.core import SignalR
2
+ from PIL import Image, ImageDraw
3
+
4
+ from dodal.devices.areadetector.plugins.MJPG import MJPG
5
+
6
+ CROSSHAIR_LENGTH_PX = 20
7
+ CROSSHAIR_OUTLINE_COLOUR = "Black"
8
+ CROSSHAIR_FILL_COLOUR = "White"
9
+
10
+
11
+ def draw_crosshair(image: Image.Image, beam_x: int, beam_y: int):
12
+ draw = ImageDraw.Draw(image)
13
+ OUTLINE_WIDTH = 1
14
+ HALF_LEN = CROSSHAIR_LENGTH_PX / 2
15
+ draw.rectangle(
16
+ [
17
+ beam_x - OUTLINE_WIDTH,
18
+ beam_y - HALF_LEN - OUTLINE_WIDTH,
19
+ beam_x + OUTLINE_WIDTH,
20
+ beam_y + HALF_LEN + OUTLINE_WIDTH,
21
+ ],
22
+ fill=CROSSHAIR_OUTLINE_COLOUR,
23
+ )
24
+ draw.rectangle(
25
+ [
26
+ beam_x - HALF_LEN - OUTLINE_WIDTH,
27
+ beam_y - OUTLINE_WIDTH,
28
+ beam_x + HALF_LEN + OUTLINE_WIDTH,
29
+ beam_y + OUTLINE_WIDTH,
30
+ ],
31
+ fill=CROSSHAIR_OUTLINE_COLOUR,
32
+ )
33
+ draw.line(
34
+ ((beam_x, beam_y - HALF_LEN), (beam_x, beam_y + HALF_LEN)),
35
+ fill=CROSSHAIR_FILL_COLOUR,
36
+ )
37
+ draw.line(
38
+ ((beam_x - HALF_LEN, beam_y), (beam_x + HALF_LEN, beam_y)),
39
+ fill=CROSSHAIR_FILL_COLOUR,
40
+ )
41
+
42
+
43
+ class SnapshotWithBeamCentre(MJPG):
44
+ """A child of MJPG which, when triggered, draws an outlined crosshair at the beam
45
+ centre in the image and saves the image to disk."""
46
+
47
+ def __init__(
48
+ self,
49
+ prefix: str,
50
+ beam_x_signal: SignalR,
51
+ beam_y_signal: SignalR,
52
+ name: str = "",
53
+ ) -> None:
54
+ with self.add_children_as_readables():
55
+ self.beam_centre_i = beam_x_signal
56
+ self.beam_centre_j = beam_y_signal
57
+ super().__init__(prefix, name)
58
+
59
+ async def post_processing(self, image: Image.Image):
60
+ beam_x = await self.beam_centre_i.get_value()
61
+ beam_y = await self.beam_centre_j.get_value()
62
+ draw_crosshair(image, beam_x, beam_y)
63
+
64
+ await self._save_image(image)
@@ -0,0 +1,57 @@
1
+ from os.path import join as path_join
2
+
3
+ from ophyd_async.core import soft_signal_rw
4
+ from PIL.Image import Image
5
+
6
+ from dodal.devices.areadetector.plugins.MJPG import IMG_FORMAT, MJPG, asyncio_save_image
7
+ from dodal.devices.oav.snapshots.grid_overlay import (
8
+ add_grid_border_overlay_to_image,
9
+ add_grid_overlay_to_image,
10
+ )
11
+ from dodal.log import LOGGER
12
+
13
+
14
+ class SnapshotWithGrid(MJPG):
15
+ def __init__(self, prefix: str, name: str = "") -> None:
16
+ with self.add_children_as_readables():
17
+ self.top_left_x = soft_signal_rw(int)
18
+ self.top_left_y = soft_signal_rw(int)
19
+ self.box_width = soft_signal_rw(int)
20
+ self.num_boxes_x = soft_signal_rw(int)
21
+ self.num_boxes_y = soft_signal_rw(int)
22
+
23
+ self.last_path_outer = soft_signal_rw(str)
24
+ self.last_path_full_overlay = soft_signal_rw(str)
25
+
26
+ super().__init__(prefix, name)
27
+
28
+ async def post_processing(self, image: Image):
29
+ # Save an unmodified image with no suffix
30
+ await self._save_image(image)
31
+
32
+ top_left_x = await self.top_left_x.get_value()
33
+ top_left_y = await self.top_left_y.get_value()
34
+ box_witdh = await self.box_width.get_value()
35
+ num_boxes_x = await self.num_boxes_x.get_value()
36
+ num_boxes_y = await self.num_boxes_y.get_value()
37
+
38
+ assert isinstance(filename_str := await self.filename.get_value(), str)
39
+ assert isinstance(directory_str := await self.directory.get_value(), str)
40
+
41
+ add_grid_border_overlay_to_image(
42
+ image, top_left_x, top_left_y, box_witdh, num_boxes_x, num_boxes_y
43
+ )
44
+
45
+ path = path_join(directory_str, f"{filename_str}_outer_overlay.{IMG_FORMAT}")
46
+ await self.last_path_outer.set(path, wait=True)
47
+ LOGGER.info(f"Saving grid outer edge at {path}")
48
+ await asyncio_save_image(image, path)
49
+
50
+ add_grid_overlay_to_image(
51
+ image, top_left_x, top_left_y, box_witdh, num_boxes_x, num_boxes_y
52
+ )
53
+
54
+ path = path_join(directory_str, f"{filename_str}_grid_overlay.{IMG_FORMAT}")
55
+ await self.last_path_full_overlay.set(path, wait=True)
56
+ LOGGER.info(f"Saving full grid overlay at {path}")
57
+ await asyncio_save_image(image, path)
@@ -5,8 +5,11 @@ import bluesky.plan_stubs as bps
5
5
  import numpy as np
6
6
  from bluesky.utils import Msg
7
7
 
8
- from dodal.devices.oav.oav_calculations import camera_coordinates_to_xyz
9
- from dodal.devices.oav.oav_detector import OAVConfigParams
8
+ from dodal.devices.oav.oav_calculations import (
9
+ calculate_beam_distance,
10
+ camera_coordinates_to_xyz,
11
+ )
12
+ from dodal.devices.oav.oav_detector import OAV
10
13
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
11
14
  from dodal.devices.smargon import Smargon
12
15
 
@@ -36,21 +39,6 @@ def bottom_right_from_top_left(
36
39
  )
37
40
 
38
41
 
39
- class ColorMode(IntEnum):
40
- """
41
- Enum to store the various color modes of the camera. We use RGB1.
42
- """
43
-
44
- MONO = 0
45
- BAYER = 1
46
- RGB1 = 2
47
- RGB2 = 3
48
- RGB3 = 4
49
- YUV444 = 5
50
- YUV422 = 6
51
- YUV421 = 7
52
-
53
-
54
42
  class EdgeOutputArrayImageType(IntEnum):
55
43
  """
56
44
  Enum to store the types of image to tweak the output array. We use Original.
@@ -64,7 +52,7 @@ class EdgeOutputArrayImageType(IntEnum):
64
52
 
65
53
 
66
54
  def get_move_required_so_that_beam_is_at_pixel(
67
- smargon: Smargon, pixel: Pixel, oav_params: OAVConfigParams
55
+ smargon: Smargon, pixel: Pixel, oav: OAV
68
56
  ) -> Generator[Msg, None, np.ndarray]:
69
57
  """Calculate the required move so that the given pixel is in the centre of the beam."""
70
58
 
@@ -78,22 +66,35 @@ def get_move_required_so_that_beam_is_at_pixel(
78
66
  )
79
67
  current_angle = yield from bps.rd(smargon.omega)
80
68
 
81
- return calculate_x_y_z_of_pixel(current_motor_xyz, current_angle, pixel, oav_params)
69
+ beam_x = yield from bps.rd(oav.beam_centre_i)
70
+ beam_y = yield from bps.rd(oav.beam_centre_j)
71
+ microns_per_pixel_x = yield from bps.rd(oav.microns_per_pixel_x)
72
+ microns_per_pixel_y = yield from bps.rd(oav.microns_per_pixel_y)
73
+
74
+ return calculate_x_y_z_of_pixel(
75
+ current_motor_xyz,
76
+ current_angle,
77
+ pixel,
78
+ (beam_x, beam_y),
79
+ (microns_per_pixel_x, microns_per_pixel_y),
80
+ )
82
81
 
83
82
 
84
83
  def calculate_x_y_z_of_pixel(
85
- current_x_y_z, current_omega, pixel: Pixel, oav_params: OAVConfigParams
84
+ current_x_y_z,
85
+ current_omega,
86
+ pixel: Pixel,
87
+ beam_centre: tuple[int, int],
88
+ microns_per_pixel: tuple[float, float],
86
89
  ) -> np.ndarray:
87
- beam_distance_px: Pixel = oav_params.calculate_beam_distance(*pixel)
90
+ beam_distance_px: Pixel = calculate_beam_distance(beam_centre, *pixel)
88
91
 
89
- assert oav_params.micronsPerXPixel
90
- assert oav_params.micronsPerYPixel
91
92
  return current_x_y_z + camera_coordinates_to_xyz(
92
93
  beam_distance_px[0],
93
94
  beam_distance_px[1],
94
95
  current_omega,
95
- oav_params.micronsPerXPixel,
96
- oav_params.micronsPerYPixel,
96
+ microns_per_pixel[0],
97
+ microns_per_pixel[1],
97
98
  )
98
99
 
99
100
 
dodal/devices/pgm.py ADDED
@@ -0,0 +1,41 @@
1
+ from enum import Enum
2
+
3
+ from ophyd_async.core import (
4
+ ConfigSignal,
5
+ StandardReadable,
6
+ )
7
+ from ophyd_async.epics.motor import Motor
8
+ from ophyd_async.epics.signal import epics_signal_rw
9
+
10
+
11
+ class PGM(StandardReadable):
12
+ """
13
+ Plane grating monochromator, it is use in soft x-ray beamline to generate monochromic beam.
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ prefix: str,
19
+ grating: type[Enum],
20
+ gratingPv: str,
21
+ name: str = "",
22
+ ) -> None:
23
+ """
24
+ Parameters
25
+ ----------
26
+ prefix:
27
+ Beamline specific part of the PV
28
+ grating:
29
+ The Enum for the grating table.
30
+ gratingPv:
31
+ The suffix pv part of grating Pv
32
+ name:
33
+ Name of the device
34
+ """
35
+ with self.add_children_as_readables():
36
+ self.energy = Motor(prefix + "ENERGY")
37
+ with self.add_children_as_readables(ConfigSignal):
38
+ self.grating = epics_signal_rw(grating, prefix + gratingPv)
39
+ self.cff = epics_signal_rw(float, prefix + "CFF")
40
+
41
+ super().__init__(name=name)
dodal/devices/qbpm.py ADDED
@@ -0,0 +1,18 @@
1
+ from ophyd_async.core import StandardReadable
2
+ from ophyd_async.epics.signal import epics_signal_r
3
+
4
+
5
+ class QBPM(StandardReadable):
6
+ """
7
+ A beam position monitor that gives a position and intensity of the beam.
8
+ """
9
+
10
+ def __init__(
11
+ self,
12
+ prefix: str,
13
+ name: str = "",
14
+ ) -> None:
15
+ with self.add_children_as_readables():
16
+ self.intensity_uA = epics_signal_r(float, f"{prefix}INTEN")
17
+
18
+ super().__init__(name)
dodal/devices/robot.py CHANGED
@@ -63,8 +63,8 @@ class BartRobot(StandardReadable, Movable):
63
63
  self.current_puck = epics_signal_r(float, prefix + "CURRENT_PUCK_RBV")
64
64
  self.current_pin = epics_signal_r(float, prefix + "CURRENT_PIN_RBV")
65
65
 
66
- self.next_sample_id = epics_signal_rw_rbv(float, prefix + "NEXT_ID")
67
- self.sample_id = epics_signal_r(float, prefix + "CURRENT_ID_RBV")
66
+ self.next_sample_id = epics_signal_rw_rbv(int, prefix + "NEXT_ID")
67
+ self.sample_id = epics_signal_r(int, prefix + "CURRENT_ID_RBV")
68
68
 
69
69
  self.load = epics_signal_x(prefix + "LOAD.PROC")
70
70
  self.program_running = epics_signal_r(bool, prefix + "PROGRAM_RUNNING")
dodal/devices/smargon.py CHANGED
@@ -40,8 +40,8 @@ class StubOffsets(Device):
40
40
  super().__init__(name)
41
41
 
42
42
  @AsyncStatus.wrap
43
- async def set(self, pos: StubPosition):
44
- if pos == StubPosition.CURRENT_AS_CENTER:
43
+ async def set(self, value: StubPosition):
44
+ if value == StubPosition.CURRENT_AS_CENTER:
45
45
  await self.center_at_current_position.set(1)
46
46
  smargon = cast(Smargon, self.parent)
47
47
  await wait_for_value(
dodal/devices/tetramm.py CHANGED
@@ -4,7 +4,7 @@ from enum import Enum
4
4
  from bluesky.protocols import Hints
5
5
  from ophyd_async.core import (
6
6
  DatasetDescriber,
7
- DetectorControl,
7
+ DetectorController,
8
8
  DetectorTrigger,
9
9
  Device,
10
10
  PathProvider,
@@ -80,7 +80,7 @@ class TetrammDriver(Device):
80
80
  super().__init__(name=name)
81
81
 
82
82
 
83
- class TetrammController(DetectorControl):
83
+ class TetrammController(DetectorController):
84
84
  """Controller for a TetrAMM current monitor
85
85
 
86
86
  Attributes:
@@ -1,3 +1,4 @@
1
+ import os
1
2
  from enum import Enum
2
3
 
3
4
  import numpy as np
@@ -54,7 +55,7 @@ class Undulator(StandardReadable, Movable):
54
55
  def __init__(
55
56
  self,
56
57
  prefix: str,
57
- id_gap_lookup_table_path: str,
58
+ id_gap_lookup_table_path: str = os.devnull,
58
59
  name: str = "",
59
60
  poles: int | None = None,
60
61
  length: float | None = None,
@@ -21,6 +21,6 @@ def lookup_table_adjuster(
21
21
  def adjust(group=None) -> Generator[Msg, None, None]:
22
22
  setpoint = lookup_table(input)
23
23
  LOGGER.info(f"lookup_table_adjuster setting {output_device.name} to {setpoint}")
24
- yield from bps.abs_set(output_device, setpoint, group=group)
24
+ yield from bps.abs_set(output_device, setpoint, group=group) # type: ignore # See: https://github.com/DiamondLightSource/dodal/issues/827
25
25
 
26
26
  return adjust
@@ -33,7 +33,10 @@ async def energy_distance_table(lookup_table_path: str) -> np.ndarray:
33
33
 
34
34
 
35
35
  def linear_interpolation_lut(filename: str) -> Callable[[float], float]:
36
- """Returns a callable that converts values by linear interpolation of lookup table values"""
36
+ """Returns a callable that converts values by linear interpolation of lookup table
37
+ values.
38
+
39
+ If the value falls outside the lookup table then the closest value will be used."""
37
40
  LOGGER.info(f"Using lookup table {filename}")
38
41
  s_and_t_vals = zip(*loadtxt(filename, comments=["#", "Units"]), strict=False)
39
42
 
@@ -54,10 +57,6 @@ def linear_interpolation_lut(filename: str) -> Callable[[float], float]:
54
57
  )
55
58
 
56
59
  def s_to_t2(s: float) -> float:
57
- if s < s_values[0] or s > s_values[len(s_values) - 1]:
58
- raise ValueError(
59
- f"Lookup table does not support extrapolation from file {filename}, s={s}"
60
- )
61
60
  return float(interp(s, s_values, t_values))
62
61
 
63
62
  return s_to_t2
dodal/devices/zebra.py CHANGED
@@ -13,6 +13,7 @@ from ophyd_async.core import (
13
13
  )
14
14
  from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
15
15
 
16
+ # These constants refer to I03's Zebra. See https://github.com/DiamondLightSource/dodal/issues/772
16
17
  # Sources
17
18
  DISCONNECT = 0
18
19
  IN1_TTL = 1
@@ -39,8 +40,10 @@ TTL_PANDA = 4
39
40
 
40
41
  # The AND gate that controls the automatic shutter
41
42
  AUTO_SHUTTER_GATE = 2
42
- # The input that triggers the automatic shutter
43
- AUTO_SHUTTER_INPUT = 1
43
+
44
+ # The first two inputs of the auto shutter gate.
45
+ AUTO_SHUTTER_INPUT_1 = 1
46
+ AUTO_SHUTTER_INPUT_2 = 2
44
47
 
45
48
 
46
49
  class ArmSource(str, Enum):
@@ -229,16 +229,19 @@ class ZocaloResults(StandardReadable, Triggerable):
229
229
  source_of_second_results = source_from_results(
230
230
  raw_results_two_sources[1]
231
231
  )
232
-
233
- # Compare results from both sources and warn if they aren't the same
234
- differences_str = get_dict_differences(
235
- raw_results_two_sources[0]["results"][0],
236
- source_of_first_results,
237
- raw_results_two_sources[1]["results"][0],
238
- source_of_second_results,
239
- )
240
- if differences_str:
241
- LOGGER.warning(differences_str)
232
+ first_results = raw_results_two_sources[0]["results"]
233
+ second_results = raw_results_two_sources[1]["results"]
234
+
235
+ if first_results and second_results:
236
+ # Compare results from both sources and warn if they aren't the same
237
+ differences_str = get_dict_differences(
238
+ first_results[0],
239
+ source_of_first_results,
240
+ second_results[0],
241
+ source_of_second_results,
242
+ )
243
+ if differences_str:
244
+ LOGGER.warning(differences_str)
242
245
 
243
246
  # Always use CPU results
244
247
  raw_results = (
@@ -1,9 +1,9 @@
1
1
  from bluesky import plan_stubs as bps
2
2
  from bluesky import preprocessors as bpp
3
- from bluesky.utils import make_decorator
3
+ from bluesky.utils import MsgGenerator, make_decorator
4
4
 
5
5
  from dodal.common.beamlines import beamline_utils
6
- from dodal.common.types import MsgGenerator, UpdatingPathProvider
6
+ from dodal.common.types import UpdatingPathProvider
7
7
 
8
8
  DATA_SESSION = "data_session"
9
9
  DATA_GROUPS = "data_groups"
@@ -1,21 +1,21 @@
1
1
  import uuid
2
2
  from collections.abc import Generator
3
- from typing import Any, TypeVar
3
+ from typing import Any, TypeVar, cast
4
4
 
5
5
  from bluesky import plan_stubs as bps
6
6
  from bluesky.preprocessors import finalize_wrapper, pchain
7
- from bluesky.utils import Msg, make_decorator
7
+ from bluesky.utils import Msg, MsgGenerator, make_decorator
8
8
  from ophyd_async.core import Device
9
9
  from ophyd_async.epics.motor import Motor
10
10
 
11
- from dodal.common import MsgGenerator
11
+ from dodal.utils import MovableReadable
12
12
 
13
- AnyDevice = TypeVar("AnyDevice", bound=Device)
13
+ MovableReadableDevice = TypeVar("MovableReadableDevice", bound=MovableReadable)
14
14
 
15
15
 
16
16
  class MoveTooLarge(Exception):
17
17
  def __init__(
18
- self, axis: Device, maximum_move: float, position: float, *args: object
18
+ self, axis: MovableReadable, maximum_move: float, position: float, *args: object
19
19
  ) -> None:
20
20
  self.axis = axis
21
21
  self.maximum_move = maximum_move
@@ -24,10 +24,10 @@ class MoveTooLarge(Exception):
24
24
 
25
25
 
26
26
  def _check_and_cache_values(
27
- devices_and_positions: dict[AnyDevice, float],
27
+ devices_and_positions: dict[MovableReadableDevice, float],
28
28
  smallest_move: float,
29
29
  maximum_move: float,
30
- ) -> Generator[Msg, Any, dict[AnyDevice, float]]:
30
+ ) -> Generator[Msg, Any, dict[MovableReadableDevice, float]]:
31
31
  """Caches the positions of all Motors on specified device if they are within
32
32
  smallest_move of home_position. Throws MoveTooLarge if they are outside maximum_move
33
33
  of the home_position
@@ -51,7 +51,9 @@ def home_and_reset_wrapper(
51
51
  wait_for_all: bool = True,
52
52
  ) -> MsgGenerator:
53
53
  home_positions = {
54
- axis: 0.0 for _, axis in device.children() if isinstance(axis, Motor)
54
+ cast(MovableReadable, axis): 0.0
55
+ for _, axis in device.children()
56
+ if isinstance(axis, Motor)
55
57
  }
56
58
  return move_and_reset_wrapper(
57
59
  plan, home_positions, smallest_move, maximum_move, group, wait_for_all
@@ -60,7 +62,7 @@ def home_and_reset_wrapper(
60
62
 
61
63
  def move_and_reset_wrapper(
62
64
  plan: MsgGenerator,
63
- device_and_positions: dict[AnyDevice, float],
65
+ device_and_positions: dict[MovableReadable, float],
64
66
  smallest_move: float,
65
67
  maximum_move: float,
66
68
  group: str | None = None,
dodal/utils.py CHANGED
@@ -13,8 +13,10 @@ from os import environ
13
13
  from types import ModuleType
14
14
  from typing import (
15
15
  Any,
16
+ Protocol,
16
17
  TypeGuard,
17
18
  TypeVar,
19
+ runtime_checkable,
18
20
  )
19
21
 
20
22
  from bluesky.protocols import (
@@ -62,6 +64,11 @@ BLUESKY_PROTOCOLS = [
62
64
  Triggerable,
63
65
  ]
64
66
 
67
+
68
+ @runtime_checkable
69
+ class MovableReadable(Movable, Readable, Protocol): ...
70
+
71
+
65
72
  AnyDevice: TypeAlias = OphydV1Device | OphydV2Device
66
73
  V1DeviceFactory: TypeAlias = Callable[..., OphydV1Device]
67
74
  V2DeviceFactory: TypeAlias = Callable[..., OphydV2Device]
dodal/beamlines/i04_1.py DELETED
@@ -1,140 +0,0 @@
1
- from dodal.common.beamlines.beamline_utils import device_instantiation
2
- from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
3
- from dodal.devices.backlight import Backlight
4
- from dodal.devices.detector import DetectorParams
5
- from dodal.devices.eiger import EigerDetector
6
- from dodal.devices.oav.oav_detector import OAV, OAVConfigParams
7
- from dodal.devices.s4_slit_gaps import S4SlitGaps
8
- from dodal.devices.synchrotron import Synchrotron
9
- from dodal.devices.undulator import Undulator
10
- from dodal.devices.zebra import Zebra
11
- from dodal.log import set_beamline as set_log_beamline
12
- from dodal.utils import BeamlinePrefix, get_beamline_name, skip_device
13
-
14
- ZOOM_PARAMS_FILE = "/dls_sw/i04-1/software/gda/config/xml/jCameraManZoomLevels.xml"
15
- DISPLAY_CONFIG = "/dls_sw/i04-1/software/gda_versions/var/display.configuration"
16
-
17
- _simulator_beamline_fallback = "s04_1"
18
- BL = get_beamline_name(_simulator_beamline_fallback)
19
- set_log_beamline(BL)
20
- set_utils_beamline(BL)
21
-
22
-
23
- def _check_for_simulation():
24
- return BL == _simulator_beamline_fallback
25
-
26
-
27
- def backlight(
28
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
29
- ) -> Backlight:
30
- """Get the i04_1 backlight device, instantiate it if it hasn't already been.
31
- If this is called when already instantiated in i04_1, it will return the existing object.
32
- """
33
- return device_instantiation(
34
- device_factory=Backlight,
35
- name="backlight",
36
- prefix="",
37
- wait=wait_for_connection,
38
- fake=fake_with_ophyd_sim,
39
- )
40
-
41
-
42
- @skip_device(_check_for_simulation)
43
- def eiger(
44
- wait_for_connection: bool = True,
45
- fake_with_ophyd_sim: bool = False,
46
- params: DetectorParams | None = None,
47
- ) -> EigerDetector:
48
- """Get the i04_1 Eiger device, instantiate it if it hasn't already been.
49
- If this is called when already instantiated in i04_1, it will return the existing object.
50
- If called with params, will update those params to the Eiger object.
51
- """
52
-
53
- def set_params(eiger: EigerDetector):
54
- if params is not None:
55
- eiger.set_detector_parameters(params)
56
-
57
- return device_instantiation(
58
- device_factory=EigerDetector,
59
- name="eiger",
60
- prefix="-EA-EIGER-01:",
61
- wait=wait_for_connection,
62
- fake=fake_with_ophyd_sim,
63
- post_create=set_params,
64
- )
65
-
66
-
67
- @skip_device(_check_for_simulation)
68
- def oav(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> OAV:
69
- """Get the i04_1 OAV device, instantiate it if it hasn't already been.
70
- If this is called when already instantiated in i04_1, it will return the existing object.
71
- """
72
- return device_instantiation(
73
- OAV,
74
- "oav",
75
- "",
76
- wait_for_connection,
77
- fake_with_ophyd_sim,
78
- params=OAVConfigParams(ZOOM_PARAMS_FILE, DISPLAY_CONFIG),
79
- )
80
-
81
-
82
- def s4_slit_gaps(
83
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
84
- ) -> S4SlitGaps:
85
- """Get the i04_1 s4_slit_gaps device, instantiate it if it hasn't already been.
86
- If this is called when already instantiated in i04_1, it will return the existing object.
87
- """
88
- return device_instantiation(
89
- S4SlitGaps,
90
- "s4_slit_gaps",
91
- "-AL-SLITS-04:",
92
- wait_for_connection,
93
- fake_with_ophyd_sim,
94
- )
95
-
96
-
97
- @skip_device(_check_for_simulation)
98
- def synchrotron(
99
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
100
- ) -> Synchrotron:
101
- """Get the i04_1 synchrotron device, instantiate it if it hasn't already been.
102
- If this is called when already instantiated in i04_1, it will return the existing object.
103
- """
104
- return device_instantiation(
105
- Synchrotron,
106
- "synchrotron",
107
- "",
108
- wait_for_connection,
109
- fake_with_ophyd_sim,
110
- bl_prefix=False,
111
- )
112
-
113
-
114
- def undulator(
115
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
116
- ) -> Undulator:
117
- """Get the i04_1 undulator device, instantiate it if it hasn't already been.
118
- If this is called when already instantiated in i04_1, it will return the existing object.
119
- """
120
- return device_instantiation(
121
- Undulator,
122
- "undulator",
123
- f"{BeamlinePrefix(BL).insertion_prefix}-MO-SERVC-01:",
124
- wait_for_connection,
125
- fake_with_ophyd_sim,
126
- bl_prefix=False,
127
- )
128
-
129
-
130
- def zebra(wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False) -> Zebra:
131
- """Get the i04_1 zebra device, instantiate it if it hasn't already been.
132
- If this is called when already instantiated in i04_1, it will return the existing object.
133
- """
134
- return device_instantiation(
135
- Zebra,
136
- "zebra",
137
- "-EA-ZEBRA-01:",
138
- wait_for_connection,
139
- fake_with_ophyd_sim,
140
- )