From 8d05fa12d5e2465411d95000e6b6467be6c841f5 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Mon, 11 Mar 2024 16:25:59 +0000 Subject: [PATCH 1/4] (#1227) Filter none values out of pin edge detection and add tests --- .../oav_grid_detection_plan.py | 32 ++++++++---- .../test_grid_detection_plan.py | 51 ++++++++++++++++++- 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/hyperion/experiment_plans/oav_grid_detection_plan.py b/src/hyperion/experiment_plans/oav_grid_detection_plan.py index b94a3dc4f..b92a385ad 100644 --- a/src/hyperion/experiment_plans/oav_grid_detection_plan.py +++ b/src/hyperion/experiment_plans/oav_grid_detection_plan.py @@ -2,7 +2,7 @@ import dataclasses import math -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Tuple import bluesky.plan_stubs as bps import bluesky.preprocessors as bpp @@ -11,6 +11,7 @@ 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.oav.pin_image_recognition.utils import NONE_VALUE from dodal.devices.smargon import Smargon from hyperion.device_setup_plans.setup_oav import ( @@ -40,6 +41,23 @@ def create_devices(context: BlueskyContext) -> OavGridDetectionComposite: return device_composite_from_context(context, OavGridDetectionComposite) +def get_min_and_max_y_of_pin( + top: np.ndarray, bottom: np.ndarray, full_image_height_px: int +) -> Tuple[int, int]: + """Gives the minimum and maximum y that would cover the whole pin. + + First filters out where no edge was found or the edge covers the full image. + If this results in no edges found then returns a min/max that covers the full image + """ + filtered_top = top[np.where((top != 0) & (top != NONE_VALUE))] + min_y = min(filtered_top) if len(filtered_top) else 0 + filtered_bottom = bottom[ + np.where((bottom != full_image_height_px) & (bottom != NONE_VALUE)) + ] + max_y = max(filtered_bottom) if len(filtered_bottom) else full_image_height_px + return min_y, max_y + + @bpp.run_decorator() def grid_detection_plan( composite: OavGridDetectionComposite, @@ -106,14 +124,10 @@ def grid_detection_plan( top_edge = top_edge[tip_x_px : tip_x_px + grid_width_pixels] bottom_edge = bottom_edge[tip_x_px : tip_x_px + grid_width_pixels] - # the edge detection line can jump to the edge of the image sometimes, filter - # those points out, and if empty after filter use the whole image - filtered_top = list(top_edge[top_edge != 0]) or [0] - filtered_bottom = list(bottom_edge[bottom_edge != full_image_height_px]) or [ - full_image_height_px - ] - min_y = min(filtered_top) - max_y = max(filtered_bottom) + min_y, max_y = get_min_and_max_y_of_pin( + top_edge, bottom_edge, full_image_height_px + ) + grid_height_px = max_y - min_y y_steps: int = math.ceil(grid_height_px / box_size_y_pixels) diff --git a/tests/unit_tests/experiment_plans/test_grid_detection_plan.py b/tests/unit_tests/experiment_plans/test_grid_detection_plan.py index 5ce300208..be6596fb9 100644 --- a/tests/unit_tests/experiment_plans/test_grid_detection_plan.py +++ b/tests/unit_tests/experiment_plans/test_grid_detection_plan.py @@ -10,12 +10,13 @@ from dodal.devices.oav.oav_detector import OAVConfigParams from dodal.devices.oav.oav_parameters import OAVParameters from dodal.devices.oav.pin_image_recognition import PinTipDetection -from dodal.devices.oav.pin_image_recognition.utils import SampleLocation +from dodal.devices.oav.pin_image_recognition.utils import NONE_VALUE, SampleLocation from dodal.devices.smargon import Smargon from hyperion.exceptions import WarningException from hyperion.experiment_plans.oav_grid_detection_plan import ( OavGridDetectionComposite, + get_min_and_max_y_of_pin, grid_detection_plan, ) from hyperion.external_interaction.callbacks.grid_detection_callback import ( @@ -360,3 +361,51 @@ def record_set(msg: Msg): assert abs_sets["snapshot.top_left_y"][0] == expected_min_y assert abs_sets["snapshot.num_boxes_y"][0] == expected_y_steps + + +@pytest.mark.parametrize( + "top, bottom, expected_min, expected_max", + [ + (np.array([1, 2, 5]), np.array([8, 9, 40]), 1, 40), + (np.array([9, 6, 10]), np.array([152, 985, 72]), 6, 985), + (np.array([5, 1]), np.array([999, 1056, 896, 10]), 1, 1056), + ], +) +def test_given_array_with_valid_top_and_bottom_then_min_and_max_as_expected( + top, bottom, expected_min, expected_max +): + min_y, max_y = get_min_and_max_y_of_pin(top, bottom, 100) + assert min_y == expected_min + assert max_y == expected_max + + +@pytest.mark.parametrize( + "top, bottom, expected_min, expected_max", + [ + (np.array([1, 2, NONE_VALUE]), np.array([8, 9, 40]), 1, 40), + (np.array([6, NONE_VALUE, 10]), np.array([152, 985, NONE_VALUE]), 6, 985), + (np.array([1, 5]), np.array([999, 1056, NONE_VALUE, 10]), 1, 1056), + ], +) +def test_given_array_with_some_invalid_top_and_bottom_sections_then_min_and_max_as_expected( + top, bottom, expected_min, expected_max +): + min_y, max_y = get_min_and_max_y_of_pin(top, bottom, 100) + assert min_y == expected_min + assert max_y == expected_max + + +@pytest.mark.parametrize( + "top, bottom, expected_min, expected_max", + [ + (np.array([NONE_VALUE, 0, NONE_VALUE]), np.array([100, NONE_VALUE]), 0, 100), + (np.array([NONE_VALUE, NONE_VALUE]), np.array([100, NONE_VALUE]), 0, 100), + (np.array([0, NONE_VALUE]), np.array([NONE_VALUE]), 0, 100), + ], +) +def test_given_array_with_all_invalid_top_and_bottom_sections_then_min_and_max_is_full_image( + top, bottom, expected_min, expected_max +): + min_y, max_y = get_min_and_max_y_of_pin(top, bottom, 100) + assert min_y == expected_min + assert max_y == expected_max From bdad2d356834d77d49a4931b7987bf80d1f36e4a Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Mon, 11 Mar 2024 16:35:38 +0000 Subject: [PATCH 2/4] (#1227) Remove unused variables from grid detection --- .../oav_grid_detection_plan.py | 35 +++---------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/src/hyperion/experiment_plans/oav_grid_detection_plan.py b/src/hyperion/experiment_plans/oav_grid_detection_plan.py index b92a385ad..1743cc87b 100644 --- a/src/hyperion/experiment_plans/oav_grid_detection_plan.py +++ b/src/hyperion/experiment_plans/oav_grid_detection_plan.py @@ -15,7 +15,6 @@ 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, ) @@ -92,9 +91,6 @@ def grid_detection_plan( LOGGER.info("OAV Centring: Camera set up") - start_positions = [] - box_numbers = [] - assert isinstance(oav.parameters.micronsPerXPixel, float) box_size_x_pixels = box_size_um / oav.parameters.micronsPerXPixel assert isinstance(oav.parameters.micronsPerYPixel, float) @@ -145,19 +141,15 @@ def grid_detection_plan( LOGGER.info(f"Drawing snapshot {grid_width_pixels} by {grid_height_px}") - boxes = ( - math.ceil(grid_width_pixels / box_size_x_pixels), - y_steps, - ) - box_numbers.append(boxes) + x_steps = (math.ceil(grid_width_pixels / box_size_x_pixels),) upper_left = (tip_x_px, min_y) yield from bps.abs_set(oav.snapshot.top_left_x, upper_left[0]) yield from bps.abs_set(oav.snapshot.top_left_y, upper_left[1]) yield from bps.abs_set(oav.snapshot.box_width, box_size_x_pixels) - yield from bps.abs_set(oav.snapshot.num_boxes_x, boxes[0]) - yield from bps.abs_set(oav.snapshot.num_boxes_y, boxes[1]) + yield from bps.abs_set(oav.snapshot.num_boxes_x, x_steps) + yield from bps.abs_set(oav.snapshot.num_boxes_y, y_steps) snapshot_filename = snapshot_template.format(angle=abs(angle)) @@ -171,23 +163,6 @@ def grid_detection_plan( yield from bps.read(smargon) yield from bps.save() - # The first frame is taken at the centre of the first box - centre_of_first_box = ( - int(upper_left[0] + box_size_x_pixels / 2), - int(upper_left[1] + box_size_y_pixels / 2), - ) - - position = yield from get_move_required_so_that_beam_is_at_pixel( - smargon, centre_of_first_box, oav.parameters + LOGGER.info( + f"Grid calculated at {angle}: {x_steps}px by {y_steps}px starting at {upper_left}px" ) - start_positions.append(position) - - LOGGER.info( - f"Calculated start position {start_positions[0][0], start_positions[0][1], start_positions[1][2]}" - ) - - LOGGER.info( - f"Calculated number of steps {box_numbers[0][0], box_numbers[0][1], box_numbers[1][1]}" - ) - - LOGGER.info(f"Step sizes: {box_size_um, box_size_um, box_size_um}") From 75d1ccdb89c419e206fee172e0c5d9c509463d70 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Mon, 11 Mar 2024 16:41:28 +0000 Subject: [PATCH 3/4] (#1227) Tidy up and fix minor mistake in grid detection --- src/hyperion/experiment_plans/oav_grid_detection_plan.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hyperion/experiment_plans/oav_grid_detection_plan.py b/src/hyperion/experiment_plans/oav_grid_detection_plan.py index 1743cc87b..1e15a319c 100644 --- a/src/hyperion/experiment_plans/oav_grid_detection_plan.py +++ b/src/hyperion/experiment_plans/oav_grid_detection_plan.py @@ -71,8 +71,8 @@ def grid_detection_plan( encompass the whole of the sample as it appears in the OAV. Args: - parameters (OAVParamaters): Object containing paramters for setting up the OAV - out_parameters (GridScanParams): The returned parameters for the gridscan + composite (OavGridDetectionComposite): Composite containing devices for doing a grid detection. + parameters (OAVParameters): Object containing parameters for setting up the OAV snapshot_template (str): A template for the name of the snapshots, expected to be filled in with an angle snapshot_dir (str): The location to save snapshots grid_width_microns (int): The width of the grid to scan in microns @@ -137,7 +137,7 @@ def grid_detection_plan( y_steps += 1 min_y -= box_size_y_pixels / 2 max_y += box_size_y_pixels / 2 - grid_height_px += 1 + grid_height_px += box_size_y_pixels LOGGER.info(f"Drawing snapshot {grid_width_pixels} by {grid_height_px}") From 97194ac104cd1b4bcf33cbf9a5b9e440455fb42f Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Mon, 11 Mar 2024 17:10:58 +0000 Subject: [PATCH 4/4] (#1227) Fix typo --- src/hyperion/experiment_plans/oav_grid_detection_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hyperion/experiment_plans/oav_grid_detection_plan.py b/src/hyperion/experiment_plans/oav_grid_detection_plan.py index d282bbdbc..43cb806cc 100644 --- a/src/hyperion/experiment_plans/oav_grid_detection_plan.py +++ b/src/hyperion/experiment_plans/oav_grid_detection_plan.py @@ -143,7 +143,7 @@ def grid_detection_plan( LOGGER.info(f"Drawing snapshot {grid_width_pixels} by {grid_height_px}") - x_steps = (math.ceil(grid_width_pixels / box_size_x_pixels),) + x_steps = math.ceil(grid_width_pixels / box_size_x_pixels) upper_left = (tip_x_px, min_y)