Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Ophyd pin-tip centring #938

Merged
merged 12 commits into from
Jan 23, 2024
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ install_requires =
xarray
doct
databroker
dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git@9129f255066131bf7ca5d1a19d4e19b91eb77263
dls-dodal @ git+https://github.com/DiamondLightSource/dodal.git@15fc272e365fd6a52af2b7ef1e16ca5bf7ba868e
pydantic<2.0 # See https://github.com/DiamondLightSource/hyperion/issues/774
scipy
zmq
Expand Down
81 changes: 63 additions & 18 deletions src/hyperion/device_setup_plans/setup_oav.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import bluesky.plan_stubs as bps
import numpy as np
from bluesky.utils import Msg
from dodal.devices.areadetector.plugins.MXSC import MXSC
from dodal.devices.oav.oav_calculations import camera_coordinates_to_xyz
from dodal.devices.oav.oav_detector import MXSC, OAV, OAVConfigParams
from dodal.devices.oav.oav_detector import OAV, OAVConfigParams
from dodal.devices.oav.oav_errors import OAVError_ZoomLevelNotFound
from dodal.devices.oav.oav_parameters import OAVParameters
from dodal.devices.oav.pin_image_recognition import PinTipDetection
from dodal.devices.oav.utils import ColorMode, EdgeOutputArrayImageType
from dodal.devices.smargon import Smargon

Expand Down Expand Up @@ -55,40 +57,63 @@ def start_mxsc(oav: OAV, min_callback_time, filename):
yield from set_using_group(oav.mxsc.output_array, EdgeOutputArrayImageType.ORIGINAL)


def pre_centring_setup_oav(oav: OAV, parameters: OAVParameters):
"""Setup OAV PVs with required values."""
yield from set_using_group(oav.cam.color_mode, ColorMode.RGB1)
yield from set_using_group(oav.cam.acquire_period, parameters.acquire_period)
yield from set_using_group(oav.cam.acquire_time, parameters.exposure)
yield from set_using_group(oav.cam.gain, parameters.gain)

def setup_pin_tip_detection_params(
pin_tip_detect_device: MXSC | PinTipDetection, parameters: OAVParameters
):
# select which blur to apply to image
yield from set_using_group(oav.mxsc.preprocess_operation, parameters.preprocess)
yield from set_using_group(
pin_tip_detect_device.preprocess_operation, parameters.preprocess
)

# sets length scale for blurring
yield from set_using_group(oav.mxsc.preprocess_ksize, parameters.preprocess_K_size)
yield from set_using_group(
pin_tip_detect_device.preprocess_ksize, parameters.preprocess_K_size
)

# Canny edge detect
# Canny edge detect - lower
yield from set_using_group(
oav.mxsc.canny_lower_threshold,
pin_tip_detect_device.canny_lower_threshold,
parameters.canny_edge_lower_threshold,
)

# Canny edge detect - upper
yield from set_using_group(
oav.mxsc.canny_upper_threshold,
pin_tip_detect_device.canny_upper_threshold,
parameters.canny_edge_upper_threshold,
)

# "Close" morphological operation
yield from set_using_group(oav.mxsc.close_ksize, parameters.close_ksize)
yield from set_using_group(
pin_tip_detect_device.close_ksize, parameters.close_ksize
)

# Sample detection
# Sample detection direction
yield from set_using_group(
oav.mxsc.sample_detection_scan_direction, parameters.direction
pin_tip_detect_device.scan_direction, parameters.direction
)

# Minimum height
yield from set_using_group(
oav.mxsc.sample_detection_min_tip_height,
pin_tip_detect_device.min_tip_height,
parameters.minimum_height,
)


def pre_centring_setup_oav(
oav: OAV,
parameters: OAVParameters,
pin_tip_detection_device: PinTipDetection | MXSC,
):
"""
Setup OAV PVs with required values.
"""
yield from set_using_group(oav.cam.color_mode, ColorMode.RGB1)
yield from set_using_group(oav.cam.acquire_period, parameters.acquire_period)
yield from set_using_group(oav.cam.acquire_time, parameters.exposure)
yield from set_using_group(oav.cam.gain, parameters.gain)

yield from setup_pin_tip_detection_params(pin_tip_detection_device, parameters)

yield from start_mxsc(
oav,
parameters.min_callback_time,
Expand Down Expand Up @@ -149,7 +174,9 @@ def get_move_required_so_that_beam_is_at_pixel(
return calculate_x_y_z_of_pixel(current_motor_xyz, current_angle, pixel, oav_params)


def wait_for_tip_to_be_found(mxsc: MXSC) -> Generator[Msg, None, Tuple[int, int]]:
def wait_for_tip_to_be_found_ad_mxsc(
mxsc: MXSC,
) -> Generator[Msg, None, Tuple[int, int]]:
pin_tip = mxsc.pin_tip
yield from bps.trigger(pin_tip, wait=True)
found_tip = yield from bps.rd(pin_tip)
Expand All @@ -163,3 +190,21 @@ def wait_for_tip_to_be_found(mxsc: MXSC) -> Generator[Msg, None, Tuple[int, int]
f"No pin found after {pin_tip.validity_timeout.get()} seconds"
)
return found_tip


def wait_for_tip_to_be_found_ophyd(
ophyd_pin_tip_detection: PinTipDetection,
) -> Generator[Msg, None, Tuple[int, int]]:
found_tip = yield from bps.rd(ophyd_pin_tip_detection)

LOGGER.info("Pin tip not found, waiting a second and trying again")

if found_tip == ophyd_pin_tip_detection.INVALID_POSITION:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand correctly that this will be adressed in #1069?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

# Wait a second and then retry
yield from bps.sleep(1)
found_tip = yield from bps.rd(ophyd_pin_tip_detection)

if found_tip == ophyd_pin_tip_detection.INVALID_POSITION:
raise WarningException("No pin found")

return found_tip # type: ignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from dodal.devices.flux import Flux
from dodal.devices.oav.oav_detector import OAV
from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
from dodal.devices.oav.pin_image_recognition import PinTipDetection
from dodal.devices.s4_slit_gaps import S4SlitGaps
from dodal.devices.smargon import Smargon
from dodal.devices.synchrotron import Synchrotron
Expand Down Expand Up @@ -71,6 +72,7 @@ class GridDetectThenXRayCentreComposite:
fast_grid_scan: FastGridScan
flux: Flux
oav: OAV
pin_tip_detection: PinTipDetection
smargon: Smargon
synchrotron: Synchrotron
s4_slit_gaps: S4SlitGaps
Expand Down Expand Up @@ -131,6 +133,7 @@ def run_grid_detection_plan(
backlight=composite.backlight,
oav=composite.oav,
smargon=composite.smargon,
pin_tip_detection=composite.pin_tip_detection,
)

yield from grid_detection_plan(
Expand Down
13 changes: 9 additions & 4 deletions src/hyperion/experiment_plans/oav_grid_detection_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@
from bluesky.preprocessors import finalize_wrapper
from dodal.devices.backlight import Backlight
from dodal.devices.oav.oav_detector import OAV
from dodal.devices.oav.pin_image_recognition import PinTipDetection
from dodal.devices.smargon import Smargon

from hyperion.device_setup_plans.setup_oav import (
get_move_required_so_that_beam_is_at_pixel,
pre_centring_setup_oav,
wait_for_tip_to_be_found,
wait_for_tip_to_be_found_ad_mxsc,
)
from hyperion.log import LOGGER
from hyperion.parameters.constants import OAV_REFRESH_DELAY
from hyperion.parameters.constants import (
OAV_REFRESH_DELAY,
)
from hyperion.utils.context import device_composite_from_context

if TYPE_CHECKING:
Expand All @@ -33,6 +36,7 @@ class OavGridDetectionComposite:
backlight: Backlight
oav: OAV
smargon: Smargon
pin_tip_detection: PinTipDetection


def create_devices(context: BlueskyContext) -> OavGridDetectionComposite:
Expand Down Expand Up @@ -83,12 +87,13 @@ def grid_detection_main_plan(
"""
oav: OAV = composite.oav
smargon: Smargon = composite.smargon

LOGGER.info("OAV Centring: Starting grid detection centring")

yield from bps.wait()

# Set relevant PVs to whatever the config dictates.
yield from pre_centring_setup_oav(oav, parameters)
yield from pre_centring_setup_oav(oav, parameters, oav.mxsc)

LOGGER.info("OAV Centring: Camera set up")

Expand All @@ -109,7 +114,7 @@ def grid_detection_main_plan(
# See #673 for improvements
yield from bps.sleep(OAV_REFRESH_DELAY)

tip_x_px, tip_y_px = yield from wait_for_tip_to_be_found(oav.mxsc)
tip_x_px, tip_y_px = yield from wait_for_tip_to_be_found_ad_mxsc(oav.mxsc)

LOGGER.info(f"Tip is at x,y: {tip_x_px},{tip_y_px}")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,17 @@ def pin_centre_then_xray_centre_plan(
oav_config_file = parameters.experiment_params.oav_centring_file

pin_tip_centring_composite = PinTipCentringComposite(
oav=composite.oav, smargon=composite.smargon, backlight=composite.backlight
oav=composite.oav,
smargon=composite.smargon,
backlight=composite.backlight,
pin_tip_detection=composite.pin_tip_detection,
)

yield from pin_tip_centre_plan(
pin_tip_centring_composite,
parameters.experiment_params.tip_offset_microns,
oav_config_file,
parameters.experiment_params.use_ophyd_pin_tip_detect,
)

grid_detect_params = create_parameters_for_grid_detection(parameters)
Expand Down
45 changes: 34 additions & 11 deletions src/hyperion/experiment_plans/pin_tip_centring_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
import numpy as np
from blueapi.core import BlueskyContext
from bluesky.utils import Msg
from dodal.devices.areadetector.plugins.MXSC import PinTipDetect
from dodal.devices.areadetector.plugins.MXSC import MXSC, PinTipDetect
from dodal.devices.backlight import Backlight
from dodal.devices.oav.oav_detector import OAV
from dodal.devices.oav.oav_parameters import OAV_CONFIG_JSON, OAVParameters
from dodal.devices.oav.pin_image_recognition import PinTipDetection
from dodal.devices.smargon import Smargon

from hyperion.device_setup_plans.setup_oav import (
Pixel,
get_move_required_so_that_beam_is_at_pixel,
pre_centring_setup_oav,
wait_for_tip_to_be_found,
wait_for_tip_to_be_found_ad_mxsc,
wait_for_tip_to_be_found_ophyd,
)
from hyperion.exceptions import WarningException
from hyperion.log import LOGGER
Expand All @@ -32,21 +34,24 @@ class PinTipCentringComposite:
backlight: Backlight
oav: OAV
smargon: Smargon
pin_tip_detection: PinTipDetection


def create_devices(context: BlueskyContext) -> PinTipCentringComposite:
return device_composite_from_context(context, PinTipCentringComposite)


def trigger_and_return_pin_tip(pin_tip: PinTipDetect) -> Generator[Msg, None, Pixel]:
def trigger_and_return_pin_tip(
pin_tip: PinTipDetect | PinTipDetection,
) -> Generator[Msg, None, Pixel]:
yield from bps.trigger(pin_tip, wait=True)
tip_x_y_px = yield from bps.rd(pin_tip)
LOGGER.info(f"Pin tip found at {tip_x_y_px}")
return tip_x_y_px


def move_pin_into_view(
oav: OAV,
pin_tip_device: PinTipDetect | PinTipDetection,
smargon: Smargon,
step_size_mm: float = DEFAULT_STEP_SIZE,
max_steps: int = 2,
Expand All @@ -56,7 +61,7 @@ def move_pin_into_view(
would take it past its limit, it moves to the limit instead.

Args:
oav (OAV): The OAV to detect the tip with
pin_tip_device (PinTipDetect | PinTipDetection): The device being used to detect the pin
smargon (Smargon): The gonio to move the tip
step_size (float, optional): Distance to move the gonio (in mm) for each
step of the search. Defaults to 0.5.
Expand All @@ -70,10 +75,10 @@ def move_pin_into_view(
"""

def pin_tip_valid(pin_x: float):
return pin_x != 0 and pin_x != oav.mxsc.pin_tip.INVALID_POSITION[0]
return pin_x != 0 and pin_x != pin_tip_device.INVALID_POSITION[0]

for _ in range(max_steps):
tip_x_px, tip_y_px = yield from trigger_and_return_pin_tip(oav.mxsc.pin_tip)
tip_x_px, tip_y_px = yield from trigger_and_return_pin_tip(pin_tip_device)

if pin_tip_valid(tip_x_px):
return (tip_x_px, tip_y_px)
Expand All @@ -97,7 +102,7 @@ def pin_tip_valid(pin_x: float):
# Some time for the view to settle after the move
yield from bps.sleep(OAV_REFRESH_DELAY)

tip_x_px, tip_y_px = yield from trigger_and_return_pin_tip(oav.mxsc.pin_tip)
tip_x_px, tip_y_px = yield from trigger_and_return_pin_tip(pin_tip_device)

if not pin_tip_valid(tip_x_px):
raise WarningException(
Expand Down Expand Up @@ -130,6 +135,7 @@ def pin_tip_centre_plan(
composite: PinTipCentringComposite,
tip_offset_microns: float,
oav_config_file: str = OAV_CONFIG_JSON,
use_ophyd_pin_tip_detect: bool = False,
):
"""Finds the tip of the pin and moves to roughly the centre based on this tip. Does
this at both the current omega angle and +90 deg from this angle so as to get a
Expand All @@ -138,11 +144,21 @@ def pin_tip_centre_plan(
Args:
tip_offset_microns (float): The x offset from the tip where the centre is assumed
to be.
use_ophyd_pin_tip_detect (bool): If true use the ophyd device to find the tip,
rather than the AD plugin.
"""
oav: OAV = composite.oav
smargon: Smargon = composite.smargon
oav_params = OAVParameters("pinTipCentring", oav_config_file)

if use_ophyd_pin_tip_detect:
pin_tip_setup = composite.pin_tip_detection
pin_tip_detect = composite.pin_tip_detection
else:
pin_tip_setup = oav.mxsc
pin_tip_detect = oav.mxsc.pin_tip

assert oav.parameters.micronsPerXPixel is not None
tip_offset_px = int(tip_offset_microns / oav.parameters.micronsPerXPixel)

def offset_and_move(tip: Pixel):
Expand All @@ -159,9 +175,11 @@ def offset_and_move(tip: Pixel):
# See #673 for improvements
yield from bps.sleep(0.3)

yield from pre_centring_setup_oav(oav, oav_params)
# Set up the old pin tip centring as we will need it for grid detection. Remove once #1068 is done
yield from pre_centring_setup_oav(oav, oav_params, oav.mxsc)
yield from pre_centring_setup_oav(oav, oav_params, pin_tip_setup)

tip = yield from move_pin_into_view(oav, smargon)
tip = yield from move_pin_into_view(pin_tip_detect, smargon)
yield from offset_and_move(tip)

yield from bps.mvr(smargon.omega, 90)
Expand All @@ -170,5 +188,10 @@ def offset_and_move(tip: Pixel):
# See #673 for improvements
yield from bps.sleep(0.3)

tip = yield from wait_for_tip_to_be_found(oav.mxsc)
if isinstance(pin_tip_setup, MXSC):
LOGGER.info("Acquiring pin-tip from AD MXSC plugin")
tip = yield from wait_for_tip_to_be_found_ad_mxsc(pin_tip_setup)
elif isinstance(pin_tip_setup, PinTipDetection):
LOGGER.info("Acquiring pin-tip from ophyd device")
tip = yield from wait_for_tip_to_be_found_ophyd(pin_tip_setup)
yield from offset_and_move(tip)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from dodal.devices.flux import Flux
from dodal.devices.focusing_mirror import FocusingMirror, VFMMirrorVoltages
from dodal.devices.oav.oav_detector import OAV
from dodal.devices.oav.pin_image_recognition import PinTipDetection
from dodal.devices.s4_slit_gaps import S4SlitGaps
from dodal.devices.smargon import Smargon
from dodal.devices.synchrotron import Synchrotron
Expand Down Expand Up @@ -60,6 +61,7 @@ class WaitForRobotLoadThenCentreComposite:
fast_grid_scan: FastGridScan
flux: Flux
oav: OAV
pin_tip_detection: PinTipDetection
smargon: Smargon
synchrotron: Synchrotron
s4_slit_gaps: S4SlitGaps
Expand Down Expand Up @@ -132,6 +134,7 @@ def wait_for_robot_load_then_centre_plan(
fast_grid_scan=composite.fast_grid_scan,
flux=composite.flux,
oav=composite.oav,
pin_tip_detection=composite.pin_tip_detection,
smargon=composite.smargon,
synchrotron=composite.synchrotron,
s4_slit_gaps=composite.s4_slit_gaps,
Expand Down
Loading