File size: 4,931 Bytes
c77c587
573c3f1
 
70542da
47201a2
 
573c3f1
 
47201a2
1c3330f
573c3f1
 
 
 
cad3317
573c3f1
 
c77c587
573c3f1
cad3317
47201a2
 
573c3f1
47201a2
 
 
 
 
 
 
 
573c3f1
 
 
cbcb276
 
c77c587
cbcb276
573c3f1
 
 
c8a65fb
 
c77c587
573c3f1
c77c587
c8a65fb
 
209d481
4e597da
c8a65fb
c77c587
573c3f1
 
c77c587
 
1c3330f
 
c77c587
bdd2388
 
c77c587
 
573c3f1
4e597da
573c3f1
c77c587
 
573c3f1
 
c77c587
573c3f1
47201a2
3ea9a92
47201a2
c77c587
 
20a6f95
c77c587
47201a2
 
 
 
 
 
 
 
 
 
 
573c3f1
47201a2
 
 
 
c77c587
573c3f1
c77c587
 
 
6ee4209
bdd2388
cad3317
573c3f1
 
 
 
 
c77c587
47201a2
c77c587
573c3f1
c77c587
 
 
b9d04ad
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
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from PIL import Image
import io
import cv2
from math import tau
import gradio as gr
from concurrent.futures import ThreadPoolExecutor

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)
    img = cv2.resize(img, (img_size, img_size), interpolation=cv2.INTER_AREA)
    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]
    
    # 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

    # largest_contour = combine_all_contours(contours)
    verts = [tuple(coord) for coord in largest_contour.squeeze()]
    xs, ys = np.asarray(list(zip(*verts)))
    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

    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)]

    with ThreadPoolExecutor(max_workers=8) as executor:
        coefs = list(executor.map(lambda n: (compute_cn(f_precomputed, n, t_values), n), indices))

    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]

    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])

        # Capture and yield the current plot as an image
        buf = io.BytesIO()
        plt.savefig(buf, format='png', bbox_inches='tight')
        buf.seek(0)
        yield np.array(Image.open(buf))

    # Generate and yield images
    for frame in range(frames):
        yield from animate(frame, coefs, np.linspace(0, 1, num=frames))

    # Generate final animation
    anim = animation.FuncAnimation(fig, animate, frames=frames, interval=5, fargs=(coefs, np.linspace(0, 1, num=frames)))
    buf = io.BytesIO()
    anim.save(buf, format='gif', fps=15)
    buf.seek(0)
    yield buf

# 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=["image", "file"],
    title="Fourier Transform Drawing",
    description="Upload an image and generate a Fourier Transform drawing animation.",
)

if __name__ == "__main__":
    interface.launch()