home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Pascal / Applications / NIH Image 1.55 / Source / Ellipse.p < prev    next >
Encoding:
Text File  |  1993-06-17  |  8.7 KB  |  331 lines  |  [TEXT/PJMM]

  1. unit Ellipse;
  2.  
  3. interface
  4.  
  5.     uses
  6.         QuickDraw, Palettes, PrintTraps, globals, Utilities, graphics;
  7.  
  8.     procedure DrawEllipse;
  9.     procedure GetEllipseParam (var Major, Minor, angle, xxcenter, yycenter: extended);
  10.     procedure ComputeSums (y, width: integer; var MaskLine: LineType);
  11.     procedure ResetSums;
  12.  
  13.  
  14. {Best-fitting ellipse routines by:}
  15.  
  16. {  Bob Rodieck}
  17. {  Department of Ophthalmology, RJ-10}
  18. {  University of Washington, }
  19. {  Seattle, WA, 98195}
  20. {  (206) 543-2449}
  21.  
  22.  
  23. {Notes on best-fitting ellipse:}
  24.  
  25. {  Consider some arbitrarily shaped closed profile, which we wish to}
  26. {  characterize in a quantitative manner by a series of terms, each }
  27. {  term providing a better approximation to the shape of the profile.  }
  28. {  Assume also that we wish to include the orientation of the profile }
  29. {  (i.e. which way is up) in our characterization. }
  30.  
  31. {  One approach is to view the profile as formed by a series harmonic }
  32. {  components, much in the same way that one can decompose a waveform}
  33. {  over a fixed interval into a series of Fourier harmonics over that }
  34. {  interval. From this perspective the first term is the mean radius,}
  35. {  or some related value (i.e. the area).  The second term is the }
  36. {  magnitude and phase of the first harmonic, which is equivalent to the}
  37. {  best-fitting ellipse.  }
  38.  
  39. {  What constitutes the best-fitting ellipse?  First, it should have the}
  40. {  same area.  In statistics, the measure that attempts to characterize some}
  41. {  two-dimensional distribution of data points is the 'ellipse of }
  42. {  concentration' (see Cramer, Mathematical Methods of Statistics, }
  43. {  Princeton Univ. Press, 945, page 283).  This measure equates the second}
  44. {  order central moments of the ellipse to those of the distribution, }
  45. {  and thereby effectively defines both the shape and size of the ellipse. }
  46.  
  47. {  This technique can be applied to a profile by assuming that it constitutes}
  48. {  a uniform distribution of points bounded by the perimeter of the profile.}
  49. {  For most 'blob-like' shapes the area of the ellipse is close to that}
  50. {  of the profile, differing by no more than about 4%. We can then make}
  51. {  a small adjustment to the size of the ellipse, so as to give it the }
  52. {  same area as that of the profile.  This is what is done here, and }
  53. {  therefore this is what we mean by 'best-fitting'. }
  54.  
  55. {  For a real pathologic case, consider a dumbell shape formed by two small}
  56. {  circles separated by a thin line. Changing the distance between the}
  57. {  circles alters the second order moments, and thus the size of the ellipse }
  58. {  of concentration, without altering the area of the profile. }
  59.  
  60.  
  61. implementation
  62.  
  63.     const
  64.         HalfPi = 1.5707963267949;
  65.  
  66.     type
  67.         TMoments = record
  68.                 n: extended;
  69.                 xm, ym,             { mean values }
  70.                 u20, u02, u11: extended; { central moments }
  71.             end;
  72.  
  73.     var
  74.         BitCount, xsum, ysum: LongInt;
  75.         x2sum, y2sum, xysum: extended;
  76.         Moments: TMoments;
  77.         gMajor, gMinor, Theta: extended;
  78.         gxCenter, gyCenter: integer;
  79.         SaveRect: rect;
  80.  
  81.  
  82.  
  83.     procedure DrawEllipse;
  84.  
  85. { basic equations: }
  86.  
  87. {    a: major axis}
  88. {    b: minor axis}
  89. {    t: theta, angle of major axis, clockwise with respect to x axis. }
  90.  
  91. {    g11*x^2 + 2*g12*x*y + g22*y^2 = 1       -- equation of ellipse}
  92.  
  93. {    g11:= ([cos(t)]/a)^2 + ([sin(t)]/b)^2}
  94. {    g12:= (1/a^2 - 1/b^2) * sin(t) * cos(t)}
  95. {    g22:= ([sin(t)]/a)^2 + ([cos(t)]/b)^2}
  96.  
  97. {    solving for x:      x:= k1*y ± sqrt( k2*y^2 + k3 )}
  98.  
  99. {    where:  k1:= -g12/g11}
  100. {            k2:= (g12^2 - g11*g22)/g11^2}
  101. {            k3:= 1/g11}
  102.  
  103. {    ymax or ymin occur when there is a single value for x, that is when:    }
  104. {            k2*y^2 + k3 = 0    }
  105.  
  106.  
  107.         const
  108.             maxY = 1000;
  109.  
  110.         type
  111.             TMinMax = record
  112.                     xmin, xmax: Integer;
  113.                 end;
  114.  
  115.         var
  116.             sint, cost, rmajor2, rminor2, g11, g12, g22, k1, k2, k3: Real;
  117.             xsave, y, ymin, ymax: Integer;
  118.             aMinMax: TMinMax;
  119.             TXList: array[0..maxY] of TMinMax;
  120.  
  121.         procedure GetMinMax (yValue: Integer; var xMinMax: TMinMax);
  122.             var
  123.                 j1, j2, yr: Real;
  124.         begin
  125.             yr := yvalue;
  126.             j2 := sqrt(k2 * sqr(yr) + k3);
  127.             j1 := k1 * yr;
  128.             with xMinMax do begin
  129.                     xmin := round(j1 - j2);
  130.                     xmax := round(j1 + j2);
  131.                 end;
  132.         end;
  133.  
  134.         procedure Plot (x: Integer);
  135.         begin
  136.             MoveTo(gxCenter + xsave, gyCenter + y);
  137.             LineTo(gxCenter + x, gyCenter + y);
  138.             xsave := x;
  139.         end;
  140.  
  141.     begin
  142.         if not EqualRect(info^.RoiRect, SaveRect) then
  143.             exit(DrawEllipse);
  144.         sint := sin(Theta);
  145.         cost := cos(Theta);
  146.         rmajor2 := 1.0 / sqr(gMajor);
  147.         rminor2 := 1.0 / sqr(gMinor);
  148.         g11 := rmajor2 * sqr(cost) + rminor2 * sqr(sint);
  149.         g12 := (rmajor2 - rminor2) * sint * cost;
  150.         g22 := rmajor2 * sqr(sint) + rminor2 * sqr(cost);
  151.         k1 := -g12 / g11;
  152.         k2 := (sqr(g12) - g11 * g22) / sqr(g11);
  153.         k3 := 1.0 / g11;
  154.         ymax := Trunc(sqrt(abs(k3 / k2)));
  155.         if ymax > maxy then
  156.             ymax := maxy;
  157.         ymin := -ymax;
  158.   { Precalculation and use of symmetry speed things up }
  159.         for y := 0 to ymax do begin
  160.                 GetMinMax(y, aMinMax);
  161.                 TXList[y] := aMinMax;
  162.             end;
  163.         xsave := TXList[ymax - 1].xmin;  {  i.e. abs(ymin+1) }
  164.         for y := ymin to ymax - 1 do
  165.             with TXList[abs(y)] do
  166.                 if y < 0 then
  167.                     Plot(xmax)
  168.                 else
  169.                     Plot(-xmin);
  170.         for y := ymax downto ymin + 1 do
  171.             with TXList[abs(y)] do
  172.                 if y < 0 then
  173.                     Plot(xmin)
  174.                 else
  175.                     Plot(-xmax);
  176.     end; { TraceOval }
  177.  
  178.  
  179.     procedure GetMoments;
  180.         var
  181.             x1, y1, x2, y2, xy: extended;
  182.     begin
  183.         with moments do begin
  184.                 if BitCount = 0 then
  185.                     exit(GetMoments);
  186.                 n := bitcount;
  187.                 x1 := xsum / n;
  188.                 y1 := ysum / n;
  189.                 x2 := x2sum / n;
  190.                 y2 := y2sum / n;
  191.                 xy := xysum / n;
  192.                 xm := x1;
  193.                 ym := y1;
  194.                 u20 := x2 - sqr(x1);
  195.                 u02 := y2 - sqr(y1);
  196.                 u11 := xy - x1 * y1;
  197.             end;
  198.     end;
  199.  
  200.  
  201.     procedure GetEllipseParam (var Major, Minor, angle, xxcenter, yycenter: extended);
  202. {Return the parameters of an ellipse that has the same second-order }
  203. {moments as those specified by 'm'. }
  204.  
  205. {See Cramer, Mathematical Methods of Statistics, }
  206. {Princeton Univ. Press 1945, page 283.}
  207.  
  208. {The elliptical parameters returned specify an ellipse that}
  209. {has the the same second order moments as that of the profile}
  210. {that generated the moments.  This ellipse need not have the same}
  211. {area as that of the profile, although its area will be close to}
  212. {that of the profile.  In order to refine our measure, we scale}
  213. {the major and minor axes so as to make the area equal to that}
  214. {of the profile. }
  215.         var
  216.             a11, a12, a22, m4, z, scale, tmp, xoffset, yoffset: extended;
  217.             width, height: integer;
  218.             str1, str2, str3: str255;
  219.     begin
  220.         with info^, info^.RoiRect do begin
  221.                 if PixelCount^[mCount] = 1 then begin
  222.                         major := 0.564;
  223.                         Minor := 0.564;
  224.                         angle := 0.0;
  225.                         xxcenter := left + 0.5;
  226.                         yycenter := top + 0.5;
  227.                         exit(GetEllipseParam);
  228.                     end;
  229.                 width := right - left;
  230.                 height := bottom - top;
  231.                 if width = 1 then begin
  232.                         major := 0.6 * height;
  233.                         Minor := 0.564;
  234.                         angle := 90.0;
  235.                         xxcenter := left + 0.5;
  236.                         yycenter := top + height / 2.0;
  237.                         exit(GetEllipseParam);
  238.                     end;
  239.                 if height = 1 then begin
  240.                         major := 0.6 * width;
  241.                         Minor := 0.564;
  242.                         angle := 0.0;
  243.                         xxcenter := left + width / 2.0;
  244.                         yycenter := top + 0.5;
  245.                         exit(GetEllipseParam);
  246.                     end;
  247.             end; {with}
  248.         GetMoments;
  249.         with moments do begin
  250.                 m4 := 4.0 * abs(u02 * u20 - sqr(u11));
  251.                 if m4 = 0.0 then
  252.                     m4 := 0.000001;
  253.                 a11 := u02 / m4;
  254.                 a12 := u11 / m4;
  255.                 a22 := u20 / m4;
  256.                 xoffset := xm;
  257.                 yoffset := ym;
  258.             end;
  259.         tmp := a11 - a22;
  260.         if tmp = 0.0 then
  261.             tmp := 0.000001;
  262.         theta := 0.5 * arctan(2.0 * a12 / tmp);
  263.         if theta < 0.0 then
  264.             theta := theta + halfpi;
  265.         if a12 > 0.0 then
  266.             theta := theta + halfpi
  267.         else if a12 = 0 then begin
  268.                 if a22 > a11 then begin
  269.                         theta := 0;
  270.                         tmp := a22;
  271.                         a22 := a11;
  272.                         a11 := tmp;
  273.                     end
  274.                 else if a11 <> a22 then
  275.                     theta := halfpi;
  276.             end;
  277.         tmp := sin(theta);
  278.         if tmp = 0.0 then
  279.             tmp := 0.000001;
  280.         z := a12 * cos(theta) / tmp;
  281.         major := sqrt(1.0 / abs(a22 + z));
  282.         minor := sqrt(1.0 / abs(a11 - z));
  283.         scale := sqrt(BitCount / (pi * major * minor));  { equalize areas }
  284.         major := major * scale;
  285.         minor := minor * scale;
  286.         angle := 180.0 * theta / pi;
  287.         with info^ do begin
  288.                 with RoiRect do begin
  289.                         xxCenter := left + xoffset;
  290.                         yyCenter := top + yoffset;
  291.                     end;
  292.                 SaveRect := RoiRect;
  293.             end;
  294.         gxCenter := round(xxCenter);
  295.         gyCenter := round(yyCenter);
  296.         gMajor := major;
  297.         gMinor := minor;
  298.     end;
  299.  
  300.  
  301.     procedure ComputeSums (y, width: integer; var MaskLine: LineType);
  302.         var
  303.             x: integer;
  304.             xe, ye: extended;
  305.     begin
  306.         for x := 0 to width - 1 do
  307.             if MaskLine[x] = BlackIndex then begin
  308.                     xsum := xsum + x;
  309.                     ysum := ysum + y;
  310.                     xe := x;
  311.                     ye := y;
  312.                     x2sum := x2sum + xe * xe;
  313.                     y2sum := y2sum + ye * ye;
  314.                     xysum := xysum + xe * ye;
  315.                     bitCount := bitCount + 1;
  316.                 end;
  317.     end;
  318.  
  319.  
  320.     procedure ResetSums;
  321.     begin
  322.         xsum := 0;
  323.         ysum := 0;
  324.         x2sum := 0.0;
  325.         y2sum := 0.0;
  326.         xysum := 0.0;
  327.         bitCount := 0;
  328.     end;
  329.  
  330.  
  331. end.