diff --git a/src/main/java/net/imglib2/algorithm/util/Grids.java b/src/main/java/net/imglib2/algorithm/util/Grids.java new file mode 100644 index 000000000..68266f961 --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/util/Grids.java @@ -0,0 +1,489 @@ +/* + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2016 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package net.imglib2.algorithm.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.IntStream; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.Localizable; +import net.imglib2.Positionable; +import net.imglib2.util.Intervals; +import net.imglib2.util.Pair; +import net.imglib2.util.ValuePair; + +/** + * + * @author Philipp Hanslovsky + * + */ +public class Grids +{ + + /** + * + * Helper interface for moving by a specified distance along a specified + * dimension. + * + */ + public static interface MoveForDimension + { + /** + * + * @param by + * Distance to move. + * @param dimension + * Dimension along which to move. + */ + public void move( long by, int dimension ); + } + + /** + * + * Helper interface to set position of specified dimension. + * + */ + public static interface SetForDimension + { + /** + * + * @param to + * Set to this value. + * @param dimension + * Affected dimension. + */ + public void set( long to, int dimension ); + } + + /** + * + * Helper interface to get current value of specified dimension + * + */ + public static interface GetForDimension + { + /** + * + * @param dimension + * @return Current value at specified dimension. + */ + public long get( int dimension ); + } + + /** + * Execute {@code runAtOffset} for each offset of a grid defined by + * {@code min}, {@code max}, and {@code blockSize}. The offset object + * {@code p} must be provided by the caller. + * + * @param min + * @param max + * @param blockSize + * @param p + * @param runAtOffset + */ + public static < P extends Positionable & Localizable > void forEachOffset( + final long[] min, + final long[] max, + final int[] blockSize, + final P p, + final Runnable runAtOffset ) + { + + assert p.numDimensions() == min.length: "Dimensionality mismatch!"; + + forEachOffset( min, max, blockSize, ( to, d ) -> p.setPosition( to, d ), d -> p.getLongPosition( d ), ( by, d ) -> p.move( by, d ), runAtOffset ); + } + + /** + * + * Execute {@code runAtOffset} for each offset of a grid defined by + * {@code min}, {@code max}, and {@code blockSize}. + * + * @param min + * @param max + * @param blockSize + * @param runAtOffset + */ + public static void forEachOffset( + final long[] min, + final long[] max, + final int[] blockSize, + final Consumer< long[] > runAtOffset ) + { + final long[] offset = new long[ min.length ]; + forEachOffset( min, max, blockSize, ( to, d ) -> offset[ d ] = to, ( d ) -> offset[ d ], ( by, d ) -> offset[ d ] += by, () -> runAtOffset.accept( offset ) ); + } + + /** + * Execute a {@link Runnable} for each offset of a grid defined by + * {@code min}, {@code max}, and {@code blockSize}. + * + * This method is agonstic of the object that represents the current offset. + * Instead, the caller provides {@code setOffsetForDimension}, + * {@code getOffsetForDimension}, and {@code moveForDimension} to move the + * offset object in the correct positions. Consequently, + * {@code runAtEachOffset} needs to be a stateful object that is aware of + * the current position. + * + * See {@link Grids#forEachOffset(long[], long[], int[], Consumer)} and + * {@link Grids#forEachOffset(long[], long[], int[], Positionable, Runnable)} + * for example/convenience implementations for {@code long[]} and + * {@code Positionable & Lozalizable} offset objects. + * + * @param min + * @param max + * @param blockSize + * @param setOffsetForDimension + * @param getOffsetForDimension + * @param moveForDimension + * @param runAtEachOffset + */ + public static void forEachOffset( + final long[] min, + final long[] max, + final int[] blockSize, + final SetForDimension setOffsetForDimension, + final GetForDimension getOffsetForDimension, + final MoveForDimension moveForDimension, + final Runnable runAtEachOffset ) + { + + assert Arrays.stream( blockSize ).filter( b -> b < 1 ).count() == 0: "Only non-zero blockSize allowed!"; + assert min.length == blockSize.length: "Dimensionality mismatch!"; + assert max.length == blockSize.length: "Dimensionality mismatch!"; + assert IntStream.range( 0, min.length ).filter( d -> max[ d ] < min[ d ] ).count() == 0: "max has to greater or equal than min for all dimensions!"; + + final int nDim = min.length; + for ( int d = 0; d < nDim; ++d ) + setOffsetForDimension.set( min[ d ], d ); + + for ( int d = 0; d < nDim; ) + { + runAtEachOffset.run(); + for ( d = 0; d < nDim; ++d ) + { + moveForDimension.move( blockSize[ d ], d ); + if ( getOffsetForDimension.get( d ) <= max[ d ] ) + break; + else + setOffsetForDimension.set( min[ d ], d ); + } + } + } + + /** + * + * Get all blocks of size {@code blockSize} contained within an interval + * specified by {@code dimensions} and their positions within a cell grid. + * + * @param dimensions + * @param blockSize + * @return list of blocks as specified by {@link Interval} and the position + * within a cell grid. + */ + public static List< Pair< Interval, long[] > > collectAllContainedIntervalsWithGridPositions( final long[] dimensions, final int[] blockSize ) + { + return collectAllOffsets( dimensions, blockSize, croppedIntervalAndGridPosition( dimensions, blockSize ) ); + } + + /** + * + * Get all blocks of size {@code blockSize} contained within an interval + * specified by {@code min}, {@code max} and their positions within a cell + * grid. + * + * @param min + * @param max + * @param blockSize + * @return list of blocks as specified by {@link Interval} and the position + * within a cell grid. + */ + public static List< Pair< Interval, long[] > > collectAllContainedIntervalsWithGridPositions( final long[] min, final long[] max, final int[] blockSize ) + { + return collectAllOffsets( min, max, blockSize, croppedIntervalAndGridPosition( min, max, blockSize ) ); + } + + /** + * + * Get all blocks of size {@code blockSize} contained within an interval + * specified by {@code dimensions} and their positions within a cell grid. + * + * @param dimensions + * @param blockSize + * @return list of blocks as specified by {@link Interval} + */ + public static List< Interval > collectAllContainedIntervals( final long[] dimensions, final int[] blockSize ) + { + return collectAllOffsets( dimensions, blockSize, new CreateAndCropBlockToFitInterval( blockSize, new FinalInterval( dimensions ) ) ); + } + + /** + * + * Get all blocks of size {@code blockSize} contained within an interval + * specified by {@code min}, {@code max} and their positions within a cell + * grid. + * + * @param min + * @param max + * @param blockSize + * @return list of blocks as specified by {@link Interval} + */ + public static List< Interval > collectAllContainedIntervals( final long[] min, final long[] max, final int[] blockSize ) + { + return collectAllOffsets( min, max, blockSize, new CreateAndCropBlockToFitInterval( blockSize, max ) ); + } + + /** + * + * Get all blocks of size {@code blockSize} contained within an interval + * specified by {@code dimensions}. + * + * @param dimensions + * @param blockSize + * @return list of blocks defined by minimum + */ + public static List< long[] > collectAllOffsets( final long[] dimensions, final int[] blockSize ) + { + return collectAllOffsets( dimensions, blockSize, block -> block.clone() ); + } + + /** + * + * Get all blocks of size {@code blockSize} contained within an interval + * specified by {@code dimensions}. + * + * @param dimensions + * @param blockSize + * @param func + * Apply this function to each block, e.g. create a + * {@link Interval} for each block. + * @return list of blocks mapped by {@code funk} + */ + public static < T > List< T > collectAllOffsets( final long[] dimensions, final int[] blockSize, final Function< long[], T > func ) + { + return collectAllOffsets( new long[ dimensions.length ], Arrays.stream( dimensions ).map( d -> d - 1 ).toArray(), blockSize, func ); + } + + /** + * + * Get all blocks of size {@code blockSize} contained within an interval + * specified by {@code min} and {@code max}. + * + * @param min + * @param max + * @param blockSize + * @return list of blocks defined by minimum + */ + public static List< long[] > collectAllOffsets( final long[] min, final long[] max, final int[] blockSize ) + { + return collectAllOffsets( min, max, blockSize, block -> block.clone() ); + } + + /** + * + * Get all blocks of size {@code blockSize} contained within an interval + * specified by {@code min} and {@code max}. + * + * @param min + * @param max + * @param blockSize + * @param func + * Apply this function to each block, e.g. create a + * {@link Interval} for each block. + * @return list of blocks mapped by {@code funk} + */ + public static < T > List< T > collectAllOffsets( final long[] min, final long[] max, final int[] blockSize, final Function< long[], T > func ) + { + final List< T > blocks = new ArrayList<>(); + forEachOffset( min, max, blockSize, offset -> blocks.add( func.apply( offset ) ) ); + return blocks; + } + + /** + * + * @author Philipp Hanslovsky + * + * Map the minimum of block to an interval specified by said minimum + * and a block size such that the maximum of the interval is no + * larger than {@code max} for any dimensions. + * + */ + public static class CreateAndCropBlockToFitInterval implements Function< long[], Interval > + { + + private final int[] blockSize; + + private final long[] max; + + /** + * + * @param blockSize + * Regular size for intervals. + * @param max + * Upper bound for intervals. + */ + public CreateAndCropBlockToFitInterval( final int[] blockSize, final long[] max ) + { + super(); + this.blockSize = blockSize; + this.max = max; + } + + /** + * Convenience constructor for {@link Interval}. Delegates to + * {@link CreateAndCropBlockToFitInterval#CreateAndCropBlockToFitInterval(int[], long[])} + * using {@code max = Intervals.maxAsLongArray( interval )}. + * + * @param blockSize + * @param interval + */ + public CreateAndCropBlockToFitInterval( final int[] blockSize, final Interval interval ) + { + this( blockSize, Intervals.maxAsLongArray( interval ) ); + } + + @Override + public Interval apply( final long[] min ) + { + final long[] max = new long[ min.length ]; + Arrays.setAll( max, d -> Math.min( min[ d ] + this.blockSize[ d ] - 1, this.max[ d ] ) ); + return new FinalInterval( min, max ); + } + + } + + /** + * + * @author Philipp Hanslovsky + * + * Map the minimum of block to its position within a grid, specified + * by the minimum of the grid, and the block size + * + */ + public static class GetGridCoordinates implements Function< long[], long[] > + { + + private final int[] blockSize; + + private final long[] gridMin; + + /** + * + * @param blockSize + * Regular size for intervals. + * @param min + * minimum of the grid + */ + public GetGridCoordinates( final int[] blockSize, final long[] min ) + { + super(); + this.blockSize = blockSize; + this.gridMin = min; + } + + /** + * Convenience constructor for {@link Interval}. Delegates to + * {@link CreateAndCropBlockToFitInterval#CreateAndCropBlockToFitInterval(int[], long[])} + * using {@code min = Intervals.minAsLongArray( interval )}. + * + * @param blockSize + * @param interval + */ + public GetGridCoordinates( final int[] blockSize, final Interval interval ) + { + this( blockSize, Intervals.minAsLongArray( interval ) ); + } + + /** + * Convenience constructor for zero min grid. Delegates to + * {@link CreateAndCropBlockToFitInterval#CreateAndCropBlockToFitInterval(int[], long[])} + * using {@code min = 0}. + * + * @param blockSize + */ + public GetGridCoordinates( final int[] blockSize ) + { + this( blockSize, new long[ blockSize.length ] ); + } + + @Override + public long[] apply( final long[] min ) + { + final long[] gridPosition = new long[ min.length ]; + Arrays.setAll( gridPosition, d -> ( min[ d ] - this.gridMin[ d ] ) / this.blockSize[ d ] ); + return gridPosition; + } + } + + public static Function< long[], Pair< Interval, long[] > > croppedIntervalAndGridPosition( + final long[] dimensions, + final int[] blockSize ) + { + final CreateAndCropBlockToFitInterval makeInterval = new CreateAndCropBlockToFitInterval( blockSize, new FinalInterval( dimensions ) ); + final GetGridCoordinates getGridCoordinates = new GetGridCoordinates( blockSize ); + return blockMinimum -> new ValuePair<>( makeInterval.apply( blockMinimum ), getGridCoordinates.apply( blockMinimum ) ); + } + + /** + * Convenience method to create {@link Function} that maps a block minimum + * into a {@link Pair} of {@link Interval} and {@link long[]} that specify + * the block and its position in grid coordinates. + * + * @param min + * minimum of the grid + * @param max + * maximum of the grid + * @param blockSize + * regular size of blocks in the grid + * @return {@link Pair} of {@link Interval} and {@link long[]} that specify + * the block and its position in grid coordinates. Blocks are + * cropped + */ + public static Function< long[], Pair< Interval, long[] > > croppedIntervalAndGridPosition( + final long[] min, + final long[] max, + final int[] blockSize ) + { + final CreateAndCropBlockToFitInterval makeInterval = new CreateAndCropBlockToFitInterval( blockSize, max ); + final GetGridCoordinates getGridCoordinates = new GetGridCoordinates( blockSize, min ); + return blockMinimum -> new ValuePair<>( makeInterval.apply( blockMinimum ), getGridCoordinates.apply( blockMinimum ) ); + } + +} diff --git a/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java b/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java new file mode 100644 index 000000000..b2adcedd9 --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java @@ -0,0 +1,249 @@ +/* + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2016 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package net.imglib2.algorithm.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.util.Intervals; + +/** + * + * @author Philipp Hanslovsky + * + * Utility methods to parallelize image processing tasks in blocks + * + */ +public class ParallelizeOverBlocks +{ + + /** + * + * Submit blocked tasks and wait for execution. + * + * @param func + * {@link Function} to be applied to each block as specified by + * first parameter {@link Interval}. + * @param interval + * @param blockSize + * @param es + * @param numTasks + * @return {@link List} of results of computation. Note that for + * computations that return void, this should be a list of + * {@link Void}. + * @throws InterruptedException + * @throws ExecutionException + */ + public static < T > List< T > parallelizeAndWait( + final Function< Interval, T > func, + final Interval interval, + final int[] blockSize, + final ExecutorService es, + final int numTasks ) throws InterruptedException, ExecutionException + { + return parallelizeAndWait( func, Intervals.minAsLongArray( interval ), Intervals.maxAsLongArray( interval ), blockSize, es, numTasks ); + } + + /** + * + * Submit blocked tasks and wait for execution. + * + * @param func + * {@link Function} to be applied to each block as specified by + * first parameter {@link Interval}. + * @param interval + * @param blockSize + * @param es + * @param numTasks + * @return List of futures of the submitted tasks. Each future contains a + * list of results. + */ + public static < T > List< Future< List< T > > > parallelize( + final Function< Interval, T > func, + final Interval interval, + final int[] blockSize, + final ExecutorService es, + final int numTasks ) + { + return parallelize( func, Intervals.minAsLongArray( interval ), Intervals.maxAsLongArray( interval ), blockSize, es, numTasks ); + } + + /** + * + * Submit blocked tasks and wait for execution. + * + * @param func + * {@link Function} to be applied to each block as specified by + * first parameter {@link Interval}. + * @param min + * @param max + * @param blockSize + * @param es + * @param numTasks + * @return {@link List} of results of computation. Note that for + * computations that return void, this should be a list of + * {@link Void}. + * @throws InterruptedException + * @throws ExecutionException + */ + public static < T > List< T > parallelizeAndWait( + final Function< Interval, T > func, + final long[] min, + final long[] max, + final int[] blockSize, + final ExecutorService es, + final int numTasks ) throws InterruptedException, ExecutionException + { + final List< Future< List< T > > > futures = parallelize( func, min, max, blockSize, es, numTasks ); + final List< T > results = new ArrayList<>(); + for ( final Future< List< T > > future : futures ) + results.addAll( future.get() ); + return results; + } + + /** + * + * Submit blocked tasks and wait for execution. + * + * @param func + * {@link Function} to be applied to each block as specified by + * first parameter {@link Interval}. + * @param min + * @param max + * @param blockSize + * @param es + * @param numTasks + * @return List of futures of the submitted tasks. Each future contains a + * list of results. + */ + public static < T > List< Future< List< T > > > parallelize( + final Function< Interval, T > func, + final long[] min, + final long[] max, + final int[] blockSize, + final ExecutorService es, + final int numTasks ) + { + final List< Interval > blocks = Grids.collectAllOffsets( min, max, blockSize, blockMin -> { + final long[] blockMax = new long[ blockMin.length ]; + for ( int d = 0; d < blockMax.length; ++d ) + blockMax[ d ] = Math.min( blockMin[ d ] + blockSize[ d ] - 1, max[ d ] ); + return new FinalInterval( blockMin, blockMax ); + } ); + return parallelize( func, blocks, es, numTasks ); + } + + /** + * + * Submit blocked tasks and wait for execution. + * + * @param func + * {@link Function} to be applied to each block as specified by + * first parameter {@link Interval}. + * @param blocks + * @param es + * @param numTasks + * @return {@link List} of results of computation. Note that for + * computations that return void, this should be a list of + * {@link Void}. + * @throws InterruptedException + * @throws ExecutionException + */ + public static < T > List< T > parallelizeAndWait( + final Function< Interval, T > func, + final List< Interval > blocks, + final ExecutorService es, + final int numTasks ) throws InterruptedException, ExecutionException + { + final List< Future< List< T > > > futures = parallelize( func, blocks, es, numTasks ); + final List< T > results = new ArrayList<>(); + for ( final Future< List< T > > future : futures ) + results.addAll( future.get() ); + return results; + } + + /** + * + * Submit blocked tasks and wait for execution. + * + * @param func + * {@link Function} to be applied to each block as specified by + * first parameter {@link Interval}. + * @param blocks + * @param es + * @param numTasks + * @return List of futures of the submitted tasks. Each future contains a + * list of results. + */ + public static < T > List< Future< List< T > > > parallelize( + final Function< Interval, T > func, + final List< Interval > blocks, + final ExecutorService es, + final int numTasks ) + { + final ArrayList< Future< List< T > > > futures = new ArrayList<>(); + final int taskSize = Math.max( blocks.size() / numTasks, 1 ); + for ( int i = 0; i < blocks.size(); ++i ) + { + final int finalI = i; + futures.add( es.submit( () -> blocks.subList( finalI, finalI + taskSize ).stream().map( func ).collect( Collectors.toList() ) ) ); + } + + return futures; + } + + /** + * Utility method to turn a {@link Consumer} into a {@link Function}. + * + * @param consumer + * @return + */ + public static < T > Function< T, Void > ofConsumer( final Consumer< T > consumer ) + { + return t -> { + consumer.accept( t ); + return null; + }; + } + +} diff --git a/src/test/java/net/imglib2/algorithm/util/GridsTest.java b/src/test/java/net/imglib2/algorithm/util/GridsTest.java new file mode 100644 index 000000000..9c482ab5c --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/util/GridsTest.java @@ -0,0 +1,174 @@ +package net.imglib2.algorithm.util; + +import java.util.List; +import java.util.stream.IntStream; + +import org.junit.Assert; +import org.junit.Test; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.RandomAccess; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.img.basictypeaccess.array.LongArray; +import net.imglib2.type.logic.BitType; +import net.imglib2.type.numeric.integer.UnsignedIntType; +import net.imglib2.util.Intervals; +import net.imglib2.util.Pair; +import net.imglib2.view.IntervalView; +import net.imglib2.view.Views; + +public class GridsTest +{ + + private final Interval interval = new FinalInterval( 3, 4, 5 ); + + private final int[] blockSize = new int[] { 2, 2, 5 }; + + @Test + public void test() + { + + final Interval interval = this.interval; + + final long[][] blockMinima = { + { 0, 0, 0 }, + { 2, 0, 0 }, + { 0, 2, 0 }, + { 2, 2, 0 } + }; + + final List< long[] > blocks = Grids.collectAllOffsets( Intervals.dimensionsAsLongArray( interval ), blockSize ); + Assert.assertEquals( blockMinima.length, blocks.size() ); + for ( int i = 0; i < blockMinima.length; ++i ) + Assert.assertArrayEquals( blockMinima[ i ], blocks.get( i ) ); + + final long[][] gridPositions = { + { 0, 0, 0 }, + { 1, 0, 0 }, + { 0, 1, 0 }, + { 1, 1, 0 } + }; + + final Interval[] intervals = { + new FinalInterval( new long[] { 0, 0, 0 }, new long[] { 1, 1, 4 } ), + new FinalInterval( new long[] { 2, 0, 0 }, new long[] { 2, 1, 4 } ), + new FinalInterval( new long[] { 0, 2, 0 }, new long[] { 1, 3, 4 } ), + new FinalInterval( new long[] { 2, 2, 0 }, new long[] { 2, 3, 4 } ) + }; + + final List< Pair< Interval, long[] > > blocksAndGridPositions = Grids.collectAllContainedIntervalsWithGridPositions( Intervals.dimensionsAsLongArray( interval ), blockSize ); + Assert.assertEquals( intervals.length, blocksAndGridPositions.size() ); + for ( int i = 0; i < intervals.length; ++i ) + { + final Interval block = blocksAndGridPositions.get( i ).getA(); + // if both contain each other they are equal + Assert.assertTrue( Intervals.contains( block, intervals[ i ] ) && Intervals.contains( intervals[ i ], block ) ); + + final long[] position = blocksAndGridPositions.get( i ).getB(); + Assert.assertArrayEquals( gridPositions[ i ], position ); + } + } + + @Test + public void testWithOffset() + { + + final long[] offset = { 101, 37, -13 }; + final Interval interval = Intervals.translate( Intervals.translate( Intervals.translate( this.interval, offset[ 0 ], 0 ), offset[ 1 ], 1 ), offset[ 2 ], 2 ); + + final long[][] blockMinima = { + { offset[ 0 ] + 0, offset[ 1 ] + 0, offset[ 2 ] + 0 }, + { offset[ 0 ] + 2, offset[ 1 ] + 0, offset[ 2 ] + 0 }, + { offset[ 0 ] + 0, offset[ 1 ] + 2, offset[ 2 ] + 0 }, + { offset[ 0 ] + 2, offset[ 1 ] + 2, offset[ 2 ] + 0 }, + }; + + final List< long[] > blocks = Grids.collectAllOffsets( Intervals.minAsLongArray( interval ), Intervals.maxAsLongArray( interval ), blockSize ); + Assert.assertEquals( blockMinima.length, blocks.size() ); + for ( int i = 0; i < blockMinima.length; ++i ) + Assert.assertArrayEquals( blockMinima[ i ], blocks.get( i ) ); + + final long[][] gridPositions = { + { 0, 0, 0 }, + { 1, 0, 0 }, + { 0, 1, 0 }, + { 1, 1, 0 } + }; + + final Interval[] intervals = { + new FinalInterval( blockMinima[ 0 ], add( blockMinima[ 0 ], new long[] { 1, 1, 4 } ) ), + new FinalInterval( blockMinima[ 1 ], add( blockMinima[ 1 ], new long[] { 0, 1, 4 } ) ), + new FinalInterval( blockMinima[ 2 ], add( blockMinima[ 2 ], new long[] { 1, 1, 4 } ) ), + new FinalInterval( blockMinima[ 3 ], add( blockMinima[ 3 ], new long[] { 0, 1, 4 } ) ) + }; + + final List< Pair< Interval, long[] > > blocksAndGridPositions = Grids.collectAllContainedIntervalsWithGridPositions( Intervals.minAsLongArray( interval ), Intervals.maxAsLongArray( interval ), blockSize ); + Assert.assertEquals( intervals.length, blocksAndGridPositions.size() ); + for ( int i = 0; i < intervals.length; ++i ) + { + final Interval block = blocksAndGridPositions.get( i ).getA(); + // if both contain each other they are equal + Assert.assertTrue( Intervals.contains( block, intervals[ i ] ) && Intervals.contains( intervals[ i ], block ) ); + + final long[] position = blocksAndGridPositions.get( i ).getB(); + Assert.assertArrayEquals( gridPositions[ i ], position ); + } + } + + @Test + public void testRandomAccess() + { + final ArrayImg< BitType, LongArray > data = ArrayImgs.bits( 1, 2, 3, 4, 5 ); + data.forEach( BitType::setZero ); + final IntervalView< BitType > translated = Views.translate( data, 5, 4, 3, 2, 1 ); + final RandomAccess< BitType > access = translated.randomAccess(); + final UnsignedIntType count = new UnsignedIntType(); + Grids.forEachOffset( + Intervals.minAsLongArray( translated ), + Intervals.maxAsLongArray( translated ), + IntStream.generate( () -> 1 ).limit( data.numDimensions() ).toArray(), + access, + () -> { + count.inc(); + access.get().setOne(); + } ); + + Assert.assertEquals( Intervals.numElements( data ), count.get() ); + data.forEach( v -> Assert.assertTrue( v.get() ) ); + } + + private static long[] add( final long[] arr, final long[] add ) + { + final long[] result = new long[ arr.length ]; + for ( int d = 0; d < result.length; ++d ) + result[ d ] = arr[ d ] + add[ d ]; + return result; + } + + @Test( expected = AssertionError.class ) + public void testZeroBlockSizeAssertion() + { + Grids.forEachOffset( new long[] { 0, 0 }, new long[] { 1, 1 }, new int[] { 1, 0 }, arr -> {} ); + } + + @Test( expected = AssertionError.class ) + public void testMinDimensionalityMismatchAssertion() + { + Grids.forEachOffset( new long[] { 0, 0, 0 }, new long[] { 1, 1 }, new int[] { 1, 1 }, arr -> {} ); + } + + @Test( expected = AssertionError.class ) + public void testMaxDimensionalityMismatchAssertion() + { + Grids.forEachOffset( new long[] { 0, 0 }, new long[] { 1, 1, 1 }, new int[] { 1, 1 }, arr -> {} ); + } + + @Test( expected = AssertionError.class ) + public void testMinLargerThanMaxAssertion() + { + Grids.forEachOffset( new long[] { 0, 2 }, new long[] { 1, 1 }, new int[] { 1, 1 }, arr -> {} ); + } + +} diff --git a/src/test/java/net/imglib2/algorithm/util/ParallelizeOverBlocksTest.java b/src/test/java/net/imglib2/algorithm/util/ParallelizeOverBlocksTest.java new file mode 100644 index 000000000..d477074fb --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/util/ParallelizeOverBlocksTest.java @@ -0,0 +1,187 @@ +/* + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2016 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package net.imglib2.algorithm.util; + +import java.util.List; +import java.util.Random; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import gnu.trove.set.hash.TDoubleHashSet; +import net.imglib2.RandomAccessible; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.gauss3.Gauss3; +import net.imglib2.algorithm.gradient.PartialDerivative; +import net.imglib2.img.array.ArrayCursor; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.img.basictypeaccess.array.DoubleArray; +import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.util.Intervals; +import net.imglib2.view.Views; + +/** + * + * @author Philipp Hanslovsky + * + */ +public class ParallelizeOverBlocksTest +{ + + private final Random rng = new Random(); + + private final long[] imgSize = { 10, 9, 8, 7 }; + + private final int[] blockSize = { 4, 5, 4, 7 }; + + private final int numBlocks = 3 * 2 * 2 * 1; + + private final ArrayImg< DoubleType, DoubleArray > data = ArrayImgs.doubles( imgSize ); + + private final int numThreads = Math.max( Math.min( Runtime.getRuntime().availableProcessors() - 1, 3 ), 3 ); + + private final ExecutorService es = Executors.newFixedThreadPool( numThreads ); + + private final int numTasks = 3 * numThreads; + + @Before + public void setup() + { + data.forEach( d -> d.set( rng.nextDouble() ) ); + } + + @After + public void tearDown() + { + this.es.shutdown(); + } + + @Test + public void ensureDataIsValid() + { + final TDoubleHashSet values = new TDoubleHashSet(); + data.forEach( v -> values.add( v.getRealDouble() ) ); + Assert.assertTrue( values.size() > 0 ); + } + + @Test + public void testGaussian() throws InterruptedException, ExecutionException + { + final double sigma = 2; + + final List< Void > results = test( ofThrowingBiConsumer( ( ra, rai ) -> Gauss3.gauss( sigma, ra, rai ) ) ); + Assert.assertEquals( numBlocks, results.size() ); + } + + @Test + public void testGradient() throws InterruptedException, ExecutionException + { + for ( int d = 0; d < data.numDimensions(); ++d ) + { + final int finalD = d; + final List< Void > results = test( ofBiConsumer( ( ra, rai ) -> PartialDerivative.gradientCentralDifference( ra, rai, finalD ) ) ); + Assert.assertEquals( numBlocks, results.size() ); + } + } + + private < T > List< T > test( + final BiFunction< RandomAccessible< DoubleType >, RandomAccessibleInterval< DoubleType >, T > func ) + throws InterruptedException, ExecutionException + { + + final RandomAccessible< DoubleType > extended = Views.extendBorder( data ); + final ArrayImg< DoubleType, DoubleArray > comparison = ArrayImgs.doubles( imgSize ); + final ArrayImg< DoubleType, DoubleArray > reference = ArrayImgs.doubles( imgSize ); + + func.apply( extended, reference ); + + final List< T > results = ParallelizeOverBlocks.parallelizeAndWait( + interval -> func.apply( extended, Views.interval( comparison, interval ) ), + Intervals.minAsLongArray( data ), + Intervals.maxAsLongArray( data ), + blockSize, + es, + numTasks ); + + for ( ArrayCursor< DoubleType > ref = reference.cursor(), comp = comparison.cursor(); ref.hasNext(); ) + Assert.assertEquals( ref.next().getRealDouble(), comp.next().getRealDouble(), 0.0 ); + + return results; + } + + @FunctionalInterface + private static interface ThrowingBiConsumer< T, U > + { + public void accept( T t, U u ) throws Exception; + + public default BiConsumer< T, U > asBiConsumer() + { + return ( t, u ) -> { + try + { + this.accept( t, u ); + } + catch ( final Exception e ) + { + throw new RuntimeException( e ); + } + }; + } + } + + private static < T, U > BiFunction< T, U, Void > ofThrowingBiConsumer( + final ThrowingBiConsumer< T, U > consumer ) + { + return ofBiConsumer( consumer.asBiConsumer() ); + } + + private static < T, U > BiFunction< T, U, Void > ofBiConsumer( + final BiConsumer< T, U > consumer ) + { + return ( t, u ) -> { + consumer.accept( t, u ); + return null; + }; + } + +}