From 5147d0cb65b62fef361945bd1d7c35f710a64550 Mon Sep 17 00:00:00 2001 From: dkazanc Date: Wed, 11 Dec 2024 16:25:30 +0000 Subject: [PATCH 1/6] adding average_radius parameter into the centering wrapper --- httomo/method_wrappers/rotation.py | 25 +++++++++++++-- tests/method_wrappers/test_rotation.py | 43 +++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/httomo/method_wrappers/rotation.py b/httomo/method_wrappers/rotation.py index 92b080105..3a440a043 100644 --- a/httomo/method_wrappers/rotation.py +++ b/httomo/method_wrappers/rotation.py @@ -74,6 +74,8 @@ def _build_kwargs( dict_params["ind"] == "mid" or dict_params["ind"] is None ): updated_params = {**dict_params, "ind": (dataset.shape[1] - 1) // 2} + if "average_radius" not in dict_params: + updated_params.update({"average_radius": 0}) return super()._build_kwargs(updated_params, dataset) def _gather_sino_slice(self, global_shape: Tuple[int, int, int]): @@ -144,6 +146,8 @@ def _run_method(self, block: T, args: Dict[str, Any]) -> T: else: assert "ind" in args slice_for_cor = args["ind"] + assert "average_radius" in args + average_radius = args["average_radius"] # append to internal sinogram, until we have the last block if self.sino is None: self.sino = np.empty( @@ -157,8 +161,25 @@ def _run_method(self, block: T, args: Dict[str, Any]) -> T: if block.is_padded: core_angles_start = block.padding[0] core_angles_stop = core_angles_start + block.shape_unpadded[0] - - data = block.data[core_angles_start:core_angles_stop, slice_for_cor, :] + if average_radius == 0: + data = block.data[core_angles_start:core_angles_stop, slice_for_cor, :] + else: + if 2 * average_radius <= block.data.shape[1]: + # averaging few sinograms to improve SNR and centering method accuracy + data = xp.mean( + block.data[ + core_angles_start:core_angles_stop, + slice_for_cor + - average_radius : slice_for_cor + + average_radius, + :, + ], + 1, + ) + else: + raise ValueError( + f"The given average_radius = {average_radius} in the centering method is larger than the half size of the block = {block.data.shape[1]//2}. Please make it smaller or 0." + ) if block.is_gpu: with catchtime() as t: diff --git a/tests/method_wrappers/test_rotation.py b/tests/method_wrappers/test_rotation.py index 5f919424a..0ae5ca1e1 100644 --- a/tests/method_wrappers/test_rotation.py +++ b/tests/method_wrappers/test_rotation.py @@ -83,7 +83,7 @@ def test_rotation_accumulates_blocks(mocker: MockerFixture, padding: Tuple[int, ) class FakeModule: - def rotation_tester(data, ind=None): + def rotation_tester(data, ind=None, average_radius=None): assert data.ndim == 2 # for 1 slice only np.testing.assert_array_equal( data, global_data[:, (GLOBAL_SHAPE[1] - 1) // 2, :] @@ -145,10 +145,11 @@ def test_rotation_gathers_single_sino_slice( ) class FakeModule: - def rotation_tester(data, ind=None): + def rotation_tester(data, ind=None, average_radius=None): assert rank == 0 # for rank 1, it shouldn't be called assert data.ndim == 2 # for 1 slice only assert ind == 0 + assert average_radius == 0 if ind_par == "mid" or ind_par is None: xp.testing.assert_array_equal( global_data[:, (GLOBAL_SHAPE[1] - 1) // 2, :], @@ -341,7 +342,7 @@ def test_rotation_normalize_sino_different_darks_flats_gpu(): def test_rotation_180(mocker: MockerFixture): class FakeModule: - def rotation_tester(data, ind): + def rotation_tester(data, ind, average_radius): return 42.0 # center of rotation mocker.patch( @@ -355,6 +356,7 @@ def rotation_tester(data, ind): make_mock_preview_config(mocker), output_mapping={"cor": "center"}, ind=5, + average_radius=0, ) block = DataSetBlock( @@ -367,6 +369,38 @@ def rotation_tester(data, ind): assert new_block == block # note: not a deep comparison +def test_rotation_180_raise_average_radius(mocker: MockerFixture): + class FakeModule: + def rotation_tester(data, ind, average_radius): + return 42.0 # center of rotation + + mocker.patch( + "httomo.method_wrappers.generic.import_module", return_value=FakeModule + ) + wrp = make_method_wrapper( + make_mock_repo(mocker, pattern=Pattern.projection), + "mocked_module_path.rotation", + "rotation_tester", + MPI.COMM_WORLD, + make_mock_preview_config(mocker), + output_mapping={"cor": "center"}, + ind=5, + average_radius=6, + ) + + block = DataSetBlock( + data=np.ones((10, 10, 10), dtype=np.float32), + aux_data=AuxiliaryData(angles=np.ones(10, dtype=np.float32)), + ) + + with pytest.raises(ValueError) as e: + _ = wrp.execute(block) + assert ( + "The given average_radius = 6 in the centering method is larger than the half size of the block = 5." + in str(e) + ) + + def test_rotation_pc_180(mocker: MockerFixture): class FakeModule: def find_center_pc(proj1, proj2=None): @@ -396,7 +430,7 @@ def find_center_pc(proj1, proj2=None): def test_rotation_360(mocker: MockerFixture): class FakeModule: - def rotation_tester(data, ind): + def rotation_tester(data, ind, average_radius): # cor, overlap, side, overlap_position - from find_center_360 return 42.0, 3.0, 1, 10.0 @@ -415,6 +449,7 @@ def rotation_tester(data, ind): "overlap_position": "pos", }, ind=5, + average_radius=0, ) block = DataSetBlock( data=np.ones((10, 10, 10), dtype=np.float32), From c0a59e54bf982743222a4ff3fb186f1d6582128f Mon Sep 17 00:00:00 2001 From: dkazanc Date: Wed, 11 Dec 2024 16:43:02 +0000 Subject: [PATCH 2/6] fixing the tests --- httomo/method_wrappers/rotation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/httomo/method_wrappers/rotation.py b/httomo/method_wrappers/rotation.py index 3a440a043..87f717e40 100644 --- a/httomo/method_wrappers/rotation.py +++ b/httomo/method_wrappers/rotation.py @@ -146,8 +146,10 @@ def _run_method(self, block: T, args: Dict[str, Any]) -> T: else: assert "ind" in args slice_for_cor = args["ind"] - assert "average_radius" in args - average_radius = args["average_radius"] + if "average_radius" not in args: + average_radius = 0 + else: + average_radius = args["average_radius"] # append to internal sinogram, until we have the last block if self.sino is None: self.sino = np.empty( From f7e31a0f769d17dd3059694b7085617f4449d735 Mon Sep 17 00:00:00 2001 From: dkazanc Date: Fri, 17 Jan 2025 16:21:24 +0000 Subject: [PATCH 3/6] adding regularisers dependency for IRIS tests --- .github/workflows/run_tests_iris.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests_iris.yml b/.github/workflows/run_tests_iris.yml index c107a8513..d17e8f187 100644 --- a/.github/workflows/run_tests_iris.yml +++ b/.github/workflows/run_tests_iris.yml @@ -36,7 +36,7 @@ jobs: run: | micromamba activate httomo pip install --upgrade --force-reinstall pillow - pip install httomolibgpu tomobar + pip install httomolibgpu tomobar ccpi-regularisation-cupy pip install --no-deps httomo-backends pip install . micromamba list From ae7baa4b58a32f635ed313a9c0b584f6e51d5b55 Mon Sep 17 00:00:00 2001 From: dkazanc Date: Mon, 20 Jan 2025 10:54:09 +0000 Subject: [PATCH 4/6] adding 2 more tests for averaging --- httomo/method_wrappers/rotation.py | 9 +- tests/method_wrappers/test_rotation.py | 107 ++++++++++++++++-- .../DLS/01_diad_pipeline_gpu.yaml | 1 - 3 files changed, 105 insertions(+), 12 deletions(-) diff --git a/httomo/method_wrappers/rotation.py b/httomo/method_wrappers/rotation.py index b8ae3ca52..8d17ad218 100644 --- a/httomo/method_wrappers/rotation.py +++ b/httomo/method_wrappers/rotation.py @@ -168,21 +168,22 @@ def _run_method(self, block: T, args: Dict[str, Any]) -> T: if average_radius == 0: data = block.data[core_angles_start:core_angles_stop, slice_for_cor, :] else: - if 2 * average_radius <= block.data.shape[1]: + if 2 * average_radius < block.data.shape[1]: # averaging few sinograms to improve SNR and centering method accuracy data = xp.mean( block.data[ core_angles_start:core_angles_stop, slice_for_cor - average_radius : slice_for_cor - + average_radius, + + average_radius + + 1, :, ], - 1, + axis=1, ) else: raise ValueError( - f"The given average_radius = {average_radius} in the centering method is larger than the half size of the block = {block.data.shape[1]//2}. Please make it smaller or 0." + f"The given average_radius = {average_radius} in the centering method is larger or equal than the half size of the block = {block.data.shape[1]//2}. Please make it smaller or 0." ) if block.is_gpu: diff --git a/tests/method_wrappers/test_rotation.py b/tests/method_wrappers/test_rotation.py index 263959566..7a1b46b6c 100644 --- a/tests/method_wrappers/test_rotation.py +++ b/tests/method_wrappers/test_rotation.py @@ -370,7 +370,11 @@ def rotation_tester(data, ind, average_radius): assert new_block == block # note: not a deep comparison -def test_rotation_180_raise_average_radius(mocker: MockerFixture): +@pytest.mark.parametrize( + "radius", + [5, 6], +) +def test_rotation_180_raise_average_radius(mocker: MockerFixture, radius): class FakeModule: def rotation_tester(data, ind, average_radius): return 42.0 # center of rotation @@ -386,20 +390,109 @@ def rotation_tester(data, ind, average_radius): make_mock_preview_config(mocker), output_mapping={"cor": "center"}, ind=5, - average_radius=6, + average_radius=radius, ) block = DataSetBlock( data=np.ones((10, 10, 10), dtype=np.float32), aux_data=AuxiliaryData(angles=np.ones(10, dtype=np.float32)), ) + if radius == 5: + with pytest.raises(ValueError) as e: + _ = wrp.execute(block) + assert ( + "The given average_radius = 5 in the centering method is larger or equal than the half size of the block = 5." + in str(e) + ) + if radius == 6: + with pytest.raises(ValueError) as e: + _ = wrp.execute(block) + assert ( + "The given average_radius = 6 in the centering method is larger or equal than the half size of the block = 5." + in str(e) + ) + + +def test_rotation_180_average_within_range(mocker: MockerFixture): + class FakeModule: + def rotation_tester(data, ind, average_radius): + midindex = 3 + original_array = np.zeros((7, 7, 7), dtype=np.float32) + original_array[:, 2, :] = 1.2 + original_array[:, 3, :] = 2.5 + original_array[:, 4, :] = 13.7 + + expected_data = np.mean( + original_array[ + :, midindex - average_radius : midindex + average_radius + 1, : + ], + axis=1, + ) + + np.testing.assert_array_equal(data, expected_data[:, np.newaxis, :]) + return 100 + + mocker.patch( + "httomo.method_wrappers.generic.import_module", return_value=FakeModule + ) + wrp = make_method_wrapper( + make_mock_repo(mocker, pattern=Pattern.projection), + "mocked_module_path.rotation", + "rotation_tester", + MPI.COMM_WORLD, + make_mock_preview_config(mocker), + output_mapping={"cor": "center"}, + ind=3, + average_radius=1, + ) + original_array = np.zeros((7, 7, 7), dtype=np.float32) + original_array[:, 2, :] = 1.2 + original_array[:, 3, :] = 2.5 + original_array[:, 4, :] = 13.7 - with pytest.raises(ValueError) as e: - _ = wrp.execute(block) - assert ( - "The given average_radius = 6 in the centering method is larger than the half size of the block = 5." - in str(e) + block = DataSetBlock( + data=original_array.copy(), + aux_data=AuxiliaryData(angles=np.ones(7, dtype=np.float32)), + ) + wrp.execute(block) + + +def test_rotation_180_average_0slices(mocker: MockerFixture): + class FakeModule: + def rotation_tester(data, ind, average_radius): + midindex = 3 + original_array = np.zeros((7, 7, 7), dtype=np.float32) + original_array[:, 2, :] = 1.2 + original_array[:, 3, :] = 2.5 + original_array[:, 4, :] = 13.7 + expected_data = original_array[:, midindex, :] + + np.testing.assert_array_equal(data, expected_data[:, np.newaxis, :]) + return 100 + + mocker.patch( + "httomo.method_wrappers.generic.import_module", return_value=FakeModule + ) + wrp = make_method_wrapper( + make_mock_repo(mocker, pattern=Pattern.projection), + "mocked_module_path.rotation", + "rotation_tester", + MPI.COMM_WORLD, + make_mock_preview_config(mocker), + output_mapping={"cor": "center"}, + ind=3, + average_radius=0, + ) + original_array = np.zeros((7, 7, 7), dtype=np.float32) + original_array[:, 2, :] = 1.2 + original_array[:, 3, :] = 2.5 + original_array[:, 4, :] = 13.7 + + block = DataSetBlock( + data=original_array.copy(), + aux_data=AuxiliaryData(angles=np.ones(7, dtype=np.float32)), ) + wrp.execute(block) def test_rotation_pc_180(mocker: MockerFixture): diff --git a/tests/samples/pipeline_template_examples/DLS/01_diad_pipeline_gpu.yaml b/tests/samples/pipeline_template_examples/DLS/01_diad_pipeline_gpu.yaml index fcfa6c4a0..1ce05dd1d 100644 --- a/tests/samples/pipeline_template_examples/DLS/01_diad_pipeline_gpu.yaml +++ b/tests/samples/pipeline_template_examples/DLS/01_diad_pipeline_gpu.yaml @@ -66,4 +66,3 @@ subfolder_name: images axis: auto file_format: tif - jpeg_quality: 95 From 3571a79b20434c78cf2d8172bc1685821dc7c2ee Mon Sep 17 00:00:00 2001 From: Yousef Moazzam Date: Mon, 20 Jan 2025 11:50:53 +0000 Subject: [PATCH 5/6] Tweak rotation wrapper `average_radius` param tests The tweaks are the following: - the "index" value was unnecessarily defined both inside and outside the dummy method function with the same value: moved this to a single variable - the expected data to be passed into the dummy method function was defined inside the dummy method function itself: moved this to be defined outside of the dummy method function for clarity - the `original_array` variable was unnecessarily defined both inside and outside the dummy method function, with the same value: moved this to a single variable - the `original_array` value was unnecessarily copied: removed the use of `copy()` --- tests/method_wrappers/test_rotation.py | 59 +++++++++++--------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/tests/method_wrappers/test_rotation.py b/tests/method_wrappers/test_rotation.py index 7a1b46b6c..32a8e01da 100644 --- a/tests/method_wrappers/test_rotation.py +++ b/tests/method_wrappers/test_rotation.py @@ -414,21 +414,19 @@ def rotation_tester(data, ind, average_radius): def test_rotation_180_average_within_range(mocker: MockerFixture): + IND = 3 + AVERAGE_RADIUS = 1 + original_array = np.zeros((7, 7, 7), dtype=np.float32) + original_array[:, 2, :] = 1.2 + original_array[:, 3, :] = 2.5 + original_array[:, 4, :] = 13.7 + expected_data = np.mean( + original_array[:, IND - AVERAGE_RADIUS : IND + AVERAGE_RADIUS + 1, :], + axis=1, + ) + class FakeModule: def rotation_tester(data, ind, average_radius): - midindex = 3 - original_array = np.zeros((7, 7, 7), dtype=np.float32) - original_array[:, 2, :] = 1.2 - original_array[:, 3, :] = 2.5 - original_array[:, 4, :] = 13.7 - - expected_data = np.mean( - original_array[ - :, midindex - average_radius : midindex + average_radius + 1, : - ], - axis=1, - ) - np.testing.assert_array_equal(data, expected_data[:, np.newaxis, :]) return 100 @@ -442,31 +440,28 @@ def rotation_tester(data, ind, average_radius): MPI.COMM_WORLD, make_mock_preview_config(mocker), output_mapping={"cor": "center"}, - ind=3, - average_radius=1, + ind=IND, + average_radius=AVERAGE_RADIUS, ) - original_array = np.zeros((7, 7, 7), dtype=np.float32) - original_array[:, 2, :] = 1.2 - original_array[:, 3, :] = 2.5 - original_array[:, 4, :] = 13.7 block = DataSetBlock( - data=original_array.copy(), + data=original_array, aux_data=AuxiliaryData(angles=np.ones(7, dtype=np.float32)), ) wrp.execute(block) def test_rotation_180_average_0slices(mocker: MockerFixture): + IND = 3 + AVERAGE_RADIUS = 0 + original_array = np.zeros((7, 7, 7), dtype=np.float32) + original_array[:, 2, :] = 1.2 + original_array[:, 3, :] = 2.5 + original_array[:, 4, :] = 13.7 + expected_data = original_array[:, IND, :] + class FakeModule: def rotation_tester(data, ind, average_radius): - midindex = 3 - original_array = np.zeros((7, 7, 7), dtype=np.float32) - original_array[:, 2, :] = 1.2 - original_array[:, 3, :] = 2.5 - original_array[:, 4, :] = 13.7 - expected_data = original_array[:, midindex, :] - np.testing.assert_array_equal(data, expected_data[:, np.newaxis, :]) return 100 @@ -480,16 +475,12 @@ def rotation_tester(data, ind, average_radius): MPI.COMM_WORLD, make_mock_preview_config(mocker), output_mapping={"cor": "center"}, - ind=3, - average_radius=0, + ind=IND, + average_radius=AVERAGE_RADIUS, ) - original_array = np.zeros((7, 7, 7), dtype=np.float32) - original_array[:, 2, :] = 1.2 - original_array[:, 3, :] = 2.5 - original_array[:, 4, :] = 13.7 block = DataSetBlock( - data=original_array.copy(), + data=original_array, aux_data=AuxiliaryData(angles=np.ones(7, dtype=np.float32)), ) wrp.execute(block) From d3dd5c12c1b053d18dad097c466b89e2998ee5a6 Mon Sep 17 00:00:00 2001 From: Yousef Moazzam Date: Mon, 20 Jan 2025 12:07:41 +0000 Subject: [PATCH 6/6] Test `average_radius` param is implicitly set to zero if not present --- tests/method_wrappers/test_rotation.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/method_wrappers/test_rotation.py b/tests/method_wrappers/test_rotation.py index 32a8e01da..102d5b0e4 100644 --- a/tests/method_wrappers/test_rotation.py +++ b/tests/method_wrappers/test_rotation.py @@ -1,4 +1,4 @@ -from typing import List, Tuple, Union +from typing import Any, Dict, List, Tuple, Union from unittest.mock import MagicMock import numpy as np from httomo.method_wrappers import make_method_wrapper @@ -451,14 +451,20 @@ def rotation_tester(data, ind, average_radius): wrp.execute(block) -def test_rotation_180_average_0slices(mocker: MockerFixture): - IND = 3 - AVERAGE_RADIUS = 0 +@pytest.mark.parametrize( + "params", + [{"ind": 3, "average_radius": 0}, {"ind": 3}], + ids=[ + "explicitly-sets-0", + "wrapper-implicitly-sets-0", + ], +) +def test_rotation_180_average_0slices(mocker: MockerFixture, params: Dict[str, Any]): original_array = np.zeros((7, 7, 7), dtype=np.float32) original_array[:, 2, :] = 1.2 original_array[:, 3, :] = 2.5 original_array[:, 4, :] = 13.7 - expected_data = original_array[:, IND, :] + expected_data = original_array[:, params["ind"], :] class FakeModule: def rotation_tester(data, ind, average_radius): @@ -475,8 +481,7 @@ def rotation_tester(data, ind, average_radius): MPI.COMM_WORLD, make_mock_preview_config(mocker), output_mapping={"cor": "center"}, - ind=IND, - average_radius=AVERAGE_RADIUS, + **params, ) block = DataSetBlock(