dls-dodal 1.54.0__py3-none-any.whl → 1.55.1__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 (39) hide show
  1. {dls_dodal-1.54.0.dist-info → dls_dodal-1.55.1.dist-info}/METADATA +2 -2
  2. {dls_dodal-1.54.0.dist-info → dls_dodal-1.55.1.dist-info}/RECORD +39 -37
  3. dodal/_version.py +2 -2
  4. dodal/beamline_specific_utils/i05_shared.py +11 -0
  5. dodal/beamlines/aithre.py +2 -3
  6. dodal/beamlines/b01_1.py +0 -12
  7. dodal/beamlines/b16.py +0 -1
  8. dodal/beamlines/b21.py +0 -2
  9. dodal/beamlines/i03.py +10 -31
  10. dodal/beamlines/i04.py +7 -38
  11. dodal/beamlines/i05.py +2 -2
  12. dodal/beamlines/i05_1.py +8 -3
  13. dodal/beamlines/i13_1.py +0 -1
  14. dodal/beamlines/i19_1.py +0 -1
  15. dodal/beamlines/i19_2.py +0 -1
  16. dodal/beamlines/i19_optics.py +1 -4
  17. dodal/beamlines/i23.py +1 -2
  18. dodal/beamlines/i24.py +0 -12
  19. dodal/common/beamlines/device_helpers.py +0 -33
  20. dodal/devices/aithre_lasershaping/__init__.py +0 -0
  21. dodal/devices/aithre_lasershaping/goniometer.py +3 -26
  22. dodal/devices/aithre_lasershaping/laser_robot.py +2 -2
  23. dodal/devices/attenuator/attenuator.py +3 -1
  24. dodal/devices/focusing_mirror.py +1 -1
  25. dodal/devices/i03/undulator_dcm.py +8 -3
  26. dodal/devices/i10/mirrors.py +2 -6
  27. dodal/devices/motors.py +75 -1
  28. dodal/devices/robot.py +1 -1
  29. dodal/devices/tetramm.py +13 -0
  30. dodal/devices/util/test_utils.py +19 -0
  31. dodal/devices/webcam.py +2 -1
  32. dodal/devices/zebra/zebra.py +1 -1
  33. dodal/devices/zebra/zebra_controlled_shutter.py +1 -1
  34. dodal/devices/zocalo/__init__.py +2 -0
  35. dodal/devices/zocalo/zocalo_results.py +16 -26
  36. {dls_dodal-1.54.0.dist-info → dls_dodal-1.55.1.dist-info}/WHEEL +0 -0
  37. {dls_dodal-1.54.0.dist-info → dls_dodal-1.55.1.dist-info}/entry_points.txt +0 -0
  38. {dls_dodal-1.54.0.dist-info → dls_dodal-1.55.1.dist-info}/licenses/LICENSE +0 -0
  39. {dls_dodal-1.54.0.dist-info → dls_dodal-1.55.1.dist-info}/top_level.txt +0 -0
dodal/devices/tetramm.py CHANGED
@@ -116,6 +116,19 @@ class TetrammController(DetectorController):
116
116
  self.set_exposure(trigger_info.livetime),
117
117
  )
118
118
 
119
+ # raise an error if asked to trigger faster than the max.
120
+ # possible speed for a tetramm
121
+ self._validate_deadtime(trigger_info)
122
+
123
+ def _validate_deadtime(self, value: TriggerInfo) -> None:
124
+ minimum_deadtime = self.get_deadtime(value.livetime)
125
+ if minimum_deadtime > value.deadtime:
126
+ msg = (
127
+ f"Tetramm {self} needs at least {minimum_deadtime}s "
128
+ f"deadtime, but trigger logic provides only {value.deadtime}s"
129
+ )
130
+ raise ValueError(msg)
131
+
119
132
  async def arm(self):
120
133
  self._arm_status = await self.start_acquiring_driver_and_ensure_status()
121
134
 
@@ -1,3 +1,6 @@
1
+ from contextlib import ExitStack
2
+
3
+ from ophyd_async.core import Device
1
4
  from ophyd_async.epics.motor import Motor
2
5
  from ophyd_async.testing import (
3
6
  callback_on_mock_put,
@@ -16,3 +19,19 @@ def patch_motor(motor: Motor, initial_position=0):
16
19
  motor.user_setpoint,
17
20
  lambda pos, *args, **kwargs: set_mock_value(motor.user_readback, pos),
18
21
  )
22
+
23
+
24
+ def patch_all_motors(parent_device: Device):
25
+ motors = []
26
+
27
+ def recursively_find_motors(device: Device):
28
+ for _, child_device in device.children():
29
+ if isinstance(child_device, Motor):
30
+ motors.append(child_device)
31
+ recursively_find_motors(child_device)
32
+
33
+ recursively_find_motors(parent_device)
34
+ motor_patch_stack = ExitStack()
35
+ for motor in motors:
36
+ motor_patch_stack.enter_context(patch_motor(motor))
37
+ return motor_patch_stack
dodal/devices/webcam.py CHANGED
@@ -12,6 +12,7 @@ from ophyd_async.core import (
12
12
  soft_signal_rw,
13
13
  )
14
14
  from PIL import Image
15
+ from yarl import URL
15
16
 
16
17
  from dodal.log import LOGGER
17
18
 
@@ -26,7 +27,7 @@ def create_placeholder_image() -> ByteString:
26
27
 
27
28
 
28
29
  class Webcam(StandardReadable, Triggerable):
29
- def __init__(self, name, prefix, url):
30
+ def __init__(self, url: URL, name: str = ""):
30
31
  self.url = url
31
32
  self.filename = soft_signal_rw(str, name="filename")
32
33
  self.directory = soft_signal_rw(str, name="directory")
@@ -286,7 +286,7 @@ class SoftInputs(StandardReadable):
286
286
  class Zebra(StandardReadable):
287
287
  """The Zebra device."""
288
288
 
289
- def __init__(self, mapping: ZebraMapping, name: str, prefix: str) -> None:
289
+ def __init__(self, mapping: ZebraMapping, prefix: str, name: str = "") -> None:
290
290
  self.mapping = mapping
291
291
  self.pc = PositionCompare(prefix, name)
292
292
  self.output = ZebraOutputPanel(prefix, name)
@@ -28,7 +28,7 @@ class ZebraShutter(StandardReadable, Movable[ZebraShutterState]):
28
28
  by a different soft input (aliased to manual_position_setpoint). Both these AND
29
29
  gates then feed into an OR gate, which then feeds to the shutter."""
30
30
 
31
- def __init__(self, prefix: str, name: str):
31
+ def __init__(self, prefix: str, name: str = ""):
32
32
  self._manual_position_setpoint = epics_signal_w(
33
33
  ZebraShutterState, prefix + "CTRL2"
34
34
  )
@@ -4,6 +4,7 @@ from dodal.devices.zocalo.zocalo_results import (
4
4
  NoZocaloSubscription,
5
5
  XrcResult,
6
6
  ZocaloResults,
7
+ ZocaloSource,
7
8
  get_full_processing_results,
8
9
  )
9
10
 
@@ -15,4 +16,5 @@ __all__ = [
15
16
  "NoResultsFromZocalo",
16
17
  "NoZocaloSubscription",
17
18
  "ZocaloStartInfo",
19
+ "ZocaloSource",
18
20
  ]
@@ -119,8 +119,7 @@ class ZocaloResults(StandardReadable, Triggerable):
119
119
 
120
120
  prefix (str): EPICS PV prefix for the device
121
121
 
122
- use_gpu (bool): When True, ZocaloResults will take the first set of
123
- results that it receives (which are likely the GPU results)
122
+ results_source (ZocaloSource): Where to get results from, GPU or CPU analysis
124
123
 
125
124
  """
126
125
 
@@ -132,7 +131,7 @@ class ZocaloResults(StandardReadable, Triggerable):
132
131
  sort_key: str = DEFAULT_SORT_KEY.value,
133
132
  timeout_s: float = DEFAULT_TIMEOUT,
134
133
  prefix: str = "",
135
- use_gpu: bool = False,
134
+ results_source: ZocaloSource = ZocaloSource.CPU,
136
135
  ) -> None:
137
136
  self.zocalo_environment = zocalo_environment
138
137
  self.sort_key = SortKeys[sort_key]
@@ -141,7 +140,7 @@ class ZocaloResults(StandardReadable, Triggerable):
141
140
  self._prefix = prefix
142
141
  self._raw_results_received: Queue = Queue()
143
142
  self.transport: CommonTransport | None = None
144
- self.use_gpu = use_gpu
143
+ self.results_source = results_source
145
144
 
146
145
  self.centre_of_mass, self._com_setter = soft_signal_r_and_setter(
147
146
  Array1D[np.float64], name="centre_of_mass"
@@ -237,9 +236,6 @@ class ZocaloResults(StandardReadable, Triggerable):
237
236
  "meant for it"
238
237
  )
239
238
  if not self.transport:
240
- LOGGER.warning(
241
- msg # AsyncStatus exception messages are poorly propagated, remove after https://github.com/bluesky/ophyd-async/issues/103
242
- )
243
239
  raise NoZocaloSubscription(msg)
244
240
 
245
241
  try:
@@ -247,16 +243,19 @@ class ZocaloResults(StandardReadable, Triggerable):
247
243
  f"waiting for results in queue - currently {self._raw_results_received.qsize()} items"
248
244
  )
249
245
 
250
- raw_results = self._raw_results_received.get(timeout=self.timeout_s)
251
- source_of_first_results = source_from_results(raw_results)
246
+ while True:
247
+ raw_results = self._raw_results_received.get(timeout=self.timeout_s)
248
+ source_of_results = source_from_results(raw_results)
252
249
 
253
- if self.use_gpu and source_of_first_results == ZocaloSource.CPU:
254
- LOGGER.warning(
255
- "Configured to use GPU results but CPU came first, using CPU results."
256
- )
250
+ if source_of_results != self.results_source:
251
+ LOGGER.warning(
252
+ f"Configured to use {self.results_source} results but {source_of_results} came first, waiting for further results."
253
+ )
254
+ else:
255
+ break
257
256
 
258
257
  LOGGER.info(
259
- f"Zocalo results from {source_from_results(raw_results)} processing: found {len(raw_results['results'])} crystals."
258
+ f"Zocalo results: found {len(raw_results['results'])} crystals."
260
259
  )
261
260
  # Sort from strongest to weakest in case of multiple crystals
262
261
  await self._put_results(
@@ -288,18 +287,9 @@ class ZocaloResults(StandardReadable, Triggerable):
288
287
 
289
288
  results = message.get("results", [])
290
289
 
291
- if self.use_gpu:
292
- self._raw_results_received.put(
293
- {"results": results, "recipe_parameters": recipe_parameters}
294
- )
295
- else:
296
- # Only add to queue if results are from CPU
297
- if not recipe_parameters.get("gpu"):
298
- self._raw_results_received.put(
299
- {"results": results, "recipe_parameters": recipe_parameters}
300
- )
301
- else:
302
- LOGGER.warning("Discarding results as they are from GPU")
290
+ self._raw_results_received.put(
291
+ {"results": results, "recipe_parameters": recipe_parameters}
292
+ )
303
293
 
304
294
  subscription = workflows.recipe.wrap_subscribe(
305
295
  self.transport,