dls-dodal 1.46.0__py3-none-any.whl → 1.48.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/METADATA +2 -2
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/RECORD +74 -63
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +0 -1
- dodal/beamlines/aithre.py +6 -0
- dodal/beamlines/b01_1.py +1 -1
- dodal/beamlines/b07.py +2 -6
- dodal/beamlines/b07_1.py +1 -3
- dodal/beamlines/i03.py +33 -21
- dodal/beamlines/i04.py +65 -26
- dodal/beamlines/i09.py +1 -3
- dodal/beamlines/i09_1.py +1 -3
- dodal/beamlines/i18.py +1 -1
- dodal/beamlines/i19_1.py +9 -6
- dodal/beamlines/i23.py +17 -1
- dodal/beamlines/i24.py +5 -5
- dodal/beamlines/p38.py +1 -1
- dodal/beamlines/p60.py +2 -6
- dodal/beamlines/p99.py +48 -4
- dodal/common/beamlines/beamline_parameters.py +3 -30
- dodal/common/data_util.py +4 -0
- dodal/devices/aithre_lasershaping/goniometer.py +36 -2
- dodal/devices/aithre_lasershaping/laser_robot.py +27 -0
- dodal/devices/aperturescatterguard.py +47 -47
- dodal/devices/current_amplifiers/struck_scaler_counter.py +1 -1
- dodal/devices/diamond_filter.py +5 -17
- dodal/devices/eiger.py +1 -1
- dodal/devices/electron_analyser/__init__.py +18 -0
- dodal/devices/electron_analyser/abstract/__init__.py +22 -0
- dodal/devices/electron_analyser/abstract/base_detector.py +223 -0
- dodal/devices/electron_analyser/abstract/base_driver_io.py +230 -0
- dodal/devices/electron_analyser/{abstract_region.py → abstract/base_region.py} +3 -9
- dodal/devices/electron_analyser/specs/__init__.py +10 -0
- dodal/devices/electron_analyser/specs/detector.py +13 -0
- dodal/devices/electron_analyser/specs/driver_io.py +89 -0
- dodal/devices/electron_analyser/{specs_region.py → specs/region.py} +1 -1
- dodal/devices/electron_analyser/types.py +6 -0
- dodal/devices/electron_analyser/util.py +13 -0
- dodal/devices/electron_analyser/vgscienta/__init__.py +11 -0
- dodal/devices/electron_analyser/vgscienta/detector.py +22 -0
- dodal/devices/electron_analyser/vgscienta/driver_io.py +67 -0
- dodal/devices/electron_analyser/{vgscienta_region.py → vgscienta/region.py} +1 -2
- dodal/devices/fast_grid_scan.py +7 -9
- dodal/devices/i03/__init__.py +3 -0
- dodal/devices/i04/__init__.py +3 -0
- dodal/devices/i04/constants.py +9 -0
- dodal/devices/i04/murko_results.py +192 -0
- dodal/devices/i10/diagnostics.py +9 -61
- dodal/devices/i18/diode.py +37 -4
- dodal/devices/i24/focus_mirrors.py +9 -13
- dodal/devices/i24/pilatus_metadata.py +9 -9
- dodal/devices/i24/pmac.py +19 -14
- dodal/devices/{i03 → mx_phase1}/beamstop.py +26 -15
- dodal/devices/oav/oav_calculations.py +2 -2
- dodal/devices/oav/oav_detector.py +80 -32
- dodal/devices/oav/oav_parameters.py +46 -16
- dodal/devices/oav/oav_to_redis_forwarder.py +2 -2
- dodal/devices/oav/utils.py +2 -2
- dodal/devices/p99/andor2_point.py +41 -0
- dodal/devices/positioner.py +49 -0
- dodal/devices/robot.py +20 -1
- dodal/devices/smargon.py +43 -4
- dodal/devices/tetramm.py +5 -2
- dodal/devices/util/adjuster_plans.py +1 -1
- dodal/devices/zebra/zebra.py +8 -0
- dodal/devices/zebra/zebra_constants_mapping.py +1 -1
- dodal/devices/zocalo/__init__.py +0 -3
- dodal/devices/zocalo/zocalo_results.py +6 -32
- dodal/log.py +14 -14
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +167 -0
- dodal/common/signal_utils.py +0 -88
- dodal/devices/electron_analyser/abstract_analyser_io.py +0 -47
- dodal/devices/electron_analyser/specs_analyser_io.py +0 -19
- dodal/devices/electron_analyser/vgscienta_analyser_io.py +0 -26
- dodal/devices/logging_ophyd_device.py +0 -17
- dodal/plan_stubs/electron_analyser/__init__.py +0 -0
- dodal/plan_stubs/electron_analyser/configure_controller.py +0 -80
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/top_level.txt +0 -0
|
@@ -5,13 +5,20 @@ from ophyd_async.core import (
|
|
|
5
5
|
DEFAULT_TIMEOUT,
|
|
6
6
|
AsyncStatus,
|
|
7
7
|
LazyMock,
|
|
8
|
+
SignalR,
|
|
8
9
|
StandardReadable,
|
|
10
|
+
derived_signal_r,
|
|
11
|
+
soft_signal_rw,
|
|
9
12
|
)
|
|
10
|
-
from ophyd_async.epics.core import epics_signal_rw
|
|
13
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
11
14
|
|
|
12
|
-
from dodal.common.signal_utils import create_r_hardware_backed_soft_signal
|
|
13
15
|
from dodal.devices.areadetector.plugins.CAM import Cam
|
|
14
|
-
from dodal.devices.oav.oav_parameters import
|
|
16
|
+
from dodal.devices.oav.oav_parameters import (
|
|
17
|
+
DEFAULT_OAV_WINDOW,
|
|
18
|
+
OAVConfig,
|
|
19
|
+
OAVConfigBase,
|
|
20
|
+
OAVConfigBeamCentre,
|
|
21
|
+
)
|
|
15
22
|
from dodal.devices.oav.snapshots.snapshot import Snapshot
|
|
16
23
|
from dodal.devices.oav.snapshots.snapshot_with_grid import SnapshotWithGrid
|
|
17
24
|
|
|
@@ -52,7 +59,10 @@ class ZoomController(StandardReadable, Movable[str]):
|
|
|
52
59
|
|
|
53
60
|
|
|
54
61
|
class OAV(StandardReadable):
|
|
55
|
-
|
|
62
|
+
beam_centre_i: SignalR[int]
|
|
63
|
+
beam_centre_j: SignalR[int]
|
|
64
|
+
|
|
65
|
+
def __init__(self, prefix: str, config: OAVConfigBase, name: str = ""):
|
|
56
66
|
self.oav_config = config
|
|
57
67
|
self._prefix = prefix
|
|
58
68
|
self._name = name
|
|
@@ -60,51 +70,40 @@ class OAV(StandardReadable):
|
|
|
60
70
|
self.zoom_controller = ZoomController(f"{_bl_prefix}-EA-OAV-01:FZOOM:", name)
|
|
61
71
|
|
|
62
72
|
self.cam = Cam(f"{prefix}CAM:", name=name)
|
|
63
|
-
|
|
64
73
|
with self.add_children_as_readables():
|
|
65
74
|
self.grid_snapshot = SnapshotWithGrid(f"{prefix}MJPG:", name)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
self.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
int, lambda: self._get_beam_position(Coords.X)
|
|
75
|
+
|
|
76
|
+
self.sizes = [self.grid_snapshot.x_size, self.grid_snapshot.y_size]
|
|
77
|
+
|
|
78
|
+
with self.add_children_as_readables():
|
|
79
|
+
self.microns_per_pixel_x = derived_signal_r(
|
|
80
|
+
self._get_microns_per_pixel,
|
|
81
|
+
zoom_level=self.zoom_controller.level,
|
|
82
|
+
size=self.sizes[Coords.X],
|
|
83
|
+
coord=soft_signal_rw(datatype=int, initial_value=Coords.X.value),
|
|
76
84
|
)
|
|
77
|
-
self.
|
|
78
|
-
|
|
85
|
+
self.microns_per_pixel_y = derived_signal_r(
|
|
86
|
+
self._get_microns_per_pixel,
|
|
87
|
+
zoom_level=self.zoom_controller.level,
|
|
88
|
+
size=self.sizes[Coords.Y],
|
|
89
|
+
coord=soft_signal_rw(datatype=int, initial_value=Coords.Y.value),
|
|
79
90
|
)
|
|
80
91
|
self.snapshot = Snapshot(
|
|
81
92
|
f"{self._prefix}MJPG:",
|
|
82
93
|
self._name,
|
|
83
94
|
)
|
|
84
95
|
|
|
85
|
-
self.sizes = [self.grid_snapshot.x_size, self.grid_snapshot.y_size]
|
|
86
|
-
|
|
87
96
|
super().__init__(name)
|
|
88
97
|
|
|
89
|
-
|
|
90
|
-
_zoom = await self.zoom_controller.level.get_value()
|
|
98
|
+
def _read_current_zoom(self, _zoom: str) -> str:
|
|
91
99
|
return _get_correct_zoom_string(_zoom)
|
|
92
100
|
|
|
93
|
-
|
|
101
|
+
def _get_microns_per_pixel(self, zoom_level: str, size: int, coord: int) -> float:
|
|
94
102
|
"""Extracts the microns per x pixel and y pixel for a given zoom level."""
|
|
95
|
-
_zoom =
|
|
103
|
+
_zoom = self._read_current_zoom(zoom_level)
|
|
96
104
|
value = self.parameters[_zoom].microns_per_pixel[coord]
|
|
97
|
-
size = await self.sizes[coord].get_value()
|
|
98
105
|
return value * DEFAULT_OAV_WINDOW[coord] / size
|
|
99
106
|
|
|
100
|
-
async def _get_beam_position(self, coord: int) -> int:
|
|
101
|
-
"""Extracts the beam location in pixels `xCentre` `yCentre`, for a requested \
|
|
102
|
-
zoom level. """
|
|
103
|
-
_zoom = await self._read_current_zoom()
|
|
104
|
-
value = self.parameters[_zoom].crosshair[coord]
|
|
105
|
-
size = await self.sizes[coord].get_value()
|
|
106
|
-
return int(value * size / DEFAULT_OAV_WINDOW[coord])
|
|
107
|
-
|
|
108
107
|
async def connect(
|
|
109
108
|
self,
|
|
110
109
|
mock: bool | LazyMock = False,
|
|
@@ -114,3 +113,52 @@ class OAV(StandardReadable):
|
|
|
114
113
|
self.parameters = self.oav_config.get_parameters()
|
|
115
114
|
|
|
116
115
|
return await super().connect(mock, timeout, force_reconnect)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class OAVBeamCentreFile(OAV):
|
|
119
|
+
"""OAV device that reads its beam centre values from a file. The config parameter
|
|
120
|
+
must be a OAVConfigBeamCentre object, as this contains a filepath to where the beam
|
|
121
|
+
centre values are stored.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, prefix: str, config: OAVConfigBeamCentre, name: str = ""):
|
|
125
|
+
super().__init__(prefix, config, name)
|
|
126
|
+
|
|
127
|
+
with self.add_children_as_readables():
|
|
128
|
+
self.beam_centre_i = derived_signal_r(
|
|
129
|
+
self._get_beam_position,
|
|
130
|
+
zoom_level=self.zoom_controller.level,
|
|
131
|
+
size=self.sizes[Coords.X],
|
|
132
|
+
coord=soft_signal_rw(datatype=int, initial_value=Coords.X.value),
|
|
133
|
+
)
|
|
134
|
+
self.beam_centre_j = derived_signal_r(
|
|
135
|
+
self._get_beam_position,
|
|
136
|
+
zoom_level=self.zoom_controller.level,
|
|
137
|
+
size=self.sizes[Coords.Y],
|
|
138
|
+
coord=soft_signal_rw(datatype=int, initial_value=Coords.Y.value),
|
|
139
|
+
)
|
|
140
|
+
# Set name so that new child signals get correct name
|
|
141
|
+
self.set_name(self.name)
|
|
142
|
+
|
|
143
|
+
def _get_beam_position(self, zoom_level: str, size: int, coord: int) -> int:
|
|
144
|
+
"""Extracts the beam location in pixels `xCentre` `yCentre`, for a requested \
|
|
145
|
+
zoom level. """
|
|
146
|
+
_zoom = self._read_current_zoom(zoom_level)
|
|
147
|
+
value = self.parameters[_zoom].crosshair[coord]
|
|
148
|
+
return int(value * size / DEFAULT_OAV_WINDOW[coord])
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class OAVBeamCentrePV(OAV):
|
|
152
|
+
"""OAV device that reads its beam centre values from PVs."""
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self, prefix: str, config: OAVConfig, name: str = "", overlay_channel: int = 1
|
|
156
|
+
):
|
|
157
|
+
with self.add_children_as_readables():
|
|
158
|
+
self.beam_centre_i = epics_signal_r(
|
|
159
|
+
int, prefix + f"OVER:{overlay_channel}:CenterX"
|
|
160
|
+
)
|
|
161
|
+
self.beam_centre_j = epics_signal_r(
|
|
162
|
+
int, prefix + f"OVER:{overlay_channel}:CenterY"
|
|
163
|
+
)
|
|
164
|
+
super().__init__(prefix, config, name)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import xml.etree.ElementTree as et
|
|
3
|
+
from abc import abstractmethod
|
|
3
4
|
from collections import ChainMap
|
|
4
5
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Any
|
|
6
|
+
from typing import Any, Generic, TypeVar
|
|
6
7
|
from xml.etree.ElementTree import Element
|
|
7
8
|
|
|
8
9
|
# GDA currently assumes this aspect ratio for the OAV window size.
|
|
@@ -107,22 +108,19 @@ class OAVParameters:
|
|
|
107
108
|
@dataclass
|
|
108
109
|
class ZoomParams:
|
|
109
110
|
microns_per_pixel: tuple[float, float]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass
|
|
114
|
+
class ZoomParamsCrosshair(ZoomParams):
|
|
110
115
|
crosshair: tuple[int, int]
|
|
111
116
|
|
|
112
117
|
|
|
113
|
-
|
|
114
|
-
""" Read the OAV config files and return a dictionary of {'zoom_level': ZoomParams}\
|
|
115
|
-
with information about microns per pixels and crosshairs.
|
|
116
|
-
"""
|
|
118
|
+
ParamType = TypeVar("ParamType", bound="ZoomParams")
|
|
117
119
|
|
|
118
|
-
def __init__(self, zoom_params_file: str, display_config_file: str):
|
|
119
|
-
self.zoom_params = self._get_zoom_params(zoom_params_file)
|
|
120
|
-
self.display_config = self._get_display_config(display_config_file)
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return file_lines
|
|
121
|
+
class OAVConfigBase(Generic[ParamType]):
|
|
122
|
+
def __init__(self, zoom_params_file: str):
|
|
123
|
+
self.zoom_params = self._get_zoom_params(zoom_params_file)
|
|
126
124
|
|
|
127
125
|
def _get_zoom_params(self, zoom_params_file: str):
|
|
128
126
|
tree = et.parse(zoom_params_file)
|
|
@@ -138,6 +136,39 @@ class OAVConfig:
|
|
|
138
136
|
um_per_pix[zoom] = (um_pix_x, um_pix_y)
|
|
139
137
|
return um_per_pix
|
|
140
138
|
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def get_parameters(self) -> dict[str, ParamType]: ...
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class OAVConfig(OAVConfigBase[ZoomParams]):
|
|
144
|
+
def get_parameters(self) -> dict[str, ZoomParams]:
|
|
145
|
+
config = {}
|
|
146
|
+
um_xy = self._read_zoom_params()
|
|
147
|
+
for zoom_key in list(um_xy.keys()):
|
|
148
|
+
config[zoom_key] = ZoomParams(
|
|
149
|
+
microns_per_pixel=um_xy[zoom_key],
|
|
150
|
+
)
|
|
151
|
+
return config
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class OAVConfigBeamCentre(OAVConfigBase[ZoomParamsCrosshair]):
|
|
155
|
+
""" Read the OAV config files and return a dictionary of {'zoom_level': ZoomParams}\
|
|
156
|
+
with information about microns per pixels and crosshairs.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def __init__(
|
|
160
|
+
self,
|
|
161
|
+
zoom_params_file: str,
|
|
162
|
+
display_config_file: str,
|
|
163
|
+
):
|
|
164
|
+
self.display_config = self._get_display_config(display_config_file)
|
|
165
|
+
super().__init__(zoom_params_file)
|
|
166
|
+
|
|
167
|
+
def _get_display_config(self, display_config_file: str):
|
|
168
|
+
with open(display_config_file) as f:
|
|
169
|
+
file_lines = f.readlines()
|
|
170
|
+
return file_lines
|
|
171
|
+
|
|
141
172
|
def _read_display_config(self) -> dict:
|
|
142
173
|
crosshairs = {}
|
|
143
174
|
for i in range(len(self.display_config)):
|
|
@@ -148,13 +179,12 @@ class OAVConfig:
|
|
|
148
179
|
crosshairs[zoom] = (x, y)
|
|
149
180
|
return crosshairs
|
|
150
181
|
|
|
151
|
-
def get_parameters(self) -> dict[str,
|
|
182
|
+
def get_parameters(self) -> dict[str, ZoomParamsCrosshair]:
|
|
152
183
|
config = {}
|
|
153
184
|
um_xy = self._read_zoom_params()
|
|
154
185
|
bc_xy = self._read_display_config()
|
|
155
186
|
for zoom_key in list(bc_xy.keys()):
|
|
156
|
-
config[zoom_key] =
|
|
157
|
-
microns_per_pixel=um_xy[zoom_key],
|
|
158
|
-
crosshair=bc_xy[zoom_key],
|
|
187
|
+
config[zoom_key] = ZoomParamsCrosshair(
|
|
188
|
+
microns_per_pixel=um_xy[zoom_key], crosshair=bc_xy[zoom_key]
|
|
159
189
|
)
|
|
160
190
|
return config
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from collections.abc import Awaitable, Callable
|
|
3
3
|
from datetime import timedelta
|
|
4
|
-
from enum import
|
|
4
|
+
from enum import IntEnum
|
|
5
5
|
from uuid import uuid4
|
|
6
6
|
|
|
7
7
|
from aiohttp import ClientResponse, ClientSession
|
|
@@ -29,7 +29,7 @@ async def get_next_jpeg(response: ClientResponse) -> bytes:
|
|
|
29
29
|
return line + await response.content.readuntil(JPEG_STOP_BYTE)
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
class Source(
|
|
32
|
+
class Source(IntEnum):
|
|
33
33
|
FULL_SCREEN = 0
|
|
34
34
|
ROI = 1
|
|
35
35
|
|
dodal/devices/oav/utils.py
CHANGED
|
@@ -7,7 +7,7 @@ from bluesky.utils import Msg
|
|
|
7
7
|
|
|
8
8
|
from dodal.devices.oav.oav_calculations import (
|
|
9
9
|
calculate_beam_distance,
|
|
10
|
-
|
|
10
|
+
camera_coordinates_to_xyz_mm,
|
|
11
11
|
)
|
|
12
12
|
from dodal.devices.oav.oav_detector import OAV
|
|
13
13
|
from dodal.devices.oav.pin_image_recognition import PinTipDetection
|
|
@@ -90,7 +90,7 @@ def calculate_x_y_z_of_pixel(
|
|
|
90
90
|
"""Get the x, y, z position of a pixel in mm"""
|
|
91
91
|
beam_distance_px: Pixel = calculate_beam_distance(beam_centre, *pixel)
|
|
92
92
|
|
|
93
|
-
return current_x_y_z +
|
|
93
|
+
return current_x_y_z + camera_coordinates_to_xyz_mm(
|
|
94
94
|
beam_distance_px[0],
|
|
95
95
|
beam_distance_px[1],
|
|
96
96
|
current_omega,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from ophyd_async.core import (
|
|
2
|
+
StandardReadableFormat,
|
|
3
|
+
)
|
|
4
|
+
from ophyd_async.epics.adandor import Andor2DriverIO
|
|
5
|
+
from ophyd_async.epics.adcore import NDPluginBaseIO, SingleTriggerDetector
|
|
6
|
+
from ophyd_async.epics.core import epics_signal_r
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Andor2Point(SingleTriggerDetector):
|
|
10
|
+
"""Using the andor2 as if it is a massive point detector, read the read uncached
|
|
11
|
+
value after a picture is taken."""
|
|
12
|
+
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
prefix: str,
|
|
16
|
+
drv_suffix: str,
|
|
17
|
+
read_uncached: dict[str, str],
|
|
18
|
+
name: str = "",
|
|
19
|
+
plugins: dict[str, NDPluginBaseIO] | None = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
prefix: str,
|
|
25
|
+
Beamline camera pv
|
|
26
|
+
drv_suffix : str,
|
|
27
|
+
Camera pv suffix
|
|
28
|
+
read_uncached: dict[str,str]
|
|
29
|
+
A dictionary contains the name and the pv suffix for the statistic plugin.
|
|
30
|
+
name: str:
|
|
31
|
+
Name of the device.
|
|
32
|
+
plugins:: Optional[dict[str, NDPluginBaseIO] | None
|
|
33
|
+
Dictionary containing plugin that are forward to the base class.
|
|
34
|
+
"""
|
|
35
|
+
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
36
|
+
for k, v in read_uncached.items():
|
|
37
|
+
setattr(self, k, epics_signal_r(float, prefix + v))
|
|
38
|
+
|
|
39
|
+
super().__init__(
|
|
40
|
+
drv=Andor2DriverIO(prefix + drv_suffix), name=name, plugins=plugins
|
|
41
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
|
|
3
|
+
from bluesky.protocols import Movable
|
|
4
|
+
from ophyd_async.core import (
|
|
5
|
+
AsyncStatus,
|
|
6
|
+
StandardReadable,
|
|
7
|
+
StrictEnum,
|
|
8
|
+
)
|
|
9
|
+
from ophyd_async.epics.core import (
|
|
10
|
+
epics_signal_rw,
|
|
11
|
+
)
|
|
12
|
+
from ophyd_async.epics.motor import Motor
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T", bound=StrictEnum)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Positioner1D(StandardReadable, Movable[T]):
|
|
18
|
+
"""1D stage with a enum table to select positions.
|
|
19
|
+
|
|
20
|
+
Use this when writing a device with an EPICS positioner on a single axis.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
prefix: str,
|
|
26
|
+
datatype: type[StrictEnum],
|
|
27
|
+
positioner_pv_suffix: str = ":MP:SELECT",
|
|
28
|
+
name: str = "",
|
|
29
|
+
) -> None:
|
|
30
|
+
self._stage_motion = Motor(prefix=prefix)
|
|
31
|
+
with self.add_children_as_readables():
|
|
32
|
+
self.stage_position = epics_signal_rw(
|
|
33
|
+
datatype,
|
|
34
|
+
read_pv=prefix + positioner_pv_suffix,
|
|
35
|
+
)
|
|
36
|
+
super().__init__(name=name)
|
|
37
|
+
|
|
38
|
+
@AsyncStatus.wrap
|
|
39
|
+
async def set(self, value: T) -> None:
|
|
40
|
+
await self.stage_position.set(value=value)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_positioner(
|
|
44
|
+
datatype: type[T],
|
|
45
|
+
prefix,
|
|
46
|
+
positioner_pv_suffix: str = ":MP:SELECT",
|
|
47
|
+
name: str = "",
|
|
48
|
+
) -> Positioner1D[T]:
|
|
49
|
+
return Positioner1D[datatype](prefix, datatype, positioner_pv_suffix, name)
|
dodal/devices/robot.py
CHANGED
|
@@ -11,7 +11,12 @@ from ophyd_async.core import (
|
|
|
11
11
|
set_and_wait_for_value,
|
|
12
12
|
wait_for_value,
|
|
13
13
|
)
|
|
14
|
-
from ophyd_async.epics.core import
|
|
14
|
+
from ophyd_async.epics.core import (
|
|
15
|
+
epics_signal_r,
|
|
16
|
+
epics_signal_rw,
|
|
17
|
+
epics_signal_rw_rbv,
|
|
18
|
+
epics_signal_x,
|
|
19
|
+
)
|
|
15
20
|
|
|
16
21
|
from dodal.log import LOGGER
|
|
17
22
|
|
|
@@ -88,6 +93,20 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
|
|
|
88
93
|
self.controller_error = ErrorStatus(prefix + "CNTL")
|
|
89
94
|
|
|
90
95
|
self.reset = epics_signal_x(prefix + "RESET.PROC")
|
|
96
|
+
self.stop = epics_signal_x(prefix + "ABORT.PROC")
|
|
97
|
+
self.init = epics_signal_x(prefix + "INIT.PROC")
|
|
98
|
+
self.soak = epics_signal_x(prefix + "SOAK.PROC")
|
|
99
|
+
self.home = epics_signal_x(prefix + "GOHM.PROC")
|
|
100
|
+
self.unload = epics_signal_x(prefix + "UNLD.PROC")
|
|
101
|
+
self.dry = epics_signal_x(prefix + "DRY.PROC")
|
|
102
|
+
self.open = epics_signal_x(prefix + "COLO.PROC")
|
|
103
|
+
self.close = epics_signal_x(prefix + "COLC.PROC")
|
|
104
|
+
self.cryomode_rbv = epics_signal_r(float, prefix + "CRYO_MODE_RBV")
|
|
105
|
+
self.cryomode = epics_signal_rw(str, prefix + "CRYO_MODE_CTRL")
|
|
106
|
+
self.gripper_temp = epics_signal_r(float, prefix + "GRIPPER_TEMP")
|
|
107
|
+
self.dewar_lid_temperature = epics_signal_rw(
|
|
108
|
+
float, prefix + "DW_1_TEMP", prefix + "DW_1_SET_POINT"
|
|
109
|
+
)
|
|
91
110
|
super().__init__(name=name)
|
|
92
111
|
|
|
93
112
|
async def pin_mounted_or_no_pin_found(self):
|
dodal/devices/smargon.py
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from collections.abc import Collection, Generator
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from math import isclose
|
|
5
|
-
from typing import cast
|
|
6
|
+
from typing import TypedDict, cast
|
|
6
7
|
|
|
7
8
|
from bluesky import plan_stubs as bps
|
|
9
|
+
from bluesky.protocols import Movable
|
|
8
10
|
from bluesky.utils import Msg
|
|
9
|
-
from ophyd_async.core import
|
|
10
|
-
|
|
11
|
+
from ophyd_async.core import (
|
|
12
|
+
AsyncStatus,
|
|
13
|
+
Device,
|
|
14
|
+
StandardReadable,
|
|
15
|
+
StrictEnum,
|
|
16
|
+
wait_for_value,
|
|
17
|
+
)
|
|
18
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
11
19
|
from ophyd_async.epics.motor import Motor
|
|
20
|
+
from typing_extensions import NotRequired
|
|
12
21
|
|
|
13
22
|
from dodal.devices.util.epics_util import SetWhenEnabled
|
|
14
23
|
|
|
@@ -91,7 +100,23 @@ class XYZLimits:
|
|
|
91
100
|
)
|
|
92
101
|
|
|
93
102
|
|
|
94
|
-
class
|
|
103
|
+
class DeferMoves(StrictEnum):
|
|
104
|
+
ON = "Defer On"
|
|
105
|
+
OFF = "Defer Off"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class CombinedMove(TypedDict):
|
|
109
|
+
"""A move on multiple axes at once using a deferred move"""
|
|
110
|
+
|
|
111
|
+
x: NotRequired[float | None]
|
|
112
|
+
y: NotRequired[float | None]
|
|
113
|
+
z: NotRequired[float | None]
|
|
114
|
+
omega: NotRequired[float | None]
|
|
115
|
+
phi: NotRequired[float | None]
|
|
116
|
+
chi: NotRequired[float | None]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Smargon(StandardReadable, Movable):
|
|
95
120
|
"""
|
|
96
121
|
Real motors added to allow stops following pin load (e.g. real_x1.stop() )
|
|
97
122
|
X1 and X2 real motors provide compound chi motion as well as the compound X travel,
|
|
@@ -116,6 +141,8 @@ class Smargon(StandardReadable):
|
|
|
116
141
|
self.stub_offsets = StubOffsets(prefix=prefix)
|
|
117
142
|
self.disabled = epics_signal_r(int, prefix + "DISABLED")
|
|
118
143
|
|
|
144
|
+
self.defer_move = epics_signal_rw(DeferMoves, prefix + "CS1:DeferMoves")
|
|
145
|
+
|
|
119
146
|
super().__init__(name)
|
|
120
147
|
|
|
121
148
|
def get_xyz_limits(self) -> Generator[Msg, None, XYZLimits]:
|
|
@@ -135,3 +162,15 @@ class Smargon(StandardReadable):
|
|
|
135
162
|
max_value = yield from bps.rd(pv.high_limit_travel)
|
|
136
163
|
limits[name] = AxisLimit(min_value, max_value)
|
|
137
164
|
return XYZLimits(**limits)
|
|
165
|
+
|
|
166
|
+
@AsyncStatus.wrap
|
|
167
|
+
async def set(self, value: CombinedMove):
|
|
168
|
+
await self.defer_move.set(DeferMoves.ON)
|
|
169
|
+
try:
|
|
170
|
+
tasks = []
|
|
171
|
+
for k, v in value.items():
|
|
172
|
+
if v is not None:
|
|
173
|
+
tasks.append(getattr(self, k).set(v))
|
|
174
|
+
await asyncio.gather(*tasks)
|
|
175
|
+
finally:
|
|
176
|
+
await self.defer_move.set(DeferMoves.OFF)
|
dodal/devices/tetramm.py
CHANGED
|
@@ -131,11 +131,14 @@ class TetrammController(DetectorController):
|
|
|
131
131
|
)
|
|
132
132
|
|
|
133
133
|
async def arm(self):
|
|
134
|
-
self._arm_status = await set_and_wait_for_value(
|
|
134
|
+
self._arm_status = await set_and_wait_for_value(
|
|
135
|
+
self._drv.acquire, True, wait_for_set_completion=False
|
|
136
|
+
)
|
|
135
137
|
|
|
136
138
|
async def wait_for_idle(self):
|
|
137
|
-
if self._arm_status:
|
|
139
|
+
if self._arm_status and not self._arm_status.done:
|
|
138
140
|
await self._arm_status
|
|
141
|
+
self._arm_status = None
|
|
139
142
|
|
|
140
143
|
def _validate_trigger(self, trigger: DetectorTrigger) -> None:
|
|
141
144
|
supported_trigger_types = {
|
|
@@ -20,6 +20,6 @@ def lookup_table_adjuster(
|
|
|
20
20
|
def adjust(group=None) -> Generator[Msg, None, None]:
|
|
21
21
|
setpoint = lookup_table(input)
|
|
22
22
|
LOGGER.info(f"lookup_table_adjuster setting {output_device.name} to {setpoint}")
|
|
23
|
-
yield from bps.abs_set(output_device, setpoint, group=group)
|
|
23
|
+
yield from bps.abs_set(output_device, setpoint, group=group)
|
|
24
24
|
|
|
25
25
|
return adjust
|
dodal/devices/zebra/zebra.py
CHANGED
|
@@ -68,6 +68,14 @@ class RotationDirection(StrictEnum):
|
|
|
68
68
|
def multiplier(self):
|
|
69
69
|
return 1 if self == RotationDirection.POSITIVE else -1
|
|
70
70
|
|
|
71
|
+
@property
|
|
72
|
+
def opposite(self) -> RotationDirection:
|
|
73
|
+
return (
|
|
74
|
+
RotationDirection.POSITIVE
|
|
75
|
+
if self == RotationDirection.NEGATIVE
|
|
76
|
+
else RotationDirection.NEGATIVE
|
|
77
|
+
)
|
|
78
|
+
|
|
71
79
|
|
|
72
80
|
class ArmDemand(Enum):
|
|
73
81
|
ARM = 1
|
|
@@ -89,7 +89,7 @@ class ZebraMapping(ZebraMappingValidations):
|
|
|
89
89
|
# Which of the Zebra's four AND gates is used to control the automatic shutter, if it's being used.
|
|
90
90
|
# After defining, the correct GateControl device can be accessed with, eg,
|
|
91
91
|
# zebra.logic_gates.and_gates[zebra.mapping.AND_GATE_FOR_AUTO_SHUTTER]. Set to -1 if not being used.
|
|
92
|
-
AND_GATE_FOR_AUTO_SHUTTER: int = Field(default
|
|
92
|
+
AND_GATE_FOR_AUTO_SHUTTER: int = Field(default=2, ge=-1, le=4)
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
class UnmappedZebraException(Exception):
|
dodal/devices/zocalo/__init__.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from collections.abc import Generator, Sequence
|
|
3
3
|
from enum import Enum
|
|
4
|
-
from inspect import get_annotations
|
|
5
4
|
from queue import Empty, Queue
|
|
6
5
|
from typing import Any, TypedDict
|
|
7
6
|
|
|
@@ -47,7 +46,6 @@ class ZocaloSource(str, Enum):
|
|
|
47
46
|
|
|
48
47
|
DEFAULT_TIMEOUT = 180
|
|
49
48
|
DEFAULT_SORT_KEY = SortKeys.max_count
|
|
50
|
-
ZOCALO_READING_PLAN_NAME = "zocalo reading"
|
|
51
49
|
CLEAR_QUEUE_WAIT_S = 2.0
|
|
52
50
|
ZOCALO_STAGE_GROUP = "clear zocalo queue"
|
|
53
51
|
|
|
@@ -389,12 +387,12 @@ def get_full_processing_results(
|
|
|
389
387
|
"""A plan that will return the raw zocalo results, ranked in descending order according to the sort key.
|
|
390
388
|
Returns empty list in the event no results found."""
|
|
391
389
|
LOGGER.info("Retrieving raw zocalo processing results")
|
|
392
|
-
com = yield from bps.rd(zocalo.centre_of_mass, default_value=[])
|
|
393
|
-
max_voxel = yield from bps.rd(zocalo.max_voxel, default_value=[])
|
|
394
|
-
max_count = yield from bps.rd(zocalo.max_count, default_value=[])
|
|
395
|
-
n_voxels = yield from bps.rd(zocalo.n_voxels, default_value=[])
|
|
396
|
-
total_count = yield from bps.rd(zocalo.total_count, default_value=[])
|
|
397
|
-
bounding_box = yield from bps.rd(zocalo.bounding_box, default_value=[])
|
|
390
|
+
com = yield from bps.rd(zocalo.centre_of_mass, default_value=[])
|
|
391
|
+
max_voxel = yield from bps.rd(zocalo.max_voxel, default_value=[])
|
|
392
|
+
max_count = yield from bps.rd(zocalo.max_count, default_value=[])
|
|
393
|
+
n_voxels = yield from bps.rd(zocalo.n_voxels, default_value=[])
|
|
394
|
+
total_count = yield from bps.rd(zocalo.total_count, default_value=[])
|
|
395
|
+
bounding_box = yield from bps.rd(zocalo.bounding_box, default_value=[])
|
|
398
396
|
return [
|
|
399
397
|
_corrected_xrc_result(
|
|
400
398
|
XrcResult(
|
|
@@ -410,27 +408,3 @@ def get_full_processing_results(
|
|
|
410
408
|
com, max_voxel, max_count, n_voxels, total_count, bounding_box, strict=True
|
|
411
409
|
)
|
|
412
410
|
]
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
def get_processing_results_from_event(
|
|
416
|
-
device_name: str, doc: dict
|
|
417
|
-
) -> Sequence[XrcResult]:
|
|
418
|
-
"""
|
|
419
|
-
Decode an event document into the corresponding x-ray centring results
|
|
420
|
-
|
|
421
|
-
Args:
|
|
422
|
-
doc A bluesky event document containing the signals read from the ZocaloResults
|
|
423
|
-
device_name The device name prefix to prepend to the document keys
|
|
424
|
-
|
|
425
|
-
Returns:
|
|
426
|
-
The list of XrcResults decoded from the event document
|
|
427
|
-
"""
|
|
428
|
-
results_keys = get_annotations(XrcResult).keys()
|
|
429
|
-
results_dict = {k: doc["data"][f"{device_name}-{k}"] for k in results_keys}
|
|
430
|
-
results_values = [results_dict[k].tolist() for k in results_keys]
|
|
431
|
-
|
|
432
|
-
def create_result(*argv):
|
|
433
|
-
kwargs = dict(zip(results_keys, argv, strict=False))
|
|
434
|
-
return XrcResult(**kwargs)
|
|
435
|
-
|
|
436
|
-
return list(map(create_result, *results_values))
|