-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfft.py
366 lines (286 loc) · 14.2 KB
/
fft.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
import argparse
import math
import time
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
from PIL import Image, ImageOps
import numpy as np
from scipy.sparse import csr_matrix, save_npz
from matplotlib import colors
from dft import DFT
def closestPowerOfTwo(n):
return 2**(n-1).bit_length()
def main():
# Getting command line input.
command = None
try:
command = parser()
except BaseException as e:
print("ERROR\t Incorrect command syntax")
return
mode = command.mode
image = command.image
# Mode 1
if mode == 1:
# Opening the image.
init = Image.open(image)
init = ImageOps.grayscale(init)
img = np.array(init)
# Computing the dimensions of the image array.
initial_shape = img.shape
# Creating a new array with dimensions that are powers of 2.
# Populating that new array with the img array.
updated_image = np.zeros(
(closestPowerOfTwo(img.shape[0]), closestPowerOfTwo(img.shape[1])))
updated_image[:img.shape[0], :img.shape[1]] = img
# Computing the 2D FFT of the image.
transient_img = DFT.FFT_Two_Dimensions(updated_image)
# Converting the array into an image for resizing using PIL.
temp = Image.fromarray(np.log(1+np.abs(transient_img)))
# Resizing image to original size and converting back into array to be able to .imshow()
final_img = temp.resize((img.shape[1], img.shape[0]), Image.ANTIALIAS)
final_img = np.array(final_img)
# Creating the plot presentation.
fig, ax = plt.subplots(1, 2)
ax[0].imshow(img, "gray")
ax[0].set_title("Original")
ax[1].imshow(final_img, "gray")
ax[1].set_title("2D FFT")
fig.suptitle("Mode 1: 2-Dimensional Fast Fourier Transform of Image")
plt.show()
# Mode 2
elif mode == 2:
# Opening the image.
init = Image.open(image)
init = ImageOps.grayscale(init)
img = np.array(init)
# Computing the dimensions of the image array.
initial_shape = img.shape
# Creating a new array with dimensions that are powers of 2.
# Populating that new array with the img array.
updated_image = np.zeros(
(closestPowerOfTwo(img.shape[0]), closestPowerOfTwo(img.shape[1])))
updated_image[:img.shape[0], :img.shape[1]] = img
# Computing the 2D FFT of the image.
transient_img = DFT.FFT_Two_Dimensions(updated_image)
I, J = transient_img.shape
threshold = 0.09
transient_img[int(I*threshold):int(I*(1-threshold))] = 0
transient_img[:, int(J*threshold):int(J*(1-threshold))] = 0
# Computing the Inverse 2D FFT of the image.
transient_img = DFT.FFT_Two_Dimensions_Inverse(transient_img).real
denoised_img = transient_img[:img.shape[0], :img.shape[1]]
# Printing denoising data.
threshold_inverse = 1-threshold
print("Fraction of Non-Zero Coefficients {} Representing ({}, {}) out of ({}, {}) Pixels".format(
threshold_inverse**2, int(I*threshold_inverse), int(J*threshold_inverse), I, J))
# Creating the plot presentation.
fig, ax = plt.subplots(1, 2)
ax[0].imshow(img, "gray")
ax[0].set_title("Original")
ax[1].imshow(denoised_img, "gray")
ax[1].set_title("Denoising Using Only Cutoff Coefficient of 0.09")
fig.suptitle(
"Mode 2: Denoised Image Using 2-Dimensional Fast Fourier Transform")
plt.show()
# Mode 3
elif mode == 3:
# Opening the image.
init = Image.open(image)
init = ImageOps.grayscale(init)
img = np.array(init)
# Computing the dimensions of the image array.
initial_shape = img.shape
# Creating a new array with dimensions that are powers of 2.
# Populating that new array with the img array.
updated_image = np.zeros(
(closestPowerOfTwo(img.shape[0]), closestPowerOfTwo(img.shape[1])))
updated_image[:img.shape[0], :img.shape[1]] = img
# Creating multiple 2D arrays for all compression levels.
transient_img_a = DFT.FFT_Two_Dimensions(updated_image)
transient_img_b = DFT.FFT_Two_Dimensions(updated_image)
transient_img_c = DFT.FFT_Two_Dimensions(updated_image)
transient_img_d = DFT.FFT_Two_Dimensions(updated_image)
transient_img_e = DFT.FFT_Two_Dimensions(updated_image)
# Computing the threshold percentile value that will be used to set coefficients equal to 0.
abs_values = np.sort(np.abs(transient_img_a.flatten()))
percentile_a = 0.20 * abs_values.shape[0]
percentile_b = 0.40 * abs_values.shape[0]
percentile_c = 0.60 * abs_values.shape[0]
percentile_d = 0.80 * abs_values.shape[0]
percentile_e = 0.95 * abs_values.shape[0]
percentile_f = abs_values.shape[0] - 1
cutoff_a = abs_values[int(percentile_a)]
cutoff_b = abs_values[int(percentile_b)]
cutoff_c = abs_values[int(percentile_c)]
cutoff_d = abs_values[int(percentile_d)]
cutoff_e = abs_values[int(percentile_e)]
# Iterating through each of the 2D arrays and setting coefficients that are under the cutoff equal to zero.
np.place(transient_img_a, abs(transient_img_a) < cutoff_a, [0])
np.place(transient_img_b, abs(transient_img_b) < cutoff_b, [0])
np.place(transient_img_c, abs(transient_img_c) < cutoff_c, [0])
np.place(transient_img_d, abs(transient_img_d) < cutoff_d, [0])
np.place(transient_img_e, abs(transient_img_e) < cutoff_e, [0])
# Counting the number of zeros in the 2D arrays.
count_a = 0
count_b = 0
count_c = 0
count_d = 0
count_e = 0
count_f = 0
I, J = transient_img_a.shape
for i in range(I):
for j in range(J):
if np.abs(transient_img_a[i, j]) == 0:
count_a += 1
if np.abs(transient_img_b[i, j]) == 0:
count_b += 1
if np.abs(transient_img_c[i, j]) == 0:
count_c += 1
if np.abs(transient_img_d[i, j]) == 0:
count_d += 1
if np.abs(transient_img_e[i, j]) == 0:
count_e += 1
# Printing the number of non-zero Fourier coefficients for each compression level.
print("For 20% compression, we have {} non-zero Fourier coefficients.".format(524288 - count_a))
print("For 40% compression, we have {} non-zero Fourier coefficients.".format(524288 - count_b))
print("For 60% compression, we have {} non-zero Fourier coefficients.".format(524288 - count_c))
print("For 80% compression, we have {} non-zero Fourier coefficients.".format(524288 - count_d))
print("For 95% compression, we have {} non-zero Fourier coefficients.".format(524288 - count_e))
# Saving compressed arrays as .txt files and .npz files to better see file size difference.
# Nope: We also save .npz files because file size difference is more apparent.
name = "compressed_" + str(int(20)) + ".txt"
np.savetxt(name, transient_img_a)
name = "compressed_" + str(int(20))
save_npz(name, csr_matrix(transient_img_a))
name = "compressed_" + str(int(40)) + ".txt"
np.savetxt(name, transient_img_b)
name = "compressed_" + str(int(40))
save_npz(name, csr_matrix(transient_img_b))
name = "compressed_" + str(int(60)) + ".txt"
np.savetxt(name, transient_img_c)
name = "compressed_" + str(int(60))
save_npz(name, csr_matrix(transient_img_c))
name = "compressed_" + str(int(80)) + ".txt"
np.savetxt(name, transient_img_d)
name = "compressed_" + str(int(80))
save_npz(name, csr_matrix(transient_img_d))
name = "compressed_" + str(int(95)) + ".txt"
np.savetxt(name, transient_img_e)
name = "compressed_" + str(int(95))
save_npz(name, csr_matrix(transient_img_e))
# Computing the Inverse 2D FFT of the compressed image.
done_a = DFT.FFT_Two_Dimensions_Inverse(transient_img_a).real
compressed_img_a = done_a[:img.shape[0], :img.shape[1]]
done_b = DFT.FFT_Two_Dimensions_Inverse(transient_img_b).real
compressed_img_b = done_b[:img.shape[0], :img.shape[1]]
done_c = DFT.FFT_Two_Dimensions_Inverse(transient_img_c).real
compressed_img_c = done_c[:img.shape[0], :img.shape[1]]
done_d = DFT.FFT_Two_Dimensions_Inverse(transient_img_d).real
compressed_img_d = done_d[:img.shape[0], :img.shape[1]]
done_e = DFT.FFT_Two_Dimensions_Inverse(transient_img_e).real
compressed_img_e = done_e[:img.shape[0], :img.shape[1]]
# Creating the plot presentation.
fig, ax = plt.subplots(2, 3)
ax[0, 0].imshow(img, "gray")
ax[0, 0].set_title("Original")
ax[0, 1].imshow(compressed_img_a, "gray")
ax[0, 1].set_title("20% Compression")
ax[0, 2].imshow(compressed_img_b, "gray")
ax[0, 2].set_title("40% Compression")
ax[1, 0].imshow(compressed_img_c, "gray")
ax[1, 0].set_title("60% Compression")
ax[1, 1].imshow(compressed_img_d, "gray")
ax[1, 1].set_title("80% Compression")
ax[1, 2].imshow(compressed_img_e, "gray")
ax[1, 2].set_title("95% Compression")
fig.suptitle(
"Mode 3: Compression")
plt.show()
# Mode 4
elif mode == 4:
# Setting up the plot.
fig, ax = plt.subplots()
ax.set_xlabel('Problem Size NxN')
ax.set_ylabel('Runtime In Seconds')
ax.set_title('Mode 4: Runtime Over Problem Size')
# Setting up our testing parameters with initial exponent, the number of trials per input,
# the counter for adding plot points at each exponent, and the initial data arrays for
# 2D FFT and 2D DFT.
exponent = 5
trials = 10
counter = 0
x_axis_data = np.zeros(6)
fft_data = np.zeros(6)
fft_data_std = np.zeros(6)
# Note: We only take two plot points for the 2D DFT because computing times for inputs with size >= 2^7
# take extremely long to compute.
dft_data = np.zeros(2)
dft_data_std = np.zeros(2)
# While loop that goes through inputs of size 2^5 to 2^10.
while exponent <= 10:
# Anouncing start of trials for particular input.
print("Starting trials with input of size " +
str(2**exponent) + "x" + str(2**exponent))
# Data arrays for both 2D Fourier Transforms.
runtimes_dft = np.zeros(10)
runtimes_fft = np.zeros(10)
# Setting up the random 2D array of size 2^exponent x 2^exponent.
x = np.random.rand(2**exponent, 2**exponent)
# Doing 10 independent trials with 10 different inputs.
for i in range(10):
# Computing runtime for 2D FFT.
start = time.time()
DFT.FFT_Two_Dimensions(x)
end = time.time()
runtimes_fft[i] = (end - start)
# If statement to prevent computation of 2D DFT as it takes too long for inputs of size 128x128.
if exponent <= 6:
# Computing runtime for 2D DFT.
start = time.time()
DFT.DFT_Two_Dimensions(x)
end = time.time()
runtimes_dft[i] = (end - start)
print("\tRunning trial " + str(i))
# Computing average and standart deviation of runtimes.
average_runtime_fft = np.average(runtimes_fft)
std_runtime_fft = np.std(runtimes_fft)
# Storing the data points in the fft_data and fft_data_std arrays.
fft_data[counter] = average_runtime_fft
fft_data_std[counter] = std_runtime_fft
# If statement to prevent computation of 2D DFT as it takes too long for inputs of size 128x128.
if exponent <= 6:
# Computing average and standart deviation of runtimes.
average_runtime_dft = np.average(runtimes_dft)
std_runtime_dft = np.std(runtimes_dft)
# Storing the data points in the dft_data and dft_data_std arrays.
dft_data[counter] = average_runtime_dft
dft_data_std[counter] = std_runtime_dft
# Printing Average Runtime and Standard Deviation for 2D DFT.
print("\t2D DFT:\tAverage Runtime: " + str(average_runtime_dft) +
"\tRuntime Standard Deviation: " + str(std_runtime_dft))
# Storing input sizes for the x-axis.
x_axis_data[counter] = 2**exponent
# Printing Average Runtime and Standard Deviation for 2D FFT.
print("\t2D FFT:\tAverage Runtime: " + str(average_runtime_fft) +
"\tRuntime Standard Deviation: " + str(std_runtime_fft) + "\n")
# Incrementing counter and exponent.
counter += 1
exponent += 1
# Plotting both 2D FFT and 2D DFT.
plt.errorbar(x_axis_data, fft_data, yerr=(fft_data_std*2), fmt='r--')
plt.errorbar(x_axis_data[0:2], dft_data,
yerr=(dft_data_std*2), fmt='g--')
plt.show()
# Invalid Mode.
else:
print("Error:\t There are only 4 modes that this program works in.")
def parser():
parser = argparse.ArgumentParser()
parser.add_argument('-m', action='store', dest='mode',
help='- [1] (Default) for fast mode where the image is converted into its FFT form and displayed \n- [2] for denoising where the image is denoised by applying an FFT, truncating high frequencies and then displayed \n- [3] for compressing and saving the image \n- [4] for plotting the runtime graphs for the report', type=int, default=1)
parser.add_argument('-i', action='store', dest='image',
help='Filename of the image we wish to take the DFT of', type=str, default='moonlanding.png')
return parser.parse_args()
if __name__ == "__main__":
main()