dls-dodal 1.66.0__py3-none-any.whl → 1.68.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.
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/METADATA +2 -2
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/RECORD +75 -65
- dodal/_version.py +2 -2
- dodal/beamlines/b07.py +1 -1
- dodal/beamlines/b07_1.py +1 -1
- dodal/beamlines/i03.py +92 -208
- dodal/beamlines/i04.py +22 -1
- dodal/beamlines/i05.py +1 -1
- dodal/beamlines/i06.py +1 -1
- dodal/beamlines/i09.py +1 -1
- dodal/beamlines/i09_1.py +27 -3
- dodal/beamlines/i09_2.py +58 -2
- dodal/beamlines/i10_optics.py +44 -25
- dodal/beamlines/i16.py +23 -0
- dodal/beamlines/i17.py +7 -3
- dodal/beamlines/i19_1.py +26 -14
- dodal/beamlines/i19_2.py +49 -38
- dodal/beamlines/i21.py +61 -2
- dodal/beamlines/i22.py +16 -1
- dodal/beamlines/p60.py +1 -1
- dodal/beamlines/training_rig.py +0 -16
- dodal/cli.py +26 -12
- dodal/common/coordination.py +3 -2
- dodal/device_manager.py +604 -0
- dodal/devices/cryostream.py +28 -57
- dodal/devices/eiger.py +41 -27
- dodal/devices/electron_analyser/__init__.py +0 -33
- dodal/devices/electron_analyser/base/__init__.py +58 -0
- dodal/devices/electron_analyser/base/base_controller.py +73 -0
- dodal/devices/electron_analyser/base/base_detector.py +214 -0
- dodal/devices/electron_analyser/{abstract → base}/base_driver_io.py +23 -42
- dodal/devices/electron_analyser/{abstract → base}/base_region.py +47 -11
- dodal/devices/electron_analyser/{util.py → base/base_util.py} +1 -1
- dodal/devices/electron_analyser/{energy_sources.py → base/energy_sources.py} +1 -1
- dodal/devices/electron_analyser/specs/__init__.py +4 -4
- dodal/devices/electron_analyser/specs/specs_detector.py +46 -0
- dodal/devices/electron_analyser/specs/{driver_io.py → specs_driver_io.py} +23 -26
- dodal/devices/electron_analyser/specs/{region.py → specs_region.py} +4 -3
- dodal/devices/electron_analyser/vgscienta/__init__.py +4 -4
- dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +52 -0
- dodal/devices/electron_analyser/vgscienta/{driver_io.py → vgscienta_driver_io.py} +25 -31
- dodal/devices/electron_analyser/vgscienta/{region.py → vgscienta_region.py} +6 -6
- dodal/devices/i04/max_pixel.py +38 -0
- dodal/devices/i09_1_shared/__init__.py +8 -1
- dodal/devices/i09_1_shared/hard_energy.py +112 -0
- dodal/devices/i09_2_shared/__init__.py +0 -0
- dodal/devices/i09_2_shared/i09_apple2.py +14 -0
- dodal/devices/i10/i10_apple2.py +24 -22
- dodal/devices/i17/i17_apple2.py +32 -20
- dodal/devices/i19/access_controlled/attenuator_motor_squad.py +61 -0
- dodal/devices/i19/access_controlled/blueapi_device.py +9 -1
- dodal/devices/i19/access_controlled/shutter.py +2 -4
- dodal/devices/i21/__init__.py +3 -1
- dodal/devices/insertion_device/__init__.py +58 -0
- dodal/devices/{apple2_undulator.py → insertion_device/apple2_undulator.py} +102 -44
- dodal/devices/insertion_device/energy_motor_lookup.py +88 -0
- dodal/devices/insertion_device/id_enum.py +17 -0
- dodal/devices/insertion_device/lookup_table_models.py +317 -0
- dodal/devices/motors.py +14 -0
- dodal/devices/robot.py +16 -11
- dodal/plans/__init__.py +1 -1
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +2 -4
- dodal/testing/electron_analyser/device_factory.py +4 -4
- dodal/testing/fixtures/devices/__init__.py +0 -0
- dodal/testing/fixtures/devices/apple2.py +78 -0
- dodal/testing/fixtures/run_engine.py +4 -0
- dodal/utils.py +6 -3
- dodal/devices/electron_analyser/abstract/__init__.py +0 -25
- dodal/devices/electron_analyser/abstract/base_detector.py +0 -63
- dodal/devices/electron_analyser/abstract/types.py +0 -12
- dodal/devices/electron_analyser/detector.py +0 -143
- dodal/devices/electron_analyser/specs/detector.py +0 -34
- dodal/devices/electron_analyser/types.py +0 -57
- dodal/devices/electron_analyser/vgscienta/detector.py +0 -48
- dodal/devices/util/lookup_tables_apple2.py +0 -390
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.66.0.dist-info → dls_dodal-1.68.0.dist-info}/top_level.txt +0 -0
- /dodal/devices/electron_analyser/{enums.py → base/base_enums.py} +0 -0
- /dodal/devices/electron_analyser/specs/{enums.py → specs_enums.py} +0 -0
- /dodal/devices/electron_analyser/vgscienta/{enums.py → vgscienta_enums.py} +0 -0
- /dodal/plans/{scanspec.py → spec_path.py} +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from typing import Generic, TypeVar
|
|
2
|
+
|
|
3
|
+
from bluesky.protocols import Reading, Stageable, Triggerable
|
|
4
|
+
from event_model import DataKey
|
|
5
|
+
from ophyd_async.core import (
|
|
6
|
+
AsyncConfigurable,
|
|
7
|
+
AsyncReadable,
|
|
8
|
+
AsyncStatus,
|
|
9
|
+
Device,
|
|
10
|
+
TriggerInfo,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from dodal.common.data_util import load_json_file_to_class
|
|
14
|
+
from dodal.devices.electron_analyser.base.base_controller import (
|
|
15
|
+
ElectronAnalyserController,
|
|
16
|
+
)
|
|
17
|
+
from dodal.devices.electron_analyser.base.base_driver_io import (
|
|
18
|
+
GenericAnalyserDriverIO,
|
|
19
|
+
TAbstractAnalyserDriverIO,
|
|
20
|
+
)
|
|
21
|
+
from dodal.devices.electron_analyser.base.base_region import (
|
|
22
|
+
GenericRegion,
|
|
23
|
+
GenericSequence,
|
|
24
|
+
TAbstractBaseRegion,
|
|
25
|
+
TAbstractBaseSequence,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BaseElectronAnalyserDetector(
|
|
30
|
+
Device,
|
|
31
|
+
Triggerable,
|
|
32
|
+
AsyncReadable,
|
|
33
|
+
AsyncConfigurable,
|
|
34
|
+
Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Detector for data acquisition of electron analyser. Can only acquire using settings
|
|
38
|
+
already configured for the device.
|
|
39
|
+
|
|
40
|
+
If possible, this should be changed to inherit from a StandardDetector. Currently,
|
|
41
|
+
StandardDetector forces you to use a file writer which doesn't apply here.
|
|
42
|
+
See issue https://github.com/bluesky/ophyd-async/issues/888
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
controller: ElectronAnalyserController[
|
|
48
|
+
TAbstractAnalyserDriverIO, TAbstractBaseRegion
|
|
49
|
+
],
|
|
50
|
+
name: str = "",
|
|
51
|
+
):
|
|
52
|
+
self._controller = controller
|
|
53
|
+
super().__init__(name)
|
|
54
|
+
|
|
55
|
+
@AsyncStatus.wrap
|
|
56
|
+
async def set(self, region: TAbstractBaseRegion) -> None:
|
|
57
|
+
await self._controller.setup_with_region(region)
|
|
58
|
+
|
|
59
|
+
@AsyncStatus.wrap
|
|
60
|
+
async def trigger(self) -> None:
|
|
61
|
+
await self._controller.prepare(TriggerInfo())
|
|
62
|
+
await self._controller.arm()
|
|
63
|
+
await self._controller.wait_for_idle()
|
|
64
|
+
|
|
65
|
+
async def read(self) -> dict[str, Reading]:
|
|
66
|
+
return await self._controller.driver.read()
|
|
67
|
+
|
|
68
|
+
async def describe(self) -> dict[str, DataKey]:
|
|
69
|
+
data = await self._controller.driver.describe()
|
|
70
|
+
# Correct the shape for image
|
|
71
|
+
prefix = self._controller.driver.name + "-"
|
|
72
|
+
energy_size = len(await self._controller.driver.energy_axis.get_value())
|
|
73
|
+
angle_size = len(await self._controller.driver.angle_axis.get_value())
|
|
74
|
+
data[prefix + "image"]["shape"] = [angle_size, energy_size]
|
|
75
|
+
return data
|
|
76
|
+
|
|
77
|
+
async def read_configuration(self) -> dict[str, Reading]:
|
|
78
|
+
return await self._controller.driver.read_configuration()
|
|
79
|
+
|
|
80
|
+
async def describe_configuration(self) -> dict[str, DataKey]:
|
|
81
|
+
return await self._controller.driver.describe_configuration()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
GenericBaseElectronAnalyserDetector = BaseElectronAnalyserDetector[
|
|
85
|
+
GenericAnalyserDriverIO, GenericRegion
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class ElectronAnalyserRegionDetector(
|
|
90
|
+
BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
|
|
91
|
+
Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
|
|
92
|
+
):
|
|
93
|
+
"""
|
|
94
|
+
Extends electron analyser detector to configure specific region settings before data
|
|
95
|
+
acquisition. It is designed to only exist inside a plan.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
controller: ElectronAnalyserController[
|
|
101
|
+
TAbstractAnalyserDriverIO, TAbstractBaseRegion
|
|
102
|
+
],
|
|
103
|
+
region: TAbstractBaseRegion,
|
|
104
|
+
name: str = "",
|
|
105
|
+
):
|
|
106
|
+
self.region = region
|
|
107
|
+
super().__init__(controller, name)
|
|
108
|
+
|
|
109
|
+
@AsyncStatus.wrap
|
|
110
|
+
async def trigger(self) -> None:
|
|
111
|
+
# Configure region parameters on the driver first before data collection.
|
|
112
|
+
await self.set(self.region)
|
|
113
|
+
await super().trigger()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
GenericElectronAnalyserRegionDetector = ElectronAnalyserRegionDetector[
|
|
117
|
+
GenericAnalyserDriverIO, GenericRegion
|
|
118
|
+
]
|
|
119
|
+
TElectronAnalyserRegionDetector = TypeVar(
|
|
120
|
+
"TElectronAnalyserRegionDetector",
|
|
121
|
+
bound=ElectronAnalyserRegionDetector,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class ElectronAnalyserDetector(
|
|
126
|
+
BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
|
|
127
|
+
Stageable,
|
|
128
|
+
Generic[TAbstractBaseSequence, TAbstractAnalyserDriverIO, TAbstractBaseRegion],
|
|
129
|
+
):
|
|
130
|
+
"""
|
|
131
|
+
Electron analyser detector with the additional functionality to load a sequence file
|
|
132
|
+
and create a list of temporary ElectronAnalyserRegionDetector objects. These will
|
|
133
|
+
setup configured region settings before data acquisition.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
sequence_class: type[TAbstractBaseSequence],
|
|
139
|
+
controller: ElectronAnalyserController[
|
|
140
|
+
TAbstractAnalyserDriverIO, TAbstractBaseRegion
|
|
141
|
+
],
|
|
142
|
+
name: str = "",
|
|
143
|
+
):
|
|
144
|
+
self._sequence_class = sequence_class
|
|
145
|
+
super().__init__(controller, name)
|
|
146
|
+
|
|
147
|
+
@AsyncStatus.wrap
|
|
148
|
+
async def stage(self) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Prepare the detector for use by ensuring it is idle and ready.
|
|
151
|
+
|
|
152
|
+
This method asynchronously stages the detector by first disarming the controller
|
|
153
|
+
to ensure the detector is not actively acquiring data, then invokes the driver's
|
|
154
|
+
stage procedure. This ensures the detector is in a known, ready state
|
|
155
|
+
before use.
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
Any exceptions raised by the driver's stage or controller's disarm methods.
|
|
159
|
+
"""
|
|
160
|
+
await self._controller.disarm()
|
|
161
|
+
|
|
162
|
+
@AsyncStatus.wrap
|
|
163
|
+
async def unstage(self) -> None:
|
|
164
|
+
"""Disarm the detector."""
|
|
165
|
+
await self._controller.disarm()
|
|
166
|
+
|
|
167
|
+
def load_sequence(self, filename: str) -> TAbstractBaseSequence:
|
|
168
|
+
"""
|
|
169
|
+
Load the sequence data from a provided json file into a sequence class.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
filename: Path to the sequence file containing the region data.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Pydantic model representing the sequence file.
|
|
176
|
+
"""
|
|
177
|
+
return load_json_file_to_class(self._sequence_class, filename)
|
|
178
|
+
|
|
179
|
+
def create_region_detector_list(
|
|
180
|
+
self, filename: str, enabled_only=True
|
|
181
|
+
) -> list[
|
|
182
|
+
ElectronAnalyserRegionDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion]
|
|
183
|
+
]:
|
|
184
|
+
"""
|
|
185
|
+
Create a list of detectors equal to the number of regions in a sequence file.
|
|
186
|
+
Each detector is responsible for setting up a specific region.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
filename: Path to the sequence file containing the region data.
|
|
190
|
+
enabled_only: If true, only include the region if enabled is True.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
List of ElectronAnalyserRegionDetector, equal to the number of regions in
|
|
194
|
+
the sequence file.
|
|
195
|
+
"""
|
|
196
|
+
seq = self.load_sequence(filename)
|
|
197
|
+
regions: list[TAbstractBaseRegion] = (
|
|
198
|
+
seq.get_enabled_regions() if enabled_only else seq.regions
|
|
199
|
+
)
|
|
200
|
+
return [
|
|
201
|
+
ElectronAnalyserRegionDetector[
|
|
202
|
+
TAbstractAnalyserDriverIO, TAbstractBaseRegion
|
|
203
|
+
](self._controller, r, self.name + "_" + r.name)
|
|
204
|
+
for r in regions
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
GenericElectronAnalyserDetector = ElectronAnalyserDetector[
|
|
209
|
+
GenericSequence, GenericAnalyserDriverIO, GenericRegion
|
|
210
|
+
]
|
|
211
|
+
TElectronAnalyserDetector = TypeVar(
|
|
212
|
+
"TElectronAnalyserDetector",
|
|
213
|
+
bound=ElectronAnalyserDetector,
|
|
214
|
+
)
|
|
@@ -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
|
|
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.
|
|
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.
|
|
32
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
)
|
|
@@ -1,17 +1,29 @@
|
|
|
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.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
from dodal.devices.electron_analyser.base.base_enums import EnergyMode, SelectedSource
|
|
10
|
+
from dodal.devices.electron_analyser.base.base_util import (
|
|
11
|
+
to_binding_energy,
|
|
12
|
+
to_kinetic_energy,
|
|
12
13
|
)
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
AnyAcqMode: TypeAlias = StrictEnum
|
|
16
|
+
AnyLensMode: TypeAlias = SupersetEnum | StrictEnum
|
|
17
|
+
AnyPassEnergy: TypeAlias = StrictEnum | float
|
|
18
|
+
AnyPsuMode: TypeAlias = SupersetEnum | StrictEnum
|
|
19
|
+
|
|
20
|
+
TAcquisitionMode = TypeVar("TAcquisitionMode", bound=AnyAcqMode)
|
|
21
|
+
# Allow SupersetEnum. Specs analysers can connect to Lens and Psu mode separately to the
|
|
22
|
+
# analyser which leaves the enum to either be "Not connected" OR the available enums
|
|
23
|
+
# when connected.
|
|
24
|
+
TLensMode = TypeVar("TLensMode", bound=AnyLensMode)
|
|
25
|
+
TPassEnergy = TypeVar("TPassEnergy", bound=AnyPassEnergy)
|
|
26
|
+
TPsuMode = TypeVar("TPsuMode", bound=AnyPsuMode)
|
|
15
27
|
|
|
16
28
|
|
|
17
29
|
def java_to_python_case(java_str: str) -> str:
|
|
@@ -88,10 +100,13 @@ class AbstractBaseRegion(
|
|
|
88
100
|
return self.energy_mode == EnergyMode.KINETIC
|
|
89
101
|
|
|
90
102
|
def switch_energy_mode(
|
|
91
|
-
self,
|
|
103
|
+
self,
|
|
104
|
+
energy_mode: EnergyMode,
|
|
105
|
+
excitation_energy: float,
|
|
106
|
+
copy: bool = True,
|
|
92
107
|
) -> Self:
|
|
93
108
|
"""
|
|
94
|
-
|
|
109
|
+
Get a region with a new energy mode: Kinetic or Binding.
|
|
95
110
|
It caculates new values for low_energy, centre_energy, high_energy, via the
|
|
96
111
|
excitation enerrgy. It doesn't calculate anything if the region is already of
|
|
97
112
|
the same energy mode.
|
|
@@ -100,8 +115,8 @@ class AbstractBaseRegion(
|
|
|
100
115
|
energy_mode: Mode you want to switch the region to.
|
|
101
116
|
excitation_energy: Energy conversion for low_energy, centre_energy, and
|
|
102
117
|
high_energy for new energy mode.
|
|
103
|
-
copy: Defaults to True. If true, create a copy of this region
|
|
104
|
-
energy_mode and return it. If False, alter this region for the
|
|
118
|
+
copy: Defaults to True. If true, create a copy of this region to alter for
|
|
119
|
+
the new energy_mode and return it. If False, alter this region for the
|
|
105
120
|
energy_mode and return it self.
|
|
106
121
|
|
|
107
122
|
Returns:
|
|
@@ -126,6 +141,25 @@ class AbstractBaseRegion(
|
|
|
126
141
|
|
|
127
142
|
return switched_r
|
|
128
143
|
|
|
144
|
+
def prepare_for_epics(self, excitation_energy: float, copy: bool = True) -> Self:
|
|
145
|
+
"""Prepares a region for epics by converting BINDING to KINETIC by calculating
|
|
146
|
+
new values for low_energy, centre_energy, and high_energy while also preserving
|
|
147
|
+
the original energy mode e.g mode BINDING will stay as BINDING.
|
|
148
|
+
|
|
149
|
+
Parameters:
|
|
150
|
+
excitation_energy: Energy conversion for low_energy, centre_energy, and
|
|
151
|
+
high_energy for new energy mode.
|
|
152
|
+
copy: Defaults to True. If true, create a copy of this region to alter to
|
|
153
|
+
calculate new energy values to return. If false, alter this region.
|
|
154
|
+
Returns:
|
|
155
|
+
Region with selected original energy mode and new calculated KINETIC energy
|
|
156
|
+
values for epics.
|
|
157
|
+
"""
|
|
158
|
+
original_energy_mode = self.energy_mode
|
|
159
|
+
r = self.switch_energy_mode(EnergyMode.KINETIC, excitation_energy, copy)
|
|
160
|
+
r.energy_mode = original_energy_mode
|
|
161
|
+
return r
|
|
162
|
+
|
|
129
163
|
@model_validator(mode="before")
|
|
130
164
|
@classmethod
|
|
131
165
|
def before_validation(cls, data: dict) -> dict:
|
|
@@ -133,6 +167,7 @@ class AbstractBaseRegion(
|
|
|
133
167
|
return energy_mode_validation(data)
|
|
134
168
|
|
|
135
169
|
|
|
170
|
+
GenericRegion = AbstractBaseRegion[AnyAcqMode, AnyLensMode, AnyPassEnergy]
|
|
136
171
|
TAbstractBaseRegion = TypeVar("TAbstractBaseRegion", bound=AbstractBaseRegion)
|
|
137
172
|
|
|
138
173
|
|
|
@@ -163,4 +198,5 @@ class AbstractBaseSequence(
|
|
|
163
198
|
return next((region for region in self.regions if region.name == name), None)
|
|
164
199
|
|
|
165
200
|
|
|
201
|
+
GenericSequence = AbstractBaseSequence[GenericRegion]
|
|
166
202
|
TAbstractBaseSequence = TypeVar("TAbstractBaseSequence", bound=AbstractBaseSequence)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
3
|
-
from .
|
|
4
|
-
from .
|
|
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,46 @@
|
|
|
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 (
|
|
9
|
+
DualEnergySource,
|
|
10
|
+
EnergySource,
|
|
11
|
+
)
|
|
12
|
+
from dodal.devices.electron_analyser.specs.specs_driver_io import SpecsAnalyserDriverIO
|
|
13
|
+
from dodal.devices.electron_analyser.specs.specs_region import (
|
|
14
|
+
SpecsRegion,
|
|
15
|
+
SpecsSequence,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SpecsDetector(
|
|
20
|
+
ElectronAnalyserDetector[
|
|
21
|
+
SpecsSequence[TLensMode, TPsuMode],
|
|
22
|
+
SpecsAnalyserDriverIO[TLensMode, TPsuMode],
|
|
23
|
+
SpecsRegion[TLensMode, TPsuMode],
|
|
24
|
+
],
|
|
25
|
+
Generic[TLensMode, TPsuMode],
|
|
26
|
+
):
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
prefix: str,
|
|
30
|
+
lens_mode_type: type[TLensMode],
|
|
31
|
+
psu_mode_type: type[TPsuMode],
|
|
32
|
+
energy_source: DualEnergySource | EnergySource,
|
|
33
|
+
name: str = "",
|
|
34
|
+
):
|
|
35
|
+
# Save to class so takes part with connect()
|
|
36
|
+
self.driver = SpecsAnalyserDriverIO[TLensMode, TPsuMode](
|
|
37
|
+
prefix, lens_mode_type, psu_mode_type
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
controller = ElectronAnalyserController[
|
|
41
|
+
SpecsAnalyserDriverIO[TLensMode, TPsuMode], SpecsRegion[TLensMode, TPsuMode]
|
|
42
|
+
](self.driver, energy_source, 0)
|
|
43
|
+
|
|
44
|
+
sequence_class = SpecsSequence[lens_mode_type, psu_mode_type]
|
|
45
|
+
|
|
46
|
+
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.
|
|
14
|
+
from dodal.devices.electron_analyser.base.base_driver_io import (
|
|
14
15
|
AbstractAnalyserDriverIO,
|
|
15
16
|
)
|
|
16
|
-
from dodal.devices.electron_analyser.
|
|
17
|
-
from dodal.devices.electron_analyser.
|
|
18
|
-
|
|
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
|
-
|
|
61
|
+
@AsyncStatus.wrap
|
|
62
|
+
async def set(self, epics_region: SpecsRegion[TLensMode, TPsuMode]):
|
|
67
63
|
await asyncio.gather(
|
|
68
|
-
self.region_name.set(
|
|
69
|
-
self.low_energy.set(
|
|
70
|
-
self.high_energy.set(
|
|
71
|
-
self.slices.set(
|
|
72
|
-
self.acquire_time.set(
|
|
73
|
-
self.lens_mode.set(
|
|
74
|
-
self.pass_energy.set(
|
|
75
|
-
self.iterations.set(
|
|
76
|
-
self.acquisition_mode.set(
|
|
77
|
-
self.snapshot_values.set(
|
|
78
|
-
self.psu_mode.set(
|
|
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
|
|
81
|
-
await self.energy_step.set(
|
|
77
|
+
if epics_region.acquisition_mode == AcquisitionMode.FIXED_TRANSMISSION:
|
|
78
|
+
await self.energy_step.set(epics_region.energy_step)
|
|
82
79
|
|
|
83
|
-
if
|
|
84
|
-
await self.centre_energy.set(
|
|
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.
|
|
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.
|
|
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 .
|
|
2
|
-
from .
|
|
3
|
-
from .
|
|
4
|
-
from .
|
|
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",
|