dls-dodal 1.47.0__py3-none-any.whl → 1.49.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 (61) hide show
  1. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/METADATA +3 -2
  2. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/RECORD +59 -49
  3. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/aithre.py +21 -0
  6. dodal/beamlines/b01_1.py +1 -1
  7. dodal/beamlines/b16.py +65 -0
  8. dodal/beamlines/b18.py +38 -0
  9. dodal/beamlines/i03.py +21 -6
  10. dodal/beamlines/i04.py +17 -10
  11. dodal/beamlines/i10.py +41 -233
  12. dodal/beamlines/i18.py +1 -1
  13. dodal/beamlines/i19_1.py +9 -6
  14. dodal/beamlines/i24.py +5 -5
  15. dodal/beamlines/k11.py +35 -0
  16. dodal/common/beamlines/beamline_parameters.py +2 -28
  17. dodal/common/beamlines/device_helpers.py +1 -0
  18. dodal/devices/aithre_lasershaping/goniometer.py +36 -2
  19. dodal/devices/aithre_lasershaping/laser_robot.py +27 -0
  20. dodal/devices/apple2_undulator.py +257 -136
  21. dodal/devices/b16/__init__.py +0 -0
  22. dodal/devices/b16/detector.py +34 -0
  23. dodal/devices/bimorph_mirror.py +29 -36
  24. dodal/devices/electron_analyser/__init__.py +21 -1
  25. dodal/devices/electron_analyser/abstract/__init__.py +0 -6
  26. dodal/devices/electron_analyser/abstract/base_detector.py +16 -128
  27. dodal/devices/electron_analyser/abstract/base_driver_io.py +122 -8
  28. dodal/devices/electron_analyser/abstract/base_region.py +7 -3
  29. dodal/devices/electron_analyser/detector.py +141 -0
  30. dodal/devices/electron_analyser/enums.py +6 -0
  31. dodal/devices/electron_analyser/specs/__init__.py +3 -2
  32. dodal/devices/electron_analyser/specs/detector.py +6 -22
  33. dodal/devices/electron_analyser/specs/driver_io.py +27 -3
  34. dodal/devices/electron_analyser/specs/enums.py +8 -0
  35. dodal/devices/electron_analyser/specs/region.py +3 -2
  36. dodal/devices/electron_analyser/types.py +30 -4
  37. dodal/devices/electron_analyser/util.py +1 -1
  38. dodal/devices/electron_analyser/vgscienta/__init__.py +3 -2
  39. dodal/devices/electron_analyser/vgscienta/detector.py +9 -23
  40. dodal/devices/electron_analyser/vgscienta/driver_io.py +33 -4
  41. dodal/devices/electron_analyser/vgscienta/enums.py +19 -0
  42. dodal/devices/electron_analyser/vgscienta/region.py +7 -23
  43. dodal/devices/fast_grid_scan.py +1 -1
  44. dodal/devices/i04/murko_results.py +93 -96
  45. dodal/devices/i10/__init__.py +0 -0
  46. dodal/devices/i10/i10_apple2.py +181 -126
  47. dodal/devices/i18/diode.py +37 -4
  48. dodal/devices/i22/nxsas.py +1 -1
  49. dodal/devices/mx_phase1/beamstop.py +23 -6
  50. dodal/devices/oav/oav_detector.py +101 -25
  51. dodal/devices/oav/oav_parameters.py +46 -16
  52. dodal/devices/oav/oav_to_redis_forwarder.py +2 -2
  53. dodal/devices/robot.py +20 -1
  54. dodal/devices/smargon.py +43 -4
  55. dodal/devices/zebra/zebra.py +8 -0
  56. dodal/plans/configure_arm_trigger_and_disarm_detector.py +167 -0
  57. dodal/plan_stubs/electron_analyser/__init__.py +0 -3
  58. dodal/plan_stubs/electron_analyser/configure_driver.py +0 -92
  59. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/entry_points.txt +0 -0
  60. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/licenses/LICENSE +0 -0
  61. {dls_dodal-1.47.0.dist-info → dls_dodal-1.49.0.dist-info}/top_level.txt +0 -0
@@ -5,14 +5,21 @@ from ophyd_async.core import (
5
5
  DEFAULT_TIMEOUT,
6
6
  AsyncStatus,
7
7
  LazyMock,
8
+ SignalR,
9
+ SignalRW,
8
10
  StandardReadable,
9
11
  derived_signal_r,
10
12
  soft_signal_rw,
11
13
  )
12
- from ophyd_async.epics.core import epics_signal_rw
14
+ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw
13
15
 
14
16
  from dodal.devices.areadetector.plugins.CAM import Cam
15
- from dodal.devices.oav.oav_parameters import DEFAULT_OAV_WINDOW, OAVConfig
17
+ from dodal.devices.oav.oav_parameters import (
18
+ DEFAULT_OAV_WINDOW,
19
+ OAVConfig,
20
+ OAVConfigBase,
21
+ OAVConfigBeamCentre,
22
+ )
16
23
  from dodal.devices.oav.snapshots.snapshot import Snapshot
17
24
  from dodal.devices.oav.snapshots.snapshot_with_grid import SnapshotWithGrid
18
25
 
@@ -30,7 +37,21 @@ def _get_correct_zoom_string(zoom: str) -> str:
30
37
  return zoom
31
38
 
32
39
 
33
- class ZoomController(StandardReadable, Movable[str]):
40
+ class BaseZoomController(StandardReadable, Movable[str]):
41
+ level: SignalRW[str]
42
+ percentage: SignalRW[float]
43
+
44
+
45
+ class NullZoomController(BaseZoomController):
46
+ def __init__(self):
47
+ self.level = soft_signal_rw(str, "1.0x")
48
+ self.percentage = soft_signal_rw(float, 100)
49
+
50
+ def set(self, value):
51
+ raise Exception("Attempting to set zoom level of a null zoom controller")
52
+
53
+
54
+ class ZoomController(BaseZoomController):
34
55
  """
35
56
  Device to control the zoom level. This should be set like
36
57
  o = OAV(name="oav")
@@ -53,12 +74,27 @@ class ZoomController(StandardReadable, Movable[str]):
53
74
 
54
75
 
55
76
  class OAV(StandardReadable):
56
- def __init__(self, prefix: str, config: OAVConfig, name: str = ""):
77
+ beam_centre_i: SignalR[int]
78
+ beam_centre_j: SignalR[int]
79
+
80
+ def __init__(
81
+ self,
82
+ prefix: str,
83
+ config: OAVConfigBase,
84
+ name: str = "",
85
+ zoom_controller: BaseZoomController | None = None,
86
+ ):
57
87
  self.oav_config = config
58
88
  self._prefix = prefix
59
89
  self._name = name
60
90
  _bl_prefix = prefix.split("-")[0]
61
- self.zoom_controller = ZoomController(f"{_bl_prefix}-EA-OAV-01:FZOOM:", name)
91
+
92
+ if not zoom_controller:
93
+ self.zoom_controller = ZoomController(
94
+ f"{_bl_prefix}-EA-OAV-01:FZOOM:", name
95
+ )
96
+ else:
97
+ self.zoom_controller = zoom_controller
62
98
 
63
99
  self.cam = Cam(f"{prefix}CAM:", name=name)
64
100
  with self.add_children_as_readables():
@@ -79,18 +115,6 @@ class OAV(StandardReadable):
79
115
  size=self.sizes[Coords.Y],
80
116
  coord=soft_signal_rw(datatype=int, initial_value=Coords.Y.value),
81
117
  )
82
- self.beam_centre_i = derived_signal_r(
83
- self._get_beam_position,
84
- zoom_level=self.zoom_controller.level,
85
- size=self.sizes[Coords.X],
86
- coord=soft_signal_rw(datatype=int, initial_value=Coords.X.value),
87
- )
88
- self.beam_centre_j = derived_signal_r(
89
- self._get_beam_position,
90
- zoom_level=self.zoom_controller.level,
91
- size=self.sizes[Coords.Y],
92
- coord=soft_signal_rw(datatype=int, initial_value=Coords.Y.value),
93
- )
94
118
  self.snapshot = Snapshot(
95
119
  f"{self._prefix}MJPG:",
96
120
  self._name,
@@ -107,14 +131,6 @@ class OAV(StandardReadable):
107
131
  value = self.parameters[_zoom].microns_per_pixel[coord]
108
132
  return value * DEFAULT_OAV_WINDOW[coord] / size
109
133
 
110
- def _get_beam_position(self, zoom_level: str, size: int, coord: int) -> int:
111
- """Extracts the beam location in pixels `xCentre` `yCentre`, for a requested \
112
- zoom level. """
113
- _zoom = self._read_current_zoom(zoom_level)
114
- value = self.parameters[_zoom].crosshair[coord]
115
-
116
- return int(value * size / DEFAULT_OAV_WINDOW[coord])
117
-
118
134
  async def connect(
119
135
  self,
120
136
  mock: bool | LazyMock = False,
@@ -124,3 +140,63 @@ class OAV(StandardReadable):
124
140
  self.parameters = self.oav_config.get_parameters()
125
141
 
126
142
  return await super().connect(mock, timeout, force_reconnect)
143
+
144
+
145
+ class OAVBeamCentreFile(OAV):
146
+ """OAV device that reads its beam centre values from a file. The config parameter
147
+ must be a OAVConfigBeamCentre object, as this contains a filepath to where the beam
148
+ centre values are stored.
149
+ """
150
+
151
+ def __init__(
152
+ self,
153
+ prefix: str,
154
+ config: OAVConfigBeamCentre,
155
+ name: str = "",
156
+ zoom_controller: BaseZoomController | None = None,
157
+ ):
158
+ super().__init__(prefix, config, name, zoom_controller)
159
+
160
+ with self.add_children_as_readables():
161
+ self.beam_centre_i = derived_signal_r(
162
+ self._get_beam_position,
163
+ zoom_level=self.zoom_controller.level,
164
+ size=self.sizes[Coords.X],
165
+ coord=soft_signal_rw(datatype=int, initial_value=Coords.X.value),
166
+ )
167
+ self.beam_centre_j = derived_signal_r(
168
+ self._get_beam_position,
169
+ zoom_level=self.zoom_controller.level,
170
+ size=self.sizes[Coords.Y],
171
+ coord=soft_signal_rw(datatype=int, initial_value=Coords.Y.value),
172
+ )
173
+ # Set name so that new child signals get correct name
174
+ self.set_name(self.name)
175
+
176
+ def _get_beam_position(self, zoom_level: str, size: int, coord: int) -> int:
177
+ """Extracts the beam location in pixels `xCentre` `yCentre`, for a requested \
178
+ zoom level. """
179
+ _zoom = self._read_current_zoom(zoom_level)
180
+ value = self.parameters[_zoom].crosshair[coord]
181
+ return int(value * size / DEFAULT_OAV_WINDOW[coord])
182
+
183
+
184
+ class OAVBeamCentrePV(OAV):
185
+ """OAV device that reads its beam centre values from PVs."""
186
+
187
+ def __init__(
188
+ self,
189
+ prefix: str,
190
+ config: OAVConfig,
191
+ name: str = "",
192
+ zoom_controller: BaseZoomController | None = None,
193
+ overlay_channel: int = 1,
194
+ ):
195
+ with self.add_children_as_readables():
196
+ self.beam_centre_i = epics_signal_r(
197
+ int, prefix + f"OVER:{overlay_channel}:CenterX"
198
+ )
199
+ self.beam_centre_j = epics_signal_r(
200
+ int, prefix + f"OVER:{overlay_channel}:CenterY"
201
+ )
202
+ super().__init__(prefix, config, name, zoom_controller)
@@ -1,8 +1,9 @@
1
1
  import json
2
2
  import xml.etree.ElementTree as et
3
+ from abc import abstractmethod
3
4
  from collections import ChainMap
4
5
  from dataclasses import dataclass
5
- from typing import Any
6
+ from typing import Any, Generic, TypeVar
6
7
  from xml.etree.ElementTree import Element
7
8
 
8
9
  # GDA currently assumes this aspect ratio for the OAV window size.
@@ -107,22 +108,19 @@ class OAVParameters:
107
108
  @dataclass
108
109
  class ZoomParams:
109
110
  microns_per_pixel: tuple[float, float]
111
+
112
+
113
+ @dataclass
114
+ class ZoomParamsCrosshair(ZoomParams):
110
115
  crosshair: tuple[int, int]
111
116
 
112
117
 
113
- class OAVConfig:
114
- """ Read the OAV config files and return a dictionary of {'zoom_level': ZoomParams}\
115
- with information about microns per pixels and crosshairs.
116
- """
118
+ ParamType = TypeVar("ParamType", bound="ZoomParams")
117
119
 
118
- def __init__(self, zoom_params_file: str, display_config_file: str):
119
- self.zoom_params = self._get_zoom_params(zoom_params_file)
120
- self.display_config = self._get_display_config(display_config_file)
121
120
 
122
- def _get_display_config(self, display_config_file: str):
123
- with open(display_config_file) as f:
124
- file_lines = f.readlines()
125
- return file_lines
121
+ class OAVConfigBase(Generic[ParamType]):
122
+ def __init__(self, zoom_params_file: str):
123
+ self.zoom_params = self._get_zoom_params(zoom_params_file)
126
124
 
127
125
  def _get_zoom_params(self, zoom_params_file: str):
128
126
  tree = et.parse(zoom_params_file)
@@ -138,6 +136,39 @@ class OAVConfig:
138
136
  um_per_pix[zoom] = (um_pix_x, um_pix_y)
139
137
  return um_per_pix
140
138
 
139
+ @abstractmethod
140
+ def get_parameters(self) -> dict[str, ParamType]: ...
141
+
142
+
143
+ class OAVConfig(OAVConfigBase[ZoomParams]):
144
+ def get_parameters(self) -> dict[str, ZoomParams]:
145
+ config = {}
146
+ um_xy = self._read_zoom_params()
147
+ for zoom_key in list(um_xy.keys()):
148
+ config[zoom_key] = ZoomParams(
149
+ microns_per_pixel=um_xy[zoom_key],
150
+ )
151
+ return config
152
+
153
+
154
+ class OAVConfigBeamCentre(OAVConfigBase[ZoomParamsCrosshair]):
155
+ """ Read the OAV config files and return a dictionary of {'zoom_level': ZoomParams}\
156
+ with information about microns per pixels and crosshairs.
157
+ """
158
+
159
+ def __init__(
160
+ self,
161
+ zoom_params_file: str,
162
+ display_config_file: str,
163
+ ):
164
+ self.display_config = self._get_display_config(display_config_file)
165
+ super().__init__(zoom_params_file)
166
+
167
+ def _get_display_config(self, display_config_file: str):
168
+ with open(display_config_file) as f:
169
+ file_lines = f.readlines()
170
+ return file_lines
171
+
141
172
  def _read_display_config(self) -> dict:
142
173
  crosshairs = {}
143
174
  for i in range(len(self.display_config)):
@@ -148,13 +179,12 @@ class OAVConfig:
148
179
  crosshairs[zoom] = (x, y)
149
180
  return crosshairs
150
181
 
151
- def get_parameters(self) -> dict[str, ZoomParams]:
182
+ def get_parameters(self) -> dict[str, ZoomParamsCrosshair]:
152
183
  config = {}
153
184
  um_xy = self._read_zoom_params()
154
185
  bc_xy = self._read_display_config()
155
186
  for zoom_key in list(bc_xy.keys()):
156
- config[zoom_key] = ZoomParams(
157
- microns_per_pixel=um_xy[zoom_key],
158
- crosshair=bc_xy[zoom_key],
187
+ config[zoom_key] = ZoomParamsCrosshair(
188
+ microns_per_pixel=um_xy[zoom_key], crosshair=bc_xy[zoom_key]
159
189
  )
160
190
  return config
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  from collections.abc import Awaitable, Callable
3
3
  from datetime import timedelta
4
- from enum import Enum
4
+ from enum import IntEnum
5
5
  from uuid import uuid4
6
6
 
7
7
  from aiohttp import ClientResponse, ClientSession
@@ -29,7 +29,7 @@ async def get_next_jpeg(response: ClientResponse) -> bytes:
29
29
  return line + await response.content.readuntil(JPEG_STOP_BYTE)
30
30
 
31
31
 
32
- class Source(Enum):
32
+ class Source(IntEnum):
33
33
  FULL_SCREEN = 0
34
34
  ROI = 1
35
35
 
dodal/devices/robot.py CHANGED
@@ -11,7 +11,12 @@ from ophyd_async.core import (
11
11
  set_and_wait_for_value,
12
12
  wait_for_value,
13
13
  )
14
- from ophyd_async.epics.core import epics_signal_r, epics_signal_rw_rbv, epics_signal_x
14
+ from ophyd_async.epics.core import (
15
+ epics_signal_r,
16
+ epics_signal_rw,
17
+ epics_signal_rw_rbv,
18
+ epics_signal_x,
19
+ )
15
20
 
16
21
  from dodal.log import LOGGER
17
22
 
@@ -88,6 +93,20 @@ class BartRobot(StandardReadable, Movable[SampleLocation]):
88
93
  self.controller_error = ErrorStatus(prefix + "CNTL")
89
94
 
90
95
  self.reset = epics_signal_x(prefix + "RESET.PROC")
96
+ self.stop = epics_signal_x(prefix + "ABORT.PROC")
97
+ self.init = epics_signal_x(prefix + "INIT.PROC")
98
+ self.soak = epics_signal_x(prefix + "SOAK.PROC")
99
+ self.home = epics_signal_x(prefix + "GOHM.PROC")
100
+ self.unload = epics_signal_x(prefix + "UNLD.PROC")
101
+ self.dry = epics_signal_x(prefix + "DRY.PROC")
102
+ self.open = epics_signal_x(prefix + "COLO.PROC")
103
+ self.close = epics_signal_x(prefix + "COLC.PROC")
104
+ self.cryomode_rbv = epics_signal_r(float, prefix + "CRYO_MODE_RBV")
105
+ self.cryomode = epics_signal_rw(str, prefix + "CRYO_MODE_CTRL")
106
+ self.gripper_temp = epics_signal_r(float, prefix + "GRIPPER_TEMP")
107
+ self.dewar_lid_temperature = epics_signal_rw(
108
+ float, prefix + "DW_1_TEMP", prefix + "DW_1_SET_POINT"
109
+ )
91
110
  super().__init__(name=name)
92
111
 
93
112
  async def pin_mounted_or_no_pin_found(self):
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