Skip to content

Commit 83fc820

Browse files
Simplify ElectronAnalyserDetector driver reference (#1592)
* Simplify ElectronAnalyserDetector driver reference * Addec comment on why driver is a child device * Updated doc string * Made controller private * Correct spelling --------- Co-authored-by: olliesilvester <[email protected]>
1 parent 17b1eb4 commit 83fc820

File tree

5 files changed

+64
-90
lines changed

5 files changed

+64
-90
lines changed

src/dodal/devices/electron_analyser/abstract/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .base_detector import (
2-
AbstractElectronAnalyserDetector,
2+
BaseElectronAnalyserDetector,
33
)
44
from .base_driver_io import AbstractAnalyserDriverIO, TAbstractAnalyserDriverIO
55
from .base_region import (
@@ -19,7 +19,7 @@
1919
"TAcquisitionMode",
2020
"TLensMode",
2121
"AbstractAnalyserDriverIO",
22-
"AbstractElectronAnalyserDetector",
22+
"BaseElectronAnalyserDetector",
2323
"AbstractAnalyserDriverIO",
2424
"TAbstractAnalyserDriverIO",
2525
]
Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from abc import abstractmethod
21
from typing import Generic
32

43
from bluesky.protocols import Reading, Triggerable
@@ -9,14 +8,14 @@
98
AsyncStatus,
109
Device,
1110
)
11+
from ophyd_async.epics.adcore import ADBaseController
1212

13-
from dodal.devices.controllers import ConstantDeadTimeController
1413
from dodal.devices.electron_analyser.abstract.base_driver_io import (
1514
TAbstractAnalyserDriverIO,
1615
)
1716

1817

19-
class AbstractElectronAnalyserDetector(
18+
class BaseElectronAnalyserDetector(
2019
Device,
2120
Triggerable,
2221
AsyncReadable,
@@ -34,43 +33,31 @@ class AbstractElectronAnalyserDetector(
3433

3534
def __init__(
3635
self,
37-
driver: TAbstractAnalyserDriverIO,
36+
controller: ADBaseController[TAbstractAnalyserDriverIO],
3837
name: str = "",
3938
):
40-
self.controller = ConstantDeadTimeController(driver, 0)
39+
self._controller = controller
4140
super().__init__(name)
4241

4342
@AsyncStatus.wrap
4443
async def trigger(self) -> None:
45-
await self.controller.arm()
46-
await self.controller.wait_for_idle()
44+
await self._controller.arm()
45+
await self._controller.wait_for_idle()
4746

4847
async def read(self) -> dict[str, Reading]:
49-
return await self.driver.read()
48+
return await self._controller.driver.read()
5049

5150
async def describe(self) -> dict[str, DataKey]:
52-
data = await self.driver.describe()
51+
data = await self._controller.driver.describe()
5352
# Correct the shape for image
54-
prefix = self.driver.name + "-"
55-
energy_size = len(await self.driver.energy_axis.get_value())
56-
angle_size = len(await self.driver.angle_axis.get_value())
53+
prefix = self._controller.driver.name + "-"
54+
energy_size = len(await self._controller.driver.energy_axis.get_value())
55+
angle_size = len(await self._controller.driver.angle_axis.get_value())
5756
data[prefix + "image"]["shape"] = [angle_size, energy_size]
5857
return data
5958

6059
async def read_configuration(self) -> dict[str, Reading]:
61-
return await self.driver.read_configuration()
60+
return await self._controller.driver.read_configuration()
6261

6362
async def describe_configuration(self) -> dict[str, DataKey]:
64-
return await self.driver.describe_configuration()
65-
66-
@property
67-
@abstractmethod
68-
def driver(self) -> TAbstractAnalyserDriverIO:
69-
"""
70-
Define common property for all implementations to access the driver. Some
71-
implementations will store this as a reference so it doesn't have conflicting
72-
parents.
73-
74-
Returns:
75-
instance of the driver.
76-
"""
63+
return await self._controller.driver.describe_configuration()

src/dodal/devices/electron_analyser/detector.py

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
from typing import Generic, TypeVar
22

33
from bluesky.protocols import Stageable
4-
from ophyd_async.core import (
5-
AsyncStatus,
6-
Reference,
7-
)
4+
from ophyd_async.core import AsyncStatus
5+
from ophyd_async.epics.adcore import ADBaseController
86

97
from dodal.common.data_util import load_json_file_to_class
8+
from dodal.devices.controllers import ConstantDeadTimeController
109
from dodal.devices.electron_analyser.abstract.base_detector import (
11-
AbstractElectronAnalyserDetector,
10+
BaseElectronAnalyserDetector,
1211
)
1312
from dodal.devices.electron_analyser.abstract.base_driver_io import (
1413
TAbstractAnalyserDriverIO,
@@ -20,35 +19,27 @@
2019

2120

2221
class ElectronAnalyserRegionDetector(
23-
AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
22+
BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO],
2423
Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion],
2524
):
2625
"""
2726
Extends electron analyser detector to configure specific region settings before data
28-
acqusition. This object must be passed in a driver and store it as a reference. It
29-
is designed to only exist inside a plan.
27+
acquisition. It is designed to only exist inside a plan.
3028
"""
3129

3230
def __init__(
3331
self,
34-
driver: TAbstractAnalyserDriverIO,
32+
controller: ADBaseController[TAbstractAnalyserDriverIO],
3533
region: TAbstractBaseRegion,
3634
name: str = "",
3735
):
38-
self._driver_ref = Reference(driver)
3936
self.region = region
40-
super().__init__(driver, name)
41-
42-
@property
43-
def driver(self) -> TAbstractAnalyserDriverIO:
44-
# Store as a reference, this implementation will be given a driver so needs to
45-
# make sure we don't get conflicting parents.
46-
return self._driver_ref()
37+
super().__init__(controller, name)
4738

4839
@AsyncStatus.wrap
4940
async def trigger(self) -> None:
5041
# Configure region parameters on the driver first before data collection.
51-
await self.driver.set(self.region)
42+
await self._controller.driver.set(self.region)
5243
await super().trigger()
5344

5445

@@ -59,7 +50,7 @@ async def trigger(self) -> None:
5950

6051

6152
class ElectronAnalyserDetector(
62-
AbstractElectronAnalyserDetector[TAbstractAnalyserDriverIO],
53+
BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO],
6354
Stageable,
6455
Generic[
6556
TAbstractAnalyserDriverIO,
@@ -79,16 +70,11 @@ def __init__(
7970
driver: TAbstractAnalyserDriverIO,
8071
name: str = "",
8172
):
82-
# Pass in driver
83-
self._driver = driver
73+
# Save driver as direct child so particpates with connect()
74+
self.driver = driver
8475
self._sequence_class = sequence_class
85-
super().__init__(self.driver, name)
86-
87-
@property
88-
def driver(self) -> TAbstractAnalyserDriverIO:
89-
# This implementation creates the driver and wants this to be the parent so it
90-
# can be used with connect() method.
91-
return self._driver
76+
controller = ConstantDeadTimeController[TAbstractAnalyserDriverIO](driver, 0)
77+
super().__init__(controller, name)
9278

9379
@AsyncStatus.wrap
9480
async def stage(self) -> None:
@@ -103,13 +89,13 @@ async def stage(self) -> None:
10389
Raises:
10490
Any exceptions raised by the driver's stage or controller's disarm methods.
10591
"""
106-
await self.controller.disarm()
92+
await self._controller.disarm()
10793
await self.driver.stage()
10894

10995
@AsyncStatus.wrap
11096
async def unstage(self) -> None:
11197
"""Disarm the detector."""
112-
await self.controller.disarm()
98+
await self._controller.disarm()
11399
await self.driver.unstage()
114100

115101
def load_sequence(self, filename: str) -> TAbstractBaseSequence:
@@ -144,7 +130,9 @@ def create_region_detector_list(
144130
seq = self.load_sequence(filename)
145131
regions = seq.get_enabled_regions() if enabled_only else seq.regions
146132
return [
147-
ElectronAnalyserRegionDetector(self.driver, r, self.name + "_" + r.name)
133+
ElectronAnalyserRegionDetector(
134+
self._controller, r, self.name + "_" + r.name
135+
)
148136
for r in regions
149137
]
150138

tests/devices/electron_analyser/abstract/test_base_detector.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import dodal.devices.i09 as i09
1515
from dodal.devices.electron_analyser.abstract import (
1616
AbstractAnalyserDriverIO,
17-
AbstractElectronAnalyserDetector,
17+
BaseElectronAnalyserDetector,
1818
)
1919
from dodal.devices.electron_analyser.energy_sources import EnergySource
2020
from dodal.devices.electron_analyser.specs import SpecsDetector
@@ -32,7 +32,7 @@ async def sim_detector(
3232
request: pytest.FixtureRequest,
3333
single_energy_source: EnergySource,
3434
RE: RunEngine,
35-
) -> AbstractElectronAnalyserDetector[AbstractAnalyserDriverIO]:
35+
) -> BaseElectronAnalyserDetector[AbstractAnalyserDriverIO]:
3636
async with init_devices(mock=True):
3737
sim_detector = await create_detector(
3838
request.param,
@@ -45,50 +45,49 @@ async def sim_detector(
4545

4646

4747
def test_analyser_detector_trigger(
48-
sim_detector: AbstractElectronAnalyserDetector[AbstractAnalyserDriverIO],
48+
sim_detector: BaseElectronAnalyserDetector[AbstractAnalyserDriverIO],
4949
RE: RunEngine,
5050
) -> None:
51-
sim_detector.controller.arm = AsyncMock()
52-
sim_detector.controller.wait_for_idle = AsyncMock()
51+
sim_detector._controller.arm = AsyncMock()
52+
sim_detector._controller.wait_for_idle = AsyncMock()
5353

5454
RE(bps.trigger(sim_detector), wait=True)
5555

56-
sim_detector.controller.arm.assert_awaited_once()
57-
sim_detector.controller.wait_for_idle.assert_awaited_once()
56+
sim_detector._controller.arm.assert_awaited_once()
57+
sim_detector._controller.wait_for_idle.assert_awaited_once()
5858

5959

6060
async def test_analyser_detector_read(
61-
sim_detector: AbstractElectronAnalyserDetector[AbstractAnalyserDriverIO],
61+
sim_detector: BaseElectronAnalyserDetector[AbstractAnalyserDriverIO],
6262
) -> None:
63-
driver = sim_detector.driver
64-
driver_read = await driver.read()
63+
driver_read = await sim_detector._controller.driver.read()
6564
await assert_reading(sim_detector, driver_read)
6665

6766

6867
async def test_analyser_describe(
69-
sim_detector: AbstractElectronAnalyserDetector[AbstractAnalyserDriverIO],
68+
sim_detector: BaseElectronAnalyserDetector[AbstractAnalyserDriverIO],
7069
) -> None:
71-
energy_array = await sim_detector.driver.energy_axis.get_value()
72-
angle_array = await sim_detector.driver.angle_axis.get_value()
70+
energy_array = await sim_detector._controller.driver.energy_axis.get_value()
71+
angle_array = await sim_detector._controller.driver.angle_axis.get_value()
7372
data = await sim_detector.describe()
74-
assert data[f"{sim_detector.name}-_driver-image"]["shape"] == [
73+
assert data[f"{sim_detector._controller.driver.image.name}"]["shape"] == [
7574
len(angle_array),
7675
len(energy_array),
7776
]
7877

7978

8079
async def test_analyser_detector_configuration(
81-
sim_detector: AbstractElectronAnalyserDetector[AbstractAnalyserDriverIO],
80+
sim_detector: BaseElectronAnalyserDetector[AbstractAnalyserDriverIO],
8281
) -> None:
83-
driver = sim_detector.driver
84-
driver_config = await driver.read_configuration()
82+
driver_config = await sim_detector._controller.driver.read_configuration()
8583
await assert_configuration(sim_detector, driver_config)
8684

8785

8886
async def test_analyser_detector_describe_configuration(
89-
sim_detector: AbstractElectronAnalyserDetector[AbstractAnalyserDriverIO],
87+
sim_detector: BaseElectronAnalyserDetector[AbstractAnalyserDriverIO],
9088
) -> None:
91-
driver = sim_detector.driver
92-
driver_describe_config = await driver.describe_configuration()
89+
driver_describe_config = (
90+
await sim_detector._controller.driver.describe_configuration()
91+
)
9392

9493
assert await sim_detector.describe_configuration() == driver_describe_config

tests/devices/electron_analyser/test_detector.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,24 @@ def test_analyser_detector_loads_sequence_correctly(
5555
async def test_analyser_detector_stage(
5656
sim_detector: GenericElectronAnalyserDetector,
5757
) -> None:
58-
sim_detector.controller.disarm = AsyncMock()
58+
sim_detector._controller.disarm = AsyncMock()
5959
sim_detector.driver.stage = AsyncMock()
6060

6161
await sim_detector.stage()
6262

63-
sim_detector.controller.disarm.assert_awaited_once()
63+
sim_detector._controller.disarm.assert_awaited_once()
6464
sim_detector.driver.stage.assert_awaited_once()
6565

6666

6767
async def test_analyser_detector_unstage(
6868
sim_detector: GenericElectronAnalyserDetector,
6969
) -> None:
70-
sim_detector.controller.disarm = AsyncMock()
70+
sim_detector._controller.disarm = AsyncMock()
7171
sim_detector.driver.unstage = AsyncMock()
7272

7373
await sim_detector.unstage()
7474

75-
sim_detector.controller.disarm.assert_awaited_once()
75+
sim_detector._controller.disarm.assert_awaited_once()
7676
sim_detector.driver.unstage.assert_awaited_once()
7777

7878

@@ -104,7 +104,7 @@ def test_analyser_detector_has_driver_as_child_and_region_detector_does_not(
104104

105105
for det in region_detectors:
106106
assert det._child_devices.get(driver_name) is None
107-
assert det.driver.parent == sim_detector
107+
assert det._controller.driver.parent == sim_detector
108108

109109

110110
async def test_analyser_region_detector_trigger_sets_driver_with_region(
@@ -117,13 +117,13 @@ async def test_analyser_region_detector_trigger_sets_driver_with_region(
117117
)
118118

119119
for reg_det in region_detectors:
120-
reg_det.driver.set = AsyncMock()
120+
reg_det._controller.driver.set = AsyncMock()
121121

122-
reg_det.controller.arm = AsyncMock()
123-
reg_det.controller.wait_for_idle = AsyncMock()
122+
reg_det._controller.arm = AsyncMock()
123+
reg_det._controller.wait_for_idle = AsyncMock()
124124

125125
RE(bps.trigger(reg_det), wait=True)
126126

127-
reg_det.controller.arm.assert_awaited_once()
128-
reg_det.controller.wait_for_idle.assert_awaited_once()
129-
reg_det.driver.set.assert_awaited_once_with(reg_det.region)
127+
reg_det._controller.arm.assert_awaited_once()
128+
reg_det._controller.wait_for_idle.assert_awaited_once()
129+
reg_det._controller.driver.set.assert_awaited_once_with(reg_det.region)

0 commit comments

Comments
 (0)