From 50fb72cb9a75d131241a61e8da97d7986637caff Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Thu, 2 Feb 2023 11:17:53 +0000 Subject: [PATCH 01/27] removes obsolete archs from packages cmake --- cmake/Modules/Packages.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/Modules/Packages.cmake b/cmake/Modules/Packages.cmake index 22a1fbe01..db324bdbe 100644 --- a/cmake/Modules/Packages.cmake +++ b/cmake/Modules/Packages.cmake @@ -219,12 +219,12 @@ if(TOMOPY_USE_CUDA) # Pascal support 70, 72 + Volta support 75 + Turing support if(NOT DEFINED CUDA_ARCH) if(CUDAToolkit_VERSION_MAJOR VERSION_LESS 11) - set(CUDA_ARCH "30;32;35;37;50;52;53;60;61;62;70;72;75") + set(CUDA_ARCH "50;52;53;60;61;62;70;72;75") else() if(CUDAToolkit_VERSION_MINOR VERSION_LESS 1) - set(CUDA_ARCH "35;37;50;52;53;60;61;62;70;72;75;80") + set(CUDA_ARCH "50;52;53;60;61;62;70;72;75;80") else() - set(CUDA_ARCH "35;37;50;52;53;60;61;62;70;72;75;80;86") + set(CUDA_ARCH "50;52;53;60;61;62;70;72;75;80;86") endif() endif() endif() From 96e0d0a7e95b9c960839836cecbb36a01825d83a Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Sun, 5 Feb 2023 22:07:40 +0000 Subject: [PATCH 02/27] working version of stripes enhancement --- include/libtomo/stripes_detect3d.h | 57 +++++++ source/libtomo/misc/utils.c | 52 +++++++ source/libtomo/misc/utils.h | 2 + source/libtomo/prep/CMakeLists.txt | 20 ++- source/libtomo/prep/stripes_detect3d.c | 207 +++++++++++++++++++++++++ source/tomopy/prep/stripe.py | 53 ++++++- source/tomopy/util/extern/prep.py | 24 ++- 7 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 include/libtomo/stripes_detect3d.h create mode 100644 source/libtomo/prep/stripes_detect3d.c diff --git a/include/libtomo/stripes_detect3d.h b/include/libtomo/stripes_detect3d.h new file mode 100644 index 000000000..ef341d453 --- /dev/null +++ b/include/libtomo/stripes_detect3d.h @@ -0,0 +1,57 @@ +// Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. + +// Copyright 2015. UChicago Argonne, LLC. This software was produced +// under U.S. Government contract DE-AC02-06CH11357 for Argonne National +// Laboratory (ANL), which is operated by UChicago Argonne, LLC for the +// U.S. Department of Energy. The U.S. Government has rights to use, +// reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR +// UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR +// ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is +// modified to produce derivative works, such modified software should +// be clearly marked, so as not to confuse it with the version available +// from ANL. + +// Additionally, redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: + +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. + +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. + +// * Neither the name of UChicago Argonne, LLC, Argonne National +// Laboratory, ANL, the U.S. Government, nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago +// Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +// C-module for detecting and emphasising stripes present in the data (3D case) +// Original author: Daniil Kazantsev, Diamond Light Source Ltd. + +#pragma once + +#ifdef WIN32 +# define DLL __declspec(dllexport) +#else +# define DLL +#endif + +DLL int +stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, + int ncores, int dimX, int dimY, int dimZ); diff --git a/source/libtomo/misc/utils.c b/source/libtomo/misc/utils.c index a598163d0..fa7ecab1f 100644 --- a/source/libtomo/misc/utils.c +++ b/source/libtomo/misc/utils.c @@ -200,3 +200,55 @@ quicksort_uint16(unsigned short* x, int first, int last) quicksort_uint16(x, j + 1, last); } } + +/* Calculate the forward difference derrivative of the 3D input in the direction of the "axis" parameter +using the step_size in pixels to skip pixels (i.e. step_size = 1 is the classical gradient) +axis = 0: horizontal direction +axis = 1: depth direction +axis = 2: vertical direction +*/ +void +gradient3D(float *input, float *output, long dimX, long dimY, long dimZ, int axis, int step_size) +{ + long i; + long j; + long k; + long i1; + long j1; + long k1; + long index; + +#pragma omp parallel for shared(input, output) private(i,j,k,i1,j1,k1,index) + for(j=0; j= dimX) + i1 = i - step_size; + output[index] = input[(dimX*dimY)*k + j*dimX+i1] - input[index]; + } + else if (axis == 1) + { + j1 = j + step_size; + if (j1 >= dimY) + j1 = j - step_size; + output[index] = input[(dimX*dimY)*k + j1*dimX+i] - input[index]; + } + else + { + k1 = k + step_size; + if (k1 >= dimZ) + k1 = k-step_size; + output[index] = input[(dimX*dimY)*k1 + j*dimX+i] - input[index]; + } + } + } + } +} diff --git a/source/libtomo/misc/utils.h b/source/libtomo/misc/utils.h index 80ea12fb7..95b69d29b 100644 --- a/source/libtomo/misc/utils.h +++ b/source/libtomo/misc/utils.h @@ -63,3 +63,5 @@ void DLL quicksort_float(float* x, int first, int last); void DLL quicksort_uint16(unsigned short* x, int first, int last); +void DLL +gradient3D(float *input, float *output, long dimX, long dimY, long dimZ, int axis, int step_size); \ No newline at end of file diff --git a/source/libtomo/prep/CMakeLists.txt b/source/libtomo/prep/CMakeLists.txt index b946fbc2d..d727553be 100755 --- a/source/libtomo/prep/CMakeLists.txt +++ b/source/libtomo/prep/CMakeLists.txt @@ -1,7 +1,23 @@ set(HEADERS "${tomopy_SOURCE_DIR}/include/libtomo/prep.h" - "${tomopy_SOURCE_DIR}/include/libtomo/stripe.h") + "${tomopy_SOURCE_DIR}/include/libtomo/stripe.h" + "${tomopy_SOURCE_DIR}/include/libtomo/stripes_detect3d.h") -tomopy_add_library(tomo-prep SHARED prep.c stripe.c ${HEADERS}) +tomopy_add_library( + tomo-prep + SHARED + prep.c + stripe.c + stripes_detect3d.c + ../misc/utils.c + ../misc/utils.h + ${HEADERS}) + +find_package(OpenMP REQUIRED COMPONENTS C) +target_link_libraries(tomo-prep PRIVATE OpenMP::OpenMP_C) +if (WIN32) +target_compile_options( + tomo-prep PRIVATE $<$:/openmp:experimental>) +endif() target_include_directories( tomo-prep diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c new file mode 100644 index 000000000..660e8f028 --- /dev/null +++ b/source/libtomo/prep/stripes_detect3d.c @@ -0,0 +1,207 @@ +// Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. + +// Copyright 2015. UChicago Argonne, LLC. This software was produced +// under U.S. Government contract DE-AC02-06CH11357 for Argonne National +// Laboratory (ANL), which is operated by UChicago Argonne, LLC for the +// U.S. Department of Energy. The U.S. Government has rights to use, +// reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR +// UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR +// ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is +// modified to produce derivative works, such modified software should +// be clearly marked, so as not to confuse it with the version available +// from ANL. + +// Additionally, redistribution and use in source and binary forms, with +// or without modification, are permitted provided that the following +// conditions are met: + +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. + +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. + +// * Neither the name of UChicago Argonne, LLC, Argonne National +// Laboratory, ANL, the U.S. Government, nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago +// Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +// C-module for detecting and emphasising stripes present in the data (3D case) +// Original author: Daniil Kazantsev, Diamond Light Source Ltd. + +#include +#include +#include + +#include "libtomo/stripes_detect3d.h" +#include "../misc/utils.h" + + +void +ratio_mean_stride3d(float* input, float* output, + long i, long j, long k, long long index, + long dimX, long dimY, long dimZ) +{ + float mean_plate; + float mean_horiz; + int radius = 3; + long i_m; + long j_m; + long k_m; + long i1; + long j1; + long k1; + + mean_plate = 0.0f; + for(j_m = -radius; j_m <= radius; j_m++) + { + j1 = j + j_m; + if((j1 < 0) || (j1 >= dimY)) + j1 = j; + for(k_m = -radius; k_m <= radius; k_m++) + { + k1 = k + k_m; + if((k1 < 0) || (k1 >= dimZ)) + k1 = k; + mean_plate += fabsf(input[((dimX * dimY) * k1 + j1 * dimX + i)]); + } + } + mean_plate /= 49.0f; + + mean_horiz = 0.0f; + for(i_m = 1; i_m <= 7; i_m++) + { + i1 = i + i_m; + if (i1 >= dimX) + i1 = i; + mean_horiz += fabsf(input[((dimX * dimY) * k + j * dimX + i1)]); + } + mean_horiz /= 7.0f; + + if ((mean_horiz > mean_plate) && (mean_horiz != 0.0f)) + { + output[index] = mean_plate/mean_horiz; + } + if ((mean_horiz < mean_plate) && (mean_plate != 0.0f)) + { + output[index] = mean_horiz/mean_plate; + } +} + + +void +vertical_median_stride3d(float* input, float* output, + int window_halflength_vertical, + int window_fulllength, + int midval_window_index, + long i, long j, long k, long long index, + long dimX, long dimY, long dimZ) +{ + int counter; + long k_m; + long k1; + float* _values; + + _values = (float*) calloc(window_fulllength, sizeof(float)); + + counter = 0; + for(k_m = -window_halflength_vertical; k_m <= window_halflength_vertical; k_m++) + { + k1 = k + k_m; + if((k1 < 0) || (k1 >= dimZ)) + k1 = k; + _values[counter] = input[((dimX * dimY) * k1 + j * dimX + i)]; + counter++; + } + quicksort_float(_values, 0, window_fulllength-1); + output[index] = _values[midval_window_index]; + + free (_values); +} + +DLL int +stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, + int ncores, int dimX, int dimY, int dimZ) +{ + long i; + long j; + long k; + long long index; + long long totalvoxels; + + float* gradient3d_x_arr; + float* mean_ratio3d_arr; + + totalvoxels = (long long) (dimX*dimY*dimZ); + + int window_fulllength = (int)(2*window_halflength_vertical + 1); + int midval_window_index = (int)(0.5f*window_fulllength) - 1; + + gradient3d_x_arr = calloc(totalvoxels, sizeof(float)); + mean_ratio3d_arr = calloc(totalvoxels, sizeof(float)); + + /* dealing here with a custom given number of cpu threads */ + if(ncores > 0) + { + // Explicitly disable dynamic teams + omp_set_dynamic(0); + // Use a number of threads for all consecutive parallel regions + omp_set_num_threads(ncores); + } + + /* Take the gradient in the horizontal direction, axis = 0*/ + gradient3D(Input, gradient3d_x_arr, (long) (dimX), (long) (dimY), (long) (dimZ), 0, 1); + + /* Here we calculate the ratio between the mean in a small 2D neighbourhood parallel to the stripe + and the mean orthogonal to the stripe. The gradient variation in the direction orthogonal to the + stripe is expected to be large (a jump), while in parallel direction small. Therefore at the stripe + edge we should get a ratio small/large. */ +#pragma omp parallel for shared(gradient3d_x_arr, mean_ratio3d_arr) private(i, j, k, index) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) + { + for(i = 0; i < dimX; i++) + { + index = ((dimX * dimY) * k + j * dimX + i); + ratio_mean_stride3d(gradient3d_x_arr, mean_ratio3d_arr, i, j, k, index, (long) (dimX), (long) (dimY), (long) (dimZ)); + } + } + } + +#pragma omp parallel for shared(mean_ratio3d_arr, Output) private(i, j, k, index) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) + { + for(i = 0; i < dimX; i++) + { + index = ((dimX * dimY) * k + j * dimX + i); + vertical_median_stride3d(mean_ratio3d_arr, Output, + window_halflength_vertical, + window_fulllength, + midval_window_index, + i, j, k, index, (long) (dimX), (long) (dimY), (long) (dimZ)); + } + } + } + + free(gradient3d_x_arr); + free(mean_ratio3d_arr); + return 0; +} \ No newline at end of file diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index cd80d47b4..6d7804391 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -82,7 +82,8 @@ 'remove_large_stripe', 'remove_dead_stripe', 'remove_all_stripe', - 'remove_stripe_based_interpolation'] + 'remove_stripe_based_interpolation', + 'stripes_detect3d'] def remove_stripe_fw( @@ -939,3 +940,53 @@ def _remove_stripe_based_interpolation(tomo, snr, size, drop_ratio, norm): sino = tomo[:, m, :] sino = _rs_interpolation(sino, snr, size, drop_ratio, norm) tomo[:, m, :] = sino + + +def stripes_detect3d(arr, window_halflength_vertical=10, ncore=None): + """ + Apply 3D stripe detection method to empasize stripe's edges in a 3D array. + + .. versionadded:: 1.13 + + Parameters + ---------- + arr : ndarray + Input 3D array of float32 data type. + size : int, optional + The size of the filter's kernel. + ncore : int, optional + Number of cores that will be assigned to jobs. All cores will be used + if unspecified. + + Returns + ------- + ndarray + Weights for stripe's edges as a 3D array of float32 data type. + + Raises + ------ + ValueError + If the input array is not three dimensional. + + """ + if ncore is None: + ncore = mproc.mp.cpu_count() + + input_type = arr.dtype + if (input_type != 'float32'): + arr = dtype.as_float32(arr) # silent convertion to float32 data type + out = np.empty_like(arr) + + if arr.ndim == 3: + dz, dy, dx = arr.shape + if (dz == 0) or (dy == 0) or (dx == 0): + raise ValueError("The length of one of dimensions is equal to zero") + else: + raise ValueError("The input array must be a 3D array") + + # perform full 3D stripes detection + extern.c_stripes_detect3d(arr, out, + window_halflength_vertical, + ncore, + dx, dy, dz) + return out \ No newline at end of file diff --git a/source/tomopy/util/extern/prep.py b/source/tomopy/util/extern/prep.py index 886cb534e..0d930c7f8 100644 --- a/source/tomopy/util/extern/prep.py +++ b/source/tomopy/util/extern/prep.py @@ -60,7 +60,8 @@ __copyright__ = "Copyright (c) 2015, UChicago Argonne, LLC." __docformat__ = 'restructuredtext en' __all__ = ['c_normalize_bg', - 'c_remove_stripe_sf'] + 'c_remove_stripe_sf', + 'c_stripes_detect3d'] LIB_TOMOPY_PREP = c_shared_lib("tomo-prep") @@ -96,3 +97,24 @@ def c_remove_stripe_sf(tomo, size): dtype.as_c_int(istart), dtype.as_c_int(iend)) tomo[:] = contiguous_tomo[:] + +def c_stripes_detect3d( + input, + output, + window_halflength_vertical, + ncore, + dx, + dy, + dz, +): + LIB_TOMOPY_PREP.stripesdetect3d_main_float.restype = dtype.as_c_void_p() + LIB_TOMOPY_PREP.stripesdetect3d_main_float( + dtype.as_c_float_p(input), + dtype.as_c_float_p(output), + dtype.as_c_int(window_halflength_vertical), + dtype.as_c_int(ncore), + dtype.as_c_int(dx), + dtype.as_c_int(dy), + dtype.as_c_int(dz), + ) + return output From 80180b1c1e805ad6c9090aa5b65cabe822e51040 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Tue, 7 Feb 2023 21:57:26 +0000 Subject: [PATCH 03/27] initiation of the stripe mask module --- include/libtomo/stripes_detect3d.h | 6 +++ source/libtomo/prep/stripes_detect3d.c | 64 +++++++++++++++++++++--- source/tomopy/prep/stripe.py | 67 ++++++++++++++++++++++++-- source/tomopy/util/extern/prep.py | 28 ++++++++++- 4 files changed, 152 insertions(+), 13 deletions(-) diff --git a/include/libtomo/stripes_detect3d.h b/include/libtomo/stripes_detect3d.h index ef341d453..6acbbe1d3 100644 --- a/include/libtomo/stripes_detect3d.h +++ b/include/libtomo/stripes_detect3d.h @@ -55,3 +55,9 @@ DLL int stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, int ncores, int dimX, int dimY, int dimZ); + +DLL int +stripesmask3d_main_float(float* Input, unsigned short* Output, + float threshold_val, + int stripe_length_min, int stripe_depth_min, + int ncores, int dimX, int dimY, int dimZ); diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index 660e8f028..b067ce20e 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -60,6 +60,7 @@ ratio_mean_stride3d(float* input, float* output, float mean_plate; float mean_horiz; int radius = 3; + int diameter = 2*radius + 1; long i_m; long j_m; long k_m; @@ -67,6 +68,7 @@ ratio_mean_stride3d(float* input, float* output, long j1; long k1; + /* calculate mean of gradientX in a 2D plate parallel to stripes direction */ mean_plate = 0.0f; for(j_m = -radius; j_m <= radius; j_m++) { @@ -83,8 +85,9 @@ ratio_mean_stride3d(float* input, float* output, } mean_plate /= 49.0f; + /* calculate mean of gradientX in a direction orthogonal to stripes direction */ mean_horiz = 0.0f; - for(i_m = 1; i_m <= 7; i_m++) + for(i_m = 1; i_m <= diameter; i_m++) { i1 = i + i_m; if (i1 >= dimX) @@ -93,6 +96,9 @@ ratio_mean_stride3d(float* input, float* output, } mean_horiz /= 7.0f; + /* calculate the ratio between two means assuming that the mean + orthogonal to stripes direction should be larger than the mean + parallel to it */ if ((mean_horiz > mean_plate) && (mean_horiz != 0.0f)) { output[index] = mean_plate/mean_horiz; @@ -103,7 +109,6 @@ ratio_mean_stride3d(float* input, float* output, } } - void vertical_median_stride3d(float* input, float* output, int window_halflength_vertical, @@ -169,8 +174,8 @@ stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_ve /* Here we calculate the ratio between the mean in a small 2D neighbourhood parallel to the stripe and the mean orthogonal to the stripe. The gradient variation in the direction orthogonal to the - stripe is expected to be large (a jump), while in parallel direction small. Therefore at the stripe - edge we should get a ratio small/large. */ + stripe is expected to be large (a jump), while in parallel direction small. Therefore at the edges + of a stripe we should get a ratio small/large or large/small. */ #pragma omp parallel for shared(gradient3d_x_arr, mean_ratio3d_arr) private(i, j, k, index) for(k = 0; k < dimZ; k++) { @@ -183,7 +188,8 @@ stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_ve } } } - + /* We process the resulting ratio map with a vertical median filter which removes + small outliers of clusters */ #pragma omp parallel for shared(mean_ratio3d_arr, Output) private(i, j, k, index) for(k = 0; k < dimZ; k++) { @@ -199,9 +205,53 @@ stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_ve i, j, k, index, (long) (dimX), (long) (dimY), (long) (dimZ)); } } - } + } free(gradient3d_x_arr); free(mean_ratio3d_arr); return 0; -} \ No newline at end of file +} + +DLL int +stripesmask3d_main_float(float* Input, unsigned short* Output, + float threshold_val, + int stripe_length_min, int stripe_depth_min, + int ncores, int dimX, int dimY, int dimZ) +{ + long i; + long j; + long k; + long long index; + long long totalvoxels; + totalvoxels = (long long) (dimX*dimY*dimZ); + + /* dealing here with a custom given number of cpu threads */ + if(ncores > 0) + { + // Explicitly disable dynamic teams + omp_set_dynamic(0); + // Use a number of threads for all consecutive parallel regions + omp_set_num_threads(ncores); + } + + /* First step is to mask all the values in the given weights input image + that are bellow a given threshold_val */ +#pragma omp parallel for shared(Input, Output) private(i, j, k, index) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) + { + for(i = 0; i < dimX; i++) + { + index = ((dimX * dimY) * k + j * dimX + i); + if (Input[index] <= threshold_val) + { + Output[index] = 1; + } + + } + } + } + + return 0; +} diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 6d7804391..0709ebb76 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -83,7 +83,8 @@ 'remove_dead_stripe', 'remove_all_stripe', 'remove_stripe_based_interpolation', - 'stripes_detect3d'] + 'stripes_detect3d', + 'stripes_mask3d'] def remove_stripe_fw( @@ -942,7 +943,7 @@ def _remove_stripe_based_interpolation(tomo, snr, size, drop_ratio, norm): tomo[:, m, :] = sino -def stripes_detect3d(arr, window_halflength_vertical=10, ncore=None): +def stripes_detect3d(arr, vertical_filter_size=10, ncore=None): """ Apply 3D stripe detection method to empasize stripe's edges in a 3D array. @@ -952,8 +953,8 @@ def stripes_detect3d(arr, window_halflength_vertical=10, ncore=None): ---------- arr : ndarray Input 3D array of float32 data type. - size : int, optional - The size of the filter's kernel. + vertical_filter_size : float, optional + The size of the vertical 1D median filer which removes outliers. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used if unspecified. @@ -986,7 +987,63 @@ def stripes_detect3d(arr, window_halflength_vertical=10, ncore=None): # perform full 3D stripes detection extern.c_stripes_detect3d(arr, out, - window_halflength_vertical, + vertical_filter_size, ncore, dx, dy, dz) + return out + +def stripes_mask3d(arr, + threshold = 0.7, + stripe_length_min = 100, + stripe_depth_min = 10, + ncore=None): + """ + Takes the result of the stripes_detect3d module as an input (weights for stripes) + and creates a binary 3D mask emphasizing stripes and removing other features. + + .. versionadded:: 1.13 + + Parameters + ---------- + arr : ndarray + Input 3D array of float32 data type, output of stripes_detect3d module. + vertical_filter_size : float, optional + The size of the vertical 1D median filer which removes outliers. + ncore : int, optional + Number of cores that will be assigned to jobs. All cores will be used + if unspecified. + + Returns + ------- + ndarray + Weights for stripe's edges as a 3D array of float32 data type. + + Raises + ------ + ValueError + If the input array is not three dimensional. + + """ + if ncore is None: + ncore = mproc.mp.cpu_count() + + input_type = arr.dtype + if (input_type != 'float32'): + arr = dtype.as_float32(arr) # silent convertion to float32 data type + out = np.uint16(np.empty_like(arr)) + + if arr.ndim == 3: + dz, dy, dx = arr.shape + if (dz == 0) or (dy == 0) or (dx == 0): + raise ValueError("The length of one of dimensions is equal to zero") + else: + raise ValueError("The input array must be a 3D array") + + # perform mask creation based on the input provided by stripes_detect3d module + extern.c_stripesmask3d(arr, out, + threshold, + stripe_length_min, + stripe_depth_min, + ncore, + dx, dy, dz) return out \ No newline at end of file diff --git a/source/tomopy/util/extern/prep.py b/source/tomopy/util/extern/prep.py index 0d930c7f8..b04fa6d17 100644 --- a/source/tomopy/util/extern/prep.py +++ b/source/tomopy/util/extern/prep.py @@ -61,7 +61,8 @@ __docformat__ = 'restructuredtext en' __all__ = ['c_normalize_bg', 'c_remove_stripe_sf', - 'c_stripes_detect3d'] + 'c_stripes_detect3d', + 'c_stripesmask3d'] LIB_TOMOPY_PREP = c_shared_lib("tomo-prep") @@ -118,3 +119,28 @@ def c_stripes_detect3d( dtype.as_c_int(dz), ) return output + +def c_stripesmask3d( + input, + output, + threshold_val, + stripe_length_min, + stripe_depth_min, + ncore, + dx, + dy, + dz, +): + LIB_TOMOPY_PREP.stripesmask3d_main_float.restype = dtype.as_c_void_p() + LIB_TOMOPY_PREP.stripesmask3d_main_float( + dtype.as_c_float_p(input), + dtype.as_c_uint16_p(output), + dtype.as_c_float(threshold_val), + dtype.as_c_int(stripe_length_min), + dtype.as_c_int(stripe_depth_min), + dtype.as_c_int(ncore), + dtype.as_c_int(dx), + dtype.as_c_int(dy), + dtype.as_c_int(dz), + ) + return output \ No newline at end of file From d314c1a2bd0f1f3e872ed7181edc7bda45e7d302 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Sun, 12 Feb 2023 23:09:46 +0000 Subject: [PATCH 04/27] work continues --- include/libtomo/stripes_detect3d.h | 5 +- source/libtomo/prep/stripes_detect3d.c | 182 +++++++++++++++++++++++-- source/tomopy/prep/stripe.py | 40 +++++- source/tomopy/util/extern/prep.py | 4 + 4 files changed, 215 insertions(+), 16 deletions(-) diff --git a/include/libtomo/stripes_detect3d.h b/include/libtomo/stripes_detect3d.h index 6acbbe1d3..f8263d626 100644 --- a/include/libtomo/stripes_detect3d.h +++ b/include/libtomo/stripes_detect3d.h @@ -59,5 +59,8 @@ stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_ve DLL int stripesmask3d_main_float(float* Input, unsigned short* Output, float threshold_val, - int stripe_length_min, int stripe_depth_min, + int stripe_length_min, + int stripe_depth_min, + int stripe_width_min, + float sensitivity, int ncores, int dimX, int dimY, int dimZ); diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index b067ce20e..620f15b54 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -47,6 +47,7 @@ #include #include #include +#include #include "libtomo/stripes_detect3d.h" #include "../misc/utils.h" @@ -74,12 +75,12 @@ ratio_mean_stride3d(float* input, float* output, { j1 = j + j_m; if((j1 < 0) || (j1 >= dimY)) - j1 = j; + j1 = j - j_m; for(k_m = -radius; k_m <= radius; k_m++) { k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) - k1 = k; + k1 = k - k_m; mean_plate += fabsf(input[((dimX * dimY) * k1 + j1 * dimX + i)]); } } @@ -91,7 +92,7 @@ ratio_mean_stride3d(float* input, float* output, { i1 = i + i_m; if (i1 >= dimX) - i1 = i; + i1 = i - i_m; mean_horiz += fabsf(input[((dimX * dimY) * k + j * dimX + i1)]); } mean_horiz /= 7.0f; @@ -129,7 +130,7 @@ vertical_median_stride3d(float* input, float* output, { k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) - k1 = k; + k1 = k-k_m; _values[counter] = input[((dimX * dimY) * k1 + j * dimX + i)]; counter++; } @@ -139,6 +140,121 @@ vertical_median_stride3d(float* input, float* output, free (_values); } +void +remove_inconsistent_stripes(unsigned short* mask, + unsigned short* out, + int stripe_length_min, + int stripe_depth_min, + float sensitivity, + long i, + long j, + long k, + long long index, + long dimX, long dimY, long dimZ) +{ + int counter_vert_voxels; + int counter_depth_voxels; + int halfstripe_length = (int)stripe_length_min/2; + int halfstripe_depth = (int)stripe_depth_min/2; + long k_m; + long k1; + long y_m; + long y1; + int threshold_verical = (int)((0.01f*sensitivity)*stripe_length_min); + int threshold_depth = (int)((0.01f*sensitivity)*stripe_depth_min); + + counter_vert_voxels = 0; + for(k_m = -halfstripe_length; k_m <= halfstripe_length; k_m++) + { + k1 = k + k_m; + if((k1 < 0) || (k1 >= dimZ)) + k1 = k - k_m; + if (mask[((dimX * dimY) * k1 + j * dimX + i)] == 1) + { + counter_vert_voxels++; + } + } + + /* Here we decide to keep the currect voxel based on the number of vertical voxels bellow it */ + if (counter_vert_voxels > threshold_verical) + { + /* The vertical non zero values seem consistent, so we asssume that this element might belong to a stripe. */ + /* We do, however, need to check the depth consistency as well. Here we assume that the stripes are not very + extended in the depth dimension compared to the features that are belong to a sample. */ + + if (stripe_depth_min != 0) + { + counter_depth_voxels = 0; + for(y_m = -halfstripe_depth; y_m <= halfstripe_depth; y_m++) + { + y1 = j + y_m; + if((y1 < 0) || (y1 >= dimY)) + y1 = j - y_m; + if (mask[((dimX * dimY) * k + y1 * dimX + i)] == 1) + { + counter_depth_voxels++; + } + } + if (counter_depth_voxels < threshold_depth) + { + out[index] = 1; + } + else + { + out[index] = 0; + } + } + else + { + out[index] = 1; + } + } + else + { + out[index] = 0; + } +} + +void +merge_stripes(unsigned short* mask, + unsigned short* out, + int stripe_width_min, + long i, + long j, + long k, + long long index, + long dimX, long dimY, long dimZ) +{ + + long x_m; + long x1; + long x2; + long x2_m; + + if (mask[index] == 1) + { + /* merging stripes in the horizontal direction */ + for(x_m=stripe_width_min; x_m>=0; x_m--) { + x1 = i + x_m; + if (x1 >= dimX) + x1 = i - x_m; + if (mask[((dimX * dimY) * k + j * dimX + x1)] == 1) + /*the other end of the mask has been found, merge all values inbetween */ + { + for(x2 = 0; x2 <= x_m; x2++) + { + x2_m = i + x2; + out[((dimX * dimY) * k + j * dimX + x2_m)] = 1; + } + break; + } + } + + } +} + + + DLL int stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, int ncores, int dimX, int dimY, int dimZ) @@ -215,7 +331,10 @@ stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_ve DLL int stripesmask3d_main_float(float* Input, unsigned short* Output, float threshold_val, - int stripe_length_min, int stripe_depth_min, + int stripe_length_min, + int stripe_depth_min, + int stripe_width_min, + float sensitivity, int ncores, int dimX, int dimY, int dimZ) { long i; @@ -225,6 +344,9 @@ stripesmask3d_main_float(float* Input, unsigned short* Output, long long totalvoxels; totalvoxels = (long long) (dimX*dimY*dimZ); + unsigned short* mask; + mask = calloc(totalvoxels, sizeof(unsigned short)); + /* dealing here with a custom given number of cpu threads */ if(ncores > 0) { @@ -235,8 +357,8 @@ stripesmask3d_main_float(float* Input, unsigned short* Output, } /* First step is to mask all the values in the given weights input image - that are bellow a given threshold_val */ -#pragma omp parallel for shared(Input, Output) private(i, j, k, index) + that are bellow a given "threshold_val" parameter */ +#pragma omp parallel for shared(Input, mask) private(i, j, k, index) for(k = 0; k < dimZ; k++) { for(j = 0; j < dimY; j++) @@ -246,12 +368,56 @@ stripesmask3d_main_float(float* Input, unsigned short* Output, index = ((dimX * dimY) * k + j * dimX + i); if (Input[index] <= threshold_val) { - Output[index] = 1; + mask[index] = 1; } } } } + /* Then we need to remove stripes that are shorter than "stripe_length_min" parameter + or inconsistent otherwise. For every pixel we will run a 1D vertical window to count + nonzero values in the mask. We also check for the depth of the mask's value, + assuming that the stripes are normally shorter in depth compare to the features that + belong to true data */ +#pragma omp parallel for shared(mask, Output) private(i, j, k, index) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) + { + for(i = 0; i < dimX; i++) + { + index = ((dimX * dimY) * k + j * dimX + i); + remove_inconsistent_stripes(mask, Output, + stripe_length_min, + stripe_depth_min, + sensitivity, + i, j, k, index, + (long) (dimX), (long) (dimY), (long) (dimZ)); + } + } + } + /* Copy output to mask */ + // copyIm_unshort(Output, mask, (long) (dimX), (long) (dimY), (long) (dimZ)); + /* We can merge stripes together if they are relatively close to each other + based on the stripe_width_min parameter */ + /* +#pragma omp parallel for shared(mask, Output) private(i, j, k, index) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) + { + for(i = 0; i < dimX; i++) + { + index = ((dimX * dimY) * k + j * dimX + i); + merge_stripes(mask, Output, + stripe_width_min, + i, j, k, index, + (long) (dimX), (long) (dimY), (long) (dimZ)); + } + } + } + */ + free(mask); return 0; } diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 0709ebb76..b4ab69ab1 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -994,8 +994,10 @@ def stripes_detect3d(arr, vertical_filter_size=10, ncore=None): def stripes_mask3d(arr, threshold = 0.7, - stripe_length_min = 100, - stripe_depth_min = 10, + stripe_length_perc = 20.0, + stripe_depth_perc = 1.0, + stripe_width_perc = 2.0, + sensitivity_perc = 80.0, ncore=None): """ Takes the result of the stripes_detect3d module as an input (weights for stripes) @@ -1006,9 +1008,11 @@ def stripes_mask3d(arr, Parameters ---------- arr : ndarray - Input 3D array of float32 data type, output of stripes_detect3d module. - vertical_filter_size : float, optional - The size of the vertical 1D median filer which removes outliers. + 3D array (float32 data type) given as [angles, detY(depth), detX]; The input array is the result of stripes_detect3d module. + stripe_length_perc : float, optional + Parameter that defines the minimal length of a stripe accepted in percents relative to the full angular dimension. + sensitivity_perc : float, optional + The value in percents to impose less strict conditions on length, depth and width of a stripe. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used if unspecified. @@ -1038,12 +1042,34 @@ def stripes_mask3d(arr, raise ValueError("The length of one of dimensions is equal to zero") else: raise ValueError("The input array must be a 3D array") - + + # calculate absolute values based on the provided percentages: + if 0.0 < stripe_length_perc <= 100.0: + stripe_length_min = (int)((0.01*stripe_length_perc)*dz) + else: + raise ValueError("stripe_length_perc value must be in (0, 100] percentage range ") + if 0.0 <= stripe_depth_perc <= 100.0: + stripe_depth_min = (int)((0.01*stripe_depth_perc)*dy) + else: + raise ValueError("stripe_depth_perc value must be in [0, 100] percentage range ") + if 0.0 < stripe_width_perc <= 100.0: + stripe_width_min = (int)((0.01*stripe_width_perc)*dx) + else: + raise ValueError("stripe_width_perc value must be in (0, 100] percentage range ") + if 0.0 < sensitivity_perc <= 100.0: + pass + else: + raise ValueError("sensitivity_perc value must be in (0, 100] percentage range ") + + # perform mask creation based on the input provided by stripes_detect3d module extern.c_stripesmask3d(arr, out, threshold, stripe_length_min, stripe_depth_min, + stripe_width_min, + sensitivity_perc, ncore, dx, dy, dz) - return out \ No newline at end of file + return out + diff --git a/source/tomopy/util/extern/prep.py b/source/tomopy/util/extern/prep.py index b04fa6d17..587e35ad5 100644 --- a/source/tomopy/util/extern/prep.py +++ b/source/tomopy/util/extern/prep.py @@ -126,6 +126,8 @@ def c_stripesmask3d( threshold_val, stripe_length_min, stripe_depth_min, + stripe_width_min, + sensitivity_perc, ncore, dx, dy, @@ -138,6 +140,8 @@ def c_stripesmask3d( dtype.as_c_float(threshold_val), dtype.as_c_int(stripe_length_min), dtype.as_c_int(stripe_depth_min), + dtype.as_c_int(stripe_width_min), + dtype.as_c_float(sensitivity_perc), dtype.as_c_int(ncore), dtype.as_c_int(dx), dtype.as_c_int(dy), From 92bdccdb1938f54905e46d839096ef916f4f9658 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Tue, 14 Feb 2023 21:50:08 +0000 Subject: [PATCH 05/27] completes the code and adds tests --- include/libtomo/stripes_detect3d.h | 4 +- source/libtomo/prep/stripes_detect3d.c | 55 ++++++++++++++---- source/tomopy/prep/stripe.py | 43 ++++++++++---- source/tomopy/util/extern/prep.py | 2 + .../test_data/stripes_detect3d.npy | Bin 0 -> 36128 bytes test/test_tomopy/test_data/stripes_mask3d.npy | Bin 0 -> 18128 bytes .../test_data/test_stripe_data.npy | Bin 0 -> 36128 bytes test/test_tomopy/test_prep/test_stripe.py | 17 ++++++ 8 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 test/test_tomopy/test_data/stripes_detect3d.npy create mode 100644 test/test_tomopy/test_data/stripes_mask3d.npy create mode 100644 test/test_tomopy/test_data/test_stripe_data.npy diff --git a/include/libtomo/stripes_detect3d.h b/include/libtomo/stripes_detect3d.h index f8263d626..c12cda7c0 100644 --- a/include/libtomo/stripes_detect3d.h +++ b/include/libtomo/stripes_detect3d.h @@ -53,7 +53,9 @@ #endif DLL int -stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, +stripesdetect3d_main_float(float* Input, float* Output, + int window_halflength_vertical, + int ratio_radius, int ncores, int dimX, int dimY, int dimZ); DLL int diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index 620f15b54..47cf6e0ee 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -55,13 +55,16 @@ void ratio_mean_stride3d(float* input, float* output, + int radius, long i, long j, long k, long long index, long dimX, long dimY, long dimZ) { - float mean_plate; + float mean_plate; float mean_horiz; - int radius = 3; + float mean_horiz2; + float min_val; int diameter = 2*radius + 1; + int all_pixels_window = diameter*diameter; long i_m; long j_m; long k_m; @@ -84,18 +87,29 @@ ratio_mean_stride3d(float* input, float* output, mean_plate += fabsf(input[((dimX * dimY) * k1 + j1 * dimX + i)]); } } - mean_plate /= 49.0f; + mean_plate /= (float)(all_pixels_window); - /* calculate mean of gradientX in a direction orthogonal to stripes direction */ + /* calculate mean of gradientX in a direction orthogonal to stripes direction */ mean_horiz = 0.0f; - for(i_m = 1; i_m <= diameter; i_m++) + for(i_m = 1; i_m <= radius; i_m++) { i1 = i + i_m; if (i1 >= dimX) i1 = i - i_m; mean_horiz += fabsf(input[((dimX * dimY) * k + j * dimX + i1)]); } - mean_horiz /= 7.0f; + mean_horiz /= (float)(radius); + + /* Calculate another mean symmetrically */ + mean_horiz2 = 0.0f; + for(i_m = -radius; i_m <= -1; i_m++) + { + i1 = i + i_m; + if (i1 < 0) + i1 = i - i_m; + mean_horiz2 += fabsf(input[((dimX * dimY) * k + j * dimX + i1)]); + } + mean_horiz2 /= (float)(radius); /* calculate the ratio between two means assuming that the mean orthogonal to stripes direction should be larger than the mean @@ -108,6 +122,21 @@ ratio_mean_stride3d(float* input, float* output, { output[index] = mean_horiz/mean_plate; } + min_val = 0.0f; + if ((mean_horiz2 > mean_plate) && (mean_horiz2 != 0.0f)) + { + min_val = mean_plate/mean_horiz2; + } + if ((mean_horiz2 < mean_plate) && (mean_plate != 0.0f)) + { + min_val = mean_horiz2/mean_plate; + } + + /* accepting the smallest value */ + if (output[index] > min_val) + { + output[index] = min_val; + } } void @@ -256,8 +285,11 @@ merge_stripes(unsigned short* mask, DLL int -stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, - int ncores, int dimX, int dimY, int dimZ) +stripesdetect3d_main_float(float* Input, float* Output, + int window_halflength_vertical, + int ratio_radius, + int ncores, + int dimX, int dimY, int dimZ) { long i; long j; @@ -300,7 +332,7 @@ stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_ve for(i = 0; i < dimX; i++) { index = ((dimX * dimY) * k + j * dimX + i); - ratio_mean_stride3d(gradient3d_x_arr, mean_ratio3d_arr, i, j, k, index, (long) (dimX), (long) (dimY), (long) (dimZ)); + ratio_mean_stride3d(gradient3d_x_arr, mean_ratio3d_arr, ratio_radius, i, j, k, index, (long) (dimX), (long) (dimY), (long) (dimZ)); } } } @@ -397,11 +429,10 @@ stripesmask3d_main_float(float* Input, unsigned short* Output, } } /* Copy output to mask */ - // copyIm_unshort(Output, mask, (long) (dimX), (long) (dimY), (long) (dimZ)); + copyIm_unshort(Output, mask, (long) (dimX), (long) (dimY), (long) (dimZ)); /* We can merge stripes together if they are relatively close to each other based on the stripe_width_min parameter */ - /* #pragma omp parallel for shared(mask, Output) private(i, j, k, index) for(k = 0; k < dimZ; k++) { @@ -417,7 +448,7 @@ stripesmask3d_main_float(float* Input, unsigned short* Output, } } } - */ + free(mask); return 0; } diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index b4ab69ab1..67c0858e5 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -943,18 +943,24 @@ def _remove_stripe_based_interpolation(tomo, snr, size, drop_ratio, norm): tomo[:, m, :] = sino -def stripes_detect3d(arr, vertical_filter_size=10, ncore=None): +def stripes_detect3d(arr, vert_filter_size_perc=5, radius_size=3, ncore=None): """ - Apply 3D stripe detection method to empasize stripe's edges in a 3D array. + Apply a stripes detection method to empasize their edges in a 3D array. + The input must be normalized projection data in range [0,1] and given in + the following axis orientation [angles, detY(depth), detX (horizontal)]. With + this orientation, the stripes are the vertical features. The method works with + full and partial stripes of constant ot varying intensity. .. versionadded:: 1.13 Parameters ---------- arr : ndarray - Input 3D array of float32 data type. - vertical_filter_size : float, optional - The size of the vertical 1D median filer which removes outliers. + Input 3D array of float32 data type, normalized and in the following axis orientation [angles, detY(depth), detX (horizontal)]. + vert_filter_size_perc : float, optional + The size (in percents relative to angular dimension) of the vertical 1D median filter to remove outliers. + radius_size : int, optional + The size of the filter to calculate the ratio. This will effect the width of the resulting mask. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used if unspecified. @@ -962,7 +968,8 @@ def stripes_detect3d(arr, vertical_filter_size=10, ncore=None): Returns ------- ndarray - Weights for stripe's edges as a 3D array of float32 data type. + Weights for stripe's edges as a 3D array of float32 data type. + The weights can be thresholded or passed to stripes_mask3d function to obtain a binary mask. Raises ------ @@ -985,9 +992,16 @@ def stripes_detect3d(arr, vertical_filter_size=10, ncore=None): else: raise ValueError("The input array must be a 3D array") - # perform full 3D stripes detection + # calculate absolute values based on the provided percentages: + if 0.0 < vert_filter_size_perc <= 100.0: + vertical_filter_size = (int)((0.01*vert_filter_size_perc)*dz) + else: + raise ValueError("vert_filter_size_perc value must be in (0, 100] percentage range ") + + # perform stripes detection extern.c_stripes_detect3d(arr, out, vertical_filter_size, + radius_size, ncore, dx, dy, dz) return out @@ -1000,8 +1014,9 @@ def stripes_mask3d(arr, sensitivity_perc = 80.0, ncore=None): """ - Takes the result of the stripes_detect3d module as an input (weights for stripes) - and creates a binary 3D mask emphasizing stripes and removing other features. + Takes the result of the stripes_detect3d module as an input and generates a + binary 3D mask with ones where stripes present. The method tries to eliminate + non-stripe features by checking the consistency in three directions. .. versionadded:: 1.13 @@ -1009,8 +1024,14 @@ def stripes_mask3d(arr, ---------- arr : ndarray 3D array (float32 data type) given as [angles, detY(depth), detX]; The input array is the result of stripes_detect3d module. + threshold : float, optional + Threshold for the given weights, the smaller values should correspond to the stripes stripe_length_perc : float, optional - Parameter that defines the minimal length of a stripe accepted in percents relative to the full angular dimension. + Parameter (in percents) that controls the minimum accepted length of a stripe relative to the full angular dimension. + stripe_depth_perc : float, optional + Parameter (in percents) that controls the minimum accepted depth of a stripe relative to the depth dimension. + stripe_width_perc : float, optional + Parameter (in percents) that controls the minimum accepted width of a stripe relative to the full horizontal dimension. sensitivity_perc : float, optional The value in percents to impose less strict conditions on length, depth and width of a stripe. ncore : int, optional @@ -1020,7 +1041,7 @@ def stripes_mask3d(arr, Returns ------- ndarray - Weights for stripe's edges as a 3D array of float32 data type. + A binary mask of uint16 data type with stripes highlighted. Raises ------ diff --git a/source/tomopy/util/extern/prep.py b/source/tomopy/util/extern/prep.py index 587e35ad5..c54c1bf99 100644 --- a/source/tomopy/util/extern/prep.py +++ b/source/tomopy/util/extern/prep.py @@ -103,6 +103,7 @@ def c_stripes_detect3d( input, output, window_halflength_vertical, + radius_size, ncore, dx, dy, @@ -113,6 +114,7 @@ def c_stripes_detect3d( dtype.as_c_float_p(input), dtype.as_c_float_p(output), dtype.as_c_int(window_halflength_vertical), + dtype.as_c_int(radius_size), dtype.as_c_int(ncore), dtype.as_c_int(dx), dtype.as_c_int(dy), diff --git a/test/test_tomopy/test_data/stripes_detect3d.npy b/test/test_tomopy/test_data/stripes_detect3d.npy new file mode 100644 index 0000000000000000000000000000000000000000..088a6e8a47c9b05360b2a409e217755c1d13e0a2 GIT binary patch literal 36128 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 L$9X2^;#~h9q^zAO literal 36128 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 diff --git a/test/test_tomopy/test_data/stripes_mask3d.npy b/test/test_tomopy/test_data/stripes_mask3d.npy index 24158a40d3185c4226ccc26c52bed800cfd5a4b9..00d8a56c386cfd8f8414703708efd566abf5a1b3 100644 GIT binary patch literal 9128 zcmeI!u?hkq9LI5Q?J3+WaPVmB0osa|hNh6tXoxQKYN?*07fy$RKWzm!9DM%>{&f)j z@Ed%ux2w&rQg`(*O-P4m7TP>-wXxb9+t@{a*|)I?Wq;|5w@uY(9WxcrOYYlISh zeP8fColjwS4Fv)yuE=0&qU&bC!iuUDJwP}KMRG)ZDR ztUt*hwm195)idFW{1sL@>4#r=vPz{HW+J@aJ6LIaIA{~2B| s%%Bzo4K!3hGu=Q#1vJwQG*mz{-9SSHG}8?h($ diff --git a/test/test_tomopy/test_prep/test_stripe.py b/test/test_tomopy/test_prep/test_stripe.py index c532ddbd0..73256ffaf 100644 --- a/test/test_tomopy/test_prep/test_stripe.py +++ b/test/test_tomopy/test_prep/test_stripe.py @@ -149,17 +149,17 @@ def test_remove_stripe_based_interpolation(self): def test_stripe_detection(self): assert_allclose( srm.stripes_detect3d(read_file('test_stripe_data.npy'), - vert_filter_size_perc=25, - radius_size=1), + size=10, + radius=1), read_file('stripes_detect3d.npy'), rtol=1e-6) def test_stripe_mask(self): assert_allclose( srm.stripes_mask3d(read_file('stripes_detect3d.npy'), - threshold=0.2, - stripe_length_perc=30.0, - stripe_depth_perc=0.0, - stripe_width_perc=20.0, - sensitivity_perc=80.0), + threshold=0.6, + min_stripe_length = 10, + min_stripe_depth = 0, + min_stripe_width = 5, + sensitivity_perc=85.0), read_file('stripes_mask3d.npy'), rtol=1e-6) From 3afa2c6a7f8aba8959611e617fa5924a79e62918 Mon Sep 17 00:00:00 2001 From: Daniel Ching Date: Mon, 27 Feb 2023 17:25:40 -0600 Subject: [PATCH 13/27] REF: Use memcpy from standard library instead of copyIm --- source/libtomo/misc/median_filt3d.c | 5 ++-- source/libtomo/misc/utils.c | 38 -------------------------- source/libtomo/misc/utils.h | 8 ------ source/libtomo/prep/stripes_detect3d.c | 14 +++++----- 4 files changed, 10 insertions(+), 55 deletions(-) diff --git a/source/libtomo/misc/median_filt3d.c b/source/libtomo/misc/median_filt3d.c index 024d632d7..0c5489f3f 100644 --- a/source/libtomo/misc/median_filt3d.c +++ b/source/libtomo/misc/median_filt3d.c @@ -47,6 +47,7 @@ #include #include #include +#include #include "libtomo/median_filt3d.h" #include "utils.h" @@ -268,7 +269,7 @@ medianfilter_main_float(float* Input, float* Output, int radius, float mu_thresh diameter = (2 * radius + 1); /* diameter of the filter's kernel */ if(mu_threshold != 0.0) { - copyIm(Input, Output, (long) (dimX), (long) (dimY), (long) (dimZ)); + memcpy(Output, Input, dimX * dimY * dimZ * sizeof(float)); } /* copy input into output */ /* dealing here with a custom given number of cpu threads */ @@ -331,7 +332,7 @@ medianfilter_main_uint16(unsigned short* Input, unsigned short* Output, int radi diameter = (2 * radius + 1); /* diameter of the filter's kernel */ if(mu_threshold != 0.0) { - copyIm_unshort(Input, Output, (long) (dimX), (long) (dimY), (long) (dimZ)); + memcpy(Output, Input, dimX * dimY * dimZ * sizeof(unsigned short)); } /* copy input into output */ /* dealing here with a custom given number of cpu threads */ diff --git a/source/libtomo/misc/utils.c b/source/libtomo/misc/utils.c index c18b1ae40..97f797c84 100644 --- a/source/libtomo/misc/utils.c +++ b/source/libtomo/misc/utils.c @@ -59,43 +59,6 @@ initlibtomo(void) # endif #endif -/* Copy Image (float) */ -void -copyIm(const float* A, float* U, long dimX, long dimY, long dimZ) -{ - long j; - for(j = 0; j < dimX * dimY * dimZ; j++) - U[j] = A[j]; -} - -/* Copy Image -unsigned char (8bit)*/ -void -copyIm_unchar(const unsigned char* A, unsigned char* U, int dimX, int dimY, int dimZ) -{ - int j; - for(j = 0; j < dimX * dimY * dimZ; j++) - U[j] = A[j]; -} - -/* Copy Image -unsigned char long long (8bit)*/ -void copyIm_unchar_long(unsigned char *A, unsigned char *U, long long totalvoxels) -{ - size_t j; -#pragma omp parallel for shared(A, U) private(j) - for (j = 0; j #include #include +#include #include "libtomo/stripe.h" #include "../misc/utils.h" @@ -608,8 +609,7 @@ int stripesmask3d_main_float(float* Input, int iter_merge; int switch_dim; size_t index; - long long totalvoxels; - totalvoxels = (long long)(dimX*dimY*dimZ); + size_t totalvoxels = dimX*dimY*dimZ; unsigned char* mask; mask = malloc(totalvoxels * sizeof(unsigned char)); @@ -645,7 +645,7 @@ int stripesmask3d_main_float(float* Input, } /* Copy mask to output */ - copyIm_unchar_long(mask, Output, totalvoxels); + memcpy(Output, mask, totalvoxels * sizeof(unsigned char)); /* the depth consistency for features */ switch_dim = 1; @@ -668,7 +668,7 @@ int stripesmask3d_main_float(float* Input, } } /* Copy output to mask */ - copyIm_unchar_long(Output, mask, totalvoxels); + memcpy(mask, Output, totalvoxels * sizeof(unsigned char)); /* Now we need to remove stripes that are shorter than "stripe_length_min" parameter @@ -699,7 +699,7 @@ int stripesmask3d_main_float(float* Input, } } /* Copy output to mask */ - copyIm_unchar_long(Output, mask, totalvoxels); + memcpy(mask, Output, totalvoxels * sizeof(unsigned char)); /* now we clean the obtained mask if the features do not hold our assumptions about the lengths */ @@ -720,7 +720,7 @@ int stripesmask3d_main_float(float* Input, } /* Copy output to mask */ - copyIm_unchar_long(Output, mask, totalvoxels); + memcpy(mask, Output, totalvoxels * sizeof(unsigned char)); /* We can merge stripes together if they are relatively close to each other @@ -744,7 +744,7 @@ int stripesmask3d_main_float(float* Input, } } /* Copy output to mask */ - copyIm_unchar_long(Output, mask, totalvoxels); + memcpy(mask, Output, totalvoxels * sizeof(unsigned char)); } free(mask); From f0f828f05c8daaa3dbfb32b9ddd48e658f219c0c Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Wed, 1 Mar 2023 22:16:21 +0000 Subject: [PATCH 14/27] removes utils, employs qsort instead --- include/libtomo/stripe.h | 3 +- source/libtomo/misc/CMakeLists.txt | 2 - source/libtomo/misc/median_filt3d.c | 25 +++- source/libtomo/misc/utils.c | 176 ------------------------- source/libtomo/misc/utils.h | 59 --------- source/libtomo/prep/CMakeLists.txt | 2 - source/libtomo/prep/stripes_detect3d.c | 14 +- 7 files changed, 31 insertions(+), 250 deletions(-) delete mode 100644 source/libtomo/misc/utils.c delete mode 100644 source/libtomo/misc/utils.h diff --git a/include/libtomo/stripe.h b/include/libtomo/stripe.h index 7d341fc47..c49735818 100644 --- a/include/libtomo/stripe.h +++ b/include/libtomo/stripe.h @@ -69,4 +69,5 @@ stripesmask3d_main_float(float* Input, int stripe_depth_min, int stripe_width_min, float sensitivity, - int ncores, long dimX, long dimY, long dimZ); \ No newline at end of file + int ncores, long dimX, long dimY, long dimZ); + \ No newline at end of file diff --git a/source/libtomo/misc/CMakeLists.txt b/source/libtomo/misc/CMakeLists.txt index 18c2ea516..a3c6431dd 100755 --- a/source/libtomo/misc/CMakeLists.txt +++ b/source/libtomo/misc/CMakeLists.txt @@ -9,8 +9,6 @@ tomopy_add_library( morph.c remove_ring.c median_filt3d.c - utils.c - utils.h ${HEADERS}) find_package(OpenMP REQUIRED COMPONENTS C) diff --git a/source/libtomo/misc/median_filt3d.c b/source/libtomo/misc/median_filt3d.c index 0c5489f3f..0b1aadfeb 100644 --- a/source/libtomo/misc/median_filt3d.c +++ b/source/libtomo/misc/median_filt3d.c @@ -50,7 +50,19 @@ #include #include "libtomo/median_filt3d.h" -#include "utils.h" + +int floatcomp(const void* elem1, const void* elem2) +{ + if(*(const float*)elem1 < *(const float*)elem2) + return -1; + return *(const float*)elem1 > *(const float*)elem2; +} +int uint16comp(const void* elem1, const void* elem2) +{ + if(*(const unsigned short*)elem1 < *(const unsigned short*)elem2) + return -1; + return *(const unsigned short*)elem1 > *(const unsigned short*)elem2; +} void medfilt3D_float(float* Input, float* Output, int radius, int sizefilter_total, @@ -91,7 +103,8 @@ medfilt3D_float(float* Input, float* Output, int radius, int sizefilter_total, } } } - quicksort_float(ValVec, 0, sizefilter_total - 1); /* perform sorting */ + + qsort(ValVec, sizefilter_total, sizeof(float), floatcomp); if(mu_threshold == 0.0F) { @@ -137,7 +150,7 @@ medfilt2D_float(float* Input, float* Output, int radius, int sizefilter_total, counter++; } } - quicksort_float(ValVec, 0, sizefilter_total - 1); /* perform sorting */ + qsort(ValVec, sizefilter_total, sizeof(float), floatcomp); if(mu_threshold == 0.0F) { @@ -192,7 +205,8 @@ medfilt3D_uint16(unsigned short* Input, unsigned short* Output, int radius, } } } - quicksort_uint16(ValVec, 0, sizefilter_total - 1); /* perform sorting */ + + qsort(ValVec, sizefilter_total, sizeof(unsigned short), uint16comp); if(mu_threshold == 0.0F) { @@ -239,7 +253,8 @@ medfilt2D_uint16(unsigned short* Input, unsigned short* Output, int radius, counter++; } } - quicksort_uint16(ValVec, 0, sizefilter_total - 1); /* perform sorting */ + + qsort(ValVec, sizefilter_total, sizeof(unsigned short), uint16comp); if(mu_threshold == 0.0F) { diff --git a/source/libtomo/misc/utils.c b/source/libtomo/misc/utils.c deleted file mode 100644 index 97f797c84..000000000 --- a/source/libtomo/misc/utils.c +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. - -// Copyright 2015. UChicago Argonne, LLC. This software was produced -// under U.S. Government contract DE-AC02-06CH11357 for Argonne National -// Laboratory (ANL), which is operated by UChicago Argonne, LLC for the -// U.S. Department of Energy. The U.S. Government has rights to use, -// reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR -// UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR -// ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is -// modified to produce derivative works, such modified software should -// be clearly marked, so as not to confuse it with the version available -// from ANL. - -// Additionally, redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: - -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. - -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in -// the documentation and/or other materials provided with the -// distribution. - -// * Neither the name of UChicago Argonne, LLC, Argonne National -// Laboratory, ANL, the U.S. Government, nor the names of its -// contributors may be used to endorse or promote products derived -// from this software without specific prior written permission. - -// THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago -// Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -#include "utils.h" -#include "stdio.h" - -// for windows build -#ifdef WIN32 -# ifdef PY3K -void -PyInit_libtomo(void) -{ -} -# else -void -initlibtomo(void) -{ -} -# endif -#endif - -/* sorting using bubble method (float)*/ -void -sort_bubble_float(float* x, int n_size) -{ - int i; - int j; - float temp; - - for(i = 0; i < n_size - 1; i++) - { - for(j = 0; j < n_size - i - 1; j++) - { - if(x[j] > x[j + 1]) - { - temp = x[j]; - x[j] = x[j + 1]; - x[j + 1] = temp; - } - } - } -} - -/* sorting using bubble method (uint16)*/ -void -sort_bubble_uint16(unsigned short* x, int n_size) -{ - int i; - int j; - unsigned short temp; - - for(i = 0; i < n_size - 1; i++) - { - for(j = 0; j < n_size - i - 1; j++) - { - if(x[j] > x[j + 1]) - { - temp = x[j]; - x[j] = x[j + 1]; - x[j + 1] = temp; - } - } - } -} - -void -quicksort_float(float* x, int first, int last) -{ - int i; - int j; - int pivot; - float temp; - - if(first < last) - { - pivot = first; - i = first; - j = last; - - while(i < j) - { - while(x[i] <= x[pivot] && i < last) - i++; - while(x[j] > x[pivot]) - j--; - if(i < j) - { - temp = x[i]; - x[i] = x[j]; - x[j] = temp; - } - } - - temp = x[pivot]; - x[pivot] = x[j]; - x[j] = temp; - quicksort_float(x, first, j - 1); - quicksort_float(x, j + 1, last); - } -} - -void -quicksort_uint16(unsigned short* x, int first, int last) -{ - int i; - int j; - int pivot; - unsigned short temp; - - if(first < last) - { - pivot = first; - i = first; - j = last; - - while(i < j) - { - while(x[i] <= x[pivot] && i < last) - i++; - while(x[j] > x[pivot]) - j--; - if(i < j) - { - temp = x[i]; - x[i] = x[j]; - x[j] = temp; - } - } - - temp = x[pivot]; - x[pivot] = x[j]; - x[j] = temp; - quicksort_uint16(x, first, j - 1); - quicksort_uint16(x, j + 1, last); - } -} diff --git a/source/libtomo/misc/utils.h b/source/libtomo/misc/utils.h deleted file mode 100644 index ceadaa1a1..000000000 --- a/source/libtomo/misc/utils.h +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. - -// Copyright 2015. UChicago Argonne, LLC. This software was produced -// under U.S. Government contract DE-AC02-06CH11357 for Argonne National -// Laboratory (ANL), which is operated by UChicago Argonne, LLC for the -// U.S. Department of Energy. The U.S. Government has rights to use, -// reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR -// UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR -// ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is -// modified to produce derivative works, such modified software should -// be clearly marked, so as not to confuse it with the version available -// from ANL. - -// Additionally, redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: - -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. - -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in -// the documentation and/or other materials provided with the -// distribution. - -// * Neither the name of UChicago Argonne, LLC, Argonne National -// Laboratory, ANL, the U.S. Government, nor the names of its -// contributors may be used to endorse or promote products derived -// from this software without specific prior written permission. - -// THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago -// Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -#pragma once - -#ifdef WIN32 -# define DLL __declspec(dllexport) -#else -# define DLL -#endif - -void DLL -sort_bubble_float(float* x, int n_size); -void DLL -sort_bubble_uint16(unsigned short* x, int n_size); -void DLL -quicksort_float(float* x, int first, int last); -void DLL -quicksort_uint16(unsigned short* x, int first, int last); diff --git a/source/libtomo/prep/CMakeLists.txt b/source/libtomo/prep/CMakeLists.txt index 908dc6801..cead98dc5 100755 --- a/source/libtomo/prep/CMakeLists.txt +++ b/source/libtomo/prep/CMakeLists.txt @@ -7,8 +7,6 @@ tomopy_add_library( prep.c stripe.c stripes_detect3d.c - ../misc/utils.c - ../misc/utils.h ${HEADERS}) find_package(OpenMP REQUIRED COMPONENTS C) diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index 23503be76..8c9bab174 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -51,8 +51,6 @@ #include #include "libtomo/stripe.h" -#include "../misc/utils.h" - /********************************************************************/ /**********************Supporting Functions**************************/ @@ -206,6 +204,13 @@ ratio_mean_stride3d(float* input, float* output, return; } +int floatcomp(const void* elem1, const void* elem2) +{ + if(*(const float*)elem1 < *(const float*)elem2) + return -1; + return *(const float*)elem1 > *(const float*)elem2; +} + void vertical_median_stride3d(float* input, float* output, int window_halflength_vertical, @@ -232,15 +237,14 @@ vertical_median_stride3d(float* input, float* output, k1 = k-k_m; _values[counter] = input[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)]; counter++; - } - quicksort_float(_values, 0, window_fulllength-1); + } + qsort(_values, window_fulllength, sizeof(float), floatcomp); output[index] = _values[midval_window_index]; free (_values); return; } - void mean_stride3d(float* input, float* output, long i, long j, long k, From 576c81287f1d5b93eb0844e8d8a49c7ab390c2d3 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Wed, 1 Mar 2023 22:27:11 +0000 Subject: [PATCH 15/27] fixes the issue around non contiguous array passed to median3d --- source/tomopy/misc/corr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/tomopy/misc/corr.py b/source/tomopy/misc/corr.py index 53d80fb9d..0ebd2ef70 100644 --- a/source/tomopy/misc/corr.py +++ b/source/tomopy/misc/corr.py @@ -396,10 +396,10 @@ def median_filter3d(arr, size=3, ncore=None): # perform full 3D filtering if (input_type == 'float32'): - extern.c_median_filt3d_float32(arr, out, kernel_half_size, dif, ncore, + extern.c_median_filt3d_float32(np.ascontiguousarray(arr), out, kernel_half_size, dif, ncore, dx, dy, dz) else: - extern.c_median_filt3d_uint16(arr, out, kernel_half_size, dif, ncore, + extern.c_median_filt3d_uint16(np.ascontiguousarray(arr), out, kernel_half_size, dif, ncore, dx, dy, dz) return out @@ -455,10 +455,10 @@ def remove_outlier3d(arr, dif, size=3, ncore=None): # perform full 3D filtering if (input_type == 'float32'): - extern.c_median_filt3d_float32(arr, out, kernel_half_size, dif, ncore, + extern.c_median_filt3d_float32(np.ascontiguousarray(arr), out, kernel_half_size, dif, ncore, dx, dy, dz) else: - extern.c_median_filt3d_uint16(arr, out, kernel_half_size, dif, ncore, + extern.c_median_filt3d_uint16(np.ascontiguousarray(arr), out, kernel_half_size, dif, ncore, dx, dy, dz) return out From 5c55ee859340bf903b8c468ec2903d32ee3a31b3 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Mon, 6 Mar 2023 09:34:04 +0000 Subject: [PATCH 16/27] Update source/tomopy/prep/stripe.py Co-authored-by: Daniel Ching --- source/tomopy/prep/stripe.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index aaebc513a..8596bfffb 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -974,7 +974,9 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): Raises ------ ValueError - If the input array is not three dimensional. + If the `tomo` is not three dimensional. + If the `size` is invlid. + If `tomo` is not scaled to [0, 1]. """ if ncore is None: ncore = mproc.mp.cpu_count() From f778f721d067435756d0358ed15be6bc853ab240 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Mon, 6 Mar 2023 09:34:21 +0000 Subject: [PATCH 17/27] Update source/tomopy/prep/stripe.py Co-authored-by: Daniel Ching --- source/tomopy/prep/stripe.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 8596bfffb..0ea4d07d1 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -944,11 +944,9 @@ def _remove_stripe_based_interpolation(tomo, snr, size, drop_ratio, norm): def stripes_detect3d(tomo, size=10, radius=3, ncore=None): """ - Apply a stripes detection method to empasize their edges in a 3D array. - The input must be normalized projection data in range [0,1] and given in - the following axis orientation [angles, detY(depth), detX (horizontal)]. With - this orientation, the stripes are the vertical features. The method works with - full and partial stripes of constant ot varying intensity. + Detect stripes in a 3D array. + + The method works with full and partial stripes of constant ot varying intensity. .. versionadded:: 1.14 From 31813cb940e33663984229836da12a3aef1871b3 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Mon, 6 Mar 2023 09:34:50 +0000 Subject: [PATCH 18/27] Update source/tomopy/prep/stripe.py Co-authored-by: Daniel Ching --- source/tomopy/prep/stripe.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 0ea4d07d1..a5dae6a87 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -952,9 +952,10 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): Parameters ---------- - inputData : ndarray - 3D tomographic data of float32 data type, normalized [0,1] and given in - [angles, detY(depth), detX (horizontal)] axis orientation. + tomo : ndarray + 3D tomographic data of float32 data type, normalized [0, 1] and given in + [angles, detY(depth), detX (horizontal)] axis orientation. With + this orientation, the stripes are the vertical features. size : int, optional The pixel size of the vertical 1D median filter to minimise false detections. Increase it if you have longer or full stripes in the data. radius : int, optional From a7bc96404e83d69b4954ce5ba05898261c754e2f Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Mon, 6 Mar 2023 14:31:41 +0000 Subject: [PATCH 19/27] suggested corrections --- include/libtomo/stripe.h | 5 ++-- source/libtomo/prep/stripes_detect3d.c | 9 +++--- source/tomopy/prep/stripe.py | 39 +++++++++++++++----------- source/tomopy/util/extern/prep.py | 12 ++++---- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/include/libtomo/stripe.h b/include/libtomo/stripe.h index c49735818..3990e5550 100644 --- a/include/libtomo/stripe.h +++ b/include/libtomo/stripe.h @@ -59,7 +59,7 @@ stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, int ratio_radius, int ncores, - long dimX, long dimY, long dimZ); + int dimX, int dimY, int dimZ); DLL int stripesmask3d_main_float(float* Input, @@ -69,5 +69,6 @@ stripesmask3d_main_float(float* Input, int stripe_depth_min, int stripe_width_min, float sensitivity, - int ncores, long dimX, long dimY, long dimZ); + int ncores, + int dimX, int dimY, int dimZ); \ No newline at end of file diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index 8c9bab174..1638ad230 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -506,13 +506,13 @@ int stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, int ratio_radius, int ncores, - long dimX, long dimY, long dimZ) + int dimX, int dimY, int dimZ) { long i; long j; long k; long long totalvoxels; - totalvoxels = (long long)(dimX*dimY*dimZ); + totalvoxels = (long long)((long)(dimX)*(long)(dimY)*(long)(dimZ)); int window_fulllength = (int)(2*window_halflength_vertical + 1); int midval_window_index = (int)(0.5f*window_fulllength) - 1; @@ -605,7 +605,8 @@ int stripesmask3d_main_float(float* Input, int stripe_depth_min, int stripe_width_min, float sensitivity, - int ncores, long dimX, long dimY, long dimZ) + int ncores, + int dimX, int dimY, int dimZ) { long i; long j; @@ -613,7 +614,7 @@ int stripesmask3d_main_float(float* Input, int iter_merge; int switch_dim; size_t index; - size_t totalvoxels = dimX*dimY*dimZ; + size_t totalvoxels = (long)(dimX)*(long)(dimY)*(long)(dimZ); unsigned char* mask; mask = malloc(totalvoxels * sizeof(unsigned char)); diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index a5dae6a87..730b49b24 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -944,7 +944,7 @@ def _remove_stripe_based_interpolation(tomo, snr, size, drop_ratio, norm): def stripes_detect3d(tomo, size=10, radius=3, ncore=None): """ - Detect stripes in a 3D array. + Detect stripes in a 3D array. Usually applied to normalized projection data. The method works with full and partial stripes of constant ot varying intensity. @@ -953,13 +953,15 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): Parameters ---------- tomo : ndarray - 3D tomographic data of float32 data type, normalized [0, 1] and given in - [angles, detY(depth), detX (horizontal)] axis orientation. With - this orientation, the stripes are the vertical features. + 3D tomographic data of float32 data type, preferably in the [0, 1] range, although + reasonable deviations accepted (e.g. the result of the normalization and the negative log taken of the raw data). + The projection data should be given with [angles, detY(depth), detX (horizontal)] axis orientation. With this orientation, + the stripes are the vertical features. size : int, optional - The pixel size of the vertical 1D median filter to minimise false detections. Increase it if you have longer or full stripes in the data. + The pixel size of the vertical (angle dimension) 1D median filter to minimise false detections. Increase it if you have longer or full stripes in the data. radius : int, optional - The pixel size of the stencil to calculate the mean ratio between vertical and horizontal orientations. The larger values will enlarge the mask width. + The pixel size of the 3D stencil to calculate the mean ratio between vertical (angular) and horizontal (detX) orientations of the detX gradient. The larger + values can affect the width of the detected stripe, use 1,2,3 values. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used if unspecified. @@ -967,15 +969,14 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): Returns ------- ndarray - Weights for stripe's edges as a 3D array of float32 data type. - The weights can be thresholded or passed to stripes_mask3d function to obtain a binary mask. + Weights in the range of [0, 1] of float32 data type where stripe's edges are highlighted with the smaller (e.g. < 0.5) values. + The weights can be manually thresholded or passed to stripes_mask3d function for further processing and a binary mask generation. Raises ------ ValueError If the `tomo` is not three dimensional. - If the `size` is invlid. - If `tomo` is not scaled to [0, 1]. + If the `size` is invalid. """ if ncore is None: ncore = mproc.mp.cpu_count() @@ -1015,7 +1016,7 @@ def stripes_mask3d(weights, """ Takes the result of the stripes_detect3d module as an input and generates a binary 3D mask with ones where stripes present. The method tries to eliminate - non-stripe features in data by checking the weight consistency in three directions. + non-stripe features in data by checking the consistency of weights in three directions. .. versionadded:: 1.14 @@ -1025,15 +1026,21 @@ def stripes_mask3d(weights, 3D weights array, a result of stripes_detect3d module given in [angles, detY(depth), detX] axis orientation. threshold : float, optional - Threshold for the given weights, the smaller values correspond to the stripes + Threshold for the given weights. This parameter defines what weights will be considered + as potential candidates for stripes. It is important to remove as many false outliers + as possible by taking the highest acceptable value. The lower value might lead to + ignoring the actual stripes and the higher can generate false alarms. The good range to try is (0.5-0.7). min_stripe_length : int, optional - Minimum accepted length of a stripe in pixels. Can be large if there are full stripes in the data. + Minimum length of a stripe in pixels with respect to the "angles" axis. If there are full stripes in the data, + then this could be >50% of the size of the the "angles" axis. min_stripe_depth : int, optional - Minimum accepted depth of a stripe in pixels. The stripes do not extend very deep, with this parameter more non-stripe features can be removed. + Minimum depth of a stripe in pixels with respect to the "detY" axis. The stripes do not extend very deep normally + in the data. By setting this parameter to the approximate depth of the stripe more false alarms can be removed. min_stripe_width : int, optional - Minimum accepted width of a stripe in pixels. The stripes can be merged together with this parameter. + Minimum width of a stripe in pixels with respect to the "detX" axis. The stripes that close to each other + can be merged together with this parameter. sensitivity_perc : float, optional - The value in percents to impose less strict conditions on length, depth and width of a stripe. + The value in percents to impose less strict conditions on length, depth and width parameters of a stripe. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used if unspecified. diff --git a/source/tomopy/util/extern/prep.py b/source/tomopy/util/extern/prep.py index ce2633d71..bdbe0a254 100644 --- a/source/tomopy/util/extern/prep.py +++ b/source/tomopy/util/extern/prep.py @@ -116,9 +116,9 @@ def c_stripes_detect3d( dtype.as_c_int(size), dtype.as_c_int(radius), dtype.as_c_int(ncore), - dtype.as_c_long(dx), - dtype.as_c_long(dy), - dtype.as_c_long(dz), + dtype.as_c_int(dx), + dtype.as_c_int(dy), + dtype.as_c_int(dz), ) return output @@ -145,8 +145,8 @@ def c_stripesmask3d( dtype.as_c_int(min_stripe_width), dtype.as_c_float(sensitivity_perc), dtype.as_c_int(ncore), - dtype.as_c_long(dx), - dtype.as_c_long(dy), - dtype.as_c_long(dz), + dtype.as_c_int(dx), + dtype.as_c_int(dy), + dtype.as_c_int(dz), ) return output From bb66655632105384d9d1fdfddabd08773ba41018 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Mon, 6 Mar 2023 14:32:44 +0000 Subject: [PATCH 20/27] Update source/tomopy/prep/stripe.py Co-authored-by: Daniel Ching --- source/tomopy/prep/stripe.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 730b49b24..efc439aa4 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -1054,6 +1054,7 @@ def stripes_mask3d(weights, ------ ValueError If the input array is not three dimensional. + If a min_stripe parameter is negative or longer than its corresponding dimension """ if ncore is None: From 2a36d1406a5144d39e54454f369da07be67f0c65 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Mon, 6 Mar 2023 14:36:14 +0000 Subject: [PATCH 21/27] raising errors for stripesmask3d --- source/tomopy/prep/stripe.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index efc439aa4..73c23de50 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -1054,7 +1054,10 @@ def stripes_mask3d(weights, ------ ValueError If the input array is not three dimensional. - If a min_stripe parameter is negative or longer than its corresponding dimension + If a min_stripe_length parameter is negative, zero or longer than its corresponding dimension ("angle") + If a min_stripe_depth parameter is negative or longer than its corresponding dimension ("detY") + If a min_stripe_width parameter is negative, zero or longer than its corresponding dimension ("detX") + If a sensitivity_perc parameter doesn't lie in the (0,100] range """ if ncore is None: From cf866970a54cefa1d76f630910f3e278c37858ff Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Mon, 20 Mar 2023 20:44:57 +0000 Subject: [PATCH 22/27] changing mask to boolean --- include/libtomo/stripe.h | 23 ++-- source/libtomo/prep/stripes_detect3d.c | 112 +++++++++--------- source/tomopy/prep/stripe.py | 16 +-- source/tomopy/util/dtype.py | 6 + source/tomopy/util/extern/prep.py | 2 +- test/test_tomopy/test_data/stripes_mask3d.npy | Bin 9128 -> 9128 bytes 6 files changed, 83 insertions(+), 76 deletions(-) diff --git a/include/libtomo/stripe.h b/include/libtomo/stripe.h index 3990e5550..4a7df0ae6 100644 --- a/include/libtomo/stripe.h +++ b/include/libtomo/stripe.h @@ -51,6 +51,13 @@ # define DLL #endif +#include +#include +#include +#include +#include +#include + DLL void remove_stripe_sf(float* data, int dx, int dy, int dz, int size, int istart, int iend); @@ -63,12 +70,12 @@ stripesdetect3d_main_float(float* Input, float* Output, DLL int stripesmask3d_main_float(float* Input, - unsigned char* Output, - float threshold_val, - int stripe_length_min, - int stripe_depth_min, - int stripe_width_min, - float sensitivity, - int ncores, - int dimX, int dimY, int dimZ); + bool* Output, + float threshold_val, + int stripe_length_min, + int stripe_depth_min, + int stripe_width_min, + float sensitivity, + int ncores, + int dimX, int dimY, int dimZ); \ No newline at end of file diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index 1638ad230..731a66b2d 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -44,12 +44,6 @@ // C-module for detecting and emphasising stripes present in the data (3D case) // Original author: Daniil Kazantsev, Diamond Light Source Ltd. -#include -#include -#include -#include -#include - #include "libtomo/stripe.h" /********************************************************************/ @@ -247,8 +241,8 @@ vertical_median_stride3d(float* input, float* output, void mean_stride3d(float* input, float* output, - long i, long j, long k, - long dimX, long dimY, long dimZ) + long i, long j, long k, + long dimX, long dimY, long dimZ) { /* a 3d mean to enusre a more stable gradient */ long i1; @@ -299,8 +293,8 @@ mean_stride3d(float* input, float* output, } void -remove_inconsistent_stripes(unsigned char* mask, - unsigned char* out, +remove_inconsistent_stripes(bool* mask, + bool* out, int stripe_length_min, int stripe_depth_min, float sensitivity, @@ -327,7 +321,7 @@ remove_inconsistent_stripes(unsigned char* mask, /* start by considering vertical features */ if (switch_dim == 0) { - if (mask[index] == 1) + if (mask[index] == true) { counter_vert_voxels = 0; for(k_m = -halfstripe_length; k_m <= halfstripe_length; k_m++) @@ -335,11 +329,11 @@ remove_inconsistent_stripes(unsigned char* mask, k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) k1 = k - k_m; - if (mask[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] == 1) + if (mask[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] == true) counter_vert_voxels++; } if (counter_vert_voxels < threshold_vertical) - out[index] = 0; + out[index] = false; } } else @@ -350,7 +344,7 @@ remove_inconsistent_stripes(unsigned char* mask, in the depth dimension compared to the features that belong to a sample. */ - if (mask[index] == 1) + if (mask[index] == true) { if (stripe_depth_min != 0) { @@ -360,11 +354,11 @@ remove_inconsistent_stripes(unsigned char* mask, y1 = j + y_m; if((y1 < 0) || (y1 >= dimY)) y1 = j - y_m; - if (mask[(size_t)(dimX * dimY * k) + (size_t)(y1 * dimX + i)] == 1) + if (mask[(size_t)(dimX * dimY * k) + (size_t)(y1 * dimX + i)] == true) counter_depth_voxels++; } if (counter_depth_voxels > threshold_depth) - out[index] = 0; + out[index] = false; } } } @@ -372,13 +366,13 @@ remove_inconsistent_stripes(unsigned char* mask, } void -remove_short_stripes(unsigned char* mask, - unsigned char* out, - int stripe_length_min, - long i, - long j, - long k, - long dimX, long dimY, long dimZ) +remove_short_stripes(bool* mask, + bool* out, + int stripe_length_min, + long i, + long j, + long k, + long dimX, long dimY, long dimZ) { int counter_vert_voxels; int halfstripe_length = (int)stripe_length_min/2; @@ -387,7 +381,7 @@ remove_short_stripes(unsigned char* mask, size_t index; index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); - if (mask[index] == 1) + if (mask[index] == true) { counter_vert_voxels = 0; for(k_m = -halfstripe_length; k_m <= halfstripe_length; k_m++) @@ -395,18 +389,18 @@ remove_short_stripes(unsigned char* mask, k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) k1 = k - k_m; - if (mask[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] == 1) + if (mask[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] == true) counter_vert_voxels++; } if (counter_vert_voxels < halfstripe_length) - out[index] = 0; + out[index] = false; } return; } void -merge_stripes(unsigned char* mask, - unsigned char* out, +merge_stripes(bool* mask, + bool* out, int stripe_length_min, int stripe_width_min, long i, @@ -429,70 +423,70 @@ merge_stripes(unsigned char* mask, size_t index; index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); - if (mask[index] == 0) + if (mask[index] == false) { - /* checking if there is a mask to the left of zero */ + /* checking if there is a mask to the left of False */ mask_left = 0; for(x = -halfstripe_width; x <=0; x++) { x_l = i + x; if (x_l < 0) x_l = i - x; - if (mask[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + x_l)] == 1) + if (mask[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + x_l)] == true) { mask_left = 1; break; } } - /* checking if there is a mask to the right of zero */ + /* checking if there is a mask to the right of False */ mask_right = 0; for(x = 0; x <= halfstripe_width; x++) { x_r = i + x; if (x_r >= dimX) x_r = i - x; - if (mask[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + x_r)] == 1) + if (mask[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + x_r)] == true) { mask_right = 1; break; } } - /* now if there is a mask from the left and from the right side of the zero value make it one */ + /* now if there is a mask from the left and from the right side of True value make it True */ if ((mask_left == 1) && (mask_right == 1)) - out[index] = 1; + out[index] = true; /* perform vertical merging */ - if (out[index] != 1) + if (out[index] == false) { - /* checking if there is a mask up of zero */ + /* checking if there is a mask up of True */ mask_up = 0; for(x = -vertical_length; x <=0; x++) { k_u = k + x; if (k_u < 0) k_u = k - x; - if (mask[(size_t)(dimX * dimY * k_u) + (size_t)(j * dimX + i)] == 1) + if (mask[(size_t)(dimX * dimY * k_u) + (size_t)(j * dimX + i)] == true) { mask_up = 1; break; } } - /* checking if there is a mask down of zero */ + /* checking if there is a mask down of False */ mask_down = 0; for(x = 0; x <= vertical_length; x++) { k_d = k + x; if (k_d >= dimZ) k_d = k - x; - if (mask[(size_t)(dimX * dimY * k_d) + (size_t)(j * dimX + i)] == 1) + if (mask[(size_t)(dimX * dimY * k_d) + (size_t)(j * dimX + i)] == true) { mask_down = 1; break; } } - /* now if there is a mask above and bellow of the zero value make it one */ + /* now if there is a mask above and bellow of the False make it True */ if ((mask_up == 1) && (mask_down == 1)) - out[index] = 1; + out[index] = true; } } return; @@ -503,10 +497,10 @@ merge_stripes(unsigned char* mask, /********************************************************************/ DLL int stripesdetect3d_main_float(float* Input, float* Output, - int window_halflength_vertical, - int ratio_radius, - int ncores, - int dimX, int dimY, int dimZ) + int window_halflength_vertical, + int ratio_radius, + int ncores, + int dimX, int dimY, int dimZ) { long i; long j; @@ -519,7 +513,7 @@ int stripesdetect3d_main_float(float* Input, float* Output, float* temp3d_arr; temp3d_arr = malloc(totalvoxels * sizeof(float)); - if (temp3d_arr == NULL) printf("Allocation of the 'temp3d_arr' array failed"); + if (temp3d_arr == NULL) printf("Memory allocation of the 'temp3d_arr' array in 'stripesdetect3d_main_float' failed"); /* dealing here with a custom given number of cpu threads */ if(ncores > 0) @@ -599,7 +593,7 @@ int stripesdetect3d_main_float(float* Input, float* Output, /********************************************************************/ DLL int stripesmask3d_main_float(float* Input, - unsigned char* Output, + bool* Output, float threshold_val, int stripe_length_min, int stripe_depth_min, @@ -616,9 +610,9 @@ int stripesmask3d_main_float(float* Input, size_t index; size_t totalvoxels = (long)(dimX)*(long)(dimY)*(long)(dimZ); - unsigned char* mask; - mask = malloc(totalvoxels * sizeof(unsigned char)); - if (mask == NULL) printf("Allocation of the 'mask' array failed"); + bool* mask; + mask = malloc(totalvoxels * sizeof(bool)); + if (mask == NULL) printf("Memory allocation of the 'mask' array in 'stripesmask3d_main_float' failed"); /* dealing here with a custom given number of cpu threads */ if(ncores > 0) @@ -642,15 +636,15 @@ int stripesmask3d_main_float(float* Input, { index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); if (Input[index] <= threshold_val) - mask[index] = 1; + mask[index] = true; else - mask[index] = 0; + mask[index] = false; } } } /* Copy mask to output */ - memcpy(Output, mask, totalvoxels * sizeof(unsigned char)); + memcpy(Output, mask, totalvoxels * sizeof(bool)); /* the depth consistency for features */ switch_dim = 1; @@ -673,7 +667,7 @@ int stripesmask3d_main_float(float* Input, } } /* Copy output to mask */ - memcpy(mask, Output, totalvoxels * sizeof(unsigned char)); + memcpy(mask, Output, totalvoxels * sizeof(bool)); /* Now we need to remove stripes that are shorter than "stripe_length_min" parameter @@ -683,7 +677,7 @@ int stripesmask3d_main_float(float* Input, belong to true data. */ -/*continue by including long vertical features and discarding shorter ones */ +/*continue by including longer vertical features and discarding the shorter ones */ switch_dim = 0; #pragma omp parallel for shared(mask, Output) private(i, j, k) for(k = 0; k < dimZ; k++) @@ -704,7 +698,7 @@ int stripesmask3d_main_float(float* Input, } } /* Copy output to mask */ - memcpy(mask, Output, totalvoxels * sizeof(unsigned char)); + memcpy(mask, Output, totalvoxels * sizeof(bool)); /* now we clean the obtained mask if the features do not hold our assumptions about the lengths */ @@ -725,7 +719,7 @@ int stripesmask3d_main_float(float* Input, } /* Copy output to mask */ - memcpy(mask, Output, totalvoxels * sizeof(unsigned char)); + memcpy(mask, Output, totalvoxels * sizeof(bool)); /* We can merge stripes together if they are relatively close to each other @@ -749,7 +743,7 @@ int stripesmask3d_main_float(float* Input, } } /* Copy output to mask */ - memcpy(mask, Output, totalvoxels * sizeof(unsigned char)); + memcpy(mask, Output, totalvoxels * sizeof(bool)); } free(mask); diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 73c23de50..0737921e8 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -958,9 +958,9 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): The projection data should be given with [angles, detY(depth), detX (horizontal)] axis orientation. With this orientation, the stripes are the vertical features. size : int, optional - The pixel size of the vertical (angle dimension) 1D median filter to minimise false detections. Increase it if you have longer or full stripes in the data. + The pixel size of the 1D median filter orthogonal to stripes orientation to minimise false detections. Increase it if you have longer or full stripes in the data. radius : int, optional - The pixel size of the 3D stencil to calculate the mean ratio between vertical (angular) and horizontal (detX) orientations of the detX gradient. The larger + The pixel size of the 3D stencil to calculate the mean ratio between the angular and detX orientations of the detX gradient. The larger values can affect the width of the detected stripe, use 1,2,3 values. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used @@ -1027,9 +1027,9 @@ def stripes_mask3d(weights, [angles, detY(depth), detX] axis orientation. threshold : float, optional Threshold for the given weights. This parameter defines what weights will be considered - as potential candidates for stripes. It is important to remove as many false outliers - as possible by taking the highest acceptable value. The lower value might lead to - ignoring the actual stripes and the higher can generate false alarms. The good range to try is (0.5-0.7). + as potential candidates for stripes. The lower value (< 0.5) will result in the most prominent stripes in the data. + Increase the threshold cautiously to avoid taking too many false alarms into account. The good range to + try is between 0.5 and 0.7. min_stripe_length : int, optional Minimum length of a stripe in pixels with respect to the "angles" axis. If there are full stripes in the data, then this could be >50% of the size of the the "angles" axis. @@ -1048,7 +1048,7 @@ def stripes_mask3d(weights, Returns ------- ndarray - A binary mask of uint8 data type with stripes highlighted. + A binary mask of bool data type with stripes highlighted as True values. Raises ------ @@ -1066,8 +1066,8 @@ def stripes_mask3d(weights, input_type = weights.dtype if (input_type != 'float32'): weights = dtype.as_float32(weights) # silent convertion to float32 data type - out = np.uint8(np.empty_like(weights, order='C')) - + out = np.zeros(np.shape(weights), dtype=bool, order='C') + if weights.ndim == 3: dz, dy, dx = weights.shape if (dz == 0) or (dy == 0) or (dx == 0): diff --git a/source/tomopy/util/dtype.py b/source/tomopy/util/dtype.py index a81de4403..f4de12ade 100644 --- a/source/tomopy/util/dtype.py +++ b/source/tomopy/util/dtype.py @@ -71,6 +71,8 @@ 'as_uint8', 'as_uint16', 'as_c_float_p', + 'as_c_bool_p', + 'as_c_uint8_p', 'as_c_uint16_p', 'as_c_int', 'as_c_int_p', @@ -117,6 +119,10 @@ def as_c_float_p(arr): return arr.ctypes.data_as(c_float_p) +def as_c_bool_p(arr): + c_bool_p = ctypes.POINTER(ctypes.c_bool) + return arr.ctypes.data_as(c_bool_p) + def as_c_uint8_p(arr): c_uint8_p = ctypes.POINTER(ctypes.c_uint8) return arr.ctypes.data_as(c_uint8_p) diff --git a/source/tomopy/util/extern/prep.py b/source/tomopy/util/extern/prep.py index bdbe0a254..82f3f13cb 100644 --- a/source/tomopy/util/extern/prep.py +++ b/source/tomopy/util/extern/prep.py @@ -138,7 +138,7 @@ def c_stripesmask3d( LIB_TOMOPY_PREP.stripesmask3d_main_float.restype = dtype.as_c_void_p() LIB_TOMOPY_PREP.stripesmask3d_main_float( dtype.as_c_float_p(input), - dtype.as_c_uint8_p(output), + dtype.as_c_bool_p(output), dtype.as_c_float(threshold_val), dtype.as_c_int(min_stripe_length), dtype.as_c_int(min_stripe_depth), diff --git a/test/test_tomopy/test_data/stripes_mask3d.npy b/test/test_tomopy/test_data/stripes_mask3d.npy index 00d8a56c386cfd8f8414703708efd566abf5a1b3..9f1882c66876d0d168fa3662fbc6b30c050f640f 100644 GIT binary patch delta 12 TcmZ4CzQTQi7-P~#@rlX+A7BJz delta 12 TcmZ4CzQTQi7-Q*1@rlX+AHM{5 From 411edfd9eb5a689fd7aa01b552122afba2a80841 Mon Sep 17 00:00:00 2001 From: Daniel Ching Date: Tue, 21 Mar 2023 17:14:59 -0500 Subject: [PATCH 23/27] STY: Use clang-format --- source/libtomo/prep/stripes_detect3d.c | 715 ++++++++++++------------- 1 file changed, 339 insertions(+), 376 deletions(-) diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index 731a66b2d..fea8e3b62 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -49,52 +49,60 @@ /********************************************************************/ /**********************Supporting Functions**************************/ /********************************************************************/ -/* Calculate the forward difference derrivative of the 3D input in the direction of the "axis" parameter -using the step_size in pixels to skip pixels (i.e. step_size = 1 is the classical gradient) +/* Calculate the forward difference derrivative of the 3D input in the +direction of the "axis" parameter using the step_size in pixels to skip pixels +(i.e. step_size = 1 is the classical gradient) axis = 0: horizontal direction axis = 1: depth direction axis = 2: vertical direction */ -void -gradient3D_local(float *input, float *output, long dimX, long dimY, long dimZ, int axis, int step_size) -{ - long i; - long j; - long k; - long i1; - long j1; - long k1; +void +gradient3D_local(float* input, float* output, long dimX, long dimY, long dimZ, int axis, + int step_size) +{ + long i; + long j; + long k; + long i1; + long j1; + long k1; size_t index; - -#pragma omp parallel for shared(input, output) private(i,j,k,i1,j1,k1,index) - for(j=0; j= dimX) + i1 = i + step_size; + if(i1 >= dimX) i1 = i - step_size; - output[index] = input[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + i1)] - input[index]; + output[index] = + input[(size_t) (dimX * dimY * k) + (size_t) (j * dimX + i1)] - + input[index]; } - else if (axis == 1) + else if(axis == 1) { - j1 = j + step_size; - if (j1 >= dimY) + j1 = j + step_size; + if(j1 >= dimY) j1 = j - step_size; - output[index] = input[(size_t)(dimX * dimY * k) + (size_t)(j1 * dimX + i)] - input[index]; + output[index] = + input[(size_t) (dimX * dimY * k) + (size_t) (j1 * dimX + i)] - + input[index]; } - else + else { - k1 = k + step_size; - if (k1 >= dimZ) - k1 = k-step_size; - output[index] = input[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] - input[index]; + k1 = k + step_size; + if(k1 >= dimZ) + k1 = k - step_size; + output[index] = + input[(size_t) (dimX * dimY * k1) + (size_t) (j * dimX + i)] - + input[index]; } } } @@ -103,27 +111,25 @@ gradient3D_local(float *input, float *output, long dimX, long dimY, long dimZ, i } void -ratio_mean_stride3d(float* input, float* output, - int radius, - long i, long j, long k, +ratio_mean_stride3d(float* input, float* output, int radius, long i, long j, long k, long dimX, long dimY, long dimZ) { - float mean_plate; - float mean_horiz; - float mean_horiz2; - float min_val; - int diameter = 2*radius + 1; - int all_pixels_window = diameter*diameter; - long i_m; - long j_m; - long k_m; - long i1; - long j1; - long k1; - size_t index; - size_t newindex; - - index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); + float mean_plate; + float mean_horiz; + float mean_horiz2; + float min_val; + int diameter = 2 * radius + 1; + int all_pixels_window = diameter * diameter; + long i_m; + long j_m; + long k_m; + long i1; + long j1; + long k1; + size_t index; + size_t newindex; + + index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); min_val = 0.0f; /* calculate mean of gradientX in a 2D plate parallel to stripes direction */ @@ -131,19 +137,19 @@ ratio_mean_stride3d(float* input, float* output, for(j_m = -radius; j_m <= radius; j_m++) { j1 = j + j_m; - if ((j1 < 0) || (j1 >= dimY)) + if((j1 < 0) || (j1 >= dimY)) j1 = j - j_m; for(k_m = -radius; k_m <= radius; k_m++) { k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) - k1 = k - k_m; - newindex = (size_t)(dimX * dimY * k1) + (size_t)(j1 * dimX + i); + k1 = k - k_m; + newindex = (size_t) (dimX * dimY * k1) + (size_t) (j1 * dimX + i); mean_plate += fabsf(input[newindex]); } } - mean_plate /= (float)(all_pixels_window); - + mean_plate /= (float) (all_pixels_window); + /* calculate mean of gradientX in a 2D plate orthogonal to stripes direction */ mean_horiz = 0.0f; for(j_m = -1; j_m <= 1; j_m++) @@ -154,14 +160,14 @@ ratio_mean_stride3d(float* input, float* output, for(i_m = 1; i_m <= radius; i_m++) { i1 = i + i_m; - if (i1 >= dimX) + if(i1 >= dimX) i1 = i - i_m; - newindex = (size_t)(dimX * dimY * k) + (size_t)(j1 * dimX + i1); + newindex = (size_t) (dimX * dimY * k) + (size_t) (j1 * dimX + i1); mean_horiz += fabsf(input[newindex]); } } - mean_horiz /= (float)(radius*3); - + mean_horiz /= (float) (radius * 3); + /* Calculate another mean symmetrically */ mean_horiz2 = 0.0f; for(j_m = -1; j_m <= 1; j_m++) @@ -172,94 +178,91 @@ ratio_mean_stride3d(float* input, float* output, for(i_m = -radius; i_m <= -1; i_m++) { i1 = i + i_m; - if (i1 < 0) + if(i1 < 0) i1 = i - i_m; - newindex = (size_t)(dimX * dimY * k) + (size_t)(j1 * dimX + i1); + newindex = (size_t) (dimX * dimY * k) + (size_t) (j1 * dimX + i1); mean_horiz2 += fabsf(input[newindex]); } } - mean_horiz2 /= (float)(radius*3); + mean_horiz2 /= (float) (radius * 3); - /* calculate the ratio between two means assuming that the mean - orthogonal to stripes direction should be larger than the mean + /* calculate the ratio between two means assuming that the mean + orthogonal to stripes direction should be larger than the mean parallel to it */ - if ((mean_horiz >= mean_plate) && (mean_horiz != 0.0f)) - output[index] = mean_plate/mean_horiz; - if ((mean_horiz < mean_plate) && (mean_plate != 0.0f)) - output[index] = mean_horiz/mean_plate; - if ((mean_horiz2 >= mean_plate) && (mean_horiz2 != 0.0f)) - min_val = mean_plate/mean_horiz2; - if ((mean_horiz2 < mean_plate) && (mean_plate != 0.0f)) - min_val = mean_horiz2/mean_plate; + if((mean_horiz >= mean_plate) && (mean_horiz != 0.0f)) + output[index] = mean_plate / mean_horiz; + if((mean_horiz < mean_plate) && (mean_plate != 0.0f)) + output[index] = mean_horiz / mean_plate; + if((mean_horiz2 >= mean_plate) && (mean_horiz2 != 0.0f)) + min_val = mean_plate / mean_horiz2; + if((mean_horiz2 < mean_plate) && (mean_plate != 0.0f)) + min_val = mean_horiz2 / mean_plate; /* accepting the smallest value */ - if (output[index] > min_val) + if(output[index] > min_val) output[index] = min_val; return; } -int floatcomp(const void* elem1, const void* elem2) +int +floatcomp(const void* elem1, const void* elem2) { - if(*(const float*)elem1 < *(const float*)elem2) + if(*(const float*) elem1 < *(const float*) elem2) return -1; - return *(const float*)elem1 > *(const float*)elem2; + return *(const float*) elem1 > *(const float*) elem2; } void -vertical_median_stride3d(float* input, float* output, - int window_halflength_vertical, - int window_fulllength, - int midval_window_index, - long i, long j, long k, - long dimX, long dimY, long dimZ) +vertical_median_stride3d(float* input, float* output, int window_halflength_vertical, + int window_fulllength, int midval_window_index, long i, long j, + long k, long dimX, long dimY, long dimZ) { - int counter; - long k_m; - long k1; - size_t index; - - index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); - - float* _values; - _values = (float*) calloc(window_fulllength, sizeof(float)); - + int counter; + long k_m; + long k1; + size_t index; + + index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); + + float* _values; + _values = (float*) calloc(window_fulllength, sizeof(float)); + counter = 0; for(k_m = -window_halflength_vertical; k_m <= window_halflength_vertical; k_m++) { k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) - k1 = k-k_m; - _values[counter] = input[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)]; + k1 = k - k_m; + _values[counter] = input[(size_t) (dimX * dimY * k1) + (size_t) (j * dimX + i)]; counter++; - } + } qsort(_values, window_fulllength, sizeof(float), floatcomp); output[index] = _values[midval_window_index]; - free (_values); + free(_values); return; } void -mean_stride3d(float* input, float* output, - long i, long j, long k, - long dimX, long dimY, long dimZ) +mean_stride3d(float* input, float* output, long i, long j, long k, long dimX, long dimY, + long dimZ) { /* a 3d mean to enusre a more stable gradient */ - long i1; - long i2; - long j1; - long j2; - long k1; - long k2; - float val1; - float val2; - float val3; - float val4; - float val5; - float val6; - size_t index; - - index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); + long i1; + long i2; + long j1; + long j2; + long k1; + long k2; + float val1; + float val2; + float val3; + float val4; + float val5; + float val6; + size_t index; + + index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); i1 = i - 1; i2 = i + 1; @@ -268,60 +271,53 @@ mean_stride3d(float* input, float* output, k1 = k - 1; k2 = k + 1; - if (i1 < 0) + if(i1 < 0) i1 = i2; - if (i2 >= dimX) + if(i2 >= dimX) i2 = i1; - if (j1 < 0) + if(j1 < 0) j1 = j2; - if (j2 >= dimY) - j2 = j1; - if (k1 < 0) + if(j2 >= dimY) + j2 = j1; + if(k1 < 0) k1 = k2; - if (k2 >= dimZ) + if(k2 >= dimZ) k2 = k1; - val1 = input[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + i1)]; - val2 = input[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + i2)]; - val3 = input[(size_t)(dimX * dimY * k) + (size_t)(j1 * dimX + i)]; - val4 = input[(size_t)(dimX * dimY * k) + (size_t)(j2 * dimX + i)]; - val5 = input[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)]; - val6 = input[(size_t)(dimX * dimY * k2) + (size_t)(j * dimX + i)]; - - output[index] = 0.1428f*(input[index] + val1 + val2 + val3 + val4 + val5 + val6); + val1 = input[(size_t) (dimX * dimY * k) + (size_t) (j * dimX + i1)]; + val2 = input[(size_t) (dimX * dimY * k) + (size_t) (j * dimX + i2)]; + val3 = input[(size_t) (dimX * dimY * k) + (size_t) (j1 * dimX + i)]; + val4 = input[(size_t) (dimX * dimY * k) + (size_t) (j2 * dimX + i)]; + val5 = input[(size_t) (dimX * dimY * k1) + (size_t) (j * dimX + i)]; + val6 = input[(size_t) (dimX * dimY * k2) + (size_t) (j * dimX + i)]; + + output[index] = 0.1428f * (input[index] + val1 + val2 + val3 + val4 + val5 + val6); return; } void -remove_inconsistent_stripes(bool* mask, - bool* out, - int stripe_length_min, - int stripe_depth_min, - float sensitivity, - int switch_dim, - long i, - long j, - long k, - long dimX, long dimY, long dimZ) +remove_inconsistent_stripes(bool* mask, bool* out, int stripe_length_min, + int stripe_depth_min, float sensitivity, int switch_dim, + long i, long j, long k, long dimX, long dimY, long dimZ) { - int counter_vert_voxels; - int counter_depth_voxels; - int halfstripe_length = (int)stripe_length_min/2; - int halfstripe_depth = (int)stripe_depth_min/2; - long k_m; - long k1; - long y_m; - long y1; - size_t index; - index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); - - int threshold_vertical = (int)((0.01f*sensitivity)*stripe_length_min); - int threshold_depth = (int)((0.01f*sensitivity)*stripe_depth_min); + int counter_vert_voxels; + int counter_depth_voxels; + int halfstripe_length = (int) stripe_length_min / 2; + int halfstripe_depth = (int) stripe_depth_min / 2; + long k_m; + long k1; + long y_m; + long y1; + size_t index; + index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); + + int threshold_vertical = (int) ((0.01f * sensitivity) * stripe_length_min); + int threshold_depth = (int) ((0.01f * sensitivity) * stripe_depth_min); /* start by considering vertical features */ - if (switch_dim == 0) + if(switch_dim == 0) { - if (mask[index] == true) + if(mask[index] == true) { counter_vert_voxels = 0; for(k_m = -halfstripe_length; k_m <= halfstripe_length; k_m++) @@ -329,36 +325,37 @@ remove_inconsistent_stripes(bool* mask, k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) k1 = k - k_m; - if (mask[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] == true) + if(mask[(size_t) (dimX * dimY * k1) + (size_t) (j * dimX + i)] == true) counter_vert_voxels++; - } - if (counter_vert_voxels < threshold_vertical) + } + if(counter_vert_voxels < threshold_vertical) out[index] = false; } } - else + else { - /* - Considering the depth of features an removing the deep ones - Here we assume that the stripes do not normally extend far - in the depth dimension compared to the features that belong to a - sample. - */ - if (mask[index] == true) + /* + Considering the depth of features an removing the deep ones + Here we assume that the stripes do not normally extend far + in the depth dimension compared to the features that belong to a + sample. + */ + if(mask[index] == true) { - if (stripe_depth_min != 0) - { - counter_depth_voxels = 0; - for(y_m = -halfstripe_depth; y_m <= halfstripe_depth; y_m++) + if(stripe_depth_min != 0) { - y1 = j + y_m; - if((y1 < 0) || (y1 >= dimY)) - y1 = j - y_m; - if (mask[(size_t)(dimX * dimY * k) + (size_t)(y1 * dimX + i)] == true) - counter_depth_voxels++; - } - if (counter_depth_voxels > threshold_depth) - out[index] = false; + counter_depth_voxels = 0; + for(y_m = -halfstripe_depth; y_m <= halfstripe_depth; y_m++) + { + y1 = j + y_m; + if((y1 < 0) || (y1 >= dimY)) + y1 = j - y_m; + if(mask[(size_t) (dimX * dimY * k) + (size_t) (y1 * dimX + i)] == + true) + counter_depth_voxels++; + } + if(counter_depth_voxels > threshold_depth) + out[index] = false; } } } @@ -366,22 +363,17 @@ remove_inconsistent_stripes(bool* mask, } void -remove_short_stripes(bool* mask, - bool* out, - int stripe_length_min, - long i, - long j, - long k, - long dimX, long dimY, long dimZ) +remove_short_stripes(bool* mask, bool* out, int stripe_length_min, long i, long j, long k, + long dimX, long dimY, long dimZ) { - int counter_vert_voxels; - int halfstripe_length = (int)stripe_length_min/2; - long k_m; - long k1; - size_t index; - index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); - - if (mask[index] == true) + int counter_vert_voxels; + int halfstripe_length = (int) stripe_length_min / 2; + long k_m; + long k1; + size_t index; + index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); + + if(mask[index] == true) { counter_vert_voxels = 0; for(k_m = -halfstripe_length; k_m <= halfstripe_length; k_m++) @@ -389,50 +381,44 @@ remove_short_stripes(bool* mask, k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) k1 = k - k_m; - if (mask[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] == true) + if(mask[(size_t) (dimX * dimY * k1) + (size_t) (j * dimX + i)] == true) counter_vert_voxels++; - } - if (counter_vert_voxels < halfstripe_length) + } + if(counter_vert_voxels < halfstripe_length) out[index] = false; } return; } void -merge_stripes(bool* mask, - bool* out, - int stripe_length_min, - int stripe_width_min, - long i, - long j, - long k, - long dimX, long dimY, long dimZ) -{ - int halfstripe_width = (int)stripe_width_min/2; - int vertical_length = 2*stripe_width_min; - - long x; - long x_l; - long x_r; - long k_u; - long k_d; - int mask_left; - int mask_right; - int mask_up; - int mask_down; - size_t index; - index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); - - if (mask[index] == false) - { +merge_stripes(bool* mask, bool* out, int stripe_length_min, int stripe_width_min, long i, + long j, long k, long dimX, long dimY, long dimZ) +{ + int halfstripe_width = (int) stripe_width_min / 2; + int vertical_length = 2 * stripe_width_min; + + long x; + long x_l; + long x_r; + long k_u; + long k_d; + int mask_left; + int mask_right; + int mask_up; + int mask_down; + size_t index; + index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); + + if(mask[index] == false) + { /* checking if there is a mask to the left of False */ mask_left = 0; - for(x = -halfstripe_width; x <=0; x++) + for(x = -halfstripe_width; x <= 0; x++) { x_l = i + x; - if (x_l < 0) + if(x_l < 0) x_l = i - x; - if (mask[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + x_l)] == true) + if(mask[(size_t) (dimX * dimY * k) + (size_t) (j * dimX + x_l)] == true) { mask_left = 1; break; @@ -440,32 +426,33 @@ merge_stripes(bool* mask, } /* checking if there is a mask to the right of False */ mask_right = 0; - for(x = 0; x <= halfstripe_width; x++) + for(x = 0; x <= halfstripe_width; x++) { x_r = i + x; - if (x_r >= dimX) + if(x_r >= dimX) x_r = i - x; - if (mask[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + x_r)] == true) + if(mask[(size_t) (dimX * dimY * k) + (size_t) (j * dimX + x_r)] == true) { mask_right = 1; break; } } - /* now if there is a mask from the left and from the right side of True value make it True */ - if ((mask_left == 1) && (mask_right == 1)) + /* now if there is a mask from the left and from the right side of True value make + * it True */ + if((mask_left == 1) && (mask_right == 1)) out[index] = true; /* perform vertical merging */ - if (out[index] == false) + if(out[index] == false) { /* checking if there is a mask up of True */ mask_up = 0; - for(x = -vertical_length; x <=0; x++) + for(x = -vertical_length; x <= 0; x++) { k_u = k + x; - if (k_u < 0) + if(k_u < 0) k_u = k - x; - if (mask[(size_t)(dimX * dimY * k_u) + (size_t)(j * dimX + i)] == true) + if(mask[(size_t) (dimX * dimY * k_u) + (size_t) (j * dimX + i)] == true) { mask_up = 1; break; @@ -473,19 +460,19 @@ merge_stripes(bool* mask, } /* checking if there is a mask down of False */ mask_down = 0; - for(x = 0; x <= vertical_length; x++) + for(x = 0; x <= vertical_length; x++) { k_d = k + x; - if (k_d >= dimZ) + if(k_d >= dimZ) k_d = k - x; - if (mask[(size_t)(dimX * dimY * k_d) + (size_t)(j * dimX + i)] == true) + if(mask[(size_t) (dimX * dimY * k_d) + (size_t) (j * dimX + i)] == true) { mask_down = 1; break; } } - /* now if there is a mask above and bellow of the False make it True */ - if ((mask_up == 1) && (mask_down == 1)) + /* now if there is a mask above and bellow of the False make it True */ + if((mask_up == 1) && (mask_down == 1)) out[index] = true; } } @@ -495,26 +482,25 @@ merge_stripes(bool* mask, /********************************************************************/ /*************************stripesdetect3d****************************/ /********************************************************************/ -DLL -int stripesdetect3d_main_float(float* Input, float* Output, - int window_halflength_vertical, - int ratio_radius, - int ncores, - int dimX, int dimY, int dimZ) +DLL int +stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, + int ratio_radius, int ncores, int dimX, int dimY, int dimZ) { long i; long j; long k; - long long totalvoxels; - totalvoxels = (long long)((long)(dimX)*(long)(dimY)*(long)(dimZ)); + long long totalvoxels; + totalvoxels = (long long) ((long) (dimX) * (long) (dimY) * (long) (dimZ)); + + int window_fulllength = (int) (2 * window_halflength_vertical + 1); + int midval_window_index = (int) (0.5f * window_fulllength) - 1; - int window_fulllength = (int)(2*window_halflength_vertical + 1); - int midval_window_index = (int)(0.5f*window_fulllength) - 1; - float* temp3d_arr; temp3d_arr = malloc(totalvoxels * sizeof(float)); - if (temp3d_arr == NULL) printf("Memory allocation of the 'temp3d_arr' array in 'stripesdetect3d_main_float' failed"); - + if(temp3d_arr == NULL) + printf("Memory allocation of the 'temp3d_arr' array in " + "'stripesdetect3d_main_float' failed"); + /* dealing here with a custom given number of cpu threads */ if(ncores > 0) { @@ -524,65 +510,60 @@ int stripesdetect3d_main_float(float* Input, float* Output, omp_set_num_threads(ncores); } -/* Perform a gentle (6-stencil) 3d mean smoothing of the data to ensure more stability in the gradient calculation */ +/* Perform a gentle (6-stencil) 3d mean smoothing of the data to ensure more stability in + * the gradient calculation */ #pragma omp parallel for shared(temp3d_arr) private(i, j, k) - for(k = 0; k < dimZ; k++) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) { - for(j = 0; j < dimY; j++) + for(i = 0; i < dimX; i++) { - for(i = 0; i < dimX; i++) - { - mean_stride3d(Input, temp3d_arr, - i, j, k, - dimX, dimY, dimZ); - } + mean_stride3d(Input, temp3d_arr, i, j, k, dimX, dimY, dimZ); } } + } /* Take the gradient in the horizontal direction, axis = 0, step = 2*/ gradient3D_local(Input, Output, dimX, dimY, dimZ, 0, 2); - + /* - Here we calculate a ratio between the mean in a small 2D neighbourhood parallel to the stripe - and the mean orthogonal to the stripe. The gradient variation in the direction orthogonal to the - stripe is expected to be large (a jump), while in parallel direction small. Therefore at the edges - of a stripe we should get a ratio small/large or large/small. + Here we calculate a ratio between the mean in a small 2D neighbourhood parallel to the + stripe and the mean orthogonal to the stripe. The gradient variation in the direction + orthogonal to the stripe is expected to be large (a jump), while in parallel direction + small. Therefore at the edges of a stripe we should get a ratio small/large or + large/small. */ #pragma omp parallel for shared(Output, temp3d_arr) private(i, j, k) - for(k = 0; k < dimZ; k++) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) { - for(j = 0; j < dimY; j++) + for(i = 0; i < dimX; i++) { - for(i = 0; i < dimX; i++) - { - ratio_mean_stride3d(Output, temp3d_arr, - ratio_radius, - i, j, k, - dimX, dimY, dimZ); - } + ratio_mean_stride3d(Output, temp3d_arr, ratio_radius, i, j, k, dimX, dimY, + dimZ); } } - - /* - We process the resulting ratio map with a vertical median filter which removes + } + + /* + We process the resulting ratio map with a vertical median filter which removes inconsistent from longer stripes features */ #pragma omp parallel for shared(temp3d_arr, Output) private(i, j, k) - for(k = 0; k < dimZ; k++) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) { - for(j = 0; j < dimY; j++) + for(i = 0; i < dimX; i++) { - for(i = 0; i < dimX; i++) - { - vertical_median_stride3d(temp3d_arr, Output, - window_halflength_vertical, - window_fulllength, - midval_window_index, - i, j, k, - dimX, dimY, dimZ); - } + vertical_median_stride3d(temp3d_arr, Output, window_halflength_vertical, + window_fulllength, midval_window_index, i, j, k, + dimX, dimY, dimZ); } } + } free(temp3d_arr); return 0; @@ -591,28 +572,25 @@ int stripesdetect3d_main_float(float* Input, float* Output, /********************************************************************/ /*************************stripesmask3d******************************/ /********************************************************************/ -DLL -int stripesmask3d_main_float(float* Input, - bool* Output, - float threshold_val, - int stripe_length_min, - int stripe_depth_min, - int stripe_width_min, - float sensitivity, - int ncores, - int dimX, int dimY, int dimZ) +DLL int +stripesmask3d_main_float(float* Input, bool* Output, float threshold_val, + int stripe_length_min, int stripe_depth_min, + int stripe_width_min, float sensitivity, int ncores, int dimX, + int dimY, int dimZ) { - long i; - long j; - long k; - int iter_merge; - int switch_dim; + long i; + long j; + long k; + int iter_merge; + int switch_dim; size_t index; - size_t totalvoxels = (long)(dimX)*(long)(dimY)*(long)(dimZ); + size_t totalvoxels = (long) (dimX) * (long) (dimY) * (long) (dimZ); - bool* mask; + bool* mask; mask = malloc(totalvoxels * sizeof(bool)); - if (mask == NULL) printf("Memory allocation of the 'mask' array in 'stripesmask3d_main_float' failed"); + if(mask == NULL) + printf( + "Memory allocation of the 'mask' array in 'stripesmask3d_main_float' failed"); /* dealing here with a custom given number of cpu threads */ if(ncores > 0) @@ -623,107 +601,95 @@ int stripesmask3d_main_float(float* Input, omp_set_num_threads(ncores); } - /* - First step is to mask all the values in the given weights input image - that are bellow a given "threshold_val" parameter + /* + First step is to mask all the values in the given weights input image + that are bellow a given "threshold_val" parameter */ #pragma omp parallel for shared(Input, mask) private(i, j, k, index) - for(k = 0; k < dimZ; k++) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) { - for(j = 0; j < dimY; j++) + for(i = 0; i < dimX; i++) { - for(i = 0; i < dimX; i++) - { - index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); - if (Input[index] <= threshold_val) - mask[index] = true; - else - mask[index] = false; - } + index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); + if(Input[index] <= threshold_val) + mask[index] = true; + else + mask[index] = false; } } - + } + /* Copy mask to output */ memcpy(Output, mask, totalvoxels * sizeof(bool)); - + /* the depth consistency for features */ - switch_dim = 1; + switch_dim = 1; #pragma omp parallel for shared(mask, Output) private(i, j, k) - for(k = 0; k < dimZ; k++) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) { - for(j = 0; j < dimY; j++) + for(i = 0; i < dimX; i++) { - for(i = 0; i < dimX; i++) - { - - remove_inconsistent_stripes(mask, Output, - stripe_length_min, - stripe_depth_min, - sensitivity, - switch_dim, - i, j, k, - dimX, dimY, dimZ); - } + remove_inconsistent_stripes(mask, Output, stripe_length_min, + stripe_depth_min, sensitivity, switch_dim, i, + j, k, dimX, dimY, dimZ); } } - /* Copy output to mask */ + } + /* Copy output to mask */ memcpy(mask, Output, totalvoxels * sizeof(bool)); - /* + /* Now we need to remove stripes that are shorter than "stripe_length_min" parameter - or inconsistent otherwise. For every pixel we will run a 1D vertical window to count - nonzero values in the mask. We also check for the depth of the mask's value, - assuming that the stripes are normally shorter in depth compare to the features that + or inconsistent otherwise. For every pixel we will run a 1D vertical window to count + nonzero values in the mask. We also check for the depth of the mask's value, + assuming that the stripes are normally shorter in depth compare to the features that belong to true data. */ -/*continue by including longer vertical features and discarding the shorter ones */ - switch_dim = 0; + /*continue by including longer vertical features and discarding the shorter ones */ + switch_dim = 0; #pragma omp parallel for shared(mask, Output) private(i, j, k) - for(k = 0; k < dimZ; k++) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) { - for(j = 0; j < dimY; j++) + for(i = 0; i < dimX; i++) { - for(i = 0; i < dimX; i++) - { - - remove_inconsistent_stripes(mask, Output, - stripe_length_min, - stripe_depth_min, - sensitivity, - switch_dim, - i, j, k, - dimX, dimY, dimZ); - } + remove_inconsistent_stripes(mask, Output, stripe_length_min, + stripe_depth_min, sensitivity, switch_dim, i, + j, k, dimX, dimY, dimZ); } } + } /* Copy output to mask */ memcpy(mask, Output, totalvoxels * sizeof(bool)); - /* now we clean the obtained mask if the features do not hold our assumptions about the lengths */ + /* now we clean the obtained mask if the features do not hold our assumptions about + * the lengths */ #pragma omp parallel for shared(mask, Output) private(i, j, k) - for(k = 0; k < dimZ; k++) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) { - for(j = 0; j < dimY; j++) + for(i = 0; i < dimX; i++) { - for(i = 0; i < dimX; i++) - { - - remove_short_stripes(mask, Output, - stripe_length_min, - i, j, k, - dimX, dimY, dimZ); - } + remove_short_stripes(mask, Output, stripe_length_min, i, j, k, dimX, dimY, + dimZ); } } + } /* Copy output to mask */ memcpy(mask, Output, totalvoxels * sizeof(bool)); - /* + /* We can merge stripes together if they are relatively close to each other - horizontally and vertically. We do that iteratively. + horizontally and vertically. We do that iteratively. */ for(iter_merge = 0; iter_merge < stripe_width_min; iter_merge++) { @@ -734,18 +700,15 @@ int stripesmask3d_main_float(float* Input, { for(i = 0; i < dimX; i++) { - merge_stripes(mask, Output, - stripe_length_min, - stripe_width_min, - i, j, k, - dimX, dimY, dimZ); + merge_stripes(mask, Output, stripe_length_min, stripe_width_min, i, j, + k, dimX, dimY, dimZ); } } - } - /* Copy output to mask */ - memcpy(mask, Output, totalvoxels * sizeof(bool)); + } + /* Copy output to mask */ + memcpy(mask, Output, totalvoxels * sizeof(bool)); } free(mask); return 0; -} \ No newline at end of file +} From ab29ec91e342e9f2e70c887acfe975dfc6abd1b3 Mon Sep 17 00:00:00 2001 From: Daniel Ching Date: Tue, 21 Mar 2023 17:29:19 -0500 Subject: [PATCH 24/27] STY: Apply clang-tidy fixes --- include/libtomo/stripe.h | 26 ++------- source/libtomo/prep/CMakeLists.txt | 4 ++ source/libtomo/prep/stripes_detect3d.c | 81 ++++++++++++++------------ 3 files changed, 55 insertions(+), 56 deletions(-) diff --git a/include/libtomo/stripe.h b/include/libtomo/stripe.h index 4a7df0ae6..4642388c6 100644 --- a/include/libtomo/stripe.h +++ b/include/libtomo/stripe.h @@ -51,31 +51,17 @@ # define DLL #endif -#include -#include -#include -#include -#include #include DLL void remove_stripe_sf(float* data, int dx, int dy, int dz, int size, int istart, int iend); DLL int -stripesdetect3d_main_float(float* Input, float* Output, - int window_halflength_vertical, - int ratio_radius, - int ncores, - int dimX, int dimY, int dimZ); +stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, + int ratio_radius, int ncores, int dimX, int dimY, int dimZ); DLL int -stripesmask3d_main_float(float* Input, - bool* Output, - float threshold_val, - int stripe_length_min, - int stripe_depth_min, - int stripe_width_min, - float sensitivity, - int ncores, - int dimX, int dimY, int dimZ); - \ No newline at end of file +stripesmask3d_main_float(float* Input, bool* Output, float threshold_val, + int stripe_length_min, int stripe_depth_min, + int stripe_width_min, float sensitivity, int ncores, int dimX, + int dimY, int dimZ); diff --git a/source/libtomo/prep/CMakeLists.txt b/source/libtomo/prep/CMakeLists.txt index cead98dc5..2148ffd6b 100755 --- a/source/libtomo/prep/CMakeLists.txt +++ b/source/libtomo/prep/CMakeLists.txt @@ -27,6 +27,10 @@ target_compile_definitions(tomo-prep PRIVATE ${${PROJECT_NAME}_DEFINITIONS}) target_compile_options( tomo-prep PRIVATE $<$:${${PROJECT_NAME}_C_FLAGS}>) +set_target_properties(tomo-prep PROPERTIES + C_STANDARD 99 # C99 or later requried for boolean type support + C_STANDARD_REQUIRED ON) + install(TARGETS tomo-prep EXPORT libtomoTargets) install( diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index fea8e3b62..b51ec8380 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -44,6 +44,13 @@ // C-module for detecting and emphasising stripes present in the data (3D case) // Original author: Daniil Kazantsev, Diamond Light Source Ltd. +#include +#include +#include +#include +#include +#include + #include "libtomo/stripe.h" /********************************************************************/ @@ -57,8 +64,8 @@ axis = 1: depth direction axis = 2: vertical direction */ void -gradient3D_local(float* input, float* output, long dimX, long dimY, long dimZ, int axis, - int step_size) +gradient3D_local(const float* input, float* output, long dimX, long dimY, long dimZ, + int axis, int step_size) { long i; long j; @@ -107,7 +114,6 @@ gradient3D_local(float* input, float* output, long dimX, long dimY, long dimZ, i } } } - return; } void @@ -131,9 +137,9 @@ ratio_mean_stride3d(float* input, float* output, int radius, long i, long j, lon index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); - min_val = 0.0f; + min_val = 0.0F; /* calculate mean of gradientX in a 2D plate parallel to stripes direction */ - mean_plate = 0.0f; + mean_plate = 0.0F; for(j_m = -radius; j_m <= radius; j_m++) { j1 = j + j_m; @@ -151,7 +157,7 @@ ratio_mean_stride3d(float* input, float* output, int radius, long i, long j, lon mean_plate /= (float) (all_pixels_window); /* calculate mean of gradientX in a 2D plate orthogonal to stripes direction */ - mean_horiz = 0.0f; + mean_horiz = 0.0F; for(j_m = -1; j_m <= 1; j_m++) { j1 = j + j_m; @@ -169,7 +175,7 @@ ratio_mean_stride3d(float* input, float* output, int radius, long i, long j, lon mean_horiz /= (float) (radius * 3); /* Calculate another mean symmetrically */ - mean_horiz2 = 0.0f; + mean_horiz2 = 0.0F; for(j_m = -1; j_m <= 1; j_m++) { j1 = j + j_m; @@ -189,19 +195,18 @@ ratio_mean_stride3d(float* input, float* output, int radius, long i, long j, lon /* calculate the ratio between two means assuming that the mean orthogonal to stripes direction should be larger than the mean parallel to it */ - if((mean_horiz >= mean_plate) && (mean_horiz != 0.0f)) + if((mean_horiz >= mean_plate) && (mean_horiz != 0.0F)) output[index] = mean_plate / mean_horiz; - if((mean_horiz < mean_plate) && (mean_plate != 0.0f)) + if((mean_horiz < mean_plate) && (mean_plate != 0.0F)) output[index] = mean_horiz / mean_plate; - if((mean_horiz2 >= mean_plate) && (mean_horiz2 != 0.0f)) + if((mean_horiz2 >= mean_plate) && (mean_horiz2 != 0.0F)) min_val = mean_plate / mean_horiz2; - if((mean_horiz2 < mean_plate) && (mean_plate != 0.0f)) + if((mean_horiz2 < mean_plate) && (mean_plate != 0.0F)) min_val = mean_horiz2 / mean_plate; /* accepting the smallest value */ if(output[index] > min_val) output[index] = min_val; - return; } int @@ -213,9 +218,10 @@ floatcomp(const void* elem1, const void* elem2) } void -vertical_median_stride3d(float* input, float* output, int window_halflength_vertical, - int window_fulllength, int midval_window_index, long i, long j, - long k, long dimX, long dimY, long dimZ) +vertical_median_stride3d(const float* input, float* output, + int window_halflength_vertical, int window_fulllength, + int midval_window_index, long i, long j, long k, long dimX, + long dimY, long dimZ) { int counter; long k_m; @@ -240,12 +246,11 @@ vertical_median_stride3d(float* input, float* output, int window_halflength_vert output[index] = _values[midval_window_index]; free(_values); - return; } void -mean_stride3d(float* input, float* output, long i, long j, long k, long dimX, long dimY, - long dimZ) +mean_stride3d(const float* input, float* output, long i, long j, long k, long dimX, + long dimY, long dimZ) { /* a 3d mean to enusre a more stable gradient */ long i1; @@ -291,19 +296,18 @@ mean_stride3d(float* input, float* output, long i, long j, long k, long dimX, lo val5 = input[(size_t) (dimX * dimY * k1) + (size_t) (j * dimX + i)]; val6 = input[(size_t) (dimX * dimY * k2) + (size_t) (j * dimX + i)]; - output[index] = 0.1428f * (input[index] + val1 + val2 + val3 + val4 + val5 + val6); - return; + output[index] = 0.1428F * (input[index] + val1 + val2 + val3 + val4 + val5 + val6); } void -remove_inconsistent_stripes(bool* mask, bool* out, int stripe_length_min, +remove_inconsistent_stripes(const bool* mask, bool* out, int stripe_length_min, int stripe_depth_min, float sensitivity, int switch_dim, long i, long j, long k, long dimX, long dimY, long dimZ) { int counter_vert_voxels; int counter_depth_voxels; - int halfstripe_length = (int) stripe_length_min / 2; - int halfstripe_depth = (int) stripe_depth_min / 2; + int halfstripe_length = stripe_length_min / 2; + int halfstripe_depth = stripe_depth_min / 2; long k_m; long k1; long y_m; @@ -311,8 +315,8 @@ remove_inconsistent_stripes(bool* mask, bool* out, int stripe_length_min, size_t index; index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); - int threshold_vertical = (int) ((0.01f * sensitivity) * stripe_length_min); - int threshold_depth = (int) ((0.01f * sensitivity) * stripe_depth_min); + int threshold_vertical = (int) ((0.01F * sensitivity) * stripe_length_min); + int threshold_depth = (int) ((0.01F * sensitivity) * stripe_depth_min); /* start by considering vertical features */ if(switch_dim == 0) @@ -359,15 +363,14 @@ remove_inconsistent_stripes(bool* mask, bool* out, int stripe_length_min, } } } - return; } void -remove_short_stripes(bool* mask, bool* out, int stripe_length_min, long i, long j, long k, - long dimX, long dimY, long dimZ) +remove_short_stripes(const bool* mask, bool* out, int stripe_length_min, long i, long j, + long k, long dimX, long dimY, long dimZ) { int counter_vert_voxels; - int halfstripe_length = (int) stripe_length_min / 2; + int halfstripe_length = stripe_length_min / 2; long k_m; long k1; size_t index; @@ -387,14 +390,13 @@ remove_short_stripes(bool* mask, bool* out, int stripe_length_min, long i, long if(counter_vert_voxels < halfstripe_length) out[index] = false; } - return; } void -merge_stripes(bool* mask, bool* out, int stripe_length_min, int stripe_width_min, long i, - long j, long k, long dimX, long dimY, long dimZ) +merge_stripes(const bool* mask, bool* out, int stripe_length_min, int stripe_width_min, + long i, long j, long k, long dimX, long dimY, long dimZ) { - int halfstripe_width = (int) stripe_width_min / 2; + int halfstripe_width = stripe_width_min / 2; int vertical_length = 2 * stripe_width_min; long x; @@ -476,7 +478,6 @@ merge_stripes(bool* mask, bool* out, int stripe_length_min, int stripe_width_min out[index] = true; } } - return; } /********************************************************************/ @@ -492,14 +493,16 @@ stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_ve long long totalvoxels; totalvoxels = (long long) ((long) (dimX) * (long) (dimY) * (long) (dimZ)); - int window_fulllength = (int) (2 * window_halflength_vertical + 1); - int midval_window_index = (int) (0.5f * window_fulllength) - 1; + int window_fulllength = (2 * window_halflength_vertical + 1); + int midval_window_index = (int) (0.5F * window_fulllength) - 1; float* temp3d_arr; temp3d_arr = malloc(totalvoxels * sizeof(float)); if(temp3d_arr == NULL) + { printf("Memory allocation of the 'temp3d_arr' array in " "'stripesdetect3d_main_float' failed"); + } /* dealing here with a custom given number of cpu threads */ if(ncores > 0) @@ -589,8 +592,10 @@ stripesmask3d_main_float(float* Input, bool* Output, float threshold_val, bool* mask; mask = malloc(totalvoxels * sizeof(bool)); if(mask == NULL) + { printf( "Memory allocation of the 'mask' array in 'stripesmask3d_main_float' failed"); + } /* dealing here with a custom given number of cpu threads */ if(ncores > 0) @@ -614,9 +619,13 @@ stripesmask3d_main_float(float* Input, bool* Output, float threshold_val, { index = (size_t) (dimX * dimY * k) + (size_t) (j * dimX + i); if(Input[index] <= threshold_val) + { mask[index] = true; + } else + { mask[index] = false; + } } } } From 475946e46db74a11059b32eaff5ba2ed962dfdd4 Mon Sep 17 00:00:00 2001 From: Daniel Ching Date: Tue, 21 Mar 2023 17:56:00 -0500 Subject: [PATCH 25/27] DOC: Reformat docstrings --- source/tomopy/prep/stripe.py | 146 +++++++++++++++++++++++------------ 1 file changed, 97 insertions(+), 49 deletions(-) diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 0737921e8..b19e7fff9 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -944,23 +944,30 @@ def _remove_stripe_based_interpolation(tomo, snr, size, drop_ratio, norm): def stripes_detect3d(tomo, size=10, radius=3, ncore=None): """ - Detect stripes in a 3D array. Usually applied to normalized projection data. + Detect stripes in a 3D array. Usually applied to normalized projection + data. + + The method works with full and partial stripes of constant ot varying + intensity. - The method works with full and partial stripes of constant ot varying intensity. - .. versionadded:: 1.14 Parameters ---------- tomo : ndarray - 3D tomographic data of float32 data type, preferably in the [0, 1] range, although - reasonable deviations accepted (e.g. the result of the normalization and the negative log taken of the raw data). - The projection data should be given with [angles, detY(depth), detX (horizontal)] axis orientation. With this orientation, - the stripes are the vertical features. + 3D tomographic data of float32 data type, preferably in the [0, 1] + range, although reasonable deviations accepted (e.g. the result of the + normalization and the negative log taken of the raw data). The + projection data should be given with [angles, detY(depth), + detX(horizontal)] axis orientation. With this orientation, the stripes + are the vertical features. size : int, optional - The pixel size of the 1D median filter orthogonal to stripes orientation to minimise false detections. Increase it if you have longer or full stripes in the data. + The pixel size of the 1D median filter orthogonal to stripes + orientation to minimise false detections. Increase it if you have + longer or full stripes in the data. radius : int, optional - The pixel size of the 3D stencil to calculate the mean ratio between the angular and detX orientations of the detX gradient. The larger + The pixel size of the 3D stencil to calculate the mean ratio between + the angular and detX orientations of the detX gradient. The larger values can affect the width of the detected stripe, use 1,2,3 values. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used @@ -969,15 +976,18 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): Returns ------- ndarray - Weights in the range of [0, 1] of float32 data type where stripe's edges are highlighted with the smaller (e.g. < 0.5) values. - The weights can be manually thresholded or passed to stripes_mask3d function for further processing and a binary mask generation. + Weights in the range of [0, 1] of float32 data type where stripe's + edges are highlighted with the smaller (e.g. < 0.5) values. The weights + can be manually thresholded or passed to stripes_mask3d function for + further processing and a binary mask generation. Raises ------ ValueError If the `tomo` is not three dimensional. + If the `size` is invalid. - """ + """ if ncore is None: ncore = mproc.mp.cpu_count() @@ -985,20 +995,26 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): if (input_type != 'float32'): tomo = dtype.as_float32(tomo) # silent convertion to float32 data type out = np.empty_like(tomo, order='C') - + if tomo.ndim == 3: dz, dy, dx = tomo.shape if (dz == 0) or (dy == 0) or (dx == 0): - raise ValueError("The length of one of dimensions is equal to zero") + msg = "The length of one of dimensions is equal to zero" + raise ValueError(msg) else: - raise ValueError("The input array must be a 3D array") + msg = "The input array must be a 3D array" + raise ValueError(msg) if size <= 0 or size > dz // 2: - raise ValueError("The size of the filter should be larger than zero and smaller than the half of the vertical dimension") + msg = ( + "The size of the filter should be larger than zero " + "and smaller than the half of the vertical dimension" + ) + raise ValueError(msg) # perform stripes detection - extern.c_stripes_detect3d(np.ascontiguousarray(tomo), - out, + extern.c_stripes_detect3d(np.ascontiguousarray(tomo), + out, size, radius, ncore, @@ -1006,7 +1022,7 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): return out -def stripes_mask3d(weights, +def stripes_mask3d(weights, threshold = 0.6, min_stripe_length = 20, min_stripe_depth = 10, @@ -1014,33 +1030,42 @@ def stripes_mask3d(weights, sensitivity_perc = 85.0, ncore=None): """ - Takes the result of the stripes_detect3d module as an input and generates a - binary 3D mask with ones where stripes present. The method tries to eliminate - non-stripe features in data by checking the consistency of weights in three directions. + Takes the result of the stripes_detect3d module as an input and generates a + binary 3D mask with ones where stripes present. + + The method tries to eliminate non-stripe features in data by checking the + consistency of weights in three directions. .. versionadded:: 1.14 Parameters ---------- weights : ndarray - 3D weights array, a result of stripes_detect3d module given in - [angles, detY(depth), detX] axis orientation. + 3D weights array, a result of stripes_detect3d module given in [angles, + detY(depth), detX] axis orientation. threshold : float, optional - Threshold for the given weights. This parameter defines what weights will be considered - as potential candidates for stripes. The lower value (< 0.5) will result in the most prominent stripes in the data. - Increase the threshold cautiously to avoid taking too many false alarms into account. The good range to - try is between 0.5 and 0.7. + Threshold for the given weights. This parameter defines what weights + will be considered as potential candidates for stripes. The lower value + (< 0.5) will result in only the most prominent stripes in the data. + Increase the threshold cautiously because increasing the threshold + increases the probability of false detections. The good range to try is + between 0.5 and 0.7. min_stripe_length : int, optional - Minimum length of a stripe in pixels with respect to the "angles" axis. If there are full stripes in the data, - then this could be >50% of the size of the the "angles" axis. + Minimum length of a stripe in pixels with respect to the "angles" axis. + If there are full stripes in the data, then this could be >50% of the + size of the the "angles" axis. min_stripe_depth : int, optional - Minimum depth of a stripe in pixels with respect to the "detY" axis. The stripes do not extend very deep normally - in the data. By setting this parameter to the approximate depth of the stripe more false alarms can be removed. + Minimum depth of a stripe in pixels with respect to the "detY" axis. + The stripes do not extend very deep normally in the data. By setting + this parameter to the approximate depth of the stripe more false alarms + can be removed. min_stripe_width : int, optional - Minimum width of a stripe in pixels with respect to the "detX" axis. The stripes that close to each other - can be merged together with this parameter. + Minimum width of a stripe in pixels with respect to the "detX" axis. + The stripes that close to each other can be merged together with this + parameter. sensitivity_perc : float, optional - The value in percents to impose less strict conditions on length, depth and width parameters of a stripe. + The value in percents to impose less strict conditions on length, depth + and width parameters of a stripe. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used if unspecified. @@ -1048,15 +1073,23 @@ def stripes_mask3d(weights, Returns ------- ndarray - A binary mask of bool data type with stripes highlighted as True values. + A binary mask of bool data type with stripes highlighted as True + values. Raises ------ ValueError If the input array is not three dimensional. - If a min_stripe_length parameter is negative, zero or longer than its corresponding dimension ("angle") - If a min_stripe_depth parameter is negative or longer than its corresponding dimension ("detY") - If a min_stripe_width parameter is negative, zero or longer than its corresponding dimension ("detX") + + If a min_stripe_length parameter is negative, zero or longer than its + corresponding dimension ("angle") + + If a min_stripe_depth parameter is negative or longer than its + corresponding dimension ("detY") + + If a min_stripe_width parameter is negative, zero or longer than its + corresponding dimension ("detX") + If a sensitivity_perc parameter doesn't lie in the (0,100] range """ @@ -1067,31 +1100,46 @@ def stripes_mask3d(weights, if (input_type != 'float32'): weights = dtype.as_float32(weights) # silent convertion to float32 data type out = np.zeros(np.shape(weights), dtype=bool, order='C') - + if weights.ndim == 3: dz, dy, dx = weights.shape if (dz == 0) or (dy == 0) or (dx == 0): - raise ValueError("The length of one of dimensions is equal to zero") + msg = "The length of one of dimensions is equal to zero" + raise ValueError(msg) else: - raise ValueError("The input array must be a 3D array") + msg = "The input array must be a 3D array" + raise ValueError(msg) if min_stripe_length <= 0 or min_stripe_length >= dz: - raise ValueError("The minimum length of a stripe cannot be zero or exceed the size of the angular dimension") + msg = ( + "The minimum length of a stripe cannot be zero " + "or exceed the size of the angular dimension" + ) + raise ValueError(msg) if min_stripe_depth < 0 or min_stripe_depth >= dy: - raise ValueError("The minimum depth of a stripe cannot exceed the size of the depth dimension") + msg = ( + "The minimum depth of a stripe cannot exceed " + "the size of the depth dimension" + ) + raise ValueError(msg) if min_stripe_width <= 0 or min_stripe_width >= dx: - raise ValueError("The minimum width of a stripe cannot be zero or exceed the size of the horizontal dimension") + msg = ( + "The minimum width of a stripe cannot be zero " + "or exceed the size of the horizontal dimension" + ) + raise ValueError(msg) if 0.0 < sensitivity_perc <= 100.0: pass else: - raise ValueError("sensitivity_perc value must be in (0, 100] percentage range ") - + msg = "sensitivity_perc value must be in (0, 100] percentage range" + raise ValueError(msg) + # perform mask creation based on the input provided by stripes_detect3d module - extern.c_stripesmask3d(np.ascontiguousarray(weights), - out, + extern.c_stripesmask3d(np.ascontiguousarray(weights), + out, threshold, min_stripe_length, min_stripe_depth, From d5f53703a716071e5e21d6e582b63cc06ef624b5 Mon Sep 17 00:00:00 2001 From: Daniel Ching Date: Thu, 23 Mar 2023 10:51:18 -0500 Subject: [PATCH 26/27] DOC: Documentation clarification for stripe removal --- source/tomopy/prep/stripe.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index b19e7fff9..56d96dc19 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -958,9 +958,9 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): 3D tomographic data of float32 data type, preferably in the [0, 1] range, although reasonable deviations accepted (e.g. the result of the normalization and the negative log taken of the raw data). The - projection data should be given with [angles, detY(depth), + projection data should be given with [angle, detY(depth), detX(horizontal)] axis orientation. With this orientation, the stripes - are the vertical features. + are features along the angle axis. size : int, optional The pixel size of the 1D median filter orthogonal to stripes orientation to minimise false detections. Increase it if you have @@ -1064,8 +1064,9 @@ def stripes_mask3d(weights, The stripes that close to each other can be merged together with this parameter. sensitivity_perc : float, optional - The value in percents to impose less strict conditions on length, depth - and width parameters of a stripe. + The value in the range [0, 100] that controls the strictness of the + minimum length, depth and width parameters of a stripe. 0 is + less-strict. 100 is more-strict. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used if unspecified. From 92991466711415c5e71731c351c88262adc73593 Mon Sep 17 00:00:00 2001 From: Daniel Ching Date: Thu, 23 Mar 2023 10:56:50 -0500 Subject: [PATCH 27/27] STY: Format using YAPF --- source/tomopy/prep/stripe.py | 367 +++++++++++++++++++---------------- 1 file changed, 197 insertions(+), 170 deletions(-) diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 56d96dc19..b5e81e468 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -45,7 +45,6 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # # POSSIBILITY OF SUCH DAMAGE. # # ######################################################################### - """ Module for pre-processing tasks. """ @@ -66,30 +65,36 @@ from scipy.ndimage import uniform_filter1d from scipy import interpolate import logging -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) __author__ = "Doga Gursoy, Eduardo X. Miqueles, Nghia Vo" __credits__ = "Juan V. Bermudez, Hugo H. Slepicka" __copyright__ = "Copyright (c) 2015, UChicago Argonne, LLC." __docformat__ = 'restructuredtext en' -__all__ = ['remove_stripe_fw', - 'remove_stripe_ti', - 'remove_stripe_sf', - 'remove_stripe_based_sorting', - 'remove_stripe_based_filtering', - 'remove_stripe_based_fitting', - 'remove_large_stripe', - 'remove_dead_stripe', - 'remove_all_stripe', - 'remove_stripe_based_interpolation', - 'stripes_detect3d', - 'stripes_mask3d'] - - -def remove_stripe_fw( - tomo, level=None, wname='db5', sigma=2, - pad=True, ncore=None, nchunk=None): +__all__ = [ + 'remove_stripe_fw', + 'remove_stripe_ti', + 'remove_stripe_sf', + 'remove_stripe_based_sorting', + 'remove_stripe_based_filtering', + 'remove_stripe_based_fitting', + 'remove_large_stripe', + 'remove_dead_stripe', + 'remove_all_stripe', + 'remove_stripe_based_interpolation', + 'stripes_detect3d', + 'stripes_mask3d', +] + + +def remove_stripe_fw(tomo, + level=None, + wname='db5', + sigma=2, + pad=True, + ncore=None, + nchunk=None): """ Remove horizontal stripes from sinogram using the Fourier-Wavelet (FW) based method :cite:`Munch:09`. @@ -120,13 +125,12 @@ def remove_stripe_fw( size = np.max(tomo.shape) level = int(np.ceil(np.log2(size))) - arr = mproc.distribute_jobs( - tomo, - func=_remove_stripe_fw, - args=(level, wname, sigma, pad), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=_remove_stripe_fw, + args=(level, wname, sigma, pad), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr @@ -165,8 +169,8 @@ def _remove_stripe_fw(tomo, level, wname, sigma, pad): fcV *= np.transpose(np.tile(damp, (mx, 1))) # Inverse FFT. - cV[n] = np.real(ifft(np.fft.ifftshift( - fcV), axis=0, extra_info=num_jobs)) + cV[n] = np.real( + ifft(np.fft.ifftshift(fcV), axis=0, extra_info=num_jobs)) # Wavelet reconstruction. for n in range(level)[::-1]: @@ -198,13 +202,12 @@ def remove_stripe_ti(tomo, nblock=0, alpha=1.5, ncore=None, nchunk=None): ndarray Corrected 3D tomographic data. """ - arr = mproc.distribute_jobs( - tomo, - func=_remove_stripe_ti, - args=(nblock, alpha), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=_remove_stripe_ti, + args=(nblock, alpha), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr @@ -226,12 +229,20 @@ def _remove_stripe_ti(tomo, nblock, alpha): def _kernel(m, n): - v = [[np.array([1, -1]), - np.array([-3 / 2, 2, -1 / 2]), - np.array([-11 / 6, 3, -3 / 2, 1 / 3])], - [np.array([-1, 2, -1]), - np.array([2, -5, 4, -1])], - [np.array([-1, 3, -3, 1])]] + v = [ + [ + np.array([1, -1]), + np.array([-3 / 2, 2, -1 / 2]), + np.array([-11 / 6, 3, -3 / 2, 1 / 3]), + ], + [ + np.array([-1, 2, -1]), + np.array([2, -5, 4, -1]), + ], + [ + np.array([-1, 3, -3, 1]), + ], + ] return v[m - 1][n - 1] @@ -344,17 +355,20 @@ def remove_stripe_sf(tomo, size=5, ncore=None, nchunk=None): Corrected 3D tomographic data. """ tomo = dtype.as_float32(tomo) - arr = mproc.distribute_jobs( - tomo, - func=extern.c_remove_stripe_sf, - args=(size,), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=extern.c_remove_stripe_sf, + args=(size,), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr -def remove_stripe_based_sorting(tomo, size=None, dim=1, ncore=None, nchunk=None): +def remove_stripe_based_sorting(tomo, + size=None, + dim=1, + ncore=None, + nchunk=None): """ Remove full and partial stripe artifacts from sinogram using Nghia Vo's approach :cite:`Vo:18` (algorithm 3). @@ -378,13 +392,12 @@ def remove_stripe_based_sorting(tomo, size=None, dim=1, ncore=None, nchunk=None) ndarray Corrected 3D tomographic data. """ - arr = mproc.distribute_jobs( - tomo, - func=_remove_stripe_based_sorting, - args=(size, dim), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=_remove_stripe_based_sorting, + args=(size, dim), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr @@ -403,14 +416,12 @@ def _rs_sort(sinogram, size, matindex, dim): """ sinogram = np.transpose(sinogram) matcomb = np.asarray(np.dstack((matindex, sinogram))) - matsort = np.asarray( - [row[row[:, 1].argsort()] for row in matcomb]) + matsort = np.asarray([row[row[:, 1].argsort()] for row in matcomb]) if dim == 1: matsort[:, :, 1] = median_filter(matsort[:, :, 1], (size, 1)) else: matsort[:, :, 1] = median_filter(matsort[:, :, 1], (size, size)) - matsortback = np.asarray( - [row[row[:, 0].argsort()] for row in matsort]) + matsortback = np.asarray([row[row[:, 0].argsort()] for row in matsort]) sino_corrected = matsortback[:, :, 1] return np.transpose(sino_corrected) @@ -427,8 +438,12 @@ def _remove_stripe_based_sorting(tomo, size, dim): tomo[:, m, :] = _rs_sort(sino, size, matindex, dim) -def remove_stripe_based_filtering( - tomo, sigma=3, size=None, dim=1, ncore=None, nchunk=None): +def remove_stripe_based_filtering(tomo, + sigma=3, + size=None, + dim=1, + ncore=None, + nchunk=None): """ Remove stripe artifacts from sinogram using Nghia Vo's approach :cite:`Vo:18` (algorithm 2). @@ -455,13 +470,12 @@ def remove_stripe_based_filtering( ndarray Corrected 3D tomographic data. """ - arr = mproc.distribute_jobs( - tomo, - func=_remove_stripe_based_filtering, - args=(sigma, size, dim), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=_remove_stripe_based_filtering, + args=(sigma, size, dim), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr @@ -488,8 +502,8 @@ def _rs_filter(sinogram, window, listsign, size, dim, pad): ifft(fft(sinolist * listsign) * window) * listsign)[pad:ncol - pad] sinosharp = sinogram - sinosmooth matindex = _create_matindex(nrow, ncol - 2 * pad) - sinosmooth_cor = np.transpose(_rs_sort( - np.transpose(sinosmooth), size, matindex, dim)) + sinosmooth_cor = np.transpose( + _rs_sort(np.transpose(sinosmooth), size, matindex, dim)) return np.transpose(sinosmooth_cor + sinosharp) @@ -504,12 +518,14 @@ def _remove_stripe_based_filtering(tomo, sigma, size, dim): size = max(5, int(0.01 * tomo.shape[2])) for m in range(tomo.shape[1]): sino = tomo[:, m, :] - tomo[:, m, :] = _rs_filter( - sino, window, listsign, size, dim, pad) + tomo[:, m, :] = _rs_filter(sino, window, listsign, size, dim, pad) -def remove_stripe_based_fitting( - tomo, order=3, sigma=(5, 20), ncore=None, nchunk=None): +def remove_stripe_based_fitting(tomo, + order=3, + sigma=(5, 20), + ncore=None, + nchunk=None): """ Remove stripe artifacts from sinogram using Nghia Vo's approach :cite:`Vo:18` (algorithm 1). @@ -534,13 +550,12 @@ def remove_stripe_based_fitting( ndarray Corrected 3D tomographic data. """ - arr = mproc.distribute_jobs( - tomo, - func=_remove_stripe_based_fitting, - args=(order, sigma), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=_remove_stripe_based_fitting, + args=(order, sigma), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr @@ -639,8 +654,13 @@ def _remove_stripe_based_fitting(tomo, order, sigma): tomo[:, m, :] = _rs_fit(sino, order, win2d, matsign, pad) -def remove_large_stripe(tomo, snr=3, size=51, drop_ratio=0.1, norm=True, - ncore=None, nchunk=None): +def remove_large_stripe(tomo, + snr=3, + size=51, + drop_ratio=0.1, + norm=True, + ncore=None, + nchunk=None): """ Remove large stripe artifacts from sinogram using Nghia Vo's approach :cite:`Vo:18` (algorithm 5). @@ -669,13 +689,12 @@ def remove_large_stripe(tomo, snr=3, size=51, drop_ratio=0.1, norm=True, ndarray Corrected 3D tomographic data. """ - arr = mproc.distribute_jobs( - tomo, - func=_remove_large_stripe, - args=(snr, size, drop_ratio, norm), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=_remove_large_stripe, + args=(snr, size, drop_ratio, norm), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr @@ -687,8 +706,8 @@ def _detect_stripe(listdata, snr): listsorted = np.sort(listdata)[::-1] xlist = np.arange(0, numdata, 1.0) ndrop = np.int16(0.25 * numdata) - (_slope, _intercept) = np.polyfit( - xlist[ndrop:-ndrop - 1], listsorted[ndrop:-ndrop - 1], 1) + (_slope, _intercept) = np.polyfit(xlist[ndrop:-ndrop - 1], + listsorted[ndrop:-ndrop - 1], 1) numt1 = _intercept + _slope * xlist[-1] noiselevel = np.abs(numt1 - _intercept) noiselevel = np.clip(noiselevel, 1e-6, None) @@ -715,8 +734,10 @@ def _rs_large(sinogram, snr, size, matindex, drop_ratio=0.1, norm=True): sinosmooth = median_filter(sinosort, (1, size)) list1 = np.mean(sinosort[ndrop:nrow - ndrop], axis=0) list2 = np.mean(sinosmooth[ndrop:nrow - ndrop], axis=0) - listfact = np.divide(list1, list2, - out=np.ones_like(list1), where=list2 != 0) + listfact = np.divide(list1, + list2, + out=np.ones_like(list1), + where=list2 != 0) # Locate stripes listmask = _detect_stripe(listfact, snr) listmask = binary_dilation(listmask, iterations=1).astype(listmask.dtype) @@ -726,11 +747,9 @@ def _rs_large(sinogram, snr, size, matindex, drop_ratio=0.1, norm=True): sinogram = sinogram / matfact sinogram1 = np.transpose(sinogram) matcombine = np.asarray(np.dstack((matindex, sinogram1))) - matsort = np.asarray( - [row[row[:, 1].argsort()] for row in matcombine]) + matsort = np.asarray([row[row[:, 1].argsort()] for row in matcombine]) matsort[:, :, 1] = np.transpose(sinosmooth) - matsortback = np.asarray( - [row[row[:, 0].argsort()] for row in matsort]) + matsortback = np.asarray([row[row[:, 0].argsort()] for row in matsort]) sino_corrected = np.transpose(matsortback[:, :, 1]) listxmiss = np.where(listmask > 0.0)[0] sinogram[:, listxmiss] = sino_corrected[:, listxmiss] @@ -744,8 +763,12 @@ def _remove_large_stripe(tomo, snr, size, drop_ratio, norm): tomo[:, m, :] = _rs_large(sino, snr, size, matindex, drop_ratio, norm) -def remove_dead_stripe(tomo, snr=3, size=51, norm=True, - ncore=None, nchunk=None): +def remove_dead_stripe(tomo, + snr=3, + size=51, + norm=True, + ncore=None, + nchunk=None): """ Remove unresponsive and fluctuating stripe artifacts from sinogram using Nghia Vo's approach :cite:`Vo:18` (algorithm 6). @@ -771,13 +794,12 @@ def remove_dead_stripe(tomo, snr=3, size=51, norm=True, ndarray Corrected 3D tomographic data. """ - arr = mproc.distribute_jobs( - tomo, - func=_remove_dead_stripe, - args=(snr, size, norm), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=_remove_dead_stripe, + args=(snr, size, norm), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr @@ -790,8 +812,10 @@ def _rs_dead(sinogram, snr, size, matindex, norm=True): sinosmooth = np.apply_along_axis(uniform_filter1d, 0, sinogram, 10) listdiff = np.sum(np.abs(sinogram - sinosmooth), axis=0) listdiffbck = median_filter(listdiff, size) - listfact = np.divide(listdiff, listdiffbck, - out=np.ones_like(listdiff), where=listdiffbck != 0) + listfact = np.divide(listdiff, + listdiffbck, + out=np.ones_like(listdiff), + where=listdiffbck != 0) listmask = _detect_stripe(listfact, snr) listmask = binary_dilation(listmask, iterations=1).astype(listmask.dtype) listmask[0:2] = 0.0 @@ -816,8 +840,13 @@ def _remove_dead_stripe(tomo, snr, size, norm): tomo[:, m, :] = _rs_dead(sino, snr, size, matindex, norm) -def remove_all_stripe(tomo, snr=3, la_size=61, sm_size=21, dim=1, - ncore=None, nchunk=None): +def remove_all_stripe(tomo, + snr=3, + la_size=61, + sm_size=21, + dim=1, + ncore=None, + nchunk=None): """ Remove all types of stripe artifacts from sinogram using Nghia Vo's approach :cite:`Vo:18` (combination of algorithm 3,4,5, and 6). @@ -845,13 +874,12 @@ def remove_all_stripe(tomo, snr=3, la_size=61, sm_size=21, dim=1, ndarray Corrected 3D tomographic data. """ - arr = mproc.distribute_jobs( - tomo, - func=_remove_all_stripe, - args=(snr, la_size, sm_size, dim), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=_remove_all_stripe, + args=(snr, la_size, sm_size, dim), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr @@ -864,8 +892,13 @@ def _remove_all_stripe(tomo, snr, la_size, sm_size, dim): tomo[:, m, :] = sino -def remove_stripe_based_interpolation(tomo, snr=3, size=31, drop_ratio=0.1, - norm=True, ncore=None, nchunk=None): +def remove_stripe_based_interpolation(tomo, + snr=3, + size=31, + drop_ratio=0.1, + norm=True, + ncore=None, + nchunk=None): """ Remove most types of stripe artifacts from sinograms based on interpolation. Derived from algorithm 4, 5, and 6 in :cite:`Vo:18`. @@ -895,13 +928,12 @@ def remove_stripe_based_interpolation(tomo, snr=3, size=31, drop_ratio=0.1, ndarray Corrected 3D tomographic data. """ - arr = mproc.distribute_jobs( - tomo, - func=_remove_stripe_based_interpolation, - args=(snr, size, drop_ratio, norm), - axis=1, - ncore=ncore, - nchunk=nchunk) + arr = mproc.distribute_jobs(tomo, + func=_remove_stripe_based_interpolation, + args=(snr, size, drop_ratio, norm), + axis=1, + ncore=ncore, + nchunk=nchunk) return arr @@ -917,8 +949,10 @@ def _rs_interpolation(sinogram, snr, size, drop_ratio=0.1, norm=True): sinosmooth = median_filter(sinosort, (1, size)) list1 = np.mean(sinosort[ndrop:nrow - ndrop], axis=0) list2 = np.mean(sinosmooth[ndrop:nrow - ndrop], axis=0) - listfact = np.divide(list1, list2, - out=np.ones_like(list1), where=list2 != 0) + listfact = np.divide(list1, + list2, + out=np.ones_like(list1), + where=list2 != 0) listmask = _detect_stripe(listfact, snr) listmask = np.float32(binary_dilation(listmask, iterations=1)) matfact = np.tile(listfact, (nrow, 1)) @@ -942,6 +976,7 @@ def _remove_stripe_based_interpolation(tomo, snr, size, drop_ratio, norm): sino = _rs_interpolation(sino, snr, size, drop_ratio, norm) tomo[:, m, :] = sino + def stripes_detect3d(tomo, size=10, radius=3, ncore=None): """ Detect stripes in a 3D array. Usually applied to normalized projection @@ -1005,30 +1040,24 @@ def stripes_detect3d(tomo, size=10, radius=3, ncore=None): msg = "The input array must be a 3D array" raise ValueError(msg) - if size <= 0 or size > dz // 2: - msg = ( - "The size of the filter should be larger than zero " - "and smaller than the half of the vertical dimension" - ) + if size <= 0 or size > dz // 2: + msg = ("The size of the filter should be larger than zero " + "and smaller than the half of the vertical dimension") raise ValueError(msg) # perform stripes detection - extern.c_stripes_detect3d(np.ascontiguousarray(tomo), - out, - size, - radius, - ncore, - dx, dy, dz) + extern.c_stripes_detect3d(np.ascontiguousarray(tomo), out, size, radius, + ncore, dx, dy, dz) return out def stripes_mask3d(weights, - threshold = 0.6, - min_stripe_length = 20, - min_stripe_depth = 10, - min_stripe_width = 5, - sensitivity_perc = 85.0, - ncore=None): + threshold=0.6, + min_stripe_length=20, + min_stripe_depth=10, + min_stripe_width=5, + sensitivity_perc=85.0, + ncore=None): """ Takes the result of the stripes_detect3d module as an input and generates a binary 3D mask with ones where stripes present. @@ -1099,7 +1128,8 @@ def stripes_mask3d(weights, input_type = weights.dtype if (input_type != 'float32'): - weights = dtype.as_float32(weights) # silent convertion to float32 data type + weights = dtype.as_float32( + weights) # silent convertion to float32 data type out = np.zeros(np.shape(weights), dtype=bool, order='C') if weights.ndim == 3: @@ -1112,24 +1142,18 @@ def stripes_mask3d(weights, raise ValueError(msg) if min_stripe_length <= 0 or min_stripe_length >= dz: - msg = ( - "The minimum length of a stripe cannot be zero " - "or exceed the size of the angular dimension" - ) + msg = ("The minimum length of a stripe cannot be zero " + "or exceed the size of the angular dimension") raise ValueError(msg) if min_stripe_depth < 0 or min_stripe_depth >= dy: - msg = ( - "The minimum depth of a stripe cannot exceed " - "the size of the depth dimension" - ) + msg = ("The minimum depth of a stripe cannot exceed " + "the size of the depth dimension") raise ValueError(msg) if min_stripe_width <= 0 or min_stripe_width >= dx: - msg = ( - "The minimum width of a stripe cannot be zero " - "or exceed the size of the horizontal dimension" - ) + msg = ("The minimum width of a stripe cannot be zero " + "or exceed the size of the horizontal dimension") raise ValueError(msg) if 0.0 < sensitivity_perc <= 100.0: @@ -1139,14 +1163,17 @@ def stripes_mask3d(weights, raise ValueError(msg) # perform mask creation based on the input provided by stripes_detect3d module - extern.c_stripesmask3d(np.ascontiguousarray(weights), - out, - threshold, - min_stripe_length, - min_stripe_depth, - min_stripe_width, - sensitivity_perc, - ncore, - dx, dy, dz) + extern.c_stripesmask3d( + np.ascontiguousarray(weights), + out, + threshold, + min_stripe_length, + min_stripe_depth, + min_stripe_width, + sensitivity_perc, + ncore, + dx, + dy, + dz, + ) return out -

`F!9OJXk9_9o)!-hJ-**AtO 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 self.eps) + + def test_stripe_detection(self): + assert_allclose( + srm.stripes_detect3d(read_file('test_stripe_data.npy'), + vert_filter_size_perc=25, + radius_size=1), + read_file('stripes_detect3d.npy'), rtol=1e-6) + + def test_stripe_mask(self): + assert_allclose( + srm.stripes_mask3d(read_file('stripes_detect3d.npy'), + threshold=0.2, + stripe_length_perc=30.0, + stripe_depth_perc=0.0, + stripe_width_perc=20.0, + sensitivity_perc=80.0), + read_file('stripes_mask3d.npy'), rtol=1e-6) \ No newline at end of file From 86cc3b42bf417f6bbb502865a3843f54c6e3012f Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Tue, 14 Feb 2023 21:57:57 +0000 Subject: [PATCH 06/27] reverts Packages --- cmake/Modules/Packages.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/Modules/Packages.cmake b/cmake/Modules/Packages.cmake index db324bdbe..22a1fbe01 100644 --- a/cmake/Modules/Packages.cmake +++ b/cmake/Modules/Packages.cmake @@ -219,12 +219,12 @@ if(TOMOPY_USE_CUDA) # Pascal support 70, 72 + Volta support 75 + Turing support if(NOT DEFINED CUDA_ARCH) if(CUDAToolkit_VERSION_MAJOR VERSION_LESS 11) - set(CUDA_ARCH "50;52;53;60;61;62;70;72;75") + set(CUDA_ARCH "30;32;35;37;50;52;53;60;61;62;70;72;75") else() if(CUDAToolkit_VERSION_MINOR VERSION_LESS 1) - set(CUDA_ARCH "50;52;53;60;61;62;70;72;75;80") + set(CUDA_ARCH "35;37;50;52;53;60;61;62;70;72;75;80") else() - set(CUDA_ARCH "50;52;53;60;61;62;70;72;75;80;86") + set(CUDA_ARCH "35;37;50;52;53;60;61;62;70;72;75;80;86") endif() endif() endif() From 5acaf3fdd5c5856e86caa3e6cbda086eba2ddfe6 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Tue, 14 Feb 2023 22:01:19 +0000 Subject: [PATCH 07/27] adding new lines --- source/libtomo/misc/utils.h | 2 +- source/tomopy/util/extern/prep.py | 2 +- test/test_tomopy/test_prep/test_stripe.py | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/source/libtomo/misc/utils.h b/source/libtomo/misc/utils.h index 95b69d29b..ea6021f71 100644 --- a/source/libtomo/misc/utils.h +++ b/source/libtomo/misc/utils.h @@ -64,4 +64,4 @@ quicksort_float(float* x, int first, int last); void DLL quicksort_uint16(unsigned short* x, int first, int last); void DLL -gradient3D(float *input, float *output, long dimX, long dimY, long dimZ, int axis, int step_size); \ No newline at end of file +gradient3D(float *input, float *output, long dimX, long dimY, long dimZ, int axis, int step_size); diff --git a/source/tomopy/util/extern/prep.py b/source/tomopy/util/extern/prep.py index c54c1bf99..399566439 100644 --- a/source/tomopy/util/extern/prep.py +++ b/source/tomopy/util/extern/prep.py @@ -149,4 +149,4 @@ def c_stripesmask3d( dtype.as_c_int(dy), dtype.as_c_int(dz), ) - return output \ No newline at end of file + return output diff --git a/test/test_tomopy/test_prep/test_stripe.py b/test/test_tomopy/test_prep/test_stripe.py index 44228b8a1..c532ddbd0 100644 --- a/test/test_tomopy/test_prep/test_stripe.py +++ b/test/test_tomopy/test_prep/test_stripe.py @@ -148,17 +148,18 @@ def test_remove_stripe_based_interpolation(self): def test_stripe_detection(self): assert_allclose( - srm.stripes_detect3d(read_file('test_stripe_data.npy'), + srm.stripes_detect3d(read_file('test_stripe_data.npy'), vert_filter_size_perc=25, radius_size=1), read_file('stripes_detect3d.npy'), rtol=1e-6) def test_stripe_mask(self): assert_allclose( - srm.stripes_mask3d(read_file('stripes_detect3d.npy'), + srm.stripes_mask3d(read_file('stripes_detect3d.npy'), threshold=0.2, stripe_length_perc=30.0, stripe_depth_perc=0.0, stripe_width_perc=20.0, sensitivity_perc=80.0), - read_file('stripes_mask3d.npy'), rtol=1e-6) \ No newline at end of file + read_file('stripes_mask3d.npy'), rtol=1e-6) + From d09bbc17043cdf40767f943d3df625c43b09b2ff Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Thu, 16 Feb 2023 13:56:44 +0000 Subject: [PATCH 08/27] some final corrections --- source/libtomo/prep/stripes_detect3d.c | 38 +++++++++++++++++--------- source/tomopy/prep/stripe.py | 10 +++---- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index 47cf6e0ee..21902a66a 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -89,27 +89,39 @@ ratio_mean_stride3d(float* input, float* output, } mean_plate /= (float)(all_pixels_window); - /* calculate mean of gradientX in a direction orthogonal to stripes direction */ + /* calculate mean of gradientX in a 2D plate orthogonal to stripes direction */ mean_horiz = 0.0f; - for(i_m = 1; i_m <= radius; i_m++) + for(j_m = -1; j_m <= 1; j_m++) { - i1 = i + i_m; - if (i1 >= dimX) - i1 = i - i_m; - mean_horiz += fabsf(input[((dimX * dimY) * k + j * dimX + i1)]); + j1 = j + j_m; + if((j1 < 0) || (j1 >= dimY)) + j1 = j - j_m; + for(i_m = 1; i_m <= radius; i_m++) + { + i1 = i + i_m; + if (i1 >= dimX) + i1 = i - i_m; + mean_horiz += fabsf(input[((dimX * dimY) * k + j1 * dimX + i1)]); + } } - mean_horiz /= (float)(radius); + mean_horiz /= (float)(radius*3); /* Calculate another mean symmetrically */ mean_horiz2 = 0.0f; - for(i_m = -radius; i_m <= -1; i_m++) + for(j_m = -1; j_m <= 1; j_m++) { - i1 = i + i_m; - if (i1 < 0) - i1 = i - i_m; - mean_horiz2 += fabsf(input[((dimX * dimY) * k + j * dimX + i1)]); + j1 = j + j_m; + if((j1 < 0) || (j1 >= dimY)) + j1 = j - j_m; + for(i_m = -radius; i_m <= -1; i_m++) + { + i1 = i + i_m; + if (i1 < 0) + i1 = i - i_m; + mean_horiz2 += fabsf(input[((dimX * dimY) * k + j1 * dimX + i1)]); + } } - mean_horiz2 /= (float)(radius); + mean_horiz2 /= (float)(radius*3); /* calculate the ratio between two means assuming that the mean orthogonal to stripes direction should be larger than the mean diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 67c0858e5..418d65e42 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -983,8 +983,8 @@ def stripes_detect3d(arr, vert_filter_size_perc=5, radius_size=3, ncore=None): input_type = arr.dtype if (input_type != 'float32'): arr = dtype.as_float32(arr) # silent convertion to float32 data type - out = np.empty_like(arr) - + out = np.empty_like(arr, order='C') + if arr.ndim == 3: dz, dy, dx = arr.shape if (dz == 0) or (dy == 0) or (dx == 0): @@ -999,7 +999,7 @@ def stripes_detect3d(arr, vert_filter_size_perc=5, radius_size=3, ncore=None): raise ValueError("vert_filter_size_perc value must be in (0, 100] percentage range ") # perform stripes detection - extern.c_stripes_detect3d(arr, out, + extern.c_stripes_detect3d(np.ascontiguousarray(arr), out, vertical_filter_size, radius_size, ncore, @@ -1055,7 +1055,7 @@ def stripes_mask3d(arr, input_type = arr.dtype if (input_type != 'float32'): arr = dtype.as_float32(arr) # silent convertion to float32 data type - out = np.uint16(np.empty_like(arr)) + out = np.uint16(np.empty_like(arr, order='C')) if arr.ndim == 3: dz, dy, dx = arr.shape @@ -1084,7 +1084,7 @@ def stripes_mask3d(arr, # perform mask creation based on the input provided by stripes_detect3d module - extern.c_stripesmask3d(arr, out, + extern.c_stripesmask3d(np.ascontiguousarray(arr), out, threshold, stripe_length_min, stripe_depth_min, From 2da61902c183c44af0d703a4a362be9ecd3ef078 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Thu, 16 Feb 2023 15:03:18 +0000 Subject: [PATCH 09/27] fixing tests --- .../test_data/stripes_detect3d.npy | Bin 36128 -> 36128 bytes test/test_tomopy/test_data/stripes_mask3d.npy | Bin 18128 -> 18128 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/test/test_tomopy/test_data/stripes_detect3d.npy b/test/test_tomopy/test_data/stripes_detect3d.npy index 088a6e8a47c9b05360b2a409e217755c1d13e0a2..c043e6e62c1bdb54d3a872a1a9e36a3df71a812d 100644 GIT binary patch literal 36128 zcmbWAd7RJX`u}Ifm@yb;VGM)DmVHkWpZl((q7s#CY1KxhL{VvxeJfca`(BD1=}@wi zD3Lu|_I)c1hA|j^@8|V#z1{O^bk6s89_RdV&Y#a-=XJfV*L}S|GrVbw2Oew_9@aIi zd--Rd>+($J^0%j!Z}9vr<*THYfBx0ZuXld>l_y{A{Ooi5@Ao|Ya+l}4|Gi85r#n68 z{r3trYt*QcTHE_~jfz!L-%b7h_*dS{#>MQpGvAnQ7uJ}61-6(SDT%gnxyvSRqr5ik z*co%a{UuYh?`o5I_g|*qJLOHc7Rlz@Ese~;J=@LHvAv9WppR)Xc6gxC=8pou4nAqt z*80ty*qP6sn|H|6jksu@nsVNB|18=*xb>nrp7@8U-}#bx^kPLjxJ4rylN@D-)=M`# zC+4<+|HRwQH52TeQ6=r3MM-w?#d5Y%w|HB6OQOB>baT^k?_H+iUyqpkSL`;U_Kz^f zYkXp=Z~8otT4-+Ij+sfe@q=;p<7dNc`3{k`ZHsXGL~4pXX%p>$t5J5{(!#dGeZ}lM zUp2R1MHjOt-}u3#&s}3uo9!`o&pTrloXjwto{6?!Ryb+;+?{B9efxv?{+<~7+oHG3 zbH}Qh6ZhX@#y)@6G=5;TnSW-ei5{L6XqIs-@M!y!=BKz*X6b@G=KD?Ic5eD#rfkb& z=Iy*$=B3wS?2pS2nAruwZS#88!g=fFAI$W3|1?#WpEdW7OtuC3T{UL{9)qg6ta5I5 z_)p>7uWBXJf7yLE_gk3JVf#$YUL#Fh?@vs}b>H}$tF(xAL^8uV>}lWykBpAD?K};;oM%5?Ui2Kw89fhNJxcUkr}5Ij z!54oD%qvvbt|*gWs}9SZjdQ4;>-M_S{?IU#rsw&UYNyluggdS@n!>oNtq1RL_}TT=hA}epJfL z%iqjIq;5CLd0)%PfqO5E2z+zolo>HU-p;I&VAuY1+?;$b%HkXK@}wJ=v+7wntDf-< zSI-ANO%Oc~UEf6X{Cn?KqGvdx=L;U^j5R-tZ@8S#ycQ|GQR6~_JzOJNd;>kh8Q*w5 zbAjnp@F%03(KEiGoY6DBp?aS6em(P8l{4ZS3qBue7Q9u!6djl$oKNMBxA;b%|705U z+~?K|6Z6DTQ=?Cof%D}p7tM>C>xgfJpZY1Ap5eT_y{pc6}q1bKCWq zK4*Lb&W*l{wyI}*W8a5)ecym{p}1)A4fL%3{6=PqeQ#eW3+EA^<*{&1AKcKuxx=#k zCiml?n$mkm{f%?o^-1=_SrPW5;SsiDqoNky2<7~rb!B}$-k`fR=#hz{*0cRth&y5!-9RL;i|N3oU1>U;d6e!TUER4!(tZB8{fz)zR~vU6q$pn zXE^ta%QEvG+AY3O``s!s2UX8aB76G2F|baUZFu{2J!dtI6wdfFdahl5yU%&xxO**} zOU+3V&V|Po70#+>moxtS(qnfR{8>3~c=1y+GxZl=&&yI0ES#Bx$~o@sa>9AwfP&)B z#m3%cGvBFTv)ZkdIXG|Xp=_Md^ZC=!7CrY*Nf&=s-*EL@yJJi?&R_L8?CTldK+l_g z+h-1RIv{%f?D;xAXZ2?|FRPPj@MrP{obhKkFI`v7Z2hge%)y*EPbwZOoYkMN%!Q5!)>~Me+um$r~pI=a#$?s%JRk&-ljc9g0~vSNyAp=o#O5e0~Az zawc!Uc}7$n;mjQT@QcI38Q)0xEZrz)@&=so=k>MAnRVl9X7dd=uc;hn$Q#uoVr|pI zNfteSu<)|XLHt=c_v>=rDCd0hmYYGBa#{Qt&P6}{OZ*wm!^RzwTu0te&aOY_q-W*) zOoii;>(a00lDvVQHP@YtuPb`iyn%1HoLep^VaXf##-lwR@%{NmdS0+QHk+Os+#e^L z`;UsXgBr%#Th0{`&ghxE@kXaVe9kS(oc1|K9?P9g&*VBd=URT!tbcKY={RMmQ9Y+G z{!?-toKr6D_I(4+>KkyzH^_CNoY6DBp`6?IId0&*WqVbFKf}4>8{1?KRvz?$_y%(j z-*EGW%UOLRBM@%UbNjP3gtO*nmovEz&W-x!wT(PKCqJX-tYfXrq#=96pVc?ot^Gyv zhH@s?;Ty{N;a)|BGkPZ1xt!HElr#Q}Z=mP+5vN4YF6U8=MtJ|cKNilpmWLTQkGQp)$^(zIS;8wU$*GjJx?!5M!(n&&~`%%0-vnK{^N%Nm28*;C*= zW5OAOoP^}9lz-yfWTUqWdUNoTJ|hB) zBR&qm89je_Um>3}{+ttM_Cd`XnxF9v<{)`vQ6NF|On!Db;~Td|jyB~U87*`0{0qwi z)po=Q=Q(%B31|EnJ;QnK&fN|NUmr zsb5Y1L)*+VPjoRy=2a8lP|mKNH{7{jIHTt=eb0zLC(XX#=ep_jJ^wBlEu5zklYP$YgNakhik{(2-U!t*oSB1<-L}>& zI=?zDaavlA~# NpPT*qkZ?|G-OuODK3Kk8xS#7LjyfW~p`6t>$j^<#1Clq` zyUBIR89l=}wN9$fS@%Kw`PHe9nU0MPnldqCP3Gz`W@_H$0oAi|PMMl$UwgekHqP`L z>$a5j=OCQXv-)#<#gnq9sGd9jwAR;i*KXBiA4JdEZzyNiPL|myP>d)+hnm0Dh$|IbIp4;qmK6Ek2x!!=_-hJPp z5x#GrXYwvkSv5`FZ2Ug0}0_czc)k&y~EPdpCQ^ zz`j>Z&9_oT&o#=mH7;lRjnkJ$NRPDo?k%R`yKz>1L-mY5YhOo?1m{+>l4S30RlKO* zBf*(Dc-sr{{v6c4ZsPDKMbEc|jWtO_9L~G)OOHg}z@IA(j1%8L&%2%|Dt#S#e(tf` z{W*v~FFb$Bpl5o~x^JE_uAVE;_)GMxoY@C$nNfzmj=h^bWmNaw=C0BCt?q+u|IBBZ zgY=?s#-Fv{a5=ZU7;hVV6>D>E%w@F~rLUvkfHVCw`5DgagG09j=b-BO*4SN=Hn1;boAmsvYmGIpeLc$j zyJK$CIoE{%`MKu9dBr#2jGi+VUG#GudS)NQpUE5ebJd$7?22AR#5Yd8mrHyD&IyB} zv*}qmqvw@vdS}ZU%6Zs>(c;hDE*%%n^mX|2-du=qJ15{ zv0!DuTqyde=vngyy(pZ?&&pYS1AkV| zWd3=XgYG^EXIIZ~rms`Z_y+zA=SCea%N#7RA&(_*y!p&=*$2tba9%j>PoutpKl5%i zenOb^qWJUL)2Cz~EHfvlXYwG@U9MZ2sq_%od8pF{Qhp|@l4o)WdHviLJ|kRIu? zwn_HA%0b^?Pk}S}8P4=bd4@ML=sA>g;lQFm{&Nd_-%x+vcs^0)AU!|*2DuJB@9xk* zIDa={r@W_3e)o)-JLQz*4fHIWJ>MX2s6Rhhr?Ri-j^DZYRL(jF@n`xCa-H@Y5%a1`U&o%p9AqDKeS`Nw_Q9#QT{J^y{Atj0 z>N_#gKdYYc=ZBK3+VTVM@$&}x+4X1M2jPrw&~LyQe`Zf1Kli9VPJF}VOs=&_PF$aO>S zjPNkxZzyMcqs>=Gd_8M^rWbYfOfRaOzs;)S`v#oJ8|;IwZ)kpQ5Efy{8)ze4T>9sky<(+*M$hy}x_5td zC`o)ndnECVi=~A#dE-Q>tCH*V9hNz${>(e9a^}68T-SWiSd(|tD0#Qqyev}sXYz)= zr|{kl=g@a6@-w}t`iAa;@EgEPLN{WJNQ_d(UO<_+@m_@^Q* z`FV9g@BfqDo&sm?3h-z8XZFFIIJ2kV8`|@eH@b~GB7L3S6_7XJOuvC|!1<%Q60GJ8 zd;`wtnK^hqwvz7~__O9Zd;>k>8*pY0;?J(0;Y{9uv-$>mH@*R9{CV%>vcg&Qtod0v z;~V%h`I&x0-(lgbdpCJQ`wjAQ?VSn!9AqEFH<*L$DfD%EzoDGT&mYeJ-RDgI{I8j{ z{JmTELDe(4jyXt=r2b6Lk8dys*}L@}*5yooM$bj_=gQ_Anm4q6b~&@Bz!^Q`&zd*j zta`>b;Ox#p?Kjjnbnk{U{;c`g<%~bOdy4iO^hjgZ>d*B2%31vxJ;RyzZuG4CAbQqZN57$*@n?L4_rU|ZcKNyP)v-Z;CfAWS z^nL@*pfD-!KaMoI_0c+13kOG!5pMV;vH7+ku+~CdM4fc6K8r+<{+H) zeNgul^$pdt-XoDW&@+1qzCnIge`Zfn-+1C+Vd*#U4e|z@wMQavz?nIilW*vrqP?g) z2kDXE%$^dOpW&=})*k7ePEoRVlb=I5C;WEO?>F=v7Cn>e=#gAK(<8xI^{jVa%t7`+ z{JH4^NxpB;BaxrcGo00*i#{E^lerOR^=CM1-uQA#Ui(haOS-m&aR&EXXVTs#GlE}fxuDWTrx4-vJcYpFB}%+On%OZ^Oz|krAG?oe5rJ9 z3uo?R$j@-bH(Y<_?kgwG_{PGf(c&A-!JIhrK3F?nPsz{f&pHR6`QfBd&eh+Iu*$h< z0q_0V%XM&G@J@c|MRiXpQ!YjHj6a9!8Q*Ywe)|p>XYLBf&#sjee#SR=PoYO5 zZ?JbOXL22S)_pK^4$?o9>s-$1F@=53nm1g|Zoi>==6(Zzer3;J(j%#!b?+uW!`YJdcJ4oRq2sjJH7wp(KDRM&pk(-l6~-n&DC<`Tt8{F z7q;}GeO6@1yA{51*YGT(oYAx9=Z|{lkz5z<_4)V)?>Up%cr;s=B4c@Kr4dtx8sLPpqB<0Mzm2zeurYp~x4)ib#+<+(8dlXluD z=O->^NWTH+tfa$#B#gYNp8_dDZ94-mSeTzTtAlH&oB`NXof+=4FF#s6Q)b z=Ad#;pPlIMDd<`II_1p!U{0LrMez+dqi6a$`VCjlaHfCOJ;l{C{!Cv7XZAt%lplBe z>2n@5_7+2~gL8w35z=qGm6%)ZWL(bd-MKD!J(9;+-v?uQh1*7{)r2$tOwX_W%$*FJ zbq>NAJ@*`5OZqz1GxwtE&*)kALFG&@s+?Wlpyy`}qUX;G7Pfyh%`3TX#qI=Ky+Cm9 zp7ucjpL6<>HsTxlKB&GyzkzSSnK`JOW5bI1_Z#>|^5A5-V-(JwZ{W{o+8@j28%?Vo z^EvaLf}Yt^@C`V3FMrC=^Q&(tXZ$%QJ-eJ2K2|~YZkMy_dF!D}pR@KGu5WPnb?}wz z_eeduj1}J?Z!ib(4VSa(xmHA!|8Avs1?ZXgZq;+K!-af*rhisF!&&uA-hlJl7k&&Z zI#573_gGg{e4}@0ofA>|s%((xVv+5bn=$X7x zx@!rGKhq<*obhMX^TfA<@7=DRUC!#yp`6h(_ZzBb=HTn^rAfc>?YX?!-n;P)^xWm; zgMN?X>UsCdpl`I^7UWE>J2omwIBUP5{_JvQA4JdWDdY|GtaA|Gc=&jh`EQ(?AD9|| zv-*asXZAtzhO1}gjBn&=9w(gL`wh)?y&frGNA8XB^$ch9+-7btZ>XMK-{5`&&b@ni zowJuW(6jo+hTe58d4o9!=M||(4V<;#z@Lk+4YRIq(AVX}S@q052xsz!%lY`0Lk7~^qvu;zyl<8q zEGL|qgZMLgW*=0}Egy)^ku&$A%DLe~L4SsGxo1w9I*$Z7(~Bx+@&YdZrge&u~W1vpOrInkUN>)S8@sGSANUx&&nBp-t%xtnSaONErJ@XFRbz8W7YI6Y#XZAtOb@&E%1=H@!G(9?% z@Hw-mC}-|B*i&>4!ddgP_H`M_+syf`ADETXdYF|prUfqaobLMucVBQOZ-nZZz1!ui zxz6=x)$@%w>)y?M9e3u+8QLaOS-`RL{IyHF;&MN&fSf zz?-F3i*L}^p=afcZ_x9j=bug#lxIwtu%>!WrK{&&s*jzfVbzR3u-t_=f6PdnDz|9b?q5O-zY1LC)+c za8^C%#2Me6Ha#;3xhv58jBhXpc~1%T4f+k`tb5Aa;W2Vwr<}qb^^G;l z`^a|_ItP_Axem^8hj*GcyB#-8x_I}N*YY!W1#s5Er%+%Xx#nL8Oc z(<8yT-sKMroYgmKOv`1L-;?)mdZvGN^{jVaE@ym0@644mdkUPj7gf&8L3{(wFMYI1 z^bBWu(SqxeWlvGx;4WA13aSkVa?U&KxX;<$2hlTkj0^5NBzZ$QN1do6echt2K|P0Z z*1W-A3QtiQP~IK%p6qCyu&JIa-Hsj z=-K70d$)4NpIy%64LIxG{l#O0MbB_nf7V>*>e=O7`D^bD>YARFGx-_LnxEmU{;c`= zlGnTH-p%`Ky#Pa8^CDcR!fNdo%F#eB!6P_SSE`w=H^6Z|{aPa}eKvv*ryr;~S}^4oJU&o&!6p zNsq)nNUlqJcckyn%UbO;Q_oQ4QS^Lfy!Y-f`%(WrC6qIH<3^l|ln8R(T|SrOXZ7dx zy{{Vflz}HN8Rg90t$71I!?{C)?m6li&g)Y0$sB|;`5DgmGkWIk3(nkc;LmX8o9;bxoaD5)?dqY__NDd_3Zjak+0&iaaKL&#F@Pt&PU!XpCf0z`+~FPXXT8Z z@n_{+veMkZ#qqNx*Kv;oXWn7q%zFxY*1Vy2U-$-`@n@HF+NA`^&!L>j&%9fqXL=-j z<7%B1lArlLWx}k2ws^B3=ZD@XEWIfA{P=UZ(&4^8lk4DIV8Z{d=Lg@pRo<=CpW)0L zRDX6kXXVQ;dd8p8v&;F1Cxd#%HyZWKFi)u&!L>jb*g9WpV6->p5;sf|rU&-ezMRnPOwuaP-eJwBfe<&1BTH}DNOwhI>TD9uvvD_Lvo@)&l%6Hl)d|nC1H{` z;EX?$pV)`x?_x+S|W*;m$CCR{x_C@^z3rRH{jg2@#cWbS#ur!jBn73lAp- zXL`{hab7n%<%sykpei+TizXY{OhGU!=) zq}L5K0I;|DGj-@u>Iv*tQDlb?sD?H1oS>Aj6r|Gm8IDVjHG z9Si!#j7Ni<@n<-bpPwyM$ZlO7Ud`Kyp6QVWjy`J4 z$CJz_TPGMeKd@+bHqNSNIIkU%SH1(viF2r);Y_YW&v54ZK{#u!<9pbT6UUo5@j=dS z7b;-jJbmQ>qy7x%HO;(xyKDW1`m=H_{Y8v$#-B|}hT(pLdnEd2@`n0`t7n&U>4pme z4Yw>7e`XH8IpKmCZ_Wv4eYb-1ea-Vqe%4+TJ;V7%dJg5RdWJK(?yYaP27Z5Uk8oxV zk~iSY9E5Yi^4vaW@`ma;)HfzRktyFD7aSSw`$p~_7ktj=Hq@2x2jM)V$|TYA67M$| zl{0z6)wA|>%DHq}X}kSIlJpy@=VC+73FpklmqgFXnOqmjnO>B$HXc}L+> z20gp}Ox{pEt3Sh8^D~@(+FaPGKcBAe-7}84N&07eV{q0p;u{yd@7mZ0|F4|!jpFYV zl=s0dg)8~_Ig~SZ=G=XeH|p0r>E~zkykx)y;asRykaLOrN!d8-9MoJ#eui_?JIRQV&^x_h^M`WE5LJFLqYf5tb^vvS5a$Q$?u{WE$dZzyN&>(~cf&g_Ff z-yAQTbq+4gYA>AYwrQ1(v-*bW8Q;)3m=JZ{pV9O3QUxvV-OAbZXY`Ce^BoYJ$v)%>iS%Qp$Wr@;Bkn+}Mc?;LZBDPAM^-i<%2 zZ;Vjzw>&ob2m#{a@Op=GM%=TOef z!H$njHrw_FbDidmoH%#xT`(JG_U=&5?A_!II3K?)m*fps&mE@*ITyY;)AU_7(R|Tw zvgjGknm1fMZ;+o|e~vDGi|i@t&)k{2`5DfM ze_i(Hp!x>yu<9G^-P&)UXL8-aCWR$$95|97zJZ?MOs)&%?CKfcAaAe_=EV64@BWN? zB+VOhKMZpIb#744dRG8v-mP5D4PFiUvvS5a;EbN}jZn`0j$Jj{*TEUz_`236!#=3K z(d9@mZ*X4+=gHphIw);&P*QuVFgUUH+`z4?A(%;Yd{@ia? zAM@WhfA{8Yqk4uj_ZzOB={Mj^u7h*&nUNMfSZVPALQ#3y-XXYTjL4J1k zZq>8)&u~^fYmda9uZ)};<%%6j^j|cl_)id`<=vns^ zGbAA!XZj8D^NoB1&gvWF4f<#DM*sP}C2ueX$#J>^JtbEhKslwg2)?5c?orA7#&_ClF zCw>cZ{^L*)3uiZPC}+)evx*iF&NaWzz7Nv#-&L}x^hhpe_7wUJ^vr#o>(Bj$W%-<4 zJ;RwfsGQlmm9y@H+!e$>e*J!f{Ood8J;OP5)oJmK8*xU@ElM_)ena(4-k^V0f2QZx zzHa)%k-nZcM4XmhG$-G<+SdDK%KP3*Ijf%GtiCZntEJ>R<*d05&hOkGZC%dCt`?B@ z6gZa{7b`tK{>(lY(dMclZ+tWJtQl47in$SI@&>-aos8?x+Vkriq~9Rd!CCL?bWc&v z^rGl_>G-_j8*mQw4b?MqFel%DGw)WKH}Ws_{zi!Rp2GbGoY_;*vvOt*#$5FFGG`9L znK`JO@n?KPInzJGneQf)Gr5kwPW8Nc?)7&oavlD>GpV@T*U^h!`M#dyjTHwnMbBII zpY%DCpP7TIXL6lg6y(f%igIS}{^M})dn@J4yOrK==)I`Er@)!LduoX)KIivF3M7v-)1-+;3_2g%P*W_jPHd3t6~(Y)bu z#y6C+?kUW-ql=Gm0 zYvetJIT-32${F8)GkS*e=#4?nnm6$0r7O0{-VNu8-f!RkH_rG5->1+cDQEOd-YETb zrf^o@AV0(T#I{*z(v&*+(6RPzS!gX+)9`N{Zk!WlguthU>f zE$4lc?Qx!Ze}q*%;~V^*40^_&@eS>fTs^mXD9-NRP(b>P^TRyzH-%x+1N6KkWLCOGRH zXWp&I8~8@6;kW*c^V&9%e%^3>gE@$A6zO)_eAhP3&l}7^@#2ixDAPkaMCYpzp0H!j;ravgeR z4nA`sxOb~R|J1|NzxNKSoK?^CNbD*220cG>5YFiNF7MmD>Mf7TdpCK*)pME38|8g) zdOz=Pw0QjndE-LwOf$<)l6??8`2dD6Z0gfn{9enaV^!y3fe{-4Fz!@ovml{?hZx;4JTh z-roJ>YxSgm#-9)6+Uxg7^!&WT;?E7o6R>4&g~@qvpWazXP2|i!2<dsUw-JrbPteNgA%n40;0&b$xin|46%ay4&!`>#5- zXWlUDaz@WO2iXVlXTGDS7v((#&gfbFS>CO#y$`~fIf!qNH<}c@WYjm{OpnAIM9)1h zl#m{&Pm3b9;q+4ep2E9T^n?nMpG(GJpH=>^kv-C*&DtLe2#QW};yulnKKj*}my&KNx`K7N{8SWU- z^ZvN}k~iSY-i@AjtctMcx!h}2gfqUuJ1luaIiqLwXL=;f&v1VC=e_3W{e7ett#^8{ z=$W2hIZyRGpFQP)8Q$L^`(>l}^X4CS2xsj#LOC-B)t`5nvKD{VzK%Ubdw!jRybscg z!kHdPIr9$NXj+tg}gYu>2)c2Li7j(ze^|L!Zx`)x?{tUVH(=|#Cmq8D}by!L~`K4olzUsqoeX+b&ghxEfj^^XI4}6s%Y~jlqi6hC`)BRz@MrW4=h91e8SU%PGkZ6_ zp?#hDMoyf`&#Gtkl=^=qh;P)Nc}RSN{#oy2*t_v(zSA7?bDa1_>-o8D$qo61vwk-L z=ZCK(`2B|JnfDa@nY^LzgZMLf!`%lz=<>7oTU0ybJ0SeI>^DX@vk#(Y@&@Mq?r zeh0+;2KROJNaT%gyI$8b{)}&^p7qWgJ*#iv&)>}1;Jq&TZpLS~xQY;jDV*`xKpnrz@2a&bklsZZ&w* z6qA4DM5Ff`x(_Doh!j1;dFVaK@(zn{Ok3~WlX(3zc|-3vT+XWJ_XqA0&iFI$DeQyl z&!0VcS@MSK&*+&sh;Pu>!I^g}^o&2>owiOm>m1bgl+fcH{8DN;hTb- z@eTB>ce&qxeZuEV-k^W3_VG6Hjp|(v7~KcaGw&(*hH^&F+>7EH>?yntqG#Px;H>W{ z%t89+r&AA@NyE3B>OIz&ne{WI-@u=_uOn}mkG$;T>6tmG{;Yat4w5&RgYKTf98}KQ z*Wnx5i*o-wEr1=8a0xg{|fd<{K!BR zDd?G8#~keL-IRL|^#!=?nJuj74?IoNtYq|8D3jo-WNFr~V0HxImWQ0`=M>Nh53 zP4M3b^*x3B3}?+7==sgY`^2Az>{{&m2AsJUZBsbjb~&5!w_cPzWzFI%qG#o-dS>s| zyn&vXgY1KP&#%5gu5*23$LsZd-$2j!v+9{Wg}oc!K+kaI9!cM=@Mq<$dkS|l?1N29 z=kht@8|axi__6nUlK2Lk*}M5}0?y<*^bF@*bvwqk1-6!?MpOy2?w^opMEBu*v zt1X`&H&4D0%yn=^&ywq|apt~GId|V!SU9tH;~Qy7>Ha+u`yhJOUQ~M|<*a(vyutm3 z>Y2Vya~=L%<%cwbp2-{P&v2&SK+npVyuqEhaz@YihThldKIq;Rz*+AY^ZxApuC>RR zz7BtO^$h3njr0AZp2-`^8Q)+I!WrLq>84WI?qmYFj~n(B{Q2I-apKR+!Q`e3#W&zg zen!tOXZDmUiD`bnfu0jS$!qmaX3_(X%e&RxyRPSF^sM>Wy%*K~89jeDY`bv2ZCqWs zMzR|zOHXlf8RIg`IYmoJ6D?(WBxK%<9AEXkDlr4 z$aVChp`2TKe>V(0lQ+KT8Rh#1?t1cbCA8e#kS-A{WF~TzMFjze^x!? z8}vwUj_6QD?vd1=mGjjio3e5C{#cZ+XEKV@3^Wz(*_GFo>uTM5EXZ)GIu6wIPreoQ& z!g=Ih-rrD7%8-5VqL-i1v-&f6<3^mBgF{<*|6ji58@DG_F!%;~UcY~UVIOQ=X}RP& zIMZ*OYWs7JoOSOeKXb>Z{*0bCj){`*Q}7M?I@dRD#2Mc}&+Z&l-*ELzk3_$roY@Cm z&g44ud}r8J$s3W=b6b4lXumgQPeIS?dV2lNr&CSt6Vna8fu7+^|6Ft7LD4h04u963 zU-hiLDE_RR@eTB>a}dtF59WEikfnciImf;|$mhIy{`5eZb}K~B=g%z`&gvWbo`OGX zFRJ&V=-KrR`i(Qaqy0UF`)B$M)$`btlje~QpPOErCW}9-Z}2@V`5E7M;bd9y4c@z% zgZdp1c_V!8Exw-h-3rcb-rzeR-Mi`e(KDQ%sxr-tDEpakrmxeyp`7sz^0RVgA9VAx z%lZ9xj`*B;A9Oi02bD9u=!zbLOs7KwB|qcO>Km$OIMa*b&%9fq=lOdIif`zh`9EVwh_%oaf42{l_v+5bnu5XZ^={M+~;f$WyQ{YT~M$hbnybrEQ z4zsWK``qWO@7=DR$Hf+req+A(n=<4&*EiU^@eSr6Jra8Ep197>8_YrO>(n>6f7U%k zIrDDi>e=;Ymoxs{^TkBzpY^VwNV%Y%M_mjPf2J4hS3js{IP;yR?kNZFX<=N>?t}2RnNRzY0u9b)Se$bD`)l;<;-2K?kT!=yPUaW9P(jipw*{SBtNU3;XHm`hP-!^ z>*$f1CSMSLhBMy*g>uF>LiMb^!Jb0DairE@^Frf6;?IBMd@XOlS$!jvGw-l2XTBd) z-ylDeH(cL*-n-Sy)oL0z)AJw7 z7;HAYm7VhkyVenc;FuS3tunR`*@pymzboF3_QQ4>?8M?%l^b>uqqtejmvt3SK_GkTsrJ?IIumGyV)`d_((ZeYg65DGLjA literal 36128 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 Cbt6^) literal 18128 zcmeI#v2IgQ5QJg7_x9``hpQ~Tf&EfR%IK9|RFMm9nwwvkG?eY6@|MkP|@#ZkUzuJE} z9nSBk&--tO^ZoJD-EO;ie*U|AvfcdNT=99dN<)5@99xs<`(bCWCbtg;MjZ@Z9 zZx7kd|0a9&KjiQ1{59u7m#=tTzTbCaXXpE@<{Yn1n+`iWqoPlE=}wlgvvcMX-|&h_ zeP?IqT56b^9(_v?4RP+fi-N_PmcFug_8(uM~@9gYcON|t-PMZ!pJENjccBgL!Jro+z8sOS@3x|1dB z?40?;H@sp}-`Ux@mKrHuoi-hIc1A^?@Y0 Date: Fri, 17 Feb 2023 14:52:19 +0000 Subject: [PATCH 10/27] requested changes --- cmake/Modules/Packages.cmake | 6 +-- include/libtomo/stripe.h | 15 ++++++ include/libtomo/stripes_detect3d.h | 68 -------------------------- source/libtomo/misc/utils.c | 51 ------------------- source/libtomo/misc/utils.h | 2 - source/libtomo/prep/CMakeLists.txt | 3 +- source/libtomo/prep/stripes_detect3d.c | 54 +++++++++++++++++++- source/tomopy/prep/stripe.py | 44 +++++++++-------- 8 files changed, 95 insertions(+), 148 deletions(-) delete mode 100644 include/libtomo/stripes_detect3d.h diff --git a/cmake/Modules/Packages.cmake b/cmake/Modules/Packages.cmake index 22a1fbe01..db324bdbe 100644 --- a/cmake/Modules/Packages.cmake +++ b/cmake/Modules/Packages.cmake @@ -219,12 +219,12 @@ if(TOMOPY_USE_CUDA) # Pascal support 70, 72 + Volta support 75 + Turing support if(NOT DEFINED CUDA_ARCH) if(CUDAToolkit_VERSION_MAJOR VERSION_LESS 11) - set(CUDA_ARCH "30;32;35;37;50;52;53;60;61;62;70;72;75") + set(CUDA_ARCH "50;52;53;60;61;62;70;72;75") else() if(CUDAToolkit_VERSION_MINOR VERSION_LESS 1) - set(CUDA_ARCH "35;37;50;52;53;60;61;62;70;72;75;80") + set(CUDA_ARCH "50;52;53;60;61;62;70;72;75;80") else() - set(CUDA_ARCH "35;37;50;52;53;60;61;62;70;72;75;80;86") + set(CUDA_ARCH "50;52;53;60;61;62;70;72;75;80;86") endif() endif() endif() diff --git a/include/libtomo/stripe.h b/include/libtomo/stripe.h index 73c52b02d..d42e2ed66 100644 --- a/include/libtomo/stripe.h +++ b/include/libtomo/stripe.h @@ -53,3 +53,18 @@ DLL void remove_stripe_sf(float* data, int dx, int dy, int dz, int size, int istart, int iend); + +DLL int +stripesdetect3d_main_float(float* Input, float* Output, + int window_halflength_vertical, + int ratio_radius, + int ncores, int dimX, int dimY, int dimZ); + +DLL int +stripesmask3d_main_float(float* Input, unsigned short* Output, + float threshold_val, + int stripe_length_min, + int stripe_depth_min, + int stripe_width_min, + float sensitivity, + int ncores, int dimX, int dimY, int dimZ); \ No newline at end of file diff --git a/include/libtomo/stripes_detect3d.h b/include/libtomo/stripes_detect3d.h deleted file mode 100644 index c12cda7c0..000000000 --- a/include/libtomo/stripes_detect3d.h +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2015, UChicago Argonne, LLC. All rights reserved. - -// Copyright 2015. UChicago Argonne, LLC. This software was produced -// under U.S. Government contract DE-AC02-06CH11357 for Argonne National -// Laboratory (ANL), which is operated by UChicago Argonne, LLC for the -// U.S. Department of Energy. The U.S. Government has rights to use, -// reproduce, and distribute this software. NEITHER THE GOVERNMENT NOR -// UChicago Argonne, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR -// ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is -// modified to produce derivative works, such modified software should -// be clearly marked, so as not to confuse it with the version available -// from ANL. - -// Additionally, redistribution and use in source and binary forms, with -// or without modification, are permitted provided that the following -// conditions are met: - -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. - -// * Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in -// the documentation and/or other materials provided with the -// distribution. - -// * Neither the name of UChicago Argonne, LLC, Argonne National -// Laboratory, ANL, the U.S. Government, nor the names of its -// contributors may be used to endorse or promote products derived -// from this software without specific prior written permission. - -// THIS SOFTWARE IS PROVIDED BY UChicago Argonne, LLC AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UChicago -// Argonne, LLC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// C-module for detecting and emphasising stripes present in the data (3D case) -// Original author: Daniil Kazantsev, Diamond Light Source Ltd. - -#pragma once - -#ifdef WIN32 -# define DLL __declspec(dllexport) -#else -# define DLL -#endif - -DLL int -stripesdetect3d_main_float(float* Input, float* Output, - int window_halflength_vertical, - int ratio_radius, - int ncores, int dimX, int dimY, int dimZ); - -DLL int -stripesmask3d_main_float(float* Input, unsigned short* Output, - float threshold_val, - int stripe_length_min, - int stripe_depth_min, - int stripe_width_min, - float sensitivity, - int ncores, int dimX, int dimY, int dimZ); diff --git a/source/libtomo/misc/utils.c b/source/libtomo/misc/utils.c index fa7ecab1f..cc1ec2e97 100644 --- a/source/libtomo/misc/utils.c +++ b/source/libtomo/misc/utils.c @@ -201,54 +201,3 @@ quicksort_uint16(unsigned short* x, int first, int last) } } -/* Calculate the forward difference derrivative of the 3D input in the direction of the "axis" parameter -using the step_size in pixels to skip pixels (i.e. step_size = 1 is the classical gradient) -axis = 0: horizontal direction -axis = 1: depth direction -axis = 2: vertical direction -*/ -void -gradient3D(float *input, float *output, long dimX, long dimY, long dimZ, int axis, int step_size) -{ - long i; - long j; - long k; - long i1; - long j1; - long k1; - long index; - -#pragma omp parallel for shared(input, output) private(i,j,k,i1,j1,k1,index) - for(j=0; j= dimX) - i1 = i - step_size; - output[index] = input[(dimX*dimY)*k + j*dimX+i1] - input[index]; - } - else if (axis == 1) - { - j1 = j + step_size; - if (j1 >= dimY) - j1 = j - step_size; - output[index] = input[(dimX*dimY)*k + j1*dimX+i] - input[index]; - } - else - { - k1 = k + step_size; - if (k1 >= dimZ) - k1 = k-step_size; - output[index] = input[(dimX*dimY)*k1 + j*dimX+i] - input[index]; - } - } - } - } -} diff --git a/source/libtomo/misc/utils.h b/source/libtomo/misc/utils.h index ea6021f71..80ea12fb7 100644 --- a/source/libtomo/misc/utils.h +++ b/source/libtomo/misc/utils.h @@ -63,5 +63,3 @@ void DLL quicksort_float(float* x, int first, int last); void DLL quicksort_uint16(unsigned short* x, int first, int last); -void DLL -gradient3D(float *input, float *output, long dimX, long dimY, long dimZ, int axis, int step_size); diff --git a/source/libtomo/prep/CMakeLists.txt b/source/libtomo/prep/CMakeLists.txt index d727553be..908dc6801 100755 --- a/source/libtomo/prep/CMakeLists.txt +++ b/source/libtomo/prep/CMakeLists.txt @@ -1,6 +1,5 @@ set(HEADERS "${tomopy_SOURCE_DIR}/include/libtomo/prep.h" - "${tomopy_SOURCE_DIR}/include/libtomo/stripe.h" - "${tomopy_SOURCE_DIR}/include/libtomo/stripes_detect3d.h") + "${tomopy_SOURCE_DIR}/include/libtomo/stripe.h") tomopy_add_library( tomo-prep diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index 21902a66a..329fa39ad 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -49,10 +49,62 @@ #include #include -#include "libtomo/stripes_detect3d.h" +#include "libtomo/stripe.h" #include "../misc/utils.h" +/* Calculate the forward difference derrivative of the 3D input in the direction of the "axis" parameter +using the step_size in pixels to skip pixels (i.e. step_size = 1 is the classical gradient) +axis = 0: horizontal direction +axis = 1: depth direction +axis = 2: vertical direction +*/ +void +gradient3D(float *input, float *output, long dimX, long dimY, long dimZ, int axis, int step_size) +{ + long i; + long j; + long k; + long i1; + long j1; + long k1; + long index; + +#pragma omp parallel for shared(input, output) private(i,j,k,i1,j1,k1,index) + for(j=0; j= dimX) + i1 = i - step_size; + output[index] = input[(dimX*dimY)*k + j*dimX+i1] - input[index]; + } + else if (axis == 1) + { + j1 = j + step_size; + if (j1 >= dimY) + j1 = j - step_size; + output[index] = input[(dimX*dimY)*k + j1*dimX+i] - input[index]; + } + else + { + k1 = k + step_size; + if (k1 >= dimZ) + k1 = k-step_size; + output[index] = input[(dimX*dimY)*k1 + j*dimX+i] - input[index]; + } + } + } + } +} + void ratio_mean_stride3d(float* input, float* output, int radius, diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 418d65e42..d2445fd74 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -943,7 +943,7 @@ def _remove_stripe_based_interpolation(tomo, snr, size, drop_ratio, norm): tomo[:, m, :] = sino -def stripes_detect3d(arr, vert_filter_size_perc=5, radius_size=3, ncore=None): +def stripes_detect3d(tomo, vert_filter_size_perc=5, radius_size=3, ncore=None): """ Apply a stripes detection method to empasize their edges in a 3D array. The input must be normalized projection data in range [0,1] and given in @@ -951,12 +951,13 @@ def stripes_detect3d(arr, vert_filter_size_perc=5, radius_size=3, ncore=None): this orientation, the stripes are the vertical features. The method works with full and partial stripes of constant ot varying intensity. - .. versionadded:: 1.13 + .. versionadded:: 1.14 Parameters ---------- - arr : ndarray - Input 3D array of float32 data type, normalized and in the following axis orientation [angles, detY(depth), detX (horizontal)]. + tomo : ndarray + 3D tomographic data of float32 data type, normalized [0,1] and given in + [angles, detY(depth), detX (horizontal)] axis orientation. vert_filter_size_perc : float, optional The size (in percents relative to angular dimension) of the vertical 1D median filter to remove outliers. radius_size : int, optional @@ -980,13 +981,13 @@ def stripes_detect3d(arr, vert_filter_size_perc=5, radius_size=3, ncore=None): if ncore is None: ncore = mproc.mp.cpu_count() - input_type = arr.dtype + input_type = tomo.dtype if (input_type != 'float32'): - arr = dtype.as_float32(arr) # silent convertion to float32 data type - out = np.empty_like(arr, order='C') + tomo = dtype.as_float32(tomo) # silent convertion to float32 data type + out = np.empty_like(tomo, order='C') - if arr.ndim == 3: - dz, dy, dx = arr.shape + if tomo.ndim == 3: + dz, dy, dx = tomo.shape if (dz == 0) or (dy == 0) or (dx == 0): raise ValueError("The length of one of dimensions is equal to zero") else: @@ -999,14 +1000,14 @@ def stripes_detect3d(arr, vert_filter_size_perc=5, radius_size=3, ncore=None): raise ValueError("vert_filter_size_perc value must be in (0, 100] percentage range ") # perform stripes detection - extern.c_stripes_detect3d(np.ascontiguousarray(arr), out, + extern.c_stripes_detect3d(np.ascontiguousarray(tomo), out, vertical_filter_size, radius_size, ncore, dx, dy, dz) return out -def stripes_mask3d(arr, +def stripes_mask3d(weights, threshold = 0.7, stripe_length_perc = 20.0, stripe_depth_perc = 1.0, @@ -1016,14 +1017,15 @@ def stripes_mask3d(arr, """ Takes the result of the stripes_detect3d module as an input and generates a binary 3D mask with ones where stripes present. The method tries to eliminate - non-stripe features by checking the consistency in three directions. + non-stripe features in data by checking the weight consistency in three directions. - .. versionadded:: 1.13 + .. versionadded:: 1.14 Parameters ---------- - arr : ndarray - 3D array (float32 data type) given as [angles, detY(depth), detX]; The input array is the result of stripes_detect3d module. + weights : ndarray + 3D weights array, a result of stripes_detect3d module given in + [angles, detY(depth), detX] axis orientation. threshold : float, optional Threshold for the given weights, the smaller values should correspond to the stripes stripe_length_perc : float, optional @@ -1052,13 +1054,13 @@ def stripes_mask3d(arr, if ncore is None: ncore = mproc.mp.cpu_count() - input_type = arr.dtype + input_type = weights.dtype if (input_type != 'float32'): - arr = dtype.as_float32(arr) # silent convertion to float32 data type - out = np.uint16(np.empty_like(arr, order='C')) + weights = dtype.as_float32(weights) # silent convertion to float32 data type + out = np.uint16(np.empty_like(weights, order='C')) - if arr.ndim == 3: - dz, dy, dx = arr.shape + if weights.ndim == 3: + dz, dy, dx = weights.shape if (dz == 0) or (dy == 0) or (dx == 0): raise ValueError("The length of one of dimensions is equal to zero") else: @@ -1084,7 +1086,7 @@ def stripes_mask3d(arr, # perform mask creation based on the input provided by stripes_detect3d module - extern.c_stripesmask3d(np.ascontiguousarray(arr), out, + extern.c_stripesmask3d(np.ascontiguousarray(weights), out, threshold, stripe_length_min, stripe_depth_min, From e4fece23e85ed0481a3d61f0fa0b4f3998b14992 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Tue, 21 Feb 2023 16:23:28 +0000 Subject: [PATCH 11/27] from 16-bit to 8 for the mask to save memory --- include/libtomo/stripe.h | 2 +- source/libtomo/prep/stripes_detect3d.c | 16 ++++++++-------- source/tomopy/prep/stripe.py | 4 ++-- source/tomopy/util/dtype.py | 5 +++++ source/tomopy/util/extern/prep.py | 2 +- test/test_tomopy/test_data/stripes_mask3d.npy | Bin 18128 -> 9128 bytes 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/include/libtomo/stripe.h b/include/libtomo/stripe.h index d42e2ed66..0c9e1e44f 100644 --- a/include/libtomo/stripe.h +++ b/include/libtomo/stripe.h @@ -61,7 +61,7 @@ stripesdetect3d_main_float(float* Input, float* Output, int ncores, int dimX, int dimY, int dimZ); DLL int -stripesmask3d_main_float(float* Input, unsigned short* Output, +stripesmask3d_main_float(float* Input, unsigned char* Output, float threshold_val, int stripe_length_min, int stripe_depth_min, diff --git a/source/libtomo/prep/stripes_detect3d.c b/source/libtomo/prep/stripes_detect3d.c index 329fa39ad..267a01544 100644 --- a/source/libtomo/prep/stripes_detect3d.c +++ b/source/libtomo/prep/stripes_detect3d.c @@ -234,8 +234,8 @@ vertical_median_stride3d(float* input, float* output, } void -remove_inconsistent_stripes(unsigned short* mask, - unsigned short* out, +remove_inconsistent_stripes(unsigned char* mask, + unsigned char* out, int stripe_length_min, int stripe_depth_min, float sensitivity, @@ -309,8 +309,8 @@ remove_inconsistent_stripes(unsigned short* mask, } void -merge_stripes(unsigned short* mask, - unsigned short* out, +merge_stripes(unsigned char* mask, + unsigned char* out, int stripe_width_min, long i, long j, @@ -425,7 +425,7 @@ stripesdetect3d_main_float(float* Input, float* Output, } DLL int -stripesmask3d_main_float(float* Input, unsigned short* Output, +stripesmask3d_main_float(float* Input, unsigned char* Output, float threshold_val, int stripe_length_min, int stripe_depth_min, @@ -440,8 +440,8 @@ stripesmask3d_main_float(float* Input, unsigned short* Output, long long totalvoxels; totalvoxels = (long long) (dimX*dimY*dimZ); - unsigned short* mask; - mask = calloc(totalvoxels, sizeof(unsigned short)); + unsigned char* mask; + mask = calloc(totalvoxels, sizeof(unsigned char)); /* dealing here with a custom given number of cpu threads */ if(ncores > 0) @@ -493,7 +493,7 @@ stripesmask3d_main_float(float* Input, unsigned short* Output, } } /* Copy output to mask */ - copyIm_unshort(Output, mask, (long) (dimX), (long) (dimY), (long) (dimZ)); + copyIm_unchar(Output, mask, (long) (dimX), (long) (dimY), (long) (dimZ)); /* We can merge stripes together if they are relatively close to each other based on the stripe_width_min parameter */ diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index d2445fd74..2265dc09d 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -1043,7 +1043,7 @@ def stripes_mask3d(weights, Returns ------- ndarray - A binary mask of uint16 data type with stripes highlighted. + A binary mask of uint8 data type with stripes highlighted. Raises ------ @@ -1057,7 +1057,7 @@ def stripes_mask3d(weights, input_type = weights.dtype if (input_type != 'float32'): weights = dtype.as_float32(weights) # silent convertion to float32 data type - out = np.uint16(np.empty_like(weights, order='C')) + out = np.uint8(np.empty_like(weights, order='C')) if weights.ndim == 3: dz, dy, dx = weights.shape diff --git a/source/tomopy/util/dtype.py b/source/tomopy/util/dtype.py index a17ec535d..a38aa91fa 100644 --- a/source/tomopy/util/dtype.py +++ b/source/tomopy/util/dtype.py @@ -117,6 +117,11 @@ def as_c_float_p(arr): return arr.ctypes.data_as(c_float_p) +def as_c_uint8_p(arr): + c_uint8_p = ctypes.POINTER(ctypes.c_uint8) + return arr.ctypes.data_as(c_uint8_p) + + def as_c_uint16_p(arr): c_uint16_p = ctypes.POINTER(ctypes.c_uint16) return arr.ctypes.data_as(c_uint16_p) diff --git a/source/tomopy/util/extern/prep.py b/source/tomopy/util/extern/prep.py index 399566439..e7f44742b 100644 --- a/source/tomopy/util/extern/prep.py +++ b/source/tomopy/util/extern/prep.py @@ -138,7 +138,7 @@ def c_stripesmask3d( LIB_TOMOPY_PREP.stripesmask3d_main_float.restype = dtype.as_c_void_p() LIB_TOMOPY_PREP.stripesmask3d_main_float( dtype.as_c_float_p(input), - dtype.as_c_uint16_p(output), + dtype.as_c_uint8_p(output), dtype.as_c_float(threshold_val), dtype.as_c_int(stripe_length_min), dtype.as_c_int(stripe_depth_min), diff --git a/test/test_tomopy/test_data/stripes_mask3d.npy b/test/test_tomopy/test_data/stripes_mask3d.npy index 94f98b199b5c989a9a342734f49f22e0e785c03e..24158a40d3185c4226ccc26c52bed800cfd5a4b9 100644 GIT binary patch literal 9128 zcmeI!F$%&k7{&2)^%U8n(4oc61Gp(J4o*^R#X(eBbx}{@g>5w@uY(9WxcrOYYlISh zeP8fColjwS4Fv)yuE=0&qU&bC!iuUDJwP}KMRG)ZDR ztUt*hwm195)idFW{1sL@>4#r=vPz{HW+J@aJ6LIaIA{~2B| s%%Bzo4K!3hGu=Q#1vJwQG*mz{-9SSHG}8?h($ literal 18128 zcmeH~F>ez=5QWbzzv8+Jk_!V}L`?@uLy;=7h!F)M$c8jgeg%IxKBX9aquF=bqX-gj z?PA}&dD`{o(Z%`m>GQ{{>&$v}Mb9cNw%=HiZ z@2A7Le)_imc{uBfH@n?-e0P5DUT??8am9Kylxs8aRW>z?FO%h|o969mgXy}bRb8KD z(synVey)?2?xoA5Ig?ISq?KKznKWm`YWRz1tT}74bV-YzR!n-HduZuiy5>wCT9KCS zrOTw}nRK!ut?Vkzq&X{A{yV7Y(B)*&TT<(@OnRO*Y0}cYbeS|~(#eXnva2+c=B!xx z`K;;C$6OHo;7LG z(!F$Cok5(OXjMvrKxPHEGh)y>yxUA2`S7pFexwBCVb* zlfCE0NxpNisCMRDQEAmktJPK2ne;qYjG{(bQD5FMY0jD$Y3W|NOqw(4WJOxpRhmh2 zR;*s`|CG4Z{C)K;e#1RkCVS6~lYHl3QSHpRqSC67R;#P3GwFG*7)6b=qQ1Oi(wsFh z($c+jnKWn8$%?eHt2C45tXTOslBUyg=Nao;oXVaolfCE0NxpNisCMRDQEAmktJPK2 zne;qYjG{(bQD5FMY0jD$Y3W|NOqw(4WJOxpRhmh2R;>KHPSa_*^NjT^PGwJ)$=-A0 zB;Pq$R6BF7sI+RN)#|G1OnROxMo}ZJs4wrBG-pkWv~(|BCe4|2vLdbQD$V4-#`y{L Cbt6^) From 74d3a1bb9b231fc0827e5099d7c1349fe2e852e6 Mon Sep 17 00:00:00 2001 From: Daniil Kazantsev Date: Mon, 27 Feb 2023 11:08:40 +0000 Subject: [PATCH 12/27] bugfixes, methods updates, tests --- include/libtomo/stripe.h | 18 +- source/libtomo/misc/utils.c | 11 + source/libtomo/misc/utils.h | 2 + source/libtomo/prep/stripes_detect3d.c | 546 +++++++++++++----- source/tomopy/prep/stripe.py | 95 ++- source/tomopy/util/dtype.py | 2 + source/tomopy/util/extern/prep.py | 32 +- .../test_data/stripes_detect3d.npy | Bin 36128 -> 36128 bytes test/test_tomopy/test_data/stripes_mask3d.npy | Bin 9128 -> 9128 bytes test/test_tomopy/test_prep/test_stripe.py | 14 +- 10 files changed, 482 insertions(+), 238 deletions(-) diff --git a/include/libtomo/stripe.h b/include/libtomo/stripe.h index 0c9e1e44f..7d341fc47 100644 --- a/include/libtomo/stripe.h +++ b/include/libtomo/stripe.h @@ -58,13 +58,15 @@ DLL int stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, int ratio_radius, - int ncores, int dimX, int dimY, int dimZ); + int ncores, + long dimX, long dimY, long dimZ); DLL int -stripesmask3d_main_float(float* Input, unsigned char* Output, - float threshold_val, - int stripe_length_min, - int stripe_depth_min, - int stripe_width_min, - float sensitivity, - int ncores, int dimX, int dimY, int dimZ); \ No newline at end of file +stripesmask3d_main_float(float* Input, + unsigned char* Output, + float threshold_val, + int stripe_length_min, + int stripe_depth_min, + int stripe_width_min, + float sensitivity, + int ncores, long dimX, long dimY, long dimZ); \ No newline at end of file diff --git a/source/libtomo/misc/utils.c b/source/libtomo/misc/utils.c index cc1ec2e97..c18b1ae40 100644 --- a/source/libtomo/misc/utils.c +++ b/source/libtomo/misc/utils.c @@ -42,6 +42,7 @@ // POSSIBILITY OF SUCH DAMAGE. #include "utils.h" +#include "stdio.h" // for windows build #ifdef WIN32 @@ -76,6 +77,16 @@ copyIm_unchar(const unsigned char* A, unsigned char* U, int dimX, int dimY, int U[j] = A[j]; } +/* Copy Image -unsigned char long long (8bit)*/ +void copyIm_unchar_long(unsigned char *A, unsigned char *U, long long totalvoxels) +{ + size_t j; +#pragma omp parallel for shared(A, U) private(j) + for (j = 0; j= dimX) i1 = i - step_size; - output[index] = input[(dimX*dimY)*k + j*dimX+i1] - input[index]; + output[index] = input[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + i1)] - input[index]; } else if (axis == 1) { j1 = j + step_size; if (j1 >= dimY) j1 = j - step_size; - output[index] = input[(dimX*dimY)*k + j1*dimX+i] - input[index]; + output[index] = input[(size_t)(dimX * dimY * k) + (size_t)(j1 * dimX + i)] - input[index]; } else { k1 = k + step_size; if (k1 >= dimZ) k1 = k-step_size; - output[index] = input[(dimX*dimY)*k1 + j*dimX+i] - input[index]; + output[index] = input[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] - input[index]; } } } } + return; } void ratio_mean_stride3d(float* input, float* output, int radius, - long i, long j, long k, long long index, + long i, long j, long k, long dimX, long dimY, long dimZ) { float mean_plate; @@ -123,20 +127,26 @@ ratio_mean_stride3d(float* input, float* output, long i1; long j1; long k1; + size_t index; + size_t newindex; + index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); + + min_val = 0.0f; /* calculate mean of gradientX in a 2D plate parallel to stripes direction */ mean_plate = 0.0f; for(j_m = -radius; j_m <= radius; j_m++) { j1 = j + j_m; - if((j1 < 0) || (j1 >= dimY)) + if ((j1 < 0) || (j1 >= dimY)) j1 = j - j_m; for(k_m = -radius; k_m <= radius; k_m++) { k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) - k1 = k - k_m; - mean_plate += fabsf(input[((dimX * dimY) * k1 + j1 * dimX + i)]); + k1 = k - k_m; + newindex = (size_t)(dimX * dimY * k1) + (size_t)(j1 * dimX + i); + mean_plate += fabsf(input[newindex]); } } mean_plate /= (float)(all_pixels_window); @@ -153,7 +163,8 @@ ratio_mean_stride3d(float* input, float* output, i1 = i + i_m; if (i1 >= dimX) i1 = i - i_m; - mean_horiz += fabsf(input[((dimX * dimY) * k + j1 * dimX + i1)]); + newindex = (size_t)(dimX * dimY * k) + (size_t)(j1 * dimX + i1); + mean_horiz += fabsf(input[newindex]); } } mean_horiz /= (float)(radius*3); @@ -170,7 +181,8 @@ ratio_mean_stride3d(float* input, float* output, i1 = i + i_m; if (i1 < 0) i1 = i - i_m; - mean_horiz2 += fabsf(input[((dimX * dimY) * k + j1 * dimX + i1)]); + newindex = (size_t)(dimX * dimY * k) + (size_t)(j1 * dimX + i1); + mean_horiz2 += fabsf(input[newindex]); } } mean_horiz2 /= (float)(radius*3); @@ -178,44 +190,37 @@ ratio_mean_stride3d(float* input, float* output, /* calculate the ratio between two means assuming that the mean orthogonal to stripes direction should be larger than the mean parallel to it */ - if ((mean_horiz > mean_plate) && (mean_horiz != 0.0f)) - { + if ((mean_horiz >= mean_plate) && (mean_horiz != 0.0f)) output[index] = mean_plate/mean_horiz; - } if ((mean_horiz < mean_plate) && (mean_plate != 0.0f)) - { output[index] = mean_horiz/mean_plate; - } - min_val = 0.0f; - if ((mean_horiz2 > mean_plate) && (mean_horiz2 != 0.0f)) - { - min_val = mean_plate/mean_horiz2; - } + if ((mean_horiz2 >= mean_plate) && (mean_horiz2 != 0.0f)) + min_val = mean_plate/mean_horiz2; if ((mean_horiz2 < mean_plate) && (mean_plate != 0.0f)) - { min_val = mean_horiz2/mean_plate; - } /* accepting the smallest value */ if (output[index] > min_val) - { output[index] = min_val; - } + return; } void vertical_median_stride3d(float* input, float* output, - int window_halflength_vertical, + int window_halflength_vertical, int window_fulllength, int midval_window_index, - long i, long j, long k, long long index, + long i, long j, long k, long dimX, long dimY, long dimZ) { int counter; long k_m; long k1; - float* _values; + size_t index; + + index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); + float* _values; _values = (float*) calloc(window_fulllength, sizeof(float)); counter = 0; @@ -224,13 +229,68 @@ vertical_median_stride3d(float* input, float* output, k1 = k + k_m; if((k1 < 0) || (k1 >= dimZ)) k1 = k-k_m; - _values[counter] = input[((dimX * dimY) * k1 + j * dimX + i)]; + _values[counter] = input[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)]; counter++; } quicksort_float(_values, 0, window_fulllength-1); output[index] = _values[midval_window_index]; free (_values); + return; +} + + +void +mean_stride3d(float* input, float* output, + long i, long j, long k, + long dimX, long dimY, long dimZ) +{ + /* a 3d mean to enusre a more stable gradient */ + long i1; + long i2; + long j1; + long j2; + long k1; + long k2; + float val1; + float val2; + float val3; + float val4; + float val5; + float val6; + size_t index; + + index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); + + i1 = i - 1; + i2 = i + 1; + j1 = j - 1; + j2 = j + 1; + k1 = k - 1; + k2 = k + 1; + + if (i1 < 0) + i1 = i2; + if (i2 >= dimX) + i2 = i1; + if (j1 < 0) + j1 = j2; + if (j2 >= dimY) + j2 = j1; + if (k1 < 0) + k1 = k2; + if (k2 >= dimZ) + k2 = k1; + + val1 = input[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + i1)]; + val2 = input[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + i2)]; + val3 = input[(size_t)(dimX * dimY * k) + (size_t)(j1 * dimX + i)]; + val4 = input[(size_t)(dimX * dimY * k) + (size_t)(j2 * dimX + i)]; + val5 = input[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)]; + val6 = input[(size_t)(dimX * dimY * k2) + (size_t)(j * dimX + i)]; + + output[index] = 0.1428f*(input[index] + val1 + val2 + val3 + val4 + val5 + val6); + return; } void @@ -239,13 +299,13 @@ remove_inconsistent_stripes(unsigned char* mask, int stripe_length_min, int stripe_depth_min, float sensitivity, + int switch_dim, long i, long j, long k, - long long index, long dimX, long dimY, long dimZ) { - int counter_vert_voxels; + int counter_vert_voxels; int counter_depth_voxels; int halfstripe_length = (int)stripe_length_min/2; int halfstripe_depth = (int)stripe_depth_min/2; @@ -253,125 +313,209 @@ remove_inconsistent_stripes(unsigned char* mask, long k1; long y_m; long y1; - int threshold_verical = (int)((0.01f*sensitivity)*stripe_length_min); + size_t index; + index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); + + int threshold_vertical = (int)((0.01f*sensitivity)*stripe_length_min); int threshold_depth = (int)((0.01f*sensitivity)*stripe_depth_min); - counter_vert_voxels = 0; - for(k_m = -halfstripe_length; k_m <= halfstripe_length; k_m++) + /* start by considering vertical features */ + if (switch_dim == 0) { - k1 = k + k_m; - if((k1 < 0) || (k1 >= dimZ)) - k1 = k - k_m; - if (mask[((dimX * dimY) * k1 + j * dimX + i)] == 1) + if (mask[index] == 1) { - counter_vert_voxels++; + counter_vert_voxels = 0; + for(k_m = -halfstripe_length; k_m <= halfstripe_length; k_m++) + { + k1 = k + k_m; + if((k1 < 0) || (k1 >= dimZ)) + k1 = k - k_m; + if (mask[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] == 1) + counter_vert_voxels++; + } + if (counter_vert_voxels < threshold_vertical) + out[index] = 0; } } - - /* Here we decide to keep the currect voxel based on the number of vertical voxels bellow it */ - if (counter_vert_voxels > threshold_verical) + else { - /* The vertical non zero values seem consistent, so we asssume that this element might belong to a stripe. */ - /* We do, however, need to check the depth consistency as well. Here we assume that the stripes are not very - extended in the depth dimension compared to the features that are belong to a sample. */ - - if (stripe_depth_min != 0) - { + /* + Considering the depth of features an removing the deep ones + Here we assume that the stripes do not normally extend far + in the depth dimension compared to the features that belong to a + sample. + */ + if (mask[index] == 1) + { + if (stripe_depth_min != 0) + { counter_depth_voxels = 0; for(y_m = -halfstripe_depth; y_m <= halfstripe_depth; y_m++) { y1 = j + y_m; if((y1 < 0) || (y1 >= dimY)) y1 = j - y_m; - if (mask[((dimX * dimY) * k + y1 * dimX + i)] == 1) - { + if (mask[(size_t)(dimX * dimY * k) + (size_t)(y1 * dimX + i)] == 1) counter_depth_voxels++; - } - } - if (counter_depth_voxels < threshold_depth) - { - out[index] = 1; } - else - { - out[index] = 0; + if (counter_depth_voxels > threshold_depth) + out[index] = 0; } } - else - { - out[index] = 1; - } } - else + return; +} + +void +remove_short_stripes(unsigned char* mask, + unsigned char* out, + int stripe_length_min, + long i, + long j, + long k, + long dimX, long dimY, long dimZ) +{ + int counter_vert_voxels; + int halfstripe_length = (int)stripe_length_min/2; + long k_m; + long k1; + size_t index; + index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); + + if (mask[index] == 1) { - out[index] = 0; + counter_vert_voxels = 0; + for(k_m = -halfstripe_length; k_m <= halfstripe_length; k_m++) + { + k1 = k + k_m; + if((k1 < 0) || (k1 >= dimZ)) + k1 = k - k_m; + if (mask[(size_t)(dimX * dimY * k1) + (size_t)(j * dimX + i)] == 1) + counter_vert_voxels++; + } + if (counter_vert_voxels < halfstripe_length) + out[index] = 0; } -} + return; +} void merge_stripes(unsigned char* mask, - unsigned char* out, + unsigned char* out, + int stripe_length_min, int stripe_width_min, long i, long j, long k, - long long index, long dimX, long dimY, long dimZ) -{ - - long x_m; - long x1; - long x2; - long x2_m; - - if (mask[index] == 1) - { - /* merging stripes in the horizontal direction */ - for(x_m=stripe_width_min; x_m>=0; x_m--) { - x1 = i + x_m; - if (x1 >= dimX) - x1 = i - x_m; - if (mask[((dimX * dimY) * k + j * dimX + x1)] == 1) - /*the other end of the mask has been found, merge all values inbetween */ +{ + int halfstripe_width = (int)stripe_width_min/2; + int vertical_length = 2*stripe_width_min; + + long x; + long x_l; + long x_r; + long k_u; + long k_d; + int mask_left; + int mask_right; + int mask_up; + int mask_down; + size_t index; + index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); + + if (mask[index] == 0) + { + /* checking if there is a mask to the left of zero */ + mask_left = 0; + for(x = -halfstripe_width; x <=0; x++) + { + x_l = i + x; + if (x_l < 0) + x_l = i - x; + if (mask[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + x_l)] == 1) + { + mask_left = 1; + break; + } + } + /* checking if there is a mask to the right of zero */ + mask_right = 0; + for(x = 0; x <= halfstripe_width; x++) + { + x_r = i + x; + if (x_r >= dimX) + x_r = i - x; + if (mask[(size_t)(dimX * dimY * k) + (size_t)(j * dimX + x_r)] == 1) { - for(x2 = 0; x2 <= x_m; x2++) - { - x2_m = i + x2; - out[((dimX * dimY) * k + j * dimX + x2_m)] = 1; - } - break; + mask_right = 1; + break; } - } + } + /* now if there is a mask from the left and from the right side of the zero value make it one */ + if ((mask_left == 1) && (mask_right == 1)) + out[index] = 1; + /* perform vertical merging */ + if (out[index] != 1) + { + /* checking if there is a mask up of zero */ + mask_up = 0; + for(x = -vertical_length; x <=0; x++) + { + k_u = k + x; + if (k_u < 0) + k_u = k - x; + if (mask[(size_t)(dimX * dimY * k_u) + (size_t)(j * dimX + i)] == 1) + { + mask_up = 1; + break; + } + } + /* checking if there is a mask down of zero */ + mask_down = 0; + for(x = 0; x <= vertical_length; x++) + { + k_d = k + x; + if (k_d >= dimZ) + k_d = k - x; + if (mask[(size_t)(dimX * dimY * k_d) + (size_t)(j * dimX + i)] == 1) + { + mask_down = 1; + break; + } + } + /* now if there is a mask above and bellow of the zero value make it one */ + if ((mask_up == 1) && (mask_down == 1)) + out[index] = 1; + } } + return; } - - -DLL int -stripesdetect3d_main_float(float* Input, float* Output, +/********************************************************************/ +/*************************stripesdetect3d****************************/ +/********************************************************************/ +DLL +int stripesdetect3d_main_float(float* Input, float* Output, int window_halflength_vertical, int ratio_radius, int ncores, - int dimX, int dimY, int dimZ) + long dimX, long dimY, long dimZ) { long i; long j; long k; - long long index; - long long totalvoxels; + long long totalvoxels; + totalvoxels = (long long)(dimX*dimY*dimZ); - float* gradient3d_x_arr; - float* mean_ratio3d_arr; - - totalvoxels = (long long) (dimX*dimY*dimZ); - - int window_fulllength = (int)(2*window_halflength_vertical + 1); + int window_fulllength = (int)(2*window_halflength_vertical + 1); int midval_window_index = (int)(0.5f*window_fulllength) - 1; - - gradient3d_x_arr = calloc(totalvoxels, sizeof(float)); - mean_ratio3d_arr = calloc(totalvoxels, sizeof(float)); - + + float* temp3d_arr; + temp3d_arr = malloc(totalvoxels * sizeof(float)); + if (temp3d_arr == NULL) printf("Allocation of the 'temp3d_arr' array failed"); + /* dealing here with a custom given number of cpu threads */ if(ncores > 0) { @@ -381,67 +525,95 @@ stripesdetect3d_main_float(float* Input, float* Output, omp_set_num_threads(ncores); } - /* Take the gradient in the horizontal direction, axis = 0*/ - gradient3D(Input, gradient3d_x_arr, (long) (dimX), (long) (dimY), (long) (dimZ), 0, 1); +/* Perform a gentle (6-stencil) 3d mean smoothing of the data to ensure more stability in the gradient calculation */ +#pragma omp parallel for shared(temp3d_arr) private(i, j, k) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) + { + for(i = 0; i < dimX; i++) + { + mean_stride3d(Input, temp3d_arr, + i, j, k, + dimX, dimY, dimZ); + } + } + } + + /* Take the gradient in the horizontal direction, axis = 0, step = 2*/ + gradient3D_local(Input, Output, dimX, dimY, dimZ, 0, 2); - /* Here we calculate the ratio between the mean in a small 2D neighbourhood parallel to the stripe + /* + Here we calculate a ratio between the mean in a small 2D neighbourhood parallel to the stripe and the mean orthogonal to the stripe. The gradient variation in the direction orthogonal to the stripe is expected to be large (a jump), while in parallel direction small. Therefore at the edges - of a stripe we should get a ratio small/large or large/small. */ -#pragma omp parallel for shared(gradient3d_x_arr, mean_ratio3d_arr) private(i, j, k, index) + of a stripe we should get a ratio small/large or large/small. + */ +#pragma omp parallel for shared(Output, temp3d_arr) private(i, j, k) for(k = 0; k < dimZ; k++) { for(j = 0; j < dimY; j++) { for(i = 0; i < dimX; i++) { - index = ((dimX * dimY) * k + j * dimX + i); - ratio_mean_stride3d(gradient3d_x_arr, mean_ratio3d_arr, ratio_radius, i, j, k, index, (long) (dimX), (long) (dimY), (long) (dimZ)); + ratio_mean_stride3d(Output, temp3d_arr, + ratio_radius, + i, j, k, + dimX, dimY, dimZ); } } } - /* We process the resulting ratio map with a vertical median filter which removes - small outliers of clusters */ -#pragma omp parallel for shared(mean_ratio3d_arr, Output) private(i, j, k, index) + + /* + We process the resulting ratio map with a vertical median filter which removes + inconsistent from longer stripes features + */ +#pragma omp parallel for shared(temp3d_arr, Output) private(i, j, k) for(k = 0; k < dimZ; k++) { for(j = 0; j < dimY; j++) { for(i = 0; i < dimX; i++) { - index = ((dimX * dimY) * k + j * dimX + i); - vertical_median_stride3d(mean_ratio3d_arr, Output, + vertical_median_stride3d(temp3d_arr, Output, window_halflength_vertical, window_fulllength, midval_window_index, - i, j, k, index, (long) (dimX), (long) (dimY), (long) (dimZ)); + i, j, k, + dimX, dimY, dimZ); } } } - free(gradient3d_x_arr); - free(mean_ratio3d_arr); + free(temp3d_arr); return 0; } -DLL int -stripesmask3d_main_float(float* Input, unsigned char* Output, - float threshold_val, - int stripe_length_min, - int stripe_depth_min, - int stripe_width_min, - float sensitivity, - int ncores, int dimX, int dimY, int dimZ) +/********************************************************************/ +/*************************stripesmask3d******************************/ +/********************************************************************/ +DLL +int stripesmask3d_main_float(float* Input, + unsigned char* Output, + float threshold_val, + int stripe_length_min, + int stripe_depth_min, + int stripe_width_min, + float sensitivity, + int ncores, long dimX, long dimY, long dimZ) { long i; long j; long k; - long long index; + int iter_merge; + int switch_dim; + size_t index; long long totalvoxels; - totalvoxels = (long long) (dimX*dimY*dimZ); + totalvoxels = (long long)(dimX*dimY*dimZ); - unsigned char* mask; - mask = calloc(totalvoxels, sizeof(unsigned char)); + unsigned char* mask; + mask = malloc(totalvoxels * sizeof(unsigned char)); + if (mask == NULL) printf("Allocation of the 'mask' array failed"); /* dealing here with a custom given number of cpu threads */ if(ncores > 0) @@ -452,8 +624,10 @@ stripesmask3d_main_float(float* Input, unsigned char* Output, omp_set_num_threads(ncores); } - /* First step is to mask all the values in the given weights input image - that are bellow a given "threshold_val" parameter */ + /* + First step is to mask all the values in the given weights input image + that are bellow a given "threshold_val" parameter + */ #pragma omp parallel for shared(Input, mask) private(i, j, k, index) for(k = 0; k < dimZ; k++) { @@ -461,58 +635,118 @@ stripesmask3d_main_float(float* Input, unsigned char* Output, { for(i = 0; i < dimX; i++) { - index = ((dimX * dimY) * k + j * dimX + i); - if (Input[index] <= threshold_val) - { + index = (size_t)(dimX * dimY * k) + (size_t)(j * dimX + i); + if (Input[index] <= threshold_val) mask[index] = 1; - } + else + mask[index] = 0; + } + } + } + + /* Copy mask to output */ + copyIm_unchar_long(mask, Output, totalvoxels); + + /* the depth consistency for features */ + switch_dim = 1; +#pragma omp parallel for shared(mask, Output) private(i, j, k) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) + { + for(i = 0; i < dimX; i++) + { + remove_inconsistent_stripes(mask, Output, + stripe_length_min, + stripe_depth_min, + sensitivity, + switch_dim, + i, j, k, + dimX, dimY, dimZ); } } } - /* Then we need to remove stripes that are shorter than "stripe_length_min" parameter + /* Copy output to mask */ + copyIm_unchar_long(Output, mask, totalvoxels); + + /* + Now we need to remove stripes that are shorter than "stripe_length_min" parameter or inconsistent otherwise. For every pixel we will run a 1D vertical window to count nonzero values in the mask. We also check for the depth of the mask's value, assuming that the stripes are normally shorter in depth compare to the features that - belong to true data */ -#pragma omp parallel for shared(mask, Output) private(i, j, k, index) + belong to true data. + */ + +/*continue by including long vertical features and discarding shorter ones */ + switch_dim = 0; +#pragma omp parallel for shared(mask, Output) private(i, j, k) for(k = 0; k < dimZ; k++) { for(j = 0; j < dimY; j++) { for(i = 0; i < dimX; i++) { - index = ((dimX * dimY) * k + j * dimX + i); + remove_inconsistent_stripes(mask, Output, stripe_length_min, stripe_depth_min, sensitivity, - i, j, k, index, - (long) (dimX), (long) (dimY), (long) (dimZ)); + switch_dim, + i, j, k, + dimX, dimY, dimZ); + } + } + } + /* Copy output to mask */ + copyIm_unchar_long(Output, mask, totalvoxels); + + /* now we clean the obtained mask if the features do not hold our assumptions about the lengths */ + +#pragma omp parallel for shared(mask, Output) private(i, j, k) + for(k = 0; k < dimZ; k++) + { + for(j = 0; j < dimY; j++) + { + for(i = 0; i < dimX; i++) + { + + remove_short_stripes(mask, Output, + stripe_length_min, + i, j, k, + dimX, dimY, dimZ); } } } + /* Copy output to mask */ - copyIm_unchar(Output, mask, (long) (dimX), (long) (dimY), (long) (dimZ)); + copyIm_unchar_long(Output, mask, totalvoxels); - /* We can merge stripes together if they are relatively close to each other - based on the stripe_width_min parameter */ -#pragma omp parallel for shared(mask, Output) private(i, j, k, index) + /* + We can merge stripes together if they are relatively close to each other + horizontally and vertically. We do that iteratively. + */ + for(iter_merge = 0; iter_merge < stripe_width_min; iter_merge++) + { +#pragma omp parallel for shared(mask, Output) private(i, j, k) for(k = 0; k < dimZ; k++) { for(j = 0; j < dimY; j++) { for(i = 0; i < dimX; i++) { - index = ((dimX * dimY) * k + j * dimX + i); merge_stripes(mask, Output, + stripe_length_min, stripe_width_min, - i, j, k, index, - (long) (dimX), (long) (dimY), (long) (dimZ)); + i, j, k, + dimX, dimY, dimZ); } } - } + } + /* Copy output to mask */ + copyIm_unchar_long(Output, mask, totalvoxels); + } free(mask); return 0; -} +} \ No newline at end of file diff --git a/source/tomopy/prep/stripe.py b/source/tomopy/prep/stripe.py index 2265dc09d..aaebc513a 100644 --- a/source/tomopy/prep/stripe.py +++ b/source/tomopy/prep/stripe.py @@ -942,26 +942,25 @@ def _remove_stripe_based_interpolation(tomo, snr, size, drop_ratio, norm): sino = _rs_interpolation(sino, snr, size, drop_ratio, norm) tomo[:, m, :] = sino - -def stripes_detect3d(tomo, vert_filter_size_perc=5, radius_size=3, ncore=None): +def stripes_detect3d(tomo, size=10, radius=3, ncore=None): """ Apply a stripes detection method to empasize their edges in a 3D array. The input must be normalized projection data in range [0,1] and given in the following axis orientation [angles, detY(depth), detX (horizontal)]. With this orientation, the stripes are the vertical features. The method works with full and partial stripes of constant ot varying intensity. - + .. versionadded:: 1.14 Parameters ---------- - tomo : ndarray + inputData : ndarray 3D tomographic data of float32 data type, normalized [0,1] and given in [angles, detY(depth), detX (horizontal)] axis orientation. - vert_filter_size_perc : float, optional - The size (in percents relative to angular dimension) of the vertical 1D median filter to remove outliers. - radius_size : int, optional - The size of the filter to calculate the ratio. This will effect the width of the resulting mask. + size : int, optional + The pixel size of the vertical 1D median filter to minimise false detections. Increase it if you have longer or full stripes in the data. + radius : int, optional + The pixel size of the stencil to calculate the mean ratio between vertical and horizontal orientations. The larger values will enlarge the mask width. ncore : int, optional Number of cores that will be assigned to jobs. All cores will be used if unspecified. @@ -975,9 +974,8 @@ def stripes_detect3d(tomo, vert_filter_size_perc=5, radius_size=3, ncore=None): Raises ------ ValueError - If the input array is not three dimensional. - - """ + If the input array is not three dimensional. + """ if ncore is None: ncore = mproc.mp.cpu_count() @@ -993,27 +991,26 @@ def stripes_detect3d(tomo, vert_filter_size_perc=5, radius_size=3, ncore=None): else: raise ValueError("The input array must be a 3D array") - # calculate absolute values based on the provided percentages: - if 0.0 < vert_filter_size_perc <= 100.0: - vertical_filter_size = (int)((0.01*vert_filter_size_perc)*dz) - else: - raise ValueError("vert_filter_size_perc value must be in (0, 100] percentage range ") + if size <= 0 or size > dz // 2: + raise ValueError("The size of the filter should be larger than zero and smaller than the half of the vertical dimension") # perform stripes detection - extern.c_stripes_detect3d(np.ascontiguousarray(tomo), out, - vertical_filter_size, - radius_size, + extern.c_stripes_detect3d(np.ascontiguousarray(tomo), + out, + size, + radius, ncore, dx, dy, dz) return out + def stripes_mask3d(weights, - threshold = 0.7, - stripe_length_perc = 20.0, - stripe_depth_perc = 1.0, - stripe_width_perc = 2.0, - sensitivity_perc = 80.0, - ncore=None): + threshold = 0.6, + min_stripe_length = 20, + min_stripe_depth = 10, + min_stripe_width = 5, + sensitivity_perc = 85.0, + ncore=None): """ Takes the result of the stripes_detect3d module as an input and generates a binary 3D mask with ones where stripes present. The method tries to eliminate @@ -1027,13 +1024,13 @@ def stripes_mask3d(weights, 3D weights array, a result of stripes_detect3d module given in [angles, detY(depth), detX] axis orientation. threshold : float, optional - Threshold for the given weights, the smaller values should correspond to the stripes - stripe_length_perc : float, optional - Parameter (in percents) that controls the minimum accepted length of a stripe relative to the full angular dimension. - stripe_depth_perc : float, optional - Parameter (in percents) that controls the minimum accepted depth of a stripe relative to the depth dimension. - stripe_width_perc : float, optional - Parameter (in percents) that controls the minimum accepted width of a stripe relative to the full horizontal dimension. + Threshold for the given weights, the smaller values correspond to the stripes + min_stripe_length : int, optional + Minimum accepted length of a stripe in pixels. Can be large if there are full stripes in the data. + min_stripe_depth : int, optional + Minimum accepted depth of a stripe in pixels. The stripes do not extend very deep, with this parameter more non-stripe features can be removed. + min_stripe_width : int, optional + Minimum accepted width of a stripe in pixels. The stripes can be merged together with this parameter. sensitivity_perc : float, optional The value in percents to impose less strict conditions on length, depth and width of a stripe. ncore : int, optional @@ -1065,32 +1062,28 @@ def stripes_mask3d(weights, raise ValueError("The length of one of dimensions is equal to zero") else: raise ValueError("The input array must be a 3D array") - - # calculate absolute values based on the provided percentages: - if 0.0 < stripe_length_perc <= 100.0: - stripe_length_min = (int)((0.01*stripe_length_perc)*dz) - else: - raise ValueError("stripe_length_perc value must be in (0, 100] percentage range ") - if 0.0 <= stripe_depth_perc <= 100.0: - stripe_depth_min = (int)((0.01*stripe_depth_perc)*dy) - else: - raise ValueError("stripe_depth_perc value must be in [0, 100] percentage range ") - if 0.0 < stripe_width_perc <= 100.0: - stripe_width_min = (int)((0.01*stripe_width_perc)*dx) - else: - raise ValueError("stripe_width_perc value must be in (0, 100] percentage range ") + + if min_stripe_length <= 0 or min_stripe_length >= dz: + raise ValueError("The minimum length of a stripe cannot be zero or exceed the size of the angular dimension") + + if min_stripe_depth < 0 or min_stripe_depth >= dy: + raise ValueError("The minimum depth of a stripe cannot exceed the size of the depth dimension") + + if min_stripe_width <= 0 or min_stripe_width >= dx: + raise ValueError("The minimum width of a stripe cannot be zero or exceed the size of the horizontal dimension") + if 0.0 < sensitivity_perc <= 100.0: pass else: raise ValueError("sensitivity_perc value must be in (0, 100] percentage range ") - # perform mask creation based on the input provided by stripes_detect3d module - extern.c_stripesmask3d(np.ascontiguousarray(weights), out, + extern.c_stripesmask3d(np.ascontiguousarray(weights), + out, threshold, - stripe_length_min, - stripe_depth_min, - stripe_width_min, + min_stripe_length, + min_stripe_depth, + min_stripe_width, sensitivity_perc, ncore, dx, dy, dz) diff --git a/source/tomopy/util/dtype.py b/source/tomopy/util/dtype.py index a38aa91fa..a81de4403 100644 --- a/source/tomopy/util/dtype.py +++ b/source/tomopy/util/dtype.py @@ -130,6 +130,8 @@ def as_c_uint16_p(arr): def as_c_int(arr): return ctypes.c_int(arr) +def as_c_long(arr): + return ctypes.c_long(arr) def as_c_int_p(arr): arr = arr.astype(np.intc, copy=False) diff --git a/source/tomopy/util/extern/prep.py b/source/tomopy/util/extern/prep.py index e7f44742b..ce2633d71 100644 --- a/source/tomopy/util/extern/prep.py +++ b/source/tomopy/util/extern/prep.py @@ -102,8 +102,8 @@ def c_remove_stripe_sf(tomo, size): def c_stripes_detect3d( input, output, - window_halflength_vertical, - radius_size, + size, + radius, ncore, dx, dy, @@ -113,12 +113,12 @@ def c_stripes_detect3d( LIB_TOMOPY_PREP.stripesdetect3d_main_float( dtype.as_c_float_p(input), dtype.as_c_float_p(output), - dtype.as_c_int(window_halflength_vertical), - dtype.as_c_int(radius_size), + dtype.as_c_int(size), + dtype.as_c_int(radius), dtype.as_c_int(ncore), - dtype.as_c_int(dx), - dtype.as_c_int(dy), - dtype.as_c_int(dz), + dtype.as_c_long(dx), + dtype.as_c_long(dy), + dtype.as_c_long(dz), ) return output @@ -126,9 +126,9 @@ def c_stripesmask3d( input, output, threshold_val, - stripe_length_min, - stripe_depth_min, - stripe_width_min, + min_stripe_length, + min_stripe_depth, + min_stripe_width, sensitivity_perc, ncore, dx, @@ -140,13 +140,13 @@ def c_stripesmask3d( dtype.as_c_float_p(input), dtype.as_c_uint8_p(output), dtype.as_c_float(threshold_val), - dtype.as_c_int(stripe_length_min), - dtype.as_c_int(stripe_depth_min), - dtype.as_c_int(stripe_width_min), + dtype.as_c_int(min_stripe_length), + dtype.as_c_int(min_stripe_depth), + dtype.as_c_int(min_stripe_width), dtype.as_c_float(sensitivity_perc), dtype.as_c_int(ncore), - dtype.as_c_int(dx), - dtype.as_c_int(dy), - dtype.as_c_int(dz), + dtype.as_c_long(dx), + dtype.as_c_long(dy), + dtype.as_c_long(dz), ) return output diff --git a/test/test_tomopy/test_data/stripes_detect3d.npy b/test/test_tomopy/test_data/stripes_detect3d.npy index c043e6e62c1bdb54d3a872a1a9e36a3df71a812d..eb6bb13bd61d548cb5388601091560f6fab955c2 100644 GIT binary patch literal 36128 zcmbWA2T+ygwuY$|s)`_j0@74$1iQG`XN$eYp4e-wi7lEKd+!<(d+cKGUDVi7v7>?r z_J-K7VAuP<3)i22Z@`?nch1ar&$%;q&OGNm&wAI|8<<(YX}yLmt*w5s8eFhb=YAdg z7Odb`uws`|1&jI>?DA9J{(al`Zu?W;PMz_uS8w05UuW%K@AqT-pF3;+ywEqHp+)^l zYX1l=T-0xf-~Z!(9O`zip~6C5l6U)Ds(Nw~`SrC`roXOH$w98FNT<70rDXy2VB}jm z^Pn`9s~131%9<#)v59=znaDJ267AS@jRp;AWh$F(wQ2gvd-Qr`EVZltmQGE)OpTtt zqoFAYw0o+Zii~?jgly@oU}JOO5RxPd%e!=%=JxbR+v4nz7?N z?X0+(dj0&0cE5T=p|?&@$A#mlcXJ<{|Mu*YHouTw%&H!@x7)?(HB$?W-EkNOw zo>IS?52$|6w>0+5T?%TDObxuY(9z$m)R2@N2Cx`QZr_4mro1pN=>~b*E)z z&OIw-QFAJMr|>4PE~Q@W$f8<1d8#L`g4E=v?#y}e9%ps)W)4->-%IBlou`Bu=ia;0 zILF_Z%bb6I9HROM*r~mKj;eo&?CChCtggtM@mg|jQ0@#Ji@!m$^37n*Vb(iz&Lh3@ z(&2YSGvz%0{^xX@BO7K_m$TdGoV}8ZGUs+F^*GOix45W23!j*AMxKYRu~&uq+@_!N z)~CUL6`&rEi_y)Ql~GEuH7Srz!!NI z&b?D@b| zxyIE%&hzgxY!vc5EXYo!roJa5=Uk;7)rHz_3NP~fOT8$v zZ-Ddb;T7mj)3H>wR~*?-TFu@-o`+1dQt$@y?3`%NoZ*d!s}8a^`r3N4HwsP&P{*}@ zkH$QE2RJLqbE!hPm~-i2gP1eC0nXNM8#3pVv6rdu&gG^_rO&fBCKb^BJD_ovJojpr zNM6@mmDAJPtD&nL4@7g=<`@AanpLNW+QutHd8{XgV;XLOr^;fz)?+N#%zz+V@ zWNs6Bx#T##KKs_xX;Ba%&n1GK)HzQNC7j_6MSAn*jFP|Z2_*uLG&eykp&wd7HW1g$+_gBdC`Sv;4 z&&czJ$9b4D{A|hj%<p1cp1ZT{_xJ#uKyn*-BptLy%&dBq_7j|lJx7#{rc!Pe(t?s^=LhuIsoLQb{47|*F zZZ|%a=0x75I!*4eH+DXJOW=$ggEQWB6}{AtIc(I|I3v&CoVeeKR^0TVh+g6BjU7LZ zXFuDuYHI>#=^KmR-_^YV&i7okp7G0e&NIAW^z+}Fvhf_;^RuVwx*#v-`9|}6+&AE7 za6UP}js0xo-2JyJW`1tq+&Rza8&h6|Q*@4r zI%nw{@bkN6kC}6&;noVh4xBLui>%0{qF;q5ywW$oS@P^u-A@fU;HuBTc0oy$cytnF zOY|hy4Ta3+pe1L_LG-$%Dd#BeY*uyux}Ey$;-h{T=b+Ft;ztJY8#&L9IoCe>M(2z? zkLvwYpMyhVuTtljKWJ-Gc3RcaiRa+l1wlLqg>%waC-w$YykZ^{d z(KjsfY~;Mfg}B$@g*Ue6xJBT6{`m{`#^MVPbk53UGl4U_5m_fEO-XfO&d4+R#;Gw; zdY;D>w^PWocmsWdn&03#IQ(iV_h8lcZX^bPn~IK$823vb{>o-H|J4kFK(gU0?0&VzsRWIrR%=o{c%I`D-F zy{>-Or|f6hQ^0w4>r>1b-T>c49dqfNk!SJ7{J)$P_7s_e;EX(jv&=zbo{gN*>yT$l z&d777-q1J~z3i1P&)^I{gER8nePbbg4kFKA?yc!Sl6;rO~Z3?o~73z&(kBDajygC*^OfLJw-T6uM^JuO$GEhSj(dla}Io7 zg6E)-GwvJU9CY7K;l6>sA-xWME-)g9UOI(PYVoxa9d<{