dls-dodal 1.47.0__py3-none-any.whl → 1.49.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/METADATA +3 -2
- {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/RECORD +59 -49
- {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/WHEEL +1 -1
- dodal/_version.py +2 -2
- dodal/beamlines/aithre.py +21 -0
- dodal/beamlines/b01_1.py +1 -1
- dodal/beamlines/b16.py +65 -0
- dodal/beamlines/b18.py +38 -0
- dodal/beamlines/i03.py +21 -6
- dodal/beamlines/i04.py +17 -10
- dodal/beamlines/i10.py +41 -233
- dodal/beamlines/i18.py +1 -1
- dodal/beamlines/i19_1.py +9 -6
- dodal/beamlines/i24.py +5 -5
- dodal/beamlines/k11.py +35 -0
- dodal/common/beamlines/beamline_parameters.py +2 -28
- dodal/common/beamlines/device_helpers.py +1 -0
- dodal/devices/aithre_lasershaping/goniometer.py +36 -2
- dodal/devices/aithre_lasershaping/laser_robot.py +27 -0
- dodal/devices/apple2_undulator.py +257 -136
- dodal/devices/b16/__init__.py +0 -0
- dodal/devices/b16/detector.py +34 -0
- dodal/devices/bimorph_mirror.py +29 -36
- dodal/devices/electron_analyser/__init__.py +21 -1
- dodal/devices/electron_analyser/abstract/__init__.py +0 -6
- dodal/devices/electron_analyser/abstract/base_detector.py +16 -128
- dodal/devices/electron_analyser/abstract/base_driver_io.py +122 -8
- dodal/devices/electron_analyser/abstract/base_region.py +7 -3
- dodal/devices/electron_analyser/detector.py +141 -0
- dodal/devices/electron_analyser/enums.py +6 -0
- dodal/devices/electron_analyser/specs/__init__.py +3 -2
- dodal/devices/electron_analyser/specs/detector.py +6 -22
- dodal/devices/electron_analyser/specs/driver_io.py +27 -3
- dodal/devices/electron_analyser/specs/enums.py +8 -0
- dodal/devices/electron_analyser/specs/region.py +3 -2
- dodal/devices/electron_analyser/types.py +30 -4
- dodal/devices/electron_analyser/util.py +1 -1
- dodal/devices/electron_analyser/vgscienta/__init__.py +3 -2
- dodal/devices/electron_analyser/vgscienta/detector.py +9 -23
- dodal/devices/electron_analyser/vgscienta/driver_io.py +33 -4
- dodal/devices/electron_analyser/vgscienta/enums.py +19 -0
- dodal/devices/electron_analyser/vgscienta/region.py +7 -23
- dodal/devices/fast_grid_scan.py +1 -1
- dodal/devices/i04/murko_results.py +93 -96
- dodal/devices/i10/__init__.py +0 -0
- dodal/devices/i10/i10_apple2.py +181 -126
- dodal/devices/i18/diode.py +37 -4
- dodal/devices/i22/nxsas.py +1 -1
- dodal/devices/mx_phase1/beamstop.py +23 -6
- dodal/devices/oav/oav_detector.py +101 -25
- dodal/devices/oav/oav_parameters.py +46 -16
- dodal/devices/oav/oav_to_redis_forwarder.py +2 -2
- dodal/devices/robot.py +20 -1
- dodal/devices/smargon.py +43 -4
- dodal/devices/zebra/zebra.py +8 -0
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +167 -0
- dodal/plan_stubs/electron_analyser/__init__.py +0 -3
- dodal/plan_stubs/electron_analyser/configure_driver.py +0 -92
- {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/top_level.txt +0 -0
|
@@ -5,14 +5,21 @@ from ophyd_async.core import (
|
|
|
5
5
|
DEFAULT_TIMEOUT,
|
|
6
6
|
AsyncStatus,
|
|
7
7
|
LazyMock,
|
|
8
|
+
SignalR,
|
|
9
|
+
SignalRW,
|
|
8
10
|
StandardReadable,
|
|
9
11
|
derived_signal_r,
|
|
10
12
|
soft_signal_rw,
|
|
11
13
|
)
|
|
12
|
-
from ophyd_async.epics.core import epics_signal_rw
|
|
14
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
13
15
|
|
|
14
16
|
from dodal.devices.areadetector.plugins.CAM import Cam
|
|
15
|
-
from dodal.devices.oav.oav_parameters import
|
|
17
|
+
from dodal.devices.oav.oav_parameters import (
|
|
18
|
+
DEFAULT_OAV_WINDOW,
|
|
19
|
+
OAVConfig,
|
|
20
|
+
OAVConfigBase,
|
|
21
|
+
OAVConfigBeamCentre,
|
|
22
|
+
)
|
|
16
23
|
from dodal.devices.oav.snapshots.snapshot import Snapshot
|
|
17
24
|
from dodal.devices.oav.snapshots.snapshot_with_grid import SnapshotWithGrid
|
|
18
25
|
|
|
@@ -30,7 +37,21 @@ def _get_correct_zoom_string(zoom: str) -> str:
|
|
|
30
37
|
return zoom
|
|
31
38
|
|
|
32
39
|
|
|
33
|
-
class
|
|
40
|
+
class BaseZoomController(StandardReadable, Movable[str]):
|
|
41
|
+
level: SignalRW[str]
|
|
42
|
+
percentage: SignalRW[float]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class NullZoomController(BaseZoomController):
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.level = soft_signal_rw(str, "1.0x")
|
|
48
|
+
self.percentage = soft_signal_rw(float, 100)
|
|
49
|
+
|
|
50
|
+
def set(self, value):
|
|
51
|
+
raise Exception("Attempting to set zoom level of a null zoom controller")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ZoomController(BaseZoomController):
|
|
34
55
|
"""
|
|
35
56
|
Device to control the zoom level. This should be set like
|
|
36
57
|
o = OAV(name="oav")
|
|
@@ -53,12 +74,27 @@ class ZoomController(StandardReadable, Movable[str]):
|
|
|
53
74
|
|
|
54
75
|
|
|
55
76
|
class OAV(StandardReadable):
|
|
56
|
-
|
|
77
|
+
beam_centre_i: SignalR[int]
|
|
78
|
+
beam_centre_j: SignalR[int]
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
prefix: str,
|
|
83
|
+
config: OAVConfigBase,
|
|
84
|
+
name: str = "",
|
|
85
|
+
zoom_controller: BaseZoomController | None = None,
|
|
86
|
+
):
|
|
57
87
|
self.oav_config = config
|
|
58
88
|
self._prefix = prefix
|
|
59
89
|
self._name = name
|
|
60
90
|
_bl_prefix = prefix.split("-")[0]
|
|
61
|
-
|
|
91
|
+
|
|
92
|
+
if not zoom_controller:
|
|
93
|
+
self.zoom_controller = ZoomController(
|
|
94
|
+
f"{_bl_prefix}-EA-OAV-01:FZOOM:", name
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
self.zoom_controller = zoom_controller
|
|
62
98
|
|
|
63
99
|
self.cam = Cam(f"{prefix}CAM:", name=name)
|
|
64
100
|
with self.add_children_as_readables():
|
|
@@ -79,18 +115,6 @@ class OAV(StandardReadable):
|
|
|
79
115
|
size=self.sizes[Coords.Y],
|
|
80
116
|
coord=soft_signal_rw(datatype=int, initial_value=Coords.Y.value),
|
|
81
117
|
)
|
|
82
|
-
self.beam_centre_i = derived_signal_r(
|
|
83
|
-
self._get_beam_position,
|
|
84
|
-
zoom_level=self.zoom_controller.level,
|
|
85
|
-
size=self.sizes[Coords.X],
|
|
86
|
-
coord=soft_signal_rw(datatype=int, initial_value=Coords.X.value),
|
|
87
|
-
)
|
|
88
|
-
self.beam_centre_j = derived_signal_r(
|
|
89
|
-
self._get_beam_position,
|
|
90
|
-
zoom_level=self.zoom_controller.level,
|
|
91
|
-
size=self.sizes[Coords.Y],
|
|
92
|
-
coord=soft_signal_rw(datatype=int, initial_value=Coords.Y.value),
|
|
93
|
-
)
|
|
94
118
|
self.snapshot = Snapshot(
|
|
95
119
|
f"{self._prefix}MJPG:",
|
|
96
120
|
self._name,
|
|
@@ -107,14 +131,6 @@ class OAV(StandardReadable):
|
|
|
107
131
|
value = self.parameters[_zoom].microns_per_pixel[coord]
|
|
108
132
|
return value * DEFAULT_OAV_WINDOW[coord] / size
|
|
109
133
|
|
|
110
|
-
def _get_beam_position(self, zoom_level: str, size: int, coord: int) -> int:
|
|
111
|
-
"""Extracts the beam location in pixels `xCentre` `yCentre`, for a requested \
|
|
112
|
-
zoom level. """
|
|
113
|
-
_zoom = self._read_current_zoom(zoom_level)
|
|
114
|
-
value = self.parameters[_zoom].crosshair[coord]
|
|
115
|
-
|
|
116
|
-
return int(value * size / DEFAULT_OAV_WINDOW[coord])
|
|
117
|
-
|
|
118
134
|
async def connect(
|
|
119
135
|
self,
|
|
120
136
|
mock: bool | LazyMock = False,
|
|
@@ -124,3 +140,63 @@ class OAV(StandardReadable):
|
|
|
124
140
|
self.parameters = self.oav_config.get_parameters()
|
|
125
141
|
|
|
126
142
|
return await super().connect(mock, timeout, force_reconnect)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class OAVBeamCentreFile(OAV):
|
|
146
|
+
"""OAV device that reads its beam centre values from a file. The config parameter
|
|
147
|
+
must be a OAVConfigBeamCentre object, as this contains a filepath to where the beam
|
|
148
|
+
centre values are stored.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(
|
|
152
|
+
self,
|
|
153
|
+
prefix: str,
|
|
154
|
+
config: OAVConfigBeamCentre,
|
|
155
|
+
name: str = "",
|
|
156
|
+
zoom_controller: BaseZoomController | None = None,
|
|
157
|
+
):
|
|
158
|
+
super().__init__(prefix, config, name, zoom_controller)
|
|
159
|
+
|
|
160
|
+
with self.add_children_as_readables():
|
|
161
|
+
self.beam_centre_i = derived_signal_r(
|
|
162
|
+
self._get_beam_position,
|
|
163
|
+
zoom_level=self.zoom_controller.level,
|
|
164
|
+
size=self.sizes[Coords.X],
|
|
165
|
+
coord=soft_signal_rw(datatype=int, initial_value=Coords.X.value),
|
|
166
|
+
)
|
|
167
|
+
self.beam_centre_j = derived_signal_r(
|
|
168
|
+
self._get_beam_position,
|
|
169
|
+
zoom_level=self.zoom_controller.level,
|
|
170
|
+
size=self.sizes[Coords.Y],
|
|
171
|
+
coord=soft_signal_rw(datatype=int, initial_value=Coords.Y.value),
|
|
172
|
+
)
|
|
173
|
+
# Set name so that new child signals get correct name
|
|
174
|
+
self.set_name(self.name)
|
|
175
|
+
|
|
176
|
+
def _get_beam_position(self, zoom_level: str, size: int, coord: int) -> int:
|
|
177
|
+
"""Extracts the beam location in pixels `xCentre` `yCentre`, for a requested \
|
|
178
|
+
zoom level. """
|
|
179
|
+
_zoom = self._read_current_zoom(zoom_level)
|
|
180
|
+
value = self.parameters[_zoom].crosshair[coord]
|
|
181
|
+
return int(value * size / DEFAULT_OAV_WINDOW[coord])
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class OAVBeamCentrePV(OAV):
|
|
185
|
+
"""OAV device that reads its beam centre values from PVs."""
|
|
186
|
+
|
|
187
|
+
def __init__(
|
|
188
|
+
self,
|
|
189
|
+
prefix: str,
|
|
190
|
+
config: OAVConfig,
|
|
191
|
+
name: str = "",
|
|
192
|
+
zoom_controller: BaseZoomController | None = None,
|
|
193
|
+
overlay_channel: int = 1,
|
|
194
|
+
):
|
|
195
|
+
with self.add_children_as_readables():
|
|
196
|
+
self.beam_centre_i = epics_signal_r(
|
|
197
|
+
int, prefix + f"OVER:{overlay_channel}:CenterX"
|
|
198
|
+
)
|
|
199
|
+
self.beam_centre_j = epics_signal_r(
|
|
200
|
+
int, prefix + f"OVER:{overlay_channel}:CenterY"
|
|
201
|
+
)
|
|
202
|
+
super().__init__(prefix, config, name, zoom_controller)
|
|
@@ -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/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/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
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import bluesky.plan_stubs as bps
|
|
4
|
+
from bluesky import preprocessors as bpp
|
|
5
|
+
from bluesky.run_engine import RunEngine
|
|
6
|
+
from ophyd_async.core import DetectorTrigger
|
|
7
|
+
from ophyd_async.fastcs.eiger import EigerDetector, EigerTriggerInfo
|
|
8
|
+
|
|
9
|
+
from dodal.beamlines.i03 import fastcs_eiger
|
|
10
|
+
from dodal.devices.detector import DetectorParams
|
|
11
|
+
from dodal.log import LOGGER, do_default_logging_setup
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@bpp.run_decorator()
|
|
15
|
+
def configure_arm_trigger_and_disarm_detector(
|
|
16
|
+
eiger: EigerDetector,
|
|
17
|
+
detector_params: DetectorParams,
|
|
18
|
+
trigger_info: EigerTriggerInfo,
|
|
19
|
+
):
|
|
20
|
+
assert detector_params.expected_energy_ev
|
|
21
|
+
start = time.time()
|
|
22
|
+
yield from bps.unstage(eiger, wait=True)
|
|
23
|
+
LOGGER.info(f"Stopping Eiger-Odin: {time.time() - start}s")
|
|
24
|
+
start = time.time()
|
|
25
|
+
yield from set_cam_pvs(eiger, detector_params, wait=True)
|
|
26
|
+
LOGGER.info(f"Setting CAM PVs: {time.time() - start}s")
|
|
27
|
+
start = time.time()
|
|
28
|
+
yield from change_roi_mode(eiger, detector_params, wait=True)
|
|
29
|
+
LOGGER.info(f"Changing ROI Mode: {time.time() - start}s")
|
|
30
|
+
start = time.time()
|
|
31
|
+
yield from bps.abs_set(eiger.odin.num_frames_chunks, 1)
|
|
32
|
+
LOGGER.info(f"Setting # of Frame Chunks: {time.time() - start}s")
|
|
33
|
+
start = time.time()
|
|
34
|
+
yield from set_mx_settings_pvs(eiger, detector_params, wait=True)
|
|
35
|
+
LOGGER.info(f"Setting MX PVs: {time.time() - start}s")
|
|
36
|
+
start = time.time()
|
|
37
|
+
yield from bps.prepare(eiger, trigger_info, wait=True)
|
|
38
|
+
LOGGER.info(f"Preparing Eiger: {time.time() - start}s")
|
|
39
|
+
start = time.time()
|
|
40
|
+
yield from bps.kickoff(eiger, wait=True)
|
|
41
|
+
LOGGER.info(f"Kickoff Eiger: {time.time() - start}s")
|
|
42
|
+
start = time.time()
|
|
43
|
+
yield from bps.trigger(eiger.drv.detector.trigger) # type: ignore
|
|
44
|
+
LOGGER.info(f"Triggering Eiger: {time.time() - start}s")
|
|
45
|
+
start = time.time()
|
|
46
|
+
yield from bps.complete(eiger, wait=True)
|
|
47
|
+
LOGGER.info(f"Completing Capture: {time.time() - start}s")
|
|
48
|
+
start = time.time()
|
|
49
|
+
yield from bps.unstage(eiger, wait=True)
|
|
50
|
+
LOGGER.info(f"Disarming Eiger: {time.time() - start}s")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def set_cam_pvs(
|
|
54
|
+
eiger: EigerDetector,
|
|
55
|
+
detector_params: DetectorParams,
|
|
56
|
+
wait: bool,
|
|
57
|
+
group="cam_pvs",
|
|
58
|
+
):
|
|
59
|
+
yield from bps.abs_set(
|
|
60
|
+
eiger.drv.detector.count_time, detector_params.exposure_time_s, group=group
|
|
61
|
+
)
|
|
62
|
+
yield from bps.abs_set(
|
|
63
|
+
eiger.drv.detector.frame_time, detector_params.exposure_time_s, group=group
|
|
64
|
+
)
|
|
65
|
+
yield from bps.abs_set(eiger.drv.detector.nexpi, 1, group=group)
|
|
66
|
+
|
|
67
|
+
if wait:
|
|
68
|
+
yield from bps.wait(group)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def change_roi_mode(
|
|
72
|
+
eiger: EigerDetector,
|
|
73
|
+
detector_params: DetectorParams,
|
|
74
|
+
wait: bool,
|
|
75
|
+
group="roi_mode",
|
|
76
|
+
):
|
|
77
|
+
detector_dimensions = (
|
|
78
|
+
detector_params.detector_size_constants.roi_size_pixels
|
|
79
|
+
if detector_params.use_roi_mode
|
|
80
|
+
else detector_params.detector_size_constants.det_size_pixels
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
yield from bps.abs_set(
|
|
84
|
+
eiger.drv.detector.roi_mode,
|
|
85
|
+
1 if detector_params.use_roi_mode else 0,
|
|
86
|
+
group=group,
|
|
87
|
+
)
|
|
88
|
+
yield from bps.abs_set(
|
|
89
|
+
eiger.odin.image_height,
|
|
90
|
+
detector_dimensions.height,
|
|
91
|
+
group=group,
|
|
92
|
+
)
|
|
93
|
+
yield from bps.abs_set(
|
|
94
|
+
eiger.odin.image_width,
|
|
95
|
+
detector_dimensions.width,
|
|
96
|
+
group=group,
|
|
97
|
+
)
|
|
98
|
+
yield from bps.abs_set(
|
|
99
|
+
eiger.odin.num_row_chunks,
|
|
100
|
+
detector_dimensions.height,
|
|
101
|
+
group=group,
|
|
102
|
+
)
|
|
103
|
+
yield from bps.abs_set(
|
|
104
|
+
eiger.odin.num_col_chunks,
|
|
105
|
+
detector_dimensions.width,
|
|
106
|
+
group=group,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if wait:
|
|
110
|
+
yield from bps.wait(group)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def set_mx_settings_pvs(
|
|
114
|
+
eiger: EigerDetector,
|
|
115
|
+
detector_params: DetectorParams,
|
|
116
|
+
wait: bool,
|
|
117
|
+
group="mx_settings",
|
|
118
|
+
):
|
|
119
|
+
beam_x_pixels, beam_y_pixels = detector_params.get_beam_position_pixels(
|
|
120
|
+
detector_params.detector_distance
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
yield from bps.abs_set(eiger.drv.detector.beam_center_x, beam_x_pixels, group)
|
|
124
|
+
yield from bps.abs_set(eiger.drv.detector.beam_center_y, beam_y_pixels, group)
|
|
125
|
+
yield from bps.abs_set(
|
|
126
|
+
eiger.drv.detector.detector_distance, detector_params.detector_distance, group
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
yield from bps.abs_set(
|
|
130
|
+
eiger.drv.detector.omega_start, detector_params.omega_start, group
|
|
131
|
+
)
|
|
132
|
+
yield from bps.abs_set(
|
|
133
|
+
eiger.drv.detector.omega_increment, detector_params.omega_increment, group
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if wait:
|
|
137
|
+
yield from bps.wait(group)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
RE = RunEngine()
|
|
142
|
+
do_default_logging_setup()
|
|
143
|
+
eiger = fastcs_eiger(connect_immediately=True)
|
|
144
|
+
RE(
|
|
145
|
+
configure_arm_trigger_and_disarm_detector(
|
|
146
|
+
eiger=eiger,
|
|
147
|
+
detector_params=DetectorParams(
|
|
148
|
+
expected_energy_ev=12800,
|
|
149
|
+
exposure_time_s=0.01,
|
|
150
|
+
directory="/dls/i03/data/2025/cm40607-2/test_new_eiger/",
|
|
151
|
+
prefix="",
|
|
152
|
+
detector_distance=255,
|
|
153
|
+
omega_start=0,
|
|
154
|
+
omega_increment=0.1,
|
|
155
|
+
num_images_per_trigger=1,
|
|
156
|
+
num_triggers=1,
|
|
157
|
+
use_roi_mode=False,
|
|
158
|
+
det_dist_to_beam_converter_path="/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt",
|
|
159
|
+
),
|
|
160
|
+
trigger_info=EigerTriggerInfo(
|
|
161
|
+
number_of_events=1,
|
|
162
|
+
energy_ev=12800,
|
|
163
|
+
trigger=DetectorTrigger.INTERNAL,
|
|
164
|
+
deadtime=0.0001,
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
)
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
from bluesky import plan_stubs as bps
|
|
2
|
-
from bluesky.utils import MsgGenerator, plan
|
|
3
|
-
from ophyd_async.epics.adcore import ADImageMode
|
|
4
|
-
|
|
5
|
-
from dodal.common.types import MsgGenerator
|
|
6
|
-
from dodal.devices.electron_analyser.abstract import (
|
|
7
|
-
AbstractAnalyserDriverIO,
|
|
8
|
-
AbstractBaseRegion,
|
|
9
|
-
)
|
|
10
|
-
from dodal.devices.electron_analyser.specs import SpecsAnalyserDriverIO, SpecsRegion
|
|
11
|
-
from dodal.devices.electron_analyser.util import to_kinetic_energy
|
|
12
|
-
from dodal.devices.electron_analyser.vgscienta import (
|
|
13
|
-
VGScientaAnalyserDriverIO,
|
|
14
|
-
VGScientaRegion,
|
|
15
|
-
)
|
|
16
|
-
from dodal.log import LOGGER
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@plan
|
|
20
|
-
def configure_analyser(
|
|
21
|
-
analyser: AbstractAnalyserDriverIO,
|
|
22
|
-
region: AbstractBaseRegion,
|
|
23
|
-
excitation_energy: float,
|
|
24
|
-
) -> MsgGenerator:
|
|
25
|
-
LOGGER.info(f'Configuring analyser with region "{region.name}"')
|
|
26
|
-
|
|
27
|
-
low_energy = to_kinetic_energy(
|
|
28
|
-
region.low_energy, region.energy_mode, excitation_energy
|
|
29
|
-
)
|
|
30
|
-
high_energy = to_kinetic_energy(
|
|
31
|
-
region.high_energy, region.energy_mode, excitation_energy
|
|
32
|
-
)
|
|
33
|
-
pass_energy_type = analyser.pass_energy_type
|
|
34
|
-
pass_energy = pass_energy_type(region.pass_energy)
|
|
35
|
-
|
|
36
|
-
# Set detector settings, wait for them all to have completed
|
|
37
|
-
# fmt: off
|
|
38
|
-
yield from bps.mv(
|
|
39
|
-
analyser.region_name, region.name,
|
|
40
|
-
analyser.energy_mode, region.energy_mode,
|
|
41
|
-
analyser.excitation_energy, excitation_energy,
|
|
42
|
-
analyser.low_energy, low_energy,
|
|
43
|
-
analyser.high_energy, high_energy,
|
|
44
|
-
analyser.slices, region.slices,
|
|
45
|
-
analyser.lens_mode, region.lens_mode,
|
|
46
|
-
analyser.pass_energy, pass_energy,
|
|
47
|
-
analyser.iterations, region.iterations,
|
|
48
|
-
analyser.acquisition_mode, region.acquisition_mode,
|
|
49
|
-
)
|
|
50
|
-
# fmt: on
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@plan
|
|
54
|
-
def configure_specs(
|
|
55
|
-
analyser: SpecsAnalyserDriverIO, region: SpecsRegion, excitation_energy: float
|
|
56
|
-
) -> MsgGenerator:
|
|
57
|
-
yield from configure_analyser(analyser, region, excitation_energy)
|
|
58
|
-
# fmt: off
|
|
59
|
-
yield from bps.mv(
|
|
60
|
-
analyser.snapshot_values, region.values,
|
|
61
|
-
analyser.psu_mode, region.psu_mode,
|
|
62
|
-
)
|
|
63
|
-
# fmt: on
|
|
64
|
-
if region.acquisition_mode == "Fixed Transmission":
|
|
65
|
-
yield from bps.mv(analyser.centre_energy, region.centre_energy)
|
|
66
|
-
|
|
67
|
-
if region.acquisition_mode == "Fixed Energy":
|
|
68
|
-
yield from bps.mv(analyser.energy_step, region.energy_step)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@plan
|
|
72
|
-
def configure_vgscienta(
|
|
73
|
-
analyser: VGScientaAnalyserDriverIO, region: VGScientaRegion, excitation_energy
|
|
74
|
-
) -> MsgGenerator:
|
|
75
|
-
yield from configure_analyser(analyser, region, excitation_energy)
|
|
76
|
-
centre_energy = to_kinetic_energy(
|
|
77
|
-
region.fix_energy, region.energy_mode, excitation_energy
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
# fmt: off
|
|
81
|
-
yield from bps.mv(
|
|
82
|
-
analyser.centre_energy, centre_energy,
|
|
83
|
-
analyser.energy_step, region.energy_step,
|
|
84
|
-
analyser.first_x_channel, region.first_x_channel,
|
|
85
|
-
analyser.first_y_channel, region.first_y_channel,
|
|
86
|
-
analyser.x_channel_size, region.x_channel_size(),
|
|
87
|
-
analyser.y_channel_size, region.y_channel_size(),
|
|
88
|
-
analyser.detector_mode, region.detector_mode,
|
|
89
|
-
analyser.excitation_energy_source, region.excitation_energy_source,
|
|
90
|
-
analyser.image_mode, ADImageMode.SINGLE,
|
|
91
|
-
)
|
|
92
|
-
# fmt: on
|
|
File without changes
|
|
File without changes
|
|
File without changes
|