File size: 5,387 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
9d0d208
 
47201a2
 
 
9d0d208
 
 
 
47201a2
9d0d208
47201a2
 
 
9d0d208
038be2c
 
 
 
 
 
 
 
 
 
9d0d208
 
 
c77c587
573c3f1
c77c587
 
 
6ee4209
bdd2388
cad3317
573c3f1
 
 
 
 
c77c587
9d0d208
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
117
118
119
120
121
122
123
124
125
126
127
128
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 the current plot as an image
        buf = io.BytesIO()
        plt.savefig(buf, format='png', bbox_inches='tight')
        buf.seek(0)
        image = np.array(Image.open(buf))

        # Yield the current image and a placeholder for the final animation
        yield (image, None)

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

    # Generate final animation as GIF
    with tempfile.NamedTemporaryFile(delete=False, suffix='.gif') as temp_file:
        anim = animation.FuncAnimation(fig, animate, frames=frames, interval=5, fargs=(coefs, np.linspace(0, 1, num=frames)))
        anim.save(temp_file.name, fps=15)

        # Read the final GIF
        with open(temp_file.name, 'rb') as gif_file:
            final_gif = np.array(Image.open(io.BytesIO(gif_file.read())))

    # Remove the temporary file
    os.remove(temp_file.name)

    # Yield the final GIF in place of the last frame
    yield (image, final_gif)

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

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