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