From 12f25e4c2172b0a88d9ce5ff7c4b1bfe92027216 Mon Sep 17 00:00:00 2001 From: Jennefer Maldonado Date: Thu, 26 Mar 2026 17:16:09 -0400 Subject: [PATCH 01/12] modified ophyd async class for merlin --- src/cditools/merlin_async.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index 1172184..a1e8a89 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -15,6 +15,8 @@ SignalR, SignalRW, StrictEnum, + SubsetEnum, + soft_signal_rw ) from ophyd_async.epics.adcore import ( ADArmLogic, @@ -22,6 +24,7 @@ ADWriterType, AreaDetector, NDPluginBaseIO, + ADBaseDataType, prepare_exposures, trigger_info_from_num_images, ) @@ -36,7 +39,7 @@ _MIN_DEAD_TIME = 0.002 -class MerlinTriggerMode(StrictEnum): +class MerlinTriggerMode(SubsetEnum): """Trigger modes for the Merlin detector""" INTERNAL = "Internal" @@ -46,6 +49,15 @@ class MerlinTriggerMode(StrictEnum): TRIGGER_BOTH_RISING = "Trigger both rising" SOFTWARE = "Software" +# class MerlinTriggerModeRBV(SubsetEnum): +# """Trigger modes for the Merlin detector""" + +# INTERNAL = "Internal" +# TRIGGER_ENABLE = "Trigger Enable" +# TRIGGER_START_RISING = "Trigger start rising" +# TRIGGER_START_FALLING = "Trigger start falling" +# TRIGGER_BOTH_RISING = "Trigger both rising " +# SOFTWARE = "Software" class MerlinTriggerModeRBV(StrictEnum): """Trigger modes for the Merlin detector""" @@ -67,6 +79,12 @@ class MerlinDriverIO(ADBaseIO): trigger_mode: A[SignalRW[MerlinTriggerMode], PvSuffix.rbv("TriggerMode")] + # Since ADMerlin doesn't set the data type readback correctly, but is always uint16, + # just turn it into a static soft signal + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, name=name) + self.data_type = soft_signal_rw(ADBaseDataType, ADBaseDataType.UINT16, name="data_type") + # The deadtime of an Merlin controller varies depending on the exact model of camera. # Ideally we would maximize performance by dynamically retrieving the deadtime at @@ -130,3 +148,4 @@ def __init__( config_sigs=config_sigs, name=name, ) + From d4ebb73f99aa692c3044ecbb05eb071043700bf2 Mon Sep 17 00:00:00 2001 From: jennmald Date: Wed, 3 Jun 2026 08:39:53 -0400 Subject: [PATCH 02/12] changes from Dan's review --- src/cditools/merlin_async.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index a1e8a89..edaec14 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -49,15 +49,6 @@ class MerlinTriggerMode(SubsetEnum): TRIGGER_BOTH_RISING = "Trigger both rising" SOFTWARE = "Software" -# class MerlinTriggerModeRBV(SubsetEnum): -# """Trigger modes for the Merlin detector""" - -# INTERNAL = "Internal" -# TRIGGER_ENABLE = "Trigger Enable" -# TRIGGER_START_RISING = "Trigger start rising" -# TRIGGER_START_FALLING = "Trigger start falling" -# TRIGGER_BOTH_RISING = "Trigger both rising " -# SOFTWARE = "Software" class MerlinTriggerModeRBV(StrictEnum): """Trigger modes for the Merlin detector""" @@ -79,7 +70,7 @@ class MerlinDriverIO(ADBaseIO): trigger_mode: A[SignalRW[MerlinTriggerMode], PvSuffix.rbv("TriggerMode")] - # Since ADMerlin doesn't set the data type readback correctly, but is always uint16, + # Since ADMerlin doesn't set the data type readback correctly, but is always uint16, # just turn it into a static soft signal def __init__(self, prefix: str, name: str = ""): super().__init__(prefix, name=name) @@ -103,7 +94,6 @@ async def prepare_internal(self, num: int, livetime: float, deadtime: float): await prepare_exposures(self.driver, num, livetime, deadtime) async def prepare_edge(self, num: int, livetime: float): - # Is this the right trigger mode? await self.driver.trigger_mode.set(MerlinTriggerMode.TRIGGER_START_RISING) await prepare_exposures(self.driver, num, livetime) @@ -148,4 +138,3 @@ def __init__( config_sigs=config_sigs, name=name, ) - From ed4b7054a2d3dcbe715a9323e85b9adc222e262b Mon Sep 17 00:00:00 2001 From: jennmald Date: Wed, 3 Jun 2026 08:42:49 -0400 Subject: [PATCH 03/12] pre commit --- src/cditools/merlin_async.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index edaec14..1d27bed 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -15,16 +15,15 @@ SignalR, SignalRW, StrictEnum, - SubsetEnum, - soft_signal_rw + soft_signal_rw, ) from ophyd_async.epics.adcore import ( ADArmLogic, + ADBaseDataType, ADBaseIO, ADWriterType, AreaDetector, NDPluginBaseIO, - ADBaseDataType, prepare_exposures, trigger_info_from_num_images, ) @@ -74,7 +73,9 @@ class MerlinDriverIO(ADBaseIO): # just turn it into a static soft signal def __init__(self, prefix: str, name: str = ""): super().__init__(prefix, name=name) - self.data_type = soft_signal_rw(ADBaseDataType, ADBaseDataType.UINT16, name="data_type") + self.data_type = soft_signal_rw( + ADBaseDataType, ADBaseDataType.UINT16, name="data_type" + ) # The deadtime of an Merlin controller varies depending on the exact model of camera. From 3183689975e91bb277674d62210363d0f58f6a56 Mon Sep 17 00:00:00 2001 From: jennmald Date: Wed, 3 Jun 2026 10:22:07 -0400 Subject: [PATCH 04/12] fix trigger modes --- src/cditools/merlin_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index 1d27bed..21ec44f 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -45,7 +45,7 @@ class MerlinTriggerMode(SubsetEnum): TRIGGER_ENABLE = "Trigger Enable" TRIGGER_START_RISING = "Trigger start rising" TRIGGER_START_FALLING = "Trigger start falling" - TRIGGER_BOTH_RISING = "Trigger both rising" + TRIGGER_BOTH_RISING = "Trigger both rising " SOFTWARE = "Software" From 78f1e2558954df7550079e569615f2699157bfac Mon Sep 17 00:00:00 2001 From: jennmald Date: Wed, 3 Jun 2026 10:31:01 -0400 Subject: [PATCH 05/12] fix ADArmLogic --- src/cditools/merlin_async.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index 21ec44f..65a9256 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -10,6 +10,7 @@ from ophyd_async.core import ( DetectorTriggerLogic, + DetectorArmLogic, PathProvider, SignalDict, SignalR, @@ -18,7 +19,6 @@ soft_signal_rw, ) from ophyd_async.epics.adcore import ( - ADArmLogic, ADBaseDataType, ADBaseIO, ADWriterType, @@ -130,7 +130,7 @@ def __init__( super().__init__( prefix=prefix, driver=driver, - arm_logic=ADArmLogic(driver), + arm_logic=DetectorArmLogic(driver), trigger_logic=MerlinTriggerLogic(driver), path_provider=path_provider, writer_type=writer_type, From 78f5ed3f9a10641bfaf49b08a429b478703b86ce Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 25 Jun 2026 20:11:25 -0400 Subject: [PATCH 06/12] MNT: fix missing imports --- src/cditools/merlin_async.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index 65a9256..1c933e9 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -9,13 +9,14 @@ from typing import Annotated as A from ophyd_async.core import ( + DetectorAcquireLogic, DetectorTriggerLogic, - DetectorArmLogic, PathProvider, SignalDict, SignalR, SignalRW, StrictEnum, + SubsetEnum, soft_signal_rw, ) from ophyd_async.epics.adcore import ( From b051deaef9eb5381550fd2426ed5553ed5e1c93c Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 25 Jun 2026 20:20:38 -0400 Subject: [PATCH 07/12] MNT: start to fix merlin for ophyd-async API changes --- src/cditools/merlin_async.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index 1c933e9..5d1ecc7 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -11,7 +11,6 @@ from ophyd_async.core import ( DetectorAcquireLogic, DetectorTriggerLogic, - PathProvider, SignalDict, SignalR, SignalRW, @@ -22,7 +21,7 @@ from ophyd_async.epics.adcore import ( ADBaseDataType, ADBaseIO, - ADWriterType, + ADWriterFactory, AreaDetector, NDPluginBaseIO, prepare_exposures, @@ -119,23 +118,19 @@ class MerlinDetector(AreaDetector[MerlinDriverIO]): def __init__( self, prefix: str, - path_provider: PathProvider | None = None, + *writer_factories: ADWriterFactory, driver_suffix="cam1:", - writer_type: ADWriterType | None = ADWriterType.HDF, - writer_suffix: str | None = None, plugins: dict[str, NDPluginBaseIO] | None = None, config_sigs: Sequence[SignalR] = (), name: str = "", ) -> None: driver = MerlinDriverIO(prefix + driver_suffix) super().__init__( - prefix=prefix, - driver=driver, - arm_logic=DetectorArmLogic(driver), + driver, + prefix, + *writer_factories, + acquire_logic=DetectorAcquireLogic(driver), trigger_logic=MerlinTriggerLogic(driver), - path_provider=path_provider, - writer_type=writer_type, - writer_suffix=writer_suffix, plugins=plugins, config_sigs=config_sigs, name=name, From f44425c1af1a90478d32a8b7bac5ee6116641a81 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 25 Jun 2026 20:27:38 -0400 Subject: [PATCH 08/12] FIX: try to use stack AD Acquire logic with merlin --- src/cditools/merlin_async.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index 5d1ecc7..17ac1dd 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -9,7 +9,6 @@ from typing import Annotated as A from ophyd_async.core import ( - DetectorAcquireLogic, DetectorTriggerLogic, SignalDict, SignalR, @@ -22,6 +21,7 @@ ADBaseDataType, ADBaseIO, ADWriterFactory, + ADAcquireLogic, AreaDetector, NDPluginBaseIO, prepare_exposures, @@ -129,7 +129,7 @@ def __init__( driver, prefix, *writer_factories, - acquire_logic=DetectorAcquireLogic(driver), + acquire_logic=ADAcquireLogic(driver), trigger_logic=MerlinTriggerLogic(driver), plugins=plugins, config_sigs=config_sigs, From 2b1cf4390d6b1510b413c28baac3c750dd979302 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 25 Jun 2026 20:32:27 -0400 Subject: [PATCH 09/12] FIX: try a strict enum --- src/cditools/merlin_async.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index 17ac1dd..82e26f6 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -14,7 +14,6 @@ SignalR, SignalRW, StrictEnum, - SubsetEnum, soft_signal_rw, ) from ophyd_async.epics.adcore import ( @@ -38,7 +37,7 @@ _MIN_DEAD_TIME = 0.002 -class MerlinTriggerMode(SubsetEnum): +class MerlinTriggerMode(StrictEnum): """Trigger modes for the Merlin detector""" INTERNAL = "Internal" From 07dbf9a4b5d6f459cc53f71be63b2549752b43a0 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 25 Jun 2026 20:33:43 -0400 Subject: [PATCH 10/12] FIX: and remove the space at the end of the enum --- src/cditools/merlin_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index 82e26f6..b0b61cf 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -55,7 +55,7 @@ class MerlinTriggerModeRBV(StrictEnum): TRIGGER_ENABLE = "Trigger Enable" TRIGGER_START_RISING = "Trigger start rising" TRIGGER_START_FALLING = "Trigger start falling" - TRIGGER_BOTH_RISING = "Trigger both rising " + TRIGGER_BOTH_RISING = "Trigger both rising" SOFTWARE = "Software" From a3e6656dae678729948bc125b3adc17878de39de Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 25 Jun 2026 20:36:05 -0400 Subject: [PATCH 11/12] FIX: and the other trailing whitespace --- src/cditools/merlin_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index b0b61cf..54b4aa4 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -44,7 +44,7 @@ class MerlinTriggerMode(StrictEnum): TRIGGER_ENABLE = "Trigger Enable" TRIGGER_START_RISING = "Trigger start rising" TRIGGER_START_FALLING = "Trigger start falling" - TRIGGER_BOTH_RISING = "Trigger both rising " + TRIGGER_BOTH_RISING = "Trigger both rising" SOFTWARE = "Software" From 71f5eae579fd6338b92c0351f48d766b33174ca1 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 25 Jun 2026 20:44:56 -0400 Subject: [PATCH 12/12] STY: fix import order --- src/cditools/merlin_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cditools/merlin_async.py b/src/cditools/merlin_async.py index 54b4aa4..e3dce09 100644 --- a/src/cditools/merlin_async.py +++ b/src/cditools/merlin_async.py @@ -17,10 +17,10 @@ soft_signal_rw, ) from ophyd_async.epics.adcore import ( + ADAcquireLogic, ADBaseDataType, ADBaseIO, ADWriterFactory, - ADAcquireLogic, AreaDetector, NDPluginBaseIO, prepare_exposures,