ophyd-async 0.9.0a1__py3-none-any.whl → 0.10.0a1__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 (157) hide show
  1. ophyd_async/__init__.py +5 -8
  2. ophyd_async/_docs_parser.py +12 -0
  3. ophyd_async/_version.py +9 -4
  4. ophyd_async/core/__init__.py +102 -74
  5. ophyd_async/core/_derived_signal.py +271 -0
  6. ophyd_async/core/_derived_signal_backend.py +300 -0
  7. ophyd_async/core/_detector.py +158 -153
  8. ophyd_async/core/_device.py +143 -115
  9. ophyd_async/core/_device_filler.py +82 -9
  10. ophyd_async/core/_flyer.py +16 -7
  11. ophyd_async/core/_hdf_dataset.py +29 -22
  12. ophyd_async/core/_log.py +14 -23
  13. ophyd_async/core/_mock_signal_backend.py +11 -3
  14. ophyd_async/core/_protocol.py +65 -45
  15. ophyd_async/core/_providers.py +28 -9
  16. ophyd_async/core/_readable.py +74 -58
  17. ophyd_async/core/_settings.py +113 -0
  18. ophyd_async/core/_signal.py +304 -174
  19. ophyd_async/core/_signal_backend.py +60 -14
  20. ophyd_async/core/_soft_signal_backend.py +18 -12
  21. ophyd_async/core/_status.py +72 -24
  22. ophyd_async/core/_table.py +54 -17
  23. ophyd_async/core/_utils.py +101 -52
  24. ophyd_async/core/_yaml_settings.py +66 -0
  25. ophyd_async/epics/__init__.py +1 -0
  26. ophyd_async/epics/adandor/__init__.py +9 -0
  27. ophyd_async/epics/adandor/_andor.py +45 -0
  28. ophyd_async/epics/adandor/_andor_controller.py +51 -0
  29. ophyd_async/epics/adandor/_andor_io.py +34 -0
  30. ophyd_async/epics/adaravis/__init__.py +8 -1
  31. ophyd_async/epics/adaravis/_aravis.py +23 -41
  32. ophyd_async/epics/adaravis/_aravis_controller.py +23 -55
  33. ophyd_async/epics/adaravis/_aravis_io.py +13 -28
  34. ophyd_async/epics/adcore/__init__.py +36 -14
  35. ophyd_async/epics/adcore/_core_detector.py +81 -0
  36. ophyd_async/epics/adcore/_core_io.py +145 -95
  37. ophyd_async/epics/adcore/_core_logic.py +179 -88
  38. ophyd_async/epics/adcore/_core_writer.py +223 -0
  39. ophyd_async/epics/adcore/_hdf_writer.py +51 -92
  40. ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
  41. ophyd_async/epics/adcore/_single_trigger.py +6 -5
  42. ophyd_async/epics/adcore/_tiff_writer.py +26 -0
  43. ophyd_async/epics/adcore/_utils.py +3 -2
  44. ophyd_async/epics/adkinetix/__init__.py +2 -1
  45. ophyd_async/epics/adkinetix/_kinetix.py +32 -27
  46. ophyd_async/epics/adkinetix/_kinetix_controller.py +11 -21
  47. ophyd_async/epics/adkinetix/_kinetix_io.py +12 -13
  48. ophyd_async/epics/adpilatus/__init__.py +7 -2
  49. ophyd_async/epics/adpilatus/_pilatus.py +28 -40
  50. ophyd_async/epics/adpilatus/_pilatus_controller.py +25 -22
  51. ophyd_async/epics/adpilatus/_pilatus_io.py +11 -9
  52. ophyd_async/epics/adsimdetector/__init__.py +8 -1
  53. ophyd_async/epics/adsimdetector/_sim.py +22 -16
  54. ophyd_async/epics/adsimdetector/_sim_controller.py +9 -43
  55. ophyd_async/epics/adsimdetector/_sim_io.py +10 -0
  56. ophyd_async/epics/advimba/__init__.py +10 -1
  57. ophyd_async/epics/advimba/_vimba.py +26 -25
  58. ophyd_async/epics/advimba/_vimba_controller.py +12 -24
  59. ophyd_async/epics/advimba/_vimba_io.py +23 -28
  60. ophyd_async/epics/core/_aioca.py +66 -30
  61. ophyd_async/epics/core/_epics_connector.py +4 -0
  62. ophyd_async/epics/core/_epics_device.py +2 -0
  63. ophyd_async/epics/core/_p4p.py +50 -18
  64. ophyd_async/epics/core/_pvi_connector.py +65 -8
  65. ophyd_async/epics/core/_signal.py +51 -51
  66. ophyd_async/epics/core/_util.py +5 -5
  67. ophyd_async/epics/demo/__init__.py +11 -49
  68. ophyd_async/epics/demo/__main__.py +31 -0
  69. ophyd_async/epics/demo/_ioc.py +32 -0
  70. ophyd_async/epics/demo/_motor.py +82 -0
  71. ophyd_async/epics/demo/_point_detector.py +42 -0
  72. ophyd_async/epics/demo/_point_detector_channel.py +22 -0
  73. ophyd_async/epics/demo/_stage.py +15 -0
  74. ophyd_async/epics/demo/{mover.db → motor.db} +2 -1
  75. ophyd_async/epics/demo/point_detector.db +59 -0
  76. ophyd_async/epics/demo/point_detector_channel.db +21 -0
  77. ophyd_async/epics/eiger/_eiger.py +1 -3
  78. ophyd_async/epics/eiger/_eiger_controller.py +11 -4
  79. ophyd_async/epics/eiger/_eiger_io.py +2 -0
  80. ophyd_async/epics/eiger/_odin_io.py +1 -2
  81. ophyd_async/epics/motor.py +83 -38
  82. ophyd_async/epics/signal.py +4 -1
  83. ophyd_async/epics/testing/__init__.py +14 -14
  84. ophyd_async/epics/testing/_example_ioc.py +68 -73
  85. ophyd_async/epics/testing/_utils.py +19 -44
  86. ophyd_async/epics/testing/test_records.db +16 -0
  87. ophyd_async/epics/testing/test_records_pva.db +17 -16
  88. ophyd_async/fastcs/__init__.py +1 -0
  89. ophyd_async/fastcs/core.py +6 -0
  90. ophyd_async/fastcs/odin/__init__.py +1 -0
  91. ophyd_async/fastcs/panda/__init__.py +8 -8
  92. ophyd_async/fastcs/panda/_block.py +29 -9
  93. ophyd_async/fastcs/panda/_control.py +12 -2
  94. ophyd_async/fastcs/panda/_hdf_panda.py +5 -1
  95. ophyd_async/fastcs/panda/_table.py +13 -7
  96. ophyd_async/fastcs/panda/_trigger.py +23 -9
  97. ophyd_async/fastcs/panda/_writer.py +27 -30
  98. ophyd_async/plan_stubs/__init__.py +16 -0
  99. ophyd_async/plan_stubs/_ensure_connected.py +12 -17
  100. ophyd_async/plan_stubs/_fly.py +3 -5
  101. ophyd_async/plan_stubs/_nd_attributes.py +9 -5
  102. ophyd_async/plan_stubs/_panda.py +14 -0
  103. ophyd_async/plan_stubs/_settings.py +152 -0
  104. ophyd_async/plan_stubs/_utils.py +3 -0
  105. ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
  106. ophyd_async/sim/__init__.py +29 -0
  107. ophyd_async/sim/__main__.py +43 -0
  108. ophyd_async/sim/_blob_detector.py +33 -0
  109. ophyd_async/sim/_blob_detector_controller.py +48 -0
  110. ophyd_async/sim/_blob_detector_writer.py +105 -0
  111. ophyd_async/sim/_mirror_horizontal.py +46 -0
  112. ophyd_async/sim/_mirror_vertical.py +74 -0
  113. ophyd_async/sim/_motor.py +233 -0
  114. ophyd_async/sim/_pattern_generator.py +124 -0
  115. ophyd_async/sim/_point_detector.py +86 -0
  116. ophyd_async/sim/_stage.py +19 -0
  117. ophyd_async/tango/__init__.py +1 -0
  118. ophyd_async/tango/core/__init__.py +6 -1
  119. ophyd_async/tango/core/_base_device.py +41 -33
  120. ophyd_async/tango/core/_converters.py +81 -0
  121. ophyd_async/tango/core/_signal.py +21 -33
  122. ophyd_async/tango/core/_tango_readable.py +2 -19
  123. ophyd_async/tango/core/_tango_transport.py +148 -74
  124. ophyd_async/tango/core/_utils.py +47 -0
  125. ophyd_async/tango/demo/_counter.py +2 -0
  126. ophyd_async/tango/demo/_detector.py +2 -0
  127. ophyd_async/tango/demo/_mover.py +10 -6
  128. ophyd_async/tango/demo/_tango/_servers.py +4 -0
  129. ophyd_async/tango/testing/__init__.py +6 -0
  130. ophyd_async/tango/testing/_one_of_everything.py +200 -0
  131. ophyd_async/testing/__init__.py +48 -7
  132. ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
  133. ophyd_async/testing/_assert.py +200 -96
  134. ophyd_async/testing/_mock_signal_utils.py +59 -73
  135. ophyd_async/testing/_one_of_everything.py +146 -0
  136. ophyd_async/testing/_single_derived.py +87 -0
  137. ophyd_async/testing/_utils.py +3 -0
  138. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/METADATA +25 -26
  139. ophyd_async-0.10.0a1.dist-info/RECORD +149 -0
  140. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/WHEEL +1 -1
  141. ophyd_async/core/_device_save_loader.py +0 -274
  142. ophyd_async/epics/demo/_mover.py +0 -95
  143. ophyd_async/epics/demo/_sensor.py +0 -37
  144. ophyd_async/epics/demo/sensor.db +0 -19
  145. ophyd_async/fastcs/panda/_utils.py +0 -16
  146. ophyd_async/sim/demo/__init__.py +0 -19
  147. ophyd_async/sim/demo/_pattern_detector/__init__.py +0 -13
  148. ophyd_async/sim/demo/_pattern_detector/_pattern_detector.py +0 -42
  149. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_controller.py +0 -62
  150. ophyd_async/sim/demo/_pattern_detector/_pattern_detector_writer.py +0 -41
  151. ophyd_async/sim/demo/_pattern_detector/_pattern_generator.py +0 -207
  152. ophyd_async/sim/demo/_sim_motor.py +0 -107
  153. ophyd_async/sim/testing/__init__.py +0 -0
  154. ophyd_async-0.9.0a1.dist-info/RECORD +0 -119
  155. ophyd_async-0.9.0a1.dist-info/entry_points.txt +0 -2
  156. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info/licenses}/LICENSE +0 -0
  157. {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.10.0a1.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,6 @@
1
1
  import asyncio
2
- from typing import Literal
3
2
 
4
- from ophyd_async.core import (
5
- AsyncStatus,
6
- DetectorController,
7
- DetectorTrigger,
8
- TriggerInfo,
9
- set_and_wait_for_value,
10
- )
3
+ from ophyd_async.core import DetectorTrigger, TriggerInfo
11
4
  from ophyd_async.epics import adcore
12
5
 
13
6
  from ._aravis_io import AravisDriverIO, AravisTriggerMode, AravisTriggerSource
@@ -18,60 +11,35 @@ from ._aravis_io import AravisDriverIO, AravisTriggerMode, AravisTriggerSource
18
11
  _HIGHEST_POSSIBLE_DEADTIME = 1961e-6
19
12
 
20
13
 
21
- class AravisController(DetectorController):
22
- GPIO_NUMBER = Literal[1, 2, 3, 4]
23
-
24
- def __init__(self, driver: AravisDriverIO, gpio_number: GPIO_NUMBER) -> None:
25
- self._drv = driver
26
- self.gpio_number = gpio_number
27
- self._arm_status: AsyncStatus | None = None
14
+ class AravisController(adcore.ADBaseController[AravisDriverIO]):
15
+ """`DetectorController` for an `AravisDriverIO`."""
28
16
 
29
17
  def get_deadtime(self, exposure: float | None) -> float:
30
18
  return _HIGHEST_POSSIBLE_DEADTIME
31
19
 
32
- async def prepare(self, trigger_info: TriggerInfo):
33
- if trigger_info.total_number_of_triggers == 0:
34
- image_mode = adcore.ImageMode.CONTINUOUS
35
- else:
36
- image_mode = adcore.ImageMode.MULTIPLE
20
+ async def prepare(self, trigger_info: TriggerInfo) -> None:
37
21
  if (exposure := trigger_info.livetime) is not None:
38
- await self._drv.acquire_time.set(exposure)
39
-
40
- trigger_mode, trigger_source = self._get_trigger_info(trigger_info.trigger)
41
- # trigger mode must be set first and on it's own!
42
- await self._drv.trigger_mode.set(trigger_mode)
43
-
44
- 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),
48
- )
22
+ await self.driver.acquire_time.set(exposure)
49
23
 
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
- def _get_trigger_info(
58
- self, trigger: DetectorTrigger
59
- ) -> tuple[AravisTriggerMode, AravisTriggerSource]:
60
- supported_trigger_types = (
24
+ if trigger_info.trigger is DetectorTrigger.INTERNAL:
25
+ # Set trigger mode off to ignore the trigger source
26
+ await self.driver.trigger_mode.set(AravisTriggerMode.OFF)
27
+ elif trigger_info.trigger in {
61
28
  DetectorTrigger.CONSTANT_GATE,
62
29
  DetectorTrigger.EDGE_TRIGGER,
63
- DetectorTrigger.INTERNAL,
64
- )
65
- if trigger not in supported_trigger_types:
66
- raise ValueError(
67
- f"{self.__class__.__name__} only supports the following trigger "
68
- f"types: {supported_trigger_types} but was asked to "
69
- f"use {trigger}"
70
- )
71
- if trigger == DetectorTrigger.INTERNAL:
72
- return AravisTriggerMode.OFF, AravisTriggerSource.FREERUN
30
+ }:
31
+ # Trigger on the rising edge of Line1
32
+ # trigger mode must be set first and on it's own!
33
+ await self.driver.trigger_mode.set(AravisTriggerMode.ON)
34
+ await self.driver.trigger_source.set(AravisTriggerSource.LINE1)
73
35
  else:
74
- return (AravisTriggerMode.ON, f"Line{self.gpio_number}") # type: ignore
36
+ raise ValueError(f"ADAravis does not support {trigger_info.trigger}")
75
37
 
76
- async def disarm(self):
77
- await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
38
+ if trigger_info.total_number_of_triggers == 0:
39
+ image_mode = adcore.ADImageMode.CONTINUOUS
40
+ else:
41
+ image_mode = adcore.ADImageMode.MULTIPLE
42
+ await asyncio.gather(
43
+ self.driver.num_images.set(trigger_info.total_number_of_triggers),
44
+ self.driver.image_mode.set(image_mode),
45
+ )
@@ -1,46 +1,31 @@
1
- from ophyd_async.core import StrictEnum, SubsetEnum
1
+ from typing import Annotated as A
2
+
3
+ from ophyd_async.core import SignalRW, StrictEnum, SubsetEnum
2
4
  from ophyd_async.epics import adcore
3
- from ophyd_async.epics.core import epics_signal_rw_rbv
5
+ from ophyd_async.epics.core import PvSuffix
4
6
 
5
7
 
6
8
  class AravisTriggerMode(StrictEnum):
7
- """GigEVision GenICAM standard: on=externally triggered"""
9
+ """GigEVision GenICAM standard TriggerMode."""
8
10
 
9
11
  ON = "On"
10
- OFF = "Off"
12
+ """Use TriggerSource to trigger each frame"""
11
13
 
12
-
13
- """A minimal set of TriggerSources that must be supported by the underlying record.
14
- To enable hardware triggered scanning, line_N must support each N in GPIO_NUMBER.
15
- To enable software triggered scanning, freerun must be supported.
16
- Other enumerated values may or may not be preset.
17
- To prevent requiring one Enum class per possible configuration, we set as this Enum
18
- but read from the underlying signal as a str.
19
- """
14
+ OFF = "Off"
15
+ """Just trigger as fast as you can"""
20
16
 
21
17
 
22
18
  class AravisTriggerSource(SubsetEnum):
23
- FREERUN = "Freerun"
19
+ """Which trigger source to use when TriggerMode=On."""
20
+
24
21
  LINE1 = "Line1"
25
22
 
26
23
 
27
24
  class AravisDriverIO(adcore.ADBaseIO):
28
- # If instantiating a new instance, ensure it is supported in the _deadtimes dict
29
- """Generic Driver supporting the Manta and Mako drivers.
30
- Fetches deadtime prior to use in a Streaming scan.
31
-
32
- Requires driver firmware up to date:
33
- - Model_RBV must be of the form "^(Mako|Manta) (model)$"
25
+ """Generic Driver supporting all GiGE cameras.
34
26
 
35
27
  This mirrors the interface provided by ADAravis/db/aravisCamera.template.
36
28
  """
37
29
 
38
- def __init__(self, prefix: str, name: str = "") -> None:
39
- self.trigger_mode = epics_signal_rw_rbv(
40
- AravisTriggerMode, prefix + "TriggerMode"
41
- )
42
- self.trigger_source = epics_signal_rw_rbv(
43
- AravisTriggerSource, # type: ignore
44
- prefix + "TriggerSource",
45
- )
46
- super().__init__(prefix, name=name)
30
+ trigger_mode: A[SignalRW[AravisTriggerMode], PvSuffix.rbv("TriggerMode")]
31
+ trigger_source: A[SignalRW[AravisTriggerSource], PvSuffix.rbv("TriggerSource")]
@@ -1,22 +1,33 @@
1
+ """Core components of the areaDetector software.
2
+
3
+ https://github.com/areaDetector/ADCore
4
+ """
5
+
6
+ from ._core_detector import AreaDetector, ContAcqAreaDetector
1
7
  from ._core_io import (
8
+ ADBaseDatasetDescriber,
2
9
  ADBaseIO,
3
- DetectorState,
10
+ ADCallbacks,
11
+ ADCompression,
12
+ ADState,
4
13
  NDArrayBaseIO,
14
+ NDCBFlushOnSoftTrgMode,
5
15
  NDFileHDFIO,
16
+ NDFileIO,
17
+ NDPluginBaseIO,
18
+ NDPluginCBIO,
6
19
  NDPluginStatsIO,
7
20
  )
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
- )
21
+ from ._core_logic import DEFAULT_GOOD_STATES, ADBaseContAcqController, ADBaseController
22
+ from ._core_writer import ADWriter
14
23
  from ._hdf_writer import ADHDFWriter
24
+ from ._jpeg_writer import ADJPEGWriter
15
25
  from ._single_trigger import SingleTriggerDetector
26
+ from ._tiff_writer import ADTIFFWriter
16
27
  from ._utils import (
17
28
  ADBaseDataType,
18
- FileWriteMode,
19
- ImageMode,
29
+ ADFileWriteMode,
30
+ ADImageMode,
20
31
  NDAttributeDataType,
21
32
  NDAttributeParam,
22
33
  NDAttributePv,
@@ -26,22 +37,33 @@ from ._utils import (
26
37
 
27
38
  __all__ = [
28
39
  "ADBaseIO",
29
- "DetectorState",
40
+ "ADCallbacks",
41
+ "ADCompression",
42
+ "ADBaseContAcqController",
43
+ "AreaDetector",
44
+ "ADState",
45
+ "ContAcqAreaDetector",
30
46
  "NDArrayBaseIO",
47
+ "NDFileIO",
31
48
  "NDFileHDFIO",
49
+ "NDPluginBaseIO",
32
50
  "NDPluginStatsIO",
33
51
  "DEFAULT_GOOD_STATES",
34
52
  "ADBaseDatasetDescriber",
35
- "set_exposure_time_and_acquire_period_if_supplied",
36
- "start_acquiring_driver_and_ensure_status",
53
+ "ADBaseController",
54
+ "ADWriter",
37
55
  "ADHDFWriter",
56
+ "ADTIFFWriter",
57
+ "ADJPEGWriter",
38
58
  "SingleTriggerDetector",
39
59
  "ADBaseDataType",
40
- "FileWriteMode",
41
- "ImageMode",
60
+ "ADFileWriteMode",
61
+ "ADImageMode",
42
62
  "NDAttributePv",
43
63
  "NDAttributeParam",
44
64
  "NDAttributeDataType",
45
65
  "stop_busy_record",
46
66
  "NDAttributePvDbrType",
67
+ "NDCBFlushOnSoftTrgMode",
68
+ "NDPluginCBIO",
47
69
  ]
@@ -0,0 +1,81 @@
1
+ from collections.abc import Sequence
2
+
3
+ from ophyd_async.core import SignalR, StandardDetector
4
+ from ophyd_async.core._providers import PathProvider
5
+
6
+ from ._core_io import ADBaseIO, NDPluginBaseIO, NDPluginCBIO
7
+ from ._core_logic import ADBaseContAcqController, ADBaseControllerT
8
+ from ._core_writer import ADWriter
9
+ from ._hdf_writer import ADHDFWriter
10
+
11
+
12
+ class AreaDetector(StandardDetector[ADBaseControllerT, ADWriter]):
13
+ def __init__(
14
+ self,
15
+ controller: ADBaseControllerT,
16
+ writer: ADWriter,
17
+ plugins: dict[str, NDPluginBaseIO] | None = None,
18
+ config_sigs: Sequence[SignalR] = (),
19
+ name: str = "",
20
+ ):
21
+ self.driver = controller.driver
22
+ self.fileio = writer.fileio
23
+
24
+ if plugins is not None:
25
+ for name, plugin in plugins.items():
26
+ setattr(self, name, plugin)
27
+
28
+ super().__init__(
29
+ controller,
30
+ writer,
31
+ (self.driver.acquire_period, self.driver.acquire_time, *config_sigs),
32
+ name=name,
33
+ )
34
+
35
+ def get_plugin(
36
+ self, name: str, plugin_type: type[NDPluginBaseIO] = NDPluginBaseIO
37
+ ) -> NDPluginBaseIO:
38
+ plugin = getattr(self, name, None)
39
+ if not isinstance(plugin, plugin_type):
40
+ raise TypeError(
41
+ f"Expected {self.name}.{name} to be a {plugin_type}, got {plugin}"
42
+ )
43
+ return plugin
44
+
45
+
46
+ class ContAcqAreaDetector(AreaDetector[ADBaseContAcqController]):
47
+ """Ophyd-async implementation of a continuously acquiring AreaDetector."""
48
+
49
+ def __init__(
50
+ self,
51
+ prefix: str,
52
+ path_provider: PathProvider,
53
+ drv_cls: type[ADBaseIO] = ADBaseIO,
54
+ drv_suffix: str = "cam1:",
55
+ cb_suffix: str = "CB1:",
56
+ writer_cls: type[ADWriter] = ADHDFWriter,
57
+ fileio_suffix: str | None = None,
58
+ name: str = "",
59
+ plugins: dict[str, NDPluginBaseIO] | None = None,
60
+ config_sigs: Sequence[SignalR] = (),
61
+ ):
62
+ self.cb_plugin = NDPluginCBIO(prefix + cb_suffix)
63
+ driver = drv_cls(prefix + drv_suffix)
64
+ controller = ADBaseContAcqController(driver, self.cb_plugin)
65
+
66
+ writer = writer_cls.with_io(
67
+ prefix,
68
+ path_provider,
69
+ # Since the CB plugin controls acq, use it when checking shape
70
+ dataset_source=self.cb_plugin,
71
+ fileio_suffix=fileio_suffix,
72
+ plugins=plugins,
73
+ )
74
+
75
+ super().__init__(
76
+ controller=controller,
77
+ writer=writer,
78
+ plugins=plugins,
79
+ name=name,
80
+ config_sigs=config_sigs,
81
+ )
@@ -1,75 +1,96 @@
1
- from ophyd_async.core import Device, StrictEnum
2
- from ophyd_async.epics.core import (
3
- epics_signal_r,
4
- epics_signal_rw,
5
- epics_signal_rw_rbv,
6
- )
1
+ import asyncio
2
+ from typing import Annotated as A
7
3
 
8
- from ._utils import ADBaseDataType, FileWriteMode, ImageMode
4
+ from ophyd_async.core import DatasetDescriber, SignalR, SignalRW, StrictEnum
5
+ from ophyd_async.epics.core import EpicsDevice, PvSuffix
9
6
 
7
+ from ._utils import ADBaseDataType, ADFileWriteMode, ADImageMode, convert_ad_dtype_to_np
10
8
 
11
- class Callback(StrictEnum):
9
+
10
+ class ADCallbacks(StrictEnum):
12
11
  ENABLE = "Enable"
13
12
  DISABLE = "Disable"
14
13
 
15
14
 
16
- class NDArrayBaseIO(Device):
17
- def __init__(self, prefix: str, name: str = "") -> None:
18
- self.unique_id = epics_signal_r(int, prefix + "UniqueId_RBV")
19
- self.nd_attributes_file = epics_signal_rw(str, prefix + "NDAttributesFile")
20
- self.acquire = epics_signal_rw_rbv(bool, prefix + "Acquire")
21
- self.array_size_x = epics_signal_r(int, prefix + "ArraySizeX_RBV")
22
- self.array_size_y = epics_signal_r(int, prefix + "ArraySizeY_RBV")
23
- self.data_type = epics_signal_r(ADBaseDataType, prefix + "DataType_RBV")
24
- self.array_counter = epics_signal_rw_rbv(int, prefix + "ArrayCounter")
25
- # There is no _RBV for this one
26
- self.wait_for_plugins = epics_signal_rw(bool, prefix + "WaitForPlugins")
27
- super().__init__(name=name)
15
+ class NDArrayBaseIO(EpicsDevice):
16
+ """Class responsible for passing detector data from drivers to pluglins.
17
+
18
+ This mirrors the interface provided by ADCore/db/NDArrayBase.template.
19
+ See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDArray.html
20
+ """
28
21
 
22
+ unique_id: A[SignalR[int], PvSuffix("UniqueId_RBV")]
23
+ nd_attributes_file: A[SignalRW[str], PvSuffix("NDAttributesFile")]
24
+ acquire: A[SignalRW[bool], PvSuffix.rbv("Acquire")]
25
+ array_size_x: A[SignalR[int], PvSuffix("ArraySizeX_RBV")]
26
+ array_size_y: A[SignalR[int], PvSuffix("ArraySizeY_RBV")]
27
+ data_type: A[SignalR[ADBaseDataType], PvSuffix("DataType_RBV")]
28
+ array_counter: A[SignalRW[int], PvSuffix.rbv("ArrayCounter")]
29
+ # There is no _RBV for this one
30
+ wait_for_plugins: A[SignalRW[bool], PvSuffix("WaitForPlugins")]
29
31
 
30
- class NDPluginBaseIO(NDArrayBaseIO):
31
- def __init__(self, prefix: str, name: str = "") -> None:
32
- self.nd_array_port = epics_signal_rw_rbv(str, prefix + "NDArrayPort")
33
- self.enable_callbacks = epics_signal_rw_rbv(
34
- Callback, prefix + "EnableCallbacks"
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(),
35
44
  )
36
- self.nd_array_address = epics_signal_rw_rbv(int, prefix + "NDArrayAddress")
37
- self.array_size0 = epics_signal_r(int, prefix + "ArraySize0_RBV")
38
- self.array_size1 = epics_signal_r(int, prefix + "ArraySize1_RBV")
39
- super().__init__(prefix, name)
45
+ return shape
40
46
 
41
47
 
42
- class NDPluginStatsIO(NDPluginBaseIO):
43
- """
44
- Plugin for computing statistics from an image or region of interest within an image.
48
+ class NDPluginBaseIO(NDArrayBaseIO):
49
+ """Base class from which plugins are derived.
50
+
51
+ This mirrors the interface provided by ADCore/db/NDPluginBase.template.
52
+ See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDPluginDriver.html
45
53
  """
46
54
 
47
- def __init__(self, prefix: str, name: str = "") -> None:
48
- # Basic statistics
49
- self.compute_statistics = epics_signal_rw(bool, prefix + "ComputeStatistics")
50
- self.bgd_width = epics_signal_rw(int, prefix + "BgdWidth")
51
- self.total_array = epics_signal_rw(float, prefix + "TotalArray")
52
- # Centroid statistics
53
- self.compute_centroid = epics_signal_rw(bool, prefix + "ComputeCentroid")
54
- self.centroid_threshold = epics_signal_rw(float, prefix + "CentroidThreshold")
55
- # X and Y Profiles
56
- self.compute_profiles = epics_signal_rw(bool, prefix + "ComputeProfiles")
57
- self.profile_size_x = epics_signal_rw(int, prefix + "ProfileSizeX")
58
- self.profile_size_y = epics_signal_rw(int, prefix + "ProfileSizeY")
59
- self.cursor_x = epics_signal_rw(int, prefix + "CursorX")
60
- self.cursor_y = epics_signal_rw(int, prefix + "CursorY")
61
- # Array Histogram
62
- self.compute_histogram = epics_signal_rw(bool, prefix + "ComputeHistogram")
63
- self.hist_size = epics_signal_rw(int, prefix + "HistSize")
64
- self.hist_min = epics_signal_rw(float, prefix + "HistMin")
65
- self.hist_max = epics_signal_rw(float, prefix + "HistMax")
66
- super().__init__(prefix, name)
67
-
68
-
69
- class DetectorState(StrictEnum):
55
+ nd_array_port: A[SignalRW[str], PvSuffix.rbv("NDArrayPort")]
56
+ enable_callbacks: A[SignalRW[ADCallbacks], PvSuffix.rbv("EnableCallbacks")]
57
+ nd_array_address: A[SignalRW[int], PvSuffix.rbv("NDArrayAddress")]
58
+ array_size0: A[SignalR[int], PvSuffix("ArraySize0_RBV")]
59
+ array_size1: A[SignalR[int], PvSuffix("ArraySize1_RBV")]
60
+ queue_size: A[SignalRW[int], PvSuffix.rbv("QueueSize")]
61
+
62
+
63
+ class NDPluginStatsIO(NDPluginBaseIO):
64
+ """Plugin for computing statistics from an image or ROI within an image.
65
+
66
+ This mirrors the interface provided by ADCore/db/NDStats.template.
67
+ See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDPluginStats.html
70
68
  """
71
- Default set of states of an AreaDetector driver.
72
- See definition in ADApp/ADSrc/ADDriver.h in https://github.com/areaDetector/ADCore
69
+
70
+ # Basic statistics
71
+ compute_statistics: A[SignalRW[bool], PvSuffix.rbv("ComputeStatistics")]
72
+ bgd_width: A[SignalRW[int], PvSuffix.rbv("BgdWidth")]
73
+ total_array: A[SignalRW[float], PvSuffix.rbv("TotalArray")]
74
+ # Centroid statistics
75
+ compute_centroid: A[SignalRW[bool], PvSuffix.rbv("ComputeCentroid")]
76
+ centroid_threshold: A[SignalRW[float], PvSuffix.rbv("CentroidThreshold")]
77
+ # X and Y Profiles
78
+ compute_profiles: A[SignalRW[bool], PvSuffix.rbv("ComputeProfiles")]
79
+ profile_size_x: A[SignalRW[int], PvSuffix.rbv("ProfileSizeX")]
80
+ profile_size_y: A[SignalRW[int], PvSuffix.rbv("ProfileSizeY")]
81
+ cursor_x: A[SignalRW[int], PvSuffix.rbv("CursorX")]
82
+ cursor_y: A[SignalRW[int], PvSuffix.rbv("CursorY")]
83
+ # Array Histogram
84
+ compute_histogram: A[SignalRW[bool], PvSuffix.rbv("ComputeHistogram")]
85
+ hist_size: A[SignalRW[int], PvSuffix.rbv("HistSize")]
86
+ hist_min: A[SignalRW[float], PvSuffix.rbv("HistMin")]
87
+ hist_max: A[SignalRW[float], PvSuffix.rbv("HistMax")]
88
+
89
+
90
+ class ADState(StrictEnum):
91
+ """Default set of states of an AreaDetector driver.
92
+
93
+ See definition in ADApp/ADSrc/ADDriver.h in https://github.com/areaDetector/ADCore.
73
94
  """
74
95
 
75
96
  IDLE = "Idle"
@@ -86,19 +107,20 @@ class DetectorState(StrictEnum):
86
107
 
87
108
 
88
109
  class ADBaseIO(NDArrayBaseIO):
89
- def __init__(self, prefix: str, name: str = "") -> None:
90
- # Define some signals
91
- self.acquire_time = epics_signal_rw_rbv(float, prefix + "AcquireTime")
92
- self.acquire_period = epics_signal_rw_rbv(float, prefix + "AcquirePeriod")
93
- self.num_images = epics_signal_rw_rbv(int, prefix + "NumImages")
94
- self.image_mode = epics_signal_rw_rbv(ImageMode, prefix + "ImageMode")
95
- self.detector_state = epics_signal_r(
96
- DetectorState, prefix + "DetectorState_RBV"
97
- )
98
- super().__init__(prefix, name=name)
110
+ """Base class from which areaDetector drivers are derived.
111
+
112
+ This mirrors the interface provided by ADCore/db/ADBase.template.
113
+ See HTML docs at https://areadetector.github.io/areaDetector/ADCore/ADDriver.html
114
+ """
115
+
116
+ acquire_time: A[SignalRW[float], PvSuffix.rbv("AcquireTime")]
117
+ acquire_period: A[SignalRW[float], PvSuffix.rbv("AcquirePeriod")]
118
+ num_images: A[SignalRW[int], PvSuffix.rbv("NumImages")]
119
+ image_mode: A[SignalRW[ADImageMode], PvSuffix.rbv("ImageMode")]
120
+ detector_state: A[SignalR[ADState], PvSuffix("DetectorState_RBV")]
99
121
 
100
122
 
101
- class Compression(StrictEnum):
123
+ class ADCompression(StrictEnum):
102
124
  NONE = "None"
103
125
  NBIT = "N-bit"
104
126
  SZIP = "szip"
@@ -109,30 +131,58 @@ class Compression(StrictEnum):
109
131
  JPEG = "JPEG"
110
132
 
111
133
 
112
- class NDFileHDFIO(NDPluginBaseIO):
113
- 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
- self.file_path = epics_signal_rw_rbv(str, prefix + "FilePath")
119
- self.file_name = epics_signal_rw_rbv(str, prefix + "FileName")
120
- self.file_path_exists = epics_signal_r(bool, prefix + "FilePathExists_RBV")
121
- self.file_template = epics_signal_rw_rbv(str, prefix + "FileTemplate")
122
- self.full_file_name = epics_signal_r(str, prefix + "FullFileName_RBV")
123
- self.file_write_mode = epics_signal_rw_rbv(
124
- FileWriteMode, prefix + "FileWriteMode"
125
- )
126
- self.num_capture = epics_signal_rw_rbv(int, prefix + "NumCapture")
127
- 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
- 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
- self.array_size0 = epics_signal_r(int, prefix + "ArraySize0")
134
- self.array_size1 = epics_signal_r(int, prefix + "ArraySize1")
135
- self.create_directory = epics_signal_rw(int, prefix + "CreateDirectory")
136
- self.num_frames_chunks = epics_signal_r(int, prefix + "NumFramesChunks_RBV")
137
- self.chunk_size_auto = epics_signal_rw_rbv(bool, prefix + "ChunkSizeAuto")
138
- super().__init__(prefix, name)
134
+ class NDFileIO(NDPluginBaseIO):
135
+ """Base class from which file plugins are derived.
136
+
137
+ This mirrors the interface provided by ADCore/db/NDFile.template.
138
+ See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDPluginFile.html
139
+ """
140
+
141
+ file_path: A[SignalRW[str], PvSuffix.rbv("FilePath")]
142
+ file_name: A[SignalRW[str], PvSuffix.rbv("FileName")]
143
+ file_path_exists: A[SignalR[bool], PvSuffix("FilePathExists_RBV")]
144
+ file_template: A[SignalRW[str], PvSuffix.rbv("FileTemplate")]
145
+ full_file_name: A[SignalR[str], PvSuffix("FullFileName_RBV")]
146
+ file_number: A[SignalRW[int], PvSuffix("FileNumber")]
147
+ auto_increment: A[SignalRW[bool], PvSuffix("AutoIncrement")]
148
+ file_write_mode: A[SignalRW[ADFileWriteMode], PvSuffix.rbv("FileWriteMode")]
149
+ num_capture: A[SignalRW[int], PvSuffix.rbv("NumCapture")]
150
+ num_captured: A[SignalR[int], PvSuffix("NumCaptured_RBV")]
151
+ capture: A[SignalRW[bool], PvSuffix.rbv("Capture")]
152
+ array_size0: A[SignalR[int], PvSuffix("ArraySize0")]
153
+ array_size1: A[SignalR[int], PvSuffix("ArraySize1")]
154
+ create_directory: A[SignalRW[int], PvSuffix("CreateDirectory")]
155
+
156
+
157
+ class NDFileHDFIO(NDFileIO):
158
+ """Plugin for storing data in HDF5 file format.
159
+
160
+ This mirrors the interface provided by ADCore/db/NDFileHDF5.template.
161
+ See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDFileHDF5.html
162
+ """
163
+
164
+ position_mode: A[SignalRW[bool], PvSuffix.rbv("PositionMode")]
165
+ compression: A[SignalRW[ADCompression], PvSuffix.rbv("Compression")]
166
+ num_extra_dims: A[SignalRW[int], PvSuffix.rbv("NumExtraDims")]
167
+ swmr_mode: A[SignalRW[bool], PvSuffix.rbv("SWMRMode")]
168
+ flush_now: A[SignalRW[bool], PvSuffix("FlushNow")]
169
+ xml_file_name: A[SignalRW[str], PvSuffix.rbv("XMLFileName")]
170
+ num_frames_chunks: A[SignalR[int], PvSuffix("NumFramesChunks_RBV")]
171
+ chunk_size_auto: A[SignalRW[bool], PvSuffix.rbv("ChunkSizeAuto")]
172
+ lazy_open: A[SignalRW[bool], PvSuffix.rbv("LazyOpen")]
173
+
174
+
175
+ class NDCBFlushOnSoftTrgMode(StrictEnum):
176
+ ON_NEW_IMAGE = "OnNewImage"
177
+ IMMEDIATELY = "Immediately"
178
+
179
+
180
+ class NDPluginCBIO(NDPluginBaseIO):
181
+ pre_count: A[SignalRW[int], PvSuffix.rbv("PreCount")]
182
+ post_count: A[SignalRW[int], PvSuffix.rbv("PostCount")]
183
+ preset_trigger_count: A[SignalRW[int], PvSuffix.rbv("PresetTriggerCount")]
184
+ trigger: A[SignalRW[bool], PvSuffix.rbv("Trigger")]
185
+ capture: A[SignalRW[bool], PvSuffix.rbv("Capture")]
186
+ flush_on_soft_trg: A[
187
+ SignalRW[NDCBFlushOnSoftTrgMode], PvSuffix.rbv("FlushOnSoftTrg")
188
+ ]