-
Notifications
You must be signed in to change notification settings - Fork 28
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
Port andor new ad format #724
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
ea285a7
ignore order of enum elements
DiamondJoseph c5ee3df
Name signal to match expected PV name
DiamondJoseph 1137327
Merge branch 'port-andor' of https://github.com/bluesky/ophyd-async i…
jwlodek 4ab6a6e
Modify changes to integrate adandor2 to work with new AD structure
jwlodek 18fb1b7
Merge branch 'main' of https://github.com/bluesky/ophyd-async into po…
jwlodek 05d0f3a
Fix test with unawaited coro
jwlodek dca8fef
Merge branch 'main' of https://github.com/bluesky/ophyd-async into po…
jwlodek 35e2a77
Potentially handle different enum values for Andor2
jwlodek 3f8f242
Use custom data type subset enum class
jwlodek dae004a
Fix incorrect dtype_numpy in tests
jwlodek 4ab9296
Make AD Image mode a subset enum, use that for andor
jwlodek 8787c16
Run linter
jwlodek File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from ._andor import Andor2Detector | ||
from ._andor_controller import Andor2Controller | ||
from ._andor_io import Andor2DriverIO | ||
|
||
__all__ = [ | ||
"Andor2Detector", | ||
"Andor2Controller", | ||
"Andor2DriverIO", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from collections.abc import Sequence | ||
|
||
from ophyd_async.core import PathProvider | ||
from ophyd_async.core._signal import SignalR | ||
from ophyd_async.epics import adcore | ||
|
||
from ._andor_controller import Andor2Controller | ||
from ._andor_io import Andor2DriverIO | ||
|
||
|
||
class Andor2Detector(adcore.AreaDetector[Andor2Controller]): | ||
""" | ||
Andor 2 area detector device (CCD detector 56fps with full chip readout). | ||
Andor model:DU897_BV. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
prefix: str, | ||
path_provider: PathProvider, | ||
drv_suffix="cam1:", | ||
writer_cls: type[adcore.ADWriter] = adcore.ADHDFWriter, | ||
fileio_suffix: str | None = None, | ||
name: str = "", | ||
config_sigs: Sequence[SignalR] = (), | ||
plugins: dict[str, adcore.NDPluginBaseIO] | None = None, | ||
): | ||
driver = Andor2DriverIO(prefix + drv_suffix) | ||
controller = Andor2Controller(driver) | ||
|
||
writer = writer_cls.with_io( | ||
prefix, | ||
path_provider, | ||
dataset_source=driver, | ||
fileio_suffix=fileio_suffix, | ||
plugins=plugins, | ||
) | ||
|
||
super().__init__( | ||
controller=controller, | ||
writer=writer, | ||
plugins=plugins, | ||
name=name, | ||
config_sigs=config_sigs, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import asyncio | ||
|
||
from ophyd_async.core import ( | ||
DetectorTrigger, | ||
TriggerInfo, | ||
) | ||
from ophyd_async.epics import adcore | ||
|
||
from ._andor_io import Andor2DriverIO, Andor2TriggerMode | ||
|
||
_MIN_DEAD_TIME = 0.1 | ||
_MAX_NUM_IMAGE = 999_999 | ||
|
||
|
||
class Andor2Controller(adcore.ADBaseController[Andor2DriverIO]): | ||
def __init__( | ||
self, | ||
driver: Andor2DriverIO, | ||
good_states: frozenset[adcore.DetectorState] = adcore.DEFAULT_GOOD_STATES, | ||
) -> None: | ||
super().__init__(driver, good_states=good_states) | ||
|
||
def get_deadtime(self, exposure: float | None) -> float: | ||
return _MIN_DEAD_TIME + (exposure or 0) | ||
|
||
async def prepare(self, trigger_info: TriggerInfo): | ||
await self.set_exposure_time_and_acquire_period_if_supplied( | ||
trigger_info.livetime | ||
) | ||
await asyncio.gather( | ||
self.driver.trigger_mode.set(self._get_trigger_mode(trigger_info.trigger)), | ||
self.driver.num_images.set( | ||
trigger_info.total_number_of_triggers or _MAX_NUM_IMAGE | ||
), | ||
self.driver.image_mode.set(adcore.ImageMode.MULTIPLE), | ||
) | ||
|
||
def _get_trigger_mode(self, trigger: DetectorTrigger) -> Andor2TriggerMode: | ||
supported_trigger_types = { | ||
DetectorTrigger.INTERNAL: Andor2TriggerMode.INTERNAL, | ||
DetectorTrigger.EDGE_TRIGGER: Andor2TriggerMode.EXT_TRIGGER, | ||
} | ||
if trigger not in supported_trigger_types: | ||
raise ValueError( | ||
f"{self.__class__.__name__} only supports the following trigger " | ||
f"types: {supported_trigger_types} but was asked to " | ||
f"use {trigger}" | ||
) | ||
return supported_trigger_types[trigger] | ||
Comment on lines
+38
to
+49
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. Nit: If we can't make a new |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from ophyd_async.core import StrictEnum, SubsetEnum | ||
from ophyd_async.epics.adcore import ADBaseIO | ||
from ophyd_async.epics.core import ( | ||
epics_signal_r, | ||
epics_signal_rw, | ||
) | ||
|
||
|
||
class Andor2TriggerMode(StrictEnum): | ||
INTERNAL = "Internal" | ||
EXT_TRIGGER = "External" | ||
EXT_START = "External Start" | ||
EXT_EXPOSURE = "External Exposure" | ||
EXT_FVP = "External FVP" | ||
SOFTWARE = "Software" | ||
|
||
|
||
class Andor2DataType(SubsetEnum): | ||
UINT16 = "UInt16" | ||
UINT32 = "UInt32" | ||
FLOAT32 = "Float32" | ||
FLOAT64 = "Float64" | ||
|
||
|
||
class Andor2DriverIO(ADBaseIO): | ||
""" | ||
Epics pv for andor model:DU897_BV as deployed on p99 | ||
""" | ||
|
||
def __init__(self, prefix: str, name: str = "") -> None: | ||
super().__init__(prefix, name=name) | ||
self.trigger_mode = epics_signal_rw(Andor2TriggerMode, prefix + "TriggerMode") | ||
self.data_type = epics_signal_r(Andor2DataType, prefix + "DataType_RBV") | ||
self.andor_accumulate_period = epics_signal_r( | ||
float, prefix + "AndorAccumulatePeriod_RBV" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
from typing import cast | ||
from unittest.mock import AsyncMock, patch | ||
|
||
import pytest | ||
from event_model import StreamDatum, StreamResource | ||
|
||
from ophyd_async.core import ( | ||
DetectorTrigger, | ||
PathProvider, | ||
TriggerInfo, | ||
) | ||
from ophyd_async.epics import adandor | ||
|
||
|
||
@pytest.fixture | ||
def test_adandor(ad_standard_det_factory) -> adandor.Andor2Detector: | ||
return ad_standard_det_factory(adandor.Andor2Detector) | ||
|
||
|
||
@pytest.mark.parametrize("exposure_time", [0.0, 0.1, 1.0, 10.0, 100.0]) | ||
async def test_deadtime_from_exposure_time( | ||
exposure_time: float, | ||
test_adandor: adandor.Andor2Detector, | ||
): | ||
assert test_adandor._controller.get_deadtime(exposure_time) == exposure_time + 0.1 | ||
|
||
|
||
async def test_hints_from_hdf_writer(test_adandor: adandor.Andor2Detector): | ||
assert test_adandor.hints == {"fields": ["test_adandor21"]} | ||
|
||
|
||
async def test_can_read(test_adandor: adandor.Andor2Detector): | ||
# Standard detector can be used as Readable | ||
assert (await test_adandor.read()) == {} | ||
|
||
|
||
async def test_decribe_describes_writer_dataset( | ||
test_adandor: adandor.Andor2Detector, one_shot_trigger_info: TriggerInfo | ||
): | ||
assert await test_adandor.describe() == {} | ||
await test_adandor.stage() | ||
await test_adandor.prepare(one_shot_trigger_info) | ||
assert await test_adandor.describe() == { | ||
"test_adandor21": { | ||
"source": "mock+ca://ANDOR21:HDF1:FullFileName_RBV", | ||
"shape": [10, 10], | ||
"dtype": "array", | ||
"dtype_numpy": "<u2", | ||
"external": "STREAM:", | ||
} | ||
} | ||
|
||
|
||
async def test_can_collect( | ||
test_adandor: adandor.Andor2Detector, | ||
static_path_provider: PathProvider, | ||
one_shot_trigger_info: TriggerInfo, | ||
): | ||
path_info = static_path_provider() | ||
full_file_name = path_info.directory_path / f"{path_info.filename}.h5" | ||
await test_adandor.stage() | ||
await test_adandor.prepare(one_shot_trigger_info) | ||
docs = [(name, doc) async for name, doc in test_adandor.collect_asset_docs(1)] | ||
assert len(docs) == 2 | ||
assert docs[0][0] == "stream_resource" | ||
stream_resource = cast(StreamResource, docs[0][1]) | ||
sr_uid = stream_resource["uid"] | ||
assert stream_resource["data_key"] == "test_adandor21" | ||
assert stream_resource["uri"] == "file://localhost/" + str(full_file_name).lstrip( | ||
"/" | ||
) | ||
assert stream_resource["parameters"] == { | ||
"dataset": "/entry/data/data", | ||
"swmr": False, | ||
"multiplier": 1, | ||
"chunk_shape": (1, 10, 10), | ||
} | ||
assert docs[1][0] == "stream_datum" | ||
stream_datum = cast(StreamDatum, 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( | ||
test_adandor: adandor.Andor2Detector, one_shot_trigger_info: TriggerInfo | ||
): | ||
assert (await test_adandor.describe_collect()) == {} | ||
await test_adandor.stage() | ||
await test_adandor.prepare(one_shot_trigger_info) | ||
assert (await test_adandor.describe_collect()) == { | ||
"test_adandor21": { | ||
"source": "mock+ca://ANDOR21:HDF1:FullFileName_RBV", | ||
"shape": [10, 10], | ||
"dtype": "array", | ||
"dtype_numpy": "<u2", | ||
"external": "STREAM:", | ||
} | ||
} | ||
|
||
|
||
async def test_unsupported_trigger_excepts(test_adandor: adandor.Andor2Detector): | ||
with patch( | ||
"ophyd_async.epics.adcore._hdf_writer.ADHDFWriter.open", new_callable=AsyncMock | ||
) as mock_open: | ||
with pytest.raises( | ||
ValueError, | ||
# str(EnumClass.value) handling changed in Python 3.11 | ||
match=( | ||
"Andor2Controller only supports the following trigger types: .* but" | ||
), | ||
): | ||
await test_adandor.prepare( | ||
TriggerInfo( | ||
number_of_triggers=0, | ||
trigger=DetectorTrigger.VARIABLE_GATE, | ||
deadtime=1.1, | ||
livetime=1, | ||
frame_timeout=3, | ||
) | ||
) | ||
|
||
mock_open.assert_called_once() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 think i did a bad thing when I first made it, may be the better way to get the dead time is to use the accumulate period rather than exposure time, which we can read from this signal: BL99P-EA-DET-03:CAM:AndorAccumulatePeriod_RBV ?
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.
Except this is a sync method so we can't read PVs here...
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.
Just out of interest, if I do want the deadtime to be from a readback channel, what is the best way to go about it?
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 answer is either:
@callumforrester do we want to open this box?