From 651cf8656878a38b3086e508fb8f8eef4a2b4d02 Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 10:53:58 +0200 Subject: [PATCH 01/10] update API --- pyclesperanto/__init__.py | 1 + pyclesperanto/_core.py | 67 +++- pyclesperanto/_tier1.py | 742 ++++++++++++++++++++++++++++++++++++++ pyclesperanto/_tier2.py | 419 ++++++++++++++++++++- pyclesperanto/_tier3.py | 15 + pyclesperanto/_tier4.py | 6 + pyclesperanto/_tier5.py | 6 +- pyclesperanto/_tier6.py | 6 + pyclesperanto/_tier7.py | 11 + pyclesperanto/_tier8.py | 3 + src/wrapper/execute_.cpp | 77 ++++ src/wrapper/tier1_.cpp | 60 +++ src/wrapper/tier2_.cpp | 35 ++ 13 files changed, 1445 insertions(+), 3 deletions(-) diff --git a/pyclesperanto/__init__.py b/pyclesperanto/__init__.py index adcebdb6..d3a95d83 100644 --- a/pyclesperanto/__init__.py +++ b/pyclesperanto/__init__.py @@ -1,6 +1,7 @@ from ._core import ( gpu_info, execute, + native_execute, select_backend, select_device, get_device, diff --git a/pyclesperanto/_core.py b/pyclesperanto/_core.py index eadccbab..07b86b39 100644 --- a/pyclesperanto/_core.py +++ b/pyclesperanto/_core.py @@ -5,7 +5,7 @@ from ._pyclesperanto import _Device as Device from ._pyclesperanto import _BackendManager as BackendManager -from ._pyclesperanto import _execute +from ._pyclesperanto import _execute, _native_execute class _current_device: @@ -216,6 +216,71 @@ def load_file(anchor, filename): _execute(device, kernel_name, kernel_source, parameters, global_size, constants) + +def native_execute(anchor = '__file__', kernel_source: str = '', kernel_name: str = '', global_size: tuple = (1, 1, 1), local_size: tuple = (1, 1, 1), parameters: dict = {}, device: Device = None): + """Execute an OpenCL kernel from a file or a string + + Call, build, and execute a kernel compatible with OpenCL language. + The kernel can be called from a file or a string. + + The parameters must still be passed as a dictionary with the correct types and order. + Buffer parameters must be passed as Array objects. Scalars must be passed as Python native float or int. + + Warning: Only 1D buffers are supported for now. + + Parameters + ---------- + anchor : str, default = '__file__' + Enter __file__ when calling this method and the corresponding open.cl + file lies in the same folder as the python file calling it. + Ignored if kernel_source is a string. + kernel_source : str + Filename of the open.cl file to be called or string containing the open.cl source code + kernel_name : str + Kernel method inside the open.cl file to be called + most clij/clesperanto kernel functions have the same name as the file they are in + global_size : tuple (z,y,x), default = (1, 1, 1) + Global_size according to OpenCL definition (usually shape of the destination image). + local_size : tuple (z,y,x), default = (1, 1, 1) + Local_size according to OpenCL definition (usually default is good). + parameters : dict(str, [Array, float, int]) + Dictionary containing parameters. Take care: They must be of the + right type and in the right order as specified in the open.cl file. + device : Device, default = None + The device to execute the kernel on. If None, use the current device + """ + + # load the kernel file + def load_file(anchor, filename): + """Load the opencl kernel file as a string""" + if anchor is None: + kernel = Path(filename).read_text() + else: + kernel = (Path(anchor).parent / filename).read_text() + return kernel + + # manage the device if not given + if not device: + device = get_device() + + # manage global range + if not isinstance(global_size, tuple): + if isinstance(global_size, list) or isinstance(global_size, np.ndarray): + global_size = tuple(global_size) + else: + global_size = (global_size,) + + # manage local range + if not isinstance(local_size, tuple): + if isinstance(local_size, list) or isinstance(local_size, np.ndarray): + local_size = tuple(local_size) + else: + local_size = (local_size,) + + _native_execute(device, kernel_name, kernel_source, parameters, global_size, local_size) + + + def gpu_info(): device_list = list_available_devices() info = [] diff --git a/pyclesperanto/_tier1.py b/pyclesperanto/_tier1.py index bf3cb3f5..2ffebfbe 100644 --- a/pyclesperanto/_tier1.py +++ b/pyclesperanto/_tier1.py @@ -7,6 +7,7 @@ from ._array import Image from ._decorators import plugin_function import numpy as np +import warnings @plugin_function(category=['filter', 'in assistant', 'bia-bob-suggestion']) @@ -38,6 +39,7 @@ def absolute( from ._pyclesperanto import _absolute as op + return op( device=device, src=input_image, @@ -84,6 +86,7 @@ def add_images_weighted( from ._pyclesperanto import _add_images_weighted as op + return op( device=device, src0=input_image0, @@ -127,6 +130,7 @@ def add_image_and_scalar( from ._pyclesperanto import _add_image_and_scalar as op + return op( device=device, src=input_image, @@ -170,6 +174,7 @@ def binary_and( from ._pyclesperanto import _binary_and as op + return op( device=device, src0=input_image0, @@ -208,6 +213,7 @@ def binary_edge_detection( from ._pyclesperanto import _binary_edge_detection as op + return op( device=device, src=input_image, @@ -246,6 +252,7 @@ def binary_not( from ._pyclesperanto import _binary_not as op + return op( device=device, src=input_image, @@ -288,6 +295,7 @@ def binary_or( from ._pyclesperanto import _binary_or as op + return op( device=device, src0=input_image0, @@ -328,6 +336,7 @@ def binary_subtract( from ._pyclesperanto import _binary_subtract as op + return op( device=device, src0=input_image0, @@ -371,6 +380,7 @@ def binary_xor( from ._pyclesperanto import _binary_xor as op + return op( device=device, src0=input_image0, @@ -416,6 +426,7 @@ def block_enumerate( from ._pyclesperanto import _block_enumerate as op + return op( device=device, src0=input_image0, @@ -458,6 +469,7 @@ def convolve( from ._pyclesperanto import _convolve as op + return op( device=device, src0=input_image0, @@ -495,6 +507,7 @@ def copy( from ._pyclesperanto import _copy as op + return op( device=device, src=input_image, @@ -538,6 +551,7 @@ def copy_slice( from ._pyclesperanto import _copy_slice as op + return op( device=device, src=input_image, @@ -579,6 +593,7 @@ def copy_horizontal_slice( from ._pyclesperanto import _copy_horizontal_slice as op + return op( device=device, src=input_image, @@ -620,6 +635,7 @@ def copy_vertical_slice( from ._pyclesperanto import _copy_vertical_slice as op + return op( device=device, src=input_image, @@ -676,6 +692,7 @@ def crop( from ._pyclesperanto import _crop as op + return op( device=device, src=input_image, @@ -715,6 +732,7 @@ def cubic_root( from ._pyclesperanto import _cubic_root as op + return op( device=device, src=input_image, @@ -752,6 +770,7 @@ def detect_label_edges( from ._pyclesperanto import _detect_label_edges as op + return op( device=device, src=input_image, @@ -793,6 +812,7 @@ def dilate_box( from ._pyclesperanto import _dilate_box as op + warnings.warn("dilate_box : This function is deprecated. Consider using dilate() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -832,6 +852,7 @@ def dilate_sphere( from ._pyclesperanto import _dilate_sphere as op + warnings.warn("dilate_sphere : This function is deprecated. Consider using dilate() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -840,6 +861,52 @@ def dilate_sphere( +@plugin_function(category=['binary processing', 'bia-bob-suggestion']) +def dilate( + input_image: Image, + output_image: Image = None, + connectivity: str = "box", + device: Device = None +) -> Image: + """Computes a binary image with pixel values 0 and 1 containing the binary dilation + of a given input image. The dilation apply the Mooreneighborhood (8 pixels in 2D + and 26 pixels in 3d) for the "box" connectivity and the vonNeumannneighborhood + (4 pixels in 2D and 6 pixels in 3d) for a "sphere" connectivity. The pixels in + the input image with pixel value not equal to 0 will be interpreted as 1. + + Parameters + ---------- + input_image: Image + Input image to process. Input image to process. + output_image: Image = None + Output result image. Output result image. + connectivity: str = "box" + Element shape, "box" or "sphere". + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_dilateBox + [2] https://clij.github.io/clij2-docs/reference_dilateSphere + """ + + from ._pyclesperanto import _dilate as op + + + return op( + device=device, + src=input_image, + dst=output_image, + connectivity=connectivity + ) + + + @plugin_function(category=['combine', 'in assistant']) def divide_images( input_image0: Image, @@ -871,6 +938,7 @@ def divide_images( from ._pyclesperanto import _divide_images as op + return op( device=device, src0=input_image0, @@ -907,6 +975,7 @@ def divide_scalar_by_image( from ._pyclesperanto import _divide_scalar_by_image as op + return op( device=device, src=input_image, @@ -948,6 +1017,7 @@ def equal( from ._pyclesperanto import _equal as op + return op( device=device, src0=input_image0, @@ -989,6 +1059,7 @@ def equal_constant( from ._pyclesperanto import _equal_constant as op + return op( device=device, src=input_image, @@ -1031,6 +1102,7 @@ def erode_box( from ._pyclesperanto import _erode_box as op + warnings.warn("erode_box : This function is deprecated. Consider using erode() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1070,6 +1142,7 @@ def erode_sphere( from ._pyclesperanto import _erode_sphere as op + warnings.warn("erode_sphere : This function is deprecated. Consider using erode() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1078,6 +1151,52 @@ def erode_sphere( +@plugin_function(category=['binary processing', 'bia-bob-suggestion']) +def erode( + input_image: Image, + output_image: Image = None, + connectivity: str = "box", + device: Device = None +) -> Image: + """Computes a binary image with pixel values 0 and 1 containing the binary erosion + of a given input image. The erosion apply the Mooreneighborhood (8 pixels in 2D + and 26 pixels in 3d) for the "box" connectivity and the vonNeumannneighborhood + (4 pixels in 2D and 6 pixels in 3d) for a "sphere" connectivity. The pixels in + the input image with pixel value not equal to 0 will be interpreted as 1. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + connectivity: str = "box" + Element shape, "box" or "sphere". + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_erodeBox + [2] https://clij.github.io/clij2-docs/reference_erodeSphere + """ + + from ._pyclesperanto import _erode as op + + + return op( + device=device, + src=input_image, + dst=output_image, + connectivity=connectivity + ) + + + @plugin_function(category=['filter', 'in assistant']) def exponential( input_image: Image, @@ -1107,6 +1226,7 @@ def exponential( from ._pyclesperanto import _exponential as op + return op( device=device, src=input_image, @@ -1152,6 +1272,7 @@ def flip( from ._pyclesperanto import _flip as op + return op( device=device, src=input_image, @@ -1202,6 +1323,7 @@ def gaussian_blur( from ._pyclesperanto import _gaussian_blur as op + return op( device=device, src=input_image, @@ -1252,6 +1374,7 @@ def generate_distance_matrix( from ._pyclesperanto import _generate_distance_matrix as op + return op( device=device, src0=input_image0, @@ -1291,6 +1414,7 @@ def gradient_x( from ._pyclesperanto import _gradient_x as op + return op( device=device, src=input_image, @@ -1329,6 +1453,7 @@ def gradient_y( from ._pyclesperanto import _gradient_y as op + return op( device=device, src=input_image, @@ -1367,6 +1492,7 @@ def gradient_z( from ._pyclesperanto import _gradient_z as op + return op( device=device, src=input_image, @@ -1407,6 +1533,7 @@ def greater( from ._pyclesperanto import _greater as op + return op( device=device, src0=input_image0, @@ -1447,6 +1574,7 @@ def greater_constant( from ._pyclesperanto import _greater_constant as op + return op( device=device, src=input_image, @@ -1488,6 +1616,7 @@ def greater_or_equal( from ._pyclesperanto import _greater_or_equal as op + return op( device=device, src0=input_image0, @@ -1529,6 +1658,7 @@ def greater_or_equal_constant( from ._pyclesperanto import _greater_or_equal_constant as op + return op( device=device, src=input_image, @@ -1577,6 +1707,7 @@ def hessian_eigenvalues( from ._pyclesperanto import _hessian_eigenvalues as op + return op( device=device, src=input_image, @@ -1615,6 +1746,7 @@ def laplace_box( from ._pyclesperanto import _laplace_box as op + warnings.warn("laplace_box : This function is deprecated. Consider using laplace() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1651,6 +1783,7 @@ def laplace_diamond( from ._pyclesperanto import _laplace_diamond as op + warnings.warn("laplace_diamond : This function is deprecated. Consider using laplace() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1659,6 +1792,48 @@ def laplace_diamond( +@plugin_function(category=['filter', 'edge detection', 'bia-bob-suggestion']) +def laplace( + input_image: Image, + output_image: Image = None, + connectivity: str = "box", + device: Device = None +) -> Image: + """Applies the Laplace operator with a "box" or a "sphere" neighborhood to an + image. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_laplaceDiamond + """ + + from ._pyclesperanto import _laplace as op + + + return op( + device=device, + src=input_image, + dst=output_image, + connectivity=connectivity + ) + + + @plugin_function(category=['filter', 'combine', 'in assistant']) def local_cross_correlation( input_image0: Image, @@ -1687,6 +1862,7 @@ def local_cross_correlation( from ._pyclesperanto import _local_cross_correlation as op + return op( device=device, src0=input_image0, @@ -1725,6 +1901,7 @@ def logarithm( from ._pyclesperanto import _logarithm as op + return op( device=device, src=input_image, @@ -1767,6 +1944,7 @@ def mask( from ._pyclesperanto import _mask as op + return op( device=device, src=input_image, @@ -1813,6 +1991,7 @@ def mask_label( from ._pyclesperanto import _mask_label as op + return op( device=device, src0=input_image0, @@ -1855,6 +2034,7 @@ def maximum_image_and_scalar( from ._pyclesperanto import _maximum_image_and_scalar as op + return op( device=device, src=input_image, @@ -1896,6 +2076,7 @@ def maximum_images( from ._pyclesperanto import _maximum_images as op + return op( device=device, src0=input_image0, @@ -1943,6 +2124,7 @@ def maximum_box( from ._pyclesperanto import _maximum_box as op + warnings.warn("maximum_box : This function is deprecated. Consider using maximum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1954,6 +2136,62 @@ def maximum_box( +@plugin_function(category=['filter', 'in assistant']) +def maximum( + input_image: Image, + output_image: Image = None, + radius_x: int = 0, + radius_y: int = 0, + radius_z: int = 0, + connectivity: str = "box", + device: Device = None +) -> Image: + """Computes the local maximum of a pixels neighborhood (box or sphere). The + neighborhood size is specified by its halfwidth, halfheight and halfdepth + (radius). + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 0 + Radius size along x axis. + radius_y: int = 0 + Radius size along y axis. + radius_z: int = 0 + Radius size along z axis. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_maximum3DBox + [2] https://clij.github.io/clij2-docs/reference_maximum3DSphere + """ + + from ._pyclesperanto import _maximum as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['projection']) def maximum_x_projection( input_image: Image, @@ -1982,6 +2220,7 @@ def maximum_x_projection( from ._pyclesperanto import _maximum_x_projection as op + return op( device=device, src=input_image, @@ -2018,6 +2257,7 @@ def maximum_y_projection( from ._pyclesperanto import _maximum_y_projection as op + return op( device=device, src=input_image, @@ -2054,6 +2294,7 @@ def maximum_z_projection( from ._pyclesperanto import _maximum_z_projection as op + return op( device=device, src=input_image, @@ -2100,6 +2341,7 @@ def mean_box( from ._pyclesperanto import _mean_box as op + warnings.warn("mean_box : This function is deprecated. Consider using mean() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -2149,6 +2391,7 @@ def mean_sphere( from ._pyclesperanto import _mean_sphere as op + warnings.warn("mean_sphere : This function is deprecated. Consider using mean() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -2160,6 +2403,61 @@ def mean_sphere( +@plugin_function(category=['filter', 'denoise', 'in assistant', 'bia-bob-suggestion']) +def mean( + input_image: Image, + output_image: Image = None, + radius_x: int = 1, + radius_y: int = 1, + radius_z: int = 1, + connectivity: str = "box", + device: Device = None +) -> Image: + """Computes the local mean average of a pixels neighborhood defined as a boxshaped + or a sphereshaped. The shape size is specified by its halfwidth, halfheight and + halfdepth (radius). + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 1 + Radius size along x axis. + radius_y: int = 1 + Radius size along y axis. + radius_z: int = 1 + Radius size along z axis. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_mean3DSphere + """ + + from ._pyclesperanto import _mean as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['projection']) def mean_x_projection( input_image: Image, @@ -2188,6 +2486,7 @@ def mean_x_projection( from ._pyclesperanto import _mean_x_projection as op + return op( device=device, src=input_image, @@ -2224,6 +2523,7 @@ def mean_y_projection( from ._pyclesperanto import _mean_y_projection as op + return op( device=device, src=input_image, @@ -2260,6 +2560,7 @@ def mean_z_projection( from ._pyclesperanto import _mean_z_projection as op + return op( device=device, src=input_image, @@ -2307,6 +2608,7 @@ def median_box( from ._pyclesperanto import _median_box as op + warnings.warn("median_box : This function is deprecated. Consider using median() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -2357,6 +2659,7 @@ def median_sphere( from ._pyclesperanto import _median_sphere as op + warnings.warn("median_sphere : This function is deprecated. Consider using median() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -2368,6 +2671,62 @@ def median_sphere( +@plugin_function(category=['filter', 'denoise', 'in assistant']) +def median( + input_image: Image, + output_image: Image = None, + radius_x: int = 1, + radius_y: int = 1, + radius_z: int = 1, + connectivity: str = "box", + device: Device = None +) -> Image: + """Computes the local median of a pixels neighborhood. The neighborhood is defined + as a box or a sphere shape. Its size is specified by its halfwidth, halfheight, + and halfdepth (radius). For technical reasons, the area of the shpae must have + less than 1000 pixels. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 1 + Radius size along x axis. + radius_y: int = 1 + Radius size along y axis. + radius_z: int = 1 + Radius size along z axis. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_median3DSphere + """ + + from ._pyclesperanto import _median as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['filter', 'in assistant']) def minimum_box( input_image: Image, @@ -2406,6 +2765,7 @@ def minimum_box( from ._pyclesperanto import _minimum_box as op + warnings.warn("minimum_box : This function is deprecated. Consider using minimum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -2417,6 +2777,61 @@ def minimum_box( +@plugin_function(category=['filter', 'in assistant']) +def minimum( + input_image: Image, + output_image: Image = None, + radius_x: int = 0, + radius_y: int = 0, + radius_z: int = 0, + connectivity: str = "box", + device: Device = None +) -> Image: + """Computes the local minimum of a pixels cube neighborhood. The cubes size is + specified by its halfwidth, halfheight and halfdepth (radius). + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 0 + Radius size along x axis. + radius_y: int = 0 + Radius size along y axis. + radius_z: int = 0 + Radius size along z axis. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_minimum3DBox + [2] https://clij.github.io/clij2-docs/reference_minimum3DSphere + """ + + from ._pyclesperanto import _minimum as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['filter', 'in assistant']) def minimum_image_and_scalar( input_image: Image, @@ -2449,6 +2864,7 @@ def minimum_image_and_scalar( from ._pyclesperanto import _minimum_image_and_scalar as op + return op( device=device, src=input_image, @@ -2490,6 +2906,7 @@ def minimum_images( from ._pyclesperanto import _minimum_images as op + return op( device=device, src0=input_image0, @@ -2527,6 +2944,7 @@ def minimum_x_projection( from ._pyclesperanto import _minimum_x_projection as op + return op( device=device, src=input_image, @@ -2563,6 +2981,7 @@ def minimum_y_projection( from ._pyclesperanto import _minimum_y_projection as op + return op( device=device, src=input_image, @@ -2599,6 +3018,7 @@ def minimum_z_projection( from ._pyclesperanto import _minimum_z_projection as op + return op( device=device, src=input_image, @@ -2645,6 +3065,7 @@ def mode_box( from ._pyclesperanto import _mode_box as op + warnings.warn("mode_box : This function is deprecated. Consider using mode() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -2694,6 +3115,7 @@ def mode_sphere( from ._pyclesperanto import _mode_sphere as op + warnings.warn("mode_sphere : This function is deprecated. Consider using mode() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -2705,6 +3127,61 @@ def mode_sphere( +@plugin_function(category=['label processing', 'in assistant', 'bia-bob-suggestion']) +def mode( + input_image: Image, + output_image: Image = None, + radius_x: int = 1, + radius_y: int = 1, + radius_z: int = 1, + connectivity: str = "box", + device: Device = None +) -> Image: + """Computes the local mode of a pixels neighborhood. This neighborhood can be + shaped as a box or a sphere. This can be used to postprocess and locally correct + semantic segmentation results. The shape size is specified by its halfwidth, + halfheight, and halfdepth (radius). For technical reasons, the intensities must + lie within a range from 0 to 255 (uint8). In case multiple values have maximum + frequency, the smallest one is returned. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 1 + Radius size along x axis. + radius_y: int = 1 + Radius size along y axis. + radius_z: int = 1 + Radius size along z axis. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + """ + + from ._pyclesperanto import _mode as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['combine', 'bia-bob-suggestion']) def modulo_images( input_image0: Image, @@ -2733,6 +3210,7 @@ def modulo_images( from ._pyclesperanto import _modulo_images as op + return op( device=device, src0=input_image0, @@ -2774,6 +3252,7 @@ def multiply_image_and_position( from ._pyclesperanto import _multiply_image_and_position as op + return op( device=device, src=input_image, @@ -2815,6 +3294,7 @@ def multiply_image_and_scalar( from ._pyclesperanto import _multiply_image_and_scalar as op + return op( device=device, src=input_image, @@ -2856,6 +3336,7 @@ def multiply_images( from ._pyclesperanto import _multiply_images as op + return op( device=device, src0=input_image0, @@ -2906,6 +3387,7 @@ def nan_to_num( from ._pyclesperanto import _nan_to_num as op + return op( device=device, src=input_image, @@ -2951,6 +3433,7 @@ def nonzero_maximum_box( from ._pyclesperanto import _nonzero_maximum_box as op + warnings.warn("nonzero_maximum_box : This function is deprecated. Consider using nonzero_maximum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -2994,6 +3477,7 @@ def nonzero_maximum_diamond( from ._pyclesperanto import _nonzero_maximum_diamond as op + warnings.warn("nonzero_maximum_diamond : This function is deprecated. Consider using nonzero_maximum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -3003,6 +3487,56 @@ def nonzero_maximum_diamond( +@plugin_function +def nonzero_maximum( + input_image: Image, + output_image0: Image, + output_image1: Image = None, + connectivity: str = "box", + device: Device = None +) -> Image: + """Apply a maximum filter of a neighborhood to the input image. The neighborhood + shape can be a box or a sphere. The size is fixed to 1 and pixels with value 0 + are ignored. Note: Pixels with 0 value in the input image will not be + overwritten in the output image. Thus, the result image should be initialized by + copying the original image in advance. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image0: Image + Output flag (0 or 1). + output_image1: Image = None + Output image where results are written into. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_nonzeroMaximumBox + [2] https://clij.github.io/clij2-docs/reference_nonzeroMaximumDiamond + """ + + from ._pyclesperanto import _nonzero_maximum as op + + + return op( + device=device, + src=input_image, + dst0=output_image0, + dst1=output_image1, + connectivity=connectivity + ) + + + @plugin_function def nonzero_minimum_box( input_image: Image, @@ -3037,6 +3571,7 @@ def nonzero_minimum_box( from ._pyclesperanto import _nonzero_minimum_box as op + warnings.warn("nonzero_minimum_box : This function is deprecated. Consider using nonzero_minimum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -3080,6 +3615,7 @@ def nonzero_minimum_diamond( from ._pyclesperanto import _nonzero_minimum_diamond as op + warnings.warn("nonzero_minimum_diamond : This function is deprecated. Consider using nonzero_minimum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -3089,6 +3625,56 @@ def nonzero_minimum_diamond( +@plugin_function +def nonzero_minimum( + input_image: Image, + output_image0: Image, + output_image1: Image = None, + connectivity: str = "box", + device: Device = None +) -> Image: + """Apply a minimum filter of a neighborhood to the input image. The neighborhood + shape can be a box or a sphere. The radius is fixed to 1 and pixels with value 0 + are ignored.Note: Pixels with 0 value in the input image will not be overwritten + in the output image. Thus, the result image should be initialized by copying the + original image in advance. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image0: Image + Output flag (0 or 1). + output_image1: Image = None + Output image where results are written into. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_nonzeroMinimumBox + [2] https://clij.github.io/clij2-docs/reference_nonzeroMinimumDiamond + """ + + from ._pyclesperanto import _nonzero_minimum as op + + + return op( + device=device, + src=input_image, + dst0=output_image0, + dst1=output_image1, + connectivity=connectivity + ) + + + @plugin_function(category=['combine', 'binarize', 'in assistant']) def not_equal( input_image0: Image, @@ -3121,6 +3707,7 @@ def not_equal( from ._pyclesperanto import _not_equal as op + return op( device=device, src0=input_image0, @@ -3162,6 +3749,7 @@ def not_equal_constant( from ._pyclesperanto import _not_equal_constant as op + return op( device=device, src=input_image, @@ -3208,6 +3796,7 @@ def paste( from ._pyclesperanto import _paste as op + return op( device=device, src=input_image, @@ -3251,6 +3840,7 @@ def onlyzero_overwrite_maximum_box( from ._pyclesperanto import _onlyzero_overwrite_maximum_box as op + warnings.warn("onlyzero_overwrite_maximum_box : This function is deprecated. Consider using onlyzero_overwrite_maximum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -3292,6 +3882,7 @@ def onlyzero_overwrite_maximum_diamond( from ._pyclesperanto import _onlyzero_overwrite_maximum_diamond as op + warnings.warn("onlyzero_overwrite_maximum_diamond : This function is deprecated. Consider using onlyzero_overwrite_maximum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -3301,6 +3892,53 @@ def onlyzero_overwrite_maximum_diamond( +@plugin_function +def onlyzero_overwrite_maximum( + input_image: Image, + output_image0: Image, + output_image1: Image = None, + connectivity: str = "box", + device: Device = None +) -> Image: + """Apply a local maximum filter to an image which only overwrites pixels with value + 0. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image0: Image + Output flag value, 0 or 1. + output_image1: Image = None + Output image. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_onlyzeroOverwriteMaximumBox + [2] https://clij.github.io/clij2-docs/reference_onlyzeroOverwriteMaximumDiamond + """ + + from ._pyclesperanto import _onlyzero_overwrite_maximum as op + + + return op( + device=device, + src=input_image, + dst0=output_image0, + dst1=output_image1, + connectivity=connectivity + ) + + + @plugin_function(category=['filter', 'in assistant']) def power( input_image: Image, @@ -3333,6 +3971,7 @@ def power( from ._pyclesperanto import _power as op + return op( device=device, src=input_image, @@ -3373,6 +4012,7 @@ def power_images( from ._pyclesperanto import _power_images as op + return op( device=device, src0=input_image0, @@ -3434,6 +4074,7 @@ def range( from ._pyclesperanto import _range as op + return op( device=device, src=input_image, @@ -3481,6 +4122,7 @@ def read_values_from_positions( from ._pyclesperanto import _read_values_from_positions as op + return op( device=device, src=input_image, @@ -3523,6 +4165,7 @@ def replace_values( from ._pyclesperanto import _replace_values as op + return op( device=device, src0=input_image0, @@ -3566,6 +4209,7 @@ def replace_value( from ._pyclesperanto import _replace_value as op + return op( device=device, src=input_image, @@ -3614,6 +4258,7 @@ def maximum_sphere( from ._pyclesperanto import _maximum_sphere as op + warnings.warn("maximum_sphere : This function is deprecated. Consider using maximum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -3663,6 +4308,7 @@ def minimum_sphere( from ._pyclesperanto import _minimum_sphere as op + warnings.warn("minimum_sphere : This function is deprecated. Consider using minimum() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -3706,6 +4352,7 @@ def multiply_matrix( from ._pyclesperanto import _multiply_matrix as op + return op( device=device, src0=input_image0, @@ -3744,6 +4391,7 @@ def reciprocal( from ._pyclesperanto import _reciprocal as op + return op( device=device, src=input_image, @@ -3781,6 +4429,7 @@ def set( from ._pyclesperanto import _set as op + return op( device=device, src=input_image, @@ -3820,6 +4469,7 @@ def set_column( from ._pyclesperanto import _set_column as op + return op( device=device, src=input_image, @@ -3857,6 +4507,7 @@ def set_image_borders( from ._pyclesperanto import _set_image_borders as op + return op( device=device, src=input_image, @@ -3896,6 +4547,7 @@ def set_plane( from ._pyclesperanto import _set_plane as op + return op( device=device, src=input_image, @@ -3930,6 +4582,7 @@ def set_ramp_x( from ._pyclesperanto import _set_ramp_x as op + return op( device=device, src=input_image @@ -3962,6 +4615,7 @@ def set_ramp_y( from ._pyclesperanto import _set_ramp_y as op + return op( device=device, src=input_image @@ -3994,6 +4648,7 @@ def set_ramp_z( from ._pyclesperanto import _set_ramp_z as op + return op( device=device, src=input_image @@ -4030,6 +4685,7 @@ def set_row( from ._pyclesperanto import _set_row as op + return op( device=device, src=input_image, @@ -4067,6 +4723,7 @@ def set_nonzero_pixels_to_pixelindex( from ._pyclesperanto import _set_nonzero_pixels_to_pixelindex as op + return op( device=device, src=input_image, @@ -4106,6 +4763,7 @@ def set_where_x_equals_y( from ._pyclesperanto import _set_where_x_equals_y as op + return op( device=device, src=input_image, @@ -4144,6 +4802,7 @@ def set_where_x_greater_than_y( from ._pyclesperanto import _set_where_x_greater_than_y as op + return op( device=device, src=input_image, @@ -4182,6 +4841,7 @@ def set_where_x_smaller_than_y( from ._pyclesperanto import _set_where_x_smaller_than_y as op + return op( device=device, src=input_image, @@ -4217,6 +4877,7 @@ def sign( from ._pyclesperanto import _sign as op + return op( device=device, src=input_image, @@ -4257,6 +4918,7 @@ def smaller( from ._pyclesperanto import _smaller as op + return op( device=device, src0=input_image0, @@ -4298,6 +4960,7 @@ def smaller_constant( from ._pyclesperanto import _smaller_constant as op + return op( device=device, src=input_image, @@ -4339,6 +5002,7 @@ def smaller_or_equal( from ._pyclesperanto import _smaller_or_equal as op + return op( device=device, src0=input_image0, @@ -4380,6 +5044,7 @@ def smaller_or_equal_constant( from ._pyclesperanto import _smaller_or_equal_constant as op + return op( device=device, src=input_image, @@ -4418,6 +5083,7 @@ def sobel( from ._pyclesperanto import _sobel as op + return op( device=device, src=input_image, @@ -4451,6 +5117,7 @@ def square_root( from ._pyclesperanto import _square_root as op + return op( device=device, src=input_image, @@ -4488,6 +5155,7 @@ def std_z_projection( from ._pyclesperanto import _std_z_projection as op + return op( device=device, src=input_image, @@ -4527,6 +5195,7 @@ def subtract_image_from_scalar( from ._pyclesperanto import _subtract_image_from_scalar as op + return op( device=device, src=input_image, @@ -4566,6 +5235,7 @@ def sum_reduction_x( from ._pyclesperanto import _sum_reduction_x as op + return op( device=device, src=input_image, @@ -4603,6 +5273,7 @@ def sum_x_projection( from ._pyclesperanto import _sum_x_projection as op + return op( device=device, src=input_image, @@ -4639,6 +5310,7 @@ def sum_y_projection( from ._pyclesperanto import _sum_y_projection as op + return op( device=device, src=input_image, @@ -4675,6 +5347,7 @@ def sum_z_projection( from ._pyclesperanto import _sum_z_projection as op + return op( device=device, src=input_image, @@ -4711,6 +5384,7 @@ def transpose_xy( from ._pyclesperanto import _transpose_xy as op + return op( device=device, src=input_image, @@ -4747,6 +5421,7 @@ def transpose_xz( from ._pyclesperanto import _transpose_xz as op + return op( device=device, src=input_image, @@ -4783,6 +5458,7 @@ def transpose_yz( from ._pyclesperanto import _transpose_yz as op + return op( device=device, src=input_image, @@ -4820,6 +5496,7 @@ def undefined_to_zero( from ._pyclesperanto import _undefined_to_zero as op + return op( device=device, src=input_image, @@ -4867,6 +5544,7 @@ def variance_box( from ._pyclesperanto import _variance_box as op + warnings.warn("variance_box : This function is deprecated. Consider using variance() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -4917,6 +5595,7 @@ def variance_sphere( from ._pyclesperanto import _variance_sphere as op + warnings.warn("variance_sphere : This function is deprecated. Consider using variance() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -4928,6 +5607,62 @@ def variance_sphere( +@plugin_function(category=['filter', 'edge detection', 'in assistant']) +def variance( + input_image: Image, + output_image: Image = None, + radius_x: int = 1, + radius_y: int = 1, + radius_z: int = 1, + connectivity: str = "box", + device: Device = None +) -> Image: + """Computes the local variance of a pixels neighborhood (box or sphere). The + neighborhood size is specified by its halfwidth, halfheight and halfdepth + (radius). If 2D images are given, radius_z will be ignored. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 1 + Radius size along x axis. + radius_y: int = 1 + Radius size along y axis. + radius_z: int = 1 + Radius size along z axis. + connectivity: str = "box" + Filter neigborhood + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_varianceBox + [2] https://clij.github.io/clij2-docs/reference_varianceSphere + """ + + from ._pyclesperanto import _variance as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function def write_values_to_positions( input_image: Image, @@ -4958,6 +5693,7 @@ def write_values_to_positions( from ._pyclesperanto import _write_values_to_positions as op + return op( device=device, src=input_image, @@ -4993,6 +5729,7 @@ def x_position_of_maximum_x_projection( from ._pyclesperanto import _x_position_of_maximum_x_projection as op + return op( device=device, src=input_image, @@ -5028,6 +5765,7 @@ def x_position_of_minimum_x_projection( from ._pyclesperanto import _x_position_of_minimum_x_projection as op + return op( device=device, src=input_image, @@ -5063,6 +5801,7 @@ def y_position_of_maximum_y_projection( from ._pyclesperanto import _y_position_of_maximum_y_projection as op + return op( device=device, src=input_image, @@ -5098,6 +5837,7 @@ def y_position_of_minimum_y_projection( from ._pyclesperanto import _y_position_of_minimum_y_projection as op + return op( device=device, src=input_image, @@ -5133,6 +5873,7 @@ def z_position_of_maximum_z_projection( from ._pyclesperanto import _z_position_of_maximum_z_projection as op + return op( device=device, src=input_image, @@ -5168,6 +5909,7 @@ def z_position_of_minimum_z_projection( from ._pyclesperanto import _z_position_of_minimum_z_projection as op + return op( device=device, src=input_image, diff --git a/pyclesperanto/_tier2.py b/pyclesperanto/_tier2.py index c6a861c8..790561fb 100644 --- a/pyclesperanto/_tier2.py +++ b/pyclesperanto/_tier2.py @@ -7,6 +7,7 @@ from ._array import Image from ._decorators import plugin_function import numpy as np +import warnings @plugin_function(category=['combine', 'in assistant', 'bia-bob-suggestion']) @@ -41,6 +42,7 @@ def absolute_difference( from ._pyclesperanto import _absolute_difference as op + return op( device=device, src0=input_image0, @@ -82,6 +84,7 @@ def add_images( from ._pyclesperanto import _add_images as op + return op( device=device, src0=input_image0, @@ -128,6 +131,7 @@ def bottom_hat_box( from ._pyclesperanto import _bottom_hat_box as op + warnings.warn("bottom_hat_box : This method is deprecated. Consider using bottom_hat() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -176,6 +180,7 @@ def bottom_hat_sphere( from ._pyclesperanto import _bottom_hat_sphere as op + warnings.warn("bottom_hat_sphere : This method is deprecated. Consider using bottom_hat() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -187,6 +192,60 @@ def bottom_hat_sphere( +@plugin_function(category=['filter', 'background removal', 'in assistant', 'bia-bob-suggestion']) +def bottom_hat( + input_image: Image, + output_image: Image = None, + radius_x: float = 1, + radius_y: float = 1, + radius_z: float = 1, + connectivity: str = "box", + device: Device = None +) -> Image: + """Applies a bottomhat filter for background subtraction to the input image. + + Parameters + ---------- + input_image: Image + The input image where the background is subtracted from. + output_image: Image = None + The output image where results are written into. + radius_x: float = 1 + Radius of the background determination region in X. + radius_y: float = 1 + Radius of the background determination region in Y. + radius_z: float = 1 + Radius of the background determination region in Z. + connectivity: str = "box" + Element shape, "box" or "sphere" + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_bottomHatBox + [2] https://clij.github.io/clij2-docs/reference_bottomHatSphere + """ + + from ._pyclesperanto import _bottom_hat as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=float(radius_x), + radius_y=float(radius_y), + radius_z=float(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['combine', 'in assistant']) def clip( input_image: Image, @@ -222,6 +281,7 @@ def clip( from ._pyclesperanto import _clip as op + return op( device=device, src=input_image, @@ -267,6 +327,7 @@ def closing_box( from ._pyclesperanto import _closing_box as op + warnings.warn("closing_box : This method is deprecated. Consider using closing() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -313,6 +374,7 @@ def closing_sphere( from ._pyclesperanto import _closing_sphere as op + warnings.warn("closing_sphere : This method is deprecated. Consider using closing() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -324,6 +386,57 @@ def closing_sphere( +@plugin_function(category=['filter', 'in assistant', 'bia-bob-suggestion']) +def closing( + input_image: Image, + output_image: Image = None, + radius_x: int = 1, + radius_y: int = 1, + radius_z: int = 0, + connectivity: str = "box", + device: Device = None +) -> Image: + """Closing operator, sphereshaped Applies morphological closing to intensity images + using a sphereshaped footprint. This operator also works with binary images. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 1 + Radius along the x axis. + radius_y: int = 1 + Radius along the y axis. + radius_z: int = 0 + Radius along the z axis. + connectivity: str = "box" + Element shape, "box" or "sphere" + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + """ + + from ._pyclesperanto import _closing as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['combine', 'transform', 'in assistant', 'bia-bob-suggestion']) def concatenate_along_x( input_image0: Image, @@ -355,6 +468,7 @@ def concatenate_along_x( from ._pyclesperanto import _concatenate_along_x as op + return op( device=device, src0=input_image0, @@ -395,6 +509,7 @@ def concatenate_along_y( from ._pyclesperanto import _concatenate_along_y as op + return op( device=device, src0=input_image0, @@ -435,6 +550,7 @@ def concatenate_along_z( from ._pyclesperanto import _concatenate_along_z as op + return op( device=device, src0=input_image0, @@ -477,6 +593,7 @@ def count_touching_neighbors( from ._pyclesperanto import _count_touching_neighbors as op + return op( device=device, src=input_image, @@ -515,6 +632,7 @@ def crop_border( from ._pyclesperanto import _crop_border as op + return op( device=device, src=input_image, @@ -561,6 +679,7 @@ def divide_by_gaussian_background( from ._pyclesperanto import _divide_by_gaussian_background as op + return op( device=device, src=input_image, @@ -597,6 +716,7 @@ def degrees_to_radians( from ._pyclesperanto import _degrees_to_radians as op + return op( device=device, src=input_image, @@ -644,6 +764,7 @@ def detect_maxima_box( from ._pyclesperanto import _detect_maxima_box as op + warnings.warn("detect_maxima_box : This method is deprecated. Consider using detect_maxima() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -655,6 +776,62 @@ def detect_maxima_box( +@plugin_function(category=['binarize', 'in assistant']) +def detect_maxima( + input_image: Image, + output_image: Image = None, + radius_x: int = 0, + radius_y: int = 0, + radius_z: int = 0, + connectivity: str = "box", + device: Device = None +) -> Image: + """Detects local maxima in a given square/cubic neighborhood. Pixels in the + resulting image are set to 1 if there is no other pixel in a given radius which + has a higher intensity, and to 0 otherwise. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 0 + Radius along the x axis. + radius_y: int = 0 + Radius along the y axis. + radius_z: int = 0 + Radius along the z axis. + connectivity: str = "box" + Element shape, "box" or "sphere" + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_detectMaximaBox + [2] https://clij.github.io/clij2-docs/reference_detectMaximaSphere + """ + + from ._pyclesperanto import _detect_maxima as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['binarize', 'in assistant']) def detect_minima_box( input_image: Image, @@ -689,11 +866,12 @@ def detect_minima_box( References ---------- - [1] https://clij.github.io/clij2-docs/reference_detectMaximaBox + [1] https://clij.github.io/clij2-docs/reference_detectMinimaBox """ from ._pyclesperanto import _detect_minima_box as op + warnings.warn("detect_minima_box : This method is deprecated. Consider using detect_minima() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -705,6 +883,62 @@ def detect_minima_box( +@plugin_function(category=['binarize', 'in assistant']) +def detect_minima( + input_image: Image, + output_image: Image = None, + radius_x: int = 0, + radius_y: int = 0, + radius_z: int = 0, + connectivity: str = "box", + device: Device = None +) -> Image: + """Detects local maxima in a given square/cubic neighborhood. Pixels in the + resulting image are set to 1 if there is no other pixel in a given radius which + has a lower intensity, and to 0 otherwise. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 0 + Radius along the x axis. + radius_y: int = 0 + Radius along the y axis. + radius_z: int = 0 + Radius along the z axis. + connectivity: str = "box" + Element shape, "box" or "sphere" + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_detectMinimaBox + [2] https://clij.github.io/clij2-docs/reference_detectMinimaSphere + """ + + from ._pyclesperanto import _detect_minima as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['filter', 'background removal', 'bia-bob-suggestion']) def difference_of_gaussian( input_image: Image, @@ -754,6 +988,7 @@ def difference_of_gaussian( from ._pyclesperanto import _difference_of_gaussian as op + return op( device=device, src=input_image, @@ -797,6 +1032,7 @@ def extend_labeling_via_voronoi( from ._pyclesperanto import _extend_labeling_via_voronoi as op + return op( device=device, src=input_image, @@ -835,6 +1071,7 @@ def invert( from ._pyclesperanto import _invert as op + return op( device=device, src=input_image, @@ -873,6 +1110,7 @@ def label_spots( from ._pyclesperanto import _label_spots as op + return op( device=device, src=input_image, @@ -906,6 +1144,7 @@ def large_hessian_eigenvalue( from ._pyclesperanto import _large_hessian_eigenvalue as op + return op( device=device, src=input_image, @@ -940,6 +1179,7 @@ def maximum_of_all_pixels( from ._pyclesperanto import _maximum_of_all_pixels as op + return op( device=device, src=input_image @@ -973,6 +1213,7 @@ def minimum_of_all_pixels( from ._pyclesperanto import _minimum_of_all_pixels as op + return op( device=device, src=input_image @@ -1009,6 +1250,7 @@ def minimum_of_masked_pixels( from ._pyclesperanto import _minimum_of_masked_pixels as op + return op( device=device, src=input_image, @@ -1052,6 +1294,7 @@ def opening_box( from ._pyclesperanto import _opening_box as op + warnings.warn("opening_box : This method is deprecated. Consider using opening() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1098,6 +1341,7 @@ def opening_sphere( from ._pyclesperanto import _opening_sphere as op + warnings.warn("opening_sphere : This method is deprecated. Consider using opening() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1109,6 +1353,57 @@ def opening_sphere( +@plugin_function(category=['filter', 'in assistant', 'bia-bob-suggestion']) +def opening( + input_image: Image, + output_image: Image = None, + radius_x: float = 1, + radius_y: float = 1, + radius_z: float = 0, + connectivity: str = "box", + device: Device = None +) -> Image: + """Opening operator, sphereshaped Applies morphological opening to intensity images + using a sphereshaped footprint. This operator also works with binary images. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: float = 1 + Radius along the x axis. + radius_y: float = 1 + Radius along the y axis. + radius_z: float = 0 + Radius along the z axis. + connectivity: str = "box" + Element shape, "box" or "sphere" + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + """ + + from ._pyclesperanto import _opening as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=float(radius_x), + radius_y=float(radius_y), + radius_z=float(radius_z), + connectivity=connectivity + ) + + + @plugin_function def radians_to_degrees( input_image: Image, @@ -1134,6 +1429,7 @@ def radians_to_degrees( from ._pyclesperanto import _radians_to_degrees as op + return op( device=device, src=input_image, @@ -1171,6 +1467,7 @@ def reduce_labels_to_label_edges( from ._pyclesperanto import _reduce_labels_to_label_edges as op + return op( device=device, src=input_image, @@ -1204,6 +1501,7 @@ def small_hessian_eigenvalue( from ._pyclesperanto import _small_hessian_eigenvalue as op + return op( device=device, src=input_image, @@ -1241,6 +1539,7 @@ def square( from ._pyclesperanto import _square as op + return op( device=device, src=input_image, @@ -1280,6 +1579,7 @@ def squared_difference( from ._pyclesperanto import _squared_difference as op + return op( device=device, src0=input_image0, @@ -1328,6 +1628,7 @@ def standard_deviation_box( from ._pyclesperanto import _standard_deviation_box as op + warnings.warn("standard_deviation_box : This method is deprecated. Consider using standard_deviation() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1378,6 +1679,7 @@ def standard_deviation_sphere( from ._pyclesperanto import _standard_deviation_sphere as op + warnings.warn("standard_deviation_sphere : This method is deprecated. Consider using standard_deviation() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1389,6 +1691,62 @@ def standard_deviation_sphere( +@plugin_function(category=['filter', 'edge detection', 'in assistant', 'bia-bob-suggestion']) +def standard_deviation( + input_image: Image, + output_image: Image = None, + radius_x: int = 1, + radius_y: int = 1, + radius_z: int = 1, + connectivity: str = "box", + device: Device = None +) -> Image: + """Computes the local standard deviation of a pixels sphere neighborhood. The box + size is specified by its halfwidth, halfheight and halfdepth (radius). If 2D + images are given, radius_z will be ignored. + + Parameters + ---------- + input_image: Image + Input image to process. + output_image: Image = None + Output result image. + radius_x: int = 1 + Radius along the x axis. + radius_y: int = 1 + Radius along the y axis. + radius_z: int = 1 + Radius along the z axis. + connectivity: str = "box" + Neigborhood shape, "box" or "sphere" + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_standardDeviationBox + [2] https://clij.github.io/clij2-docs/reference_standardDeviationSphere + """ + + from ._pyclesperanto import _standard_deviation as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=int(radius_x), + radius_y=int(radius_y), + radius_z=int(radius_z), + connectivity=connectivity + ) + + + @plugin_function(category=['filter', 'background removal', 'in assistant', 'bia-bob-suggestion']) def subtract_gaussian_background( input_image: Image, @@ -1427,6 +1785,7 @@ def subtract_gaussian_background( from ._pyclesperanto import _subtract_gaussian_background as op + return op( device=device, src=input_image, @@ -1469,6 +1828,7 @@ def subtract_images( from ._pyclesperanto import _subtract_images as op + return op( device=device, src0=input_image0, @@ -1504,6 +1864,7 @@ def sum_of_all_pixels( from ._pyclesperanto import _sum_of_all_pixels as op + return op( device=device, src=input_image @@ -1548,6 +1909,7 @@ def top_hat_box( from ._pyclesperanto import _top_hat_box as op + warnings.warn("top_hat_box : This method is deprecated. Consider using top_hat() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1596,6 +1958,7 @@ def top_hat_sphere( from ._pyclesperanto import _top_hat_sphere as op + warnings.warn("top_hat_sphere : This method is deprecated. Consider using top_hat() instead.", DeprecationWarning) return op( device=device, src=input_image, @@ -1605,3 +1968,57 @@ def top_hat_sphere( radius_z=float(radius_z) ) + + +@plugin_function(category=['filter', 'background removal', 'in assistant', 'bia-bob-suggestion']) +def top_hat( + input_image: Image, + output_image: Image = None, + radius_x: float = 1, + radius_y: float = 1, + radius_z: float = 1, + connectivity: str = "box", + device: Device = None +) -> Image: + """Applies a tophat filter for background subtraction to the input image. + + Parameters + ---------- + input_image: Image + The input image where the background is subtracted from. + output_image: Image = None + The output image where results are written into. + radius_x: float = 1 + Radius of the background determination region in X. + radius_y: float = 1 + Radius of the background determination region in Y. + radius_z: float = 1 + Radius of the background determination region in Z. + connectivity: str = "box" + Element shape, "box" or "sphere" + device: Device = None + Device to perform the operation on. + + Returns + ------- + Image + + References + ---------- + [1] https://clij.github.io/clij2-docs/reference_topHatBox + [2] https://clij.github.io/clij2-docs/reference_topHatSphere + """ + + from ._pyclesperanto import _top_hat as op + + + return op( + device=device, + src=input_image, + dst=output_image, + radius_x=float(radius_x), + radius_y=float(radius_y), + radius_z=float(radius_z), + connectivity=connectivity + ) + diff --git a/pyclesperanto/_tier3.py b/pyclesperanto/_tier3.py index 8320291e..6c813c0b 100644 --- a/pyclesperanto/_tier3.py +++ b/pyclesperanto/_tier3.py @@ -7,6 +7,7 @@ from ._array import Image from ._decorators import plugin_function import numpy as np +import warnings @plugin_function @@ -35,6 +36,7 @@ def bounding_box( from ._pyclesperanto import _bounding_box as op + return op( device=device, src=input_image @@ -67,6 +69,7 @@ def center_of_mass( from ._pyclesperanto import _center_of_mass as op + return op( device=device, src=input_image @@ -106,6 +109,7 @@ def exclude_labels( from ._pyclesperanto import _exclude_labels as op + return op( device=device, src=input_image, @@ -151,6 +155,7 @@ def exclude_labels_on_edges( from ._pyclesperanto import _exclude_labels_on_edges as op + return op( device=device, src=input_image, @@ -190,6 +195,7 @@ def flag_existing_labels( from ._pyclesperanto import _flag_existing_labels as op + return op( device=device, src=input_image, @@ -228,6 +234,7 @@ def gamma_correction( from ._pyclesperanto import _gamma_correction as op + return op( device=device, src=input_image, @@ -268,6 +275,7 @@ def generate_binary_overlap_matrix( from ._pyclesperanto import _generate_binary_overlap_matrix as op + return op( device=device, src0=input_image0, @@ -307,6 +315,7 @@ def generate_touch_matrix( from ._pyclesperanto import _generate_touch_matrix as op + return op( device=device, src=input_image, @@ -362,6 +371,7 @@ def histogram( from ._pyclesperanto import _histogram as op + return op( device=device, src=input_image, @@ -403,6 +413,7 @@ def jaccard_index( from ._pyclesperanto import _jaccard_index as op + return op( device=device, src0=input_image0, @@ -441,6 +452,7 @@ def labelled_spots_to_pointlist( from ._pyclesperanto import _labelled_spots_to_pointlist as op + return op( device=device, src=input_image, @@ -471,6 +483,7 @@ def maximum_position( from ._pyclesperanto import _maximum_position as op + return op( device=device, src=input_image @@ -503,6 +516,7 @@ def mean_of_all_pixels( from ._pyclesperanto import _mean_of_all_pixels as op + return op( device=device, src=input_image @@ -532,6 +546,7 @@ def minimum_position( from ._pyclesperanto import _minimum_position as op + return op( device=device, src=input_image diff --git a/pyclesperanto/_tier4.py b/pyclesperanto/_tier4.py index 9acc0f21..8563cc3c 100644 --- a/pyclesperanto/_tier4.py +++ b/pyclesperanto/_tier4.py @@ -7,6 +7,7 @@ from ._array import Image from ._decorators import plugin_function import numpy as np +import warnings @plugin_function @@ -37,6 +38,7 @@ def label_bounding_box( from ._pyclesperanto import _label_bounding_box as op + return op( device=device, src=input_image, @@ -72,6 +74,7 @@ def mean_squared_error( from ._pyclesperanto import _mean_squared_error as op + return op( device=device, src0=input_image0, @@ -108,6 +111,7 @@ def spots_to_pointlist( from ._pyclesperanto import _spots_to_pointlist as op + return op( device=device, src=input_image, @@ -148,6 +152,7 @@ def relabel_sequential( from ._pyclesperanto import _relabel_sequential as op + return op( device=device, src=input_image, @@ -186,6 +191,7 @@ def threshold_otsu( from ._pyclesperanto import _threshold_otsu as op + return op( device=device, src=input_image, diff --git a/pyclesperanto/_tier5.py b/pyclesperanto/_tier5.py index 58b29a60..e6b53698 100644 --- a/pyclesperanto/_tier5.py +++ b/pyclesperanto/_tier5.py @@ -7,6 +7,7 @@ from ._array import Image from ._decorators import plugin_function import numpy as np +import warnings @plugin_function(category=['combine']) @@ -37,6 +38,7 @@ def array_equal( from ._pyclesperanto import _array_equal as op + return op( device=device, src0=input_image0, @@ -75,6 +77,7 @@ def combine_labels( from ._pyclesperanto import _combine_labels as op + return op( device=device, src0=input_image0, @@ -101,7 +104,7 @@ def connected_components_labeling( output_image: Image = None Output label image. connectivity: str = 'box' - Defines pixel neighborhood relationship. + Defines pixel neighborhood relationship, "box" or "sphere". device: Device = None Device to perform the operation on. @@ -116,6 +119,7 @@ def connected_components_labeling( from ._pyclesperanto import _connected_components_labeling as op + return op( device=device, src=input_image, diff --git a/pyclesperanto/_tier6.py b/pyclesperanto/_tier6.py index d94b977e..a1d1bb46 100644 --- a/pyclesperanto/_tier6.py +++ b/pyclesperanto/_tier6.py @@ -7,6 +7,7 @@ from ._array import Image from ._decorators import plugin_function import numpy as np +import warnings @plugin_function(category=['label processing', 'in assistant', 'bia-bob-suggestion']) @@ -38,6 +39,7 @@ def dilate_labels( from ._pyclesperanto import _dilate_labels as op + return op( device=device, src=input_image, @@ -79,6 +81,7 @@ def erode_labels( from ._pyclesperanto import _erode_labels as op + return op( device=device, src=input_image, @@ -125,6 +128,7 @@ def gauss_otsu_labeling( from ._pyclesperanto import _gauss_otsu_labeling as op + return op( device=device, src0=input_image0, @@ -164,6 +168,7 @@ def masked_voronoi_labeling( from ._pyclesperanto import _masked_voronoi_labeling as op + return op( device=device, src=input_image, @@ -201,6 +206,7 @@ def voronoi_labeling( from ._pyclesperanto import _voronoi_labeling as op + return op( device=device, src=input_image, diff --git a/pyclesperanto/_tier7.py b/pyclesperanto/_tier7.py index 86360467..c3461bb6 100644 --- a/pyclesperanto/_tier7.py +++ b/pyclesperanto/_tier7.py @@ -7,6 +7,7 @@ from ._array import Image from ._decorators import plugin_function import numpy as np +import warnings @plugin_function @@ -46,6 +47,7 @@ def affine_transform( from ._pyclesperanto import _affine_transform as op + return op( device=device, src=input_image, @@ -100,6 +102,7 @@ def eroded_otsu_labeling( from ._pyclesperanto import _eroded_otsu_labeling as op + return op( device=device, src=input_image, @@ -164,6 +167,7 @@ def rigid_transform( from ._pyclesperanto import _rigid_transform as op + return op( device=device, src=input_image, @@ -226,6 +230,7 @@ def rotate( from ._pyclesperanto import _rotate as op + return op( device=device, src=input_image, @@ -283,6 +288,7 @@ def scale( from ._pyclesperanto import _scale as op + return op( device=device, src=input_image, @@ -334,6 +340,7 @@ def translate( from ._pyclesperanto import _translate as op + return op( device=device, src=input_image, @@ -378,6 +385,7 @@ def closing_labels( from ._pyclesperanto import _closing_labels as op + return op( device=device, src=input_image, @@ -415,6 +423,7 @@ def erode_connected_labels( from ._pyclesperanto import _erode_connected_labels as op + return op( device=device, src=input_image, @@ -456,6 +465,7 @@ def opening_labels( from ._pyclesperanto import _opening_labels as op + return op( device=device, src=input_image, @@ -506,6 +516,7 @@ def voronoi_otsu_labeling( from ._pyclesperanto import _voronoi_otsu_labeling as op + return op( device=device, src=input_image, diff --git a/pyclesperanto/_tier8.py b/pyclesperanto/_tier8.py index bdba7bb1..d3a889bb 100644 --- a/pyclesperanto/_tier8.py +++ b/pyclesperanto/_tier8.py @@ -7,6 +7,7 @@ from ._array import Image from ._decorators import plugin_function import numpy as np +import warnings @plugin_function(category=['label processing', 'in assistant', 'bia-bob-suggestion']) @@ -40,6 +41,7 @@ def smooth_labels( from ._pyclesperanto import _smooth_labels as op + return op( device=device, src=input_image, @@ -79,6 +81,7 @@ def smooth_connected_labels( from ._pyclesperanto import _smooth_connected_labels as op + return op( device=device, src=input_image, diff --git a/src/wrapper/execute_.cpp b/src/wrapper/execute_.cpp index b50d1c94..531cf866 100644 --- a/src/wrapper/execute_.cpp +++ b/src/wrapper/execute_.cpp @@ -77,8 +77,85 @@ auto py_execute(const cle::Device::Pointer &device, const std::string &kernel_na cle::execute(device, kernel_info, clic_parameters, global_range, clic_constants); } +auto py_native_execute(const cle::Device::Pointer &device, const std::string &kernel_name, const std::string &kernel_source, const py::dict ¶meters, const py::tuple &global, const py::tuple &local) -> void +{ + cle::RangeArray global_range = {1, 1, 1}; + switch (global.size()) + { + case 1: + global_range[0] = global[0].cast(); + break; + case 2: + global_range[0] = global[1].cast(); + global_range[1] = global[0].cast(); + break; + case 3: + global_range[0] = global[2].cast(); + global_range[1] = global[1].cast(); + global_range[2] = global[0].cast(); + break; + default: + throw std::invalid_argument("Error: range tuple must have 3 elements or less. Received " + std::to_string(global.size()) + " elements."); + break; + } + + cle::RangeArray local_range = {1, 1, 1}; + switch (local.size()) + { + case 1: + local_range[0] = local[0].cast(); + break; + case 2: + local_range[0] = local[1].cast(); + local_range[1] = local[0].cast(); + break; + case 3: + local_range[0] = local[2].cast(); + local_range[1] = local[1].cast(); + local_range[2] = local[0].cast(); + break; + default: + throw std::invalid_argument("Error: range tuple must have 3 elements or less. Received " + std::to_string(local.size()) + " elements."); + break; + } + + // manage kernel name and code + const cle::KernelInfo kernel_info = {kernel_name, kernel_source}; + + // convert py::dict paramter to vector> + cle::ParameterList clic_parameters; + for (auto item : parameters) + { + // check if item.second is cle::Array::Pointer + if (py::isinstance(item.second)) + { + clic_parameters.push_back({item.first.cast(), item.second.cast()}); + } + else if (py::isinstance(item.second)) + { + // convert py::int to int + clic_parameters.push_back({item.first.cast(), item.second.cast()}); + } + else if (py::isinstance(item.second)) + { + // convert py::float to float + clic_parameters.push_back({item.first.cast(), item.second.cast()}); + } + else + { + throw std::invalid_argument("Error: parameter type not supported. Received " + std::string(py::str(item.second.get_type()).cast())); + } + } + + // execute + cle::native_execute(device, kernel_info, clic_parameters, global_range, local_range); +} + auto execute_(py::module_ &m) -> void { m.def("_execute", &py_execute, "Call execute function from C++.", py::arg("device"), py::arg("kernel_name"), py::arg("kernel_source"), py::arg("parameters"), py::arg("range"), py::arg("constants")); + + m.def("_native_execute", &py_native_execute, "Call native_execute function from C++.", + py::arg("device"), py::arg("kernel_name"), py::arg("kernel_source"), py::arg("parameters"), py::arg("global"), py::arg("local")); } diff --git a/src/wrapper/tier1_.cpp b/src/wrapper/tier1_.cpp index ff293f01..675b20df 100644 --- a/src/wrapper/tier1_.cpp +++ b/src/wrapper/tier1_.cpp @@ -110,6 +110,11 @@ m.def("_dilate_sphere", &cle::tier1::dilate_sphere_func, "Call dilate_sphere fro py::arg("device"), py::arg("src"), py::arg("dst")); +m.def("_dilate", &cle::tier1::dilate_func, "Call dilate from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("connectivity")); + + m.def("_divide_images", &cle::tier1::divide_images_func, "Call divide_images from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src0"), py::arg("src1"), py::arg("dst")); @@ -140,6 +145,11 @@ m.def("_erode_sphere", &cle::tier1::erode_sphere_func, "Call erode_sphere from C py::arg("device"), py::arg("src"), py::arg("dst")); +m.def("_erode", &cle::tier1::erode_func, "Call erode from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("connectivity")); + + m.def("_exponential", &cle::tier1::exponential_func, "Call exponential from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst")); @@ -210,6 +220,11 @@ m.def("_laplace_diamond", &cle::tier1::laplace_diamond_func, "Call laplace_diamo py::arg("device"), py::arg("src"), py::arg("dst")); +m.def("_laplace", &cle::tier1::laplace_func, "Call laplace from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("connectivity")); + + m.def("_local_cross_correlation", &cle::tier1::local_cross_correlation_func, "Call local_cross_correlation from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src0"), py::arg("src1"), py::arg("dst")); @@ -245,6 +260,11 @@ m.def("_maximum_box", &cle::tier1::maximum_box_func, "Call maximum_box from C++. py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_maximum", &cle::tier1::maximum_func, "Call maximum from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_maximum_x_projection", &cle::tier1::maximum_x_projection_func, "Call maximum_x_projection from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst")); @@ -270,6 +290,11 @@ m.def("_mean_sphere", &cle::tier1::mean_sphere_func, "Call mean_sphere from C++. py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_mean", &cle::tier1::mean_func, "Call mean from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_mean_x_projection", &cle::tier1::mean_x_projection_func, "Call mean_x_projection from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst")); @@ -295,11 +320,21 @@ m.def("_median_sphere", &cle::tier1::median_sphere_func, "Call median_sphere fro py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_median", &cle::tier1::median_func, "Call median from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_minimum_box", &cle::tier1::minimum_box_func, "Call minimum_box from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_minimum", &cle::tier1::minimum_func, "Call minimum from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_minimum_image_and_scalar", &cle::tier1::minimum_image_and_scalar_func, "Call minimum_image_and_scalar from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("scalar")); @@ -335,6 +370,11 @@ m.def("_mode_sphere", &cle::tier1::mode_sphere_func, "Call mode_sphere from C++. py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_mode", &cle::tier1::mode_func, "Call mode from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_modulo_images", &cle::tier1::modulo_images_func, "Call modulo_images from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src0"), py::arg("src1"), py::arg("dst")); @@ -370,6 +410,11 @@ m.def("_nonzero_maximum_diamond", &cle::tier1::nonzero_maximum_diamond_func, "Ca py::arg("device"), py::arg("src"), py::arg("dst0"), py::arg("dst1")); +m.def("_nonzero_maximum", &cle::tier1::nonzero_maximum_func, "Call nonzero_maximum from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst0"), py::arg("dst1"), py::arg("connectivity")); + + m.def("_nonzero_minimum_box", &cle::tier1::nonzero_minimum_box_func, "Call nonzero_minimum_box from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst0"), py::arg("dst1")); @@ -380,6 +425,11 @@ m.def("_nonzero_minimum_diamond", &cle::tier1::nonzero_minimum_diamond_func, "Ca py::arg("device"), py::arg("src"), py::arg("dst0"), py::arg("dst1")); +m.def("_nonzero_minimum", &cle::tier1::nonzero_minimum_func, "Call nonzero_minimum from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst0"), py::arg("dst1"), py::arg("connectivity")); + + m.def("_not_equal", &cle::tier1::not_equal_func, "Call not_equal from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src0"), py::arg("src1"), py::arg("dst")); @@ -405,6 +455,11 @@ m.def("_onlyzero_overwrite_maximum_diamond", &cle::tier1::onlyzero_overwrite_max py::arg("device"), py::arg("src"), py::arg("dst0"), py::arg("dst1")); +m.def("_onlyzero_overwrite_maximum", &cle::tier1::onlyzero_overwrite_maximum_func, "Call onlyzero_overwrite_maximum from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst0"), py::arg("dst1"), py::arg("connectivity")); + + m.def("_power", &cle::tier1::power_func, "Call power from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("scalar")); @@ -610,6 +665,11 @@ m.def("_variance_sphere", &cle::tier1::variance_sphere_func, "Call variance_sphe py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_variance", &cle::tier1::variance_func, "Call variance from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_write_values_to_positions", &cle::tier1::write_values_to_positions_func, "Call write_values_to_positions from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst")); diff --git a/src/wrapper/tier2_.cpp b/src/wrapper/tier2_.cpp index ae36f1d0..12e09e57 100644 --- a/src/wrapper/tier2_.cpp +++ b/src/wrapper/tier2_.cpp @@ -30,6 +30,11 @@ m.def("_bottom_hat_sphere", &cle::tier2::bottom_hat_sphere_func, "Call bottom_ha py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_bottom_hat", &cle::tier2::bottom_hat_func, "Call bottom_hat from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_clip", &cle::tier2::clip_func, "Call clip from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("min_intensity"), py::arg("max_intensity")); @@ -45,6 +50,11 @@ m.def("_closing_sphere", &cle::tier2::closing_sphere_func, "Call closing_sphere py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_closing", &cle::tier2::closing_func, "Call closing from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_concatenate_along_x", &cle::tier2::concatenate_along_x_func, "Call concatenate_along_x from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src0"), py::arg("src1"), py::arg("dst")); @@ -85,11 +95,21 @@ m.def("_detect_maxima_box", &cle::tier2::detect_maxima_box_func, "Call detect_ma py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_detect_maxima", &cle::tier2::detect_maxima_func, "Call detect_maxima from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_detect_minima_box", &cle::tier2::detect_minima_box_func, "Call detect_minima_box from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_detect_minima", &cle::tier2::detect_minima_func, "Call detect_minima from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_difference_of_gaussian", &cle::tier2::difference_of_gaussian_func, "Call difference_of_gaussian from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("sigma1_x"), py::arg("sigma1_y"), py::arg("sigma1_z"), py::arg("sigma2_x"), py::arg("sigma2_y"), py::arg("sigma2_z")); @@ -140,6 +160,11 @@ m.def("_opening_sphere", &cle::tier2::opening_sphere_func, "Call opening_sphere py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_opening", &cle::tier2::opening_func, "Call opening from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_radians_to_degrees", &cle::tier2::radians_to_degrees_func, "Call radians_to_degrees from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst")); @@ -175,6 +200,11 @@ m.def("_standard_deviation_sphere", &cle::tier2::standard_deviation_sphere_func, py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); +m.def("_standard_deviation", &cle::tier2::standard_deviation_func, "Call standard_deviation from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + + m.def("_subtract_gaussian_background", &cle::tier2::subtract_gaussian_background_func, "Call subtract_gaussian_background from C++.", py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("sigma_x"), py::arg("sigma_y"), py::arg("sigma_z")); @@ -199,5 +229,10 @@ m.def("_top_hat_sphere", &cle::tier2::top_hat_sphere_func, "Call top_hat_sphere py::return_value_policy::take_ownership, py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z")); + +m.def("_top_hat", &cle::tier2::top_hat_func, "Call top_hat from C++.", + py::return_value_policy::take_ownership, + py::arg("device"), py::arg("src"), py::arg("dst"), py::arg("radius_x"), py::arg("radius_y"), py::arg("radius_z"), py::arg("connectivity")); + } From d1488f0c503702027ad4bbd09448c092e4b1b0a3 Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 10:54:11 +0200 Subject: [PATCH 02/10] update tests --- tests/test_bottom_hat_box.py | 4 +-- tests/test_bottom_hat_sphere.py | 2 +- tests/test_closing_box.py | 4 +-- tests/test_closing_sphere.py | 4 +-- ... => test_connected_components_labeling.py} | 17 +++++++++++ ...t_connected_components_labeling_diamond.py | 20 ------------- tests/test_detect_maxima_box.py | 2 +- tests/test_detect_minima_box.py | 2 +- tests/test_dilate_box.py | 4 +-- tests/test_dilate_sphere.py | 2 +- tests/test_erode_box.py | 2 +- tests/test_erode_labels.py | 2 +- tests/test_erode_sphere.py | 2 +- tests/test_execute.py | 28 +++++++++++++++++++ tests/test_laplace_box.py | 2 +- ...lace_diamond.py => test_laplace_sphere.py} | 4 +-- tests/test_maximum_box.py | 2 +- tests/test_maximum_sphere.py | 4 +-- tests/test_mean_box.py | 2 +- tests/test_mean_sphere.py | 2 +- tests/test_median_box.py | 4 +-- tests/test_median_sphere.py | 2 +- tests/test_minimum_box.py | 2 +- tests/test_mode_box.py | 4 +-- tests/test_mode_sphere.py | 4 +-- tests/test_nonzero_maximum_box.py | 2 +- ...mond.py => test_nonzero_maximum_sphere.py} | 4 +-- tests/test_nonzero_minimum_box.py | 2 +- ...mond.py => test_nonzero_minimum_sphere.py} | 4 +-- tests/test_onlyzero_overwrite_maximum_box.py | 2 +- ...test_onlyzero_overwrite_maximum_diamond.py | 4 +-- tests/test_opening_box.py | 4 +-- tests/test_opening_sphere.py | 4 +-- tests/test_standard_deviation_box.py | 2 +- tests/test_standard_deviation_sphere.py | 2 +- tests/test_top_hat_box.py | 4 +-- tests/test_top_hat_sphere.py | 2 +- tests/test_variance_box.py | 2 +- tests/test_variance_sphere.py | 2 +- 39 files changed, 96 insertions(+), 71 deletions(-) rename tests/{test_connected_components_labeling_box.py => test_connected_components_labeling.py} (76%) delete mode 100644 tests/test_connected_components_labeling_diamond.py rename tests/{test_laplace_diamond.py => test_laplace_sphere.py} (89%) rename tests/{test_nonzero_maximum_diamond.py => test_nonzero_maximum_sphere.py} (89%) rename tests/{test_nonzero_minimum_diamond.py => test_nonzero_minimum_sphere.py} (89%) diff --git a/tests/test_bottom_hat_box.py b/tests/test_bottom_hat_box.py index dec26d94..381576d5 100644 --- a/tests/test_bottom_hat_box.py +++ b/tests/test_bottom_hat_box.py @@ -4,7 +4,7 @@ cle.select_device("TX") -def test_bottom_hat_sphere(): +def test_bottom_hat_box(): test = cle.push( np.asarray( [ @@ -18,7 +18,7 @@ def test_bottom_hat_sphere(): ) result = cle.create_like(test) - cle.bottom_hat_box(test, result, 1, 1, 0) + cle.bottom_hat(test, result, 1, 1, 0) print(result) diff --git a/tests/test_bottom_hat_sphere.py b/tests/test_bottom_hat_sphere.py index 38a1d6a8..a58d014f 100644 --- a/tests/test_bottom_hat_sphere.py +++ b/tests/test_bottom_hat_sphere.py @@ -18,7 +18,7 @@ def test_bottom_hat_sphere(): ) result = cle.create_like(test) - cle.bottom_hat_sphere(test, result, 1, 1, 0) + cle.bottom_hat(test, result, 1, 1, 0, "sphere") print(result) diff --git a/tests/test_closing_box.py b/tests/test_closing_box.py index 28e8b8f8..9dcb8393 100644 --- a/tests/test_closing_box.py +++ b/tests/test_closing_box.py @@ -31,7 +31,7 @@ def test_close_box_2d(): ) ) - gpu_output = cle.closing_box(gpu_input, radius_x=1, radius_y=1) + gpu_output = cle.closing(gpu_input, radius_x=1, radius_y=1) a = cle.pull(gpu_output) b = cle.pull(gpu_reference) @@ -90,7 +90,7 @@ def test_close_box_3d(): ) ) - gpu_output = cle.closing_box(gpu_input, radius_x=1, radius_y=1) + gpu_output = cle.closing(gpu_input, radius_x=1, radius_y=1) a = cle.pull(gpu_output) b = cle.pull(gpu_reference) diff --git a/tests/test_closing_sphere.py b/tests/test_closing_sphere.py index 952ca7cd..063469f5 100644 --- a/tests/test_closing_sphere.py +++ b/tests/test_closing_sphere.py @@ -31,7 +31,7 @@ def test_close_sphere_2d(): ) ) - gpu_output = cle.closing_sphere(gpu_input, radius_x=1, radius_y=1) + gpu_output = cle.closing(gpu_input, radius_x=1, radius_y=1, connectivity="sphere") a = cle.pull(gpu_output) b = cle.pull(gpu_reference) @@ -90,7 +90,7 @@ def test_close_sphere_3d(): ) ) - gpu_output = cle.closing_sphere(gpu_input, radius_x=1, radius_y=1) + gpu_output = cle.closing(gpu_input, radius_x=1, radius_y=1, connectivity="sphere") a = cle.pull(gpu_output) b = cle.pull(gpu_reference) diff --git a/tests/test_connected_components_labeling_box.py b/tests/test_connected_components_labeling.py similarity index 76% rename from tests/test_connected_components_labeling_box.py rename to tests/test_connected_components_labeling.py index 010d861a..547f57f6 100644 --- a/tests/test_connected_components_labeling_box.py +++ b/tests/test_connected_components_labeling.py @@ -21,6 +21,23 @@ def test_connected_components_labeling_box(): assert np.array_equal(a, b) + +def test_connected_components_labeling_sphere(): + gpu_input = cle.push(np.asarray([[0, 1, 0, 1], [0, 1, 0, 0], [1, 0, 0, 1]])) + + gpu_reference = cle.push(np.asarray([[0, 1, 0, 2], [0, 1, 0, 0], [3, 0, 0, 4]])) + + gpu_output = cle.connected_components_labeling(gpu_input, connectivity="sphere") + + a = cle.pull(gpu_output) + b = cle.pull(gpu_reference) + + print(b) + print(a) + + assert np.array_equal(a, b) + + def test_connected_components_labeling_box_blobs(): # initialize GPU print("Used GPU: " + cle.get_device().name) diff --git a/tests/test_connected_components_labeling_diamond.py b/tests/test_connected_components_labeling_diamond.py deleted file mode 100644 index c07e4b58..00000000 --- a/tests/test_connected_components_labeling_diamond.py +++ /dev/null @@ -1,20 +0,0 @@ -import pyclesperanto as cle -import numpy as np - -cle.select_device("TX") - - -def test_connected_components_labeling_diamond(): - gpu_input = cle.push(np.asarray([[0, 1, 0, 1], [0, 1, 0, 0], [1, 0, 0, 1]])) - - gpu_reference = cle.push(np.asarray([[0, 1, 0, 2], [0, 1, 0, 0], [3, 0, 0, 4]])) - - gpu_output = cle.connected_components_labeling(gpu_input, connectivity="diamond") - - a = cle.pull(gpu_output) - b = cle.pull(gpu_reference) - - print(b) - print(a) - - assert np.array_equal(a, b) diff --git a/tests/test_detect_maxima_box.py b/tests/test_detect_maxima_box.py index e4262fb9..27ab7e2f 100644 --- a/tests/test_detect_maxima_box.py +++ b/tests/test_detect_maxima_box.py @@ -30,7 +30,7 @@ def test_detect_maxima_box(): ) ) - cle.detect_maxima_box(gpu_input, gpu_output) + cle.detect_maxima(gpu_input, gpu_output) a = cle.pull(gpu_output) b = cle.pull(gpu_reference) diff --git a/tests/test_detect_minima_box.py b/tests/test_detect_minima_box.py index 5b27e6af..680da709 100644 --- a/tests/test_detect_minima_box.py +++ b/tests/test_detect_minima_box.py @@ -30,7 +30,7 @@ def test_detect_minima_box(): ) ) - cle.detect_minima_box(gpu_input, gpu_output) + cle.detect_minima(gpu_input, gpu_output) a = cle.pull(gpu_output) b = cle.pull(gpu_reference) diff --git a/tests/test_dilate_box.py b/tests/test_dilate_box.py index ee164a44..719b892b 100644 --- a/tests/test_dilate_box.py +++ b/tests/test_dilate_box.py @@ -4,7 +4,7 @@ cle.select_device("TX") -def test_dilate_box(): +def test_dilate(): test = cle.push( np.asarray( [ @@ -30,7 +30,7 @@ def test_dilate_box(): ) result = cle.create(test) - cle.dilate_box(test, result) + cle.dilate(test, result) print(result) diff --git a/tests/test_dilate_sphere.py b/tests/test_dilate_sphere.py index e5ad3dc5..83ecb8ab 100644 --- a/tests/test_dilate_sphere.py +++ b/tests/test_dilate_sphere.py @@ -30,7 +30,7 @@ def test_dilate_sphere(): ) result = cle.create(test) - cle.dilate_sphere(test, result) + cle.dilate(test, result, "sphere") print(result) diff --git a/tests/test_erode_box.py b/tests/test_erode_box.py index cc1573bb..ca5195e4 100644 --- a/tests/test_erode_box.py +++ b/tests/test_erode_box.py @@ -30,7 +30,7 @@ def test_erode_box(): ) result = cle.create(test) - cle.erode_box(test, result) + cle.erode(test, result) print(result) diff --git a/tests/test_erode_labels.py b/tests/test_erode_labels.py index 6d64c184..ad3fe099 100644 --- a/tests/test_erode_labels.py +++ b/tests/test_erode_labels.py @@ -60,7 +60,7 @@ def test_erode_labels_2d_1(): for r in range(5): print("r", r) - gpu_reference = cle.minimum_box(gpu_input, radius_x=r, radius_y=r) + gpu_reference = cle.minimum(gpu_input, radius_x=r, radius_y=r) gpu_output = cle.erode_labels(gpu_input, radius=r, relabel=False) print(gpu_output) diff --git a/tests/test_erode_sphere.py b/tests/test_erode_sphere.py index 359384e6..3907c466 100644 --- a/tests/test_erode_sphere.py +++ b/tests/test_erode_sphere.py @@ -30,7 +30,7 @@ def test_erode_sphere(): ) result = cle.create(test) - cle.erode_sphere(test, result) + cle.erode(test, result, "sphere") print(result) diff --git a/tests/test_execute.py b/tests/test_execute.py index 1dc3d64d..6c0c18bf 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -22,6 +22,33 @@ } """ +add_native_ocl = """ +__kernel void add_arrays(__global const float* a, __global const float* b, __global float* c, const unsigned int n) { + int id = get_global_id(0); + if (id < n) { + c[id] = a[id] + b[id]; + } +} +""" + + +def test_execute_native(): + input1 = cle.push(np.ones(10).astype(float)) + input2 = cle.push(np.ones(10).astype(float) * 2) + output = cle.create(input1) + + param = {'a': input1, 'b': input2, 'c': output, 'n': int(np.prod(input1.shape))} + cle.native_execute(kernel_source=add_native_ocl, kernel_name="add_arrays", global_size=input1.shape, local_size=(1,1,1), parameters=param) + + print(output) + + a = cle.pull(output) + assert (np.min(a) == 3) + assert (np.max(a) == 3) + assert (np.mean(a) == 3) + + + def test_execute_absolute(): input = cle.push(np.asarray([ [1, -1], @@ -38,3 +65,4 @@ def test_execute_absolute(): assert (np.min(a) == 1) assert (np.max(a) == 1) assert (np.mean(a) == 1) + diff --git a/tests/test_laplace_box.py b/tests/test_laplace_box.py index 19952c65..eb613cd5 100644 --- a/tests/test_laplace_box.py +++ b/tests/test_laplace_box.py @@ -30,7 +30,7 @@ def test_laplace_box(): ) result = cle.create(test1) - cle.laplace_box(test1, result) + cle.laplace(test1, result) a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_laplace_diamond.py b/tests/test_laplace_sphere.py similarity index 89% rename from tests/test_laplace_diamond.py rename to tests/test_laplace_sphere.py index fa199e3a..e6f8dad2 100644 --- a/tests/test_laplace_diamond.py +++ b/tests/test_laplace_sphere.py @@ -4,7 +4,7 @@ cle.select_device("TX") -def test_laplace_diamond(): +def test_laplace_sphere(): test1 = cle.push( np.asarray( [ @@ -30,7 +30,7 @@ def test_laplace_diamond(): ) result = cle.create(test1) - cle.laplace_diamond(test1, result) + cle.laplace(test1, result, connectivity="sphere") a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_maximum_box.py b/tests/test_maximum_box.py index d391422b..f67870c6 100644 --- a/tests/test_maximum_box.py +++ b/tests/test_maximum_box.py @@ -30,7 +30,7 @@ def test_maximum_box(): ) result = cle.create(test1) - cle.maximum_box(test1, result, 1, 1, 0) + cle.maximum(test1, result, 1, 1, 0) a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_maximum_sphere.py b/tests/test_maximum_sphere.py index f4946d7b..b6a29ee1 100644 --- a/tests/test_maximum_sphere.py +++ b/tests/test_maximum_sphere.py @@ -8,7 +8,7 @@ def test_maximum_sphere_1(): test = cle.push(np.asarray([[1, 1, 1], [1, 2, 1], [1, 1, 1]])) test2 = cle.create(test) - cle.maximum_sphere(test, test2, 1, 1, 1) + cle.maximum(test, test2, 1, 1, 1, "sphere") a = cle.pull(test2) assert np.min(a) == 1 @@ -27,7 +27,7 @@ def test_maximum_sphere_1(): def test_maximum_sphere_2(): gpu_a = cle.push(np.asarray([[1, 1, 1], [1, 2, 1], [1, 1, 1]])) gpu_b = cle.create(gpu_a) - cle.maximum_sphere(gpu_a, gpu_b, 1, 1, 1) + cle.maximum(gpu_a, gpu_b, 1, 1, 1, "sphere") a = cle.pull(gpu_b) assert np.min(a) == 1 diff --git a/tests/test_mean_box.py b/tests/test_mean_box.py index e3be999d..1809b690 100644 --- a/tests/test_mean_box.py +++ b/tests/test_mean_box.py @@ -30,7 +30,7 @@ def test_mean_box(): ) result = cle.create(test1) - cle.mean_box(test1, result, 1, 1, 0) + cle.mean(test1, result, 1, 1, 0) a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_mean_sphere.py b/tests/test_mean_sphere.py index 4de52b4b..5916c011 100644 --- a/tests/test_mean_sphere.py +++ b/tests/test_mean_sphere.py @@ -30,7 +30,7 @@ def test_mean_sphere(): ) result = cle.create(test1, dtype=float) - cle.mean_sphere(test1, result, 1, 1, 0) + cle.mean(test1, result, 1, 1, 0, "sphere") a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_median_box.py b/tests/test_median_box.py index fe2c2d19..3763369a 100644 --- a/tests/test_median_box.py +++ b/tests/test_median_box.py @@ -4,7 +4,7 @@ cle.select_device("TX") -def test_median_box(): +def test_median(): test1 = cle.push( np.asarray( [ @@ -30,7 +30,7 @@ def test_median_box(): ) result = cle.create(test1) - cle.median_box(test1, result, 1, 1, 0) + cle.median(test1, result, 1, 1, 0) a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_median_sphere.py b/tests/test_median_sphere.py index 3b70b347..971e7aa8 100644 --- a/tests/test_median_sphere.py +++ b/tests/test_median_sphere.py @@ -30,7 +30,7 @@ def test_median_sphere(): ) result = cle.create(test1) - cle.median_sphere(test1, result, 1, 1, 0) + cle.median(test1, result, 1, 1, 0, "sphere") a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_minimum_box.py b/tests/test_minimum_box.py index e4ab9879..fc8f65e3 100644 --- a/tests/test_minimum_box.py +++ b/tests/test_minimum_box.py @@ -30,7 +30,7 @@ def test_minimum_box(): ) result = cle.create(test1) - cle.minimum_box(test1, result, 1, 1, 0) + cle.minimum(test1, result, 1, 1, 0) a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_mode_box.py b/tests/test_mode_box.py index b49b2801..06c735e6 100644 --- a/tests/test_mode_box.py +++ b/tests/test_mode_box.py @@ -26,7 +26,7 @@ def test_mode_box_2d(): ] ) - result = cle.mode_box(image, None, 1, 1, 1) + result = cle.mode(image, None, 1, 1, 1) assert cle.array_equal(result, reference) @@ -58,6 +58,6 @@ def test_mode_box_3d(): ] ) - result = cle.mode_box(image, None, 1, 1, 1) + result = cle.mode(image, None, 1, 1, 1) assert cle.array_equal(result, reference) diff --git a/tests/test_mode_sphere.py b/tests/test_mode_sphere.py index f3a33652..585106b5 100644 --- a/tests/test_mode_sphere.py +++ b/tests/test_mode_sphere.py @@ -26,7 +26,7 @@ def test_mode_sphere_2d(): ] ) - result = cle.mode_sphere(image, None, 1, 1, 1) + result = cle.mode(image, None, 1, 1, 1, "sphere") assert cle.array_equal(result, reference) @@ -58,6 +58,6 @@ def test_mode_sphere_3d(): ] ) - result = cle.mode_sphere(image, None, 1, 1, 1) + result = cle.mode(image, None, 1, 1, 1, "sphere") assert cle.array_equal(result, reference) diff --git a/tests/test_nonzero_maximum_box.py b/tests/test_nonzero_maximum_box.py index 8f386819..0312afbe 100644 --- a/tests/test_nonzero_maximum_box.py +++ b/tests/test_nonzero_maximum_box.py @@ -35,7 +35,7 @@ def test_nonzero_maximum_box(): # as nonzero filters don't touch zero values, we need to initialize the result in advance cle.set(result, 0) - cle.nonzero_maximum_box(test, flag, result) + cle.nonzero_maximum(test, flag, result) print(result) diff --git a/tests/test_nonzero_maximum_diamond.py b/tests/test_nonzero_maximum_sphere.py similarity index 89% rename from tests/test_nonzero_maximum_diamond.py rename to tests/test_nonzero_maximum_sphere.py index 4b385faf..6b3147ef 100644 --- a/tests/test_nonzero_maximum_diamond.py +++ b/tests/test_nonzero_maximum_sphere.py @@ -4,7 +4,7 @@ cle.select_device("TX") -def test_nonzero_maximum_diamond(): +def test_nonzero_maximum_sphere(): test = cle.push( np.asarray( [ @@ -35,7 +35,7 @@ def test_nonzero_maximum_diamond(): # as nonzero filters don't touch zero values, we need to initialize the result in advance cle.set(result, 0) - cle.nonzero_maximum_diamond(test, flag, result) + cle.nonzero_maximum(test, flag, result, connectivity="sphere") print(result) diff --git a/tests/test_nonzero_minimum_box.py b/tests/test_nonzero_minimum_box.py index f7cd7150..349fa85f 100644 --- a/tests/test_nonzero_minimum_box.py +++ b/tests/test_nonzero_minimum_box.py @@ -35,7 +35,7 @@ def test_nonzero_minimum_box(): # as nonzero filters don't touch zero values, we need to initialize the result in advance cle.set(result, 0) - cle.nonzero_minimum_box(test, flag, result) + cle.nonzero_minimum(test, flag, result) print(result) diff --git a/tests/test_nonzero_minimum_diamond.py b/tests/test_nonzero_minimum_sphere.py similarity index 89% rename from tests/test_nonzero_minimum_diamond.py rename to tests/test_nonzero_minimum_sphere.py index 67668cd5..eee88e08 100644 --- a/tests/test_nonzero_minimum_diamond.py +++ b/tests/test_nonzero_minimum_sphere.py @@ -4,7 +4,7 @@ cle.select_device("TX") -def test_nonzero_minimum_diamond(): +def test_nonzero_minimum_sphere(): test = cle.push( np.asarray( [ @@ -35,7 +35,7 @@ def test_nonzero_minimum_diamond(): # as nonzero filters don't touch zero values, we need to initialize the result in advance cle.set(result, 0) - cle.nonzero_minimum_diamond(test, flag, result) + cle.nonzero_minimum(test, flag, result, connectivity="sphere") print(result) diff --git a/tests/test_onlyzero_overwrite_maximum_box.py b/tests/test_onlyzero_overwrite_maximum_box.py index fda461be..8f6d4fd5 100644 --- a/tests/test_onlyzero_overwrite_maximum_box.py +++ b/tests/test_onlyzero_overwrite_maximum_box.py @@ -31,7 +31,7 @@ def test_onlyzero_overwrite_maximum_box(): result = cle.create(test1) flag = cle.create((1, 1, 1)) - cle.onlyzero_overwrite_maximum_box(test1, flag, result) + cle.onlyzero_overwrite_maximum(test1, flag, result) print(result) diff --git a/tests/test_onlyzero_overwrite_maximum_diamond.py b/tests/test_onlyzero_overwrite_maximum_diamond.py index faccd598..ce8b37de 100644 --- a/tests/test_onlyzero_overwrite_maximum_diamond.py +++ b/tests/test_onlyzero_overwrite_maximum_diamond.py @@ -4,7 +4,7 @@ cle.select_device("TX") -def onlyzero_overwrite_maximum_diamond(): +def onlyzero_overwrite_maximum_sphere(): test1 = cle.push( np.asarray( [ @@ -31,7 +31,7 @@ def onlyzero_overwrite_maximum_diamond(): result = cle.create(test1) flag = cle.create((1, 1, 1)) - cle.onlyzero_overwrite_maximum_diamond(test1, flag, result) + cle.onlyzero_overwrite_maximum(test1, flag, result, connectivity="sphere") print(result) diff --git a/tests/test_opening_box.py b/tests/test_opening_box.py index 492ce9f6..cec93512 100644 --- a/tests/test_opening_box.py +++ b/tests/test_opening_box.py @@ -31,7 +31,7 @@ def test_opening_box_2d(): ) ) - gpu_output = cle.opening_box(gpu_input, radius_x=1, radius_y=1) + gpu_output = cle.opening(gpu_input, radius_x=1, radius_y=1) a = cle.pull(gpu_output) b = cle.pull(gpu_reference) @@ -90,7 +90,7 @@ def test_opening_box_3d(): ) ) - gpu_output = cle.opening_box(gpu_input, radius_x=1, radius_y=1) + gpu_output = cle.opening(gpu_input, radius_x=1, radius_y=1) a = cle.pull(gpu_output) b = cle.pull(gpu_reference) diff --git a/tests/test_opening_sphere.py b/tests/test_opening_sphere.py index 4bd0c7f5..9f951dab 100644 --- a/tests/test_opening_sphere.py +++ b/tests/test_opening_sphere.py @@ -31,7 +31,7 @@ def test_opening_sphere_2d(): ) ) - gpu_output = cle.opening_sphere(gpu_input, radius_x=1, radius_y=1) + gpu_output = cle.opening(gpu_input, radius_x=1, radius_y=1, connectivity="sphere") a = cle.pull(gpu_output) b = cle.pull(gpu_reference) @@ -90,7 +90,7 @@ def test_opening_sphere_3d(): ) ) - gpu_output = cle.opening_sphere(gpu_input, radius_x=1, radius_y=1) + gpu_output = cle.opening(gpu_input, radius_x=1, radius_y=1, connectivity="sphere") a = cle.pull(gpu_output) b = cle.pull(gpu_reference) diff --git a/tests/test_standard_deviation_box.py b/tests/test_standard_deviation_box.py index 5a0534d1..4076e8c9 100644 --- a/tests/test_standard_deviation_box.py +++ b/tests/test_standard_deviation_box.py @@ -30,7 +30,7 @@ def test_standard_deviation_box(): ) result = cle.create(test1, dtype=float) - cle.standard_deviation_box(test1, result, 1, 1, 0) + cle.standard_deviation(test1, result, 1, 1, 0) a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_standard_deviation_sphere.py b/tests/test_standard_deviation_sphere.py index b3e805e0..49ead4e0 100644 --- a/tests/test_standard_deviation_sphere.py +++ b/tests/test_standard_deviation_sphere.py @@ -30,7 +30,7 @@ def test_standard_deviation_sphere(): ) result = cle.create(test1, dtype=float) - cle.standard_deviation_sphere(test1, result, 1, 1, 0) + cle.standard_deviation(test1, result, 1, 1, 0, connectivity="sphere") a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_top_hat_box.py b/tests/test_top_hat_box.py index 295bd7d8..8b034911 100644 --- a/tests/test_top_hat_box.py +++ b/tests/test_top_hat_box.py @@ -4,7 +4,7 @@ cle.select_device("TX") -def test_top_hat_sphere(): +def test_top_hat_box(): test = cle.push( np.asarray( [ @@ -18,7 +18,7 @@ def test_top_hat_sphere(): ) result = cle.create(test) - cle.top_hat_box(test, result, 1, 1, 0) + cle.top_hat(test, result, 1, 1, 0) print(result) diff --git a/tests/test_top_hat_sphere.py b/tests/test_top_hat_sphere.py index c696f3b5..c37a4220 100644 --- a/tests/test_top_hat_sphere.py +++ b/tests/test_top_hat_sphere.py @@ -18,7 +18,7 @@ def test_top_hat_sphere(): ) result = cle.create(test) - cle.top_hat_sphere(test, result, 1, 1, 0) + cle.top_hat(test, result, 1, 1, 0, connectivity="sphere") print(result) diff --git a/tests/test_variance_box.py b/tests/test_variance_box.py index 3fd642ea..0d8c3628 100644 --- a/tests/test_variance_box.py +++ b/tests/test_variance_box.py @@ -30,7 +30,7 @@ def test_variance_box(): ) result = cle.create(test1, dtype=float) - cle.variance_box(test1, result, 1, 1, 0) + cle.variance(test1, result, 1, 1, 0) a = cle.pull(result) b = cle.pull(reference) diff --git a/tests/test_variance_sphere.py b/tests/test_variance_sphere.py index bfc5eb4d..821ac2dd 100644 --- a/tests/test_variance_sphere.py +++ b/tests/test_variance_sphere.py @@ -30,7 +30,7 @@ def test_variance_sphere(): ) result = cle.create(test1, dtype=float) - cle.variance_sphere(test1, result, 1, 1, 0) + cle.variance(test1, result, 1, 1, 0, connectivity="sphere") a = cle.pull(result) b = cle.pull(reference) From d07be6fc4605766f2e0bcb9c9985c21e1ff85cf6 Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 10:54:56 +0200 Subject: [PATCH 03/10] run CI on new clic version (branch) --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b3038199..446c8ee1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,8 +23,10 @@ option(BUILD_SHARED_LIBS OFF) ## CLIc dependency -set(CLIC_REPO_TAG 0.9.1) # branch name for dev +set(CLIC_REPO_TAG release-0.10.0) # branch name for dev set(CLIC_REPO_URL https://github.com/clEsperanto/CLIc.git) +set(BUILD_OPENCL_BACKEND ON CACHE BOOL "Build with OCL if FOUND" FORCE) +set(BUILD_CUDA_BACKEND ON CACHE BOOL "Build with CUDA if FOUND" FORCE) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/clic EXCLUDE_FROM_ALL) From ad4c95c356d6f81309ad08c1d1e6081d1e01a072 Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 11:48:23 +0200 Subject: [PATCH 04/10] bump version --- CMakeLists.txt | 2 +- pyclesperanto/_version.py | 4 ++-- pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 446c8ee1..d3d87d5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ option(BUILD_SHARED_LIBS OFF) ## CLIc dependency -set(CLIC_REPO_TAG release-0.10.0) # branch name for dev +set(CLIC_REPO_TAG 0.10.0) # branch name for dev set(CLIC_REPO_URL https://github.com/clEsperanto/CLIc.git) set(BUILD_OPENCL_BACKEND ON CACHE BOOL "Build with OCL if FOUND" FORCE) set(BUILD_CUDA_BACKEND ON CACHE BOOL "Build with CUDA if FOUND" FORCE) diff --git a/pyclesperanto/_version.py b/pyclesperanto/_version.py index 224c6efb..797a1e9c 100644 --- a/pyclesperanto/_version.py +++ b/pyclesperanto/_version.py @@ -1,10 +1,10 @@ # pyclesperanto version -VERSION_CODE = 0, 9, 1 +VERSION_CODE = 0, 10, 0 VERSION_STATUS = "" VERSION = ".".join(str(x) for x in VERSION_CODE) + VERSION_STATUS # clic version -CLIC_VERSION_CODE = 0, 9, 1 +CLIC_VERSION_CODE = 0, 10, 0 CLIC_VERSION_STATUS = "" CLIC_VERSION = ".".join(str(x) for x in CLIC_VERSION_CODE) + CLIC_VERSION_STATUS diff --git a/pyproject.toml b/pyproject.toml index 96702aa6..d397ec9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ description = "GPU-accelerated image processing in python using OpenCL" name = "pyclesperanto" readme = "README.md" requires-python = ">=3.7" -version = "0.9.1" +version = "0.10.0" [project.urls] Documentation = "https://clesperanto.github.io/pyclesperanto/" From a6ac192edc30291f2d78a540807d29b859434781 Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 11:50:35 +0200 Subject: [PATCH 05/10] add missing load file --- pyclesperanto/_core.py | 52 +++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/pyclesperanto/_core.py b/pyclesperanto/_core.py index 07b86b39..d8fb546a 100644 --- a/pyclesperanto/_core.py +++ b/pyclesperanto/_core.py @@ -27,7 +27,7 @@ def select_device(device_id: Union[str, int] = "", device_type: str = "all") -> If selecting the device by string, the function compares the device name and substring. (e.g. "NVIDIA", "RTX", "Iris", etc. will match the device name "NVIDIA RTX 2080" or "Intel Iris Pro") - If selecting the device by index, the function will select the device at the given index in the list + If selecting the device by index, the function will select the device at the given index in the list of available devices. (e.g. 0, 1, 2, etc. will select the first, second, third, etc. device in the list) If device_id is an empty string, the function will select the first available device. The device_type enables selecting the type of device to be selected (e.g. "all", "cpu", "gpu") @@ -62,7 +62,7 @@ def list_available_devices(device_type: str = "all") -> list: """Retrieve a list of names of available devices Will search the system for backend compatible device available and return a list of their names. - This will NOT set the device! + This will NOT set the device! Use 'select_device' to select devices. Use 'get_device' to retrieve the current device. @@ -130,7 +130,7 @@ def select_backend(backend: str = "opencl") -> str: def wait_for_kernel_to_finish(flag: bool = True, device: Device = None): """Wait for kernel to finish - Enforce the system to wait for the kernel to finish before continuing. Introducing a + Enforce the system to wait for the kernel to finish before continuing. Introducing a slowdown in the workflow. This is useful for debugging purposes, benchmarking and profiling, as well as for complex workflows where the order of operations is important. @@ -159,7 +159,15 @@ def default_initialisation(): ) -def execute(anchor = '__file__', kernel_source: str = '', kernel_name: str = '', global_size: tuple = (1, 1, 1), parameters: dict = {}, constants: dict = {}, device: Device = None): +def execute( + anchor="__file__", + kernel_source: str = "", + kernel_name: str = "", + global_size: tuple = (1, 1, 1), + parameters: dict = {}, + constants: dict = {}, + device: Device = None, +): """Execute a kernel from a file or a string Call, build, and execute a kernel compatible with CLIj framework. @@ -171,7 +179,7 @@ def execute(anchor = '__file__', kernel_source: str = '', kernel_name: str = '', Enter __file__ when calling this method and the corresponding open.cl file lies in the same folder as the python file calling it. Ignored if kernel_source is a string. - kernel_source : str + kernel_source : str Filename of the open.cl file to be called or string containing the open.cl source code kernel_name : str Kernel method inside the open.cl file to be called @@ -196,10 +204,10 @@ def load_file(anchor, filename): kernel = Path(filename).read_text() else: kernel = (Path(anchor).parent / filename).read_text() - return kernel - + return kernel + # test if kernel_source ends with .cl or .cu - if kernel_source.endswith('.cl') or kernel_source.endswith('.cu'): + if kernel_source.endswith(".cl") or kernel_source.endswith(".cu"): kernel_source = load_file(anchor, kernel_source) # manage the device if not given @@ -212,12 +220,19 @@ def load_file(anchor, filename): global_size = tuple(global_size) else: global_size = (global_size,) - - _execute(device, kernel_name, kernel_source, parameters, global_size, constants) + _execute(device, kernel_name, kernel_source, parameters, global_size, constants) -def native_execute(anchor = '__file__', kernel_source: str = '', kernel_name: str = '', global_size: tuple = (1, 1, 1), local_size: tuple = (1, 1, 1), parameters: dict = {}, device: Device = None): +def native_execute( + anchor="__file__", + kernel_source: str = "", + kernel_name: str = "", + global_size: tuple = (1, 1, 1), + local_size: tuple = (1, 1, 1), + parameters: dict = {}, + device: Device = None, +): """Execute an OpenCL kernel from a file or a string Call, build, and execute a kernel compatible with OpenCL language. @@ -225,7 +240,7 @@ def native_execute(anchor = '__file__', kernel_source: str = '', kernel_name: st The parameters must still be passed as a dictionary with the correct types and order. Buffer parameters must be passed as Array objects. Scalars must be passed as Python native float or int. - + Warning: Only 1D buffers are supported for now. Parameters @@ -234,7 +249,7 @@ def native_execute(anchor = '__file__', kernel_source: str = '', kernel_name: st Enter __file__ when calling this method and the corresponding open.cl file lies in the same folder as the python file calling it. Ignored if kernel_source is a string. - kernel_source : str + kernel_source : str Filename of the open.cl file to be called or string containing the open.cl source code kernel_name : str Kernel method inside the open.cl file to be called @@ -257,7 +272,11 @@ def load_file(anchor, filename): kernel = Path(filename).read_text() else: kernel = (Path(anchor).parent / filename).read_text() - return kernel + return kernel + + # test if kernel_source ends with .cl or .cu + if kernel_source.endswith(".cl") or kernel_source.endswith(".cu"): + kernel_source = load_file(anchor, kernel_source) # manage the device if not given if not device: @@ -276,9 +295,10 @@ def load_file(anchor, filename): local_size = tuple(local_size) else: local_size = (local_size,) - - _native_execute(device, kernel_name, kernel_source, parameters, global_size, local_size) + _native_execute( + device, kernel_name, kernel_source, parameters, global_size, local_size + ) def gpu_info(): From 5ef61b2d9504809875c0ca244d74f10e0d287cb0 Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 11:58:35 +0200 Subject: [PATCH 06/10] add check on cmake clic --- src/clic/CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/clic/CMakeLists.txt b/src/clic/CMakeLists.txt index 94d6b93e..9f6dac90 100644 --- a/src/clic/CMakeLists.txt +++ b/src/clic/CMakeLists.txt @@ -1,7 +1,14 @@ - ## fetch CLIc from repo release include(FetchContent) +if(NOT DEFINED CLIC_REPO_URL) + message(FATAL_ERROR "CLIC_REPO_URL is not set") +endif() + +if(NOT DEFINED CLIC_REPO_TAG) + message(FATAL_ERROR "CLIC_REPO_TAG is not set") +endif() + FetchContent_Declare( CLIc GIT_REPOSITORY ${CLIC_REPO_URL} From 539fc8a9fbe2831901efa1e9f9360fbd5f3eb1a2 Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 13:12:33 +0200 Subject: [PATCH 07/10] add tests --- tests/test_add_images.py | 32 +--- tests/test_concatenate.py | 78 +++++++++ tests/test_morpho_on_label.py | 134 ++++++++++++++++ tests/test_transform.py | 289 ++++++++++++++++++++++++++++++++++ 4 files changed, 503 insertions(+), 30 deletions(-) create mode 100644 tests/test_concatenate.py create mode 100644 tests/test_morpho_on_label.py create mode 100644 tests/test_transform.py diff --git a/tests/test_add_images.py b/tests/test_add_images.py index cc9f7c56..29034156 100755 --- a/tests/test_add_images.py +++ b/tests/test_add_images.py @@ -7,37 +7,9 @@ input2 = np.asarray([4, 5, 6]) -def test_add_images_weighted_missing_params(): +def test_add_images(): reference = np.asarray([5, 7, 9]) - output = cle.add_images_weighted(input1, input2, None, 1, 1) + output = cle.add_images(input1, input2, None) result = cle.pull(output) assert np.array_equal(result, reference) - - -reference = np.asarray([9, 12, 15]) - - -def test_add_images_weighted_none_output(): - output = cle.add_images_weighted(input1, input2, None, 1, 2) - result = cle.pull(output) - assert np.array_equal(result, reference) - - -def test_add_images_weighted_named_params(): - output = cle.add_images_weighted(input1, input2, None, factor0=1, factor1=2) - result = cle.pull(output) - assert np.array_equal(result, reference) - - -def test_add_images_weighted_named_params_missing_params(): - output = cle.add_images_weighted(input1, input2, factor0=1, factor1=2) - result = cle.pull(output) - assert np.array_equal(result, reference) - - -def test_add_images_weighted_wrong_order_missing_params(): - reference = np.asarray([9, 12, 15]) - output = cle.add_images_weighted(input1, input2, factor1=2, factor0=1) - result = cle.pull(output) - assert np.array_equal(result, reference) diff --git a/tests/test_concatenate.py b/tests/test_concatenate.py new file mode 100644 index 00000000..51230a37 --- /dev/null +++ b/tests/test_concatenate.py @@ -0,0 +1,78 @@ +import pyclesperanto as cle +import numpy as np + + +def test_concate_along_x(): + 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.concatenate_along_x(test1, test2) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.allclose(a, b, 0.01) + + +def test_concate_along_y(): + test1 = cle.push(np.asarray([[1, 1], [1, 1]])) + test2 = cle.push(np.asarray([[2, 2], [2, 2]])) + + reference = cle.push(np.asarray([[1, 1], [1, 1], [2, 2], [2, 2]])) + + result = cle.concatenate_along_y(test1, test2) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.allclose(a, b, 0.01) + + +def test_concate_along_z(): + 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], + ], + [ + [1, 1], + ], + [ + [2, 2], + ], + [[2, 2]], + [[2, 2]], + ] + ) + ) + + result = cle.concatenate_along_z(test1, test2) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.allclose(a, b, 0.01) diff --git a/tests/test_morpho_on_label.py b/tests/test_morpho_on_label.py new file mode 100644 index 00000000..e2b73809 --- /dev/null +++ b/tests/test_morpho_on_label.py @@ -0,0 +1,134 @@ +import pyclesperanto as cle +import numpy as np + + +def test_erode_connected_labels_2d(): + + gpu_input = cle.push( + np.asarray( + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 0, 0, 0, 0, 0, 0], + ] + ) + ) + + gpu_reference = cle.push( + np.asarray( + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 2, 2, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ] + ) + ) + gpu_output = cle.erode_connected_labels(gpu_input, radius=1) + + a = cle.pull(gpu_output) + b = cle.pull(gpu_reference) + + print(a) + print(b) + + assert np.array_equal(a, b) + + +def test_erode_connected_labels_3d(): + + gpu_input = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 1, 1, 2, 2, 2, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + ] + ) + ) + + gpu_reference = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 2, 2, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ], + ] + ) + ) + gpu_output = cle.erode_connected_labels(gpu_input, radius=1) + + a = cle.pull(gpu_output) + b = cle.pull(gpu_reference) + + print(a) + print(b) + + assert np.array_equal(a, b) diff --git a/tests/test_transform.py b/tests/test_transform.py new file mode 100644 index 00000000..2881e7c6 --- /dev/null +++ b/tests/test_transform.py @@ -0,0 +1,289 @@ +import pyclesperanto as cle +import numpy as np + + +def test_rotate(): + source = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 1, 1], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + reference = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + result = cle.rotate(source, angle_z=45.0, centered=False) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.array_equal(a, b) + + +def test_rotate_around_center(): + source = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 1, 1], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + reference = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 1, 0, 0], + ] + ] + ) + ) + + result = cle.rotate(source, angle_z=90.0, centered=True) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.array_equal(a, b) + + +def test_rotation_auto_size(): + source = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + reference = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + ] + ] + ) + ) + + result = cle.rotate(source, angle_z=45, resize=True) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.array_equal(a, b) + + +def test_scale_centered(): + source = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + reference = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + result = cle.scale(source, factor_y=2) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.array_equal(a, b) + + +def test_scale_not_centered(): + source = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + reference = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + result = cle.scale(source, factor_y=2, centered=False) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.array_equal(a, b) + + +def test_scale_auto_size(): + source = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + reference = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ] + ] + ) + ) + + result = cle.scale(source, factor_x=2, factor_y=2, resize=True) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.array_equal(a, b) + + +def test_translate(): + source = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + reference = cle.push( + np.asarray( + [ + [ + [0, 0, 0, 0, 0], + [0, 1, 1, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ] + ] + ) + ) + + result = cle.translate(source, translate_x=-1, translate_y=-1) + + a = cle.pull(result) + b = cle.pull(reference) + + print(a) + print(b) + + assert np.array_equal(a, b) From e6c18edf4f3bf8a7b92aea098d19c3f7dcbab8d1 Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 14:12:35 +0200 Subject: [PATCH 08/10] add test for coverage --- tests/test_api_list.py | 7 +++++++ tests/test_imshow.py | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tests/test_api_list.py create mode 100644 tests/test_imshow.py diff --git a/tests/test_api_list.py b/tests/test_api_list.py new file mode 100644 index 00000000..5beaea1b --- /dev/null +++ b/tests/test_api_list.py @@ -0,0 +1,7 @@ +import pyclesperanto as cle + + +def test_list_operations(): + + cle.list_operations(search_term="erode") + assert True diff --git a/tests/test_imshow.py b/tests/test_imshow.py new file mode 100644 index 00000000..6a8a1e19 --- /dev/null +++ b/tests/test_imshow.py @@ -0,0 +1,26 @@ +import matplotlib.pyplot as plt +import pyclesperanto as cle +import numpy as np +from unittest.mock import patch + + +@patch("matplotlib.pyplot.show") +def test_imshow(mock_show): + image = cle.push(np.random.random((512, 512))) + cle.imshow(image) + + +@patch("matplotlib.pyplot.show") +def test_imshow_3d(mock_show): + image = cle.push(np.random.random((10, 512, 512))) + cle.imshow(image, colorbar=True) + plt.show() + plt.close() + + +@patch("matplotlib.pyplot.show") +def test_imshow_label(mock_show): + image = cle.push(np.random.random((512, 512))) + label = cle.connected_components_labeling(image > 0.8) + cle.imshow(label, labels=True) + plt.show() From ed5bfef3806ada959fef50626e2a2e7e2ae5f84a Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 16:20:45 +0200 Subject: [PATCH 09/10] update doc --- docs/source/contribute.rst | 59 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/docs/source/contribute.rst b/docs/source/contribute.rst index 7728d5d9..d1b5d50a 100644 --- a/docs/source/contribute.rst +++ b/docs/source/contribute.rst @@ -6,10 +6,63 @@ Contributing This section of the documentation in under construction. +py-clesperanto is a Python API layer for the `CLIc library `__. +Several operation and functionality are directly inherited from the CLIc library, compiled and imported in the package as ``_clesperanto``. +Arround this C++ core, the package as several Python code to ensure proper integration with the Python ecosystem (numpy, etc.). + +Therefore, it is possible to contribute to the development of py-clesperanto either on the C++ side or on the Python side depending on what you are trying to achieve. + +Environment setup +------------------ + +Like for any other python development we encourage you to use a virtual environment to develop py-clesperanto. +Here we will describe how to setup a development environment for py-clesperanto with `conda/mamba`. + +1. Create a new environment with `conda/mamba`: + +.. code-block:: bash + + mamba create -n pycle-dev python=3.9 -c conda-forge + conda activate pycle-dev + +2. Install the dependencies: + +.. code-block:: bash + + mamba install -c conda-forge numpy pytest jupyter scikit-image black flake8 pre-commit + +3. Setup pre-commit: + +.. code-block:: bash + + pre-commit install + +4. Install py-clesperanto in development mode: + +.. code-block:: bash + + pip install . -v + +5. Run the tests: + +.. code-block:: bash + + pytest + +All tests should pass. +The ``pip install`` command will compile and install the package in the environment. +You will now be able to modify the ``pyclesperanto`` package and see the changes directly in the environment. +The ``pre-commit`` tool will ensure that the code is properly formatted and linted before any commit. + +.. warning:: + + ``-e`` flag of the ``pip install`` command is not available for the moment. + + Versioning ---------- -pyClesperanto version folows the `CLIc `__ versioning. +pyClesperanto version folows the `CLIc `__ versioning. Although they are not necessarily made to be identically, the versioning is kept in sync as much as possible. In order to update the version of pyClesperanto, you will need to follow the following steps: @@ -19,6 +72,6 @@ In order to update the version of pyClesperanto, you will need to follow the fol 3. Update the version in the `pyclesperanto/_version.py `__ file. Both ``VERSION`` and ``CLIC_VERSION`` should be updated accordingly. -.. note:: +.. note:: - The version tag in the ``pyproject.toml`` file should be made automatic in the near future. \ No newline at end of file + The version tag in the ``pyproject.toml`` file should be made automatic in the near future. From 7e2e493bcfcea0a40c22bfb156a81923b5e93f01 Mon Sep 17 00:00:00 2001 From: Stephane Rigaud Date: Wed, 17 Apr 2024 16:27:11 +0200 Subject: [PATCH 10/10] mv execution to functionality --- pyclesperanto/__init__.py | 4 +- pyclesperanto/_core.py | 144 --------------------- pyclesperanto/_functionalities.py | 199 ++++++++++++++++++++++-------- 3 files changed, 146 insertions(+), 201 deletions(-) diff --git a/pyclesperanto/__init__.py b/pyclesperanto/__init__.py index d3a95d83..2c11ebb5 100644 --- a/pyclesperanto/__init__.py +++ b/pyclesperanto/__init__.py @@ -1,7 +1,5 @@ from ._core import ( gpu_info, - execute, - native_execute, select_backend, select_device, get_device, @@ -13,7 +11,7 @@ ) from ._array import Array, Image, is_image from ._memory import create, create_like, push, pull -from ._functionalities import imshow, list_operations +from ._functionalities import imshow, list_operations, execute, native_execute from ._tier1 import * from ._tier2 import * diff --git a/pyclesperanto/_core.py b/pyclesperanto/_core.py index d8fb546a..b48b90d6 100644 --- a/pyclesperanto/_core.py +++ b/pyclesperanto/_core.py @@ -1,11 +1,9 @@ from typing import Optional, Union -from pathlib import Path import numpy as np import warnings from ._pyclesperanto import _Device as Device from ._pyclesperanto import _BackendManager as BackendManager -from ._pyclesperanto import _execute, _native_execute class _current_device: @@ -159,148 +157,6 @@ def default_initialisation(): ) -def execute( - anchor="__file__", - kernel_source: str = "", - kernel_name: str = "", - global_size: tuple = (1, 1, 1), - parameters: dict = {}, - constants: dict = {}, - device: Device = None, -): - """Execute a kernel from a file or a string - - Call, build, and execute a kernel compatible with CLIj framework. - The kernel can be called from a file or a string. - - Parameters - ---------- - anchor : str, default = '__file__' - Enter __file__ when calling this method and the corresponding open.cl - file lies in the same folder as the python file calling it. - Ignored if kernel_source is a string. - kernel_source : str - Filename of the open.cl file to be called or string containing the open.cl source code - kernel_name : str - Kernel method inside the open.cl file to be called - most clij/clesperanto kernel functions have the same name as the file they are in - global_size : tuple (z,y,x), default = (1, 1, 1) - Global_size according to OpenCL definition (usually shape of the destination image). - parameters : dict(str, [Array, float, int]) - Dictionary containing parameters. Take care: They must be of the - right type and in the right order as specified in the open.cl file. - constants: dict(str, int), optional - Dictionary with names/values which will be added to the define - statements. They are necessary, e.g. to create arrays of a given - maximum size in OpenCL as variable array lengths are not supported. - device : Device, default = None - The device to execute the kernel on. If None, use the current device - """ - - # load the kernel file - def load_file(anchor, filename): - """Load the opencl kernel file as a string""" - if anchor is None: - kernel = Path(filename).read_text() - else: - kernel = (Path(anchor).parent / filename).read_text() - return kernel - - # test if kernel_source ends with .cl or .cu - if kernel_source.endswith(".cl") or kernel_source.endswith(".cu"): - kernel_source = load_file(anchor, kernel_source) - - # manage the device if not given - if not device: - device = get_device() - - # manage global range - if not isinstance(global_size, tuple): - if isinstance(global_size, list) or isinstance(global_size, np.ndarray): - global_size = tuple(global_size) - else: - global_size = (global_size,) - - _execute(device, kernel_name, kernel_source, parameters, global_size, constants) - - -def native_execute( - anchor="__file__", - kernel_source: str = "", - kernel_name: str = "", - global_size: tuple = (1, 1, 1), - local_size: tuple = (1, 1, 1), - parameters: dict = {}, - device: Device = None, -): - """Execute an OpenCL kernel from a file or a string - - Call, build, and execute a kernel compatible with OpenCL language. - The kernel can be called from a file or a string. - - The parameters must still be passed as a dictionary with the correct types and order. - Buffer parameters must be passed as Array objects. Scalars must be passed as Python native float or int. - - Warning: Only 1D buffers are supported for now. - - Parameters - ---------- - anchor : str, default = '__file__' - Enter __file__ when calling this method and the corresponding open.cl - file lies in the same folder as the python file calling it. - Ignored if kernel_source is a string. - kernel_source : str - Filename of the open.cl file to be called or string containing the open.cl source code - kernel_name : str - Kernel method inside the open.cl file to be called - most clij/clesperanto kernel functions have the same name as the file they are in - global_size : tuple (z,y,x), default = (1, 1, 1) - Global_size according to OpenCL definition (usually shape of the destination image). - local_size : tuple (z,y,x), default = (1, 1, 1) - Local_size according to OpenCL definition (usually default is good). - parameters : dict(str, [Array, float, int]) - Dictionary containing parameters. Take care: They must be of the - right type and in the right order as specified in the open.cl file. - device : Device, default = None - The device to execute the kernel on. If None, use the current device - """ - - # load the kernel file - def load_file(anchor, filename): - """Load the opencl kernel file as a string""" - if anchor is None: - kernel = Path(filename).read_text() - else: - kernel = (Path(anchor).parent / filename).read_text() - return kernel - - # test if kernel_source ends with .cl or .cu - if kernel_source.endswith(".cl") or kernel_source.endswith(".cu"): - kernel_source = load_file(anchor, kernel_source) - - # manage the device if not given - if not device: - device = get_device() - - # manage global range - if not isinstance(global_size, tuple): - if isinstance(global_size, list) or isinstance(global_size, np.ndarray): - global_size = tuple(global_size) - else: - global_size = (global_size,) - - # manage local range - if not isinstance(local_size, tuple): - if isinstance(local_size, list) or isinstance(local_size, np.ndarray): - local_size = tuple(local_size) - else: - local_size = (local_size,) - - _native_execute( - device, kernel_name, kernel_source, parameters, global_size, local_size - ) - - def gpu_info(): device_list = list_available_devices() info = [] diff --git a/pyclesperanto/_functionalities.py b/pyclesperanto/_functionalities.py index 94d066cb..f4245240 100644 --- a/pyclesperanto/_functionalities.py +++ b/pyclesperanto/_functionalities.py @@ -1,63 +1,154 @@ from os import path from typing import Optional, Union +from pathlib import Path +import numpy as np +from ._pyclesperanto import _execute, _native_execute from ._array import Array, Image from ._memory import pull from ._core import Device, get_device -# def execute( -# anchor: str, -# kernel_filepath: str, -# kernel_name: str, -# parameters: dict, -# range: Optional[tuple] = None, -# device: Optional[Device] = None, -# ): -# """Execute a custom OpenCL kernel. - -# Parameters -# ---------- -# anchor : str -# Path to the directory where the kernel file is located. -# opencl_kernel_filename : str -# Name of the OpenCL kernel file, e.g. "my_kernel.cl". -# kernel_name : str -# Name of the kernel function to be executed in the OpenCL kernel file. -# parameters : dict -# Dictionary of parameters to be passed to the kernel function, e.g. {"src": src, "dst": dst}. -# range : tuple, optional -# Global size of the kernel execution, by default None. -# device : Device, optional -# Device to be used for execution, by default None. -# """ -# from ._pyclesperanto import _std_variant as std_variant -# from ._pyclesperanto import _execute as op - -# if range is None: -# range = (1, 1, 1) -# else: -# if len(range) == 2: -# range = (1, range[0], range[1]) -# if len(range) == 1: -# range = (1, 1, range[0]) - -# if device is None: -# device = parameters["src"].device or parameters["src1"].device or get_device() - -# kernel_source = open(path.join(anchor, kernel_filepath), "r").read() - -# cpp_parameter_map = { -# key: std_variant(val.super) if isinstance(val, Array) else std_variant(val) -# for key, val in parameters.items() -# } -# op( -# device, -# kernel_name=kernel_name, -# kernel_source=kernel_source, -# parameters=cpp_parameter_map, -# range=range, -# # constants=cpp_constant_map, -# ) + +def execute( + anchor="__file__", + kernel_source: str = "", + kernel_name: str = "", + global_size: tuple = (1, 1, 1), + parameters: dict = {}, + constants: dict = {}, + device: Device = None, +): + """Execute a kernel from a file or a string + + Call, build, and execute a kernel compatible with CLIj framework. + The kernel can be called from a file or a string. + + Parameters + ---------- + anchor : str, default = '__file__' + Enter __file__ when calling this method and the corresponding open.cl + file lies in the same folder as the python file calling it. + Ignored if kernel_source is a string. + kernel_source : str + Filename of the open.cl file to be called or string containing the open.cl source code + kernel_name : str + Kernel method inside the open.cl file to be called + most clij/clesperanto kernel functions have the same name as the file they are in + global_size : tuple (z,y,x), default = (1, 1, 1) + Global_size according to OpenCL definition (usually shape of the destination image). + parameters : dict(str, [Array, float, int]) + Dictionary containing parameters. Take care: They must be of the + right type and in the right order as specified in the open.cl file. + constants: dict(str, int), optional + Dictionary with names/values which will be added to the define + statements. They are necessary, e.g. to create arrays of a given + maximum size in OpenCL as variable array lengths are not supported. + device : Device, default = None + The device to execute the kernel on. If None, use the current device + """ + + # load the kernel file + def load_file(anchor, filename): + """Load the opencl kernel file as a string""" + if anchor is None: + kernel = Path(filename).read_text() + else: + kernel = (Path(anchor).parent / filename).read_text() + return kernel + + # test if kernel_source ends with .cl or .cu + if kernel_source.endswith(".cl") or kernel_source.endswith(".cu"): + kernel_source = load_file(anchor, kernel_source) + + # manage the device if not given + if not device: + device = get_device() + + # manage global range + if not isinstance(global_size, tuple): + if isinstance(global_size, list) or isinstance(global_size, np.ndarray): + global_size = tuple(global_size) + else: + global_size = (global_size,) + + _execute(device, kernel_name, kernel_source, parameters, global_size, constants) + + +def native_execute( + anchor="__file__", + kernel_source: str = "", + kernel_name: str = "", + global_size: tuple = (1, 1, 1), + local_size: tuple = (1, 1, 1), + parameters: dict = {}, + device: Device = None, +): + """Execute an OpenCL kernel from a file or a string + + Call, build, and execute a kernel compatible with OpenCL language. + The kernel can be called from a file or a string. + + The parameters must still be passed as a dictionary with the correct types and order. + Buffer parameters must be passed as Array objects. Scalars must be passed as Python native float or int. + + Warning: Only 1D buffers are supported for now. + + Parameters + ---------- + anchor : str, default = '__file__' + Enter __file__ when calling this method and the corresponding open.cl + file lies in the same folder as the python file calling it. + Ignored if kernel_source is a string. + kernel_source : str + Filename of the open.cl file to be called or string containing the open.cl source code + kernel_name : str + Kernel method inside the open.cl file to be called + most clij/clesperanto kernel functions have the same name as the file they are in + global_size : tuple (z,y,x), default = (1, 1, 1) + Global_size according to OpenCL definition (usually shape of the destination image). + local_size : tuple (z,y,x), default = (1, 1, 1) + Local_size according to OpenCL definition (usually default is good). + parameters : dict(str, [Array, float, int]) + Dictionary containing parameters. Take care: They must be of the + right type and in the right order as specified in the open.cl file. + device : Device, default = None + The device to execute the kernel on. If None, use the current device + """ + + # load the kernel file + def load_file(anchor, filename): + """Load the opencl kernel file as a string""" + if anchor is None: + kernel = Path(filename).read_text() + else: + kernel = (Path(anchor).parent / filename).read_text() + return kernel + + # test if kernel_source ends with .cl or .cu + if kernel_source.endswith(".cl") or kernel_source.endswith(".cu"): + kernel_source = load_file(anchor, kernel_source) + + # manage the device if not given + if not device: + device = get_device() + + # manage global range + if not isinstance(global_size, tuple): + if isinstance(global_size, list) or isinstance(global_size, np.ndarray): + global_size = tuple(global_size) + else: + global_size = (global_size,) + + # manage local range + if not isinstance(local_size, tuple): + if isinstance(local_size, list) or isinstance(local_size, np.ndarray): + local_size = tuple(local_size) + else: + local_size = (local_size,) + + _native_execute( + device, kernel_name, kernel_source, parameters, global_size, local_size + ) def imshow( @@ -73,7 +164,7 @@ def imshow( alpha: Optional[float] = None, continue_drawing: Optional[bool] = False, ): - """Visualize an image, e.g. in Jupyter notebooks using matplotlib. + """Visualize an image, e.g. in Jupyter notebooks using matplotlib. Parameters ----------