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.
- ophyd_async/_version.py +1 -1
- ophyd_async/core/__init__.py +13 -20
- ophyd_async/core/_detector.py +61 -37
- ophyd_async/core/_device.py +102 -80
- ophyd_async/core/_device_filler.py +17 -8
- ophyd_async/core/_flyer.py +2 -2
- ophyd_async/core/_readable.py +30 -23
- ophyd_async/core/_settings.py +104 -0
- ophyd_async/core/_signal.py +55 -17
- ophyd_async/core/_signal_backend.py +4 -1
- ophyd_async/core/_soft_signal_backend.py +2 -1
- ophyd_async/core/_table.py +18 -10
- ophyd_async/core/_utils.py +5 -3
- ophyd_async/core/_yaml_settings.py +64 -0
- ophyd_async/epics/adandor/__init__.py +9 -0
- ophyd_async/epics/adandor/_andor.py +45 -0
- ophyd_async/epics/adandor/_andor_controller.py +49 -0
- ophyd_async/epics/adandor/_andor_io.py +36 -0
- ophyd_async/epics/adaravis/__init__.py +3 -1
- ophyd_async/epics/adaravis/_aravis.py +23 -37
- ophyd_async/epics/adaravis/_aravis_controller.py +13 -22
- ophyd_async/epics/adcore/__init__.py +15 -8
- ophyd_async/epics/adcore/_core_detector.py +41 -0
- ophyd_async/epics/adcore/_core_io.py +35 -10
- ophyd_async/epics/adcore/_core_logic.py +98 -86
- ophyd_async/epics/adcore/_core_writer.py +219 -0
- ophyd_async/epics/adcore/_hdf_writer.py +38 -62
- ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
- ophyd_async/epics/adcore/_single_trigger.py +4 -3
- ophyd_async/epics/adcore/_tiff_writer.py +26 -0
- ophyd_async/epics/adcore/_utils.py +2 -1
- ophyd_async/epics/adkinetix/_kinetix.py +29 -24
- ophyd_async/epics/adkinetix/_kinetix_controller.py +9 -21
- ophyd_async/epics/adpilatus/__init__.py +2 -2
- ophyd_async/epics/adpilatus/_pilatus.py +27 -39
- ophyd_async/epics/adpilatus/_pilatus_controller.py +44 -22
- ophyd_async/epics/adsimdetector/__init__.py +3 -3
- ophyd_async/epics/adsimdetector/_sim.py +33 -17
- ophyd_async/epics/advimba/_vimba.py +23 -23
- ophyd_async/epics/advimba/_vimba_controller.py +10 -24
- ophyd_async/epics/core/_aioca.py +31 -14
- ophyd_async/epics/core/_p4p.py +40 -16
- ophyd_async/epics/core/_util.py +1 -1
- ophyd_async/epics/motor.py +18 -10
- ophyd_async/epics/sim/_ioc.py +29 -0
- ophyd_async/epics/{demo → sim}/_mover.py +10 -4
- ophyd_async/epics/testing/__init__.py +14 -14
- ophyd_async/epics/testing/_example_ioc.py +48 -65
- ophyd_async/epics/testing/_utils.py +17 -45
- ophyd_async/epics/testing/test_records.db +8 -0
- ophyd_async/fastcs/panda/__init__.py +0 -2
- ophyd_async/fastcs/panda/_control.py +7 -2
- ophyd_async/fastcs/panda/_hdf_panda.py +3 -1
- ophyd_async/fastcs/panda/_table.py +4 -1
- ophyd_async/plan_stubs/__init__.py +14 -0
- ophyd_async/plan_stubs/_ensure_connected.py +11 -17
- ophyd_async/plan_stubs/_fly.py +1 -1
- ophyd_async/plan_stubs/_nd_attributes.py +7 -5
- ophyd_async/plan_stubs/_panda.py +13 -0
- ophyd_async/plan_stubs/_settings.py +125 -0
- ophyd_async/plan_stubs/_wait_for_awaitable.py +13 -0
- ophyd_async/sim/__init__.py +19 -0
- ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_controller.py +9 -2
- ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_generator.py +13 -6
- ophyd_async/tango/core/_signal.py +3 -1
- ophyd_async/tango/core/_tango_transport.py +12 -14
- ophyd_async/tango/{demo → sim}/_mover.py +5 -2
- ophyd_async/testing/__init__.py +19 -0
- ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
- ophyd_async/testing/_assert.py +88 -40
- ophyd_async/testing/_mock_signal_utils.py +3 -3
- ophyd_async/testing/_one_of_everything.py +126 -0
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/METADATA +2 -2
- ophyd_async-0.9.0a2.dist-info/RECORD +129 -0
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/WHEEL +1 -1
- ophyd_async/core/_device_save_loader.py +0 -274
- ophyd_async/epics/adsimdetector/_sim_controller.py +0 -51
- ophyd_async/fastcs/panda/_utils.py +0 -16
- ophyd_async/sim/demo/__init__.py +0 -19
- ophyd_async/sim/testing/__init__.py +0 -0
- ophyd_async-0.9.0a1.dist-info/RECORD +0 -119
- ophyd_async-0.9.0a1.dist-info/entry_points.txt +0 -2
- /ophyd_async/epics/{demo → sim}/__init__.py +0 -0
- /ophyd_async/epics/{demo → sim}/_sensor.py +0 -0
- /ophyd_async/epics/{demo → sim}/mover.db +0 -0
- /ophyd_async/epics/{demo → sim}/sensor.db +0 -0
- /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/__init__.py +0 -0
- /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector.py +0 -0
- /ophyd_async/sim/{demo/_pattern_detector → _pattern_detector}/_pattern_detector_writer.py +0 -0
- /ophyd_async/sim/{demo/_sim_motor.py → _sim_motor.py} +0 -0
- /ophyd_async/tango/{demo → sim}/__init__.py +0 -0
- /ophyd_async/tango/{demo → sim}/_counter.py +0 -0
- /ophyd_async/tango/{demo → sim}/_detector.py +0 -0
- /ophyd_async/tango/{demo → sim}/_tango/__init__.py +0 -0
- /ophyd_async/tango/{demo → sim}/_tango/_servers.py +0 -0
- {ophyd_async-0.9.0a1.dist-info → ophyd_async-0.9.0a2.dist-info}/LICENSE +0 -0
- {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
|
|
1
|
+
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from
|
|
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(
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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(
|
|
18
|
+
class AravisController(adcore.ADBaseController[AravisDriverIO]):
|
|
22
19
|
GPIO_NUMBER = Literal[1, 2, 3, 4]
|
|
23
20
|
|
|
24
|
-
def __init__(
|
|
25
|
-
self
|
|
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.
|
|
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.
|
|
43
|
+
await self.driver.trigger_mode.set(trigger_mode)
|
|
43
44
|
|
|
44
45
|
await asyncio.gather(
|
|
45
|
-
self.
|
|
46
|
-
self.
|
|
47
|
-
self.
|
|
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
|
-
|
|
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
|
-
"
|
|
36
|
-
"
|
|
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
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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.
|
|
62
|
-
driver.
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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())
|