ophyd-async 0.9.0a1__py3-none-any.whl → 0.9.0a2__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 (97) hide show
  1. ophyd_async/_version.py +1 -1
  2. ophyd_async/core/__init__.py +13 -20
  3. ophyd_async/core/_detector.py +61 -37
  4. ophyd_async/core/_device.py +102 -80
  5. ophyd_async/core/_device_filler.py +17 -8
  6. ophyd_async/core/_flyer.py +2 -2
  7. ophyd_async/core/_readable.py +30 -23
  8. ophyd_async/core/_settings.py +104 -0
  9. ophyd_async/core/_signal.py +55 -17
  10. ophyd_async/core/_signal_backend.py +4 -1
  11. ophyd_async/core/_soft_signal_backend.py +2 -1
  12. ophyd_async/core/_table.py +18 -10
  13. ophyd_async/core/_utils.py +5 -3
  14. ophyd_async/core/_yaml_settings.py +64 -0
  15. ophyd_async/epics/adandor/__init__.py +9 -0
  16. ophyd_async/epics/adandor/_andor.py +45 -0
  17. ophyd_async/epics/adandor/_andor_controller.py +49 -0
  18. ophyd_async/epics/adandor/_andor_io.py +36 -0
  19. ophyd_async/epics/adaravis/__init__.py +3 -1
  20. ophyd_async/epics/adaravis/_aravis.py +23 -37
  21. ophyd_async/epics/adaravis/_aravis_controller.py +13 -22
  22. ophyd_async/epics/adcore/__init__.py +15 -8
  23. ophyd_async/epics/adcore/_core_detector.py +41 -0
  24. ophyd_async/epics/adcore/_core_io.py +35 -10
  25. ophyd_async/epics/adcore/_core_logic.py +98 -86
  26. ophyd_async/epics/adcore/_core_writer.py +219 -0
  27. ophyd_async/epics/adcore/_hdf_writer.py +38 -62
  28. ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
  29. ophyd_async/epics/adcore/_single_trigger.py +4 -3
  30. ophyd_async/epics/adcore/_tiff_writer.py +26 -0
  31. ophyd_async/epics/adcore/_utils.py +2 -1
  32. ophyd_async/epics/adkinetix/_kinetix.py +29 -24
  33. ophyd_async/epics/adkinetix/_kinetix_controller.py +9 -21
  34. ophyd_async/epics/adpilatus/__init__.py +2 -2
  35. ophyd_async/epics/adpilatus/_pilatus.py +27 -39
  36. ophyd_async/epics/adpilatus/_pilatus_controller.py +44 -22
  37. ophyd_async/epics/adsimdetector/__init__.py +3 -3
  38. ophyd_async/epics/adsimdetector/_sim.py +33 -17
  39. ophyd_async/epics/advimba/_vimba.py +23 -23
  40. ophyd_async/epics/advimba/_vimba_controller.py +10 -24
  41. ophyd_async/epics/core/_aioca.py +31 -14
  42. ophyd_async/epics/core/_p4p.py +40 -16
  43. ophyd_async/epics/core/_util.py +1 -1
  44. ophyd_async/epics/motor.py +18 -10
  45. ophyd_async/epics/sim/_ioc.py +29 -0
  46. ophyd_async/epics/{demo → sim}/_mover.py +10 -4
  47. ophyd_async/epics/testing/__init__.py +14 -14
  48. ophyd_async/epics/testing/_example_ioc.py +48 -65
  49. ophyd_async/epics/testing/_utils.py +17 -45
  50. ophyd_async/epics/testing/test_records.db +8 -0
  51. ophyd_async/fastcs/panda/__init__.py +0 -2
  52. ophyd_async/fastcs/panda/_control.py +7 -2
  53. ophyd_async/fastcs/panda/_hdf_panda.py +3 -1
  54. ophyd_async/fastcs/panda/_table.py +4 -1
  55. ophyd_async/plan_stubs/__init__.py +14 -0
  56. ophyd_async/plan_stubs/_ensure_connected.py +11 -17
  57. ophyd_async/plan_stubs/_fly.py +1 -1
  58. ophyd_async/plan_stubs/_nd_attributes.py +7 -5
  59. ophyd_async/plan_stubs/_panda.py +13 -0
  60. ophyd_async/plan_stubs/_settings.py +125 -0
  61. ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
  62. ophyd_async/sim/__init__.py +19 -0
  63. ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_controller.py +9 -2
  64. ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_generator.py +13 -6
  65. ophyd_async/tango/core/_signal.py +3 -1
  66. ophyd_async/tango/core/_tango_transport.py +12 -14
  67. ophyd_async/tango/{demo → sim}/_mover.py +5 -2
  68. ophyd_async/testing/__init__.py +19 -0
  69. ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
  70. ophyd_async/testing/_assert.py +88 -40
  71. ophyd_async/testing/_mock_signal_utils.py +3 -3
  72. ophyd_async/testing/_one_of_everything.py +126 -0
  73. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/METADATA +2 -2
  74. ophyd_async-0.9.0a2.dist-info/RECORD +129 -0
  75. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/WHEEL +1 -1
  76. ophyd_async/core/_device_save_loader.py +0 -274
  77. ophyd_async/epics/adsimdetector/_sim_controller.py +0 -51
  78. ophyd_async/fastcs/panda/_utils.py +0 -16
  79. ophyd_async/sim/demo/__init__.py +0 -19
  80. ophyd_async/sim/testing/__init__.py +0 -0
  81. ophyd_async-0.9.0a1.dist-info/RECORD +0 -119
  82. ophyd_async-0.9.0a1.dist-info/entry_points.txt +0 -2
  83. /ophyd_async/epics/{demo → sim}/__init__.py +0 -0
  84. /ophyd_async/epics/{demo → sim}/_sensor.py +0 -0
  85. /ophyd_async/epics/{demo → sim}/mover.db +0 -0
  86. /ophyd_async/epics/{demo → sim}/sensor.db +0 -0
  87. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/__init__.py +0 -0
  88. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector.py +0 -0
  89. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_writer.py +0 -0
  90. /ophyd_async/sim/{demo/_sim_motor.py → _sim_motor.py} +0 -0
  91. /ophyd_async/tango/{demo → sim}/__init__.py +0 -0
  92. /ophyd_async/tango/{demo → sim}/_counter.py +0 -0
  93. /ophyd_async/tango/{demo → sim}/_detector.py +0 -0
  94. /ophyd_async/tango/{demo → sim}/_tango/__init__.py +0 -0
  95. /ophyd_async/tango/{demo → sim}/_tango/_servers.py +0 -0
  96. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/LICENSE +0 -0
  97. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/top_level.txt +0 -0
@@ -1,61 +1,47 @@
1
- from typing import get_args
1
+ from collections.abc import Sequence
2
2
 
3
- from bluesky.protocols import HasHints, Hints
4
-
5
- from ophyd_async.core import PathProvider, StandardDetector
3
+ from ophyd_async.core import PathProvider
4
+ from ophyd_async.core._signal import SignalR
6
5
  from ophyd_async.epics import adcore
7
6
 
8
7
  from ._aravis_controller import AravisController
9
8
  from ._aravis_io import AravisDriverIO
10
9
 
11
10
 
12
- class AravisDetector(StandardDetector, HasHints):
11
+ class AravisDetector(adcore.AreaDetector[AravisController]):
13
12
  """
14
13
  Ophyd-async implementation of an ADAravis Detector.
15
14
  The detector may be configured for an external trigger on a GPIO port,
16
15
  which must be done prior to preparing the detector
17
16
  """
18
17
 
19
- _controller: AravisController
20
- _writer: adcore.ADHDFWriter
21
-
22
18
  def __init__(
23
19
  self,
24
20
  prefix: str,
25
21
  path_provider: PathProvider,
26
22
  drv_suffix="cam1:",
27
- hdf_suffix="HDF1:",
28
- name="",
23
+ writer_cls: type[adcore.ADWriter] = adcore.ADHDFWriter,
24
+ fileio_suffix: str | None = None,
25
+ name: str = "",
29
26
  gpio_number: AravisController.GPIO_NUMBER = 1,
27
+ config_sigs: Sequence[SignalR] = (),
28
+ plugins: dict[str, adcore.NDPluginBaseIO] | None = None,
30
29
  ):
31
- self.drv = AravisDriverIO(prefix + drv_suffix)
32
- self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
30
+ driver = AravisDriverIO(prefix + drv_suffix)
31
+ controller = AravisController(driver, gpio_number=gpio_number)
32
+
33
+ writer = writer_cls.with_io(
34
+ prefix,
35
+ path_provider,
36
+ dataset_source=driver,
37
+ fileio_suffix=fileio_suffix,
38
+ plugins=plugins,
39
+ )
33
40
 
34
41
  super().__init__(
35
- AravisController(self.drv, gpio_number=gpio_number),
36
- adcore.ADHDFWriter(
37
- self.hdf,
38
- path_provider,
39
- lambda: self.name,
40
- adcore.ADBaseDatasetDescriber(self.drv),
41
- ),
42
- config_sigs=(self.drv.acquire_time,),
42
+ controller=controller,
43
+ writer=writer,
44
+ plugins=plugins,
43
45
  name=name,
46
+ config_sigs=config_sigs,
44
47
  )
45
-
46
- def get_external_trigger_gpio(self):
47
- return self._controller.gpio_number
48
-
49
- def set_external_trigger_gpio(self, gpio_number: AravisController.GPIO_NUMBER):
50
- supported_gpio_numbers = get_args(AravisController.GPIO_NUMBER)
51
- if gpio_number not in supported_gpio_numbers:
52
- raise ValueError(
53
- f"{self.__class__.__name__} only supports the following GPIO "
54
- f"indices: {supported_gpio_numbers} but was asked to "
55
- f"use {gpio_number}"
56
- )
57
- self._controller.gpio_number = gpio_number
58
-
59
- @property
60
- def hints(self) -> Hints:
61
- return self._writer.hints
@@ -2,11 +2,8 @@ import asyncio
2
2
  from typing import Literal
3
3
 
4
4
  from ophyd_async.core import (
5
- AsyncStatus,
6
- DetectorController,
7
5
  DetectorTrigger,
8
6
  TriggerInfo,
9
- set_and_wait_for_value,
10
7
  )
11
8
  from ophyd_async.epics import adcore
12
9
 
@@ -18,13 +15,17 @@ from ._aravis_io import AravisDriverIO, AravisTriggerMode, AravisTriggerSource
18
15
  _HIGHEST_POSSIBLE_DEADTIME = 1961e-6
19
16
 
20
17
 
21
- class AravisController(DetectorController):
18
+ class AravisController(adcore.ADBaseController[AravisDriverIO]):
22
19
  GPIO_NUMBER = Literal[1, 2, 3, 4]
23
20
 
24
- def __init__(self, driver: AravisDriverIO, gpio_number: GPIO_NUMBER) -> None:
25
- self._drv = driver
21
+ def __init__(
22
+ self,
23
+ driver: AravisDriverIO,
24
+ good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
25
+ gpio_number: GPIO_NUMBER = 1,
26
+ ) -> None:
27
+ super().__init__(driver, good_states=good_states)
26
28
  self.gpio_number = gpio_number
27
- self._arm_status: AsyncStatus | None = None
28
29
 
29
30
  def get_deadtime(self, exposure: float | None) -> float:
30
31
  return _HIGHEST_POSSIBLE_DEADTIME
@@ -35,25 +36,18 @@ class AravisController(DetectorController):
35
36
  else:
36
37
  image_mode = adcore.ImageMode.MULTIPLE
37
38
  if (exposure := trigger_info.livetime) is not None:
38
- await self._drv.acquire_time.set(exposure)
39
+ await self.driver.acquire_time.set(exposure)
39
40
 
40
41
  trigger_mode, trigger_source = self._get_trigger_info(trigger_info.trigger)
41
42
  # trigger mode must be set first and on it's own!
42
- await self._drv.trigger_mode.set(trigger_mode)
43
+ await self.driver.trigger_mode.set(trigger_mode)
43
44
 
44
45
  await asyncio.gather(
45
- self._drv.trigger_source.set(trigger_source),
46
- self._drv.num_images.set(trigger_info.total_number_of_triggers),
47
- self._drv.image_mode.set(image_mode),
46
+ self.driver.trigger_source.set(trigger_source),
47
+ self.driver.num_images.set(trigger_info.total_number_of_triggers),
48
+ self.driver.image_mode.set(image_mode),
48
49
  )
49
50
 
50
- async def arm(self):
51
- self._arm_status = await set_and_wait_for_value(self._drv.acquire, True)
52
-
53
- async def wait_for_idle(self):
54
- if self._arm_status:
55
- await self._arm_status
56
-
57
51
  def _get_trigger_info(
58
52
  self, trigger: DetectorTrigger
59
53
  ) -> tuple[AravisTriggerMode, AravisTriggerSource]:
@@ -72,6 +66,3 @@ class AravisController(DetectorController):
72
66
  return AravisTriggerMode.OFF, AravisTriggerSource.FREERUN
73
67
  else:
74
68
  return (AravisTriggerMode.ON, f"Line{self.gpio_number}") # type: ignore
75
-
76
- async def disarm(self):
77
- await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
@@ -1,18 +1,20 @@
1
+ from ._core_detector import AreaDetector
1
2
  from ._core_io import (
3
+ ADBaseDatasetDescriber,
2
4
  ADBaseIO,
3
5
  DetectorState,
4
6
  NDArrayBaseIO,
5
7
  NDFileHDFIO,
8
+ NDFileIO,
9
+ NDPluginBaseIO,
6
10
  NDPluginStatsIO,
7
11
  )
8
- from ._core_logic import (
9
- DEFAULT_GOOD_STATES,
10
- ADBaseDatasetDescriber,
11
- set_exposure_time_and_acquire_period_if_supplied,
12
- start_acquiring_driver_and_ensure_status,
13
- )
12
+ from ._core_logic import DEFAULT_GOOD_STATES, ADBaseController
13
+ from ._core_writer import ADWriter
14
14
  from ._hdf_writer import ADHDFWriter
15
+ from ._jpeg_writer import ADJPEGWriter
15
16
  from ._single_trigger import SingleTriggerDetector
17
+ from ._tiff_writer import ADTIFFWriter
16
18
  from ._utils import (
17
19
  ADBaseDataType,
18
20
  FileWriteMode,
@@ -26,15 +28,20 @@ from ._utils import (
26
28
 
27
29
  __all__ = [
28
30
  "ADBaseIO",
31
+ "AreaDetector",
29
32
  "DetectorState",
30
33
  "NDArrayBaseIO",
34
+ "NDFileIO",
31
35
  "NDFileHDFIO",
36
+ "NDPluginBaseIO",
32
37
  "NDPluginStatsIO",
33
38
  "DEFAULT_GOOD_STATES",
34
39
  "ADBaseDatasetDescriber",
35
- "set_exposure_time_and_acquire_period_if_supplied",
36
- "start_acquiring_driver_and_ensure_status",
40
+ "ADBaseController",
41
+ "ADWriter",
37
42
  "ADHDFWriter",
43
+ "ADTIFFWriter",
44
+ "ADJPEGWriter",
38
45
  "SingleTriggerDetector",
39
46
  "ADBaseDataType",
40
47
  "FileWriteMode",
@@ -0,0 +1,41 @@
1
+ from collections.abc import Sequence
2
+
3
+ from ophyd_async.core import SignalR, StandardDetector
4
+
5
+ from ._core_io import NDPluginBaseIO
6
+ from ._core_logic import ADBaseControllerT
7
+ from ._core_writer import ADWriter
8
+
9
+
10
+ class AreaDetector(StandardDetector[ADBaseControllerT, ADWriter]):
11
+ def __init__(
12
+ self,
13
+ controller: ADBaseControllerT,
14
+ writer: ADWriter,
15
+ plugins: dict[str, NDPluginBaseIO] | None = None,
16
+ config_sigs: Sequence[SignalR] = (),
17
+ name: str = "",
18
+ ):
19
+ self.driver = controller.driver
20
+ self.fileio = writer.fileio
21
+
22
+ if plugins is not None:
23
+ for name, plugin in plugins.items():
24
+ setattr(self, name, plugin)
25
+
26
+ super().__init__(
27
+ controller,
28
+ writer,
29
+ (self.driver.acquire_period, self.driver.acquire_time, *config_sigs),
30
+ name=name,
31
+ )
32
+
33
+ def get_plugin(
34
+ self, name: str, plugin_type: type[NDPluginBaseIO] = NDPluginBaseIO
35
+ ) -> NDPluginBaseIO:
36
+ plugin = getattr(self, name, None)
37
+ if not isinstance(plugin, plugin_type):
38
+ raise TypeError(
39
+ f"Expected {self.name}.{name} to be a {plugin_type}, got {plugin}"
40
+ )
41
+ return plugin
@@ -1,11 +1,14 @@
1
+ import asyncio
2
+
1
3
  from ophyd_async.core import Device, StrictEnum
4
+ from ophyd_async.core._providers import DatasetDescriber
2
5
  from ophyd_async.epics.core import (
3
6
  epics_signal_r,
4
7
  epics_signal_rw,
5
8
  epics_signal_rw_rbv,
6
9
  )
7
10
 
8
- from ._utils import ADBaseDataType, FileWriteMode, ImageMode
11
+ from ._utils import ADBaseDataType, FileWriteMode, ImageMode, convert_ad_dtype_to_np
9
12
 
10
13
 
11
14
  class Callback(StrictEnum):
@@ -27,6 +30,21 @@ class NDArrayBaseIO(Device):
27
30
  super().__init__(name=name)
28
31
 
29
32
 
33
+ class ADBaseDatasetDescriber(DatasetDescriber):
34
+ def __init__(self, driver: NDArrayBaseIO) -> None:
35
+ self._driver = driver
36
+
37
+ async def np_datatype(self) -> str:
38
+ return convert_ad_dtype_to_np(await self._driver.data_type.get_value())
39
+
40
+ async def shape(self) -> tuple[int, int]:
41
+ shape = await asyncio.gather(
42
+ self._driver.array_size_y.get_value(),
43
+ self._driver.array_size_x.get_value(),
44
+ )
45
+ return shape
46
+
47
+
30
48
  class NDPluginBaseIO(NDArrayBaseIO):
31
49
  def __init__(self, prefix: str, name: str = "") -> None:
32
50
  self.nd_array_port = epics_signal_rw_rbv(str, prefix + "NDArrayPort")
@@ -36,6 +54,7 @@ class NDPluginBaseIO(NDArrayBaseIO):
36
54
  self.nd_array_address = epics_signal_rw_rbv(int, prefix + "NDArrayAddress")
37
55
  self.array_size0 = epics_signal_r(int, prefix + "ArraySize0_RBV")
38
56
  self.array_size1 = epics_signal_r(int, prefix + "ArraySize1_RBV")
57
+ self.queue_size = epics_signal_rw(int, prefix + "QueueSize")
39
58
  super().__init__(prefix, name)
40
59
 
41
60
 
@@ -109,30 +128,36 @@ class Compression(StrictEnum):
109
128
  JPEG = "JPEG"
110
129
 
111
130
 
112
- class NDFileHDFIO(NDPluginBaseIO):
131
+ class NDFileIO(NDPluginBaseIO):
113
132
  def __init__(self, prefix: str, name="") -> None:
114
- # Define some signals
115
- self.position_mode = epics_signal_rw_rbv(bool, prefix + "PositionMode")
116
- self.compression = epics_signal_rw_rbv(Compression, prefix + "Compression")
117
- self.num_extra_dims = epics_signal_rw_rbv(int, prefix + "NumExtraDims")
118
133
  self.file_path = epics_signal_rw_rbv(str, prefix + "FilePath")
119
134
  self.file_name = epics_signal_rw_rbv(str, prefix + "FileName")
120
135
  self.file_path_exists = epics_signal_r(bool, prefix + "FilePathExists_RBV")
121
136
  self.file_template = epics_signal_rw_rbv(str, prefix + "FileTemplate")
122
137
  self.full_file_name = epics_signal_r(str, prefix + "FullFileName_RBV")
138
+ self.file_number = epics_signal_rw(int, prefix + "FileNumber")
139
+ self.auto_increment = epics_signal_rw(bool, prefix + "AutoIncrement")
123
140
  self.file_write_mode = epics_signal_rw_rbv(
124
141
  FileWriteMode, prefix + "FileWriteMode"
125
142
  )
126
143
  self.num_capture = epics_signal_rw_rbv(int, prefix + "NumCapture")
127
144
  self.num_captured = epics_signal_r(int, prefix + "NumCaptured_RBV")
128
- self.swmr_mode = epics_signal_rw_rbv(bool, prefix + "SWMRMode")
129
- self.lazy_open = epics_signal_rw_rbv(bool, prefix + "LazyOpen")
130
145
  self.capture = epics_signal_rw_rbv(bool, prefix + "Capture")
131
- self.flush_now = epics_signal_rw(bool, prefix + "FlushNow")
132
- self.xml_file_name = epics_signal_rw_rbv(str, prefix + "XMLFileName")
133
146
  self.array_size0 = epics_signal_r(int, prefix + "ArraySize0")
134
147
  self.array_size1 = epics_signal_r(int, prefix + "ArraySize1")
135
148
  self.create_directory = epics_signal_rw(int, prefix + "CreateDirectory")
149
+ super().__init__(prefix, name)
150
+
151
+
152
+ class NDFileHDFIO(NDFileIO):
153
+ def __init__(self, prefix: str, name="") -> None:
154
+ self.position_mode = epics_signal_rw_rbv(bool, prefix + "PositionMode")
155
+ self.compression = epics_signal_rw_rbv(Compression, prefix + "Compression")
156
+ self.num_extra_dims = epics_signal_rw_rbv(int, prefix + "NumExtraDims")
157
+ self.swmr_mode = epics_signal_rw_rbv(bool, prefix + "SWMRMode")
158
+ self.flush_now = epics_signal_rw(bool, prefix + "FlushNow")
159
+ self.xml_file_name = epics_signal_rw_rbv(str, prefix + "XMLFileName")
136
160
  self.num_frames_chunks = epics_signal_r(int, prefix + "NumFramesChunks_RBV")
137
161
  self.chunk_size_auto = epics_signal_rw_rbv(bool, prefix + "ChunkSizeAuto")
162
+ self.lazy_open = epics_signal_rw_rbv(bool, prefix + "LazyOpen")
138
163
  super().__init__(prefix, name)
@@ -1,15 +1,17 @@
1
1
  import asyncio
2
+ from typing import Generic, TypeVar
2
3
 
3
4
  from ophyd_async.core import (
4
5
  DEFAULT_TIMEOUT,
5
6
  AsyncStatus,
6
- DatasetDescriber,
7
7
  DetectorController,
8
+ DetectorTrigger,
9
+ TriggerInfo,
8
10
  set_and_wait_for_value,
9
11
  )
10
- from ophyd_async.epics.adcore._utils import convert_ad_dtype_to_np
11
12
 
12
13
  from ._core_io import ADBaseIO, DetectorState
14
+ from ._utils import ImageMode, stop_busy_record
13
15
 
14
16
  # Default set of states that we should consider "good" i.e. the acquisition
15
17
  # is complete and went well
@@ -17,93 +19,103 @@ DEFAULT_GOOD_STATES: frozenset[DetectorState] = frozenset(
17
19
  [DetectorState.IDLE, DetectorState.ABORTED]
18
20
  )
19
21
 
20
-
21
- class ADBaseDatasetDescriber(DatasetDescriber):
22
- def __init__(self, driver: ADBaseIO) -> None:
23
- self._driver = driver
24
-
25
- async def np_datatype(self) -> str:
26
- return convert_ad_dtype_to_np(await self._driver.data_type.get_value())
27
-
28
- async def shape(self) -> tuple[int, int]:
29
- shape = await asyncio.gather(
30
- self._driver.array_size_y.get_value(),
31
- self._driver.array_size_x.get_value(),
22
+ ADBaseIOT = TypeVar("ADBaseIOT", bound=ADBaseIO)
23
+ ADBaseControllerT = TypeVar("ADBaseControllerT", bound="ADBaseController")
24
+
25
+
26
+ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
27
+ def __init__(
28
+ self,
29
+ driver: ADBaseIOT,
30
+ good_states: frozenset[DetectorState] = DEFAULT_GOOD_STATES,
31
+ ) -> None:
32
+ self.driver = driver
33
+ self.good_states = good_states
34
+ self.frame_timeout = DEFAULT_TIMEOUT
35
+ self._arm_status: AsyncStatus | None = None
36
+
37
+ async def prepare(self, trigger_info: TriggerInfo) -> None:
38
+ if trigger_info.trigger != DetectorTrigger.INTERNAL:
39
+ msg = (
40
+ "fly scanning (i.e. external triggering) is not supported for this "
41
+ "device"
42
+ )
43
+ raise TypeError(msg)
44
+ self.frame_timeout = (
45
+ DEFAULT_TIMEOUT + await self.driver.acquire_time.get_value()
32
46
  )
33
- return shape
34
-
35
-
36
- async def set_exposure_time_and_acquire_period_if_supplied(
37
- controller: DetectorController,
38
- driver: ADBaseIO,
39
- exposure: float | None = None,
40
- timeout: float = DEFAULT_TIMEOUT,
41
- ) -> None:
42
- """
43
- Sets the exposure time if it is not None and the acquire period to the
44
- exposure time plus the deadtime. This is expected behavior for most
45
- AreaDetectors, but some may require more specialized handling.
46
-
47
- Parameters
48
- ----------
49
- controller:
50
- Controller that can supply a deadtime.
51
- driver:
52
- The driver to start acquiring. Must subclass ADBaseIO.
53
- exposure:
54
- Desired exposure time, this is a noop if it is None.
55
- timeout:
56
- How long to wait for the exposure time and acquire period to be set.
57
- """
58
- if exposure is not None:
59
- full_frame_time = exposure + controller.get_deadtime(exposure)
60
47
  await asyncio.gather(
61
- driver.acquire_time.set(exposure, timeout=timeout),
62
- driver.acquire_period.set(full_frame_time, timeout=timeout),
48
+ self.driver.num_images.set(trigger_info.total_number_of_triggers),
49
+ self.driver.image_mode.set(ImageMode.MULTIPLE),
63
50
  )
64
51
 
65
-
66
- async def start_acquiring_driver_and_ensure_status(
67
- driver: ADBaseIO,
68
- good_states: frozenset[DetectorState] = frozenset(DEFAULT_GOOD_STATES),
69
- timeout: float = DEFAULT_TIMEOUT,
70
- ) -> AsyncStatus:
71
- """
72
- Start acquiring driver, raising ValueError if the detector is in a bad state.
73
-
74
- This sets driver.acquire to True, and waits for it to be True up to a timeout.
75
- Then, it checks that the DetectorState PV is in DEFAULT_GOOD_STATES, and otherwise
76
- raises a ValueError.
77
-
78
- Parameters
79
- ----------
80
- driver:
81
- The driver to start acquiring. Must subclass ADBaseIO.
82
- good_states:
83
- set of states defined in DetectorState enum which are considered good states.
84
- timeout:
85
- How long to wait for driver.acquire to readback True (i.e. acquiring).
86
-
87
- Returns
88
- -------
89
- AsyncStatus:
90
- An AsyncStatus that can be awaited to set driver.acquire to True and perform
91
- subsequent raising (if applicable) due to detector state.
92
- """
93
-
94
- status = await set_and_wait_for_value(
95
- driver.acquire, True, timeout=timeout, wait_for_set_completion=False
96
- )
97
-
98
- async def complete_acquisition() -> None:
99
- """NOTE: possible race condition here between the callback from
100
- set_and_wait_for_value and the detector state updating."""
101
- await status
102
- state = await driver.detector_state.get_value()
103
- if state not in good_states:
104
- raise ValueError(
105
- f"Final detector state {state.value} not in valid end "
106
- f"states: {good_states}"
52
+ async def arm(self):
53
+ self._arm_status = await self.start_acquiring_driver_and_ensure_status()
54
+
55
+ async def wait_for_idle(self):
56
+ if self._arm_status:
57
+ await self._arm_status
58
+
59
+ async def disarm(self):
60
+ # We can't use caput callback as we already used it in arm() and we can't have
61
+ # 2 or they will deadlock
62
+ await stop_busy_record(self.driver.acquire, False, timeout=1)
63
+
64
+ async def set_exposure_time_and_acquire_period_if_supplied(
65
+ self,
66
+ exposure: float | None = None,
67
+ timeout: float = DEFAULT_TIMEOUT,
68
+ ) -> None:
69
+ """
70
+ Sets the exposure time if it is not None and the acquire period to the
71
+ exposure time plus the deadtime. This is expected behavior for most
72
+ AreaDetectors, but some may require more specialized handling.
73
+
74
+ Parameters
75
+ ----------
76
+ exposure:
77
+ Desired exposure time, this is a noop if it is None.
78
+ timeout:
79
+ How long to wait for the exposure time and acquire period to be set.
80
+ """
81
+ if exposure is not None:
82
+ full_frame_time = exposure + self.get_deadtime(exposure)
83
+ await asyncio.gather(
84
+ self.driver.acquire_time.set(exposure, timeout=timeout),
85
+ self.driver.acquire_period.set(full_frame_time, timeout=timeout),
107
86
  )
108
87
 
109
- return AsyncStatus(complete_acquisition())
88
+ async def start_acquiring_driver_and_ensure_status(self) -> AsyncStatus:
89
+ """
90
+ Start acquiring driver, raising ValueError if the detector is in a bad state.
91
+
92
+ This sets driver.acquire to True, and waits for it to be True up to a timeout.
93
+ Then, it checks that the DetectorState PV is in DEFAULT_GOOD_STATES,
94
+ and otherwise raises a ValueError.
95
+
96
+ Returns
97
+ -------
98
+ AsyncStatus:
99
+ An AsyncStatus that can be awaited to set driver.acquire to True and perform
100
+ subsequent raising (if applicable) due to detector state.
101
+ """
102
+
103
+ status = await set_and_wait_for_value(
104
+ self.driver.acquire,
105
+ True,
106
+ timeout=DEFAULT_TIMEOUT,
107
+ wait_for_set_completion=False,
108
+ )
109
+
110
+ async def complete_acquisition() -> None:
111
+ """NOTE: possible race condition here between the callback from
112
+ set_and_wait_for_value and the detector state updating."""
113
+ await status
114
+ state = await self.driver.detector_state.get_value()
115
+ if state not in self.good_states:
116
+ raise ValueError(
117
+ f"Final detector state {state.value} not "
118
+ "in valid end states: {self.good_states}"
119
+ )
120
+
121
+ return AsyncStatus(complete_acquisition())