Parametric curves from 2D data using DFT

There is a very cool application in maple for generating parametric Curves from 2-D Data using Discrete Fourier Transforms https://www.maplesoft.com/Applications/Detail.aspx?id=154546

I wonder if a similar implementation is already available in Mathematica. If not, then how could one translate the maple code into a Mathematica one?

The code can be found in the PDF on the same page and is included at the bottom of this answer for reference.

It is straightforward to translate to Mathematica:

ClearAll[parCurve2D]
parCurve2D[{x_?(VectorQ[#, NumericQ] &), y_?(VectorQ[#, NumericQ] &)}] :=
Module[{n, dftX, dftY, px, py, ax, ay, curveX, curveY},
n = Length[x];

{dftX, dftY} = Fourier /@ {x, y};

{px, py} = Re[Arg[#[[2 ;; Ceiling[n/2]]]]] & /@ {dftX, dftY};
{ax, ay} = Abs[ #[[2 ;; Ceiling[n/2]]] ] & /@ {dftX, dftY};

curveX = 3 Sum[ax[[i]] Sin[Pi/2 - 2 Pi/n*i*t + px[[i]] ], {i, 1, Ceiling[n/2] - 1}];
curveY = 3 Sum[ay[[i]] Sin[Pi/2 - 2 Pi/n*i*t + py[[i]] ], {i, 1, Ceiling[n/2] - 1}];

ParametricPlot[{curveX, curveY}, {t, 0, n/2}]
]


Using the example points in that PDF as well, one can generate the same figure:

parCurve2D[{x, y}]


Data points:

x = {558, 533, 512, 509, 519, 538, 527, 501, 485, 493, 499, 499, 465,
406, 413, 441, 430, 406, 397, 393, 392, 397, 403, 406, 397, 365,
356, 354, 326, 311, 336, 355, 315, 278, 278, 249, 242, 232, 204,
208, 209, 204, 206, 242, 242, 263, 274, 287, 334, 361, 320, 314,
344, 355, 360, 379, 406, 407, 399, 393, 391, 394, 404, 415, 439,
431, 402, 438, 487, 503, 497, 486, 488, 514, 538, 537, 511, 508,
518, 546};
y = {420, 412, 431, 480, 522, 505, 444, 431, 480, 591, 605, 548, 444,
416, 426, 478, 516, 533, 488, 395, 305, 356, 463, 525, 418, 414,
488, 465, 416, 458, 497, 495, 463, 409, 488, 514, 458, 512, 441,
441, 485, 412, 508, 493, 463, 516, 437, 403, 503, 482, 495, 429,
416, 499, 439, 407, 490, 533, 392, 309, 333, 452, 520, 529, 499,
452, 418, 416, 492, 593, 606, 544, 448, 429, 482, 518, 508, 450,
424, 414};


Original Maple code from Generating Parametric Curves from 2-D Data using Discrete Fourier Transforms:

n := numelems(X):
P_X := Re(argument~(dft_X[2..ceil(n / 2)])):
A_X := abs(dft_X[2..ceil(n / 2)]):
P_Y := Re(argument~(dft_Y[2..ceil(n / 2)])):
A_Y := abs(dft_Y[2..ceil(n / 2)]):

curve_X := evalf[3](add(seq(A_X[i] * sin(Pi / 2 - 2 * Pi / n * i
* t + P_X[i]), i = 1..ceil(n / 2) - 1)));
curve_Y := evalf[3](add(seq(A_Y[i] * sin(Pi / 2 - 2 * Pi / n * i
* t + P_Y[i]), i = 1..ceil(n / 2) - 1)));

plot([curve_X, curve_Y, t = 0 .. n / 2])

• thanks for the reply. I think there is a problem with your implementation though. The reconstructed curve doesn't fit the data. Oct 5 at 17:00
• @SpaceChild Neither did the original implementation in Maple: you will notice that the raw points are not centered around the origin, but the resulting Maple parametric function is, so they would not have fit anyway. There may also be a scaling factor difference: I noticed that the Maple code introduced a factor of 3 in the parametric curves; I left it there in the MMA code as well. You may play with that factor to get a better fit, but you should mean-center the points first. Consider: Show[parCurve2D[{x, y}], ListPlot[Standardize[Transpose[{x, y}], Mean, 1 &]], PlotRange -> All ]. Oct 5 at 18:57
• you're right. Ok it seems reasonable to appear centered at the origin, but as regards the scaling I would expect the scaling factor to occur naturally from the Fourier transform. Interestingly the scaling factor that should be used after deleting the coefficients 3 is 1/8. Any idea why is this? Oct 5 at 19:20

In MMA we have the function "Interpolation". With this it is easy to do what you want.

Here is a simple example. First we create some points on an arbitrary test curve:

dat = Table[{Sin[x], Sin[2 x + 1]}, {x, 0, 2 Pi, 2 Pi/20}];
ListLinePlot[dat, Epilog -> Point[dat]]


Then we interpolate the x and y values

fun0[x_] = {Interpolation[dat[[All, 1]]][x],
Interpolation[dat[[All, 2]]][x]}


Now the argument of this function runs from 1 to Length[dat]. To make a more convenient parametrization, we may define a function with argument between 0 and 1:

fun[x_] = fun0[x (Length[dat] - 1) + 1]

ParametricPlot[fun[x ], {x, 0, 1}, Epilog -> Point[dat]]


As an additional benefit, we get a smooth curve.

• yes right, one can exploit various sorts of curve fitting, but I am interested particularly in the DFT method. Oct 5 at 16:19
• Why makes things complicated? Oct 5 at 16:21