%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import skimage as ski
from skimage.morphology import disk
import scipy.ndimage as ndimage
No measurement system is perfect and therefore, you will also not get perfect images of the sample you measured. In the figure you can see the slice as it would be in a perfect world to the left and what you actually would measure with an imaging system. Some are of higher quality than others but there are still a collection of factors that have an impact on the image quality.
Traditionally, the processing of image data can be divided into a series of sub tasks that provide the final resul.
plt.figure(figsize=[15,6]);
plt.subplot(1,3,1);plt.imshow(np.random.normal(0,1,[100,100])); plt.title('Gaussian');plt.axis('off');
plt.subplot(1,3,2);plt.imshow(0.90<np.random.uniform(0,1,size=[100,100]),cmap='gray'); plt.title("Salt and pepper"),plt.axis('off');
plt.subplot(1,3,3);plt.imshow(ski.filters.gaussian(np.random.normal(0,1,size=[100,100]),sigma=1),cmap='gray'); plt.title("Structured"),plt.axis('off');
plt.tight_layout()
from scipy.stats import norm
rv = norm(loc = -1., scale = 1.0);rv1 = norm(loc = 0., scale = 2.0); rv2 = norm(loc = 2., scale = 3.0)
x = np.arange(-10, 10, .1)
#plot the pdfs of these normal distributions
plt.plot(x, rv.pdf(x),label='$\mu$=-1, $\sigma$=1')
plt.plot(x, rv1.pdf(x),label='$\mu$=0, $\sigma$=2')
plt.plot(x, rv2.pdf(x),label='$\mu$=2, $\sigma$=3')
plt.legend()
from scipy.stats import poisson
mu=3
fig, ax = plt.subplots(1, 1)
x = np.arange(poisson.ppf(0.01, mu), poisson.ppf(0.999, mu))
ax.plot(x, poisson.pmf(x, mu), 'bo', ms=8, label='poisson pmf')
ax.vlines(x, 0, poisson.pmf(x, mu), colors='b', lw=5, alpha=0.5)
plt.figure(figsize=[15,8])
x=np.linspace(0,2*np.pi,100);
y=10*np.sin(x)+11; ng=np.random.normal(0,1,size=len(x)); npoi = np.random.poisson(y);
plt.subplot(2,2,1); plt.plot(x,y+ng);plt.plot(x,y); plt.axis('off');plt.title('Gaussian'); plt.subplot(2,2,3);plt.plot(x,ng);plt.axis('off');
plt.subplot(2,2,2); plt.plot(x,npoi);plt.plot(x,y); plt.axis('off');plt.title('Poisson'); plt.subplot(2,2,4);plt.plot(x,npoi-y);plt.axis('off');
$sp(x)=\left\{\begin{array}{ll} -1 & x\leq\lambda_1\\ 0 & \lambda_1< x \leq \lambda_2\\ 1 & \lambda_2<x \end{array}\right.\qquad \begin{array}{l}x\in\mathcal{U}(0,1)\\\lambda_1<\lambda_2\\ \lambda_1+\lambda_2 = \mbox{noise fraction} \end{array}$
def snp(dims,Pblack,Pwhite) : # Noise model function
uni=np.random.uniform(0,1,dims)
img=(Pwhite<uni).astype(float)-(uni<Pblack).astype(float)
return img
img10_90=snp([100,100],0.1,0.9); img5_95=snp([100,100],0.05,0.95);img1_99=snp([100,100],0.01,0.99)
plt.figure(figsize=[15,5])
plt.subplot(1,3,1); plt.imshow(img1_99,cmap='gray'); plt.title('$\lambda_1$=1% and $\lambda_2$=99%',fontsize=16); plt.axis('off');
plt.subplot(1,3,2); plt.imshow(img5_95,cmap='gray'); plt.title('$\lambda_1$=5% and $\lambda_2$=95%',fontsize=16); plt.axis('off');
plt.subplot(1,3,3); plt.imshow(img10_90,cmap='gray'); plt.title('$\lambda_1$=10% and $\lambda_2$=90%',fontsize=16); plt.axis('off');
$SNR \sim \sqrt{N}$
exptime=np.array([50,100,200,500,1000,2000,5000,10000])
snr = np.array([ 8.45949767, 11.40011621, 16.38118766, 21.12056507, 31.09116641,40.65323123, 55.60833117, 68.21108979]);
marker_style = dict(color='cornflowerblue', linestyle='-', marker='o',markersize=10, markerfacecoloralt='gray');
plt.figure(figsize=(15,5))
plt.subplot(1,3,2);plt.plot(exptime/1000,snr, **marker_style);plt.xlabel('Exposure time [s]');plt.ylabel('SNR [1]')
img50ms=plt.imread('ext-figures/lecture03/tower_50ms.png'); img10000ms=plt.imread('ext-figures/lecture03/tower_10000ms.png');
plt.subplot(1,3,1);plt.imshow(img50ms); plt.subplot(1,3,3); plt.imshow(img10000ms);
Generate an $m \times n$ random fields with different distributions:
np.random.normal(mu,sigma, size=[rows,cols])
np.random.uniform(low,high,size=[rows,cols])
np.mean(f)
, np.var(f)
, np.std(f)
Computes the mean, variance, and standard deviation of an image $f$.np.min(f)
,np.max(f)
Finds minimum and maximum values in $f$.np.median(f)
, np.rank()
Selects different values from the sorted data.Filters are characterized by the type of information they suppress
Computed using the convolution operation
$$g(x)=h*f(x)=\int_{\Omega}f(x-\tau) h(\tau) d\tau$$where
Mean or Box filter | Gauss filter |
All weights have the same value. | $$G=\exp{-\frac{x^2+y^2}{2\,\sigma^2}}$$ |
Example: $$B=\frac{1}{25}\cdot\begin{array}{|c|c|c|c|c|} \hline 1 & 1 & 1 & 1& 1\\ \hline 1 & 1 & 1 & 1& 1\\ \hline 1 & 1 & 1 & 1& 1\\ \hline 1 & 1 & 1 & 1& 1\\ \hline 1 & 1 & 1 & 1& 1\\ \hline \end{array} $$ |
Example: |
img = plt.imread('ext-figures/lecture03/input_orig.png');
noise = np.random.normal(0,1,size=img.shape); snr10=img+0.1*noise; snr5=img+0.2*noise; snr2=img+0.5*noise;plt.figure(figsize=[15,12])
plt.subplot(3,4,1); plt.imshow(img); plt.axis('off');plt.title('No noise'); plt.subplot(3,4,2); plt.imshow(snr10); plt.title('SNR=10'); plt.axis('off');plt.subplot(3,4,3); plt.imshow(snr5); plt.title('SNR=5'); plt.axis('off');plt.subplot(3,4,4); plt.imshow(snr2); plt.title('SNR=2'); plt.axis('off');
plt.subplot(3,4,5); plt.imshow(ski.filters.gaussian(img,sigma=1)); plt.title('Gaussian $\sigma$=1'); plt.axis('off');plt.subplot(3,4,6); plt.imshow(ski.filters.gaussian(snr10,sigma=1)); plt.axis('off');plt.subplot(3,4,7); plt.imshow(ski.filters.gaussian(snr5,sigma=1)); plt.axis('off');plt.subplot(3,4,8); plt.imshow(ski.filters.gaussian(snr2,sigma=1)); plt.axis('off');
plt.subplot(3,4,9); plt.imshow(ski.filters.gaussian(img,sigma=3)); plt.title('Gaussian $\sigma$=3'); plt.axis('off');plt.subplot(3,4,10); plt.imshow(ski.filters.gaussian(snr10,sigma=3)); plt.axis('off');plt.subplot(3,4,11); plt.imshow(ski.filters.gaussian(snr5,sigma=3)); plt.axis('off');plt.subplot(3,4,12); plt.imshow(ski.filters.gaussian(snr2,sigma=3)); plt.axis('off');
The asociative and commutative laws apply to convoution
$$(a * b)*c=a*(b*c) \quad \mbox{ and } \quad a * b = b * a $$A convolution kernel is called separable if it can be split in two or more parts:
$$\begin{array}{|c|c|c|} \hline \cdot & \cdot & \cdot\\ \hline \cdot & \cdot & \cdot\\ \hline \cdot & \cdot& \cdot\\ \hline \end{array}= \begin{array}{|c|} \hline \cdot \\ \hline \cdot \\ \hline \cdot \\ \hline \end{array} * \begin{array}{|c|c|c|} \hline \cdot & \cdot & \cdot\\ \hline \end{array}$$ $$\exp{-\frac{x^2+y^2}{2\,\sigma^2}}=\exp{-\frac{x^2}{2\,\sigma^2}}*\exp{-\frac{y^2}{2\,\sigma^2}}$$Separability reduces the number of computations $\rightarrow$ faster processing
img = plt.imread('ext-figures/lecture03/grasshopper.png'); noise=img+np.random.normal(0,0.1,size=img.shape); spots=img+0.2*snp(img.shape,0,0.8); noise=(noise-noise.min())/(noise.max()-noise.min());spots=(spots-spots.min())/(spots.max()-spots.min());
plt.figure(figsize=[15,10]);
plt.subplot(2,3,1); plt.imshow(spots); plt.title('Spots'); plt.axis('off');
plt.subplot(2,3,2); plt.imshow(ski.filters.gaussian(spots,sigma=1)); plt.title('Gauss filter'); plt.axis('off');
plt.subplot(2,3,3); plt.imshow(ski.filters.median(spots,disk(3))); plt.title('Median'); plt.axis('off');
plt.subplot(2,3,4); plt.imshow(noise); plt.title('Gaussian noise'); plt.axis('off');
plt.subplot(2,3,5); plt.imshow(ski.filters.gaussian(noise,sigma=1)); plt.title('Gauss filter'); plt.axis('off');
plt.subplot(2,3,6); plt.imshow(ski.filters.median(noise,disk(3))); plt.title('Median'); plt.axis('off');
Problem | Example | Possible solutions |
---|---|---|
|
![]() |
|
High-pass filters enhance rapid changes $\rightarrow$ ideal for edge detection
$\frac{\partial}{\partial x}=\frac{1}{32}\cdot\begin{array}{|c|c|c|} \hline -3 & 0 & 3\\ \hline -10 & 0 & 10\\ \hline -3 & 0 & 3\\ \hline \end{array}$
$\frac{\partial}{\partial\,y}=\frac{1}{32}\cdot\begin{array}{|c|c|c|} \hline -3 & -10 & -3\\ \hline 0 & 0 & 0\\ \hline 3 & 10 & 3\\ \hline \end{array}$
img=plt.imread('ext-figures/lecture03/orig.png')
k = np.array([[-3,-10,-3],[0,0,0],[3,10,3]]);
plt.figure(figsize=[15,8])
plt.subplot(1,3,1); plt.imshow(img);plt.title('Original');
plt.subplot(1,3,2); plt.imshow(ndimage.convolve(img,np.transpose(k)));plt.title('$\partial / \partial x$');
plt.subplot(1,3,3); plt.imshow(ndimage.convolve(img,k));plt.title('$\partial / \partial y$');
img=plt.imread('ext-figures/lecture03/orig.png');
plt.figure(figsize=[15,6])
plt.subplot(1,2,1);plt.imshow(ski.filters.laplace(img),clim=[-0.08,0.08]); plt.title('Laplacian'); plt.colorbar();
plt.subplot(1,2,2);plt.imshow(ski.filters.sobel(img)); plt.title('Sobel'); plt.colorbar();
In practice - you never see the transform equations. The Fast Fourier Transform algorithm is available in numerical libraries and tools.
x = np.linspace(0,50,100); s0=np.sin(0.5*x); s1=np.sin(2*x);
plt.figure(figsize=[15,5])
plt.subplot(2,3,1);plt.plot(x,s0); plt.axis('off');plt.title('$s_0$');plt.subplot(2,3,4);plt.plot(np.abs(np.fft.fftshift(np.fft.fft(s0))));plt.axis('off');
plt.subplot(2,3,2);plt.plot(x,s1); plt.axis('off');plt.title('$s_1$');plt.subplot(2,3,5);plt.plot(np.abs(np.fft.fftshift(np.fft.fft(s1))));plt.axis('off');
plt.subplot(2,3,3);plt.plot(x,s0+s1); plt.axis('off');plt.title('$s_0+s_1$');plt.subplot(2,3,6);plt.plot(np.abs(np.fft.fftshift(np.fft.fft(s0+s1))));plt.axis('off');
$\mathcal{F}\{a * b\} = \mathcal{F}\{a\} \cdot \mathcal{F}\{b\} $ $\mathcal{F}\{a \cdot b\} = \mathcal{F}\{a\} * \mathcal{F}\{b\} $
img=plt.imread('ext-figures/lecture03/bp_ex_original.png'); noise=np.random.normal(0,0.2,size=img.shape); nimg=img+noise;
plt.figure(figsize=[15,3])
plt.subplot(1,3,1); plt.imshow(img);plt.title('Image');
plt.subplot(1,3,2); plt.imshow(noise);plt.title('Noise');
plt.subplot(1,3,3); plt.imshow(nimg);plt.title('Image + noise');
plt.figure(figsize=[15,3])
plt.subplot(1,3,1); plt.imshow(np.log(np.abs(np.fft.fftshift(np.fft.fft2(img)))));plt.title('Image');
plt.subplot(1,3,2); plt.imshow(np.log(np.abs(np.fft.fftshift(np.fft.fft2(noise)))));plt.title('Noise');
plt.subplot(1,3,3); plt.imshow(np.log(np.abs(np.fft.fftshift(np.fft.fft2(nimg)))));plt.title('Image + noise');
How can we suppress noise without destroying relevant image features?
def ripple(size=128,angle=0,w0=0.1) :
w=w0*np.linspace(0,1,size);
[x,y]=np.meshgrid(w,w);
img=np.sin((np.sin(angle)*x)+(np.cos(angle)*y));
return img
N=64;
d0=ripple(N,angle=1/180*np.pi,w0=100);
d30=ripple(N,angle=30/180*np.pi,w0=100);
d60=ripple(N,angle=60/180*np.pi,w0=100);
d90=ripple(N,angle=89/180*np.pi,w0=100);
plt.figure(figsize=[15,8])
plt.subplot(2,4,1); plt.imshow(d0); plt.title('$0^{\circ}$'); plt.axis('off');
plt.subplot(2,4,2); plt.imshow(d30); plt.title('$30^{\circ}$'); plt.axis('off');
plt.subplot(2,4,3); plt.imshow(d60); plt.title('$60^{\circ}$'); plt.axis('off');
plt.subplot(2,4,4); plt.imshow(d90); plt.title('$90^{\circ}$'); plt.axis('off');
plt.subplot(2,4,5); plt.imshow((np.abs(np.fft.fftshift(np.fft.fft2(d0))))); plt.axis('off');
plt.subplot(2,4,6); plt.imshow((np.abs(np.fft.fftshift(np.fft.fft2(d30))))); plt.axis('off');
plt.subplot(2,4,7); plt.imshow((np.abs(np.fft.fftshift(np.fft.fft2(d60))))); plt.axis('off');
plt.subplot(2,4,8); plt.imshow((np.abs(np.fft.fftshift(np.fft.fft2(d90))))); plt.axis('off');
plt.figure(figsize=[8,10])
plt.subplot(4,1,1);plt.imshow(plt.imread('ext-figures/lecture03/raw_img.png')); plt.title('$a$'); plt.axis('off');
plt.subplot(4,1,2);plt.imshow(plt.imread('ext-figures/lecture03/raw_spec.png')); plt.title('$\mathcal{F}(a)$'); plt.axis('off');
plt.subplot(4,1,3);plt.imshow(plt.imread('ext-figures/lecture03/filt_spec.png')); plt.title('Kernel $H$'); plt.axis('off');
plt.subplot(4,1,4);plt.imshow(plt.imread('ext-figures/lecture03/filt_img.png')); plt.title('$a_{filtered}$'); plt.axis('off');
Reconstructed CT slice before filter | Reconstructed CT slice after stripe filter |
![]() |
![]() |
Intensity variations are suppressed using the stripe filter on all projections.
The FFT is only working with data of size in $2^N$. If your data has a different length, you have to pad (fill with constant value) up the next $2^N$.
e.g. from scipy import ndimage
ndimage.filters.convolve(f,h)
Linear filter using kernel $h$ on image $f$.ndimage.filters.median_filter(f,\[n,m\])
Median filter using an $n \times m$ filter neighborhoodnp.fft.fft2(f)
Computes the 2D Fast Fourier Transform of image $f$np.fft.ifft2(F)
Computes the inverse Fast Fourier Transform $F$.np.fft.fftshift()
Rearranges the data to center the $\omega$=0. Works for 1D and 2D.np.abs(f), np.angle(f)
Computes amplitude and argument of a complex number.np.real(f), np.imag(f)
Gives the real and imaginary parts of a complex number.Original | Wavelet transformed |
---|---|
![]() |
![]() |
The noise is found in the detail part of the WT
Makes one level of the wavelet transform or its inverse using wavelet base specified by 'wn'.
Performs N levels of wavelet decomposition using a specified wavelet base.
Estimating threshold parameters for wavelet denoising.
Wavelet denoising and compression using information from wavedec2 and *wbmpen.
The heat transport equation $$\frac{\partial T}{\partial t}=\kappa\,\nabla^2 T$$
Original | Iterations |
![]() |
def g(x,lambd,n) :
g=1/(1+(x/lambd)**n)
return g
We want to control the diffusion process...
The contrast function $G$ is our control function $$G(x)=\frac{1}{1+\left(\frac{x}{\lambda}\right)^n}$$
x=np.linspace(0,1,100);
plt.plot(x,g(x,lambd=0.5,n=12));
plt.xlabel('Image intensity (x)'); plt.ylabel('G(x)');plt.tight_layout()
![]() |
![]() |
Image | Diffusivity map |
A more robust filter is obtained with
$$\frac{\partial u}{\partial t}=G(|\nabla_{\sigma} u|)\,\nabla^2 u$$ - _$u$_ Image to be filtered - _$G(\cdot)$_ Non-linear function to control the contrast - _$\tau$_ Time increment per numerical iteration - _$N$_ Number of iterations - _$\nabla_{\sigma}$_ Gradient smoothed by a Gaussian filter, width $\sigma$Neutron CT slice from a real-time experiment observing the coalescence of cold mixed bitumen.
Original | Iterations of non-linear diffusion |
![]() |
with $|u|_{BV}=\int_{\Omega}|\nabla u|^2$
We want smooth regions with sharp edges\ldots
The image $f$ is filtered by solving
$$\begin{eqnarray} \frac{\partial u}{\partial t}&=& \mathrm{div}\left(\frac{\nabla u}{|\nabla u|}\right)+\lambda\,(f-u+v)\nonumber\\ \frac{\partial v}{\partial t}&=& \alpha\, (f-u) \end{eqnarray}$$The requirements varies between different data sets.
The solution time ($T=N\cdot\tau$) is essential to the result
Smoothing normally consider information from the neighborhood like
Non-local smoothing average similiar intensities in a global sense.
The non-local means filter is defined as $$u(p)=\frac{1}{C(p)}\sum_{q\in\Omega}v(q)\,f(p,q)$$ where
$v$ and $u$ input and result images.
$C(p)$ is the sum of all pixel weights as
$C(p)=\sum_{q\in\Omega}f(p,q)$
$f(p,q)$ is the weighting function
$f(p,q)=e^{-\frac{|B(q)-B(p)|^2}{h^2}}$
B(x) is a neighborhood operator e.g. local average around $x$
The orignal filter compares all pixels with all pixels\ldots\
It has been shown that not all pixels have to be compared to achieve a good filter effect. i.e. $\Omega$ in the filter equations can be replaced by $\Omega_i<<\Omega$
Compute pixel-wise difference between image $f$ and $g$
Difference images provide first diagnosis about processing performance
General purpose can be controlled
![]() | ![]() |
Often 'real' data
An evaluation procedure need a metric to compare the performance
We have looked at different ways to suppress noise and artifacts:
Which one you select depends on