dls-dodal 1.43.0__py3-none-any.whl → 1.45.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 (70) hide show
  1. {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/METADATA +4 -3
  2. {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/RECORD +66 -49
  3. {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/__init__.py +2 -0
  6. dodal/beamlines/b01_1.py +8 -0
  7. dodal/beamlines/b07.py +27 -0
  8. dodal/beamlines/b07_1.py +25 -0
  9. dodal/beamlines/i03.py +11 -0
  10. dodal/beamlines/i09.py +25 -0
  11. dodal/beamlines/i09_1.py +25 -0
  12. dodal/beamlines/i10.py +19 -35
  13. dodal/beamlines/i13_1.py +22 -48
  14. dodal/beamlines/i19_1.py +17 -5
  15. dodal/beamlines/i19_2.py +13 -3
  16. dodal/beamlines/i19_optics.py +4 -2
  17. dodal/beamlines/i20_1.py +2 -1
  18. dodal/beamlines/i23.py +10 -0
  19. dodal/beamlines/p60.py +21 -0
  20. dodal/common/data_util.py +20 -0
  21. dodal/common/signal_utils.py +43 -4
  22. dodal/common/visit.py +1 -41
  23. dodal/devices/aperturescatterguard.py +3 -3
  24. dodal/devices/baton.py +17 -0
  25. dodal/devices/current_amplifiers/current_amplifier.py +1 -6
  26. dodal/devices/current_amplifiers/current_amplifier_detector.py +2 -2
  27. dodal/devices/current_amplifiers/femto.py +0 -5
  28. dodal/devices/current_amplifiers/sr570.py +0 -5
  29. dodal/devices/detector/det_dist_to_beam_converter.py +16 -23
  30. dodal/devices/detector/detector.py +2 -1
  31. dodal/devices/electron_analyser/__init__.py +0 -0
  32. dodal/devices/electron_analyser/abstract_analyser_io.py +47 -0
  33. dodal/devices/electron_analyser/abstract_region.py +112 -0
  34. dodal/devices/electron_analyser/specs_analyser_io.py +19 -0
  35. dodal/devices/electron_analyser/specs_region.py +26 -0
  36. dodal/devices/electron_analyser/vgscienta_analyser_io.py +26 -0
  37. dodal/devices/electron_analyser/vgscienta_region.py +90 -0
  38. dodal/devices/fast_grid_scan.py +2 -2
  39. dodal/devices/i03/beamstop.py +2 -2
  40. dodal/devices/i10/diagnostics.py +239 -0
  41. dodal/devices/i10/slits.py +93 -6
  42. dodal/devices/i13_1/merlin.py +1 -2
  43. dodal/devices/i13_1/merlin_controller.py +12 -8
  44. dodal/devices/i19/beamstop.py +30 -0
  45. dodal/devices/i19/blueapi_device.py +102 -0
  46. dodal/devices/i19/hutch_access.py +2 -0
  47. dodal/devices/i19/shutter.py +24 -40
  48. dodal/devices/i22/nxsas.py +1 -3
  49. dodal/devices/i24/focus_mirrors.py +3 -3
  50. dodal/devices/i24/pilatus_metadata.py +2 -2
  51. dodal/devices/motors.py +21 -0
  52. dodal/devices/oav/oav_detector.py +7 -9
  53. dodal/devices/oav/snapshots/snapshot.py +21 -0
  54. dodal/devices/oav/snapshots/snapshot_image_processing.py +74 -0
  55. dodal/devices/turbo_slit.py +8 -2
  56. dodal/devices/undulator.py +9 -7
  57. dodal/devices/util/adjuster_plans.py +1 -2
  58. dodal/devices/util/lookup_tables.py +38 -0
  59. dodal/devices/util/test_utils.py +1 -0
  60. dodal/plan_stubs/electron_analyser/__init__.py +0 -0
  61. dodal/plan_stubs/electron_analyser/configure_controller.py +80 -0
  62. dodal/plan_stubs/motor_utils.py +10 -12
  63. dodal/utils.py +0 -7
  64. dodal/devices/i13_1/merlin_io.py +0 -17
  65. dodal/devices/oav/microns_for_zoom_levels.json +0 -55
  66. dodal/devices/oav/snapshots/snapshot_with_beam_centre.py +0 -64
  67. dodal/devices/util/motor_utils.py +0 -6
  68. {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/entry_points.txt +0 -0
  69. {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info/licenses}/LICENSE +0 -0
  70. {dls_dodal-1.43.0.dist-info → dls_dodal-1.45.0.dist-info}/top_level.txt +0 -0
dodal/beamlines/i13_1.py CHANGED
@@ -3,19 +3,20 @@ from pathlib import Path
3
3
  from ophyd_async.epics.adaravis import AravisDetector
4
4
 
5
5
  from dodal.common.beamlines.beamline_utils import (
6
- device_instantiation,
6
+ device_factory,
7
7
  get_path_provider,
8
8
  set_path_provider,
9
9
  )
10
10
  from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
11
- from dodal.common.beamlines.device_helpers import CAM_SUFFIX, HDF5_SUFFIX
12
11
  from dodal.common.visit import LocalDirectoryServiceClient, StaticVisitPathProvider
13
12
  from dodal.devices.i13_1.merlin import Merlin
14
13
  from dodal.devices.motors import XYZPositioner
15
14
  from dodal.log import set_beamline as set_log_beamline
16
- from dodal.utils import get_beamline_name
15
+ from dodal.utils import BeamlinePrefix, get_beamline_name
17
16
 
18
17
  BL = get_beamline_name("i13-1")
18
+ PREFIX_BL13I = BeamlinePrefix(BL) # Can't use this yet as returns BL13I
19
+ PREFIX = "BL13J"
19
20
  set_log_beamline(BL)
20
21
  set_utils_beamline(BL)
21
22
  set_path_provider(
@@ -27,59 +28,32 @@ set_path_provider(
27
28
  )
28
29
 
29
30
 
30
- def sample_xyz_stage(
31
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
32
- ) -> XYZPositioner:
33
- return device_instantiation(
34
- XYZPositioner,
35
- prefix="BL13J-MO-PI-02:",
36
- name="sample_xyz_stage",
37
- wait=wait_for_connection,
38
- fake=fake_with_ophyd_sim,
39
- bl_prefix=False,
40
- )
31
+ @device_factory()
32
+ def sample_xyz_stage() -> XYZPositioner:
33
+ return XYZPositioner(prefix=f"{PREFIX}-MO-PI-02:")
41
34
 
42
35
 
43
- def sample_xyz_lab_fa_stage(
44
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
45
- ) -> XYZPositioner:
46
- return device_instantiation(
47
- XYZPositioner,
48
- prefix="BL13J-MO-PI-02:FIXANG:",
49
- name="sample_xyz_lab_fa_stage",
50
- wait=wait_for_connection,
51
- fake=fake_with_ophyd_sim,
52
- bl_prefix=False,
53
- )
36
+ @device_factory()
37
+ def sample_xyz_lab_fa_stage() -> XYZPositioner:
38
+ return XYZPositioner(prefix=f"{PREFIX}-MO-PI-02:FIXANG:")
54
39
 
55
40
 
56
- def side_camera(
57
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
58
- ) -> AravisDetector:
59
- return device_instantiation(
60
- AravisDetector,
61
- prefix="BL13J-OP-FLOAT-03:",
62
- name="side_camera",
63
- bl_prefix=False,
64
- drv_suffix=CAM_SUFFIX,
65
- fileio_suffix=HDF5_SUFFIX,
41
+ @device_factory()
42
+ def side_camera() -> AravisDetector:
43
+ return AravisDetector(
44
+ prefix=f"{PREFIX}-OP-FLOAT-03:",
45
+ drv_suffix="CAM:",
46
+ fileio_suffix="HDF5:",
66
47
  path_provider=get_path_provider(),
67
- wait=wait_for_connection,
68
- fake=fake_with_ophyd_sim,
69
48
  )
70
49
 
71
50
 
72
- def merlin(
73
- wait_for_connection: bool = True, fake_with_ophyd_sim: bool = False
74
- ) -> Merlin:
75
- return device_instantiation(
76
- Merlin,
77
- prefix="BL13J-EA-DET-04:",
51
+ @device_factory()
52
+ def merlin() -> Merlin:
53
+ return Merlin(
54
+ prefix=f"{PREFIX}-EA-DET-04:",
78
55
  name="merlin",
79
- bl_prefix=False,
80
- drv_suffix=CAM_SUFFIX,
81
- fileio_suffix=HDF5_SUFFIX,
56
+ drv_suffix="CAM:",
57
+ fileio_suffix="HDF5:",
82
58
  path_provider=get_path_provider(),
83
- wait=wait_for_connection,
84
- fake=fake_with_ophyd_sim,
85
59
  )
dodal/beamlines/i19_1.py CHANGED
@@ -4,7 +4,9 @@ from dodal.common.beamlines.beamline_utils import (
4
4
  from dodal.common.beamlines.beamline_utils import (
5
5
  set_beamline as set_utils_beamline,
6
6
  )
7
- from dodal.devices.i19.shutter import HutchConditionalShutter, HutchState
7
+ from dodal.devices.i19.beamstop import BeamStop
8
+ from dodal.devices.i19.blueapi_device import HutchState
9
+ from dodal.devices.i19.shutter import AccessControlledShutter
8
10
  from dodal.devices.oav.oav_detector import OAV
9
11
  from dodal.devices.oav.oav_parameters import OAVConfig
10
12
  from dodal.devices.synchrotron import Synchrotron
@@ -35,10 +37,20 @@ ZOOM_PARAMS_FILE = (
35
37
  DISPLAY_CONFIG = "/dls_sw/i19-1/software/daq_configuration/domain/display.configuration"
36
38
 
37
39
 
40
+ # Needs to wait until enum is fixed on the beamline
41
+ # See https://github.com/DiamondLightSource/dodal/issues/1150
42
+ @device_factory(skip=True)
43
+ def beamstop() -> BeamStop:
44
+ """Get the i19-1 beamstop device, instantiate it if it hasn't already been.
45
+ If this is called when already instantiated in i19-1, it will return the existing object.
46
+ """
47
+ return BeamStop(prefix=f"{PREFIX.beamline_prefix}-RS-ABSB-01:")
48
+
49
+
38
50
  @device_factory()
39
51
  def oav() -> OAV:
40
52
  return OAV(
41
- prefix=f"{PREFIX.beamline_prefix}-DI-OAV-01:",
53
+ prefix=f"{PREFIX.beamline_prefix}-EA-OAV-01:",
42
54
  config=OAVConfig(ZOOM_PARAMS_FILE, DISPLAY_CONFIG),
43
55
  )
44
56
 
@@ -56,11 +68,11 @@ def zebra() -> Zebra:
56
68
 
57
69
 
58
70
  @device_factory()
59
- def shutter() -> HutchConditionalShutter:
60
- """Get the i19-2 hutch shutter device, instantiate it if it hasn't already been.
71
+ def shutter() -> AccessControlledShutter:
72
+ """Get the i19-1 hutch shutter device, instantiate it if it hasn't already been.
61
73
  If this is called when already instantiated, it will return the existing object.
62
74
  """
63
- return HutchConditionalShutter(
75
+ return AccessControlledShutter(
64
76
  prefix=f"{PREFIX.beamline_prefix}-PS-SHTR-01:",
65
77
  hutch=HutchState.EH1,
66
78
  )
dodal/beamlines/i19_2.py CHANGED
@@ -4,7 +4,9 @@ from dodal.common.beamlines.beamline_utils import (
4
4
  from dodal.common.beamlines.beamline_utils import (
5
5
  set_beamline as set_utils_beamline,
6
6
  )
7
- from dodal.devices.i19.shutter import HutchConditionalShutter, HutchState
7
+ from dodal.devices.i19.beamstop import BeamStop
8
+ from dodal.devices.i19.blueapi_device import HutchState
9
+ from dodal.devices.i19.shutter import AccessControlledShutter
8
10
  from dodal.devices.synchrotron import Synchrotron
9
11
  from dodal.devices.zebra.zebra import Zebra
10
12
  from dodal.devices.zebra.zebra_constants_mapping import (
@@ -30,6 +32,14 @@ I19_2_ZEBRA_MAPPING = ZebraMapping(
30
32
  )
31
33
 
32
34
 
35
+ @device_factory()
36
+ def beamstop() -> BeamStop:
37
+ """Get the i19-2 beamstop device, instantiate it if it hasn't already been.
38
+ If this is called when already instantiated in i19-2, it will return the existing object.
39
+ """
40
+ return BeamStop(prefix=f"{PREFIX.beamline_prefix}-OP-ABSB-02:")
41
+
42
+
33
43
  @device_factory()
34
44
  def zebra() -> Zebra:
35
45
  """Get the i19-2 zebra device, instantiate it if it hasn't already been.
@@ -43,11 +53,11 @@ def zebra() -> Zebra:
43
53
 
44
54
 
45
55
  @device_factory()
46
- def shutter() -> HutchConditionalShutter:
56
+ def shutter() -> AccessControlledShutter:
47
57
  """Get the i19-2 hutch shutter device, instantiate it if it hasn't already been.
48
58
  If this is called when already instantiated, it will return the existing object.
49
59
  """
50
- return HutchConditionalShutter(
60
+ return AccessControlledShutter(
51
61
  prefix=f"{PREFIX.beamline_prefix}-PS-SHTR-01:",
52
62
  hutch=HutchState.EH2,
53
63
  )
@@ -5,7 +5,7 @@ from dodal.common.beamlines.beamline_utils import (
5
5
  set_beamline as set_utils_beamline,
6
6
  )
7
7
  from dodal.devices.hutch_shutter import HutchShutter
8
- from dodal.devices.i19.hutch_access import HutchAccessControl
8
+ from dodal.devices.i19.hutch_access import ACCESS_DEVICE_NAME, HutchAccessControl
9
9
  from dodal.log import set_beamline as set_log_beamline
10
10
  from dodal.utils import BeamlinePrefix
11
11
 
@@ -31,4 +31,6 @@ def access_control() -> HutchAccessControl:
31
31
  """Get a device that checks the active hutch for i19, instantiate it if it hasn't already been.
32
32
  If this is called when already instantiated, it will return the existing object.
33
33
  """
34
- return HutchAccessControl(f"{PREFIX.beamline_prefix}-OP-STAT-01:", "access_control")
34
+ return HutchAccessControl(
35
+ f"{PREFIX.beamline_prefix}-OP-STAT-01:", ACCESS_DEVICE_NAME
36
+ )
dodal/beamlines/i20_1.py CHANGED
@@ -32,7 +32,8 @@ set_path_provider(
32
32
  )
33
33
 
34
34
 
35
- @device_factory()
35
+ # NOTE this is mock as we cannot move items on the beamline until we get sign-off to do so
36
+ @device_factory(mock=True)
36
37
  def turbo_slit() -> TurboSlit:
37
38
  """
38
39
  turboslit for selecting energy from the polychromator
dodal/beamlines/i23.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from dodal.common.beamlines.beamline_utils import device_factory
2
2
  from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
3
+ from dodal.devices.motors import SixAxisGonio
3
4
  from dodal.devices.oav.pin_image_recognition import PinTipDetection
4
5
  from dodal.log import set_beamline as set_log_beamline
5
6
  from dodal.utils import BeamlinePrefix, get_beamline_name, get_hostname
@@ -28,3 +29,12 @@ def oav_pin_tip_detection() -> PinTipDetection:
28
29
  f"{PREFIX.beamline_prefix}-DI-OAV-01:",
29
30
  "pin_tip_detection",
30
31
  )
32
+
33
+
34
+ @device_factory()
35
+ def gonio() -> SixAxisGonio:
36
+ """Get the i23 goniometer"""
37
+
38
+ return SixAxisGonio(
39
+ f"{PREFIX.beamline_prefix}-MO-GONIO-01:",
40
+ )
dodal/beamlines/p60.py ADDED
@@ -0,0 +1,21 @@
1
+ from dodal.common.beamlines.beamline_utils import (
2
+ device_factory,
3
+ )
4
+ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline
5
+ from dodal.devices.electron_analyser.vgscienta_analyser_io import (
6
+ VGScientaAnalyserDriverIO,
7
+ )
8
+ from dodal.log import set_beamline as set_log_beamline
9
+ from dodal.utils import BeamlinePrefix, get_beamline_name
10
+
11
+ BL = get_beamline_name("p60")
12
+ PREFIX = BeamlinePrefix(BL)
13
+ set_log_beamline(BL)
14
+ set_utils_beamline(BL)
15
+
16
+
17
+ @device_factory()
18
+ def analyser_driver() -> VGScientaAnalyserDriverIO:
19
+ return VGScientaAnalyserDriverIO(
20
+ name="analyser_driver", prefix=f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:"
21
+ )
@@ -0,0 +1,20 @@
1
+ from typing import TypeVar
2
+
3
+ from pydantic import BaseModel
4
+
5
+ TBaseModel = TypeVar("TBaseModel", bound=BaseModel)
6
+
7
+
8
+ def load_json_file_to_class(
9
+ t: type[TBaseModel],
10
+ file: str,
11
+ ) -> TBaseModel:
12
+ with open(file) as f:
13
+ json_obj = f.read()
14
+ cls = t.model_validate_json(json_obj)
15
+ return cls
16
+
17
+
18
+ def save_class_to_json_file(model: BaseModel, file: str) -> None:
19
+ with open(file, "w") as f:
20
+ f.write(model.model_dump_json())
@@ -2,22 +2,26 @@ from collections.abc import Callable, Coroutine
2
2
  from typing import Any
3
3
 
4
4
  from bluesky.protocols import Reading
5
- from ophyd_async.core import SignalDatatypeT, SignalR, SoftSignalBackend
5
+ from ophyd_async.core import SignalDatatypeT, SignalR, SignalRW, SoftSignalBackend
6
+
7
+ SetHardwareType = Callable[[SignalDatatypeT], Coroutine[Any, Any, None]]
6
8
 
7
9
 
8
10
  class HardwareBackedSoftSignalBackend(SoftSignalBackend[SignalDatatypeT]):
9
11
  def __init__(
10
12
  self,
11
13
  get_from_hardware_func: Callable[[], Coroutine[Any, Any, SignalDatatypeT]],
14
+ set_to_hardware_func: SetHardwareType | None = None,
12
15
  *args,
13
16
  **kwargs,
14
17
  ) -> None:
15
18
  self.get_from_hardware_func = get_from_hardware_func
19
+ self.set_to_hardware_func = set_to_hardware_func
16
20
  super().__init__(*args, **kwargs)
17
21
 
18
22
  async def _update_value(self):
19
23
  new_value = await self.get_from_hardware_func()
20
- await self.put(new_value, True)
24
+ self.set_value(new_value)
21
25
 
22
26
  async def get_reading(self) -> Reading:
23
27
  await self._update_value()
@@ -27,8 +31,39 @@ class HardwareBackedSoftSignalBackend(SoftSignalBackend[SignalDatatypeT]):
27
31
  await self._update_value()
28
32
  return await super().get_value()
29
33
 
34
+ async def put(self, value: SignalDatatypeT | None, wait: bool) -> None:
35
+ if self.set_to_hardware_func:
36
+ write_value = self.initial_value if value is None else value
37
+ await self.set_to_hardware_func(write_value)
38
+
39
+
40
+ def create_rw_hardware_backed_soft_signal(
41
+ datatype: type[SignalDatatypeT],
42
+ get_from_hardware_func: Callable[[], Coroutine[Any, Any, SignalDatatypeT]],
43
+ set_to_hardware_func: SetHardwareType,
44
+ units: str | None = None,
45
+ precision: int | None = None,
46
+ ):
47
+ """Creates a soft signal that, when read will call the function passed into
48
+ `get_from_hardware_func` and return this. When set it will call `set_to_hardware_func`
49
+ and send something to the hardware.
50
+
51
+ This will allow you to make soft signals derived from arbitrary hardware signals.
52
+ However, calling subscribe on this signal does not give you a sensible value. See https://github.com/bluesky/ophyd-async/issues/525
53
+ for a more full solution.
54
+ """
55
+ return SignalRW(
56
+ backend=HardwareBackedSoftSignalBackend(
57
+ get_from_hardware_func,
58
+ set_to_hardware_func,
59
+ datatype,
60
+ units=units,
61
+ precision=precision,
62
+ )
63
+ )
64
+
30
65
 
31
- def create_hardware_backed_soft_signal(
66
+ def create_r_hardware_backed_soft_signal(
32
67
  datatype: type[SignalDatatypeT],
33
68
  get_from_hardware_func: Callable[[], Coroutine[Any, Any, SignalDatatypeT]],
34
69
  units: str | None = None,
@@ -44,6 +79,10 @@ def create_hardware_backed_soft_signal(
44
79
  """
45
80
  return SignalR(
46
81
  backend=HardwareBackedSoftSignalBackend(
47
- get_from_hardware_func, datatype, units=units, precision=precision
82
+ get_from_hardware_func,
83
+ None,
84
+ datatype,
85
+ units=units,
86
+ precision=precision,
48
87
  )
49
88
  )
dodal/common/visit.py CHANGED
@@ -3,8 +3,7 @@ from pathlib import Path
3
3
  from typing import Literal
4
4
 
5
5
  from aiohttp import ClientSession
6
- from event_model import RunStart
7
- from ophyd_async.core import FilenameProvider, PathInfo, PathProvider
6
+ from ophyd_async.core import FilenameProvider, PathInfo
8
7
  from pydantic import BaseModel
9
8
 
10
9
  from dodal.common.types import UpdatingPathProvider
@@ -151,42 +150,3 @@ class StaticVisitPathProvider(UpdatingPathProvider):
151
150
  return PathInfo(
152
151
  directory_path=self._root, filename=self._filename_provider(device_name)
153
152
  )
154
-
155
-
156
- DEFAULT_TEMPLATE = "{device_name}-{instrument}-{scan_id}"
157
-
158
-
159
- class StartDocumentPathProvider(PathProvider):
160
- """A PathProvider that sources from metadata in a RunStart document.
161
-
162
- This uses metadata from a RunStart document to determine file names and data session
163
- directories. The file naming defaults to "{device_name}-{instrument}-{scan_id}", so
164
- the file name is incremented by scan number. A template can be included in the
165
- StartDocument to allow for custom naming conventions.
166
-
167
- """
168
-
169
- def __init__(self) -> None:
170
- self._doc = {}
171
-
172
- def update_run(self, name: str, start_doc: RunStart) -> None:
173
- """Cache a start document.
174
-
175
- This can be plugged into the run engine's subscribe method.
176
- """
177
- if name == "start":
178
- self._doc = start_doc
179
-
180
- def __call__(self, device_name: str | None = None) -> PathInfo:
181
- """Returns the directory path and filename for a given data_session.
182
-
183
- The default template for file naming is: "{device_name}-{instrument}-{scan_id}"
184
- however, this can be changed by providing a template in the start document. For
185
- example: "template": "custom-{device_name}--{scan_id}".
186
-
187
- If you do not provide a data_session_directory it will default to "/tmp".
188
- """
189
- template = self._doc.get("template", DEFAULT_TEMPLATE)
190
- sub_path = template.format_map(self._doc | {"device_name": device_name})
191
- data_session_directory = Path(self._doc.get("data_session_directory", "/tmp"))
192
- return PathInfo(directory_path=data_session_directory, filename=sub_path)
@@ -12,7 +12,7 @@ from ophyd_async.core import (
12
12
  from pydantic import BaseModel, Field
13
13
 
14
14
  from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
15
- from dodal.common.signal_utils import create_hardware_backed_soft_signal
15
+ from dodal.common.signal_utils import create_r_hardware_backed_soft_signal
16
16
  from dodal.devices.aperture import Aperture
17
17
  from dodal.devices.scatterguard import Scatterguard
18
18
 
@@ -164,7 +164,7 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
164
164
  ) -> None:
165
165
  self.aperture = Aperture(prefix + "-MO-MAPT-01:")
166
166
  self.scatterguard = Scatterguard(prefix + "-MO-SCAT-01:")
167
- self.radius = create_hardware_backed_soft_signal(
167
+ self.radius = create_r_hardware_backed_soft_signal(
168
168
  float, self._get_current_radius, units="µm"
169
169
  )
170
170
  self._loaded_positions = loaded_positions
@@ -181,7 +181,7 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
181
181
  )
182
182
 
183
183
  with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
184
- self.selected_aperture = create_hardware_backed_soft_signal(
184
+ self.selected_aperture = create_r_hardware_backed_soft_signal(
185
185
  ApertureValue, self._get_current_aperture_position
186
186
  )
187
187
 
dodal/devices/baton.py ADDED
@@ -0,0 +1,17 @@
1
+ from typing import Annotated as A
2
+
3
+ from ophyd_async.core import (
4
+ SignalRW,
5
+ StandardReadable,
6
+ )
7
+ from ophyd_async.core import StandardReadableFormat as Format
8
+ from ophyd_async.epics.core import EpicsDevice, PvSuffix
9
+
10
+
11
+ class Baton(StandardReadable, EpicsDevice):
12
+ requested_user: A[
13
+ SignalRW[str], PvSuffix("REQUESTED_USER"), Format.HINTED_UNCACHED_SIGNAL
14
+ ]
15
+ current_user: A[
16
+ SignalRW[str], PvSuffix("CURRENT_USER"), Format.HINTED_UNCACHED_SIGNAL
17
+ ]
@@ -23,7 +23,6 @@ class CurrentAmp(ABC, StandardReadable, Movable):
23
23
  super().__init__(name)
24
24
 
25
25
  @abstractmethod
26
- @AsyncStatus.wrap
27
26
  async def increase_gain(self, value: int = 1) -> None:
28
27
  """Increase gain, increment by 1 by default.
29
28
 
@@ -31,7 +30,6 @@ class CurrentAmp(ABC, StandardReadable, Movable):
31
30
  bool: True if success.
32
31
  """
33
32
 
34
- @AsyncStatus.wrap
35
33
  @abstractmethod
36
34
  async def decrease_gain(self, value: int = 1) -> None:
37
35
  """Decrease gain, decrement by 1 by default.
@@ -40,21 +38,18 @@ class CurrentAmp(ABC, StandardReadable, Movable):
40
38
  bool: True if success.
41
39
  """
42
40
 
43
- @AsyncStatus.wrap
44
41
  @abstractmethod
45
- async def get_gain(self) -> type[Enum]:
42
+ async def get_gain(self) -> Enum:
46
43
  """Get the current gain setting
47
44
 
48
45
  Returns:
49
46
  Enum: The member name of the current gain setting in gain_conversion_table.
50
47
  """
51
48
 
52
- @AsyncStatus.wrap
53
49
  @abstractmethod
54
50
  async def get_upperlimit(self) -> float:
55
51
  """Get the upper limit of the current amplifier"""
56
52
 
57
- @AsyncStatus.wrap
58
53
  @abstractmethod
59
54
  async def get_lowerlimit(self) -> float:
60
55
  """Get the lower limit of the current amplifier"""
@@ -92,8 +92,8 @@ class CurrentAmpDet(StandardReadable, Preparable):
92
92
  self.current_amp().get_gain(),
93
93
  self.counter().get_voltage_per_sec(),
94
94
  )
95
- correction_factor = current_gain.value
96
- corrected_current = voltage_per_sec / correction_factor
95
+ assert isinstance(current_gain.value, float)
96
+ corrected_current = voltage_per_sec / current_gain.value
97
97
  return corrected_current
98
98
 
99
99
  @AsyncStatus.wrap
@@ -114,7 +114,6 @@ class FemtoDDPCA(CurrentAmp):
114
114
  # wait for current amplifier's bandpass filter to settle.
115
115
  await asyncio.sleep(self.raise_timetable[SEN_setting].value)
116
116
 
117
- @AsyncStatus.wrap
118
117
  async def increase_gain(self, value: int = 1) -> None:
119
118
  current_gain = int((await self.get_gain()).name.split("_")[-1])
120
119
  current_gain += value
@@ -122,7 +121,6 @@ class FemtoDDPCA(CurrentAmp):
122
121
  raise ValueError("Gain at max value")
123
122
  await self.set(self.gain_conversion_table[f"SEN_{current_gain}"])
124
123
 
125
- @AsyncStatus.wrap
126
124
  async def decrease_gain(self, value: int = 1) -> None:
127
125
  current_gain = int((await self.get_gain()).name.split("_")[-1])
128
126
  current_gain -= value
@@ -130,14 +128,11 @@ class FemtoDDPCA(CurrentAmp):
130
128
  raise ValueError("Gain at min value")
131
129
  await self.set(self.gain_conversion_table[f"SEN_{current_gain}"])
132
130
 
133
- @AsyncStatus.wrap
134
131
  async def get_gain(self) -> Enum:
135
132
  return self.gain_conversion_table[(await self.gain.get_value()).name]
136
133
 
137
- @AsyncStatus.wrap
138
134
  async def get_upperlimit(self) -> float:
139
135
  return self.upperlimit
140
136
 
141
- @AsyncStatus.wrap
142
137
  async def get_lowerlimit(self) -> float:
143
138
  return self.lowerlimit
@@ -178,7 +178,6 @@ class SR570(CurrentAmp):
178
178
  )
179
179
  await asyncio.sleep(self.raise_timetable[coarse_gain.name].value)
180
180
 
181
- @AsyncStatus.wrap
182
181
  async def increase_gain(self, value=3) -> None:
183
182
  current_gain = int((await self.get_gain()).name.split("_")[-1])
184
183
  current_gain += value
@@ -189,7 +188,6 @@ class SR570(CurrentAmp):
189
188
  raise ValueError("Gain at max value")
190
189
  await self.set(self.gain_conversion_table[f"SEN_{current_gain}"])
191
190
 
192
- @AsyncStatus.wrap
193
191
  async def decrease_gain(self, value=3) -> None:
194
192
  current_gain = int((await self.get_gain()).name.split("_")[-1])
195
193
  current_gain -= value
@@ -198,17 +196,14 @@ class SR570(CurrentAmp):
198
196
  raise ValueError("Gain at min value")
199
197
  await self.set(self.gain_conversion_table[f"SEN_{current_gain}"])
200
198
 
201
- @AsyncStatus.wrap
202
199
  async def get_gain(self) -> Enum:
203
200
  result = await asyncio.gather(
204
201
  self.coarse_gain.get_value(), self.fine_gain.get_value()
205
202
  )
206
203
  return self.gain_conversion_table[self.combined_table(result).name]
207
204
 
208
- @AsyncStatus.wrap
209
205
  async def get_upperlimit(self) -> float:
210
206
  return self.upperlimit
211
207
 
212
- @AsyncStatus.wrap
213
208
  async def get_lowerlimit(self) -> float:
214
209
  return self.lowerlimit
@@ -1,6 +1,9 @@
1
1
  from enum import Enum
2
2
 
3
- from numpy import interp, loadtxt
3
+ from dodal.devices.util.lookup_tables import (
4
+ linear_extrapolation_lut,
5
+ parse_lookup_table,
6
+ )
4
7
 
5
8
 
6
9
  class Axis(Enum):
@@ -11,12 +14,20 @@ class Axis(Enum):
11
14
  class DetectorDistanceToBeamXYConverter:
12
15
  def __init__(self, lookup_file: str):
13
16
  self.lookup_file: str = lookup_file
14
- self.lookup_table_values: list = self.parse_table()
17
+ lookup_table_columns: list = parse_lookup_table(self.lookup_file)
18
+ self._d_to_x = linear_extrapolation_lut(
19
+ lookup_table_columns[0], lookup_table_columns[1]
20
+ )
21
+ self._d_to_y = linear_extrapolation_lut(
22
+ lookup_table_columns[0], lookup_table_columns[2]
23
+ )
15
24
 
16
25
  def get_beam_xy_from_det_dist(self, det_dist_mm: float, beam_axis: Axis) -> float:
17
- beam_axis_values = self.lookup_table_values[beam_axis.value]
18
- det_dist_array = self.lookup_table_values[0]
19
- return float(interp(det_dist_mm, det_dist_array, beam_axis_values))
26
+ return (
27
+ self._d_to_x(det_dist_mm)
28
+ if beam_axis == Axis.X_AXIS
29
+ else self._d_to_y(det_dist_mm)
30
+ )
20
31
 
21
32
  def get_beam_axis_pixels(
22
33
  self,
@@ -41,21 +52,3 @@ class DetectorDistanceToBeamXYConverter:
41
52
  return self.get_beam_axis_pixels(
42
53
  det_distance, image_size_pixels, det_dim, Axis.X_AXIS
43
54
  )
44
-
45
- def reload_lookup_table(self):
46
- self.lookup_table_values = self.parse_table()
47
-
48
- def parse_table(self) -> list:
49
- rows = loadtxt(self.lookup_file, delimiter=" ", comments=["#", "Units"])
50
- columns = list(zip(*rows, strict=False))
51
-
52
- return columns
53
-
54
- def __eq__(self, other):
55
- if not isinstance(other, DetectorDistanceToBeamXYConverter):
56
- return NotImplemented
57
- if self.lookup_file != other.lookup_file:
58
- return False
59
- if self.lookup_table_values != other.lookup_table_values:
60
- return False
61
- return True
@@ -1,4 +1,5 @@
1
1
  from enum import Enum, auto
2
+ from functools import cached_property
2
3
  from pathlib import Path
3
4
 
4
5
  from pydantic import BaseModel, Field, field_serializer, field_validator
@@ -49,7 +50,7 @@ class DetectorParams(BaseModel):
49
50
  False # Remove in https://github.com/DiamondLightSource/hyperion/issues/1395
50
51
  )
51
52
 
52
- @property
53
+ @cached_property
53
54
  def beam_xy_converter(self) -> DetectorDistanceToBeamXYConverter:
54
55
  return DetectorDistanceToBeamXYConverter(self.det_dist_to_beam_converter_path)
55
56
 
File without changes