package fr.unistra.pelican.algorithms.segmentation.qfz.gray;
import java.util.ArrayList;
import fr.unistra.pelican.Algorithm;
import fr.unistra.pelican.AlgorithmException;
import fr.unistra.pelican.Image;
import fr.unistra.pelican.IntegerImage;
import fr.unistra.pelican.algorithms.conversion.AverageChannels;
import fr.unistra.pelican.algorithms.conversion.RGBToGray;
import fr.unistra.pelican.util.Point4D;
/**
* Compute connected components of the image according to smooth connectivity defined by local range alpha.
*
* Omega is optional parameter which induces a maximum variance for the pixels values of a same connected component
*
* Alpha and omega values are used in byte precision.
*
* Only deal with Gray Levels, non-gray images will be transformed. X,Y,Z,T dimensions are taken into account.
*
* Algorithm is mine and quick made, probably better implementation exists but this one is quite fast.
*
*
* @author Jonathan Weber
*
*/
public class GraySmoothConnectivityCC extends Algorithm {
/**
* Image to process
*/
public Image inputImage;
/**
* alpha value for local range
*/
public int alpha;
/**
* optional omega value for global range
*/
public int omega=-1;
/**
* Connectivity used to determine the flat zones
*/
public Point4D[] neighbourhood;
/**
* Flat Zones labels
*/
public IntegerImage outputImage;
private static final int INITVALUE = -2;
private static final int INQUEUE = -1;
private ArrayList<Point4D> neighboursToExpand;
private int xDim;
private int yDim;
private int zDim;
private int tDim;
private int currentLabel;
private int minValueOfCC;
private int maxValueOfCC;
/**
* Constructor
*
*/
public GraySmoothConnectivityCC()
{
super();
super.inputs = "inputImage,alpha,neighbourhood";
super.options = "omega";
super.outputs = "outputImage";
}
@Override
public void launch() throws AlgorithmException {
xDim = inputImage.getXDim();
yDim = inputImage.getYDim();
zDim = inputImage.getZDim();
tDim = inputImage.getTDim();
neighboursToExpand = new ArrayList<Point4D>();
outputImage= new IntegerImage(xDim,yDim,zDim,tDim,1);
outputImage.fill(INITVALUE);
// Transforming image in grey level if not
if(inputImage.getBDim()==3)
{
inputImage = RGBToGray.exec(inputImage);
} else if(inputImage.getBDim()!=1)
{
inputImage = AverageChannels.exec(inputImage);
}
currentLabel=-1;
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)==INITVALUE)
{
outputImage.setPixelXYZTInt(x, y, z, t, ++currentLabel);
if(omega>-1)
{
minValueOfCC = inputImage.getPixelXYZTByte(x, y, z, t);
maxValueOfCC = inputImage.getPixelXYZTByte(x, y, z, t);
}
addUnlabelledNeighboursRespectToKValueToQueue(x, y, z, t);
while(neighboursToExpand.size()!=0)
{
expandCurrentLabelTo(neighboursToExpand.get(0));
neighboursToExpand.remove(0);
}
}
}
}
private void expandCurrentLabelTo(Point4D pixel)
{
ArrayList<Point4D> neighboursWithCurrentLabel = getNeighboursWithCurrentLabels(pixel.x, pixel.y, pixel.z, pixel.t);
boolean isExpandPossible=true;
int pixelValue = inputImage.getPixelXYZTByte(pixel.x, pixel.y, pixel.z, pixel.t);
for(int i=0;i<neighboursWithCurrentLabel.size();i++)
{
Point4D neighbourPixel = neighboursWithCurrentLabel.get(i);
if(Math.abs(pixelValue-inputImage.getPixelXYZTByte(neighbourPixel.x, neighbourPixel.y, neighbourPixel.z, neighbourPixel.t))>alpha)
{
isExpandPossible=false;
}
}
if(omega>-1&&isExpandPossible)
{
if(pixelValue>maxValueOfCC)
{
if(pixelValue-minValueOfCC>omega)
{
isExpandPossible=false;
}
else
{
maxValueOfCC=pixelValue;
}
}
else if(pixelValue<minValueOfCC)
{
if(maxValueOfCC-pixelValue>omega)
{
isExpandPossible=false;
}
else
{
minValueOfCC=pixelValue;
}
}
}
if(isExpandPossible)
{
outputImage.setPixelXYZTInt(pixel.x, pixel.y, pixel.z, pixel.t, currentLabel);
addUnlabelledNeighboursRespectToKValueToQueue(pixel.x, pixel.y, pixel.z, pixel.t);
}
else
{
outputImage.setPixelXYZTInt(pixel.x, pixel.y, pixel.z, pixel.t, INITVALUE);
}
}
private ArrayList<Point4D> getNeighboursWithCurrentLabels(int x,int y, int z, int t)
{
ArrayList<Point4D> neighboursWithCurrentLabel = new ArrayList<Point4D>();
for(int i=0;i<neighbourhood.length;i++)
{
int locX = x + neighbourhood[i].x;
int locY = y + neighbourhood[i].y;
int locZ = z + neighbourhood[i].z;
int locT = t + neighbourhood[i].t;
if(locX>=0&&locY>=0&&locZ>=0&&locT>=0&&locX<xDim&&locY<yDim&&locZ<zDim&&locT<tDim)
{
if(outputImage.getPixelXYZTInt(locX, locY, locZ, locT)==currentLabel)
{
neighboursWithCurrentLabel.add(new Point4D(locX, locY, locZ, locT));
}
}
}
return neighboursWithCurrentLabel;
}
private void addUnlabelledNeighboursRespectToKValueToQueue(int x, int y, int z, int t)
{
int pixelValue = inputImage.getPixelXYZTByte(x, y, z, t);
for(int i=0;i<neighbourhood.length;i++)
{
int locX = x + neighbourhood[i].x;
int locY = y + neighbourhood[i].y;
int locZ = z + neighbourhood[i].z;
int locT = t + neighbourhood[i].t;
if(locX>=0&&locY>=0&&locZ>=0&&locT>=0&&locX<xDim&&locY<yDim&&locZ<zDim&&locT<tDim)
{
if(outputImage.getPixelXYZTInt(locX, locY, locZ, locT)==INITVALUE)
{
if(Math.abs(pixelValue-inputImage.getPixelXYZTByte(locX, locY, locZ, locT))<=alpha)
{
neighboursToExpand.add(new Point4D(locX, locY, locZ, locT));
outputImage.setPixelXYZTInt(locX, locY, locZ, locT,INQUEUE);
}
}
}
}
}
/**
*
* @param inputImage
* @param alpha local range in byte precision
* @param neighbourhood neighbourhood under consideration in Point4D array
* @return
*/
public static IntegerImage exec(Image inputImage, int alpha, Point4D[] neighbourhood)
{
return (IntegerImage)new GraySmoothConnectivityCC().process(inputImage,alpha,neighbourhood);
}
/**
*
* @param inputImage
* @param alpha local range in byte precision
* @param neighbourhood neighbourhood under consideration in Point4D array
* @param omega global range in byte precision
* @return
*/
public static IntegerImage exec(Image inputImage, int alpha, Point4D[] neighbourhood, int omega)
{
return (IntegerImage)new GraySmoothConnectivityCC().process(inputImage, alpha, neighbourhood, omega);
}
}