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 " );
+
+    }
+}