From 8fa3ad9774cc153237f68912c46001563a52d7b9 Mon Sep 17 00:00:00 2001 From: Shihab Suliman Date: Wed, 22 Jan 2025 12:09:11 +0000 Subject: [PATCH] Add files to staged --- .../epics/eiger/det_dist_to_beam_converter.py | 61 ++++++++++ tests/epics/eiger/test_beam_converter.py | 106 ++++++++++++++++++ tests/epics/eiger/test_lookup_table_2.txt | 5 + 3 files changed, 172 insertions(+) create mode 100644 src/ophyd_async/epics/eiger/det_dist_to_beam_converter.py create mode 100644 tests/epics/eiger/test_beam_converter.py create mode 100644 tests/epics/eiger/test_lookup_table_2.txt diff --git a/src/ophyd_async/epics/eiger/det_dist_to_beam_converter.py b/src/ophyd_async/epics/eiger/det_dist_to_beam_converter.py new file mode 100644 index 000000000..25f4977ae --- /dev/null +++ b/src/ophyd_async/epics/eiger/det_dist_to_beam_converter.py @@ -0,0 +1,61 @@ +from enum import Enum + +from numpy import interp, loadtxt + + +class Axis(Enum): + X_AXIS = 1 + Y_AXIS = 2 + + +class DetectorDistanceToBeamXYConverter: + def __init__(self, lookup_file: str): + self.lookup_file: str = lookup_file + self.lookup_table_values: list = self.parse_table() + + def get_beam_xy_from_det_dist(self, det_dist_mm: float, beam_axis: Axis) -> float: + beam_axis_values = self.lookup_table_values[beam_axis.value] + det_dist_array = self.lookup_table_values[0] + return float(interp(det_dist_mm, det_dist_array, beam_axis_values)) + + def get_beam_axis_pixels( + self, + det_distance: float, + image_size_pixels: int, + det_dim: float, + beam_axis: Axis, + ) -> float: + beam_mm = self.get_beam_xy_from_det_dist(det_distance, beam_axis) + return beam_mm * image_size_pixels / det_dim + + def get_beam_y_pixels( + self, det_distance: float, image_size_pixels: int, det_dim: float + ) -> float: + return self.get_beam_axis_pixels( + det_distance, image_size_pixels, det_dim, Axis.Y_AXIS + ) + + def get_beam_x_pixels( + self, det_distance: float, image_size_pixels: int, det_dim: float + ) -> float: + return self.get_beam_axis_pixels( + det_distance, image_size_pixels, det_dim, Axis.X_AXIS + ) + + def reload_lookup_table(self): + self.lookup_table_values = self.parse_table() + + def parse_table(self) -> list: + rows = loadtxt(self.lookup_file, delimiter=" ", comments=["#", "Units"]) + columns = list(zip(*rows, strict=False)) + + return columns + + def __eq__(self, other): + if not isinstance(other, DetectorDistanceToBeamXYConverter): + return NotImplemented + if self.lookup_file != other.lookup_file: + return False + if self.lookup_table_values != other.lookup_table_values: + return False + return True diff --git a/tests/epics/eiger/test_beam_converter.py b/tests/epics/eiger/test_beam_converter.py new file mode 100644 index 000000000..1cc9c6c48 --- /dev/null +++ b/tests/epics/eiger/test_beam_converter.py @@ -0,0 +1,106 @@ +from unittest.mock import Mock, patch + +import pytest + +from ophyd_async.epics.eiger.det_dist_to_beam_converter import ( + Axis, + DetectorDistanceToBeamXYConverter, +) + +LOOKUP_TABLE_TEST_VALUES = [(100.0, 200.0), (150.0, 151.0), (160.0, 165.0)] + + +@pytest.fixture +def fake_converter(): + with patch.object( + DetectorDistanceToBeamXYConverter, + "parse_table", + return_value=LOOKUP_TABLE_TEST_VALUES, + ): + yield DetectorDistanceToBeamXYConverter("test.txt") + + +def test_converter_eq(): + test_file = "tests/epics/eiger/test_lookup_table.txt" + test_converter = DetectorDistanceToBeamXYConverter(test_file) + test_converter_dupe = DetectorDistanceToBeamXYConverter(test_file) + test_file_2 = "tests/epics/eiger/test_lookup_table_2.txt" + test_converter_2 = DetectorDistanceToBeamXYConverter(test_file_2) + assert test_converter != 1 + assert test_converter == test_converter_dupe + assert test_converter != test_converter_2 + previous_value = test_converter_dupe.lookup_table_values[0] + test_converter_dupe.lookup_table_values[0] = (7.5, 23.5) + assert test_converter != test_converter_dupe + test_converter_dupe.lookup_table_values[0] = previous_value + + +@pytest.mark.parametrize( + "detector_distance, axis, expected_value", + [ + (100.0, Axis.Y_AXIS, 160.0), + (200.0, Axis.X_AXIS, 151.0), + (150.0, Axis.X_AXIS, 150.5), + (190.0, Axis.Y_AXIS, 164.5), + ], +) +def test_interpolate_beam_xy_from_det_distance( + fake_converter: DetectorDistanceToBeamXYConverter, + detector_distance: float, + axis: Axis, + expected_value: float, +): + assert isinstance( + fake_converter.get_beam_xy_from_det_dist(detector_distance, axis), float + ) + + assert ( + fake_converter.get_beam_xy_from_det_dist(detector_distance, axis) + == expected_value + ) + + +def test_get_beam_in_pixels(fake_converter: DetectorDistanceToBeamXYConverter): + detector_distance = 100.0 + image_size_pixels = 100 + detector_dimensions = 200.0 + interpolated_x_value = 150.0 + interpolated_y_value = 160.0 + + def mock_callback(dist: float, axis: Axis): + match axis: + case Axis.X_AXIS: + return interpolated_x_value + case Axis.Y_AXIS: + return interpolated_y_value + + fake_converter.get_beam_xy_from_det_dist = Mock() + fake_converter.get_beam_xy_from_det_dist.side_effect = mock_callback + expected_y_value = interpolated_y_value * image_size_pixels / detector_dimensions + expected_x_value = interpolated_x_value * image_size_pixels / detector_dimensions + + calculated_y_value = fake_converter.get_beam_y_pixels( + detector_distance, image_size_pixels, detector_dimensions + ) + + assert calculated_y_value == expected_y_value + assert ( + fake_converter.get_beam_x_pixels( + detector_distance, image_size_pixels, detector_dimensions + ) + == expected_x_value + ) + + +def test_parse_table(): + test_file = "tests/epics/eiger/test_lookup_table.txt" + test_converter = DetectorDistanceToBeamXYConverter(test_file) + + assert test_converter.lookup_file == test_file + assert test_converter.lookup_table_values == LOOKUP_TABLE_TEST_VALUES + assert test_converter.parse_table() == LOOKUP_TABLE_TEST_VALUES + + test_converter.reload_lookup_table() + + assert test_converter.lookup_file == test_file + assert test_converter.lookup_table_values == LOOKUP_TABLE_TEST_VALUES diff --git a/tests/epics/eiger/test_lookup_table_2.txt b/tests/epics/eiger/test_lookup_table_2.txt new file mode 100644 index 000000000..16fa297a0 --- /dev/null +++ b/tests/epics/eiger/test_lookup_table_2.txt @@ -0,0 +1,5 @@ +# Beam converter lookup table for testing + +Units det_dist beam_x beam_y +100.0 150.0 160.0 +200.0 151.0 165.0