dls-dodal 1.29.4__py3-none-any.whl → 1.30.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.29.4.dist-info → dls_dodal-1.30.0.dist-info}/METADATA +27 -42
- dls_dodal-1.30.0.dist-info/RECORD +132 -0
- {dls_dodal-1.29.4.dist-info → dls_dodal-1.30.0.dist-info}/WHEEL +1 -1
- dls_dodal-1.30.0.dist-info/entry_points.txt +3 -0
- dodal/__init__.py +1 -4
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +3 -1
- dodal/beamlines/i03.py +28 -23
- dodal/beamlines/i04.py +34 -12
- dodal/beamlines/i13_1.py +66 -0
- dodal/beamlines/i22.py +5 -5
- dodal/beamlines/i24.py +1 -1
- dodal/beamlines/p38.py +7 -7
- dodal/beamlines/p45.py +7 -5
- dodal/beamlines/p99.py +61 -0
- dodal/cli.py +6 -3
- dodal/common/beamlines/beamline_parameters.py +2 -2
- dodal/common/beamlines/beamline_utils.py +6 -5
- dodal/common/maths.py +1 -3
- dodal/common/types.py +2 -3
- dodal/common/udc_directory_provider.py +14 -3
- dodal/common/visit.py +2 -3
- dodal/devices/CTAB.py +22 -17
- dodal/devices/aperturescatterguard.py +114 -136
- dodal/devices/areadetector/adaravis.py +8 -6
- dodal/devices/areadetector/adsim.py +2 -3
- dodal/devices/areadetector/adutils.py +20 -12
- dodal/devices/cryostream.py +19 -7
- dodal/devices/detector/__init__.py +13 -2
- dodal/devices/detector/det_dim_constants.py +2 -2
- dodal/devices/detector/det_dist_to_beam_converter.py +1 -1
- dodal/devices/detector/detector.py +5 -5
- dodal/devices/detector/detector_motion.py +38 -31
- dodal/devices/eiger.py +11 -15
- dodal/devices/eiger_odin.py +9 -10
- dodal/devices/fast_grid_scan.py +4 -3
- dodal/devices/fluorescence_detector_motion.py +13 -4
- dodal/devices/focusing_mirror.py +4 -4
- dodal/devices/hutch_shutter.py +4 -4
- dodal/devices/i22/dcm.py +4 -3
- dodal/devices/i22/fswitch.py +4 -4
- dodal/devices/i22/nxsas.py +23 -32
- dodal/devices/i24/pmac.py +47 -8
- dodal/devices/ipin.py +7 -4
- dodal/devices/linkam3.py +11 -5
- dodal/devices/logging_ophyd_device.py +1 -1
- dodal/devices/motors.py +31 -5
- dodal/devices/oav/grid_overlay.py +1 -0
- dodal/devices/oav/microns_for_zoom_levels.json +1 -1
- dodal/devices/oav/oav_detector.py +2 -1
- dodal/devices/oav/oav_parameters.py +18 -10
- dodal/devices/oav/oav_to_redis_forwarder.py +100 -0
- dodal/devices/oav/pin_image_recognition/__init__.py +6 -6
- dodal/devices/oav/pin_image_recognition/utils.py +5 -6
- dodal/devices/oav/utils.py +2 -2
- dodal/devices/p99/__init__.py +0 -0
- dodal/devices/p99/sample_stage.py +43 -0
- dodal/devices/robot.py +30 -18
- dodal/devices/scintillator.py +8 -5
- dodal/devices/smargon.py +3 -3
- dodal/devices/status.py +2 -31
- dodal/devices/tetramm.py +4 -4
- dodal/devices/thawer.py +5 -3
- dodal/devices/undulator_dcm.py +6 -8
- dodal/devices/util/adjuster_plans.py +2 -2
- dodal/devices/util/epics_util.py +5 -7
- dodal/devices/util/lookup_tables.py +2 -3
- dodal/devices/util/save_panda.py +87 -0
- dodal/devices/util/test_utils.py +17 -0
- dodal/devices/webcam.py +3 -3
- dodal/devices/xbpm_feedback.py +0 -23
- dodal/devices/zebra.py +10 -10
- dodal/devices/zebra_controlled_shutter.py +3 -3
- dodal/devices/zocalo/zocalo_interaction.py +10 -2
- dodal/devices/zocalo/zocalo_results.py +31 -18
- dodal/log.py +14 -5
- dodal/plans/data_session_metadata.py +1 -0
- dodal/plans/motor_util_plans.py +117 -0
- dodal/utils.py +65 -22
- dls_dodal-1.29.4.dist-info/RECORD +0 -125
- dls_dodal-1.29.4.dist-info/entry_points.txt +0 -2
- dodal/devices/qbpm1.py +0 -8
- {dls_dodal-1.29.4.dist-info → dls_dodal-1.30.0.dist-info}/LICENSE +0 -0
- {dls_dodal-1.29.4.dist-info → dls_dodal-1.30.0.dist-info}/top_level.txt +0 -0
- /dodal/devices/i24/{I24_detector_motion.py → i24_detector_motion.py} +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from collections import
|
|
2
|
+
from collections import namedtuple
|
|
3
3
|
from dataclasses import asdict, dataclass
|
|
4
4
|
from enum import Enum
|
|
5
5
|
|
|
6
6
|
from bluesky.protocols import Movable, Reading
|
|
7
|
-
from
|
|
7
|
+
from event_model.documents.event_descriptor import DataKey
|
|
8
|
+
from ophyd_async.core import AsyncStatus, HintedSignal, SignalR, StandardReadable
|
|
8
9
|
from ophyd_async.core.soft_signal_backend import SoftConverter, SoftSignalBackend
|
|
9
10
|
|
|
11
|
+
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
|
|
10
12
|
from dodal.devices.aperture import Aperture
|
|
11
13
|
from dodal.devices.scatterguard import Scatterguard
|
|
12
|
-
from dodal.log import LOGGER
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class InvalidApertureMove(Exception):
|
|
@@ -39,14 +40,10 @@ class ApertureScatterguardTolerances:
|
|
|
39
40
|
|
|
40
41
|
@dataclass
|
|
41
42
|
class SingleAperturePosition:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
radius_microns: float | None = None
|
|
47
|
-
location: ApertureFiveDimensionalLocation = ApertureFiveDimensionalLocation(
|
|
48
|
-
0, 0, 0, 0, 0
|
|
49
|
-
)
|
|
43
|
+
name: str
|
|
44
|
+
GDA_name: str
|
|
45
|
+
radius_microns: float | None
|
|
46
|
+
location: ApertureFiveDimensionalLocation
|
|
50
47
|
|
|
51
48
|
|
|
52
49
|
# Use StrEnum once we stop python 3.10 support
|
|
@@ -64,7 +61,7 @@ def position_from_params(
|
|
|
64
61
|
name: str,
|
|
65
62
|
GDA_name: AperturePositionGDANames,
|
|
66
63
|
radius_microns: float | None,
|
|
67
|
-
params:
|
|
64
|
+
params: GDABeamlineParameters,
|
|
68
65
|
) -> SingleAperturePosition:
|
|
69
66
|
return SingleAperturePosition(
|
|
70
67
|
name,
|
|
@@ -80,7 +77,9 @@ def position_from_params(
|
|
|
80
77
|
)
|
|
81
78
|
|
|
82
79
|
|
|
83
|
-
def
|
|
80
|
+
def load_tolerances_from_beamline_params(
|
|
81
|
+
params: GDABeamlineParameters,
|
|
82
|
+
) -> ApertureScatterguardTolerances:
|
|
84
83
|
return ApertureScatterguardTolerances(
|
|
85
84
|
ap_x=params["miniap_x_tolerance"],
|
|
86
85
|
ap_y=params["miniap_y_tolerance"],
|
|
@@ -90,76 +89,50 @@ def tolerances_from_params(params: dict) -> ApertureScatterguardTolerances:
|
|
|
90
89
|
)
|
|
91
90
|
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
LARGE: SingleAperturePosition
|
|
99
|
-
MEDIUM: SingleAperturePosition
|
|
100
|
-
SMALL: SingleAperturePosition
|
|
101
|
-
ROBOT_LOAD: SingleAperturePosition
|
|
102
|
-
|
|
103
|
-
tolerances: ApertureScatterguardTolerances
|
|
104
|
-
|
|
105
|
-
UNKNOWN = SingleAperturePosition(
|
|
106
|
-
"Unknown", "UNKNOWN", None, ApertureFiveDimensionalLocation(0, 0, 0, 0, 0)
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
@classmethod
|
|
110
|
-
def from_gda_beamline_params(cls, params):
|
|
111
|
-
return cls(
|
|
112
|
-
LARGE=position_from_params(
|
|
113
|
-
"Large", AperturePositionGDANames.LARGE_APERTURE, 100, params
|
|
114
|
-
),
|
|
115
|
-
MEDIUM=position_from_params(
|
|
116
|
-
"Medium", AperturePositionGDANames.MEDIUM_APERTURE, 50, params
|
|
117
|
-
),
|
|
118
|
-
SMALL=position_from_params(
|
|
119
|
-
"Small", AperturePositionGDANames.SMALL_APERTURE, 20, params
|
|
120
|
-
),
|
|
121
|
-
ROBOT_LOAD=position_from_params(
|
|
122
|
-
"Robot load", AperturePositionGDANames.ROBOT_LOAD, None, params
|
|
123
|
-
),
|
|
124
|
-
tolerances=tolerances_from_params(params),
|
|
125
|
-
)
|
|
92
|
+
class AperturePosition(Enum):
|
|
93
|
+
ROBOT_LOAD = 0
|
|
94
|
+
SMALL = 1
|
|
95
|
+
MEDIUM = 2
|
|
96
|
+
LARGE = 3
|
|
126
97
|
|
|
127
|
-
def get_position_from_gda_aperture_name(
|
|
128
|
-
self, gda_aperture_name: AperturePositionGDANames
|
|
129
|
-
) -> SingleAperturePosition:
|
|
130
|
-
apertures = [ap for ap in self.as_list() if ap.GDA_name == gda_aperture_name]
|
|
131
|
-
if not apertures:
|
|
132
|
-
raise ValueError(
|
|
133
|
-
f"Tried to convert unknown aperture name {gda_aperture_name} to a SingleAperturePosition"
|
|
134
|
-
)
|
|
135
|
-
else:
|
|
136
|
-
return apertures[0]
|
|
137
98
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
99
|
+
def load_positions_from_beamline_parameters(
|
|
100
|
+
params: GDABeamlineParameters,
|
|
101
|
+
) -> dict[AperturePosition, SingleAperturePosition]:
|
|
102
|
+
return {
|
|
103
|
+
AperturePosition.ROBOT_LOAD: position_from_params(
|
|
104
|
+
"Robot load", AperturePositionGDANames.ROBOT_LOAD, None, params
|
|
105
|
+
),
|
|
106
|
+
AperturePosition.SMALL: position_from_params(
|
|
107
|
+
"Small", AperturePositionGDANames.SMALL_APERTURE, 20, params
|
|
108
|
+
),
|
|
109
|
+
AperturePosition.MEDIUM: position_from_params(
|
|
110
|
+
"Medium", AperturePositionGDANames.MEDIUM_APERTURE, 50, params
|
|
111
|
+
),
|
|
112
|
+
AperturePosition.LARGE: position_from_params(
|
|
113
|
+
"Large", AperturePositionGDANames.LARGE_APERTURE, 100, params
|
|
114
|
+
),
|
|
115
|
+
}
|
|
145
116
|
|
|
146
117
|
|
|
147
118
|
class ApertureScatterguard(StandardReadable, Movable):
|
|
148
|
-
def __init__(
|
|
149
|
-
self
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
loaded_positions: dict[AperturePosition, SingleAperturePosition],
|
|
122
|
+
tolerances: ApertureScatterguardTolerances,
|
|
123
|
+
prefix: str = "",
|
|
124
|
+
name: str = "",
|
|
125
|
+
) -> None:
|
|
126
|
+
self._aperture = Aperture(prefix + "-MO-MAPT-01:")
|
|
127
|
+
self._scatterguard = Scatterguard(prefix + "-MO-SCAT-01:")
|
|
128
|
+
self._loaded_positions = loaded_positions
|
|
129
|
+
self._tolerances = tolerances
|
|
153
130
|
aperture_backend = SoftSignalBackend(
|
|
154
|
-
SingleAperturePosition,
|
|
131
|
+
SingleAperturePosition, self._loaded_positions[AperturePosition.ROBOT_LOAD]
|
|
155
132
|
)
|
|
156
133
|
aperture_backend.converter = self.ApertureConverter()
|
|
157
134
|
self.selected_aperture = self.SelectedAperture(backend=aperture_backend)
|
|
158
|
-
self.
|
|
159
|
-
read=[
|
|
160
|
-
self.selected_aperture,
|
|
161
|
-
]
|
|
162
|
-
)
|
|
135
|
+
self.add_readables([self.selected_aperture], wrapper=HintedSignal)
|
|
163
136
|
super().__init__(name)
|
|
164
137
|
|
|
165
138
|
class ApertureConverter(SoftConverter):
|
|
@@ -176,43 +149,47 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
176
149
|
class SelectedAperture(SignalR):
|
|
177
150
|
async def read(self, *args, **kwargs):
|
|
178
151
|
assert isinstance(self.parent, ApertureScatterguard)
|
|
179
|
-
|
|
152
|
+
assert self._backend
|
|
153
|
+
await self._backend.put(await self.parent.get_current_aperture_position())
|
|
180
154
|
return {self.name: await self._backend.get_reading()}
|
|
181
155
|
|
|
182
|
-
async def describe(self):
|
|
183
|
-
return
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
], # TODO describe properly - see https://github.com/DiamondLightSource/dodal/issues/253
|
|
193
|
-
},
|
|
194
|
-
),
|
|
195
|
-
],
|
|
196
|
-
)
|
|
156
|
+
async def describe(self) -> dict[str, DataKey]:
|
|
157
|
+
return {
|
|
158
|
+
self._name: DataKey(
|
|
159
|
+
dtype="array",
|
|
160
|
+
shape=[
|
|
161
|
+
-1,
|
|
162
|
+
], # TODO describe properly - see https://github.com/DiamondLightSource/dodal/issues/253,
|
|
163
|
+
source=self._backend.source(self._name), # type: ignore
|
|
164
|
+
)
|
|
165
|
+
}
|
|
197
166
|
|
|
198
|
-
def
|
|
199
|
-
|
|
200
|
-
|
|
167
|
+
def get_position_from_gda_aperture_name(
|
|
168
|
+
self, gda_aperture_name: AperturePositionGDANames
|
|
169
|
+
) -> AperturePosition:
|
|
170
|
+
for aperture, detail in self._loaded_positions.items():
|
|
171
|
+
if detail.GDA_name == gda_aperture_name:
|
|
172
|
+
return aperture
|
|
173
|
+
raise ValueError(
|
|
174
|
+
f"Tried to convert unknown aperture name {gda_aperture_name} to a SingleAperturePosition"
|
|
175
|
+
)
|
|
201
176
|
|
|
202
|
-
def
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
raise InvalidApertureMove(f"Unknown aperture: {pos}")
|
|
177
|
+
def get_gda_name_for_position(self, position: AperturePosition) -> str:
|
|
178
|
+
detailed_position = self._loaded_positions[position]
|
|
179
|
+
return detailed_position.GDA_name
|
|
206
180
|
|
|
207
|
-
|
|
181
|
+
@AsyncStatus.wrap
|
|
182
|
+
async def set(self, value: AperturePosition):
|
|
183
|
+
position = self._loaded_positions[value]
|
|
184
|
+
await self._safe_move_within_datacollection_range(position.location)
|
|
208
185
|
|
|
209
186
|
def _get_motor_list(self):
|
|
210
187
|
return [
|
|
211
|
-
self.
|
|
212
|
-
self.
|
|
213
|
-
self.
|
|
214
|
-
self.
|
|
215
|
-
self.
|
|
188
|
+
self._aperture.x,
|
|
189
|
+
self._aperture.y,
|
|
190
|
+
self._aperture.z,
|
|
191
|
+
self._scatterguard.x,
|
|
192
|
+
self._scatterguard.y,
|
|
216
193
|
]
|
|
217
194
|
|
|
218
195
|
@AsyncStatus.wrap
|
|
@@ -223,31 +200,32 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
223
200
|
aperture_x, aperture_y, aperture_z, scatterguard_x, scatterguard_y = positions
|
|
224
201
|
|
|
225
202
|
await asyncio.gather(
|
|
226
|
-
self.
|
|
227
|
-
self.
|
|
228
|
-
self.
|
|
229
|
-
self.
|
|
230
|
-
self.
|
|
203
|
+
self._aperture.x.set(aperture_x),
|
|
204
|
+
self._aperture.y.set(aperture_y),
|
|
205
|
+
self._aperture.z.set(aperture_z),
|
|
206
|
+
self._scatterguard.x.set(scatterguard_x),
|
|
207
|
+
self._scatterguard.y.set(scatterguard_y),
|
|
231
208
|
)
|
|
232
209
|
|
|
233
|
-
async def
|
|
210
|
+
async def get_current_aperture_position(self) -> SingleAperturePosition:
|
|
234
211
|
"""
|
|
235
212
|
Returns the current aperture position using readback values
|
|
236
213
|
for SMALL, MEDIUM, LARGE. ROBOT_LOAD position defined when
|
|
237
214
|
mini aperture y <= ROBOT_LOAD.location.aperture_y + tolerance.
|
|
238
215
|
If no position is found then raises InvalidApertureMove.
|
|
239
216
|
"""
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
217
|
+
current_ap_y = await self._aperture.y.user_readback.get_value(cached=False)
|
|
218
|
+
robot_load_ap_y = self._loaded_positions[
|
|
219
|
+
AperturePosition.ROBOT_LOAD
|
|
220
|
+
].location.aperture_y
|
|
221
|
+
if await self._aperture.large.get_value(cached=False) == 1:
|
|
222
|
+
return self._loaded_positions[AperturePosition.LARGE]
|
|
223
|
+
elif await self._aperture.medium.get_value(cached=False) == 1:
|
|
224
|
+
return self._loaded_positions[AperturePosition.MEDIUM]
|
|
225
|
+
elif await self._aperture.small.get_value(cached=False) == 1:
|
|
226
|
+
return self._loaded_positions[AperturePosition.SMALL]
|
|
227
|
+
elif current_ap_y <= robot_load_ap_y + self._tolerances.ap_y:
|
|
228
|
+
return self._loaded_positions[AperturePosition.ROBOT_LOAD]
|
|
251
229
|
|
|
252
230
|
raise InvalidApertureMove("Current aperture/scatterguard state unrecognised")
|
|
253
231
|
|
|
@@ -259,46 +237,46 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
259
237
|
See https://github.com/DiamondLightSource/hyperion/wiki/Aperture-Scatterguard-Collisions
|
|
260
238
|
for why this is required.
|
|
261
239
|
"""
|
|
262
|
-
assert self.
|
|
240
|
+
assert self._loaded_positions is not None
|
|
263
241
|
# unpacking the position
|
|
264
242
|
aperture_x, aperture_y, aperture_z, scatterguard_x, scatterguard_y = pos
|
|
265
243
|
|
|
266
|
-
ap_z_in_position = await self.
|
|
244
|
+
ap_z_in_position = await self._aperture.z.motor_done_move.get_value()
|
|
267
245
|
if not ap_z_in_position:
|
|
268
246
|
raise InvalidApertureMove(
|
|
269
247
|
"ApertureScatterguard z is still moving. Wait for it to finish "
|
|
270
248
|
"before triggering another move."
|
|
271
249
|
)
|
|
272
250
|
|
|
273
|
-
current_ap_z = await self.
|
|
251
|
+
current_ap_z = await self._aperture.z.user_readback.get_value()
|
|
274
252
|
diff_on_z = abs(current_ap_z - aperture_z)
|
|
275
|
-
if diff_on_z > self.
|
|
253
|
+
if diff_on_z > self._tolerances.ap_z:
|
|
276
254
|
raise InvalidApertureMove(
|
|
277
255
|
"ApertureScatterguard safe move is not yet defined for positions "
|
|
278
256
|
"outside of LARGE, MEDIUM, SMALL, ROBOT_LOAD. "
|
|
279
|
-
f"Current aperture z ({current_ap_z}), outside of tolerance ({self.
|
|
257
|
+
f"Current aperture z ({current_ap_z}), outside of tolerance ({self._tolerances.ap_z}) from target ({aperture_z})."
|
|
280
258
|
)
|
|
281
259
|
|
|
282
|
-
current_ap_y = await self.
|
|
260
|
+
current_ap_y = await self._aperture.y.user_readback.get_value()
|
|
283
261
|
|
|
284
262
|
if aperture_y > current_ap_y:
|
|
285
263
|
await asyncio.gather(
|
|
286
|
-
self.
|
|
287
|
-
self.
|
|
264
|
+
self._scatterguard.x.set(scatterguard_x),
|
|
265
|
+
self._scatterguard.y.set(scatterguard_y),
|
|
288
266
|
)
|
|
289
267
|
await asyncio.gather(
|
|
290
|
-
self.
|
|
291
|
-
self.
|
|
292
|
-
self.
|
|
268
|
+
self._aperture.x.set(aperture_x),
|
|
269
|
+
self._aperture.y.set(aperture_y),
|
|
270
|
+
self._aperture.z.set(aperture_z),
|
|
293
271
|
)
|
|
294
272
|
return
|
|
295
273
|
await asyncio.gather(
|
|
296
|
-
self.
|
|
297
|
-
self.
|
|
298
|
-
self.
|
|
274
|
+
self._aperture.x.set(aperture_x),
|
|
275
|
+
self._aperture.y.set(aperture_y),
|
|
276
|
+
self._aperture.z.set(aperture_z),
|
|
299
277
|
)
|
|
300
278
|
|
|
301
279
|
await asyncio.gather(
|
|
302
|
-
self.
|
|
303
|
-
self.
|
|
280
|
+
self._scatterguard.x.set(scatterguard_x),
|
|
281
|
+
self._scatterguard.y.set(scatterguard_y),
|
|
304
282
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
|
-
from ophyd import
|
|
4
|
-
from ophyd import DetectorBase, EpicsSignal, Signal
|
|
4
|
+
from ophyd import EpicsSignal, Signal
|
|
5
5
|
from ophyd.areadetector.base import ADComponent as Cpt
|
|
6
6
|
from ophyd.areadetector.detectors import DetectorBase
|
|
7
7
|
|
|
@@ -60,7 +60,7 @@ class AdAravisDetector(SingleTriggerV33, DetectorBase):
|
|
|
60
60
|
signal.put_complete = True
|
|
61
61
|
self.cam.acquire.put_complete = True
|
|
62
62
|
|
|
63
|
-
def stage(self, *args, **kwargs):
|
|
63
|
+
def stage(self, *args, **kwargs) -> list[object]:
|
|
64
64
|
# We have to manually set the acquire period bcause the EPICS driver
|
|
65
65
|
# doesn't do it for us. If acquire time is a staged signal, we use the
|
|
66
66
|
# stage value to calculate the acquire period, otherwise we perform
|
|
@@ -69,13 +69,15 @@ class AdAravisDetector(SingleTriggerV33, DetectorBase):
|
|
|
69
69
|
acquire_time = self.stage_sigs[self.cam.acquire_time]
|
|
70
70
|
else:
|
|
71
71
|
acquire_time = self.cam.acquire_time.get()
|
|
72
|
-
self.stage_sigs[self.cam.acquire_period] =
|
|
72
|
+
self.stage_sigs[self.cam.acquire_period] = (
|
|
73
|
+
float(acquire_time) + _ACQUIRE_BUFFER_PERIOD
|
|
74
|
+
)
|
|
73
75
|
|
|
74
76
|
# Ensure detector warmed up
|
|
75
77
|
self._prime_hdf()
|
|
76
78
|
|
|
77
79
|
# Now calling the super method should set the acquire period
|
|
78
|
-
super(
|
|
80
|
+
return super().stage(*args, **kwargs)
|
|
79
81
|
|
|
80
82
|
def _prime_hdf(self) -> None:
|
|
81
83
|
"""
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from ophyd import Component as Cpt
|
|
2
1
|
from ophyd.areadetector.base import ADComponent as Cpt
|
|
3
2
|
from ophyd.areadetector.detectors import DetectorBase
|
|
4
3
|
|
|
@@ -33,7 +32,7 @@ class AdSimDetector(SingleTriggerV33, DetectorBase):
|
|
|
33
32
|
**self.stage_sigs, # type: ignore
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
def stage(self, *args, **kwargs):
|
|
35
|
+
def stage(self, *args, **kwargs) -> list[object]:
|
|
37
36
|
# We have to manually set the acquire period bcause the EPICS driver
|
|
38
37
|
# doesn't do it for us. If acquire time is a staged signal, we use the
|
|
39
38
|
# stage value to calculate the acquire period, otherwise we perform
|
|
@@ -45,4 +44,4 @@ class AdSimDetector(SingleTriggerV33, DetectorBase):
|
|
|
45
44
|
self.stage_sigs[self.cam.acquire_period] = acquire_time
|
|
46
45
|
|
|
47
46
|
# Now calling the super method should set the acquire period
|
|
48
|
-
super(
|
|
47
|
+
return super().stage(*args, **kwargs)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import time as ttime
|
|
2
2
|
|
|
3
3
|
from ophyd import Component as Cpt
|
|
4
|
-
from ophyd import EpicsSignal, EpicsSignalRO, Staged
|
|
4
|
+
from ophyd import DetectorBase, Device, EpicsSignal, EpicsSignalRO, Staged
|
|
5
5
|
from ophyd.areadetector import ADTriggerStatus, TriggerBase
|
|
6
6
|
from ophyd.areadetector.cam import AreaDetectorCam
|
|
7
7
|
from ophyd.areadetector.filestore_mixins import FileStoreHDF5, FileStoreIterativeWrite
|
|
@@ -14,6 +14,8 @@ class SingleTriggerV33(TriggerBase):
|
|
|
14
14
|
def __init__(self, *args, image_name=None, **kwargs):
|
|
15
15
|
super().__init__(*args, **kwargs)
|
|
16
16
|
if image_name is None:
|
|
17
|
+
# Ensure that this mixin is part of valid device with name
|
|
18
|
+
assert isinstance(self, Device)
|
|
17
19
|
image_name = "_".join([self.name, "image"])
|
|
18
20
|
self._image_name = image_name
|
|
19
21
|
|
|
@@ -29,10 +31,12 @@ class SingleTriggerV33(TriggerBase):
|
|
|
29
31
|
|
|
30
32
|
def _acq_done(*args, **kwargs):
|
|
31
33
|
# TODO sort out if anything useful in here
|
|
32
|
-
self._status._finished()
|
|
34
|
+
self._status._finished() # noqa: SLF001
|
|
33
35
|
|
|
34
36
|
self._acquisition_signal.put(1, use_complete=True, callback=_acq_done)
|
|
35
|
-
|
|
37
|
+
# Ensure that this mixin is part of valid Detector with generate_datum
|
|
38
|
+
assert isinstance(self, DetectorBase)
|
|
39
|
+
self.generate_datum(self._image_name, ttime.time())
|
|
36
40
|
return self._status
|
|
37
41
|
|
|
38
42
|
|
|
@@ -54,15 +58,18 @@ class SynchronisedAdDriverBase(AreaDetectorCam):
|
|
|
54
58
|
|
|
55
59
|
def ensure_nonblocking(self):
|
|
56
60
|
self.stage_sigs["wait_for_plugins"] = "Yes"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
cpt
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
if self.parent is not None:
|
|
62
|
+
for c in self.parent.component_names:
|
|
63
|
+
cpt = getattr(self.parent, c)
|
|
64
|
+
if cpt is self:
|
|
65
|
+
continue
|
|
66
|
+
if hasattr(cpt, "ensure_nonblocking"):
|
|
67
|
+
cpt.ensure_nonblocking()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ophyd code to be removed, only used for adim
|
|
71
|
+
# https://github.com/DiamondLightSource/dodal/issues/404
|
|
72
|
+
class Hdf5Writer(HDF5Plugin, FileStoreHDF5, FileStoreIterativeWrite): # type: ignore
|
|
66
73
|
""" """
|
|
67
74
|
|
|
68
75
|
pool_max_buffers = None
|
|
@@ -70,4 +77,5 @@ class Hdf5Writer(HDF5Plugin, FileStoreHDF5, FileStoreIterativeWrite):
|
|
|
70
77
|
file_number_write = None
|
|
71
78
|
|
|
72
79
|
def get_frames_per_point(self):
|
|
80
|
+
assert isinstance(self.parent, DetectorBase)
|
|
73
81
|
return self.parent.cam.num_images.get()
|
dodal/devices/cryostream.py
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
from
|
|
2
|
-
from ophyd import Device, EpicsSignal, EpicsSignalRO
|
|
1
|
+
from enum import Enum
|
|
3
2
|
|
|
3
|
+
from ophyd_async.core import StandardReadable
|
|
4
|
+
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
|
|
7
|
+
class InOut(str, Enum):
|
|
8
|
+
IN = "In"
|
|
9
|
+
OUT = "Out"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CryoStream(StandardReadable):
|
|
13
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
14
|
+
self.course = epics_signal_rw(InOut, f"{prefix}-EA-CJET-01:COARSE:CTRL")
|
|
15
|
+
self.fine = epics_signal_rw(InOut, f"{prefix}-EA-CJET-01:FINE:CTRL")
|
|
16
|
+
self.temperature_k = epics_signal_r(float, f"{prefix}-EA-CSTRM-01:TEMP")
|
|
17
|
+
self.back_pressure_bar = epics_signal_r(
|
|
18
|
+
float, f"{prefix}-EA-CSTRM-01:BACKPRESS"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
super().__init__(name)
|
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
from dodal.devices.detector.detector import
|
|
1
|
+
from dodal.devices.detector.det_dim_constants import EIGER2_X_16M_SIZE
|
|
2
|
+
from dodal.devices.detector.detector import (
|
|
3
|
+
DetectorDistanceToBeamXYConverter,
|
|
4
|
+
DetectorParams,
|
|
5
|
+
TriggerMode,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"DetectorParams",
|
|
10
|
+
"EIGER2_X_16M_SIZE",
|
|
11
|
+
"TriggerMode",
|
|
12
|
+
"DetectorDistanceToBeamXYConverter",
|
|
13
|
+
]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Generic, TypeVar
|
|
2
2
|
|
|
3
3
|
from pydantic.dataclasses import dataclass
|
|
4
4
|
|
|
@@ -11,7 +11,7 @@ class DetectorSize(Generic[T]):
|
|
|
11
11
|
height: T
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
ALL_DETECTORS:
|
|
14
|
+
ALL_DETECTORS: dict[str, "DetectorSizeConstants"] = {}
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@dataclass
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from enum import Enum, auto
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, root_validator, validator
|
|
5
5
|
|
|
@@ -41,8 +41,8 @@ class DetectorParams(BaseModel):
|
|
|
41
41
|
det_dist_to_beam_converter_path: str
|
|
42
42
|
trigger_mode: TriggerMode = TriggerMode.SET_FRAMES
|
|
43
43
|
detector_size_constants: DetectorSizeConstants = EIGER2_X_16M_SIZE
|
|
44
|
-
beam_xy_converter: DetectorDistanceToBeamXYConverter
|
|
45
|
-
run_number: int
|
|
44
|
+
beam_xy_converter: DetectorDistanceToBeamXYConverter = None # type: ignore # Filled in by validator
|
|
45
|
+
run_number: int = None # type: ignore # Filled in by validator
|
|
46
46
|
enable_dev_shm: bool = (
|
|
47
47
|
False # Remove in https://github.com/DiamondLightSource/hyperion/issues/1395
|
|
48
48
|
)
|
|
@@ -80,7 +80,7 @@ class DetectorParams(BaseModel):
|
|
|
80
80
|
directory += "/"
|
|
81
81
|
return directory
|
|
82
82
|
|
|
83
|
-
def get_beam_position_mm(self, detector_distance: float) ->
|
|
83
|
+
def get_beam_position_mm(self, detector_distance: float) -> tuple[float, float]:
|
|
84
84
|
x_beam_mm = self.beam_xy_converter.get_beam_xy_from_det_dist(
|
|
85
85
|
detector_distance, Axis.X_AXIS
|
|
86
86
|
)
|
|
@@ -105,7 +105,7 @@ class DetectorParams(BaseModel):
|
|
|
105
105
|
roi_size = self.detector_size_constants.roi_size_pixels
|
|
106
106
|
return roi_size if self.use_roi_mode else full_size
|
|
107
107
|
|
|
108
|
-
def get_beam_position_pixels(self, detector_distance: float) ->
|
|
108
|
+
def get_beam_position_pixels(self, detector_distance: float) -> tuple[float, float]:
|
|
109
109
|
full_size_pixels = self.detector_size_constants.det_size_pixels
|
|
110
110
|
roi_size_pixels = self.get_detector_size_pizels()
|
|
111
111
|
|