diff --git a/pyclesperanto_prototype/_tier3/__init__.py b/pyclesperanto_prototype/_tier3/__init__.py index 5036b154..38c95bc8 100644 --- a/pyclesperanto_prototype/_tier3/__init__.py +++ b/pyclesperanto_prototype/_tier3/__init__.py @@ -56,6 +56,7 @@ from ._standard_deviation_of_touching_neighbors_map import standard_deviation_of_touching_neighbors_map from ._standard_deviation_of_proximal_neighbors_map import standard_deviation_of_proximal_neighbors_map from ._standard_deviation_of_proximal_neighbors_map import standard_deviation_of_proximal_neighbors_map as standard_deviation_of_distal_neighbors_map - +from ._stitch_horizontally_linear_blending import stitch_horizontally_linear_blending +from ._stitch_vertically_linear_blending import stitch_vertically_linear_blending from ._subtract_gaussian_background import subtract_gaussian_background -from ._z_position_range_projection import z_position_range_projection \ No newline at end of file +from ._z_position_range_projection import z_position_range_projection diff --git a/pyclesperanto_prototype/_tier3/_stitch_horizontally_linear_blending.py b/pyclesperanto_prototype/_tier3/_stitch_horizontally_linear_blending.py new file mode 100644 index 00000000..d1a349c4 --- /dev/null +++ b/pyclesperanto_prototype/_tier3/_stitch_horizontally_linear_blending.py @@ -0,0 +1,51 @@ +from .._tier0 import plugin_function +from .._tier0 import create +from .._tier0 import create_none +from .._tier0 import Image +from .._tier1 import paste + +@plugin_function(output_creator=create_none, categories=['combine', 'transform', 'in assistant']) +def stitch_horizontally_linear_blending(image1 : Image, image2 : Image, destination : Image = None, num_pixels_overlap:int=0) -> Image: + """Combines two images in X by linearly blending them in an overlapping region. + + Parameters + ---------- + image1 : Image + image2 : Image + destination : Image, optional + num_pixels_overlap : int, optional + + Returns + ------- + destination + """ + from .._tier0 import create, asarray + from .._tier1 import set_ramp_x, crop + from .._tier1 import subtract_image_from_scalar + from .._tier2 import combine_horizontally + + num_pixels_overlap = int(num_pixels_overlap) + image1_width = image1.shape[-1] + image2_width = image2.shape[-1] + image1_height = image1.shape[-2] + image2_height = image2.shape[-2] + image1_depth = 1 if len(image1.shape) == 2 else image1.shape[-3] + image2_depth = 1 if len(image2.shape) == 2 else image2.shape[-3] + + # crop out left, right and the two overlapping parts + left_part = crop(image1, width=image1_width - num_pixels_overlap, height=image1_height, depth=image1_depth) + center_part1 = crop(image1, start_x=image1_width - num_pixels_overlap, width=num_pixels_overlap, height=image1_height, depth=image1_depth) + center_part2 = crop(image2, width=num_pixels_overlap, height=image2_height, depth=image2_depth) + right_part = crop(image2, start_x=num_pixels_overlap, width=image2_width - num_pixels_overlap, height=image2_height, depth=image2_depth) + + # setup a gradient for the blending + gradient = create(center_part1.shape) + set_ramp_x(gradient) + gradient_right_left = (gradient + 1) / (gradient.shape[-1]+1) + gradient_left_right = subtract_image_from_scalar(gradient_right_left, scalar=1) + + # compute the overlapping image by multiplying both images with the gradient + center_part = asarray(center_part1) * gradient_left_right + asarray(center_part2) * gradient_right_left + + # combine images vertically + return combine_horizontally(combine_horizontally(left_part, center_part), right_part, destination) diff --git a/pyclesperanto_prototype/_tier3/_stitch_vertically_linear_blending.py b/pyclesperanto_prototype/_tier3/_stitch_vertically_linear_blending.py new file mode 100644 index 00000000..7c9282da --- /dev/null +++ b/pyclesperanto_prototype/_tier3/_stitch_vertically_linear_blending.py @@ -0,0 +1,51 @@ +from .._tier0 import plugin_function +from .._tier0 import create +from .._tier0 import create_none +from .._tier0 import Image +from .._tier1 import paste + +@plugin_function(output_creator=create_none, categories=['combine', 'transform', 'in assistant']) +def stitch_vertically_linear_blending(image1 : Image, image2 : Image, destination : Image = None, num_pixels_overlap:int=0) -> Image: + """Combines two images in Y by linearly blending them in an overlapping region. + + Parameters + ---------- + image1 : Image + image2 : Image + destination : Image, optional + num_pixels_overlap : int, optional + + Returns + ------- + destination + """ + from .._tier0 import create, asarray + from .._tier1 import set_ramp_y, crop + from .._tier1 import subtract_image_from_scalar + from .._tier2 import combine_vertically + + num_pixels_overlap = int(num_pixels_overlap) + image1_width = image1.shape[-1] + image2_width = image2.shape[-1] + image1_height = image1.shape[-2] + image2_height = image2.shape[-2] + image1_depth = 1 if len(image1.shape) == 2 else image1.shape[-3] + image2_depth = 1 if len(image2.shape) == 2 else image2.shape[-3] + + # crop out left, right and the two overlapping parts + top_part = crop(image1, width=image1_width, height=image1_height - num_pixels_overlap, depth=image1_depth) + center_part1 = crop(image1, start_y=image1_height-num_pixels_overlap, width=image1_width, height=num_pixels_overlap, depth=image1_depth) + center_part2 = crop(image2, width=image2_width, height=num_pixels_overlap, depth=image2_depth) + bottom_part = crop(image2, start_y=num_pixels_overlap, width=image2_width, height=image2_height-num_pixels_overlap, depth=image2_depth) + + # setup a gradient for the blending + gradient = create(center_part1.shape) + set_ramp_y(gradient) + gradient_right_left = (gradient + 1) / (gradient.shape[-2]+1) + gradient_left_right = subtract_image_from_scalar(gradient_right_left, scalar=1) + + # compute the overlapping image by multiplying both images with the gradient + center_part = asarray(center_part1) * gradient_left_right + asarray(center_part2) * gradient_right_left + + # combine images vertically + return combine_vertically(combine_vertically(top_part, center_part), bottom_part, destination) diff --git a/tests/test_stitch_horizontally_linear_blending.py b/tests/test_stitch_horizontally_linear_blending.py new file mode 100644 index 00000000..9d8a7979 --- /dev/null +++ b/tests/test_stitch_horizontally_linear_blending.py @@ -0,0 +1,80 @@ +import pyclesperanto_prototype as cle +import numpy as np + + +def test_stitch_horizontally_linear_blending_overlap0(): + test1 = cle.push(np.asarray([ + [1, 1], + [1, 1] + ])) + test2 = cle.push(np.asarray([ + [2, 2, 2], + [2, 2, 2] + ])) + + reference = cle.push(np.asarray([ + [1, 1, 2, 2, 2], + [1, 1, 2, 2, 2] + ])) + + result = cle.stitch_horizontally_linear_blending(test1, test2) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert (np.allclose(a, b, 0.01)) + +def test_stitch_horizontally_linear_blending_overlap1(): + test1 = cle.push(np.asarray([ + [1, 1], + [1, 1] + ])) + test2 = cle.push(np.asarray([ + [2, 2, 2], + [2, 2, 2] + ])) + + reference = cle.push(np.asarray([ + [1, 1.5, 2, 2], + [1, 1.5, 2, 2] + ])) + + result = cle.stitch_horizontally_linear_blending(test1, test2, num_pixels_overlap=1) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert (np.allclose(a, b, 0.01)) + + +def test_stitch_horizontally_linear_blending_overlap2(): + test1 = cle.push(np.asarray([ + [1, 1, 1], + [1, 1, 1] + ])) + test2 = cle.push(np.asarray([ + [2, 2, 2], + [2, 2, 2] + ])) + + reference = cle.push(np.asarray([ + [1, 1.33, 1.67, 2], + [1, 1.33, 1.67, 2] + ])) + + result = cle.stitch_horizontally_linear_blending(test1, test2, num_pixels_overlap=2) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert (np.allclose(a, b, 0.01)) + diff --git a/tests/test_stitch_vertically_linear_blending.py b/tests/test_stitch_vertically_linear_blending.py new file mode 100644 index 00000000..79747c1d --- /dev/null +++ b/tests/test_stitch_vertically_linear_blending.py @@ -0,0 +1,96 @@ +import pyclesperanto_prototype as cle +import numpy as np + + +def test_stitch_vertically_linear_blending_overlap0(): + test1 = cle.push(np.asarray([ + [1, 1], + [1, 1], + [1, 1] + ])) + test2 = cle.push(np.asarray([ + [2, 2], + [2, 2], + [2, 2] + ])) + + reference = cle.push(np.asarray([ + [1, 1], + [1, 1], + [1, 1], + [2, 2], + [2, 2], + [2, 2], + ])) + + result = cle.stitch_vertically_linear_blending(test1, test2) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert (np.allclose(a, b, 0.01)) + + +def test_stitch_vertically_linear_blending_overlap1(): + test1 = cle.push(np.asarray([ + [1, 1], + [1, 1], + [1, 1] + ])) + test2 = cle.push(np.asarray([ + [2, 2], + [2, 2], + [2, 2] + ])) + + reference = cle.push(np.asarray([ + [1, 1], + [1, 1], + [1.5, 1.5], + [2, 2], + [2, 2], + ])) + + result = cle.stitch_vertically_linear_blending(test1, test2, num_pixels_overlap=1) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert (np.allclose(a, b, 0.01)) + + +def test_stitch_vertically_linear_blending_overlap2(): + test1 = cle.push(np.asarray([ + [1, 1], + [1, 1], + [1, 1] + ])) + test2 = cle.push(np.asarray([ + [2, 2], + [2, 2], + [2, 2] + ])) + + reference = cle.push(np.asarray([ + [1, 1], + [1.33, 1.33], + [1.67, 1.67], + [2, 2], + ])) + + result = cle.stitch_vertically_linear_blending(test1, test2, num_pixels_overlap=2) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert (np.allclose(a, b, 0.01)) +