Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding median3d and dezinger3d filters into tomopy.misc.corr #596

Merged
merged 24 commits into from Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8828b6a
work on adding median/dezinger3d has started
dkazanc Dec 19, 2022
fac87c5
working version of the wrapper
dkazanc Jan 2, 2023
59446ee
added utils files
dkazanc Jan 2, 2023
b053d4d
completes implementation of median and dezinger 3d, tests added
dkazanc Jan 4, 2023
65f1779
reverting Packages
dkazanc Jan 4, 2023
01326ac
DOC: Add some text to docstrings
carterbox Jan 4, 2023
6ac0084
BLD: Use modern CMake to link OpenMP
carterbox Jan 5, 2023
464868d
BLD: Address compiler warning
carterbox Jan 5, 2023
2aa7271
STY: Format all new code with clang-format, yapf
carterbox Jan 5, 2023
c43b5eb
STY: Run clang-tidy on new code
carterbox Jan 5, 2023
77b4340
resolving suggestions
dkazanc Jan 5, 2023
26211e4
Use more concise conversion from size to half_filter width
carterbox Jan 5, 2023
b931cb4
REF: Use longlong for any linear index of volume
carterbox Jan 6, 2023
51566bb
adds 2D version of the filter, fixes suggestions and changes the test
dkazanc Jan 9, 2023
5a5ec1c
2D filtering has been removed
dkazanc Jan 10, 2023
03e9274
Require OpenMP_C
carterbox Jan 10, 2023
a7b12ff
BLD: Don't shadow OpenMP link with project flags
carterbox Jan 10, 2023
074c009
BLD: Move math.h public link to correct module
carterbox Jan 10, 2023
9c564b4
STY: Formatting and unused includes
carterbox Jan 10, 2023
bee4e18
REF: Remove some functions from public API
carterbox Jan 10, 2023
f3c0500
STY: Update docstrings for new python functions
carterbox Jan 10, 2023
f0b0506
BLD: Add openmp:experimental on Windows
carterbox Jan 10, 2023
8eedf30
BUG: Readd stdlib.h to includes
carterbox Jan 10, 2023
c4b321a
Merge branch 'master' into median
dkazanc Jan 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 33 additions & 18 deletions source/tomopy/misc/corr.py
Expand Up @@ -348,25 +348,24 @@ def callback(total, description, unit):
return arr


def median_filter3d(arr, kernel_half_size=1, ncore=None):
def median_filter3d(arr, size=3, ncore=None):
"""
Apply 3D median filter to 3D array.

Parameters
----------
arr : ndarray
Input 3D array.
kernel_half_size : int, optional
The half size of the filter's kernel, i.e. 1 results in the full kernel
size of 3 x 3 x 3.
Input 3D array either float32 or uint16 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
Median filtered 3D array.
Median filtered 3D array either float32 or uint16 data type.
Raises
------
ValueError
Expand All @@ -375,7 +374,7 @@ def median_filter3d(arr, kernel_half_size=1, ncore=None):
"""
carterbox marked this conversation as resolved.
Show resolved Hide resolved
input_type = arr.dtype
if (input_type != 'float32') and (input_type != 'uint16'):
arr = dtype.as_float32(arr) # silently convert to float32 data type
arr = dtype.as_float32(arr) # silent convertion to float32 data type
out = np.empty_like(arr)
dif = 0.0 # set to 0 to avoid selective filtering

Expand All @@ -387,7 +386,16 @@ def median_filter3d(arr, kernel_half_size=1, ncore=None):
if ncore is None:
ncore = mproc.mp.cpu_count()

#deal with different data types
# convert the full kernel size (odd int) to a half size as the C function requires
if size < 3:
size = 3 # check if the kernel size is not too small
if (size % 2) == 0:
# dealing with even integers
kernel_half_size = (int)(0.5*size)
else:
kernel_half_size = (int)(0.5*(size-1))
carterbox marked this conversation as resolved.
Show resolved Hide resolved

# deal with different data types
if (input_type == 'float32'):
extern.c_median_filt3d_float32(arr, out, kernel_half_size, dif, ncore,
dx, dy, dz)
Expand All @@ -397,29 +405,27 @@ def median_filter3d(arr, kernel_half_size=1, ncore=None):
return out


def remove_outlier3d(arr, kernel_half_size=1, dif=0.1, ncore=None):
def remove_outlier3d(arr, dif=0.1, size=3, ncore=None):
"""
Also a so-called dezinger. Selectively applies 3D median filter to 3D array
to remove outliers specifically.
Selectively applies 3D median filter to a 3D array to remove outliers specifically. Also called a dezinger.

Parameters
----------
arr : ndarray
Input 3D array.
kernel_half_size : int, optional
The half size of the filter's kernel, i.e. 1 results in the full kernel
size of 3 x 3 x 3.
Input 3D array either float32 or uint16 data type.
dif : float
Expected difference value between outlier value and the median value of
the array.
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
Dezingered 3D array.
Dezingered 3D array either float32 or uint16 data type.
Raises
------
ValueError
Expand All @@ -428,7 +434,7 @@ def remove_outlier3d(arr, kernel_half_size=1, dif=0.1, ncore=None):
"""
carterbox marked this conversation as resolved.
Show resolved Hide resolved
input_type = arr.dtype
if (input_type != 'float32') and (input_type != 'uint16'):
arr = dtype.as_float32(arr) # silently convert to float32 data type
arr = dtype.as_float32(arr) # silent convertion to float32 data type
out = np.empty_like(arr)

if np.ndim(arr) == 3:
Expand All @@ -439,7 +445,16 @@ def remove_outlier3d(arr, kernel_half_size=1, dif=0.1, ncore=None):
if ncore is None:
ncore = mproc.mp.cpu_count()

#deal with different data types
# convert the full kernel size (odd int) to a half size as the C function requires
if size < 3:
size = 3 # check if the kernel size is not too small
if (size % 2) == 0:
# dealing with even integers
kernel_half_size = (int)(0.5*size)
else:
kernel_half_size = (int)(0.5*(size-1))
carterbox marked this conversation as resolved.
Show resolved Hide resolved

# deal with different data types
if (input_type == 'float32'):
extern.c_median_filt3d_float32(arr, out, kernel_half_size, dif, ncore,
dx, dy, dz)
Expand Down
Binary file added test/test_tomopy/test_data/median_filter3d.npy
Binary file not shown.
35 changes: 10 additions & 25 deletions test/test_tomopy/test_misc/test_corr.py
Expand Up @@ -48,7 +48,7 @@
import unittest

import numpy as np
from numpy.testing import assert_allclose
from numpy.testing import assert_allclose, assert_equal
from tomopy.misc.corr import (
gaussian_filter,
median_filter,
Expand Down Expand Up @@ -113,32 +113,17 @@ def test_median_filter_nonfinite(self):
)

def test_median_filter3d(self):
# generate a small noncubic volume and apply random noise
A = np.float32(np.zeros((30,40,20)))
A[:] = 0.1
A[10:20,20:30,:] = 0.5
np.random.seed(1)
A_noise = A + np.random.normal(loc=0,
scale=0.03 * A,
size=np.shape(A))
A_filtered = median_filter3d(np.float32(A_noise))
max_filtered = 0.513833
assert_allclose(np.max(A_filtered), max_filtered, rtol=1e-06)
A = np.arange(4*5*6).reshape(4,5,6)
A_median = median_filter3d(np.float32(A))
assert_equal(read_file('median_filter3d.npy'), A_median)

def test_remove_outlier3d(self):
# generate a small noncubic volume and apply random noise with an outlier
A = np.float32(np.zeros((30,40,20)))
A[:] = 0.1
A[10:20,20:30,:] = 0.5
np.random.seed(1)
A_noise = A + np.random.normal(loc=0,
scale=0.03 * A,
size=np.shape(A))
A[15,25,0] = 1.5 # placing an outlier
A_filtered = remove_outlier3d(np.float32(A_noise), kernel_half_size=1, dif = 0.5)
max_filtered = 0.56252176
assert_allclose(np.max(A_filtered), max_filtered, rtol=1e-06)

A = np.arange(4*5*6).reshape(4,5,6)
A[2,2,2] = 1000.0 # introduce an outlier
A_dezinged = remove_outlier3d(np.float32(A), dif = 500, size=3)
A[2,2,2] = 75 # substituted value by dezinger
assert_equal(np.float32(A), A_dezinged)

def test_remove_neg(self):
assert_allclose(remove_neg([-2, -1, 0, 1, 2]), [0, 0, 0, 1, 2])

Expand Down