diff --git a/src/main/java/fr/pasteur/ida/zellige/utils/AmplitudeBackgroundForeGroundClassification.java b/src/main/java/fr/pasteur/ida/zellige/utils/AmplitudeBackgroundForeGroundClassification.java new file mode 100644 index 0000000000000000000000000000000000000000..42d92821a709e67c0dfca51b845507f3eea3d8e9 --- /dev/null +++ b/src/main/java/fr/pasteur/ida/zellige/utils/AmplitudeBackgroundForeGroundClassification.java @@ -0,0 +1,205 @@ +package fr.pasteur.ida.zellige.utils; + +import fr.pasteur.ida.zellige.surfaceConstruction.construction.SurfacesExtraction; +import net.imglib2.Cursor; +import net.imglib2.Interval; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.binary.Thresholder; +import net.imglib2.algorithm.util.Grids; +import net.imglib2.img.Img; +import net.imglib2.img.ImgFactory; +import net.imglib2.img.array.ArrayImgFactory; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.type.NativeType; +import net.imglib2.type.logic.BitType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Util; +import net.imglib2.view.IntervalView; +import net.imglib2.view.Views; + +import java.util.List; + +/** + * This class makes it possible to get a binary classification of the pixels (background/ foreground) according to their amplitude and their neighborhood. + * First, the amplitude of all local maximums is computed and stored as an {@link Img}. This {@link Img} is cut into squares (15x15) then depending on the number of pixels contained in + * each square, the latter will be classified as background or foreground. + */ +public class AmplitudeBackgroundForeGroundClassification +{ + + public static < T extends RealType< T > & NativeType< T > > Img< FloatType > find( final Img< T > source, double amplitudeThreshold, double sizePercent ) + { + // Prepare output. + + Img< BitType > amplitude = new ArrayImgFactory<>( new BitType() ).create( source ); + MaximumAmplitude< T > maximumAmplitude = new MaximumAmplitude<>( source, amplitude, amplitudeThreshold ); + final ImgFactory< FloatType > factory = Util.getArrayOrCellImgFactory( source, new FloatType() ); + Img< FloatType > output = factory.create( source ); + new computeBackgroundForegroundGrid( maximumAmplitude.getAmplitude(), output, sizePercent ); + ImageJFunctions.show( output, "new class amplitude" ); + return output; + } + + private static final class MaximumAmplitude< T extends RealType< T > & NativeType< T > > + { + private final Img< T > input; + private final double amplitudeThreshold; + private Img< BitType > amplitude; + + public MaximumAmplitude( Img< T > input, Img< BitType > amplitude, double amplitudeThreshold ) + { + this.input = input; + this.amplitude = amplitude; + this.amplitudeThreshold = amplitudeThreshold; + run(); + } + + private static < T extends RealType< T > & NativeType< T > > Img< T > getAmplitude( + Img< T > max, RandomAccessibleInterval< T > min ) + { + Img< T > amp = max.factory().create( max ); + RandomAccess< T > maxAccess = max.randomAccess(); + RandomAccess< T > minAccess = min.randomAccess(); + RandomAccess< T > ampAccess = amp.randomAccess(); + for ( int x = 0; x <= SurfacesExtraction.getX() - 1; x++ ) + { + maxAccess.setPosition( x, 0 ); + for ( int y = 0; y <= SurfacesExtraction.getY() - 1; y++ ) + { + maxAccess.setPosition( y, 1 ); + for ( int z = 0; z <= SurfacesExtraction.getZ() - 1; z++ ) + + { + maxAccess.setPosition( z, 2 ); + float maxValue = maxAccess.get().getRealFloat(); + if ( maxValue != 0 ) + { + minAccess.setPosition( maxAccess ); + double amplitude = getAmplitude( maxValue, minAccess ); + ampAccess.setPosition( maxAccess ); + ampAccess.get().setReal( amplitude ); + } + } + } + } + return amp; + } + + private static < T extends RealType< T > & NativeType< T > > double getAmplitude( float maxValue, RandomAccess< T > minAccess ) + { + double up = findValueUp( minAccess, maxValue ); + double down = findValueDown( minAccess, maxValue ); + double result = Math.max( Math.abs( maxValue - up ), Math.abs( maxValue - down ) ); + if ( result == 0 ) + { + return maxValue; + + } + return ( result ); + } + + private static < T extends RealType< T > & NativeType< T > > double findValueUp( + RandomAccess< T > minAccess, float maxValue ) + { + int start = minAccess.getIntPosition( 2 ); + for ( int z = start - 1; z >= 0; z-- ) + { + minAccess.setPosition( z, 2 ); + double value = minAccess.get().getRealDouble(); + if ( value != 0 ) + { + return value; + } + } + return maxValue; + } + + private static < T extends RealType< T > & NativeType< T > > double findValueDown( + RandomAccess< T > minAccess, float maxValue ) + { + int start = minAccess.getIntPosition( 2 ); + for ( int z = start + 1; z <= SurfacesExtraction.getZ() - 1; z++ ) + { + minAccess.setPosition( z, 2 ); + double value = minAccess.get().getRealDouble(); + if ( value != 0 ) + { + return value; + } + } + return maxValue; + } + + public void run() + { + Img< T > maximums = LocalMaximumDetection.findMaximums( input, input.factory() ); + Img< T > minimums = LocalMinimumDetection.findMinimums( input, input.factory() ); + Img< T > amp = getAmplitude( maximums, minimums ); + T TMax = Threshold.getFirstMaxValue( maximums, false ); + TMax.mul( amplitudeThreshold ); + this.amplitude = Thresholder.threshold( amp.copy(), TMax, true, 2 ); + + } + + public Img< BitType > getAmplitude() + { + return amplitude; + } + } + + + private static final class computeBackgroundForegroundGrid + { + private final Img< BitType > source; + private final Img< FloatType > output; + private final double sizePercent; + + private computeBackgroundForegroundGrid( final Img< BitType > source, Img< FloatType > output, double sizePercent ) + { + this.source = source; + this.output = output; + this.sizePercent = sizePercent; + run(); + } + + public void run() + { + long X = source.dimension( 0 ); + long Y = source.dimension( 1 ); + long Z = source.dimension( 2 ); + List< Interval > intervals = Grids.collectAllContainedIntervals( new long[]{ X, Y, Z }, + new int[]{ 15, 15, 1 } ); + + for ( Interval interval : intervals ) + { + IntervalView< BitType > viewSource = Views.offsetInterval( source, interval ); + IntervalView< FloatType > viewOutput = Views.offsetInterval( output, interval ); + int foreground = isForeground( viewSource, sizePercent ) ? 1 : 0; + viewOutput.forEach( pixel -> pixel.setReal( foreground ) ); + } + } + public static < T extends RealType< T > & NativeType< T > > boolean isForeground( IntervalView< T > intervalView, double sizePercent ) + { + double sum = 0; + Cursor< T > cursor = intervalView.cursor(); + while ( cursor.hasNext() ) + { + cursor.fwd(); + if ( cursor.get().getRealDouble() != 0 ) + { + sum++; + } + } + return ( sum ) > ( intervalView.dimension( 0 ) * intervalView.dimension( 1 ) * sizePercent ); + } + + + public Img< FloatType > getOutput() + { + return output; + } + } + +}