package fr.unistra.pelican.algorithms.spatial;
import java.awt.Point;
import java.util.LinkedList;
import fr.unistra.pelican.Algorithm;
import fr.unistra.pelican.AlgorithmException;
import fr.unistra.pelican.BooleanImage;
import fr.unistra.pelican.Image;
import fr.unistra.pelican.IntegerImage;
import fr.unistra.pelican.algorithms.conversion.GrayToPseudoColors;
import fr.unistra.pelican.algorithms.io.ImageLoader;
import fr.unistra.pelican.algorithms.visualisation.Viewer2D;
import fr.unistra.pelican.util.HierarchicalQueue;
import fr.unistra.pelican.util.Tools;
/**
* This class realize a topographic distance transform using a FIFO queue as in
* watershed segmentation
*
* @author Lefevre
*/
public class TopographicTransform extends Algorithm {
/*
* Input Image
*/
public Image inputImage;
public BooleanImage mask;
public boolean trueDistance = true;
/**
* (optionnally) flag to push background borders around the image
*/
public boolean border = false;
/**
* Flag to compute hue-based distance
*/
public boolean hue = false;
public HierarchicalQueue queue=null;
/*
* Output Image
*/
public IntegerImage outputImage;
private final int NULL = 0;
private int xdim = 0;
private int ydim = 0;
private BooleanImage mask2 = null;
/**
* Constructor
*
*/
public TopographicTransform() {
super.inputs = "inputImage,mask";
super.options = "trueDistance,border,hue,queue";
super.outputs = "outputImage";
}
/*
* (non-Javadoc)
*
* @see fr.unistra.pelican.Algorithm#launch()
*/
public void launch() throws AlgorithmException {
xdim = inputImage.getXDim();
ydim = inputImage.getYDim();
IntegerImage output = new IntegerImage(inputImage.getXDim(), inputImage
.getYDim(), 1, 1, 3);// label, distance, candidate
outputImage = new IntegerImage(inputImage.getXDim(), inputImage.getYDim(),
inputImage.getZDim(), inputImage.getTDim(), 1);
int scale = Math.max(inputImage.getXDim(), inputImage.getYDim());// 500;
if (queue==null)
queue = new HierarchicalQueue(scale * scale * 255);
else queue.reset();
mask2 = mask.copyImage(true);
if (border) {
for (int x = 0; x < xdim; x++) {
mask2.setPixelXYBoolean(x, 0, true);
mask2.setPixelXYBoolean(x, ydim - 1, true);
}
for (int y = 0; y < ydim; y++) {
mask2.setPixelXYBoolean(0, y, true);
mask2.setPixelXYBoolean(xdim - 1, y, true);
}
}
//Viewer2D.exec(mask2,"masque bord="+border);
// Put the marker pixels in the queue
for (int y = 0; y < ydim; y++)
for (int x = 0; x < xdim; x++) {
if (mask2.getPixelXYBoolean(x, y)) {
output.setPixelXYBInt(x, y, 0, 0); // label 0 le bord
output.setPixelXYBInt(x, y, 1, 0); // distance 0
if (bord(x, y))
queue.add(new Point(x, y), NULL);
else
output.setPixelXYBInt(x, y, 0, 1); // label 1
}
}
//Viewer2D.exec(output.scaleToVisibleRange(),"marqueurs bord="+border);
// Perform the flooding
int labeled = 0;
int current=0;
Point p=null;
while (!queue.isEmpty()) {
current=queue.getCurrent();
p = queue.get();
// System.out.println(p.x+" "+p.y);
// Get the label and check if it has not been labeled before
if (output.getPixelXYBInt(p.x, p.y, 0) != NULL)
continue;
if (labeled % (inputImage.size() / inputImage.getBDim() / 10) == 0)
System.out.print('.');
// Definitely set the label from the candidate
//int label = output.getPixelXYBInt(p.x, p.y, 2);
int label=1;
output.setPixelXYBInt(p.x, p.y, 0, label);
labeled++;
// get the non labelled 8-neighbours of (x,y)
Point[] neighbours = getNonLabelledNeighbours(output, p.x, p.y);
for (int i = 0; i < neighbours.length; i++) {
// get the current distance for this neighbour
int ndist = output.getPixelXYBInt(neighbours[i].x, neighbours[i].y, 1);
// compute the geodesic distance between p and its
// neighbor IN THE APPROPRIATE BAND
double val = 0;
if (!hue) {
for (int b = 0; b < inputImage.getBDim(); b++) {
double val1 = inputImage.getPixelXYBDouble(neighbours[i].x,
neighbours[i].y, b);
double val2 = inputImage.getPixelXYBDouble(p.x, p.y, b);
val += (val1 - val2)*(val1-val2);
// int val1 = inputImage.getPixelXYBByte(neighbours[i].x,
// neighbours[i].y, b);
// int val2 = inputImage.getPixelXYBByte(p.x, p.y, b);
// val += Math.abs(val1 - val2);
}
val/=inputImage.getBDim();
val=Math.sqrt(val);
val*=255;
} else {
// compute hue-base distance
val = Tools.HSLDistance(inputImage.getVectorPixelXYZDouble(
neighbours[i].x, neighbours[i].y,0), inputImage
.getVectorPixelXYZDouble(p.x, p.y, 0));
val=Math.ceil(255*val);
if (val == 0
&& Tools.HSLDistance(inputImage.getVectorPixelXYZDouble(
neighbours[i].x, neighbours[i].y, 0), inputImage
.getVectorPixelXYZDouble(p.x, p.y, 0)) != 0)
System.out.println(val);
}
if (trueDistance)
val = scale * val + 1;// val += 1; // pour la distance
// topographique de Philipp
if(mask2.getPixelXYBoolean(p.x,p.y))
val=0;
int pdist = (int)val + current;//queue.getCurrent();
// update distance and candidate if necessary
if (ndist == 0 || pdist < ndist) {
output.setPixelXYBInt(neighbours[i].x, neighbours[i].y, 1, pdist);
//output.setPixelXYBInt(neighbours[i].x, neighbours[i].y, 2, label);
// add him to the appropriate queue
queue.add(neighbours[i], pdist);
}
}
}
outputImage.setImage4D(output.getImage4D(1, Image.B), 0, Image.B);
return;
}
private boolean bord(int x, int y) {
boolean bord = false;
if (x > 0 && !mask2.getPixelXYBoolean(x - 1, y))
bord = true;
else if (x < xdim - 1 && !mask2.getPixelXYBoolean(x + 1, y))
bord = true;
else if (y > 0 && !mask2.getPixelXYBoolean(x, y - 1))
bord = true;
else if (y < ydim - 1 && !mask2.getPixelXYBoolean(x, y + 1))
bord = true;
if (x > 0 && y > 0 && !mask2.getPixelXYBoolean(x - 1, y - 1))
bord = true;
if (x < xdim - 1 && y > 0 && !mask2.getPixelXYBoolean(x + 1, y - 1))
bord = true;
if (x > 0 && y < ydim - 1 && !mask2.getPixelXYBoolean(x - 1, y + 1))
bord = true;
if (x < xdim - 1 && y < ydim - 1 && !mask2.getPixelXYBoolean(x + 1, y + 1))
bord = true;
return bord;
}
private Point[] getNonLabelledNeighbours(IntegerImage output, int x, int y) {
Point[] neighbours = new Point[8];
int cnt = 0;
for (int j = y - 1; j <= y + 1; j++) {
for (int i = x - 1; i <= x + 1; i++) {
if (i < 0 || i >= output.getXDim() || j < 0 || j >= output.getYDim())
continue;
int z = output.getPixelXYBInt(i, j, 0);
if (!(i == x && j == y) && z == NULL)
neighbours[cnt++] = new Point(i, j);
}
}
if (cnt < 8) {
Point[] tmp = new Point[cnt];
for (int i = 0; i < cnt; i++)
tmp[i] = neighbours[i];
neighbours = tmp;
}
return neighbours;
}
public static void main(String args[]) {
Image rgb = ImageLoader.exec("samples/billes.png");
BooleanImage mask = new BooleanImage(rgb.getXDim(), rgb.getYDim(), 1, 1, 1);
mask.setPixelXYBoolean(11, 11, true);
mask.setPixelXYBoolean(11, 12, true);
mask.setPixelXYBoolean(12, 11, true);
mask.setPixelXYBoolean(12, 12, true);
// rgb=ImageLoader.exec("samples/simple.png");
// mask=ManualThresholding.exec(rgb,0.5);
// Viewer2D.exec(rgb, "Original image (RGB)");
// Viewer2D.exec(mask, "Binary mask");
Viewer2D
.exec(GrayToPseudoColors.exec(TopographicTransform.exec(rgb, mask)),
"distance");
Viewer2D.exec(GrayToPseudoColors.exec(TopographicTransform.exec(rgb, mask,
true, true)), "distance avec bords");
Viewer2D.exec(GrayToPseudoColors.exec(TopographicTransform.exec(rgb, mask,
false)), "pseudo-distance");
Viewer2D.exec(GrayToPseudoColors.exec(TopographicTransform.exec(rgb, mask,
false, true)), "pseudo-distance avec bords");
}
/**
* See header.
*/
public static IntegerImage exec(Image input, BooleanImage mask) {
return (IntegerImage) new TopographicTransform().process(input, mask);
}
public static IntegerImage exec(Image input, BooleanImage mask,
boolean trueDistance) {
return (IntegerImage) new TopographicTransform().process(input, mask,
trueDistance);
}
public static IntegerImage exec(Image input, BooleanImage mask,
boolean trueDistance, boolean border) {
return (IntegerImage) new TopographicTransform().process(input, mask,
trueDistance, border);
}
public static IntegerImage exec(Image input, BooleanImage mask,
boolean trueDistance, boolean border,HierarchicalQueue queue) {
return (IntegerImage) new TopographicTransform().process(input, mask,
trueDistance, border,queue);
}
public static IntegerImage exec(Image input, BooleanImage mask,
boolean trueDistance, boolean border,boolean hue) {
return (IntegerImage) new TopographicTransform().process(input, mask,
trueDistance, border,hue);
}
public static IntegerImage exec(Image input, BooleanImage mask,
boolean trueDistance, boolean border,boolean hue,HierarchicalQueue queue) {
return (IntegerImage) new TopographicTransform().process(input, mask,
trueDistance, border,hue,queue);
}
}