dls-dodal 1.62.0__py3-none-any.whl → 1.64.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.62.0.dist-info → dls_dodal-1.64.0.dist-info}/METADATA +3 -3
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.64.0.dist-info}/RECORD +89 -76
- dls_dodal-1.64.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/i07.py +21 -0
- dodal/beamlines/i09.py +11 -4
- dodal/beamlines/i09_1.py +10 -4
- dodal/beamlines/i09_2.py +30 -0
- dodal/beamlines/i10.py +7 -69
- dodal/beamlines/i10_1.py +35 -0
- dodal/beamlines/i10_optics.py +231 -0
- dodal/beamlines/i15_1.py +5 -5
- dodal/beamlines/i17.py +60 -1
- dodal/beamlines/i18.py +15 -9
- dodal/beamlines/i19_1.py +3 -3
- dodal/beamlines/i19_2.py +2 -2
- dodal/beamlines/i19_optics.py +4 -1
- dodal/beamlines/i21.py +31 -1
- dodal/beamlines/i24.py +3 -3
- dodal/cli.py +7 -7
- dodal/common/visit.py +4 -4
- dodal/devices/aperturescatterguard.py +6 -4
- dodal/devices/apple2_undulator.py +225 -126
- dodal/devices/attenuator/filter_selections.py +6 -6
- dodal/devices/b07_1/ccmc.py +1 -1
- dodal/devices/common_dcm.py +63 -16
- dodal/devices/current_amplifiers/femto.py +4 -4
- dodal/devices/current_amplifiers/sr570.py +3 -3
- dodal/devices/fast_grid_scan.py +4 -4
- dodal/devices/fast_shutter.py +19 -7
- dodal/devices/i02_2/__init__.py +0 -0
- dodal/devices/i03/dcm.py +4 -2
- dodal/devices/i03/undulator_dcm.py +1 -1
- dodal/devices/i04/murko_results.py +35 -14
- dodal/devices/i07/__init__.py +0 -0
- dodal/devices/i07/dcm.py +33 -0
- dodal/devices/i09/__init__.py +1 -2
- dodal/devices/i09_1_shared/__init__.py +3 -0
- dodal/devices/i09_1_shared/hard_undulator_functions.py +111 -0
- 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 +7 -46
- 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/i22/dcm.py +3 -3
- 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/util/lookup_tables.py +8 -2
- 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 +2 -2
- dodal/plans/save_panda.py +7 -7
- dodal/plans/verify_undulator_gap.py +4 -4
- dodal/testing/fixtures/__init__.py +0 -0
- dodal/testing/fixtures/run_engine.py +46 -0
- dodal/testing/fixtures/utils.py +57 -0
- dls_dodal-1.62.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.62.0.dist-info → dls_dodal-1.64.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.64.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.62.0.dist-info → dls_dodal-1.64.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
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
|
|
|
@@ -107,7 +107,7 @@ class DCM(BaseDCM[RollCrystal, PitchAndRollCrystal]):
|
|
|
107
107
|
|
|
108
108
|
async def read(self) -> dict[str, Reading]:
|
|
109
109
|
default_reading = await super().read()
|
|
110
|
-
energy: float = default_reading[f"{self.name}-
|
|
110
|
+
energy: float = default_reading[f"{self.name}-energy_in_keV"]["value"]
|
|
111
111
|
if energy > 0.0:
|
|
112
112
|
wavelength = _CONVERSION_CONSTANT / energy
|
|
113
113
|
else:
|
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)
|
|
@@ -13,13 +13,19 @@ from numpy import interp, loadtxt
|
|
|
13
13
|
from dodal.log import LOGGER
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
async def energy_distance_table(
|
|
16
|
+
async def energy_distance_table(
|
|
17
|
+
lookup_table_path: str,
|
|
18
|
+
comments: Sequence[str] = ["#", "Units"],
|
|
19
|
+
skiprows: int = 0,
|
|
20
|
+
) -> np.ndarray:
|
|
17
21
|
"""
|
|
18
22
|
Returns a numpy formatted lookup table for required positions of an ID gap to
|
|
19
23
|
provide emission at a given beam energy.
|
|
20
24
|
|
|
21
25
|
Args:
|
|
22
26
|
lookup_table_path: Path to lookup table
|
|
27
|
+
comments: Lines starting with any of these strings will be ignored
|
|
28
|
+
skiprows: Number of rows to skip at the start of the file
|
|
23
29
|
|
|
24
30
|
Returns:
|
|
25
31
|
ndarray: Lookup table
|
|
@@ -29,7 +35,7 @@ async def energy_distance_table(lookup_table_path: str) -> np.ndarray:
|
|
|
29
35
|
# decodes the text
|
|
30
36
|
async with aiofiles.open(lookup_table_path) as stream:
|
|
31
37
|
raw_table = await stream.read()
|
|
32
|
-
return loadtxt(StringIO(raw_table), comments=
|
|
38
|
+
return loadtxt(StringIO(raw_table), comments=comments, skiprows=skiprows)
|
|
33
39
|
|
|
34
40
|
|
|
35
41
|
def parse_lookup_table(filename: str) -> list[Sequence]:
|
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:
|
dodal/log.py
CHANGED
|
@@ -57,22 +57,22 @@ class CircularMemoryHandler(logging.Handler):
|
|
|
57
57
|
"""Loosely based on the MemoryHandler, which keeps a buffer and writes it when full
|
|
58
58
|
or when there is a record of specific level. This instead keeps a circular buffer
|
|
59
59
|
that always contains the last {capacity} number of messages, this is only flushed
|
|
60
|
-
when a log of specific {
|
|
60
|
+
when a log of specific {flush_level} comes in. On flush this buffer is then passed to
|
|
61
61
|
the {target} handler.
|
|
62
62
|
|
|
63
63
|
The CircularMemoryHandler becomes the owner of the target handler which will be closed
|
|
64
64
|
on close of this handler.
|
|
65
65
|
"""
|
|
66
66
|
|
|
67
|
-
def __init__(self, capacity,
|
|
67
|
+
def __init__(self, capacity, flush_level=logging.ERROR, target=None):
|
|
68
68
|
logging.Handler.__init__(self)
|
|
69
69
|
self.buffer: deque[logging.LogRecord] = deque(maxlen=capacity)
|
|
70
|
-
self.
|
|
70
|
+
self.flush_level = flush_level
|
|
71
71
|
self.target = target
|
|
72
72
|
|
|
73
73
|
def emit(self, record):
|
|
74
74
|
self.buffer.append(record)
|
|
75
|
-
if record.levelno >= self.
|
|
75
|
+
if record.levelno >= self.flush_level:
|
|
76
76
|
self.flush()
|
|
77
77
|
|
|
78
78
|
def flush(self):
|
|
@@ -149,7 +149,7 @@ def set_up_graylog_handler(logger: Logger, host: str, port: int):
|
|
|
149
149
|
return graylog_handler
|
|
150
150
|
|
|
151
151
|
|
|
152
|
-
def
|
|
152
|
+
def set_up_info_file_handler(logger, path: Path, filename: str):
|
|
153
153
|
"""Set up a file handler for the logger, at INFO level, which will keep 30 days
|
|
154
154
|
of logs, rotating once per day. Creates the directory if necessary."""
|
|
155
155
|
print(f"Logging to INFO file handler {path / filename}")
|
|
@@ -162,7 +162,7 @@ def set_up_INFO_file_handler(logger, path: Path, filename: str):
|
|
|
162
162
|
return file_handler
|
|
163
163
|
|
|
164
164
|
|
|
165
|
-
def
|
|
165
|
+
def set_up_debug_memory_handler(
|
|
166
166
|
logger: Logger, path: Path, filename: str, capacity: int
|
|
167
167
|
):
|
|
168
168
|
"""Set up a Memory handler which holds 200k lines, and writes them to an hourly
|
|
@@ -178,7 +178,7 @@ def set_up_DEBUG_memory_handler(
|
|
|
178
178
|
file_handler.setFormatter(DEFAULT_FORMATTER)
|
|
179
179
|
memory_handler = CircularMemoryHandler(
|
|
180
180
|
capacity=capacity,
|
|
181
|
-
|
|
181
|
+
flush_level=logging.ERROR,
|
|
182
182
|
target=file_handler,
|
|
183
183
|
)
|
|
184
184
|
memory_handler.setLevel(logging.DEBUG)
|
|
@@ -223,8 +223,8 @@ def set_up_all_logging_handlers(
|
|
|
223
223
|
"graylog_handler": set_up_graylog_handler(
|
|
224
224
|
logger, *get_graylog_configuration(dev_mode, graylog_port)
|
|
225
225
|
),
|
|
226
|
-
"info_file_handler":
|
|
227
|
-
"debug_memory_handler":
|
|
226
|
+
"info_file_handler": set_up_info_file_handler(logger, logging_path, filename),
|
|
227
|
+
"debug_memory_handler": set_up_debug_memory_handler(
|
|
228
228
|
logger, debug_logging_path or logging_path, filename, error_log_buffer_lines
|
|
229
229
|
),
|
|
230
230
|
}
|
dodal/plan_stubs/motor_utils.py
CHANGED
|
@@ -9,7 +9,7 @@ from ophyd_async.core import Device
|
|
|
9
9
|
from ophyd_async.epics.motor import Motor
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class MoveTooLargeError(Exception):
|
|
13
13
|
def __init__(
|
|
14
14
|
self,
|
|
15
15
|
axis: Motor,
|
|
@@ -29,14 +29,14 @@ def check_and_cache_values(
|
|
|
29
29
|
maximum_move: float,
|
|
30
30
|
) -> Generator[Msg, Any, dict[Motor, float]]:
|
|
31
31
|
"""Caches the positions of all Motors on specified device if they are within
|
|
32
|
-
smallest_move of home_position. Throws
|
|
32
|
+
smallest_move of home_position. Throws MoveTooLargeError if they are outside maximum_move
|
|
33
33
|
of the home_position
|
|
34
34
|
"""
|
|
35
35
|
positions = {}
|
|
36
36
|
for axis, new_position in devices_and_positions.items():
|
|
37
37
|
position = yield from bps.rd(axis)
|
|
38
38
|
if abs(position - new_position) > maximum_move:
|
|
39
|
-
raise
|
|
39
|
+
raise MoveTooLargeError(axis, maximum_move, position)
|
|
40
40
|
if abs(position - new_position) > smallest_move:
|
|
41
41
|
positions[axis] = position
|
|
42
42
|
return positions
|
|
@@ -68,7 +68,7 @@ def move_and_reset_wrapper(
|
|
|
68
68
|
) -> MsgGenerator:
|
|
69
69
|
"""Wrapper that does the following:
|
|
70
70
|
1. Caches the positions of all Motors on device
|
|
71
|
-
2. Throws a
|
|
71
|
+
2. Throws a MoveTooLargeError exception if any positions are maximum_move away from home_position
|
|
72
72
|
2. Moves any motor that is more than smallest_move away from the home_position to home_position
|
|
73
73
|
3. Runs the specified plan
|
|
74
74
|
4. Moves all motors back to their cached positions
|
|
@@ -155,7 +155,7 @@ def set_mx_settings_pvs(
|
|
|
155
155
|
|
|
156
156
|
|
|
157
157
|
if __name__ == "__main__":
|
|
158
|
-
|
|
158
|
+
run_engine = RunEngine()
|
|
159
159
|
do_default_logging_setup()
|
|
160
160
|
|
|
161
161
|
path_provider = StaticPathProvider(
|
|
@@ -166,7 +166,7 @@ if __name__ == "__main__":
|
|
|
166
166
|
set_path_provider(path_provider)
|
|
167
167
|
|
|
168
168
|
eiger = fastcs_eiger(connect_immediately=True)
|
|
169
|
-
|
|
169
|
+
run_engine(
|
|
170
170
|
configure_arm_trigger_and_disarm_detector(
|
|
171
171
|
eiger=eiger,
|
|
172
172
|
detector_params=DetectorParams(
|
dodal/plans/save_panda.py
CHANGED
|
@@ -15,7 +15,7 @@ from dodal.beamlines import module_name_for_beamline
|
|
|
15
15
|
from dodal.utils import make_device
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def main(argv: list[str]):
|
|
18
|
+
def main(argv: list[str] | None = None):
|
|
19
19
|
"""CLI Utility to save the panda configuration."""
|
|
20
20
|
parser = ArgumentParser(description="Save an ophyd_async panda to yaml")
|
|
21
21
|
parser.add_argument(
|
|
@@ -39,7 +39,7 @@ def main(argv: list[str]):
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
# this exit()s with message/help unless args parsed successfully
|
|
42
|
-
args = parser.parse_args(argv
|
|
42
|
+
args = parser.parse_args(argv)
|
|
43
43
|
|
|
44
44
|
beamline = args.beamline
|
|
45
45
|
device_name = args.device_name
|
|
@@ -71,7 +71,7 @@ def main(argv: list[str]):
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def _save_panda(beamline, device_name, output_directory, file_name):
|
|
74
|
-
|
|
74
|
+
run_engine = RunEngine()
|
|
75
75
|
print("Creating devices...")
|
|
76
76
|
module_name = module_name_for_beamline(beamline)
|
|
77
77
|
try:
|
|
@@ -86,18 +86,18 @@ def _save_panda(beamline, device_name, output_directory, file_name):
|
|
|
86
86
|
print(
|
|
87
87
|
f"Saving to {output_directory}/{file_name} from {device_name} on {beamline}..."
|
|
88
88
|
)
|
|
89
|
-
_save_panda_to_yaml(
|
|
89
|
+
_save_panda_to_yaml(run_engine, cast(Device, panda), file_name, output_directory)
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def _save_panda_to_yaml(
|
|
93
|
-
|
|
93
|
+
run_engine: RunEngine, panda: Device, file_name: str, output_directory: str
|
|
94
94
|
):
|
|
95
95
|
def save_to_file():
|
|
96
96
|
provider = YamlSettingsProvider(output_directory)
|
|
97
97
|
yield from store_settings(provider, file_name, panda)
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
run_engine(save_to_file())
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
if __name__ == "__main__": # pragma: no cover
|
|
103
|
-
sys.exit(main(sys.argv))
|
|
103
|
+
sys.exit(main(sys.argv[1:]))
|
|
@@ -2,18 +2,18 @@ from typing import Protocol, runtime_checkable
|
|
|
2
2
|
|
|
3
3
|
from bluesky import plan_stubs as bps
|
|
4
4
|
|
|
5
|
-
from dodal.devices.common_dcm import
|
|
5
|
+
from dodal.devices.common_dcm import DoubleCrystalMonochromatorBase
|
|
6
6
|
from dodal.devices.undulator import Undulator
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@runtime_checkable
|
|
10
10
|
class CheckUndulatorDevices(Protocol):
|
|
11
11
|
undulator: Undulator
|
|
12
|
-
dcm:
|
|
12
|
+
dcm: DoubleCrystalMonochromatorBase
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def verify_undulator_gap(devices: CheckUndulatorDevices):
|
|
16
16
|
"""Verify Undulator gap is correct - it may not be after a beam dump"""
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
yield from bps.abs_set(devices.undulator,
|
|
18
|
+
energy_in_keV = yield from bps.rd(devices.dcm.energy_in_keV.user_readback) # noqa: N806
|
|
19
|
+
yield from bps.abs_set(devices.undulator, energy_in_keV, wait=True)
|
|
File without changes
|