From 2dc4ebd7694fefff8a4e76ec22538f6ec1479805 Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Mon, 22 Jan 2018 12:02:34 -0500 Subject: [PATCH 1/4] Add utility methods to parallelize over blocks --- .../imglib2/algorithm/util/BlockOffsets.java | 132 +++++++++ .../algorithm/util/ParallelizeOverBlocks.java | 255 ++++++++++++++++++ .../util/ParallelizeOverBlocksTest.java | 187 +++++++++++++ 3 files changed, 574 insertions(+) create mode 100644 src/main/java/net/imglib2/algorithm/util/BlockOffsets.java create mode 100644 src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java create mode 100644 src/test/java/net/imglib2/algorithm/util/ParallelizeOverBlocksTest.java diff --git a/src/main/java/net/imglib2/algorithm/util/BlockOffsets.java b/src/main/java/net/imglib2/algorithm/util/BlockOffsets.java new file mode 100644 index 000000000..e2b365259 --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/util/BlockOffsets.java @@ -0,0 +1,132 @@ +/* + * #%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.Function; + +import net.imglib2.Interval; + +/** + * + * @author Philipp Hanslovsky + * + */ +public class BlockOffsets +{ + + /** + * + * 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 ); + } + + /** + * + * 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 ); + } + + /** + * + * 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<>(); + final int nDim = min.length; + final long[] offset = min.clone(); + for ( int d = 0; d < nDim; ) + { + final long[] target = offset.clone(); + blocks.add( func.apply( target ) ); + for ( d = 0; d < nDim; ++d ) + { + offset[ d ] += blockSize[ d ]; + if ( offset[ d ] <= max[ d ] ) + break; + else + offset[ d ] = 0; + } + } + return blocks; + } + +} 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..174349d87 --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java @@ -0,0 +1,255 @@ +/* + * #%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. + * @throws InterruptedException + * @throws ExecutionException + */ + 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. + * @throws InterruptedException + * @throws ExecutionException + */ + 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 = BlockOffsets.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. + * @throws InterruptedException + * @throws ExecutionException + */ + 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/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; + }; + } + +} From 7a0557c56d8c6f15b46dba0e3a489d9674f3a01f Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Thu, 15 Feb 2018 12:18:15 -0500 Subject: [PATCH 2/4] Rename to Grids, add tests, and add more convenience methods - Rename `BlockOffsets` to `Grids` to be closer to imglib2 namespace (`CellGrid` etc) - Add tests (detected a bug for min != 0) - new convenience methods for: - retrieving complete intervals (instead of just min) - retrieving complete intervals and position within cell grid --- .../imglib2/algorithm/util/BlockOffsets.java | 132 ------- .../net/imglib2/algorithm/util/Grids.java | 348 ++++++++++++++++++ .../algorithm/util/ParallelizeOverBlocks.java | 2 +- .../net/imglib2/algorithm/util/GridsTest.java | 119 ++++++ 4 files changed, 468 insertions(+), 133 deletions(-) delete mode 100644 src/main/java/net/imglib2/algorithm/util/BlockOffsets.java create mode 100644 src/main/java/net/imglib2/algorithm/util/Grids.java create mode 100644 src/test/java/net/imglib2/algorithm/util/GridsTest.java diff --git a/src/main/java/net/imglib2/algorithm/util/BlockOffsets.java b/src/main/java/net/imglib2/algorithm/util/BlockOffsets.java deleted file mode 100644 index e2b365259..000000000 --- a/src/main/java/net/imglib2/algorithm/util/BlockOffsets.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * #%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.Function; - -import net.imglib2.Interval; - -/** - * - * @author Philipp Hanslovsky - * - */ -public class BlockOffsets -{ - - /** - * - * 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 ); - } - - /** - * - * 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 ); - } - - /** - * - * 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<>(); - final int nDim = min.length; - final long[] offset = min.clone(); - for ( int d = 0; d < nDim; ) - { - final long[] target = offset.clone(); - blocks.add( func.apply( target ) ); - for ( d = 0; d < nDim; ++d ) - { - offset[ d ] += blockSize[ d ]; - if ( offset[ d ] <= max[ d ] ) - break; - else - offset[ d ] = 0; - } - } - return blocks; - } - -} 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..41773d76a --- /dev/null +++ b/src/main/java/net/imglib2/algorithm/util/Grids.java @@ -0,0 +1,348 @@ +/* + * #%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.Function; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.util.Intervals; +import net.imglib2.util.Pair; +import net.imglib2.util.ValuePair; + +/** + * + * @author Philipp Hanslovsky + * + */ +public class Grids +{ + /** + * + * 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 ); + } + + /** + * + * 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 ); + } + + /** + * + * 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<>(); + final int nDim = min.length; + final long[] offset = min.clone(); + for ( int d = 0; d < nDim; ) + { + final long[] target = offset.clone(); + blocks.add( func.apply( target ) ); + for ( d = 0; d < nDim; ++d ) + { + offset[ d ] += blockSize[ d ]; + if ( offset[ d ] <= max[ d ] ) + break; + else + offset[ d ] = min[ d ]; + } + } + 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 + * @param interval + */ + 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 index 174349d87..64b8d5efe 100644 --- a/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java +++ b/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java @@ -168,7 +168,7 @@ public static < T > List< Future< List< T > > > parallelize( final ExecutorService es, final int numTasks ) { - final List< Interval > blocks = BlockOffsets.collectAllOffsets( min, max, blockSize, blockMin -> { + 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 ] ); 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..ca5f44727 --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/util/GridsTest.java @@ -0,0 +1,119 @@ +package net.imglib2.algorithm.util; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.util.Intervals; +import net.imglib2.util.Pair; + +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 ); + } + } + + 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; + } + +} From b6b33e2ae0faffc4ecc43413536e2404e58d7f57 Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Fri, 9 Mar 2018 15:16:51 -0500 Subject: [PATCH 3/4] Generalize interface to allow aribtrary operations for all offsets in grid. This addresses the conversation in #53 --- .../net/imglib2/algorithm/util/Grids.java | 176 ++++++++++++++++-- .../net/imglib2/algorithm/util/GridsTest.java | 55 ++++++ 2 files changed, 214 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/imglib2/algorithm/util/Grids.java b/src/main/java/net/imglib2/algorithm/util/Grids.java index 41773d76a..c8fb69896 100644 --- a/src/main/java/net/imglib2/algorithm/util/Grids.java +++ b/src/main/java/net/imglib2/algorithm/util/Grids.java @@ -37,10 +37,14 @@ 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; @@ -52,6 +56,158 @@ */ 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 + * {@link 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 @@ -125,7 +281,7 @@ public static List< Interval > collectAllContainedIntervals( final long[] min, f */ public static List< long[] > collectAllOffsets( final long[] dimensions, final int[] blockSize ) { - return collectAllOffsets( dimensions, blockSize, block -> block ); + return collectAllOffsets( dimensions, blockSize, block -> block.clone() ); } /** @@ -157,7 +313,7 @@ public static < T > List< T > collectAllOffsets( final long[] dimensions, final */ public static List< long[] > collectAllOffsets( final long[] min, final long[] max, final int[] blockSize ) { - return collectAllOffsets( min, max, blockSize, block -> block ); + return collectAllOffsets( min, max, blockSize, block -> block.clone() ); } /** @@ -176,21 +332,7 @@ public static List< long[] > collectAllOffsets( final long[] min, final long[] m 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<>(); - final int nDim = min.length; - final long[] offset = min.clone(); - for ( int d = 0; d < nDim; ) - { - final long[] target = offset.clone(); - blocks.add( func.apply( target ) ); - for ( d = 0; d < nDim; ++d ) - { - offset[ d ] += blockSize[ d ]; - if ( offset[ d ] <= max[ d ] ) - break; - else - offset[ d ] = min[ d ]; - } - } + forEachOffset( min, max, blockSize, offset -> blocks.add( func.apply( offset ) ) ); return blocks; } diff --git a/src/test/java/net/imglib2/algorithm/util/GridsTest.java b/src/test/java/net/imglib2/algorithm/util/GridsTest.java index ca5f44727..9c482ab5c 100644 --- a/src/test/java/net/imglib2/algorithm/util/GridsTest.java +++ b/src/test/java/net/imglib2/algorithm/util/GridsTest.java @@ -1,14 +1,23 @@ 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 { @@ -108,6 +117,28 @@ public void testWithOffset() } } + @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 ]; @@ -116,4 +147,28 @@ private static long[] add( final long[] arr, final long[] add ) 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 -> {} ); + } + } From b62fdcbfd18b5620eeb2e0e4e0e5d97d4c7713d9 Mon Sep 17 00:00:00 2001 From: Philipp Hanslovsky Date: Wed, 18 Apr 2018 16:00:38 -0400 Subject: [PATCH 4/4] Fix JavaDoc --- src/main/java/net/imglib2/algorithm/util/Grids.java | 3 +-- .../net/imglib2/algorithm/util/ParallelizeOverBlocks.java | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/net/imglib2/algorithm/util/Grids.java b/src/main/java/net/imglib2/algorithm/util/Grids.java index c8fb69896..68266f961 100644 --- a/src/main/java/net/imglib2/algorithm/util/Grids.java +++ b/src/main/java/net/imglib2/algorithm/util/Grids.java @@ -110,7 +110,7 @@ public static interface GetForDimension /** * Execute {@code runAtOffset} for each offset of a grid defined by * {@code min}, {@code max}, and {@code blockSize}. The offset object - * {@link p} must be provided by the caller. + * {@code p} must be provided by the caller. * * @param min * @param max @@ -437,7 +437,6 @@ public GetGridCoordinates( final int[] blockSize, final Interval interval ) * using {@code min = 0}. * * @param blockSize - * @param interval */ public GetGridCoordinates( final int[] blockSize ) { diff --git a/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java b/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java index 64b8d5efe..b2adcedd9 100644 --- a/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java +++ b/src/main/java/net/imglib2/algorithm/util/ParallelizeOverBlocks.java @@ -97,8 +97,6 @@ public static < T > List< T > parallelizeAndWait( * @param numTasks * @return List of futures of the submitted tasks. Each future contains a * list of results. - * @throws InterruptedException - * @throws ExecutionException */ public static < T > List< Future< List< T > > > parallelize( final Function< Interval, T > func, @@ -157,8 +155,6 @@ public static < T > List< T > parallelizeAndWait( * @param numTasks * @return List of futures of the submitted tasks. Each future contains a * list of results. - * @throws InterruptedException - * @throws ExecutionException */ public static < T > List< Future< List< T > > > parallelize( final Function< Interval, T > func, @@ -218,8 +214,6 @@ public static < T > List< T > parallelizeAndWait( * @param numTasks * @return List of futures of the submitted tasks. Each future contains a * list of results. - * @throws InterruptedException - * @throws ExecutionException */ public static < T > List< Future< List< T > > > parallelize( final Function< Interval, T > func,