dls-dodal 1.47.0__py3-none-any.whl → 1.49.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 (61) hide show
  1. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/METADATA +3 -2
  2. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/RECORD +59 -49
  3. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/aithre.py +21 -0
  6. dodal/beamlines/b01_1.py +1 -1
  7. dodal/beamlines/b16.py +65 -0
  8. dodal/beamlines/b18.py +38 -0
  9. dodal/beamlines/i03.py +21 -6
  10. dodal/beamlines/i04.py +17 -10
  11. dodal/beamlines/i10.py +41 -233
  12. dodal/beamlines/i18.py +1 -1
  13. dodal/beamlines/i19_1.py +9 -6
  14. dodal/beamlines/i24.py +5 -5
  15. dodal/beamlines/k11.py +35 -0
  16. dodal/common/beamlines/beamline_parameters.py +2 -28
  17. dodal/common/beamlines/device_helpers.py +1 -0
  18. dodal/devices/aithre_lasershaping/goniometer.py +36 -2
  19. dodal/devices/aithre_lasershaping/laser_robot.py +27 -0
  20. dodal/devices/apple2_undulator.py +257 -136
  21. dodal/devices/b16/__init__.py +0 -0
  22. dodal/devices/b16/detector.py +34 -0
  23. dodal/devices/bimorph_mirror.py +29 -36
  24. dodal/devices/electron_analyser/__init__.py +21 -1
  25. dodal/devices/electron_analyser/abstract/__init__.py +0 -6
  26. dodal/devices/electron_analyser/abstract/base_detector.py +16 -128
  27. dodal/devices/electron_analyser/abstract/base_driver_io.py +122 -8
  28. dodal/devices/electron_analyser/abstract/base_region.py +7 -3
  29. dodal/devices/electron_analyser/detector.py +141 -0
  30. dodal/devices/electron_analyser/enums.py +6 -0
  31. dodal/devices/electron_analyser/specs/__init__.py +3 -2
  32. dodal/devices/electron_analyser/specs/detector.py +6 -22
  33. dodal/devices/electron_analyser/specs/driver_io.py +27 -3
  34. dodal/devices/electron_analyser/specs/enums.py +8 -0
  35. dodal/devices/electron_analyser/specs/region.py +3 -2
  36. dodal/devices/electron_analyser/types.py +30 -4
  37. dodal/devices/electron_analyser/util.py +1 -1
  38. dodal/devices/electron_analyser/vgscienta/__init__.py +3 -2
  39. dodal/devices/electron_analyser/vgscienta/detector.py +9 -23
  40. dodal/devices/electron_analyser/vgscienta/driver_io.py +33 -4
  41. dodal/devices/electron_analyser/vgscienta/enums.py +19 -0
  42. dodal/devices/electron_analyser/vgscienta/region.py +7 -23
  43. dodal/devices/fast_grid_scan.py +1 -1
  44. dodal/devices/i04/murko_results.py +93 -96
  45. dodal/devices/i10/__init__.py +0 -0
  46. dodal/devices/i10/i10_apple2.py +181 -126
  47. dodal/devices/i18/diode.py +37 -4
  48. dodal/devices/i22/nxsas.py +1 -1
  49. dodal/devices/mx_phase1/beamstop.py +23 -6
  50. dodal/devices/oav/oav_detector.py +101 -25
  51. dodal/devices/oav/oav_parameters.py +46 -16
  52. dodal/devices/oav/oav_to_redis_forwarder.py +2 -2
  53. dodal/devices/robot.py +20 -1
  54. dodal/devices/smargon.py +43 -4
  55. dodal/devices/zebra/zebra.py +8 -0
  56. dodal/plans/configure_arm_trigger_and_disarm_detector.py +167 -0
  57. dodal/plan_stubs/electron_analyser/__init__.py +0 -3
  58. dodal/plan_stubs/electron_analyser/configure_driver.py +0 -92
  59. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/entry_points.txt +0 -0
  60. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/licenses/LICENSE +0 -0
  61. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,8 @@
1
1
  import asyncio
2
- from collections.abc import Mapping
3
2
  from typing import Annotated as A
4
3
 
5
4
  from bluesky.protocols import Movable
6
5
  from ophyd_async.core import (
7
- DEFAULT_TIMEOUT,
8
6
  AsyncStatus,
9
7
  DeviceVector,
10
8
  SignalR,
@@ -12,6 +10,7 @@ from ophyd_async.core import (
12
10
  SignalW,
13
11
  StandardReadable,
14
12
  StrictEnum,
13
+ set_and_wait_for_other_value,
15
14
  wait_for_value,
16
15
  )
17
16
  from ophyd_async.core import StandardReadableFormat as Format
@@ -23,6 +22,8 @@ from ophyd_async.epics.core import (
23
22
  epics_signal_x,
24
23
  )
25
24
 
25
+ DEFAULT_TIMEOUT = 60
26
+
26
27
 
27
28
  class BimorphMirrorOnOff(StrictEnum):
28
29
  ON = "ON"
@@ -41,7 +42,7 @@ class BimorphMirrorStatus(StrictEnum):
41
42
  ERROR = "Error"
42
43
 
43
44
 
44
- class BimorphMirrorChannel(StandardReadable, Movable[float], EpicsDevice):
45
+ class BimorphMirrorChannel(StandardReadable, EpicsDevice):
45
46
  """Collection of PVs comprising a single bimorph channel.
46
47
 
47
48
  Attributes:
@@ -56,23 +57,13 @@ class BimorphMirrorChannel(StandardReadable, Movable[float], EpicsDevice):
56
57
  status: A[SignalR[BimorphMirrorOnOff], PvSuffix("STATUS"), Format.CONFIG_SIGNAL]
57
58
  shift: A[SignalW[float], PvSuffix("SHIFT")]
58
59
 
59
- @AsyncStatus.wrap
60
- async def set(self, value: float):
61
- """Sets channel's VOUT to given value.
62
-
63
- Args:
64
- value: float to set VOUT to
65
- """
66
- await self.output_voltage.set(value)
67
-
68
60
 
69
- class BimorphMirror(StandardReadable, Movable[Mapping[int, float]]):
61
+ class BimorphMirror(StandardReadable, Movable[list[float]]):
70
62
  """Class to represent CAENels Bimorph Mirrors.
71
63
 
72
64
  Attributes:
73
65
  channels: DeviceVector of BimorphMirrorChannel, indexed from 1, for each channel
74
66
  enabled: Writeable BimorphOnOff
75
- commit_target_voltages: Procable signal that writes values in each channel's VTRGT to VOUT
76
67
  status: Readable BimorphMirrorStatus Busy/Idle status
77
68
  err: Alarm status"""
78
69
 
@@ -103,49 +94,51 @@ class BimorphMirror(StandardReadable, Movable[Mapping[int, float]]):
103
94
  super().__init__(name=name)
104
95
 
105
96
  @AsyncStatus.wrap
106
- async def set(self, value: Mapping[int, float], tolerance: float = 0.0001) -> None:
107
- """Sets bimorph voltages in parrallel via target voltage and all proc.
97
+ async def set(self, value: list[float]) -> None:
98
+ """Sets bimorph voltages in parallel via target voltage and all proc.
108
99
 
109
100
  Args:
110
- value: Dict of channel numbers to target voltages
101
+ value: List of float target voltages
111
102
 
112
103
  Raises:
113
104
  ValueError: On set to non-existent channel"""
114
105
 
115
- if any(key not in self.channels for key in value):
106
+ if len(value) != len(self.channels):
116
107
  raise ValueError(
117
- f"Attempting to put to non-existent channels: {[key for key in value if (key not in self.channels)]}"
108
+ f"Length of value input array does not match number of \
109
+ channels: {len(value)} and {len(self.channels)}"
118
110
  )
119
111
 
120
- # Write target voltages:
121
- await asyncio.gather(
122
- *[
123
- self.channels[i].target_voltage.set(target, wait=True)
124
- for i, target in value.items()
125
- ]
126
- )
112
+ # Write target voltages in serial
113
+ # Voltages are written in serial as bimorph PSU cannot handle simultaneous sets
114
+ for i, target in enumerate(value):
115
+ await wait_for_value(
116
+ self.status, BimorphMirrorStatus.IDLE, timeout=DEFAULT_TIMEOUT
117
+ )
118
+ await set_and_wait_for_other_value(
119
+ self.channels[i + 1].target_voltage,
120
+ target,
121
+ self.status,
122
+ BimorphMirrorStatus.BUSY,
123
+ )
127
124
 
128
125
  # Trigger set target voltages:
126
+ await wait_for_value(
127
+ self.status, BimorphMirrorStatus.IDLE, timeout=DEFAULT_TIMEOUT
128
+ )
129
129
  await self.commit_target_voltages.trigger()
130
130
 
131
131
  # Wait for values to propogate to voltage out rbv:
132
132
  await asyncio.gather(
133
133
  *[
134
134
  wait_for_value(
135
- self.channels[i].output_voltage,
136
- tolerance_func_builder(tolerance, target),
135
+ self.channels[i + 1].output_voltage,
136
+ target,
137
137
  timeout=DEFAULT_TIMEOUT,
138
138
  )
139
- for i, target in value.items()
139
+ for i, target in enumerate(value)
140
140
  ],
141
141
  wait_for_value(
142
142
  self.status, BimorphMirrorStatus.IDLE, timeout=DEFAULT_TIMEOUT
143
143
  ),
144
144
  )
145
-
146
-
147
- def tolerance_func_builder(tolerance: float, target_value: float):
148
- def is_within_value(x):
149
- return abs(x - target_value) <= tolerance
150
-
151
- return is_within_value
@@ -1,8 +1,28 @@
1
- from .types import EnergyMode
1
+ from .detector import (
2
+ ElectronAnalyserDetector,
3
+ ElectronAnalyserRegionDetector,
4
+ TElectronAnalyserDetector,
5
+ TElectronAnalyserRegionDetector,
6
+ )
7
+ from .enums import EnergyMode
8
+ from .types import (
9
+ ElectronAnalyserDetectorImpl,
10
+ ElectronAnalyserDriverImpl,
11
+ GenericElectronAnalyserDetector,
12
+ GenericElectronAnalyserRegionDetector,
13
+ )
2
14
  from .util import to_binding_energy, to_kinetic_energy
3
15
 
4
16
  __all__ = [
5
17
  "to_binding_energy",
6
18
  "to_kinetic_energy",
7
19
  "EnergyMode",
20
+ "ElectronAnalyserDetector",
21
+ "ElectronAnalyserDetectorImpl",
22
+ "ElectronAnalyserDriverImpl",
23
+ "TElectronAnalyserDetector",
24
+ "ElectronAnalyserRegionDetector",
25
+ "TElectronAnalyserRegionDetector",
26
+ "GenericElectronAnalyserDetector",
27
+ "GenericElectronAnalyserRegionDetector",
8
28
  ]
@@ -1,9 +1,6 @@
1
1
  from .base_detector import (
2
2
  AbstractAnalyserDriverIO,
3
3
  AbstractElectronAnalyserDetector,
4
- AbstractElectronAnalyserRegionDetector,
5
- TAbstractElectronAnalyserDetector,
6
- TAbstractElectronAnalyserRegionDetector,
7
4
  )
8
5
  from .base_driver_io import AbstractAnalyserDriverIO, TAbstractAnalyserDriverIO
9
6
  from .base_region import (
@@ -20,9 +17,6 @@ __all__ = [
20
17
  "TAbstractBaseSequence",
21
18
  "AbstractAnalyserDriverIO",
22
19
  "AbstractElectronAnalyserDetector",
23
- "AbstractElectronAnalyserRegionDetector",
24
- "TAbstractElectronAnalyserDetector",
25
- "TAbstractElectronAnalyserRegionDetector",
26
20
  "AbstractAnalyserDriverIO",
27
21
  "TAbstractAnalyserDriverIO",
28
22
  ]
@@ -1,40 +1,31 @@
1
1
  import asyncio
2
2
  from abc import abstractmethod
3
- from typing import Generic, TypeVar
3
+ from typing import Generic
4
4
 
5
- from bluesky.protocols import (
6
- Reading,
7
- Stageable,
8
- Triggerable,
9
- )
5
+ from bluesky.protocols import Reading, Stageable, Triggerable
10
6
  from event_model import DataKey
11
7
  from ophyd_async.core import (
8
+ AsyncConfigurable,
9
+ AsyncReadable,
12
10
  AsyncStatus,
13
11
  Device,
14
- Reference,
15
12
  )
16
- from ophyd_async.core._protocol import AsyncConfigurable, AsyncReadable
17
13
  from ophyd_async.epics.adcore import (
18
14
  ADBaseController,
19
15
  )
20
16
 
21
- from dodal.common.data_util import load_json_file_to_class
22
17
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
23
18
  AbstractAnalyserDriverIO,
24
19
  TAbstractAnalyserDriverIO,
25
20
  )
26
- from dodal.devices.electron_analyser.abstract.base_region import (
27
- TAbstractBaseRegion,
28
- TAbstractBaseSequence,
29
- )
30
21
 
31
22
 
32
- class AnalyserController(ADBaseController[AbstractAnalyserDriverIO]):
23
+ class ElectronAnalyserController(ADBaseController[AbstractAnalyserDriverIO]):
33
24
  def get_deadtime(self, exposure: float | None) -> float:
34
25
  return 0
35
26
 
36
27
 
37
- class BaseElectronAnalyserDetector(
28
+ class AbstractElectronAnalyserDetector(
38
29
  Device,
39
30
  Stageable,
40
31
  Triggerable,
@@ -46,17 +37,19 @@ class BaseElectronAnalyserDetector(
46
37
  Detector for data acquisition of electron analyser. Can only acquire using settings
47
38
  already configured for the device.
48
39
 
49
- If possible, this should be changed to inheirt from a StandardDetector. Currently,
40
+ If possible, this should be changed to inherit from a StandardDetector. Currently,
50
41
  StandardDetector forces you to use a file writer which doesn't apply here.
51
42
  See issue https://github.com/bluesky/ophyd-async/issues/888
52
43
  """
53
44
 
54
45
  def __init__(
55
46
  self,
56
- name: str,
57
47
  driver: TAbstractAnalyserDriverIO,
48
+ name: str = "",
58
49
  ):
59
- self.controller: AnalyserController = AnalyserController(driver=driver)
50
+ self.controller: ElectronAnalyserController = ElectronAnalyserController(
51
+ driver=driver
52
+ )
60
53
  super().__init__(name)
61
54
 
62
55
  @AsyncStatus.wrap
@@ -96,115 +89,10 @@ class BaseElectronAnalyserDetector(
96
89
  @abstractmethod
97
90
  def driver(self) -> TAbstractAnalyserDriverIO:
98
91
  """
99
- Define property for the driver. Some implementations will store this as a
100
- reference so it doesn't run into errors with conflicting parents.
101
- """
102
-
103
-
104
- class AbstractElectronAnalyserRegionDetector(
105
- BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO],
106
- Stageable,
107
- Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
108
- ):
109
- """
110
- Extends electron analyser detector to configure specific region settings before data
111
- acqusition. This object must be passed in a driver and store it as a reference. It
112
- is designed to only exist inside a plan.
113
- """
92
+ Define common property for all implementations to access the driver. Some
93
+ implementations will store this as a reference so it doesn't have conflicting
94
+ parents.
114
95
 
115
- def __init__(
116
- self, name: str, driver: TAbstractAnalyserDriverIO, region: TAbstractBaseRegion
117
- ):
118
- self._driver_ref = Reference(driver)
119
- self.region = region
120
- super().__init__(name, driver)
121
-
122
- @property
123
- def driver(self) -> TAbstractAnalyserDriverIO:
124
- # Store as a reference, this implementation will be given a driver so needs to
125
- # make sure we don't get conflicting parents.
126
- return self._driver_ref()
127
-
128
- @AsyncStatus.wrap
129
- async def stage(self) -> None:
130
- super().stage()
131
- self.configure_region()
132
-
133
- @abstractmethod
134
- def configure_region(self):
135
- """
136
- Setup analyser with configured region.
96
+ Returns:
97
+ instance of the driver.
137
98
  """
138
-
139
-
140
- TAbstractElectronAnalyserRegionDetector = TypeVar(
141
- "TAbstractElectronAnalyserRegionDetector",
142
- bound=AbstractElectronAnalyserRegionDetector,
143
- )
144
-
145
-
146
- class AbstractElectronAnalyserDetector(
147
- BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO],
148
- Generic[TAbstractAnalyserDriverIO, TAbstractBaseSequence, TAbstractBaseRegion],
149
- ):
150
- """
151
- Electron analyser detector with the additional functionality to load a sequence file
152
- and create a list of temporary ElectronAnalyserRegionDetector objects. These will
153
- setup configured region settings before data acquisition.
154
- """
155
-
156
- def __init__(
157
- self, prefix: str, name: str, sequence_class: type[TAbstractBaseSequence]
158
- ):
159
- self._driver = self._create_driver(prefix)
160
- self._sequence_class = sequence_class
161
- super().__init__(name, self.driver)
162
-
163
- @property
164
- def driver(self) -> TAbstractAnalyserDriverIO:
165
- # This implementation creates the driver and wants this to be the parent so it
166
- # can be used with connect() method.
167
- return self._driver
168
-
169
- def load_sequence(self, filename: str) -> TAbstractBaseSequence:
170
- return load_json_file_to_class(self._sequence_class, filename)
171
-
172
- @abstractmethod
173
- def _create_driver(self, prefix: str) -> TAbstractAnalyserDriverIO:
174
- """
175
- Define implementation of the driver used for this detector.
176
- """
177
-
178
- @abstractmethod
179
- def _create_region_detector(
180
- self, driver: TAbstractAnalyserDriverIO, region: TAbstractBaseRegion
181
- ) -> AbstractElectronAnalyserRegionDetector[
182
- TAbstractAnalyserDriverIO, TAbstractBaseRegion
183
- ]:
184
- """
185
- Define a way to create a temporary detector object that will always setup a
186
- specific region before acquiring.
187
- """
188
-
189
- def create_region_detector_list(
190
- self, filename: str
191
- ) -> list[
192
- AbstractElectronAnalyserRegionDetector[
193
- TAbstractAnalyserDriverIO, TAbstractBaseRegion
194
- ]
195
- ]:
196
- """
197
- Create a list of detectors that will setup a specific region from the sequence
198
- file when used.
199
- """
200
- seq = self.load_sequence(filename)
201
- return [
202
- self._create_region_detector(self.driver, r)
203
- for r in seq.get_enabled_regions()
204
- ]
205
-
206
-
207
- TAbstractElectronAnalyserDetector = TypeVar(
208
- "TAbstractElectronAnalyserDetector",
209
- bound=AbstractElectronAnalyserDetector,
210
- )
@@ -1,29 +1,46 @@
1
+ import asyncio
1
2
  from abc import ABC, abstractmethod
2
- from typing import TypeVar
3
+ from typing import Generic, TypeVar
3
4
 
4
5
  import numpy as np
6
+ from bluesky.protocols import Movable, Preparable
5
7
  from ophyd_async.core import (
6
8
  Array1D,
9
+ AsyncStatus,
7
10
  SignalR,
8
11
  StandardReadable,
9
12
  StandardReadableFormat,
13
+ StrictEnum,
10
14
  derived_signal_r,
11
15
  soft_signal_rw,
12
16
  )
13
17
  from ophyd_async.epics.adcore import ADBaseIO
14
18
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
19
+ from ophyd_async.epics.motor import Motor
15
20
 
16
- from dodal.devices.electron_analyser.types import EnergyMode
17
- from dodal.devices.electron_analyser.util import to_binding_energy
21
+ from dodal.devices.electron_analyser.abstract.base_region import (
22
+ TAbstractBaseRegion,
23
+ )
24
+ from dodal.devices.electron_analyser.enums import EnergyMode
25
+ from dodal.devices.electron_analyser.util import to_binding_energy, to_kinetic_energy
18
26
 
19
27
 
20
- class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
28
+ class AbstractAnalyserDriverIO(
29
+ ABC,
30
+ StandardReadable,
31
+ ADBaseIO,
32
+ Preparable,
33
+ Movable[TAbstractBaseRegion],
34
+ Generic[TAbstractBaseRegion],
35
+ ):
21
36
  """
22
37
  Generic device to configure electron analyser with new region settings.
23
38
  Electron analysers should inherit from this class for further specialisation.
24
39
  """
25
40
 
26
- def __init__(self, prefix: str, name: str = "") -> None:
41
+ def __init__(
42
+ self, prefix: str, acquisition_mode_type: type[StrictEnum], name: str = ""
43
+ ) -> None:
27
44
  with self.add_children_as_readables():
28
45
  self.image = epics_signal_r(Array1D[np.float64], prefix + "IMAGE")
29
46
  self.spectrum = epics_signal_r(Array1D[np.float64], prefix + "INT_SPECTRUM")
@@ -47,8 +64,12 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
47
64
  )
48
65
  self.energy_step = epics_signal_rw(float, prefix + "STEP_SIZE")
49
66
  self.iterations = epics_signal_rw(int, prefix + "NumExposures")
50
- self.acquisition_mode = epics_signal_rw(str, prefix + "ACQ_MODE")
67
+ self.acquisition_mode = epics_signal_rw(
68
+ acquisition_mode_type, prefix + "ACQ_MODE"
69
+ )
70
+ self.excitation_energy_source = soft_signal_rw(str, initial_value="")
51
71
 
72
+ with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
52
73
  # Read once per scan after data acquired
53
74
  self.energy_axis = self._create_energy_axis_signal(prefix)
54
75
  self.binding_energy_axis = derived_signal_r(
@@ -71,16 +92,79 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
71
92
 
72
93
  super().__init__(prefix=prefix, name=name)
73
94
 
95
+ @AsyncStatus.wrap
96
+ async def prepare(self, value: Motor):
97
+ """
98
+ Prepare the driver for a region by passing in the energy source motor selected
99
+ by a region.
100
+
101
+ Args:
102
+ value: The motor that contains the information on the current excitation
103
+ energy. Needed to prepare region for epics to accuratly calculate
104
+ kinetic energy for an energy scan when in binding energy mode.
105
+ """
106
+ energy_source = value
107
+ excitation_energy_value = await energy_source.user_readback.get_value() # eV
108
+ excitation_energy_source_name = energy_source.name
109
+
110
+ await asyncio.gather(
111
+ self.excitation_energy.set(excitation_energy_value),
112
+ self.excitation_energy_source.set(excitation_energy_source_name),
113
+ )
114
+
115
+ @AsyncStatus.wrap
116
+ async def set(self, region: TAbstractBaseRegion):
117
+ """
118
+ This should encompass all core region logic which is common to every electron
119
+ analyser for setting up the driver.
120
+
121
+ Args:
122
+ region: Contains the parameters to setup the driver for a scan.
123
+ """
124
+ pass_energy_type = self.pass_energy_type
125
+ pass_energy = pass_energy_type(region.pass_energy)
126
+
127
+ excitation_energy = await self.excitation_energy.get_value()
128
+ low_energy = to_kinetic_energy(
129
+ region.low_energy, region.energy_mode, excitation_energy
130
+ )
131
+ high_energy = to_kinetic_energy(
132
+ region.high_energy, region.energy_mode, excitation_energy
133
+ )
134
+ await asyncio.gather(
135
+ self.region_name.set(region.name),
136
+ self.energy_mode.set(region.energy_mode),
137
+ self.low_energy.set(low_energy),
138
+ self.high_energy.set(high_energy),
139
+ self.slices.set(region.slices),
140
+ self.lens_mode.set(region.lens_mode),
141
+ self.pass_energy.set(pass_energy),
142
+ self.iterations.set(region.iterations),
143
+ self.acquisition_mode.set(region.acquisition_mode),
144
+ )
145
+
74
146
  @abstractmethod
75
147
  def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
76
148
  """
77
149
  The signal that defines the angle axis. Depends on analyser model.
150
+
151
+ Args:
152
+ prefix: PV string used for connecting to angle axis.
153
+
154
+ Returns:
155
+ Signal that can give us angle axis array data.
78
156
  """
79
157
 
80
158
  @abstractmethod
81
159
  def _create_energy_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
82
160
  """
83
161
  The signal that defines the energy axis. Depends on analyser model.
162
+
163
+ Args:
164
+ prefix: PV string used for connecting to energy axis.
165
+
166
+ Returns:
167
+ Signal that can give us energy axis array data.
84
168
  """
85
169
 
86
170
  def _calculate_binding_energy_axis(
@@ -89,6 +173,20 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
89
173
  excitation_energy: float,
90
174
  energy_mode: EnergyMode,
91
175
  ) -> Array1D[np.float64]:
176
+ """
177
+ Calculate the binding energy axis to calibrate the spectra data. Function for a
178
+ derived signal.
179
+
180
+ Args:
181
+ energy_axis: Array data of the original energy_axis from epics.
182
+ excitation_energy: The excitation energy value used for the scan of this
183
+ region.
184
+ energy_mode: The energy_mode of the region that was used for the scan
185
+ of this region.
186
+
187
+ Returns:
188
+ Array that is the correct axis for the spectra data.
189
+ """
92
190
  is_binding = energy_mode == EnergyMode.BINDING
93
191
  return np.array(
94
192
  [
@@ -102,6 +200,18 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
102
200
  def _calculate_total_time(
103
201
  self, total_steps: int, step_time: float, iterations: int
104
202
  ) -> float:
203
+ """
204
+ Calulcate the total time the scan takes for this region. Function for a derived
205
+ signal.
206
+
207
+ Args:
208
+ total_steps: Number of steps for the region.
209
+ step_time: Time for each step for the region.
210
+ iterations: The number of iterations the region collected data for.
211
+
212
+ Returns:
213
+ Calculated total time in seconds.
214
+ """
105
215
  return total_steps * step_time * iterations
106
216
 
107
217
  def _calculate_total_intensity(self, spectrum: Array1D[np.float64]) -> float:
@@ -111,8 +221,12 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
111
221
  @abstractmethod
112
222
  def pass_energy_type(self) -> type:
113
223
  """
114
- Return the type the pass_energy should be. Each one is unfortunately different
115
- for the underlying analyser software and cannot be changed on epics side.
224
+ Return the type the pass_energy should be. Depends on underlying analyser
225
+ software.
226
+
227
+ Returns:
228
+ Type the pass energy parameter from a region needs to be cast to so it can
229
+ be set correctly on the signal.
116
230
  """
117
231
 
118
232
 
@@ -3,9 +3,12 @@ from abc import ABC
3
3
  from collections.abc import Callable
4
4
  from typing import Generic, TypeVar
5
5
 
6
+ from ophyd_async.core import StrictEnum
6
7
  from pydantic import BaseModel, Field, model_validator
7
8
 
8
- from dodal.devices.electron_analyser.types import EnergyMode
9
+ from dodal.devices.electron_analyser.enums import EnergyMode
10
+
11
+ TStrictEnum = TypeVar("TStrictEnum", bound=StrictEnum)
9
12
 
10
13
 
11
14
  def java_to_python_case(java_str: str) -> str:
@@ -43,7 +46,7 @@ def energy_mode_validation(data: dict) -> dict:
43
46
  return data
44
47
 
45
48
 
46
- class AbstractBaseRegion(ABC, JavaToPythonModel):
49
+ class AbstractBaseRegion(ABC, JavaToPythonModel, Generic[TStrictEnum]):
47
50
  """
48
51
  Generic region model that holds the data. Specialised region models should inherit
49
52
  this to extend functionality. All energy units are assumed to be in eV.
@@ -53,10 +56,11 @@ class AbstractBaseRegion(ABC, JavaToPythonModel):
53
56
  enabled: bool = False
54
57
  slices: int = 1
55
58
  iterations: int = 1
59
+ excitation_energy_source: str = "source1"
56
60
  # These ones we need subclasses to provide default values
57
61
  lens_mode: str
58
62
  pass_energy: int
59
- acquisition_mode: str
63
+ acquisition_mode: TStrictEnum
60
64
  low_energy: float
61
65
  high_energy: float
62
66
  step_time: float