From b7e7afe53e6cb658da58174bf6409882ccc2af6e Mon Sep 17 00:00:00 2001 From: Emily Massahud Date: Tue, 12 Mar 2024 14:40:36 +1100 Subject: [PATCH 1/8] increase max-line-length --- src/pyFAI/ring_extraction.py | 77 +++++++++--------------------------- 1 file changed, 19 insertions(+), 58 deletions(-) diff --git a/src/pyFAI/ring_extraction.py b/src/pyFAI/ring_extraction.py index c118a1586..7f27aa35b 100644 --- a/src/pyFAI/ring_extraction.py +++ b/src/pyFAI/ring_extraction.py @@ -42,6 +42,7 @@ import logging +import random from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Optional @@ -58,11 +59,7 @@ class RingExtraction: """Class to perform extraction of control points from a calibration image.""" - def __init__( - self, - single_geometry: SingleGeometry, - massif: Optional[Massif] = None, - ): + def __init__(self, single_geometry: SingleGeometry, massif: Optional[Massif] = None): """ Parameters ---------- @@ -91,9 +88,7 @@ def __init__( self.two_theta_values = self._get_unique_two_theta_values_in_image() def extract_control_points( - self, - max_number_of_rings: Optional[int] = None, - points_per_degree: float = 1, + self, max_number_of_rings: Optional[int] = None, points_per_degree: float = 1 ) -> ControlPoints: """ Primary method of RingExtraction class. Runs extract_control_points_in_one_ring for all @@ -120,13 +115,9 @@ def extract_control_points( tasks = {} with ThreadPoolExecutor() as executor: - for ring_index in range( - min(max_number_of_rings, self.two_theta_values.size) - ): + for ring_index in range(min(max_number_of_rings, self.two_theta_values.size)): future = executor.submit( - self.extract_list_of_peaks_in_one_ring, - ring_index, - points_per_degree, + self.extract_list_of_peaks_in_one_ring, ring_index, points_per_degree ) tasks[future] = ring_index @@ -139,9 +130,7 @@ def extract_control_points( return control_points def extract_list_of_peaks_in_one_ring( - self, - ring_index: int, - points_per_degree: float = 1.0 + self, ring_index: int, points_per_degree: float = 1.0 ) -> Optional[list[tuple[float, float]]]: """ Using massif.peaks_from_area, get all pixel coordinates inside a mask of pixels around a @@ -162,36 +151,25 @@ def extract_list_of_peaks_in_one_ring( peaks at given ring index """ marching_squares_algorithm = marchingsquares.MarchingSquaresMergeImpl( - self.two_theta_array, - mask=self.detector.mask, - use_minmax_cache=True, + self.two_theta_array, mask=self.detector.mask, use_minmax_cache=True ) initial_mask = self._create_mask_around_ring(ring_index) mask_size = initial_mask.sum(dtype=int) if mask_size > 0: - final_mask, upper_limit = self._remove_low_intensity_pixels_from_mask( - initial_mask - ) + final_mask, upper_limit = self._remove_low_intensity_pixels_from_mask(initial_mask) pixels_at_two_theta_level = marching_squares_algorithm.find_pixels( self.two_theta_values[ring_index] ) - seeds = set( - (i[0], i[1]) - for i in pixels_at_two_theta_level - if final_mask[i[0], i[1]] - ) + seeds = set((i[0], i[1]) for i in pixels_at_two_theta_level if final_mask[i[0], i[1]]) num_points_to_keep = self._calculate_num_of_points_to_keep( - pixels_at_two_theta_level, - points_per_degree, + pixels_at_two_theta_level, points_per_degree ) if num_points_to_keep > 0: - min_distance_between_control_points = ( - len(seeds) / 2.0 / num_points_to_keep - ) + min_distance_between_control_points = len(seeds) / 2.0 / num_points_to_keep # original code has a comment here which seems outdates, but I also didn't # understand where this calculation comes from, so I just left it as is @@ -230,11 +208,7 @@ def _get_unique_two_theta_values_in_image(self) -> np.ndarray: ) largest_two_theta_in_image = self.two_theta_array.max() two_theta_values_in_image = np.array( - [ - two_theta - for two_theta in two_theta_values - if two_theta <= largest_two_theta_in_image - ] + [two_theta for two_theta in two_theta_values if two_theta <= largest_two_theta_in_image] ) return two_theta_values_in_image @@ -255,14 +229,10 @@ def _create_mask_around_ring(self, ring_index: int) -> np.ndarray: Mask of valid pixels around each ring """ two_theta_min_max = self._get_two_theta_min_max(ring_index) - two_theta_min, two_theta_max = ( - two_theta_min_max["min"], - two_theta_min_max["max"], - ) + two_theta_min, two_theta_max = (two_theta_min_max["min"], two_theta_min_max["max"]) initial_mask = np.logical_and( - self.two_theta_array >= two_theta_min, - self.two_theta_array < two_theta_max, + self.two_theta_array >= two_theta_min, self.two_theta_array < two_theta_max ) if self.detector.mask is not None: detector_mask_bool = self.detector.mask.astype(bool) @@ -289,8 +259,7 @@ def _get_two_theta_min_max(self, ring_index: int) -> dict[str, float]: delta_two_theta = (self.two_theta_values[1] - self.two_theta_values[0]) / 4 else: delta_two_theta = ( - self.two_theta_values[ring_index] - - self.two_theta_values[ring_index - 1] + self.two_theta_values[ring_index] - self.two_theta_values[ring_index - 1] ) / 4 two_theta_min = self.two_theta_values[ring_index] - delta_two_theta @@ -298,9 +267,7 @@ def _get_two_theta_min_max(self, ring_index: int) -> dict[str, float]: return {"min": two_theta_min, "max": two_theta_max} - def _remove_low_intensity_pixels_from_mask( - self, mask: np.ndarray - ) -> tuple[np.ndarray, float]: + def _remove_low_intensity_pixels_from_mask(self, mask: np.ndarray) -> tuple[np.ndarray, float]: """ Creates a final mask of valid pixels to be used in peak extraction, by removing low intensity pixels from the initial mask @@ -320,9 +287,7 @@ def _remove_low_intensity_pixels_from_mask( high_pixel_intensity_coords = self.image > upper_limit final_mask = np.logical_and(high_pixel_intensity_coords, mask) final_mask_size = final_mask.sum(dtype=int) - minimum_mask_size = ( - 1000 # copied this from original method, don't know why this number - ) + minimum_mask_size = 1000 # copied this from original method, don't know why this number if final_mask_size < minimum_mask_size: upper_limit = mean final_mask = np.logical_and(self.image > upper_limit, mask) @@ -355,9 +320,7 @@ def _calculate_mean_and_std_of_intensities_in_mask( return mean, std def _calculate_num_of_points_to_keep( - self, - pixels_at_two_theta_level: np.ndarray, - points_per_degree: float, + self, pixels_at_two_theta_level: np.ndarray, points_per_degree: float ) -> int: """ Calculate number of azimuthal degrees in ring, then multiply by self.points_per_degree @@ -375,9 +338,7 @@ def _calculate_num_of_points_to_keep( Number of points to keep as control points """ image_shape = self.image.shape - azimuthal_angles_array = self.single_geometry.geometry_refinement.chiArray( - image_shape - ) + azimuthal_angles_array = self.single_geometry.geometry_refinement.chiArray(image_shape) azimuthal_degrees_array_in_ring = azimuthal_angles_array[ pixels_at_two_theta_level[:, 0].clip(0, image_shape[0]), pixels_at_two_theta_level[:, 1].clip(0, image_shape[1]), From fb6ad73bed2ef843062d7243ba2681f287a45f2b Mon Sep 17 00:00:00 2001 From: Emily Massahud Date: Tue, 12 Mar 2024 14:46:14 +1100 Subject: [PATCH 2/8] add _calculate_min_distance_between_control_points --- src/pyFAI/ring_extraction.py | 52 +++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/pyFAI/ring_extraction.py b/src/pyFAI/ring_extraction.py index 7f27aa35b..8dea17b74 100644 --- a/src/pyFAI/ring_extraction.py +++ b/src/pyFAI/ring_extraction.py @@ -169,9 +169,11 @@ def extract_list_of_peaks_in_one_ring( pixels_at_two_theta_level, points_per_degree ) if num_points_to_keep > 0: - min_distance_between_control_points = len(seeds) / 2.0 / num_points_to_keep - # original code has a comment here which seems outdates, but I also didn't - # understand where this calculation comes from, so I just left it as is + min_distance_between_control_points = ( + self._calculate_min_distance_between_control_points( + pixels_at_two_theta_level.tolist(), points_per_degree + ) + ) logger.info( "Extracting datapoints for ring %s (2theta = %.2f deg); searching for %i pts" @@ -347,3 +349,47 @@ def _calculate_num_of_points_to_keep( np.rad2deg(azimuthal_degrees_array_in_ring).round() ).size return int(number_of_azimuthal_degrees * points_per_degree) + + def _calculate_min_distance_between_control_points( + self, pixel_list_at_two_theta_level: list[list[int]], points_per_degree: float + ) -> float: + """ + Get a random value from the ring and subtract from beam_centre_coords to calculate ring + radius; grabbing any value from pixel_list_at_two_theta_level will lead to the same radius. + then calculate the distance between 2 points separated by 1 degree in ring circumference: + ring_radius * 0.0174533 (equals a degree in radians) + only good for shapes which are close to circular. + + Parameters + ---------- + pixel_list_at_two_theta_level : list[list[int]] + List of pixels in the image located in the ring at the current two theta value + + Returns + ------- + float + Minimum accepted distance between 2 control points + """ + + # TODO implementation for elliptical rings needs semi-minor axis to ensure the min value for + # arc, and ellipse centre, which differs from beam centre in elliptical projections + beam_centre_coords = self._get_beam_centre_coords() + random_point_in_ring = np.array(random.sample(pixel_list_at_two_theta_level, 1)[0]) + ring_radius_px = np.sqrt(sum(x**2 for x in (random_point_in_ring - beam_centre_coords))) + one_degree = 1 + ring_dist_in_one_deg = ring_radius_px * np.radians(one_degree) + return ring_dist_in_one_deg / points_per_degree + + def _get_beam_centre_coords(self) -> np.ndarray: + """ + Use AzimuthalIntegrator's method `getFit2D` to get the pixel coordinates of the beam centre + + Returns + ------- + np.ndarray + beam centre coordinates [y,x], to match pyFAI order. + """ + fit_2d = self.single_geometry.get_ai().getFit2D() + beam_centre_x = fit_2d["centerX"] + beam_centre_y = fit_2d["centerY"] + return np.array([beam_centre_y, beam_centre_x]) From bfff803b71d116f7074e89d5b45aa1a36da6f65b Mon Sep 17 00:00:00 2001 From: Emily Massahud Date: Tue, 12 Mar 2024 14:51:40 +1100 Subject: [PATCH 3/8] rename np to numpy --- src/pyFAI/ring_extraction.py | 60 +++++++------ src/pyFAI/test/test_ring_extraction.py | 120 +++++++++++-------------- 2 files changed, 82 insertions(+), 98 deletions(-) diff --git a/src/pyFAI/ring_extraction.py b/src/pyFAI/ring_extraction.py index 8dea17b74..e4d2cc632 100644 --- a/src/pyFAI/ring_extraction.py +++ b/src/pyFAI/ring_extraction.py @@ -46,7 +46,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Optional -import numpy as np +import numpy from silx.image import marchingsquares from .control_points import ControlPoints @@ -179,7 +179,7 @@ def extract_list_of_peaks_in_one_ring( "Extracting datapoints for ring %s (2theta = %.2f deg); searching for %i pts" " out of %i with I>%.1f, dmin=%.1f", ring_index, - np.degrees(self.two_theta_values[ring_index]), + numpy.degrees(self.two_theta_values[ring_index]), num_points_to_keep, final_mask.sum(dtype=int), upper_limit, @@ -196,25 +196,25 @@ def extract_list_of_peaks_in_one_ring( return None return None - def _get_unique_two_theta_values_in_image(self) -> np.ndarray: + def _get_unique_two_theta_values_in_image(self) -> numpy.ndarray: """ Calculates all two theta values covered by the image with the current detector and geometry Returns ------- - np.ndarray + numpy.ndarray array containing all two theta values for calibrant at present wavelength """ - two_theta_values = np.unique( - np.array([i for i in self.calibrant.get_2th() if i is not None]) + two_theta_values = numpy.unique( + numpy.array([i for i in self.calibrant.get_2th() if i is not None]) ) largest_two_theta_in_image = self.two_theta_array.max() - two_theta_values_in_image = np.array( + two_theta_values_in_image = numpy.array( [two_theta for two_theta in two_theta_values if two_theta <= largest_two_theta_in_image] ) return two_theta_values_in_image - def _create_mask_around_ring(self, ring_index: int) -> np.ndarray: + def _create_mask_around_ring(self, ring_index: int) -> numpy.ndarray: """ Creates a mask of valid pixels around each ring, of thickness equal to 1/2 the distance between the centre of two adjacent rings. @@ -227,13 +227,13 @@ def _create_mask_around_ring(self, ring_index: int) -> np.ndarray: Returns ------- - np.ndarray + numpy.ndarray Mask of valid pixels around each ring """ two_theta_min_max = self._get_two_theta_min_max(ring_index) two_theta_min, two_theta_max = (two_theta_min_max["min"], two_theta_min_max["max"]) - initial_mask = np.logical_and( + initial_mask = numpy.logical_and( self.two_theta_array >= two_theta_min, self.two_theta_array < two_theta_max ) if self.detector.mask is not None: @@ -254,7 +254,7 @@ def _get_two_theta_min_max(self, ring_index: int) -> dict[str, float]: ring number Returns ------- - dict[str, np.ndarray] + dict[str, float] dictionary containing minimum and maximum values for two theta """ if ring_index == 0: @@ -269,35 +269,37 @@ def _get_two_theta_min_max(self, ring_index: int) -> dict[str, float]: return {"min": two_theta_min, "max": two_theta_max} - def _remove_low_intensity_pixels_from_mask(self, mask: np.ndarray) -> tuple[np.ndarray, float]: + def _remove_low_intensity_pixels_from_mask( + self, mask: numpy.ndarray + ) -> tuple[numpy.ndarray, float]: """ Creates a final mask of valid pixels to be used in peak extraction, by removing low intensity pixels from the initial mask Parameters ---------- - mask : np.ndarray + mask : numpy.ndarray mask of valid pixels Returns ------- - tuple[np.ndarray, float] + tuple[numpy.ndarray, float] final mask of valid pixels, upper limit of intensity to mask """ mean, std = self._calculate_mean_and_std_of_intensities_in_mask(mask) upper_limit = mean + std high_pixel_intensity_coords = self.image > upper_limit - final_mask = np.logical_and(high_pixel_intensity_coords, mask) + final_mask = numpy.logical_and(high_pixel_intensity_coords, mask) final_mask_size = final_mask.sum(dtype=int) minimum_mask_size = 1000 # copied this from original method, don't know why this number if final_mask_size < minimum_mask_size: upper_limit = mean - final_mask = np.logical_and(self.image > upper_limit, mask) + final_mask = numpy.logical_and(self.image > upper_limit, mask) final_mask_size = final_mask.sum() return final_mask, upper_limit def _calculate_mean_and_std_of_intensities_in_mask( - self, mask: np.ndarray + self, mask: numpy.ndarray ) -> tuple[float, float]: """ Calculates mean and standard deviation of pixel intensities of image which are emcompassed @@ -305,7 +307,7 @@ def _calculate_mean_and_std_of_intensities_in_mask( Parameters ---------- - mask : np.ndarray + mask : numpy.ndarray mask of valid pixels Returns @@ -315,21 +317,21 @@ def _calculate_mean_and_std_of_intensities_in_mask( """ flattened_mask = mask.flatten() flattened_image = self.image.flatten() - pixel_intensities_in_mask = flattened_image[np.where(flattened_mask)] + pixel_intensities_in_mask = flattened_image[numpy.where(flattened_mask)] mean = pixel_intensities_in_mask.mean() std = pixel_intensities_in_mask.std() return mean, std def _calculate_num_of_points_to_keep( - self, pixels_at_two_theta_level: np.ndarray, points_per_degree: float + self, pixels_at_two_theta_level: numpy.ndarray, points_per_degree: float ) -> int: """ Calculate number of azimuthal degrees in ring, then multiply by self.points_per_degree Parameters ---------- - pixels_at_two_theta_level : np.ndarray + pixels_at_two_theta_level : numpy.ndarray Array of pixels in the image located in the ring at the current two theta value points_per_degree : float number of control points per azimuthal degree (increase for better precision) @@ -345,8 +347,8 @@ def _calculate_num_of_points_to_keep( pixels_at_two_theta_level[:, 0].clip(0, image_shape[0]), pixels_at_two_theta_level[:, 1].clip(0, image_shape[1]), ] - number_of_azimuthal_degrees = np.unique( - np.rad2deg(azimuthal_degrees_array_in_ring).round() + number_of_azimuthal_degrees = numpy.unique( + numpy.rad2deg(azimuthal_degrees_array_in_ring).round() ).size return int(number_of_azimuthal_degrees * points_per_degree) @@ -374,22 +376,22 @@ def _calculate_min_distance_between_control_points( # TODO implementation for elliptical rings needs semi-minor axis to ensure the min value for # arc, and ellipse centre, which differs from beam centre in elliptical projections beam_centre_coords = self._get_beam_centre_coords() - random_point_in_ring = np.array(random.sample(pixel_list_at_two_theta_level, 1)[0]) - ring_radius_px = np.sqrt(sum(x**2 for x in (random_point_in_ring - beam_centre_coords))) + random_point_in_ring = numpy.array(random.sample(pixel_list_at_two_theta_level, 1)[0]) + ring_radius_px = numpy.sqrt(sum(x**2 for x in (random_point_in_ring - beam_centre_coords))) one_degree = 1 - ring_dist_in_one_deg = ring_radius_px * np.radians(one_degree) + ring_dist_in_one_deg = ring_radius_px * numpy.radians(one_degree) return ring_dist_in_one_deg / points_per_degree - def _get_beam_centre_coords(self) -> np.ndarray: + def _get_beam_centre_coords(self) -> numpy.ndarray: """ Use AzimuthalIntegrator's method `getFit2D` to get the pixel coordinates of the beam centre Returns ------- - np.ndarray + numpy.ndarray beam centre coordinates [y,x], to match pyFAI order. """ fit_2d = self.single_geometry.get_ai().getFit2D() beam_centre_x = fit_2d["centerX"] beam_centre_y = fit_2d["centerY"] - return np.array([beam_centre_y, beam_centre_x]) + return numpy.array([beam_centre_y, beam_centre_x]) diff --git a/src/pyFAI/test/test_ring_extraction.py b/src/pyFAI/test/test_ring_extraction.py index a6bda5f7e..7ac7a0b85 100644 --- a/src/pyFAI/test/test_ring_extraction.py +++ b/src/pyFAI/test/test_ring_extraction.py @@ -39,7 +39,7 @@ import unittest from unittest import mock -import numpy as np +import numpy from .. import ring_extraction from ..ring_extraction import RingExtraction @@ -50,18 +50,16 @@ def setUp(self): self.single_geometry = mock.MagicMock() self.massif = mock.MagicMock() self.ring_extraction = RingExtraction(self.single_geometry, self.massif) - two_theta_values = np.array([1, 2, 3, 4, 5]) + two_theta_values = numpy.array([1, 2, 3, 4, 5]) self.ring_extraction.two_theta_values = two_theta_values - self.ones_array = np.ones((5, 5)) + self.ones_array = numpy.ones((5, 5)) self.ring_extraction.two_theta_array = self.ones_array self.expected_two_theta_dictionary = { "min": 0.75, "max": 1.25, } - self.mock_np = mock.patch( - ring_extraction.__name__ + ".np", autospec=True - ).start() + self.mock_numpy = mock.patch(ring_extraction.__name__ + ".numpy", autospec=True).start() def tearDown(self): mock.patch.stopall() @@ -90,9 +88,7 @@ def test_calib_attributes(self): class TestExtractControlPoints(RingExtractionTestBase): def setUp(self): super().setUp() - self.mock_control_points = mock.patch( - (ring_extraction.__name__ + ".ControlPoints") - ).start() + self.mock_control_points = mock.patch((ring_extraction.__name__ + ".ControlPoints")).start() self.mock_extract_peaks_in_one_ring = mock.patch.object( self.ring_extraction, "extract_list_of_peaks_in_one_ring" ).start() @@ -105,9 +101,7 @@ def test_extract_control_points(self): self.ring_extraction.extract_control_points(max_number_of_rings=3) # Assert - self.mock_control_points.assert_called_once_with( - calibrant=self.ring_extraction.calibrant - ) + self.mock_control_points.assert_called_once_with(calibrant=self.ring_extraction.calibrant) self.assertEqual(self.mock_extract_peaks_in_one_ring.call_count, 3) self.mock_extract_peaks_in_one_ring.assert_has_calls( @@ -131,10 +125,10 @@ def setUp(self): self.mock_remove_low_intensity_pixels = mock.patch.object( RingExtraction, "_remove_low_intensity_pixels_from_mask", - return_value=(np.ones((5, 5)), 0), + return_value=(numpy.ones((5, 5)), 0), ).start() self.mock_create_mask_around_ring = mock.patch.object( - RingExtraction, "_create_mask_around_ring", return_value=np.ones((5, 5)) + RingExtraction, "_create_mask_around_ring", return_value=numpy.ones((5, 5)) ).start() self.mock_marching_squares = mock.patch( ring_extraction.__name__ + ".marchingsquares.MarchingSquaresMergeImpl" @@ -152,22 +146,18 @@ def test_extract_list_of_peaks_in_one_ring( # Assert self.mock_marching_squares.assert_called_once() self.assertTrue( - np.array_equal( - self.mock_marching_squares.call_args_list[0][0][0], self.ones_array - ) + numpy.array_equal(self.mock_marching_squares.call_args_list[0][0][0], self.ones_array) ) self.assertEqual( self.mock_marching_squares.call_args_list[0][1]["mask"], "detector_mask_string", ) - self.assertTrue( - self.mock_marching_squares.call_args_list[0][1]["use_minmax_cache"], True - ) + self.assertTrue(self.mock_marching_squares.call_args_list[0][1]["use_minmax_cache"], True) self.mock_create_mask_around_ring.assert_called_once_with(1) self.mock_remove_low_intensity_pixels.assert_called_once() self.assertTrue( - np.array_equal( + numpy.array_equal( self.mock_remove_low_intensity_pixels.call_args_list[0][0][0], self.ones_array, ) @@ -176,7 +166,7 @@ def test_extract_list_of_peaks_in_one_ring( self.ring_extraction.massif.peaks_from_area.assert_called_once() self.assertTrue( - np.array_equal( + numpy.array_equal( self.ring_extraction.massif.peaks_from_area.call_args_list[0][0][0], self.ones_array, ) @@ -203,22 +193,22 @@ class TestGetUniqueTwoThetaValuesInImage(RingExtractionTestBase): def test_get_unique_two_theta_values_in_image( self, ): - two_theta_values = np.array([0.5, 1, 1.5, 2, 2.5, 1]) + two_theta_values = numpy.array([0.5, 1, 1.5, 2, 2.5, 1]) self.ring_extraction.calibrant = mock.MagicMock() - self.mock_np.array.return_value = two_theta_values + self.mock_numpy.array.return_value = two_theta_values # Act self.ring_extraction._get_unique_two_theta_values_in_image() # Assert self.ring_extraction.calibrant.get_2th.assert_called_once_with() - self.mock_np.unique.assert_called_once_with(two_theta_values) + self.mock_numpy.unique.assert_called_once_with(two_theta_values) class TestCreateMaskAroundRing(RingExtractionTestBase): def setUp(self): super().setUp() - zeros_array = np.zeros((3, 3)) + zeros_array = numpy.zeros((3, 3)) self.ring_extraction.two_theta_array = zeros_array self.mock_get_two_theta_min_max = mock.patch.object( RingExtraction, @@ -235,14 +225,14 @@ def test_create_mask_around_ring(self): # Assert self.mock_get_two_theta_min_max.assert_called_once_with(0) - self.mock_np.logical_and.assert_called_once() + self.mock_numpy.logical_and.assert_called_once() first_call_logical_and = ( - np.zeros((3, 3), dtype=bool), - np.ones((3, 3), dtype=bool), + numpy.zeros((3, 3), dtype=bool), + numpy.ones((3, 3), dtype=bool), ) self.assertTrue( - np.array_equal( - self.mock_np.logical_and.call_args_list[0][0], first_call_logical_and + numpy.array_equal( + self.mock_numpy.logical_and.call_args_list[0][0], first_call_logical_and ) ) @@ -262,18 +252,18 @@ def test_get_two_theta_min_max(self): class TestRemoveLowIntensityPixelsFromMask(RingExtractionTestBase): def setUp(self): super().setUp() - self.mock_np.logical_and.side_effect = [ - np.ones((3, 3), dtype=bool), - np.ones((3, 3), dtype=bool), - np.ones((32, 32), dtype=bool), + self.mock_numpy.logical_and.side_effect = [ + numpy.ones((3, 3), dtype=bool), + numpy.ones((3, 3), dtype=bool), + numpy.ones((32, 32), dtype=bool), ] self.mock_calculate_mean_and_std_of_intensities_in_mask = mock.patch.object( RingExtraction, "_calculate_mean_and_std_of_intensities_in_mask", return_value=(0.9, 0.1), ).start() - self.ring_extraction.image = np.ones((3, 3)) - self.mock_mask = np.ones((3, 3), dtype=bool) + self.ring_extraction.image = numpy.ones((3, 3)) + self.mock_mask = numpy.ones((3, 3), dtype=bool) def tearDown(self): mock.patch.stopall() @@ -286,22 +276,20 @@ def test_remove_low_intensity_pixels_from_mask(self): ) = self.ring_extraction._remove_low_intensity_pixels_from_mask(self.mock_mask) # Assert - self.assertIsInstance(final_mask, np.ndarray) + self.assertIsInstance(final_mask, numpy.ndarray) self.assertEqual(upper_limit, 0.9) self.mock_calculate_mean_and_std_of_intensities_in_mask.assert_called_once_with( self.mock_mask ) - expected_first_call = (np.zeros((3, 3)), np.ones((3, 3))) - expected_second_call = (np.ones((3, 3)), np.ones((3, 3))) - actual_calls = self.mock_np.logical_and.call_args_list - self.assertEqual( - len(actual_calls), 2, "np.logical_and was not called twice as expected" - ) - self.assertTrue(np.array_equal(actual_calls[0][0][0], expected_first_call[0])) - self.assertTrue(np.array_equal(actual_calls[0][0][1], expected_first_call[1])) - self.assertTrue(np.array_equal(actual_calls[1][0][0], expected_second_call[0])) - self.assertTrue(np.array_equal(actual_calls[1][0][1], expected_second_call[1])) + expected_first_call = (numpy.zeros((3, 3)), numpy.ones((3, 3))) + expected_second_call = (numpy.ones((3, 3)), numpy.ones((3, 3))) + actual_calls = self.mock_numpy.logical_and.call_args_list + self.assertEqual(len(actual_calls), 2, "numpy.logical_and was not called twice as expected") + self.assertTrue(numpy.array_equal(actual_calls[0][0][0], expected_first_call[0])) + self.assertTrue(numpy.array_equal(actual_calls[0][0][1], expected_first_call[1])) + self.assertTrue(numpy.array_equal(actual_calls[1][0][0], expected_second_call[0])) + self.assertTrue(numpy.array_equal(actual_calls[1][0][1], expected_second_call[1])) def test_remove_low_intensity_pixels_from_mask_enough_points(self): # Act @@ -312,24 +300,22 @@ def test_remove_low_intensity_pixels_from_mask_enough_points(self): self.mock_mask ) - expected_call_args = (np.zeros((3, 3)), np.ones((3, 3))) - actual_call_args = self.mock_np.logical_and.call_args_list[0][0] - self.assertTrue(np.array_equal(actual_call_args[0], expected_call_args[0])) - self.assertTrue(np.array_equal(actual_call_args[1], expected_call_args[1])) + expected_call_args = (numpy.zeros((3, 3)), numpy.ones((3, 3))) + actual_call_args = self.mock_numpy.logical_and.call_args_list[0][0] + self.assertTrue(numpy.array_equal(actual_call_args[0], expected_call_args[0])) + self.assertTrue(numpy.array_equal(actual_call_args[1], expected_call_args[1])) class TestCalcMeanStdOfIntensitiesInMask(RingExtractionTestBase): def test_calculate_mean_and_std_of_intensities_in_mask(self): mock_mask = mock.MagicMock() - mock_flattened_mask = np.ones(9, dtype=bool) + mock_flattened_mask = numpy.ones(9, dtype=bool) mock_mask.flatten.return_value = mock_flattened_mask mock_image = mock.MagicMock() mock_flattened_image = mock.MagicMock() mock_image.flatten.return_value = mock_flattened_image - mock_pixel_intensities_in_mask = mock_flattened_image[ - np.where(mock_flattened_mask) - ] + mock_pixel_intensities_in_mask = mock_flattened_image[numpy.where(mock_flattened_mask)] mean = 1 std = 0.1 mock_pixel_intensities_in_mask.mean.return_value = mean @@ -338,35 +324,31 @@ def test_calculate_mean_and_std_of_intensities_in_mask(self): self.ring_extraction.image = mock_image # Act - result = self.ring_extraction._calculate_mean_and_std_of_intensities_in_mask( - mock_mask - ) + result = self.ring_extraction._calculate_mean_and_std_of_intensities_in_mask(mock_mask) # Assert self.assertEqual(result, (mean, std)) mock_mask.flatten.assert_called_once_with() mock_image.flatten.assert_called_once_with() - self.mock_np.where.assert_called_once_with(mock_flattened_mask) + self.mock_numpy.where.assert_called_once_with(mock_flattened_mask) class TestCalcPointsToKeep(RingExtractionTestBase): def test_calculate_num_of_points_to_keep(self): - self.ring_extraction.image = np.zeros((10, 10)) - pixel_list_at_two_theta_level = np.array([[2, 2], [3, 3], [4, 4]]) - sample_array = np.array([[1, 2], [3, 4]]) + self.ring_extraction.image = numpy.zeros((10, 10)) + pixel_list_at_two_theta_level = numpy.array([[2, 2], [3, 3], [4, 4]]) + sample_array = numpy.array([[1, 2], [3, 4]]) - self.mock_np.unique.return_value = np.array([1, 2, 3, 4]) - self.mock_np.rad2deg.return_value = sample_array + self.mock_numpy.unique.return_value = numpy.array([1, 2, 3, 4]) + self.mock_numpy.rad2deg.return_value = sample_array # Act points_to_keep = self.ring_extraction._calculate_num_of_points_to_keep( pixel_list_at_two_theta_level, 1 ) # Assert - self.single_geometry.geometry_refinement.chiArray.assert_called_once_with( - (10, 10) - ) - self.mock_np.unique.assert_called_once_with(sample_array) + self.single_geometry.geometry_refinement.chiArray.assert_called_once_with((10, 10)) + self.mock_numpy.unique.assert_called_once_with(sample_array) self.assertEqual(points_to_keep, 4) From 6a3ff5293aef1edb93b6d562f4f161b999c77555 Mon Sep 17 00:00:00 2001 From: Emily Massahud Date: Tue, 12 Mar 2024 14:55:30 +1100 Subject: [PATCH 4/8] fix indentation of teardown method --- src/pyFAI/test/test_ring_extraction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyFAI/test/test_ring_extraction.py b/src/pyFAI/test/test_ring_extraction.py index 7ac7a0b85..d8b00fa53 100644 --- a/src/pyFAI/test/test_ring_extraction.py +++ b/src/pyFAI/test/test_ring_extraction.py @@ -134,8 +134,8 @@ def setUp(self): ring_extraction.__name__ + ".marchingsquares.MarchingSquaresMergeImpl" ).start() - def tearDown(self): - mock.patch.stopall() + def tearDown(self): + mock.patch.stopall() def test_extract_list_of_peaks_in_one_ring( self, From 2ceae7382eb540873b967ef1a351b88345d7e618 Mon Sep 17 00:00:00 2001 From: Emily Massahud Date: Tue, 12 Mar 2024 15:56:43 +1100 Subject: [PATCH 5/8] fix TestExtractOneRing --- src/pyFAI/test/test_ring_extraction.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/pyFAI/test/test_ring_extraction.py b/src/pyFAI/test/test_ring_extraction.py index d8b00fa53..a1678cf1d 100644 --- a/src/pyFAI/test/test_ring_extraction.py +++ b/src/pyFAI/test/test_ring_extraction.py @@ -128,11 +128,21 @@ def setUp(self): return_value=(numpy.ones((5, 5)), 0), ).start() self.mock_create_mask_around_ring = mock.patch.object( - RingExtraction, "_create_mask_around_ring", return_value=numpy.ones((5, 5)) + RingExtraction, "_create_mask_around_ring", return_value=self.ones_array ).start() self.mock_marching_squares = mock.patch( ring_extraction.__name__ + ".marchingsquares.MarchingSquaresMergeImpl" ).start() + mock_marching_squares_instance = self.mock_marching_squares.return_value + + self.pixels_at_two_theta_level = numpy.array(([1, 1], [2, 2], [3, 3])) + mock_marching_squares_instance.find_pixels.return_value = self.pixels_at_two_theta_level + + self.mock_calculate_min_distance_between_control_points = mock.patch.object( + RingExtraction, + "_calculate_min_distance_between_control_points", + return_value=0.1, + ).start() def tearDown(self): mock.patch.stopall() @@ -162,7 +172,13 @@ def test_extract_list_of_peaks_in_one_ring( self.ones_array, ) ) - self.mock_calculate_num_of_points_to_keep.assert_called_once() + self.mock_calculate_num_of_points_to_keep.assert_called_once_with( + self.pixels_at_two_theta_level, 1 + ) + + self.mock_calculate_min_distance_between_control_points.assert_called_once_with( + [[1, 1], [2, 2], [3, 3]], 1 + ) self.ring_extraction.massif.peaks_from_area.assert_called_once() self.assertTrue( @@ -177,11 +193,11 @@ def test_extract_list_of_peaks_in_one_ring( ) self.assertEqual( self.ring_extraction.massif.peaks_from_area.call_args_list[0][1]["dmin"], - 0, + 0.1, ) self.assertEqual( self.ring_extraction.massif.peaks_from_area.call_args_list[0][1]["seed"], - set(), + set(((1, 1), (2, 2), (3, 3))), ) self.assertEqual( self.ring_extraction.massif.peaks_from_area.call_args_list[0][1]["ring"], From c11c6b091dfb0dde564261eacaf321439dd8e80e Mon Sep 17 00:00:00 2001 From: Emily Massahud Date: Tue, 12 Mar 2024 15:57:01 +1100 Subject: [PATCH 6/8] fix type hint in docstrings --- src/pyFAI/ring_extraction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyFAI/ring_extraction.py b/src/pyFAI/ring_extraction.py index e4d2cc632..7e9579aa6 100644 --- a/src/pyFAI/ring_extraction.py +++ b/src/pyFAI/ring_extraction.py @@ -147,7 +147,7 @@ def extract_list_of_peaks_in_one_ring( Returns ------- - Optional[List[Tuple[float]]] + Optional[list[tuple[float, float]]] peaks at given ring index """ marching_squares_algorithm = marchingsquares.MarchingSquaresMergeImpl( From 3331df293e1fcba19e0137546143dac384469c01 Mon Sep 17 00:00:00 2001 From: Emily Massahud Date: Tue, 12 Mar 2024 16:47:49 +1100 Subject: [PATCH 7/8] add TestCalcMinDistBetweenControlPoints --- src/pyFAI/test/test_ring_extraction.py | 39 +++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/pyFAI/test/test_ring_extraction.py b/src/pyFAI/test/test_ring_extraction.py index a1678cf1d..9a1289621 100644 --- a/src/pyFAI/test/test_ring_extraction.py +++ b/src/pyFAI/test/test_ring_extraction.py @@ -59,7 +59,8 @@ def setUp(self): "max": 1.25, } - self.mock_numpy = mock.patch(ring_extraction.__name__ + ".numpy", autospec=True).start() + self.numpy_patcher = mock.patch(ring_extraction.__name__ + ".numpy", autospec=True) + self.mock_numpy = self.numpy_patcher.start() def tearDown(self): mock.patch.stopall() @@ -206,11 +207,10 @@ def test_extract_list_of_peaks_in_one_ring( class TestGetUniqueTwoThetaValuesInImage(RingExtractionTestBase): - def test_get_unique_two_theta_values_in_image( - self, - ): + def test_get_unique_two_theta_values_in_image(self): two_theta_values = numpy.array([0.5, 1, 1.5, 2, 2.5, 1]) self.ring_extraction.calibrant = mock.MagicMock() + self.mock_numpy.array.return_value = two_theta_values # Act @@ -368,6 +368,36 @@ def test_calculate_num_of_points_to_keep(self): self.assertEqual(points_to_keep, 4) +class TestCalcMinDistBetweenControlPoints(RingExtractionTestBase): + def setUp(self): + super().setUp() + self.numpy_patcher.stop() + self.beam_centre_coords = numpy.array((1.5, 2.1)) + self.mock_get_beam_centre_coords = mock.patch.object( + self.ring_extraction, + "_get_beam_centre_coords", + return_value=self.beam_centre_coords, + ).start() + + def tearDown(self): + mock.patch.stopall() + + def test_calculate_min_distance_between_control_points(self): + # Arrange + pixel_list_at_two_theta_level = [[5, 5]] + + # Act + min_dist_between_control_points = ( + self.ring_extraction._calculate_min_distance_between_control_points( + pixel_list_at_two_theta_level, 1 + ) + ) + + # Assert + self.mock_get_beam_centre_coords.assert_called_once_with() + self.assertAlmostEqual(min_dist_between_control_points, 0.0793309) + + def suite(): testsuite = unittest.TestSuite() loader = unittest.defaultTestLoader.loadTestsFromTestCase @@ -380,6 +410,7 @@ def suite(): testsuite.addTest(loader(TestRemoveLowIntensityPixelsFromMask)) testsuite.addTest(loader(TestCalcMeanStdOfIntensitiesInMask)) testsuite.addTest(loader(TestCalcPointsToKeep)) + testsuite.addTest(loader(TestCalcMinDistBetweenControlPoints)) return testsuite From 3e8b7733903d76cc91386116a3159da51fe8d384 Mon Sep 17 00:00:00 2001 From: Emily Massahud Date: Tue, 12 Mar 2024 17:29:44 +1100 Subject: [PATCH 8/8] add TestGetBeamCentreCoords --- src/pyFAI/test/test_ring_extraction.py | 33 +++++++++++++------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/pyFAI/test/test_ring_extraction.py b/src/pyFAI/test/test_ring_extraction.py index 9a1289621..f72b75dc2 100644 --- a/src/pyFAI/test/test_ring_extraction.py +++ b/src/pyFAI/test/test_ring_extraction.py @@ -94,9 +94,6 @@ def setUp(self): self.ring_extraction, "extract_list_of_peaks_in_one_ring" ).start() - def tearDown(self): - mock.patch.stopall() - def test_extract_control_points(self): # Act self.ring_extraction.extract_control_points(max_number_of_rings=3) @@ -145,9 +142,6 @@ def setUp(self): return_value=0.1, ).start() - def tearDown(self): - mock.patch.stopall() - def test_extract_list_of_peaks_in_one_ring( self, ): @@ -232,9 +226,6 @@ def setUp(self): return_value=self.expected_two_theta_dictionary, ).start() - def tearDown(self): - mock.patch.stopall() - def test_create_mask_around_ring(self): # Act self.ring_extraction._create_mask_around_ring(ring_index=0) @@ -281,9 +272,6 @@ def setUp(self): self.ring_extraction.image = numpy.ones((3, 3)) self.mock_mask = numpy.ones((3, 3), dtype=bool) - def tearDown(self): - mock.patch.stopall() - def test_remove_low_intensity_pixels_from_mask(self): # Act ( @@ -379,9 +367,6 @@ def setUp(self): return_value=self.beam_centre_coords, ).start() - def tearDown(self): - mock.patch.stopall() - def test_calculate_min_distance_between_control_points(self): # Arrange pixel_list_at_two_theta_level = [[5, 5]] @@ -398,6 +383,22 @@ def test_calculate_min_distance_between_control_points(self): self.assertAlmostEqual(min_dist_between_control_points, 0.0793309) +class TestGetBeamCentreCoords(RingExtractionTestBase): + def setUp(self): + super().setUp() + self.numpy_patcher.stop() + self.mock_ai = self.single_geometry.get_ai.return_value + self.mock_ai.getFit2D.return_value = {"centerX": 1, "centerY": 2} + + def test_get_beam_centre_coords(self): + # Act + beam_centre = self.ring_extraction._get_beam_centre_coords() + + # Assert + self.single_geometry.get_ai().getFit2D.assert_called_once_with() + self.assertTrue(numpy.array_equal(beam_centre, numpy.array((2, 1)))) + + def suite(): testsuite = unittest.TestSuite() loader = unittest.defaultTestLoader.loadTestsFromTestCase @@ -411,7 +412,7 @@ def suite(): testsuite.addTest(loader(TestCalcMeanStdOfIntensitiesInMask)) testsuite.addTest(loader(TestCalcPointsToKeep)) testsuite.addTest(loader(TestCalcMinDistBetweenControlPoints)) - + testsuite.addTest(loader(TestGetBeamCentreCoords)) return testsuite