Skip to content

Commit

Permalink
Merge pull request #162 from DiamondLightSource/reshape_in_ophyd
Browse files Browse the repository at this point in the history
Reshape OAV stream in ophyd and switch to using ophyd-async
  • Loading branch information
Tom Willemsen authored Oct 6, 2023
2 parents 65229df + 2cf74ef commit 16468c9
Show file tree
Hide file tree
Showing 7 changed files with 16 additions and 112 deletions.
9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@ classifiers = [
]
description = "Ophyd devices and other utils that could be used across DLS beamlines"
dependencies = [
"ophyd@git+https://github.com/DominicOram/ophyd@31a1762435bcb275d2555068233549885eab02e7", # Switch back to just "ophyd" once ophyd#1148, ophyd#1155, ophyd#1140 merged.
"ophyd",
"ophyd-async",
"bluesky",
"pyepics",
"dataclasses-json",
"pillow",
"requests",
"graypy",
"pydantic<2.0",
"opencv-python-headless", # For pin-tip detection.
"aioca", # Required for CA support with Ophyd V2.
"p4p", # Required for PVA support with Ophyd V2.
"opencv-python-headless", # For pin-tip detection.
"aioca", # Required for CA support with ophyd-async.
"p4p", # Required for PVA support with ophyd-async.
]
dynamic = ["version"]
license.file = "LICENSE"
Expand Down
4 changes: 2 additions & 2 deletions src/dodal/beamlines/beamline_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from bluesky.run_engine import call_in_bluesky_event_loop
from ophyd import Device as OphydV1Device
from ophyd.sim import make_fake_device
from ophyd.v2.core import Device as OphydV2Device
from ophyd.v2.core import wait_for_connection as v2_device_wait_for_connection
from ophyd_async.core import Device as OphydV2Device
from ophyd_async.core import wait_for_connection as v2_device_wait_for_connection

from dodal.utils import AnyDevice, BeamlinePrefix, skip_device

Expand Down
37 changes: 4 additions & 33 deletions src/dodal/devices/oav/pin_image_recognition/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
from typing import Optional, Tuple, Type, TypeVar

import numpy as np
from bluesky.protocols import Descriptor
from bluesky.protocols import Descriptor, Readable, Reading
from numpy.typing import NDArray
from ophyd.v2.core import Device, Readable, Reading, SimSignalBackend
from ophyd.v2.epics import SignalR, SignalRW, epics_signal_r
from ophyd_async.core import Device, SignalR, SignalRW, SimSignalBackend
from ophyd_async.epics.signal import epics_signal_r

from dodal.devices.oav.pin_image_recognition.utils import (
ARRAY_PROCESSING_FUNCTIONS_MAP,
Expand Down Expand Up @@ -48,13 +48,6 @@ def __init__(self, prefix: str, name: str = ""):
NDArray[np.uint8], f"pva://{prefix}PVA:ARRAY"
)

self.oav_width: SignalR[int] = epics_signal_r(
int, f"{prefix}PVA:ArraySize1_RBV"
)
self.oav_height: SignalR[int] = epics_signal_r(
int, f"{prefix}PVA:ArraySize2_RBV"
)

# Soft parameters for pin-tip detection.
self.timeout: SignalRW[float] = _create_soft_signal(float, "timeout")
self.preprocess: SignalRW[int] = _create_soft_signal(int, "preprocess")
Expand Down Expand Up @@ -84,7 +77,6 @@ async def _get_tip_position(
Returns tuple of:
((tip_x, tip_y), timestamp)
"""

preprocess_key = await self.preprocess.get_value()
preprocess_iter = await self.preprocess_iterations.get_value()
preprocess_ksize = await self.preprocess_ksize.get_value()
Expand All @@ -111,30 +103,9 @@ async def _get_tip_position(
array_data: NDArray[np.uint8] = array_reading[""]["value"]
timestamp: float = array_reading[""]["timestamp"]

height: int = await self.oav_height.get_value()
width: int = await self.oav_width.get_value()

num_pixels: int = height * width # type: ignore
value_len = array_data.shape[0]

try:
if value_len == num_pixels * 3:
# RGB data
value = array_data.reshape(height, width, 3)
elif value_len == num_pixels:
# Grayscale data
value = array_data.reshape(height, width)
else:
# Something else?
raise ValueError(
"Unexpected data array size: expected {} (grayscale data) or {} (rgb data), got {}",
num_pixels,
num_pixels * 3,
value_len,
)

start_time = time.time()
location = sample_detection.processArray(value)
location = sample_detection.processArray(array_data)
end_time = time.time()
LOGGER.debug(
"Sample location detection took {}ms".format(
Expand Down
2 changes: 1 addition & 1 deletion src/dodal/devices/oav/pin_image_recognition/manual_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async def acquire():
try:
import matplotlib.pyplot as plt

plt.imshow(img[""]["value"].reshape(768, 1024, 3))
plt.imshow(img[""]["value"])
plt.show()
except ImportError:
print("matplotlib not available; cannot show acquired image")
2 changes: 1 addition & 1 deletion src/dodal/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
WritesExternalAssets,
)
from ophyd.device import Device as OphydV1Device
from ophyd.v2.core import Device as OphydV2Device
from ophyd_async.core import Device as OphydV2Device

try:
from typing import TypeAlias
Expand Down
2 changes: 1 addition & 1 deletion tests/beamlines/unit_tests/test_beamline_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from ophyd import Device
from ophyd.device import Device as OphydV1Device
from ophyd.sim import FakeEpicsSignal
from ophyd.v2.core import Device as OphydV2Device
from ophyd_async.core import Device as OphydV2Device

from dodal.beamlines import beamline_utils, i03
from dodal.devices.aperturescatterguard import ApertureScatterguard
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import asyncio
from unittest.mock import MagicMock, patch
from unittest.mock import patch

import numpy as np
import pytest
from numpy.typing import NDArray
from ophyd.v2.core import set_sim_value
from ophyd_async.core import set_sim_value

from dodal.devices.oav.pin_image_recognition import MxSampleDetect, PinTipDetection
from dodal.devices.oav.pin_image_recognition.utils import SampleLocation

EVENT_LOOP = asyncio.new_event_loop()

Expand Down Expand Up @@ -90,68 +87,3 @@ async def test_invalid_processing_func_uses_identity_function():
# Assert captured preprocess function is the identitiy function
arg = object()
assert arg == captured_func(arg)


@pytest.mark.parametrize(
"input_array,height,width,reshaped",
[
(np.zeros(shape=(1,)), 1, 1, np.zeros(shape=(1, 1))),
(np.zeros(shape=(3,)), 1, 1, np.zeros(shape=(1, 1, 3))),
(np.zeros(shape=(1920 * 1080)), 1080, 1920, np.zeros(shape=(1080, 1920))),
(
np.zeros(shape=(1920 * 1080 * 3)),
1080,
1920,
np.zeros(shape=(1080, 1920, 3)),
),
],
)
def test_when_data_supplied_THEN_reshaped_correctly_before_call_to_process_array(
input_array: NDArray, height: int, width: int, reshaped: NDArray
):
device = EVENT_LOOP.run_until_complete(_get_pin_tip_detection_device())

device.array_data._backend._set_value(input_array) # type: ignore
set_sim_value(device.oav_height, height)
set_sim_value(device.oav_width, width)

MxSampleDetect.processArray = MagicMock(
autospec=True,
return_value=SampleLocation(
tip_x=10, tip_y=20, edge_bottom=np.array([]), edge_top=np.array([])
),
)

result = EVENT_LOOP.run_until_complete(device.read())

MxSampleDetect.processArray.assert_called_once()
np.testing.assert_array_equal(MxSampleDetect.processArray.call_args[0][0], reshaped)

assert result[""]["value"] == (10, 20)


@pytest.mark.parametrize(
"input_array,height,width",
[
(np.zeros(shape=(0,)), 1080, 1920),
(np.zeros(shape=(1920 * 1080 * 2)), 1080, 1920),
],
)
def test_when_invalid_data_length_supplied_THEN_no_call_to_process_array(
input_array: NDArray, height: int, width: int
):
device = EVENT_LOOP.run_until_complete(_get_pin_tip_detection_device())

set_sim_value(device.array_data, input_array)
set_sim_value(device.oav_height, height)
set_sim_value(device.oav_width, width)

MxSampleDetect.processArray = MagicMock(
autospec=True,
)

result = EVENT_LOOP.run_until_complete(device.read())

MxSampleDetect.processArray.assert_not_called()

assert result[""]["value"] == (None, None)

0 comments on commit 16468c9

Please sign in to comment.