dls-dodal 1.47.0__py3-none-any.whl → 1.48.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 (42) hide show
  1. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/METADATA +2 -2
  2. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/RECORD +40 -40
  3. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/aithre.py +6 -0
  6. dodal/beamlines/b01_1.py +1 -1
  7. dodal/beamlines/i03.py +21 -6
  8. dodal/beamlines/i04.py +17 -10
  9. dodal/beamlines/i18.py +1 -1
  10. dodal/beamlines/i19_1.py +9 -6
  11. dodal/beamlines/i24.py +5 -5
  12. dodal/common/beamlines/beamline_parameters.py +2 -28
  13. dodal/devices/aithre_lasershaping/goniometer.py +36 -2
  14. dodal/devices/aithre_lasershaping/laser_robot.py +27 -0
  15. dodal/devices/electron_analyser/__init__.py +10 -0
  16. dodal/devices/electron_analyser/abstract/__init__.py +0 -6
  17. dodal/devices/electron_analyser/abstract/base_detector.py +69 -56
  18. dodal/devices/electron_analyser/abstract/base_driver_io.py +114 -5
  19. dodal/devices/electron_analyser/abstract/base_region.py +1 -0
  20. dodal/devices/electron_analyser/specs/__init__.py +1 -2
  21. dodal/devices/electron_analyser/specs/detector.py +5 -21
  22. dodal/devices/electron_analyser/specs/driver_io.py +27 -2
  23. dodal/devices/electron_analyser/vgscienta/__init__.py +1 -2
  24. dodal/devices/electron_analyser/vgscienta/detector.py +8 -22
  25. dodal/devices/electron_analyser/vgscienta/driver_io.py +31 -3
  26. dodal/devices/electron_analyser/vgscienta/region.py +0 -1
  27. dodal/devices/fast_grid_scan.py +1 -1
  28. dodal/devices/i04/murko_results.py +93 -96
  29. dodal/devices/i18/diode.py +37 -4
  30. dodal/devices/mx_phase1/beamstop.py +23 -6
  31. dodal/devices/oav/oav_detector.py +61 -23
  32. dodal/devices/oav/oav_parameters.py +46 -16
  33. dodal/devices/oav/oav_to_redis_forwarder.py +2 -2
  34. dodal/devices/robot.py +20 -1
  35. dodal/devices/smargon.py +43 -4
  36. dodal/devices/zebra/zebra.py +8 -0
  37. dodal/plans/configure_arm_trigger_and_disarm_detector.py +167 -0
  38. dodal/plan_stubs/electron_analyser/__init__.py +0 -3
  39. dodal/plan_stubs/electron_analyser/configure_driver.py +0 -92
  40. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/entry_points.txt +0 -0
  41. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/licenses/LICENSE +0 -0
  42. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/top_level.txt +0 -0
@@ -2,21 +2,19 @@ import asyncio
2
2
  from abc import abstractmethod
3
3
  from typing import Generic, TypeVar
4
4
 
5
- from bluesky.protocols import (
6
- Reading,
7
- Stageable,
8
- Triggerable,
9
- )
5
+ from bluesky.protocols import Preparable, 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
12
  Reference,
15
13
  )
16
- from ophyd_async.core._protocol import AsyncConfigurable, AsyncReadable
17
14
  from ophyd_async.epics.adcore import (
18
15
  ADBaseController,
19
16
  )
17
+ from ophyd_async.epics.motor import Motor
20
18
 
21
19
  from dodal.common.data_util import load_json_file_to_class
22
20
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
@@ -29,12 +27,12 @@ from dodal.devices.electron_analyser.abstract.base_region import (
29
27
  )
30
28
 
31
29
 
32
- class AnalyserController(ADBaseController[AbstractAnalyserDriverIO]):
30
+ class ElectronAnalyserController(ADBaseController[AbstractAnalyserDriverIO]):
33
31
  def get_deadtime(self, exposure: float | None) -> float:
34
32
  return 0
35
33
 
36
34
 
37
- class BaseElectronAnalyserDetector(
35
+ class AbstractElectronAnalyserDetector(
38
36
  Device,
39
37
  Stageable,
40
38
  Triggerable,
@@ -46,7 +44,7 @@ class BaseElectronAnalyserDetector(
46
44
  Detector for data acquisition of electron analyser. Can only acquire using settings
47
45
  already configured for the device.
48
46
 
49
- If possible, this should be changed to inheirt from a StandardDetector. Currently,
47
+ If possible, this should be changed to inherit from a StandardDetector. Currently,
50
48
  StandardDetector forces you to use a file writer which doesn't apply here.
51
49
  See issue https://github.com/bluesky/ophyd-async/issues/888
52
50
  """
@@ -56,7 +54,9 @@ class BaseElectronAnalyserDetector(
56
54
  name: str,
57
55
  driver: TAbstractAnalyserDriverIO,
58
56
  ):
59
- self.controller: AnalyserController = AnalyserController(driver=driver)
57
+ self.controller: ElectronAnalyserController = ElectronAnalyserController(
58
+ driver=driver
59
+ )
60
60
  super().__init__(name)
61
61
 
62
62
  @AsyncStatus.wrap
@@ -96,14 +96,18 @@ class BaseElectronAnalyserDetector(
96
96
  @abstractmethod
97
97
  def driver(self) -> TAbstractAnalyserDriverIO:
98
98
  """
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.
99
+ Define common property for all implementations to access the driver. Some
100
+ implementations will store this as a reference so it doesn't have conflicting
101
+ parents.
102
+
103
+ Returns:
104
+ instance of the driver.
101
105
  """
102
106
 
103
107
 
104
- class AbstractElectronAnalyserRegionDetector(
105
- BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO],
106
- Stageable,
108
+ class ElectronAnalyserRegionDetector(
109
+ AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
110
+ Preparable,
107
111
  Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
108
112
  ):
109
113
  """
@@ -126,26 +130,31 @@ class AbstractElectronAnalyserRegionDetector(
126
130
  return self._driver_ref()
127
131
 
128
132
  @AsyncStatus.wrap
129
- async def stage(self) -> None:
130
- super().stage()
131
- self.configure_region()
132
-
133
- @abstractmethod
134
- def configure_region(self):
133
+ async def prepare(self, value: Motor) -> None:
135
134
  """
136
- Setup analyser with configured region.
135
+ Prepare driver with the region stored and energy_source motor.
136
+
137
+ Args:
138
+ value: The excitation energy source that the region has selected.
137
139
  """
140
+ excitation_energy_source = value
141
+ await self.driver.prepare(excitation_energy_source)
142
+ await self.driver.set(self.region)
138
143
 
139
144
 
140
- TAbstractElectronAnalyserRegionDetector = TypeVar(
141
- "TAbstractElectronAnalyserRegionDetector",
142
- bound=AbstractElectronAnalyserRegionDetector,
145
+ TElectronAnalyserRegionDetector = TypeVar(
146
+ "TElectronAnalyserRegionDetector",
147
+ bound=ElectronAnalyserRegionDetector,
143
148
  )
144
149
 
145
150
 
146
- class AbstractElectronAnalyserDetector(
147
- BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO],
148
- Generic[TAbstractAnalyserDriverIO, TAbstractBaseSequence, TAbstractBaseRegion],
151
+ class ElectronAnalyserDetector(
152
+ AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
153
+ Generic[
154
+ TAbstractAnalyserDriverIO,
155
+ TAbstractBaseSequence,
156
+ TAbstractBaseRegion,
157
+ ],
149
158
  ):
150
159
  """
151
160
  Electron analyser detector with the additional functionality to load a sequence file
@@ -154,9 +163,14 @@ class AbstractElectronAnalyserDetector(
154
163
  """
155
164
 
156
165
  def __init__(
157
- self, prefix: str, name: str, sequence_class: type[TAbstractBaseSequence]
166
+ self,
167
+ prefix: str,
168
+ sequence_class: type[TAbstractBaseSequence],
169
+ driver: TAbstractAnalyserDriverIO,
170
+ name: str = "",
158
171
  ):
159
- self._driver = self._create_driver(prefix)
172
+ # Pass in driver
173
+ self._driver = driver
160
174
  self._sequence_class = sequence_class
161
175
  super().__init__(name, self.driver)
162
176
 
@@ -167,44 +181,43 @@ class AbstractElectronAnalyserDetector(
167
181
  return self._driver
168
182
 
169
183
  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
184
  """
185
+ Load the sequence data from a provided json file into a sequence class.
177
186
 
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
+ Args:
188
+ filename: Path to the sequence file containing the region data.
189
+
190
+ Returns:
191
+ Pydantic model representing the sequence file.
187
192
  """
193
+ return load_json_file_to_class(self._sequence_class, filename)
188
194
 
189
195
  def create_region_detector_list(
190
- self, filename: str
196
+ self, filename: str, enabled_only=True
191
197
  ) -> list[
192
- AbstractElectronAnalyserRegionDetector[
193
- TAbstractAnalyserDriverIO, TAbstractBaseRegion
194
- ]
198
+ ElectronAnalyserRegionDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion]
195
199
  ]:
196
200
  """
197
- Create a list of detectors that will setup a specific region from the sequence
198
- file when used.
201
+ Create a list of detectors equal to the number of regions in a sequence file.
202
+ Each detector is responsible for setting up a specific region.
203
+
204
+ Args:
205
+ filename: Path to the sequence file containing the region data.
206
+ enabled_only: If true, only include the region if enabled is True.
207
+
208
+ Returns:
209
+ List of ElectronAnalyserRegionDetector, equal to the number of regions in
210
+ the sequence file.
199
211
  """
200
212
  seq = self.load_sequence(filename)
213
+ regions = seq.get_enabled_regions() if enabled_only else seq.regions
201
214
  return [
202
- self._create_region_detector(self.driver, r)
203
- for r in seq.get_enabled_regions()
215
+ ElectronAnalyserRegionDetector(self.name + "_" + r.name, self.driver, r)
216
+ for r in regions
204
217
  ]
205
218
 
206
219
 
207
- TAbstractElectronAnalyserDetector = TypeVar(
208
- "TAbstractElectronAnalyserDetector",
209
- bound=AbstractElectronAnalyserDetector,
220
+ TElectronAnalyserDetector = TypeVar(
221
+ "TElectronAnalyserDetector",
222
+ bound=ElectronAnalyserDetector,
210
223
  )
@@ -1,9 +1,12 @@
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,
@@ -12,12 +15,23 @@ from ophyd_async.core import (
12
15
  )
13
16
  from ophyd_async.epics.adcore import ADBaseIO
14
17
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
18
+ from ophyd_async.epics.motor import Motor
15
19
 
20
+ from dodal.devices.electron_analyser.abstract.base_region import (
21
+ TAbstractBaseRegion,
22
+ )
16
23
  from dodal.devices.electron_analyser.types import EnergyMode
17
- from dodal.devices.electron_analyser.util import to_binding_energy
24
+ from dodal.devices.electron_analyser.util import to_binding_energy, to_kinetic_energy
18
25
 
19
26
 
20
- class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
27
+ class AbstractAnalyserDriverIO(
28
+ ABC,
29
+ StandardReadable,
30
+ ADBaseIO,
31
+ Preparable,
32
+ Movable[TAbstractBaseRegion],
33
+ Generic[TAbstractBaseRegion],
34
+ ):
21
35
  """
22
36
  Generic device to configure electron analyser with new region settings.
23
37
  Electron analysers should inherit from this class for further specialisation.
@@ -48,7 +62,9 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
48
62
  self.energy_step = epics_signal_rw(float, prefix + "STEP_SIZE")
49
63
  self.iterations = epics_signal_rw(int, prefix + "NumExposures")
50
64
  self.acquisition_mode = epics_signal_rw(str, prefix + "ACQ_MODE")
65
+ self.excitation_energy_source = soft_signal_rw(str, initial_value="")
51
66
 
67
+ with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
52
68
  # Read once per scan after data acquired
53
69
  self.energy_axis = self._create_energy_axis_signal(prefix)
54
70
  self.binding_energy_axis = derived_signal_r(
@@ -71,16 +87,79 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
71
87
 
72
88
  super().__init__(prefix=prefix, name=name)
73
89
 
90
+ @AsyncStatus.wrap
91
+ async def prepare(self, value: Motor):
92
+ """
93
+ Prepare the driver for a region by passing in the energy source motor selected
94
+ by a region.
95
+
96
+ Args:
97
+ value: The motor that contains the information on the current excitation
98
+ energy. Needed to prepare region for epics to accuratly calculate
99
+ kinetic energy for an energy scan when in binding energy mode.
100
+ """
101
+ energy_source = value
102
+ excitation_energy_value = await energy_source.user_readback.get_value() # eV
103
+ excitation_energy_source_name = energy_source.name
104
+
105
+ await asyncio.gather(
106
+ self.excitation_energy.set(excitation_energy_value),
107
+ self.excitation_energy_source.set(excitation_energy_source_name),
108
+ )
109
+
110
+ @AsyncStatus.wrap
111
+ async def set(self, region: TAbstractBaseRegion):
112
+ """
113
+ This should encompass all core region logic which is common to every electron
114
+ analyser for setting up the driver.
115
+
116
+ Args:
117
+ region: Contains the parameters to setup the driver for a scan.
118
+ """
119
+ pass_energy_type = self.pass_energy_type
120
+ pass_energy = pass_energy_type(region.pass_energy)
121
+
122
+ excitation_energy = await self.excitation_energy.get_value()
123
+ low_energy = to_kinetic_energy(
124
+ region.low_energy, region.energy_mode, excitation_energy
125
+ )
126
+ high_energy = to_kinetic_energy(
127
+ region.high_energy, region.energy_mode, excitation_energy
128
+ )
129
+ await asyncio.gather(
130
+ self.region_name.set(region.name),
131
+ self.energy_mode.set(region.energy_mode),
132
+ self.low_energy.set(low_energy),
133
+ self.high_energy.set(high_energy),
134
+ self.slices.set(region.slices),
135
+ self.lens_mode.set(region.lens_mode),
136
+ self.pass_energy.set(pass_energy),
137
+ self.iterations.set(region.iterations),
138
+ self.acquisition_mode.set(region.acquisition_mode),
139
+ )
140
+
74
141
  @abstractmethod
75
142
  def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
76
143
  """
77
144
  The signal that defines the angle axis. Depends on analyser model.
145
+
146
+ Args:
147
+ prefix: PV string used for connecting to angle axis.
148
+
149
+ Returns:
150
+ Signal that can give us angle axis array data.
78
151
  """
79
152
 
80
153
  @abstractmethod
81
154
  def _create_energy_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
82
155
  """
83
156
  The signal that defines the energy axis. Depends on analyser model.
157
+
158
+ Args:
159
+ prefix: PV string used for connecting to energy axis.
160
+
161
+ Returns:
162
+ Signal that can give us energy axis array data.
84
163
  """
85
164
 
86
165
  def _calculate_binding_energy_axis(
@@ -89,6 +168,20 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
89
168
  excitation_energy: float,
90
169
  energy_mode: EnergyMode,
91
170
  ) -> Array1D[np.float64]:
171
+ """
172
+ Calculate the binding energy axis to calibrate the spectra data. Function for a
173
+ derived signal.
174
+
175
+ Args:
176
+ energy_axis: Array data of the original energy_axis from epics.
177
+ excitation_energy: The excitation energy value used for the scan of this
178
+ region.
179
+ energy_mode: The energy_mode of the region that was used for the scan
180
+ of this region.
181
+
182
+ Returns:
183
+ Array that is the correct axis for the spectra data.
184
+ """
92
185
  is_binding = energy_mode == EnergyMode.BINDING
93
186
  return np.array(
94
187
  [
@@ -102,6 +195,18 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
102
195
  def _calculate_total_time(
103
196
  self, total_steps: int, step_time: float, iterations: int
104
197
  ) -> float:
198
+ """
199
+ Calulcate the total time the scan takes for this region. Function for a derived
200
+ signal.
201
+
202
+ Args:
203
+ total_steps: Number of steps for the region.
204
+ step_time: Time for each step for the region.
205
+ iterations: The number of iterations the region collected data for.
206
+
207
+ Returns:
208
+ Calculated total time in seconds.
209
+ """
105
210
  return total_steps * step_time * iterations
106
211
 
107
212
  def _calculate_total_intensity(self, spectrum: Array1D[np.float64]) -> float:
@@ -111,8 +216,12 @@ class AbstractAnalyserDriverIO(ABC, StandardReadable, ADBaseIO):
111
216
  @abstractmethod
112
217
  def pass_energy_type(self) -> type:
113
218
  """
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.
219
+ Return the type the pass_energy should be. Depends on underlying analyser
220
+ software.
221
+
222
+ Returns:
223
+ Type the pass energy parameter from a region needs to be cast to so it can
224
+ be set correctly on the signal.
116
225
  """
117
226
 
118
227
 
@@ -53,6 +53,7 @@ class AbstractBaseRegion(ABC, JavaToPythonModel):
53
53
  enabled: bool = False
54
54
  slices: int = 1
55
55
  iterations: int = 1
56
+ excitation_energy_source: str = "source1"
56
57
  # These ones we need subclasses to provide default values
57
58
  lens_mode: str
58
59
  pass_energy: int
@@ -1,10 +1,9 @@
1
- from .detector import SpecsDetector, SpecsRegionDetector
1
+ from .detector import SpecsDetector
2
2
  from .driver_io import SpecsAnalyserDriverIO
3
3
  from .region import SpecsRegion, SpecsSequence
4
4
 
5
5
  __all__ = [
6
6
  "SpecsDetector",
7
- "SpecsRegionDetector",
8
7
  "SpecsAnalyserDriverIO",
9
8
  "SpecsRegion",
10
9
  "SpecsSequence",
@@ -1,29 +1,13 @@
1
1
  from dodal.devices.electron_analyser.abstract.base_detector import (
2
- AbstractElectronAnalyserDetector,
3
- AbstractElectronAnalyserRegionDetector,
2
+ ElectronAnalyserDetector,
4
3
  )
5
4
  from dodal.devices.electron_analyser.specs.driver_io import SpecsAnalyserDriverIO
6
5
  from dodal.devices.electron_analyser.specs.region import SpecsRegion, SpecsSequence
7
6
 
8
7
 
9
- class SpecsRegionDetector(
10
- AbstractElectronAnalyserRegionDetector[SpecsAnalyserDriverIO, SpecsRegion]
11
- ):
12
- def configure_region(self):
13
- # ToDo - Need to move configure plans to here and rewrite tests
14
- pass
15
-
16
-
17
8
  class SpecsDetector(
18
- AbstractElectronAnalyserDetector[SpecsAnalyserDriverIO, SpecsSequence, SpecsRegion]
9
+ ElectronAnalyserDetector[SpecsAnalyserDriverIO, SpecsSequence, SpecsRegion]
19
10
  ):
20
- def __init__(self, prefix: str, name: str):
21
- super().__init__(prefix, name, SpecsSequence)
22
-
23
- def _create_driver(self, prefix: str) -> SpecsAnalyserDriverIO:
24
- return SpecsAnalyserDriverIO(prefix, "driver")
25
-
26
- def _create_region_detector(
27
- self, driver: SpecsAnalyserDriverIO, region: SpecsRegion
28
- ) -> SpecsRegionDetector:
29
- return SpecsRegionDetector(self.name, driver, region)
11
+ def __init__(self, prefix: str, name: str = ""):
12
+ driver = SpecsAnalyserDriverIO(prefix=prefix)
13
+ super().__init__(prefix, SpecsSequence, driver, name)
@@ -1,13 +1,22 @@
1
+ import asyncio
2
+
1
3
  import numpy as np
2
- from ophyd_async.core import Array1D, SignalR, StandardReadableFormat, derived_signal_r
4
+ from ophyd_async.core import (
5
+ Array1D,
6
+ AsyncStatus,
7
+ SignalR,
8
+ StandardReadableFormat,
9
+ derived_signal_r,
10
+ )
3
11
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
4
12
 
5
13
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
6
14
  AbstractAnalyserDriverIO,
7
15
  )
16
+ from dodal.devices.electron_analyser.specs.region import SpecsRegion
8
17
 
9
18
 
10
- class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO):
19
+ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
11
20
  def __init__(self, prefix: str, name: str = "") -> None:
12
21
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
13
22
  # Used for setting up region data acquisition.
@@ -21,6 +30,22 @@ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO):
21
30
 
22
31
  super().__init__(prefix, name)
23
32
 
33
+ @AsyncStatus.wrap
34
+ async def set(self, region: SpecsRegion):
35
+ await super().set(region)
36
+
37
+ await asyncio.gather(
38
+ self.snapshot_values.set(region.values),
39
+ self.psu_mode.set(region.psu_mode),
40
+ )
41
+ # ToDo - This needs to be changed to an Enum
42
+ # https://github.com/DiamondLightSource/dodal/issues/1258
43
+ if region.acquisition_mode == "Fixed Transmission":
44
+ await self.centre_energy.set(region.centre_energy)
45
+
46
+ if self.acquisition_mode == "Fixed Energy":
47
+ await self.energy_step.set(region.energy_step)
48
+
24
49
  def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
25
50
  angle_axis = derived_signal_r(
26
51
  self._calculate_angle_axis,
@@ -1,10 +1,9 @@
1
- from .detector import VGScientaDetector, VGScientaRegionDetector
1
+ from .detector import VGScientaDetector
2
2
  from .driver_io import VGScientaAnalyserDriverIO
3
3
  from .region import VGScientaExcitationEnergySource, VGScientaRegion, VGScientaSequence
4
4
 
5
5
  __all__ = [
6
6
  "VGScientaDetector",
7
- "VGScientaRegionDetector",
8
7
  "VGScientaAnalyserDriverIO",
9
8
  "VGScientaExcitationEnergySource",
10
9
  "VGScientaRegion",
@@ -1,6 +1,5 @@
1
1
  from dodal.devices.electron_analyser.abstract.base_detector import (
2
- AbstractElectronAnalyserDetector,
3
- AbstractElectronAnalyserRegionDetector,
2
+ ElectronAnalyserDetector,
4
3
  )
5
4
  from dodal.devices.electron_analyser.vgscienta.driver_io import (
6
5
  VGScientaAnalyserDriverIO,
@@ -11,26 +10,13 @@ from dodal.devices.electron_analyser.vgscienta.region import (
11
10
  )
12
11
 
13
12
 
14
- class VGScientaRegionDetector(
15
- AbstractElectronAnalyserRegionDetector[VGScientaAnalyserDriverIO, VGScientaRegion]
16
- ):
17
- def configure_region(self):
18
- # ToDo - Need to move configure plans to here and rewrite tests
19
- pass
20
-
21
-
22
13
  class VGScientaDetector(
23
- AbstractElectronAnalyserDetector[
24
- VGScientaAnalyserDriverIO, VGScientaSequence, VGScientaRegion
14
+ ElectronAnalyserDetector[
15
+ VGScientaAnalyserDriverIO,
16
+ VGScientaSequence,
17
+ VGScientaRegion,
25
18
  ]
26
19
  ):
27
- def __init__(self, prefix: str, name: str):
28
- super().__init__(prefix, name, VGScientaSequence)
29
-
30
- def _create_driver(self, prefix: str) -> VGScientaAnalyserDriverIO:
31
- return VGScientaAnalyserDriverIO(prefix, "driver")
32
-
33
- def _create_region_detector(
34
- self, driver: VGScientaAnalyserDriverIO, region: VGScientaRegion
35
- ) -> VGScientaRegionDetector:
36
- return VGScientaRegionDetector(self.name, driver, region)
20
+ def __init__(self, prefix: str, name: str = ""):
21
+ driver = VGScientaAnalyserDriverIO(prefix)
22
+ super().__init__(prefix, VGScientaSequence, driver, name)
@@ -1,19 +1,28 @@
1
+ import asyncio
2
+
1
3
  import numpy as np
2
- from ophyd_async.core import Array1D, SignalR, StandardReadableFormat, soft_signal_rw
4
+ from ophyd_async.core import (
5
+ Array1D,
6
+ AsyncStatus,
7
+ SignalR,
8
+ StandardReadableFormat,
9
+ )
10
+ from ophyd_async.epics.adcore import ADImageMode
3
11
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
4
12
 
5
13
  from dodal.devices.electron_analyser.abstract.base_driver_io import (
6
14
  AbstractAnalyserDriverIO,
7
15
  )
16
+ from dodal.devices.electron_analyser.util import to_kinetic_energy
8
17
  from dodal.devices.electron_analyser.vgscienta.region import (
9
18
  DetectorMode,
19
+ VGScientaRegion,
10
20
  )
11
21
 
12
22
 
13
- class VGScientaAnalyserDriverIO(AbstractAnalyserDriverIO):
23
+ class VGScientaAnalyserDriverIO(AbstractAnalyserDriverIO[VGScientaRegion]):
14
24
  def __init__(self, prefix: str, name: str = "") -> None:
15
25
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
16
- self.excitation_energy_source = soft_signal_rw(str, initial_value=None)
17
26
  # Used for setting up region data acquisition.
18
27
  self.centre_energy = epics_signal_rw(float, prefix + "CENTRE_ENERGY")
19
28
  self.first_x_channel = epics_signal_rw(int, prefix + "MinX")
@@ -28,6 +37,25 @@ class VGScientaAnalyserDriverIO(AbstractAnalyserDriverIO):
28
37
 
29
38
  super().__init__(prefix, name)
30
39
 
40
+ @AsyncStatus.wrap
41
+ async def set(self, region: VGScientaRegion):
42
+ await super().set(region)
43
+
44
+ excitation_energy = await self.excitation_energy.get_value()
45
+ centre_energy = to_kinetic_energy(
46
+ region.fix_energy, region.energy_mode, excitation_energy
47
+ )
48
+ await asyncio.gather(
49
+ self.centre_energy.set(centre_energy),
50
+ self.energy_step.set(region.energy_step),
51
+ self.first_x_channel.set(region.first_x_channel),
52
+ self.first_y_channel.set(region.first_y_channel),
53
+ self.x_channel_size.set(region.x_channel_size()),
54
+ self.y_channel_size.set(region.y_channel_size()),
55
+ self.detector_mode.set(region.detector_mode),
56
+ self.image_mode.set(ADImageMode.SINGLE),
57
+ )
58
+
31
59
  def _create_energy_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
32
60
  return epics_signal_r(Array1D[np.float64], prefix + "X_SCALE_RBV")
33
61
 
@@ -40,7 +40,6 @@ class VGScientaRegion(AbstractBaseRegion):
40
40
  energy_step: float = Field(default=200.0)
41
41
  # Specific to this class
42
42
  id: str = Field(default=str(uuid.uuid4()), alias="region_id")
43
- excitation_energy_source: str = "source1"
44
43
  fix_energy: float = 9.0
45
44
  total_steps: float = 13.0
46
45
  total_time: float = 13.0
@@ -234,7 +234,7 @@ class FastGridScanCommon(StandardReadable, Flyable, ABC, Generic[ParamType]):
234
234
  }
235
235
  super().__init__(name)
236
236
 
237
- def _calculate_expected_images(self, x: float, y: float, z: float) -> float:
237
+ def _calculate_expected_images(self, x: int, y: int, z: int) -> int:
238
238
  LOGGER.info(f"Reading num of images found {x, y, z} images in each axis")
239
239
  first_grid = x * y
240
240
  second_grid = x * z