-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding driver and controller classes for Kinetix and Vimba cameras #216
Changes from 5 commits
5f3df2c
45d4d2d
7356221
8158bca
439469b
5c351ce
534d5e1
b6ad051
d7f7980
ad9e817
9b7aebf
450aa0a
3a79f7c
658b6d4
e808b0e
a05d898
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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._drv = driver | ||
self.good_states = good_states | ||
DiamondJoseph marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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._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._drv.acquire_time.set(exposure) | ||
return await start_acquiring_driver_and_ensure_status( | ||
self._drv, good_states=self.good_states | ||
) | ||
DiamondJoseph marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
async def disarm(self): | ||
await stop_busy_record(self._drv.acquire, False, timeout=1) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
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, | ||
VimbaExposeOutMode, | ||
VimbaOnOff, | ||
VimbaTriggerSource, | ||
) | ||
from ..utils import ImageMode, stop_busy_record | ||
|
||
TRIGGER_MODE = { | ||
DetectorTrigger.internal: VimbaOnOff.off, | ||
DetectorTrigger.constant_gate: VimbaOnOff.on, | ||
DetectorTrigger.variable_gate: VimbaOnOff.on, | ||
DetectorTrigger.edge_trigger: VimbaOnOff.on, | ||
} | ||
|
||
EXPOSE_OUT_MODE = { | ||
DetectorTrigger.internal: VimbaExposeOutMode.timed, | ||
DetectorTrigger.constant_gate: VimbaExposeOutMode.trigger_width, | ||
DetectorTrigger.variable_gate: VimbaExposeOutMode.trigger_width, | ||
DetectorTrigger.edge_trigger: VimbaExposeOutMode.timed, | ||
} | ||
|
||
|
||
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 | ||
|
||
async def arm( | ||
self, | ||
num: int, | ||
trigger: DetectorTrigger = DetectorTrigger.internal, | ||
exposure: Optional[float] = None, | ||
) -> AsyncStatus: | ||
await asyncio.gather( | ||
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._drv.acquire_time.set(exposure) | ||
if trigger != DetectorTrigger.internal: | ||
self._drv.trigger_source.set(VimbaTriggerSource.line1) | ||
return await start_acquiring_driver_and_ensure_status( | ||
self._drv, good_states=self.good_states | ||
) | ||
|
||
async def disarm(self): | ||
await stop_busy_record(self._drv.acquire, False, timeout=1) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from enum import Enum | ||
|
||
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.mode = ad_rw(KinetixReadoutMode, prefix + "ReadoutPortIdx") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For areaDetector I've tended to name the attribute after the PV suffix, is there a reason to name this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had I didn't want to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you point me to the corresponding record in the template please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could these enum strings be pushed down to the template? Then having a I've tried not to add any translation in the "bucket of PVs" layer, and put the logic in the Either way, I think |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
from enum import Enum | ||
|
||
from ..utils import ad_rw | ||
from .ad_base import ADBase | ||
|
||
|
||
class VimbaPixelFormat(str, Enum): | ||
internal = "Mono8" | ||
ext_enable = "Mono12" | ||
ext_trigger = "Ext. Trigger" | ||
mult_trigger = "Mult. Trigger" | ||
alignment = "Alignment" | ||
|
||
|
||
class VimbaConvertFormat(str, Enum): | ||
none = "None" | ||
mono8 = "Mono8" | ||
mono16 = "Mono16" | ||
rgb8 = "RGB8" | ||
rgb16 = "RGB16" | ||
|
||
|
||
class VimbaTriggerSource(str, Enum): | ||
freerun = "Freerun" | ||
line1 = "Line1" | ||
line2 = "Line2" | ||
fixed_rate = "FixedRate" | ||
software = "Software" | ||
action0 = "Action0" | ||
action1 = "Action1" | ||
DiamondJoseph marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class VimbaOverlap(str, Enum): | ||
off = "Off" | ||
prev_frame = "PreviousFrame" | ||
|
||
|
||
class VimbaOnOff(str, Enum): | ||
on = "On" | ||
off = "Off" | ||
|
||
|
||
class VimbaExposeOutMode(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( | ||
VimbaConvertFormat, prefix + "ConvertPixelFormat" | ||
) # Pixel format of data outputted to AD | ||
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know enough about AD DetectorStates, so I ignored this param for the Pilatus and Aravis- do we think it's important to expose? I can go back and adjust those detectors before there's any pickup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no reason to have to override the good states when instantiating the Controller, I think we know up front what are the good states for a given detector.
@jwlodek any reason you added it as an init arg? If not then I recommend we:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only reason was that it was done this way for the Pilatus when I was writing these classes.