dls-dodal 1.67.0__py3-none-any.whl → 1.69.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 (86) hide show
  1. {dls_dodal-1.67.0.dist-info → dls_dodal-1.69.0.dist-info}/METADATA +2 -32
  2. {dls_dodal-1.67.0.dist-info → dls_dodal-1.69.0.dist-info}/RECORD +79 -71
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/adsim.py +30 -23
  5. dodal/beamlines/b07.py +1 -1
  6. dodal/beamlines/b07_1.py +1 -1
  7. dodal/beamlines/i02_1.py +14 -42
  8. dodal/beamlines/i02_2.py +5 -11
  9. dodal/beamlines/i03.py +4 -1
  10. dodal/beamlines/i03_supervisor.py +19 -0
  11. dodal/beamlines/i04.py +74 -179
  12. dodal/beamlines/i05.py +9 -1
  13. dodal/beamlines/i06.py +1 -1
  14. dodal/beamlines/i06_1.py +24 -0
  15. dodal/beamlines/i09.py +53 -9
  16. dodal/beamlines/i09_1.py +9 -1
  17. dodal/beamlines/i09_2.py +7 -6
  18. dodal/beamlines/i10_optics.py +1 -1
  19. dodal/beamlines/i16.py +34 -0
  20. dodal/beamlines/i17.py +1 -1
  21. dodal/beamlines/i20_1.py +14 -0
  22. dodal/beamlines/i21.py +71 -4
  23. dodal/beamlines/i23.py +19 -25
  24. dodal/beamlines/i24.py +55 -105
  25. dodal/beamlines/p60.py +12 -2
  26. dodal/common/__init__.py +2 -1
  27. dodal/common/maths.py +80 -0
  28. dodal/devices/eiger.py +44 -23
  29. dodal/devices/electron_analyser/__init__.py +0 -33
  30. dodal/devices/electron_analyser/base/__init__.py +58 -0
  31. dodal/devices/electron_analyser/base/base_controller.py +84 -0
  32. dodal/devices/electron_analyser/base/base_detector.py +214 -0
  33. dodal/devices/electron_analyser/{abstract → base}/base_driver_io.py +23 -42
  34. dodal/devices/electron_analyser/{enums.py → base/base_enums.py} +0 -5
  35. dodal/devices/electron_analyser/{abstract → base}/base_region.py +48 -11
  36. dodal/devices/electron_analyser/{util.py → base/base_util.py} +1 -1
  37. dodal/devices/electron_analyser/{energy_sources.py → base/energy_sources.py} +27 -26
  38. dodal/devices/electron_analyser/specs/__init__.py +4 -4
  39. dodal/devices/electron_analyser/specs/specs_detector.py +47 -0
  40. dodal/devices/electron_analyser/specs/{driver_io.py → specs_driver_io.py} +23 -26
  41. dodal/devices/electron_analyser/specs/{region.py → specs_region.py} +4 -3
  42. dodal/devices/electron_analyser/vgscienta/__init__.py +4 -4
  43. dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +53 -0
  44. dodal/devices/electron_analyser/vgscienta/{driver_io.py → vgscienta_driver_io.py} +25 -31
  45. dodal/devices/electron_analyser/vgscienta/{region.py → vgscienta_region.py} +6 -6
  46. dodal/devices/fast_shutter.py +108 -25
  47. dodal/devices/i04/beam_centre.py +84 -0
  48. dodal/devices/i04/max_pixel.py +4 -17
  49. dodal/devices/i04/murko_results.py +18 -3
  50. dodal/devices/i09_2_shared/i09_apple2.py +0 -72
  51. dodal/devices/i10/i10_apple2.py +7 -7
  52. dodal/devices/i17/i17_apple2.py +6 -6
  53. dodal/devices/i21/__init__.py +3 -1
  54. dodal/devices/i24/commissioning_jungfrau.py +9 -10
  55. dodal/devices/insertion_device/__init__.py +62 -0
  56. dodal/devices/insertion_device/apple2_controller.py +380 -0
  57. dodal/devices/insertion_device/apple2_undulator.py +152 -481
  58. dodal/devices/insertion_device/energy.py +88 -0
  59. dodal/devices/insertion_device/energy_motor_lookup.py +1 -1
  60. dodal/devices/insertion_device/enum.py +17 -0
  61. dodal/devices/insertion_device/lookup_table_models.py +66 -36
  62. dodal/devices/insertion_device/polarisation.py +36 -0
  63. dodal/devices/oav/oav_detector.py +66 -1
  64. dodal/devices/oav/utils.py +17 -0
  65. dodal/devices/robot.py +35 -18
  66. dodal/devices/selectable_source.py +38 -0
  67. dodal/devices/zebra/zebra.py +15 -0
  68. dodal/devices/zebra/zebra_constants_mapping.py +1 -0
  69. dodal/plans/configure_arm_trigger_and_disarm_detector.py +0 -1
  70. dodal/testing/__init__.py +0 -0
  71. dodal/testing/electron_analyser/device_factory.py +4 -4
  72. dodal/testing/fixtures/devices/apple2.py +1 -1
  73. dodal/testing/fixtures/run_engine.py +4 -0
  74. dodal/devices/electron_analyser/abstract/__init__.py +0 -25
  75. dodal/devices/electron_analyser/abstract/base_detector.py +0 -63
  76. dodal/devices/electron_analyser/abstract/types.py +0 -12
  77. dodal/devices/electron_analyser/detector.py +0 -143
  78. dodal/devices/electron_analyser/specs/detector.py +0 -34
  79. dodal/devices/electron_analyser/types.py +0 -57
  80. dodal/devices/electron_analyser/vgscienta/detector.py +0 -48
  81. {dls_dodal-1.67.0.dist-info → dls_dodal-1.69.0.dist-info}/WHEEL +0 -0
  82. {dls_dodal-1.67.0.dist-info → dls_dodal-1.69.0.dist-info}/entry_points.txt +0 -0
  83. {dls_dodal-1.67.0.dist-info → dls_dodal-1.69.0.dist-info}/licenses/LICENSE +0 -0
  84. {dls_dodal-1.67.0.dist-info → dls_dodal-1.69.0.dist-info}/top_level.txt +0 -0
  85. /dodal/devices/electron_analyser/specs/{enums.py → specs_enums.py} +0 -0
  86. /dodal/devices/electron_analyser/vgscienta/{enums.py → vgscienta_enums.py} +0 -0
@@ -1,5 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
- from typing import Generic, TypeVar
2
+ from typing import Generic, TypeAlias, TypeVar
3
3
 
4
4
  import numpy as np
5
5
  from bluesky.protocols import Movable
@@ -9,27 +9,29 @@ from ophyd_async.core import (
9
9
  SignalR,
10
10
  StandardReadable,
11
11
  StandardReadableFormat,
12
+ StrictEnum,
13
+ SupersetEnum,
12
14
  derived_signal_r,
13
15
  soft_signal_rw,
14
16
  )
15
- from ophyd_async.epics.adcore import ADBaseIO, ADImageMode
17
+ from ophyd_async.epics.adcore import ADBaseIO
16
18
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
17
19
 
18
- from dodal.devices.electron_analyser.abstract.base_region import (
20
+ from dodal.devices.electron_analyser.base.base_enums import EnergyMode
21
+ from dodal.devices.electron_analyser.base.base_region import (
22
+ AnyAcqMode,
23
+ AnyLensMode,
24
+ AnyPassEnergy,
25
+ GenericRegion,
19
26
  TAbstractBaseRegion,
20
- )
21
- from dodal.devices.electron_analyser.abstract.types import (
22
27
  TAcquisitionMode,
23
28
  TLensMode,
24
29
  TPassEnergy,
25
- TPsuMode,
26
- )
27
- from dodal.devices.electron_analyser.energy_sources import (
28
- DualEnergySource,
29
- EnergySource,
30
30
  )
31
- from dodal.devices.electron_analyser.enums import EnergyMode
32
- from dodal.devices.electron_analyser.util import to_binding_energy
31
+ from dodal.devices.electron_analyser.base.base_util import to_binding_energy
32
+
33
+ AnyPsuMode: TypeAlias = SupersetEnum | StrictEnum
34
+ TPsuMode = TypeVar("TPsuMode", bound=AnyPsuMode)
33
35
 
34
36
 
35
37
  class AbstractAnalyserDriverIO(
@@ -52,7 +54,6 @@ class AbstractAnalyserDriverIO(
52
54
  lens_mode_type: type[TLensMode],
53
55
  psu_mode_type: type[TPsuMode],
54
56
  pass_energy_type: type[TPassEnergy],
55
- energy_source: EnergySource | DualEnergySource,
56
57
  name: str = "",
57
58
  ) -> None:
58
59
  """
@@ -87,7 +88,6 @@ class AbstractAnalyserDriverIO(
87
88
  self.total_intensity = derived_signal_r(
88
89
  self._calculate_total_intensity, spectrum=self.spectrum
89
90
  )
90
- self.energy_source = energy_source
91
91
 
92
92
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
93
93
  # Read once per scan after data acquired
@@ -96,6 +96,9 @@ class AbstractAnalyserDriverIO(
96
96
  self.energy_mode = soft_signal_rw(
97
97
  EnergyMode, initial_value=EnergyMode.KINETIC
98
98
  )
99
+ self.cached_excitation_energy = soft_signal_rw(
100
+ float, initial_value=0, units="eV"
101
+ )
99
102
  self.low_energy = epics_signal_rw(float, prefix + "LOW_ENERGY")
100
103
  self.centre_energy = epics_signal_rw(float, prefix + "CENTRE_ENERGY")
101
104
  self.high_energy = epics_signal_rw(float, prefix + "HIGH_ENERGY")
@@ -121,7 +124,7 @@ class AbstractAnalyserDriverIO(
121
124
  self._calculate_binding_energy_axis,
122
125
  "eV",
123
126
  energy_axis=self.energy_axis,
124
- excitation_energy=self.energy_source.energy,
127
+ excitation_energy=self.cached_excitation_energy,
125
128
  energy_mode=self.energy_mode,
126
129
  )
127
130
  self.angle_axis = self._create_angle_axis_signal(prefix)
@@ -134,34 +137,9 @@ class AbstractAnalyserDriverIO(
134
137
  iterations=self.iterations,
135
138
  )
136
139
 
137
- @AsyncStatus.wrap
138
- async def stage(self) -> None:
139
- await self.image_mode.set(ADImageMode.SINGLE)
140
- await super().stage()
141
-
142
- @AsyncStatus.wrap
143
- async def set(self, region: TAbstractBaseRegion):
144
- """
145
- Take a region object and setup the driver with it. If using a DualEnergySource,
146
- set it to use the source selected by the region. It also converts the region to
147
- kinetic mode before we move the driver signals to the region parameter values:
148
-
149
- Args:
150
- region: Contains the parameters to setup the driver for a scan.
151
- """
152
- if isinstance(self.energy_source, DualEnergySource):
153
- self.energy_source.selected_source.set(region.excitation_energy_source)
154
- excitation_energy = await self.energy_source.energy.get_value()
155
-
156
- # Switch to kinetic energy as epics doesn't support BINDING.
157
- ke_region = region.switch_energy_mode(EnergyMode.KINETIC, excitation_energy)
158
- await self._set_region(ke_region)
159
- # Set the true energy mode from original region so binding_energy_axis can be
160
- # calculated correctly.
161
- await self.energy_mode.set(region.energy_mode)
162
-
163
140
  @abstractmethod
164
- async def _set_region(self, ke_region: TAbstractBaseRegion):
141
+ @AsyncStatus.wrap
142
+ async def set(self, epics_region: TAbstractBaseRegion):
165
143
  """
166
144
  Move a group of signals defined in a region. Each implementation of this class
167
145
  is responsible for implementing this method correctly.
@@ -245,6 +223,9 @@ class AbstractAnalyserDriverIO(
245
223
  return float(np.sum(spectrum, dtype=np.float64))
246
224
 
247
225
 
226
+ GenericAnalyserDriverIO = AbstractAnalyserDriverIO[
227
+ GenericRegion, AnyAcqMode, AnyLensMode, AnyPsuMode, AnyPassEnergy
228
+ ]
248
229
  TAbstractAnalyserDriverIO = TypeVar(
249
230
  "TAbstractAnalyserDriverIO", bound=AbstractAnalyserDriverIO
250
231
  )
@@ -4,8 +4,3 @@ from ophyd_async.core import StrictEnum
4
4
  class EnergyMode(StrictEnum):
5
5
  KINETIC = "Kinetic"
6
6
  BINDING = "Binding"
7
-
8
-
9
- class SelectedSource(StrictEnum):
10
- SOURCE1 = "source1"
11
- SOURCE2 = "source2"
@@ -1,17 +1,30 @@
1
1
  import re
2
2
  from abc import ABC
3
3
  from collections.abc import Callable
4
- from typing import Generic, Self, TypeVar
4
+ from typing import Generic, Self, TypeAlias, TypeVar
5
5
 
6
+ from ophyd_async.core import StrictEnum, SupersetEnum
6
7
  from pydantic import BaseModel, Field, model_validator
7
8
 
8
- from dodal.devices.electron_analyser.abstract.types import (
9
- TAcquisitionMode,
10
- TLensMode,
11
- TPassEnergy,
9
+ from dodal.devices.electron_analyser.base.base_enums import EnergyMode
10
+ from dodal.devices.electron_analyser.base.base_util import (
11
+ to_binding_energy,
12
+ to_kinetic_energy,
12
13
  )
13
- from dodal.devices.electron_analyser.enums import EnergyMode, SelectedSource
14
- from dodal.devices.electron_analyser.util import to_binding_energy, to_kinetic_energy
14
+ from dodal.devices.selectable_source import SelectedSource
15
+
16
+ AnyAcqMode: TypeAlias = StrictEnum
17
+ AnyLensMode: TypeAlias = SupersetEnum | StrictEnum
18
+ AnyPassEnergy: TypeAlias = StrictEnum | float
19
+ AnyPsuMode: TypeAlias = SupersetEnum | StrictEnum
20
+
21
+ TAcquisitionMode = TypeVar("TAcquisitionMode", bound=AnyAcqMode)
22
+ # Allow SupersetEnum. Specs analysers can connect to Lens and Psu mode separately to the
23
+ # analyser which leaves the enum to either be "Not connected" OR the available enums
24
+ # when connected.
25
+ TLensMode = TypeVar("TLensMode", bound=AnyLensMode)
26
+ TPassEnergy = TypeVar("TPassEnergy", bound=AnyPassEnergy)
27
+ TPsuMode = TypeVar("TPsuMode", bound=AnyPsuMode)
15
28
 
16
29
 
17
30
  def java_to_python_case(java_str: str) -> str:
@@ -88,10 +101,13 @@ class AbstractBaseRegion(
88
101
  return self.energy_mode == EnergyMode.KINETIC
89
102
 
90
103
  def switch_energy_mode(
91
- self, energy_mode: EnergyMode, excitation_energy: float, copy: bool = True
104
+ self,
105
+ energy_mode: EnergyMode,
106
+ excitation_energy: float,
107
+ copy: bool = True,
92
108
  ) -> Self:
93
109
  """
94
- Switch region with to a new energy mode with a new energy mode: Kinetic or Binding.
110
+ Get a region with a new energy mode: Kinetic or Binding.
95
111
  It caculates new values for low_energy, centre_energy, high_energy, via the
96
112
  excitation enerrgy. It doesn't calculate anything if the region is already of
97
113
  the same energy mode.
@@ -100,8 +116,8 @@ class AbstractBaseRegion(
100
116
  energy_mode: Mode you want to switch the region to.
101
117
  excitation_energy: Energy conversion for low_energy, centre_energy, and
102
118
  high_energy for new energy mode.
103
- copy: Defaults to True. If true, create a copy of this region for the new
104
- energy_mode and return it. If False, alter this region for the
119
+ copy: Defaults to True. If true, create a copy of this region to alter for
120
+ the new energy_mode and return it. If False, alter this region for the
105
121
  energy_mode and return it self.
106
122
 
107
123
  Returns:
@@ -126,6 +142,25 @@ class AbstractBaseRegion(
126
142
 
127
143
  return switched_r
128
144
 
145
+ def prepare_for_epics(self, excitation_energy: float, copy: bool = True) -> Self:
146
+ """Prepares a region for epics by converting BINDING to KINETIC by calculating
147
+ new values for low_energy, centre_energy, and high_energy while also preserving
148
+ the original energy mode e.g mode BINDING will stay as BINDING.
149
+
150
+ Parameters:
151
+ excitation_energy: Energy conversion for low_energy, centre_energy, and
152
+ high_energy for new energy mode.
153
+ copy: Defaults to True. If true, create a copy of this region to alter to
154
+ calculate new energy values to return. If false, alter this region.
155
+ Returns:
156
+ Region with selected original energy mode and new calculated KINETIC energy
157
+ values for epics.
158
+ """
159
+ original_energy_mode = self.energy_mode
160
+ r = self.switch_energy_mode(EnergyMode.KINETIC, excitation_energy, copy)
161
+ r.energy_mode = original_energy_mode
162
+ return r
163
+
129
164
  @model_validator(mode="before")
130
165
  @classmethod
131
166
  def before_validation(cls, data: dict) -> dict:
@@ -133,6 +168,7 @@ class AbstractBaseRegion(
133
168
  return energy_mode_validation(data)
134
169
 
135
170
 
171
+ GenericRegion = AbstractBaseRegion[AnyAcqMode, AnyLensMode, AnyPassEnergy]
136
172
  TAbstractBaseRegion = TypeVar("TAbstractBaseRegion", bound=AbstractBaseRegion)
137
173
 
138
174
 
@@ -163,4 +199,5 @@ class AbstractBaseSequence(
163
199
  return next((region for region in self.regions if region.name == name), None)
164
200
 
165
201
 
202
+ GenericSequence = AbstractBaseSequence[GenericRegion]
166
203
  TAbstractBaseSequence = TypeVar("TAbstractBaseSequence", bound=AbstractBaseSequence)
@@ -1,4 +1,4 @@
1
- from dodal.devices.electron_analyser.enums import EnergyMode
1
+ from dodal.devices.electron_analyser.base.base_enums import EnergyMode
2
2
 
3
3
 
4
4
  def to_kinetic_energy(
@@ -3,14 +3,14 @@ from abc import abstractmethod
3
3
  from ophyd_async.core import (
4
4
  Reference,
5
5
  SignalR,
6
+ SignalRW,
6
7
  StandardReadable,
7
8
  StandardReadableFormat,
8
9
  derived_signal_r,
9
10
  soft_signal_r_and_setter,
10
- soft_signal_rw,
11
11
  )
12
12
 
13
- from dodal.devices.electron_analyser.enums import SelectedSource
13
+ from dodal.devices.selectable_source import SelectedSource, get_obj_from_selected_source
14
14
 
15
15
 
16
16
  class AbstractEnergySource(StandardReadable):
@@ -51,51 +51,52 @@ class EnergySource(AbstractEnergySource):
51
51
  return self._source_ref()
52
52
 
53
53
 
54
+ def get_float_from_selected_source(
55
+ selected: SelectedSource, s1: float, s2: float
56
+ ) -> float:
57
+ """Wrapper function to provide type hints for derived signal."""
58
+ return get_obj_from_selected_source(selected, s1, s2)
59
+
60
+
54
61
  class DualEnergySource(AbstractEnergySource):
55
62
  """
56
63
  Holds two EnergySource devices and provides a signal to read energy depending on
57
- which source is selected. This is controlled by a selected_source signal which can
58
- switch source using SelectedSource enum. Both sources energy is recorded in the
59
- read, the energy signal is used as a helper signal to know which source is being
60
- used.
64
+ which source is selected. The energy is the one that corrosponds to the
65
+ selected_source signal. For example, selected_source is source1 if selected_source
66
+ is at SelectedSource.SOURCE1 and vise versa for source2 and SelectedSource.SOURCE2.
61
67
  """
62
68
 
63
69
  def __init__(
64
- self, source1: SignalR[float], source2: SignalR[float], name: str = ""
70
+ self,
71
+ source1: SignalR[float],
72
+ source2: SignalR[float],
73
+ selected_source: SignalRW[SelectedSource],
74
+ name: str = "",
65
75
  ):
66
76
  """
67
77
  Args:
68
- source1: Default energy signal to select.
69
- source2: Secondary energy signal to select.
70
- name: name of this device.
78
+ source1: Energy source that corrosponds to SelectedSource.SOURCE1.
79
+ source2: Energy source that corrosponds to SelectedSource.SOURCE2.
80
+ selected_source: Signal that decides the active energy source.
81
+ name: Name of this device.
71
82
  """
72
83
 
84
+ self.selected_source_ref = Reference(selected_source)
73
85
  with self.add_children_as_readables():
74
- self.selected_source = soft_signal_rw(
75
- SelectedSource, initial_value=SelectedSource.SOURCE1
76
- )
77
86
  self.source1 = EnergySource(source1)
78
87
  self.source2 = EnergySource(source2)
79
88
 
80
89
  self._selected_energy = derived_signal_r(
81
- self._get_excitation_energy,
90
+ get_float_from_selected_source,
82
91
  "eV",
83
- selected_source=self.selected_source,
84
- source1=self.source1.energy,
85
- source2=self.source2.energy,
92
+ selected=self.selected_source_ref(),
93
+ s1=self.source1.energy,
94
+ s2=self.source2.energy,
86
95
  )
96
+ self.add_readables([selected_source])
87
97
 
88
98
  super().__init__(name)
89
99
 
90
- def _get_excitation_energy(
91
- self, selected_source: SelectedSource, source1: float, source2: float
92
- ) -> float:
93
- match selected_source:
94
- case SelectedSource.SOURCE1:
95
- return source1
96
- case SelectedSource.SOURCE2:
97
- return source2
98
-
99
100
  @property
100
101
  def energy(self) -> SignalR[float]:
101
102
  return self._selected_energy
@@ -1,7 +1,7 @@
1
- from .detector import SpecsDetector
2
- from .driver_io import SpecsAnalyserDriverIO
3
- from .enums import AcquisitionMode
4
- from .region import SpecsRegion, SpecsSequence
1
+ from .specs_detector import SpecsDetector
2
+ from .specs_driver_io import SpecsAnalyserDriverIO
3
+ from .specs_enums import AcquisitionMode
4
+ from .specs_region import SpecsRegion, SpecsSequence
5
5
 
6
6
  __all__ = [
7
7
  "SpecsDetector",
@@ -0,0 +1,47 @@
1
+ from typing import Generic
2
+
3
+ from dodal.devices.electron_analyser.base.base_controller import (
4
+ ElectronAnalyserController,
5
+ )
6
+ from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector
7
+ from dodal.devices.electron_analyser.base.base_region import TLensMode, TPsuMode
8
+ from dodal.devices.electron_analyser.base.energy_sources import AbstractEnergySource
9
+ from dodal.devices.electron_analyser.specs.specs_driver_io import SpecsAnalyserDriverIO
10
+ from dodal.devices.electron_analyser.specs.specs_region import (
11
+ SpecsRegion,
12
+ SpecsSequence,
13
+ )
14
+ from dodal.devices.fast_shutter import FastShutter
15
+ from dodal.devices.selectable_source import SourceSelector
16
+
17
+
18
+ class SpecsDetector(
19
+ ElectronAnalyserDetector[
20
+ SpecsSequence[TLensMode, TPsuMode],
21
+ SpecsAnalyserDriverIO[TLensMode, TPsuMode],
22
+ SpecsRegion[TLensMode, TPsuMode],
23
+ ],
24
+ Generic[TLensMode, TPsuMode],
25
+ ):
26
+ def __init__(
27
+ self,
28
+ prefix: str,
29
+ lens_mode_type: type[TLensMode],
30
+ psu_mode_type: type[TPsuMode],
31
+ energy_source: AbstractEnergySource,
32
+ shutter: FastShutter | None = None,
33
+ source_selector: SourceSelector | None = None,
34
+ name: str = "",
35
+ ):
36
+ # Save to class so takes part with connect()
37
+ self.driver = SpecsAnalyserDriverIO[TLensMode, TPsuMode](
38
+ prefix, lens_mode_type, psu_mode_type
39
+ )
40
+
41
+ controller = ElectronAnalyserController[
42
+ SpecsAnalyserDriverIO[TLensMode, TPsuMode], SpecsRegion[TLensMode, TPsuMode]
43
+ ](self.driver, energy_source, shutter, source_selector)
44
+
45
+ sequence_class = SpecsSequence[lens_mode_type, psu_mode_type]
46
+
47
+ super().__init__(sequence_class, controller, name)
@@ -4,22 +4,19 @@ from typing import Generic
4
4
  import numpy as np
5
5
  from ophyd_async.core import (
6
6
  Array1D,
7
+ AsyncStatus,
7
8
  SignalR,
8
9
  StandardReadableFormat,
9
10
  derived_signal_r,
10
11
  )
11
12
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
12
13
 
13
- from dodal.devices.electron_analyser.abstract.base_driver_io import (
14
+ from dodal.devices.electron_analyser.base.base_driver_io import (
14
15
  AbstractAnalyserDriverIO,
15
16
  )
16
- from dodal.devices.electron_analyser.abstract.types import TLensMode, TPsuMode
17
- from dodal.devices.electron_analyser.energy_sources import (
18
- DualEnergySource,
19
- EnergySource,
20
- )
21
- from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
22
- from dodal.devices.electron_analyser.specs.region import SpecsRegion
17
+ from dodal.devices.electron_analyser.base.base_region import TLensMode, TPsuMode
18
+ from dodal.devices.electron_analyser.specs.specs_enums import AcquisitionMode
19
+ from dodal.devices.electron_analyser.specs.specs_region import SpecsRegion
23
20
 
24
21
 
25
22
  class SpecsAnalyserDriverIO(
@@ -37,7 +34,6 @@ class SpecsAnalyserDriverIO(
37
34
  prefix: str,
38
35
  lens_mode_type: type[TLensMode],
39
36
  psu_mode_type: type[TPsuMode],
40
- energy_source: EnergySource | DualEnergySource,
41
37
  name: str = "",
42
38
  ) -> None:
43
39
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
@@ -59,29 +55,30 @@ class SpecsAnalyserDriverIO(
59
55
  lens_mode_type=lens_mode_type,
60
56
  psu_mode_type=psu_mode_type,
61
57
  pass_energy_type=float,
62
- energy_source=energy_source,
63
58
  name=name,
64
59
  )
65
60
 
66
- async def _set_region(self, ke_region: SpecsRegion[TLensMode, TPsuMode]):
61
+ @AsyncStatus.wrap
62
+ async def set(self, epics_region: SpecsRegion[TLensMode, TPsuMode]):
67
63
  await asyncio.gather(
68
- self.region_name.set(ke_region.name),
69
- self.low_energy.set(ke_region.low_energy),
70
- self.high_energy.set(ke_region.high_energy),
71
- self.slices.set(ke_region.slices),
72
- self.acquire_time.set(ke_region.acquire_time),
73
- self.lens_mode.set(ke_region.lens_mode),
74
- self.pass_energy.set(ke_region.pass_energy),
75
- self.iterations.set(ke_region.iterations),
76
- self.acquisition_mode.set(ke_region.acquisition_mode),
77
- self.snapshot_values.set(ke_region.values),
78
- self.psu_mode.set(ke_region.psu_mode),
64
+ self.region_name.set(epics_region.name),
65
+ self.low_energy.set(epics_region.low_energy),
66
+ self.high_energy.set(epics_region.high_energy),
67
+ self.slices.set(epics_region.slices),
68
+ self.acquire_time.set(epics_region.acquire_time),
69
+ self.lens_mode.set(epics_region.lens_mode),
70
+ self.pass_energy.set(epics_region.pass_energy),
71
+ self.iterations.set(epics_region.iterations),
72
+ self.acquisition_mode.set(epics_region.acquisition_mode),
73
+ self.snapshot_values.set(epics_region.values),
74
+ self.psu_mode.set(epics_region.psu_mode),
75
+ self.energy_mode.set(epics_region.energy_mode),
79
76
  )
80
- if ke_region.acquisition_mode == AcquisitionMode.FIXED_TRANSMISSION:
81
- await self.energy_step.set(ke_region.energy_step)
77
+ if epics_region.acquisition_mode == AcquisitionMode.FIXED_TRANSMISSION:
78
+ await self.energy_step.set(epics_region.energy_step)
82
79
 
83
- if ke_region.acquisition_mode == AcquisitionMode.FIXED_ENERGY:
84
- await self.centre_energy.set(ke_region.centre_energy)
80
+ if epics_region.acquisition_mode == AcquisitionMode.FIXED_ENERGY:
81
+ await self.centre_energy.set(epics_region.centre_energy)
85
82
 
86
83
  def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
87
84
  angle_axis = derived_signal_r(
@@ -2,12 +2,13 @@ from typing import Generic
2
2
 
3
3
  from pydantic import Field
4
4
 
5
- from dodal.devices.electron_analyser.abstract.base_region import (
5
+ from dodal.devices.electron_analyser.base.base_region import (
6
6
  AbstractBaseRegion,
7
7
  AbstractBaseSequence,
8
+ TLensMode,
9
+ TPsuMode,
8
10
  )
9
- from dodal.devices.electron_analyser.abstract.types import TLensMode, TPsuMode
10
- from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
11
+ from dodal.devices.electron_analyser.specs.specs_enums import AcquisitionMode
11
12
 
12
13
 
13
14
  class SpecsRegion(
@@ -1,7 +1,7 @@
1
- from .detector import VGScientaDetector
2
- from .driver_io import VGScientaAnalyserDriverIO
3
- from .enums import AcquisitionMode, DetectorMode
4
- from .region import VGScientaRegion, VGScientaSequence
1
+ from .vgscienta_detector import VGScientaDetector
2
+ from .vgscienta_driver_io import VGScientaAnalyserDriverIO
3
+ from .vgscienta_enums import AcquisitionMode, DetectorMode
4
+ from .vgscienta_region import VGScientaRegion, VGScientaSequence
5
5
 
6
6
  __all__ = [
7
7
  "VGScientaDetector",
@@ -0,0 +1,53 @@
1
+ from typing import Generic
2
+
3
+ from dodal.devices.electron_analyser.base.base_controller import (
4
+ ElectronAnalyserController,
5
+ )
6
+ from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector
7
+ from dodal.devices.electron_analyser.base.base_region import TLensMode, TPsuMode
8
+ from dodal.devices.electron_analyser.base.energy_sources import AbstractEnergySource
9
+ from dodal.devices.electron_analyser.vgscienta.vgscienta_driver_io import (
10
+ VGScientaAnalyserDriverIO,
11
+ )
12
+ from dodal.devices.electron_analyser.vgscienta.vgscienta_region import (
13
+ TPassEnergyEnum,
14
+ VGScientaRegion,
15
+ VGScientaSequence,
16
+ )
17
+ from dodal.devices.fast_shutter import FastShutter
18
+ from dodal.devices.selectable_source import SourceSelector
19
+
20
+
21
+ class VGScientaDetector(
22
+ ElectronAnalyserDetector[
23
+ VGScientaSequence[TLensMode, TPsuMode, TPassEnergyEnum],
24
+ VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum],
25
+ VGScientaRegion[TLensMode, TPassEnergyEnum],
26
+ ],
27
+ Generic[TLensMode, TPsuMode, TPassEnergyEnum],
28
+ ):
29
+ def __init__(
30
+ self,
31
+ prefix: str,
32
+ lens_mode_type: type[TLensMode],
33
+ psu_mode_type: type[TPsuMode],
34
+ pass_energy_type: type[TPassEnergyEnum],
35
+ energy_source: AbstractEnergySource,
36
+ shutter: FastShutter | None = None,
37
+ source_selector: SourceSelector | None = None,
38
+ name: str = "",
39
+ ):
40
+ # Save to class so takes part with connect()
41
+ self.driver = VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum](
42
+ prefix, lens_mode_type, psu_mode_type, pass_energy_type
43
+ )
44
+
45
+ controller = ElectronAnalyserController[
46
+ VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum],
47
+ VGScientaRegion[TLensMode, TPassEnergyEnum],
48
+ ](self.driver, energy_source, shutter, source_selector)
49
+
50
+ sequence_class = VGScientaSequence[
51
+ lens_mode_type, psu_mode_type, pass_energy_type
52
+ ]
53
+ super().__init__(sequence_class, controller, name)
@@ -4,28 +4,22 @@ from typing import Generic
4
4
  import numpy as np
5
5
  from ophyd_async.core import (
6
6
  Array1D,
7
+ AsyncStatus,
7
8
  SignalR,
8
9
  StandardReadableFormat,
9
10
  )
10
11
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
11
12
 
12
- from dodal.devices.electron_analyser.abstract.base_driver_io import (
13
+ from dodal.devices.electron_analyser.base.base_driver_io import (
13
14
  AbstractAnalyserDriverIO,
14
15
  )
15
- from dodal.devices.electron_analyser.abstract.types import (
16
- TLensMode,
17
- TPassEnergyEnum,
18
- TPsuMode,
19
- )
20
- from dodal.devices.electron_analyser.energy_sources import (
21
- DualEnergySource,
22
- EnergySource,
23
- )
24
- from dodal.devices.electron_analyser.vgscienta.enums import (
16
+ from dodal.devices.electron_analyser.base.base_region import TLensMode, TPsuMode
17
+ from dodal.devices.electron_analyser.vgscienta.vgscienta_enums import (
25
18
  AcquisitionMode,
26
19
  DetectorMode,
27
20
  )
28
- from dodal.devices.electron_analyser.vgscienta.region import (
21
+ from dodal.devices.electron_analyser.vgscienta.vgscienta_region import (
22
+ TPassEnergyEnum,
29
23
  VGScientaRegion,
30
24
  )
31
25
 
@@ -46,7 +40,6 @@ class VGScientaAnalyserDriverIO(
46
40
  lens_mode_type: type[TLensMode],
47
41
  psu_mode_type: type[TPsuMode],
48
42
  pass_energy_type: type[TPassEnergyEnum],
49
- energy_source: EnergySource | DualEnergySource,
50
43
  name: str = "",
51
44
  ) -> None:
52
45
  with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
@@ -67,28 +60,29 @@ class VGScientaAnalyserDriverIO(
67
60
  lens_mode_type,
68
61
  psu_mode_type,
69
62
  pass_energy_type,
70
- energy_source,
71
63
  name,
72
64
  )
73
65
 
74
- async def _set_region(self, ke_region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
66
+ @AsyncStatus.wrap
67
+ async def set(self, epics_region: VGScientaRegion[TLensMode, TPassEnergyEnum]):
75
68
  await asyncio.gather(
76
- self.region_name.set(ke_region.name),
77
- self.low_energy.set(ke_region.low_energy),
78
- self.centre_energy.set(ke_region.centre_energy),
79
- self.high_energy.set(ke_region.high_energy),
80
- self.slices.set(ke_region.slices),
81
- self.lens_mode.set(ke_region.lens_mode),
82
- self.pass_energy.set(ke_region.pass_energy),
83
- self.iterations.set(ke_region.iterations),
84
- self.acquire_time.set(ke_region.acquire_time),
85
- self.acquisition_mode.set(ke_region.acquisition_mode),
86
- self.energy_step.set(ke_region.energy_step),
87
- self.detector_mode.set(ke_region.detector_mode),
88
- self.region_min_x.set(ke_region.min_x),
89
- self.region_size_x.set(ke_region.size_x),
90
- self.region_min_y.set(ke_region.min_y),
91
- self.region_size_y.set(ke_region.size_y),
69
+ self.region_name.set(epics_region.name),
70
+ self.low_energy.set(epics_region.low_energy),
71
+ self.centre_energy.set(epics_region.centre_energy),
72
+ self.high_energy.set(epics_region.high_energy),
73
+ self.slices.set(epics_region.slices),
74
+ self.lens_mode.set(epics_region.lens_mode),
75
+ self.pass_energy.set(epics_region.pass_energy),
76
+ self.iterations.set(epics_region.iterations),
77
+ self.acquire_time.set(epics_region.acquire_time),
78
+ self.acquisition_mode.set(epics_region.acquisition_mode),
79
+ self.energy_step.set(epics_region.energy_step),
80
+ self.detector_mode.set(epics_region.detector_mode),
81
+ self.region_min_x.set(epics_region.min_x),
82
+ self.region_size_x.set(epics_region.size_x),
83
+ self.region_min_y.set(epics_region.min_y),
84
+ self.region_size_y.set(epics_region.size_y),
85
+ self.energy_mode.set(epics_region.energy_mode),
92
86
  )
93
87
 
94
88
  def _create_energy_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]: