dls-dodal 1.47.0__py3-none-any.whl → 1.48.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 (42) hide show
  1. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/METADATA +2 -2
  2. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/RECORD +40 -40
  3. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/aithre.py +6 -0
  6. dodal/beamlines/b01_1.py +1 -1
  7. dodal/beamlines/i03.py +21 -6
  8. dodal/beamlines/i04.py +17 -10
  9. dodal/beamlines/i18.py +1 -1
  10. dodal/beamlines/i19_1.py +9 -6
  11. dodal/beamlines/i24.py +5 -5
  12. dodal/common/beamlines/beamline_parameters.py +2 -28
  13. dodal/devices/aithre_lasershaping/goniometer.py +36 -2
  14. dodal/devices/aithre_lasershaping/laser_robot.py +27 -0
  15. dodal/devices/electron_analyser/__init__.py +10 -0
  16. dodal/devices/electron_analyser/abstract/__init__.py +0 -6
  17. dodal/devices/electron_analyser/abstract/base_detector.py +69 -56
  18. dodal/devices/electron_analyser/abstract/base_driver_io.py +114 -5
  19. dodal/devices/electron_analyser/abstract/base_region.py +1 -0
  20. dodal/devices/electron_analyser/specs/__init__.py +1 -2
  21. dodal/devices/electron_analyser/specs/detector.py +5 -21
  22. dodal/devices/electron_analyser/specs/driver_io.py +27 -2
  23. dodal/devices/electron_analyser/vgscienta/__init__.py +1 -2
  24. dodal/devices/electron_analyser/vgscienta/detector.py +8 -22
  25. dodal/devices/electron_analyser/vgscienta/driver_io.py +31 -3
  26. dodal/devices/electron_analyser/vgscienta/region.py +0 -1
  27. dodal/devices/fast_grid_scan.py +1 -1
  28. dodal/devices/i04/murko_results.py +93 -96
  29. dodal/devices/i18/diode.py +37 -4
  30. dodal/devices/mx_phase1/beamstop.py +23 -6
  31. dodal/devices/oav/oav_detector.py +61 -23
  32. dodal/devices/oav/oav_parameters.py +46 -16
  33. dodal/devices/oav/oav_to_redis_forwarder.py +2 -2
  34. dodal/devices/robot.py +20 -1
  35. dodal/devices/smargon.py +43 -4
  36. dodal/devices/zebra/zebra.py +8 -0
  37. dodal/plans/configure_arm_trigger_and_disarm_detector.py +167 -0
  38. dodal/plan_stubs/electron_analyser/__init__.py +0 -3
  39. dodal/plan_stubs/electron_analyser/configure_driver.py +0 -92
  40. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/entry_points.txt +0 -0
  41. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/licenses/LICENSE +0 -0
  42. {dls_dodal-1.47.0.dist-info → dls_dodal-1.48.0.dist-info}/top_level.txt +0 -0
dodal/devices/smargon.py CHANGED
@@ -1,14 +1,23 @@
1
+ import asyncio
1
2
  from collections.abc import Collection, Generator
2
3
  from dataclasses import dataclass
3
4
  from enum import Enum
4
5
  from math import isclose
5
- from typing import cast
6
+ from typing import TypedDict, cast
6
7
 
7
8
  from bluesky import plan_stubs as bps
9
+ from bluesky.protocols import Movable
8
10
  from bluesky.utils import Msg
9
- from ophyd_async.core import AsyncStatus, Device, StandardReadable, wait_for_value
10
- from ophyd_async.epics.core import epics_signal_r
11
+ from ophyd_async.core import (
12
+ AsyncStatus,
13
+ Device,
14
+ StandardReadable,
15
+ StrictEnum,
16
+ wait_for_value,
17
+ )
18
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
11
19
  from ophyd_async.epics.motor import Motor
20
+ from typing_extensions import NotRequired
12
21
 
13
22
  from dodal.devices.util.epics_util import SetWhenEnabled
14
23
 
@@ -91,7 +100,23 @@ class XYZLimits:
91
100
  )
92
101
 
93
102
 
94
- class Smargon(StandardReadable):
103
+ class DeferMoves(StrictEnum):
104
+ ON = "Defer On"
105
+ OFF = "Defer Off"
106
+
107
+
108
+ class CombinedMove(TypedDict):
109
+ """A move on multiple axes at once using a deferred move"""
110
+
111
+ x: NotRequired[float | None]
112
+ y: NotRequired[float | None]
113
+ z: NotRequired[float | None]
114
+ omega: NotRequired[float | None]
115
+ phi: NotRequired[float | None]
116
+ chi: NotRequired[float | None]
117
+
118
+
119
+ class Smargon(StandardReadable, Movable):
95
120
  """
96
121
  Real motors added to allow stops following pin load (e.g. real_x1.stop() )
97
122
  X1 and X2 real motors provide compound chi motion as well as the compound X travel,
@@ -116,6 +141,8 @@ class Smargon(StandardReadable):
116
141
  self.stub_offsets = StubOffsets(prefix=prefix)
117
142
  self.disabled = epics_signal_r(int, prefix + "DISABLED")
118
143
 
144
+ self.defer_move = epics_signal_rw(DeferMoves, prefix + "CS1:DeferMoves")
145
+
119
146
  super().__init__(name)
120
147
 
121
148
  def get_xyz_limits(self) -> Generator[Msg, None, XYZLimits]:
@@ -135,3 +162,15 @@ class Smargon(StandardReadable):
135
162
  max_value = yield from bps.rd(pv.high_limit_travel)
136
163
  limits[name] = AxisLimit(min_value, max_value)
137
164
  return XYZLimits(**limits)
165
+
166
+ @AsyncStatus.wrap
167
+ async def set(self, value: CombinedMove):
168
+ await self.defer_move.set(DeferMoves.ON)
169
+ try:
170
+ tasks = []
171
+ for k, v in value.items():
172
+ if v is not None:
173
+ tasks.append(getattr(self, k).set(v))
174
+ await asyncio.gather(*tasks)
175
+ finally:
176
+ await self.defer_move.set(DeferMoves.OFF)
@@ -68,6 +68,14 @@ class RotationDirection(StrictEnum):
68
68
  def multiplier(self):
69
69
  return 1 if self == RotationDirection.POSITIVE else -1
70
70
 
71
+ @property
72
+ def opposite(self) -> RotationDirection:
73
+ return (
74
+ RotationDirection.POSITIVE
75
+ if self == RotationDirection.NEGATIVE
76
+ else RotationDirection.NEGATIVE
77
+ )
78
+
71
79
 
72
80
  class ArmDemand(Enum):
73
81
  ARM = 1
@@ -0,0 +1,167 @@
1
+ import time
2
+
3
+ import bluesky.plan_stubs as bps
4
+ from bluesky import preprocessors as bpp
5
+ from bluesky.run_engine import RunEngine
6
+ from ophyd_async.core import DetectorTrigger
7
+ from ophyd_async.fastcs.eiger import EigerDetector, EigerTriggerInfo
8
+
9
+ from dodal.beamlines.i03 import fastcs_eiger
10
+ from dodal.devices.detector import DetectorParams
11
+ from dodal.log import LOGGER, do_default_logging_setup
12
+
13
+
14
+ @bpp.run_decorator()
15
+ def configure_arm_trigger_and_disarm_detector(
16
+ eiger: EigerDetector,
17
+ detector_params: DetectorParams,
18
+ trigger_info: EigerTriggerInfo,
19
+ ):
20
+ assert detector_params.expected_energy_ev
21
+ start = time.time()
22
+ yield from bps.unstage(eiger, wait=True)
23
+ LOGGER.info(f"Stopping Eiger-Odin: {time.time() - start}s")
24
+ start = time.time()
25
+ yield from set_cam_pvs(eiger, detector_params, wait=True)
26
+ LOGGER.info(f"Setting CAM PVs: {time.time() - start}s")
27
+ start = time.time()
28
+ yield from change_roi_mode(eiger, detector_params, wait=True)
29
+ LOGGER.info(f"Changing ROI Mode: {time.time() - start}s")
30
+ start = time.time()
31
+ yield from bps.abs_set(eiger.odin.num_frames_chunks, 1)
32
+ LOGGER.info(f"Setting # of Frame Chunks: {time.time() - start}s")
33
+ start = time.time()
34
+ yield from set_mx_settings_pvs(eiger, detector_params, wait=True)
35
+ LOGGER.info(f"Setting MX PVs: {time.time() - start}s")
36
+ start = time.time()
37
+ yield from bps.prepare(eiger, trigger_info, wait=True)
38
+ LOGGER.info(f"Preparing Eiger: {time.time() - start}s")
39
+ start = time.time()
40
+ yield from bps.kickoff(eiger, wait=True)
41
+ LOGGER.info(f"Kickoff Eiger: {time.time() - start}s")
42
+ start = time.time()
43
+ yield from bps.trigger(eiger.drv.detector.trigger) # type: ignore
44
+ LOGGER.info(f"Triggering Eiger: {time.time() - start}s")
45
+ start = time.time()
46
+ yield from bps.complete(eiger, wait=True)
47
+ LOGGER.info(f"Completing Capture: {time.time() - start}s")
48
+ start = time.time()
49
+ yield from bps.unstage(eiger, wait=True)
50
+ LOGGER.info(f"Disarming Eiger: {time.time() - start}s")
51
+
52
+
53
+ def set_cam_pvs(
54
+ eiger: EigerDetector,
55
+ detector_params: DetectorParams,
56
+ wait: bool,
57
+ group="cam_pvs",
58
+ ):
59
+ yield from bps.abs_set(
60
+ eiger.drv.detector.count_time, detector_params.exposure_time_s, group=group
61
+ )
62
+ yield from bps.abs_set(
63
+ eiger.drv.detector.frame_time, detector_params.exposure_time_s, group=group
64
+ )
65
+ yield from bps.abs_set(eiger.drv.detector.nexpi, 1, group=group)
66
+
67
+ if wait:
68
+ yield from bps.wait(group)
69
+
70
+
71
+ def change_roi_mode(
72
+ eiger: EigerDetector,
73
+ detector_params: DetectorParams,
74
+ wait: bool,
75
+ group="roi_mode",
76
+ ):
77
+ detector_dimensions = (
78
+ detector_params.detector_size_constants.roi_size_pixels
79
+ if detector_params.use_roi_mode
80
+ else detector_params.detector_size_constants.det_size_pixels
81
+ )
82
+
83
+ yield from bps.abs_set(
84
+ eiger.drv.detector.roi_mode,
85
+ 1 if detector_params.use_roi_mode else 0,
86
+ group=group,
87
+ )
88
+ yield from bps.abs_set(
89
+ eiger.odin.image_height,
90
+ detector_dimensions.height,
91
+ group=group,
92
+ )
93
+ yield from bps.abs_set(
94
+ eiger.odin.image_width,
95
+ detector_dimensions.width,
96
+ group=group,
97
+ )
98
+ yield from bps.abs_set(
99
+ eiger.odin.num_row_chunks,
100
+ detector_dimensions.height,
101
+ group=group,
102
+ )
103
+ yield from bps.abs_set(
104
+ eiger.odin.num_col_chunks,
105
+ detector_dimensions.width,
106
+ group=group,
107
+ )
108
+
109
+ if wait:
110
+ yield from bps.wait(group)
111
+
112
+
113
+ def set_mx_settings_pvs(
114
+ eiger: EigerDetector,
115
+ detector_params: DetectorParams,
116
+ wait: bool,
117
+ group="mx_settings",
118
+ ):
119
+ beam_x_pixels, beam_y_pixels = detector_params.get_beam_position_pixels(
120
+ detector_params.detector_distance
121
+ )
122
+
123
+ yield from bps.abs_set(eiger.drv.detector.beam_center_x, beam_x_pixels, group)
124
+ yield from bps.abs_set(eiger.drv.detector.beam_center_y, beam_y_pixels, group)
125
+ yield from bps.abs_set(
126
+ eiger.drv.detector.detector_distance, detector_params.detector_distance, group
127
+ )
128
+
129
+ yield from bps.abs_set(
130
+ eiger.drv.detector.omega_start, detector_params.omega_start, group
131
+ )
132
+ yield from bps.abs_set(
133
+ eiger.drv.detector.omega_increment, detector_params.omega_increment, group
134
+ )
135
+
136
+ if wait:
137
+ yield from bps.wait(group)
138
+
139
+
140
+ if __name__ == "__main__":
141
+ RE = RunEngine()
142
+ do_default_logging_setup()
143
+ eiger = fastcs_eiger(connect_immediately=True)
144
+ RE(
145
+ configure_arm_trigger_and_disarm_detector(
146
+ eiger=eiger,
147
+ detector_params=DetectorParams(
148
+ expected_energy_ev=12800,
149
+ exposure_time_s=0.01,
150
+ directory="/dls/i03/data/2025/cm40607-2/test_new_eiger/",
151
+ prefix="",
152
+ detector_distance=255,
153
+ omega_start=0,
154
+ omega_increment=0.1,
155
+ num_images_per_trigger=1,
156
+ num_triggers=1,
157
+ use_roi_mode=False,
158
+ det_dist_to_beam_converter_path="/dls_sw/i03/software/daq_configuration/lookup/DetDistToBeamXYConverter.txt",
159
+ ),
160
+ trigger_info=EigerTriggerInfo(
161
+ number_of_events=1,
162
+ energy_ev=12800,
163
+ trigger=DetectorTrigger.INTERNAL,
164
+ deadtime=0.0001,
165
+ ),
166
+ )
167
+ )
@@ -1,3 +0,0 @@
1
- from .configure_driver import configure_analyser, configure_specs, configure_vgscienta
2
-
3
- __all__ = ["configure_analyser", "configure_specs", "configure_vgscienta"]
@@ -1,92 +0,0 @@
1
- from bluesky import plan_stubs as bps
2
- from bluesky.utils import MsgGenerator, plan
3
- from ophyd_async.epics.adcore import ADImageMode
4
-
5
- from dodal.common.types import MsgGenerator
6
- from dodal.devices.electron_analyser.abstract import (
7
- AbstractAnalyserDriverIO,
8
- AbstractBaseRegion,
9
- )
10
- from dodal.devices.electron_analyser.specs import SpecsAnalyserDriverIO, SpecsRegion
11
- from dodal.devices.electron_analyser.util import to_kinetic_energy
12
- from dodal.devices.electron_analyser.vgscienta import (
13
- VGScientaAnalyserDriverIO,
14
- VGScientaRegion,
15
- )
16
- from dodal.log import LOGGER
17
-
18
-
19
- @plan
20
- def configure_analyser(
21
- analyser: AbstractAnalyserDriverIO,
22
- region: AbstractBaseRegion,
23
- excitation_energy: float,
24
- ) -> MsgGenerator:
25
- LOGGER.info(f'Configuring analyser with region "{region.name}"')
26
-
27
- low_energy = to_kinetic_energy(
28
- region.low_energy, region.energy_mode, excitation_energy
29
- )
30
- high_energy = to_kinetic_energy(
31
- region.high_energy, region.energy_mode, excitation_energy
32
- )
33
- pass_energy_type = analyser.pass_energy_type
34
- pass_energy = pass_energy_type(region.pass_energy)
35
-
36
- # Set detector settings, wait for them all to have completed
37
- # fmt: off
38
- yield from bps.mv(
39
- analyser.region_name, region.name,
40
- analyser.energy_mode, region.energy_mode,
41
- analyser.excitation_energy, excitation_energy,
42
- analyser.low_energy, low_energy,
43
- analyser.high_energy, high_energy,
44
- analyser.slices, region.slices,
45
- analyser.lens_mode, region.lens_mode,
46
- analyser.pass_energy, pass_energy,
47
- analyser.iterations, region.iterations,
48
- analyser.acquisition_mode, region.acquisition_mode,
49
- )
50
- # fmt: on
51
-
52
-
53
- @plan
54
- def configure_specs(
55
- analyser: SpecsAnalyserDriverIO, region: SpecsRegion, excitation_energy: float
56
- ) -> MsgGenerator:
57
- yield from configure_analyser(analyser, region, excitation_energy)
58
- # fmt: off
59
- yield from bps.mv(
60
- analyser.snapshot_values, region.values,
61
- analyser.psu_mode, region.psu_mode,
62
- )
63
- # fmt: on
64
- if region.acquisition_mode == "Fixed Transmission":
65
- yield from bps.mv(analyser.centre_energy, region.centre_energy)
66
-
67
- if region.acquisition_mode == "Fixed Energy":
68
- yield from bps.mv(analyser.energy_step, region.energy_step)
69
-
70
-
71
- @plan
72
- def configure_vgscienta(
73
- analyser: VGScientaAnalyserDriverIO, region: VGScientaRegion, excitation_energy
74
- ) -> MsgGenerator:
75
- yield from configure_analyser(analyser, region, excitation_energy)
76
- centre_energy = to_kinetic_energy(
77
- region.fix_energy, region.energy_mode, excitation_energy
78
- )
79
-
80
- # fmt: off
81
- yield from bps.mv(
82
- analyser.centre_energy, centre_energy,
83
- analyser.energy_step, region.energy_step,
84
- analyser.first_x_channel, region.first_x_channel,
85
- analyser.first_y_channel, region.first_y_channel,
86
- analyser.x_channel_size, region.x_channel_size(),
87
- analyser.y_channel_size, region.y_channel_size(),
88
- analyser.detector_mode, region.detector_mode,
89
- analyser.excitation_energy_source, region.excitation_energy_source,
90
- analyser.image_mode, ADImageMode.SINGLE,
91
- )
92
- # fmt: on