dls-dodal 1.29.4__py3-none-any.whl → 1.31.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.31.0.dist-info}/METADATA +29 -44
- dls_dodal-1.31.0.dist-info/RECORD +134 -0
- {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/WHEEL +1 -1
- dls_dodal-1.31.0.dist-info/entry_points.txt +3 -0
- dodal/__init__.py +1 -4
- dodal/_version.py +2 -2
- dodal/beamline_specific_utils/i03.py +1 -4
- dodal/beamlines/__init__.py +7 -1
- dodal/beamlines/i03.py +34 -29
- dodal/beamlines/i04.py +39 -16
- dodal/beamlines/i13_1.py +66 -0
- dodal/beamlines/i22.py +22 -22
- dodal/beamlines/i24.py +1 -1
- dodal/beamlines/p38.py +21 -21
- dodal/beamlines/p45.py +18 -16
- dodal/beamlines/p99.py +61 -0
- dodal/beamlines/training_rig.py +64 -0
- dodal/cli.py +6 -3
- dodal/common/beamlines/beamline_parameters.py +7 -6
- dodal/common/beamlines/beamline_utils.py +15 -14
- dodal/common/maths.py +1 -3
- dodal/common/types.py +6 -5
- dodal/common/udc_directory_provider.py +39 -21
- dodal/common/visit.py +60 -62
- dodal/devices/CTAB.py +22 -17
- dodal/devices/aperture.py +1 -1
- dodal/devices/aperturescatterguard.py +139 -209
- dodal/devices/areadetector/adaravis.py +8 -6
- dodal/devices/areadetector/adsim.py +2 -3
- dodal/devices/areadetector/adutils.py +20 -12
- dodal/devices/areadetector/plugins/MJPG.py +2 -1
- dodal/devices/backlight.py +12 -1
- dodal/devices/cryostream.py +19 -7
- dodal/devices/dcm.py +1 -1
- 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 +33 -32
- 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 +18 -27
- dodal/devices/fluorescence_detector_motion.py +13 -4
- dodal/devices/focusing_mirror.py +6 -6
- dodal/devices/hutch_shutter.py +4 -4
- dodal/devices/i22/dcm.py +5 -4
- dodal/devices/i22/fswitch.py +10 -6
- dodal/devices/i22/nxsas.py +55 -43
- dodal/devices/i24/aperture.py +1 -1
- dodal/devices/i24/beamstop.py +1 -1
- dodal/devices/i24/dcm.py +1 -1
- dodal/devices/i24/{I24_detector_motion.py → i24_detector_motion.py} +1 -1
- dodal/devices/i24/pmac.py +67 -12
- dodal/devices/ipin.py +7 -4
- dodal/devices/linkam3.py +12 -6
- dodal/devices/logging_ophyd_device.py +1 -1
- dodal/devices/motors.py +32 -6
- 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 +129 -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 +31 -20
- dodal/devices/scatterguard.py +1 -1
- dodal/devices/scintillator.py +8 -5
- dodal/devices/slits.py +1 -1
- dodal/devices/smargon.py +4 -4
- dodal/devices/status.py +2 -31
- dodal/devices/tetramm.py +23 -19
- dodal/devices/thawer.py +5 -3
- dodal/devices/training_rig/__init__.py +0 -0
- dodal/devices/training_rig/sample_stage.py +10 -0
- dodal/devices/turbo_slit.py +1 -1
- dodal/devices/undulator.py +1 -1
- dodal/devices/undulator_dcm.py +6 -8
- dodal/devices/util/adjuster_plans.py +3 -3
- 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 +1 -25
- dodal/devices/xspress3/xspress3.py +1 -1
- dodal/devices/zebra.py +15 -10
- dodal/devices/zebra_controlled_shutter.py +26 -11
- dodal/devices/zocalo/zocalo_interaction.py +10 -2
- dodal/devices/zocalo/zocalo_results.py +36 -19
- dodal/log.py +46 -15
- dodal/plans/check_topup.py +65 -10
- dodal/plans/data_session_metadata.py +8 -9
- 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/beamstop.py +0 -8
- dodal/devices/qbpm1.py +0 -8
- {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/LICENSE +0 -0
- {dls_dodal-1.29.4.dist-info → dls_dodal-1.31.0.dist-info}/top_level.txt +0 -0
|
@@ -1,226 +1,147 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
|
-
from collections import OrderedDict, namedtuple
|
|
3
|
-
from dataclasses import asdict, dataclass
|
|
4
4
|
from enum import Enum
|
|
5
5
|
|
|
6
|
-
from bluesky.protocols import Movable
|
|
7
|
-
from ophyd_async.core import
|
|
8
|
-
|
|
6
|
+
from bluesky.protocols import Movable
|
|
7
|
+
from ophyd_async.core import (
|
|
8
|
+
AsyncStatus,
|
|
9
|
+
HintedSignal,
|
|
10
|
+
StandardReadable,
|
|
11
|
+
soft_signal_rw,
|
|
12
|
+
)
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
9
14
|
|
|
15
|
+
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
|
|
10
16
|
from dodal.devices.aperture import Aperture
|
|
11
17
|
from dodal.devices.scatterguard import Scatterguard
|
|
12
|
-
from dodal.log import LOGGER
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
class InvalidApertureMove(Exception):
|
|
16
21
|
pass
|
|
17
22
|
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
|
|
24
|
+
class AperturePosition(BaseModel):
|
|
25
|
+
aperture_x: float
|
|
26
|
+
aperture_y: float
|
|
27
|
+
aperture_z: float
|
|
28
|
+
scatterguard_x: float
|
|
29
|
+
scatterguard_y: float
|
|
30
|
+
radius: float | None = Field(json_schema_extra={"units": "µm"}, default=None)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def values(self) -> tuple[float, float, float, float, float]:
|
|
34
|
+
return (
|
|
35
|
+
self.aperture_x,
|
|
36
|
+
self.aperture_y,
|
|
37
|
+
self.aperture_z,
|
|
38
|
+
self.scatterguard_x,
|
|
39
|
+
self.scatterguard_y,
|
|
40
|
+
)
|
|
30
41
|
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# Default values are needed as ophyd_async sim does not respect initial_values of
|
|
43
|
-
# soft signal backends see https://github.com/bluesky/ophyd-async/issues/266
|
|
44
|
-
name: str = ""
|
|
45
|
-
GDA_name: str = ""
|
|
46
|
-
radius_microns: float | None = None
|
|
47
|
-
location: ApertureFiveDimensionalLocation = ApertureFiveDimensionalLocation(
|
|
48
|
-
0, 0, 0, 0, 0
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
# Use StrEnum once we stop python 3.10 support
|
|
53
|
-
class AperturePositionGDANames(str, Enum):
|
|
54
|
-
LARGE_APERTURE = "LARGE_APERTURE"
|
|
55
|
-
MEDIUM_APERTURE = "MEDIUM_APERTURE"
|
|
56
|
-
SMALL_APERTURE = "SMALL_APERTURE"
|
|
57
|
-
ROBOT_LOAD = "ROBOT_LOAD"
|
|
42
|
+
@staticmethod
|
|
43
|
+
def tolerances_from_gda_params(
|
|
44
|
+
params: GDABeamlineParameters,
|
|
45
|
+
) -> AperturePosition:
|
|
46
|
+
return AperturePosition(
|
|
47
|
+
aperture_x=params["miniap_x_tolerance"],
|
|
48
|
+
aperture_y=params["miniap_y_tolerance"],
|
|
49
|
+
aperture_z=params["miniap_z_tolerance"],
|
|
50
|
+
scatterguard_x=params["sg_x_tolerance"],
|
|
51
|
+
scatterguard_y=params["sg_y_tolerance"],
|
|
52
|
+
)
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
radius_microns,
|
|
73
|
-
ApertureFiveDimensionalLocation(
|
|
74
|
-
params[f"miniap_x_{GDA_name}"],
|
|
75
|
-
params[f"miniap_y_{GDA_name}"],
|
|
76
|
-
params[f"miniap_z_{GDA_name}"],
|
|
77
|
-
params[f"sg_x_{GDA_name}"],
|
|
78
|
-
params[f"sg_y_{GDA_name}"],
|
|
79
|
-
),
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def tolerances_from_params(params: dict) -> ApertureScatterguardTolerances:
|
|
84
|
-
return ApertureScatterguardTolerances(
|
|
85
|
-
ap_x=params["miniap_x_tolerance"],
|
|
86
|
-
ap_y=params["miniap_y_tolerance"],
|
|
87
|
-
ap_z=params["miniap_z_tolerance"],
|
|
88
|
-
sg_x=params["sg_x_tolerance"],
|
|
89
|
-
sg_y=params["sg_y_tolerance"],
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@dataclass
|
|
94
|
-
class AperturePositions:
|
|
95
|
-
"""Holds the motor positions needed to select a particular aperture size. This class should be instantiated with definitions for its sizes
|
|
96
|
-
using from_gda_beamline_params"""
|
|
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),
|
|
54
|
+
@staticmethod
|
|
55
|
+
def from_gda_params(
|
|
56
|
+
name: ApertureValue,
|
|
57
|
+
radius: float | None,
|
|
58
|
+
params: GDABeamlineParameters,
|
|
59
|
+
) -> AperturePosition:
|
|
60
|
+
return AperturePosition(
|
|
61
|
+
aperture_x=params[f"miniap_x_{name.value}"],
|
|
62
|
+
aperture_y=params[f"miniap_y_{name.value}"],
|
|
63
|
+
aperture_z=params[f"miniap_z_{name.value}"],
|
|
64
|
+
scatterguard_x=params[f"sg_x_{name.value}"],
|
|
65
|
+
scatterguard_y=params[f"sg_y_{name.value}"],
|
|
66
|
+
radius=radius,
|
|
125
67
|
)
|
|
126
68
|
|
|
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
69
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
70
|
+
class ApertureValue(str, Enum):
|
|
71
|
+
"""Maps from a short usable name to the value name in the GDA Beamline parameters"""
|
|
72
|
+
|
|
73
|
+
ROBOT_LOAD = "ROBOT_LOAD"
|
|
74
|
+
SMALL = "SMALL_APERTURE"
|
|
75
|
+
MEDIUM = "MEDIUM_APERTURE"
|
|
76
|
+
LARGE = "LARGE_APERTURE"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def load_positions_from_beamline_parameters(
|
|
80
|
+
params: GDABeamlineParameters,
|
|
81
|
+
) -> dict[ApertureValue, AperturePosition]:
|
|
82
|
+
return {
|
|
83
|
+
ApertureValue.ROBOT_LOAD: AperturePosition.from_gda_params(
|
|
84
|
+
ApertureValue.ROBOT_LOAD, None, params
|
|
85
|
+
),
|
|
86
|
+
ApertureValue.SMALL: AperturePosition.from_gda_params(
|
|
87
|
+
ApertureValue.SMALL, 20, params
|
|
88
|
+
),
|
|
89
|
+
ApertureValue.MEDIUM: AperturePosition.from_gda_params(
|
|
90
|
+
ApertureValue.MEDIUM, 50, params
|
|
91
|
+
),
|
|
92
|
+
ApertureValue.LARGE: AperturePosition.from_gda_params(
|
|
93
|
+
ApertureValue.LARGE, 100, params
|
|
94
|
+
),
|
|
95
|
+
}
|
|
145
96
|
|
|
146
97
|
|
|
147
98
|
class ApertureScatterguard(StandardReadable, Movable):
|
|
148
|
-
def __init__(
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
loaded_positions: dict[ApertureValue, AperturePosition],
|
|
102
|
+
tolerances: AperturePosition,
|
|
103
|
+
prefix: str = "",
|
|
104
|
+
name: str = "",
|
|
105
|
+
) -> None:
|
|
149
106
|
self.aperture = Aperture(prefix + "-MO-MAPT-01:")
|
|
150
107
|
self.scatterguard = Scatterguard(prefix + "-MO-SCAT-01:")
|
|
151
|
-
self.
|
|
152
|
-
self.
|
|
153
|
-
|
|
154
|
-
|
|
108
|
+
self.radius = soft_signal_rw(float, units="µm")
|
|
109
|
+
self._loaded_positions = loaded_positions
|
|
110
|
+
self._tolerances = tolerances
|
|
111
|
+
self.add_readables(
|
|
112
|
+
[
|
|
113
|
+
self.aperture.x.user_readback,
|
|
114
|
+
self.aperture.y.user_readback,
|
|
115
|
+
self.aperture.z.user_readback,
|
|
116
|
+
self.scatterguard.x.user_readback,
|
|
117
|
+
self.scatterguard.y.user_readback,
|
|
118
|
+
self.radius,
|
|
119
|
+
],
|
|
155
120
|
)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
self.set_readable_signals(
|
|
159
|
-
read=[
|
|
160
|
-
self.selected_aperture,
|
|
161
|
-
]
|
|
162
|
-
)
|
|
163
|
-
super().__init__(name)
|
|
164
|
-
|
|
165
|
-
class ApertureConverter(SoftConverter):
|
|
166
|
-
# Ophyd-async #311 should add a default converter for dataclasses to do this
|
|
167
|
-
def reading(
|
|
168
|
-
self, value: SingleAperturePosition, timestamp: float, severity: int
|
|
169
|
-
) -> Reading:
|
|
170
|
-
return Reading(
|
|
171
|
-
value=asdict(value),
|
|
172
|
-
timestamp=timestamp,
|
|
173
|
-
alarm_severity=-1 if severity > 2 else severity,
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
class SelectedAperture(SignalR):
|
|
177
|
-
async def read(self, *args, **kwargs):
|
|
178
|
-
assert isinstance(self.parent, ApertureScatterguard)
|
|
179
|
-
await self._backend.put(await self.parent._get_current_aperture_position())
|
|
180
|
-
return {self.name: await self._backend.get_reading()}
|
|
181
|
-
|
|
182
|
-
async def describe(self):
|
|
183
|
-
return OrderedDict(
|
|
184
|
-
[
|
|
185
|
-
(
|
|
186
|
-
self._name,
|
|
187
|
-
{
|
|
188
|
-
"source": self._backend.source(self._name), # type: ignore
|
|
189
|
-
"dtype": "array",
|
|
190
|
-
"shape": [
|
|
191
|
-
-1,
|
|
192
|
-
], # TODO describe properly - see https://github.com/DiamondLightSource/dodal/issues/253
|
|
193
|
-
},
|
|
194
|
-
),
|
|
195
|
-
],
|
|
196
|
-
)
|
|
121
|
+
with self.add_children_as_readables(HintedSignal):
|
|
122
|
+
self.selected_aperture = soft_signal_rw(ApertureValue)
|
|
197
123
|
|
|
198
|
-
|
|
199
|
-
LOGGER.info(f"{self.name} loaded in {positions}")
|
|
200
|
-
self.aperture_positions = positions
|
|
201
|
-
|
|
202
|
-
def set(self, pos: SingleAperturePosition) -> AsyncStatus:
|
|
203
|
-
assert isinstance(self.aperture_positions, AperturePositions)
|
|
204
|
-
if pos not in self.aperture_positions.as_list():
|
|
205
|
-
raise InvalidApertureMove(f"Unknown aperture: {pos}")
|
|
124
|
+
super().__init__(name)
|
|
206
125
|
|
|
207
|
-
|
|
126
|
+
def get_position_from_gda_aperture_name(
|
|
127
|
+
self, gda_aperture_name: str
|
|
128
|
+
) -> ApertureValue:
|
|
129
|
+
return ApertureValue(gda_aperture_name)
|
|
208
130
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
self.aperture.z,
|
|
214
|
-
self.scatterguard.x,
|
|
215
|
-
self.scatterguard.y,
|
|
216
|
-
]
|
|
131
|
+
@AsyncStatus.wrap
|
|
132
|
+
async def set(self, value: ApertureValue):
|
|
133
|
+
position = self._loaded_positions[value]
|
|
134
|
+
await self._safe_move_within_datacollection_range(position, value)
|
|
217
135
|
|
|
218
136
|
@AsyncStatus.wrap
|
|
219
|
-
async def _set_raw_unsafe(self,
|
|
137
|
+
async def _set_raw_unsafe(self, position: AperturePosition):
|
|
220
138
|
"""Accept the risks and move in an unsafe way. Collisions possible."""
|
|
139
|
+
if position.radius is not None:
|
|
140
|
+
await self.radius.set(position.radius)
|
|
221
141
|
|
|
222
|
-
|
|
223
|
-
|
|
142
|
+
aperture_x, aperture_y, aperture_z, scatterguard_x, scatterguard_y = (
|
|
143
|
+
position.values
|
|
144
|
+
)
|
|
224
145
|
|
|
225
146
|
await asyncio.gather(
|
|
226
147
|
self.aperture.x.set(aperture_x),
|
|
@@ -229,39 +150,41 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
229
150
|
self.scatterguard.x.set(scatterguard_x),
|
|
230
151
|
self.scatterguard.y.set(scatterguard_y),
|
|
231
152
|
)
|
|
153
|
+
try:
|
|
154
|
+
value = await self.get_current_aperture_position()
|
|
155
|
+
self.selected_aperture.set(value)
|
|
156
|
+
except InvalidApertureMove:
|
|
157
|
+
self.selected_aperture.set(None) # type: ignore
|
|
232
158
|
|
|
233
|
-
async def
|
|
159
|
+
async def get_current_aperture_position(self) -> ApertureValue:
|
|
234
160
|
"""
|
|
235
161
|
Returns the current aperture position using readback values
|
|
236
162
|
for SMALL, MEDIUM, LARGE. ROBOT_LOAD position defined when
|
|
237
163
|
mini aperture y <= ROBOT_LOAD.location.aperture_y + tolerance.
|
|
238
164
|
If no position is found then raises InvalidApertureMove.
|
|
239
165
|
"""
|
|
240
|
-
assert isinstance(self.aperture_positions, AperturePositions)
|
|
241
166
|
current_ap_y = await self.aperture.y.user_readback.get_value(cached=False)
|
|
242
|
-
robot_load_ap_y = self.
|
|
167
|
+
robot_load_ap_y = self._loaded_positions[ApertureValue.ROBOT_LOAD].aperture_y
|
|
243
168
|
if await self.aperture.large.get_value(cached=False) == 1:
|
|
244
|
-
return
|
|
169
|
+
return ApertureValue.LARGE
|
|
245
170
|
elif await self.aperture.medium.get_value(cached=False) == 1:
|
|
246
|
-
return
|
|
171
|
+
return ApertureValue.MEDIUM
|
|
247
172
|
elif await self.aperture.small.get_value(cached=False) == 1:
|
|
248
|
-
return
|
|
249
|
-
elif current_ap_y <= robot_load_ap_y + self.
|
|
250
|
-
return
|
|
173
|
+
return ApertureValue.SMALL
|
|
174
|
+
elif current_ap_y <= robot_load_ap_y + self._tolerances.aperture_y:
|
|
175
|
+
return ApertureValue.ROBOT_LOAD
|
|
251
176
|
|
|
252
177
|
raise InvalidApertureMove("Current aperture/scatterguard state unrecognised")
|
|
253
178
|
|
|
254
179
|
async def _safe_move_within_datacollection_range(
|
|
255
|
-
self,
|
|
180
|
+
self, position: AperturePosition, value: ApertureValue
|
|
256
181
|
):
|
|
257
182
|
"""
|
|
258
183
|
Move the aperture and scatterguard combo safely to a new position.
|
|
259
184
|
See https://github.com/DiamondLightSource/hyperion/wiki/Aperture-Scatterguard-Collisions
|
|
260
185
|
for why this is required.
|
|
261
186
|
"""
|
|
262
|
-
assert self.
|
|
263
|
-
# unpacking the position
|
|
264
|
-
aperture_x, aperture_y, aperture_z, scatterguard_x, scatterguard_y = pos
|
|
187
|
+
assert self._loaded_positions is not None
|
|
265
188
|
|
|
266
189
|
ap_z_in_position = await self.aperture.z.motor_done_move.get_value()
|
|
267
190
|
if not ap_z_in_position:
|
|
@@ -271,17 +194,23 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
271
194
|
)
|
|
272
195
|
|
|
273
196
|
current_ap_z = await self.aperture.z.user_readback.get_value()
|
|
274
|
-
diff_on_z = abs(current_ap_z - aperture_z)
|
|
275
|
-
if diff_on_z > self.
|
|
197
|
+
diff_on_z = abs(current_ap_z - position.aperture_z)
|
|
198
|
+
if diff_on_z > self._tolerances.aperture_z:
|
|
276
199
|
raise InvalidApertureMove(
|
|
277
200
|
"ApertureScatterguard safe move is not yet defined for positions "
|
|
278
201
|
"outside of LARGE, MEDIUM, SMALL, ROBOT_LOAD. "
|
|
279
|
-
f"Current aperture z ({current_ap_z}), outside of tolerance ({self.
|
|
202
|
+
f"Current aperture z ({current_ap_z}), outside of tolerance ({self._tolerances.aperture_z}) from target ({position.aperture_z})."
|
|
280
203
|
)
|
|
281
204
|
|
|
282
205
|
current_ap_y = await self.aperture.y.user_readback.get_value()
|
|
206
|
+
if position.radius is not None:
|
|
207
|
+
await self.radius.set(position.radius)
|
|
208
|
+
|
|
209
|
+
aperture_x, aperture_y, aperture_z, scatterguard_x, scatterguard_y = (
|
|
210
|
+
position.values
|
|
211
|
+
)
|
|
283
212
|
|
|
284
|
-
if aperture_y > current_ap_y:
|
|
213
|
+
if position.aperture_y > current_ap_y:
|
|
285
214
|
await asyncio.gather(
|
|
286
215
|
self.scatterguard.x.set(scatterguard_x),
|
|
287
216
|
self.scatterguard.y.set(scatterguard_y),
|
|
@@ -302,3 +231,4 @@ class ApertureScatterguard(StandardReadable, Movable):
|
|
|
302
231
|
self.scatterguard.x.set(scatterguard_x),
|
|
303
232
|
self.scatterguard.y.set(scatterguard_y),
|
|
304
233
|
)
|
|
234
|
+
await self.selected_aperture.set(value)
|
|
@@ -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()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import threading
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from io import BytesIO
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
import requests
|
|
@@ -70,7 +71,7 @@ class MJPG(Device, ABC):
|
|
|
70
71
|
try:
|
|
71
72
|
response = requests.get(url_str, stream=True)
|
|
72
73
|
response.raise_for_status()
|
|
73
|
-
with Image.open(response.
|
|
74
|
+
with Image.open(BytesIO(response.content)) as image:
|
|
74
75
|
self.post_processing(image)
|
|
75
76
|
st.set_finished()
|
|
76
77
|
except requests.HTTPError as e:
|
dodal/devices/backlight.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from asyncio import sleep
|
|
1
2
|
from enum import Enum
|
|
2
3
|
|
|
3
4
|
from ophyd_async.core import AsyncStatus, StandardReadable
|
|
@@ -17,6 +18,8 @@ class BacklightPosition(str, Enum):
|
|
|
17
18
|
class Backlight(StandardReadable):
|
|
18
19
|
"""Simple device to trigger the pneumatic in/out."""
|
|
19
20
|
|
|
21
|
+
TIME_TO_MOVE_S = 1 # Tested using a stopwatch on the beamline 09/2024
|
|
22
|
+
|
|
20
23
|
def __init__(self, prefix: str, name: str = "") -> None:
|
|
21
24
|
with self.add_children_as_readables():
|
|
22
25
|
self.power = epics_signal_rw(BacklightPower, prefix + "-EA-BLIT-01:TOGGLE")
|
|
@@ -28,9 +31,17 @@ class Backlight(StandardReadable):
|
|
|
28
31
|
@AsyncStatus.wrap
|
|
29
32
|
async def set(self, position: BacklightPosition):
|
|
30
33
|
"""This setter will turn the backlight on when we move it in to the beam and off
|
|
31
|
-
when we move it out.
|
|
34
|
+
when we move it out.
|
|
35
|
+
|
|
36
|
+
Moving the backlight in/out is a pneumatic axis and we have no readback on its
|
|
37
|
+
position so it appears to us to instantly move. In fact it does take some time
|
|
38
|
+
to move completely in/out so we sleep here to simulate this.
|
|
39
|
+
"""
|
|
40
|
+
old_position = await self.position.get_value()
|
|
32
41
|
await self.position.set(position)
|
|
33
42
|
if position == BacklightPosition.OUT:
|
|
34
43
|
await self.power.set(BacklightPower.OFF)
|
|
35
44
|
else:
|
|
36
45
|
await self.power.set(BacklightPower.ON)
|
|
46
|
+
if old_position != position:
|
|
47
|
+
await sleep(self.TIME_TO_MOVE_S)
|
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)
|
dodal/devices/dcm.py
CHANGED
|
@@ -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
|