"""Fourier 2D shape reconstruction with epicycles representation"""
# Original version from D. Shiffman (2019), adapted for vedo by M. Musy (2022)
# https://thecodingtrain.com/CodingChallenges/130.2-fourier-transform-drawing.html
import numpy as np
import vedo

order = 20  # restrict to this nr of fourier coefficients in reconstruction


def DFT(x):
    X = []
    N = len(x)
    for freq in range(N):
        re, im = [0, 0]
        for n in range(N):
            phi = (2 * np.pi * freq * n) / N
            re += x[n] * np.cos(phi)
            im -= x[n] * np.sin(phi)
        re, im = [re/N, im/N]
        amp = np.sqrt(re*re + im*im)
        phase = np.arctan2(im, re)
        X.append([re, im, freq, amp, phase])
    return vedo.utils.sort_by_column(X, 3, invert=True)


def epicycles(time, rotation, fourier, order):
    global objs
    plt.remove(objs)
    x, y = [0, 0]
    objs, path = [], []
    for i in range(len(fourier[:order])):
        re, im, freq, amp, phase = fourier[i]
        if amp > 0.2:
            c = vedo.Circle([x,y], amp).wireframe().lw(1)
            objs.append(c)
        x += amp * np.cos(freq * time + phase + rotation)
        y += amp * np.sin(freq * time + phase + rotation)
        path.append([x,y])

    if len(points)>0:
        hline = vedo.Line([x,y], points[-1]).c('red5').lw(1)
        pline = vedo.Line(path).c('green5').lw(2)
        oline = vedo.Line(points).c('red4').lw(5)
        objs += [hline, pline, oline]
        plt.add(objs).render()
    return [x, y]


# Load some 2D shape and make it symmetric
shaper  = vedo.Assembly(vedo.dataurl+'timecourse1d.npy')[55]
# shaper = shape.clone().mirror('x').reverse()
shapel = vedo.Line(np.flip(shaper.clone().mirror('x').coordinates, axis=0))
shape = vedo.merge(shaper, shapel)
x, y, _ = shape.points.T

# Compute Fourier Discrete Transform in x and y separately:
fourierX = DFT(x)
fourierY = DFT(y)

vedo.settings.default_font = 'Glasgo'

plt = vedo.Plotter(size=(1500,750), bg='black', axes=1, interactive=False)
txt = vedo.Text2D(f"{__doc__} (order={order})", c='red9', bg='white', pos='bottom-center')
plt.show(shape, txt, mode='image', zoom=1.9)

objs, points = [], []
times = np.linspace(0, 2*np.pi, len(fourierX), endpoint=False)
for time in times:
    x, _ = epicycles(time,       0, fourierX, order)
    _, y = epicycles(time, np.pi/2, fourierY, order)
    points.append([x, y])

plt.interactive().close()

