<jupyter_start><jupyter_text>5. Simple image filtering<jupyter_code>import inspect
import matplotlib.pyplot as plt
import numpy as np
import scipy.signal
import IPython.display
%matplotlib nbagg
from cued_sf2_lab.familiarisation import load_mat_img, plot_image
from cued_sf2_lab.simple_image_filtering import halfcos, convse
# load the image as we did before
X, cmaps_dict = load_mat_img(img='lighthouse.mat', img_info='X', cmap_info={'map', 'map2'})<jupyter_output><empty_output><jupyter_text>An effective image lowpass filter, of odd length $N$, may be obtained by defining the impulseresponse $h(n)$ to be a sampled half-cosine pulse:$$h(n) = G \cos \left(\frac{n\pi}{N + 1}\right),\qquad \text{for} \qquad \frac{-(N - 1)}{2} \le n \le \frac{N - 1}{2}$$where $G$ is a gain factor, which, in order to give unity gain at zero frequency, should be calculated such that$$\sum_{n=-(N-1)/2}^{(N-1)/2} h(n) = 1$$(This may be done most easily by first calculating h(n) with G = 1, summing all terms,and then dividing them all by the result.)Take a look at the `halfcos` function below and check that it generates h for a given N:<jupyter_code># this is just to make it appear in a cell - you can use `halfcos??` to quickly read any function
IPython.display.Code(inspect.getsource(halfcos), language="python")<jupyter_output><empty_output><jupyter_text>Use the [`np.convolve` function in a for loop](https://numpy.org/doc/stable/reference/generated/numpy.convolve.html) to convolve a 15-sample half-cosine with each row of the test image, Lighthouse. Observe the resulting image `Xf` and note the increased width and the gradual fade to black at the edges, caused by the `convolve` assuming the signal is zero outside the range of the input vectors (the behavior when `mode='full'`).<jupyter_code># your code here

# fig, ax = plt.subplots()
Xf = ...
# plot_image(Xf, ax=ax)<jupyter_output><empty_output><jupyter_text>Trim the filtered image `Xf` to its correct size using `Xf[:, 7:256+7]` and display it:<jupyter_code># your code here<jupyter_output><empty_output><jupyter_text>Note that darkening of the sides is still visible, since the lowpass filterassumes that the intensity is zero outside the image. Image trimming and convolution of all the image rows can also be achieved using the [`scipy.signal.convolve`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.convolve.html) function with the `mode='same'` argument. Note we have to turn `h` into a 2d filter by wrapping it in `[]`:```pythonXf = scipy.signal.convolve(X, [h])```<jupyter_code># your code here - use `scipy.signal.convolve` instead of the for loop containing `np.convolve`:
fig, ax = plt.subplots()

# define a 15-sample halfcosine
h = halfcos(15)
Xf = scipy.signal.convolve(X, [h], mode='same')

plot_image(Xf, ax=ax);<jupyter_output><empty_output><jupyter_text>Symmetric extension is a technique to minimise edge effects when images offinite size are filtered.  It assumes that the image is surrounded by aflat mirror along each edge so it extends into mirror-images (symmetricextensions) of itself in all directions over an infinite plane.  If the filterimpulse response is symmetrical about its mid point, then the filtered imagewill also be symmetrically extended in all directions with the same period asthe original images.  Hence it is only necessary to define the filtered imageover the same area as the original image, for it to be defined over the wholeinfinite plane.Let us consider a one-dimensional example for a 4-point input signal$a,b,c,d$.  This may be symmetrically extended in one of two ways:$$ \ldots d,c,b,\underbrace{a,b,c,d,}_{\mbox{original}}c,b,a \ldots\quad \mbox{or} \quad \ldots d,c,b,a,\underbrace{a,b,c,d,}_{\mbox{original}}d,c,b,a \ldots$$The left-hand method, where the end points are not repeated at each boundary, is mostsuitable when the signal is to be filtered by a filter of odd length. The other method is most suited to filters of even length.In Python, a matrix with symmetrically extended rows can be obtained with [`np.pad`](https://numpy.org/doc/stable/reference/generated/numpy.pad.html) using the `reflect` and `symmetric` modes:<jupyter_code>x = np.array([
    ["a", "b", "c", "d"],
    ["A", "B", "C", "D"]])
print(np.pad(x, [(0, 0), (2, 2)], mode='reflect'))    # for filters of odd length
print()
print(np.pad(x, [(0, 0), (2, 2)], mode='symmetric'))  # for filters of even length<jupyter_output>[['c' 'b' 'a' 'b' 'c' 'd' 'c' 'b']
 ['C' 'B' 'A' 'B' 'C' 'D' 'C' 'B']]

[['b' 'a' 'a' 'b' 'c' 'd' 'd' 'c']
 ['B' 'A' 'A' 'B' 'C' 'D' 'D' 'C']]<jupyter_text>Here, `[(0, 0), (2, 2)]` reads as _"pad with 0 entries above and below, and 2 entries to the left and right"_. The function `convse` make use of this to filterthe rows of matrix `X` using the appropriate form ofsymmetric extension. The filtering is performed by accumulating shiftedversions of `X` in `Xe`, each weighted by the appropriate element of`h`. Check that you understand how this function works.<jupyter_code>IPython.display.Code(inspect.getsource(convse), language="python")<jupyter_output><empty_output><jupyter_text>Note that this `convse` is actually provided as part of scipy, as [`scipy.ndimage.convolve1d`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.convolve1d.html), but `convse` is much easier to understand the implementation of.Use `convse` to filter the rows of your image with the15-tap half-cosine filter, noting the absence of edge effects.<jupyter_code># your code here<jupyter_output><empty_output><jupyter_text>Now filter the columns of the _row-filtered_ image by use of the Python transpose operation `.T`.(Note that unlike the Matlab version of this lab, the function `conv2se` has not been provided) Does it make any difference whether the rows or columns are filtered first? (You should test this accurately by measuring the maximum absolute pixel difference between the row-column and column-row filtered images. Beware of scientific notation, used by Python for very small numbers!)<jupyter_code># your code here<jupyter_output><empty_output><jupyter_text>This process of separate row and column filtering is known as*separable* 2-D filtering, and is much more efficient thanthe more general non-separable 2-D filtering. It is possible to construct a 2-D _high-pass_ filter by subtracting the 2-D low-passresult from the original. Note that your 2-D lowpass filter `h` _must_ have a DC gain (sum of all filter coefficients) of unity for this to correctly produce a highpass filter. Thehighpass image `Y` now contains negative, as well as positivepixel values, so it is sensible to display the result using `imshow(Y)` which automatically compensates for this. Try generating both low-pass and high-pass versions of `X` using a range of different odd-length half-cosine filters. Comment on the relative effects of these filters<jupyter_code># your code here<jupyter_output><empty_output><jupyter_text>One way to assess sets of filtered images like these is to contrast the *energy* content. In this context, the energy `E` of an image `X` is given by the sum of the squares of the individual pixel values:```pythonE = np.sum(X**2.0)```Remember that $a^b$ is spelt `a**b` in Python, not `a^b`. We make sure to use `2.0` and not `2`, as `np.array(16, dtype=np.uint8)**2` overflows the bounds of `uint8` and gives `0`, while `2.0` tells numpy to use at least `float64` instead. What do you observe about the energy of the highpass images, compared with that of the lowpass images?<jupyter_code># your code here<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10790133227806357, 'fraction_numerical': 0.008837884184144572, 'mean_word_length': 5.339464882943144, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 44, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '585965', 'license': 'MIT', 'n_tokens_mistral': 2422, 'n_tokens_neox': 2336, 'n_words': 978, 'path': '5-simple-image-filtering.ipynb', 'repo_name': 'sigproc/cued_sf2_lab', 'size': '7581'}