fourier_epicycles.py 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. """Fourier 2D shape reconstruction with epicycles representation"""
  2. # Original version from D. Shiffman (2019), adapted for vedo by M. Musy (2022)
  3. # https://thecodingtrain.com/CodingChallenges/130.2-fourier-transform-drawing.html
  4. import numpy as np
  5. import vedo
  6. order = 20 # restrict to this nr of fourier coefficients in reconstruction
  7. def DFT(x):
  8. X = []
  9. N = len(x)
  10. for freq in range(N):
  11. re, im = [0, 0]
  12. for n in range(N):
  13. phi = (2 * np.pi * freq * n) / N
  14. re += x[n] * np.cos(phi)
  15. im -= x[n] * np.sin(phi)
  16. re, im = [re/N, im/N]
  17. amp = np.sqrt(re*re + im*im)
  18. phase = np.arctan2(im, re)
  19. X.append([re, im, freq, amp, phase])
  20. return vedo.utils.sort_by_column(X, 3, invert=True)
  21. def epicycles(time, rotation, fourier, order):
  22. global objs
  23. plt.remove(objs)
  24. x, y = [0, 0]
  25. objs, path = [], []
  26. for i in range(len(fourier[:order])):
  27. re, im, freq, amp, phase = fourier[i]
  28. if amp > 0.2:
  29. c = vedo.Circle([x,y], amp).wireframe().lw(1)
  30. objs.append(c)
  31. x += amp * np.cos(freq * time + phase + rotation)
  32. y += amp * np.sin(freq * time + phase + rotation)
  33. path.append([x,y])
  34. if len(points)>0:
  35. hline = vedo.Line([x,y], points[-1]).c('red5').lw(1)
  36. pline = vedo.Line(path).c('green5').lw(2)
  37. oline = vedo.Line(points).c('red4').lw(5)
  38. objs += [hline, pline, oline]
  39. plt.add(objs).render()
  40. return [x, y]
  41. # Load some 2D shape and make it symmetric
  42. shaper = vedo.Assembly(vedo.dataurl+'timecourse1d.npy')[55]
  43. # shaper = shape.clone().mirror('x').reverse()
  44. shapel = vedo.Line(np.flip(shaper.clone().mirror('x').coordinates, axis=0))
  45. shape = vedo.merge(shaper, shapel)
  46. x, y, _ = shape.points.T
  47. # Compute Fourier Discrete Transform in x and y separately:
  48. fourierX = DFT(x)
  49. fourierY = DFT(y)
  50. vedo.settings.default_font = 'Glasgo'
  51. plt = vedo.Plotter(size=(1500,750), bg='black', axes=1, interactive=False)
  52. txt = vedo.Text2D(f"{__doc__} (order={order})", c='red9', bg='white', pos='bottom-center')
  53. plt.show(shape, txt, mode='image', zoom=1.9)
  54. objs, points = [], []
  55. times = np.linspace(0, 2*np.pi, len(fourierX), endpoint=False)
  56. for time in times:
  57. x, _ = epicycles(time, 0, fourierX, order)
  58. _, y = epicycles(time, np.pi/2, fourierY, order)
  59. points.append([x, y])
  60. plt.interactive().close()