Last active
July 1, 2019 13:46
-
-
Save bhatiaabhinav/d4f75d014cdec0c78a0ae75a6ad42f4f to your computer and use it in GitHub Desktop.
Generate random or guided Fourier art.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
A script to Fourier-ize drawings made using mouse/stylus. Or simply generate random art. | |
Author: Abhinav Bhatia | |
Email: [email protected] | |
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') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Requirements:
pip3 install numpy matplotlib
sudo apt install ffmpeg imagemagick
To generate random art, run:
To generate guided art (make a fourier approximation of something drawn on a canvas with a mouse):
To save, uncomment one of the last two lines and comment
plt.show()
.