Skip to content

Instantly share code, notes, and snippets.

@bhatiaabhinav
Last active July 1, 2019 13:46
Show Gist options
  • Save bhatiaabhinav/d4f75d014cdec0c78a0ae75a6ad42f4f to your computer and use it in GitHub Desktop.
Save bhatiaabhinav/d4f75d014cdec0c78a0ae75a6ad42f4f to your computer and use it in GitHub Desktop.
Generate random or guided Fourier art.
'''
A script to Fourier-ize drawings made using mouse/stylus. Or simply generate random art.
Author: Abhinav Bhatia
Email: bhatiaabhinav93@gmail.com
Examples: https://i.imgur.com/c5EKAh6.gif, https://i.imgur.com/kpYHxho.gif
'''
import sys
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
plt.style.use('dark_background')
def get_fourier_coefficients(zs, n, ts=None, subsamples=1000):
'''takes points zs (complex numbers) as a function of times and returns the fourier coefficients of n frequenciens from -n/2 to n/2
c_f = integration_0_1 e^(-2.pi.i.f.t).z(t).dt.
where f is the frequency
dt = 1 / subsamples.
More subsamples leads to better numerical intergration.
'''
c = np.zeros(n) + np.zeros(n) * 1j
if ts is None:
ts = np.linspace(0, 1, num=len(zs))
assert len(zs) == len(ts)
# make ts between 0 and 1:
ts = ts - np.min(ts)
ts = ts / np.max(ts)
interpolated_ts = np.linspace(0, 1, num=subsamples)
interpolated_zs = np.interp(interpolated_ts, ts, zs)
for idx in range(n):
f = idx - n // 2
for t, z in zip(interpolated_ts, interpolated_zs):
dt = 1 / len(interpolated_ts)
c[idx] += np.exp(-2 * np.pi * 1j * f * t) * z * dt
return c
def record_points_from_clicks(filename):
'''a simple tool to record a series of points by clicking at an empty matplotlib graph'''
fig = plt.figure("Close after you are done drawing") # type: plt.Figure
ax = plt.axes(xlim=(-2, 2), ylim=(-2, 2)) # type: plt.Axes
tdata = []
xdata = []
ydata = []
start_time = time.time()
line, = ax.plot(xdata, ydata, lw=3)
def update(i):
line.set_data(xdata, ydata)
return line,
anim = FuncAnimation(fig, update, interval=60) # noqa: F841
ax.recording = False
with open(filename, 'w') as file:
def on_press(event):
if event.inaxes != ax:
return
ax.recording = True
def on_move(event):
if not ax.recording:
return
if event.inaxes != ax:
return
x, y = event.xdata, event.ydata
t = time.time() - start_time
if x is None or y is None:
return
file.write("{t},{x},{y}\n".format(t=t, x=x, y=y))
tdata.append(t)
xdata.append(x)
ydata.append(y)
def on_release(event):
ax.recording = False
cid_press = fig.canvas.mpl_connect('button_press_event', on_press)
cid_move = fig.canvas.mpl_connect('motion_notify_event', on_move)
cid_release = fig.canvas.mpl_connect('button_release_event', on_release)
plt.show()
fig.canvas.mpl_disconnect(cid_press)
fig.canvas.mpl_disconnect(cid_move)
fig.canvas.mpl_disconnect(cid_release)
return tdata, xdata, ydata
def record_and_get_fourier(filename, n):
'''record a series of points by clicking at an empty matplotlib graph and apply the fourier transform to get the series as sum of n rotating complex numbers with integer frequencies -n/2 to n/2'''
tdata, xdata, ydata = record_points_from_clicks(filename)
zs = np.asarray(xdata) + np.asarray(ydata) * 1j
return get_fourier_coefficients(zs, n, ts=tdata)
def random_fourier_weigths(n):
'''generate random fourier coefficients to make a random art. n is number of frequencies'''
fourier_weights = (2 * np.random.rand(n) - 1) + (2 * np.random.rand(n) - 1) * 1j
fourier_weights = 4 * fourier_weights / n
return fourier_weights
if __name__ == '__main__':
guided_mode = len(sys.argv) > 1 and sys.argv[1].lower() == 'guided'
# initialize:
np.random.seed(int(time.time()))
n_frequencies = 25 if guided_mode else 11
fourier_weights = record_and_get_fourier('record.txt', n_frequencies) if guided_mode else random_fourier_weigths(n_frequencies)
fps = 60 # frames per second.
time_rate = 0.2 # simulated second per actual second. 0.2 means 5 times slower.
duration = 1 # of simulated video
fig = plt.figure('Fourier Animation Art with Matplotlib') # type: plt.Figure
ax = plt.axes(xlim=(-2, 2), ylim=(-2, 2)) # type: plt.Axes
xdata = []
ydata = []
line, = ax.plot(xdata, ydata, lw=3)
# turn off axes and set title
plt.axis('off')
plt.title('{0}Fourier Animation Art with Matplotlib'.format('Guided ' if guided_mode else 'Random '))
def init():
xdata.clear()
ydata.clear()
line.set_data(xdata, ydata)
return line,
def animate_fourier_art(i):
t = i / fps
t = t * time_rate
z = np.sum([fourier_weights[idx] * np.exp(2 * np.pi * 1j * (idx - len(fourier_weights) // 2) * t) for idx in range(len(fourier_weights))])
xdata.append(z.real)
ydata.append(z.imag)
line.set_data(xdata, ydata)
return line,
frames = None if duration is None else int(fps * duration / time_rate)
anim = FuncAnimation(fig, animate_fourier_art, init_func=init, frames=frames, interval=1000 / fps, blit=True)
plt.show()
# anim.save('art.gif', writer='imagemagick')
# anim.save('art.mp4', writer='ffmpeg')
@bhatiaabhinav
Copy link
Author

bhatiaabhinav commented Jul 1, 2019

Requirements:

  • python3 with numpy and matplotlib packages.
    • pip3 install numpy matplotlib
  • ffmpeg or imagemagick to enable saving as mp4 or gif respectively.
    • On ubuntu, sudo apt install ffmpeg imagemagick

To generate random art, run:

python3 fourier_art.py

To generate guided art (make a fourier approximation of something drawn on a canvas with a mouse):

python3 fourier_art.py guided

To save, uncomment one of the last two lines and comment plt.show().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment