# this notebook demonstrates phase bias in fourier synthesis using images
# questions? contact <asaha2[at]lbl.gov>
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
import seaborn as sns
import skimage
from skimage import io
mpl.rcParams['text.usetex'] = True
mpl.rcParams['text.latex.preamble'] = r"\usepackage{sfmath}"
mpl.rcParams['font.family'] = 'sans-serif'
mpl.rcParams['font.sans-serif'] = 'cm'
plt.rcParams['figure.dpi'] = 300
plt.rcParams['savefig.dpi'] = 300
# import iconic duo of images and convert to greyscale (dimensions of images must agree)
img1 = skimage.io.imread('/Users/ambarneil/PycharmProjects/faes_resurrected/kyogre500.png', as_gray=True)
img2 = skimage.io.imread('/Users/ambarneil/PycharmProjects/faes_resurrected/groudon500.png', as_gray=True)
# print dimensions of input images and throw error if they don't match
print(f"Dimensions of image 1: {np.shape(img1)}")
print(f"Dimensions of image 2: {np.shape(img2)}")
if np.shape(img1) != np.shape(img2):
raise ValueError(r"Dimensions of input images do not match. Please resize accordingly...")
elif np.shape(img1) == np.shape(img2):
print(r"Dimensions of input images match. Computing FFTs...")
# calculate 2D FFTs
img1_fft = np.fft.fftshift(np.fft.fft2(img1, norm="backward"))
img2_fft = np.fft.fftshift(np.fft.fft2(img2, norm="backward"))
# plot images alongside their FFTs
rows = 2
columns = 2
fig = plt.figure(figsize=(10, 10))
plt.tight_layout()
fig.add_subplot(rows, columns, 1)
plt.imshow(img1, cmap='gray')
plt.axis('off')
plt.title(r"Image 1")
fig.add_subplot(rows, columns, 2)
plt.imshow(img2, cmap='gray')
plt.axis('off')
plt.title(r"Image 2")
fig.add_subplot(rows, columns, 3)
plt.imshow(np.abs(img1_fft), cmap='cubehelix', norm=LogNorm(vmin=1))
plt.colorbar(fraction=0.045, pad=0.04)
plt.axis('off')
plt.title(r"FFT(Image 1)")
fig.add_subplot(rows, columns, 4)
plt.imshow(np.abs(img2_fft), cmap='cubehelix', norm=LogNorm(vmin=1))
plt.colorbar(fraction=0.045, pad=0.04)
plt.axis('off')
plt.title(r"FFT(Image 2)")
plt.show()
Dimensions of image 1: (500, 500) Dimensions of image 2: (500, 500) Dimensions of input images match. Computing FFTs...
# compress dynamic range of FFTs with log
img1_fft_lnabs = np.log(np.abs(img1_fft)+1)
img2_fft_lnabs = np.log(np.abs(img2_fft)+1)
img1_phase = np.arctan2(np.imag(img1_fft), np.real(img1_fft))
img2_phase = np.arctan2(np.imag(img2_fft), np.real(img2_fft))
# plot histograms
rows = 1
columns = 2
fig = plt.figure(figsize=(10, 5))
plt.tight_layout()
sns.set_style('darkgrid')
fig.add_subplot(rows, columns, 1)
sns.histplot(img1_fft_lnabs.ravel(), bins=100, color=r"#00acd5")
plt.title(r"Histogram of Fourier amplitudes for image 1")
plt.ylabel("Counts")
plt.xlabel("ln(Amplitude)")
plt.ylim(0, 14000)
fig.add_subplot(rows, columns, 2)
sns.histplot(img2_fft_lnabs.ravel(), bins=100, color=r"#ef8fd2")
plt.title(r"Histogram of Fourier amplitudes for image 2")
plt.ylabel("Counts")
plt.xlabel("ln(Amplitude)")
plt.ylim(0, 14000)
plt.show()
img1_phase = np.arctan2(np.imag(img1_fft), np.real(img1_fft))
img2_phase = np.arctan2(np.imag(img2_fft), np.real(img2_fft))
# define binning
rbins1 = np.linspace(0, 8, 40)
abins1 = np.linspace(-np.pi, np.pi, 81)
rbins2 = np.linspace(0, 8, 40)
abins2 = np.linspace(-np.pi, np.pi, 81)
#calculate histogram
hist1, _, _ = np.histogram2d(img1_phase.ravel(), img1_fft_lnabs.ravel(), bins=(abins1, rbins1))
A, R = np.meshgrid(abins1, rbins1)
hist2, _, _ = np.histogram2d(img2_phase.ravel(), img2_fft_lnabs.ravel(), bins=(abins2, rbins2))
A2, R2 = np.meshgrid(abins2, rbins2)
# plot
fig, ax = plt.subplots(1, 2, subplot_kw=dict(projection="polar"))
plt.tight_layout()
ax[0].set_title(r"Polar histogram of phases for image 1")
ax[1].set_title(r"Polar histogram of phases for image 2")
pc = ax[0].pcolormesh(A, R, hist1.T, cmap="cubehelix")
pc2 = ax[1].pcolormesh(A2, R2, hist2.T, cmap="cubehelix")
plt.show()
# extract amplitudes and phases from FFTs
img1_amplitude = np.abs(img1_fft)
img2_amplitude = np.abs(img2_fft)
img1_phase = np.arctan2(np.imag(img1_fft), np.real(img1_fft))
img2_phase = np.arctan2(np.imag(img2_fft), np.real(img2_fft))
# reconstruct image from amplitudes of image 1 and phases of image 2
img1_img2_comb = np.multiply(img1_amplitude, np.exp(1j * img2_phase))
img1amp_img2phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img1_img2_comb)))
shift1 = img1amp_img2phase + np.min(img1amp_img2phase)
shift1[shift1 > 255] = 255
img1amp_img2phase[img1amp_img2phase > 255] = 255
img1amp_img2phase[img1amp_img2phase < 0] = 0
# reconstruct image from amplitudes of image 2 and phases of image 1
img2_img1_comb = np.multiply(img2_amplitude, np.exp(1j * img1_phase))
img2amp_img1phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img2_img1_comb)))
shift2 = img2amp_img1phase + np.min(img2amp_img1phase)
shift2[shift2 > 255] = 255
img2amp_img1phase[img2amp_img1phase > 255] = 255
img2amp_img1phase[img2amp_img1phase < 0] = 0
# control: reconstruct image from amplitudes of image 1 and phases of image 1
img1_img1_comb = np.multiply(img1_amplitude, np.exp(1j * img1_phase))
img1amp_img1phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img1_img1_comb)))
shift3 = img1amp_img1phase + np.min(img1amp_img1phase)
shift3[shift3 > 255] = 255
img1amp_img1phase[img1amp_img1phase > 255] = 255
img1amp_img1phase[img1amp_img1phase < 0] = 0
# control: reconstruct image from amplitudes of image 2 and phases of image 2
img2_img2_comb = np.multiply(img2_amplitude, np.exp(1j * img2_phase))
img2amp_img2phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img2_img2_comb)))
shift4 = img2amp_img2phase + np.min(img2amp_img2phase)
shift4[shift4 > 255] = 255
img2amp_img2phase[img2amp_img2phase > 255] = 255
img2amp_img2phase[img2amp_img2phase < 0] = 0
# plot reconstructions
rows = 2
columns = 2
fig = plt.figure(figsize=(10, 10))
plt.tight_layout()
fig.add_subplot(rows, columns, 1)
plt.imshow(img1amp_img1phase, cmap='gray')
plt.axis('off')
plt.title(r"Image 1's amplitudes + image 1's phases")
fig.add_subplot(rows, columns, 2)
plt.imshow(img1amp_img2phase, cmap='gray')
plt.axis('off')
plt.title(r"Image 1's amplitudes + image 2's phases")
fig.add_subplot(rows, columns, 3)
plt.imshow(img2amp_img2phase, cmap='gray')
plt.axis('off')
plt.title(r"Image 2's amplitudes + image 2's phases")
fig.add_subplot(rows, columns, 4)
plt.imshow(img2amp_img1phase, cmap='gray')
plt.axis('off')
plt.title(r"Image 2's amplitudes + image 1's phases")
plt.show()
"""
inspired by these seminal references:
[1] G. N. Ramachandran and R. Srinivasan, "An apparent paradox in crystal structure analysis," Nature 190, 159-161 (1961)
<https://dx.doi.org/10.1038/190159a0>
[2] A. V. Oppenheim and J. S. Lim, "The importance of phase in signals," Proceedings of the IEEE 69, 529-541 (1981)
<https://dx.doi.org/10.1109/PROC.1981.12022>
[3] R. J. Read, "Model phases: probabilities and bias," Methods in Enzymology 277, 110-128 (1997)
<https://dx.doi.org/10.1016/S0076-6879(97)77009-5>
for a slightly contrarian view on the relative importance of phases vs. amplitudes, see:
[4] I. Juvells et al, "The role of amplitude and phase of the Fourier transform in the digital image processing," Am. J. Phys. 59, 744 (1991)
<https://dx.doi.org/10.1119/1.16754>
[5] A. W. Lohmann et al, "Significance of phase and amplitude in the Fourier domain," J. Opt. Soc. Am. A 14, 2901 (1997)
<https://dx.doi.org/10.1364/JOSAA.14.002901>
note that the "counterexamples" cited in references [4] and [5] seem cherrypicked and certainly irrelevant to crystallography
"""
# generate random uniform distributions of amplitudes and phases using min/max of genuine arrays as upper and lower bounds
random_amps1 = np.random.uniform(low=np.min(img1_amplitude), high=np.max(img1_amplitude), size=np.shape(img1_amplitude))
random_amps2 = np.random.uniform(low=np.min(img2_amplitude), high=np.max(img2_amplitude), size=np.shape(img2_amplitude))
random_phases1 = np.random.uniform(low=np.min(img1_phase), high=np.max(img1_phase), size=np.shape(img1_phase))
random_phases2 = np.random.uniform(low=np.min(img2_phase), high=np.max(img2_phase), size=np.shape(img2_phase))
"""
# generate random gaussian distributions of amplitudes and phases using mean/std dev of genuine arrays as input parameters
random_amps1 = np.random.normal(loc=np.mean(img1_amplitude), scale=np.std(img1_amplitude), size=np.shape(img1_amplitude))
random_amps2 = np.random.normal(loc=np.mean(img2_amplitude), scale=np.std(img2_amplitude), size=np.shape(img2_amplitude))
random_phases1 = np.random.normal(loc=np.mean(img1_phase), scale=np.std(img1_phase), size=np.shape(img1_phase))
random_phases2 = np.random.normal(loc=np.mean(img2_phase), scale=np.std(img2_phase), size=np.shape(img2_phase))
"""
# reconstruct image from amplitudes of image 1 and random phases
img1_rand1_comb = np.multiply(img1_amplitude, np.exp(1j * random_phases1))
img1amp_rand1phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img1_rand1_comb)))
shift5 = img1amp_rand1phase + np.min(img1amp_rand1phase)
shift5[shift5 > 255] = 255
img1amp_rand1phase[img1amp_rand1phase > 255] = 255
img1amp_rand1phase[img1amp_rand1phase < 0] = 0
# reconstruct image from phases of image 1 and random amplitudes
img1_rand2_comb = np.multiply(random_amps1, np.exp(1j * img1_phase))
rand1amp_img1phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img1_rand2_comb)))
shift6 = rand1amp_img1phase + np.min(rand1amp_img1phase)
shift6[shift6 > 255] = 255
rand1amp_img1phase[rand1amp_img1phase > 255] = 255
rand1amp_img1phase[rand1amp_img1phase < 0] = 0
# reconstruct image from amplitudes of image 2 and random phases
img2_rand1_comb = np.multiply(img2_amplitude, np.exp(1j * random_phases2))
img2amp_rand2phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img2_rand1_comb)))
shift7 = img2amp_rand2phase + np.min(img2amp_rand2phase)
shift7[shift7 > 255] = 255
img2amp_rand2phase[img2amp_rand2phase > 255] = 255
img2amp_rand2phase[img2amp_rand2phase < 0] = 0
# reconstruct image from phases of image 2 and random amplitudes
img2_rand2_comb = np.multiply(random_amps2, np.exp(1j * img2_phase))
rand2amp_img2phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img2_rand2_comb)))
shift8 = rand2amp_img2phase + np.min(rand2amp_img2phase)
shift8[shift8 > 255] = 255
rand2amp_img2phase[rand2amp_img2phase > 255] = 255
rand2amp_img2phase[rand2amp_img2phase < 0] = 0
# plot reconstructions
rows = 2
columns = 2
fig = plt.figure(figsize=(10, 10))
plt.tight_layout()
fig.add_subplot(rows, columns, 1)
plt.imshow(img1amp_rand1phase, cmap='gray')
plt.axis('off')
plt.title(r"Image 1's amplitudes + random phases")
fig.add_subplot(rows, columns, 2)
plt.imshow(rand1amp_img1phase, cmap='gray')
plt.axis('off')
plt.title(r"Random amplitudes + image 1's phases")
fig.add_subplot(rows, columns, 3)
plt.imshow(img2amp_rand2phase, cmap='gray')
plt.axis('off')
plt.title(r"Image 2's amplitudes + random phases")
fig.add_subplot(rows, columns, 4)
plt.imshow(rand2amp_img2phase, cmap='gray')
plt.axis('off')
plt.title(r"Random amplitudes + image 2's phases")
plt.show()
# generate flat arrays consisting of a single value, the median of the random distributions computed earlier
flat_amps1 = np.full(np.shape(random_amps1), np.median(random_amps1))
flat_amps2 = np.full(np.shape(random_amps2), np.median(random_amps2))
flat_phases1 = np.full(np.shape(random_phases1), np.median(random_phases1))
flat_phases2 = np.full(np.shape(random_phases2), np.median(random_phases2))
# reconstruct image from amplitudes of image 1 and median phases
img1_med1_comb = np.multiply(img1_amplitude, np.exp(1j * flat_phases1))
img1amp_med1phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img1_med1_comb)))
shift9 = img1amp_med1phase + np.min(img1amp_med1phase)
shift9[shift9 > 255] = 255
img1amp_med1phase[img1amp_med1phase > 255] = 255
img1amp_med1phase[img1amp_med1phase < 0] = 0
# reconstruct image from phases of image 1 and median amplitudes
img1_med2_comb = np.multiply(flat_amps1, np.exp(1j * img1_phase))
med1amp_img1phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img1_med2_comb)))
shift10 = med1amp_img1phase + np.min(med1amp_img1phase)
shift10[shift10 > 255] = 255
med1amp_img1phase[med1amp_img1phase > 255] = 255
med1amp_img1phase[med1amp_img1phase < 0] = 0
# reconstruct image from amplitudes of image 2 and median phases
img2_med1_comb = np.multiply(img2_amplitude, np.exp(1j * flat_phases2))
img2amp_med2phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img2_med1_comb)))
shift11 = img2amp_med2phase + np.min(img2amp_med2phase)
shift11[shift11 > 255] = 255
img2amp_med2phase[img2amp_med2phase > 255] = 255
img2amp_med2phase[img2amp_med2phase < 0] = 0
# reconstruct image from phases of image 2 and median amplitudes
img2_med2_comb = np.multiply(flat_amps2, np.exp(1j * img2_phase))
med2amp_img2phase = np.abs(np.fft.ifft2(np.fft.ifftshift(img2_med2_comb)))
shift12 = med2amp_img2phase + np.min(med2amp_img2phase)
shift12[shift12 > 255] = 255
med2amp_img2phase[med2amp_img2phase > 255] = 255
med2amp_img2phase[med2amp_img2phase < 0] = 0
# plot reconstructions
rows = 2
columns = 2
fig = plt.figure(figsize=(10, 10))
plt.tight_layout()
fig.add_subplot(rows, columns, 1)
plt.imshow(img1amp_med1phase, cmap='gray')
plt.axis('off')
plt.title(r"Image 1's amplitudes + median phases")
fig.add_subplot(rows, columns, 2)
plt.imshow(med1amp_img1phase, cmap='gray')
plt.axis('off')
plt.title(r"Median amplitudes + image 1's phases")
fig.add_subplot(rows, columns, 3)
plt.imshow(img2amp_med2phase, cmap='gray')
plt.axis('off')
plt.title(r"Image 2's amplitudes + median phases")
fig.add_subplot(rows, columns, 4)
plt.imshow(med2amp_img2phase, cmap='gray')
plt.axis('off')
plt.title(r"Median amplitudes + image 2's phases")
plt.show()