unit Background;
{************************************************************************}
{*     by Michael Castle and Janice Keller                                                                                                    *}
{*     University of Michigan Mental Health Research Institute (MHRI)                                                     *}
{*     e-mail address: mike.castle@umich.edu                                                                                  *}
{************************************************************************}
{*     Rolling ball and rolling disk algorithms inspired by Stanley Sternberg's article, "Biomedical        *}
{*  Image Processing", IEEE Computer, January 1983.  Discussions with Rork Kuick and Tom               *}
{*  Ford-Holevinski also contributed to the development of the algorithms and improvements in their   *}
{*  efficiency.                                                                                                                                                *}
{************************************************************************}

interface

	uses
		Types, Memory, QuickDraw, Packages, Menus, Events, Fonts, Scrap, ToolUtils, Resources, Errors, Palettes, Windows,
		globals, Utilities, Graphics, Camera, Filters;

	procedure DoBackgroundMenuEvent (MenuItem: integer);


implementation

	type
		IntRow = array[0..MaxLine] of Integer;
		BackSubKindType = (RollingHorizontalArc, RollingVerticalArc, RollingBothArcs, RollingBall);

	var
		ArcTrimPer: integer;                                  {trim off percentage of each side of the rolling ball patch}
		shrinkfactor: integer;                                 {shrink the image and ball by this factor before rolling ball}
		BackSubKind: BackSubKindType;                {which kind of background subtraction are we doing}
		IntPlotWidth: Boolean;
		Intplotwidthval: integer;
		BoundRect: rect;
		xxcenter, yycenter: integer;                                        {center of rectangular mask used in MinIn2DMask}
		xmaskmin, ymaskmin: integer;                                   {upper left corner of mask used in AvgIn2DMask}
		backgroundptr, ballptr, smallimageptr: ptr;              {ptrs to background, rolling ball, shrunk image memory}
		backgroundaddr, balladdr, smallimageaddr: longint;   {addrs of background, rolling ball shrunk image memory}
		patchwidth: LongInt;                                                     {x or y dimension of the rolling ball patch}
		leftroll, rightroll, toproll, bottomroll: integer;         {bounds of the shrunk image}
		Aborting: boolean;





	procedure RollBall;
{*******************************************************************************}
{*     RollBall 'rolls' a filtering object over a (shrunken) image in order to find the image's smooth continuous    *}
{*  background.  For the purpose of explaining this algorithm, imagine that the 2D grayscale image has a third     *}
{*  (height) dimension defined by the intensity value (0-255) at every point in the image.  The center of the      *}
{*  filtering object, a patch from the top of a sphere having radius BallRadius, is moved along each scan line of     *}
{*  the image so that the patch is tangent to the image at one or more points with every other point on the patch    *}
{*  below the corresponding (x,y) point of the image.  Any point either on or below the patch during this process*}
{*  is considered part of the background.  Shrinking the image before running this procedure is advised due to      *}
{*  the fourth-degree complexity of the algorithm.  Care has been taken to avoid unnecessary operations (exp.      *}
{*  multiplication inside loops) in this code.                                                                                                                *}
{*******************************************************************************}
		var
			halfpatchwidth,                                {distance in x or y from patch center to any edge}
			ptsbelowlastpatch,                           {number of points we may ignore because they were below last patch}
			left, right, top, bottom,                   {}
			xpt, ypt,                                           {current (x,y) point in the shrunken image}
			xpt2, ypt2,                                      {current (x,y) point in the patch relative to upper left corner}
			xval, yval,                                        {location in ball in shrunken image coordinates}
			zdif,                                                  {difference in z (height) between point on ball and point on image}
			zmin,                                                {smallest zdif for ball patch with center at current point}
			zctr,                                                 {current height of the center of the sphere of which the patch is a part}
			zadd: integer;                                    {height of a point on patch relative to the xy-plane of the shrunken image}
			ballpt,                                               {index to chunk of memory storing the precomputed ball patch}
			imgpt,                                               {index to chunk of memory storing the shrunken image}
			backgrpt,                                          {index to chunk of memory storing the calculated background}
			ybackgrpt,                                        {displacement to current background scan line}
			ybackgrinc,                                      {distance in memory between two shrunken y-points in background}
			smallimagewidth: longint;               {length of a scan line in shrunken image}
			p1, p2: ptr;                                      {temporary pointers to background, ball, or small image}
	begin
		UpdateMeter(20, 'Finding Background...');
		left := 1;
		right := rightroll - leftroll - 1;
		top := 1;
		bottom := bottomroll - toproll - 1;
		smallimagewidth := right - left + 3;
		halfpatchwidth := patchwidth div 2;
		ybackgrinc := shrinkfactor * (BoundRect.right - BoundRect.left);  {real dist btwn 2 adjacent (dy=1) shrunk pts}
		zctr := 0;                                            {start z-center in the xy-plane}
		for ypt := top to (bottom + patchwidth) do begin
				for xpt := left to (right + patchwidth) do {while patch is tangent to edges or within image...}
					begin                                           {xpt is far right edge of ball patch}
{do we have to move the patch up or down to make it tangent to but not above image?...}
						zmin := 255;                              {highest could ever be 255}
						ballpt := balladdr;
						ypt2 := ypt - patchwidth;          {ypt2 is top edge of ball patch}
						imgpt := smallimageaddr + ypt2 * smallimagewidth + xpt - patchwidth;
						while ypt2 <= ypt do begin
								xpt2 := xpt - patchwidth;      {xpt2 is far left edge of ball patch}
								while xpt2 <= xpt do            {check every point on ball patch}
									begin                                   {only examine points on }
										if ((xpt2 >= left) and (xpt2 <= right) and (ypt2 >= top) and (ypt2 <= bottom)) then begin
												p1 := ptr(ballpt);
												p2 := ptr(imgpt);
												zdif := BAND(p2^, 255) - (zctr + BAND(p1^, 255));  {curve - circle points}
												if (zdif < zmin) then begin {keep most negative, since ball should always be below curve}
														zmin := zdif;
													end;
											end;  {if xpt2,ypt2}
										ballpt := ballpt + 1;          {step thru the ball patch memory}
										xpt2 := xpt2 + 1;
										imgpt := imgpt + 1;
									end;  {while xpt2 }
								ypt2 := ypt2 + 1;
								imgpt := imgpt - patchwidth - 1 + smallimagewidth;
							end;  {while ypt2}
						if (zmin <> 0) then
							zctr := zctr + zmin;                {move ball up or down if we find a new minimum}
						if (zmin < 0) then
							ptsbelowlastpatch := halfpatchwidth    {ignore left half of ball patch when dz < 0}
						else
							ptsbelowlastpatch := 0;
{now compare every point on ball with background,  and keep highest number}
						yval := ypt - patchwidth;
						ypt2 := 0;
						ballpt := balladdr;
						ybackgrpt := backgroundaddr + (yval - top + 1) * ybackgrinc;
						while ypt2 <= patchwidth do begin
								xval := xpt - patchwidth + ptsbelowlastpatch;
								xpt2 := ptsbelowlastpatch;
								ballpt := ballpt + ptsbelowlastpatch;
								backgrpt := ybackgrpt + (xval - left + 1) * shrinkfactor;
								while xpt2 <= patchwidth do begin     {for all the points in the ball patch}
										if ((xval >= left) and (xval <= right) and (yval >= top) and (yval <= bottom)) then begin
												p1 := ptr(ballpt);
												zadd := zctr + BAND(p1^, 255);
												p1 := ptr(backgrpt);
												if (zadd > BAND(p1^, 255)) then  {keep largest adjustment}
													p1^ := zadd;
											end;
										ballpt := ballpt + 1;
										xval := xval + 1;
										xpt2 := xpt2 + 1;
										backgrpt := backgrpt + shrinkfactor;     {move to next point in x}
									end;  {while xpt2}
								yval := yval + 1;
								ypt2 := ypt2 + 1;
								ybackgrpt := ybackgrpt + ybackgrinc;       {move to next point in y}
							end;  {while ypt2}
					end;  {for xpt }
				if ((ypt mod 5) = 0) or not FasterBackgroundSubtraction then begin
						UpdateMeter(20 + (ord4(ypt - top) * 70) div (bottom + patchwidth - top), 'Finding Background...');
						if CommandPeriod then begin
								beep;
								Aborting := true;
								Exit(RollBall);
							end;
					end;
			end;  {for ypt}
	end;


	function MinIn2DMask {(xmaskmin,ymaskmin: integer)}
		: integer;
{*******************************************************************************}
{*     MinInMask finds the minimum pixel value in a shrinkfactor X shrinkfactor mask.                                           *}
{*******************************************************************************}
		var
			i, j,                                           {loop indices to step through mask}
			thispixel,                                  {value at current pixel in mask}
			min,                                          {temporary minimum value in mask}
			nextrowoffset: integer;             {distance in memory from end of mask in this row to beginning in next}
			paddr: longint;                           {address of current mask pixel}
			p: ptr;                                        {pointer to current pixel in mask}
	begin
		with info^ do begin
				min := 255;
				nextrowoffset := bytesperrow - shrinkfactor;
				paddr := ord4(PicBaseAddr) + ymaskmin * bytesperrow + xmaskmin;
				for j := 1 to shrinkfactor do begin
						for i := 1 to shrinkfactor do begin
								p := ptr(paddr);
								thispixel := BAND(p^, 255);
								if (thispixel < min) then
									min := thispixel;
								paddr := paddr + 1;
							end;     {for i}
						paddr := paddr + nextrowoffset;
					end;     {for j}
				MinIn2DMask := min;
			end; {with}
	end;


	procedure GetRollingBall;
{******************************************************************************}
{*     This procedure computes the location of each point on the rolling ball patch relative to the center of the     *}
{*  sphere containing it.  The patch is located in the top half of this sphere.  The vertical axis of the sphere         *}
{*  passes through the center of the patch.  The projection of the patch in the xy-plane below is a square.           *}
{******************************************************************************}
		var
			rsquare,                                                                         {rolling ball radius squared}
			xtrim,                                                                            {# of pixels trimmed off each end of ball to make patch}
			xval, yval,                                                                     {x,y-values on patch relative to center of rolling ball}
			smallballradius, diam,                                                  {radius and diameter of rolling ball}
			temp,                                                                             {value must be >=0 to take square root}
			halfpatchwidth: integer;                                                {distance in x or y from center of patch to any edge}
			i,                                                                                    {index into rolling ball patch memory}
			ballsize: LongInt;                                                           {size of rolling ball memory}
			p: ptr;                                                                            {pointer to current point on the ball patch}
	begin
		smallballradius := ballradius div shrinkfactor;           {operate on small-sized image with small-sized ball}
		if smallballradius < 1 then
			smallballradius := 1;
		rsquare := smallballradius * smallballradius;
		diam := smallballradius * 2;
		xtrim := (ArcTrimPer * diam) div 100;                      {only use a patch of the rolling ball}
		patchwidth := diam - xtrim - xtrim;
		halfpatchwidth := smallballradius - xtrim;                   {this is half the patch width}
		ballsize := (patchwidth + 1) * (patchwidth + 1);
		ballptr := NewPtrClear(ballsize);
		p := ballptr;
		for i := 0 to ballsize - 1 do begin
				xval := i mod (patchwidth + 1) - halfpatchwidth;
				yval := i div (patchwidth + 1) - halfpatchwidth;
				temp := rsquare - (xval * xval) - (yval * yval);
				if (temp >= 0) then
					p^ := round(sqrt(temp))
				else
					p^ := 0;
				p := ptr(ord4(p) + 1);
			end;
	end;


	procedure InterpolateBackground2D; {(leftroll, rightroll, toproll, bottomroll: integer; backgroundaddr: longint)}
{******************************************************************************}
{*     This procedure uses bilinear interpolation to find the points in the full-scale background given the points *}
{*  from the shrunken image background.  Since the shrunken background is found from an image composed of    *}
{*  minima (over a sufficiently large mask), it is certain that no point in the full-scale interpolated                 *}
{*  background has a higher pixel value than the corresponding point in the original image.                                  *}
{******************************************************************************}
		var
			i, ii,                                                   {horizontal loop indices}
			j, jj,                                                  {vertical loop indices}
			hloc, vloc,                                          {position of current pixel in calculated background}
			vinc,                                                   {memory offset from current calculated pos to current interpolated pos}
			lastvalue, nextvalue: integer;           {calculated pixel values between which we are interpolating}
			p,                                                        {pointer to current interpolated pixel value}
			bglastptr, bgnextptr: ptr;                 {pointers to calculated pixel values between which we are interpolating}
			width: LongInt;
	begin
		vloc := 0;
		with BoundRect do begin
				width := right - left;
				for j := 1 to bottomroll - toproll - 1 do begin     {interpolate to find background interior}
						hloc := 0;
						vloc := vloc + shrinkfactor;
						for i := 1 to rightroll - leftroll do begin
								hloc := hloc + shrinkfactor;
								bgnextptr := ptr(backgroundaddr + vloc * width + hloc);
								bglastptr := ptr(ord4(bgnextptr) - shrinkfactor);
								nextvalue := BAND(bgnextptr^, 255);
								lastvalue := BAND(bglastptr^, 255);
								for ii := 1 to shrinkfactor - 1 do begin     {interpolate horizontally}
										p := ptr(ord4(bgnextptr) - ii);
										p^ := lastvalue + (shrinkfactor - ii) * (nextvalue - lastvalue) div shrinkfactor;
									end;     {for ii}
								for ii := 0 to shrinkfactor - 1 do begin     {interpolate vertically}
										bglastptr := ptr(backgroundaddr + (vloc - shrinkfactor) * width + hloc - ii);
										bgnextptr := ptr(backgroundaddr + vloc * width + hloc - ii);
										lastvalue := BAND(bglastptr^, 255);
										nextvalue := BAND(bgnextptr^, 255);
										vinc := 0;
										for jj := 1 to shrinkfactor - 1 do begin
												vinc := vinc - right + left;
												p := ptr(ord4(bgnextptr) + vinc);
												p^ := lastvalue + (shrinkfactor - jj) * (nextvalue - lastvalue) div shrinkfactor;
											end;     {for jj}
									end;     {for ii}
							end;     {for i}
					end;     {for j}
			end;   {with boundrect}
	end;


	procedure ExtrapolateBackground2D; {(leftroll, rightroll, toproll, bottomroll: integer; backgroundaddr: longint)}
{******************************************************************************}
{*     This procedure uses linear extrapolation to find pixel values on the top, left, right, and bottom edges of      *}
{*  the background.  First it finds the top and bottom edge points by extrapolating from the edges of the                *}
{*  calculated and interpolated background interior.  Then it uses the edge points on the new calculated,               *}
{*  interpolated, and extrapolated background to find all of the left and right edge points.  If extrapolation yields *}
{*  values below zero or above 255, then they are set to zero and 255 respectively.                                              *}
{******************************************************************************}
		var
			ii, jj,                                                 {horizontal and vertical loop indices}
			hloc, vloc,                                          {position of current pixel in calculated/interpolated background}
			edgeslope,                                          {difference of last two consecutive pixel values on an edge}
			pvalue,                                               {current extrapolated pixel value}
			lastvalue, nextvalue: integer;           {calculated pixel values from which we are extrapolating}
			p,                                                        {pointer to current extrapolated pixel value}
			bglastptr, bgnextptr: ptr;                 {pointers to calculated pixel values from which we are extrapolating}
			width: LongInt;
	begin
		with BoundRect do begin
				width := right - left;
				for hloc := shrinkfactor to shrinkfactor * (rightroll - leftroll) - 1 do begin     {extrapolate on top and bottom}
						bglastptr := ptr(backgroundaddr + shrinkfactor * width + hloc);
						bgnextptr := ptr(backgroundaddr + (shrinkfactor + 1) * width + hloc);
						lastvalue := BAND(bglastptr^, 255);
						nextvalue := BAND(bgnextptr^, 255);
						edgeslope := nextvalue - lastvalue;
						p := bglastptr;
						pvalue := lastvalue;
						for jj := 1 to shrinkfactor do begin
								p := ptr(ord4(p) - right + left);
								pvalue := pvalue - edgeslope;
								if (pvalue < 0) then
									p^ := 0
								else if (pvalue > 255) then
									p^ := 255
								else
									p^ := pvalue;
							end;     {for jj}
						bglastptr := ptr(backgroundaddr + (shrinkfactor * (bottomroll - toproll - 1) - 1) * width + hloc);
						bgnextptr := ptr(backgroundaddr + shrinkfactor * (bottomroll - toproll - 1) * width + hloc);
						lastvalue := BAND(bglastptr^, 255);
						nextvalue := BAND(bgnextptr^, 255);
						edgeslope := nextvalue - lastvalue;
						p := bgnextptr;
						pvalue := nextvalue;
						for jj := 1 to (bottom - top - 1) - shrinkfactor * (bottomroll - toproll - 1) do begin
								p := ptr(ord4(p) + right - left);
								pvalue := pvalue + edgeslope;
								if (pvalue < 0) then
									p^ := 0
								else if (pvalue > 255) then
									p^ := 255
								else
									p^ := pvalue;
							end;     {for jj}
					end;     {for hloc}
				for vloc := top to bottom - 1 do begin     {extrapolate on left and right}
						bglastptr := ptr(backgroundaddr + (vloc - top) * width + shrinkfactor);
						bgnextptr := ptr(ord4(bglastptr) + 1);
						lastvalue := BAND(bglastptr^, 255);
						nextvalue := BAND(bgnextptr^, 255);
						edgeslope := nextvalue - lastvalue;
						p := bglastptr;
						pvalue := lastvalue;
						for ii := 1 to shrinkfactor do begin
								p := ptr(ord4(p) - 1);
								pvalue := pvalue - edgeslope;
								if (pvalue < 0) then
									p^ := 0
								else if (pvalue > 255) then
									p^ := 255
								else
									p^ := pvalue;
							end;     {for ii}
						bgnextptr := ptr(backgroundaddr + (vloc - top) * width + shrinkfactor * (rightroll - leftroll - 1) - 1);
						bglastptr := ptr(ord4(bgnextptr) - 1);
						lastvalue := BAND(bglastptr^, 255);
						nextvalue := BAND(bgnextptr^, 255);
						edgeslope := nextvalue - lastvalue;
						p := bgnextptr;
						pvalue := nextvalue;
						for ii := 1 to (right - left - 1) - shrinkfactor * (rightroll - leftroll - 1) + 1 do begin
								p := ptr(ord4(p) + 1);
								pvalue := pvalue + edgeslope;
								if (pvalue < 0) then
									p^ := 0
								else if (pvalue > 255) then
									p^ := 255
								else
									p^ := pvalue;
							end;     {for ii}
					end;     {for vloc}
			end;   {with BoundRect}
	end;


	procedure SubtractBackground2D;
{*****************************************************************************}
{*     This procedure subtracts each pixel from the calculated/interpolated/extrapolated background from the  *}
{*  corresponding pixel value in the original image.  The resulting image is stored in place of the original        *}
{*  image.  Any pixel subtractions with results below zero are given the value zero.                                           *}
{*****************************************************************************}
		var
			hloc, vloc,                                          {current pixel location in image and background}
			pvalue: integer;                                 {difference at current pixel location}
			offset,                                                 {offset in memory from beginning of original image to current scan line}
			backgrpt: LongInt;                              {offset to current point in background}
			p: ptr;                                                {temporary pointer to image or background points}
			Databand: Linetype;                           {current scan line in image}
			ControlKey: boolean;
	begin
		backgrpt := 0;
		ControlKey := ControlKeyDown;
		with Info^, BoundRect do begin
				for vloc := top to bottom - 1 do begin
						GetLine(0, vloc, pixelsperline, Databand);
						for hloc := left to right - 1 do begin
								p := ptr(backgroundaddr + backgrpt);
								pvalue := Databand[hloc] - BAND(p^, 255);
								if ControlKey then
									pvalue := BAND(p^, 255);
								if pvalue < 0 then
									Databand[hloc] := 0
								else
									Databand[hloc] := pvalue;
								backgrpt := backgrpt + 1;
							end;     {for}
						offset := vloc * BytesPerRow;
						p := ptr(ord4(PicBaseAddr) + offset);
						BlockMove(@Databand, p, pixelsperline);
					end;  {for}
			end;     {with}
	end;


	procedure Background2D;
{******************************************************************************}
{*     This procedure implements a rolling-ball algorithm for the removal of smooth continuous background       *}
{*  from a two-dimensional gel image.  It rolls the ball (actually a square patch on the top of a sphere) on a       *}
{*  low-resolution (by a factor of 'shrinkfactor' times) copy of the original image in order to increase speed     *}
{*  with little loss in accuracy.  It uses interpolation and extrapolation to blow the shrunk image to full size.     *}
{******************************************************************************}
		var
			tport: Grafptr;
			i,                                     {loop index for shrunk image memory}
			backgroundsize,              {size of the background memory}
			smallimagesize: LongInt;     {size of the shrunk image memory}
			p: ptr;                             {pointer to current pixel in shrunk image memory}
			table: FateTable;             {not used}
			width: LongInt;
	begin
		ShowWatch;
		UpdateMeter(0, 'Building Rolling Ball...');
		GetPort(tPort);
		with Info^ do begin
				SetPort(GrafPtr(osPort));
				BoundRect := roiRect;
			end;
		GetRollingBall;                                                                  {precompute the rolling ball}
		UpdateMeter(3, 'Building Rolling Ball...');
		balladdr := ord4(ballptr);
		with BoundRect do begin
				width := right - left;
				leftroll := left div shrinkfactor;                                  {left and right edges of shrunken image or roi}
				rightroll := right div shrinkfactor - 1;                      {on which to roll ball}
				toproll := top div shrinkfactor;
				bottomroll := bottom div shrinkfactor - 1;
				backgroundsize := width * (bottom - top);
				backgroundptr := NewPtrClear(backgroundsize);
				Aborting := backgroundptr = nil;
				backgroundaddr := ord4(backgroundptr);
				smallimagesize := ord4(rightroll - leftroll + 1);
				smallimagesize := smallimagesize * (bottomroll - toproll + 1);
				smallimageptr := NewPtrClear(smallimagesize);
				Aborting := Aborting or (smallimageptr = nil);
				smallimageaddr := ord4(smallimageptr);
				if not aborting then begin
						UpdateMeter(6, 'Smoothing Image ');
						filter(unweightedAvg, 1, table);                                {smooth image before shrinking}
						UpdateMeter(10, concat('Shrinking Image ', long2str(shrinkfactor), ' times...'));
						for i := 0 to smallimagesize - 1 do begin                {create a lower resolution image for ball-rolling}
								p := ptr(smallimageaddr + i);
								xmaskmin := left + shrinkfactor * (i mod (rightroll - leftroll + 1));
								ymaskmin := top + shrinkfactor * (i div (rightroll - leftroll + 1));
								p^ := MinIn2DMask;                            {each point in small image is minimum of its neighborhood}
							end;
						if not aborting then begin
								Undo;        {restore original unsmoothed image}
								RollBall;
							end;
					end
				else
					beep;
				if not Aborting then begin
						UpdateMeter(90, 'Interpolating Background...');
						InterpolateBackground2D;                              {interpolate to find background interior}
						UpdateMeter(95, 'Extrapolating Background...');
						ExtrapolateBackground2D;                             {extrapolate on top and bottom}
						UpdateMeter(98, 'Subtracting Background...');
						SubtractBackground2D;                                  {subtract background from original image}
						UpdateMeter(100, 'Subtracting Background...');
					end;
			end;   {with boundrect}
		DisposePtr(backgroundptr);                           {free up background, rolling ball, shrunk image memory}
		DisposePtr(ballptr);
		DisposePtr(smallimageptr);
		DisposeWindow(MeterWindow);
		MeterWindow := nil;
		SetPort(tPort);
	end;


	procedure RollArc (left, rightminusone, diam: integer; var background, ballpoints: IntRow; var Dataline: Linetype);
		var
			xpt, xpt2, xval, ydif, ymin, yctr, bpt, yadd: integer;
	begin
		for xpt := left to rightminusone do begin
				background[xpt] := -255;         {init background curve to minimum values}
			end;
		yctr := 0;                                   {start y-center at the x axis}
		for xpt := left to (rightminusone + diam - 1) do {while semicircle is tangent to edges or within curve...}
			begin                                       {xpt is far right edge of semi-circle}
{do we have to move the circle?...}
				ymin := 256;                          {highest could ever be 255}
				bpt := 0;
				xpt2 := xpt - diam;                {xpt2 is far left edge of semi-circle}
				while xpt2 <= xpt do            {check every point on semicircle}
					begin
						if ((xpt2 >= left) and (xpt2 <= rightminusone)) then begin  {only examine points on curve}
								ydif := dataline[xpt2] - (yctr + ballpoints[bpt]);                {curve minus circle points}
								if (ydif < ymin) then begin {keep most negative, since ball should always be below curve}
										ymin := ydif;
									end;
							end;  {if xpt2 }
						bpt := bpt + 1;
						xpt2 := xpt2 + 1;
					end;  {while xpt2 }
				if (ymin <> 256) then{if we found a new minimum...}
					yctr := yctr + ymin;   {move circle up or down}
{now compare every point on semi with background,  and keep highest number}
				xval := xpt - diam;
				xpt2 := 0;
				while xpt2 <= diam do begin  {for all the points in the semicircle}
						if ((xval >= left) and (xval <= rightminusone)) then begin
								yadd := yctr + ballpoints[xpt2];
								if (yadd > background[xval]) then  {keep largest adjustment}
									background[xval] := yadd;
							end;
						xval := xval + 1;
						xpt2 := xpt2 + 1;
					end;  {while xpt2}
			end;  {for xpt }
	end;


	function MinIn1DMask (var Databand: LineType; xcenter: integer): integer;
{*******************************************************************************}
{*     MinIn1DMask finds the minimum pixel value in a (2*shrinkfactor-1) mask about the point xcenter in the *}
{*  current line.  This code must run FAST because it gets called OFTEN!                                                                   *}
{*******************************************************************************}
		var
			i,                                                                              {index to pixels in the mask}
			temp: integer;                                                          {temporary minimum value}
	begin
		temp := Databand[xcenter - shrinkfactor + 1];
		for i := xcenter - shrinkfactor + 2 to xcenter + shrinkfactor - 1 do
			if (Databand[i] < temp) then
				temp := Databand[i];
		MinIn1DMask := temp;
	end;


{******************************************************************************}
{*     This procedure computes the location of each point on the rolling arc relative to the center of the circle     *}
{*  containing it.  The arc is located in the top half of this circle.  The vertical axis of the circle passes through  *}
{*  the midpoint of the arc.  The projection of the arc on the x-axis below is a line segment.                                 *}
{******************************************************************************}
	procedure GetRollingArc (var arcpoints: IntRow; var arcwidth: integer);
		var
			xpt,                                                                                 {x-point along arc}
			xval,                                                                               {x-point in arc array}
			rsquare,                                                                         {shrunken arc radius squared}
			xtrim,                                                                            {points to be trimmed off each end of arc}
			smallballradius,                                                            {radius of shrunken arc which actually rolls}
			diam: integer;                                                                 {diameter of shrunken arc's circle}
	begin
		smallballradius := ballradius div shrinkfactor;            { operate on small-sized image with small-sized ball}
		rsquare := smallballradius * smallballradius;
		for xpt := -smallballradius to smallballradius do        { find the ballpoints for arc based at  (x,y)=(0,0) }
			begin
				xval := xpt + smallballradius;                                     {offset, can't have negative index}
				arcpoints[xval] := round(sqrt(abs(rsquare - (xpt * xpt))));  {Ys are positive, top half of circle}
			end;
		diam := smallballradius * 2;
		xtrim := (ArcTrimPer * diam) div 100;                       {how many points to trim off each end}
		arcwidth := diam - xtrim - xtrim;
		for xpt := -smallballradius to smallballradius - xtrim - xtrim do begin
				xval := xpt + smallballradius;
				arcpoints[xval] := arcpoints[xval + xtrim];
			end;
		for xpt := smallballradius - xtrim - xtrim + 1 to smallballradius do begin
				xval := xpt + smallballradius;
				arcpoints[xval] := 0;
			end;
	end;


	procedure ExtrapolateBackground1D (var Backline, Dataline: LineType; background: IntRow; leftroll, rightroll: integer);
{******************************************************************************}
{*     This procedure uses linear extrapolation to find pixel values on the left and right edges of the current        *}
{*  line of the background.  It finds the edge points by extrapolating from the edges of the calculated and               *}
{*  interpolated background interior.  If extrapolation yields values below zero or above 255, then they are set *}
{*  to zero and 255 respectively.                                                                                                                               *}
{******************************************************************************}
		var
			i,                                                                             {index to edges of background array}
			hloc,                                                                       {}
			edgeslope: integer;                                                 {}
	begin
		with BoundRect do begin
				edgeslope := (background[leftroll + 1] - background[leftroll + 2]) div shrinkfactor;
				for i := left to shrinkfactor * (leftroll + 1) - 1 do begin     {extrapolate on left edge}
						hloc := shrinkfactor * (leftroll + 1) - 1 + left - i;
						if (Backline[hloc + 1] + edgeslope < 0) then
							Backline[hloc] := 0
						else if (Backline[hloc + 1] + edgeslope > Dataline[hloc]) then
							Backline[hloc] := Dataline[hloc]
						else
							Backline[hloc] := Backline[hloc + 1] + edgeslope;
					end;
				edgeslope := (background[rightroll] - background[rightroll - 1]) div shrinkfactor;
				for hloc := shrinkfactor * rightroll to right - 1 do begin     {extrapolate on right edge}
						if (Backline[hloc - 1] + edgeslope < 0) then
							Backline[hloc] := 0
						else if (Backline[hloc - 1] + edgeslope > Dataline[hloc]) then
							Backline[hloc] := Dataline[hloc]
						else
							Backline[hloc] := Backline[hloc - 1] + edgeslope;
					end;
			end;     {with}
	end;

	procedure Background1D;
		var
			tport: Grafptr;
			hloc, arcwidth, leftroll, rightroll, numpixels: integer;
			left, right, top, bottom: integer;                      {image bounds; ROTATED if RollingVerticalArc}
			i, j, maskwidth: integer;
			background, arcpoints: IntRow;
			vloc, offset: LongInt;
			p: ptr;
			Dataline, Backline, Smalldataline: Linetype;
			str: str255;
	begin
		ShowWatch;
		UpdateMeter(0, concat('Shrinking Image ', long2str(shrinkfactor), ' times...'));
		GetPort(tPort);
		with Info^ do begin
				SetPort(GrafPtr(osPort));
				BoundRect := roiRect;
			end;
		GetRollingArc(arcpoints, arcwidth);
		maskwidth := shrinkfactor + shrinkfactor - 1;
		case BackSubKind of
			RollingHorizontalArc:  begin
					left := BoundRect.left;
					top := BoundRect.top;
					right := BoundRect.right;
					bottom := BoundRect.bottom;
					numpixels := Info^.pixelsperline;
					str := 'Rolling Disk Horizontally...';
				end;
			RollingVerticalArc:  begin
					left := BoundRect.top;
					top := BoundRect.left;
					right := BoundRect.bottom;
					bottom := BoundRect.right;
					numpixels := Info^.nlines;
					str := 'Rolling Disk Vertically...';
				end;
		end;     {case}
		leftroll := left div shrinkfactor;                                  {left and right edges of shrunken image or roi}
		rightroll := right div shrinkfactor - 1;                      {on which to roll arc}
		for vloc := top to bottom - 1 do begin  {for ROI}
				case BackSubKind of
					RollingHorizontalArc: 
						GetLine(0, vloc, numpixels, Dataline);
					RollingVerticalArc: 
						GetColumn(vloc, 0, numpixels, Dataline);
				end;     {case}
				for i := leftroll + 1 to rightroll do
					smalldataline[i] := MinIn1DMask(Dataline, shrinkfactor * i - 1);
				RollArc(leftroll + 1, rightroll, arcwidth, background, arcpoints, smalldataline);  {roll arc on one line}
				for i := leftroll + 1 to rightroll do begin           {interpolate to find interior background points}
						hloc := shrinkfactor * i - 1;
						Backline[hloc] := background[i];
						for j := 1 to shrinkfactor - 1 do
							Backline[hloc - j] := background[i - 1] + (shrinkfactor - j) * (background[i] - background[i - 1]) div shrinkfactor;
					end;
				ExtrapolateBackground1D(Backline, Dataline, background, leftroll, rightroll);
				for i := left to right - 1 do begin                                {subtract background from current scan line}
						Dataline[i] := Dataline[i] - Backline[i];
						if Dataline[i] < 0 then
							Dataline[i] := 0;
					end;
				case BackSubKind of
					RollingHorizontalArc: 
						with Info^ do begin
								offset := vloc * BytesPerRow;
								p := ptr(ord4(PicBaseAddr) + offset);
								BlockMove(@Dataline, p, numpixels);            {fast whole line write}
							end;
					RollingVerticalArc: 
						PutColumn(vloc, 0, numpixels, Dataline);         {slow whole column write}
				end;     {case}
				if ((vloc mod 8) = 0) and (vloc > 16) then begin
						UpdateMeter(((vloc - top) * 100) div (bottom - top - 1), str);
						if CommandPeriod then begin
								beep;
								Aborting := true;
								leave;
							end;
					end;
			end;
		UpdateMeter(100, str);
		DisposeWindow(MeterWindow);
		MeterWindow := nil;
		SetPort(tPort);
	end;

	procedure SetUpGel;
		var
			frame: rect;
			AutoSelectAll: boolean;
			p: ptr;
	begin
		if NotinBounds or NotRectangular then
			exit(SetUpGel);
		StopDigitizing;
		AutoSelectAll := not Info^.RoiShowing;
		if AutoSelectAll then
			SelectAll(false);
		SetupUndoFromClip;
		with info^ do begin
				frame := roiRect;
				if ((LutMode = GrayScale) or (LutMode = CustomGrayscale)) and (not IdentityFunction) then
					ApplyLookupTable;
				changes := true;
			end;
		case BackSubKind of
			RollingHorizontalArc, RollingVerticalArc: 
				Background1D;    {--------------> call background subtract <-------------------}
			RollingBall: 
				Background2D;
			RollingBothArcs:  begin
					BackSubKind := RollingHorizontalArc;           {remove horizontal streaks}
					Background1D;
					BackSubKind := RollingVerticalArc;               {remove vertical streaks}
					if not aborting then
						Background1D;
					BackSubKind := RollingBothArcs;                   {leave BackSubKind as we found it}
				end;
		end;     {case}
		UpdatePicWindow;
		SetUpRoiRect;
		WhatToUndo := UndoFilter;
		Info^.changes := true;
		ShowWatch;
		if AutoSelectAll then
			KillRoi;
		if Aborting then begin
				Undo;
				WhatToUndo := NothingToUndo;
				UpdatePicWindow;
			end;
	end;


	procedure GetBallRadius;
		var
			SaveRadius: integer;
			canceled: boolean;
	begin
		SaveRadius := BallRadius;
		BallRadius := GetInt('Rolling BallRadius:', BallRadius, canceled);
		if (BallRadius < 1) or (BallRadius > 319) or canceled then begin
				BallRadius := SaveRadius;
				if not canceled then
					beep;
			end;
	end;


	procedure DoBackgroundMenuEvent (MenuItem: integer);
		var
			map_array: Ptr;
	begin
		if MenuItem <= RemoveStreaksItem then
			if not CheckCalibration
				then exit(DoBackgroundMenuEvent);
		MeterWindow := nil;
		Aborting := false;
		case MenuItem of
			HorizontalItem, VerticalItem:  begin
					if FasterBackgroundSubtraction then begin
							ArcTrimPer := 20;
							shrinkfactor := 4;
						end
					else begin
							ArcTrimPer := 10;
							shrinkfactor := 2;
						end;
					if MenuItem = HorizontalItem then
						BackSubKind := RollingHorizontalArc
					else
						BackSubKind := RollingVerticalArc;
					SetUpGel;
				end;
			Sub2DItem:  begin
					if FasterBackgroundSubtraction then begin
							if BallRadius > 15 then begin
									ArcTrimPer := 20;     {trim 40% in x and y}
									shrinkfactor := 8;
								end
							else begin
									ArcTrimPer := 16;  {trim 32% in x and y}
									shrinkfactor := 4;
								end
						end
					else begin  {faster not checked}
							if BallRadius > 15 then begin
									ArcTrimPer := 16;  {trim 32% in x and y}
									shrinkfactor := 4;
								end
							else begin
									ArcTrimPer := 12;   {trim 24% in x and y}
									ShrinkFactor := 2;
								end
						end;
					BackSubKind := RollingBall;
					SetUpGel;
				end;
			RemoveStreaksItem:  begin
					ArcTrimPer := 20;
					shrinkfactor := 4;
					BackSubKind := RollingBothArcs;
					SetUpGel;
				end;
			FasterItem: 
				FasterBackgroundSubtraction := not FasterBackgroundSubtraction;
			RadiusItem: 
				GetBallRadius;
		end; {case}
	end;


end.
