package fr.unistra.pelican.algorithms.segmentation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.TreeMap;
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.PelicanException;
import fr.unistra.pelican.util.Point4D;
/**
* This class performs the seed region growing according to
* the algorithm presented in R. Adams and L. Bischof.
* Seeded region growing. IEEE Transaction on Pattern
* Analysis and Machine Intelligence, 16(6) :641–647, 1994.
*
* @author Jonathan Weber
*
*/
public class SeededRegionGrowing extends Algorithm {
/**
* Original Image
*/
public Image inputImage;
/**
* Seeds image
*
* Label seed from 0 to Integer.MAX_VALUE
* Rest of the image set to SeededRegionGrowingBasedOnLUT.UNLABELED
*
*/
public IntegerImage seeds;
/**
* Neighbourhood taken into account
*/
public Point4D[] neighbourhood;
/**
* Option
*
* Indicates if the SRG frontiers
* have to be merged into regions
*
*/
public boolean mergeFrontiers=false;
/**
* Seed region growing result
*
* Frontiers label is the highest one (if not merged into regions)
*/
public IntegerImage outputImage;
public static final int UNLABELED=-1;
public static final int IGNORE=-2;
public static int FRONTIER;
public SeededRegionGrowing()
{
super.inputs="inputImage,seeds,neighbourhood";
super.options="mergeFrontiers";
super.outputs="outputImage";
}
@Override
public void launch() throws AlgorithmException
{
int xDim = inputImage.getXDim();
int yDim = inputImage.getYDim();
int zDim = inputImage.getZDim();
int tDim = inputImage.getTDim();
int bDim = inputImage.getBDim();
outputImage=seeds.copyImage(true);
int size = outputImage.size();
double[] pointDelta = new double[size];
Arrays.fill(pointDelta, Double.MAX_VALUE);
FRONTIER=outputImage.maximumInt()+1;
if(bDim!=1)
{
throw(new PelicanException("Image must be monoband !"));
}
//Initialize region means
RegionMeans[] means = new RegionMeans[outputImage.maximumInt()+1];
for(int i=0;i<means.length;i++)
means[i]= new RegionMeans();
for(int i=0;i<size;i++)
if(outputImage.getPixelInt(i)!=UNLABELED&&outputImage.getPixelInt(i)!=IGNORE)
{
means[outputImage.getPixelInt(i)].addPixel(inputImage.getPixelByte(i));
}
//Init graph
// Init SSL
TreeMap<Double,ArrayList<Point4D>> ssl = new TreeMap<Double,ArrayList<Point4D>>();
for(int t=0;t<tDim;t++)
for(int z=0;z<zDim;z++)
for(int y=0;y<yDim;y++)
for(int x=0;x<xDim;x++)
{
if(outputImage.getPixelXYZTInt(x, y, z, t)==UNLABELED)
{
double delta = Double.MAX_VALUE;
for(Point4D n : neighbourhood)
{
int locX = x+n.x;
int locY = y+n.y;
int locZ = z+n.z;
int locT = t+n.t;
if(!outputImage.isOutOfBoundsXYZT(locX, locY, locZ, locT))
{
int label=outputImage.getPixelXYZTInt(locX, locY, locZ, locT);
if(label!=UNLABELED&&label!=IGNORE)
{
delta=Math.min(delta, Math.abs(inputImage.getPixelXYZTByte(x, y, z, t)-means[label].getMean()));
}
}
}
if(delta!=Double.MAX_VALUE)
{
addToSSL(new Point4D(x,y,z,t),delta,ssl,pointDelta);
}
}
}
//Main Algorithm
int count=0;
while(!ssl.isEmpty())
{
count++;
/*if(count%10000==0)
System.out.println(count+"/"+seeds.size());*/
Point4D point = getFirstFromSSL(ssl);
if(seeds.getPixelXYZTInt(point.x, point.y, point.z, point.t)==UNLABELED)
{
int currentLabel=-1;
boolean frontierFlag=false;
for(Point4D n : neighbourhood)
{
int locX = point.x+n.x;
int locY = point.y+n.y;
int locZ = point.z+n.z;
int locT = point.t+n.t;
if(!outputImage.isOutOfBoundsXYZT(locX, locY, locZ, locT))
{
int neighbourLabel = outputImage.getPixelXYZTInt(locX,locY,locZ,locT);
if(neighbourLabel!=UNLABELED&&neighbourLabel!=FRONTIER&&neighbourLabel!=IGNORE)
{
if(currentLabel==-1)
{
currentLabel=neighbourLabel;
}
else if(currentLabel!=neighbourLabel)
{
frontierFlag=true;
break;
}
}
}
}
if(frontierFlag)
{
outputImage.setPixelXYZTInt(point.x, point.y, point.z, point.t, FRONTIER);
//System.out.println("Frontier : "+FRONTIER);
}
else
{
outputImage.setPixelXYZTInt(point.x, point.y, point.z, point.t, currentLabel);
//System.out.println("Label : "+currentLabel);
means[currentLabel].addPixel(inputImage.getPixelXYZTByte(point.x, point.y, point.z, point.t));
for(Point4D n : neighbourhood)
{
int locX = point.x+n.x;
int locY = point.y+n.y;
int locZ = point.z+n.z;
int locT = point.t+n.t;
if(!outputImage.isOutOfBoundsXYZT(locX, locY, locZ, locT))
{
if(outputImage.getPixelXYZTInt(locX, locY, locZ, locT)==UNLABELED)
{
addToSSL(new Point4D(locX, locY, locZ, locT),Math.abs(inputImage.getPixelXYZTByte(locX, locY, locZ, locT)-means[currentLabel].getMean()),ssl,pointDelta);
}
}
}
}
}
}
//In some particular configurations there are still unlabeled pixels
//here we put all these pixels to frontier values
for(int i=0;i<outputImage.size();i++)
{
if(outputImage.getPixelInt(i)==UNLABELED)
{
outputImage.setPixelInt(i, FRONTIER);
}
}
if(mergeFrontiers)
{
do
{
BooleanImage frontiers = outputImage.newBooleanImage();
for(int i=0;i<frontiers.size();i++)
if(outputImage.getPixelInt(i)==FRONTIER)
frontiers.setPixelBoolean(i, true);
else
frontiers.setPixelBoolean(i, false);
int currentIndex=0;
for(int t=0;t<tDim;t++)
for(int z=0;z<zDim;z++)
for(int y=0;y<yDim;y++)
for(int x=0;x<xDim;x++,currentIndex++)
{
if(frontiers.getPixelBoolean(currentIndex))
{
int pixelValue = inputImage.getPixelByte(currentIndex);
int currentLabel=FRONTIER;
double currentDelta=Double.MAX_VALUE;
for(Point4D n : neighbourhood)
{
int locX = x+n.x;
int locY = y+n.y;
int locZ = z+n.z;
int locT = t+n.t;
if(locX>=0&&locX<xDim&&locY>=0&&locY<yDim&&locZ>=0&&locZ<zDim&&locT>=0&&locT<tDim)
{
int locIndex = outputImage.getLinearIndexXYZT_(locX, locY, locZ, locT);
if(!frontiers.getPixelBoolean(locIndex)&&outputImage.getPixelInt(locIndex)!=IGNORE)
{
int neighbourValue = inputImage.getPixelByte(locIndex);
double locDelta = Math.abs(pixelValue-neighbourValue);
if(locDelta<currentDelta)
{
currentLabel=outputImage.getPixelInt(locIndex);
currentDelta=locDelta;
}
}
}
}
outputImage.setPixelInt(currentIndex, currentLabel);
}
}
}while(outputImage.maximumInt()==FRONTIER);
}
}
private void addToSSL(Point4D point, double delta,TreeMap<Double,ArrayList<Point4D>> ssl, double[] pointDelta)
{
boolean addPoint=true;
if(pointDelta[outputImage.getLinearIndexXYZT_(point.x, point.y, point.z, point.t)]!=Double.MAX_VALUE)
{
if(delta<pointDelta[outputImage.getLinearIndexXYZT_(point.x, point.y, point.z, point.t)])
{
ArrayList<Point4D> tmp = ssl.get(pointDelta[outputImage.getLinearIndexXYZT_(point.x, point.y, point.z, point.t)]);
tmp.remove(point);
if(tmp.isEmpty())
{
ssl.remove(pointDelta[outputImage.getLinearIndexXYZT_(point.x, point.y, point.z, point.t)]);
}
}
else
{
addPoint=false;
}
}
if(addPoint)
{
pointDelta[outputImage.getLinearIndexXYZT_(point.x, point.y, point.z, point.t)]=delta;
if(ssl.containsKey(delta))
{
ssl.get(delta).add(point);
} else
{
ArrayList<Point4D> tmp = new ArrayList<Point4D>();
tmp.add(point);
ssl.put(delta, tmp);
}
}
}
private Point4D getFirstFromSSL(TreeMap<Double,ArrayList<Point4D>> ssl)
{
ArrayList<Point4D> tmp = ssl.get(ssl.firstKey());
Point4D point = tmp.remove(0);
if(tmp.isEmpty())
{
ssl.pollFirstEntry();
}
return point;
}
/**
* Perform a seed region growing
*
* @param inputImage Original Image
* @param seeds Seeds image
* @param neighbourhood Neighbourhood taken into account
* @return segmentation
*/
public static final IntegerImage exec(Image inputImage, IntegerImage seeds, Point4D[] neighbourhood)
{
return (IntegerImage)new SeededRegionGrowing().process(inputImage,seeds,neighbourhood);
}
/**
* Perform a seed region growing
*
* @param inputImage Original Image
* @param seeds Seeds image
* @param neighbourhood Neighbourhood taken into account
* @param mergeFrontiers true if the frontiers have to be merged into regions
* @return segmentation
*/
public static final IntegerImage exec(Image inputImage, IntegerImage seeds, Point4D[] neighbourhood, boolean mergeFrontiers)
{
return (IntegerImage)new SeededRegionGrowing().process(inputImage,seeds,neighbourhood,mergeFrontiers);
}
private class RegionMeans
{
int sumOfPixelValues;
int numberOfPixels;
public RegionMeans()
{
sumOfPixelValues=0;
numberOfPixels=0;
}
public double getMean()
{
return sumOfPixelValues/(double)numberOfPixels;
}
public void addPixel(int pixelValues)
{
numberOfPixels++;
sumOfPixelValues+=pixelValues;
}
}
}