diff --git a/src/main/java/fr/pasteur/ida/zellige/steps/selection/util/Binning.java b/src/main/java/fr/pasteur/ida/zellige/steps/selection/util/Binning.java new file mode 100644 index 0000000000000000000000000000000000000000..cab9faf0df2c3a93a421d634bc5d46ca910dc45c --- /dev/null +++ b/src/main/java/fr/pasteur/ida/zellige/steps/selection/util/Binning.java @@ -0,0 +1,210 @@ +package fr.pasteur.ida.zellige.steps.selection.util; + +import io.scif.img.ImgOpener; +import net.imagej.ImageJ; +import net.imglib2.Cursor; +import net.imglib2.RandomAccess; +import net.imglib2.img.Img; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.view.IntervalView; +import net.imglib2.view.Views; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static fr.pasteur.ida.zellige.steps.Utils.setPosition; +import static fr.pasteur.ida.zellige.steps.Utils.setPositionAndGet; + +@SuppressWarnings( "unchecked" ) +public class Binning< T extends RealType< T > & NativeType< T > > +{ + + private final static Logger LOGGER = LoggerFactory.getLogger( Binning.class ); + + public Img< T > getOutput() + { + return output; + } + + private final Img< T > output; + + + /** + * Reduces the size of a stack by binning groups of pixels of user-specified square sizes (bin). The resulting pixel is obtained by taking the upper left pixel of the group. + * The image is rescaled if the bin value is not a multiple of the image dimensions by adding lines and/or columns. The process is parallelized according to the number of available processors (1/4). + * + * @param input the image to binned + * @param bin the bin size + * @param <T> the input type + * @return the binned input image + */ + public static < T extends RealType< T > & NativeType< T > > Img< T > binning( Img< T > input, int bin ) + { + LOGGER.debug( "Staring process...." ); + double startTime = System.currentTimeMillis(); + Binning< T > binning = new Binning<>( resize( input, bin ), bin ); + final int threads = Runtime.getRuntime().availableProcessors() / 4; + LOGGER.info( "Numbers of processors: {}", threads ); + final List< Integer > chunks = new ArrayList<>(); + for ( int i = 0; i < input.dimension( 2 ); i++ ) + { + chunks.add( i ); // size equals to number of slices + } + final List< Runnable > runnables = + chunks.stream() + .map( chunk -> new Binning2D<>( Views.hyperSlice( input, 2, chunk ), Views.hyperSlice( binning.getOutput(), 2, chunk ), bin ) ) + .collect( Collectors.toList() ); + final ExecutorService executorService = Executors.newFixedThreadPool( threads ); + runnables.forEach( executorService::submit ); + executorService.shutdown(); + try + { + if ( executorService.awaitTermination( 300, TimeUnit.SECONDS ) ) + { + double endTime = System.currentTimeMillis(); + LOGGER.debug( "Process completed in {}s" , (endTime-startTime)/1000); + return binning.getOutput(); + } + else + { + LOGGER.debug( "The binning has failed." ); + return null; + } + } + catch ( InterruptedException e ) + { + + throw new RuntimeException( e ); + } + + } + + + /** + * @param input the image to binned + * @param bin the bin size + */ + public Binning( Img< T > input, int bin ) + { + int[] dimensions = new int[]{ + // X and Y dimensions are divided by the bin value, the other dimensions are the same + ( int ) input.dimension( 0 ) / ( bin ), + ( int ) input.dimension( 1 ) / ( bin ), + ( int ) input.dimension( 2 ) }; + output = input.factory().create( dimensions ); + } + + /** + * Bins a 2D image. + * @param <T> the image type + */ + private static final class Binning2D< T extends RealType< T > & NativeType< T > > implements Runnable + { + private final IntervalView< T > inputSlice; + private final IntervalView< T > outputSlice; + private final int bin; + + /** + * Constructor + * + * @param inputSlice the input image + * @param outputSlice the output binned image + * @param bin the bin size + */ + public Binning2D( IntervalView< T > inputSlice, IntervalView< T > outputSlice, int bin ) + { + this.inputSlice = inputSlice; + this.outputSlice = outputSlice; + this.bin = bin; + } + + + /** + * Binning process. + */ + @Override + public void run() + { + RandomAccess< T > ra = inputSlice.randomAccess(); + Cursor< T > c = outputSlice.cursor(); + + for ( int y = 0; y < inputSlice.dimension( 1 ); y = y + bin ) + + { + for ( int x = 0; x < inputSlice.dimension( 0 ); x = x + bin ) + { + c.fwd(); + c.get().set( setPositionAndGet( ra, x, y ) ); + } + } + LOGGER.info( "Binning : Section processed" ); + } + } + + + /** + * Rescales a 3D image according to the bin value. + * + * @param input the image to binned + * @param bin the bin size + * @param <T> the input type + * @return the input or the rescaled input if necessary + */ + public static < T extends RealType< T > & NativeType< T > > Img< T > resize( Img< T > input, int bin ) + { + + int moduloX = ( int ) ( input.dimension( 0 ) % bin ); + int moduloY = ( int ) ( input.dimension( 1 ) % bin ); + + if ( moduloX == 0 && moduloY == 0 ) + { + return input; + } + else + { + int dimX = ( int ) input.dimension( 0 ) + ( bin - moduloX ); + int dimY = ( int ) input.dimension( 1 ) + ( bin - moduloY ); + Img< T > output = input.factory().create( dimX, dimY, input.dimension( 4 ) ); + RandomAccess< T > inputAccess = input.randomAccess(); + RandomAccess< T > outputAccess = input.randomAccess(); + for ( int z = 0; z < input.dimension( 4 ); z++ ) + { + for ( int y = 0; y < input.dimension( 1 ); y++ ) + { + for ( int x = 0; x < input.dimension( 0 ); x++ ) + { + setPosition( outputAccess, x, y, z ); + outputAccess.get().set( setPositionAndGet( inputAccess, x, y, z ) ); + } + } + LOGGER.info( " The input image has been rescaled." ); + } + return output; + } + + } + + public static void main( String[] args ) throws InterruptedException + { + ImageJ ij = new ImageJ(); + ij.launch( args ); + final ImgOpener io = new ImgOpener(); + final Img< FloatType > kernel = ( Img< FloatType > ) io.openImgs( "doc/Scan1_volume_crop.tif" ).get( 0 ); + ImageJFunctions.show( kernel, "original" ); + + Img< FloatType > output = Binning.binning( kernel, 7 ); + + assert output != null; + ImageJFunctions.show( output, "output " ); + + } +}