I coded a bilateral filter class in cython based on numpy's neighborhood 
iterator (thanks to T. J's code example).  I tried to parallel the code by 
replacing the standard loop (commented line 150) by a prange loop (line 151). 
The result are series of compilation errors mainly due the the use of 
iterators. Is there an *easy* way to work with nmpy iterators while the GIL is 
released?

Platfoem: numpy 1.6.1 on python 2.7.2 and cython 0.15.1
system: gcc on linux

   Nadav
# A Cython + Neighbourhood based biliteral filter
# Nadav Horesh
###########################################################
#
#  Convert double buffer to short
#  >> After a comparison between the paraller and the serial versions
#


cimport numpy as np
import numpy as np
import cython
from cython.parallel import prange

cdef extern from "math.h":
    double exp(double x)

cdef extern from "math.h":
    float expf(float x)
    float fabsf(float  x)

cdef extern:
    int abs(int x)


############   T J UNMODIFIED CODE #############

cdef extern from "numpy/arrayobject.h":

    ctypedef extern class numpy.flatiter [object PyArrayIterObject]:
        cdef int nd_m1
        cdef np.npy_intp index, size
        cdef np.ndarray ao
        cdef char *dataptr                

    # This isn't exposed to the Python API.
    # So we can't use the same approach we used to define flatiter
    ctypedef struct PyArrayNeighborhoodIterObject:
        int nd_m1
        np.npy_intp index, size
        np.PyArrayObject *ao # note the change from np.ndarray
        char *dataptr
        
    object PyArray_NeighborhoodIterNew(flatiter it, np.npy_intp* bounds,
                                       int mode, np.ndarray fill_value)
    int PyArrayNeighborhoodIter_Next(PyArrayNeighborhoodIterObject *it)
    int PyArrayNeighborhoodIter_Reset(PyArrayNeighborhoodIterObject *it)
    
    object PyArray_IterNew(object arr)
    void PyArray_ITER_NEXT(flatiter it)
    np.npy_intp PyArray_SIZE(np.ndarray arr)

    cdef enum:
        NPY_NEIGHBORHOOD_ITER_ZERO_PADDING,
        NPY_NEIGHBORHOOD_ITER_ONE_PADDING,
        NPY_NEIGHBORHOOD_ITER_CONSTANT_PADDING,
        NPY_NEIGHBORHOOD_ITER_CIRCULAR_PADDING,
        NPY_NEIGHBORHOOD_ITER_MIRROR_PADDING

np.import_array()

############  END OF T J UNMODIFIED CODE #############

cdef int GAUSS_SAMP = 32
cdef int GAUSS_IDX_MAX = GAUSS_SAMP - 1


class Cbilateral_filter(object):
    '''
    A fully cythonic simple bilateral filtering.

    The class provides the bilaterl filter function to be called by
    generic_filter.
    initialization parameters:
      spat_sig:    The sigma of the spatial Gaussian filter
      inten_sig:   The sigma of the gray-levels Gaussian filter
      filter_size: (int) The size of the spatial convolution kernel. If
                   not set, it is set to ~ 4*spat_sig.
    '''
    def __init__(self, float spat_sig, float inten_sig, filter_size=None):
        if filter_size is not None and filter_size >= 2:
            self.xy_size = int(filter_size)
        else:
            self.xy_size = int(round(spat_sig*4))
            # Make filter size odd
            self.xy_size += 1-self.xy_size%2
        x = np.arange(self.xy_size, dtype=np.float32)
        x = (x-x.mean())**2
        #xy_ker: Spatial convolution kernel
        self.xy_ker = np.exp(-np.add.outer(x,x)/(2*spat_sig**2)).ravel()
        self.xy_ker /= self.xy_ker.sum()
        self.inten_sig = 2 * inten_sig**2
        # self.index in the coordinate of the middle point
        self.index = (self.xy_size+1) * (self.xy_size // 2)

        ## An initialization for LUT instead of a Gaussian function call
        ## (for the fc_filter method)

        x = np.linspace(0,3.0, GAUSS_SAMP)
        self.gauss_lut = np.exp(-x**2/2)
        self.x_quant = 3*inten_sig / GAUSS_IDX_MAX
        self.gauss_lut_float32 = np.exp(-x**2/2).astype(np.float32)


#    @cython.boundscheck(False)
#    @cython.wraparound(False)
    @cython.cdivision(True)
    def filterf(self, np.ndarray[dtype=np.float32_t, ndim=2] data not None):

        # Define an iterator over the input array (image)
        cdef arr_iter = PyArray_IterNew(<object>data)
        # xsize, ysize: Input array dimensions
        cdef unsigned int ysize = data.shape[0], xsize=data.shape[1]

        # Define output array and a c pointer to iterate over it 
        cdef np.ndarray[np.float32_t, ndim=2] output = np.empty_like(data)
        cdef float *out_ptr = <float *>output.data

        # Get the alreasy initialized spatial and  "z" Gaussian kernels
        cdef np.ndarray[dtype=np.float32_t, ndim=1] kernel = self.xy_ker
        cdef np.ndarray[dtype=np.float32_t, ndim=1] gauss_lut_arr = 
self.gauss_lut_float32


        # Iterators over the spatial kernel and input data
        cdef float *pdata = <float *>data.data, *pker=<float *>kernel.data
        cdef float *gauss_lut = <float *>gauss_lut_arr.data  # C pointer to 
iterate over "z"

        # Misc temporary data for the convolution
        cdef float sigma   = self.inten_sig
        cdef float weight_i, weight, result, centre, dat_i
        cdef float x_quant = self.x_quant

         # Misc untegers, loop indeces and spatial kernel size
        cdef unsigned int i, j, k, centre_idx, exp_i

        cdef int dim = self.xy_size
        cdef unsigned int ker_lentgh  = dim*dim

        # Neighbourhood iteraor
        cdef np.npy_intp* bounds_ptr = [-dim//2, dim//2, -dim//2, dim//2]
        # Create the Python object and keep a reference to it
        cdef object neigh_iter = PyArray_NeighborhoodIterNew(arr_iter,
            bounds_ptr, NPY_NEIGHBORHOOD_ITER_MIRROR_PADDING, None)
        cdef PyArrayNeighborhoodIterObject *pneigh_iter = \
            <PyArrayNeighborhoodIterObject *> neigh_iter

        if not data.flags.c_contiguous:
            raise ValueError('Input array must be C contiguous')

        for i in range(ysize):
#            for j in range(xsize):
            for j in prange(xsize, nogil=True):
                centre_idx = i*xsize+j
                centre = pdata[centre_idx]
                weight = 1.0E-30
                result = 0.0
                for k in range(ker_lentgh):
                    dat_i = (<float *>pneigh_iter.dataptr)[0]
                    exp_i = <int>(0.5 + fabsf((dat_i-centre) / x_quant))
                    weight_i = 0.0
                    if exp_i <= GAUSS_IDX_MAX:
                        weight_i = gauss_lut[exp_i] * pker[k]
                    weight += weight_i;
                    result += dat_i * weight_i
                    PyArrayNeighborhoodIter_Next(pneigh_iter)
                out_ptr[centre_idx] = result/weight
                PyArray_ITER_NEXT(arr_iter)
                PyArrayNeighborhoodIter_Reset(pneigh_iter)
        return output
_______________________________________________
NumPy-Discussion mailing list
NumPy-Discussion@scipy.org
http://mail.scipy.org/mailman/listinfo/numpy-discussion

Reply via email to