ophyd-async 0.8.0a5__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.
- ophyd_async/_version.py +2 -2
- ophyd_async/core/__init__.py +17 -46
- ophyd_async/core/_detector.py +68 -44
- ophyd_async/core/_device.py +120 -79
- ophyd_async/core/_device_filler.py +17 -8
- ophyd_async/core/_flyer.py +2 -2
- ophyd_async/core/_protocol.py +0 -28
- ophyd_async/core/_readable.py +30 -23
- ophyd_async/core/_settings.py +104 -0
- ophyd_async/core/_signal.py +164 -151
- ophyd_async/core/_signal_backend.py +4 -1
- ophyd_async/core/_soft_signal_backend.py +2 -1
- ophyd_async/core/_table.py +27 -14
- ophyd_async/core/_utils.py +30 -5
- 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 +21 -30
- ophyd_async/epics/adaravis/_aravis_io.py +4 -4
- ophyd_async/epics/adcore/__init__.py +15 -8
- ophyd_async/epics/adcore/_core_detector.py +41 -0
- ophyd_async/epics/adcore/_core_io.py +56 -31
- ophyd_async/epics/adcore/_core_logic.py +99 -84
- ophyd_async/epics/adcore/_core_writer.py +219 -0
- ophyd_async/epics/adcore/_hdf_writer.py +33 -59
- ophyd_async/epics/adcore/_jpeg_writer.py +26 -0
- ophyd_async/epics/adcore/_single_trigger.py +5 -4
- ophyd_async/epics/adcore/_tiff_writer.py +26 -0
- ophyd_async/epics/adcore/_utils.py +37 -36
- ophyd_async/epics/adkinetix/_kinetix.py +29 -24
- ophyd_async/epics/adkinetix/_kinetix_controller.py +15 -27
- ophyd_async/epics/adkinetix/_kinetix_io.py +7 -7
- ophyd_async/epics/adpilatus/__init__.py +2 -2
- ophyd_async/epics/adpilatus/_pilatus.py +28 -40
- ophyd_async/epics/adpilatus/_pilatus_controller.py +47 -25
- ophyd_async/epics/adpilatus/_pilatus_io.py +5 -5
- 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 +21 -35
- ophyd_async/epics/advimba/_vimba_io.py +23 -23
- ophyd_async/epics/core/_aioca.py +52 -21
- ophyd_async/epics/core/_p4p.py +59 -16
- ophyd_async/epics/core/_pvi_connector.py +4 -2
- ophyd_async/epics/core/_signal.py +9 -2
- ophyd_async/epics/core/_util.py +10 -1
- ophyd_async/epics/eiger/_eiger_controller.py +10 -5
- ophyd_async/epics/eiger/_eiger_io.py +3 -3
- ophyd_async/epics/motor.py +26 -15
- ophyd_async/epics/sim/_ioc.py +29 -0
- ophyd_async/epics/{demo → sim}/_mover.py +12 -6
- ophyd_async/epics/{demo → sim}/_sensor.py +2 -2
- ophyd_async/epics/testing/__init__.py +24 -0
- ophyd_async/epics/testing/_example_ioc.py +91 -0
- ophyd_async/epics/testing/_utils.py +50 -0
- ophyd_async/epics/testing/test_records.db +174 -0
- ophyd_async/epics/testing/test_records_pva.db +177 -0
- ophyd_async/fastcs/core.py +2 -2
- ophyd_async/fastcs/panda/__init__.py +0 -2
- ophyd_async/fastcs/panda/_block.py +9 -9
- ophyd_async/fastcs/panda/_control.py +9 -4
- ophyd_async/fastcs/panda/_hdf_panda.py +7 -2
- ophyd_async/fastcs/panda/_table.py +4 -1
- ophyd_async/fastcs/panda/_trigger.py +7 -7
- ophyd_async/plan_stubs/__init__.py +14 -0
- ophyd_async/plan_stubs/_ensure_connected.py +11 -17
- ophyd_async/plan_stubs/_fly.py +2 -2
- 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/sim/{demo/_sim_motor.py → _sim_motor.py} +34 -32
- ophyd_async/tango/__init__.py +0 -43
- ophyd_async/tango/{signal → core}/__init__.py +7 -2
- ophyd_async/tango/{base_devices → core}/_base_device.py +38 -64
- ophyd_async/tango/{signal → core}/_signal.py +16 -4
- ophyd_async/tango/{base_devices → core}/_tango_readable.py +3 -4
- ophyd_async/tango/{signal → core}/_tango_transport.py +13 -15
- ophyd_async/tango/{demo → sim}/_counter.py +6 -7
- ophyd_async/tango/{demo → sim}/_mover.py +13 -9
- ophyd_async/testing/__init__.py +52 -0
- ophyd_async/testing/__pytest_assert_rewrite.py +4 -0
- ophyd_async/testing/_assert.py +176 -0
- ophyd_async/{core → testing}/_mock_signal_utils.py +15 -11
- ophyd_async/testing/_one_of_everything.py +126 -0
- ophyd_async/testing/_wait_for_pending.py +22 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/METADATA +50 -48
- ophyd_async-0.9.0.dist-info/RECORD +129 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.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/tango/base_devices/__init__.py +0 -4
- ophyd_async-0.8.0a5.dist-info/RECORD +0 -112
- ophyd_async-0.8.0a5.dist-info/entry_points.txt +0 -2
- /ophyd_async/epics/{demo → sim}/__init__.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/tango/{demo → sim}/__init__.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.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/LICENSE +0 -0
- {ophyd_async-0.8.0a5.dist-info → ophyd_async-0.9.0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
|
|
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.
|
|
19
|
-
DetectorTrigger.
|
|
20
|
-
DetectorTrigger.
|
|
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
|
-
|
|
43
|
+
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
|
|
44
|
+
readout_time: float = PilatusReadoutTime.PILATUS3,
|
|
27
45
|
) -> None:
|
|
28
|
-
|
|
46
|
+
super().__init__(driver, good_states=good_states)
|
|
29
47
|
self._readout_time = readout_time
|
|
30
|
-
|
|
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
|
|
38
|
-
|
|
67
|
+
await self.set_exposure_time_and_acquire_period_if_supplied(
|
|
68
|
+
trigger_info.livetime
|
|
39
69
|
)
|
|
40
70
|
await asyncio.gather(
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
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.
|
|
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
|
|
53
|
-
|
|
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.
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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,35 +1,51 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import PathProvider, SignalR
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
1
|
+
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
-
from ophyd_async.core import PathProvider,
|
|
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(
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
@@ -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,56 +9,44 @@ from ophyd_async.epics import adcore
|
|
|
11
9
|
from ._vimba_io import VimbaDriverIO, VimbaExposeOutMode, VimbaOnOff, VimbaTriggerSource
|
|
12
10
|
|
|
13
11
|
TRIGGER_MODE = {
|
|
14
|
-
DetectorTrigger.
|
|
15
|
-
DetectorTrigger.
|
|
16
|
-
DetectorTrigger.
|
|
17
|
-
DetectorTrigger.
|
|
12
|
+
DetectorTrigger.INTERNAL: VimbaOnOff.OFF,
|
|
13
|
+
DetectorTrigger.CONSTANT_GATE: VimbaOnOff.ON,
|
|
14
|
+
DetectorTrigger.VARIABLE_GATE: VimbaOnOff.ON,
|
|
15
|
+
DetectorTrigger.EDGE_TRIGGER: VimbaOnOff.ON,
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
EXPOSE_OUT_MODE = {
|
|
21
|
-
DetectorTrigger.
|
|
22
|
-
DetectorTrigger.
|
|
23
|
-
DetectorTrigger.
|
|
24
|
-
DetectorTrigger.
|
|
19
|
+
DetectorTrigger.INTERNAL: VimbaExposeOutMode.TIMED,
|
|
20
|
+
DetectorTrigger.CONSTANT_GATE: VimbaExposeOutMode.TRIGGER_WIDTH,
|
|
21
|
+
DetectorTrigger.VARIABLE_GATE: VimbaExposeOutMode.TRIGGER_WIDTH,
|
|
22
|
+
DetectorTrigger.EDGE_TRIGGER: VimbaExposeOutMode.TIMED,
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
|
|
28
|
-
class VimbaController(
|
|
26
|
+
class VimbaController(adcore.ADBaseController[VimbaDriverIO]):
|
|
29
27
|
def __init__(
|
|
30
28
|
self,
|
|
31
29
|
driver: VimbaDriverIO,
|
|
30
|
+
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES,
|
|
32
31
|
) -> None:
|
|
33
|
-
|
|
34
|
-
self._arm_status: AsyncStatus | None = None
|
|
32
|
+
super().__init__(driver, good_states=good_states)
|
|
35
33
|
|
|
36
34
|
def get_deadtime(self, exposure: float | None) -> float:
|
|
37
35
|
return 0.001
|
|
38
36
|
|
|
39
37
|
async def prepare(self, trigger_info: TriggerInfo):
|
|
40
38
|
await asyncio.gather(
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
self.
|
|
39
|
+
self.driver.trigger_mode.set(TRIGGER_MODE[trigger_info.trigger]),
|
|
40
|
+
self.driver.exposure_mode.set(EXPOSE_OUT_MODE[trigger_info.trigger]),
|
|
41
|
+
self.driver.num_images.set(trigger_info.total_number_of_triggers),
|
|
42
|
+
self.driver.image_mode.set(adcore.ImageMode.MULTIPLE),
|
|
45
43
|
)
|
|
46
44
|
if trigger_info.livetime is not None and trigger_info.trigger not in [
|
|
47
|
-
DetectorTrigger.
|
|
48
|
-
DetectorTrigger.
|
|
45
|
+
DetectorTrigger.VARIABLE_GATE,
|
|
46
|
+
DetectorTrigger.CONSTANT_GATE,
|
|
49
47
|
]:
|
|
50
|
-
await self.
|
|
51
|
-
if trigger_info.trigger != DetectorTrigger.
|
|
52
|
-
self.
|
|
48
|
+
await self.driver.acquire_time.set(trigger_info.livetime)
|
|
49
|
+
if trigger_info.trigger != DetectorTrigger.INTERNAL:
|
|
50
|
+
self.driver.trigger_source.set(VimbaTriggerSource.LINE1)
|
|
53
51
|
else:
|
|
54
|
-
self.
|
|
55
|
-
|
|
56
|
-
async def arm(self):
|
|
57
|
-
self._arm_status = await adcore.start_acquiring_driver_and_ensure_status(
|
|
58
|
-
self._drv
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
async def wait_for_idle(self):
|
|
62
|
-
if self._arm_status:
|
|
63
|
-
await self._arm_status
|
|
64
|
-
|
|
65
|
-
async def disarm(self):
|
|
66
|
-
await adcore.stop_busy_record(self._drv.acquire, False, timeout=1)
|
|
52
|
+
self.driver.trigger_source.set(VimbaTriggerSource.FREERUN)
|
|
@@ -4,44 +4,44 @@ from ophyd_async.epics.core import epics_signal_rw_rbv
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class VimbaPixelFormat(StrictEnum):
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
INTERNAL = "Mono8"
|
|
8
|
+
EXT_ENABLE = "Mono12"
|
|
9
|
+
EXT_TRIGGER = "Ext. Trigger"
|
|
10
|
+
MULT_TRIGGER = "Mult. Trigger"
|
|
11
|
+
ALIGNMENT = "Alignment"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class VimbaConvertFormat(StrictEnum):
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
NONE = "None"
|
|
16
|
+
MONO8 = "Mono8"
|
|
17
|
+
MONO16 = "Mono16"
|
|
18
|
+
RGB8 = "RGB8"
|
|
19
|
+
RGB16 = "RGB16"
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class VimbaTriggerSource(StrictEnum):
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
FREERUN = "Freerun"
|
|
24
|
+
LINE1 = "Line1"
|
|
25
|
+
LINE2 = "Line2"
|
|
26
|
+
FIXED_RATE = "FixedRate"
|
|
27
|
+
SOFTWARE = "Software"
|
|
28
|
+
ACTION0 = "Action0"
|
|
29
|
+
ACTION1 = "Action1"
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class VimbaOverlap(StrictEnum):
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
OFF = "Off"
|
|
34
|
+
PREV_FRAME = "PreviousFrame"
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class VimbaOnOff(StrictEnum):
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
ON = "On"
|
|
39
|
+
OFF = "Off"
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class VimbaExposeOutMode(StrictEnum):
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
TIMED = "Timed" # Use ExposureTime PV
|
|
44
|
+
TRIGGER_WIDTH = "TriggerWidth" # Expose for length of high signal
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
class VimbaDriverIO(adcore.ADBaseIO):
|
ophyd_async/epics/core/_aioca.py
CHANGED
|
@@ -35,12 +35,14 @@ from ophyd_async.core import (
|
|
|
35
35
|
|
|
36
36
|
from ._util import EpicsSignalBackend, format_datatype, get_supported_values
|
|
37
37
|
|
|
38
|
+
logger = logging.getLogger("ophyd_async")
|
|
39
|
+
|
|
38
40
|
|
|
39
41
|
def _limits_from_augmented_value(value: AugmentedValue) -> Limits:
|
|
40
42
|
def get_limits(limit: str) -> LimitsRange | None:
|
|
41
43
|
low = getattr(value, f"lower_{limit}_limit", nan)
|
|
42
44
|
high = getattr(value, f"upper_{limit}_limit", nan)
|
|
43
|
-
if not (isnan(low) and isnan(high)):
|
|
45
|
+
if not (isnan(low) and isnan(high)) and not high == low == 0:
|
|
44
46
|
return LimitsRange(
|
|
45
47
|
low=None if isnan(low) else low,
|
|
46
48
|
high=None if isnan(high) else high,
|
|
@@ -59,14 +61,20 @@ def _limits_from_augmented_value(value: AugmentedValue) -> Limits:
|
|
|
59
61
|
|
|
60
62
|
|
|
61
63
|
def _metadata_from_augmented_value(
|
|
62
|
-
|
|
64
|
+
datatype: type[SignalDatatypeT] | None,
|
|
65
|
+
value: AugmentedValue,
|
|
66
|
+
metadata: SignalMetadata,
|
|
63
67
|
) -> SignalMetadata:
|
|
64
68
|
metadata = metadata.copy()
|
|
65
|
-
if hasattr(value, "units"):
|
|
69
|
+
if hasattr(value, "units") and datatype not in (str, bool):
|
|
66
70
|
metadata["units"] = value.units
|
|
67
|
-
if
|
|
71
|
+
if (
|
|
72
|
+
hasattr(value, "precision")
|
|
73
|
+
and not isnan(value.precision)
|
|
74
|
+
and datatype is not int
|
|
75
|
+
):
|
|
68
76
|
metadata["precision"] = value.precision
|
|
69
|
-
if limits := _limits_from_augmented_value(value):
|
|
77
|
+
if (limits := _limits_from_augmented_value(value)) and datatype is not bool:
|
|
70
78
|
metadata["limits"] = limits
|
|
71
79
|
return metadata
|
|
72
80
|
|
|
@@ -100,6 +108,11 @@ class DisconnectedCaConverter(CaConverter):
|
|
|
100
108
|
raise NotImplementedError("No PV has been set as connect() has not been called")
|
|
101
109
|
|
|
102
110
|
|
|
111
|
+
class CaIntConverter(CaConverter[int]):
|
|
112
|
+
def value(self, value: AugmentedValue) -> int:
|
|
113
|
+
return int(value) # type: ignore
|
|
114
|
+
|
|
115
|
+
|
|
103
116
|
class CaArrayConverter(CaConverter[np.ndarray]):
|
|
104
117
|
def value(self, value: AugmentedValue) -> np.ndarray:
|
|
105
118
|
# A less expensive conversion
|
|
@@ -202,7 +215,7 @@ def make_converter(
|
|
|
202
215
|
and get_unique({k: v.precision for k, v in values.items()}, "precision") == 0
|
|
203
216
|
):
|
|
204
217
|
# Allow int signals to represent float records when prec is 0
|
|
205
|
-
return
|
|
218
|
+
return CaIntConverter(int, pv_dbr)
|
|
206
219
|
elif datatype in (None, inferred_datatype):
|
|
207
220
|
# If datatype matches what we are given then allow it and use inferred converter
|
|
208
221
|
return converter_cls(inferred_datatype, pv_dbr)
|
|
@@ -247,7 +260,7 @@ class CaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
|
|
|
247
260
|
pv, format=FORMAT_CTRL, timeout=timeout
|
|
248
261
|
)
|
|
249
262
|
except CANothing as exc:
|
|
250
|
-
|
|
263
|
+
logger.debug(f"signal ca://{pv} timed out")
|
|
251
264
|
raise NotConnected(f"ca://{pv}") from exc
|
|
252
265
|
|
|
253
266
|
async def connect(self, timeout: float):
|
|
@@ -280,17 +293,33 @@ class CaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
|
|
|
280
293
|
write_value = self.initial_values[self.write_pv]
|
|
281
294
|
else:
|
|
282
295
|
write_value = self.converter.write_value(value)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
296
|
+
try:
|
|
297
|
+
await caput(
|
|
298
|
+
self.write_pv,
|
|
299
|
+
write_value,
|
|
300
|
+
datatype=self.converter.write_dbr,
|
|
301
|
+
wait=wait,
|
|
302
|
+
timeout=None,
|
|
303
|
+
)
|
|
304
|
+
except CANothing as exc:
|
|
305
|
+
# If we ran into a write error, check to see if there is a list
|
|
306
|
+
# of valid choices, and if the value we tried to write is in that list.
|
|
307
|
+
valid_choices = self.converter.metadata.get("choices")
|
|
308
|
+
if valid_choices:
|
|
309
|
+
if value not in valid_choices:
|
|
310
|
+
msg = (
|
|
311
|
+
f"{value} is not a valid choice for {self.write_pv}, "
|
|
312
|
+
f"valid choices: {self.converter.metadata.get('choices')}"
|
|
313
|
+
)
|
|
314
|
+
raise ValueError(msg) from exc
|
|
315
|
+
raise
|
|
316
|
+
raise
|
|
290
317
|
|
|
291
318
|
async def get_datakey(self, source: str) -> DataKey:
|
|
292
319
|
value = await self._caget(self.read_pv, FORMAT_CTRL)
|
|
293
|
-
metadata = _metadata_from_augmented_value(
|
|
320
|
+
metadata = _metadata_from_augmented_value(
|
|
321
|
+
self.datatype, value, self.converter.metadata
|
|
322
|
+
)
|
|
294
323
|
return make_datakey(
|
|
295
324
|
self.converter.datatype, self.converter.value(value), source, metadata
|
|
296
325
|
)
|
|
@@ -308,16 +337,18 @@ class CaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
|
|
|
308
337
|
return self.converter.value(value)
|
|
309
338
|
|
|
310
339
|
def set_callback(self, callback: Callback[Reading[SignalDatatypeT]] | None) -> None:
|
|
340
|
+
if callback and self.subscription:
|
|
341
|
+
msg = "Cannot set a callback when one is already set"
|
|
342
|
+
raise RuntimeError(msg)
|
|
343
|
+
|
|
344
|
+
if self.subscription:
|
|
345
|
+
self.subscription.close()
|
|
346
|
+
self.subscription = None
|
|
347
|
+
|
|
311
348
|
if callback:
|
|
312
|
-
assert (
|
|
313
|
-
not self.subscription
|
|
314
|
-
), "Cannot set a callback when one is already set"
|
|
315
349
|
self.subscription = camonitor(
|
|
316
350
|
self.read_pv,
|
|
317
351
|
lambda v: callback(self._make_reading(v)),
|
|
318
352
|
datatype=self.converter.read_dbr,
|
|
319
353
|
format=FORMAT_TIME,
|
|
320
354
|
)
|
|
321
|
-
elif self.subscription:
|
|
322
|
-
self.subscription.close()
|
|
323
|
-
self.subscription = None
|