dls-dodal 1.58.0__py3-none-any.whl → 1.60.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.58.0.dist-info → dls_dodal-1.60.0.dist-info}/METADATA +3 -3
- {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/RECORD +71 -47
- dodal/_version.py +2 -2
- dodal/beamlines/__init__.py +1 -0
- dodal/beamlines/b07.py +10 -5
- dodal/beamlines/b07_1.py +10 -5
- dodal/beamlines/b21.py +22 -0
- dodal/beamlines/i02_1.py +80 -0
- dodal/beamlines/i03.py +5 -3
- dodal/beamlines/i04.py +5 -3
- dodal/beamlines/i09.py +10 -9
- dodal/beamlines/i09_1.py +10 -5
- dodal/beamlines/i10-1.py +25 -0
- dodal/beamlines/i10.py +17 -1
- dodal/beamlines/i11.py +0 -17
- dodal/beamlines/i15.py +242 -0
- dodal/beamlines/i15_1.py +156 -0
- dodal/beamlines/i19_1.py +3 -1
- dodal/beamlines/i19_2.py +12 -1
- dodal/beamlines/i21.py +27 -0
- dodal/beamlines/i22.py +12 -2
- dodal/beamlines/i24.py +32 -3
- dodal/beamlines/k07.py +31 -0
- dodal/beamlines/p60.py +10 -9
- dodal/common/watcher_utils.py +1 -1
- dodal/devices/apple2_undulator.py +18 -142
- dodal/devices/attenuator/attenuator.py +48 -2
- dodal/devices/attenuator/filter.py +3 -0
- dodal/devices/attenuator/filter_selections.py +26 -0
- dodal/devices/eiger.py +2 -1
- dodal/devices/electron_analyser/__init__.py +4 -0
- dodal/devices/electron_analyser/abstract/base_driver_io.py +30 -18
- dodal/devices/electron_analyser/energy_sources.py +101 -0
- dodal/devices/electron_analyser/specs/detector.py +6 -6
- dodal/devices/electron_analyser/specs/driver_io.py +7 -15
- dodal/devices/electron_analyser/vgscienta/detector.py +6 -6
- dodal/devices/electron_analyser/vgscienta/driver_io.py +7 -14
- dodal/devices/fast_grid_scan.py +130 -64
- dodal/devices/focusing_mirror.py +30 -0
- dodal/devices/i02_1/__init__.py +0 -0
- dodal/devices/i02_1/fast_grid_scan.py +61 -0
- dodal/devices/i02_1/sample_motors.py +19 -0
- dodal/devices/i04/murko_results.py +69 -23
- dodal/devices/i10/i10_apple2.py +282 -140
- dodal/devices/i15/dcm.py +77 -0
- dodal/devices/i15/focussing_mirror.py +71 -0
- dodal/devices/i15/jack.py +39 -0
- dodal/devices/i15/laue.py +18 -0
- dodal/devices/i15/motors.py +27 -0
- dodal/devices/i15/multilayer_mirror.py +25 -0
- dodal/devices/i15/rail.py +17 -0
- dodal/devices/i21/__init__.py +3 -0
- dodal/devices/i21/enums.py +8 -0
- dodal/devices/i22/nxsas.py +2 -0
- dodal/devices/i24/commissioning_jungfrau.py +114 -0
- dodal/devices/motors.py +52 -1
- dodal/devices/slits.py +18 -0
- dodal/devices/smargon.py +0 -56
- dodal/devices/temperture_controller/__init__.py +3 -0
- dodal/devices/temperture_controller/lakeshore/__init__.py +0 -0
- dodal/devices/temperture_controller/lakeshore/lakeshore.py +204 -0
- dodal/devices/temperture_controller/lakeshore/lakeshore_io.py +112 -0
- dodal/devices/tetramm.py +38 -16
- dodal/devices/v2f.py +39 -0
- dodal/devices/zebra/zebra.py +1 -0
- dodal/devices/zebra/zebra_constants_mapping.py +1 -1
- dodal/parameters/experiment_parameter_base.py +1 -5
- {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/WHEEL +0 -0
- {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/entry_points.txt +0 -0
- {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/licenses/LICENSE +0 -0
- {dls_dodal-1.58.0.dist-info → dls_dodal-1.60.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from ophyd_async.core import SignalR, derived_signal_r, soft_signal_r_and_setter
|
|
2
|
+
from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
3
|
+
|
|
4
|
+
from dodal.devices.fast_grid_scan import (
|
|
5
|
+
FastGridScanCommon,
|
|
6
|
+
GridScanParamsCommon,
|
|
7
|
+
MotionProgram,
|
|
8
|
+
WithDwellTime,
|
|
9
|
+
)
|
|
10
|
+
from dodal.log import LOGGER
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ZebraGridScanParamsTwoD(GridScanParamsCommon, WithDwellTime):
|
|
14
|
+
"""
|
|
15
|
+
Params for 2D Zebra FGS. Adds on the dwell time, which is really the time
|
|
16
|
+
between trigger positions.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ZebraFastGridScanTwoD(FastGridScanCommon[ZebraGridScanParamsTwoD]):
|
|
21
|
+
"""i02-1's EPICS interface for the 2D FGS differs slightly from the standard 3D
|
|
22
|
+
version:
|
|
23
|
+
- No Z steps, Z step sizes, or Y2 start positions, or Z2 start
|
|
24
|
+
- No scan valid PV - see https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
25
|
+
- No program_number - see https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self, prefix: str, motion_controller_prefix: str, name: str = ""
|
|
30
|
+
) -> None:
|
|
31
|
+
full_prefix = prefix + "FGS:"
|
|
32
|
+
super().__init__(full_prefix, motion_controller_prefix, name)
|
|
33
|
+
|
|
34
|
+
# This signal could be put in the common device if the prefix gets standardised.
|
|
35
|
+
# See https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
36
|
+
self.dwell_time_ms = epics_signal_rw_rbv(float, f"{full_prefix}EXPOSURE_TIME")
|
|
37
|
+
|
|
38
|
+
self.movable_params["dwell_time_ms"] = self.dwell_time_ms
|
|
39
|
+
|
|
40
|
+
def _create_expected_images_signal(self):
|
|
41
|
+
return derived_signal_r(
|
|
42
|
+
self._calculate_expected_images,
|
|
43
|
+
x=self.x_steps,
|
|
44
|
+
y=self.y_steps,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def _calculate_expected_images(self, x: int, y: int) -> int:
|
|
48
|
+
LOGGER.info(f"Reading num of images found {x, y} images in each axis")
|
|
49
|
+
return x * y
|
|
50
|
+
|
|
51
|
+
# VMXm triggers the grid scan through GDA, which has its own validity check
|
|
52
|
+
# so whilst this PV is being added, it isn't essential
|
|
53
|
+
def _create_scan_invalid_signal(self, prefix: str) -> SignalR[float]:
|
|
54
|
+
return soft_signal_r_and_setter(float, 0)[0]
|
|
55
|
+
|
|
56
|
+
def _create_motion_program(self, motion_controller_prefix):
|
|
57
|
+
return MotionProgram(motion_controller_prefix, has_prog_num=False)
|
|
58
|
+
|
|
59
|
+
# To be standardised in https://github.com/DiamondLightSource/mx-bluesky/issues/1203
|
|
60
|
+
def _create_position_counter(self, prefix: str):
|
|
61
|
+
return epics_signal_rw_rbv(int, f"{prefix}POS_COUNTER")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ophyd_async.core import StandardReadable
|
|
2
|
+
from ophyd_async.epics.motor import Motor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SampleMotors(StandardReadable):
|
|
6
|
+
"""Virtual Smaract motors on i02-1 (VMXm)"""
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
prefix: str,
|
|
11
|
+
name: str = "",
|
|
12
|
+
):
|
|
13
|
+
# See https://github.com/DiamondLightSource/mx-bluesky/issues/1212
|
|
14
|
+
# regarding a potential motion issue with omega
|
|
15
|
+
with self.add_children_as_readables():
|
|
16
|
+
self.x = Motor(f"{prefix}X")
|
|
17
|
+
self.z = Motor(f"{prefix}Z")
|
|
18
|
+
self.omega = Motor(f"{prefix}OMEGA")
|
|
19
|
+
super().__init__(name=name)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import pickle
|
|
3
|
+
from dataclasses import dataclass
|
|
3
4
|
from enum import Enum
|
|
4
5
|
from typing import TypedDict
|
|
5
6
|
|
|
@@ -21,9 +22,6 @@ from dodal.log import LOGGER
|
|
|
21
22
|
|
|
22
23
|
NO_MURKO_RESULT = (-1, -1)
|
|
23
24
|
|
|
24
|
-
MurkoResult = dict
|
|
25
|
-
FullMurkoResults = dict[str, list[MurkoResult]]
|
|
26
|
-
|
|
27
25
|
|
|
28
26
|
class MurkoMetadata(TypedDict):
|
|
29
27
|
zoom_percentage: float
|
|
@@ -42,6 +40,19 @@ class Coord(Enum):
|
|
|
42
40
|
z = 2
|
|
43
41
|
|
|
44
42
|
|
|
43
|
+
@dataclass
|
|
44
|
+
class MurkoResult:
|
|
45
|
+
centre_px: tuple
|
|
46
|
+
x_dist_mm: float
|
|
47
|
+
y_dist_mm: float
|
|
48
|
+
omega: float
|
|
49
|
+
uuid: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class NoResultsFound(ValueError):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
45
56
|
class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
46
57
|
"""Device that takes crystal centre values from Murko and uses them to set the
|
|
47
58
|
x, y, z coordinate of the sample to be in line with the beam centre.
|
|
@@ -59,6 +70,8 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
59
70
|
"""
|
|
60
71
|
|
|
61
72
|
TIMEOUT_S = 2
|
|
73
|
+
PERCENTAGE_TO_USE = 25
|
|
74
|
+
NUMBER_OF_WRONG_RESULTS_TO_LOG = 5
|
|
62
75
|
|
|
63
76
|
def __init__(
|
|
64
77
|
self,
|
|
@@ -74,12 +87,10 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
74
87
|
db=redis_db,
|
|
75
88
|
)
|
|
76
89
|
self.pubsub = self.redis_client.pubsub()
|
|
77
|
-
self._last_omega = 0
|
|
78
90
|
self.sample_id = soft_signal_rw(str) # Should get from redis
|
|
79
91
|
self.stop_angle = stop_angle
|
|
80
|
-
|
|
81
|
-
self.
|
|
82
|
-
self.omegas = []
|
|
92
|
+
|
|
93
|
+
self._reset()
|
|
83
94
|
|
|
84
95
|
with self.add_children_as_readables():
|
|
85
96
|
# Diffs from current x/y/z
|
|
@@ -88,6 +99,10 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
88
99
|
self.z_mm, self._z_mm_setter = soft_signal_r_and_setter(float)
|
|
89
100
|
super().__init__(name=name)
|
|
90
101
|
|
|
102
|
+
def _reset(self):
|
|
103
|
+
self._last_omega = 0
|
|
104
|
+
self.results: list[MurkoResult] = []
|
|
105
|
+
|
|
91
106
|
@AsyncStatus.wrap
|
|
92
107
|
async def stage(self):
|
|
93
108
|
await self.pubsub.subscribe("murko-results")
|
|
@@ -97,6 +112,7 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
97
112
|
|
|
98
113
|
@AsyncStatus.wrap
|
|
99
114
|
async def unstage(self):
|
|
115
|
+
self._reset()
|
|
100
116
|
await self.pubsub.unsubscribe()
|
|
101
117
|
|
|
102
118
|
@AsyncStatus.wrap
|
|
@@ -106,19 +122,26 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
106
122
|
while self._last_omega < self.stop_angle:
|
|
107
123
|
# waits here for next batch to be received
|
|
108
124
|
message = await self.pubsub.get_message(timeout=self.TIMEOUT_S)
|
|
109
|
-
if message is None:
|
|
110
|
-
|
|
125
|
+
if message is None:
|
|
126
|
+
continue
|
|
111
127
|
await self.process_batch(message, sample_id)
|
|
112
128
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
if not self.results:
|
|
130
|
+
raise NoResultsFound("No results retrieved from Murko")
|
|
131
|
+
|
|
132
|
+
for result in self.results:
|
|
133
|
+
LOGGER.debug(result)
|
|
134
|
+
|
|
135
|
+
self.filter_outliers()
|
|
117
136
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
137
|
+
x_dists_mm = [result.x_dist_mm for result in self.results]
|
|
138
|
+
y_dists_mm = [result.y_dist_mm for result in self.results]
|
|
139
|
+
omegas = [result.omega for result in self.results]
|
|
140
|
+
|
|
141
|
+
LOGGER.info(f"Using average of x beam distances: {x_dists_mm}")
|
|
142
|
+
avg_x = float(np.mean(x_dists_mm))
|
|
143
|
+
LOGGER.info(f"Finding least square y and z from y distances: {y_dists_mm}")
|
|
144
|
+
best_y, best_z = get_yz_least_squares(y_dists_mm, omegas)
|
|
122
145
|
# x, y, z are relative to beam centre. Need to move negative these values to get centred.
|
|
123
146
|
self._x_mm_setter(-avg_x)
|
|
124
147
|
self._y_mm_setter(-best_y)
|
|
@@ -163,15 +186,38 @@ class MurkoResultsDevice(StandardReadable, Triggerable, Stageable):
|
|
|
163
186
|
centre_px[0],
|
|
164
187
|
centre_px[1],
|
|
165
188
|
)
|
|
166
|
-
self.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
self.results.append(
|
|
190
|
+
MurkoResult(
|
|
191
|
+
centre_px=centre_px,
|
|
192
|
+
x_dist_mm=beam_dist_px[0] * metadata["microns_per_x_pixel"] / 1000,
|
|
193
|
+
y_dist_mm=beam_dist_px[1] * metadata["microns_per_y_pixel"] / 1000,
|
|
194
|
+
omega=omega,
|
|
195
|
+
uuid=metadata["uuid"],
|
|
196
|
+
)
|
|
171
197
|
)
|
|
172
|
-
self.omegas.append(omega)
|
|
173
198
|
self._last_omega = omega
|
|
174
199
|
|
|
200
|
+
def filter_outliers(self):
|
|
201
|
+
"""Whilst murko is not fully trained it often gives us poor results.
|
|
202
|
+
When it is wrong it usually picks up the base of the pin, rather than the tip,
|
|
203
|
+
meaning that by keeping only a percentage of the results with the smallest X we
|
|
204
|
+
remove many of the outliers.
|
|
205
|
+
"""
|
|
206
|
+
LOGGER.info(f"Number of results before filtering: {len(self.results)}")
|
|
207
|
+
sorted_results = sorted(self.results, key=lambda item: item.centre_px[0])
|
|
208
|
+
|
|
209
|
+
worst_results = [
|
|
210
|
+
r.uuid for r in sorted_results[-self.NUMBER_OF_WRONG_RESULTS_TO_LOG :]
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
LOGGER.info(
|
|
214
|
+
f"Worst {self.NUMBER_OF_WRONG_RESULTS_TO_LOG} murko results were {worst_results}"
|
|
215
|
+
)
|
|
216
|
+
cutoff = max(1, int(len(sorted_results) * self.PERCENTAGE_TO_USE / 100))
|
|
217
|
+
smallest_x = sorted_results[:cutoff]
|
|
218
|
+
self.results = smallest_x
|
|
219
|
+
LOGGER.info(f"Number of results after filtering: {len(self.results)}")
|
|
220
|
+
|
|
175
221
|
|
|
176
222
|
def get_yz_least_squares(vertical_dists: list, omegas: list) -> tuple[float, float]:
|
|
177
223
|
"""Get the least squares solution for y and z from the vertical distances and omega angles.
|