Skip to content

Commit bc36da8

Browse files
Add ability to move scintillator in to beam for i04 (#1583)
* Add scint into beam + change Enum comment to be be more descriptive compared to just In and Out. --------- Co-authored-by: Dominic Oram <[email protected]>
1 parent 7b14545 commit bc36da8

File tree

3 files changed

+78
-25
lines changed

3 files changed

+78
-25
lines changed

src/dodal/devices/oav/pin_image_recognition/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ class ScanDirections(Enum):
1414
REVERSE = -1
1515

1616

17+
"""
18+
See https://opencv24-python-tutorials.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html
19+
for description of functions below.
20+
"""
21+
22+
1723
def identity(*args, **kwargs) -> Callable[[np.ndarray], np.ndarray]:
1824
return lambda arr: arr
1925

src/dodal/devices/scintillator.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88

99

1010
class InOut(StrictEnum):
11-
"""Currently Hyperion only needs to move the scintillator out for data collection."""
11+
"""Moves scintillator in and out of the beam."""
1212

13-
OUT = "Out"
13+
OUT = "Out" # Out of beam
14+
IN = "In" # In to beam
1415
UNKNOWN = "Unknown"
1516

1617

@@ -45,42 +46,63 @@ def __init__(
4546
self._scintillator_out_yz_mm = [
4647
float(beamline_parameters[f"scin_{axis}_SCIN_OUT"]) for axis in ("y", "z")
4748
]
49+
self._scintillator_in_yz_mm = [
50+
float(beamline_parameters[f"scin_{axis}_SCIN_IN"]) for axis in ("y", "z")
51+
]
4852
self._yz_tolerance_mm = [
4953
float(beamline_parameters[f"scin_{axis}_tolerance"]) for axis in ("y", "z")
5054
]
5155

5256
super().__init__(name)
5357

54-
def _get_selected_position(self, y: float, z: float) -> InOut:
55-
current_pos = [y, z]
56-
if all(
58+
def _check_position(self, current_pos: list[float], pos_to_check: list[float]):
59+
return all(
5760
isclose(axis_pos, axis_in_beam, abs_tol=axis_tolerance)
5861
for axis_pos, axis_in_beam, axis_tolerance in zip(
5962
current_pos,
60-
self._scintillator_out_yz_mm,
63+
pos_to_check,
6164
self._yz_tolerance_mm,
6265
strict=False,
6366
)
64-
):
67+
)
68+
69+
def _get_selected_position(self, y: float, z: float) -> InOut:
70+
current_pos = [y, z]
71+
if self._check_position(current_pos, self._scintillator_out_yz_mm):
6572
return InOut.OUT
73+
74+
elif self._check_position(current_pos, self._scintillator_in_yz_mm):
75+
return InOut.IN
76+
6677
else:
6778
return InOut.UNKNOWN
6879

80+
async def _check_aperture_parked(self):
81+
if (
82+
await self._aperture_scatterguard().selected_aperture.get_value()
83+
!= ApertureValue.PARKED
84+
):
85+
raise ValueError(
86+
f"Cannot move scintillator if aperture/scatterguard is not parked. Position is currently {await self._aperture_scatterguard().selected_aperture.get_value()}"
87+
)
88+
6989
async def _set_selected_position(self, position: InOut) -> None:
7090
match position:
7191
case InOut.OUT:
7292
current_y = await self.y_mm.user_readback.get_value()
7393
current_z = await self.z_mm.user_readback.get_value()
7494
if self._get_selected_position(current_y, current_z) == InOut.OUT:
7595
return
76-
if (
77-
self._aperture_scatterguard().selected_aperture.get_value()
78-
!= ApertureValue.PARKED
79-
):
80-
raise ValueError(
81-
"Cannot move scintillator out if aperture/scatterguard is not parked"
82-
)
96+
await self._check_aperture_parked()
8397
await self.y_mm.set(self._scintillator_out_yz_mm[0])
8498
await self.z_mm.set(self._scintillator_out_yz_mm[1])
99+
case InOut.IN:
100+
current_y = await self.y_mm.user_readback.get_value()
101+
current_z = await self.z_mm.user_readback.get_value()
102+
if self._get_selected_position(current_y, current_z) == InOut.IN:
103+
return
104+
await self._check_aperture_parked()
105+
await self.z_mm.set(self._scintillator_in_yz_mm[1])
106+
await self.y_mm.set(self._scintillator_in_yz_mm[0])
85107
case _:
86108
raise ValueError(f"Cannot set scintillator to position {position}")

tests/devices/test_scintillator.py

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from collections.abc import AsyncGenerator
22
from contextlib import ExitStack
3-
from unittest.mock import MagicMock
3+
from unittest.mock import AsyncMock, MagicMock
44

55
import pytest
66
from ophyd_async.core import init_devices
7-
from ophyd_async.testing import get_mock_put
7+
from ophyd_async.testing import assert_value, get_mock_put
88

99
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
1010
from dodal.devices.aperturescatterguard import ApertureScatterguard, ApertureValue
@@ -32,6 +32,8 @@ async def scintillator_and_ap_sg(
3232
) -> AsyncGenerator[tuple[Scintillator, MagicMock], None]:
3333
async with init_devices(mock=True):
3434
mock_ap_sg = MagicMock()
35+
mock_ap_sg.return_value.selected_aperture.set = AsyncMock()
36+
mock_ap_sg.return_value.selected_aperture.get_value = AsyncMock()
3537
scintillator = Scintillator(
3638
prefix="",
3739
name="test_scin",
@@ -49,6 +51,7 @@ async def scintillator_and_ap_sg(
4951
@pytest.mark.parametrize(
5052
"y, z, expected_position",
5153
[
54+
(100.855, 101.5115, InOut.IN),
5255
(-0.02, 0.1, InOut.OUT),
5356
(0.1, 0.1, InOut.UNKNOWN),
5457
(10.2, 15.6, InOut.UNKNOWN),
@@ -84,34 +87,56 @@ async def test_given_aperture_scatterguard_parked_when_set_to_out_position_then_
8487

8588
await scintillator.selected_pos.set(InOut.OUT)
8689

87-
assert await scintillator.y_mm.user_setpoint.get_value() == -0.02
88-
assert await scintillator.z_mm.user_setpoint.get_value() == 0.1
90+
await assert_value(scintillator.y_mm.user_setpoint, -0.02)
91+
await assert_value(scintillator.z_mm.user_setpoint, 0.1)
8992

9093

91-
async def test_given_aperture_scatterguard_not_parked_when_set_to_out_position_then_exception_raised(
94+
async def test_given_aperture_scatterguard_parked_when_set_to_in_position_then_returns_expected(
9295
scintillator_and_ap_sg: tuple[Scintillator, ApertureScatterguard],
96+
):
97+
scintillator, ap_sg = scintillator_and_ap_sg
98+
ap_sg.return_value.selected_aperture.get_value.return_value = ApertureValue.PARKED # type: ignore
99+
100+
await scintillator.selected_pos.set(InOut.IN)
101+
102+
await assert_value(scintillator.y_mm.user_setpoint, 100.855)
103+
await assert_value(scintillator.z_mm.user_setpoint, 101.5115)
104+
105+
106+
@pytest.mark.parametrize("scint_pos", [InOut.OUT, InOut.IN])
107+
async def test_given_aperture_scatterguard_not_parked_when_set_to_in_or_out_position_then_exception_raised(
108+
scintillator_and_ap_sg: tuple[Scintillator, ApertureScatterguard], scint_pos
93109
):
94110
for position in ApertureValue:
95111
if position != ApertureValue.PARKED:
96112
scintillator, ap_sg = scintillator_and_ap_sg
97113
ap_sg.return_value.selected_aperture.get_value.return_value = position # type: ignore
98-
99114
with pytest.raises(ValueError):
100-
await scintillator.selected_pos.set(InOut.OUT)
115+
await scintillator.selected_pos.set(scint_pos)
101116

102117

103-
async def test_given_scintillator_already_out_when_moved_out_then_does_nothing(
118+
@pytest.mark.parametrize(
119+
"y, z, expected_position",
120+
[
121+
(100.855, 101.5115, InOut.IN),
122+
(-0.02, 0.1, InOut.OUT),
123+
],
124+
)
125+
async def test_given_scintillator_already_out_when_moved_in_or_out_then_does_nothing(
104126
scintillator_and_ap_sg: tuple[Scintillator, ApertureScatterguard],
127+
expected_position,
128+
y,
129+
z,
105130
):
106131
scintillator, ap_sg = scintillator_and_ap_sg
107-
await scintillator.y_mm.set(0)
108-
await scintillator.z_mm.set(0)
132+
await scintillator.y_mm.set(y)
133+
await scintillator.z_mm.set(z)
109134

110135
get_mock_put(scintillator.y_mm.user_setpoint).reset_mock()
111136
get_mock_put(scintillator.z_mm.user_setpoint).reset_mock()
112137

113138
ap_sg.return_value.selected_aperture.get_value.return_value = ApertureValue.LARGE # type: ignore
114-
await scintillator.selected_pos.set(InOut.OUT)
139+
await scintillator.selected_pos.set(expected_position)
115140

116141
get_mock_put(scintillator.y_mm.user_setpoint).assert_not_called()
117142
get_mock_put(scintillator.z_mm.user_setpoint).assert_not_called()

0 commit comments

Comments
 (0)