Filters

A filter is an operation on signals that either removes features or extracts some component. SciPy has a complete set of known filters as well as the tools to allow construction of new ones. The complete list of filters in SciPy is long, and we encourage the reader to explore the help documents of the scipy.signal and scipy.ndimage modules for the complete picture. We will introduce in these pages, as an exposition, some of the most used filters in the treatment of audio or image processing.

We start by creating a signal worth filtering:

>>> from numpy import sin, cos, pi, linspace
>>> f=lambda t: cos(pi*t) + 0.2*sin(5*pi*t+0.1) + 0.2*sin(30*pi*t) + 0.1*sin(32*pi*t+0.1) + 0.1*sin(47* pi*t+0.8)
>>> t=linspace(0,4,400); signal=f(t)

First, we test the classical smoothing filter of Wiener and Kolmogorov, wiener. We present in a plot the original signal (in black) and the corresponding filtered data, with a choice of Wiener window of size 55 samples (in blue). Next we compare the result of applying the median filter, medfilt, with a kernel of the same size as before (in red):

>>> from scipy.signal import wiener, medfilt
>>> import matplotlib.pylab as plt
>>> plt.plot(t,signal,'k', label='The signal')
>>> plt.plot(t,wiener(signal,mysize=55),'r',linewidth=3, label='Wiener filtered')
>>> plt.plot(t,medfilt(signal,kernel_size=55),'b',linewidth=3, label='Medfilt filtered')
>>> plt.legend()
>>> plt.show()

This gives us the following graph showing the comparison of smoothing filters (Wiener, in red, is the one that has its starting point just above 0.5 and Medfilt, in blue, has its starting point just below 0.5):

Filters

Most of the filters in the scipy.signal module can be adapted to work with arrays of any dimension. But in the particular case of images, we prefer to use the implementations in the scipy.ndimage module, since they are coded with these objects in mind. For instance, to perform a median filter on an image for smoothing, we use scipy.ndimage.median_filter. Let us show an example. We will start by loading Lena to array, and corrupting the image with Gaussian noise (zero mean and standard deviation of 16):

>>> from scipy.stats import norm     # Gaussian distribution
>>> import matplotlib.pyplot as plt
>>> import scipy.misc
>>> import scipy.ndimage
>>> plt.gray()
>>> lena=scipy.misc.lena().astype(float)
>>> plt.subplot(221);
>>> plt.imshow(lena)
>>> lena+=norm(loc=0,scale=16).rvs(lena.shape)
>>> plt.subplot(222);
>>> plt.imshow(lena)
>>> denoised_lena = scipy.ndimage.median_filter(lena,3)
>>> plt.subplot(224); 
>>> plt.imshow(denoised_lena)

The set of filters for images come in two flavors – statistical and morphological. For example, among the filters of statistical nature, we have the Sobel algorithm oriented to detection of edges (singularities along curves). Its syntax is as follows:

sobel(image, axis=-1, output=None, mode='reflect', cval=0.0)

The optional parameter, axis, indicates the dimension in which the computations are performed. By default, this is always the last axis (-1). The mode parameter, which is one of the strings 'reflect', 'constant', 'nearest', 'mirror', or 'wrap', indicates how to handle the border of the image in case there is insufficient data to perform the computations there. In case mode is 'constant', we may indicate the value to use in the border with the cval parameter. Let's look into the following code snippet which illustrates the use of sobel filter:

>>> from scipy.ndimage.filters import sobel
>>> import numpy
>>> lena=scipy.misc.lena()
>>> sblX=sobel(lena,axis=0); sblY=sobel(lena,axis=1)
>>> sbl=numpy.hypot(sblX,sblY)
>>> plt.subplot(223); 
>>> plt.imshow(sbl) 
>>> plt.show()

The following screenshot illustrates the previous two filters in action—Lena (upper-left), noisy Lena (upper-right), edge map with sobel (lower-left), and median filter (lower-right):

Filters

The LTI system theory

To investigate the response of a time-invariant linear system to input signals, we have many resources in the scipy.signal module. As a matter of fact, to simplify representation of objects, we have an lti class (linear-time invariant class) with associated methods such as bode (to calculate bode magnitude and phase data), impulse, output, and step.

Whether we are working with continuous or discrete-time linear systems, we have routines to simulate such systems (lsim and lsim2 for continuous, dsim for discrete), as well as compute impulses (impulse and impulse2 for continuous, dimpulse for discrete) and steps (step and step2 for continuous, dstep for discrete).

Transforming a system from continuous to discrete is possible with cont2discrete, but in either case we are able to provide for any system with any of its representations, as well as to convert from one to another. For instance, if we have the zeros z, poles p, and system gain k of the transfer function, we may obtain the polynomial representation (numerator first, then denominator) with zpk2tf(z,p,k). If we have numerator (num) and denominator (dem) of the transfer function, we obtain the state-space with tf2ss(num,dem). This operation is reversible with the ss2tf routine. The change of representation from zero-pole-gain to/from state-space is also contemplated in the (zpk2ss, ss2zpk) module.

Filter design

There are routines in the scipy.signal module that allow the creation of different kinds of filters with diverse methods. For instance, the bilinear function returns a digital filter from an analog using a bilinear transform. Finite impulse response (FIR) filters can be designed by the window method with the firwin and firwin2 routines. Infinite impulse response (IIR) filters can be designed in two different ways, via iirdesign or iirfilter. Butterworth filters can be designed with the butter routine. There are also routines to design filters of Chebyshev (cheby1, cheby2), Cauer (ellip), and Bessel (bessel).

Window functions

No signal processing computational system would be complete without an extensive list of windows—mathematical functions that are zero valued outside specific domains. In this section, we will use a few of the coded windows implemented in the scipy.signal module to design very simple smoothing filters by using convolution.

We will be testing them on the same one-dimensional signal we employed before, for comparison.

We start by showing the plot of four well-known window functions – Boxcar, Hamming, Blackman-Harris (Nuttall version), and triangular. We will use a size of 31 samples:

>>> from scipy.signal import boxcar, hamming, nuttall, triang
>>> import matplotlib.pylab as plt
>>> windows=['boxcar', 'hamming', 'nuttall', 'triang']
>>> plt.subplot(121)
>>> for w in windows:
        eval( 'plt.plot(' + w + '(31))' )
        plt.ylim([-0.5,2]); plt.xlim([-1,32])
        plt.legend(windows)

We need to extend the original signal by fifteen samples for plotting purposes:

>>> plt.subplot(122)
>>> import numpy
>>> from numpy import sin, cos, pi, linspace
>>> f=lambda t: cos(pi*t) + 0.2*sin(5*pi*t+0.1) + 0.2*sin(30*pi*t) + 0.1*sin(32*pi*t+0.1) + 0.1*sin(47* pi*t+0.8)
>>> t=linspace(0,4,400); signal=f(t)
>>> extended_signal=numpy.r_[signal[15:0:-1],signal,signal[-1:-15:- 1]]
>>> plt.plot(extended_signal,'k')

The final step is the filter itself, which we perform by a simple convolution:

>>> for w in windows:
        window = eval( w+'(31)')
        output=numpy.convolve(window/window.sum(),signal)
        plt.plot(output,linewidth=2)
        plt.ylim([-2,3]); plt.legend(['original']+windows)
>>> plt.show()

This produces the following output, showing convolution of a signal with different windows:

Window functions

Image interpolation

The set of filters on images that performs some geometric manipulation of the input is classically termed image interpolation, since this numerical technique is the root of all the algorithms. As a matter of fact, SciPy collects all these under the submodule, scipy.ndimage.interpolation, for ease of access. This section is best explained through examples, going over the most meaningful routines for geometric transformation. The starting point is the image, Lena. We now assume that all functions from the submodule have been imported into the session.

We need to apply an affine transformation on the domain of the image, given in matrix form as follows:

Image interpolation

To apply the transformation on the domain of the image we issue the affine_transform command (note that the syntax is self-explanatory):

>>> import scipy.misc
>>> import numpy 
>>> import matplotlib.pylab as plt 
>>> from scipy.ndimage.interpolation import affine_transform
>>> lena=scipy.misc.lena() 
>>> A=numpy.mat("0,1;-1,1.25"); b=[-400,0]
>>> Ab_Lena=affine_transform(lena,A,b,output_shape=(512*2.2,512*2.2))
>>> plt.gray() 
>>> plt.subplot(121) 
>>> plt.imshow(Ab_Lena)

For a general transformation, we use the geometric_transform routine with the following syntax:

geometric_transform(input, mapping, output_shape=None, 
                    output=None, order=3, mode='constant',
cval=0.0, prefilter=True, extra_arguments=(),
extra_keywords={})

We need to provide a rank-2 map from tuples to tuples as the parameter mapping. For instance, we desired to apply the Möbius transform for complex-valued number z (where we assume the values of a, b, c, and d are already defined and they are complex-valued numbers) in the following formula:

Image interpolation

We would have to code it in the following way:

>>> def f(z):
        temp = a*(z[0]+1j*z[1]) + b
        temp /= c*(z[0]+1j*z[1])+d
        return (temp.real, temp.imag)

In both functions, the values of the grid that cannot be computed directly with the formula are inferred with spline interpolation. We may specify the order of this interpolation with the order parameter. The points outside the domain of definition are not interpolated, but filled according to some predetermined rule. We may impose this rule by passing a string to the mode option. The choices are – 'constant', to use a constant value that we may impose with the cval option; 'nearest', that continues the last value of the interpolation on each level line; and 'reflect' or 'wrap', which are self-explanatory.

For example, for the values a = 2**15*(1+1j), b = 0, c = -2**8*(1-1j*2), and d = 2**18-1j*2**14, we obtain (after imposing the reflect mode) the result, as shown just after this line of code:

>>> from scipy.ndimage.interpolation import geometric_transform 
>>> a = 2**15*(1+1j); b = 0; c = -2**8*(1-1j*2); d = 2**18-1j*2**14
>>> Moebius_Lena = geometric_transform(lena,f,mode='reflect')
>>> plt.subplot(122); 
>>> plt.imshow(Moebius_Lena) 
>>> plt.show()

The following screenshot shows affine transformation (left) and geometric transformation (right):

Image interpolation

For special cases of rotations, shifts, or dilations, we have the syntactic sugar routines, rotate(input,angle), shift(input, offset), and zoom(input,dilation_factor).

Given any image, we know the value of the array at pixel values (with integer coordinates) in the domain. But what would the corresponding value of a location be without integer coordinates? We may obtain that information with the valuable routine, map_coordinates. Note that the syntax may be confusing, especially with the coordinates parameter:

map_coordinates(input, coordinates, output=None, order=3, 
                mode='constant', cval=0.0, prefilter=True)

For instance, if we wish to evaluate Lena at the locations (10.5, 11.7) and (12.3, 1.4), we collect the coordinates as a sequence of sequences; the first internal sequence contains the x values, and the second, the y values. We may specify the order of splines used with order, and the interpolation scheme outside of the domain, if needed, as in the previous examples. Let's evaluate Lena at the locations (which we just discussed in our example) using following code snippet:

>>> import scipy.misc 
>>> from scipy.ndimage.interpolation import map_coordinates
>>> lena=scipy.misc.lena().astype(float)
>>> coordinates=[[10.5, 12.3], [11.7, 1.4]]
>>> map_coordinates(lena, coordinates, order=1)

The output is shown as:

array([ 157.2 ,  157.42])

Further, we evaluate Lena with order=2 as shown in following line of code:

>>> map_coordinates(lena, coordinates, order=2)

The output is shown as:

array([ 157.80641507,  157.6741489 ])

Morphology

We also have the possibility of creating and applying filters to images based on mathematical morphology, both to binary and gray-scale images. The four basic morphological operations are opening (binary_opening), closing (binary_closing), dilation (binary_dilation), and erosion (binary_erosion). Note that the syntax of each of these filters is very simple, since we only need two ingredients – the signal to filter and the structuring element to perform the morphological operation. Let's take a look into the general syntax for these morphological operations:

binary_operation(signal, structuring_element)

We have illustrated the use some of these operations towards an application to obtain the structural model of an oxide, but we will postpone this example until we cover the notions of triangulations and Voronoi diagrams in Chapter 7, SciPy for Computational Geometry.

We may use combinations of these four basic morphological operations to create more complex filters for the removal of holes, hit-or-miss transforms (to find the location of specific patterns in binary images), denoising, edge detection, and many more. The module even provides us with some of the most common filters constructed this way. For instance, for the location of the letter e in a text (which we covered in Chapter 2, Working with the NumPy Array As a First Step to SciPy, as an application of correlation), we could use the following command instead:

>>> binary_hit_or_miss(text, letterE)

For comparative purposes, let's apply this command to the example from Chapter 2, Working with the NumPy Array As a First Step to SciPy:

>>> import numpy
>>> import scipy.ndimage
>>> import matplotlib.pylab as plt
>>> from scipy.ndimage.morphology import binary_hit_or_miss
>>> text = scipy.ndimage.imread('CHAP_05_input_textImage.png')
>>> letterE = text[37:53,275:291]
>>> HitorMiss = binary_hit_or_miss(text, structure1=letterE, origin1=1) 
>>> eLocation = numpy.where(HitorMiss==True)
>>> x=eLocation[1]; y=eLocation[0]
>>> plt.imshow(text, cmap=plt.cm.gray, interpolation='nearest')
>>> plt.autoscale(False)
>>> plt.plot(x,y,'wo',markersize=10)
>>> plt.axis('off')
>>> plt.show()

This generates the following output, which the reader should compare with the corresponding one on Chapter 2, Working with the NumPy Array As a First Step to SciPy:

Morphology

For gray-scale images, we may use a structuring element (structuring_element) or a footprint. The syntax is, therefore, a little different:

grey_operation(signal, [structuring_element, footprint, size, ...])

If we desire to use a completely flat and rectangular structuring element (all ones), then it is enough to indicate the size as a tuple. For instance, to perform gray-scale dilation of a flat element of size (15,15) on our classical image of Lena, we issue the following command:

>>> grey_dilation(lena, size=(15,15))

The last kind of morphological operations coded in the scipy.ndimage module perform distance and feature transforms. Distance transforms create a map that assigns to each pixel the distance to the nearest object. Feature transforms provide the index of the closest background element instead. These operations are used to decompose images into different labels. We may even choose different metrics such as Euclidean distance, chessboard distance, and taxicab distance. The syntax for the distance transform (distance_transform) using a brute force algorithm is as follows:

distance_transform_bf(signal, metric='euclidean', sampling=None,
return_distances=True, return_indices=False,
                      distances=None, indices=None)

We indicate the metric with the strings such as 'euclidean', 'taxicab', or 'chessboard'. If we desire to provide the feature transform instead, we switch return_distances to False and return_indices to True.

Similar routines are available with more sophisticated algorithms – distance_transform_cdt (using chamfering for taxicab and chessboard distances). For Euclidean distance, we also have distance_transform_edt. All these use the same syntax.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.77.153