dls-dodal 1.48.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.
- {dls_dodal-1.48.0.dist-info → dls_dodal-1.49.0.dist-info}/METADATA +2 -1
- {dls_dodal-1.48.0.dist-info → dls_dodal-1.49.0.dist-info}/RECORD +39 -29
- dodal/_version.py +2 -2
- dodal/beamlines/aithre.py +15 -0
- dodal/beamlines/b16.py +65 -0
- dodal/beamlines/b18.py +38 -0
- dodal/beamlines/i10.py +41 -233
- dodal/beamlines/k11.py +35 -0
- dodal/common/beamlines/device_helpers.py +1 -0
- dodal/devices/apple2_undulator.py +257 -136
- dodal/devices/b16/__init__.py +0 -0
- dodal/devices/b16/detector.py +34 -0
- dodal/devices/bimorph_mirror.py +29 -36
- dodal/devices/electron_analyser/__init__.py +12 -2
- dodal/devices/electron_analyser/abstract/base_detector.py +3 -128
- dodal/devices/electron_analyser/abstract/base_driver_io.py +8 -3
- dodal/devices/electron_analyser/abstract/base_region.py +6 -3
- dodal/devices/electron_analyser/detector.py +141 -0
- dodal/devices/electron_analyser/enums.py +6 -0
- dodal/devices/electron_analyser/specs/__init__.py +2 -0
- dodal/devices/electron_analyser/specs/detector.py +1 -1
- dodal/devices/electron_analyser/specs/driver_io.py +4 -5
- dodal/devices/electron_analyser/specs/enums.py +8 -0
- dodal/devices/electron_analyser/specs/region.py +3 -2
- dodal/devices/electron_analyser/types.py +30 -4
- dodal/devices/electron_analyser/util.py +1 -1
- dodal/devices/electron_analyser/vgscienta/__init__.py +2 -0
- dodal/devices/electron_analyser/vgscienta/detector.py +1 -1
- dodal/devices/electron_analyser/vgscienta/driver_io.py +2 -1
- dodal/devices/electron_analyser/vgscienta/enums.py +19 -0
- dodal/devices/electron_analyser/vgscienta/region.py +7 -22
- dodal/devices/i10/__init__.py +0 -0
- dodal/devices/i10/i10_apple2.py +181 -126
- dodal/devices/i22/nxsas.py +1 -1
- dodal/devices/oav/oav_detector.py +45 -7
- {dls_dodal-1.48.0.dist-info → dls_dodal-1.49.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.48.0.dist-info → dls_dodal-1.49.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.48.0.dist-info → dls_dodal-1.49.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.48.0.dist-info → dls_dodal-1.49.0.dist-info}/top_level.txt +0 -0
dodal/devices/bimorph_mirror.py
CHANGED
|
@@ -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,
|
|
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[
|
|
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:
|
|
107
|
-
"""Sets bimorph voltages in
|
|
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:
|
|
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
|
|
106
|
+
if len(value) != len(self.channels):
|
|
116
107
|
raise ValueError(
|
|
117
|
-
f"
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
135
|
+
self.channels[i + 1].output_voltage,
|
|
136
|
+
target,
|
|
137
137
|
timeout=DEFAULT_TIMEOUT,
|
|
138
138
|
)
|
|
139
|
-
for i, target in value
|
|
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,10 +1,16 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .detector import (
|
|
2
2
|
ElectronAnalyserDetector,
|
|
3
3
|
ElectronAnalyserRegionDetector,
|
|
4
4
|
TElectronAnalyserDetector,
|
|
5
5
|
TElectronAnalyserRegionDetector,
|
|
6
6
|
)
|
|
7
|
-
from .
|
|
7
|
+
from .enums import EnergyMode
|
|
8
|
+
from .types import (
|
|
9
|
+
ElectronAnalyserDetectorImpl,
|
|
10
|
+
ElectronAnalyserDriverImpl,
|
|
11
|
+
GenericElectronAnalyserDetector,
|
|
12
|
+
GenericElectronAnalyserRegionDetector,
|
|
13
|
+
)
|
|
8
14
|
from .util import to_binding_energy, to_kinetic_energy
|
|
9
15
|
|
|
10
16
|
__all__ = [
|
|
@@ -12,7 +18,11 @@ __all__ = [
|
|
|
12
18
|
"to_kinetic_energy",
|
|
13
19
|
"EnergyMode",
|
|
14
20
|
"ElectronAnalyserDetector",
|
|
21
|
+
"ElectronAnalyserDetectorImpl",
|
|
22
|
+
"ElectronAnalyserDriverImpl",
|
|
15
23
|
"TElectronAnalyserDetector",
|
|
16
24
|
"ElectronAnalyserRegionDetector",
|
|
17
25
|
"TElectronAnalyserRegionDetector",
|
|
26
|
+
"GenericElectronAnalyserDetector",
|
|
27
|
+
"GenericElectronAnalyserRegionDetector",
|
|
18
28
|
]
|
|
@@ -1,30 +1,23 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from abc import abstractmethod
|
|
3
|
-
from typing import Generic
|
|
3
|
+
from typing import Generic
|
|
4
4
|
|
|
5
|
-
from bluesky.protocols import
|
|
5
|
+
from bluesky.protocols import Reading, Stageable, Triggerable
|
|
6
6
|
from event_model import DataKey
|
|
7
7
|
from ophyd_async.core import (
|
|
8
8
|
AsyncConfigurable,
|
|
9
9
|
AsyncReadable,
|
|
10
10
|
AsyncStatus,
|
|
11
11
|
Device,
|
|
12
|
-
Reference,
|
|
13
12
|
)
|
|
14
13
|
from ophyd_async.epics.adcore import (
|
|
15
14
|
ADBaseController,
|
|
16
15
|
)
|
|
17
|
-
from ophyd_async.epics.motor import Motor
|
|
18
16
|
|
|
19
|
-
from dodal.common.data_util import load_json_file_to_class
|
|
20
17
|
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
21
18
|
AbstractAnalyserDriverIO,
|
|
22
19
|
TAbstractAnalyserDriverIO,
|
|
23
20
|
)
|
|
24
|
-
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
25
|
-
TAbstractBaseRegion,
|
|
26
|
-
TAbstractBaseSequence,
|
|
27
|
-
)
|
|
28
21
|
|
|
29
22
|
|
|
30
23
|
class ElectronAnalyserController(ADBaseController[AbstractAnalyserDriverIO]):
|
|
@@ -51,8 +44,8 @@ class AbstractElectronAnalyserDetector(
|
|
|
51
44
|
|
|
52
45
|
def __init__(
|
|
53
46
|
self,
|
|
54
|
-
name: str,
|
|
55
47
|
driver: TAbstractAnalyserDriverIO,
|
|
48
|
+
name: str = "",
|
|
56
49
|
):
|
|
57
50
|
self.controller: ElectronAnalyserController = ElectronAnalyserController(
|
|
58
51
|
driver=driver
|
|
@@ -103,121 +96,3 @@ class AbstractElectronAnalyserDetector(
|
|
|
103
96
|
Returns:
|
|
104
97
|
instance of the driver.
|
|
105
98
|
"""
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
class ElectronAnalyserRegionDetector(
|
|
109
|
-
AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
|
|
110
|
-
Preparable,
|
|
111
|
-
Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
|
|
112
|
-
):
|
|
113
|
-
"""
|
|
114
|
-
Extends electron analyser detector to configure specific region settings before data
|
|
115
|
-
acqusition. This object must be passed in a driver and store it as a reference. It
|
|
116
|
-
is designed to only exist inside a plan.
|
|
117
|
-
"""
|
|
118
|
-
|
|
119
|
-
def __init__(
|
|
120
|
-
self, name: str, driver: TAbstractAnalyserDriverIO, region: TAbstractBaseRegion
|
|
121
|
-
):
|
|
122
|
-
self._driver_ref = Reference(driver)
|
|
123
|
-
self.region = region
|
|
124
|
-
super().__init__(name, driver)
|
|
125
|
-
|
|
126
|
-
@property
|
|
127
|
-
def driver(self) -> TAbstractAnalyserDriverIO:
|
|
128
|
-
# Store as a reference, this implementation will be given a driver so needs to
|
|
129
|
-
# make sure we don't get conflicting parents.
|
|
130
|
-
return self._driver_ref()
|
|
131
|
-
|
|
132
|
-
@AsyncStatus.wrap
|
|
133
|
-
async def prepare(self, value: Motor) -> None:
|
|
134
|
-
"""
|
|
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.
|
|
139
|
-
"""
|
|
140
|
-
excitation_energy_source = value
|
|
141
|
-
await self.driver.prepare(excitation_energy_source)
|
|
142
|
-
await self.driver.set(self.region)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
TElectronAnalyserRegionDetector = TypeVar(
|
|
146
|
-
"TElectronAnalyserRegionDetector",
|
|
147
|
-
bound=ElectronAnalyserRegionDetector,
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
class ElectronAnalyserDetector(
|
|
152
|
-
AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
|
|
153
|
-
Generic[
|
|
154
|
-
TAbstractAnalyserDriverIO,
|
|
155
|
-
TAbstractBaseSequence,
|
|
156
|
-
TAbstractBaseRegion,
|
|
157
|
-
],
|
|
158
|
-
):
|
|
159
|
-
"""
|
|
160
|
-
Electron analyser detector with the additional functionality to load a sequence file
|
|
161
|
-
and create a list of temporary ElectronAnalyserRegionDetector objects. These will
|
|
162
|
-
setup configured region settings before data acquisition.
|
|
163
|
-
"""
|
|
164
|
-
|
|
165
|
-
def __init__(
|
|
166
|
-
self,
|
|
167
|
-
prefix: str,
|
|
168
|
-
sequence_class: type[TAbstractBaseSequence],
|
|
169
|
-
driver: TAbstractAnalyserDriverIO,
|
|
170
|
-
name: str = "",
|
|
171
|
-
):
|
|
172
|
-
# Pass in driver
|
|
173
|
-
self._driver = driver
|
|
174
|
-
self._sequence_class = sequence_class
|
|
175
|
-
super().__init__(name, self.driver)
|
|
176
|
-
|
|
177
|
-
@property
|
|
178
|
-
def driver(self) -> TAbstractAnalyserDriverIO:
|
|
179
|
-
# This implementation creates the driver and wants this to be the parent so it
|
|
180
|
-
# can be used with connect() method.
|
|
181
|
-
return self._driver
|
|
182
|
-
|
|
183
|
-
def load_sequence(self, filename: str) -> TAbstractBaseSequence:
|
|
184
|
-
"""
|
|
185
|
-
Load the sequence data from a provided json file into a sequence class.
|
|
186
|
-
|
|
187
|
-
Args:
|
|
188
|
-
filename: Path to the sequence file containing the region data.
|
|
189
|
-
|
|
190
|
-
Returns:
|
|
191
|
-
Pydantic model representing the sequence file.
|
|
192
|
-
"""
|
|
193
|
-
return load_json_file_to_class(self._sequence_class, filename)
|
|
194
|
-
|
|
195
|
-
def create_region_detector_list(
|
|
196
|
-
self, filename: str, enabled_only=True
|
|
197
|
-
) -> list[
|
|
198
|
-
ElectronAnalyserRegionDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion]
|
|
199
|
-
]:
|
|
200
|
-
"""
|
|
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.
|
|
211
|
-
"""
|
|
212
|
-
seq = self.load_sequence(filename)
|
|
213
|
-
regions = seq.get_enabled_regions() if enabled_only else seq.regions
|
|
214
|
-
return [
|
|
215
|
-
ElectronAnalyserRegionDetector(self.name + "_" + r.name, self.driver, r)
|
|
216
|
-
for r in regions
|
|
217
|
-
]
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
TElectronAnalyserDetector = TypeVar(
|
|
221
|
-
"TElectronAnalyserDetector",
|
|
222
|
-
bound=ElectronAnalyserDetector,
|
|
223
|
-
)
|
|
@@ -10,6 +10,7 @@ from ophyd_async.core import (
|
|
|
10
10
|
SignalR,
|
|
11
11
|
StandardReadable,
|
|
12
12
|
StandardReadableFormat,
|
|
13
|
+
StrictEnum,
|
|
13
14
|
derived_signal_r,
|
|
14
15
|
soft_signal_rw,
|
|
15
16
|
)
|
|
@@ -20,7 +21,7 @@ from ophyd_async.epics.motor import Motor
|
|
|
20
21
|
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
21
22
|
TAbstractBaseRegion,
|
|
22
23
|
)
|
|
23
|
-
from dodal.devices.electron_analyser.
|
|
24
|
+
from dodal.devices.electron_analyser.enums import EnergyMode
|
|
24
25
|
from dodal.devices.electron_analyser.util import to_binding_energy, to_kinetic_energy
|
|
25
26
|
|
|
26
27
|
|
|
@@ -37,7 +38,9 @@ class AbstractAnalyserDriverIO(
|
|
|
37
38
|
Electron analysers should inherit from this class for further specialisation.
|
|
38
39
|
"""
|
|
39
40
|
|
|
40
|
-
def __init__(
|
|
41
|
+
def __init__(
|
|
42
|
+
self, prefix: str, acquisition_mode_type: type[StrictEnum], name: str = ""
|
|
43
|
+
) -> None:
|
|
41
44
|
with self.add_children_as_readables():
|
|
42
45
|
self.image = epics_signal_r(Array1D[np.float64], prefix + "IMAGE")
|
|
43
46
|
self.spectrum = epics_signal_r(Array1D[np.float64], prefix + "INT_SPECTRUM")
|
|
@@ -61,7 +64,9 @@ class AbstractAnalyserDriverIO(
|
|
|
61
64
|
)
|
|
62
65
|
self.energy_step = epics_signal_rw(float, prefix + "STEP_SIZE")
|
|
63
66
|
self.iterations = epics_signal_rw(int, prefix + "NumExposures")
|
|
64
|
-
self.acquisition_mode = epics_signal_rw(
|
|
67
|
+
self.acquisition_mode = epics_signal_rw(
|
|
68
|
+
acquisition_mode_type, prefix + "ACQ_MODE"
|
|
69
|
+
)
|
|
65
70
|
self.excitation_energy_source = soft_signal_rw(str, initial_value="")
|
|
66
71
|
|
|
67
72
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
@@ -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.
|
|
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.
|
|
@@ -57,7 +60,7 @@ class AbstractBaseRegion(ABC, JavaToPythonModel):
|
|
|
57
60
|
# These ones we need subclasses to provide default values
|
|
58
61
|
lens_mode: str
|
|
59
62
|
pass_energy: int
|
|
60
|
-
acquisition_mode:
|
|
63
|
+
acquisition_mode: TStrictEnum
|
|
61
64
|
low_energy: float
|
|
62
65
|
high_energy: float
|
|
63
66
|
step_time: float
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from typing import Generic, TypeVar
|
|
2
|
+
|
|
3
|
+
from bluesky.protocols import Preparable
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
AsyncStatus,
|
|
6
|
+
Reference,
|
|
7
|
+
)
|
|
8
|
+
from ophyd_async.epics.motor import Motor
|
|
9
|
+
|
|
10
|
+
from dodal.common.data_util import load_json_file_to_class
|
|
11
|
+
from dodal.devices.electron_analyser.abstract.base_detector import (
|
|
12
|
+
AbstractElectronAnalyserDetector,
|
|
13
|
+
)
|
|
14
|
+
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
15
|
+
TAbstractAnalyserDriverIO,
|
|
16
|
+
)
|
|
17
|
+
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
18
|
+
TAbstractBaseRegion,
|
|
19
|
+
TAbstractBaseSequence,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ElectronAnalyserRegionDetector(
|
|
24
|
+
AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
|
|
25
|
+
Preparable,
|
|
26
|
+
Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
Extends electron analyser detector to configure specific region settings before data
|
|
30
|
+
acqusition. This object must be passed in a driver and store it as a reference. It
|
|
31
|
+
is designed to only exist inside a plan.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
driver: TAbstractAnalyserDriverIO,
|
|
37
|
+
region: TAbstractBaseRegion,
|
|
38
|
+
name: str = "",
|
|
39
|
+
):
|
|
40
|
+
self._driver_ref = Reference(driver)
|
|
41
|
+
self.region = region
|
|
42
|
+
super().__init__(driver, name)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def driver(self) -> TAbstractAnalyserDriverIO:
|
|
46
|
+
# Store as a reference, this implementation will be given a driver so needs to
|
|
47
|
+
# make sure we don't get conflicting parents.
|
|
48
|
+
return self._driver_ref()
|
|
49
|
+
|
|
50
|
+
@AsyncStatus.wrap
|
|
51
|
+
async def prepare(self, value: Motor) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Prepare driver with the region stored and energy_source motor.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
value: The excitation energy source that the region has selected.
|
|
57
|
+
"""
|
|
58
|
+
excitation_energy_source = value
|
|
59
|
+
await self.driver.prepare(excitation_energy_source)
|
|
60
|
+
await self.driver.set(self.region)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
TElectronAnalyserRegionDetector = TypeVar(
|
|
64
|
+
"TElectronAnalyserRegionDetector",
|
|
65
|
+
bound=ElectronAnalyserRegionDetector,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ElectronAnalyserDetector(
|
|
70
|
+
AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
|
|
71
|
+
Generic[
|
|
72
|
+
TAbstractAnalyserDriverIO,
|
|
73
|
+
TAbstractBaseSequence,
|
|
74
|
+
TAbstractBaseRegion,
|
|
75
|
+
],
|
|
76
|
+
):
|
|
77
|
+
"""
|
|
78
|
+
Electron analyser detector with the additional functionality to load a sequence file
|
|
79
|
+
and create a list of temporary ElectronAnalyserRegionDetector objects. These will
|
|
80
|
+
setup configured region settings before data acquisition.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
prefix: str,
|
|
86
|
+
sequence_class: type[TAbstractBaseSequence],
|
|
87
|
+
driver: TAbstractAnalyserDriverIO,
|
|
88
|
+
name: str = "",
|
|
89
|
+
):
|
|
90
|
+
# Pass in driver
|
|
91
|
+
self._driver = driver
|
|
92
|
+
self._sequence_class = sequence_class
|
|
93
|
+
super().__init__(self.driver, name)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def driver(self) -> TAbstractAnalyserDriverIO:
|
|
97
|
+
# This implementation creates the driver and wants this to be the parent so it
|
|
98
|
+
# can be used with connect() method.
|
|
99
|
+
return self._driver
|
|
100
|
+
|
|
101
|
+
def load_sequence(self, filename: str) -> TAbstractBaseSequence:
|
|
102
|
+
"""
|
|
103
|
+
Load the sequence data from a provided json file into a sequence class.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
filename: Path to the sequence file containing the region data.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Pydantic model representing the sequence file.
|
|
110
|
+
"""
|
|
111
|
+
return load_json_file_to_class(self._sequence_class, filename)
|
|
112
|
+
|
|
113
|
+
def create_region_detector_list(
|
|
114
|
+
self, filename: str, enabled_only=True
|
|
115
|
+
) -> list[
|
|
116
|
+
ElectronAnalyserRegionDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion]
|
|
117
|
+
]:
|
|
118
|
+
"""
|
|
119
|
+
Create a list of detectors equal to the number of regions in a sequence file.
|
|
120
|
+
Each detector is responsible for setting up a specific region.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
filename: Path to the sequence file containing the region data.
|
|
124
|
+
enabled_only: If true, only include the region if enabled is True.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List of ElectronAnalyserRegionDetector, equal to the number of regions in
|
|
128
|
+
the sequence file.
|
|
129
|
+
"""
|
|
130
|
+
seq = self.load_sequence(filename)
|
|
131
|
+
regions = seq.get_enabled_regions() if enabled_only else seq.regions
|
|
132
|
+
return [
|
|
133
|
+
ElectronAnalyserRegionDetector(self.driver, r, self.name + "_" + r.name)
|
|
134
|
+
for r in regions
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
TElectronAnalyserDetector = TypeVar(
|
|
139
|
+
"TElectronAnalyserDetector",
|
|
140
|
+
bound=ElectronAnalyserDetector,
|
|
141
|
+
)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from .detector import SpecsDetector
|
|
2
2
|
from .driver_io import SpecsAnalyserDriverIO
|
|
3
|
+
from .enums import AcquisitionMode
|
|
3
4
|
from .region import SpecsRegion, SpecsSequence
|
|
4
5
|
|
|
5
6
|
__all__ = [
|
|
6
7
|
"SpecsDetector",
|
|
7
8
|
"SpecsAnalyserDriverIO",
|
|
9
|
+
"AcquisitionMode",
|
|
8
10
|
"SpecsRegion",
|
|
9
11
|
"SpecsSequence",
|
|
10
12
|
]
|
|
@@ -13,6 +13,7 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
|
13
13
|
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
14
14
|
AbstractAnalyserDriverIO,
|
|
15
15
|
)
|
|
16
|
+
from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
|
|
16
17
|
from dodal.devices.electron_analyser.specs.region import SpecsRegion
|
|
17
18
|
|
|
18
19
|
|
|
@@ -28,7 +29,7 @@ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
|
|
|
28
29
|
self.min_angle_axis = epics_signal_r(float, prefix + "Y_MIN_RBV")
|
|
29
30
|
self.max_angle_axis = epics_signal_r(float, prefix + "Y_MAX_RBV")
|
|
30
31
|
|
|
31
|
-
super().__init__(prefix, name)
|
|
32
|
+
super().__init__(prefix, AcquisitionMode, name)
|
|
32
33
|
|
|
33
34
|
@AsyncStatus.wrap
|
|
34
35
|
async def set(self, region: SpecsRegion):
|
|
@@ -38,12 +39,10 @@ class SpecsAnalyserDriverIO(AbstractAnalyserDriverIO[SpecsRegion]):
|
|
|
38
39
|
self.snapshot_values.set(region.values),
|
|
39
40
|
self.psu_mode.set(region.psu_mode),
|
|
40
41
|
)
|
|
41
|
-
|
|
42
|
-
# https://github.com/DiamondLightSource/dodal/issues/1258
|
|
43
|
-
if region.acquisition_mode == "Fixed Transmission":
|
|
42
|
+
if region.acquisition_mode == AcquisitionMode.FIXED_TRANSMISSION:
|
|
44
43
|
await self.centre_energy.set(region.centre_energy)
|
|
45
44
|
|
|
46
|
-
if self.acquisition_mode ==
|
|
45
|
+
if self.acquisition_mode == AcquisitionMode.FIXED_ENERGY:
|
|
47
46
|
await self.energy_step.set(region.energy_step)
|
|
48
47
|
|
|
49
48
|
def _create_angle_axis_signal(self, prefix: str) -> SignalR[Array1D[np.float64]]:
|
|
@@ -4,13 +4,14 @@ from dodal.devices.electron_analyser.abstract.base_region import (
|
|
|
4
4
|
AbstractBaseRegion,
|
|
5
5
|
AbstractBaseSequence,
|
|
6
6
|
)
|
|
7
|
+
from dodal.devices.electron_analyser.specs.enums import AcquisitionMode
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
class SpecsRegion(AbstractBaseRegion):
|
|
10
|
+
class SpecsRegion(AbstractBaseRegion[AcquisitionMode]):
|
|
10
11
|
# Override base class with defaults
|
|
11
12
|
lens_mode: str = "SmallArea"
|
|
12
13
|
pass_energy: int = 5
|
|
13
|
-
acquisition_mode:
|
|
14
|
+
acquisition_mode: AcquisitionMode = AcquisitionMode.FIXED_TRANSMISSION
|
|
14
15
|
low_energy: float = Field(default=800, alias="start_energy")
|
|
15
16
|
high_energy: float = Field(default=850, alias="end_energy")
|
|
16
17
|
step_time: float = Field(default=1.0, alias="exposure_time")
|
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
from
|
|
1
|
+
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
2
|
+
AbstractAnalyserDriverIO,
|
|
3
|
+
)
|
|
4
|
+
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
5
|
+
AbstractBaseRegion,
|
|
6
|
+
AbstractBaseSequence,
|
|
7
|
+
)
|
|
8
|
+
from dodal.devices.electron_analyser.detector import (
|
|
9
|
+
ElectronAnalyserDetector,
|
|
10
|
+
ElectronAnalyserRegionDetector,
|
|
11
|
+
)
|
|
12
|
+
from dodal.devices.electron_analyser.specs.detector import (
|
|
13
|
+
SpecsAnalyserDriverIO,
|
|
14
|
+
SpecsDetector,
|
|
15
|
+
)
|
|
16
|
+
from dodal.devices.electron_analyser.vgscienta.detector import (
|
|
17
|
+
VGScientaAnalyserDriverIO,
|
|
18
|
+
VGScientaDetector,
|
|
19
|
+
)
|
|
2
20
|
|
|
21
|
+
ElectronAnalyserDetectorImpl = VGScientaDetector | SpecsDetector
|
|
22
|
+
ElectronAnalyserDriverImpl = VGScientaAnalyserDriverIO | SpecsAnalyserDriverIO
|
|
3
23
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
24
|
+
GenericElectronAnalyserDetector = ElectronAnalyserDetector[
|
|
25
|
+
AbstractAnalyserDriverIO[AbstractBaseRegion],
|
|
26
|
+
AbstractBaseSequence,
|
|
27
|
+
AbstractBaseRegion,
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
GenericElectronAnalyserRegionDetector = ElectronAnalyserRegionDetector[
|
|
31
|
+
AbstractAnalyserDriverIO[AbstractBaseRegion], AbstractBaseRegion
|
|
32
|
+
]
|