ophyd-async 0.1.0__py3-none-any.whl → 0.3.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 (94) hide show
  1. ophyd_async/__init__.py +1 -4
  2. ophyd_async/_version.py +2 -2
  3. ophyd_async/core/__init__.py +91 -19
  4. ophyd_async/core/_providers.py +68 -0
  5. ophyd_async/core/async_status.py +90 -42
  6. ophyd_async/core/detector.py +341 -0
  7. ophyd_async/core/device.py +226 -0
  8. ophyd_async/core/device_save_loader.py +286 -0
  9. ophyd_async/core/flyer.py +85 -0
  10. ophyd_async/core/mock_signal_backend.py +82 -0
  11. ophyd_async/core/mock_signal_utils.py +145 -0
  12. ophyd_async/core/{_device/_signal/signal.py → signal.py} +249 -61
  13. ophyd_async/core/{_device/_backend/signal_backend.py → signal_backend.py} +12 -5
  14. ophyd_async/core/{_device/_backend/sim_signal_backend.py → soft_signal_backend.py} +54 -48
  15. ophyd_async/core/standard_readable.py +261 -0
  16. ophyd_async/core/utils.py +127 -30
  17. ophyd_async/epics/_backend/_aioca.py +62 -43
  18. ophyd_async/epics/_backend/_p4p.py +100 -52
  19. ophyd_async/epics/_backend/common.py +25 -0
  20. ophyd_async/epics/areadetector/__init__.py +16 -15
  21. ophyd_async/epics/areadetector/aravis.py +63 -0
  22. ophyd_async/epics/areadetector/controllers/__init__.py +5 -0
  23. ophyd_async/epics/areadetector/controllers/ad_sim_controller.py +52 -0
  24. ophyd_async/epics/areadetector/controllers/aravis_controller.py +78 -0
  25. ophyd_async/epics/areadetector/controllers/kinetix_controller.py +49 -0
  26. ophyd_async/epics/areadetector/controllers/pilatus_controller.py +61 -0
  27. ophyd_async/epics/areadetector/controllers/vimba_controller.py +66 -0
  28. ophyd_async/epics/areadetector/drivers/__init__.py +21 -0
  29. ophyd_async/epics/areadetector/drivers/ad_base.py +107 -0
  30. ophyd_async/epics/areadetector/drivers/aravis_driver.py +38 -0
  31. ophyd_async/epics/areadetector/drivers/kinetix_driver.py +27 -0
  32. ophyd_async/epics/areadetector/drivers/pilatus_driver.py +21 -0
  33. ophyd_async/epics/areadetector/drivers/vimba_driver.py +63 -0
  34. ophyd_async/epics/areadetector/kinetix.py +46 -0
  35. ophyd_async/epics/areadetector/pilatus.py +45 -0
  36. ophyd_async/epics/areadetector/single_trigger_det.py +18 -10
  37. ophyd_async/epics/areadetector/utils.py +91 -13
  38. ophyd_async/epics/areadetector/vimba.py +43 -0
  39. ophyd_async/epics/areadetector/writers/__init__.py +5 -0
  40. ophyd_async/epics/areadetector/writers/_hdfdataset.py +10 -0
  41. ophyd_async/epics/areadetector/writers/_hdffile.py +54 -0
  42. ophyd_async/epics/areadetector/writers/hdf_writer.py +142 -0
  43. ophyd_async/epics/areadetector/writers/nd_file_hdf.py +40 -0
  44. ophyd_async/epics/areadetector/writers/nd_plugin.py +38 -0
  45. ophyd_async/epics/demo/__init__.py +78 -51
  46. ophyd_async/epics/demo/demo_ad_sim_detector.py +35 -0
  47. ophyd_async/epics/motion/motor.py +67 -52
  48. ophyd_async/epics/pvi/__init__.py +3 -0
  49. ophyd_async/epics/pvi/pvi.py +318 -0
  50. ophyd_async/epics/signal/__init__.py +8 -3
  51. ophyd_async/epics/signal/signal.py +27 -10
  52. ophyd_async/log.py +130 -0
  53. ophyd_async/panda/__init__.py +24 -7
  54. ophyd_async/panda/_common_blocks.py +49 -0
  55. ophyd_async/panda/_hdf_panda.py +48 -0
  56. ophyd_async/panda/_panda_controller.py +37 -0
  57. ophyd_async/panda/_table.py +158 -0
  58. ophyd_async/panda/_trigger.py +39 -0
  59. ophyd_async/panda/_utils.py +15 -0
  60. ophyd_async/panda/writers/__init__.py +3 -0
  61. ophyd_async/panda/writers/_hdf_writer.py +220 -0
  62. ophyd_async/panda/writers/_panda_hdf_file.py +58 -0
  63. ophyd_async/plan_stubs/__init__.py +13 -0
  64. ophyd_async/plan_stubs/ensure_connected.py +22 -0
  65. ophyd_async/plan_stubs/fly.py +149 -0
  66. ophyd_async/protocols.py +126 -0
  67. ophyd_async/sim/__init__.py +11 -0
  68. ophyd_async/sim/demo/__init__.py +3 -0
  69. ophyd_async/sim/demo/sim_motor.py +103 -0
  70. ophyd_async/sim/pattern_generator.py +318 -0
  71. ophyd_async/sim/sim_pattern_detector_control.py +55 -0
  72. ophyd_async/sim/sim_pattern_detector_writer.py +34 -0
  73. ophyd_async/sim/sim_pattern_generator.py +37 -0
  74. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/METADATA +35 -67
  75. ophyd_async-0.3.0.dist-info/RECORD +86 -0
  76. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/WHEEL +1 -1
  77. ophyd_async/core/_device/__init__.py +0 -0
  78. ophyd_async/core/_device/_backend/__init__.py +0 -0
  79. ophyd_async/core/_device/_signal/__init__.py +0 -0
  80. ophyd_async/core/_device/device.py +0 -60
  81. ophyd_async/core/_device/device_collector.py +0 -121
  82. ophyd_async/core/_device/device_vector.py +0 -14
  83. ophyd_async/core/_device/standard_readable.py +0 -72
  84. ophyd_async/epics/areadetector/ad_driver.py +0 -18
  85. ophyd_async/epics/areadetector/directory_provider.py +0 -18
  86. ophyd_async/epics/areadetector/hdf_streamer_det.py +0 -167
  87. ophyd_async/epics/areadetector/nd_file_hdf.py +0 -22
  88. ophyd_async/epics/areadetector/nd_plugin.py +0 -13
  89. ophyd_async/epics/signal/pvi_get.py +0 -22
  90. ophyd_async/panda/panda.py +0 -332
  91. ophyd_async-0.1.0.dist-info/RECORD +0 -45
  92. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/LICENSE +0 -0
  93. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/entry_points.txt +0 -0
  94. {ophyd_async-0.1.0.dist-info → ophyd_async-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,149 @@
1
+ from typing import List
2
+
3
+ import bluesky.plan_stubs as bps
4
+ from bluesky.utils import short_uid
5
+
6
+ from ophyd_async.core.detector import DetectorTrigger, StandardDetector, TriggerInfo
7
+ from ophyd_async.core.flyer import HardwareTriggeredFlyable
8
+ from ophyd_async.core.utils import in_micros
9
+ from ophyd_async.panda._table import SeqTable, SeqTableRow, seq_table_from_rows
10
+ from ophyd_async.panda._trigger import SeqTableInfo
11
+
12
+
13
+ def prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
14
+ flyer: HardwareTriggeredFlyable[SeqTableInfo],
15
+ detectors: List[StandardDetector],
16
+ number_of_frames: int,
17
+ exposure: float,
18
+ shutter_time: float,
19
+ repeats: int = 1,
20
+ period: float = 0.0,
21
+ ):
22
+ """Prepare a hardware triggered flyable and one or more detectors.
23
+
24
+ Prepare a hardware triggered flyable and one or more detectors with the
25
+ same trigger. This method constructs TriggerInfo and a static sequence
26
+ table from required parameters. The table is required to prepare the flyer,
27
+ and the TriggerInfo is required to prepare the detector(s).
28
+
29
+ This prepares all supplied detectors with the same trigger.
30
+
31
+ """
32
+ if not detectors:
33
+ raise ValueError("No detectors provided. There must be at least one.")
34
+
35
+ deadtime = max(det.controller.get_deadtime(exposure) for det in detectors)
36
+
37
+ trigger_info = TriggerInfo(
38
+ num=number_of_frames * repeats,
39
+ trigger=DetectorTrigger.constant_gate,
40
+ deadtime=deadtime,
41
+ livetime=exposure,
42
+ )
43
+ trigger_time = number_of_frames * (exposure + deadtime)
44
+ pre_delay = max(period - 2 * shutter_time - trigger_time, 0)
45
+
46
+ table: SeqTable = seq_table_from_rows(
47
+ # Wait for pre-delay then open shutter
48
+ SeqTableRow(
49
+ time1=in_micros(pre_delay),
50
+ time2=in_micros(shutter_time),
51
+ outa2=True,
52
+ ),
53
+ # Keeping shutter open, do N triggers
54
+ SeqTableRow(
55
+ repeats=number_of_frames,
56
+ time1=in_micros(exposure),
57
+ outa1=True,
58
+ outb1=True,
59
+ time2=in_micros(deadtime),
60
+ outa2=True,
61
+ ),
62
+ # Add the shutter close
63
+ SeqTableRow(time2=in_micros(shutter_time)),
64
+ )
65
+
66
+ table_info = SeqTableInfo(table, repeats)
67
+
68
+ for det in detectors:
69
+ yield from bps.prepare(det, trigger_info, wait=False, group="prep")
70
+ yield from bps.prepare(flyer, table_info, wait=False, group="prep")
71
+ yield from bps.wait(group="prep")
72
+
73
+
74
+ def fly_and_collect(
75
+ stream_name: str,
76
+ flyer: HardwareTriggeredFlyable[SeqTableInfo],
77
+ detectors: List[StandardDetector],
78
+ ):
79
+ """Kickoff, complete and collect with a flyer and multiple detectors.
80
+
81
+ This stub takes a flyer and one or more detectors that have been prepared. It
82
+ declares a stream for the detectors, then kicks off the detectors and the flyer.
83
+ The detectors are collected until the flyer and detectors have completed.
84
+
85
+ """
86
+ yield from bps.declare_stream(*detectors, name=stream_name, collect=True)
87
+ yield from bps.kickoff(flyer, wait=True)
88
+ for detector in detectors:
89
+ yield from bps.kickoff(detector)
90
+
91
+ # collect_while_completing
92
+ group = short_uid(label="complete")
93
+
94
+ yield from bps.complete(flyer, wait=False, group=group)
95
+ for detector in detectors:
96
+ yield from bps.complete(detector, wait=False, group=group)
97
+
98
+ done = False
99
+ while not done:
100
+ try:
101
+ yield from bps.wait(group=group, timeout=0.5)
102
+ except TimeoutError:
103
+ pass
104
+ else:
105
+ done = True
106
+ yield from bps.collect(
107
+ *detectors,
108
+ return_payload=False,
109
+ name=stream_name,
110
+ )
111
+ yield from bps.wait(group=group)
112
+
113
+
114
+ def time_resolved_fly_and_collect_with_static_seq_table(
115
+ stream_name: str,
116
+ flyer: HardwareTriggeredFlyable[SeqTableInfo],
117
+ detectors: List[StandardDetector],
118
+ number_of_frames: int,
119
+ exposure: float,
120
+ shutter_time: float,
121
+ repeats: int = 1,
122
+ period: float = 0.0,
123
+ ):
124
+ """Run a scan wth a flyer and multiple detectors.
125
+
126
+ The stub demonstrates the standard basic flow for a flyscan:
127
+
128
+ - Prepare the flyer and detectors with a trigger
129
+ - Fly and collect:
130
+ - Declare the stream and kickoff the scan
131
+ - Collect while completing
132
+
133
+ This needs to be used in a plan that instantates detectors and a flyer,
134
+ stages/unstages the devices, and opens and closes the run.
135
+
136
+ """
137
+
138
+ # Set up scan and prepare trigger
139
+ yield from prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
140
+ flyer,
141
+ detectors,
142
+ number_of_frames=number_of_frames,
143
+ exposure=exposure,
144
+ shutter_time=shutter_time,
145
+ repeats=repeats,
146
+ period=period,
147
+ )
148
+ # Run the fly scan
149
+ yield from fly_and_collect(stream_name, flyer, detectors)
@@ -0,0 +1,126 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import abstractmethod
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ Any,
7
+ Dict,
8
+ Generic,
9
+ Protocol,
10
+ TypeVar,
11
+ runtime_checkable,
12
+ )
13
+
14
+ from bluesky.protocols import DataKey, HasName, Reading
15
+
16
+ if TYPE_CHECKING:
17
+ from ophyd_async.core.async_status import AsyncStatus
18
+
19
+
20
+ @runtime_checkable
21
+ class AsyncReadable(HasName, Protocol):
22
+ @abstractmethod
23
+ async def read(self) -> Dict[str, Reading]:
24
+ """Return an OrderedDict mapping string field name(s) to dictionaries
25
+ of values and timestamps and optional per-point metadata.
26
+
27
+ Example return value:
28
+
29
+ .. code-block:: python
30
+
31
+ OrderedDict(('channel1',
32
+ {'value': 5, 'timestamp': 1472493713.271991}),
33
+ ('channel2',
34
+ {'value': 16, 'timestamp': 1472493713.539238}))
35
+ """
36
+ ...
37
+
38
+ @abstractmethod
39
+ async def describe(self) -> Dict[str, DataKey]:
40
+ """Return an OrderedDict with exactly the same keys as the ``read``
41
+ method, here mapped to per-scan metadata about each field.
42
+
43
+ Example return value:
44
+
45
+ .. code-block:: python
46
+
47
+ OrderedDict(('channel1',
48
+ {'source': 'XF23-ID:SOME_PV_NAME',
49
+ 'dtype': 'number',
50
+ 'shape': []}),
51
+ ('channel2',
52
+ {'source': 'XF23-ID:SOME_PV_NAME',
53
+ 'dtype': 'number',
54
+ 'shape': []}))
55
+ """
56
+ ...
57
+
58
+
59
+ @runtime_checkable
60
+ class AsyncConfigurable(Protocol):
61
+ @abstractmethod
62
+ async def read_configuration(self) -> Dict[str, Reading]:
63
+ """Same API as ``read`` but for slow-changing fields related to configuration.
64
+ e.g., exposure time. These will typically be read only once per run.
65
+ """
66
+ ...
67
+
68
+ @abstractmethod
69
+ async def describe_configuration(self) -> Dict[str, DataKey]:
70
+ """Same API as ``describe``, but corresponding to the keys in
71
+ ``read_configuration``.
72
+ """
73
+ ...
74
+
75
+
76
+ @runtime_checkable
77
+ class AsyncPausable(Protocol):
78
+ @abstractmethod
79
+ async def pause(self) -> None:
80
+ """Perform device-specific work when the RunEngine pauses."""
81
+ ...
82
+
83
+ @abstractmethod
84
+ async def resume(self) -> None:
85
+ """Perform device-specific work when the RunEngine resumes after a pause."""
86
+ ...
87
+
88
+
89
+ @runtime_checkable
90
+ class AsyncStageable(Protocol):
91
+ @abstractmethod
92
+ def stage(self) -> AsyncStatus:
93
+ """An optional hook for "setting up" the device for acquisition.
94
+
95
+ It should return a ``Status`` that is marked done when the device is
96
+ done staging.
97
+ """
98
+ ...
99
+
100
+ @abstractmethod
101
+ def unstage(self) -> AsyncStatus:
102
+ """A hook for "cleaning up" the device after acquisition.
103
+
104
+ It should return a ``Status`` that is marked done when the device is finished
105
+ unstaging.
106
+ """
107
+ ...
108
+
109
+
110
+ C = TypeVar("C", contravariant=True)
111
+
112
+
113
+ class Watcher(Protocol, Generic[C]):
114
+ @staticmethod
115
+ def __call__(
116
+ *,
117
+ current: C,
118
+ initial: C,
119
+ target: C,
120
+ name: str | None,
121
+ unit: str | None,
122
+ precision: float | None,
123
+ fraction: float | None,
124
+ time_elapsed: float | None,
125
+ time_remaining: float | None,
126
+ ) -> Any: ...
@@ -0,0 +1,11 @@
1
+ from .pattern_generator import PatternGenerator
2
+ from .sim_pattern_detector_control import SimPatternDetectorControl
3
+ from .sim_pattern_detector_writer import SimPatternDetectorWriter
4
+ from .sim_pattern_generator import SimPatternDetector
5
+
6
+ __all__ = [
7
+ "PatternGenerator",
8
+ "SimPatternDetectorControl",
9
+ "SimPatternDetectorWriter",
10
+ "SimPatternDetector",
11
+ ]
@@ -0,0 +1,3 @@
1
+ from .sim_motor import SimMotor
2
+
3
+ __all__ = ["SimMotor"]
@@ -0,0 +1,103 @@
1
+ import asyncio
2
+ import contextlib
3
+ import time
4
+
5
+ from bluesky.protocols import Movable, Stoppable
6
+
7
+ from ophyd_async.core import StandardReadable
8
+ from ophyd_async.core.async_status import AsyncStatus, WatchableAsyncStatus
9
+ from ophyd_async.core.signal import (
10
+ observe_value,
11
+ soft_signal_r_and_setter,
12
+ soft_signal_rw,
13
+ )
14
+ from ophyd_async.core.standard_readable import ConfigSignal, HintedSignal
15
+ from ophyd_async.core.utils import WatcherUpdate
16
+
17
+
18
+ class SimMotor(StandardReadable, Movable, Stoppable):
19
+ def __init__(self, name="", instant=True) -> None:
20
+ """
21
+ Simulated motor device
22
+
23
+ args:
24
+ - prefix: str: Signal names prefix
25
+ - name: str: name of device
26
+ - instant: bool: whether to move instantly, or with a delay
27
+ """
28
+ # Define some signals
29
+ with self.add_children_as_readables(HintedSignal):
30
+ self.user_readback, self._user_readback_set = soft_signal_r_and_setter(
31
+ float, 0
32
+ )
33
+ with self.add_children_as_readables(ConfigSignal):
34
+ self.velocity = soft_signal_rw(float, 0 if instant else 1.0)
35
+ self.units = soft_signal_rw(str, "mm")
36
+ self.user_setpoint = soft_signal_rw(float, 0)
37
+
38
+ # Whether set() should complete successfully or not
39
+ self._set_success = True
40
+ self._move_status: AsyncStatus | None = None
41
+
42
+ super().__init__(name=name)
43
+
44
+ async def _move(self, old_position: float, new_position: float, move_time: float):
45
+ start = time.monotonic()
46
+ distance = abs(new_position - old_position)
47
+ while True:
48
+ time_elapsed = round(time.monotonic() - start, 2)
49
+
50
+ # update position based on time elapsed
51
+ if time_elapsed >= move_time:
52
+ # successfully reached our target position
53
+ self._user_readback_set(new_position)
54
+ break
55
+ else:
56
+ current_position = old_position + distance * time_elapsed / move_time
57
+
58
+ self._user_readback_set(current_position)
59
+
60
+ # 10hz update loop
61
+ await asyncio.sleep(0.1)
62
+
63
+ @WatchableAsyncStatus.wrap
64
+ async def set(self, new_position: float):
65
+ """
66
+ Asynchronously move the motor to a new position.
67
+ """
68
+ # Make sure any existing move tasks are stopped
69
+ await self.stop()
70
+ old_position, units, velocity = await asyncio.gather(
71
+ self.user_setpoint.get_value(),
72
+ self.units.get_value(),
73
+ self.velocity.get_value(),
74
+ )
75
+ # If zero velocity, do instant move
76
+ move_time = abs(new_position - old_position) / velocity if velocity else 0
77
+ self._move_status = AsyncStatus(
78
+ self._move(old_position, new_position, move_time)
79
+ )
80
+ # If stop is called then this will raise a CancelledError, ignore it
81
+ with contextlib.suppress(asyncio.CancelledError):
82
+ async for current_position in observe_value(
83
+ self.user_readback, done_status=self._move_status
84
+ ):
85
+ yield WatcherUpdate(
86
+ current=current_position,
87
+ initial=old_position,
88
+ target=new_position,
89
+ name=self.name,
90
+ unit=units,
91
+ )
92
+ if not self._set_success:
93
+ raise RuntimeError("Motor was stopped")
94
+
95
+ async def stop(self, success=True):
96
+ """
97
+ Stop the motor if it is moving
98
+ """
99
+ self._set_success = success
100
+ if self._move_status:
101
+ self._move_status.task.cancel()
102
+ self._move_status = None
103
+ await self.user_setpoint.set(await self.user_readback.get_value())