import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def get_fourier_coefficients(points):
"""Transform spatial points into Fourier coefficients (DFT)."""
N = len(points)
# Convert points to complex numbers (x + iy)
z = np.array([complex(p[0], p[1]) for p in points])
# Compute the coefficients
coeffs = np.fft.fft(z) / N
return coeffs
def animate_fourier(points, num_circles=None):
coeffs = get_fourier_coefficients(points)
N = len(coeffs)
# Sort coefficients by frequency magnitude (largest circles first)
# This makes the animation look much more organized
freqs = np.fft.fftfreq(N, 1/N)
indices = np.argsort(np.abs(coeffs))[::-1]
if num_circles:
indices = indices[:num_circles]
sorted_coeffs = coeffs[indices]
sorted_freqs = freqs[indices]
fig, ax = plt.subplots(figsize=(8, 8))
ax.set_aspect('equal')
ax.set_xlim(np.min(points[:,0])-50, np.max(points[:,0])+50)
ax.set_ylim(np.min(points[:,1])-50, np.max(points[:,1])+50)
line, = ax.plot([], [], color='orange', lw=2) # The path being traced
circles = [ax.plot([], [], 'b-', alpha=0.3)[0] for _ in range(len(sorted_coeffs))]
trace_x, trace_y = [], []
def update(t):
# t goes from 0 to 1
current_z = 0 + 0j
prev_z = 0 + 0j
for i, (c, f) in enumerate(zip(sorted_coeffs, sorted_freqs)):
prev_z = current_z
# The heart of Fourier: sum of c * e^(i * 2pi * f * t)
current_z += c * np.exp(2j * np.pi * f * t)
# Update circle visuals
radius = np.abs(c)
theta = np.linspace(0, 2*np.pi, 50)
circles[i].set_data(prev_z.real + radius*np.cos(theta),
prev_z.imag + radius*np.sin(theta))
trace_x.append(current_z.real)
trace_y.append(current_z.imag)
line.set_data(trace_x, trace_y)
return [line] + circles
ani = FuncAnimation(fig, update, frames=np.linspace(0, 1, N), interval=20, blit=True)
plt.show()
# --- Example: Hand-drawn "Bird-ish" Shape ---
# Replace this with SVG path data parsed into a numpy array
t = np.linspace(0, 2*np.pi, 100)
x = 100 * (np.cos(t) + 0.5 * np.cos(2*t))
y = 100 * (np.sin(t) - 0.5 * np.sin(2*t))
bird_points = np.column_stack((x, y))
animate_fourier(bird_points, num_circles=50)
aW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKZnJvbSBtYXRwbG90bGliLmFuaW1hdGlvbiBpbXBvcnQgRnVuY0FuaW1hdGlvbgoKZGVmIGdldF9mb3VyaWVyX2NvZWZmaWNpZW50cyhwb2ludHMpOgogICAgIiIiVHJhbnNmb3JtIHNwYXRpYWwgcG9pbnRzIGludG8gRm91cmllciBjb2VmZmljaWVudHMgKERGVCkuIiIiCiAgICBOID0gbGVuKHBvaW50cykKICAgICMgQ29udmVydCBwb2ludHMgdG8gY29tcGxleCBudW1iZXJzICh4ICsgaXkpCiAgICB6ID0gbnAuYXJyYXkoW2NvbXBsZXgocFswXSwgcFsxXSkgZm9yIHAgaW4gcG9pbnRzXSkKICAgICMgQ29tcHV0ZSB0aGUgY29lZmZpY2llbnRzCiAgICBjb2VmZnMgPSBucC5mZnQuZmZ0KHopIC8gTgogICAgcmV0dXJuIGNvZWZmcwoKZGVmIGFuaW1hdGVfZm91cmllcihwb2ludHMsIG51bV9jaXJjbGVzPU5vbmUpOgogICAgY29lZmZzID0gZ2V0X2ZvdXJpZXJfY29lZmZpY2llbnRzKHBvaW50cykKICAgIE4gPSBsZW4oY29lZmZzKQogICAgCiAgICAjIFNvcnQgY29lZmZpY2llbnRzIGJ5IGZyZXF1ZW5jeSBtYWduaXR1ZGUgKGxhcmdlc3QgY2lyY2xlcyBmaXJzdCkKICAgICMgVGhpcyBtYWtlcyB0aGUgYW5pbWF0aW9uIGxvb2sgbXVjaCBtb3JlIG9yZ2FuaXplZAogICAgZnJlcXMgPSBucC5mZnQuZmZ0ZnJlcShOLCAxL04pCiAgICBpbmRpY2VzID0gbnAuYXJnc29ydChucC5hYnMoY29lZmZzKSlbOjotMV0KICAgIGlmIG51bV9jaXJjbGVzOgogICAgICAgIGluZGljZXMgPSBpbmRpY2VzWzpudW1fY2lyY2xlc10KICAgIAogICAgc29ydGVkX2NvZWZmcyA9IGNvZWZmc1tpbmRpY2VzXQogICAgc29ydGVkX2ZyZXFzID0gZnJlcXNbaW5kaWNlc10KCiAgICBmaWcsIGF4ID0gcGx0LnN1YnBsb3RzKGZpZ3NpemU9KDgsIDgpKQogICAgYXguc2V0X2FzcGVjdCgnZXF1YWwnKQogICAgYXguc2V0X3hsaW0obnAubWluKHBvaW50c1s6LDBdKS01MCwgbnAubWF4KHBvaW50c1s6LDBdKSs1MCkKICAgIGF4LnNldF95bGltKG5wLm1pbihwb2ludHNbOiwxXSktNTAsIG5wLm1heChwb2ludHNbOiwxXSkrNTApCgogICAgbGluZSwgPSBheC5wbG90KFtdLCBbXSwgY29sb3I9J29yYW5nZScsIGx3PTIpICAjIFRoZSBwYXRoIGJlaW5nIHRyYWNlZAogICAgY2lyY2xlcyA9IFtheC5wbG90KFtdLCBbXSwgJ2ItJywgYWxwaGE9MC4zKVswXSBmb3IgXyBpbiByYW5nZShsZW4oc29ydGVkX2NvZWZmcykpXQogICAgdHJhY2VfeCwgdHJhY2VfeSA9IFtdLCBbXQoKICAgIGRlZiB1cGRhdGUodCk6CiAgICAgICAgIyB0IGdvZXMgZnJvbSAwIHRvIDEKICAgICAgICBjdXJyZW50X3ogPSAwICsgMGoKICAgICAgICBwcmV2X3ogPSAwICsgMGoKICAgICAgICAKICAgICAgICBmb3IgaSwgKGMsIGYpIGluIGVudW1lcmF0ZSh6aXAoc29ydGVkX2NvZWZmcywgc29ydGVkX2ZyZXFzKSk6CiAgICAgICAgICAgIHByZXZfeiA9IGN1cnJlbnRfegogICAgICAgICAgICAjIFRoZSBoZWFydCBvZiBGb3VyaWVyOiBzdW0gb2YgYyAqIGVeKGkgKiAycGkgKiBmICogdCkKICAgICAgICAgICAgY3VycmVudF96ICs9IGMgKiBucC5leHAoMmogKiBucC5waSAqIGYgKiB0KQogICAgICAgICAgICAKICAgICAgICAgICAgIyBVcGRhdGUgY2lyY2xlIHZpc3VhbHMKICAgICAgICAgICAgcmFkaXVzID0gbnAuYWJzKGMpCiAgICAgICAgICAgIHRoZXRhID0gbnAubGluc3BhY2UoMCwgMipucC5waSwgNTApCiAgICAgICAgICAgIGNpcmNsZXNbaV0uc2V0X2RhdGEocHJldl96LnJlYWwgKyByYWRpdXMqbnAuY29zKHRoZXRhKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJldl96LmltYWcgKyByYWRpdXMqbnAuc2luKHRoZXRhKSkKICAgICAgICAKICAgICAgICB0cmFjZV94LmFwcGVuZChjdXJyZW50X3oucmVhbCkKICAgICAgICB0cmFjZV95LmFwcGVuZChjdXJyZW50X3ouaW1hZykKICAgICAgICBsaW5lLnNldF9kYXRhKHRyYWNlX3gsIHRyYWNlX3kpCiAgICAgICAgcmV0dXJuIFtsaW5lXSArIGNpcmNsZXMKCiAgICBhbmkgPSBGdW5jQW5pbWF0aW9uKGZpZywgdXBkYXRlLCBmcmFtZXM9bnAubGluc3BhY2UoMCwgMSwgTiksIGludGVydmFsPTIwLCBibGl0PVRydWUpCiAgICBwbHQuc2hvdygpCgojIC0tLSBFeGFtcGxlOiBIYW5kLWRyYXduICJCaXJkLWlzaCIgU2hhcGUgLS0tCiMgUmVwbGFjZSB0aGlzIHdpdGggU1ZHIHBhdGggZGF0YSBwYXJzZWQgaW50byBhIG51bXB5IGFycmF5CnQgPSBucC5saW5zcGFjZSgwLCAyKm5wLnBpLCAxMDApCnggPSAxMDAgKiAobnAuY29zKHQpICsgMC41ICogbnAuY29zKDIqdCkpCnkgPSAxMDAgKiAobnAuc2luKHQpIC0gMC41ICogbnAuc2luKDIqdCkpCmJpcmRfcG9pbnRzID0gbnAuY29sdW1uX3N0YWNrKCh4LCB5KSkKCmFuaW1hdGVfZm91cmllcihiaXJkX3BvaW50cywgbnVtX2NpcmNsZXM9NTAp