dls-dodal 1.61.0__py3-none-any.whl → 1.63.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.61.0.dist-info → dls_dodal-1.63.0.dist-info}/METADATA +1 -1
- {dls_dodal-1.61.0.dist-info → dls_dodal-1.63.0.dist-info}/RECORD +81 -73
- dls_dodal-1.63.0.dist-info/entry_points.txt +3 -0
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/adsim.py +5 -3
- dodal/beamlines/b21.py +3 -1
- dodal/beamlines/i02_2.py +32 -0
- dodal/beamlines/i03.py +9 -0
- dodal/beamlines/i04.py +1 -1
- dodal/beamlines/i09.py +10 -3
- dodal/beamlines/i09_1.py +9 -3
- dodal/beamlines/i10.py +7 -69
- dodal/beamlines/i10_1.py +35 -0
- dodal/beamlines/i10_optics.py +205 -0
- dodal/beamlines/i15_1.py +5 -5
- dodal/beamlines/i17.py +50 -1
- dodal/beamlines/i18.py +15 -9
- dodal/beamlines/i19_1.py +3 -3
- dodal/beamlines/i19_2.py +12 -2
- dodal/beamlines/i19_optics.py +4 -1
- dodal/beamlines/i24.py +3 -3
- dodal/cli.py +4 -4
- dodal/common/visit.py +4 -4
- dodal/devices/aperturescatterguard.py +6 -4
- dodal/devices/apple2_undulator.py +211 -114
- dodal/devices/attenuator/filter_selections.py +6 -6
- dodal/devices/common_dcm.py +62 -15
- dodal/devices/controllers.py +8 -6
- dodal/devices/current_amplifiers/femto.py +4 -4
- dodal/devices/current_amplifiers/sr570.py +3 -3
- dodal/devices/fast_grid_scan.py +97 -21
- dodal/devices/fast_shutter.py +69 -0
- dodal/devices/i02_1/fast_grid_scan.py +1 -1
- dodal/devices/i02_2/__init__.py +0 -0
- dodal/devices/i03/dcm.py +4 -2
- dodal/devices/i04/murko_results.py +35 -14
- dodal/devices/i09/__init__.py +1 -2
- dodal/devices/i10/__init__.py +29 -0
- dodal/devices/i10/diagnostics.py +37 -5
- dodal/devices/i10/i10_apple2.py +125 -229
- dodal/devices/i10/slits.py +38 -6
- dodal/devices/i15/dcm.py +6 -44
- dodal/devices/i17/__init__.py +0 -0
- dodal/devices/i17/i17_apple2.py +51 -0
- dodal/devices/i19/access_controlled/__init__.py +0 -0
- dodal/devices/i19/{shutter.py → access_controlled/shutter.py} +7 -4
- dodal/devices/i19/mapt_configuration.py +38 -0
- dodal/devices/i19/pin_col_stages.py +170 -0
- dodal/devices/i22/dcm.py +2 -2
- dodal/devices/i24/dcm.py +2 -2
- dodal/devices/oav/oav_detector.py +1 -1
- dodal/devices/oav/oav_parameters.py +4 -4
- dodal/devices/oav/oav_to_redis_forwarder.py +4 -4
- dodal/devices/oav/pin_image_recognition/__init__.py +3 -3
- dodal/devices/oav/pin_image_recognition/utils.py +1 -1
- dodal/devices/oav/snapshots/snapshot.py +1 -1
- dodal/devices/oav/snapshots/snapshot_image_processing.py +12 -12
- dodal/devices/oav/snapshots/snapshot_with_grid.py +1 -1
- dodal/devices/oav/utils.py +2 -2
- dodal/devices/pgm.py +3 -3
- dodal/devices/robot.py +5 -5
- dodal/devices/tetramm.py +9 -5
- dodal/devices/thawer.py +0 -4
- dodal/devices/v2f.py +2 -2
- dodal/devices/zebra/zebra_constants_mapping.py +2 -2
- dodal/devices/zocalo/__init__.py +4 -4
- dodal/devices/zocalo/zocalo_results.py +4 -4
- dodal/log.py +9 -9
- dodal/plan_stubs/motor_utils.py +4 -4
- dodal/plans/configure_arm_trigger_and_disarm_detector.py +29 -7
- dodal/plans/save_panda.py +7 -7
- dodal/plans/verify_undulator_gap.py +2 -2
- dls_dodal-1.61.0.dist-info/entry_points.txt +0 -3
- dodal/beamlines/i10-1.py +0 -25
- dodal/devices/i09/dcm.py +0 -26
- {dls_dodal-1.61.0.dist-info → dls_dodal-1.63.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.61.0.dist-info → dls_dodal-1.63.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.61.0.dist-info → dls_dodal-1.63.0.dist-info}/top_level.txt +0 -0
- /dodal/devices/areadetector/plugins/{CAM.py → cam.py} +0 -0
- /dodal/devices/areadetector/plugins/{MJPG.py → mjpg.py} +0 -0
- /dodal/devices/i18/{KBMirror.py → kb_mirror.py} +0 -0
- /dodal/devices/i19/{blueapi_device.py → access_controlled/blueapi_device.py} +0 -0
- /dodal/devices/i19/{hutch_access.py → access_controlled/hutch_access.py} +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from ophyd_async.core import DeviceVector, SignalR, StandardReadable, SubsetEnum
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw, epics_signal_x
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MAPTConfigurationTable(StandardReadable):
|
|
6
|
+
"""Readable device that can be used to build the read-only MAPT (Mini Apertures)
|
|
7
|
+
configuration table in controls for aperture motors in the available positions.
|
|
8
|
+
For each aperture it sets up a readable signal with the position of all the motors
|
|
9
|
+
in the MAPT configuration.
|
|
10
|
+
This device can be used to build the table for both eh1 and eh2 on I19, in
|
|
11
|
+
combination with the MAPTConfigurationControl device.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self, prefix: str, motor_name: str, aperture_list: list[int], name: str = ""
|
|
16
|
+
) -> None:
|
|
17
|
+
with self.add_children_as_readables():
|
|
18
|
+
self.in_positions: DeviceVector[SignalR[float]] = DeviceVector(
|
|
19
|
+
{
|
|
20
|
+
pos: epics_signal_r(float, f"{prefix}:{pos}UM:{motor_name}")
|
|
21
|
+
for pos in aperture_list
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
super().__init__(name)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MAPTConfigurationControl(StandardReadable):
|
|
28
|
+
"""A device to control the MAPT (Mini Aperture) configuration. It provides a signal
|
|
29
|
+
to set the configuration PV to the requested value and a triggerable signal that
|
|
30
|
+
will move all the motors to the correct position."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self, prefix: str, aperture_request: type[SubsetEnum], name: str = ""
|
|
34
|
+
) -> None:
|
|
35
|
+
with self.add_children_as_readables():
|
|
36
|
+
self.select_config = epics_signal_rw(aperture_request, f"{prefix}")
|
|
37
|
+
self.apply_selection = epics_signal_x(f"{prefix}:APPLY.PROC")
|
|
38
|
+
super().__init__(name)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from enum import StrEnum
|
|
3
|
+
|
|
4
|
+
from bluesky.protocols import Movable
|
|
5
|
+
from ophyd_async.core import AsyncStatus, StandardReadable, SubsetEnum
|
|
6
|
+
from ophyd_async.epics.core import epics_signal_r
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from dodal.devices.i19.mapt_configuration import (
|
|
10
|
+
MAPTConfigurationControl,
|
|
11
|
+
MAPTConfigurationTable,
|
|
12
|
+
)
|
|
13
|
+
from dodal.devices.motors import XYStage
|
|
14
|
+
from dodal.log import LOGGER
|
|
15
|
+
|
|
16
|
+
_PIN = "-MO-PIN-01:"
|
|
17
|
+
_COL = "-MO-COL-01:"
|
|
18
|
+
_CONFIG = "-OP-PCOL-01:"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PinColRequest(StrEnum):
|
|
22
|
+
"""Aperture request positions."""
|
|
23
|
+
|
|
24
|
+
PCOL20 = "20um"
|
|
25
|
+
PCOL40 = "40um"
|
|
26
|
+
PCOL100 = "100um"
|
|
27
|
+
PCOL3000 = "3000um"
|
|
28
|
+
OUT = "OUT"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# NOTE. Using subset enum because from the OUT positions should only be used by
|
|
32
|
+
# the beamline scientists from the synoptic. Another option will be needed in the
|
|
33
|
+
# device for OUT position.
|
|
34
|
+
class _PinColPosition(SubsetEnum):
|
|
35
|
+
"""Aperture request IN positions."""
|
|
36
|
+
|
|
37
|
+
PCOL20 = "20um"
|
|
38
|
+
PCOL40 = "40um"
|
|
39
|
+
PCOL100 = "100um"
|
|
40
|
+
PCOL3000 = "3000um"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AperturePosition(BaseModel):
|
|
44
|
+
"""Describe the positions of the pinhole and collimator stage motors for
|
|
45
|
+
one of the available apertures.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
pinhole_x: The position of the x motor on the pinhole stage
|
|
49
|
+
pinhole_y: The position of the y motor on the pinhole stage
|
|
50
|
+
collimator_x: The position of the x motor on the collimator stage
|
|
51
|
+
collimator_y: The position of the y motor on the collimator stage
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
pinhole_x: float
|
|
55
|
+
pinhole_y: float
|
|
56
|
+
collimator_x: float
|
|
57
|
+
collimator_y: float
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PinColConfiguration(StandardReadable):
|
|
61
|
+
"""Full MAPT configuration table, including out positions and selection for the
|
|
62
|
+
Pinhole and Collimator control."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, prefix: str, apertures: list[int], name: str = "") -> None:
|
|
65
|
+
with self.add_children_as_readables():
|
|
66
|
+
self.configuration = MAPTConfigurationControl(prefix, _PinColPosition)
|
|
67
|
+
self.pin_x = MAPTConfigurationTable(prefix, "PINX", apertures)
|
|
68
|
+
self.pin_y = MAPTConfigurationTable(prefix, "PINY", apertures)
|
|
69
|
+
self.col_x = MAPTConfigurationTable(prefix, "COLX", apertures)
|
|
70
|
+
self.col_y = MAPTConfigurationTable(prefix, "COLY", apertures)
|
|
71
|
+
self.pin_x_out = epics_signal_r(float, f"{prefix}:OUT:PINX")
|
|
72
|
+
self.col_x_out = epics_signal_r(float, f"{prefix}:OUT:COLX")
|
|
73
|
+
super().__init__(name)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PinholeCollimatorControl(StandardReadable, Movable[str]):
|
|
77
|
+
"""Device to control the Pinhole and Collimator stages moves on I19-2, using the
|
|
78
|
+
MAPT configuration table to look up the positions."""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
prefix: str,
|
|
83
|
+
name: str = "",
|
|
84
|
+
pin_infix: str = _PIN,
|
|
85
|
+
col_infix: str = _COL,
|
|
86
|
+
config_infix: str = _CONFIG,
|
|
87
|
+
):
|
|
88
|
+
self._aperture_sizes = [self._get_aperture_size(i) for i in _PinColPosition]
|
|
89
|
+
with self.add_children_as_readables():
|
|
90
|
+
self._pinhole = XYStage(f"{prefix}{pin_infix}")
|
|
91
|
+
self._collimator = XYStage(f"{prefix}{col_infix}")
|
|
92
|
+
self.mapt = PinColConfiguration(
|
|
93
|
+
f"{prefix}{config_infix}CONFIG", apertures=self._aperture_sizes
|
|
94
|
+
)
|
|
95
|
+
super().__init__(name=name)
|
|
96
|
+
|
|
97
|
+
def _get_aperture_size(self, ap_request: str) -> int:
|
|
98
|
+
return int(ap_request.strip("um"))
|
|
99
|
+
|
|
100
|
+
async def _get_motor_positions_for_requested_aperture(
|
|
101
|
+
self, ap_request: _PinColPosition
|
|
102
|
+
) -> AperturePosition:
|
|
103
|
+
val = self._get_aperture_size(ap_request.value)
|
|
104
|
+
|
|
105
|
+
pinx = await self.mapt.pin_x.in_positions[val].get_value()
|
|
106
|
+
piny = await self.mapt.pin_y.in_positions[val].get_value()
|
|
107
|
+
colx = await self.mapt.col_x.in_positions[val].get_value()
|
|
108
|
+
coly = await self.mapt.col_y.in_positions[val].get_value()
|
|
109
|
+
|
|
110
|
+
return AperturePosition(
|
|
111
|
+
pinhole_x=pinx, pinhole_y=piny, collimator_x=colx, collimator_y=coly
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
async def _safe_move_out(self):
|
|
115
|
+
"""Move the pinhole and collimator stages safely to the out position, which
|
|
116
|
+
involves only the x motors of the stages.
|
|
117
|
+
In order to avoid a collision, we have to make sure that the collimator stage is
|
|
118
|
+
always moved out first and the pinhole stage second.
|
|
119
|
+
"""
|
|
120
|
+
LOGGER.info("Moving pinhole and collimator stages to out position")
|
|
121
|
+
colx_out = await self.mapt.col_x_out.get_value()
|
|
122
|
+
pin_x_out = await self.mapt.pin_x_out.get_value()
|
|
123
|
+
# First move Collimator x motor
|
|
124
|
+
LOGGER.debug(f"Move collimator stage x motor to {colx_out}")
|
|
125
|
+
await self._collimator.x.set(colx_out)
|
|
126
|
+
# Then move Pinhole x motor
|
|
127
|
+
LOGGER.debug(f"Move pinhole stage x motor to {pin_x_out}")
|
|
128
|
+
await self._pinhole.x.set(pin_x_out)
|
|
129
|
+
|
|
130
|
+
async def _safe_move_in(self, value: _PinColPosition):
|
|
131
|
+
"""Move the pinhole and collimator stages safely to the in position.
|
|
132
|
+
In order to avoid a collision, we have to make sure that the pinhole stage is
|
|
133
|
+
always moved in before the collimator stage."""
|
|
134
|
+
LOGGER.info(
|
|
135
|
+
f"Moving pinhole and collimator stages to in position: {value.value}"
|
|
136
|
+
)
|
|
137
|
+
await self.mapt.configuration.select_config.set(value, wait=True)
|
|
138
|
+
# NOTE. The apply PV will not be used here unless fixed in controls first.
|
|
139
|
+
# This is to avoid collisions. A safe move in will move first the pinhole stage
|
|
140
|
+
# and then the collimator stage, but apply will try to move all the motors
|
|
141
|
+
# at the same time.
|
|
142
|
+
aperture_positions = await self._get_motor_positions_for_requested_aperture(
|
|
143
|
+
value
|
|
144
|
+
)
|
|
145
|
+
LOGGER.debug(f"Moving motors to {aperture_positions}")
|
|
146
|
+
|
|
147
|
+
# First move Pinhole motors,
|
|
148
|
+
LOGGER.debug("Moving pinhole stage in")
|
|
149
|
+
await asyncio.gather(
|
|
150
|
+
self._pinhole.x.set(aperture_positions.pinhole_x),
|
|
151
|
+
self._pinhole.y.set(aperture_positions.pinhole_y),
|
|
152
|
+
)
|
|
153
|
+
# Then move Collimator motors
|
|
154
|
+
LOGGER.debug("Moving collimator stage in")
|
|
155
|
+
await asyncio.gather(
|
|
156
|
+
self._collimator.x.set(aperture_positions.collimator_x),
|
|
157
|
+
self._collimator.y.set(aperture_positions.collimator_y),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
@AsyncStatus.wrap
|
|
161
|
+
async def set(self, value: PinColRequest):
|
|
162
|
+
"""Moves the motor stages to the position for the requested aperture while
|
|
163
|
+
avoiding possible collisions.
|
|
164
|
+
The request coming from a plan should always be one of accepted request values:
|
|
165
|
+
('20um', '40um', '100um', '3000um', 'OUT').
|
|
166
|
+
"""
|
|
167
|
+
if value is PinColRequest.OUT:
|
|
168
|
+
await self._safe_move_out()
|
|
169
|
+
else:
|
|
170
|
+
await self._safe_move_in(_PinColPosition(value))
|
dodal/devices/i22/dcm.py
CHANGED
|
@@ -13,7 +13,7 @@ from ophyd_async.epics.motor import Motor
|
|
|
13
13
|
|
|
14
14
|
from dodal.common.crystal_metadata import CrystalMetadata
|
|
15
15
|
from dodal.devices.common_dcm import (
|
|
16
|
-
|
|
16
|
+
DoubleCrystalMonochromatorWithDSpacing,
|
|
17
17
|
PitchAndRollCrystal,
|
|
18
18
|
RollCrystal,
|
|
19
19
|
)
|
|
@@ -23,7 +23,7 @@ from dodal.devices.common_dcm import (
|
|
|
23
23
|
_CONVERSION_CONSTANT = 12.3984
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
class DCM(
|
|
26
|
+
class DCM(DoubleCrystalMonochromatorWithDSpacing[RollCrystal, PitchAndRollCrystal]):
|
|
27
27
|
"""
|
|
28
28
|
A double crystal monochromator (DCM), used to select the energy of the beam.
|
|
29
29
|
|
dodal/devices/i24/dcm.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from ophyd_async.epics.core import epics_signal_r
|
|
2
2
|
|
|
3
3
|
from dodal.devices.common_dcm import (
|
|
4
|
-
|
|
4
|
+
DoubleCrystalMonochromatorWithDSpacing,
|
|
5
5
|
PitchAndRollCrystal,
|
|
6
6
|
RollCrystal,
|
|
7
7
|
)
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class DCM(
|
|
10
|
+
class DCM(DoubleCrystalMonochromatorWithDSpacing[RollCrystal, PitchAndRollCrystal]):
|
|
11
11
|
"""
|
|
12
12
|
A double crystal monocromator device, used to select the beam energy.
|
|
13
13
|
"""
|
|
@@ -13,7 +13,7 @@ from ophyd_async.core import (
|
|
|
13
13
|
)
|
|
14
14
|
from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
|
|
15
15
|
|
|
16
|
-
from dodal.devices.areadetector.plugins.
|
|
16
|
+
from dodal.devices.areadetector.plugins.cam import Cam
|
|
17
17
|
from dodal.devices.oav.oav_parameters import (
|
|
18
18
|
DEFAULT_OAV_WINDOW,
|
|
19
19
|
OAVConfig,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import xml.etree.ElementTree as et
|
|
3
2
|
from abc import abstractmethod
|
|
4
3
|
from collections import ChainMap
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
from typing import Any, Generic, TypeVar
|
|
6
|
+
from xml.etree import ElementTree
|
|
7
7
|
from xml.etree.ElementTree import Element
|
|
8
8
|
|
|
9
9
|
# GDA currently assumes this aspect ratio for the OAV window size.
|
|
@@ -98,11 +98,11 @@ class OAVParameters:
|
|
|
98
98
|
self.direction: int = update("direction", int)
|
|
99
99
|
self.max_tip_distance: float = update("max_tip_distance", float, default=300)
|
|
100
100
|
|
|
101
|
-
def get_max_tip_distance_in_pixels(self,
|
|
101
|
+
def get_max_tip_distance_in_pixels(self, microns_per_pixel: float) -> float:
|
|
102
102
|
"""
|
|
103
103
|
Get the maximum tip distance in pixels.
|
|
104
104
|
"""
|
|
105
|
-
return self.max_tip_distance /
|
|
105
|
+
return self.max_tip_distance / microns_per_pixel
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
@dataclass
|
|
@@ -123,7 +123,7 @@ class OAVConfigBase(Generic[ParamType]):
|
|
|
123
123
|
self.zoom_params = self._get_zoom_params(zoom_params_file)
|
|
124
124
|
|
|
125
125
|
def _get_zoom_params(self, zoom_params_file: str):
|
|
126
|
-
tree =
|
|
126
|
+
tree = ElementTree.parse(zoom_params_file)
|
|
127
127
|
root = tree.getroot()
|
|
128
128
|
return root.findall(".//zoomLevel")
|
|
129
129
|
|
|
@@ -21,12 +21,12 @@ from dodal.log import LOGGER
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
async def get_next_jpeg(response: ClientResponse) -> bytes:
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
jpeg_start_byte = b"\xff\xd8"
|
|
25
|
+
jpeg_stop_byte = b"\xff\xd9"
|
|
26
26
|
while True:
|
|
27
27
|
line = await response.content.readline()
|
|
28
|
-
if line.startswith(
|
|
29
|
-
return line + await response.content.readuntil(
|
|
28
|
+
if line.startswith(jpeg_start_byte):
|
|
29
|
+
return line + await response.content.readuntil(jpeg_stop_byte)
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class Source(IntEnum):
|
|
@@ -25,7 +25,7 @@ from dodal.log import LOGGER
|
|
|
25
25
|
Tip = Array1D[np.int32]
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class
|
|
28
|
+
class InvalidPinError(Exception):
|
|
29
29
|
pass
|
|
30
30
|
|
|
31
31
|
|
|
@@ -88,7 +88,7 @@ class PinTipDetection(StandardReadable):
|
|
|
88
88
|
|
|
89
89
|
def _set_triggered_values(self, results: SampleLocation):
|
|
90
90
|
if results.tip_x is None or results.tip_y is None:
|
|
91
|
-
raise
|
|
91
|
+
raise InvalidPinError
|
|
92
92
|
else:
|
|
93
93
|
tip = np.array([results.tip_x, results.tip_y])
|
|
94
94
|
self._tip_setter(tip)
|
|
@@ -128,7 +128,7 @@ class PinTipDetection(StandardReadable):
|
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
start_time = time.time()
|
|
131
|
-
location = sample_detection.
|
|
131
|
+
location = sample_detection.process_array(array_data)
|
|
132
132
|
end_time = time.time()
|
|
133
133
|
LOGGER.debug(
|
|
134
134
|
f"Sample location detection took {(end_time - start_time) * 1000.0}ms"
|
|
@@ -140,7 +140,7 @@ class MxSampleDetect:
|
|
|
140
140
|
|
|
141
141
|
self.min_tip_height = min_tip_height
|
|
142
142
|
|
|
143
|
-
def
|
|
143
|
+
def process_array(self, arr: np.ndarray) -> SampleLocation:
|
|
144
144
|
# Get a greyscale version of the input.
|
|
145
145
|
if arr.ndim == 3:
|
|
146
146
|
gray_arr = cv2.cvtColor(arr, cv2.COLOR_BGR2GRAY)
|
|
@@ -16,32 +16,32 @@ def draw_crosshair(image: Image.Image, beam_x: int, beam_y: int):
|
|
|
16
16
|
beam_y: The y-coordinate of the crosshair (pixels)
|
|
17
17
|
"""
|
|
18
18
|
draw = ImageDraw.Draw(image)
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
outline_width = 1
|
|
20
|
+
half_len = CROSSHAIR_LENGTH_PX / 2
|
|
21
21
|
draw.rectangle(
|
|
22
22
|
[
|
|
23
|
-
beam_x -
|
|
24
|
-
beam_y -
|
|
25
|
-
beam_x +
|
|
26
|
-
beam_y +
|
|
23
|
+
beam_x - outline_width,
|
|
24
|
+
beam_y - half_len - outline_width,
|
|
25
|
+
beam_x + outline_width,
|
|
26
|
+
beam_y + half_len + outline_width,
|
|
27
27
|
],
|
|
28
28
|
fill=CROSSHAIR_OUTLINE_COLOUR,
|
|
29
29
|
)
|
|
30
30
|
draw.rectangle(
|
|
31
31
|
[
|
|
32
|
-
beam_x -
|
|
33
|
-
beam_y -
|
|
34
|
-
beam_x +
|
|
35
|
-
beam_y +
|
|
32
|
+
beam_x - half_len - outline_width,
|
|
33
|
+
beam_y - outline_width,
|
|
34
|
+
beam_x + half_len + outline_width,
|
|
35
|
+
beam_y + outline_width,
|
|
36
36
|
],
|
|
37
37
|
fill=CROSSHAIR_OUTLINE_COLOUR,
|
|
38
38
|
)
|
|
39
39
|
draw.line(
|
|
40
|
-
((beam_x, beam_y -
|
|
40
|
+
((beam_x, beam_y - half_len), (beam_x, beam_y + half_len)),
|
|
41
41
|
fill=CROSSHAIR_FILL_COLOUR,
|
|
42
42
|
)
|
|
43
43
|
draw.line(
|
|
44
|
-
((beam_x -
|
|
44
|
+
((beam_x - half_len, beam_y), (beam_x + half_len, beam_y)),
|
|
45
45
|
fill=CROSSHAIR_FILL_COLOUR,
|
|
46
46
|
)
|
|
47
47
|
|
|
@@ -3,7 +3,7 @@ from os.path import join as path_join
|
|
|
3
3
|
from ophyd_async.core import soft_signal_rw
|
|
4
4
|
from PIL.Image import Image
|
|
5
5
|
|
|
6
|
-
from dodal.devices.areadetector.plugins.
|
|
6
|
+
from dodal.devices.areadetector.plugins.mjpg import IMG_FORMAT, MJPG, asyncio_save_image
|
|
7
7
|
from dodal.devices.oav.snapshots.grid_overlay import (
|
|
8
8
|
add_grid_border_overlay_to_image,
|
|
9
9
|
add_grid_overlay_to_image,
|
dodal/devices/oav/utils.py
CHANGED
|
@@ -16,7 +16,7 @@ from dodal.devices.smargon import Smargon
|
|
|
16
16
|
Pixel = tuple[int, int]
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
class
|
|
19
|
+
class PinNotFoundError(Exception):
|
|
20
20
|
pass
|
|
21
21
|
|
|
22
22
|
|
|
@@ -106,6 +106,6 @@ def wait_for_tip_to_be_found(
|
|
|
106
106
|
found_tip = yield from bps.rd(ophyd_pin_tip_detection.triggered_tip)
|
|
107
107
|
if all(found_tip == ophyd_pin_tip_detection.INVALID_POSITION):
|
|
108
108
|
timeout = yield from bps.rd(ophyd_pin_tip_detection.validity_timeout)
|
|
109
|
-
raise
|
|
109
|
+
raise PinNotFoundError(f"No pin found after {timeout} seconds")
|
|
110
110
|
|
|
111
111
|
return Pixel((int(found_tip[0]), int(found_tip[1])))
|
dodal/devices/pgm.py
CHANGED
|
@@ -16,7 +16,7 @@ class PGM(StandardReadable):
|
|
|
16
16
|
self,
|
|
17
17
|
prefix: str,
|
|
18
18
|
grating: type[StrictEnum],
|
|
19
|
-
|
|
19
|
+
grating_pv: str = "GRATINGSELECT:SELECT",
|
|
20
20
|
name: str = "",
|
|
21
21
|
) -> None:
|
|
22
22
|
"""
|
|
@@ -26,7 +26,7 @@ class PGM(StandardReadable):
|
|
|
26
26
|
Beamline specific part of the PV
|
|
27
27
|
grating:
|
|
28
28
|
The Enum for the grating table.
|
|
29
|
-
|
|
29
|
+
grating_pv:
|
|
30
30
|
The suffix pv part of grating Pv
|
|
31
31
|
name:
|
|
32
32
|
Name of the device
|
|
@@ -34,7 +34,7 @@ class PGM(StandardReadable):
|
|
|
34
34
|
with self.add_children_as_readables():
|
|
35
35
|
self.energy = Motor(prefix + "ENERGY")
|
|
36
36
|
with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL):
|
|
37
|
-
self.grating = epics_signal_rw(grating, prefix +
|
|
37
|
+
self.grating = epics_signal_rw(grating, prefix + grating_pv)
|
|
38
38
|
self.cff = epics_signal_rw(float, prefix + "CFF")
|
|
39
39
|
|
|
40
40
|
super().__init__(name=name)
|
dodal/devices/robot.py
CHANGED
|
@@ -25,7 +25,7 @@ WAIT_FOR_OLD_PIN_MSG = "Waiting on old pin unloaded"
|
|
|
25
25
|
WAIT_FOR_NEW_PIN_MSG = "Waiting on new pin loaded"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class
|
|
28
|
+
class RobotLoadError(Exception):
|
|
29
29
|
error_code: int
|
|
30
30
|
error_string: str
|
|
31
31
|
|
|
@@ -58,7 +58,7 @@ class ErrorStatus(Device):
|
|
|
58
58
|
error_code = await self.code.get_value()
|
|
59
59
|
if error_code:
|
|
60
60
|
error_string = await self.str.get_value()
|
|
61
|
-
raise
|
|
61
|
+
raise RobotLoadError(int(error_code), error_string) from raise_from
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
class BartRobot(StandardReadable, Movable[SampleLocation]):
|
|
@@ -119,12 +119,12 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
|
|
|
119
119
|
async def pin_mounted_or_no_pin_found(self):
|
|
120
120
|
"""This co-routine will finish when either a pin is detected or the robot gives
|
|
121
121
|
an error saying no pin was found (whichever happens first). In the case where no
|
|
122
|
-
pin was found a
|
|
122
|
+
pin was found a RobotLoadError error is raised.
|
|
123
123
|
"""
|
|
124
124
|
|
|
125
125
|
async def raise_if_no_pin():
|
|
126
126
|
await wait_for_value(self.prog_error.code, self.NO_PIN_ERROR_CODE, None)
|
|
127
|
-
raise
|
|
127
|
+
raise RobotLoadError(self.NO_PIN_ERROR_CODE, "Pin was not detected")
|
|
128
128
|
|
|
129
129
|
async def wfv():
|
|
130
130
|
await wait_for_value(self.gonio_pin_sensor, PinMounted.PIN_MOUNTED, None)
|
|
@@ -184,4 +184,4 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
|
|
|
184
184
|
except TimeoutError as e:
|
|
185
185
|
await self.prog_error.raise_if_error(e)
|
|
186
186
|
await self.controller_error.raise_if_error(e)
|
|
187
|
-
raise
|
|
187
|
+
raise RobotLoadError(0, "Robot timed out") from e
|
dodal/devices/tetramm.py
CHANGED
|
@@ -24,7 +24,9 @@ from ophyd_async.epics.adcore import (
|
|
|
24
24
|
NDFileHDFIO,
|
|
25
25
|
NDPluginBaseIO,
|
|
26
26
|
)
|
|
27
|
-
from ophyd_async.epics.core import PvSuffix, epics_signal_r
|
|
27
|
+
from ophyd_async.epics.core import PvSuffix, epics_signal_r, stop_busy_record
|
|
28
|
+
|
|
29
|
+
from dodal.log import LOGGER
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
class TetrammRange(StrictEnum):
|
|
@@ -110,6 +112,7 @@ class TetrammController(DetectorController):
|
|
|
110
112
|
current_trig_status = await self.driver.trigger_mode.get_value()
|
|
111
113
|
|
|
112
114
|
if current_trig_status == TetrammTrigger.FREE_RUN: # if freerun turn off first
|
|
115
|
+
LOGGER.info("Disarming TetrAMM from free run")
|
|
113
116
|
await self.disarm()
|
|
114
117
|
|
|
115
118
|
# trigger mode must be set first and on its own!
|
|
@@ -144,13 +147,14 @@ class TetrammController(DetectorController):
|
|
|
144
147
|
await wait_for_value(self._file_io.acquire, False, timeout=None)
|
|
145
148
|
|
|
146
149
|
async def unstage(self):
|
|
147
|
-
|
|
148
|
-
await self._file_io.acquire.set(False)
|
|
150
|
+
LOGGER.info("Unstaging TetrAMM")
|
|
151
|
+
await self._file_io.acquire.set(False, wait=False)
|
|
149
152
|
|
|
150
153
|
async def disarm(self):
|
|
151
154
|
# We can't use caput callback as we already used it in arm() and we can't have
|
|
152
|
-
# 2 or they will deadlock
|
|
153
|
-
|
|
155
|
+
# 2 or they will deadlock. Therefore must use stop_busy_record
|
|
156
|
+
LOGGER.info("Disarming TetrAMM")
|
|
157
|
+
await stop_busy_record(self.driver.acquire, False, timeout=DEFAULT_TIMEOUT)
|
|
154
158
|
|
|
155
159
|
async def set_exposure(self, exposure: float) -> None:
|
|
156
160
|
"""Set the exposure time and acquire period.
|
dodal/devices/thawer.py
CHANGED
|
@@ -14,10 +14,6 @@ from ophyd_async.epics.core import epics_signal_rw
|
|
|
14
14
|
from dodal.log import LOGGER
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class ThawingException(Exception):
|
|
18
|
-
pass
|
|
19
|
-
|
|
20
|
-
|
|
21
17
|
class ThawingTimer(Device, Stoppable, Movable[float]):
|
|
22
18
|
def __init__(self, control_signal: SignalRW[OnOff]) -> None:
|
|
23
19
|
self._control_signal_ref = Reference(control_signal)
|
dodal/devices/v2f.py
CHANGED
|
@@ -29,10 +29,10 @@ class QDV2F(StandardReadable):
|
|
|
29
29
|
self,
|
|
30
30
|
prefix: str,
|
|
31
31
|
name: str = "",
|
|
32
|
-
|
|
32
|
+
intensity_suffix="I",
|
|
33
33
|
) -> None:
|
|
34
34
|
with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
|
|
35
|
-
self.intensity = epics_signal_r(float, f"{prefix}{
|
|
35
|
+
self.intensity = epics_signal_r(float, f"{prefix}{intensity_suffix}")
|
|
36
36
|
with self.add_children_as_readables():
|
|
37
37
|
self.gain = epics_signal_rw(V2FGain, f"{prefix}GAIN")
|
|
38
38
|
|
|
@@ -13,7 +13,7 @@ class ZebraMappingValidations(BaseModel):
|
|
|
13
13
|
value = object.__getattribute__(self, name)
|
|
14
14
|
if not name.startswith("__"):
|
|
15
15
|
if value == -1:
|
|
16
|
-
raise
|
|
16
|
+
raise UnmappedZebraError(
|
|
17
17
|
f"'{type(self).__name__}.{name}' was accessed but is set to -1. Please check the zebra mappings against the zebra's physical configuration"
|
|
18
18
|
)
|
|
19
19
|
return value
|
|
@@ -92,5 +92,5 @@ class ZebraMapping(ZebraMappingValidations):
|
|
|
92
92
|
AND_GATE_FOR_AUTO_SHUTTER: int = Field(default=2, ge=-1, le=4)
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
class
|
|
95
|
+
class UnmappedZebraError(Exception):
|
|
96
96
|
pass
|
dodal/devices/zocalo/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from dodal.devices.zocalo.zocalo_interaction import ZocaloStartInfo, ZocaloTrigger
|
|
2
2
|
from dodal.devices.zocalo.zocalo_results import (
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
NoResultsFromZocaloError,
|
|
4
|
+
NoZocaloSubscriptionError,
|
|
5
5
|
XrcResult,
|
|
6
6
|
ZocaloResults,
|
|
7
7
|
ZocaloSource,
|
|
@@ -13,8 +13,8 @@ __all__ = [
|
|
|
13
13
|
"XrcResult",
|
|
14
14
|
"ZocaloTrigger",
|
|
15
15
|
"get_full_processing_results",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
16
|
+
"NoResultsFromZocaloError",
|
|
17
|
+
"NoZocaloSubscriptionError",
|
|
18
18
|
"ZocaloStartInfo",
|
|
19
19
|
"ZocaloSource",
|
|
20
20
|
]
|
|
@@ -24,11 +24,11 @@ from dodal.devices.zocalo.zocalo_interaction import _get_zocalo_connection
|
|
|
24
24
|
from dodal.log import LOGGER
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class
|
|
27
|
+
class NoResultsFromZocaloError(Exception):
|
|
28
28
|
pass
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
class
|
|
31
|
+
class NoZocaloSubscriptionError(Exception):
|
|
32
32
|
pass
|
|
33
33
|
|
|
34
34
|
|
|
@@ -234,7 +234,7 @@ class ZocaloResults(StandardReadable, Triggerable):
|
|
|
234
234
|
"meant for it"
|
|
235
235
|
)
|
|
236
236
|
if not self.transport:
|
|
237
|
-
raise
|
|
237
|
+
raise NoZocaloSubscriptionError(msg)
|
|
238
238
|
|
|
239
239
|
try:
|
|
240
240
|
LOGGER.info(
|
|
@@ -266,7 +266,7 @@ class ZocaloResults(StandardReadable, Triggerable):
|
|
|
266
266
|
)
|
|
267
267
|
except Empty as timeout_exception:
|
|
268
268
|
LOGGER.warning("Timed out waiting for zocalo results!")
|
|
269
|
-
raise
|
|
269
|
+
raise NoResultsFromZocaloError(
|
|
270
270
|
"Timed out waiting for Zocalo results"
|
|
271
271
|
) from timeout_exception
|
|
272
272
|
finally:
|