dls-dodal 1.49.0__py3-none-any.whl → 1.51.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.
Files changed (72) hide show
  1. {dls_dodal-1.49.0.dist-info → dls_dodal-1.51.0.dist-info}/METADATA +3 -4
  2. {dls_dodal-1.49.0.dist-info → dls_dodal-1.51.0.dist-info}/RECORD +65 -60
  3. dodal/_version.py +2 -2
  4. dodal/beamlines/adsim.py +5 -3
  5. dodal/beamlines/b01_1.py +41 -5
  6. dodal/beamlines/b07.py +11 -1
  7. dodal/beamlines/b07_1.py +11 -1
  8. dodal/beamlines/b16.py +8 -4
  9. dodal/beamlines/b21.py +148 -0
  10. dodal/beamlines/i03.py +6 -11
  11. dodal/beamlines/i04.py +5 -5
  12. dodal/beamlines/i09.py +22 -1
  13. dodal/beamlines/i09_1.py +9 -1
  14. dodal/beamlines/i09_2.py +24 -0
  15. dodal/beamlines/i10.py +5 -6
  16. dodal/beamlines/i13_1.py +5 -5
  17. dodal/beamlines/i18.py +5 -6
  18. dodal/beamlines/i22.py +7 -1
  19. dodal/beamlines/i24.py +3 -3
  20. dodal/beamlines/p45.py +4 -3
  21. dodal/beamlines/p60.py +18 -1
  22. dodal/beamlines/p99.py +5 -5
  23. dodal/beamlines/training_rig.py +3 -3
  24. dodal/common/beamlines/beamline_utils.py +5 -2
  25. dodal/devices/aithre_lasershaping/goniometer.py +4 -5
  26. dodal/devices/aperture.py +4 -7
  27. dodal/devices/aperturescatterguard.py +2 -2
  28. dodal/devices/b07/__init__.py +3 -0
  29. dodal/devices/b07/grating.py +9 -0
  30. dodal/devices/b07_1/__init__.py +3 -0
  31. dodal/devices/b07_1/grating.py +10 -0
  32. dodal/devices/detector/detector_motion.py +19 -17
  33. dodal/devices/electron_analyser/abstract/base_driver_io.py +24 -25
  34. dodal/devices/electron_analyser/detector.py +3 -13
  35. dodal/devices/electron_analyser/specs/detector.py +9 -3
  36. dodal/devices/electron_analyser/specs/driver_io.py +5 -2
  37. dodal/devices/electron_analyser/vgscienta/detector.py +9 -3
  38. dodal/devices/electron_analyser/vgscienta/driver_io.py +5 -6
  39. dodal/devices/electron_analyser/vgscienta/region.py +0 -1
  40. dodal/devices/fast_grid_scan.py +1 -2
  41. dodal/devices/hutch_shutter.py +6 -6
  42. dodal/devices/i04/constants.py +1 -1
  43. dodal/devices/i09/__init__.py +4 -0
  44. dodal/devices/i09/dcm.py +26 -0
  45. dodal/devices/i09/grating.py +7 -0
  46. dodal/devices/i10/mirrors.py +4 -6
  47. dodal/devices/i10/rasor/rasor_motors.py +0 -14
  48. dodal/devices/i19/beamstop.py +3 -7
  49. dodal/devices/i24/aperture.py +4 -6
  50. dodal/devices/i24/beamstop.py +5 -8
  51. dodal/devices/i24/pmac.py +4 -8
  52. dodal/devices/motors.py +92 -35
  53. dodal/devices/p45.py +0 -12
  54. dodal/devices/p60/__init__.py +3 -0
  55. dodal/devices/p60/lab_xray_source.py +21 -0
  56. dodal/devices/pgm.py +1 -1
  57. dodal/devices/robot.py +11 -7
  58. dodal/devices/smargon.py +8 -9
  59. dodal/devices/zocalo/zocalo_results.py +27 -78
  60. dodal/plans/bimorph.py +333 -0
  61. dodal/plans/configure_arm_trigger_and_disarm_detector.py +7 -5
  62. dodal/devices/adsim.py +0 -13
  63. dodal/devices/i18/table.py +0 -14
  64. dodal/devices/i18/thor_labs_stage.py +0 -12
  65. dodal/devices/i24/i24_detector_motion.py +0 -12
  66. dodal/devices/scatterguard.py +0 -11
  67. dodal/devices/training_rig/__init__.py +0 -0
  68. dodal/devices/training_rig/sample_stage.py +0 -10
  69. {dls_dodal-1.49.0.dist-info → dls_dodal-1.51.0.dist-info}/WHEEL +0 -0
  70. {dls_dodal-1.49.0.dist-info → dls_dodal-1.51.0.dist-info}/entry_points.txt +0 -0
  71. {dls_dodal-1.49.0.dist-info → dls_dodal-1.51.0.dist-info}/licenses/LICENSE +0 -0
  72. {dls_dodal-1.49.0.dist-info → dls_dodal-1.51.0.dist-info}/top_level.txt +0 -0
dodal/devices/p45.py CHANGED
@@ -41,15 +41,3 @@ class TomoStageWithStretchAndSkew(StandardReadable):
41
41
  self.y = SampleY(prefix)
42
42
  self.theta = SampleTheta(prefix)
43
43
  super().__init__(name)
44
-
45
-
46
- class Choppers(StandardReadable):
47
- """
48
- Grouping for the P45 chopper motors
49
- """
50
-
51
- def __init__(self, prefix: str, name: str = ""):
52
- with self.add_children_as_readables():
53
- self.x = Motor(prefix + "ENDAT")
54
- self.y = Motor(prefix + "BISS")
55
- super().__init__(name)
@@ -0,0 +1,3 @@
1
+ from dodal.devices.p60.lab_xray_source import LabXraySource, LabXraySourceReadable
2
+
3
+ __all__ = ["LabXraySource", "LabXraySourceReadable"]
@@ -0,0 +1,21 @@
1
+ from ophyd_async.core import (
2
+ StandardReadable,
3
+ StrictEnum,
4
+ soft_signal_r_and_setter,
5
+ )
6
+
7
+
8
+ class LabXraySource(StrictEnum):
9
+ AL_KALPHA = 1486.6
10
+ MG_KALPHA = 1253.6
11
+
12
+
13
+ class LabXraySourceReadable(StandardReadable):
14
+ """Simple device to get the laboratory x-ray tube energy reading"""
15
+
16
+ def __init__(self, xraysource: LabXraySource, name: str = "") -> None:
17
+ with self.add_children_as_readables():
18
+ self.energy_ev, _ = soft_signal_r_and_setter(
19
+ float, initial_value=xraysource.value, units="eV"
20
+ )
21
+ super().__init__(name)
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
- gratingPv: str,
19
+ gratingPv: str = "GRATINGSELECT:SELECT",
20
20
  name: str = "",
21
21
  ) -> None:
22
22
  """
dodal/devices/robot.py CHANGED
@@ -7,6 +7,7 @@ from ophyd_async.core import (
7
7
  AsyncStatus,
8
8
  Device,
9
9
  StandardReadable,
10
+ StandardReadableFormat,
10
11
  StrictEnum,
11
12
  set_and_wait_for_value,
12
13
  wait_for_value,
@@ -74,18 +75,21 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
74
75
  LOAD_TOLERANCE_MM = 0.02
75
76
 
76
77
  def __init__(self, name: str, prefix: str) -> None:
77
- self.barcode = epics_signal_r(str, prefix + "BARCODE")
78
- self.gonio_pin_sensor = epics_signal_r(PinMounted, prefix + "PIN_MOUNTED")
78
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
79
+ self.barcode = epics_signal_r(str, prefix + "BARCODE")
80
+ self.gonio_pin_sensor = epics_signal_r(PinMounted, prefix + "PIN_MOUNTED")
81
+
82
+ self.current_puck = epics_signal_r(float, prefix + "CURRENT_PUCK_RBV")
83
+ self.current_pin = epics_signal_r(float, prefix + "CURRENT_PIN_RBV")
79
84
 
80
85
  self.next_pin = epics_signal_rw_rbv(float, prefix + "NEXT_PIN")
81
86
  self.next_puck = epics_signal_rw_rbv(float, prefix + "NEXT_PUCK")
82
- self.current_puck = epics_signal_r(float, prefix + "CURRENT_PUCK_RBV")
83
- self.current_pin = epics_signal_r(float, prefix + "CURRENT_PIN_RBV")
84
87
 
85
- self.next_sample_id = epics_signal_rw_rbv(int, prefix + "NEXT_ID")
86
88
  self.sample_id = epics_signal_r(int, prefix + "CURRENT_ID_RBV")
89
+ self.next_sample_id = epics_signal_rw_rbv(int, prefix + "NEXT_ID")
87
90
 
88
91
  self.load = epics_signal_x(prefix + "LOAD.PROC")
92
+ self.unload = epics_signal_x(prefix + "UNLD.PROC")
89
93
  self.program_running = epics_signal_r(bool, prefix + "PROGRAM_RUNNING")
90
94
  self.program_name = epics_signal_r(str, prefix + "PROGRAM_NAME")
91
95
 
@@ -93,7 +97,7 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
93
97
  self.controller_error = ErrorStatus(prefix + "CNTL")
94
98
 
95
99
  self.reset = epics_signal_x(prefix + "RESET.PROC")
96
- self.stop = epics_signal_x(prefix + "ABORT.PROC")
100
+ self.abort = epics_signal_x(prefix + "ABORT.PROC")
97
101
  self.init = epics_signal_x(prefix + "INIT.PROC")
98
102
  self.soak = epics_signal_x(prefix + "SOAK.PROC")
99
103
  self.home = epics_signal_x(prefix + "GOHM.PROC")
@@ -173,7 +177,7 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
173
177
  self._load_pin_and_puck(value),
174
178
  timeout=self.LOAD_TIMEOUT + self.NOT_BUSY_TIMEOUT,
175
179
  )
176
- except (asyncio.TimeoutError, TimeoutError) as e:
180
+ except TimeoutError as e:
177
181
  # Will only need to catch asyncio.TimeoutError after https://github.com/bluesky/ophyd-async/issues/572
178
182
  await self.prog_error.raise_if_error(e)
179
183
  await self.controller_error.raise_if_error(e)
dodal/devices/smargon.py CHANGED
@@ -3,7 +3,7 @@ from collections.abc import Collection, Generator
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
5
  from math import isclose
6
- from typing import TypedDict, cast
6
+ from typing import NotRequired, TypedDict, cast
7
7
 
8
8
  from bluesky import plan_stubs as bps
9
9
  from bluesky.protocols import Movable
@@ -11,14 +11,13 @@ from bluesky.utils import Msg
11
11
  from ophyd_async.core import (
12
12
  AsyncStatus,
13
13
  Device,
14
- StandardReadable,
15
14
  StrictEnum,
16
15
  wait_for_value,
17
16
  )
18
17
  from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
19
18
  from ophyd_async.epics.motor import Motor
20
- from typing_extensions import NotRequired
21
19
 
20
+ from dodal.devices.motors import XYZStage
22
21
  from dodal.devices.util.epics_util import SetWhenEnabled
23
22
 
24
23
 
@@ -116,7 +115,7 @@ class CombinedMove(TypedDict):
116
115
  chi: NotRequired[float | None]
117
116
 
118
117
 
119
- class Smargon(StandardReadable, Movable):
118
+ class Smargon(XYZStage, Movable):
120
119
  """
121
120
  Real motors added to allow stops following pin load (e.g. real_x1.stop() )
122
121
  X1 and X2 real motors provide compound chi motion as well as the compound X travel,
@@ -126,9 +125,6 @@ class Smargon(StandardReadable, Movable):
126
125
 
127
126
  def __init__(self, prefix: str = "", name: str = ""):
128
127
  with self.add_children_as_readables():
129
- self.x = Motor(prefix + "X")
130
- self.y = Motor(prefix + "Y")
131
- self.z = Motor(prefix + "Z")
132
128
  self.chi = Motor(prefix + "CHI")
133
129
  self.phi = Motor(prefix + "PHI")
134
130
  self.omega = Motor(prefix + "OMEGA")
@@ -143,7 +139,7 @@ class Smargon(StandardReadable, Movable):
143
139
 
144
140
  self.defer_move = epics_signal_rw(DeferMoves, prefix + "CS1:DeferMoves")
145
141
 
146
- super().__init__(name)
142
+ super().__init__(prefix, name)
147
143
 
148
144
  def get_xyz_limits(self) -> Generator[Msg, None, XYZLimits]:
149
145
  """Obtain a plan stub that returns the smargon XYZ axis limits
@@ -171,6 +167,9 @@ class Smargon(StandardReadable, Movable):
171
167
  for k, v in value.items():
172
168
  if v is not None:
173
169
  tasks.append(getattr(self, k).set(v))
174
- await asyncio.gather(*tasks)
175
170
  finally:
176
171
  await self.defer_move.set(DeferMoves.OFF)
172
+ # The set() coroutines will not complete until after defer moves has been
173
+ # switched back off so we cannot wait for them until this point.
174
+ # see https://github.com/DiamondLightSource/dodal/issues/1315
175
+ await asyncio.gather(*tasks)
@@ -10,7 +10,6 @@ import workflows.recipe
10
10
  import workflows.transport
11
11
  from bluesky.protocols import Triggerable
12
12
  from bluesky.utils import Msg
13
- from deepdiff.diff import DeepDiff
14
13
  from ophyd_async.core import (
15
14
  Array1D,
16
15
  AsyncStatus,
@@ -49,6 +48,9 @@ DEFAULT_SORT_KEY = SortKeys.max_count
49
48
  CLEAR_QUEUE_WAIT_S = 2.0
50
49
  ZOCALO_STAGE_GROUP = "clear zocalo queue"
51
50
 
51
+ # Sentinel value required for inserting into the soft signal array
52
+ _NO_SAMPLE_ID = -1
53
+
52
54
 
53
55
  class XrcResult(TypedDict):
54
56
  """
@@ -69,6 +71,7 @@ class XrcResult(TypedDict):
69
71
  as the volume of whole boxes as a half-open range i.e such that
70
72
  p1 = (x1, y1, z1) <= p < p2 = (x2, y2, z2) and
71
73
  p2 - p1 gives the dimensions in whole voxels.
74
+ sample_id: The sample id associated with the centre.
72
75
  """
73
76
 
74
77
  centre_of_mass: list[float]
@@ -77,6 +80,7 @@ class XrcResult(TypedDict):
77
80
  n_voxels: int
78
81
  total_count: int
79
82
  bounding_box: list[list[int]]
83
+ sample_id: int | None
80
84
 
81
85
 
82
86
  def bbox_size(result: XrcResult):
@@ -86,18 +90,6 @@ def bbox_size(result: XrcResult):
86
90
  ]
87
91
 
88
92
 
89
- def get_dict_differences(
90
- dict1: dict, dict1_source: str, dict2: dict, dict2_source: str
91
- ) -> str | None:
92
- """Returns a string containing dict1 and dict2 if there are differences between them, greater than a
93
- 1e-5 tolerance. If dictionaries are identical, return None"""
94
-
95
- diff = DeepDiff(dict1, dict2, math_epsilon=1e-5, ignore_numeric_type_changes=True)
96
-
97
- if diff:
98
- return f"Zocalo results from {dict1_source} and {dict2_source} are not identical.\n Results from {dict1_source}: {dict1}\n Results from {dict2_source}: {dict2}"
99
-
100
-
101
93
  def source_from_results(results):
102
94
  return (
103
95
  ZocaloSource.GPU.value
@@ -127,10 +119,6 @@ class ZocaloResults(StandardReadable, Triggerable):
127
119
 
128
120
  prefix (str): EPICS PV prefix for the device
129
121
 
130
- use_cpu_and_gpu (bool): When True, ZocaloResults will wait for results from the
131
- CPU and the GPU, compare them, and provide a warning if the results differ. When
132
- False, ZocaloResults will only use results from the CPU
133
-
134
122
  use_gpu (bool): When True, ZocaloResults will take the first set of
135
123
  results that it receives (which are likely the GPU results)
136
124
 
@@ -144,7 +132,6 @@ class ZocaloResults(StandardReadable, Triggerable):
144
132
  sort_key: str = DEFAULT_SORT_KEY.value,
145
133
  timeout_s: float = DEFAULT_TIMEOUT,
146
134
  prefix: str = "",
147
- use_cpu_and_gpu: bool = False,
148
135
  use_gpu: bool = False,
149
136
  ) -> None:
150
137
  self.zocalo_environment = zocalo_environment
@@ -154,7 +141,6 @@ class ZocaloResults(StandardReadable, Triggerable):
154
141
  self._prefix = prefix
155
142
  self._raw_results_received: Queue = Queue()
156
143
  self.transport: CommonTransport | None = None
157
- self.use_cpu_and_gpu = use_cpu_and_gpu
158
144
  self.use_gpu = use_gpu
159
145
 
160
146
  self.centre_of_mass, self._com_setter = soft_signal_r_and_setter(
@@ -175,6 +161,9 @@ class ZocaloResults(StandardReadable, Triggerable):
175
161
  self.total_count, self._total_count_setter = soft_signal_r_and_setter(
176
162
  Array1D[np.uint64], name="total_count"
177
163
  )
164
+ self.sample_id, self._sample_id_setter = soft_signal_r_and_setter(
165
+ Array1D[np.int64], name="sample_id"
166
+ )
178
167
  self.ispyb_dcid, self._ispyb_dcid_setter = soft_signal_r_and_setter(
179
168
  int, name="ispyb_dcid"
180
169
  )
@@ -189,6 +178,7 @@ class ZocaloResults(StandardReadable, Triggerable):
189
178
  self.total_count,
190
179
  self.centre_of_mass,
191
180
  self.bounding_box,
181
+ self.sample_id,
192
182
  self.ispyb_dcid,
193
183
  self.ispyb_dcgid,
194
184
  ],
@@ -197,13 +187,15 @@ class ZocaloResults(StandardReadable, Triggerable):
197
187
  super().__init__(name)
198
188
 
199
189
  async def _put_results(self, results: Sequence[XrcResult], recipe_parameters):
200
- centres_of_mass = np.array([r["centre_of_mass"] for r in results])
201
- self._com_setter(centres_of_mass)
190
+ self._com_setter(np.array([r["centre_of_mass"] for r in results]))
202
191
  self._bounding_box_setter(np.array([r["bounding_box"] for r in results]))
203
192
  self._max_voxel_setter(np.array([r["max_voxel"] for r in results]))
204
193
  self._max_count_setter(np.array([r["max_count"] for r in results]))
205
194
  self._n_voxels_setter(np.array([r["n_voxels"] for r in results]))
206
195
  self._total_count_setter(np.array([r["total_count"] for r in results]))
196
+ self._sample_id_setter(
197
+ np.array([r.get("sample_id") or _NO_SAMPLE_ID for r in results])
198
+ )
207
199
  self._ispyb_dcid_setter(recipe_parameters["dcid"])
208
200
  self._ispyb_dcgid_setter(recipe_parameters["dcgid"])
209
201
 
@@ -218,11 +210,6 @@ class ZocaloResults(StandardReadable, Triggerable):
218
210
  clearing the queue. Plans using this device should wait on ZOCALO_STAGE_GROUP
219
211
  before triggering processing for the experiment"""
220
212
 
221
- if self.use_cpu_and_gpu and self.use_gpu:
222
- raise ValueError(
223
- "Cannot compare GPU and CPU results and use GPU results at the same time."
224
- )
225
-
226
213
  LOGGER.info("Subscribing to results queue")
227
214
  try:
228
215
  self._subscribe_to_results()
@@ -268,55 +255,6 @@ class ZocaloResults(StandardReadable, Triggerable):
268
255
  "Configured to use GPU results but CPU came first, using CPU results."
269
256
  )
270
257
 
271
- if self.use_cpu_and_gpu:
272
- # Wait for results from CPU and GPU, warn and continue if only GPU times out. Error if CPU times out
273
- if source_of_first_results == ZocaloSource.CPU:
274
- LOGGER.warning("Received zocalo results from CPU before GPU")
275
- raw_results_two_sources = [raw_results]
276
- try:
277
- raw_results_two_sources.append(
278
- self._raw_results_received.get(timeout=self.timeout_s / 2)
279
- )
280
- source_of_second_results = source_from_results(
281
- raw_results_two_sources[1]
282
- )
283
- first_results = raw_results_two_sources[0]["results"]
284
- second_results = raw_results_two_sources[1]["results"]
285
-
286
- if first_results and second_results:
287
- # Compare results from both sources and warn if they aren't the same
288
- differences_str = get_dict_differences(
289
- first_results[0],
290
- source_of_first_results,
291
- second_results[0],
292
- source_of_second_results,
293
- )
294
- if differences_str:
295
- LOGGER.warning(differences_str)
296
-
297
- # Always use CPU results
298
- raw_results = (
299
- raw_results_two_sources[0]
300
- if source_of_first_results == ZocaloSource.CPU
301
- else raw_results_two_sources[1]
302
- )
303
-
304
- except Empty as err:
305
- source_of_missing_results = (
306
- ZocaloSource.CPU.value
307
- if source_of_first_results == ZocaloSource.GPU.value
308
- else ZocaloSource.GPU.value
309
- )
310
- if source_of_missing_results == ZocaloSource.GPU.value:
311
- LOGGER.warning(
312
- f"Zocalo results from {source_of_missing_results} timed out. Using results from {source_of_first_results}"
313
- )
314
- else:
315
- LOGGER.error(
316
- f"Zocalo results from {source_of_missing_results} timed out and GPU results not yet reliable"
317
- )
318
- raise err
319
-
320
258
  LOGGER.info(
321
259
  f"Zocalo results from {source_from_results(raw_results)} processing: found {len(raw_results['results'])} crystals."
322
260
  )
@@ -350,7 +288,7 @@ class ZocaloResults(StandardReadable, Triggerable):
350
288
 
351
289
  results = message.get("results", [])
352
290
 
353
- if self.use_cpu_and_gpu or self.use_gpu:
291
+ if self.use_gpu:
354
292
  self._raw_results_received.put(
355
293
  {"results": results, "recipe_parameters": recipe_parameters}
356
294
  )
@@ -360,6 +298,8 @@ class ZocaloResults(StandardReadable, Triggerable):
360
298
  self._raw_results_received.put(
361
299
  {"results": results, "recipe_parameters": recipe_parameters}
362
300
  )
301
+ else:
302
+ LOGGER.warning("Discarding results as they are from GPU")
363
303
 
364
304
  subscription = workflows.recipe.wrap_subscribe(
365
305
  self.transport,
@@ -393,6 +333,7 @@ def get_full_processing_results(
393
333
  n_voxels = yield from bps.rd(zocalo.n_voxels, default_value=[])
394
334
  total_count = yield from bps.rd(zocalo.total_count, default_value=[])
395
335
  bounding_box = yield from bps.rd(zocalo.bounding_box, default_value=[])
336
+ sample_id = yield from bps.rd(zocalo.sample_id, default_value=[])
396
337
  return [
397
338
  _corrected_xrc_result(
398
339
  XrcResult(
@@ -402,9 +343,17 @@ def get_full_processing_results(
402
343
  n_voxels=int(n),
403
344
  total_count=int(tc),
404
345
  bounding_box=bb.tolist(),
346
+ sample_id=int(s_id) if s_id != _NO_SAMPLE_ID else None,
405
347
  )
406
348
  )
407
- for com, mv, mc, n, tc, bb in zip(
408
- com, max_voxel, max_count, n_voxels, total_count, bounding_box, strict=True
349
+ for com, mv, mc, n, tc, bb, s_id in zip(
350
+ com,
351
+ max_voxel,
352
+ max_count,
353
+ n_voxels,
354
+ total_count,
355
+ bounding_box,
356
+ sample_id,
357
+ strict=True,
409
358
  )
410
359
  ]