From c6bef2eb5789cf27ab9e65db69884880fb00db44 Mon Sep 17 00:00:00 2001 From: dkazanc Date: Tue, 4 Aug 2026 15:51:17 +0100 Subject: [PATCH 1/4] initial modifications --- httomolib/misc/blend.py | 99 +++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 52 deletions(-) diff --git a/httomolib/misc/blend.py b/httomolib/misc/blend.py index b0653ba..a7a1a42 100644 --- a/httomolib/misc/blend.py +++ b/httomolib/misc/blend.py @@ -36,27 +36,21 @@ def seam_blend_stitched_data( data: np.ndarray, - seam_index: Optional[int] = None, - blending_width: Optional[int] = None, - path_to_stitched_params_file: Optional[str] = None, - shift_seam_index: int = 0, + overlap: int, + shift_seam_index: Optional[int] = None, ) -> np.ndarray: """ - Function blends the seam present in the stitched projection data. It uses the redundant by blending_width data present on both sides of the seam. + Function blends the seam present in the stitched projection data using the overlap value provided. Used in HTTomo for seamless stitching of datasets coming from two different PCO cameras. Parameters ---------- data : np.ndarray 3d array of the stitched data, assuming the following axis ["angles", "detY", "detX"]. - seam_index : Optional, int - The horizontal index of the seam along the 'detX' axis. If None and 'path_to_stitched_params_file' is provided, it will be taken from the file, otherwise middle of the horizontal axis. - blending_width : Optional, int - The area for symmetric blending (e.g. with the ramp filter) around the seam position (seam_index) of the stitched data. If None and 'path_to_stitched_params_file' is provided, it will be taken from the file, otherwise 0. - path_to_stitched_params_file : Optional, str - Path to the text file with the stiching parameters. If provided 'seam_index' and 'blending_width' parameters will be overridden by the ones provided in the file. - shift_seam_index : int - performs a shift of the seam index with seam_index - shift_seam_index. This is purely an HTTomo related feature and should be ignored by users. + overlap : int + Overlap between the LEFT and the RIGHT images of the stitched data. Usually known from the experiment. + shift_seam_index : optional, int + performs a shift of the 'seam index'. This is an HTTomo related feature and should be ignored by users. Raises ---------- ValueError: When data is not 3D. @@ -66,63 +60,64 @@ def seam_blend_stitched_data( np.ndarray: stitched data without the seam. """ ### Data and parameters checks ### - methods_name = "seam_blender" + methods_name = "seam_blend_stitched_data" __check_if_data_3D_array(data, methods_name) __check_if_data_correct_type( data, accepted_type=["float64", "float32", "uint8", "uint16", "uint32"], methods_name=methods_name, ) - __check_variable_type(seam_index, [int, type(None)], "seam_index", [], methods_name) __check_variable_type( - blending_width, [int, type(None)], "blending_width", [], methods_name - ) - __check_variable_type( - path_to_stitched_params_file, - [str, type(None)], - "path_to_stitched_params_file", - [], - methods_name, + shift_seam_index, [int, type(None)], "shift_seam_index", [], methods_name ) ################################### angles_dim, detY, detX = data.shape + seam_index = int(detX // 2) - if path_to_stitched_params_file is not None: - params = {} - with open(path_to_stitched_params_file) as f: - for line in f: - key, value = line.split() - params[key] = int(value) - - blending_width = params.get("blending_width") - seam_index = params.get("seam_index") - - if blending_width is None: - blending_width = 0 - if seam_index is None: - seam_index = int(detX // 2) + if shift_seam_index is None: + shift_seam_index = 0 - blending_width *= 2 seam_index -= shift_seam_index - if seam_index >= detX - blending_width: - err_str = f"Seam index given as '{seam_index}' must be smaller than the horizontal dimension of the data '{detX}' minus blending width." + if seam_index < 0 or seam_index >= detX: + err_str = f"Seam index '{seam_index}' cannot be negative or larger than the horizontal dimension of the data '{detX}'. Check 'shift_seam_index'." raise ValueError(err_str) - # Split regions of data - left_part = data[:, :, 0 : (seam_index - blending_width)] - right_part = data[:, :, (seam_index + blending_width) : :] + ramp = np.linspace(0, 1, overlap) + + blended_data = np.empty((angles_dim, detY, detX - overlap), dtype=data.dtype) + + blended_data[:, :, 0 : seam_index - overlap] = data[ + :, :, 0 : seam_index - overlap + ] # left side + blended_data[:, :, seam_index::] = data[:, :, overlap:] # right side + blended_data[:, :, seam_index - overlap : seam_index] = ( + data[:, :, seam_index - overlap : seam_index] * ramp[::-1] + + data[:, :, seam_index : seam_index + overlap] * ramp + ) # overlap area + + # # Split regions of data + # left_part = data[:, :, 0 : (seam_index - blending_width)] + # right_part = data[:, :, (seam_index + blending_width) : :] + + # # Overlap regions + # left_overlap = data[:, :, (seam_index - blending_width) : seam_index] + # right_overlap = data[:, :, seam_index : (seam_index + blending_width)] - # Overlap regions - left_overlap = data[:, :, (seam_index - blending_width) : seam_index] - right_overlap = data[:, :, seam_index : (seam_index + blending_width)] + # # Create ramp weights (0 → 1) + # ramp = np.float32(np.linspace(0, 1, blending_width)) + # ramp = np.tile(ramp, (detY, 1)) - # Create ramp weights (0 → 1) - ramp = np.float32(np.linspace(0, 1, blending_width)) - ramp = np.tile(ramp, (detY, 1)) + # # Blend + # blended_overlap = (1 - ramp) * left_overlap + ramp * right_overlap - # Blend - blended_overlap = (1 - ramp) * left_overlap + ramp * right_overlap + # if you have two cameras with shape (2560, 2160) [y, x], + # and say h overlap of 100 and v offest of 60, I create an + # empty array of (2560*2 - 100, 2160 - 60), then fill the l + # eftmost area with pco3[:2560-100, 60:], fill the rightmost + # area with pco2[100:, :-60], and fill the middle with + # ramp = np.linspace(0, 1, 100); pco3[-100:, 60:]*ramp[::-1] + pco2[:100, :-60]*ramp - return np.dstack([left_part, blended_overlap, right_part]) + # return np.dstack([left_part, blended_overlap, right_part]) + return blended_data From 22692680e2806578325e8c2937940c1a565a097d Mon Sep 17 00:00:00 2001 From: dkazanc Date: Thu, 6 Aug 2026 09:14:02 +0100 Subject: [PATCH 2/4] seam blend method complete --- httomolib/misc/blend.py | 45 +++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/httomolib/misc/blend.py b/httomolib/misc/blend.py index a7a1a42..24e5804 100644 --- a/httomolib/misc/blend.py +++ b/httomolib/misc/blend.py @@ -27,6 +27,7 @@ __check_variable_type, __check_if_data_3D_array, __check_if_data_correct_type, + __check_if_positive_nonzero, ) __all__ = [ @@ -37,11 +38,12 @@ def seam_blend_stitched_data( data: np.ndarray, overlap: int, + seam_index: int = 2560, shift_seam_index: Optional[int] = None, ) -> np.ndarray: """ Function blends the seam present in the stitched projection data using the overlap value provided. - Used in HTTomo for seamless stitching of datasets coming from two different PCO cameras. + Used in HTTomo for seamless stitching of datasets coming from two different (PCO) cameras. Parameters ---------- @@ -49,8 +51,10 @@ def seam_blend_stitched_data( 3d array of the stitched data, assuming the following axis ["angles", "detY", "detX"]. overlap : int Overlap between the LEFT and the RIGHT images of the stitched data. Usually known from the experiment. + seam_index : int + The horizontal index of the seam in the stitched data. Normally equals to the width of one frame/projection before the stitching. shift_seam_index : optional, int - performs a shift of the 'seam index'. This is an HTTomo related feature and should be ignored by users. + performs a shift of the 'seam index' that is introduced by the data cropping. This is an HTTomo related feature and should be ignored by users. Raises ---------- ValueError: When data is not 3D. @@ -67,19 +71,25 @@ def seam_blend_stitched_data( accepted_type=["float64", "float32", "uint8", "uint16", "uint32"], methods_name=methods_name, ) + __check_if_positive_nonzero( + seam_index, + "seam_index", + True, + True, + methods_name, + ) + __check_variable_type(seam_index, [int], "seam_index", [], methods_name) __check_variable_type( shift_seam_index, [int, type(None)], "shift_seam_index", [], methods_name ) ################################### - angles_dim, detY, detX = data.shape - seam_index = int(detX // 2) - if shift_seam_index is None: shift_seam_index = 0 seam_index -= shift_seam_index + angles_dim, detY, detX = data.shape if seam_index < 0 or seam_index >= detX: err_str = f"Seam index '{seam_index}' cannot be negative or larger than the horizontal dimension of the data '{detX}'. Check 'shift_seam_index'." raise ValueError(err_str) @@ -91,33 +101,10 @@ def seam_blend_stitched_data( blended_data[:, :, 0 : seam_index - overlap] = data[ :, :, 0 : seam_index - overlap ] # left side - blended_data[:, :, seam_index::] = data[:, :, overlap:] # right side + blended_data[:, :, seam_index::] = data[:, :, seam_index + overlap :] # right side blended_data[:, :, seam_index - overlap : seam_index] = ( data[:, :, seam_index - overlap : seam_index] * ramp[::-1] + data[:, :, seam_index : seam_index + overlap] * ramp ) # overlap area - # # Split regions of data - # left_part = data[:, :, 0 : (seam_index - blending_width)] - # right_part = data[:, :, (seam_index + blending_width) : :] - - # # Overlap regions - # left_overlap = data[:, :, (seam_index - blending_width) : seam_index] - # right_overlap = data[:, :, seam_index : (seam_index + blending_width)] - - # # Create ramp weights (0 → 1) - # ramp = np.float32(np.linspace(0, 1, blending_width)) - # ramp = np.tile(ramp, (detY, 1)) - - # # Blend - # blended_overlap = (1 - ramp) * left_overlap + ramp * right_overlap - - # if you have two cameras with shape (2560, 2160) [y, x], - # and say h overlap of 100 and v offest of 60, I create an - # empty array of (2560*2 - 100, 2160 - 60), then fill the l - # eftmost area with pco3[:2560-100, 60:], fill the rightmost - # area with pco2[100:, :-60], and fill the middle with - # ramp = np.linspace(0, 1, 100); pco3[-100:, 60:]*ramp[::-1] + pco2[:100, :-60]*ramp - - # return np.dstack([left_part, blended_overlap, right_part]) return blended_data From 6b4c8fcb8494427805802c62b31b077b6126de21 Mon Sep 17 00:00:00 2001 From: algol Date: Thu, 6 Aug 2026 09:31:11 +0100 Subject: [PATCH 3/4] adding tests --- tests/test_misc/test_blend.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/test_misc/test_blend.py b/tests/test_misc/test_blend.py index c1854bd..855ece5 100644 --- a/tests/test_misc/test_blend.py +++ b/tests/test_misc/test_blend.py @@ -5,19 +5,23 @@ def test_seam_blend_stitched_data(data_stitched): - result = seam_blend_stitched_data(data_stitched, seam_index=151, blending_width=30) + result = seam_blend_stitched_data( + data_stitched, overlap=20, seam_index=151, shift_seam_index=None + ) assert result.flags.c_contiguous - assert result.dtype == np.float32 - assert result.shape == (300, 4, 240) + assert result.dtype == np.uint16 + assert result.shape == (300, 4, 280) + np.testing.assert_array_almost_equal(int(np.mean(result)), 11520) -def test_seam_blend_stitched_data_txt(data_stitched, data_stitched_txt): +def test_seam_blend_stitched_data_shift(data_stitched): result = seam_blend_stitched_data( - data_stitched, path_to_stiched_params_file=data_stitched_txt + data_stitched, overlap=20, seam_index=151, shift_seam_index=10 ) assert result.flags.c_contiguous - assert result.dtype == np.float32 - assert result.shape == (300, 4, 240) + assert result.dtype == np.uint16 + assert result.shape == (300, 4, 280) + np.testing.assert_array_almost_equal(int(np.mean(result)), 11506) From be58fa9fe08a9184542c917c9f4c2e6fd41a3e31 Mon Sep 17 00:00:00 2001 From: algol Date: Thu, 6 Aug 2026 09:56:18 +0100 Subject: [PATCH 4/4] removing txt file --- tests/conftest.py | 6 ------ tests/test_data/stitching_params.txt | 2 -- 2 files changed, 8 deletions(-) delete mode 100644 tests/test_data/stitching_params.txt diff --git a/tests/conftest.py b/tests/conftest.py index 7da3fd3..e80e634 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -56,12 +56,6 @@ def data_stitched(test_data_path): return np.load(in_file) -@pytest.fixture(scope="session") -def data_stitched_txt(test_data_path): - in_file = os.path.join(test_data_path, "stitching_params.txt") - return in_file - - @pytest.fixture def host_data(data_file): return np.copy(data_file["data"]) diff --git a/tests/test_data/stitching_params.txt b/tests/test_data/stitching_params.txt deleted file mode 100644 index 3ddde47..0000000 --- a/tests/test_data/stitching_params.txt +++ /dev/null @@ -1,2 +0,0 @@ -blending_width 30 -seam_index 151