Mean Filter in python without loops

Viewed 9086

I have been asked to create a mean_filter function on a 1-D array with given kernel, assuming zero padding.

A mean filter is an algorithm meant to remove noise. It takes an array, a kernel (say K), and replaces each value of the array by the mean of surrounding K values, itself inclusive.

This algorithm is used in image processing.

I was able to do this-

def mean_filter(arr, k):
    # applies mean filter to 1-d array with the kernel size 2k+1 . Write your code here
    p=len(arr)
    arr2=np.zeros(2*k+p, dtype=float)
    arr3=np.zeros(2*k+p, dtype=float)
    arr4=np.zeros(p, dtype=float)

    for i in range(p):
        arr2[k+i]=arr[i]

    for i in range(k,k+p):
        sum=0
        for j in range(-k,k+1):
            sum+=arr2[i+j]
        arr3[i]=sum/float(2*k+1)

    for i in range(p):
        arr4[i]=arr3[k+i]

    return arr4

But what they expect from me is to do this without any kind of loops.

The instruction reads- "This task should be accomplished without any kind of loops, comprehensions or functions like np.vectorize, etc.

Do not use inbuilt convolve functions for this task"

I have literally no clue how to do this. Could you suggest something? Clues would be appreciated.

3 Answers

By example:

import numpy as np
k=2
kern=np.ones(2*k+1)/(2*k+1)
arr=np.random.random((10))
out=np.convolve(arr,kern, mode='same')

I found a solution that is inspired from a matlab function I stumbled across while trying to implement a different edge behavior with no padding in mean filtering: https://www.mathworks.com/matlabcentral/fileexchange/23287-smooth2a . It works with matrix multiplication, no loop or convolve is involved. With 1D array, it would give something like this:

import scipy.sparse
import numpy as np

def mean_filter(arr, k):
    p = len(arr)
    diag_offset = np.linspace(-(k//2), k//2, k, dtype=int)
    eL = scipy.sparse.diags(np.ones((k, p)), offsets=diag_offset, shape=(p, p))
    nrmlize = eL @ np.ones_like(arr)
    return (eL @ arr) / nrmlize

How this works is you create a matrix with as many ones diagonals as your kernel size, then do the dot product with your array. The result is an array where each element is the sum of the elements of the original array over the kernel. Basically, this is the equivalent of a convolution. Finally you normalize this sum by the kernel size.

One thing to note is that at the edges of the array where you cannot build a full kernel, as much of the kernel that fits on your array is used (this is why you cannot just normalize by a scalar afterwards).

This solution can also be extended to work on multidimensional arrays.

def meanfilt (x, k):
    """Apply a length-k mean filter to a 1D array x.
    Boundaries are extended by repeating endpoints.
    """
    
    import numpy as np

    assert k % 2 == 1, "Mean filter length must be odd."
    assert x.ndim == 1, "Input must be one-dimensional."
    
    k2 = (k - 1) // 2
    y = np.zeros ((len (x), k), dtype=x.dtype)
    y[:,k2] = x
    for i in range (k2):
        j = k2 - i
        y[j:,i] = x[:-j]
        y[:j,i] = x[0]
        y[:-j,-(i+1)] = x[j:]
        y[-j:,-(i+1)] = x[-1]
    return np.mean (y, axis=1)

Note: The answer is picked from the repo

Related