fourier-draw / app.py
staghado's picture
Update app.py
573c3f1
raw
history blame
4.99 kB
import os
import io
import cv2
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from PIL import Image
from math import tau
from concurrent.futures import ThreadPoolExecutor
import gradio as gr
def fourier_transform_drawing(input_image, frames, coefficients, img_size, blur_kernel_size, desired_range, num_points, theta_points):
# Convert PIL to OpenCV image
img = cv2.cvtColor(np.array(input_image), cv2.COLOR_RGB2BGR)
# Resize the image for faster processing
img = cv2.resize(img, (img_size, img_size), interpolation=cv2.INTER_AREA)
# Image processing
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(imgray, (blur_kernel_size, blur_kernel_size), 0)
_, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# find the contour with the largest area
# largest_contour_idx = np.argmax([cv2.contourArea(c) for c in contours])
# largest_contour = contours[largest_contour_idx]
# Combine all contours
def combine_all_contours(contours):
combined_contour = np.array([], dtype=np.int32).reshape(0, 1, 2)
for contour in contours:
combined_contour = np.vstack((combined_contour, contour))
return combined_contour
combined_contour = combine_all_contours(contours)
verts = [tuple(coord) for coord in combined_contour.squeeze()]
xs, ys = np.asarray(list(zip(*verts)))
# Scale the coordinates
x_range, y_range = np.max(xs) - np.min(xs), np.max(ys) - np.min(ys)
scale_x, scale_y = desired_range / x_range, desired_range / y_range
xs = (xs - np.mean(xs)) * scale_x
ys = (-ys + np.mean(ys)) * scale_y
# Compute Fourier coefficients
t_list = np.linspace(0, tau, len(xs))
t_values = np.linspace(0, tau, num_points)
f_precomputed = np.interp(t_values, t_list, xs + 1j * ys)
def compute_cn(f_exp, n, t_values):
coef = np.trapz(f_exp * np.exp(-n * t_values * 1j), t_values) / tau
return coef
N = coefficients
indices = [0] + [j for i in range(1, N + 1) for j in (i, -i)]
# Parallel computation of coefficients
with ThreadPoolExecutor(max_workers=8) as executor:
coefs = list(executor.map(lambda n: (compute_cn(f_precomputed, n, t_values), n), indices))
# Animation setup
fig, ax = plt.subplots()
circles = [ax.plot([], [], 'b-')[0] for _ in range(-N, N + 1)]
circle_lines = [ax.plot([], [], 'g-')[0] for _ in range(-N, N + 1)]
drawing, = ax.plot([], [], 'r-', linewidth=2)
ax.set_xlim(-desired_range, desired_range)
ax.set_ylim(-desired_range, desired_range)
ax.set_axis_off()
ax.set_aspect('equal')
fig.set_size_inches(15, 15)
draw_x, draw_y = [], []
theta = np.linspace(0, tau, theta_points)
coefs_static = [(np.linalg.norm(c), fr) for c, fr in coefs]
# Animation function
def animate(i, coefs, time):
center = (0, 0)
for idx, (r, fr) in enumerate(coefs_static):
c_dynamic = coefs[idx][0] * np.exp(1j * (fr * tau * time[i]))
x, y = center[0] + r * np.cos(theta), center[1] + r * np.sin(theta)
circle_lines[idx].set_data([center[0], center[0] + np.real(c_dynamic)], [center[1], center[1] + np.imag(c_dynamic)])
circles[idx].set_data(x, y)
center = (center[0] + np.real(c_dynamic), center[1] + np.imag(c_dynamic))
draw_x.append(center[0])
draw_y.append(center[1])
drawing.set_data(draw_x[:i+1], draw_y[:i+1])
# Create and save the animation
anim = animation.FuncAnimation(fig, animate, frames=frames, interval=5, fargs=(coefs, np.linspace(0, 1, num=frames)))
output_animation = "output.mp4"
anim.save(output_animation, fps=15)
plt.close(fig)
return output_animation
# Gradio interface setup
interface = gr.Interface(
fn=fourier_transform_drawing,
inputs=[
gr.Image(label="Input Image", sources=['upload'], type="pil"),
gr.Slider(minimum=5, maximum=500, value=100, label="Number of Frames"),
gr.Slider(minimum=1, maximum=500, value=50, label="Number of Coefficients"),
gr.Number(value=224, label="Image Size (px)", precision=0),
gr.Slider(minimum=3, maximum=11, step=2, value=5, label="Blur Kernel Size (odd number)"),
gr.Number(value=400, label="Desired Range for Scaling", precision=0),
gr.Number(value=1000, label="Number of Points for Integration", precision=0),
gr.Slider(minimum=50, maximum=500, value=80, label="Theta Points for Animation")
],
outputs=gr.Video(),
title="Fourier Transform Drawing",
description="Upload an image and generate a Fourier Transform drawing animation.",
examples=[["Fourier2.jpg", 100, 200, 224, 5, 400, 1000, 80], ["Luffy.png", 100, 100, 224, 5, 400, 1000, 80]]
)
if __name__ == "__main__":
interface.launch()