Frequencies in an image
The image of an object consists of many frequencies. High frequencies correspond to quickly varying intensity and likewise low frequencies correspond to slowly varying features. So high frequencies capture the details of an image, while low frequencies capture the overall shape. Due to the diffraction limit, a microscope can only capture frequencies up to a certain point. Roughly speaking, the diffraction limit depends on the wavelength of the length of the strength of your strongest lens.
If you look at an image whose resolution is lower than your camera resolution, you will see that in Fourier space your image lives inside a certain circle. This circle is not sharply defined, but rather smoothed out. To find out what this radius is you could do two things. You could find the circle in Fourier space that contains most of your image. Or, you could transform your image to Fourier spae, cut out everything outside a particular circle, transform back and see if your image has degraded. If your image has not degraded (by much), you know that your circle contains all of your image's frequencies.
Explanation of image
Below, I did this procedure on an artificially created image. The width of the stripes is given by 1 1 2 2 4 4 8 8
etc. In Fourier space I cut out everything outside of the red circle, which corresponds to $\lambda=8$. In the final image you can see that the stripes of width 4 4
, which have a wavelength of 8, can be resolved just fine. But stripes narrower than that are not resolvable.
Scale in Fourier space
Note that an important part of this procedure relies on knowing which parts in Fourier space correspond to which wavelengths. To figure this out you can make use of the following facts.
- I define the window of the image in real space to be of width $[0,x_{max})$. This means the size of a pixel is given by $dx=n/x_{max}$, where $n$ is the number of pixels. Note that $x_{max}$ is excluded because of periodicity.
- The Fast Fourier Transform (FFT) sends this image to $[0,k_{max})$. Likewise, we have $dk=n/k_{max}$. To relate Fourier space to real space we need a linking equation:
\begin{align}
\begin{cases}
n\,dx\,dk=1&\text{$k$ in cycles/length}\\
n\,dx\,dk=2\pi&\text{$k$ in radians/length}
\end{cases}
\end{align}
So $k$ is either defined as $k=2\pi/\lambda$ for radians/length or $k=1/\lambda$ for cycles per length. Numpy, for example, uses cycles per length. But in physics we often use the radians per length convention.
- The FFT defines the (0,0) point of an image as the origin. If you want to display images with the origin in the center, you may need to "roll" the image a few times to place the top-left corner in the center.
As a final note, this is just one hackish way to get the resolution of the image. There are probably better ways.
I hope you enjoy your thesis!
import numpy as np
import matplotlib.pyplot as plt
n = 150
img = np.zeros((n, n))
# Making a multiple resolution image
cur_width = 1
pointer = 0
while pointer < n:
img[pointer:pointer+cur_width,pointer:] = 1
img[pointer:,pointer:pointer+cur_width] = 1
pointer += 2*cur_width
cur_width *= 2
# Adding some noise
img += np.random.rand(*img.shape)*.1
fig, axes = plt.subplots(ncols=3, dpi=200)
plt.rcParams.update({'font.size': 8})
axes[0].imshow(img)
axes[0].set_xlabel('x (pixels)')
# Calculating FFT and centering
fft = np.fft.fftshift(img)
fft = np.fft.fft2(fft)
fft = np.fft.fftshift(fft)
# cutting out hole in frequency space
cutoff_wavelength = 8
cutoff_k = 1/cutoff_wavelength
dx = 1.0
k = np.fft.fftfreq(n, d=dx)
k = np.fft.fftshift(k)
Kx, Ky = np.meshgrid(k, k)
fft_cutoff = fft*(Kx**2 + Ky**2 < cutoff_k**2)
# plotting result with right scale
dk = 1/(n*dx)
krange = n*dk
kmin = -krange/2
kmax = krange/2 - dk
axes[1].imshow(np.log(1 + np.abs(fft)), extent=[kmin, kmax, kmin, kmax])
circ = plt.Circle((0,0), cutoff_k, color='r', fill=False)
axes[1].add_patch(circ)
axes[1].set_xlabel('k (1/pixels)')
# transforming back to real space, making sure the zero frequency is in the (0, 0) corner
img_low_resolution = np.fft.fftshift(fft_cutoff)
img_low_resolution = np.fft.ifft2(img_low_resolution)
img_low_resolution = np.fft.fftshift(img_low_resolution)
img_low_resolution[n-8:n, :] = img[n-8:n,:]*np.max(img_low_resolution)
axes[2].imshow(np.real(img_low_resolution))
axes[2].set_xlabel('x (pixels)')
plt.tight_layout()
plt.show()