staghado commited on
Commit
c371552
1 Parent(s): 3cabf9c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -89
app.py CHANGED
@@ -2,16 +2,13 @@ import numpy as np
2
  import matplotlib.pyplot as plt
3
  import matplotlib.animation as animation
4
  from PIL import Image
5
- import io
6
- import os
7
  import cv2
8
  from math import tau
9
  import gradio as gr
10
  from concurrent.futures import ThreadPoolExecutor
11
  import tempfile
12
 
13
- def fourier_transform_drawing(input_image, frames, coefficients, img_size, blur_kernel_size, desired_range, num_points, theta_points):
14
- # Convert PIL to OpenCV image
15
  img = cv2.cvtColor(np.array(input_image), cv2.COLOR_RGB2BGR)
16
  img = cv2.resize(img, (img_size, img_size), interpolation=cv2.INTER_AREA)
17
  imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
@@ -19,17 +16,8 @@ def fourier_transform_drawing(input_image, frames, coefficients, img_size, blur_
19
  _, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
20
  contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
21
 
22
- # find the contour with the largest area
23
  largest_contour_idx = np.argmax([cv2.contourArea(c) for c in contours])
24
  largest_contour = contours[largest_contour_idx]
25
-
26
- # def combine_all_contours(contours):
27
- # combined_contour = np.array([], dtype=np.int32).reshape(0, 1, 2)
28
- # for contour in contours:
29
- # combined_contour = np.vstack((combined_contour, contour))
30
- # return combined_contour
31
-
32
- # largest_contour = combine_all_contours(contours)
33
  verts = [tuple(coord) for coord in largest_contour.squeeze()]
34
  xs, ys = np.asarray(list(zip(*verts)))
35
  x_range, y_range = np.max(xs) - np.min(xs), np.max(ys) - np.min(ys)
@@ -37,23 +25,28 @@ def fourier_transform_drawing(input_image, frames, coefficients, img_size, blur_
37
  xs = (xs - np.mean(xs)) * scale_x
38
  ys = (-ys + np.mean(ys)) * scale_y
39
 
 
 
 
 
 
 
 
40
  t_list = np.linspace(0, tau, len(xs))
41
  t_values = np.linspace(0, tau, num_points)
42
  f_precomputed = np.interp(t_values, t_list, xs + 1j * ys)
43
 
44
- def compute_cn(f_exp, n, t_values):
45
- coef = np.trapz(f_exp * np.exp(-n * t_values * 1j), t_values) / tau
46
- return coef
47
-
48
  N = coefficients
49
  indices = [0] + [j for i in range(1, N + 1) for j in (i, -i)]
50
-
51
  with ThreadPoolExecutor(max_workers=8) as executor:
52
- coefs = list(executor.map(lambda n: (compute_cn(f_precomputed, n, t_values), n), indices))
 
 
53
 
 
54
  fig, ax = plt.subplots()
55
- circles = [ax.plot([], [], 'b-')[0] for _ in range(-N, N + 1)]
56
- circle_lines = [ax.plot([], [], 'g-')[0] for _ in range(-N, N + 1)]
57
  drawing, = ax.plot([], [], 'r-', linewidth=2)
58
 
59
  ax.set_xlim(-desired_range, desired_range)
@@ -61,82 +54,75 @@ def fourier_transform_drawing(input_image, frames, coefficients, img_size, blur_
61
  ax.set_axis_off()
62
  ax.set_aspect('equal')
63
  fig.set_size_inches(15, 15)
64
-
65
- draw_x, draw_y = [], []
66
- theta = np.linspace(0, tau, theta_points)
67
- coefs_static = [(np.linalg.norm(c), fr) for c, fr in coefs]
68
- last_image = None
69
-
70
- # Initialize the background
71
  fig.canvas.draw()
72
  background = fig.canvas.copy_from_bbox(ax.bbox)
73
-
74
- def animate(i, coefs, time, fig, ax, background, circles, circle_lines, drawing, draw_x, draw_y, coefs_static, theta):
75
- # Restore the background to erase old frames
76
- fig.canvas.restore_region(background)
77
-
78
- center = (0, 0)
79
- for idx, (r, fr) in enumerate(coefs_static):
80
- c_dynamic = coefs[idx][0] * np.exp(1j * (fr * tau * time[i]))
81
- x, y = center[0] + r * np.cos(theta), center[1] + r * np.sin(theta)
82
- circle_lines[idx].set_data([center[0], center[0] + np.real(c_dynamic)], [center[1], center[1] + np.imag(c_dynamic)])
83
- circles[idx].set_data(x, y)
84
- center = (center[0] + np.real(c_dynamic), center[1] + np.imag(c_dynamic))
85
-
86
- draw_x.append(center[0])
87
- draw_y.append(center[1])
88
- drawing.set_data(draw_x[:i+1], draw_y[:i+1])
89
-
90
- # Draw only the updated elements
91
- for circle in circles:
92
- ax.draw_artist(circle)
93
- for line in circle_lines:
94
- ax.draw_artist(line)
95
- ax.draw_artist(drawing)
96
-
97
- # Blit only the updated area
98
- fig.canvas.blit(ax.bbox)
99
-
100
- # Capture the current canvas state as a PIL Image
101
- canvas = fig.canvas
102
- w, h = canvas.get_width_height()
103
- buf = np.frombuffer(canvas.buffer_rgba(), dtype=np.uint8)
104
- image = Image.fromarray(buf.reshape(h, w, 4), 'RGBA').convert('RGB')
105
- last_image = image
106
-
107
- yield (last_image, None)
108
-
109
- # Generate and yield images for each frame
110
  time = np.linspace(0, 1, num=frames)
111
- for frame in range(frames):
112
- yield from animate(frame, coefs, time, fig, ax, background, circles, circle_lines, drawing, draw_x, draw_y, coefs_static, theta)
113
 
114
- # Generate final animation
 
 
 
 
 
 
 
 
 
115
  with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
116
- anim = animation.FuncAnimation(fig, animate, frames=frames, interval=5, fargs=(coefs, np.linspace(0, 1, num=frames)))
117
  anim.save(temp_file.name, fps=15)
 
118
 
119
- yield (last_image, temp_file.name)
120
-
121
- # Gradio interface setup
122
- interface = gr.Interface(
123
- fn=fourier_transform_drawing,
124
- inputs=[
125
- gr.Image(label="Input Image", sources=['upload'], type="pil"),
126
- gr.Slider(minimum=5, maximum=500, value=100, label="Number of Frames"),
127
- gr.Slider(minimum=1, maximum=500, value=50, label="Number of Coefficients"),
128
- gr.Number(value=224, label="Image Size (px)", precision=0),
129
- gr.Slider(minimum=3, maximum=11, step=2, value=5, label="Blur Kernel Size (odd number)"),
130
- gr.Number(value=400, label="Desired Range for Scaling", precision=0),
131
- gr.Number(value=1000, label="Number of Points for Integration", precision=0),
132
- gr.Slider(minimum=50, maximum=500, value=80, label="Theta Points for Animation")
133
- ],
134
- outputs=["image", gr.Video()],
135
- title="Fourier Transform Drawing",
136
- description="Upload an image and generate a Fourier Transform drawing animation.",
137
- )
138
 
139
  if __name__ == "__main__":
140
- # define queue - required for generators
141
  interface.queue()
142
  interface.launch()
 
2
  import matplotlib.pyplot as plt
3
  import matplotlib.animation as animation
4
  from PIL import Image
 
 
5
  import cv2
6
  from math import tau
7
  import gradio as gr
8
  from concurrent.futures import ThreadPoolExecutor
9
  import tempfile
10
 
11
+ def process_image(input_image, img_size, blur_kernel_size, desired_range):
 
12
  img = cv2.cvtColor(np.array(input_image), cv2.COLOR_RGB2BGR)
13
  img = cv2.resize(img, (img_size, img_size), interpolation=cv2.INTER_AREA)
14
  imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 
16
  _, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
17
  contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
18
 
 
19
  largest_contour_idx = np.argmax([cv2.contourArea(c) for c in contours])
20
  largest_contour = contours[largest_contour_idx]
 
 
 
 
 
 
 
 
21
  verts = [tuple(coord) for coord in largest_contour.squeeze()]
22
  xs, ys = np.asarray(list(zip(*verts)))
23
  x_range, y_range = np.max(xs) - np.min(xs), np.max(ys) - np.min(ys)
 
25
  xs = (xs - np.mean(xs)) * scale_x
26
  ys = (-ys + np.mean(ys)) * scale_y
27
 
28
+ return xs, ys
29
+
30
+ def compute_cn(f_exp, n, t_values):
31
+ coef = np.trapz(f_exp * np.exp(-n * t_values * 1j), t_values) / tau
32
+ return coef
33
+
34
+ def calculate_fourier_coefficients(xs, ys, num_points, coefficients):
35
  t_list = np.linspace(0, tau, len(xs))
36
  t_values = np.linspace(0, tau, num_points)
37
  f_precomputed = np.interp(t_values, t_list, xs + 1j * ys)
38
 
 
 
 
 
39
  N = coefficients
40
  indices = [0] + [j for i in range(1, N + 1) for j in (i, -i)]
 
41
  with ThreadPoolExecutor(max_workers=8) as executor:
42
+ coefs = list(executor.map(lambda n: compute_cn(f_precomputed, n, t_values), indices))
43
+
44
+ return coefs
45
 
46
+ def setup_animation_env(img_size, desired_range, coefficients):
47
  fig, ax = plt.subplots()
48
+ circles = [ax.plot([], [], 'b-')[0] for _ in range(-coefficients, coefficients + 1)]
49
+ circle_lines = [ax.plot([], [], 'g-')[0] for _ in range(-coefficients, coefficients + 1)]
50
  drawing, = ax.plot([], [], 'r-', linewidth=2)
51
 
52
  ax.set_xlim(-desired_range, desired_range)
 
54
  ax.set_axis_off()
55
  ax.set_aspect('equal')
56
  fig.set_size_inches(15, 15)
 
 
 
 
 
 
 
57
  fig.canvas.draw()
58
  background = fig.canvas.copy_from_bbox(ax.bbox)
59
+
60
+ return fig, ax, background, circles, circle_lines, drawing
61
+
62
+ def animate(frame, coefs, time, fig, ax, background, circles, circle_lines, drawing, draw_x, draw_y, coefs_static, theta):
63
+ fig.canvas.restore_region(background)
64
+
65
+ center = (0, 0)
66
+ for idx, (r, fr) in enumerate(coefs_static):
67
+ c_dynamic = coefs[idx][0] * np.exp(1j * (fr * tau * time[frame]))
68
+ x, y = center[0] + r * np.cos(theta), center[1] + r * np.sin(theta)
69
+ circle_lines[idx].set_data([center[0], center[0] + np.real(c_dynamic)], [center[1], center[1] + np.imag(c_dynamic)])
70
+ circles[idx].set_data(x, y)
71
+ center = (center[0] + np.real(c_dynamic), center[1] + np.imag(c_dynamic))
72
+
73
+ draw_x.append(center[0])
74
+ draw_y.append(center[1])
75
+ drawing.set_data(draw_x, draw_y)
76
+
77
+ for circle in circles:
78
+ ax.draw_artist(circle)
79
+ for line in circle_lines:
80
+ ax.draw_artist(line)
81
+ ax.draw_artist(drawing)
82
+
83
+ fig.canvas.blit(ax.bbox)
84
+
85
+ def generate_animation(frames, coefs, img_size, desired_range, theta_points, coefficients):
86
+ fig, ax, background, circles, circle_lines, drawing = setup_animation_env(img_size, desired_range, coefficients)
87
+ coefs_static = [(np.linalg.norm(c), fr) for c, fr in coefs]
 
 
 
 
 
 
 
 
88
  time = np.linspace(0, 1, num=frames)
89
+ theta = np.linspace(0, tau, theta_points)
90
+ draw_x, draw_y = [], []
91
 
92
+ anim = animation.FuncAnimation(fig, animate, frames=frames, interval=5, fargs=(coefs, time, fig, ax, background, circles, circle_lines, drawing, draw_x, draw_y, coefs_static, theta))
93
+
94
+ return anim
95
+
96
+ def fourier_transform_drawing(input_image, frames, coefficients, img_size, blur_kernel_size, desired_range, num_points, theta_points):
97
+ xs, ys = process_image(input_image, img_size, blur_kernel_size, desired_range)
98
+ coefs = calculate_fourier_coefficients(xs, ys, num_points, coefficients)
99
+ anim = generate_animation(frames, coefs, img_size, desired_range, theta_points, coefficients)
100
+
101
+ # Saving the animation
102
  with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
 
103
  anim.save(temp_file.name, fps=15)
104
+ return Image.fromarray(np.zeros((img_size, img_size), dtype=np.uint8)), temp_file.name
105
 
106
+ def setup_gradio_interface():
107
+ interface = gr.Interface(
108
+ fn=fourier_transform_drawing,
109
+ inputs=[
110
+ gr.Image(label="Input Image", sources=['upload'], type="pil"),
111
+ gr.Slider(minimum=5, maximum=500, value=100, label="Number of Frames"),
112
+ gr.Slider(minimum=1, maximum=500, value=50, label="Number of Coefficients"),
113
+ gr.Number(value=224, label="Image Size (px)", precision=0),
114
+ gr.Slider(minimum=3, maximum=11, step=2, value=5, label="Blur Kernel Size (odd number)"),
115
+ gr.Number(value=400, label="Desired Range for Scaling", precision=0),
116
+ gr.Number(value=1000, label="Number of Points for Integration", precision=0),
117
+ gr.Slider(minimum=50, maximum=500, value=80, label="Theta Points for Animation")
118
+ ],
119
+ outputs=["image", gr.Video()],
120
+ title="Fourier Transform Drawing",
121
+ description="Upload an image and generate a Fourier Transform drawing animation."
122
+ )
123
+ return interface
 
124
 
125
  if __name__ == "__main__":
126
+ interface = setup_gradio_interface()
127
  interface.queue()
128
  interface.launch()