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 erosion 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 GrayErosion extends Algorithm { /////////////// // CONSTANTS // /////////////// /** The "naive" erosion algorithm will be used by default. */ public static final int NO_OPTIMIZATION = 0; /** An erosion with a square se will be subdivided in two successives erosions : * the first with an horizontal line se, the second with a vertical line se. */ public static final int RECTANGLE_OPTIMIZATION = 1; public static final int HLINE_OPTIMIZATION = 2; public static final int VLINE_OPTIMIZATION = 3; /** An erosion with an horizontal line se will be faster using van Herk's algorithm. */ public static final int VANHERK_HLINE_OPTIMIZATION = 4; /** An erosion with an vertical line se will be faster using van Herk's algorithm. */ public static final int VANHERK_VLINE_OPTIMIZATION = 5; // if you see an other way to make things faster, put the corresponding constant here //////////// // 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 #NO_OPTIMIZATION}, things will go faster * if given an appropriate structuring element. */ public int optimization = NO_OPTIMIZATION; /////////////////////// // ALGORITHM PROFILE // /////////////////////// public GrayErosion() { super.inputs = "inputImage,se"; super.options = "mask,optimization"; super.outputs = "outputImage"; } //////////////////// // "EXEC" METHODS // //////////////////// /** Performs a gray erosion with a 2-D flat structuring element. * @param inputImage Input image * @param se Flat structuring element used in the morphological operation * @return The output image */ public static Image exec(Image inputImage, BooleanImage se) { return (Image) new GrayErosion().process(inputImage, se); } /** Performs a gray erosion 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 The output image */ public static Image exec(Image inputImage, BooleanImage se, BooleanImage mask) { return (Image) new GrayErosion().process(inputImage, se, mask); } /** Performs a specific gray erosion 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 opt Way of optimize things. Should be one of this class XXX_OPTIMIZATION constants. * @return Output image. */ public static Image exec(Image inputImage, BooleanImage se, BooleanImage mask, int opt ) { return ( Image ) new GrayErosion().process(inputImage, se, mask, opt ); } ///////////////////// // "LAUNCH" METHOD // ///////////////////// /** @see fr.unistra.pelican.Algorithm#launch() */ public void launch() { switch ( this.optimization ) { case RECTANGLE_OPTIMIZATION: this.rectangleErosion(); break; case HLINE_OPTIMIZATION: this.standardErosion(); break; case VLINE_OPTIMIZATION: this.standardErosion(); break; case VANHERK_HLINE_OPTIMIZATION: this.horizontalErosion(); break; case VANHERK_VLINE_OPTIMIZATION: this.verticalErosion(); break; default : // try to find a possible optimization ... int opt = wichOptimization( this.se, this.inputImage ); if ( opt == NO_OPTIMIZATION ) this.standardErosion(); else this.outputImage = GrayErosion.exec( this.inputImage,this.se,this.mask,opt ); } } /////////////////// // OTHER METHODS // /////////////////// /** Attempts to find wich type of optimization is the most time efficient, * according to the shape of a structuring element. * @param se Structuring element. * @param image Image on wich algorithm will be processed. * @return One of this class' XXX_OPTIMIZATION codes. */ public static int wichOptimization( BooleanImage se, Image image ) { int xdim = se.getXDim(); int ydim = se.getYDim(); int size = se.size(); int fgsize = se.getSum(); if ( fgsize == size ) { if ( ydim == 1 ) if ( image.getXDim()%xdim == 0 ) return VANHERK_HLINE_OPTIMIZATION; else return HLINE_OPTIMIZATION; if ( xdim == 1 ) if ( image.getYDim()%ydim == 0 ) return VANHERK_VLINE_OPTIMIZATION; else return VLINE_OPTIMIZATION; return RECTANGLE_OPTIMIZATION; } return NO_OPTIMIZATION; } // endfunc /** Returns the min 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 getMinGray(int x, int y, int z, int t, int b, Point4D[] points) { double min = Double.MAX_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 (min > value) min = value; flag = true; } // fi } // rof i // FIXME: Strange, if nothing is under the se, what is the right way? return ( flag ) ? min : this.inputImage.getPixelDouble( x,y,z,t,b ); } // endfunc // // STANDARD EROSION METHOD /** Performs a standard "naive" erosion. */ private void standardErosion() { this.outputImage = this.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 = this.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.getMinGray( x,y,z,t,b, points ) ); else this.outputImage.setPixelDouble( x,y,z,t,b, 0. ); } // rof } // endfunc // // RECTANGLE EROSION METHOD /** Performs a faster erosion with a square structuring element. */ private void rectangleErosion() { this.outputImage = this.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 = GrayErosion.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 = GrayErosion.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.MAX_VALUE; if ( y%len == 0 ) buffers.g[y] = px; else buffers.g[y] = Math.min( 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.MAX_VALUE; if ( y%len == len-1 ) buffers.h[y] = px; else { if ( y+1 < buffers.size ) buffers.h[y] = Math.min( 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.MAX_VALUE; if ( x%len == 0 ) buffers.g[x] = px; else buffers.g[x] = Math.min( 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.MAX_VALUE; if ( x%len == len-1 ) buffers.h[x] = px; else { if ( x+1 < buffers.size ) buffers.h[x] = Math.min( buffers.h[x+1],px ); else buffers.h[x] = px; } } // rof x } // endfunc /** Performs a faster erosion (van Herk,1992) 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 horizontalErosion() { 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.MAX_VALUE; else px = buffers.h[ n ]; } else { if ( n < 0 ) px = buffers.g[ m ]; else px = Math.min( buffers.g[ m ],buffers.h[ n ] ); } this.outputImage.setPixelDouble( x,y,z,t,b, px ); } // rof x } // rof } // endfunc /** Performs a faster erosion (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 verticalErosion() { 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(); 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.MAX_VALUE; else px = buffers.h[ n ]; } else { if ( n < 0 ) px = buffers.g[ m ]; else px = Math.min( buffers.g[ m ],buffers.h[ n ] ); } this.outputImage.setPixelDouble( x,y,z,t,b, px ); } // rof x } // rof } // endfunc }