package fr.unistra.pelican.algorithms.morphology.gray;
import fr.unistra.pelican.*;
import fr.unistra.pelican.util.Point4D;
import fr.unistra.pelican.util.buffers.DoubleBuffers;
/**
* Performs a gray dilation with a 2-D flat structuring element.
* <p>
* TODO: Optimization for standard dilation, i will do the rest further - Jonathan
*
* @author PELICAN team.
*/
public class GrayDilation extends Algorithm {
////////////
// FIELDS //
////////////
/** Input image. */
public Image inputImage;
/** Flat structuring element used in the morphological operation. */
public BooleanImage se;
/** Mask to limit the computing to a specified area. */
public BooleanImage mask=null;
/** Output image. */
public Image outputImage;
/** If different from {@link GrayErosion#NO_OPTIMIZATION}, things will go faster
* if given an appropriate structuring element.
*/
public int optimization = GrayErosion.NO_OPTIMIZATION;
///////////////////////
// ALGORITHM PROFILE //
///////////////////////
public GrayDilation() {
super.inputs = "inputImage,se";
super.options = "mask,optimization";
super.outputs = "outputImage";
}
////////////////////
// "EXEC" METHODS //
////////////////////
/** Performs a gray dilation with a 2-D flat structuring element.
* @param inputImage Input image
* @param se Flat structuring element used in the morphological operation
* @return Output image
*/
@SuppressWarnings("unchecked")
public static <T extends Image> T exec(T inputImage, BooleanImage se) {
return (T) new GrayDilation().process(inputImage, se);
}
/** Performs a gray dilation with a 2-D flat structuring element and a mask.
* @param inputImage Input image
* @param se Flat structuring element used in the morphological operation
* @param mask Mask used to only compute a part of the image
* @return Output image
*/
@SuppressWarnings("unchecked")
public static <T extends Image> T exec(T inputImage, BooleanImage se, BooleanImage mask) {
return (T) new GrayDilation().process(inputImage, se, mask);
}
/** Performs a specific gray dilation with a 2-D flat structuring element and a mask.
* @param inputImage Input image.
* @param se Flat structuring element used in the morphological operation.
* @param mask Mask used to only compute a part of the image.
* @param o Way of optimize things. Should be one of GrayErosion.XXX_OPTIMIZATION constants.
* @return Output image.
*/
@SuppressWarnings("unchecked")
public static <T extends Image> T exec(T inputImage, BooleanImage se, BooleanImage mask, int o ) {
return ( T ) new GrayDilation().process(inputImage, se, mask, o );
}
/////////////////////
// "LAUNCH" METHOD //
/////////////////////
/** @see fr.unistra.pelican.Algorithm#launch() */
public void launch() {
switch ( this.optimization ) {
case GrayErosion.RECTANGLE_OPTIMIZATION:
this.rectangleDilation();
break;
case GrayErosion.HLINE_OPTIMIZATION:
this.standardDilation();
break;
case GrayErosion.VLINE_OPTIMIZATION:
this.standardDilation();
break;
case GrayErosion.VANHERK_HLINE_OPTIMIZATION:
this.horizontalDilation();
break;
case GrayErosion.VANHERK_VLINE_OPTIMIZATION:
this.verticalDilation();
break;
default : // try to find a possible optimization ...
int opt = GrayErosion.wichOptimization( this.se, this.inputImage );
if ( opt == GrayErosion.NO_OPTIMIZATION )
this.standardDilation();
else this.outputImage = GrayDilation.exec( this.inputImage,this.se,this.mask,opt );
}
}
///////////////////
// OTHER METHODS //
///////////////////
/** Returns the max value under a flat structuring element.
* @param x X coordinate.
* @param y Y coordinate.
* @param z Z coordinate.
* @param t T coordinate.
* @param b B coordinate.
* @param points Present points of {@link #se}.
*/
private double getMaxGray(int x, int y, int z, int t, int b, Point4D[] points) {
double max = Double.MIN_VALUE;
boolean flag = false;
for ( int i = 0 ; i < points.length ; i++ ) {
int valX = x - this.se.getCenter().x + points[i].x;
int valY = y - this.se.getCenter().y + points[i].y;
int valZ = z - this.se.getCenter().z + points[i].z;
int valT = t - this.se.getCenter().t + points[i].t;
if ( valX >= 0 && valX < this.inputImage.getXDim()
&& valY >= 0 && valY < this.inputImage.getYDim()
&& valZ >= 0 && valZ < this.inputImage.getZDim()
&& valT >= 0 && valT < this.inputImage.getTDim()
&& this.inputImage.isPresent( valX,valY,valZ,valT,b ) ) {
double value = this.inputImage.getPixelDouble( valX, valY, valZ, valT, b );
if ( max < value ) max = value;
flag = true;
} // fi
} // rof i
// FIXME: Strange, if nothing is under the se, what is the right way?
return (flag == true) ? max : this.inputImage.getPixelDouble( x,y,z,t,b );
} // endfunc
//
// STANDARD DILATION METHOD
/** Performs a standard "naive" dilation. */
private void standardDilation() {
this.outputImage = inputImage.copyImage( false );
int xDim = this.inputImage.getXDim();
int yDim = this.inputImage.getYDim();
int tDim = this.inputImage.getTDim();
int bDim = this.inputImage.getBDim();
int zDim = this.inputImage.getZDim();
Point4D[] points = se.foreground();
boolean isHere;
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++ )
for ( int b = 0 ; b < bDim ; b++ ) {
if(mask!=null)
isHere = this.inputImage.isPresent( x,y,z,t,b )&&mask.getPixelXYZTBBoolean(x, y, z, t, b);
else
isHere = this.inputImage.isPresent( x,y,z,t,b );
if ( isHere )
this.outputImage.setPixelDouble( x,y,z,t,b, this.getMaxGray( x,y,z,t,b, points ) );
else this.outputImage.setPixelDouble( x,y,z,t,b, 0. );
} // rof
} // endfunc
//
// RECTANGLE DILATION METHOD
/** Performs a faster dilation with a square structuring element. */
private void rectangleDilation() {
this.outputImage = inputImage.copyImage( false );
int xdim = this.se.getXDim();
int ydim = this.se.getYDim();
BooleanImage optSe = fr.unistra.pelican.util.morphology.FlatStructuringElement2D.
createHorizontalLineFlatStructuringElement(
xdim, new java.awt.Point( this.se.getCenter().x, 0 ) );
this.outputImage = GrayDilation.exec( this.inputImage,optSe,null );
optSe = fr.unistra.pelican.util.morphology.FlatStructuringElement2D.
createVerticalLineFlatStructuringElement(
ydim, new java.awt.Point( 0, this.se.getCenter().y ) );
this.outputImage = GrayDilation.exec( this.outputImage,optSe );
} // endfunc
//
// LINES EROSION MATERIAL
/** Initializes buffers corresponding to line <tt>y</tt>.
* @param x Column of {@link inputImage} that <tt>buffers</tt> must represent at call's end.
* @param z Z coordinate.
* @param t T coordinate.
* @param b B coordinate.
* @param buffers Up and down buffers.
* @param len Length of horizontal line structuring element {@link #se}.
*/
private void initColumnBuffers( int x, int z, int t, int b, DoubleBuffers buffers, int len ) {
double px;
boolean isHere;
for ( int y = 0 ; y < buffers.size ; y++ ) { // fill g
isHere = this.inputImage.isPresent( x,y,z,t,b );
if ( isHere ) px = this.inputImage.getPixelXYZTBDouble( x,y,z,t,b );
else px = Double.MIN_VALUE;
if ( y%len == 0 ) buffers.g[y] = px;
else buffers.g[y] = Math.max( buffers.g[y-1],px );
} // rof x
for ( int y = buffers.size-1 ; y >= 0 ; y-- ) { // fill h
isHere = this.inputImage.isPresent( x,y,z,t,b );
if ( isHere ) px = this.inputImage.getPixelXYZTBDouble( x,y,z,t,b );
else px = Double.MIN_VALUE;
if ( y%len == len-1 ) buffers.h[y] = px;
else {
if ( y+1 < buffers.size ) buffers.h[y] = Math.max( buffers.h[y+1],px );
else buffers.h[y] = px;
}
} // rof x
} // endfunc
/** Initializes buffers corresponding to line <tt>y</tt>.
* @param y Line of {@link inputImage} that <tt>buffers</tt> must represent at call's end.
* @param z Z coordinate.
* @param t T coordinate.
* @param b B coordinate.
* @param buffers Left and right buffers.
* @param len Length of horizontal line structuring element {@link #se}.
*/
private void initRowBuffers( int y, int z, int t, int b, DoubleBuffers buffers, int len ) {
double px;
boolean isHere;
for ( int x = 0 ; x < buffers.size ; x++ ) { // fill g
isHere = this.inputImage.isPresent( x,y,z,t,b );
if ( isHere ) px = this.inputImage.getPixelXYZTBDouble( x,y,z,t,b );
else px = Double.MIN_VALUE;
if ( x%len == 0 ) buffers.g[x] = px;
else buffers.g[x] = Math.max( buffers.g[x-1],px );
} // rof x
for ( int x = buffers.size-1 ; x >= 0 ; x-- ) { // fill h
isHere = this.inputImage.isPresent( x,y,z,t,b );
if ( isHere ) px = this.inputImage.getPixelXYZTBDouble( x,y,z,t,b );
else px = Double.MIN_VALUE;
if ( x%len == len-1 ) buffers.h[x] = px;
else {
if ( x+1 < buffers.size ) buffers.h[x] = Math.max( buffers.h[x+1],px );
else buffers.h[x] = px;
}
} // rof x
} // endfunc
/** Performs a faster dilation with a horizontal structuring element.
* <p>
* M. van Herk, <i>A fast algorithm for local minimum and maximum filters on rectangular and
* octogonal kernels</i> (1992).
* Algorithm leeched from P. Soille, <i>Morphological Image Analysis</i> (3.9.1).
*/
private void horizontalDilation() {
this.outputImage = this.inputImage.copyImage( false );
int xdim = this.inputImage.getXDim();
int ydim = this.inputImage.getYDim();
int zdim = this.inputImage.getZDim();
int tdim = this.inputImage.getTDim();
int bdim = this.inputImage.getBDim();
int size = xdim;
DoubleBuffers buffers = new DoubleBuffers( size );
int lambda = this.se.getXDim(); // lambada !
int o = this.se.getCenter().x;
assert buffers.size%lambda == 0;
double px;
int m,n;
for ( int b = 0 ; b < bdim ; b++ )
for ( int t = 0 ; t < tdim ; t++ )
for ( int z = 0 ; z < zdim ; z++ )
for ( int y = 0 ; y < ydim ; y++ ) {
this.initRowBuffers( y,z,t,b, buffers,lambda );
for ( int x = 0 ; x < xdim ; x++ ) {
m = x+lambda-o-1;
n = x-o;
if ( m >= xdim ) {
if ( n < 0 ) px = Double.MIN_VALUE;
else px = buffers.h[ n ];
} else {
if ( n < 0 ) px = buffers.g[ m ];
else px = Math.max( buffers.g[ m ],buffers.h[ n ] );
}
this.outputImage.setPixelDouble( x,y,z,t,b, px );
} // rof x
} // rof
} // endfunc
/** Performs a faster dilation (van Herk,1992) with a vertical structuring element.
* <p>
* M. van Herk, <i>A fast algorithm for local minimum and maximum filters on rectangular and
* octogonal kernels</i> (1992).
* Algorithm leeched from P. Soille, <i>Morphological Image Analysis</i> (3.9.1).
*/
private void verticalDilation() {
this.outputImage = this.inputImage.copyImage( false );
int xdim = this.inputImage.getXDim();
int ydim = this.inputImage.getYDim();
int zdim = this.inputImage.getZDim();
int tdim = this.inputImage.getTDim();
int bdim = this.inputImage.getBDim();
int size = ydim;
DoubleBuffers buffers = new DoubleBuffers( size );
int lambda = this.se.getYDim(); // lambada !
int o = this.se.getCenter().y;
assert buffers.size%lambda == 0;
double px;
int m,n;
for ( int b = 0 ; b < bdim ; b++ )
for ( int t = 0 ; t < tdim ; t++ )
for ( int z = 0 ; z < zdim ; z++ )
for ( int x = 0 ; x < xdim ; x++ ) {
this.initColumnBuffers( x,z,t,b, buffers,lambda );
for ( int y = 0 ; y < ydim ; y++ ) {
m = y+lambda-o-1;
n = y-o;
if ( m >= ydim ) {
if ( n < 0 ) px = Double.MIN_VALUE;
else px = buffers.h[ n ];
} else {
if ( n < 0 ) px = buffers.g[ m ];
else px = Math.max( buffers.g[ m ],buffers.h[ n ] );
}
this.outputImage.setPixelDouble( x,y,z,t,b, px );
} // rof x
} // rof
} // endfunc
}