package fr.unistra.pelican.algorithms.segmentation;
import java.util.ArrayList;
import fr.unistra.pelican.Algorithm;
import fr.unistra.pelican.AlgorithmException;
import fr.unistra.pelican.BooleanImage;
import fr.unistra.pelican.Image;
import fr.unistra.pelican.InvalidNumberOfParametersException;
import fr.unistra.pelican.InvalidTypeOfParameterException;
import fr.unistra.pelican.PelicanException;
import fr.unistra.pelican.algorithms.conversion.RGBToHSV;
import fr.unistra.pelican.algorithms.io.ImageLoader;
import fr.unistra.pelican.algorithms.visualisation.Viewer2D;
import fr.unistra.pelican.util.multiscale.Pyramid;
/**
* binarize an image and return a boolean image. it is based on a multiscale
* hue-based analysis Work on all format. Lefevre et al. CGIV 2002
*
* @author lefevre
*
*/
public class MultiscaleBinarisation extends Algorithm {
public Image inputImage;
public int depth;
public double THR1;
public double THR2;
public boolean adaptive;
public int returned;
public boolean small;
public BooleanImage outputImage;
private final static boolean DEBUG = false;
/**
* Constructor
*
*/
public MultiscaleBinarisation() {
super();
super.inputs = "inputImage,depth";
super.outputs = "outputImage";
;
}
/*
* (non-Javadoc)
*
* @see fr.unistra.pelican.Algorithm#launch()
*/
public void launch() throws AlgorithmException {
outputImage = new BooleanImage(inputImage.getXDim(), inputImage
.getYDim(), inputImage.getZDim(), inputImage.getTDim(), 1);
Pyramid input = new Pyramid(inputImage, depth, true);
Pyramid labels = new Pyramid(outputImage, depth, false);
BooleanImage current, previous;
Image model;
double bg[], values[], diff;
boolean label;
// int k=(int)Math.pow(2,input.getDepth()-1);
// Compute hue model at lowest scale
model = input.getTop();
try {
model = (Image) new RGBToHSV().process(model);
} catch (PelicanException ex) {
throw new AlgorithmException(ex.getMessage());
}
bg = computeLocalHueStats(model, 0, model.getXDim() - 1, 0, model
.getYDim() - 1, null);
if (bg == null)
return;
current = (BooleanImage) labels.getTop();
current.fill(true);
// Performs the processing on a coarse-to-fine basis (top to bottom of
// the pyramid)
for (int d = input.getDepth() - 2; d >= 0; d--) {
previous = current;
current = (BooleanImage) labels.getScale(d);
model = input.getScale(d);
// Convert in HSV
try {
model = (Image) new RGBToHSV().process(model);
} catch (PelicanException ex) {
throw new AlgorithmException(ex.getMessage());
}
// Perform local matching
for (int x = 0; x < previous.getXDim(); x++)
for (int y = 0; y < previous.getYDim(); y++)
for (int z = 0; z < previous.getZDim(); z++)
for (int t = 0; t < previous.getTDim(); t++) {
// If corresponding lowest scale region is
// background, propagate it
if (previous.getPixelBoolean(x, y, z, t, 0) == false)
label = false;
// Otherwise compute hue and perform matching
else {
values = computeLocalHueStats(model, 2 * x,
2 * x + 1, 2 * y, 2 * y + 1, null);
// if hue cannot be computed, we have to check
// at finer scale
if (values == null)
label = true;
else {
// compare mean values
diff = Math.abs(values[0] - bg[0]);
if (diff > Math.PI)
diff = 2 * Math.PI - diff;
// check both diff with mean and local range
if (diff > 2 * Math.PI * THR1
|| values[1] > 2 * Math.PI * THR2)
label = true;
else
label = false;
}
}
// propagate labels
current.setPixelBoolean(2 * x, 2 * y, z, t, 0,
label);
current.setPixelBoolean(2 * x, 2 * y + 1, z, t, 0,
label);
current.setPixelBoolean(2 * x + 1, 2 * y, z, t, 0,
label);
current.setPixelBoolean(2 * x + 1, 2 * y + 1, z, t,
0, label);
}
// recompute background model if adaptive flag is set
if (adaptive) {
bg = computeLocalHueStats(model, 0, model.getXDim() - 1, 0,
model.getYDim() - 1, current);
if (bg == null)
return;
}
}
if (small)
outputImage = (BooleanImage) labels.getScale(returned);
else
outputImage = (BooleanImage) labels.extractImage(returned);
if (DEBUG)
try {
new Viewer2D().process(input.convertToImage(), "input");
new Viewer2D().process(labels.convertToImage(), "output");
} catch (PelicanException ex) {
throw new AlgorithmException(ex.getMessage());
}
}
private double[] computeLocalHueStats(Image img, int xmin, int xmax,
int ymin, int ymax, BooleanImage mask) {
if (mask != null
&& (img.getXDim() != mask.getXDim()
|| img.getYDim() != mask.getYDim()
|| img.getZDim() != mask.getZDim() || img.getTDim() != mask
.getTDim()))
return null;
double res[] = new double[2];
double mean;
double range;
double max = 0;
double min = 1;
double sin = 0;
double cos = 0;
double angle;
double valid = (xmax - xmin + 1) * (ymax - ymin + 1) * img.getZDim()
* img.getTDim();
// Analysis of each pixel of the image
for (int x = xmin; x <= xmax; x++)
for (int y = ymin; y <= ymax; y++)
for (int z = 0; z < img.getZDim(); z++)
for (int t = 0; t < img.getTDim(); t++) {
if (mask != null && mask.getPixelBoolean(x, y, z, t, 0))
valid--;
else {
// check if value or saturation are null
if (img.getPixelDouble(x, y, z, t, 2) == 0
|| img.getPixelDouble(x, y, z, t, 1) == 0)
valid--;
// if hue is valid, compute stats
else {
if (img.getPixelDouble(x, y, z, t, 0) < min)
min = img.getPixelDouble(x, y, z, t, 0);
if (img.getPixelDouble(x, y, z, t, 0) > max)
max = img.getPixelDouble(x, y, z, t, 0);
angle = 2 * Math.PI
* img.getPixelDouble(x, y, z, t, 0);
sin += Math.sin(angle);
cos += Math.cos(angle);
}
}
}
// Check validity
if (valid == 0)
return null;
// Compute the hue mean
if (cos == 0)
mean = 0;
else
mean = Math.atan(sin / cos);
if (cos < 0)
mean += Math.PI;
// Compute the hue range
double dmin, dmax;
dmax = Math.abs(2 * Math.PI * max - mean);
if (dmax > Math.PI)
dmax = 2 * Math.PI - dmax;
dmin = Math.abs(mean - 2 * Math.PI * min);
if (dmin > Math.PI)
dmin = 2 * Math.PI - dmin;
range = dmin + dmax;
if (dmin == 0 || dmax == 0)
range *= 2;
// Generates results
res[0] = mean;
res[1] = range;
return res;
}
/**
* Static fonction that binarize an image.
*
* @param image
* @param threshold
* @return image binarized
* @throws InvalidTypeOfParameterException
* @throws AlgorithmException
* @throws InvalidNumberOfParametersException
*/
public static BooleanImage process(Image image, Integer depth,
Double thrMatching, Double thrRange, Boolean adaptive,
Integer returnedScale, Boolean small)
throws InvalidTypeOfParameterException, AlgorithmException,
InvalidNumberOfParametersException {
MultiscaleBinarisation binarisation = new MultiscaleBinarisation();
ArrayList input = new ArrayList();
input.add(image);
input.add(depth);
input.add(thrMatching);
input.add(thrRange);
input.add(adaptive);
input.add(returnedScale);
input.add(small);
binarisation.setInput(input);
binarisation.launch();
return (BooleanImage) binarisation.getOutput().get(0);
}
public static void main(String args[]) throws PelicanException {
Image input = (Image) new ImageLoader().process("samples/foot.png");
new Viewer2D().process(input, "original");
BooleanImage res = MultiscaleBinarisation.process(input, 6, 1.0 / 36,
1.0 / 36, true, 3, false);
new Viewer2D().process(res, "resultat");
}
}