View Raw SPL
/*****************************************************************************
*                                                                            *
*   CIRCFIT.SPL  Copyright (C) 2020 DSP Development Corporation              *
*                               All Rights Reserved                          *
*                                                                            *
*   Author:       Randy Race                                                 *
*                                                                            *
*   Synopsis:     Least squares fit to a circle                              *
*                                                                            *
*   Revisions:    19 Jun 2020  RRR  Creation                                 *
*                                                                            *
*****************************************************************************/

#if @HELP_CIRCFIT

    CIRCFIT

    Purpose: Computes the least squares fit of a circle.

    Syntax:  CIRCFIT(x, y)

             (circ, coef) = CIRCFIT(x, y)

              x - A series, the X values.

              y - A series, the Y values.

    Alternate Syntax:  
    
             CIRCFIT(xy)

             (circ, coef) = CIRCFIT(xy)

              xy - An XY series, the input circle.

    Returns: An XY series, the fitted circle.

             (circ, coef) = CIRCFIT(x, y) returns the fitted circle and
             the coefficients as two series. The coefficients are of the
             form:

             coef == {xc, yc, r}

             where XC, and YC are the center coordinates and R is the radius.

    Example:
             W1: linspace(0, 2*pi, 100)
             W2: 5 * cos(W1) + grand(length(w1), 1)
             W3: 5 * sin(W1) + grand(length(w1), 1)
             W4: xy(w2, w3);setaspect(1)
             W5: circfit(w2, w3);setaspect(1);overp(w4);setx(-10, 10);sety(-10, 10)

             W4 contains a noisy circle. W5 computes the least squares fit and
             overplots the original circle.
 
    Example:
             W1: linspace(0, 2*pi, 100)
             W2: 5 * cos(W1) + grand(length(w1), 1)
             W3: 5 * sin(W1) + grand(length(w1), 1)
             W4: xy(w2, w3);setaspect(1)
             W5: circfit(w4);setaspect(1);overp(w4);setx(-10, 10);sety(-10, 10)

             Same as above except the input to CIRCFIT is an XY series.

    Example:
             W1: linspace(0, 2*pi, 100);
             W2: 3 * cos(w1) + 5
             W3: 3 * sin(w1) + 7
             W4: xy(w2, w3);setaspect(1)
             W5: w2[1..3]
             W6: w3[1..3]
             W7: (data, coef) = circfit(w5, w6);coef;table
             W8: xy(w7[3] * cos(w1) + w7[1], w7[3] * sin(w1) + w7[2])

             W4 contains a circle of radius 3 with a center at {5, 7}.

             W7 performs a three point fit of the circle where the resulting
             coefficients COEF == {xc, yc, r} == {5, 7, 3}.

             W8 computes and displays the resulting fitted circle.

    Remarks:
             CIRCFIT uses the \^ matrix solve operator to perform the least
             squares fit.

    See Also:
             \^
             Linfit
             Sinfit
             Sinfit3
             Sinfit4
             XY
#endif


/* least squares fit to a circle */
circfit(x, y)
{
        local a, xc, yc, r, coef, th, circ, xe, ye;
        
        if (argc < 2)
        {
                if (argc < 1)
                {
                        x = {};
                }

                y = yvals(x);
                x = xvals(x);
        }

        if (isempty(x))
        {
                error(sprintf("%s = XY or X and Y input series required", __FUNC__));
        }

        /* least squares fit */
        a = ravel(x, y, ones(size(x))) \^ (-(x^2 + y^2));

        /* circle center */
        xc = -0.5 * a[1];
        yc = -0.5 * a[2];

        /* circle radius */
        r = sqrt((a[1]^2 + a[2]^2) / 4 - a[3]);

        /* group center and radius */
        coef = {xc, yc, r};

        /* compute resulting circle */
        th = linspace(0, 2*pi, length(x));
        xe = r * cos(th) + xc;
        ye = r * sin(th) + yc;

        circ = xy(xe, ye);

        return(circ, coef);
}