|
BackgroundSubtracter |
|
package ij.plugin.filter;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.measure.*;
import java.awt.*;
/** Implements ImageJ's Subtract Background command. Based on
the NIH Image Pascal version by Michael Castle and Janice
Keller of the University of Michigan Mental Health Research
Institute. Rolling ball algorithm inspired by Stanley
Sternberg's article, "Biomedical Image Processing",
IEEE Computer, January 1983.
*/
public class BackgroundSubtracter implements PlugInFilter {
private static int radius = 50; // default rolling ball radius
private static boolean lightBackground = Prefs.get("bs.background", true);
private ImagePlus imp;
private boolean canceled;
private int slice;
private boolean invert;
public int setup(String arg, ImagePlus imp) {
IJ.register(BackgroundSubtracter.class);
this.imp = imp;
if (imp!=null) {
showDialog();
if (canceled)
return DONE;
}
IJ.register(BackgroundSubtracter.class);
return IJ.setupDialog(imp, DOES_8G+DOES_16+DOES_RGB);
}
public void run(ImageProcessor ip) {
if (canceled)
return;
slice++;
if (slice>1)
IJ.showStatus("Subtract Background: "+slice+"/"+imp.getStackSize());
if (ip instanceof ColorProcessor)
subtractRGBBackround((ColorProcessor)ip, radius);
else
subtractBackround(ip, radius);
if (slice==1 && ip instanceof ShortProcessor)
imp.getProcessor().resetMinAndMax();
}
public void showDialog() {
String options = Macro.getOptions();
if (options!=null)
Macro.setOptions(options.replaceAll("white", "light"));
GenericDialog gd = new GenericDialog("Subtract Background");
gd.addNumericField("Rolling Ball Radius:", radius, 0);
gd.addCheckbox("Light Background", lightBackground);
gd.showDialog();
if (gd.wasCanceled())
canceled = true;
else {
radius = (int)gd.getNextNumber();
lightBackground = gd.getNextBoolean();
Prefs.set("bs.background", lightBackground);
}
boolean invertedLut = imp.isInvertedLut();
invert = (invertedLut && !lightBackground) || (!invertedLut && lightBackground);
}
public void subtractRGBBackround(ColorProcessor ip, int ballRadius) {
int width = ip.getWidth();
int height = ip.getHeight();
byte[] H = new byte[width*height];
byte[] S = new byte[width*height];
byte[] B = new byte[width*height];
ip.getHSB(H, S, B);
ByteProcessor brightness = new ByteProcessor(width, height, B, null);
subtractBackround(brightness, radius);
ip.setHSB(H, S, (byte[])brightness.getPixels());
}
/** 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.
*/
public void subtractBackround(ImageProcessor ip, int ballRadius) {
if (imp!=null)
imp.killRoi();
else
ip.resetRoi();
ip.setProgressBar(null);
IJ.showProgress(0.0);
if (invert)
ip.invert();
RollingBall ball = new RollingBall(ballRadius);
//new ImagePlus("ball", new ByteProcessor(ball.patchwidth+1, ball.patchwidth+1, ball.data, null)).show();
//ImageProcessor smallImage = ip.resize(ip.getWidth()/ball.shrinkfactor, ip.getHeight()/ball.shrinkfactor);
ImageProcessor smallImage = shrinkImage(ip, ball.shrinkfactor);
//new ImagePlus("small image", smallImage).show();
if (slice==1)
IJ.showStatus("Rolling ball ("+ball.shrinkfactor+")...");
ImageProcessor background;
if (ip instanceof ShortProcessor) {
background = rollBall16(ball, ip, smallImage);
interpolateBackground16(background, ball);
extrapolateBackground16(background, ball);
} else {
background = rollBall(ball, ip, smallImage);
interpolateBackground(background, ball);
extrapolateBackground(background, ball);
}
IJ.showProgress(0.9);
if (IJ.altKeyDown())
new ImagePlus("background", background).show();
ip.copyBits(background, 0, 0, Blitter.SUBTRACT);
if (invert)
ip.invert();
IJ.showProgress(1.0);
}
/** '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 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.
*/
ImageProcessor rollBall(RollingBall ball, ImageProcessor image, ImageProcessor smallImage) {
int halfpatchwidth; //distance in x or y from patch center to any edge
int ptsbelowlastpatch; //number of points we may ignore because they were below last patch
int xpt2, ypt2; // current (x,y) point in the patch relative to upper left corner
int xval, yval; // location in ball in shrunken image coordinates
int zdif; // difference in z (height) between point on ball and point on image
int zmin; // smallest zdif for ball patch with center at current point
int zctr; // current height of the center of the sphere of which the patch is a part
int zadd; // height of a point on patch relative to the xy-plane of the shrunken image
int ballpt; // index to array storing the precomputed ball patch
int imgpt; // index to array storing the shrunken image
int backgrpt; // index to array storing the calculated background
int ybackgrpt; // displacement to current background scan line
int p1, p2; // temporary indexes to background, ball, or small image
int ybackgrinc; // distance in memory between two shrunken y-points in background
int smallimagewidth; // length of a scan line in shrunken image
int left, right, top, bottom;
byte[] pixels = (byte[])smallImage.getPixels();
byte[] patch = ball.data;
int width = image.getWidth();
int height = image.getHeight();
int swidth = smallImage.getWidth();
int sheight = smallImage.getHeight();
ImageProcessor background = new ByteProcessor(width, height);
byte[] backgroundpixels = (byte[])background.getPixels();
int shrinkfactor = ball.shrinkfactor;
int leftroll = 0;
int rightroll = width/shrinkfactor-1;
int toproll = 0;
int bottomroll = height/shrinkfactor-1;
left = 1;
right = rightroll - leftroll - 1;
top = 1;
bottom = bottomroll - toproll - 1;
smallimagewidth = swidth;
int patchwidth = ball.patchwidth;
halfpatchwidth = patchwidth / 2;
ybackgrinc = shrinkfactor*width; // real dist btwn 2 adjacent (dy=1) shrunk pts
zctr = 0; // start z-center in the xy-plane
for (int ypt=top; ypt<=(bottom+patchwidth); ypt++) {
for (int xpt=left; xpt<=(right+patchwidth); xpt++) {// while patch is tangent to edges or within image...
// 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 = 0;
ypt2 = ypt - patchwidth; // ypt2 is top edge of ball patch
imgpt = ypt2*smallimagewidth + xpt - patchwidth;
while (ypt2<=ypt) {
xpt2 = xpt-patchwidth; // xpt2 is far left edge of ball patch
while (xpt2<=xpt) { // check every point on ball patch
// only examine points on
if ((xpt2>=left) && (xpt2<=right) && (ypt2>=top) && (ypt2<=bottom)) {
p1 = ballpt;
p2 = imgpt;
zdif = (pixels[p2]&255) - (zctr + (patch[p1]&255)); //curve - circle points
//if (xpt==50 && ypt==50) IJ.write(zdif
//+" "+(pixels[p2]&255)
//+" "+zctr
//+" "+(patch[p1]&255)
//+" "+p2
//+" "+p1
//);
if (zdif<zmin) // keep most negative, since ball should always be below curve
zmin = zdif;
} // if xpt2,ypt2
ballpt++;
xpt2++;
imgpt++;
} // while xpt2
ypt2++;
imgpt = imgpt - patchwidth - 1 + smallimagewidth;
} // while ypt2
if (zmin!=0)
zctr += zmin; // move ball up or down if we find a new minimum
if (zmin<0)
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 = 0;
ybackgrpt = (yval - top + 1) * ybackgrinc;
while (ypt2<=patchwidth) {
xval = xpt - patchwidth + ptsbelowlastpatch;
xpt2 = ptsbelowlastpatch;
ballpt += ptsbelowlastpatch;
backgrpt = ybackgrpt + (xval - left + 1) * shrinkfactor;
while (xpt2<=patchwidth) { // for all the points in the ball patch
if ((xval >= left) && (xval <= right) && (yval >= top) && (yval <= bottom)) {
p1 = ballpt;
zadd = zctr + (patch[p1]&255);
p1 = backgrpt;
//if (backgrpt>=backgroundpixels.length) backgrpt = 0; //(debug)
if (zadd>(backgroundpixels[p1]&255)) //keep largest adjustment}
backgroundpixels[p1] = (byte)zadd;
}
ballpt++;
xval++;
xpt2++;
backgrpt += shrinkfactor; // move to next point in x
} // while xpt2
yval++;
ypt2++;
ybackgrpt += ybackgrinc; // move to next point in y
} // while ypt2
} // for xpt
if (ypt%20==0)
IJ.showProgress(0.2+0.6*ypt/(bottom+patchwidth));
} // for ypt
return background;
}
/** Creates a lower resolution image for ball-rolling. */
ImageProcessor shrinkImage(ImageProcessor ip, int shrinkfactor) {
int width = ip.getWidth();
int height = ip.getHeight();
int swidth = width/shrinkfactor;
int sheight = height/shrinkfactor;
ImageProcessor ip2 = ip.duplicate();
ip2.smooth();
IJ.showProgress(0.1);
ImageProcessor smallImage = ip.createProcessor(swidth, sheight);
int xmaskmin, ymaskmin, min, thispixel;
for (int y=0; y<sheight; y++) {
for (int x=0; x<swidth; x++) {
xmaskmin = shrinkfactor*x;
ymaskmin = shrinkfactor*y;
min = 65535;
for (int j=0; j<shrinkfactor; j++) {
for (int k=0; k<shrinkfactor; k++) {
thispixel = ip2.getPixel(xmaskmin+j, ymaskmin+k);
if (thispixel<min)
min = thispixel;
}
}
smallImage.putPixel(x,y,min); // each point in small image is minimum of its neighborhood
}
}
//new ImagePlus("smallImage", smallImage).show();
return smallImage;
}
/** 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
*/
void interpolateBackground(ImageProcessor background, RollingBall ball) {
int hloc, vloc; // position of current pixel in calculated background
int vinc; // memory offset from current calculated pos to current interpolated pos
int lastvalue, nextvalue; // calculated pixel values between which we are interpolating
int p; // pointer to current interpolated pixel value
int bglastptr, bgnextptr; // pointers to calculated pixel values between which we are interpolating
int width = background.getWidth();
int height = background.getHeight();
int shrinkfactor = ball.shrinkfactor;
int leftroll = 0;
int rightroll = width/shrinkfactor-1;
int toproll = 0;
int bottomroll = height/shrinkfactor-1;
byte[] pixels = (byte[])background.getPixels();
vloc = 0;
for (int j=1; j<=(bottomroll-toproll-1); j++) { //interpolate to find background interior
hloc = 0;
vloc += shrinkfactor;
for (int i=1; i<=(rightroll-leftroll); i++) {
hloc += shrinkfactor;
bgnextptr = vloc*width + hloc;
bglastptr = bgnextptr - shrinkfactor;
nextvalue = pixels[bgnextptr]&255;
lastvalue = pixels[bglastptr]&255;
for (int ii=1; ii<=(shrinkfactor-1); ii++) { //interpolate horizontally
p = bgnextptr - ii;
pixels[p] = (byte)(lastvalue+(shrinkfactor-ii)*(nextvalue-lastvalue)/shrinkfactor);
}
for (int ii=0; ii<=(shrinkfactor-1); ii++) { //interpolate vertically
bglastptr = (vloc-shrinkfactor)*width+hloc-ii;
bgnextptr = vloc*width+hloc-ii;
lastvalue = pixels[bglastptr]&255;
nextvalue = pixels[bgnextptr]&255;
vinc = 0;
for (int jj=1; jj<=(shrinkfactor-1); jj++) {
vinc = vinc-width;
p = bgnextptr+vinc;
pixels[p] = (byte)(lastvalue+(shrinkfactor-jj)*(nextvalue-lastvalue)/shrinkfactor);
} // for jj
} // for ii
} // for i
} // for j
}
/** 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.
*/
void extrapolateBackground(ImageProcessor background, RollingBall ball) {
int edgeslope; // difference of last two consecutive pixel values on an edge
int pvalue; // current extrapolated pixel value
int lastvalue, nextvalue; //calculated pixel values from which we are extrapolating
int p; // pointer to current extrapolated pixel value
int bglastptr, bgnextptr; // pointers to calculated pixel values from which we are extrapolating
int width = background.getWidth();
int height = background.getHeight();
int shrinkfactor = ball.shrinkfactor;
int leftroll = 0;
int rightroll = width/shrinkfactor-1;
int toproll = 0;
int bottomroll = height/shrinkfactor-1;
byte[] pixels = (byte[])background.getPixels();
for (int hloc=shrinkfactor; hloc<=(shrinkfactor*(rightroll-leftroll)-1); hloc++) {
// extrapolate on top and bottom
bglastptr = shrinkfactor*width+hloc;
bgnextptr = (shrinkfactor+1)*width+hloc;
lastvalue = pixels[bglastptr]&255;
nextvalue = pixels[bgnextptr]&255;
edgeslope = nextvalue-lastvalue;
p = bglastptr;
pvalue = lastvalue;
for (int jj=1; jj<=shrinkfactor; jj++) {
p = p-width;
pvalue = pvalue-edgeslope;
if (pvalue<0)
pixels[p] = 0;
else if (pvalue>255)
pixels[p] = (byte)255;
else
pixels[p] = (byte)pvalue;
} // for jj
bglastptr = (shrinkfactor*(bottomroll-toproll-1)-1)*width+hloc;
bgnextptr = shrinkfactor*(bottomroll-toproll-1)*width+hloc;
lastvalue = pixels[bglastptr]&255;
nextvalue = pixels[bgnextptr]&255;
edgeslope = nextvalue-lastvalue;
p = bgnextptr;
pvalue = nextvalue;
for (int jj=1; jj<=((height-1)-shrinkfactor*(bottomroll-toproll-1)); jj++) {
p += width;
pvalue += edgeslope;
if (pvalue<0)
pixels[p] = 0;
else if (pvalue>255)
pixels[p] = (byte)255;
else
pixels[p] = (byte)pvalue;
} // for jj
} // for hloc
for (int vloc=0; vloc<height; vloc++) {
// extrapolate on left and right
bglastptr = vloc*width+shrinkfactor;
bgnextptr = bglastptr+1;
lastvalue = pixels[bglastptr]&255;
nextvalue = pixels[bgnextptr]&255;
edgeslope = nextvalue-lastvalue;
p = bglastptr;
pvalue = lastvalue;
for (int ii=1; ii<=shrinkfactor; ii++) {
p--;
pvalue = pvalue - edgeslope;
if (pvalue<0)
pixels[p] = 0;
else if (pvalue>255)
pixels[p] = (byte)255;
else
pixels[p] = (byte)pvalue;
} // for ii
bgnextptr = vloc*width+shrinkfactor*(rightroll-leftroll-1)-1;
bglastptr = bgnextptr-1;
lastvalue = pixels[bglastptr]&255;
nextvalue = pixels[bgnextptr]&255;
edgeslope = nextvalue-lastvalue;
p = bgnextptr;
pvalue = nextvalue;
for (int ii=1; ii<=((width-1)-shrinkfactor*(rightroll-leftroll-1)+1); ii++) {
p++;
pvalue = pvalue+edgeslope;
if (pvalue<0)
pixels[p] = 0;
else if (pvalue>255)
pixels[p] = (byte)255;
else
pixels[p] = (byte)pvalue;
} // for ii
} // for vloc
}
/** This is a 16-bit version of the rollBall() method. */
ImageProcessor rollBall16(RollingBall ball, ImageProcessor image, ImageProcessor smallImage) {
int halfpatchwidth; //distance in x or y from patch center to any edge
int ptsbelowlastpatch; //number of points we may ignore because they were below last patch
int xpt2, ypt2; // current (x,y) point in the patch relative to upper left corner
int xval, yval; // location in ball in shrunken image coordinates
int zdif; // difference in z (height) between point on ball and point on image
int zmin; // smallest zdif for ball patch with center at current point
int zctr; // current height of the center of the sphere of which the patch is a part
int zadd; // height of a point on patch relative to the xy-plane of the shrunken image
int ballpt; // index to array storing the precomputed ball patch
int imgpt; // index to array storing the shrunken image
int backgrpt; // index to array storing the calculated background
int ybackgrpt; // displacement to current background scan line
int p1, p2; // temporary indexes to background, ball, or small image
int ybackgrinc; // distance in memory between two shrunken y-points in background
int smallimagewidth; // length of a scan line in shrunken image
int left, right, top, bottom;
short[] pixels = (short[])smallImage.getPixels();
byte[] patch = ball.data;
int width = image.getWidth();
int height = image.getHeight();
int swidth = smallImage.getWidth();
int sheight = smallImage.getHeight();
ImageProcessor background = new ShortProcessor(width, height);
short[] backgroundpixels = (short[])background.getPixels();
int shrinkfactor = ball.shrinkfactor;
int leftroll = 0;
int rightroll = width/shrinkfactor-1;
int toproll = 0;
int bottomroll = height/shrinkfactor-1;
left = 1;
right = rightroll - leftroll - 1;
top = 1;
bottom = bottomroll - toproll - 1;
smallimagewidth = swidth;
int patchwidth = ball.patchwidth;
halfpatchwidth = patchwidth / 2;
ybackgrinc = shrinkfactor*width; // real dist btwn 2 adjacent (dy=1) shrunk pts
zctr = 0; // start z-center in the xy-plane
for (int ypt=top; ypt<=(bottom+patchwidth); ypt++) {
for (int xpt=left; xpt<=(right+patchwidth); xpt++) {// while patch is tangent to edges or within image...
// 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 = 65535; // highest possible value
ballpt = 0;
ypt2 = ypt - patchwidth; // ypt2 is top edge of ball patch
imgpt = ypt2*smallimagewidth + xpt - patchwidth;
while (ypt2<=ypt) {
xpt2 = xpt-patchwidth; // xpt2 is far left edge of ball patch
while (xpt2<=xpt) { // check every point on ball patch
// only examine points on
if ((xpt2>=left) && (xpt2<=right) && (ypt2>=top) && (ypt2<=bottom)) {
p1 = ballpt;
p2 = imgpt;
zdif = (pixels[p2]&0xffff) - (zctr + (patch[p1]&255)); //curve - circle points
if (zdif<zmin) // keep most negative, since ball should always be below curve
zmin = zdif;
} // if xpt2,ypt2
ballpt++;
xpt2++;
imgpt++;
} // while xpt2
ypt2++;
imgpt = imgpt - patchwidth - 1 + smallimagewidth;
} // while ypt2
if (zmin!=0)
zctr += zmin; // move ball up or down if we find a new minimum
if (zmin<0)
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 = 0;
ybackgrpt = (yval - top + 1) * ybackgrinc;
while (ypt2<=patchwidth) {
xval = xpt - patchwidth + ptsbelowlastpatch;
xpt2 = ptsbelowlastpatch;
ballpt += ptsbelowlastpatch;
backgrpt = ybackgrpt + (xval - left + 1) * shrinkfactor;
while (xpt2<=patchwidth) { // for all the points in the ball patch
if ((xval >= left) && (xval <= right) && (yval >= top) && (yval <= bottom)) {
p1 = ballpt;
zadd = zctr + (patch[p1]&255);
p1 = backgrpt;
//if (backgrpt>=backgroundpixels.length) backgrpt = 0; //(debug)
if (zadd>(backgroundpixels[p1]&0xffff)) //keep largest adjustment}
backgroundpixels[p1] = (short)zadd;
}
ballpt++;
xval++;
xpt2++;
backgrpt += shrinkfactor; // move to next point in x
} // while xpt2
yval++;
ypt2++;
ybackgrpt += ybackgrinc; // move to next point in y
} // while ypt2
} // for xpt
if (ypt%20==0)
IJ.showProgress(0.2+0.6*ypt/(bottom+patchwidth));
} // for ypt
return background;
}
/** This is a 16-bit version of the interpolateBackground(0 method. */
void interpolateBackground16(ImageProcessor background, RollingBall ball) {
int hloc, vloc; // position of current pixel in calculated background
int vinc; // memory offset from current calculated pos to current interpolated pos
int lastvalue, nextvalue; // calculated pixel values between which we are interpolating
int p; // pointer to current interpolated pixel value
int bglastptr, bgnextptr; // pointers to calculated pixel values between which we are interpolating
int width = background.getWidth();
int height = background.getHeight();
int shrinkfactor = ball.shrinkfactor;
int leftroll = 0;
int rightroll = width/shrinkfactor-1;
int toproll = 0;
int bottomroll = height/shrinkfactor-1;
short[] pixels = (short[])background.getPixels();
vloc = 0;
for (int j=1; j<=(bottomroll-toproll-1); j++) { //interpolate to find background interior
hloc = 0;
vloc += shrinkfactor;
for (int i=1; i<=(rightroll-leftroll); i++) {
hloc += shrinkfactor;
bgnextptr = vloc*width + hloc;
bglastptr = bgnextptr - shrinkfactor;
nextvalue = pixels[bgnextptr]&0xffff;
lastvalue = pixels[bglastptr]&0xffff;
for (int ii=1; ii<=(shrinkfactor-1); ii++) { //interpolate horizontally
p = bgnextptr - ii;
pixels[p] = (short)(lastvalue+(shrinkfactor-ii)*(nextvalue-lastvalue)/shrinkfactor);
}
for (int ii=0; ii<=(shrinkfactor-1); ii++) { //interpolate vertically
bglastptr = (vloc-shrinkfactor)*width+hloc-ii;
bgnextptr = vloc*width+hloc-ii;
lastvalue = pixels[bglastptr]&0xffff;
nextvalue = pixels[bgnextptr]&0xffff;
vinc = 0;
for (int jj=1; jj<=(shrinkfactor-1); jj++) {
vinc = vinc-width;
p = bgnextptr+vinc;
pixels[p] = (short)(lastvalue+(shrinkfactor-jj)*(nextvalue-lastvalue)/shrinkfactor);
} // for jj
} // for ii
} // for i
} // for j
}
/** This is a 16-bit version of the extrapolateBackground() method. */
void extrapolateBackground16(ImageProcessor background, RollingBall ball) {
int edgeslope; // difference of last two consecutive pixel values on an edge
int pvalue; // current extrapolated pixel value
int lastvalue, nextvalue; //calculated pixel values from which we are extrapolating
int p; // pointer to current extrapolated pixel value
int bglastptr, bgnextptr; // pointers to calculated pixel values from which we are extrapolating
int width = background.getWidth();
int height = background.getHeight();
int shrinkfactor = ball.shrinkfactor;
int leftroll = 0;
int rightroll = width/shrinkfactor-1;
int toproll = 0;
int bottomroll = height/shrinkfactor-1;
short[] pixels = (short[])background.getPixels();
for (int hloc=shrinkfactor; hloc<=(shrinkfactor*(rightroll-leftroll)-1); hloc++) {
// extrapolate on top and bottom
bglastptr = shrinkfactor*width+hloc;
bgnextptr = (shrinkfactor+1)*width+hloc;
lastvalue = pixels[bglastptr]&0xffff;
nextvalue = pixels[bgnextptr]&0xffff;
edgeslope = nextvalue-lastvalue;
p = bglastptr;
pvalue = lastvalue;
for (int jj=1; jj<=shrinkfactor; jj++) {
p = p-width;
pvalue = pvalue-edgeslope;
if (pvalue<0)
pixels[p] = 0;
else if (pvalue>65535)
pixels[p] = (short)65535;
else
pixels[p] = (short)pvalue;
} // for jj
bglastptr = (shrinkfactor*(bottomroll-toproll-1)-1)*width+hloc;
bgnextptr = shrinkfactor*(bottomroll-toproll-1)*width+hloc;
lastvalue = pixels[bglastptr]&0xffff;
nextvalue = pixels[bgnextptr]&0xffff;
edgeslope = nextvalue-lastvalue;
p = bgnextptr;
pvalue = nextvalue;
for (int jj=1; jj<=((height-1)-shrinkfactor*(bottomroll-toproll-1)); jj++) {
p += width;
pvalue += edgeslope;
if (pvalue<0)
pixels[p] = 0;
else if (pvalue>65535)
pixels[p] = (short)65535;
else
pixels[p] = (short)pvalue;
} // for jj
} // for hloc
for (int vloc=0; vloc<height; vloc++) {
// extrapolate on left and right
bglastptr = vloc*width+shrinkfactor;
bgnextptr = bglastptr+1;
lastvalue = pixels[bglastptr]&0xffff;
nextvalue = pixels[bgnextptr]&0xffff;
edgeslope = nextvalue-lastvalue;
p = bglastptr;
pvalue = lastvalue;
for (int ii=1; ii<=shrinkfactor; ii++) {
p--;
pvalue = pvalue - edgeslope;
if (pvalue<0)
pixels[p] = 0;
else if (pvalue>65535)
pixels[p] = (short)65535;
else
pixels[p] = (short)pvalue;
} // for ii
bgnextptr = vloc*width+shrinkfactor*(rightroll-leftroll-1)-1;
bglastptr = bgnextptr-1;
lastvalue = pixels[bglastptr]&0xffff;
nextvalue = pixels[bgnextptr]&0xffff;
edgeslope = nextvalue-lastvalue;
p = bgnextptr;
pvalue = nextvalue;
for (int ii=1; ii<=((width-1)-shrinkfactor*(rightroll-leftroll-1)+1); ii++) {
p++;
pvalue = pvalue+edgeslope;
if (pvalue<0)
pixels[p] = 0;
else if (pvalue>65535)
pixels[p] = (short)65535;
else
pixels[p] = (short)pvalue;
} // for ii
} // for vloc
}
}
class RollingBall {
byte[] data;
int patchwidth;
int shrinkfactor;
RollingBall(int radius) {
int arcTrimPer;
if (radius<=10) {
shrinkfactor = 1;
arcTrimPer = 12; // trim 24% in x and y
} else if (radius<=30) {
shrinkfactor = 2;
arcTrimPer = 12; // trim 24% in x and y
} else if (radius<=100) {
shrinkfactor = 4;
arcTrimPer = 16; // trim 32% in x and y
} else {
shrinkfactor = 8;
arcTrimPer = 20; // trim 40% in x and y
}
buildRollingBall(radius, arcTrimPer);
}
/** 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.
*/
void buildRollingBall(int ballradius, int arcTrimPer) {
int rsquare; // rolling ball radius squared
int xtrim; // # of pixels trimmed off each end of ball to make patch
int xval, yval; // x,y-values on patch relative to center of rolling ball
int smallballradius, diam; // radius and diameter of rolling ball
int temp; // value must be >=0 to take square root
int halfpatchwidth; // distance in x or y from center of patch to any edge
int ballsize; // size of rolling ball array
this.shrinkfactor = shrinkfactor;
smallballradius = ballradius/shrinkfactor;
if (smallballradius<1)
smallballradius = 1;
rsquare = smallballradius*smallballradius;
diam = smallballradius*2;
xtrim = (arcTrimPer*diam)/100; // only use a patch of the rolling ball
patchwidth = diam - xtrim - xtrim;
halfpatchwidth = smallballradius - xtrim;
ballsize = (patchwidth+1)*(patchwidth+1);
data = new byte[ballsize];
for (int i=0; i<ballsize; i++) {
xval = i % (patchwidth+1) - halfpatchwidth;
yval = i / (patchwidth+1) - halfpatchwidth;
temp = rsquare - (xval*xval) - (yval*yval);
if (temp >= 0)
data[i] = (byte)Math.round(Math.sqrt(temp));
else
data[i] = 0;
}
}
}
|
BackgroundSubtracter |
|