dls-dodal 1.46.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 (81) hide show
  1. {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/METADATA +2 -2
  2. {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/RECORD +74 -63
  3. {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/WHEEL +1 -1
  4. dodal/_version.py +2 -2
  5. dodal/beamlines/__init__.py +0 -1
  6. dodal/beamlines/aithre.py +6 -0
  7. dodal/beamlines/b01_1.py +1 -1
  8. dodal/beamlines/b07.py +2 -6
  9. dodal/beamlines/b07_1.py +1 -3
  10. dodal/beamlines/i03.py +33 -21
  11. dodal/beamlines/i04.py +65 -26
  12. dodal/beamlines/i09.py +1 -3
  13. dodal/beamlines/i09_1.py +1 -3
  14. dodal/beamlines/i18.py +1 -1
  15. dodal/beamlines/i19_1.py +9 -6
  16. dodal/beamlines/i23.py +17 -1
  17. dodal/beamlines/i24.py +5 -5
  18. dodal/beamlines/p38.py +1 -1
  19. dodal/beamlines/p60.py +2 -6
  20. dodal/beamlines/p99.py +48 -4
  21. dodal/common/beamlines/beamline_parameters.py +3 -30
  22. dodal/common/data_util.py +4 -0
  23. dodal/devices/aithre_lasershaping/goniometer.py +36 -2
  24. dodal/devices/aithre_lasershaping/laser_robot.py +27 -0
  25. dodal/devices/aperturescatterguard.py +47 -47
  26. dodal/devices/current_amplifiers/struck_scaler_counter.py +1 -1
  27. dodal/devices/diamond_filter.py +5 -17
  28. dodal/devices/eiger.py +1 -1
  29. dodal/devices/electron_analyser/__init__.py +18 -0
  30. dodal/devices/electron_analyser/abstract/__init__.py +22 -0
  31. dodal/devices/electron_analyser/abstract/base_detector.py +223 -0
  32. dodal/devices/electron_analyser/abstract/base_driver_io.py +230 -0
  33. dodal/devices/electron_analyser/{abstract_region.py → abstract/base_region.py} +3 -9
  34. dodal/devices/electron_analyser/specs/__init__.py +10 -0
  35. dodal/devices/electron_analyser/specs/detector.py +13 -0
  36. dodal/devices/electron_analyser/specs/driver_io.py +89 -0
  37. dodal/devices/electron_analyser/{specs_region.py → specs/region.py} +1 -1
  38. dodal/devices/electron_analyser/types.py +6 -0
  39. dodal/devices/electron_analyser/util.py +13 -0
  40. dodal/devices/electron_analyser/vgscienta/__init__.py +11 -0
  41. dodal/devices/electron_analyser/vgscienta/detector.py +22 -0
  42. dodal/devices/electron_analyser/vgscienta/driver_io.py +67 -0
  43. dodal/devices/electron_analyser/{vgscienta_region.py → vgscienta/region.py} +1 -2
  44. dodal/devices/fast_grid_scan.py +7 -9
  45. dodal/devices/i03/__init__.py +3 -0
  46. dodal/devices/i04/__init__.py +3 -0
  47. dodal/devices/i04/constants.py +9 -0
  48. dodal/devices/i04/murko_results.py +192 -0
  49. dodal/devices/i10/diagnostics.py +9 -61
  50. dodal/devices/i18/diode.py +37 -4
  51. dodal/devices/i24/focus_mirrors.py +9 -13
  52. dodal/devices/i24/pilatus_metadata.py +9 -9
  53. dodal/devices/i24/pmac.py +19 -14
  54. dodal/devices/{i03 → mx_phase1}/beamstop.py +26 -15
  55. dodal/devices/oav/oav_calculations.py +2 -2
  56. dodal/devices/oav/oav_detector.py +80 -32
  57. dodal/devices/oav/oav_parameters.py +46 -16
  58. dodal/devices/oav/oav_to_redis_forwarder.py +2 -2
  59. dodal/devices/oav/utils.py +2 -2
  60. dodal/devices/p99/andor2_point.py +41 -0
  61. dodal/devices/positioner.py +49 -0
  62. dodal/devices/robot.py +20 -1
  63. dodal/devices/smargon.py +43 -4
  64. dodal/devices/tetramm.py +5 -2
  65. dodal/devices/util/adjuster_plans.py +1 -1
  66. dodal/devices/zebra/zebra.py +8 -0
  67. dodal/devices/zebra/zebra_constants_mapping.py +1 -1
  68. dodal/devices/zocalo/__init__.py +0 -3
  69. dodal/devices/zocalo/zocalo_results.py +6 -32
  70. dodal/log.py +14 -14
  71. dodal/plans/configure_arm_trigger_and_disarm_detector.py +167 -0
  72. dodal/common/signal_utils.py +0 -88
  73. dodal/devices/electron_analyser/abstract_analyser_io.py +0 -47
  74. dodal/devices/electron_analyser/specs_analyser_io.py +0 -19
  75. dodal/devices/electron_analyser/vgscienta_analyser_io.py +0 -26
  76. dodal/devices/logging_ophyd_device.py +0 -17
  77. dodal/plan_stubs/electron_analyser/__init__.py +0 -0
  78. dodal/plan_stubs/electron_analyser/configure_controller.py +0 -80
  79. {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/entry_points.txt +0 -0
  80. {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/licenses/LICENSE +0 -0
  81. {dls_dodal-1.46.0.dist-info → dls_dodal-1.48.0.dist-info}/top_level.txt +0 -0
@@ -2,17 +2,18 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
 
5
- from bluesky.protocols import Movable, Preparable
5
+ from bluesky.protocols import Preparable
6
6
  from ophyd_async.core import (
7
7
  AsyncStatus,
8
8
  StandardReadable,
9
9
  StandardReadableFormat,
10
10
  StrictEnum,
11
+ derived_signal_r,
12
+ derived_signal_rw,
11
13
  )
12
14
  from pydantic import BaseModel, Field
13
15
 
14
16
  from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
15
- from dodal.common.signal_utils import create_r_hardware_backed_soft_signal
16
17
  from dodal.devices.aperture import Aperture
17
18
  from dodal.devices.scatterguard import Scatterguard
18
19
 
@@ -123,21 +124,21 @@ def load_positions_from_beamline_parameters(
123
124
  }
124
125
 
125
126
 
126
- class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable):
127
+ class ApertureScatterguard(StandardReadable, Preparable):
127
128
  """Move the aperture and scatterguard assembly in a safe way. There are two ways to
128
129
  interact with the device depending on if you want simplicity or move flexibility.
129
130
 
130
131
  Examples:
131
132
  The simple interface is using::
132
133
 
133
- await aperture_scatterguard.set(ApertureValue.LARGE)
134
+ await aperture_scatterguard.selected_aperture.set(ApertureValue.LARGE)
134
135
 
135
136
  This will move the assembly so that the large aperture is in the beam, regardless
136
137
  of where the assembly currently is.
137
138
 
138
139
  We may also want to move the assembly out of the beam with::
139
140
 
140
- await aperture_scatterguard.set(ApertureValue.OUT_OF_BEAM)
141
+ await aperture_scatterguard.selected_aperture.set(ApertureValue.OUT_OF_BEAM)
141
142
 
142
143
  Note, to make sure we do this as quickly as possible, the scatterguard will stay
143
144
  in the same position relative to the aperture.
@@ -149,7 +150,7 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
149
150
 
150
151
  Then, at a later time, move back into the beam::
151
152
 
152
- await aperture_scatterguard.set(ApertureValue.LARGE)
153
+ await aperture_scatterguard.selected_aperture.set(ApertureValue.LARGE)
153
154
 
154
155
  Given the prepare has been done this move will now be faster as only the y is
155
156
  left to move.
@@ -164,11 +165,24 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
164
165
  ) -> None:
165
166
  self.aperture = Aperture(prefix + "-MO-MAPT-01:")
166
167
  self.scatterguard = Scatterguard(prefix + "-MO-SCAT-01:")
167
- self.radius = create_r_hardware_backed_soft_signal(
168
- float, self._get_current_radius, units="µm"
169
- )
170
168
  self._loaded_positions = loaded_positions
171
169
  self._tolerances = tolerances
170
+ with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
171
+ self.selected_aperture = derived_signal_rw(
172
+ self._get_current_aperture_position,
173
+ self._set_current_aperture_position,
174
+ large=self.aperture.large,
175
+ medium=self.aperture.medium,
176
+ small=self.aperture.small,
177
+ current_ap_y=self.aperture.y.user_readback,
178
+ )
179
+
180
+ self.radius = derived_signal_r(
181
+ self._get_current_radius,
182
+ current_aperture=self.selected_aperture,
183
+ derived_units="µm",
184
+ )
185
+
172
186
  self.add_readables(
173
187
  [
174
188
  self.aperture.x.user_readback,
@@ -180,17 +194,9 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
180
194
  ],
181
195
  )
182
196
 
183
- with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL):
184
- self.selected_aperture = create_r_hardware_backed_soft_signal(
185
- ApertureValue, self._get_current_aperture_position
186
- )
187
-
188
197
  super().__init__(name)
189
198
 
190
- @AsyncStatus.wrap
191
- async def set(self, value: ApertureValue):
192
- """This set will move the aperture into the beam or move the whole assembly out"""
193
-
199
+ async def _set_current_aperture_position(self, value: ApertureValue) -> None:
194
200
  position = self._loaded_positions[value]
195
201
  await self._check_safe_to_move(position.aperture_z)
196
202
 
@@ -231,6 +237,27 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
231
237
  "triggering another move."
232
238
  )
233
239
 
240
+ def _get_current_radius(self, current_aperture: ApertureValue) -> float:
241
+ return self._loaded_positions[current_aperture].radius
242
+
243
+ def _is_out_of_beam(self, current_ap_y: float) -> bool:
244
+ out_ap_y = self._loaded_positions[ApertureValue.OUT_OF_BEAM].aperture_y
245
+ return current_ap_y <= out_ap_y + self._tolerances.aperture_y
246
+
247
+ def _get_current_aperture_position(
248
+ self, large: float, medium: float, small: float, current_ap_y: float
249
+ ) -> ApertureValue:
250
+ if large == 1:
251
+ return ApertureValue.LARGE
252
+ elif medium == 1:
253
+ return ApertureValue.MEDIUM
254
+ elif small == 1:
255
+ return ApertureValue.SMALL
256
+ elif self._is_out_of_beam(current_ap_y):
257
+ return ApertureValue.OUT_OF_BEAM
258
+
259
+ raise InvalidApertureMove("Current aperture/scatterguard state unrecognised")
260
+
234
261
  async def _safe_move_whilst_in_beam(self, position: AperturePosition):
235
262
  """
236
263
  Move the aperture and scatterguard combo safely to a new position.
@@ -282,33 +309,6 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
282
309
  self.scatterguard.y.set(scatterguard_y),
283
310
  )
284
311
 
285
- async def _is_out_of_beam(self) -> bool:
286
- current_ap_y = await self.aperture.y.user_readback.get_value()
287
- out_ap_y = self._loaded_positions[ApertureValue.OUT_OF_BEAM].aperture_y
288
- return current_ap_y <= out_ap_y + self._tolerances.aperture_y
289
-
290
- async def _get_current_aperture_position(self) -> ApertureValue:
291
- """
292
- Returns the current aperture position using readback values
293
- for SMALL, MEDIUM, LARGE. ROBOT_LOAD position defined when
294
- mini aperture y <= ROBOT_LOAD.location.aperture_y + tolerance.
295
- If no position is found then raises InvalidApertureMove.
296
- """
297
- if await self.aperture.large.get_value(cached=False) == 1:
298
- return ApertureValue.LARGE
299
- elif await self.aperture.medium.get_value(cached=False) == 1:
300
- return ApertureValue.MEDIUM
301
- elif await self.aperture.small.get_value(cached=False) == 1:
302
- return ApertureValue.SMALL
303
- elif await self._is_out_of_beam():
304
- return ApertureValue.OUT_OF_BEAM
305
-
306
- raise InvalidApertureMove("Current aperture/scatterguard state unrecognised")
307
-
308
- async def _get_current_radius(self) -> float:
309
- current_value = await self._get_current_aperture_position()
310
- return self._loaded_positions[current_value].radius
311
-
312
312
  @AsyncStatus.wrap
313
313
  async def prepare(self, value: ApertureValue):
314
314
  """Moves the assembly to the position for the specified aperture, whilst keeping
@@ -317,7 +317,7 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
317
317
  Moving the assembly whilst out of the beam has no collision risk so we can just
318
318
  move all the motors together.
319
319
  """
320
- if await self._is_out_of_beam():
320
+ if self._is_out_of_beam(await self.aperture.y.user_readback.get_value()):
321
321
  aperture_x, _, aperture_z, scatterguard_x, scatterguard_y = (
322
322
  self._loaded_positions[value].values
323
323
  )
@@ -329,4 +329,4 @@ class ApertureScatterguard(StandardReadable, Movable[ApertureValue], Preparable)
329
329
  self.scatterguard.y.set(scatterguard_y),
330
330
  )
331
331
  else:
332
- await self.set(value)
332
+ await self.selected_aperture.set(value)
@@ -18,7 +18,7 @@ class CountMode(StrictEnum):
18
18
 
19
19
  class CountState(StrictEnum):
20
20
  DONE = "Done"
21
- COUNT = "Count" # type: ignore
21
+ COUNT = "Count"
22
22
 
23
23
 
24
24
  COUNT_PER_VOLTAGE = 100000
@@ -1,8 +1,8 @@
1
- from typing import Generic, TypeVar
1
+ from typing import TypeVar
2
2
 
3
- from ophyd_async.core import StandardReadable, StrictEnum
4
- from ophyd_async.epics.core import epics_signal_rw
5
- from ophyd_async.epics.motor import Motor
3
+ from ophyd_async.core import StrictEnum
4
+
5
+ from dodal.devices.positioner import Positioner1D
6
6
 
7
7
 
8
8
  class _Filters(StrictEnum):
@@ -25,22 +25,10 @@ class I04Filters(_Filters):
25
25
  T = TypeVar("T", bound=_Filters)
26
26
 
27
27
 
28
- class DiamondFilter(StandardReadable, Generic[T]):
28
+ class DiamondFilter(Positioner1D[T]):
29
29
  """
30
30
  A filter set that is used to reduce the heat load on the monochromator.
31
31
 
32
32
  It has 4 slots that can contain filters of different thickness. Changing the thickness
33
33
  signal will move the filter set to select this filter.
34
34
  """
35
-
36
- def __init__(
37
- self,
38
- prefix: str,
39
- data_type: type[T],
40
- name: str = "",
41
- ) -> None:
42
- with self.add_children_as_readables():
43
- self.y_motor = Motor(prefix + "Y")
44
- self.thickness = epics_signal_rw(data_type, f"{prefix}Y:MP:SELECT")
45
-
46
- super().__init__(name)
dodal/devices/eiger.py CHANGED
@@ -35,7 +35,7 @@ class InternalEigerTriggerMode(Enum):
35
35
  AVAILABLE_TIMEOUTS = {
36
36
  "i03": EigerTimeouts(
37
37
  stale_params_timeout=60,
38
- general_status_timeout=10,
38
+ general_status_timeout=20,
39
39
  meta_file_ready_timeout=30,
40
40
  all_frames_timeout=120, # Long timeout for meta file to compensate for filesystem issues
41
41
  arming_timeout=60,
@@ -0,0 +1,18 @@
1
+ from .abstract.base_detector import (
2
+ ElectronAnalyserDetector,
3
+ ElectronAnalyserRegionDetector,
4
+ TElectronAnalyserDetector,
5
+ TElectronAnalyserRegionDetector,
6
+ )
7
+ from .types import EnergyMode
8
+ from .util import to_binding_energy, to_kinetic_energy
9
+
10
+ __all__ = [
11
+ "to_binding_energy",
12
+ "to_kinetic_energy",
13
+ "EnergyMode",
14
+ "ElectronAnalyserDetector",
15
+ "TElectronAnalyserDetector",
16
+ "ElectronAnalyserRegionDetector",
17
+ "TElectronAnalyserRegionDetector",
18
+ ]
@@ -0,0 +1,22 @@
1
+ from .base_detector import (
2
+ AbstractAnalyserDriverIO,
3
+ AbstractElectronAnalyserDetector,
4
+ )
5
+ from .base_driver_io import AbstractAnalyserDriverIO, TAbstractAnalyserDriverIO
6
+ from .base_region import (
7
+ AbstractBaseRegion,
8
+ AbstractBaseSequence,
9
+ TAbstractBaseRegion,
10
+ TAbstractBaseSequence,
11
+ )
12
+
13
+ __all__ = [
14
+ "AbstractBaseRegion",
15
+ "AbstractBaseSequence",
16
+ "TAbstractBaseRegion",
17
+ "TAbstractBaseSequence",
18
+ "AbstractAnalyserDriverIO",
19
+ "AbstractElectronAnalyserDetector",
20
+ "AbstractAnalyserDriverIO",
21
+ "TAbstractAnalyserDriverIO",
22
+ ]
@@ -0,0 +1,223 @@
1
+ import asyncio
2
+ from abc import abstractmethod
3
+ from typing import Generic, TypeVar
4
+
5
+ from bluesky.protocols import Preparable, Reading, Stageable, Triggerable
6
+ from event_model import DataKey
7
+ from ophyd_async.core import (
8
+ AsyncConfigurable,
9
+ AsyncReadable,
10
+ AsyncStatus,
11
+ Device,
12
+ Reference,
13
+ )
14
+ from ophyd_async.epics.adcore import (
15
+ ADBaseController,
16
+ )
17
+ from ophyd_async.epics.motor import Motor
18
+
19
+ from dodal.common.data_util import load_json_file_to_class
20
+ from dodal.devices.electron_analyser.abstract.base_driver_io import (
21
+ AbstractAnalyserDriverIO,
22
+ TAbstractAnalyserDriverIO,
23
+ )
24
+ from dodal.devices.electron_analyser.abstract.base_region import (
25
+ TAbstractBaseRegion,
26
+ TAbstractBaseSequence,
27
+ )
28
+
29
+
30
+ class ElectronAnalyserController(ADBaseController[AbstractAnalyserDriverIO]):
31
+ def get_deadtime(self, exposure: float | None) -> float:
32
+ return 0
33
+
34
+
35
+ class AbstractElectronAnalyserDetector(
36
+ Device,
37
+ Stageable,
38
+ Triggerable,
39
+ AsyncReadable,
40
+ AsyncConfigurable,
41
+ Generic[TAbstractAnalyserDriverIO],
42
+ ):
43
+ """
44
+ Detector for data acquisition of electron analyser. Can only acquire using settings
45
+ already configured for the device.
46
+
47
+ If possible, this should be changed to inherit from a StandardDetector. Currently,
48
+ StandardDetector forces you to use a file writer which doesn't apply here.
49
+ See issue https://github.com/bluesky/ophyd-async/issues/888
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ name: str,
55
+ driver: TAbstractAnalyserDriverIO,
56
+ ):
57
+ self.controller: ElectronAnalyserController = ElectronAnalyserController(
58
+ driver=driver
59
+ )
60
+ super().__init__(name)
61
+
62
+ @AsyncStatus.wrap
63
+ async def trigger(self) -> None:
64
+ await self.controller.arm()
65
+ await self.controller.wait_for_idle()
66
+
67
+ @AsyncStatus.wrap
68
+ async def stage(self) -> None:
69
+ """Make sure the detector is idle and ready to be used."""
70
+ await asyncio.gather(self.controller.disarm())
71
+
72
+ @AsyncStatus.wrap
73
+ async def unstage(self) -> None:
74
+ """Disarm the detector."""
75
+ await asyncio.gather(self.controller.disarm())
76
+
77
+ async def read(self) -> dict[str, Reading]:
78
+ return await self.driver.read()
79
+
80
+ async def describe(self) -> dict[str, DataKey]:
81
+ data = await self.driver.describe()
82
+ # Correct the shape for image
83
+ prefix = self.driver.name + "-"
84
+ energy_size = len(await self.driver.energy_axis.get_value())
85
+ angle_size = len(await self.driver.angle_axis.get_value())
86
+ data[prefix + "image"]["shape"] = [angle_size, energy_size]
87
+ return data
88
+
89
+ async def read_configuration(self) -> dict[str, Reading]:
90
+ return await self.driver.read_configuration()
91
+
92
+ async def describe_configuration(self) -> dict[str, DataKey]:
93
+ return await self.driver.describe_configuration()
94
+
95
+ @property
96
+ @abstractmethod
97
+ def driver(self) -> TAbstractAnalyserDriverIO:
98
+ """
99
+ Define common property for all implementations to access the driver. Some
100
+ implementations will store this as a reference so it doesn't have conflicting
101
+ parents.
102
+
103
+ Returns:
104
+ instance of the driver.
105
+ """
106
+
107
+
108
+ class ElectronAnalyserRegionDetector(
109
+ AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
110
+ Preparable,
111
+ Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
112
+ ):
113
+ """
114
+ Extends electron analyser detector to configure specific region settings before data
115
+ acqusition. This object must be passed in a driver and store it as a reference. It
116
+ is designed to only exist inside a plan.
117
+ """
118
+
119
+ def __init__(
120
+ self, name: str, driver: TAbstractAnalyserDriverIO, region: TAbstractBaseRegion
121
+ ):
122
+ self._driver_ref = Reference(driver)
123
+ self.region = region
124
+ super().__init__(name, driver)
125
+
126
+ @property
127
+ def driver(self) -> TAbstractAnalyserDriverIO:
128
+ # Store as a reference, this implementation will be given a driver so needs to
129
+ # make sure we don't get conflicting parents.
130
+ return self._driver_ref()
131
+
132
+ @AsyncStatus.wrap
133
+ async def prepare(self, value: Motor) -> None:
134
+ """
135
+ Prepare driver with the region stored and energy_source motor.
136
+
137
+ Args:
138
+ value: The excitation energy source that the region has selected.
139
+ """
140
+ excitation_energy_source = value
141
+ await self.driver.prepare(excitation_energy_source)
142
+ await self.driver.set(self.region)
143
+
144
+
145
+ TElectronAnalyserRegionDetector = TypeVar(
146
+ "TElectronAnalyserRegionDetector",
147
+ bound=ElectronAnalyserRegionDetector,
148
+ )
149
+
150
+
151
+ class ElectronAnalyserDetector(
152
+ AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
153
+ Generic[
154
+ TAbstractAnalyserDriverIO,
155
+ TAbstractBaseSequence,
156
+ TAbstractBaseRegion,
157
+ ],
158
+ ):
159
+ """
160
+ Electron analyser detector with the additional functionality to load a sequence file
161
+ and create a list of temporary ElectronAnalyserRegionDetector objects. These will
162
+ setup configured region settings before data acquisition.
163
+ """
164
+
165
+ def __init__(
166
+ self,
167
+ prefix: str,
168
+ sequence_class: type[TAbstractBaseSequence],
169
+ driver: TAbstractAnalyserDriverIO,
170
+ name: str = "",
171
+ ):
172
+ # Pass in driver
173
+ self._driver = driver
174
+ self._sequence_class = sequence_class
175
+ super().__init__(name, self.driver)
176
+
177
+ @property
178
+ def driver(self) -> TAbstractAnalyserDriverIO:
179
+ # This implementation creates the driver and wants this to be the parent so it
180
+ # can be used with connect() method.
181
+ return self._driver
182
+
183
+ def load_sequence(self, filename: str) -> TAbstractBaseSequence:
184
+ """
185
+ Load the sequence data from a provided json file into a sequence class.
186
+
187
+ Args:
188
+ filename: Path to the sequence file containing the region data.
189
+
190
+ Returns:
191
+ Pydantic model representing the sequence file.
192
+ """
193
+ return load_json_file_to_class(self._sequence_class, filename)
194
+
195
+ def create_region_detector_list(
196
+ self, filename: str, enabled_only=True
197
+ ) -> list[
198
+ ElectronAnalyserRegionDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion]
199
+ ]:
200
+ """
201
+ Create a list of detectors equal to the number of regions in a sequence file.
202
+ Each detector is responsible for setting up a specific region.
203
+
204
+ Args:
205
+ filename: Path to the sequence file containing the region data.
206
+ enabled_only: If true, only include the region if enabled is True.
207
+
208
+ Returns:
209
+ List of ElectronAnalyserRegionDetector, equal to the number of regions in
210
+ the sequence file.
211
+ """
212
+ seq = self.load_sequence(filename)
213
+ regions = seq.get_enabled_regions() if enabled_only else seq.regions
214
+ return [
215
+ ElectronAnalyserRegionDetector(self.name + "_" + r.name, self.driver, r)
216
+ for r in regions
217
+ ]
218
+
219
+
220
+ TElectronAnalyserDetector = TypeVar(
221
+ "TElectronAnalyserDetector",
222
+ bound=ElectronAnalyserDetector,
223
+ )