ScriptC.mw

Parametric Curve Fitting for a Script Letter 

Initial Data 

The "curve" being used for this example is a script letter C. This illustration is presented using Maple. 

 

In handwriting, a script C was written on graph paper and then a reasonably small set of Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-mi( data points was read off from the graph paper. The chosen data points are spaced closer together in regions of high curvature and spaced farther apart in regions of smaller curvature, to achieve the goal of keeping the data set small. See the LinePlot below which shows how the data points are spaced. 

 

In this example, the number of data points is 15. 

 

> Typesetting:-mrow(Typesetting:-mi(
 

> `:=`(xlist, [1.0, 3.0, 5.3, 5.9, 4.6, 2.5, 1.4, 1.8, 2.3, 3.5, 5.3, 6.0, 5.6, 4.3, 3.6])
 

[1.0, 3.0, 5.3, 5.9, 4.6, 2.5, 1.4, 1.8, 2.3, 3.5, 5.3, 6.0, 5.6, 4.3, 3.6] (1)
 

> `:=`(n, nops(xlist))
 

15 (2)
 

> `:=`(ylist, [10.0, 10.2, 12.0, 13.5, 13.8, 12.1, 8.8, 2.0, .8, .4, 1.5, 2.9, 4.2, 4.5, 3.6])
 

[10.0, 10.2, 12.0, 13.5, 13.8, 12.1, 8.8, 2.0, .8, .4, 1.5, 2.9, 4.2, 4.5, 3.6] (3)
 

> nops(ylist)
 

15 (4)
 

> `:=`(DataPoints, [seq([xlist[i], ylist[i]], i = 1 .. n)])
 

[[1.0, 10.0], [3.0, 10.2], [5.3, 12.0], [5.9, 13.5], [4.6, 13.8], [2.5, 12.1], [1.4, 8.8], [1.8, 2.0], [2.3, .8], [3.5, .4], [5.3, 1.5], [6.0, 2.9], [5.6, 4.2], [4.3, 4.5], [3.6, 3.6]]
[[1.0, 10.0], [3.0, 10.2], [5.3, 12.0], [5.9, 13.5], [4.6, 13.8], [2.5, 12.1], [1.4, 8.8], [1.8, 2.0], [2.3, .8], [3.5, .4], [5.3, 1.5], [6.0, 2.9], [5.6, 4.2], [4.3, 4.5], [3.6, 3.6]]
(5)
 

> `:=`(origDataPlot, plot(DataPoints, x = 0 .. 7, style = point, symbol = cross, colour = black, symbolsize = 16)); -1
`:=`(origDataPlot, plot(DataPoints, x = 0 .. 7, style = point, symbol = cross, colour = black, symbolsize = 16)); -1
`:=`(origDataPlot, plot(DataPoints, x = 0 .. 7, style = point, symbol = cross, colour = black, symbolsize = 16)); -1
 

 

The following plot of the data points is not very informative by itself, but when superimposed on future plots it becomes informative. 

 

See the LinePlot below for a better idea of the order of the data points. 

 

> origDataPlot
 

Plot_2d
 

>
 

Piecewise-linear Interpolation 

Piecewise-linear (straight-line) interpolation of the data gives an indication of the shape of the letter being represented. For a smooth interpolating curve see the following sections which employ cubic spline interpolation. 

 

> `:=`(LinePlot, plot(DataPoints, x = 0 .. 7, style = line)); -1
 

> plots:-display(origDataPlot, LinePlot)
 

Plot_2d
 

>
 

Cubic Spline Interpolation using Dumb Parameterization 

We proceed to do cubic spline interpolation as follows. We parameterize the curve by a parameter Typesetting:-mrow(Typesetting:-mi( and then compute two cubic spline fits, Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( fitting data Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( and Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( fitting data Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi(. The desired curve is then represented by Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( as Typesetting:-mrow(Typesetting:-mi( varies over its range of values. 

 

As discussed in class and in the CS 370 Course Notes, the simplest parameterization is achieved by setting Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( for all Typesetting:-mrow(Typesetting:-mi(. However, this leads to a distortion of the curve (illustrated below) due to the fact that in some parts of the curve, successive data points are farther apart than in other parts of the curve. Equally-spaced parameter values fail to reflect this property of the given data. 

 

> for i to n do `:=`(tlist[i], i) end do; -1
 

 

Change Typesetting:-mrow(Typesetting:-mi( from a Maple table construct to a Maple list construct. 

> `:=`(tlist, [seq(tlist[i], i = 1 .. n)])
 

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] (6)
 

 

Do cubic spline interpolation to define Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( and Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi(. 

> with(CurveFitting); -1
 

> `:=`(x[cs], Spline(tlist, xlist, t)); -1
 

> `:=`(y[cs], Spline(tlist, ylist, t)); -1
 

 

The representation used in Maple for the cubic splines computed by the above commands is a piecewise construct. Below we display the cubic spline Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( and we round the coefficients to just four significant digits so as to use less space for this display. 

 

> evalf[4](x[cs])
 

piecewise(`<`(t, 2.), `+`(`-`(.8292), `*`(1.829, `*`(t)), `*`(.1708, `*`(`^`(`+`(t, `-`(1.)), 3)))), `<`(t, 3.), `+`(`-`(1.683), `*`(2.342, `*`(t)), `*`(.5125, `*`(`^`(`+`(t, `-`(2.)), 2))), `-`(`*`(.... (7)
 

>
 

 

The unapply command appearing below is a Maple construct to transform the given expression into functional form which is useful for subsequent evaluations. For example, if Typesetting:-mrow(Typesetting:-mi( then the result of the command Typesetting:-mrow(Typesetting:-mi( is a function Typesetting:-mrow(Typesetting:-mi( such that invoking Typesetting:-mrow(Typesetting:-mi( evaluates the expression at the value Typesetting:-mrow(Typesetting:-mi(. 

 

> `:=`(x[cs], unapply(x[cs], t)); -1; `:=`(y[cs], unapply(y[cs], t)); -1
 

>
 

The refine Procedure 

Corresponding to the Matlab procedure refine presented in the CS 370 Course Notes, we define a Maple procedure to refine the list of evaluation points, Typesetting:-mrow(Typesetting:-mi(, for the purpose of plotting. In other words, we define a denser set of evaluation points corresponding to the parameter Typesetting:-mrow(Typesetting:-mi( so that a plot based on evaluations at the dense set of Typesetting:-mrow(Typesetting:-mi(-values will more accurately reflect the cubic spline functions which have been computed. 

 

This procedure will be invoked in the form refine(tlist, refFactor) and the result returned will be a new list of points generated by refining the original list tlist by a factor refFactor. 

 

> `:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
`:=`(refine, proc (`::`(tlist, list), `::`(refFactor, integer)) local n, newlist, k, dt, j; `:=`(n, nops(tlist)); `:=`(newlist, NULL); for k to `+`(n, `-`(1)) do `:=`(dt, `+`(tlist[`+`(k, 1)], `-`(tli...
 

>
 

Plots based on the Dumb Parameterization 

 

First we use a refinement factor of 3 and we display the resulting curve based on evaluating Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( at 43 points. 

 

> `:=`(t[ref], refine(tlist, 3))
 

[1, `/`(4, 3), `/`(5, 3), 2, `/`(7, 3), `/`(8, 3), 3, `/`(10, 3), `/`(11, 3), 4, `/`(13, 3), `/`(14, 3), 5, `/`(16, 3), `/`(17, 3), 6, `/`(19, 3), `/`(20, 3), 7, `/`(22, 3), `/`(23, 3), 8, `/`(25, 3),...
[1, `/`(4, 3), `/`(5, 3), 2, `/`(7, 3), `/`(8, 3), 3, `/`(10, 3), `/`(11, 3), 4, `/`(13, 3), `/`(14, 3), 5, `/`(16, 3), `/`(17, 3), 6, `/`(19, 3), `/`(20, 3), 7, `/`(22, 3), `/`(23, 3), 8, `/`(25, 3),...
(8)
 

> `:=`(t[ref], evalf(t[ref]))
 

[1., 1.333333333, 1.666666667, 2., 2.333333333, 2.666666667, 3., 3.333333333, 3.666666667, 4., 4.333333333, 4.666666667, 5., 5.333333333, 5.666666667, 6., 6.333333333, 6.666666667, 7., 7.333333333, 7....
[1., 1.333333333, 1.666666667, 2., 2.333333333, 2.666666667, 3., 3.333333333, 3.666666667, 4., 4.333333333, 4.666666667, 5., 5.333333333, 5.666666667, 6., 6.333333333, 6.666666667, 7., 7.333333333, 7....
[1., 1.333333333, 1.666666667, 2., 2.333333333, 2.666666667, 3., 3.333333333, 3.666666667, 4., 4.333333333, 4.666666667, 5., 5.333333333, 5.666666667, 6., 6.333333333, 6.666666667, 7., 7.333333333, 7....
[1., 1.333333333, 1.666666667, 2., 2.333333333, 2.666666667, 3., 3.333333333, 3.666666667, 4., 4.333333333, 4.666666667, 5., 5.333333333, 5.666666667, 6., 6.333333333, 6.666666667, 7., 7.333333333, 7....
[1., 1.333333333, 1.666666667, 2., 2.333333333, 2.666666667, 3., 3.333333333, 3.666666667, 4., 4.333333333, 4.666666667, 5., 5.333333333, 5.666666667, 6., 6.333333333, 6.666666667, 7., 7.333333333, 7....
(9)
 

> `:=`(N, nops(t[ref]))
 

43 (10)
 

> `:=`(DataPoints, [seq([x[cs](t[ref][i]), y[cs](t[ref][i])], i = 1 .. N)]); -1
 

> `:=`(ref3Plot, plot(DataPoints, x = 0 .. 7)); -1
 

> plots:-display(origDataPlot, ref3Plot)
 

Plot_2d
 

>
 

 

The above plot shows a fairly smooth curve but with some strange kinks. 

 

Let us try a refinement factor of 25 for evaluating the same curve Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( to see whether the kinks in the above plot are an artifact of not evaluating at a sufficiently dense set of Typesetting:-mrow(Typesetting:-mi(-values. 

 

> `:=`(t[ref], refine(tlist, 25)); -1
 

> `:=`(t[ref], evalf(t[ref])); -1
 

> `:=`(N, nops(t[ref]))
 

351 (11)
 

> `:=`(DataPoints, [seq([x[cs](t[ref][i]), y[cs](t[ref][i])], i = 1 .. N)]); -1
 

> `:=`(ref25Plot, plot(DataPoints, x = 0 .. 7)); -1
 

> plots:-display(origDataPlot, ref25Plot)
 

Plot_2d
 

>
 

 

Although this has achieved some smoothing in parts of the script C, the main kink appearing in the lower part of the curve has not disappeared. This kink is inherent in the particular cubic spline fitting we have done based on the dumb parameterization used above. 

 

>
 

Cubic Spline Interpolation using Smart Parameterization 

Repeating what was stated above, we parameterize the curve by a parameter Typesetting:-mrow(Typesetting:-mi( and then compute two cubic spline fits, Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( fitting data Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( and Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( fitting data Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi(, where Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( are the original 15 data points. The desired curve is then represented by Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( as Typesetting:-mrow(Typesetting:-mi( varies over its range of values. 

 

This time we define the parameter values Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( using a formula based on the distance between successive data points, as discussed in the CS 370 Course Notes. 

 

Recall the original data points. 

> xlist
 

[1.0, 3.0, 5.3, 5.9, 4.6, 2.5, 1.4, 1.8, 2.3, 3.5, 5.3, 6.0, 5.6, 4.3, 3.6] (12)
 

> ylist
 

[10.0, 10.2, 12.0, 13.5, 13.8, 12.1, 8.8, 2.0, .8, .4, 1.5, 2.9, 4.2, 4.5, 3.6] (13)
 

> n
 

15 (14)
 

 

Define a parameterization based on the distance between successive points. 

 

> `:=`(tlist[1], 1.0); -1
 

> for i to `+`(n, `-`(1)) do `:=`(tlist[`+`(i, 1)], `+`(tlist[i], sqrt(`+`(`*`(`^`(`+`(xlist[`+`(i, 1)], `-`(xlist[i])), 2)), `*`(`^`(`+`(ylist[`+`(i, 1)], `-`(ylist[i])), 2)))))) end do; -1
for i to `+`(n, `-`(1)) do `:=`(tlist[`+`(i, 1)], `+`(tlist[i], sqrt(`+`(`*`(`^`(`+`(xlist[`+`(i, 1)], `-`(xlist[i])), 2)), `*`(`^`(`+`(ylist[`+`(i, 1)], `-`(ylist[i])), 2)))))) end do; -1
for i to `+`(n, `-`(1)) do `:=`(tlist[`+`(i, 1)], `+`(tlist[i], sqrt(`+`(`*`(`^`(`+`(xlist[`+`(i, 1)], `-`(xlist[i])), 2)), `*`(`^`(`+`(ylist[`+`(i, 1)], `-`(ylist[i])), 2)))))) end do; -1
 

 

Change Typesetting:-mrow(Typesetting:-mi( from a Maple table construct to a Maple list construct. 

> `:=`(tlist, [seq(tlist[i], i = 1 .. n)])
 

[1.0, 3.009975124, 5.930591497, 7.546140939, 8.880307345, 11.58215856, 15.06066399, 21.87241854, 23.17241854, 24.43732960, 26.54683191, 28.11207949, 29.47222654, 30.80639295, 31.94656838]
[1.0, 3.009975124, 5.930591497, 7.546140939, 8.880307345, 11.58215856, 15.06066399, 21.87241854, 23.17241854, 24.43732960, 26.54683191, 28.11207949, 29.47222654, 30.80639295, 31.94656838]
[1.0, 3.009975124, 5.930591497, 7.546140939, 8.880307345, 11.58215856, 15.06066399, 21.87241854, 23.17241854, 24.43732960, 26.54683191, 28.11207949, 29.47222654, 30.80639295, 31.94656838]
(15)
 

 

Do cubic spline interpolation to define Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( and Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi(. 

> `:=`(x[cs], Spline(tlist, xlist, t)); -1
 

> `:=`(y[cs], Spline(tlist, ylist, t)); -1
 

> `:=`(x[cs], unapply(x[cs], t)); -1; `:=`(y[cs], unapply(y[cs], t)); -1
 

 

First we use a refinement factor of 3 and we display the resulting curve based on evaluating Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( at 43 points. 

 

> `:=`(t[ref], refine(tlist, 3)); -1
 

> `:=`(t[ref], evalf(t[ref])); -1
 

> `:=`(N, nops(t[ref]))
 

43 (16)
 

> `:=`(DataPoints, [seq([x[cs](t[ref][i]), y[cs](t[ref][i])], i = 1 .. N)]); -1
 

> `:=`(ref3Plot, plot(DataPoints, x = 0 .. 7)); -1
 

> plots:-display(origDataPlot, ref3Plot)
 

Plot_2d
 

>
 

 

This time we have a somewhat smooth curve and the most significant kink appearing in the previous attempt has disappeared. 

 

Let us try a refinement factor of 25 for evaluating the curve Typesetting:-mrow(Typesetting:-mfenced(Typesetting:-mrow(Typesetting:-msub(Typesetting:-mi( in order to produce a plot which more accurately reflects the cubic spline fit. 

 

> `:=`(t[ref], refine(tlist, 25)); -1
 

> `:=`(t[ref], evalf(t[ref])); -1
 

> `:=`(N, nops(t[ref]))
 

351 (17)
 

> `:=`(DataPoints, [seq([x[cs](t[ref][i]), y[cs](t[ref][i])], i = 1 .. N)]); -1
 

> `:=`(ref25Plot, plot(DataPoints, x = 0 .. 7)); -1
 

> plots:-display(origDataPlot, ref25Plot)
 

Plot_2d
 

>
 

 

We now have a smooth curve through the given data points. The smart parameterization based on the distance between successive data points avoids the distortion caused by a simple-minded parameterization. 

 

>