/*########################################################################## # # Copyright (c) 2017-2019 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. # # ###########################################################################*/ // __authors__ = ["H. Payno"] // __license__ = "MIT" // __date__ = "10/02/2017" #ifndef MEDIAN_FILTER #define MEDIAN_FILTER #include #include #include #include #include #include #include /* Needed for pytohn2.7 on Windows... */ #ifndef INFINITY #define INFINITY (DBL_MAX+DBL_MAX) #endif #ifndef NAN #define NAN (INFINITY-INFINITY) #endif // Modes for the median filter enum MODE{ NEAREST=0, REFLECT=1, MIRROR=2, SHRINK=3, CONSTANT=4, }; // Simple function browsing a deque and registering the min and max values // and if those values are unique or not template void getMinMax(std::vector& v, T& min, T&max, typename std::vector::const_iterator end){ // init min and max values typename std::vector::const_iterator it = v.begin(); if (v.size() == 0){ raise(SIGINT); }else{ min = max = *it; } it++; // Browse all the deque while(it!=end){ // check if repeated (should always be before min/max setting) T value = *it; if(value > max) max = value; if(value < min) min = value; it++; } } // apply the median filter only on limited part of the vector // In case of even number of elements (either due to NaNs in the window // or for image borders in shrink mode): // the highest of the 2 central values is returned template inline T median(std::vector& v, int window_size) { int pivot = window_size / 2; std::nth_element(v.begin(), v.begin() + pivot, v.begin()+window_size); return v[pivot]; } // return the index into 0, (length_max - 1) in reflect mode inline int reflect(int index, int length_max){ int res = index; // if the index is negative get the positive symmetrical value if(res < 0){ res += 1; res = -res; } // then apply the reflect algorithm. Frequency is 2 max length res = res % (2*length_max); if(res >= length_max){ res = 2*length_max - res -1; res = res % length_max; } return res; } // return the index into 0, (length_max - 1) in mirror mode inline int mirror(int index, int length_max){ int res = index; // if the index is negative get the positive symmetrical value if(res < 0){ res = -res; } int rightLimit = length_max -1; // apply the redundancy each two right limit res = res % (2*rightLimit); if(res >= length_max){ int distToRedundancy = (2*rightLimit) - res; res = distToRedundancy; } return res; } /* Provide a way to access NaN that also works for integers*/ template inline T NotANumber(void) { assert(false); //This should never be called return 0; } template<> inline float NotANumber(void) { return NAN; } template<> inline double NotANumber(void) { return NAN; } // Browse the column of pixel_x template void median_filter( const T* input, T* output, int* kernel_dim, // two values : 0:width, 1:height int* image_dim, // two values : 0:width, 1:height int y_pixel, // the x pixel to process int x_pixel_range_min, int x_pixel_range_max, bool conditional, int pMode, T cval) { assert(kernel_dim[0] > 0); assert(kernel_dim[1] > 0); assert(y_pixel >= 0); assert(image_dim[0] > 0); assert(image_dim[1] > 0); assert(y_pixel >= 0); assert(y_pixel < image_dim[0]); assert(x_pixel_range_max < image_dim[1]); assert(x_pixel_range_min <= x_pixel_range_max); // kernel odd assertion assert((kernel_dim[0] - 1)%2 == 0); assert((kernel_dim[1] - 1)%2 == 0); // # this should be move up to avoid calculation each time int halfKernel_x = (kernel_dim[1] - 1) / 2; int halfKernel_y = (kernel_dim[0] - 1) / 2; MODE mode = static_cast(pMode); // init buffer std::vector window_values(kernel_dim[0]*kernel_dim[1]); bool not_horizontal_border = (y_pixel >= halfKernel_y && y_pixel < image_dim[0] - halfKernel_y); for(int x_pixel=x_pixel_range_min; x_pixel <= x_pixel_range_max; x_pixel ++ ){ typename std::vector::iterator it = window_values.begin(); // fill the vector if (not_horizontal_border && x_pixel >= halfKernel_x && x_pixel < image_dim[1] - halfKernel_x) { //This is not a border, just fill it for(int win_y=y_pixel-halfKernel_y; win_y<= y_pixel+halfKernel_y; win_y++) { for(int win_x = x_pixel-halfKernel_x; win_x <= x_pixel+halfKernel_x; win_x++){ T value = input[win_y*image_dim[1] + win_x]; if (value == value) { // Ignore NaNs *it = value; ++it; } } } } else { // This is a border, handle the special case for(int win_y=y_pixel-halfKernel_y; win_y<= y_pixel+halfKernel_y; win_y++) { for(int win_x = x_pixel-halfKernel_x; win_x <= x_pixel+halfKernel_x; win_x++) { T value = 0; int index_x = win_x; int index_y = win_y; switch(mode){ case NEAREST: index_x = std::min(std::max(win_x, 0), image_dim[1] - 1); index_y = std::min(std::max(win_y, 0), image_dim[0] - 1); value = input[index_y*image_dim[1] + index_x]; break; case REFLECT: index_x = reflect(win_x, image_dim[1]); index_y = reflect(win_y, image_dim[0]); value = input[index_y*image_dim[1] + index_x]; break; case MIRROR: index_x = mirror(win_x, image_dim[1]); // deal with 1d case if(win_y == 0 && image_dim[0] == 1){ index_y = 0; }else{ index_y = mirror(win_y, image_dim[0]); } value = input[index_y*image_dim[1] + index_x]; break; case SHRINK: if ((index_x < 0) || (index_x > image_dim[1] -1) || (index_y < 0) || (index_y > image_dim[0] -1)) { continue; } value = input[index_y*image_dim[1] + index_x]; break; case CONSTANT: if ((index_x < 0) || (index_x > image_dim[1] -1) || (index_y < 0) || (index_y > image_dim[0] -1)) { value = cval; } else { value = input[index_y*image_dim[1] + index_x]; } break; } if (value == value) { // Ignore NaNs *it = value; ++it; } } } } //window_size can be smaller than kernel size in shrink mode or if there is NaNs int window_size = std::distance(window_values.begin(), it); if (window_size == 0) { // Window is empty, this is the case when all values are NaNs output[image_dim[1]*y_pixel + x_pixel] = NotANumber(); } else { // apply the median value if needed for this pixel const T currentPixelValue = input[image_dim[1]*y_pixel + x_pixel]; if (conditional == true){ typename std::vector::iterator window_end = window_values.begin() + window_size; T min = 0; T max = 0; getMinMax(window_values, min, max, window_end); // NaNs are propagated through unchanged if ((currentPixelValue == max) || (currentPixelValue == min)){ output[image_dim[1]*y_pixel + x_pixel] = median(window_values, window_size); }else{ output[image_dim[1]*y_pixel + x_pixel] = currentPixelValue; } }else{ output[image_dim[1]*y_pixel + x_pixel] = median(window_values, window_size); } } } } #endif // MEDIAN_FILTER