File size: 5,824 Bytes
1c3330f
 
c77c587
 
 
 
1c3330f
c77c587
 
70542da
c8a65fb
1c3330f
c77c587
cad3317
cbcb276
 
 
1c3330f
c77c587
 
 
 
cad3317
 
 
a526c29
cad3317
a526c29
1c3330f
c77c587
1c3330f
cad3317
 
 
cbcb276
c77c587
 
 
cbcb276
365c88b
1c3330f
365c88b
 
 
1c3330f
 
cbcb276
 
365c88b
1c3330f
d6c7eed
cbcb276
 
c77c587
7f9a9bc
cbcb276
 
 
c8a65fb
 
cbcb276
 
 
 
c8a65fb
c77c587
c8a65fb
 
 
 
c77c587
c8a65fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c77c587
 
 
 
 
 
 
1c3330f
 
c77c587
bdd2388
 
c77c587
 
3ea9a92
 
 
 
c77c587
 
 
 
3ea9a92
 
 
 
c77c587
3ea9a92
c77c587
3ea9a92
c77c587
 
 
20a6f95
c77c587
 
 
 
 
1c3330f
26d12c4
c77c587
 
 
26d12c4
c77c587
 
 
 
 
6ee4209
bdd2388
cad3317
 
c77c587
26d12c4
c77c587
bdd2388
3ea9a92
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import os
import io
import cv2
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
import gradio as gr
from scipy.integrate import quad_vec
from math import tau
from PIL import Image
from concurrent.futures import ThreadPoolExecutor


def fourier_transform_drawing(input_image, frames, coefficients, img_size):
    """
    
    """
    # Convert PIL to OpenCV image(array)
    input_image = np.array(input_image)
    img = cv2.cvtColor(input_image, cv2.COLOR_RGB2BGR)

    # processing
    # resize the image to a smaller size for faster processing
    dim = (img_size, img_size)
    img = cv2.resize(img, dim, interpolation=cv2.INTER_AREA)
    
    imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(imgray, (5, 5), 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]

    verts = [tuple(coord) for coord in contours[largest_contour_idx].squeeze()]

    xs, ys = zip(*verts)
    xs, ys = np.asarray(xs), np.asarray(ys)

    # calculate the range of xs and ys
    x_range = np.max(xs) - np.min(xs)
    y_range = np.max(ys) - np.min(ys)
    
    # determine the scale factors
    desired_range = 400
    scale_x = desired_range / x_range
    scale_y = desired_range / y_range
    
    # apply scaling
    # ys needs to be flipped vertically
    xs = (xs - np.mean(xs)) * scale_x
    ys = (-ys + np.mean(ys)) * scale_y

    # compute the Fourier coefficients
    num_points = 1000  # how many points to use for numerical integration
    t_values = np.linspace(0, tau, num_points)
    t_list = np.linspace(0, tau, len(xs))
    
    def compute_cn(f_exp, n, t_values):
        """
        Integrate the contour along axis (-1) using the composite trapezoidal rule.
        https://numpy.org/doc/stable/reference/generated/numpy.trapz.html#r7aa6c77779c0-2
        """
        coef = np.trapz(f_exp * np.exp(-n * t_values * 1j), t_values) / tau
        return coef
    
    # Pre-compute the interpolated values
    f_exp_precomputed = np.interp(t_values, t_list, xs + 1j * ys)
    
    N = coefficients
    indices = [0] + [j for i in range(1, N + 1) for j in (i, -i)]

    print("Number of threads used:", os.cpu_count())
    
    # Parallelize the computation of coefficients
    with ThreadPoolExecutor() as executor:
        coefs = list(executor.map(lambda n: (compute_cn(f_exp_precomputed, n, t_values), n), indices))
    
    # Ensure the zeroth coefficient is computed only once
    coefs = [(coefs[0][0], 0)] + coefs[1:]

    # def compute_cn(n, t_list, xs, ys):
    #     """
    #     Integrate the contour along axis (-1) using the composite trapezoidal rule.
    #     https://numpy.org/doc/stable/reference/generated/numpy.trapz.html#r7aa6c77779c0-2
    #     """
    #     f_exp = np.interp(t_values, t_list, xs + 1j * ys) * np.exp(-n * t_values * 1j)
    #     coef = np.trapz(f_exp, t_values) / tau
    #     return coef

    # N = coefficients
    # coefs = [(compute_cn(0, t_list, xs, ys), 0)] + [(compute_cn(j, t_list, xs, ys), j) for i in range(1, N+1) for j in (i, -i)]

    # animate the drawings
    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 = [], []

    # Pre-compute static values outside the animate function
    theta = np.linspace(0, tau, 80)
    coefs_static = [(np.linalg.norm(c), fr) for c, fr in coefs]  # Assuming `r` remains constant
    
    def animate(i, coefs, time):
        t = time[i]
        center = (0, 0)
    
        # Loop over coefficients
        for _, (r, fr) in enumerate(coefs_static):
            c_dynamic = coefs[_][0] * np.exp(1j * (fr * tau * t))  # Dynamic part of 'c'
            x, y = center[0] + r * np.cos(theta), center[1] + r * np.sin(theta)
            circle_lines[_].set_data([center[0], center[0] + np.real(c_dynamic)], [center[1], center[1] + np.imag(c_dynamic)])
            circles[_].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])

    drawing_time = 1
    time = np.linspace(0, drawing_time, num=frames)    
    anim = animation.FuncAnimation(fig, animate, frames=frames, interval=5, fargs=(coefs, time)) 

    # save the animation as an MP4 file
    output_animation = "output.mp4"
    anim.save(output_animation, fps=15)
    plt.close(fig)

    return output_animation

# Gradio interface
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", precision=0)
    ],
    outputs=gr.Video(),
    title="Fourier Transform Drawing",
    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",
    examples=[["Fourier2.jpg", 100, 200, 224], ["Luffy.png", 100, 100, 224]]
)

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