├── .gitattributes ├── Earth.png ├── Elizabeth_Tower_London.jpg ├── Malta_Balconies.png ├── README.md └── fourier_synthesis.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Earth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codetoday-london/2D-Fourier-Transforms-In-Python/417d8c046a0100f195ee107a6353d0a8cc7a75a5/Earth.png -------------------------------------------------------------------------------- /Elizabeth_Tower_London.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codetoday-london/2D-Fourier-Transforms-In-Python/417d8c046a0100f195ee107a6353d0a8cc7a75a5/Elizabeth_Tower_London.jpg -------------------------------------------------------------------------------- /Malta_Balconies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codetoday-london/2D-Fourier-Transforms-In-Python/417d8c046a0100f195ee107a6353d0a8cc7a75a5/Malta_Balconies.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2D Fourier Transforms In Python 2 | 3 | This repo is linked to the article ["How to Create Any Image Using Only Sine Functions | 2D Fourier Transforms in Python"](https://thepythoncodingbook.com/2021/08/30/2d-fourier-transform-in-python-and-fourier-synthesis-of-images/) on The Python Coding Book Blog. 4 | 5 | It contains sample images used in the article, and the final version of the code described in the article 6 | -------------------------------------------------------------------------------- /fourier_synthesis.py: -------------------------------------------------------------------------------- 1 | """ 2 | fourier_synthesis.py 3 | Deconstruct any image into its constituent sinusoidal gratings 4 | and reconstruct the image by summing all the terms 5 | 6 | Stephen Gruppetta 7 | ThePythonCodingBook.com 8 | Link to article: https://thepythoncodingbook.com/2021/08/30/2d-fourier-transform-in-python-and-fourier-synthesis-of-images/ 9 | """ 10 | 11 | import numpy as np 12 | import matplotlib.pyplot as plt 13 | 14 | image_filename = "Elizabeth_Tower_London.jpg" 15 | 16 | def calculate_2dft(input): 17 | ft = np.fft.ifftshift(input) 18 | ft = np.fft.fft2(ft) 19 | return np.fft.fftshift(ft) 20 | 21 | def calculate_2dift(input): 22 | ift = np.fft.ifftshift(input) 23 | ift = np.fft.ifft2(ift) 24 | ift = np.fft.fftshift(ift) 25 | return ift.real 26 | 27 | def calculate_distance_from_centre(coords, centre): 28 | # Distance from centre is √(x^2 + y^2) 29 | return np.sqrt( 30 | (coords[0] - centre) ** 2 + (coords[1] - centre) ** 2 31 | ) 32 | 33 | def find_symmetric_coordinates(coords, centre): 34 | return (centre + (centre - coords[0]), 35 | centre + (centre - coords[1])) 36 | 37 | def display_plots(individual_grating, reconstruction, idx): 38 | plt.subplot(121) 39 | plt.imshow(individual_grating) 40 | plt.axis("off") 41 | plt.subplot(122) 42 | plt.imshow(reconstruction) 43 | plt.axis("off") 44 | plt.suptitle(f"Terms: {idx}") 45 | plt.pause(0.01) 46 | 47 | # Read and process image 48 | image = plt.imread(image_filename) 49 | image = image[:, :, :3].mean(axis=2) # Convert to grayscale 50 | 51 | # Array dimensions (array is square) and centre pixel 52 | # Use smallest of the dimensions and ensure it's odd 53 | array_size = min(image.shape) - 1 + min(image.shape) % 2 54 | 55 | # Crop image so it's a square image 56 | image = image[:array_size, :array_size] 57 | centre = int((array_size - 1) / 2) 58 | 59 | # Get all coordinate pairs in the left half of the array, 60 | # including the column at the centre of the array (which 61 | # includes the centre pixel) 62 | coords_left_half = ( 63 | (x, y) for x in range(array_size) for y in range(centre+1) 64 | ) 65 | 66 | # Sort points based on distance from centre 67 | coords_left_half = sorted( 68 | coords_left_half, 69 | key=lambda x: calculate_distance_from_centre(x, centre) 70 | ) 71 | 72 | plt.set_cmap("gray") 73 | 74 | ft = calculate_2dft(image) 75 | 76 | # Show grayscale image and its Fourier transform 77 | plt.subplot(121) 78 | plt.imshow(image) 79 | plt.axis("off") 80 | plt.subplot(122) 81 | plt.imshow(np.log(abs(ft))) 82 | plt.axis("off") 83 | plt.pause(2) 84 | 85 | # Reconstruct image 86 | fig = plt.figure() 87 | # Step 1 88 | # Set up empty arrays for final image and 89 | # individual gratings 90 | rec_image = np.zeros(image.shape) 91 | individual_grating = np.zeros( 92 | image.shape, dtype="complex" 93 | ) 94 | idx = 0 95 | 96 | # All steps are displayed until display_all_until value 97 | display_all_until = 200 98 | # After this, skip which steps to display using the 99 | # display_step value 100 | display_step = 10 101 | # Work out index of next step to display 102 | next_display = display_all_until + display_step 103 | 104 | # Step 2 105 | for coords in coords_left_half: 106 | # Central column: only include if points in top half of 107 | # the central column 108 | if not (coords[1] == centre and coords[0] > centre): 109 | idx += 1 110 | symm_coords = find_symmetric_coordinates( 111 | coords, centre 112 | ) 113 | # Step 3 114 | # Copy values from Fourier transform into 115 | # individual_grating for the pair of points in 116 | # current iteration 117 | individual_grating[coords] = ft[coords] 118 | individual_grating[symm_coords] = ft[symm_coords] 119 | 120 | # Step 4 121 | # Calculate inverse Fourier transform to give the 122 | # reconstructed grating. Add this reconstructed 123 | # grating to the reconstructed image 124 | rec_grating = calculate_2dift(individual_grating) 125 | rec_image += rec_grating 126 | 127 | # Clear individual_grating array, ready for 128 | # next iteration 129 | individual_grating[coords] = 0 130 | individual_grating[symm_coords] = 0 131 | 132 | # Don't display every step 133 | if idx < display_all_until or idx == next_display: 134 | if idx > display_all_until: 135 | next_display += display_step 136 | # Accelerate animation the further the 137 | # iteration runs by increasing 138 | # display_step 139 | display_step += 10 140 | display_plots(rec_grating, rec_image, idx) 141 | 142 | plt.show() --------------------------------------------------------------------------------