staghado commited on
Commit
573c3f1
·
1 Parent(s): 209d481

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -80
app.py CHANGED
@@ -1,98 +1,68 @@
1
  import os
2
  import io
3
  import cv2
4
- import matplotlib.animation as animation
5
- import matplotlib.pyplot as plt
6
  import numpy as np
7
- import gradio as gr
8
- from scipy.integrate import quad_vec
9
- from math import tau
10
  from PIL import Image
 
11
  from concurrent.futures import ThreadPoolExecutor
 
12
 
 
 
 
13
 
14
- def fourier_transform_drawing(input_image, frames, coefficients, img_size):
15
- """
16
-
17
- """
18
- # Convert PIL to OpenCV image(array)
19
- input_image = np.array(input_image)
20
- img = cv2.cvtColor(input_image, cv2.COLOR_RGB2BGR)
21
-
22
- # processing
23
- # resize the image to a smaller size for faster processing
24
- dim = (img_size, img_size)
25
- img = cv2.resize(img, dim, interpolation=cv2.INTER_AREA)
26
-
27
  imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
28
- blurred = cv2.GaussianBlur(imgray, (5, 5), 0)
29
- (_, thresh) = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
30
  contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
31
-
32
  # find the contour with the largest area
33
  # largest_contour_idx = np.argmax([cv2.contourArea(c) for c in contours])
34
  # largest_contour = contours[largest_contour_idx]
35
-
36
- # combine contours
37
  def combine_all_contours(contours):
38
  combined_contour = np.array([], dtype=np.int32).reshape(0, 1, 2)
39
-
40
  for contour in contours:
41
  combined_contour = np.vstack((combined_contour, contour))
42
-
43
  return combined_contour
44
-
45
- largest_contour = combine_all_contours(contours)
46
-
47
- verts = [tuple(coord) for coord in largest_contour.squeeze()]
48
 
49
- xs, ys = zip(*verts)
50
- xs, ys = np.asarray(xs), np.asarray(ys)
 
51
 
52
- # calculate the range of xs and ys
53
- x_range = np.max(xs) - np.min(xs)
54
- y_range = np.max(ys) - np.min(ys)
55
-
56
- # determine the scale factors
57
- desired_range = 400
58
- scale_x = desired_range / x_range
59
- scale_y = desired_range / y_range
60
-
61
- # apply scaling
62
- # ys needs to be flipped vertically
63
  xs = (xs - np.mean(xs)) * scale_x
64
  ys = (-ys + np.mean(ys)) * scale_y
65
 
66
- # compute the Fourier coefficients
67
- num_points = 1000 # how many points to use for numerical integration
68
- t_values = np.linspace(0, tau, num_points)
69
  t_list = np.linspace(0, tau, len(xs))
70
-
 
 
71
  def compute_cn(f_exp, n, t_values):
72
- """
73
- Integrate the contour along axis (-1) using the composite trapezoidal rule.
74
- https://numpy.org/doc/stable/reference/generated/numpy.trapz.html#r7aa6c77779c0-2
75
- """
76
  coef = np.trapz(f_exp * np.exp(-n * t_values * 1j), t_values) / tau
77
  return coef
78
-
79
- # pre-compute the interpolated values
80
- f_precomputed = np.interp(t_values, t_list, xs + 1j * ys)
81
-
82
  N = coefficients
83
  indices = [0] + [j for i in range(1, N + 1) for j in (i, -i)]
84
 
85
- # parallelize the computation of coefficients
86
  with ThreadPoolExecutor(max_workers=8) as executor:
87
- print("Number of threads used:", executor._max_workers)
88
  coefs = list(executor.map(lambda n: (compute_cn(f_precomputed, n, t_values), n), indices))
89
-
90
- coefs = [(coefs[0][0], 0)] + coefs[1:]
91
 
92
- # animate the drawings
93
  fig, ax = plt.subplots()
94
- circles = [ax.plot([], [], 'b-')[0] for _ in range(-N, N+1)]
95
- circle_lines = [ax.plot([], [], 'g-')[0] for _ in range(-N, N+1)]
96
  drawing, = ax.plot([], [], 'r-', linewidth=2)
97
 
98
  ax.set_xlim(-desired_range, desired_range)
@@ -102,49 +72,47 @@ def fourier_transform_drawing(input_image, frames, coefficients, img_size):
102
  fig.set_size_inches(15, 15)
103
 
104
  draw_x, draw_y = [], []
105
-
106
- # pre-compute static values outside the animate function
107
- theta = np.linspace(0, tau, 80)
108
  coefs_static = [(np.linalg.norm(c), fr) for c, fr in coefs]
109
-
 
110
  def animate(i, coefs, time):
111
  center = (0, 0)
112
-
113
- for _, (r, fr) in enumerate(coefs_static):
114
- c_dynamic = coefs[_][0] * np.exp(1j * (fr * tau * time[i]))
115
  x, y = center[0] + r * np.cos(theta), center[1] + r * np.sin(theta)
116
- circle_lines[_].set_data([center[0], center[0] + np.real(c_dynamic)], [center[1], center[1] + np.imag(c_dynamic)])
117
- circles[_].set_data(x, y)
118
  center = (center[0] + np.real(c_dynamic), center[1] + np.imag(c_dynamic))
119
 
120
  draw_x.append(center[0])
121
  draw_y.append(center[1])
122
  drawing.set_data(draw_x[:i+1], draw_y[:i+1])
123
 
124
- drawing_time = 1
125
- time = np.linspace(0, drawing_time, num=frames)
126
- anim = animation.FuncAnimation(fig, animate, frames=frames, interval=5, fargs=(coefs, time))
127
-
128
- # save the animation as an MP4 file
129
  output_animation = "output.mp4"
130
  anim.save(output_animation, fps=15)
131
  plt.close(fig)
132
-
133
  return output_animation
134
 
135
- # Gradio interface
136
  interface = gr.Interface(
137
  fn=fourier_transform_drawing,
138
  inputs=[
139
  gr.Image(label="Input Image", sources=['upload'], type="pil"),
140
  gr.Slider(minimum=5, maximum=500, value=100, label="Number of Frames"),
141
  gr.Slider(minimum=1, maximum=500, value=50, label="Number of Coefficients"),
142
- gr.Number(value=224, label="Image size", precision=0)
 
 
 
 
143
  ],
144
  outputs=gr.Video(),
145
  title="Fourier Transform Drawing",
146
- description="Upload an image and generate a Fourier Transform drawing animation. You can find out more about the project here : https://github.com/staghado/fourier-draw",
147
- examples=[["Fourier2.jpg", 100, 200, 224], ["Luffy.png", 100, 100, 224]]
148
  )
149
 
150
  if __name__ == "__main__":
 
1
  import os
2
  import io
3
  import cv2
 
 
4
  import numpy as np
5
+ import matplotlib.pyplot as plt
6
+ import matplotlib.animation as animation
 
7
  from PIL import Image
8
+ from math import tau
9
  from concurrent.futures import ThreadPoolExecutor
10
+ import gradio as gr
11
 
12
+ def fourier_transform_drawing(input_image, frames, coefficients, img_size, blur_kernel_size, desired_range, num_points, theta_points):
13
+ # Convert PIL to OpenCV image
14
+ img = cv2.cvtColor(np.array(input_image), cv2.COLOR_RGB2BGR)
15
 
16
+ # Resize the image for faster processing
17
+ img = cv2.resize(img, (img_size, img_size), interpolation=cv2.INTER_AREA)
18
+
19
+ # Image processing
 
 
 
 
 
 
 
 
 
20
  imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
21
+ blurred = cv2.GaussianBlur(imgray, (blur_kernel_size, blur_kernel_size), 0)
22
+ _, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
23
  contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
24
+
25
  # find the contour with the largest area
26
  # largest_contour_idx = np.argmax([cv2.contourArea(c) for c in contours])
27
  # largest_contour = contours[largest_contour_idx]
28
+
29
+ # Combine all contours
30
  def combine_all_contours(contours):
31
  combined_contour = np.array([], dtype=np.int32).reshape(0, 1, 2)
 
32
  for contour in contours:
33
  combined_contour = np.vstack((combined_contour, contour))
 
34
  return combined_contour
 
 
 
 
35
 
36
+ combined_contour = combine_all_contours(contours)
37
+ verts = [tuple(coord) for coord in combined_contour.squeeze()]
38
+ xs, ys = np.asarray(list(zip(*verts)))
39
 
40
+ # Scale the coordinates
41
+ x_range, y_range = np.max(xs) - np.min(xs), np.max(ys) - np.min(ys)
42
+ scale_x, scale_y = desired_range / x_range, desired_range / y_range
 
 
 
 
 
 
 
 
43
  xs = (xs - np.mean(xs)) * scale_x
44
  ys = (-ys + np.mean(ys)) * scale_y
45
 
46
+ # Compute Fourier coefficients
 
 
47
  t_list = np.linspace(0, tau, len(xs))
48
+ t_values = np.linspace(0, tau, num_points)
49
+ f_precomputed = np.interp(t_values, t_list, xs + 1j * ys)
50
+
51
  def compute_cn(f_exp, n, t_values):
 
 
 
 
52
  coef = np.trapz(f_exp * np.exp(-n * t_values * 1j), t_values) / tau
53
  return coef
54
+
 
 
 
55
  N = coefficients
56
  indices = [0] + [j for i in range(1, N + 1) for j in (i, -i)]
57
 
58
+ # Parallel computation of coefficients
59
  with ThreadPoolExecutor(max_workers=8) as executor:
 
60
  coefs = list(executor.map(lambda n: (compute_cn(f_precomputed, n, t_values), n), indices))
 
 
61
 
62
+ # Animation setup
63
  fig, ax = plt.subplots()
64
+ circles = [ax.plot([], [], 'b-')[0] for _ in range(-N, N + 1)]
65
+ circle_lines = [ax.plot([], [], 'g-')[0] for _ in range(-N, N + 1)]
66
  drawing, = ax.plot([], [], 'r-', linewidth=2)
67
 
68
  ax.set_xlim(-desired_range, desired_range)
 
72
  fig.set_size_inches(15, 15)
73
 
74
  draw_x, draw_y = [], []
75
+ theta = np.linspace(0, tau, theta_points)
 
 
76
  coefs_static = [(np.linalg.norm(c), fr) for c, fr in coefs]
77
+
78
+ # Animation function
79
  def animate(i, coefs, time):
80
  center = (0, 0)
81
+ for idx, (r, fr) in enumerate(coefs_static):
82
+ c_dynamic = coefs[idx][0] * np.exp(1j * (fr * tau * time[i]))
 
83
  x, y = center[0] + r * np.cos(theta), center[1] + r * np.sin(theta)
84
+ circle_lines[idx].set_data([center[0], center[0] + np.real(c_dynamic)], [center[1], center[1] + np.imag(c_dynamic)])
85
+ circles[idx].set_data(x, y)
86
  center = (center[0] + np.real(c_dynamic), center[1] + np.imag(c_dynamic))
87
 
88
  draw_x.append(center[0])
89
  draw_y.append(center[1])
90
  drawing.set_data(draw_x[:i+1], draw_y[:i+1])
91
 
92
+ # Create and save the animation
93
+ anim = animation.FuncAnimation(fig, animate, frames=frames, interval=5, fargs=(coefs, np.linspace(0, 1, num=frames)))
 
 
 
94
  output_animation = "output.mp4"
95
  anim.save(output_animation, fps=15)
96
  plt.close(fig)
 
97
  return output_animation
98
 
99
+ # Gradio interface setup
100
  interface = gr.Interface(
101
  fn=fourier_transform_drawing,
102
  inputs=[
103
  gr.Image(label="Input Image", sources=['upload'], type="pil"),
104
  gr.Slider(minimum=5, maximum=500, value=100, label="Number of Frames"),
105
  gr.Slider(minimum=1, maximum=500, value=50, label="Number of Coefficients"),
106
+ gr.Number(value=224, label="Image Size (px)", precision=0),
107
+ gr.Slider(minimum=3, maximum=11, step=2, value=5, label="Blur Kernel Size (odd number)"),
108
+ gr.Number(value=400, label="Desired Range for Scaling", precision=0),
109
+ gr.Number(value=1000, label="Number of Points for Integration", precision=0),
110
+ gr.Slider(minimum=50, maximum=500, value=80, label="Theta Points for Animation")
111
  ],
112
  outputs=gr.Video(),
113
  title="Fourier Transform Drawing",
114
+ description="Upload an image and generate a Fourier Transform drawing animation.",
115
+ examples=[["Fourier2.jpg", 100, 200, 224, 5, 400, 1000, 80], ["Luffy.png", 100, 100, 224, 5, 400, 1000, 80]]
116
  )
117
 
118
  if __name__ == "__main__":