From 5f3df2cf78f95c45758f56f0d9d1f99827d34081 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Mon, 15 Apr 2024 10:33:57 -0400 Subject: [PATCH 01/13] Adding driver and controller classes for Kinetix and Vimba cameras --- .../controllers/kinetix_controller.py | 55 ++++++++++++++++ .../controllers/vimba_controller.py | 65 +++++++++++++++++++ .../areadetector/drivers/kinetix_driver.py | 21 ++++++ .../areadetector/drivers/vimba_driver.py | 51 +++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py create mode 100644 src/ophyd_async/epics/areadetector/controllers/vimba_controller.py create mode 100644 src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py create mode 100644 src/ophyd_async/epics/areadetector/drivers/vimba_driver.py diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py new file mode 100644 index 0000000000..ac3dc2964f --- /dev/null +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -0,0 +1,55 @@ +import asyncio +from typing import Optional, Set + +from ophyd_async.core import AsyncStatus, DetectorControl, DetectorTrigger +from ophyd_async.epics.areadetector.drivers.ad_base import ( + DEFAULT_GOOD_STATES, + DetectorState, + start_acquiring_driver_and_ensure_status, +) + +from ..drivers.kinetix_driver import KinetixDriver, KinetixTriggerMode +from ..utils import ImageMode, stop_busy_record + +KINETIX_TRIGGER_MODE_MAP = { + DetectorTrigger.internal: KinetixTriggerMode.internal, + DetectorTrigger.constant_gate: KinetixTriggerMode.gate, + DetectorTrigger.variable_gate: KinetixTriggerMode.gate, + DetectorTrigger.edge_trigger: KinetixTriggerMode.edge, +} + + +class KinetixController(DetectorControl): + def __init__( + self, + driver: KinetixDriver, + good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), + ) -> None: + self.driver = driver + self.good_states = good_states + + def get_deadtime(self, exposure: float) -> float: + return 0.001 + + async def arm( + self, + num: int, + trigger: DetectorTrigger = DetectorTrigger.internal, + exposure: Optional[float] = None, + ) -> AsyncStatus: + await asyncio.gather( + self.driver.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]), + self.driver.num_images.set(999_999 if num == 0 else num), + self.driver.image_mode.set(ImageMode.multiple), + ) + if exposure is not None and trigger not in [ + DetectorTrigger.variable_gate, + DetectorTrigger.constant_gate, + ]: + await self.driver.acquire_time.set(exposure) + return await start_acquiring_driver_and_ensure_status( + self.driver, good_states=self.good_states + ) + + async def disarm(self): + await self.driver.acquire.set(0) \ No newline at end of file diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py new file mode 100644 index 0000000000..c506d1cafe --- /dev/null +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -0,0 +1,65 @@ +import asyncio +from typing import Optional, Set + +from ophyd_async.core import AsyncStatus, DetectorControl, DetectorTrigger +from ophyd_async.epics.areadetector.drivers.ad_base import ( + DEFAULT_GOOD_STATES, + DetectorState, + start_acquiring_driver_and_ensure_status, +) + +from ..drivers.vimba_driver import VimbaDriver, ExposeOutMode, OnOff, TriggerSource +from ..utils import ImageMode, stop_busy_record + +TRIGGER_MODE = { + DetectorTrigger.internal: OnOff.off, + DetectorTrigger.constant_gate: OnOff.on, + DetectorTrigger.variable_gate: OnOff.on, + DetectorTrigger.edge_trigger: OnOff.on, +} + +EXPOSE_OUT_MODE = { + DetectorTrigger.internal: ExposeOutMode.timed, + DetectorTrigger.constant_gate: ExposeOutMode.trigger_width, + DetectorTrigger.variable_gate: ExposeOutMode.trigger_width, + DetectorTrigger.edge_trigger: ExposeOutMode.timed, +} + + +class VimbaController(DetectorControl): + def __init__( + self, + driver: VimbaDriver, + good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), + ) -> None: + self.driver = driver + self.good_states = good_states + + def get_deadtime(self, exposure: float) -> float: + return 0.001 + + async def arm( + self, + num: int, + trigger: DetectorTrigger = DetectorTrigger.internal, + exposure: Optional[float] = None, + ) -> AsyncStatus: + await asyncio.gather( + self.driver.trigger_mode.set(TRIGGER_MODE[trigger]), + self.driver.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), + self.driver.num_images.set(999_999 if num == 0 else num), + self.driver.image_mode.set(ImageMode.multiple), + ) + if exposure is not None and trigger not in [ + DetectorTrigger.variable_gate, + DetectorTrigger.constant_gate, + ]: + await self.driver.acquire_time.set(exposure) + if trigger != DetectorTrigger.internal: + self.driver.trigger_source = TriggerSource.line1 + return await start_acquiring_driver_and_ensure_status( + self.driver, good_states=self.good_states + ) + + async def disarm(self): + await self.driver.acquire.set(0) \ No newline at end of file diff --git a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py new file mode 100644 index 0000000000..7262c0e370 --- /dev/null +++ b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py @@ -0,0 +1,21 @@ +from enum import Enum + +from ..utils import ad_rw, ad_r +from .ad_base import ADBase + +class KinetixTriggerMode(str, Enum): + internal = "Internal" + edge = "Rising Edge" + gate = "Exp. Gate" + +class KinetixReadoutMode(str, Enum): + sensitivity = 1 + speed = 2 + dynamic_range = 3 + +class KinetixDriver(ADBase): + def __init__(self, prefix: str) -> None: + super().__init__(prefix) + # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") + self.trigger_mode = ad_rw(KinetixTriggerMode, prefix + "TriggerMode") + self.readout_mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") \ No newline at end of file diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py new file mode 100644 index 0000000000..57153e9bb4 --- /dev/null +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -0,0 +1,51 @@ +from enum import Enum + +from ..utils import ad_rw +from .ad_base import ADBase + + +class PixelFormat(str, Enum): + internal = "Mono8" + ext_enable = "Mono12" + ext_trigger = "Ext. Trigger" + mult_trigger = "Mult. Trigger" + alignment = "Alignment" + + +class ConvertFormat(str, Enum): + none = "None" + mono8 = "Mono8" + mono16 = "Mono16" + rgb8 = "RGB8" + rgb16 = "RGB16" + +class TriggerSource(str, Enum): + freerun = "Freerun" + line1 = "Line1" + line2 = "Line2" + fixed_rate = "FixedRate" + software = "Software" + action0 = "Action0" + action1 = "Action1" + +class Overlap(str, Enum): + off = "Off" + prev_frame = "PreviousFrame" + +class OnOff(str, Enum): + on = "On" + off = "Off" + +class ExposeOutMode(str, Enum): + timed = "Timed" # Use ExposureTime PV + trigger_width = "TriggerWidth" # Expose for length of high signal + +class VimbaDriver(ADBase): + def __init__(self, prefix: str) -> None: + # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") + self.convert_format = ad_rw(ConvertFormat, prefix + "ConvertPixelFormat") + self.trigger_source = ad_rw(TriggerSource, prefix + "TriggerSource") + self.trigger_mode = ad_rw(OnOff, prefix + "TriggerMode") + self.overlap = ad_rw(Overlap, prefix + "TriggerOverlap") + self.expose_out_mode = ad_rw(ExposeOutMode, prefix + "ExposureMode") + super().__init__(prefix) \ No newline at end of file From 45d4d2d87473dc366883dae66b9389a9dfb85cc2 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Mon, 15 Apr 2024 10:47:55 -0400 Subject: [PATCH 02/13] Make linter and mypy happy --- .../epics/areadetector/controllers/kinetix_controller.py | 2 +- .../epics/areadetector/controllers/vimba_controller.py | 4 ++-- src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py | 2 +- src/ophyd_async/epics/areadetector/drivers/vimba_driver.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py index ac3dc2964f..c2b81222a2 100644 --- a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -52,4 +52,4 @@ async def arm( ) async def disarm(self): - await self.driver.acquire.set(0) \ No newline at end of file + await self.driver.acquire.set(0) diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index c506d1cafe..0ee06a5d4f 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -56,10 +56,10 @@ async def arm( ]: await self.driver.acquire_time.set(exposure) if trigger != DetectorTrigger.internal: - self.driver.trigger_source = TriggerSource.line1 + self.driver.trigger_source.set(TriggerSource.line1) return await start_acquiring_driver_and_ensure_status( self.driver, good_states=self.good_states ) async def disarm(self): - await self.driver.acquire.set(0) \ No newline at end of file + await self.driver.acquire.set(0) diff --git a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py index 7262c0e370..5e2b2cbf5d 100644 --- a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py @@ -18,4 +18,4 @@ def __init__(self, prefix: str) -> None: super().__init__(prefix) # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") self.trigger_mode = ad_rw(KinetixTriggerMode, prefix + "TriggerMode") - self.readout_mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") \ No newline at end of file + self.readout_mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py index 57153e9bb4..827e0f6ad4 100644 --- a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -48,4 +48,4 @@ def __init__(self, prefix: str) -> None: self.trigger_mode = ad_rw(OnOff, prefix + "TriggerMode") self.overlap = ad_rw(Overlap, prefix + "TriggerOverlap") self.expose_out_mode = ad_rw(ExposeOutMode, prefix + "ExposureMode") - super().__init__(prefix) \ No newline at end of file + super().__init__(prefix) From 73562212e76734a52bdfc4e5fa28eb68662839f6 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Mon, 15 Apr 2024 11:28:10 -0400 Subject: [PATCH 03/13] More linter fixes --- .../areadetector/controllers/kinetix_controller.py | 2 +- .../areadetector/controllers/vimba_controller.py | 11 +++++++++-- .../epics/areadetector/drivers/kinetix_driver.py | 7 +++++-- .../epics/areadetector/drivers/vimba_driver.py | 5 +++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py index c2b81222a2..535c500dc9 100644 --- a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -9,7 +9,7 @@ ) from ..drivers.kinetix_driver import KinetixDriver, KinetixTriggerMode -from ..utils import ImageMode, stop_busy_record +from ..utils import ImageMode KINETIX_TRIGGER_MODE_MAP = { DetectorTrigger.internal: KinetixTriggerMode.internal, diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index 0ee06a5d4f..38995ccbca 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -8,8 +8,15 @@ start_acquiring_driver_and_ensure_status, ) -from ..drivers.vimba_driver import VimbaDriver, ExposeOutMode, OnOff, TriggerSource -from ..utils import ImageMode, stop_busy_record +from ..drivers.vimba_driver import ( + VimbaDriver, + ExposeOutMode, + OnOff, + TriggerSource, +) + +from ..utils import ImageMode + TRIGGER_MODE = { DetectorTrigger.internal: OnOff.off, diff --git a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py index 5e2b2cbf5d..1a2b866daf 100644 --- a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py @@ -1,21 +1,24 @@ from enum import Enum -from ..utils import ad_rw, ad_r +from ..utils import ad_rw from .ad_base import ADBase + class KinetixTriggerMode(str, Enum): internal = "Internal" edge = "Rising Edge" gate = "Exp. Gate" + class KinetixReadoutMode(str, Enum): sensitivity = 1 speed = 2 dynamic_range = 3 + class KinetixDriver(ADBase): def __init__(self, prefix: str) -> None: super().__init__(prefix) # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") self.trigger_mode = ad_rw(KinetixTriggerMode, prefix + "TriggerMode") - self.readout_mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") + self.mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py index 827e0f6ad4..34279cf478 100644 --- a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -19,6 +19,7 @@ class ConvertFormat(str, Enum): rgb8 = "RGB8" rgb16 = "RGB16" + class TriggerSource(str, Enum): freerun = "Freerun" line1 = "Line1" @@ -28,18 +29,22 @@ class TriggerSource(str, Enum): action0 = "Action0" action1 = "Action1" + class Overlap(str, Enum): off = "Off" prev_frame = "PreviousFrame" + class OnOff(str, Enum): on = "On" off = "Off" + class ExposeOutMode(str, Enum): timed = "Timed" # Use ExposureTime PV trigger_width = "TriggerWidth" # Expose for length of high signal + class VimbaDriver(ADBase): def __init__(self, prefix: str) -> None: # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") From 8158bca88b31278710490c35b0cddf8fd75cc816 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Tue, 16 Apr 2024 14:17:52 -0400 Subject: [PATCH 04/13] Remove pilatus specific line, resolve linter problem --- .../epics/areadetector/controllers/kinetix_controller.py | 2 +- .../epics/areadetector/controllers/vimba_controller.py | 2 +- src/ophyd_async/epics/areadetector/drivers/vimba_driver.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py index 535c500dc9..08ae2d8c73 100644 --- a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -39,7 +39,7 @@ async def arm( ) -> AsyncStatus: await asyncio.gather( self.driver.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]), - self.driver.num_images.set(999_999 if num == 0 else num), + self.driver.num_images.set(num), self.driver.image_mode.set(ImageMode.multiple), ) if exposure is not None and trigger not in [ diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index 38995ccbca..0c5878ca2c 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -54,7 +54,7 @@ async def arm( await asyncio.gather( self.driver.trigger_mode.set(TRIGGER_MODE[trigger]), self.driver.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), - self.driver.num_images.set(999_999 if num == 0 else num), + self.driver.num_images.set(num), self.driver.image_mode.set(ImageMode.multiple), ) if exposure is not None and trigger not in [ diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py index 34279cf478..be7770dc72 100644 --- a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -48,7 +48,9 @@ class ExposeOutMode(str, Enum): class VimbaDriver(ADBase): def __init__(self, prefix: str) -> None: # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") - self.convert_format = ad_rw(ConvertFormat, prefix + "ConvertPixelFormat") + self.convert_format = ad_rw( + ConvertFormat, prefix + "ConvertPixelFormat" + ) # Pixel format of data outputted to AD self.trigger_source = ad_rw(TriggerSource, prefix + "TriggerSource") self.trigger_mode = ad_rw(OnOff, prefix + "TriggerMode") self.overlap = ad_rw(Overlap, prefix + "TriggerOverlap") From 439469bfeff106e86fa6e36b883a01b1b997d11b Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Thu, 18 Apr 2024 09:57:30 -0400 Subject: [PATCH 05/13] Modify Vimba/Kinetix controllers/drivers to match new aravis/pilatus --- .../controllers/kinetix_controller.py | 16 +++---- .../controllers/vimba_controller.py | 44 +++++++++---------- .../areadetector/drivers/vimba_driver.py | 22 +++++----- 3 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py index 08ae2d8c73..64a5ff4ffc 100644 --- a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -9,7 +9,7 @@ ) from ..drivers.kinetix_driver import KinetixDriver, KinetixTriggerMode -from ..utils import ImageMode +from ..utils import ImageMode, stop_busy_record KINETIX_TRIGGER_MODE_MAP = { DetectorTrigger.internal: KinetixTriggerMode.internal, @@ -25,7 +25,7 @@ def __init__( driver: KinetixDriver, good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), ) -> None: - self.driver = driver + self._drv = driver self.good_states = good_states def get_deadtime(self, exposure: float) -> float: @@ -38,18 +38,18 @@ async def arm( exposure: Optional[float] = None, ) -> AsyncStatus: await asyncio.gather( - self.driver.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]), - self.driver.num_images.set(num), - self.driver.image_mode.set(ImageMode.multiple), + self._drv.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]), + self._drv.num_images.set(num), + self._drv.image_mode.set(ImageMode.multiple), ) if exposure is not None and trigger not in [ DetectorTrigger.variable_gate, DetectorTrigger.constant_gate, ]: - await self.driver.acquire_time.set(exposure) + await self._drv.acquire_time.set(exposure) return await start_acquiring_driver_and_ensure_status( - self.driver, good_states=self.good_states + self._drv, good_states=self.good_states ) async def disarm(self): - await self.driver.acquire.set(0) + await stop_busy_record(self._drv.acquire, False, timeout=1) diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index 0c5878ca2c..3faaa7c9f6 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -10,26 +10,24 @@ from ..drivers.vimba_driver import ( VimbaDriver, - ExposeOutMode, - OnOff, - TriggerSource, + VimbaExposeOutMode, + VimbaOnOff, + VimbaTriggerSource, ) - -from ..utils import ImageMode - +from ..utils import ImageMode, stop_busy_record TRIGGER_MODE = { - DetectorTrigger.internal: OnOff.off, - DetectorTrigger.constant_gate: OnOff.on, - DetectorTrigger.variable_gate: OnOff.on, - DetectorTrigger.edge_trigger: OnOff.on, + DetectorTrigger.internal: VimbaOnOff.off, + DetectorTrigger.constant_gate: VimbaOnOff.on, + DetectorTrigger.variable_gate: VimbaOnOff.on, + DetectorTrigger.edge_trigger: VimbaOnOff.on, } EXPOSE_OUT_MODE = { - DetectorTrigger.internal: ExposeOutMode.timed, - DetectorTrigger.constant_gate: ExposeOutMode.trigger_width, - DetectorTrigger.variable_gate: ExposeOutMode.trigger_width, - DetectorTrigger.edge_trigger: ExposeOutMode.timed, + DetectorTrigger.internal: VimbaExposeOutMode.timed, + DetectorTrigger.constant_gate: VimbaExposeOutMode.trigger_width, + DetectorTrigger.variable_gate: VimbaExposeOutMode.trigger_width, + DetectorTrigger.edge_trigger: VimbaExposeOutMode.timed, } @@ -39,7 +37,7 @@ def __init__( driver: VimbaDriver, good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), ) -> None: - self.driver = driver + self._drv = driver self.good_states = good_states def get_deadtime(self, exposure: float) -> float: @@ -52,21 +50,21 @@ async def arm( exposure: Optional[float] = None, ) -> AsyncStatus: await asyncio.gather( - self.driver.trigger_mode.set(TRIGGER_MODE[trigger]), - self.driver.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), - self.driver.num_images.set(num), - self.driver.image_mode.set(ImageMode.multiple), + self._drv.trigger_mode.set(TRIGGER_MODE[trigger]), + self._drv.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), + self._drv.num_images.set(num), + self._drv.image_mode.set(ImageMode.multiple), ) if exposure is not None and trigger not in [ DetectorTrigger.variable_gate, DetectorTrigger.constant_gate, ]: - await self.driver.acquire_time.set(exposure) + await self._drv.acquire_time.set(exposure) if trigger != DetectorTrigger.internal: - self.driver.trigger_source.set(TriggerSource.line1) + self._drv.trigger_source.set(VimbaTriggerSource.line1) return await start_acquiring_driver_and_ensure_status( - self.driver, good_states=self.good_states + self._drv, good_states=self.good_states ) async def disarm(self): - await self.driver.acquire.set(0) + await stop_busy_record(self._drv.acquire, False, timeout=1) diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py index be7770dc72..8112b21dd9 100644 --- a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -4,7 +4,7 @@ from .ad_base import ADBase -class PixelFormat(str, Enum): +class VimbaPixelFormat(str, Enum): internal = "Mono8" ext_enable = "Mono12" ext_trigger = "Ext. Trigger" @@ -12,7 +12,7 @@ class PixelFormat(str, Enum): alignment = "Alignment" -class ConvertFormat(str, Enum): +class VimbaConvertFormat(str, Enum): none = "None" mono8 = "Mono8" mono16 = "Mono16" @@ -20,7 +20,7 @@ class ConvertFormat(str, Enum): rgb16 = "RGB16" -class TriggerSource(str, Enum): +class VimbaTriggerSource(str, Enum): freerun = "Freerun" line1 = "Line1" line2 = "Line2" @@ -30,17 +30,17 @@ class TriggerSource(str, Enum): action1 = "Action1" -class Overlap(str, Enum): +class VimbaOverlap(str, Enum): off = "Off" prev_frame = "PreviousFrame" -class OnOff(str, Enum): +class VimbaOnOff(str, Enum): on = "On" off = "Off" -class ExposeOutMode(str, Enum): +class VimbaExposeOutMode(str, Enum): timed = "Timed" # Use ExposureTime PV trigger_width = "TriggerWidth" # Expose for length of high signal @@ -49,10 +49,10 @@ class VimbaDriver(ADBase): def __init__(self, prefix: str) -> None: # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") self.convert_format = ad_rw( - ConvertFormat, prefix + "ConvertPixelFormat" + VimbaConvertFormat, prefix + "ConvertPixelFormat" ) # Pixel format of data outputted to AD - self.trigger_source = ad_rw(TriggerSource, prefix + "TriggerSource") - self.trigger_mode = ad_rw(OnOff, prefix + "TriggerMode") - self.overlap = ad_rw(Overlap, prefix + "TriggerOverlap") - self.expose_out_mode = ad_rw(ExposeOutMode, prefix + "ExposureMode") + self.trig_source = ad_rw(VimbaTriggerSource, prefix + "TriggerSource") + self.trigger_mode = ad_rw(VimbaOnOff, prefix + "TriggerMode") + self.overlap = ad_rw(VimbaOverlap, prefix + "TriggerOverlap") + self.expose_mode = ad_rw(VimbaExposeOutMode, prefix + "ExposureMode") super().__init__(prefix) From 534d5e1f8f089aea90781faebe4685544da794f8 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Wed, 24 Apr 2024 15:15:00 -0400 Subject: [PATCH 06/13] Add tests for kinetix and vimba, add detector classes for both. --- .../epics/areadetector/__init__.py | 4 + .../controllers/vimba_controller.py | 6 +- .../epics/areadetector/drivers/__init__.py | 4 + src/ophyd_async/epics/areadetector/kinetix.py | 48 ++++++ src/ophyd_async/epics/areadetector/vimba.py | 45 +++++ tests/epics/areadetector/test_kinetix.py | 150 ++++++++++++++++ tests/epics/areadetector/test_vimba.py | 162 ++++++++++++++++++ 7 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 src/ophyd_async/epics/areadetector/kinetix.py create mode 100644 src/ophyd_async/epics/areadetector/vimba.py create mode 100644 tests/epics/areadetector/test_kinetix.py create mode 100644 tests/epics/areadetector/test_vimba.py diff --git a/src/ophyd_async/epics/areadetector/__init__.py b/src/ophyd_async/epics/areadetector/__init__.py index cbad83e071..309827cb5e 100644 --- a/src/ophyd_async/epics/areadetector/__init__.py +++ b/src/ophyd_async/epics/areadetector/__init__.py @@ -1,4 +1,5 @@ from .aravis import AravisDetector +from .kinetix import KinetixDetector from .pilatus import PilatusDetector from .single_trigger_det import SingleTriggerDet from .utils import ( @@ -9,9 +10,12 @@ ad_r, ad_rw, ) +from .vimba import VimbaDetector __all__ = [ "AravisDetector", + "KinetixDetector", + "VimbaDetector", "SingleTriggerDet", "FileWriteMode", "ImageMode", diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index 3faaa7c9f6..324b1a5c6c 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -51,7 +51,7 @@ async def arm( ) -> AsyncStatus: await asyncio.gather( self._drv.trigger_mode.set(TRIGGER_MODE[trigger]), - self._drv.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), + self._drv.expose_mode.set(EXPOSE_OUT_MODE[trigger]), self._drv.num_images.set(num), self._drv.image_mode.set(ImageMode.multiple), ) @@ -61,7 +61,9 @@ async def arm( ]: await self._drv.acquire_time.set(exposure) if trigger != DetectorTrigger.internal: - self._drv.trigger_source.set(VimbaTriggerSource.line1) + self._drv.trig_source.set(VimbaTriggerSource.line1) + else: + self._drv.trig_source.set(VimbaTriggerSource.freerun) return await start_acquiring_driver_and_ensure_status( self._drv, good_states=self.good_states ) diff --git a/src/ophyd_async/epics/areadetector/drivers/__init__.py b/src/ophyd_async/epics/areadetector/drivers/__init__.py index c24c42a1cf..451bd48b21 100644 --- a/src/ophyd_async/epics/areadetector/drivers/__init__.py +++ b/src/ophyd_async/epics/areadetector/drivers/__init__.py @@ -5,13 +5,17 @@ start_acquiring_driver_and_ensure_status, ) from .aravis_driver import AravisDriver +from .kinetix_driver import KinetixDriver from .pilatus_driver import PilatusDriver +from .vimba_driver import VimbaDriver __all__ = [ "ADBase", "ADBaseShapeProvider", "PilatusDriver", "AravisDriver", + "KinetixDriver", + "VimbaDriver", "start_acquiring_driver_and_ensure_status", "DetectorState", ] diff --git a/src/ophyd_async/epics/areadetector/kinetix.py b/src/ophyd_async/epics/areadetector/kinetix.py new file mode 100644 index 0000000000..36ec479a49 --- /dev/null +++ b/src/ophyd_async/epics/areadetector/kinetix.py @@ -0,0 +1,48 @@ +from bluesky.protocols import HasHints, Hints + +from ophyd_async.core import DirectoryProvider, StandardDetector +from ophyd_async.epics.areadetector.controllers.kinetix_controller import ( + KinetixController, +) +from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider +from ophyd_async.epics.areadetector.drivers.kinetix_driver import KinetixDriver +from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF + + +class KinetixDetector(StandardDetector, HasHints): + """ + Ophyd-async implementation of an ADKinetix Detector. + https://github.com/NSLS-II/ADKinetix + """ + + _controller: KinetixController + _writer: HDFWriter + + def __init__( + self, + name: str, + directory_provider: DirectoryProvider, + driver: KinetixDriver, + hdf: NDFileHDF, + **scalar_sigs: str, + ): + # Must be child of Detector to pick up connect() + self.drv = driver + self.hdf = hdf + + super().__init__( + KinetixController(self.drv), + HDFWriter( + self.hdf, + directory_provider, + lambda: self.name, + ADBaseShapeProvider(self.drv), + **scalar_sigs, + ), + config_sigs=(self.drv.acquire_time, self.drv.acquire), + name=name, + ) + + @property + def hints(self) -> Hints: + return self._writer.hints diff --git a/src/ophyd_async/epics/areadetector/vimba.py b/src/ophyd_async/epics/areadetector/vimba.py new file mode 100644 index 0000000000..5e764b5b20 --- /dev/null +++ b/src/ophyd_async/epics/areadetector/vimba.py @@ -0,0 +1,45 @@ +from bluesky.protocols import HasHints, Hints + +from ophyd_async.core import DirectoryProvider, StandardDetector +from ophyd_async.epics.areadetector.controllers.vimba_controller import VimbaController +from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider +from ophyd_async.epics.areadetector.drivers.vimba_driver import VimbaDriver +from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF + + +class VimbaDetector(StandardDetector, HasHints): + """ + Ophyd-async implementation of an ADVimba Detector. + """ + + _controller: VimbaController + _writer: HDFWriter + + def __init__( + self, + name: str, + directory_provider: DirectoryProvider, + driver: VimbaDriver, + hdf: NDFileHDF, + **scalar_sigs: str, + ): + # Must be child of Detector to pick up connect() + self.drv = driver + self.hdf = hdf + + super().__init__( + VimbaController(self.drv), + HDFWriter( + self.hdf, + directory_provider, + lambda: self.name, + ADBaseShapeProvider(self.drv), + **scalar_sigs, + ), + config_sigs=(self.drv.acquire_time, self.drv.acquire), + name=name, + ) + + @property + def hints(self) -> Hints: + return self._writer.hints diff --git a/tests/epics/areadetector/test_kinetix.py b/tests/epics/areadetector/test_kinetix.py new file mode 100644 index 0000000000..bb79a0b5f8 --- /dev/null +++ b/tests/epics/areadetector/test_kinetix.py @@ -0,0 +1,150 @@ +import pytest +from bluesky.run_engine import RunEngine + +from ophyd_async.core import ( + DetectorTrigger, + DeviceCollector, + DirectoryProvider, + set_sim_value, +) +from ophyd_async.epics.areadetector.drivers.kinetix_driver import KinetixDriver +from ophyd_async.epics.areadetector.kinetix import KinetixDetector +from ophyd_async.epics.areadetector.writers.nd_file_hdf import NDFileHDF + + +@pytest.fixture +async def adkinetix_driver(RE: RunEngine) -> KinetixDriver: + async with DeviceCollector(sim=True): + driver = KinetixDriver("DRV:") + + return driver + + +@pytest.fixture +async def hdf(RE: RunEngine) -> NDFileHDF: + async with DeviceCollector(sim=True): + hdf = NDFileHDF("HDF:") + + return hdf + + +@pytest.fixture +async def adkinetix( + RE: RunEngine, + static_directory_provider: DirectoryProvider, + adkinetix_driver: KinetixDriver, + hdf: NDFileHDF, +) -> KinetixDetector: + async with DeviceCollector(sim=True): + adkinetix = KinetixDetector( + "adkinetix", + static_directory_provider, + driver=adkinetix_driver, + hdf=hdf, + ) + + return adkinetix + + +async def test_get_deadtime( + adkinetix: KinetixDetector, +): + # Currently Kinetix driver doesn't support getting deadtime. + assert adkinetix._controller.get_deadtime(0) == 0.001 + + +async def test_trigger_modes(adkinetix: KinetixDetector): + set_sim_value(adkinetix.drv.trigger_mode, "Internal") + + async def setup_trigger_mode(trig_mode: DetectorTrigger): + await adkinetix.controller.arm(num=1, trigger=trig_mode) + # Prevent timeouts + set_sim_value(adkinetix.drv.acquire, True) + + # Default TriggerSource + assert (await adkinetix.drv.trigger_mode.get_value()) == "Internal" + + await setup_trigger_mode(DetectorTrigger.edge_trigger) + assert (await adkinetix.drv.trigger_mode.get_value()) == "Rising Edge" + + await setup_trigger_mode(DetectorTrigger.constant_gate) + assert (await adkinetix.drv.trigger_mode.get_value()) == "Exp. Gate" + + await setup_trigger_mode(DetectorTrigger.internal) + assert (await adkinetix.drv.trigger_mode.get_value()) == "Internal" + + await setup_trigger_mode(DetectorTrigger.variable_gate) + assert (await adkinetix.drv.trigger_mode.get_value()) == "Exp. Gate" + + +async def test_hints_from_hdf_writer(adkinetix: KinetixDetector): + assert adkinetix.hints == {"fields": ["adkinetix"]} + + +async def test_can_read(adkinetix: KinetixDetector): + # Standard detector can be used as Readable + assert (await adkinetix.read()) == {} + + +async def test_decribe_describes_writer_dataset(adkinetix: KinetixDetector): + set_sim_value(adkinetix._writer.hdf.file_path_exists, True) + set_sim_value(adkinetix._writer.hdf.capture, True) + + assert await adkinetix.describe() == {} + await adkinetix.stage() + assert await adkinetix.describe() == { + "adkinetix": { + "source": "soft://adkinetix-hdf-full_file_name", + "shape": (0, 0), + "dtype": "array", + "external": "STREAM:", + } + } + + +async def test_can_collect( + adkinetix: KinetixDetector, static_directory_provider: DirectoryProvider +): + directory_info = static_directory_provider() + full_file_name = directory_info.root / directory_info.resource_dir / "foo.h5" + set_sim_value(adkinetix.hdf.full_file_name, str(full_file_name)) + set_sim_value(adkinetix._writer.hdf.file_path_exists, True) + set_sim_value(adkinetix._writer.hdf.capture, True) + await adkinetix.stage() + docs = [(name, doc) async for name, doc in adkinetix.collect_asset_docs(1)] + assert len(docs) == 2 + assert docs[0][0] == "stream_resource" + stream_resource = docs[0][1] + sr_uid = stream_resource["uid"] + assert stream_resource["data_key"] == "adkinetix" + assert stream_resource["spec"] == "AD_HDF5_SWMR_SLICE" + assert stream_resource["root"] == str(directory_info.root) + assert stream_resource["resource_path"] == str( + directory_info.resource_dir / "foo.h5" + ) + assert stream_resource["path_semantics"] == "posix" + assert stream_resource["resource_kwargs"] == { + "path": "/entry/data/data", + "multiplier": 1, + "timestamps": "/entry/instrument/NDAttributes/NDArrayTimeStamp", + } + assert docs[1][0] == "stream_datum" + stream_datum = docs[1][1] + assert stream_datum["stream_resource"] == sr_uid + assert stream_datum["seq_nums"] == {"start": 0, "stop": 0} + assert stream_datum["indices"] == {"start": 0, "stop": 1} + + +async def test_can_decribe_collect(adkinetix: KinetixDetector): + set_sim_value(adkinetix._writer.hdf.file_path_exists, True) + set_sim_value(adkinetix._writer.hdf.capture, True) + assert (await adkinetix.describe_collect()) == {} + await adkinetix.stage() + assert (await adkinetix.describe_collect()) == { + "adkinetix": { + "source": "soft://adkinetix-hdf-full_file_name", + "shape": (0, 0), + "dtype": "array", + "external": "STREAM:", + } + } diff --git a/tests/epics/areadetector/test_vimba.py b/tests/epics/areadetector/test_vimba.py new file mode 100644 index 0000000000..002ddb1b7e --- /dev/null +++ b/tests/epics/areadetector/test_vimba.py @@ -0,0 +1,162 @@ +import pytest +from bluesky.run_engine import RunEngine + +from ophyd_async.core import ( + DetectorTrigger, + DeviceCollector, + DirectoryProvider, + set_sim_value, +) +from ophyd_async.epics.areadetector.drivers.vimba_driver import VimbaDriver +from ophyd_async.epics.areadetector.vimba import VimbaDetector +from ophyd_async.epics.areadetector.writers.nd_file_hdf import NDFileHDF + + +@pytest.fixture +async def advimba_driver(RE: RunEngine) -> VimbaDriver: + async with DeviceCollector(sim=True): + driver = VimbaDriver("DRV:") + + return driver + + +@pytest.fixture +async def hdf(RE: RunEngine) -> NDFileHDF: + async with DeviceCollector(sim=True): + hdf = NDFileHDF("HDF:") + + return hdf + + +@pytest.fixture +async def advimba( + RE: RunEngine, + static_directory_provider: DirectoryProvider, + advimba_driver: VimbaDriver, + hdf: NDFileHDF, +) -> VimbaDetector: + async with DeviceCollector(sim=True): + advimba = VimbaDetector( + "advimba", + static_directory_provider, + driver=advimba_driver, + hdf=hdf, + ) + + return advimba + + +async def test_get_deadtime( + advimba: VimbaDetector, +): + # Currently Vimba controller doesn't support getting deadtime. + assert advimba._controller.get_deadtime(0) == 0.001 + + +async def test_arming_trig_modes(advimba: VimbaDetector): + set_sim_value(advimba.drv.trig_source, "Freerun") + set_sim_value(advimba.drv.trigger_mode, "Off") + set_sim_value(advimba.drv.expose_mode, "Timed") + + async def setup_trigger_mode(trig_mode: DetectorTrigger): + await advimba.controller.arm(num=1, trigger=trig_mode) + # Prevent timeouts + set_sim_value(advimba.drv.acquire, True) + + # Default TriggerSource + assert (await advimba.drv.trig_source.get_value()) == "Freerun" + assert (await advimba.drv.trigger_mode.get_value()) == "Off" + assert (await advimba.drv.expose_mode.get_value()) == "Timed" + + await setup_trigger_mode(DetectorTrigger.edge_trigger) + assert (await advimba.drv.trig_source.get_value()) == "Line1" + assert (await advimba.drv.trigger_mode.get_value()) == "On" + assert (await advimba.drv.expose_mode.get_value()) == "Timed" + + await setup_trigger_mode(DetectorTrigger.constant_gate) + assert (await advimba.drv.trig_source.get_value()) == "Line1" + assert (await advimba.drv.trigger_mode.get_value()) == "On" + assert (await advimba.drv.expose_mode.get_value()) == "TriggerWidth" + + await setup_trigger_mode(DetectorTrigger.internal) + assert (await advimba.drv.trig_source.get_value()) == "Freerun" + assert (await advimba.drv.trigger_mode.get_value()) == "Off" + assert (await advimba.drv.expose_mode.get_value()) == "Timed" + + await setup_trigger_mode(DetectorTrigger.variable_gate) + assert (await advimba.drv.trig_source.get_value()) == "Line1" + assert (await advimba.drv.trigger_mode.get_value()) == "On" + assert (await advimba.drv.expose_mode.get_value()) == "TriggerWidth" + + +async def test_hints_from_hdf_writer(advimba: VimbaDetector): + assert advimba.hints == {"fields": ["advimba"]} + + +async def test_can_read(advimba: VimbaDetector): + # Standard detector can be used as Readable + assert (await advimba.read()) == {} + + +async def test_decribe_describes_writer_dataset(advimba: VimbaDetector): + set_sim_value(advimba._writer.hdf.file_path_exists, True) + set_sim_value(advimba._writer.hdf.capture, True) + + assert await advimba.describe() == {} + await advimba.stage() + assert await advimba.describe() == { + "advimba": { + "source": "soft://advimba-hdf-full_file_name", + "shape": (0, 0), + "dtype": "array", + "external": "STREAM:", + } + } + + +async def test_can_collect( + advimba: VimbaDetector, static_directory_provider: DirectoryProvider +): + directory_info = static_directory_provider() + full_file_name = directory_info.root / directory_info.resource_dir / "foo.h5" + set_sim_value(advimba.hdf.full_file_name, str(full_file_name)) + set_sim_value(advimba._writer.hdf.file_path_exists, True) + set_sim_value(advimba._writer.hdf.capture, True) + await advimba.stage() + docs = [(name, doc) async for name, doc in advimba.collect_asset_docs(1)] + assert len(docs) == 2 + assert docs[0][0] == "stream_resource" + stream_resource = docs[0][1] + sr_uid = stream_resource["uid"] + assert stream_resource["data_key"] == "advimba" + assert stream_resource["spec"] == "AD_HDF5_SWMR_SLICE" + assert stream_resource["root"] == str(directory_info.root) + assert stream_resource["resource_path"] == str( + directory_info.resource_dir / "foo.h5" + ) + assert stream_resource["path_semantics"] == "posix" + assert stream_resource["resource_kwargs"] == { + "path": "/entry/data/data", + "multiplier": 1, + "timestamps": "/entry/instrument/NDAttributes/NDArrayTimeStamp", + } + assert docs[1][0] == "stream_datum" + stream_datum = docs[1][1] + assert stream_datum["stream_resource"] == sr_uid + assert stream_datum["seq_nums"] == {"start": 0, "stop": 0} + assert stream_datum["indices"] == {"start": 0, "stop": 1} + + +async def test_can_decribe_collect(advimba: VimbaDetector): + set_sim_value(advimba._writer.hdf.file_path_exists, True) + set_sim_value(advimba._writer.hdf.capture, True) + assert (await advimba.describe_collect()) == {} + await advimba.stage() + assert (await advimba.describe_collect()) == { + "advimba": { + "source": "soft://advimba-hdf-full_file_name", + "shape": (0, 0), + "dtype": "array", + "external": "STREAM:", + } + } From b6ad0512c8e8bb701645c890f3cb81c28e393bff Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Mon, 15 Apr 2024 10:33:57 -0400 Subject: [PATCH 07/13] Adding driver and controller classes for Kinetix and Vimba cameras --- .../controllers/kinetix_controller.py | 55 ++++++++++++++++ .../controllers/vimba_controller.py | 65 +++++++++++++++++++ .../areadetector/drivers/kinetix_driver.py | 21 ++++++ .../areadetector/drivers/vimba_driver.py | 51 +++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py create mode 100644 src/ophyd_async/epics/areadetector/controllers/vimba_controller.py create mode 100644 src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py create mode 100644 src/ophyd_async/epics/areadetector/drivers/vimba_driver.py diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py new file mode 100644 index 0000000000..ac3dc2964f --- /dev/null +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -0,0 +1,55 @@ +import asyncio +from typing import Optional, Set + +from ophyd_async.core import AsyncStatus, DetectorControl, DetectorTrigger +from ophyd_async.epics.areadetector.drivers.ad_base import ( + DEFAULT_GOOD_STATES, + DetectorState, + start_acquiring_driver_and_ensure_status, +) + +from ..drivers.kinetix_driver import KinetixDriver, KinetixTriggerMode +from ..utils import ImageMode, stop_busy_record + +KINETIX_TRIGGER_MODE_MAP = { + DetectorTrigger.internal: KinetixTriggerMode.internal, + DetectorTrigger.constant_gate: KinetixTriggerMode.gate, + DetectorTrigger.variable_gate: KinetixTriggerMode.gate, + DetectorTrigger.edge_trigger: KinetixTriggerMode.edge, +} + + +class KinetixController(DetectorControl): + def __init__( + self, + driver: KinetixDriver, + good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), + ) -> None: + self.driver = driver + self.good_states = good_states + + def get_deadtime(self, exposure: float) -> float: + return 0.001 + + async def arm( + self, + num: int, + trigger: DetectorTrigger = DetectorTrigger.internal, + exposure: Optional[float] = None, + ) -> AsyncStatus: + await asyncio.gather( + self.driver.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]), + self.driver.num_images.set(999_999 if num == 0 else num), + self.driver.image_mode.set(ImageMode.multiple), + ) + if exposure is not None and trigger not in [ + DetectorTrigger.variable_gate, + DetectorTrigger.constant_gate, + ]: + await self.driver.acquire_time.set(exposure) + return await start_acquiring_driver_and_ensure_status( + self.driver, good_states=self.good_states + ) + + async def disarm(self): + await self.driver.acquire.set(0) \ No newline at end of file diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py new file mode 100644 index 0000000000..c506d1cafe --- /dev/null +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -0,0 +1,65 @@ +import asyncio +from typing import Optional, Set + +from ophyd_async.core import AsyncStatus, DetectorControl, DetectorTrigger +from ophyd_async.epics.areadetector.drivers.ad_base import ( + DEFAULT_GOOD_STATES, + DetectorState, + start_acquiring_driver_and_ensure_status, +) + +from ..drivers.vimba_driver import VimbaDriver, ExposeOutMode, OnOff, TriggerSource +from ..utils import ImageMode, stop_busy_record + +TRIGGER_MODE = { + DetectorTrigger.internal: OnOff.off, + DetectorTrigger.constant_gate: OnOff.on, + DetectorTrigger.variable_gate: OnOff.on, + DetectorTrigger.edge_trigger: OnOff.on, +} + +EXPOSE_OUT_MODE = { + DetectorTrigger.internal: ExposeOutMode.timed, + DetectorTrigger.constant_gate: ExposeOutMode.trigger_width, + DetectorTrigger.variable_gate: ExposeOutMode.trigger_width, + DetectorTrigger.edge_trigger: ExposeOutMode.timed, +} + + +class VimbaController(DetectorControl): + def __init__( + self, + driver: VimbaDriver, + good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), + ) -> None: + self.driver = driver + self.good_states = good_states + + def get_deadtime(self, exposure: float) -> float: + return 0.001 + + async def arm( + self, + num: int, + trigger: DetectorTrigger = DetectorTrigger.internal, + exposure: Optional[float] = None, + ) -> AsyncStatus: + await asyncio.gather( + self.driver.trigger_mode.set(TRIGGER_MODE[trigger]), + self.driver.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), + self.driver.num_images.set(999_999 if num == 0 else num), + self.driver.image_mode.set(ImageMode.multiple), + ) + if exposure is not None and trigger not in [ + DetectorTrigger.variable_gate, + DetectorTrigger.constant_gate, + ]: + await self.driver.acquire_time.set(exposure) + if trigger != DetectorTrigger.internal: + self.driver.trigger_source = TriggerSource.line1 + return await start_acquiring_driver_and_ensure_status( + self.driver, good_states=self.good_states + ) + + async def disarm(self): + await self.driver.acquire.set(0) \ No newline at end of file diff --git a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py new file mode 100644 index 0000000000..7262c0e370 --- /dev/null +++ b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py @@ -0,0 +1,21 @@ +from enum import Enum + +from ..utils import ad_rw, ad_r +from .ad_base import ADBase + +class KinetixTriggerMode(str, Enum): + internal = "Internal" + edge = "Rising Edge" + gate = "Exp. Gate" + +class KinetixReadoutMode(str, Enum): + sensitivity = 1 + speed = 2 + dynamic_range = 3 + +class KinetixDriver(ADBase): + def __init__(self, prefix: str) -> None: + super().__init__(prefix) + # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") + self.trigger_mode = ad_rw(KinetixTriggerMode, prefix + "TriggerMode") + self.readout_mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") \ No newline at end of file diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py new file mode 100644 index 0000000000..57153e9bb4 --- /dev/null +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -0,0 +1,51 @@ +from enum import Enum + +from ..utils import ad_rw +from .ad_base import ADBase + + +class PixelFormat(str, Enum): + internal = "Mono8" + ext_enable = "Mono12" + ext_trigger = "Ext. Trigger" + mult_trigger = "Mult. Trigger" + alignment = "Alignment" + + +class ConvertFormat(str, Enum): + none = "None" + mono8 = "Mono8" + mono16 = "Mono16" + rgb8 = "RGB8" + rgb16 = "RGB16" + +class TriggerSource(str, Enum): + freerun = "Freerun" + line1 = "Line1" + line2 = "Line2" + fixed_rate = "FixedRate" + software = "Software" + action0 = "Action0" + action1 = "Action1" + +class Overlap(str, Enum): + off = "Off" + prev_frame = "PreviousFrame" + +class OnOff(str, Enum): + on = "On" + off = "Off" + +class ExposeOutMode(str, Enum): + timed = "Timed" # Use ExposureTime PV + trigger_width = "TriggerWidth" # Expose for length of high signal + +class VimbaDriver(ADBase): + def __init__(self, prefix: str) -> None: + # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") + self.convert_format = ad_rw(ConvertFormat, prefix + "ConvertPixelFormat") + self.trigger_source = ad_rw(TriggerSource, prefix + "TriggerSource") + self.trigger_mode = ad_rw(OnOff, prefix + "TriggerMode") + self.overlap = ad_rw(Overlap, prefix + "TriggerOverlap") + self.expose_out_mode = ad_rw(ExposeOutMode, prefix + "ExposureMode") + super().__init__(prefix) \ No newline at end of file From d7f798000879ea2ce037cdf72fc9bfcddb8894c0 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Mon, 15 Apr 2024 10:47:55 -0400 Subject: [PATCH 08/13] Make linter and mypy happy --- .../epics/areadetector/controllers/kinetix_controller.py | 2 +- .../epics/areadetector/controllers/vimba_controller.py | 4 ++-- src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py | 2 +- src/ophyd_async/epics/areadetector/drivers/vimba_driver.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py index ac3dc2964f..c2b81222a2 100644 --- a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -52,4 +52,4 @@ async def arm( ) async def disarm(self): - await self.driver.acquire.set(0) \ No newline at end of file + await self.driver.acquire.set(0) diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index c506d1cafe..0ee06a5d4f 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -56,10 +56,10 @@ async def arm( ]: await self.driver.acquire_time.set(exposure) if trigger != DetectorTrigger.internal: - self.driver.trigger_source = TriggerSource.line1 + self.driver.trigger_source.set(TriggerSource.line1) return await start_acquiring_driver_and_ensure_status( self.driver, good_states=self.good_states ) async def disarm(self): - await self.driver.acquire.set(0) \ No newline at end of file + await self.driver.acquire.set(0) diff --git a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py index 7262c0e370..5e2b2cbf5d 100644 --- a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py @@ -18,4 +18,4 @@ def __init__(self, prefix: str) -> None: super().__init__(prefix) # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") self.trigger_mode = ad_rw(KinetixTriggerMode, prefix + "TriggerMode") - self.readout_mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") \ No newline at end of file + self.readout_mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py index 57153e9bb4..827e0f6ad4 100644 --- a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -48,4 +48,4 @@ def __init__(self, prefix: str) -> None: self.trigger_mode = ad_rw(OnOff, prefix + "TriggerMode") self.overlap = ad_rw(Overlap, prefix + "TriggerOverlap") self.expose_out_mode = ad_rw(ExposeOutMode, prefix + "ExposureMode") - super().__init__(prefix) \ No newline at end of file + super().__init__(prefix) From ad9e817dc03a9226b42010c18149c5b8bc68affc Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Mon, 15 Apr 2024 11:28:10 -0400 Subject: [PATCH 09/13] More linter fixes --- .../areadetector/controllers/kinetix_controller.py | 2 +- .../areadetector/controllers/vimba_controller.py | 11 +++++++++-- .../epics/areadetector/drivers/kinetix_driver.py | 7 +++++-- .../epics/areadetector/drivers/vimba_driver.py | 5 +++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py index c2b81222a2..535c500dc9 100644 --- a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -9,7 +9,7 @@ ) from ..drivers.kinetix_driver import KinetixDriver, KinetixTriggerMode -from ..utils import ImageMode, stop_busy_record +from ..utils import ImageMode KINETIX_TRIGGER_MODE_MAP = { DetectorTrigger.internal: KinetixTriggerMode.internal, diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index 0ee06a5d4f..38995ccbca 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -8,8 +8,15 @@ start_acquiring_driver_and_ensure_status, ) -from ..drivers.vimba_driver import VimbaDriver, ExposeOutMode, OnOff, TriggerSource -from ..utils import ImageMode, stop_busy_record +from ..drivers.vimba_driver import ( + VimbaDriver, + ExposeOutMode, + OnOff, + TriggerSource, +) + +from ..utils import ImageMode + TRIGGER_MODE = { DetectorTrigger.internal: OnOff.off, diff --git a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py index 5e2b2cbf5d..1a2b866daf 100644 --- a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py @@ -1,21 +1,24 @@ from enum import Enum -from ..utils import ad_rw, ad_r +from ..utils import ad_rw from .ad_base import ADBase + class KinetixTriggerMode(str, Enum): internal = "Internal" edge = "Rising Edge" gate = "Exp. Gate" + class KinetixReadoutMode(str, Enum): sensitivity = 1 speed = 2 dynamic_range = 3 + class KinetixDriver(ADBase): def __init__(self, prefix: str) -> None: super().__init__(prefix) # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") self.trigger_mode = ad_rw(KinetixTriggerMode, prefix + "TriggerMode") - self.readout_mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") + self.mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py index 827e0f6ad4..34279cf478 100644 --- a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -19,6 +19,7 @@ class ConvertFormat(str, Enum): rgb8 = "RGB8" rgb16 = "RGB16" + class TriggerSource(str, Enum): freerun = "Freerun" line1 = "Line1" @@ -28,18 +29,22 @@ class TriggerSource(str, Enum): action0 = "Action0" action1 = "Action1" + class Overlap(str, Enum): off = "Off" prev_frame = "PreviousFrame" + class OnOff(str, Enum): on = "On" off = "Off" + class ExposeOutMode(str, Enum): timed = "Timed" # Use ExposureTime PV trigger_width = "TriggerWidth" # Expose for length of high signal + class VimbaDriver(ADBase): def __init__(self, prefix: str) -> None: # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") From 9b7aebf1010699b24d4a72b1b4e774083fa47d71 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Tue, 16 Apr 2024 14:17:52 -0400 Subject: [PATCH 10/13] Remove pilatus specific line, resolve linter problem --- .../epics/areadetector/controllers/kinetix_controller.py | 2 +- .../epics/areadetector/controllers/vimba_controller.py | 2 +- src/ophyd_async/epics/areadetector/drivers/vimba_driver.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py index 535c500dc9..08ae2d8c73 100644 --- a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -39,7 +39,7 @@ async def arm( ) -> AsyncStatus: await asyncio.gather( self.driver.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]), - self.driver.num_images.set(999_999 if num == 0 else num), + self.driver.num_images.set(num), self.driver.image_mode.set(ImageMode.multiple), ) if exposure is not None and trigger not in [ diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index 38995ccbca..0c5878ca2c 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -54,7 +54,7 @@ async def arm( await asyncio.gather( self.driver.trigger_mode.set(TRIGGER_MODE[trigger]), self.driver.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), - self.driver.num_images.set(999_999 if num == 0 else num), + self.driver.num_images.set(num), self.driver.image_mode.set(ImageMode.multiple), ) if exposure is not None and trigger not in [ diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py index 34279cf478..be7770dc72 100644 --- a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -48,7 +48,9 @@ class ExposeOutMode(str, Enum): class VimbaDriver(ADBase): def __init__(self, prefix: str) -> None: # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") - self.convert_format = ad_rw(ConvertFormat, prefix + "ConvertPixelFormat") + self.convert_format = ad_rw( + ConvertFormat, prefix + "ConvertPixelFormat" + ) # Pixel format of data outputted to AD self.trigger_source = ad_rw(TriggerSource, prefix + "TriggerSource") self.trigger_mode = ad_rw(OnOff, prefix + "TriggerMode") self.overlap = ad_rw(Overlap, prefix + "TriggerOverlap") From 450aa0a8e36c9c48f4044ea85f87aa5e3c6228b1 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Thu, 18 Apr 2024 09:57:30 -0400 Subject: [PATCH 11/13] Modify Vimba/Kinetix controllers/drivers to match new aravis/pilatus --- .../controllers/kinetix_controller.py | 16 +++---- .../controllers/vimba_controller.py | 44 +++++++++---------- .../areadetector/drivers/vimba_driver.py | 22 +++++----- 3 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py index 08ae2d8c73..64a5ff4ffc 100644 --- a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -9,7 +9,7 @@ ) from ..drivers.kinetix_driver import KinetixDriver, KinetixTriggerMode -from ..utils import ImageMode +from ..utils import ImageMode, stop_busy_record KINETIX_TRIGGER_MODE_MAP = { DetectorTrigger.internal: KinetixTriggerMode.internal, @@ -25,7 +25,7 @@ def __init__( driver: KinetixDriver, good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), ) -> None: - self.driver = driver + self._drv = driver self.good_states = good_states def get_deadtime(self, exposure: float) -> float: @@ -38,18 +38,18 @@ async def arm( exposure: Optional[float] = None, ) -> AsyncStatus: await asyncio.gather( - self.driver.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]), - self.driver.num_images.set(num), - self.driver.image_mode.set(ImageMode.multiple), + self._drv.trigger_mode.set(KINETIX_TRIGGER_MODE_MAP[trigger]), + self._drv.num_images.set(num), + self._drv.image_mode.set(ImageMode.multiple), ) if exposure is not None and trigger not in [ DetectorTrigger.variable_gate, DetectorTrigger.constant_gate, ]: - await self.driver.acquire_time.set(exposure) + await self._drv.acquire_time.set(exposure) return await start_acquiring_driver_and_ensure_status( - self.driver, good_states=self.good_states + self._drv, good_states=self.good_states ) async def disarm(self): - await self.driver.acquire.set(0) + await stop_busy_record(self._drv.acquire, False, timeout=1) diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index 0c5878ca2c..3faaa7c9f6 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -10,26 +10,24 @@ from ..drivers.vimba_driver import ( VimbaDriver, - ExposeOutMode, - OnOff, - TriggerSource, + VimbaExposeOutMode, + VimbaOnOff, + VimbaTriggerSource, ) - -from ..utils import ImageMode - +from ..utils import ImageMode, stop_busy_record TRIGGER_MODE = { - DetectorTrigger.internal: OnOff.off, - DetectorTrigger.constant_gate: OnOff.on, - DetectorTrigger.variable_gate: OnOff.on, - DetectorTrigger.edge_trigger: OnOff.on, + DetectorTrigger.internal: VimbaOnOff.off, + DetectorTrigger.constant_gate: VimbaOnOff.on, + DetectorTrigger.variable_gate: VimbaOnOff.on, + DetectorTrigger.edge_trigger: VimbaOnOff.on, } EXPOSE_OUT_MODE = { - DetectorTrigger.internal: ExposeOutMode.timed, - DetectorTrigger.constant_gate: ExposeOutMode.trigger_width, - DetectorTrigger.variable_gate: ExposeOutMode.trigger_width, - DetectorTrigger.edge_trigger: ExposeOutMode.timed, + DetectorTrigger.internal: VimbaExposeOutMode.timed, + DetectorTrigger.constant_gate: VimbaExposeOutMode.trigger_width, + DetectorTrigger.variable_gate: VimbaExposeOutMode.trigger_width, + DetectorTrigger.edge_trigger: VimbaExposeOutMode.timed, } @@ -39,7 +37,7 @@ def __init__( driver: VimbaDriver, good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), ) -> None: - self.driver = driver + self._drv = driver self.good_states = good_states def get_deadtime(self, exposure: float) -> float: @@ -52,21 +50,21 @@ async def arm( exposure: Optional[float] = None, ) -> AsyncStatus: await asyncio.gather( - self.driver.trigger_mode.set(TRIGGER_MODE[trigger]), - self.driver.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), - self.driver.num_images.set(num), - self.driver.image_mode.set(ImageMode.multiple), + self._drv.trigger_mode.set(TRIGGER_MODE[trigger]), + self._drv.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), + self._drv.num_images.set(num), + self._drv.image_mode.set(ImageMode.multiple), ) if exposure is not None and trigger not in [ DetectorTrigger.variable_gate, DetectorTrigger.constant_gate, ]: - await self.driver.acquire_time.set(exposure) + await self._drv.acquire_time.set(exposure) if trigger != DetectorTrigger.internal: - self.driver.trigger_source.set(TriggerSource.line1) + self._drv.trigger_source.set(VimbaTriggerSource.line1) return await start_acquiring_driver_and_ensure_status( - self.driver, good_states=self.good_states + self._drv, good_states=self.good_states ) async def disarm(self): - await self.driver.acquire.set(0) + await stop_busy_record(self._drv.acquire, False, timeout=1) diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py index be7770dc72..8112b21dd9 100644 --- a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -4,7 +4,7 @@ from .ad_base import ADBase -class PixelFormat(str, Enum): +class VimbaPixelFormat(str, Enum): internal = "Mono8" ext_enable = "Mono12" ext_trigger = "Ext. Trigger" @@ -12,7 +12,7 @@ class PixelFormat(str, Enum): alignment = "Alignment" -class ConvertFormat(str, Enum): +class VimbaConvertFormat(str, Enum): none = "None" mono8 = "Mono8" mono16 = "Mono16" @@ -20,7 +20,7 @@ class ConvertFormat(str, Enum): rgb16 = "RGB16" -class TriggerSource(str, Enum): +class VimbaTriggerSource(str, Enum): freerun = "Freerun" line1 = "Line1" line2 = "Line2" @@ -30,17 +30,17 @@ class TriggerSource(str, Enum): action1 = "Action1" -class Overlap(str, Enum): +class VimbaOverlap(str, Enum): off = "Off" prev_frame = "PreviousFrame" -class OnOff(str, Enum): +class VimbaOnOff(str, Enum): on = "On" off = "Off" -class ExposeOutMode(str, Enum): +class VimbaExposeOutMode(str, Enum): timed = "Timed" # Use ExposureTime PV trigger_width = "TriggerWidth" # Expose for length of high signal @@ -49,10 +49,10 @@ class VimbaDriver(ADBase): def __init__(self, prefix: str) -> None: # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") self.convert_format = ad_rw( - ConvertFormat, prefix + "ConvertPixelFormat" + VimbaConvertFormat, prefix + "ConvertPixelFormat" ) # Pixel format of data outputted to AD - self.trigger_source = ad_rw(TriggerSource, prefix + "TriggerSource") - self.trigger_mode = ad_rw(OnOff, prefix + "TriggerMode") - self.overlap = ad_rw(Overlap, prefix + "TriggerOverlap") - self.expose_out_mode = ad_rw(ExposeOutMode, prefix + "ExposureMode") + self.trig_source = ad_rw(VimbaTriggerSource, prefix + "TriggerSource") + self.trigger_mode = ad_rw(VimbaOnOff, prefix + "TriggerMode") + self.overlap = ad_rw(VimbaOverlap, prefix + "TriggerOverlap") + self.expose_mode = ad_rw(VimbaExposeOutMode, prefix + "ExposureMode") super().__init__(prefix) From 3a79f7c3881317a937a18e7de93298aaab8e13a7 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Wed, 24 Apr 2024 15:15:00 -0400 Subject: [PATCH 12/13] Add tests for kinetix and vimba, add detector classes for both. --- .../epics/areadetector/__init__.py | 4 + .../controllers/vimba_controller.py | 6 +- .../epics/areadetector/drivers/__init__.py | 4 + src/ophyd_async/epics/areadetector/kinetix.py | 48 ++++++ src/ophyd_async/epics/areadetector/vimba.py | 45 +++++ tests/epics/areadetector/test_kinetix.py | 150 ++++++++++++++++ tests/epics/areadetector/test_vimba.py | 162 ++++++++++++++++++ 7 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 src/ophyd_async/epics/areadetector/kinetix.py create mode 100644 src/ophyd_async/epics/areadetector/vimba.py create mode 100644 tests/epics/areadetector/test_kinetix.py create mode 100644 tests/epics/areadetector/test_vimba.py diff --git a/src/ophyd_async/epics/areadetector/__init__.py b/src/ophyd_async/epics/areadetector/__init__.py index cbad83e071..309827cb5e 100644 --- a/src/ophyd_async/epics/areadetector/__init__.py +++ b/src/ophyd_async/epics/areadetector/__init__.py @@ -1,4 +1,5 @@ from .aravis import AravisDetector +from .kinetix import KinetixDetector from .pilatus import PilatusDetector from .single_trigger_det import SingleTriggerDet from .utils import ( @@ -9,9 +10,12 @@ ad_r, ad_rw, ) +from .vimba import VimbaDetector __all__ = [ "AravisDetector", + "KinetixDetector", + "VimbaDetector", "SingleTriggerDet", "FileWriteMode", "ImageMode", diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index 3faaa7c9f6..324b1a5c6c 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -51,7 +51,7 @@ async def arm( ) -> AsyncStatus: await asyncio.gather( self._drv.trigger_mode.set(TRIGGER_MODE[trigger]), - self._drv.expose_out_mode.set(EXPOSE_OUT_MODE[trigger]), + self._drv.expose_mode.set(EXPOSE_OUT_MODE[trigger]), self._drv.num_images.set(num), self._drv.image_mode.set(ImageMode.multiple), ) @@ -61,7 +61,9 @@ async def arm( ]: await self._drv.acquire_time.set(exposure) if trigger != DetectorTrigger.internal: - self._drv.trigger_source.set(VimbaTriggerSource.line1) + self._drv.trig_source.set(VimbaTriggerSource.line1) + else: + self._drv.trig_source.set(VimbaTriggerSource.freerun) return await start_acquiring_driver_and_ensure_status( self._drv, good_states=self.good_states ) diff --git a/src/ophyd_async/epics/areadetector/drivers/__init__.py b/src/ophyd_async/epics/areadetector/drivers/__init__.py index c24c42a1cf..451bd48b21 100644 --- a/src/ophyd_async/epics/areadetector/drivers/__init__.py +++ b/src/ophyd_async/epics/areadetector/drivers/__init__.py @@ -5,13 +5,17 @@ start_acquiring_driver_and_ensure_status, ) from .aravis_driver import AravisDriver +from .kinetix_driver import KinetixDriver from .pilatus_driver import PilatusDriver +from .vimba_driver import VimbaDriver __all__ = [ "ADBase", "ADBaseShapeProvider", "PilatusDriver", "AravisDriver", + "KinetixDriver", + "VimbaDriver", "start_acquiring_driver_and_ensure_status", "DetectorState", ] diff --git a/src/ophyd_async/epics/areadetector/kinetix.py b/src/ophyd_async/epics/areadetector/kinetix.py new file mode 100644 index 0000000000..36ec479a49 --- /dev/null +++ b/src/ophyd_async/epics/areadetector/kinetix.py @@ -0,0 +1,48 @@ +from bluesky.protocols import HasHints, Hints + +from ophyd_async.core import DirectoryProvider, StandardDetector +from ophyd_async.epics.areadetector.controllers.kinetix_controller import ( + KinetixController, +) +from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider +from ophyd_async.epics.areadetector.drivers.kinetix_driver import KinetixDriver +from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF + + +class KinetixDetector(StandardDetector, HasHints): + """ + Ophyd-async implementation of an ADKinetix Detector. + https://github.com/NSLS-II/ADKinetix + """ + + _controller: KinetixController + _writer: HDFWriter + + def __init__( + self, + name: str, + directory_provider: DirectoryProvider, + driver: KinetixDriver, + hdf: NDFileHDF, + **scalar_sigs: str, + ): + # Must be child of Detector to pick up connect() + self.drv = driver + self.hdf = hdf + + super().__init__( + KinetixController(self.drv), + HDFWriter( + self.hdf, + directory_provider, + lambda: self.name, + ADBaseShapeProvider(self.drv), + **scalar_sigs, + ), + config_sigs=(self.drv.acquire_time, self.drv.acquire), + name=name, + ) + + @property + def hints(self) -> Hints: + return self._writer.hints diff --git a/src/ophyd_async/epics/areadetector/vimba.py b/src/ophyd_async/epics/areadetector/vimba.py new file mode 100644 index 0000000000..5e764b5b20 --- /dev/null +++ b/src/ophyd_async/epics/areadetector/vimba.py @@ -0,0 +1,45 @@ +from bluesky.protocols import HasHints, Hints + +from ophyd_async.core import DirectoryProvider, StandardDetector +from ophyd_async.epics.areadetector.controllers.vimba_controller import VimbaController +from ophyd_async.epics.areadetector.drivers import ADBaseShapeProvider +from ophyd_async.epics.areadetector.drivers.vimba_driver import VimbaDriver +from ophyd_async.epics.areadetector.writers import HDFWriter, NDFileHDF + + +class VimbaDetector(StandardDetector, HasHints): + """ + Ophyd-async implementation of an ADVimba Detector. + """ + + _controller: VimbaController + _writer: HDFWriter + + def __init__( + self, + name: str, + directory_provider: DirectoryProvider, + driver: VimbaDriver, + hdf: NDFileHDF, + **scalar_sigs: str, + ): + # Must be child of Detector to pick up connect() + self.drv = driver + self.hdf = hdf + + super().__init__( + VimbaController(self.drv), + HDFWriter( + self.hdf, + directory_provider, + lambda: self.name, + ADBaseShapeProvider(self.drv), + **scalar_sigs, + ), + config_sigs=(self.drv.acquire_time, self.drv.acquire), + name=name, + ) + + @property + def hints(self) -> Hints: + return self._writer.hints diff --git a/tests/epics/areadetector/test_kinetix.py b/tests/epics/areadetector/test_kinetix.py new file mode 100644 index 0000000000..bb79a0b5f8 --- /dev/null +++ b/tests/epics/areadetector/test_kinetix.py @@ -0,0 +1,150 @@ +import pytest +from bluesky.run_engine import RunEngine + +from ophyd_async.core import ( + DetectorTrigger, + DeviceCollector, + DirectoryProvider, + set_sim_value, +) +from ophyd_async.epics.areadetector.drivers.kinetix_driver import KinetixDriver +from ophyd_async.epics.areadetector.kinetix import KinetixDetector +from ophyd_async.epics.areadetector.writers.nd_file_hdf import NDFileHDF + + +@pytest.fixture +async def adkinetix_driver(RE: RunEngine) -> KinetixDriver: + async with DeviceCollector(sim=True): + driver = KinetixDriver("DRV:") + + return driver + + +@pytest.fixture +async def hdf(RE: RunEngine) -> NDFileHDF: + async with DeviceCollector(sim=True): + hdf = NDFileHDF("HDF:") + + return hdf + + +@pytest.fixture +async def adkinetix( + RE: RunEngine, + static_directory_provider: DirectoryProvider, + adkinetix_driver: KinetixDriver, + hdf: NDFileHDF, +) -> KinetixDetector: + async with DeviceCollector(sim=True): + adkinetix = KinetixDetector( + "adkinetix", + static_directory_provider, + driver=adkinetix_driver, + hdf=hdf, + ) + + return adkinetix + + +async def test_get_deadtime( + adkinetix: KinetixDetector, +): + # Currently Kinetix driver doesn't support getting deadtime. + assert adkinetix._controller.get_deadtime(0) == 0.001 + + +async def test_trigger_modes(adkinetix: KinetixDetector): + set_sim_value(adkinetix.drv.trigger_mode, "Internal") + + async def setup_trigger_mode(trig_mode: DetectorTrigger): + await adkinetix.controller.arm(num=1, trigger=trig_mode) + # Prevent timeouts + set_sim_value(adkinetix.drv.acquire, True) + + # Default TriggerSource + assert (await adkinetix.drv.trigger_mode.get_value()) == "Internal" + + await setup_trigger_mode(DetectorTrigger.edge_trigger) + assert (await adkinetix.drv.trigger_mode.get_value()) == "Rising Edge" + + await setup_trigger_mode(DetectorTrigger.constant_gate) + assert (await adkinetix.drv.trigger_mode.get_value()) == "Exp. Gate" + + await setup_trigger_mode(DetectorTrigger.internal) + assert (await adkinetix.drv.trigger_mode.get_value()) == "Internal" + + await setup_trigger_mode(DetectorTrigger.variable_gate) + assert (await adkinetix.drv.trigger_mode.get_value()) == "Exp. Gate" + + +async def test_hints_from_hdf_writer(adkinetix: KinetixDetector): + assert adkinetix.hints == {"fields": ["adkinetix"]} + + +async def test_can_read(adkinetix: KinetixDetector): + # Standard detector can be used as Readable + assert (await adkinetix.read()) == {} + + +async def test_decribe_describes_writer_dataset(adkinetix: KinetixDetector): + set_sim_value(adkinetix._writer.hdf.file_path_exists, True) + set_sim_value(adkinetix._writer.hdf.capture, True) + + assert await adkinetix.describe() == {} + await adkinetix.stage() + assert await adkinetix.describe() == { + "adkinetix": { + "source": "soft://adkinetix-hdf-full_file_name", + "shape": (0, 0), + "dtype": "array", + "external": "STREAM:", + } + } + + +async def test_can_collect( + adkinetix: KinetixDetector, static_directory_provider: DirectoryProvider +): + directory_info = static_directory_provider() + full_file_name = directory_info.root / directory_info.resource_dir / "foo.h5" + set_sim_value(adkinetix.hdf.full_file_name, str(full_file_name)) + set_sim_value(adkinetix._writer.hdf.file_path_exists, True) + set_sim_value(adkinetix._writer.hdf.capture, True) + await adkinetix.stage() + docs = [(name, doc) async for name, doc in adkinetix.collect_asset_docs(1)] + assert len(docs) == 2 + assert docs[0][0] == "stream_resource" + stream_resource = docs[0][1] + sr_uid = stream_resource["uid"] + assert stream_resource["data_key"] == "adkinetix" + assert stream_resource["spec"] == "AD_HDF5_SWMR_SLICE" + assert stream_resource["root"] == str(directory_info.root) + assert stream_resource["resource_path"] == str( + directory_info.resource_dir / "foo.h5" + ) + assert stream_resource["path_semantics"] == "posix" + assert stream_resource["resource_kwargs"] == { + "path": "/entry/data/data", + "multiplier": 1, + "timestamps": "/entry/instrument/NDAttributes/NDArrayTimeStamp", + } + assert docs[1][0] == "stream_datum" + stream_datum = docs[1][1] + assert stream_datum["stream_resource"] == sr_uid + assert stream_datum["seq_nums"] == {"start": 0, "stop": 0} + assert stream_datum["indices"] == {"start": 0, "stop": 1} + + +async def test_can_decribe_collect(adkinetix: KinetixDetector): + set_sim_value(adkinetix._writer.hdf.file_path_exists, True) + set_sim_value(adkinetix._writer.hdf.capture, True) + assert (await adkinetix.describe_collect()) == {} + await adkinetix.stage() + assert (await adkinetix.describe_collect()) == { + "adkinetix": { + "source": "soft://adkinetix-hdf-full_file_name", + "shape": (0, 0), + "dtype": "array", + "external": "STREAM:", + } + } diff --git a/tests/epics/areadetector/test_vimba.py b/tests/epics/areadetector/test_vimba.py new file mode 100644 index 0000000000..002ddb1b7e --- /dev/null +++ b/tests/epics/areadetector/test_vimba.py @@ -0,0 +1,162 @@ +import pytest +from bluesky.run_engine import RunEngine + +from ophyd_async.core import ( + DetectorTrigger, + DeviceCollector, + DirectoryProvider, + set_sim_value, +) +from ophyd_async.epics.areadetector.drivers.vimba_driver import VimbaDriver +from ophyd_async.epics.areadetector.vimba import VimbaDetector +from ophyd_async.epics.areadetector.writers.nd_file_hdf import NDFileHDF + + +@pytest.fixture +async def advimba_driver(RE: RunEngine) -> VimbaDriver: + async with DeviceCollector(sim=True): + driver = VimbaDriver("DRV:") + + return driver + + +@pytest.fixture +async def hdf(RE: RunEngine) -> NDFileHDF: + async with DeviceCollector(sim=True): + hdf = NDFileHDF("HDF:") + + return hdf + + +@pytest.fixture +async def advimba( + RE: RunEngine, + static_directory_provider: DirectoryProvider, + advimba_driver: VimbaDriver, + hdf: NDFileHDF, +) -> VimbaDetector: + async with DeviceCollector(sim=True): + advimba = VimbaDetector( + "advimba", + static_directory_provider, + driver=advimba_driver, + hdf=hdf, + ) + + return advimba + + +async def test_get_deadtime( + advimba: VimbaDetector, +): + # Currently Vimba controller doesn't support getting deadtime. + assert advimba._controller.get_deadtime(0) == 0.001 + + +async def test_arming_trig_modes(advimba: VimbaDetector): + set_sim_value(advimba.drv.trig_source, "Freerun") + set_sim_value(advimba.drv.trigger_mode, "Off") + set_sim_value(advimba.drv.expose_mode, "Timed") + + async def setup_trigger_mode(trig_mode: DetectorTrigger): + await advimba.controller.arm(num=1, trigger=trig_mode) + # Prevent timeouts + set_sim_value(advimba.drv.acquire, True) + + # Default TriggerSource + assert (await advimba.drv.trig_source.get_value()) == "Freerun" + assert (await advimba.drv.trigger_mode.get_value()) == "Off" + assert (await advimba.drv.expose_mode.get_value()) == "Timed" + + await setup_trigger_mode(DetectorTrigger.edge_trigger) + assert (await advimba.drv.trig_source.get_value()) == "Line1" + assert (await advimba.drv.trigger_mode.get_value()) == "On" + assert (await advimba.drv.expose_mode.get_value()) == "Timed" + + await setup_trigger_mode(DetectorTrigger.constant_gate) + assert (await advimba.drv.trig_source.get_value()) == "Line1" + assert (await advimba.drv.trigger_mode.get_value()) == "On" + assert (await advimba.drv.expose_mode.get_value()) == "TriggerWidth" + + await setup_trigger_mode(DetectorTrigger.internal) + assert (await advimba.drv.trig_source.get_value()) == "Freerun" + assert (await advimba.drv.trigger_mode.get_value()) == "Off" + assert (await advimba.drv.expose_mode.get_value()) == "Timed" + + await setup_trigger_mode(DetectorTrigger.variable_gate) + assert (await advimba.drv.trig_source.get_value()) == "Line1" + assert (await advimba.drv.trigger_mode.get_value()) == "On" + assert (await advimba.drv.expose_mode.get_value()) == "TriggerWidth" + + +async def test_hints_from_hdf_writer(advimba: VimbaDetector): + assert advimba.hints == {"fields": ["advimba"]} + + +async def test_can_read(advimba: VimbaDetector): + # Standard detector can be used as Readable + assert (await advimba.read()) == {} + + +async def test_decribe_describes_writer_dataset(advimba: VimbaDetector): + set_sim_value(advimba._writer.hdf.file_path_exists, True) + set_sim_value(advimba._writer.hdf.capture, True) + + assert await advimba.describe() == {} + await advimba.stage() + assert await advimba.describe() == { + "advimba": { + "source": "soft://advimba-hdf-full_file_name", + "shape": (0, 0), + "dtype": "array", + "external": "STREAM:", + } + } + + +async def test_can_collect( + advimba: VimbaDetector, static_directory_provider: DirectoryProvider +): + directory_info = static_directory_provider() + full_file_name = directory_info.root / directory_info.resource_dir / "foo.h5" + set_sim_value(advimba.hdf.full_file_name, str(full_file_name)) + set_sim_value(advimba._writer.hdf.file_path_exists, True) + set_sim_value(advimba._writer.hdf.capture, True) + await advimba.stage() + docs = [(name, doc) async for name, doc in advimba.collect_asset_docs(1)] + assert len(docs) == 2 + assert docs[0][0] == "stream_resource" + stream_resource = docs[0][1] + sr_uid = stream_resource["uid"] + assert stream_resource["data_key"] == "advimba" + assert stream_resource["spec"] == "AD_HDF5_SWMR_SLICE" + assert stream_resource["root"] == str(directory_info.root) + assert stream_resource["resource_path"] == str( + directory_info.resource_dir / "foo.h5" + ) + assert stream_resource["path_semantics"] == "posix" + assert stream_resource["resource_kwargs"] == { + "path": "/entry/data/data", + "multiplier": 1, + "timestamps": "/entry/instrument/NDAttributes/NDArrayTimeStamp", + } + assert docs[1][0] == "stream_datum" + stream_datum = docs[1][1] + assert stream_datum["stream_resource"] == sr_uid + assert stream_datum["seq_nums"] == {"start": 0, "stop": 0} + assert stream_datum["indices"] == {"start": 0, "stop": 1} + + +async def test_can_decribe_collect(advimba: VimbaDetector): + set_sim_value(advimba._writer.hdf.file_path_exists, True) + set_sim_value(advimba._writer.hdf.capture, True) + assert (await advimba.describe_collect()) == {} + await advimba.stage() + assert (await advimba.describe_collect()) == { + "advimba": { + "source": "soft://advimba-hdf-full_file_name", + "shape": (0, 0), + "dtype": "array", + "external": "STREAM:", + } + } From 658b6d458d494ea10103340b94c70fb6a6ab4ad6 Mon Sep 17 00:00:00 2001 From: Jakub Wlodek Date: Fri, 26 Apr 2024 16:42:02 -0400 Subject: [PATCH 13/13] Remove configurable detector states, use default --- .../areadetector/controllers/kinetix_controller.py | 10 ++-------- .../epics/areadetector/controllers/vimba_controller.py | 10 ++-------- .../epics/areadetector/drivers/kinetix_driver.py | 4 ++-- .../epics/areadetector/drivers/vimba_driver.py | 4 ++-- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py index 64a5ff4ffc..288b8a156c 100644 --- a/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/kinetix_controller.py @@ -1,10 +1,8 @@ import asyncio -from typing import Optional, Set +from typing import Optional from ophyd_async.core import AsyncStatus, DetectorControl, DetectorTrigger from ophyd_async.epics.areadetector.drivers.ad_base import ( - DEFAULT_GOOD_STATES, - DetectorState, start_acquiring_driver_and_ensure_status, ) @@ -23,10 +21,8 @@ class KinetixController(DetectorControl): def __init__( self, driver: KinetixDriver, - good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), ) -> None: self._drv = driver - self.good_states = good_states def get_deadtime(self, exposure: float) -> float: return 0.001 @@ -47,9 +43,7 @@ async def arm( DetectorTrigger.constant_gate, ]: await self._drv.acquire_time.set(exposure) - return await start_acquiring_driver_and_ensure_status( - self._drv, good_states=self.good_states - ) + return await start_acquiring_driver_and_ensure_status(self._drv) async def disarm(self): await stop_busy_record(self._drv.acquire, False, timeout=1) diff --git a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py index 324b1a5c6c..82fe420281 100644 --- a/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py +++ b/src/ophyd_async/epics/areadetector/controllers/vimba_controller.py @@ -1,10 +1,8 @@ import asyncio -from typing import Optional, Set +from typing import Optional from ophyd_async.core import AsyncStatus, DetectorControl, DetectorTrigger from ophyd_async.epics.areadetector.drivers.ad_base import ( - DEFAULT_GOOD_STATES, - DetectorState, start_acquiring_driver_and_ensure_status, ) @@ -35,10 +33,8 @@ class VimbaController(DetectorControl): def __init__( self, driver: VimbaDriver, - good_states: Set[DetectorState] = set(DEFAULT_GOOD_STATES), ) -> None: self._drv = driver - self.good_states = good_states def get_deadtime(self, exposure: float) -> float: return 0.001 @@ -64,9 +60,7 @@ async def arm( self._drv.trig_source.set(VimbaTriggerSource.line1) else: self._drv.trig_source.set(VimbaTriggerSource.freerun) - return await start_acquiring_driver_and_ensure_status( - self._drv, good_states=self.good_states - ) + return await start_acquiring_driver_and_ensure_status(self._drv) async def disarm(self): await stop_busy_record(self._drv.acquire, False, timeout=1) diff --git a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py index 1a2b866daf..ab0bd01af4 100644 --- a/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/kinetix_driver.py @@ -17,8 +17,8 @@ class KinetixReadoutMode(str, Enum): class KinetixDriver(ADBase): - def __init__(self, prefix: str) -> None: - super().__init__(prefix) + def __init__(self, prefix: str, name: str = "") -> None: # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") self.trigger_mode = ad_rw(KinetixTriggerMode, prefix + "TriggerMode") self.mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") + super().__init__(prefix, name) diff --git a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py index 8112b21dd9..4cec75dd2e 100644 --- a/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py +++ b/src/ophyd_async/epics/areadetector/drivers/vimba_driver.py @@ -46,7 +46,7 @@ class VimbaExposeOutMode(str, Enum): class VimbaDriver(ADBase): - def __init__(self, prefix: str) -> None: + def __init__(self, prefix: str, name: str = "") -> None: # self.pixel_format = ad_rw(PixelFormat, prefix + "PixelFormat") self.convert_format = ad_rw( VimbaConvertFormat, prefix + "ConvertPixelFormat" @@ -55,4 +55,4 @@ def __init__(self, prefix: str) -> None: self.trigger_mode = ad_rw(VimbaOnOff, prefix + "TriggerMode") self.overlap = ad_rw(VimbaOverlap, prefix + "TriggerOverlap") self.expose_mode = ad_rw(VimbaExposeOutMode, prefix + "ExposureMode") - super().__init__(prefix) + super().__init__(prefix, name)