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,7 +1,7 @@
|
|
|
1
1
|
from enum import Enum, auto
|
|
2
|
-
from
|
|
2
|
+
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel,
|
|
4
|
+
from pydantic import BaseModel, Field, field_serializer, field_validator
|
|
5
5
|
|
|
6
6
|
from dodal.devices.detector.det_dim_constants import (
|
|
7
7
|
EIGER2_X_16M_SIZE,
|
|
@@ -28,9 +28,12 @@ class DetectorParams(BaseModel):
|
|
|
28
28
|
"""Holds parameters for the detector. Provides access to a list of Dectris detector
|
|
29
29
|
sizes and a converter for distance to beam centre."""
|
|
30
30
|
|
|
31
|
+
# https://github.com/pydantic/pydantic/issues/8379
|
|
32
|
+
# Must use model_dump(by_alias=True) if serialising!
|
|
33
|
+
|
|
31
34
|
expected_energy_ev: float | None = None
|
|
32
35
|
exposure_time: float
|
|
33
|
-
directory: str
|
|
36
|
+
directory: str # : Path https://github.com/DiamondLightSource/dodal/issues/774
|
|
34
37
|
prefix: str
|
|
35
38
|
detector_distance: float
|
|
36
39
|
omega_start: float
|
|
@@ -39,48 +42,46 @@ class DetectorParams(BaseModel):
|
|
|
39
42
|
num_triggers: int
|
|
40
43
|
use_roi_mode: bool
|
|
41
44
|
det_dist_to_beam_converter_path: str
|
|
45
|
+
override_run_number: int | None = Field(default=None, alias="run_number")
|
|
42
46
|
trigger_mode: TriggerMode = TriggerMode.SET_FRAMES
|
|
43
47
|
detector_size_constants: DetectorSizeConstants = EIGER2_X_16M_SIZE
|
|
44
|
-
beam_xy_converter: DetectorDistanceToBeamXYConverter
|
|
45
|
-
run_number: int
|
|
46
48
|
enable_dev_shm: bool = (
|
|
47
49
|
False # Remove in https://github.com/DiamondLightSource/hyperion/issues/1395
|
|
48
50
|
)
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
values["beam_xy_converter"] = DetectorDistanceToBeamXYConverter(
|
|
61
|
-
values["det_dist_to_beam_converter_path"]
|
|
52
|
+
@property
|
|
53
|
+
def beam_xy_converter(self) -> DetectorDistanceToBeamXYConverter:
|
|
54
|
+
return DetectorDistanceToBeamXYConverter(self.det_dist_to_beam_converter_path)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def run_number(self) -> int:
|
|
58
|
+
return (
|
|
59
|
+
get_run_number(self.directory, self.prefix)
|
|
60
|
+
if self.override_run_number is None
|
|
61
|
+
else self.override_run_number
|
|
62
62
|
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
) -> DetectorSizeConstants:
|
|
63
|
+
|
|
64
|
+
@field_serializer("detector_size_constants")
|
|
65
|
+
def serialize_detector_size_constants(self, size: DetectorSizeConstants):
|
|
66
|
+
return size.det_type_string
|
|
67
|
+
|
|
68
|
+
@field_validator("detector_size_constants", mode="before")
|
|
69
|
+
@classmethod
|
|
70
|
+
def _parse_detector_size_constants(cls, det_type: str) -> DetectorSizeConstants:
|
|
71
71
|
return (
|
|
72
72
|
det_type
|
|
73
73
|
if isinstance(det_type, DetectorSizeConstants)
|
|
74
74
|
else constants_from_type(det_type)
|
|
75
75
|
)
|
|
76
76
|
|
|
77
|
-
@
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
@field_validator("directory", mode="before")
|
|
78
|
+
@classmethod
|
|
79
|
+
def _parse_directory(cls, directory: str | Path) -> str:
|
|
80
|
+
path = Path(directory)
|
|
81
|
+
assert path.is_dir()
|
|
82
|
+
return str(path)
|
|
82
83
|
|
|
83
|
-
def get_beam_position_mm(self, detector_distance: float) ->
|
|
84
|
+
def get_beam_position_mm(self, detector_distance: float) -> tuple[float, float]:
|
|
84
85
|
x_beam_mm = self.beam_xy_converter.get_beam_xy_from_det_dist(
|
|
85
86
|
detector_distance, Axis.X_AXIS
|
|
86
87
|
)
|
|
@@ -105,7 +106,7 @@ class DetectorParams(BaseModel):
|
|
|
105
106
|
roi_size = self.detector_size_constants.roi_size_pixels
|
|
106
107
|
return roi_size if self.use_roi_mode else full_size
|
|
107
108
|
|
|
108
|
-
def get_beam_position_pixels(self, detector_distance: float) ->
|
|
109
|
+
def get_beam_position_pixels(self, detector_distance: float) -> tuple[float, float]:
|
|
109
110
|
full_size_pixels = self.detector_size_constants.det_size_pixels
|
|
110
111
|
roi_size_pixels = self.get_detector_size_pizels()
|
|
111
112
|
|
|
@@ -1,37 +1,44 @@
|
|
|
1
|
-
from enum import
|
|
1
|
+
from enum import Enum
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
3
|
+
from ophyd_async.core import Device
|
|
4
|
+
from ophyd_async.epics.motor import Motor
|
|
5
|
+
from ophyd_async.epics.signal import epics_signal_r, epics_signal_rw
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
class ShutterState(
|
|
8
|
-
CLOSED =
|
|
9
|
-
OPEN =
|
|
8
|
+
class ShutterState(str, Enum):
|
|
9
|
+
CLOSED = "Closed"
|
|
10
|
+
OPEN = "Open"
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class DetectorMotion(Device):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
14
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
15
|
+
device_prefix = "-MO-DET-01:"
|
|
16
|
+
pmac_prefix = "-MO-PMAC-02:"
|
|
17
|
+
|
|
18
|
+
self.upstream_x = Motor(f"{prefix}{device_prefix}UPSTREAMX")
|
|
19
|
+
self.downstream_x = Motor(f"{prefix}{device_prefix}DOWNSTREAMX")
|
|
20
|
+
self.x = Motor(f"{prefix}{device_prefix}X")
|
|
21
|
+
self.y = Motor(f"{prefix}{device_prefix}Y")
|
|
22
|
+
self.z = Motor(f"{prefix}{device_prefix}Z")
|
|
23
|
+
self.yaw = Motor(f"{prefix}{device_prefix}YAW")
|
|
24
|
+
|
|
25
|
+
self.shutter = epics_signal_rw(
|
|
26
|
+
ShutterState, f"{prefix}{device_prefix}SET_SHUTTER_STATE"
|
|
27
|
+
)
|
|
28
|
+
self.shutter_closed_lim = epics_signal_r(
|
|
29
|
+
float, f"{prefix}{device_prefix}CLOSE_LIMIT"
|
|
30
|
+
) # on limit = 1, off = 0
|
|
31
|
+
self.shutter_open_lim = epics_signal_r(
|
|
32
|
+
float, f"{prefix}{device_prefix}OPEN_LIMIT"
|
|
33
|
+
) # on limit = 1, off = 0
|
|
34
|
+
self.z_disabled = epics_signal_r(
|
|
35
|
+
float, f"{prefix}{device_prefix}Z:DISABLED"
|
|
36
|
+
) # robot interlock, 0=ok to move, 1=blocked
|
|
37
|
+
self.crate_power = epics_signal_r(
|
|
38
|
+
float, f"{prefix}{pmac_prefix}CRATE2_HEALTHY"
|
|
39
|
+
) # returns 0 if no power
|
|
40
|
+
self.in_robot_load_safe_position = epics_signal_r(
|
|
41
|
+
int, f"{prefix}{pmac_prefix}GPIO_INP_BITS.B2"
|
|
42
|
+
) # returns 1 if safe
|
|
43
|
+
|
|
44
|
+
super().__init__(name)
|
dodal/devices/eiger.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# type: ignore # Eiger will soon be ophyd-async https://github.com/DiamondLightSource/dodal/issues/700
|
|
1
2
|
from enum import Enum
|
|
2
3
|
|
|
3
4
|
from ophyd import Component, Device, EpicsSignalRO, Signal
|
|
@@ -12,10 +13,6 @@ from dodal.log import LOGGER
|
|
|
12
13
|
|
|
13
14
|
FREE_RUN_MAX_IMAGES = 1000000
|
|
14
15
|
|
|
15
|
-
# TODO present for testing purposes, remove
|
|
16
|
-
TEST_1169_FIX = True
|
|
17
|
-
TEST_1169_INJECT = False
|
|
18
|
-
|
|
19
16
|
|
|
20
17
|
class InternalEigerTriggerMode(Enum):
|
|
21
18
|
INTERNAL_SERIES = 0
|
|
@@ -56,17 +53,15 @@ class EigerDetector(Device):
|
|
|
56
53
|
cls,
|
|
57
54
|
params: DetectorParams,
|
|
58
55
|
name: str = "EigerDetector",
|
|
59
|
-
*args,
|
|
60
|
-
**kwargs,
|
|
61
56
|
):
|
|
62
|
-
det = cls(name=name
|
|
57
|
+
det = cls(name=name)
|
|
63
58
|
det.set_detector_parameters(params)
|
|
64
59
|
return det
|
|
65
60
|
|
|
66
61
|
def set_detector_parameters(self, detector_params: DetectorParams):
|
|
67
62
|
self.detector_params = detector_params
|
|
68
63
|
if self.detector_params is None:
|
|
69
|
-
raise
|
|
64
|
+
raise ValueError("Parameters for scan must be specified")
|
|
70
65
|
|
|
71
66
|
to_check = [
|
|
72
67
|
(
|
|
@@ -103,7 +98,7 @@ class EigerDetector(Device):
|
|
|
103
98
|
|
|
104
99
|
def stage(self):
|
|
105
100
|
self.wait_on_arming_if_started()
|
|
106
|
-
if
|
|
101
|
+
if not self.is_armed():
|
|
107
102
|
LOGGER.info("Eiger not armed, arming")
|
|
108
103
|
|
|
109
104
|
self.async_stage().wait(timeout=self.ARMING_TIMEOUT)
|
|
@@ -318,7 +313,9 @@ class EigerDetector(Device):
|
|
|
318
313
|
|
|
319
314
|
def _finish_arm(self) -> Status:
|
|
320
315
|
LOGGER.info("Eiger staging: Finishing arming")
|
|
321
|
-
|
|
316
|
+
status = Status()
|
|
317
|
+
status.set_finished()
|
|
318
|
+
return status
|
|
322
319
|
|
|
323
320
|
def forward_bit_depth_to_filewriter(self):
|
|
324
321
|
bit_depth = self.bit_depth.get()
|
|
@@ -341,6 +338,10 @@ class EigerDetector(Device):
|
|
|
341
338
|
functions_to_do_arm.append(self.enable_roi_mode)
|
|
342
339
|
|
|
343
340
|
arming_sequence_funcs = [
|
|
341
|
+
# If a beam dump occurs after arming the eiger but prior to eiger staging,
|
|
342
|
+
# the odin may timeout which will cause the arming sequence to be retried;
|
|
343
|
+
# if this previously completed successfully we must reset the odin first
|
|
344
|
+
self.odin.stop,
|
|
344
345
|
lambda: self.change_dev_shm(detector_params.enable_dev_shm),
|
|
345
346
|
lambda: self.set_detector_threshold(detector_params.expected_energy_ev),
|
|
346
347
|
self.set_cam_pvs,
|
|
@@ -354,11 +355,6 @@ class EigerDetector(Device):
|
|
|
354
355
|
self._wait_fan_ready,
|
|
355
356
|
self._finish_arm,
|
|
356
357
|
]
|
|
357
|
-
if TEST_1169_FIX:
|
|
358
|
-
# If a beam dump occurs after arming the eiger but prior to eiger staging,
|
|
359
|
-
# the odin may timeout which will cause the arming sequence to be retried;
|
|
360
|
-
# if this previously completed successfully we must reset the odin first
|
|
361
|
-
arming_sequence_funcs.insert(0, self.odin.stop)
|
|
362
358
|
|
|
363
359
|
functions_to_do_arm.extend(arming_sequence_funcs)
|
|
364
360
|
|
dodal/devices/eiger_odin.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# type: ignore # Eiger will soon be ophyd-async https://github.com/DiamondLightSource/dodal/issues/700
|
|
3
2
|
from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
|
4
3
|
from ophyd.areadetector.plugins import HDF5Plugin_V22
|
|
5
4
|
from ophyd.sim import NullStatus
|
|
@@ -59,12 +58,12 @@ class OdinNodesStatus(Device):
|
|
|
59
58
|
node_3 = Component(OdinNode, "OD4:")
|
|
60
59
|
|
|
61
60
|
@property
|
|
62
|
-
def nodes(self) ->
|
|
61
|
+
def nodes(self) -> list[OdinNode]:
|
|
63
62
|
return [self.node_0, self.node_1, self.node_2, self.node_3]
|
|
64
63
|
|
|
65
64
|
def check_node_frames_from_attr(
|
|
66
65
|
self, node_get_func, error_message_verb: str
|
|
67
|
-
) ->
|
|
66
|
+
) -> tuple[bool, str]:
|
|
68
67
|
nodes_frames_values = [0] * len(self.nodes)
|
|
69
68
|
frames_details = []
|
|
70
69
|
for node_number, node_pv in enumerate(self.nodes):
|
|
@@ -75,17 +74,17 @@ class OdinNodesStatus(Device):
|
|
|
75
74
|
bad_frames = any(v != 0 for v in nodes_frames_values)
|
|
76
75
|
return bad_frames, "\n".join(frames_details)
|
|
77
76
|
|
|
78
|
-
def check_frames_timed_out(self) ->
|
|
77
|
+
def check_frames_timed_out(self) -> tuple[bool, str]:
|
|
79
78
|
return self.check_node_frames_from_attr(
|
|
80
79
|
lambda node: node.frames_timed_out.get(), "timed out"
|
|
81
80
|
)
|
|
82
81
|
|
|
83
|
-
def check_frames_dropped(self) ->
|
|
82
|
+
def check_frames_dropped(self) -> tuple[bool, str]:
|
|
84
83
|
return self.check_node_frames_from_attr(
|
|
85
84
|
lambda node: node.frames_dropped.get(), "dropped"
|
|
86
85
|
)
|
|
87
86
|
|
|
88
|
-
def get_error_state(self) ->
|
|
87
|
+
def get_error_state(self) -> tuple[bool, str]:
|
|
89
88
|
is_error = []
|
|
90
89
|
error_messages = []
|
|
91
90
|
for node_number, node_pv in enumerate(self.nodes):
|
|
@@ -99,7 +98,7 @@ class OdinNodesStatus(Device):
|
|
|
99
98
|
|
|
100
99
|
def get_init_state(self) -> bool:
|
|
101
100
|
is_initialised = []
|
|
102
|
-
for
|
|
101
|
+
for node_pv in self.nodes:
|
|
103
102
|
is_initialised.append(node_pv.fr_initialised.get())
|
|
104
103
|
is_initialised.append(node_pv.fp_initialised.get())
|
|
105
104
|
return all(is_initialised)
|
|
@@ -132,7 +131,7 @@ class EigerOdin(Device):
|
|
|
132
131
|
frames_timed_out, frames_timed_out_details = self.nodes.check_frames_timed_out()
|
|
133
132
|
|
|
134
133
|
if not is_initialised:
|
|
135
|
-
raise
|
|
134
|
+
raise RuntimeError(error_message)
|
|
136
135
|
if frames_dropped:
|
|
137
136
|
self.log.error(f"Frames dropped: {frames_dropped_details}")
|
|
138
137
|
if frames_timed_out:
|
|
@@ -140,7 +139,7 @@ class EigerOdin(Device):
|
|
|
140
139
|
|
|
141
140
|
return is_initialised and not frames_dropped and not frames_timed_out
|
|
142
141
|
|
|
143
|
-
def check_odin_initialised(self) ->
|
|
142
|
+
def check_odin_initialised(self) -> tuple[bool, str]:
|
|
144
143
|
is_error_state, error_messages = self.nodes.get_error_state()
|
|
145
144
|
to_check = [
|
|
146
145
|
(not self.fan.consumers_connected.get(), "EigerFan is not connected"),
|
dodal/devices/fast_grid_scan.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Generic, TypeVar
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
from bluesky.plan_stubs import mv
|
|
@@ -20,7 +20,7 @@ from ophyd_async.epics.signal import (
|
|
|
20
20
|
epics_signal_rw_rbv,
|
|
21
21
|
epics_signal_x,
|
|
22
22
|
)
|
|
23
|
-
from pydantic import
|
|
23
|
+
from pydantic import field_validator
|
|
24
24
|
from pydantic.dataclasses import dataclass
|
|
25
25
|
|
|
26
26
|
from dodal.log import LOGGER
|
|
@@ -69,9 +69,6 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
|
|
|
69
69
|
y2_start: float = 0.1
|
|
70
70
|
z1_start: float = 0.1
|
|
71
71
|
z2_start: float = 0.1
|
|
72
|
-
x_axis: GridAxis = GridAxis(0, 0, 0)
|
|
73
|
-
y_axis: GridAxis = GridAxis(0, 0, 0)
|
|
74
|
-
z_axis: GridAxis = GridAxis(0, 0, 0)
|
|
75
72
|
|
|
76
73
|
# Whether to set the stub offsets after centering
|
|
77
74
|
set_stub_offsets: bool = False
|
|
@@ -91,28 +88,20 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
|
|
|
91
88
|
"z2_start": self.z2_start,
|
|
92
89
|
}
|
|
93
90
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"x_axis": {"exclude": True},
|
|
98
|
-
"y_axis": {"exclude": True},
|
|
99
|
-
"z_axis": {"exclude": True},
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
@validator("x_axis", always=True)
|
|
103
|
-
def _get_x_axis(cls, x_axis: GridAxis, values: dict[str, Any]) -> GridAxis:
|
|
104
|
-
return GridAxis(values["x_start"], values["x_step_size"], values["x_steps"])
|
|
91
|
+
@property
|
|
92
|
+
def x_axis(self) -> GridAxis:
|
|
93
|
+
return GridAxis(self.x_start, self.x_step_size, self.x_steps)
|
|
105
94
|
|
|
106
|
-
@
|
|
107
|
-
def
|
|
108
|
-
return GridAxis(
|
|
95
|
+
@property
|
|
96
|
+
def y_axis(self) -> GridAxis:
|
|
97
|
+
return GridAxis(self.y1_start, self.y_step_size, self.y_steps)
|
|
109
98
|
|
|
110
|
-
@
|
|
111
|
-
def
|
|
112
|
-
return GridAxis(
|
|
99
|
+
@property
|
|
100
|
+
def z_axis(self) -> GridAxis:
|
|
101
|
+
return GridAxis(self.z2_start, self.z_step_size, self.z_steps)
|
|
113
102
|
|
|
114
103
|
def get_num_images(self):
|
|
115
|
-
return self.x_steps * self.y_steps + self.
|
|
104
|
+
return self.x_steps * (self.y_steps + self.z_steps)
|
|
116
105
|
|
|
117
106
|
@property
|
|
118
107
|
def is_3d_grid_scan(self):
|
|
@@ -126,7 +115,7 @@ class GridScanParamsCommon(AbstractExperimentWithBeamParams):
|
|
|
126
115
|
:return: The motor position this corresponds to.
|
|
127
116
|
:raises: IndexError if the desired position is outside the grid."""
|
|
128
117
|
for position, axis in zip(
|
|
129
|
-
grid_position, [self.x_axis, self.y_axis, self.z_axis]
|
|
118
|
+
grid_position, [self.x_axis, self.y_axis, self.z_axis], strict=False
|
|
130
119
|
):
|
|
131
120
|
if not axis.is_within(position):
|
|
132
121
|
raise IndexError(f"{grid_position} is outside the bounds of the grid")
|
|
@@ -155,7 +144,8 @@ class ZebraGridScanParams(GridScanParamsCommon):
|
|
|
155
144
|
param_positions["dwell_time_ms"] = self.dwell_time_ms
|
|
156
145
|
return param_positions
|
|
157
146
|
|
|
158
|
-
@
|
|
147
|
+
@field_validator("dwell_time_ms")
|
|
148
|
+
@classmethod
|
|
159
149
|
def non_integer_dwell_time(cls, dwell_time_ms: float) -> float:
|
|
160
150
|
dwell_time_floor_rounded = np.floor(dwell_time_ms)
|
|
161
151
|
dwell_time_is_close = np.isclose(
|
|
@@ -191,9 +181,10 @@ class MotionProgram(Device):
|
|
|
191
181
|
class ExpectedImages(SignalR[int]):
|
|
192
182
|
def __init__(self, parent: "FastGridScanCommon") -> None:
|
|
193
183
|
super().__init__(SoftSignalBackend(int))
|
|
194
|
-
self.parent
|
|
184
|
+
self.parent = parent
|
|
195
185
|
|
|
196
|
-
async def get_value(self):
|
|
186
|
+
async def get_value(self, cached: bool | None = None):
|
|
187
|
+
assert isinstance(self.parent, FastGridScanCommon)
|
|
197
188
|
x = await self.parent.x_steps.get_value()
|
|
198
189
|
y = await self.parent.y_steps.get_value()
|
|
199
190
|
z = await self.parent.z_steps.get_value()
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
from
|
|
2
|
-
from ophyd import Device, EpicsSignal
|
|
1
|
+
from enum import Enum
|
|
3
2
|
|
|
3
|
+
from ophyd_async.core import StandardReadable
|
|
4
|
+
from ophyd_async.epics.signal import epics_signal_r
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
class FluorescenceDetectorControlState(Enum):
|
|
6
8
|
OUT = 0
|
|
7
9
|
IN = 1
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
|
|
12
|
+
class FluorescenceDetector(StandardReadable):
|
|
13
|
+
def __init__(self, prefix: str, name: str = ""):
|
|
14
|
+
with self.add_children_as_readables():
|
|
15
|
+
self.pos = epics_signal_r(
|
|
16
|
+
FluorescenceDetectorControlState, prefix + "-EA-FLU-01:CTRL"
|
|
17
|
+
)
|
|
18
|
+
super().__init__(name)
|
dodal/devices/focusing_mirror.py
CHANGED
|
@@ -2,13 +2,15 @@ from enum import Enum
|
|
|
2
2
|
|
|
3
3
|
from ophyd_async.core import (
|
|
4
4
|
AsyncStatus,
|
|
5
|
+
ConfigSignal,
|
|
5
6
|
Device,
|
|
6
7
|
DeviceVector,
|
|
8
|
+
HintedSignal,
|
|
7
9
|
StandardReadable,
|
|
8
10
|
observe_value,
|
|
11
|
+
soft_signal_r_and_setter,
|
|
9
12
|
)
|
|
10
|
-
from ophyd_async.
|
|
11
|
-
from ophyd_async.epics.motion import Motor
|
|
13
|
+
from ophyd_async.epics.motor import Motor
|
|
12
14
|
from ophyd_async.epics.signal import (
|
|
13
15
|
epics_signal_r,
|
|
14
16
|
epics_signal_rw,
|
|
@@ -144,10 +146,8 @@ class FocusingMirror(StandardReadable):
|
|
|
144
146
|
# regardless of orientation of the mirror
|
|
145
147
|
self.incident_angle = Motor(prefix + "PITCH")
|
|
146
148
|
|
|
147
|
-
self.
|
|
148
|
-
|
|
149
|
-
config=[self.type],
|
|
150
|
-
)
|
|
149
|
+
self.add_readables([self.incident_angle.user_readback], wrapper=HintedSignal)
|
|
150
|
+
self.add_readables([self.type], wrapper=ConfigSignal)
|
|
151
151
|
super().__init__(name)
|
|
152
152
|
|
|
153
153
|
|
dodal/devices/hutch_shutter.py
CHANGED
|
@@ -74,20 +74,20 @@ class HutchShutter(StandardReadable, Movable):
|
|
|
74
74
|
super().__init__(name)
|
|
75
75
|
|
|
76
76
|
@AsyncStatus.wrap
|
|
77
|
-
async def set(self,
|
|
77
|
+
async def set(self, value: ShutterDemand):
|
|
78
78
|
interlock_state = await self.interlock.shutter_safe_to_operate()
|
|
79
79
|
if not interlock_state:
|
|
80
80
|
raise ShutterNotSafeToOperateError(
|
|
81
81
|
"The hutch has not been locked, not operating shutter."
|
|
82
82
|
)
|
|
83
|
-
if
|
|
83
|
+
if value == ShutterDemand.OPEN:
|
|
84
84
|
await self.control.set(ShutterDemand.RESET, wait=True)
|
|
85
|
-
await self.control.set(
|
|
85
|
+
await self.control.set(value, wait=True)
|
|
86
86
|
return await wait_for_value(
|
|
87
87
|
self.status, match=ShutterState.OPEN, timeout=DEFAULT_TIMEOUT
|
|
88
88
|
)
|
|
89
89
|
else:
|
|
90
|
-
await self.control.set(
|
|
90
|
+
await self.control.set(value, wait=True)
|
|
91
91
|
return await wait_for_value(
|
|
92
92
|
self.status, match=ShutterState.CLOSED, timeout=DEFAULT_TIMEOUT
|
|
93
93
|
)
|
dodal/devices/i22/dcm.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import time
|
|
2
|
+
from collections.abc import Sequence
|
|
2
3
|
from dataclasses import dataclass
|
|
3
|
-
from typing import
|
|
4
|
+
from typing import Literal
|
|
4
5
|
|
|
5
6
|
from bluesky.protocols import Reading
|
|
6
7
|
from event_model.documents.event_descriptor import DataKey
|
|
7
8
|
from ophyd_async.core import ConfigSignal, StandardReadable, soft_signal_r_and_setter
|
|
8
|
-
from ophyd_async.epics.
|
|
9
|
+
from ophyd_async.epics.motor import Motor
|
|
9
10
|
from ophyd_async.epics.signal import epics_signal_r
|
|
10
11
|
|
|
11
12
|
# Conversion constant for energy and wavelength, taken from the X-Ray data booklet
|
|
@@ -127,7 +128,7 @@ class DoubleCrystalMonochromator(StandardReadable):
|
|
|
127
128
|
|
|
128
129
|
super().__init__(name)
|
|
129
130
|
|
|
130
|
-
async def describe(self) ->
|
|
131
|
+
async def describe(self) -> dict[str, DataKey]:
|
|
131
132
|
default_describe = await super().describe()
|
|
132
133
|
return {
|
|
133
134
|
f"{self.name}-wavelength": DataKey(
|
|
@@ -139,7 +140,7 @@ class DoubleCrystalMonochromator(StandardReadable):
|
|
|
139
140
|
**default_describe,
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
async def read(self) ->
|
|
143
|
+
async def read(self) -> dict[str, Reading]:
|
|
143
144
|
default_reading = await super().read()
|
|
144
145
|
energy: float = default_reading[f"{self.name}-energy"]["value"]
|
|
145
146
|
if energy > 0.0:
|
dodal/devices/i22/fswitch.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import time
|
|
3
3
|
from enum import Enum
|
|
4
|
-
from typing import Dict
|
|
5
4
|
|
|
6
|
-
from bluesky.protocols import
|
|
7
|
-
from
|
|
8
|
-
from ophyd_async.core
|
|
5
|
+
from bluesky.protocols import Reading
|
|
6
|
+
from event_model import DataKey
|
|
7
|
+
from ophyd_async.core import (
|
|
8
|
+
ConfigSignal,
|
|
9
|
+
DeviceVector,
|
|
10
|
+
StandardReadable,
|
|
11
|
+
soft_signal_r_and_setter,
|
|
12
|
+
)
|
|
9
13
|
from ophyd_async.epics.signal import epics_signal_r
|
|
10
14
|
|
|
11
15
|
|
|
@@ -74,7 +78,7 @@ class FSwitch(StandardReadable):
|
|
|
74
78
|
|
|
75
79
|
super().__init__(name)
|
|
76
80
|
|
|
77
|
-
async def describe(self) ->
|
|
81
|
+
async def describe(self) -> dict[str, DataKey]:
|
|
78
82
|
default_describe = await super().describe()
|
|
79
83
|
return {
|
|
80
84
|
FSwitch.NUM_LENSES_FIELD_NAME: DataKey(
|
|
@@ -83,7 +87,7 @@ class FSwitch(StandardReadable):
|
|
|
83
87
|
**default_describe,
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
async def read(self) ->
|
|
90
|
+
async def read(self) -> dict[str, Reading]:
|
|
87
91
|
result = await asyncio.gather(
|
|
88
92
|
*(filter.get_value() for filter in self.filters.values())
|
|
89
93
|
)
|