dls-dodal 1.46.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.
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/METADATA +2 -2
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/RECORD +74 -63
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +0 -1
- dodal/beamlines/aithre.py +6 -0
- dodal/beamlines/b01_1.py +1 -1
- dodal/beamlines/b07.py +2 -6
- dodal/beamlines/b07_1.py +1 -3
- dodal/beamlines/i03.py +33 -21
- dodal/beamlines/i04.py +65 -26
- dodal/beamlines/i09.py +1 -3
- dodal/beamlines/i09_1.py +1 -3
- dodal/beamlines/i18.py +1 -1
- dodal/beamlines/i19_1.py +9 -6
- dodal/beamlines/i23.py +17 -1
- dodal/beamlines/i24.py +5 -5
- dodal/beamlines/p38.py +1 -1
- dodal/beamlines/p60.py +2 -6
- dodal/beamlines/p99.py +48 -4
- dodal/common/beamlines/beamline_parameters.py +3 -30
- dodal/common/data_util.py +4 -0
- dodal/devices/aithre_lasershaping/goniometer.py +36 -2
- dodal/devices/aithre_lasershaping/laser_robot.py +27 -0
- dodal/devices/aperturescatterguard.py +47 -47
- dodal/devices/current_amplifiers/struck_scaler_counter.py +1 -1
- dodal/devices/diamond_filter.py +5 -17
- dodal/devices/eiger.py +1 -1
- dodal/devices/electron_analyser/__init__.py +18 -0
- dodal/devices/electron_analyser/abstract/__init__.py +22 -0
- dodal/devices/electron_analyser/abstract/base_detector.py +223 -0
- dodal/devices/electron_analyser/abstract/base_driver_io.py +230 -0
- dodal/devices/electron_analyser/{abstract_region.py → abstract/base_region.py} +3 -9
- dodal/devices/electron_analyser/specs/__init__.py +10 -0
- dodal/devices/electron_analyser/specs/detector.py +13 -0
- dodal/devices/electron_analyser/specs/driver_io.py +89 -0
- dodal/devices/electron_analyser/{specs_region.py → specs/region.py} +1 -1
- dodal/devices/electron_analyser/types.py +6 -0
- dodal/devices/electron_analyser/util.py +13 -0
- dodal/devices/electron_analyser/vgscienta/__init__.py +11 -0
- dodal/devices/electron_analyser/vgscienta/detector.py +22 -0
- dodal/devices/electron_analyser/vgscienta/driver_io.py +67 -0
- dodal/devices/electron_analyser/{vgscienta_region.py → vgscienta/region.py} +1 -2
- dodal/devices/fast_grid_scan.py +7 -9
- dodal/devices/i03/__init__.py +3 -0
- dodal/devices/i04/__init__.py +3 -0
- dodal/devices/i04/constants.py +9 -0
- dodal/devices/i04/murko_results.py +192 -0
- dodal/devices/i10/diagnostics.py +9 -61
- dodal/devices/i18/diode.py +37 -4
- dodal/devices/i24/focus_mirrors.py +9 -13
- dodal/devices/i24/pilatus_metadata.py +9 -9
- dodal/devices/i24/pmac.py +19 -14
- dodal/devices/{i03 → mx_phase1}/beamstop.py +26 -15
- dodal/devices/oav/oav_calculations.py +2 -2
- dodal/devices/oav/oav_detector.py +80 -32
- dodal/devices/oav/oav_parameters.py +46 -16
- dodal/devices/oav/oav_to_redis_forwarder.py +2 -2
- dodal/devices/oav/utils.py +2 -2
- dodal/devices/p99/andor2_point.py +41 -0
- dodal/devices/positioner.py +49 -0
- dodal/devices/robot.py +20 -1
- dodal/devices/smargon.py +43 -4
- dodal/devices/tetramm.py +5 -2
- dodal/devices/util/adjuster_plans.py +1 -1
- dodal/devices/zebra/zebra.py +8 -0
- dodal/devices/zebra/zebra_constants_mapping.py +1 -1
- dodal/devices/zocalo/__init__.py +0 -3
- dodal/devices/zocalo/zocalo_results.py +6 -32
- dodal/log.py +14 -14
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +167 -0
- dodal/common/signal_utils.py +0 -88
- dodal/devices/electron_analyser/abstract_analyser_io.py +0 -47
- dodal/devices/electron_analyser/specs_analyser_io.py +0 -19
- dodal/devices/electron_analyser/vgscienta_analyser_io.py +0 -26
- dodal/devices/logging_ophyd_device.py +0 -17
- dodal/plan_stubs/electron_analyser/__init__.py +0 -0
- dodal/plan_stubs/electron_analyser/configure_controller.py +0 -80
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/top_level.txt +0 -0
|
@@ -2,17 +2,18 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
|
|
5
|
-
from bluesky.protocols import
|
|
5
|
+
from bluesky.protocols import Preparable
|
|
6
6
|
from ophyd_async.core import (
|
|
7
7
|
AsyncStatus,
|
|
8
8
|
StandardReadable,
|
|
9
9
|
StandardReadableFormat,
|
|
10
10
|
StrictEnum,
|
|
11
|
+
derived_signal_r,
|
|
12
|
+
derived_signal_rw,
|
|
11
13
|
)
|
|
12
14
|
from pydantic import BaseModel, Field
|
|
13
15
|
|
|
14
16
|
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
|
|
15
|
-
from dodal.common.signal_utils import create_r_hardware_backed_soft_signal
|
|
16
17
|
from dodal.devices.aperture import Aperture
|
|
17
18
|
from dodal.devices.scatterguard import Scatterguard
|
|
18
19
|
|
|
@@ -123,21 +124,21 @@ def load_positions_from_beamline_parameters(
|
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
|
|
126
|
-
class ApertureScatterguard(StandardReadable,
|
|
127
|
+
class ApertureScatterguard(StandardReadable, Preparable):
|
|
127
128
|
"""Move the aperture and scatterguard assembly in a safe way. There are two ways to
|
|
128
129
|
interact with the device depending on if you want simplicity or move flexibility.
|
|
129
130
|
|
|
130
131
|
Examples:
|
|
131
132
|
The simple interface is using::
|
|
132
133
|
|
|
133
|
-
await aperture_scatterguard.set(ApertureValue.LARGE)
|
|
134
|
+
await aperture_scatterguard.selected_aperture.set(ApertureValue.LARGE)
|
|
134
135
|
|
|
135
136
|
This will move the assembly so that the large aperture is in the beam, regardless
|
|
136
137
|
of where the assembly currently is.
|
|
137
138
|
|
|
138
139
|
We may also want to move the assembly out of the beam with::
|
|
139
140
|
|
|
140
|
-
await aperture_scatterguard.set(ApertureValue.OUT_OF_BEAM)
|
|
141
|
+
await aperture_scatterguard.selected_aperture.set(ApertureValue.OUT_OF_BEAM)
|
|
141
142
|
|
|
142
143
|
Note, to make sure we do this as quickly as possible, the scatterguard will stay
|
|
143
144
|
in the same position relative to the aperture.
|
|
@@ -149,7 +150,7 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
|
|
|
149
150
|
|
|
150
151
|
Then, at a later time, move back into the beam::
|
|
151
152
|
|
|
152
|
-
await
|
|
153
|
+
await aperture_scatterguard.selected_aperture.set(ApertureValue.LARGE)
|
|
153
154
|
|
|
154
155
|
Given the prepare has been done this move will now be faster as only the y is
|
|
155
156
|
left to move.
|
|
@@ -164,11 +165,24 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
|
|
|
164
165
|
) -> None:
|
|
165
166
|
self.aperture = Aperture(prefix + "-MO-MAPT-01:")
|
|
166
167
|
self.scatterguard = Scatterguard(prefix + "-MO-SCAT-01:")
|
|
167
|
-
self.radius = create_r_hardware_backed_soft_signal(
|
|
168
|
-
float, self._get_current_radius, units="µm"
|
|
169
|
-
)
|
|
170
168
|
self._loaded_positions = loaded_positions
|
|
171
169
|
self._tolerances = tolerances
|
|
170
|
+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
171
|
+
self.selected_aperture = derived_signal_rw(
|
|
172
|
+
self._get_current_aperture_position,
|
|
173
|
+
self._set_current_aperture_position,
|
|
174
|
+
large=self.aperture.large,
|
|
175
|
+
medium=self.aperture.medium,
|
|
176
|
+
small=self.aperture.small,
|
|
177
|
+
current_ap_y=self.aperture.y.user_readback,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
self.radius = derived_signal_r(
|
|
181
|
+
self._get_current_radius,
|
|
182
|
+
current_aperture=self.selected_aperture,
|
|
183
|
+
derived_units="µm",
|
|
184
|
+
)
|
|
185
|
+
|
|
172
186
|
self.add_readables(
|
|
173
187
|
[
|
|
174
188
|
self.aperture.x.user_readback,
|
|
@@ -180,17 +194,9 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
|
|
|
180
194
|
],
|
|
181
195
|
)
|
|
182
196
|
|
|
183
|
-
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
184
|
-
self.selected_aperture = create_r_hardware_backed_soft_signal(
|
|
185
|
-
ApertureValue, self._get_current_aperture_position
|
|
186
|
-
)
|
|
187
|
-
|
|
188
197
|
super().__init__(name)
|
|
189
198
|
|
|
190
|
-
|
|
191
|
-
async def set(self, value: ApertureValue):
|
|
192
|
-
"""This set will move the aperture into the beam or move the whole assembly out"""
|
|
193
|
-
|
|
199
|
+
async def _set_current_aperture_position(self, value: ApertureValue) -> None:
|
|
194
200
|
position = self._loaded_positions[value]
|
|
195
201
|
await self._check_safe_to_move(position.aperture_z)
|
|
196
202
|
|
|
@@ -231,6 +237,27 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
|
|
|
231
237
|
"triggering another move."
|
|
232
238
|
)
|
|
233
239
|
|
|
240
|
+
def _get_current_radius(self, current_aperture: ApertureValue) -> float:
|
|
241
|
+
return self._loaded_positions[current_aperture].radius
|
|
242
|
+
|
|
243
|
+
def _is_out_of_beam(self, current_ap_y: float) -> bool:
|
|
244
|
+
out_ap_y = self._loaded_positions[ApertureValue.OUT_OF_BEAM].aperture_y
|
|
245
|
+
return current_ap_y <= out_ap_y + self._tolerances.aperture_y
|
|
246
|
+
|
|
247
|
+
def _get_current_aperture_position(
|
|
248
|
+
self, large: float, medium: float, small: float, current_ap_y: float
|
|
249
|
+
) -> ApertureValue:
|
|
250
|
+
if large == 1:
|
|
251
|
+
return ApertureValue.LARGE
|
|
252
|
+
elif medium == 1:
|
|
253
|
+
return ApertureValue.MEDIUM
|
|
254
|
+
elif small == 1:
|
|
255
|
+
return ApertureValue.SMALL
|
|
256
|
+
elif self._is_out_of_beam(current_ap_y):
|
|
257
|
+
return ApertureValue.OUT_OF_BEAM
|
|
258
|
+
|
|
259
|
+
raise InvalidApertureMove("Current aperture/scatterguard state unrecognised")
|
|
260
|
+
|
|
234
261
|
async def _safe_move_whilst_in_beam(self, position: AperturePosition):
|
|
235
262
|
"""
|
|
236
263
|
Move the aperture and scatterguard combo safely to a new position.
|
|
@@ -282,33 +309,6 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
|
|
|
282
309
|
self.scatterguard.y.set(scatterguard_y),
|
|
283
310
|
)
|
|
284
311
|
|
|
285
|
-
async def _is_out_of_beam(self) -> bool:
|
|
286
|
-
current_ap_y = await self.aperture.y.user_readback.get_value()
|
|
287
|
-
out_ap_y = self._loaded_positions[ApertureValue.OUT_OF_BEAM].aperture_y
|
|
288
|
-
return current_ap_y <= out_ap_y + self._tolerances.aperture_y
|
|
289
|
-
|
|
290
|
-
async def _get_current_aperture_position(self) -> ApertureValue:
|
|
291
|
-
"""
|
|
292
|
-
Returns the current aperture position using readback values
|
|
293
|
-
for SMALL, MEDIUM, LARGE. ROBOT_LOAD position defined when
|
|
294
|
-
mini aperture y <= ROBOT_LOAD.location.aperture_y + tolerance.
|
|
295
|
-
If no position is found then raises InvalidApertureMove.
|
|
296
|
-
"""
|
|
297
|
-
if await self.aperture.large.get_value(cached=False) == 1:
|
|
298
|
-
return ApertureValue.LARGE
|
|
299
|
-
elif await self.aperture.medium.get_value(cached=False) == 1:
|
|
300
|
-
return ApertureValue.MEDIUM
|
|
301
|
-
elif await self.aperture.small.get_value(cached=False) == 1:
|
|
302
|
-
return ApertureValue.SMALL
|
|
303
|
-
elif await self._is_out_of_beam():
|
|
304
|
-
return ApertureValue.OUT_OF_BEAM
|
|
305
|
-
|
|
306
|
-
raise InvalidApertureMove("Current aperture/scatterguard state unrecognised")
|
|
307
|
-
|
|
308
|
-
async def _get_current_radius(self) -> float:
|
|
309
|
-
current_value = await self._get_current_aperture_position()
|
|
310
|
-
return self._loaded_positions[current_value].radius
|
|
311
|
-
|
|
312
312
|
@AsyncStatus.wrap
|
|
313
313
|
async def prepare(self, value: ApertureValue):
|
|
314
314
|
"""Moves the assembly to the position for the specified aperture, whilst keeping
|
|
@@ -317,7 +317,7 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
|
|
|
317
317
|
Moving the assembly whilst out of the beam has no collision risk so we can just
|
|
318
318
|
move all the motors together.
|
|
319
319
|
"""
|
|
320
|
-
if await self.
|
|
320
|
+
if self._is_out_of_beam(await self.aperture.y.user_readback.get_value()):
|
|
321
321
|
aperture_x, _, aperture_z, scatterguard_x, scatterguard_y = (
|
|
322
322
|
self._loaded_positions[value].values
|
|
323
323
|
)
|
|
@@ -329,4 +329,4 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
|
|
|
329
329
|
self.scatterguard.y.set(scatterguard_y),
|
|
330
330
|
)
|
|
331
331
|
else:
|
|
332
|
-
await self.set(value)
|
|
332
|
+
await self.selected_aperture.set(value)
|
dodal/devices/diamond_filter.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import TypeVar
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import
|
|
4
|
-
|
|
5
|
-
from
|
|
3
|
+
from ophyd_async.core import StrictEnum
|
|
4
|
+
|
|
5
|
+
from dodal.devices.positioner import Positioner1D
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class _Filters(StrictEnum):
|
|
@@ -25,22 +25,10 @@ class I04Filters(_Filters):
|
|
|
25
25
|
T = TypeVar("T", bound=_Filters)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class DiamondFilter(
|
|
28
|
+
class DiamondFilter(Positioner1D[T]):
|
|
29
29
|
"""
|
|
30
30
|
A filter set that is used to reduce the heat load on the monochromator.
|
|
31
31
|
|
|
32
32
|
It has 4 slots that can contain filters of different thickness. Changing the thickness
|
|
33
33
|
signal will move the filter set to select this filter.
|
|
34
34
|
"""
|
|
35
|
-
|
|
36
|
-
def __init__(
|
|
37
|
-
self,
|
|
38
|
-
prefix: str,
|
|
39
|
-
data_type: type[T],
|
|
40
|
-
name: str = "",
|
|
41
|
-
) -> None:
|
|
42
|
-
with self.add_children_as_readables():
|
|
43
|
-
self.y_motor = Motor(prefix + "Y")
|
|
44
|
-
self.thickness = epics_signal_rw(data_type, f"{prefix}Y:MP:SELECT")
|
|
45
|
-
|
|
46
|
-
super().__init__(name)
|
dodal/devices/eiger.py
CHANGED
|
@@ -35,7 +35,7 @@ class InternalEigerTriggerMode(Enum):
|
|
|
35
35
|
AVAILABLE_TIMEOUTS = {
|
|
36
36
|
"i03": EigerTimeouts(
|
|
37
37
|
stale_params_timeout=60,
|
|
38
|
-
general_status_timeout=
|
|
38
|
+
general_status_timeout=20,
|
|
39
39
|
meta_file_ready_timeout=30,
|
|
40
40
|
all_frames_timeout=120, # Long timeout for meta file to compensate for filesystem issues
|
|
41
41
|
arming_timeout=60,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .abstract.base_detector import (
|
|
2
|
+
ElectronAnalyserDetector,
|
|
3
|
+
ElectronAnalyserRegionDetector,
|
|
4
|
+
TElectronAnalyserDetector,
|
|
5
|
+
TElectronAnalyserRegionDetector,
|
|
6
|
+
)
|
|
7
|
+
from .types import EnergyMode
|
|
8
|
+
from .util import to_binding_energy, to_kinetic_energy
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"to_binding_energy",
|
|
12
|
+
"to_kinetic_energy",
|
|
13
|
+
"EnergyMode",
|
|
14
|
+
"ElectronAnalyserDetector",
|
|
15
|
+
"TElectronAnalyserDetector",
|
|
16
|
+
"ElectronAnalyserRegionDetector",
|
|
17
|
+
"TElectronAnalyserRegionDetector",
|
|
18
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from .base_detector import (
|
|
2
|
+
AbstractAnalyserDriverIO,
|
|
3
|
+
AbstractElectronAnalyserDetector,
|
|
4
|
+
)
|
|
5
|
+
from .base_driver_io import AbstractAnalyserDriverIO, TAbstractAnalyserDriverIO
|
|
6
|
+
from .base_region import (
|
|
7
|
+
AbstractBaseRegion,
|
|
8
|
+
AbstractBaseSequence,
|
|
9
|
+
TAbstractBaseRegion,
|
|
10
|
+
TAbstractBaseSequence,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AbstractBaseRegion",
|
|
15
|
+
"AbstractBaseSequence",
|
|
16
|
+
"TAbstractBaseRegion",
|
|
17
|
+
"TAbstractBaseSequence",
|
|
18
|
+
"AbstractAnalyserDriverIO",
|
|
19
|
+
"AbstractElectronAnalyserDetector",
|
|
20
|
+
"AbstractAnalyserDriverIO",
|
|
21
|
+
"TAbstractAnalyserDriverIO",
|
|
22
|
+
]
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from bluesky.protocols import Preparable, Reading, Stageable, Triggerable
|
|
6
|
+
from event_model import DataKey
|
|
7
|
+
from ophyd_async.core import (
|
|
8
|
+
AsyncConfigurable,
|
|
9
|
+
AsyncReadable,
|
|
10
|
+
AsyncStatus,
|
|
11
|
+
Device,
|
|
12
|
+
Reference,
|
|
13
|
+
)
|
|
14
|
+
from ophyd_async.epics.adcore import (
|
|
15
|
+
ADBaseController,
|
|
16
|
+
)
|
|
17
|
+
from ophyd_async.epics.motor import Motor
|
|
18
|
+
|
|
19
|
+
from dodal.common.data_util import load_json_file_to_class
|
|
20
|
+
from dodal.devices.electron_analyser.abstract.base_driver_io import (
|
|
21
|
+
AbstractAnalyserDriverIO,
|
|
22
|
+
TAbstractAnalyserDriverIO,
|
|
23
|
+
)
|
|
24
|
+
from dodal.devices.electron_analyser.abstract.base_region import (
|
|
25
|
+
TAbstractBaseRegion,
|
|
26
|
+
TAbstractBaseSequence,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ElectronAnalyserController(ADBaseController[AbstractAnalyserDriverIO]):
|
|
31
|
+
def get_deadtime(self, exposure: float | None) -> float:
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AbstractElectronAnalyserDetector(
|
|
36
|
+
Device,
|
|
37
|
+
Stageable,
|
|
38
|
+
Triggerable,
|
|
39
|
+
AsyncReadable,
|
|
40
|
+
AsyncConfigurable,
|
|
41
|
+
Generic[TAbstractAnalyserDriverIO],
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Detector for data acquisition of electron analyser. Can only acquire using settings
|
|
45
|
+
already configured for the device.
|
|
46
|
+
|
|
47
|
+
If possible, this should be changed to inherit from a StandardDetector. Currently,
|
|
48
|
+
StandardDetector forces you to use a file writer which doesn't apply here.
|
|
49
|
+
See issue https://github.com/bluesky/ophyd-async/issues/888
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
name: str,
|
|
55
|
+
driver: TAbstractAnalyserDriverIO,
|
|
56
|
+
):
|
|
57
|
+
self.controller: ElectronAnalyserController = ElectronAnalyserController(
|
|
58
|
+
driver=driver
|
|
59
|
+
)
|
|
60
|
+
super().__init__(name)
|
|
61
|
+
|
|
62
|
+
@AsyncStatus.wrap
|
|
63
|
+
async def trigger(self) -> None:
|
|
64
|
+
await self.controller.arm()
|
|
65
|
+
await self.controller.wait_for_idle()
|
|
66
|
+
|
|
67
|
+
@AsyncStatus.wrap
|
|
68
|
+
async def stage(self) -> None:
|
|
69
|
+
"""Make sure the detector is idle and ready to be used."""
|
|
70
|
+
await asyncio.gather(self.controller.disarm())
|
|
71
|
+
|
|
72
|
+
@AsyncStatus.wrap
|
|
73
|
+
async def unstage(self) -> None:
|
|
74
|
+
"""Disarm the detector."""
|
|
75
|
+
await asyncio.gather(self.controller.disarm())
|
|
76
|
+
|
|
77
|
+
async def read(self) -> dict[str, Reading]:
|
|
78
|
+
return await self.driver.read()
|
|
79
|
+
|
|
80
|
+
async def describe(self) -> dict[str, DataKey]:
|
|
81
|
+
data = await self.driver.describe()
|
|
82
|
+
# Correct the shape for image
|
|
83
|
+
prefix = self.driver.name + "-"
|
|
84
|
+
energy_size = len(await self.driver.energy_axis.get_value())
|
|
85
|
+
angle_size = len(await self.driver.angle_axis.get_value())
|
|
86
|
+
data[prefix + "image"]["shape"] = [angle_size, energy_size]
|
|
87
|
+
return data
|
|
88
|
+
|
|
89
|
+
async def read_configuration(self) -> dict[str, Reading]:
|
|
90
|
+
return await self.driver.read_configuration()
|
|
91
|
+
|
|
92
|
+
async def describe_configuration(self) -> dict[str, DataKey]:
|
|
93
|
+
return await self.driver.describe_configuration()
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def driver(self) -> TAbstractAnalyserDriverIO:
|
|
98
|
+
"""
|
|
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.
|
|
105
|
+
"""
|
|
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
|
+
)
|