ophyd-async 0.8.0a6__py3-none-any.whl → 0.9.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 (110) hide show
  1. ophyd_async/_version.py +2 -2
  2. ophyd_async/core/__init__.py +15 -46
  3. ophyd_async/core/_detector.py +68 -44
  4. ophyd_async/core/_device.py +120 -79
  5. ophyd_async/core/_device_filler.py +17 -8
  6. ophyd_async/core/_flyer.py +2 -2
  7. ophyd_async/core/_protocol.py +0 -28
  8. ophyd_async/core/_readable.py +30 -23
  9. ophyd_async/core/_settings.py +104 -0
  10. ophyd_async/core/_signal.py +91 -151
  11. ophyd_async/core/_signal_backend.py +4 -1
  12. ophyd_async/core/_soft_signal_backend.py +2 -1
  13. ophyd_async/core/_table.py +18 -10
  14. ophyd_async/core/_utils.py +30 -5
  15. ophyd_async/core/_yaml_settings.py +64 -0
  16. ophyd_async/epics/adandor/__init__.py +9 -0
  17. ophyd_async/epics/adandor/_andor.py +45 -0
  18. ophyd_async/epics/adandor/_andor_controller.py +49 -0
  19. ophyd_async/epics/adandor/_andor_io.py +36 -0
  20. ophyd_async/epics/adaravis/__init__.py +3 -1
  21. ophyd_async/epics/adaravis/_aravis.py +23 -37
  22. ophyd_async/epics/adaravis/_aravis_controller.py +21 -30
  23. ophyd_async/epics/adaravis/_aravis_io.py +4 -4
  24. ophyd_async/epics/adcore/__init__.py +15 -8
  25. ophyd_async/epics/adcore/_core_detector.py +41 -0
  26. ophyd_async/epics/adcore/_core_io.py +56 -31
  27. ophyd_async/epics/adcore/_core_logic.py +99 -86
  28. ophyd_async/epics/adcore/_core_writer.py +219 -0
  29. ophyd_async/epics/adcore/_hdf_writer.py +33 -59
  30. ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
  31. ophyd_async/epics/adcore/_single_trigger.py +5 -4
  32. ophyd_async/epics/adcore/_tiff_writer.py +26 -0
  33. ophyd_async/epics/adcore/_utils.py +37 -36
  34. ophyd_async/epics/adkinetix/_kinetix.py +29 -24
  35. ophyd_async/epics/adkinetix/_kinetix_controller.py +15 -27
  36. ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
  37. ophyd_async/epics/adpilatus/__init__.py +2 -2
  38. ophyd_async/epics/adpilatus/_pilatus.py +28 -40
  39. ophyd_async/epics/adpilatus/_pilatus_controller.py +47 -25
  40. ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
  41. ophyd_async/epics/adsimdetector/__init__.py +3 -3
  42. ophyd_async/epics/adsimdetector/_sim.py +33 -17
  43. ophyd_async/epics/advimba/_vimba.py +23 -23
  44. ophyd_async/epics/advimba/_vimba_controller.py +21 -35
  45. ophyd_async/epics/advimba/_vimba_io.py +23 -23
  46. ophyd_async/epics/core/_aioca.py +52 -21
  47. ophyd_async/epics/core/_p4p.py +59 -16
  48. ophyd_async/epics/core/_pvi_connector.py +4 -2
  49. ophyd_async/epics/core/_signal.py +9 -2
  50. ophyd_async/epics/core/_util.py +10 -1
  51. ophyd_async/epics/eiger/_eiger_controller.py +4 -4
  52. ophyd_async/epics/eiger/_eiger_io.py +3 -3
  53. ophyd_async/epics/motor.py +26 -15
  54. ophyd_async/epics/sim/_ioc.py +29 -0
  55. ophyd_async/epics/{demo → sim}/_mover.py +12 -6
  56. ophyd_async/epics/{demo → sim}/_sensor.py +2 -2
  57. ophyd_async/epics/testing/__init__.py +14 -14
  58. ophyd_async/epics/testing/_example_ioc.py +53 -67
  59. ophyd_async/epics/testing/_utils.py +17 -45
  60. ophyd_async/epics/testing/test_records.db +22 -0
  61. ophyd_async/fastcs/core.py +2 -2
  62. ophyd_async/fastcs/panda/__init__.py +0 -2
  63. ophyd_async/fastcs/panda/_block.py +9 -9
  64. ophyd_async/fastcs/panda/_control.py +9 -4
  65. ophyd_async/fastcs/panda/_hdf_panda.py +7 -2
  66. ophyd_async/fastcs/panda/_table.py +4 -1
  67. ophyd_async/fastcs/panda/_trigger.py +7 -7
  68. ophyd_async/plan_stubs/__init__.py +14 -0
  69. ophyd_async/plan_stubs/_ensure_connected.py +11 -17
  70. ophyd_async/plan_stubs/_fly.py +2 -2
  71. ophyd_async/plan_stubs/_nd_attributes.py +7 -5
  72. ophyd_async/plan_stubs/_panda.py +13 -0
  73. ophyd_async/plan_stubs/_settings.py +125 -0
  74. ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
  75. ophyd_async/sim/__init__.py +19 -0
  76. ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_controller.py +9 -2
  77. ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_generator.py +13 -6
  78. ophyd_async/sim/{demo/_sim_motor.py → _sim_motor.py} +34 -32
  79. ophyd_async/tango/core/_signal.py +3 -1
  80. ophyd_async/tango/core/_tango_transport.py +13 -15
  81. ophyd_async/tango/{demo → sim}/_mover.py +5 -2
  82. ophyd_async/testing/__init__.py +52 -0
  83. ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
  84. ophyd_async/testing/_assert.py +176 -0
  85. ophyd_async/{core → testing}/_mock_signal_utils.py +15 -11
  86. ophyd_async/testing/_one_of_everything.py +126 -0
  87. ophyd_async/testing/_wait_for_pending.py +22 -0
  88. {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0.dist-info}/METADATA +4 -2
  89. ophyd_async-0.9.0.dist-info/RECORD +129 -0
  90. {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0.dist-info}/WHEEL +1 -1
  91. ophyd_async/core/_device_save_loader.py +0 -274
  92. ophyd_async/epics/adsimdetector/_sim_controller.py +0 -51
  93. ophyd_async/fastcs/panda/_utils.py +0 -16
  94. ophyd_async/sim/demo/__init__.py +0 -19
  95. ophyd_async/sim/testing/__init__.py +0 -0
  96. ophyd_async-0.8.0a6.dist-info/RECORD +0 -116
  97. ophyd_async-0.8.0a6.dist-info/entry_points.txt +0 -2
  98. /ophyd_async/epics/{demo → sim}/__init__.py +0 -0
  99. /ophyd_async/epics/{demo → sim}/mover.db +0 -0
  100. /ophyd_async/epics/{demo → sim}/sensor.db +0 -0
  101. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/__init__.py +0 -0
  102. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector.py +0 -0
  103. /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_writer.py +0 -0
  104. /ophyd_async/tango/{demo → sim}/__init__.py +0 -0
  105. /ophyd_async/tango/{demo → sim}/_counter.py +0 -0
  106. /ophyd_async/tango/{demo → sim}/_detector.py +0 -0
  107. /ophyd_async/tango/{demo → sim}/_tango/__init__.py +0 -0
  108. /ophyd_async/tango/{demo → sim}/_tango/_servers.py +0 -0
  109. {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0.dist-info}/LICENSE +0 -0
  110. {ophyd_async-0.8.0a6.dist-info → ophyd_async-0.9.0.dist-info}/top_level.txt +0 -0
@@ -6,47 +6,48 @@ from ophyd_async.core import (
6
6
  SignalR,
7
7
  SignalRW,
8
8
  StrictEnum,
9
+ SubsetEnum,
9
10
  wait_for_value,
10
11
  )
11
12
 
12
13
 
13
14
  class ADBaseDataType(StrictEnum):
14
- Int8 = "Int8"
15
- UInt8 = "UInt8"
16
- Int16 = "Int16"
17
- UInt16 = "UInt16"
18
- Int32 = "Int32"
19
- UInt32 = "UInt32"
20
- Int64 = "Int64"
21
- UInt64 = "UInt64"
22
- Float32 = "Float32"
23
- Float64 = "Float64"
15
+ INT8 = "Int8"
16
+ UINT8 = "UInt8"
17
+ INT16 = "Int16"
18
+ UINT16 = "UInt16"
19
+ INT32 = "Int32"
20
+ UINT32 = "UInt32"
21
+ INT64 = "Int64"
22
+ UINT64 = "UInt64"
23
+ FLOAT32 = "Float32"
24
+ FLOAT64 = "Float64"
24
25
 
25
26
 
26
27
  def convert_ad_dtype_to_np(ad_dtype: ADBaseDataType) -> str:
27
28
  ad_dtype_to_np_dtype = {
28
- ADBaseDataType.Int8: "|i1",
29
- ADBaseDataType.UInt8: "|u1",
30
- ADBaseDataType.Int16: "<i2",
31
- ADBaseDataType.UInt16: "<u2",
32
- ADBaseDataType.Int32: "<i4",
33
- ADBaseDataType.UInt32: "<u4",
34
- ADBaseDataType.Int64: "<i8",
35
- ADBaseDataType.UInt64: "<u8",
36
- ADBaseDataType.Float32: "<f4",
37
- ADBaseDataType.Float64: "<f8",
29
+ ADBaseDataType.INT8: "|i1",
30
+ ADBaseDataType.UINT8: "|u1",
31
+ ADBaseDataType.INT16: "<i2",
32
+ ADBaseDataType.UINT16: "<u2",
33
+ ADBaseDataType.INT32: "<i4",
34
+ ADBaseDataType.UINT32: "<u4",
35
+ ADBaseDataType.INT64: "<i8",
36
+ ADBaseDataType.UINT64: "<u8",
37
+ ADBaseDataType.FLOAT32: "<f4",
38
+ ADBaseDataType.FLOAT64: "<f8",
38
39
  }
39
40
  return ad_dtype_to_np_dtype[ad_dtype]
40
41
 
41
42
 
42
43
  def convert_pv_dtype_to_np(datatype: str) -> str:
43
44
  _pvattribute_to_ad_datatype = {
44
- "DBR_SHORT": ADBaseDataType.Int16,
45
- "DBR_ENUM": ADBaseDataType.Int16,
46
- "DBR_INT": ADBaseDataType.Int32,
47
- "DBR_LONG": ADBaseDataType.Int32,
48
- "DBR_FLOAT": ADBaseDataType.Float32,
49
- "DBR_DOUBLE": ADBaseDataType.Float64,
45
+ "DBR_SHORT": ADBaseDataType.INT16,
46
+ "DBR_ENUM": ADBaseDataType.INT16,
47
+ "DBR_INT": ADBaseDataType.INT32,
48
+ "DBR_LONG": ADBaseDataType.INT32,
49
+ "DBR_FLOAT": ADBaseDataType.FLOAT32,
50
+ "DBR_DOUBLE": ADBaseDataType.FLOAT64,
50
51
  }
51
52
  if datatype in ["DBR_STRING", "DBR_CHAR"]:
52
53
  np_datatype = "s40"
@@ -62,9 +63,9 @@ def convert_pv_dtype_to_np(datatype: str) -> str:
62
63
 
63
64
  def convert_param_dtype_to_np(datatype: str) -> str:
64
65
  _paramattribute_to_ad_datatype = {
65
- "INT": ADBaseDataType.Int32,
66
- "INT64": ADBaseDataType.Int64,
67
- "DOUBLE": ADBaseDataType.Float64,
66
+ "INT": ADBaseDataType.INT32,
67
+ "INT64": ADBaseDataType.INT64,
68
+ "DOUBLE": ADBaseDataType.FLOAT64,
68
69
  }
69
70
  if datatype in ["STRING"]:
70
71
  np_datatype = "s40"
@@ -79,15 +80,15 @@ def convert_param_dtype_to_np(datatype: str) -> str:
79
80
 
80
81
 
81
82
  class FileWriteMode(StrictEnum):
82
- single = "Single"
83
- capture = "Capture"
84
- stream = "Stream"
83
+ SINGLE = "Single"
84
+ CAPTURE = "Capture"
85
+ STREAM = "Stream"
85
86
 
86
87
 
87
- class ImageMode(StrictEnum):
88
- single = "Single"
89
- multiple = "Multiple"
90
- continuous = "Continuous"
88
+ class ImageMode(SubsetEnum):
89
+ SINGLE = "Single"
90
+ MULTIPLE = "Multiple"
91
+ CONTINUOUS = "Continuous"
91
92
 
92
93
 
93
94
  class NDAttributeDataType(StrictEnum):
@@ -1,44 +1,49 @@
1
- from bluesky.protocols import HasHints, Hints
1
+ from collections.abc import Sequence
2
2
 
3
- from ophyd_async.core import PathProvider, StandardDetector
4
- from ophyd_async.epics import adcore
3
+ from ophyd_async.core import PathProvider, SignalR
4
+ from ophyd_async.epics.adcore import (
5
+ ADHDFWriter,
6
+ ADWriter,
7
+ AreaDetector,
8
+ NDPluginBaseIO,
9
+ )
5
10
 
6
11
  from ._kinetix_controller import KinetixController
7
12
  from ._kinetix_io import KinetixDriverIO
8
13
 
9
14
 
10
- class KinetixDetector(StandardDetector, HasHints):
15
+ class KinetixDetector(AreaDetector[KinetixController]):
11
16
  """
12
17
  Ophyd-async implementation of an ADKinetix Detector.
13
18
  https://github.com/NSLS-II/ADKinetix
14
19
  """
15
20
 
16
- _controller: KinetixController
17
- _writer: adcore.ADHDFWriter
18
-
19
21
  def __init__(
20
22
  self,
21
23
  prefix: str,
22
24
  path_provider: PathProvider,
23
- drv_suffix="cam1:",
24
- hdf_suffix="HDF1:",
25
- name="",
25
+ drv_suffix: str = "cam1:",
26
+ writer_cls: type[ADWriter] = ADHDFWriter,
27
+ fileio_suffix: str | None = None,
28
+ name: str = "",
29
+ plugins: dict[str, NDPluginBaseIO] | None = None,
30
+ config_sigs: Sequence[SignalR] = (),
26
31
  ):
27
- self.drv = KinetixDriverIO(prefix + drv_suffix)
28
- self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
32
+ driver = KinetixDriverIO(prefix + drv_suffix)
33
+ controller = KinetixController(driver)
34
+
35
+ writer = writer_cls.with_io(
36
+ prefix,
37
+ path_provider,
38
+ dataset_source=driver,
39
+ fileio_suffix=fileio_suffix,
40
+ plugins=plugins,
41
+ )
29
42
 
30
43
  super().__init__(
31
- KinetixController(self.drv),
32
- adcore.ADHDFWriter(
33
- self.hdf,
34
- path_provider,
35
- lambda: self.name,
36
- adcore.ADBaseDatasetDescriber(self.drv),
37
- ),
38
- config_sigs=(self.drv.acquire_time,),
44
+ controller=controller,
45
+ writer=writer,
46
+ plugins=plugins,
39
47
  name=name,
48
+ config_sigs=config_sigs,
40
49
  )
41
-
42
- @property
43
- def hints(self) -> Hints:
44
- return self._writer.hints
@@ -1,8 +1,6 @@
1
1
  import asyncio
2
2
 
3
3
  from ophyd_async.core import (
4
- AsyncStatus,
5
- DetectorController,
6
4
  DetectorTrigger,
7
5
  TriggerInfo,
8
6
  )
@@ -11,44 +9,34 @@ from ophyd_async.epics import adcore
11
9
  from ._kinetix_io import KinetixDriverIO, KinetixTriggerMode
12
10
 
13
11
  KINETIX_TRIGGER_MODE_MAP = {
14
- DetectorTrigger.internal: KinetixTriggerMode.internal,
15
- DetectorTrigger.constant_gate: KinetixTriggerMode.gate,
16
- DetectorTrigger.variable_gate: KinetixTriggerMode.gate,
17
- DetectorTrigger.edge_trigger: KinetixTriggerMode.edge,
12
+ DetectorTrigger.INTERNAL: KinetixTriggerMode.INTERNAL,
13
+ DetectorTrigger.CONSTANT_GATE: KinetixTriggerMode.GATE,
14
+ DetectorTrigger.VARIABLE_GATE: KinetixTriggerMode.GATE,
15
+ DetectorTrigger.EDGE_TRIGGER: KinetixTriggerMode.EDGE,
18
16
  }
19
17
 
20
18
 
21
- class KinetixController(DetectorController):
19
+ class KinetixController(adcore.ADBaseController[KinetixDriverIO]):
22
20
  def __init__(
23
21
  self,
24
22
  driver: KinetixDriverIO,
23
+ good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
25
24
  ) -> None:
26
- self._drv = driver
27
- self._arm_status: AsyncStatus | None = None
25
+ super().__init__(driver, good_states=good_states)
28
26
 
29
27
  def get_deadtime(self, exposure: float | None) -> float:
30
28
  return 0.001
31
29
 
32
30
  async def prepare(self, trigger_info: TriggerInfo):
33
31
  await asyncio.gather(
34
- self._drv.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger_info.trigger]),
35
- self._drv.num_images.set(trigger_info.total_number_of_triggers),
36
- self._drv.image_mode.set(adcore.ImageMode.multiple),
32
+ self.driver.trigger_mode.set(
33
+ KINETIX_TRIGGER_MODE_MAP[trigger_info.trigger]
34
+ ),
35
+ self.driver.num_images.set(trigger_info.total_number_of_triggers),
36
+ self.driver.image_mode.set(adcore.ImageMode.MULTIPLE),
37
37
  )
38
38
  if trigger_info.livetime is not None and trigger_info.trigger not in [
39
- DetectorTrigger.variable_gate,
40
- DetectorTrigger.constant_gate,
39
+ DetectorTrigger.VARIABLE_GATE,
40
+ DetectorTrigger.CONSTANT_GATE,
41
41
  ]:
42
- await self._drv.acquire_time.set(trigger_info.livetime)
43
-
44
- async def arm(self):
45
- self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
46
- self._drv
47
- )
48
-
49
- async def wait_for_idle(self):
50
- if self._arm_status:
51
- await self._arm_status
52
-
53
- async def disarm(self):
54
- await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
42
+ await self.driver.acquire_time.set(trigger_info.livetime)
@@ -4,16 +4,16 @@ from ophyd_async.epics.core import epics_signal_rw_rbv
4
4
 
5
5
 
6
6
  class KinetixTriggerMode(StrictEnum):
7
- internal = "Internal"
8
- edge = "Rising Edge"
9
- gate = "Exp. Gate"
7
+ INTERNAL = "Internal"
8
+ EDGE = "Rising Edge"
9
+ GATE = "Exp. Gate"
10
10
 
11
11
 
12
12
  class KinetixReadoutMode(StrictEnum):
13
- sensitivity = 1
14
- speed = 2
15
- dynamic_range = 3
16
- sub_electron = 4
13
+ SENSITIVITY = 1
14
+ SPEED = 2
15
+ DYNAMIC_RANGE = 3
16
+ SUB_ELECTRON = 4
17
17
 
18
18
 
19
19
  class KinetixDriverIO(adcore.ADBaseIO):
@@ -1,5 +1,5 @@
1
- from ._pilatus import PilatusDetector, PilatusReadoutTime
2
- from ._pilatus_controller import PilatusController
1
+ from ._pilatus import PilatusDetector
2
+ from ._pilatus_controller import PilatusController, PilatusReadoutTime
3
3
  from ._pilatus_io import PilatusDriverIO, PilatusTriggerMode
4
4
 
5
5
  __all__ = [
@@ -1,58 +1,46 @@
1
- from enum import Enum
1
+ from collections.abc import Sequence
2
2
 
3
- from bluesky.protocols import Hints
3
+ from ophyd_async.core import PathProvider
4
+ from ophyd_async.core._signal import SignalR
5
+ from ophyd_async.epics.adcore._core_detector import AreaDetector
6
+ from ophyd_async.epics.adcore._core_io import NDPluginBaseIO
7
+ from ophyd_async.epics.adcore._core_writer import ADWriter
8
+ from ophyd_async.epics.adcore._hdf_writer import ADHDFWriter
4
9
 
5
- from ophyd_async.core import PathProvider, StandardDetector
6
- from ophyd_async.epics import adcore
7
-
8
- from ._pilatus_controller import PilatusController
10
+ from ._pilatus_controller import PilatusController, PilatusReadoutTime
9
11
  from ._pilatus_io import PilatusDriverIO
10
12
 
11
13
 
12
- #: Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
13
- #: The required minimum time difference between ExpPeriod and ExpTime
14
- #: (readout time) is 2.28 ms
15
- #: We provide an option to override for newer Pilatus models
16
- class PilatusReadoutTime(float, Enum):
17
- """Pilatus readout time per model in ms"""
18
-
19
- # Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
20
- pilatus2 = 2.28e-3
21
-
22
- # Cite: https://media.dectris.com/user-manual-pilatus3-2020.pdf
23
- pilatus3 = 0.95e-3
24
-
25
-
26
- class PilatusDetector(StandardDetector):
14
+ class PilatusDetector(AreaDetector[PilatusController]):
27
15
  """A Pilatus StandardDetector writing HDF files"""
28
16
 
29
- _controller: PilatusController
30
- _writer: adcore.ADHDFWriter
31
-
32
17
  def __init__(
33
18
  self,
34
19
  prefix: str,
35
20
  path_provider: PathProvider,
36
- readout_time: PilatusReadoutTime = PilatusReadoutTime.pilatus3,
21
+ readout_time: PilatusReadoutTime = PilatusReadoutTime.PILATUS3,
37
22
  drv_suffix: str = "cam1:",
38
- hdf_suffix: str = "HDF1:",
23
+ writer_cls: type[ADWriter] = ADHDFWriter,
24
+ fileio_suffix: str | None = None,
39
25
  name: str = "",
26
+ plugins: dict[str, NDPluginBaseIO] | None = None,
27
+ config_sigs: Sequence[SignalR] = (),
40
28
  ):
41
- self.drv = PilatusDriverIO(prefix + drv_suffix)
42
- self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
29
+ driver = PilatusDriverIO(prefix + drv_suffix)
30
+ controller = PilatusController(driver)
31
+
32
+ writer = writer_cls.with_io(
33
+ prefix,
34
+ path_provider,
35
+ dataset_source=driver,
36
+ fileio_suffix=fileio_suffix,
37
+ plugins=plugins,
38
+ )
43
39
 
44
40
  super().__init__(
45
- PilatusController(self.drv, readout_time=readout_time.value),
46
- adcore.ADHDFWriter(
47
- self.hdf,
48
- path_provider,
49
- lambda: self.name,
50
- adcore.ADBaseDatasetDescriber(self.drv),
51
- ),
52
- config_sigs=(self.drv.acquire_time,),
41
+ controller=controller,
42
+ writer=writer,
43
+ plugins=plugins,
53
44
  name=name,
45
+ config_sigs=config_sigs,
54
46
  )
55
-
56
- @property
57
- def hints(self) -> Hints:
58
- return self._writer.hints
@@ -1,9 +1,9 @@
1
1
  import asyncio
2
+ from enum import Enum
3
+ from typing import TypeVar, get_args
2
4
 
3
5
  from ophyd_async.core import (
4
6
  DEFAULT_TIMEOUT,
5
- AsyncStatus,
6
- DetectorController,
7
7
  DetectorTrigger,
8
8
  TriggerInfo,
9
9
  wait_for_value,
@@ -13,58 +13,83 @@ from ophyd_async.epics import adcore
13
13
  from ._pilatus_io import PilatusDriverIO, PilatusTriggerMode
14
14
 
15
15
 
16
- class PilatusController(DetectorController):
16
+ #: Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
17
+ #: The required minimum time difference between ExpPeriod and ExpTime
18
+ #: (readout time) is 2.28 ms
19
+ #: We provide an option to override for newer Pilatus models
20
+ class PilatusReadoutTime(float, Enum):
21
+ """Pilatus readout time per model in ms"""
22
+
23
+ # Cite: https://media.dectris.com/User_Manual-PILATUS2-V1_4.pdf
24
+ PILATUS2 = 2.28e-3
25
+
26
+ # Cite: https://media.dectris.com/user-manual-pilatus3-2020.pdf
27
+ PILATUS3 = 0.95e-3
28
+
29
+
30
+ PilatusControllerT = TypeVar("PilatusControllerT", bound="PilatusController")
31
+
32
+
33
+ class PilatusController(adcore.ADBaseController[PilatusDriverIO]):
17
34
  _supported_trigger_types = {
18
- DetectorTrigger.internal: PilatusTriggerMode.internal,
19
- DetectorTrigger.constant_gate: PilatusTriggerMode.ext_enable,
20
- DetectorTrigger.variable_gate: PilatusTriggerMode.ext_enable,
35
+ DetectorTrigger.INTERNAL: PilatusTriggerMode.INTERNAL,
36
+ DetectorTrigger.CONSTANT_GATE: PilatusTriggerMode.EXT_ENABLE,
37
+ DetectorTrigger.VARIABLE_GATE: PilatusTriggerMode.EXT_ENABLE,
21
38
  }
22
39
 
23
40
  def __init__(
24
41
  self,
25
42
  driver: PilatusDriverIO,
26
- readout_time: float,
43
+ good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
44
+ readout_time: float = PilatusReadoutTime.PILATUS3,
27
45
  ) -> None:
28
- self._drv = driver
46
+ super().__init__(driver, good_states=good_states)
29
47
  self._readout_time = readout_time
30
- self._arm_status: AsyncStatus | None = None
48
+
49
+ @classmethod
50
+ def controller_and_drv(
51
+ cls: type[PilatusControllerT],
52
+ prefix: str,
53
+ good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
54
+ name: str = "",
55
+ readout_time: float = PilatusReadoutTime.PILATUS3,
56
+ ) -> tuple[PilatusControllerT, PilatusDriverIO]:
57
+ driver_cls = get_args(cls.__orig_bases__[0])[0] # type: ignore
58
+ driver = driver_cls(prefix, name=name)
59
+ controller = cls(driver, good_states=good_states, readout_time=readout_time)
60
+ return controller, driver
31
61
 
32
62
  def get_deadtime(self, exposure: float | None) -> float:
33
63
  return self._readout_time
34
64
 
35
65
  async def prepare(self, trigger_info: TriggerInfo):
36
66
  if trigger_info.livetime is not None:
37
- await adcore.set_exposure_time_and_acquire_period_if_supplied(
38
- self, self._drv, trigger_info.livetime
67
+ await self.set_exposure_time_and_acquire_period_if_supplied(
68
+ trigger_info.livetime
39
69
  )
40
70
  await asyncio.gather(
41
- self._drv.trigger_mode.set(self._get_trigger_mode(trigger_info.trigger)),
42
- self._drv.num_images.set(
71
+ self.driver.trigger_mode.set(self._get_trigger_mode(trigger_info.trigger)),
72
+ self.driver.num_images.set(
43
73
  999_999
44
74
  if trigger_info.total_number_of_triggers == 0
45
75
  else trigger_info.total_number_of_triggers
46
76
  ),
47
- self._drv.image_mode.set(adcore.ImageMode.multiple),
77
+ self.driver.image_mode.set(adcore.ImageMode.MULTIPLE),
48
78
  )
49
79
 
50
80
  async def arm(self):
51
81
  # Standard arm the detector and wait for the acquire PV to be True
52
- self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
53
- self._drv
54
- )
82
+ self._arm_status = await self.start_acquiring_driver_and_ensure_status()
83
+
55
84
  # The pilatus has an additional PV that goes True when the camserver
56
85
  # is actually ready. Should wait for that too or we risk dropping
57
86
  # a frame
58
87
  await wait_for_value(
59
- self._drv.armed,
88
+ self.driver.armed,
60
89
  True,
61
90
  timeout=DEFAULT_TIMEOUT,
62
91
  )
63
92
 
64
- async def wait_for_idle(self):
65
- if self._arm_status:
66
- await self._arm_status
67
-
68
93
  @classmethod
69
94
  def _get_trigger_mode(cls, trigger: DetectorTrigger) -> PilatusTriggerMode:
70
95
  if trigger not in cls._supported_trigger_types.keys():
@@ -74,6 +99,3 @@ class PilatusController(DetectorController):
74
99
  f"use {trigger}"
75
100
  )
76
101
  return cls._supported_trigger_types[trigger]
77
-
78
- async def disarm(self):
79
- await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
@@ -4,11 +4,11 @@ from ophyd_async.epics.core import epics_signal_r, epics_signal_rw_rbv
4
4
 
5
5
 
6
6
  class PilatusTriggerMode(StrictEnum):
7
- internal = "Internal"
8
- ext_enable = "Ext. Enable"
9
- ext_trigger = "Ext. Trigger"
10
- mult_trigger = "Mult. Trigger"
11
- alignment = "Alignment"
7
+ INTERNAL = "Internal"
8
+ EXT_ENABLE = "Ext. Enable"
9
+ EXT_TRIGGER = "Ext. Trigger"
10
+ MULT_TRIGGER = "Mult. Trigger"
11
+ ALIGNMENT = "Alignment"
12
12
 
13
13
 
14
14
  class PilatusDriverIO(adcore.ADBaseIO):
@@ -1,7 +1,7 @@
1
- from ._sim import SimDetector
2
- from ._sim_controller import SimController
1
+ from ._sim import SimController, SimDetector, SimDriverIO
3
2
 
4
3
  __all__ = [
5
- "SimDetector",
4
+ "SimDriverIO",
6
5
  "SimController",
6
+ "SimDetector",
7
7
  ]
@@ -1,35 +1,51 @@
1
1
  from collections.abc import Sequence
2
2
 
3
- from ophyd_async.core import PathProvider, SignalR, StandardDetector
3
+ from ophyd_async.core import PathProvider, SignalR
4
4
  from ophyd_async.epics import adcore
5
5
 
6
- from ._sim_controller import SimController
7
6
 
7
+ class SimDriverIO(adcore.ADBaseIO): ...
8
8
 
9
- class SimDetector(StandardDetector):
10
- _controller: SimController
11
- _writer: adcore.ADHDFWriter
12
9
 
10
+ class SimController(adcore.ADBaseController[SimDriverIO]):
11
+ def __init__(
12
+ self,
13
+ driver: SimDriverIO,
14
+ good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
15
+ ) -> None:
16
+ super().__init__(driver, good_states=good_states)
17
+
18
+ def get_deadtime(self, exposure: float | None) -> float:
19
+ return 0.001
20
+
21
+
22
+ class SimDetector(adcore.AreaDetector[SimController]):
13
23
  def __init__(
14
24
  self,
15
25
  prefix: str,
16
26
  path_provider: PathProvider,
17
27
  drv_suffix="cam1:",
18
- hdf_suffix="HDF1:",
19
- name: str = "",
28
+ writer_cls: type[adcore.ADWriter] = adcore.ADHDFWriter,
29
+ fileio_suffix: str | None = None,
30
+ name="",
20
31
  config_sigs: Sequence[SignalR] = (),
32
+ plugins: dict[str, adcore.NDPluginBaseIO] | None = None,
21
33
  ):
22
- self.drv = adcore.ADBaseIO(prefix + drv_suffix)
23
- self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
34
+ driver = SimDriverIO(prefix + drv_suffix)
35
+ controller = SimController(driver)
36
+
37
+ writer = writer_cls.with_io(
38
+ prefix,
39
+ path_provider,
40
+ dataset_source=driver,
41
+ fileio_suffix=fileio_suffix,
42
+ plugins=plugins,
43
+ )
24
44
 
25
45
  super().__init__(
26
- SimController(self.drv),
27
- adcore.ADHDFWriter(
28
- self.hdf,
29
- path_provider,
30
- lambda: self.name,
31
- adcore.ADBaseDatasetDescriber(self.drv),
32
- ),
33
- config_sigs=(self.drv.acquire_period, self.drv.acquire_time, *config_sigs),
46
+ controller=controller,
47
+ writer=writer,
48
+ plugins=plugins,
34
49
  name=name,
50
+ config_sigs=config_sigs,
35
51
  )
@@ -1,43 +1,43 @@
1
- from bluesky.protocols import HasHints, Hints
1
+ from collections.abc import Sequence
2
2
 
3
- from ophyd_async.core import PathProvider, StandardDetector
3
+ from ophyd_async.core import PathProvider, SignalR
4
4
  from ophyd_async.epics import adcore
5
5
 
6
6
  from ._vimba_controller import VimbaController
7
7
  from ._vimba_io import VimbaDriverIO
8
8
 
9
9
 
10
- class VimbaDetector(StandardDetector, HasHints):
10
+ class VimbaDetector(adcore.AreaDetector[VimbaController]):
11
11
  """
12
12
  Ophyd-async implementation of an ADVimba Detector.
13
13
  """
14
14
 
15
- _controller: VimbaController
16
- _writer: adcore.ADHDFWriter
17
-
18
15
  def __init__(
19
16
  self,
20
17
  prefix: str,
21
18
  path_provider: PathProvider,
22
- drv_suffix="cam1:",
23
- hdf_suffix="HDF1:",
24
- name="",
19
+ drv_suffix: str = "cam1:",
20
+ writer_cls: type[adcore.ADWriter] = adcore.ADHDFWriter,
21
+ fileio_suffix: str | None = None,
22
+ name: str = "",
23
+ plugins: dict[str, adcore.NDPluginBaseIO] | None = None,
24
+ config_sigs: Sequence[SignalR] = (),
25
25
  ):
26
- self.drv = VimbaDriverIO(prefix + drv_suffix)
27
- self.hdf = adcore.NDFileHDFIO(prefix + hdf_suffix)
26
+ driver = VimbaDriverIO(prefix + drv_suffix)
27
+ controller = VimbaController(driver)
28
+
29
+ writer = writer_cls.with_io(
30
+ prefix,
31
+ path_provider,
32
+ dataset_source=driver,
33
+ fileio_suffix=fileio_suffix,
34
+ plugins=plugins,
35
+ )
28
36
 
29
37
  super().__init__(
30
- VimbaController(self.drv),
31
- adcore.ADHDFWriter(
32
- self.hdf,
33
- path_provider,
34
- lambda: self.name,
35
- adcore.ADBaseDatasetDescriber(self.drv),
36
- ),
37
- config_sigs=(self.drv.acquire_time,),
38
+ controller=controller,
39
+ writer=writer,
40
+ plugins=plugins,
38
41
  name=name,
42
+ config_sigs=config_sigs,
39
43
  )
40
-
41
- @property
42
- def hints(self) -> Hints:
43
- return self._writer.hints