diff options
Diffstat (limited to 'src/silx/math/medianfilter/medianfilter.pyx')
-rw-r--r-- | src/silx/math/medianfilter/medianfilter.pyx | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/src/silx/math/medianfilter/medianfilter.pyx b/src/silx/math/medianfilter/medianfilter.pyx new file mode 100644 index 0000000..f4b7c51 --- /dev/null +++ b/src/silx/math/medianfilter/medianfilter.pyx @@ -0,0 +1,495 @@ +# /*########################################################################## +# +# Copyright (c) 2015-2018 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""This module provides median filter function for 1D and 2D arrays. +""" + +__authors__ = ["H. Payno", "J. Kieffer"] +__license__ = "MIT" +__date__ = "02/05/2017" + + +from cython.parallel import prange +cimport cython +cimport silx.math.medianfilter.median_filter as median_filter +import numpy +cimport numpy as cnumpy +from libcpp cimport bool + +import numbers + +ctypedef unsigned long uint64 +ctypedef unsigned int uint32 +ctypedef unsigned short uint16 + + +MODES = {'nearest': 0, 'reflect': 1, 'mirror': 2, 'shrink': 3, 'constant': 4} + + +def medfilt1d(data, + kernel_size=3, + bool conditional=False, + mode='nearest', + cval=0): + """Function computing the median filter of the given input. + + Behavior at boundaries: the algorithm is reducing the size of the + window/kernel for pixels at boundaries (there is no mirroring). + + Not-a-Number (NaN) float values are ignored. + If the window only contains NaNs, it evaluates to NaN. + + In event of an even number of valid values in the window (either + because of NaN values or on image border in shrink mode), + the highest of the 2 central sorted values is taken. + + :param numpy.ndarray data: the array for which we want to apply + the median filter. Should be 1d. + :param kernel_size: the dimension of the kernel. + :type kernel_size: int + :param bool conditional: True if we want to apply a conditional median + filtering. + :param str mode: the algorithm used to determine how values at borders + are determined: 'nearest', 'reflect', 'mirror', 'shrink', 'constant' + :param cval: Value used outside borders in 'constant' mode + + :returns: the array with the median value for each pixel. + """ + return medfilt(data, kernel_size, conditional, mode, cval) + + +def medfilt2d(image, + kernel_size=3, + bool conditional=False, + mode='nearest', + cval=0): + """Function computing the median filter of the given input. + Behavior at boundaries: the algorithm is reducing the size of the + window/kernel for pixels at boundaries (there is no mirroring). + + Not-a-Number (NaN) float values are ignored. + If the window only contains NaNs, it evaluates to NaN. + + In event of an even number of valid values in the window (either + because of NaN values or on image border in shrink mode), + the highest of the 2 central sorted values is taken. + + :param numpy.ndarray data: the array for which we want to apply + the median filter. Should be 2d. + :param kernel_size: the dimension of the kernel. + :type kernel_size: For 1D should be an int for 2D should be a tuple or + a list of (kernel_height, kernel_width) + :param bool conditional: True if we want to apply a conditional median + filtering. + :param str mode: the algorithm used to determine how values at borders + are determined: 'nearest', 'reflect', 'mirror', 'shrink', 'constant' + :param cval: Value used outside borders in 'constant' mode + + :returns: the array with the median value for each pixel. + """ + return medfilt(image, kernel_size, conditional, mode, cval) + + +def medfilt(data, + kernel_size=3, + bool conditional=False, + mode='nearest', + cval=0): + """Function computing the median filter of the given input. + Behavior at boundaries: the algorithm is reducing the size of the + window/kernel for pixels at boundaries (there is no mirroring). + + Not-a-Number (NaN) float values are ignored. + If the window only contains NaNs, it evaluates to NaN. + + In event of an even number of valid values in the window (either + because of NaN values or on image border in shrink mode), + the highest of the 2 central sorted values is taken. + + :param numpy.ndarray data: the array for which we want to apply + the median filter. Should be 1d or 2d. + :param kernel_size: the dimension of the kernel. + :type kernel_size: For 1D should be an int for 2D should be a tuple or + a list of (kernel_height, kernel_width) + :param bool conditional: True if we want to apply a conditional median + filtering. + :param str mode: the algorithm used to determine how values at borders + are determined: 'nearest', 'reflect', 'mirror', 'shrink', 'constant' + :param cval: Value used outside borders in 'constant' mode + + :returns: the array with the median value for each pixel. + """ + if mode not in MODES: + err = 'Requested mode %s is unknown.' % mode + raise ValueError(err) + + if data.ndim > 2: + raise ValueError( + "Invalid data shape. Dimension of the array should be 1 or 2") + + # Handle case of scalar kernel size + if isinstance(kernel_size, numbers.Integral): + kernel_size = [kernel_size] * data.ndim + + assert len(kernel_size) == data.ndim + + # Convert 1D arrays to 2D + reshaped = False + if len(data.shape) == 1: + data = data.reshape(1, data.shape[0]) + kernel_size = [1, kernel_size[0]] + reshaped = True + + # simple median filter apply into a 2D buffer + output_buffer = numpy.zeros_like(data) + check(data, output_buffer) + + ker_dim = numpy.array(kernel_size, dtype=numpy.int32) + + if data.dtype == numpy.float64: + medfilterfc = _median_filter_float64 + elif data.dtype == numpy.float32: + medfilterfc = _median_filter_float32 + elif data.dtype == numpy.int64: + medfilterfc = _median_filter_int64 + elif data.dtype == numpy.uint64: + medfilterfc = _median_filter_uint64 + elif data.dtype == numpy.int32: + medfilterfc = _median_filter_int32 + elif data.dtype == numpy.uint32: + medfilterfc = _median_filter_uint32 + elif data.dtype == numpy.int16: + medfilterfc = _median_filter_int16 + elif data.dtype == numpy.uint16: + medfilterfc = _median_filter_uint16 + else: + raise ValueError("%s type is not managed by the median filter" % data.dtype) + + medfilterfc(input_buffer=data, + output_buffer=output_buffer, + kernel_size=ker_dim, + conditional=conditional, + mode=MODES[mode], + cval=cval) + + if reshaped: + output_buffer.shape = -1 # Convert to 1D array + + return output_buffer + + +def check(input_buffer, output_buffer): + """Simple check on the two buffers to make sure we can apply the median filter + """ + if (input_buffer.flags['C_CONTIGUOUS'] is False): + raise ValueError('<input_buffer> must be a C_CONTIGUOUS numpy array.') + + if (output_buffer.flags['C_CONTIGUOUS'] is False): + raise ValueError('<output_buffer> must be a C_CONTIGUOUS numpy array.') + + if not (len(input_buffer.shape) <= 2): + raise ValueError('<input_buffer> dimension must mo higher than 2.') + + if not (len(output_buffer.shape) <= 2): + raise ValueError('<output_buffer> dimension must mo higher than 2.') + + if not(input_buffer.dtype == output_buffer.dtype): + raise ValueError('input buffer and output_buffer must be of the same type') + + if not (input_buffer.shape == output_buffer.shape): + raise ValueError('input buffer and output_buffer must be of the same dimension and same dimension') + + +######### implementations of the include/median_filter.hpp function ############ +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def reflect(int index, int length_max): + """find the correct index into [0, length_max-1] for index in reflect mode + + :param int index: the index to move into [0, length_max-1] in reflect mode + :param int length_max: the higher bound limit + """ + return median_filter.reflect(index, length_max) + + +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def mirror(int index, int length_max): + """find the correct index into [0, length_max-1] for index in mirror mode + + :param int index: the index to move into [0, length_max-1] in mirror mode + :param int length_max: the higher bound limit + """ + return median_filter.mirror(index, length_max) + + +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def _median_filter_float32(float[:, ::1] input_buffer not None, + float[:, ::1] output_buffer not None, + cnumpy.int32_t[::1] kernel_size not None, + bool conditional, + int mode, + float cval): + + cdef: + int y = 0 + int image_dim = input_buffer.shape[1] - 1 + int[2] buffer_shape + buffer_shape[0] = input_buffer.shape[0] + buffer_shape[1] = input_buffer.shape[1] + + for y in prange(input_buffer.shape[0], nogil=True): + median_filter.median_filter[float](<float*> & input_buffer[0,0], + <float*> & output_buffer[0,0], + <int*>& kernel_size[0], + <int*>buffer_shape, + y, + 0, + image_dim, + conditional, + mode, + cval) + + +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def _median_filter_float64(double[:, ::1] input_buffer not None, + double[:, ::1] output_buffer not None, + cnumpy.int32_t[::1] kernel_size not None, + bool conditional, + int mode, + double cval): + + cdef: + int y = 0 + int image_dim = input_buffer.shape[1] - 1 + int[2] buffer_shape + buffer_shape[0] = input_buffer.shape[0] + buffer_shape[1] = input_buffer.shape[1] + + for y in prange(input_buffer.shape[0], nogil=True): + median_filter.median_filter[double](<double*> & input_buffer[0, 0], + <double*> & output_buffer[0, 0], + <int*>&kernel_size[0], + <int*>buffer_shape, + y, + 0, + image_dim, + conditional, + mode, + cval) + + +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def _median_filter_int64(cnumpy.int64_t[:, ::1] input_buffer not None, + cnumpy.int64_t[:, ::1] output_buffer not None, + cnumpy.int32_t[::1] kernel_size not None, + bool conditional, + int mode, + cnumpy.int64_t cval): + + cdef: + int y = 0 + int image_dim = input_buffer.shape[1] - 1 + int[2] buffer_shape + buffer_shape[0] = input_buffer.shape[0] + buffer_shape[1] = input_buffer.shape[1] + + for y in prange(input_buffer.shape[0], nogil=True): + median_filter.median_filter[long](<long*> & input_buffer[0,0], + <long*> & output_buffer[0, 0], + <int*>&kernel_size[0], + <int*>buffer_shape, + y, + 0, + image_dim, + conditional, + mode, + cval) + +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def _median_filter_uint64(cnumpy.uint64_t[:, ::1] input_buffer not None, + cnumpy.uint64_t[:, ::1] output_buffer not None, + cnumpy.int32_t[::1] kernel_size not None, + bool conditional, + int mode, + cnumpy.uint64_t cval): + + cdef: + int y = 0 + int image_dim = input_buffer.shape[1] - 1 + int[2] buffer_shape + buffer_shape[0] = input_buffer.shape[0] + buffer_shape[1] = input_buffer.shape[1] + + for y in prange(input_buffer.shape[0], nogil=True): + median_filter.median_filter[uint64](<uint64*> & input_buffer[0,0], + <uint64*> & output_buffer[0, 0], + <int*>&kernel_size[0], + <int*>buffer_shape, + y, + 0, + image_dim, + conditional, + mode, + cval) + + +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def _median_filter_int32(cnumpy.int32_t[:, ::1] input_buffer not None, + cnumpy.int32_t[:, ::1] output_buffer not None, + cnumpy.int32_t[::1] kernel_size not None, + bool conditional, + int mode, + cnumpy.int32_t cval): + + cdef: + int y = 0 + int image_dim = input_buffer.shape[1] - 1 + int[2] buffer_shape + buffer_shape[0] = input_buffer.shape[0] + buffer_shape[1] = input_buffer.shape[1] + + for y in prange(input_buffer.shape[0], nogil=True): + median_filter.median_filter[int](<int*> & input_buffer[0,0], + <int*> & output_buffer[0, 0], + <int*>&kernel_size[0], + <int*>buffer_shape, + y, + 0, + image_dim, + conditional, + mode, + cval) + + +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def _median_filter_uint32(cnumpy.uint32_t[:, ::1] input_buffer not None, + cnumpy.uint32_t[:, ::1] output_buffer not None, + cnumpy.int32_t[::1] kernel_size not None, + bool conditional, + int mode, + cnumpy.uint32_t cval): + + cdef: + int y = 0 + int image_dim = input_buffer.shape[1] - 1 + int[2] buffer_shape + buffer_shape[0] = input_buffer.shape[0] + buffer_shape[1] = input_buffer.shape[1] + + for y in prange(input_buffer.shape[0], nogil=True): + median_filter.median_filter[uint32](<uint32*> & input_buffer[0,0], + <uint32*> & output_buffer[0, 0], + <int*>&kernel_size[0], + <int*>buffer_shape, + y, + 0, + image_dim, + conditional, + mode, + cval) + + +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def _median_filter_int16(cnumpy.int16_t[:, ::1] input_buffer not None, + cnumpy.int16_t[:, ::1] output_buffer not None, + cnumpy.int32_t[::1] kernel_size not None, + bool conditional, + int mode, + cnumpy.int16_t cval): + + cdef: + int y = 0 + int image_dim = input_buffer.shape[1] - 1 + int[2] buffer_shape + buffer_shape[0] = input_buffer.shape[0] + buffer_shape[1] = input_buffer.shape[1] + + for y in prange(input_buffer.shape[0], nogil=True): + median_filter.median_filter[short](<short*> & input_buffer[0,0], + <short*> & output_buffer[0, 0], + <int*>&kernel_size[0], + <int*>buffer_shape, + y, + 0, + image_dim, + conditional, + mode, + cval) + + +@cython.cdivision(True) +@cython.boundscheck(False) +@cython.wraparound(False) +@cython.initializedcheck(False) +def _median_filter_uint16( + cnumpy.uint16_t[:, ::1] input_buffer not None, + cnumpy.uint16_t[:, ::1] output_buffer not None, + cnumpy.int32_t[::1] kernel_size not None, + bool conditional, + int mode, + cnumpy.uint16_t cval): + + cdef: + int y = 0 + int image_dim = input_buffer.shape[1] - 1 + int[2] buffer_shape, + buffer_shape[0] = input_buffer.shape[0] + buffer_shape[1] = input_buffer.shape[1] + + for y in prange(input_buffer.shape[0], nogil=True): + median_filter.median_filter[uint16](<uint16*> & input_buffer[0, 0], + <uint16*> & output_buffer[0, 0], + <int*>&kernel_size[0], + <int*>buffer_shape, + y, + 0, + image_dim, + conditional, + mode, + cval) |