diff --git a/src/main/java/net/imglib2/algorithm/binary/Thresholder.java b/src/main/java/net/imglib2/algorithm/binary/Thresholder.java index 2e14f7ee6..2d3df79cb 100644 --- a/src/main/java/net/imglib2/algorithm/binary/Thresholder.java +++ b/src/main/java/net/imglib2/algorithm/binary/Thresholder.java @@ -33,16 +33,12 @@ */ package net.imglib2.algorithm.binary; -import java.util.Vector; +import java.util.function.BiConsumer; -import net.imglib2.Cursor; -import net.imglib2.RandomAccess; -import net.imglib2.converter.Converter; -import net.imglib2.exception.IncompatibleTypeException; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; -import net.imglib2.multithreading.Chunk; -import net.imglib2.multithreading.SimpleMultiThreading; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.Parallelization; import net.imglib2.type.Type; import net.imglib2.type.logic.BitType; @@ -73,95 +69,39 @@ public class Thresholder */ public static final < T extends Type< T > & Comparable< T >> Img< BitType > threshold( final Img< T > source, final T threshold, final boolean above, final int numThreads ) { - final ImgFactory< T > factory = source.factory(); - try - { - final ImgFactory< BitType > bitFactory = factory.imgFactory( new BitType() ); - final Img< BitType > target = bitFactory.create( source ); - - final Converter< T, BitType > converter; - if ( above ) - { - converter = new Converter< T, BitType >() - { - @Override - public void convert( final T input, final BitType output ) - { - output.set( input.compareTo( threshold ) > 0 ); - } - }; - } - else - { - converter = new Converter< T, BitType >() - { - @Override - public void convert( final T input, final BitType output ) - { - output.set( input.compareTo( threshold ) < 0 ); - } - }; - } - - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( target.size(), numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); + return Parallelization.runWithNumThreads( numThreads, + () -> threshold( source, threshold, above ) ); + } - if ( target.iterationOrder().equals( source.iterationOrder() ) ) - { - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Thresholder thread " + i ) - { - @Override - public void run() - { - final Cursor< BitType > cursorTarget = target.cursor(); - cursorTarget.jumpFwd( chunk.getStartPosition() ); - final Cursor< T > cursorSource = source.cursor(); - cursorSource.jumpFwd( chunk.getStartPosition() ); - for ( long steps = 0; steps < chunk.getLoopSize(); steps++ ) - { - cursorTarget.fwd(); - cursorSource.fwd(); - converter.convert( cursorSource.get(), cursorTarget.get() ); - } - } - }; - } - } - else - { - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Thresholder thread " + i ) - { - @Override - public void run() - { - final Cursor< BitType > cursorTarget = target.cursor(); - cursorTarget.jumpFwd( chunk.getStartPosition() ); - final RandomAccess< T > ra = source.randomAccess( target ); - for ( long steps = 0; steps < chunk.getLoopSize(); steps++ ) - { - cursorTarget.fwd(); - ra.setPosition( cursorTarget ); - converter.convert( ra.get(), cursorTarget.get() ); - } - } - }; - } - } + /** + * Returns a new boolean {@link Img} generated by thresholding the values of + * the source image. + * + * @param source + * the image to threshold. + * @param threshold + * the threshold. + * @param above + * if {@code true}, the target value will be true for source + * values above the threshold, {@code false} otherwise. + * @return a new {@link Img} of type {@link BitType} and of same dimension + * that the source image. + */ + public static < T extends Type< T > & Comparable< T > > Img< BitType > threshold( Img< T > source, T threshold, boolean above ) + { + final ImgFactory< BitType > factory = source.factory().imgFactory( new BitType() ); + final Img< BitType > target = factory.create( source ); + final BiConsumer< T, BitType > converter = getThresholdConverter( threshold, above ); + LoopBuilder.setImages( source, target ).multiThreaded().forEachPixel( converter ); + return target; + } - SimpleMultiThreading.startAndJoin( threads ); - return target; - } - catch ( final IncompatibleTypeException e ) - { - e.printStackTrace(); - return null; - } + private static < T extends Type< T > & Comparable< T > > BiConsumer< T, BitType > getThresholdConverter( T threshold, boolean above ) + { + if ( above ) + return ( input, output ) -> output.set( input.compareTo( threshold ) > 0 ); + else + return ( input, output ) -> output.set( input.compareTo( threshold ) < 0 ); } } diff --git a/src/main/java/net/imglib2/algorithm/dog/DifferenceOfGaussian.java b/src/main/java/net/imglib2/algorithm/dog/DifferenceOfGaussian.java index ec163a7d9..9d3eaa9db 100644 --- a/src/main/java/net/imglib2/algorithm/dog/DifferenceOfGaussian.java +++ b/src/main/java/net/imglib2/algorithm/dog/DifferenceOfGaussian.java @@ -33,26 +33,20 @@ */ package net.imglib2.algorithm.dog; -import java.util.ArrayList; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -import net.imglib2.Cursor; -import net.imglib2.IterableInterval; -import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.gauss3.Gauss3; -import net.imglib2.exception.IncompatibleTypeException; import net.imglib2.img.Img; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.Parallelization; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.NumericType; import net.imglib2.util.Util; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; +import java.util.concurrent.ExecutorService; + /** * Compute Difference-of-Gaussian of a {@link RandomAccessible}. * @@ -66,7 +60,7 @@ public class DifferenceOfGaussian * of sigmaLarger (where {@code sigmaLarger > sigmaSmaller}). *

* Creates an appropriate temporary image and calls - * {@link #DoG(double[], double[], RandomAccessible, RandomAccessible, RandomAccessibleInterval, ExecutorService)} + * {@link #DoG(double[], double[], RandomAccessible, RandomAccessible, RandomAccessibleInterval)} * . *

* @@ -80,21 +74,18 @@ public class DifferenceOfGaussian * convolution). * @param dog * the Difference-of-Gaussian result image. - * @param service - * service providing threads for multi-threading */ public static < I extends NumericType< I >, T extends NumericType< T > & NativeType< T > > void DoG( final double[] sigmaSmaller, final double[] sigmaLarger, final RandomAccessible< I > input, - final RandomAccessibleInterval< T > dog, - final ExecutorService service ) + final RandomAccessibleInterval< T > dog ) { final T type = Util.getTypeFromInterval( dog ); final Img< T > g1 = Util.getArrayOrCellImgFactory( dog, type ).create( dog ); final long[] translation = new long[ dog.numDimensions() ]; dog.min( translation ); - DoG( sigmaSmaller, sigmaLarger, input, Views.translate( g1, translation ), dog, service ); + DoG( sigmaSmaller, sigmaLarger, input, Views.translate( g1, translation ), dog ); } /** @@ -115,88 +106,59 @@ public static < I extends NumericType< I >, T extends NumericType< T > & NativeT * dog result image. * @param dog * the Difference-of-Gaussian result image. - * @param service - * how many threads to use for the computation. */ public static < I extends NumericType< I >, T extends NumericType< T > & NativeType< T > > void DoG( final double[] sigmaSmaller, final double[] sigmaLarger, final RandomAccessible< I > input, final RandomAccessible< T > tmp, + final RandomAccessibleInterval< T > dog ) + { + final IntervalView< T > tmpInterval = Views.interval( tmp, dog ); + Gauss3.gauss( sigmaSmaller, input, tmpInterval ); + Gauss3.gauss( sigmaLarger, input, dog ); + LoopBuilder.setImages( dog, tmpInterval ).multiThreaded().forEachPixel( ( d, t ) -> d.sub( t ) ); + } + + /** + * @deprecated Please use: + * + *

+ * {@code Parallelization.withExecutor( service ).run( () -> DoG( sigmaSmaller, sigmaLarger, input, dog ) )} + * + * @see Parallelization + */ + public static < I extends NumericType< I >, T extends NumericType< T > & NativeType< T > > void DoG( + final double[] sigmaSmaller, + final double[] sigmaLarger, + final RandomAccessible< I > input, final RandomAccessibleInterval< T > dog, final ExecutorService service ) { - final IntervalView< T > tmpInterval = Views.interval( tmp, dog ); - try - { - Gauss3.gauss( sigmaSmaller, input, tmpInterval, service ); - Gauss3.gauss( sigmaLarger, input, dog, service ); - } - catch ( final IncompatibleTypeException e ) - { - e.printStackTrace(); - } - final IterableInterval< T > dogIterable = Views.iterable( dog ); - final IterableInterval< T > tmpIterable = Views.iterable( tmpInterval ); - final long size = dogIterable.size(); - // FIXME find better heuristic? - final int numThreads = Runtime.getRuntime().availableProcessors(); - final int numTasks = numThreads <= 1 ? 1 : numThreads * 20; - final long taskSize = size / numTasks; - final ArrayList< Future< Void > > futures = new ArrayList<>(); - for ( int taskNum = 0; taskNum < numTasks; ++taskNum ) - { - final long fromIndex = taskNum * taskSize; - final long thisTaskSize = ( taskNum == numTasks - 1 ) ? size - fromIndex : taskSize; - if ( dogIterable.iterationOrder().equals( tmpIterable.iterationOrder() ) ) - futures.add( service.submit( new Callable< Void >() - { - @Override - public Void call() - { - final Cursor< T > dogCursor = dogIterable.cursor(); - final Cursor< T > tmpCursor = tmpIterable.cursor(); - dogCursor.jumpFwd( fromIndex ); - tmpCursor.jumpFwd( fromIndex ); - for ( int i = 0; i < thisTaskSize; ++i ) - dogCursor.next().sub( tmpCursor.next() ); - return null; - } - } ) ); - else - futures.add( service.submit( new Callable< Void >() - { - @Override - public Void call() - { - final Cursor< T > dogCursor = dogIterable.localizingCursor(); - final RandomAccess< T > tmpAccess = tmpInterval.randomAccess(); - dogCursor.jumpFwd( fromIndex ); - for ( int i = 0; i < thisTaskSize; ++i ) - { - final T o = dogCursor.next(); - tmpAccess.setPosition( dogCursor ); - o.sub( tmpAccess.get() ); - } - return null; - } - } ) ); - } - for ( final Future< Void > f : futures ) - { - try - { - f.get(); - } - catch ( final InterruptedException e ) - { - e.printStackTrace(); - } - catch ( final ExecutionException e ) - { - e.printStackTrace(); - } - } + Parallelization.runWithExecutor( service, + () -> DoG( sigmaSmaller, sigmaLarger, input, dog ) + ); + } + + /** + * @deprecated Please use: + * + *

+ * {@code Parallelization.withExecutor( service ). run( () -> DoG( sigmaSmaller, sigmaLarger, input, tmp, dog ) ); } + * + * @see Parallelization + */ + public static < I extends NumericType< I >, T extends NumericType< T > & NativeType< T > > void DoG( + final double[] sigmaSmaller, + final double[] sigmaLarger, + final RandomAccessible< I > input, + final RandomAccessible< T > tmp, + final RandomAccessibleInterval< T > dog, + final ExecutorService service ) + { + Parallelization.runWithExecutor( service, + () -> DoG( sigmaSmaller, sigmaLarger, input, tmp, dog ) + ); } /** diff --git a/src/main/java/net/imglib2/algorithm/gradient/PartialDerivative.java b/src/main/java/net/imglib2/algorithm/gradient/PartialDerivative.java index 343ea5da5..e4a1d3188 100644 --- a/src/main/java/net/imglib2/algorithm/gradient/PartialDerivative.java +++ b/src/main/java/net/imglib2/algorithm/gradient/PartialDerivative.java @@ -11,13 +11,13 @@ * %% * 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 @@ -34,21 +34,16 @@ package net.imglib2.algorithm.gradient; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import net.imglib2.Cursor; -import net.imglib2.FinalInterval; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.TaskExecutor; +import net.imglib2.parallel.Parallelization; +import net.imglib2.parallel.TaskExecutors; import net.imglib2.type.numeric.NumericType; import net.imglib2.util.Intervals; -import net.imglib2.view.IntervalView; import net.imglib2.view.Views; /** @@ -62,105 +57,67 @@ public class PartialDerivative { // nice version... /** - * Compute the partial derivative (central difference approximation) of source - * in a particular dimension: - * {@code d_f( x ) = ( f( x + e ) - f( x - e ) ) / 2}, - * where {@code e} is the unit vector along that dimension. - * - * @param source - * source image, has to provide valid data in the interval of the - * gradient image plus a one pixel border in dimension. - * @param gradient - * output image - * @param dimension - * along which dimension the partial derivatives are computed + * @deprecated + * Use {@link #gradientCentralDifference(RandomAccessible, RandomAccessibleInterval, int)} + * instead. */ + @Deprecated public static < T extends NumericType< T > > void gradientCentralDifference2( final RandomAccessible< T > source, final RandomAccessibleInterval< T > gradient, final int dimension ) { - final Cursor< T > front = Views.flatIterable( Views.interval( source, Intervals.translate( gradient, 1, dimension ) ) ).cursor(); - final Cursor< T > back = Views.flatIterable( Views.interval( source, Intervals.translate( gradient, -1, dimension ) ) ).cursor(); - - for ( final T t : Views.flatIterable( gradient ) ) - { - t.set( front.next() ); - t.sub( back.next() ); - t.mul( 0.5 ); - } + gradientCentralDifference( source, gradient, dimension ); } // parallel version... /** - * Compute the partial derivative (central difference approximation) of source - * in a particular dimension: - * {@code d_f( x ) = ( f( x + e ) - f( x - e ) ) / 2}, - * where {@code e} is the unit vector along that dimension. - * - * @param source - * source image, has to provide valid data in the interval of the - * gradient image plus a one pixel border in dimension. - * @param gradient - * output image - * @param dimension - * along which dimension the partial derivatives are computed - * @param nTasks - * Number of tasks for gradient computation. - * @param es - * {@link ExecutorService} providing workers for gradient - * computation. Service is managed (created, shutdown) by caller. + * @deprecated + * Use {@link #gradientCentralDifference(RandomAccessible, RandomAccessibleInterval, int)} + * instead. + *

+ * Read {@link Parallelization} to learn how to run the method multi-threaded. Here is an example: + *

+ *

+	 * {@code
+	 * TaskExecutor taskExecutor = TaskExecutors.forExecutorServiceAndNumTasks( executorService, numTasks );
+	 * Parallelization.runWithExecutor( taskExecutor, () -> {
+	 *     gradientCentralDerivativeParallel( source, result, dimension );
+	 * } );
+	 * }
+	 * 
*/ + @Deprecated public static < T extends NumericType< T > > void gradientCentralDifferenceParallel( final RandomAccessible< T > source, - final RandomAccessibleInterval< T > gradient, + final RandomAccessibleInterval< T > result, final int dimension, final int nTasks, - final ExecutorService es ) throws InterruptedException, ExecutionException + final ExecutorService es ) { - final int nDim = source.numDimensions(); - if ( nDim < 2 ) - { - gradientCentralDifference( source, gradient, dimension ); - return; - } - - long dimensionMax = Long.MIN_VALUE; - int dimensionArgMax = -1; - - for ( int d = 0; d < nDim; ++d ) - { - final long size = gradient.dimension( d ); - if ( d != dimension && size > dimensionMax ) - { - dimensionMax = size; - dimensionArgMax = d; - } - } - - final long stepSize = Math.max( dimensionMax / nTasks, 1 ); - final long stepSizeMinusOne = stepSize - 1; - final long min = gradient.min( dimensionArgMax ); - final long max = gradient.max( dimensionArgMax ); - - final ArrayList< Callable< Void > > tasks = new ArrayList<>(); - for ( long currentMin = min, minZeroBase = 0; minZeroBase < dimensionMax; currentMin += stepSize, minZeroBase += stepSize ) - { - final long currentMax = Math.min( currentMin + stepSizeMinusOne, max ); - final long[] mins = new long[ nDim ]; - final long[] maxs = new long[ nDim ]; - gradient.min( mins ); - gradient.max( maxs ); - mins[ dimensionArgMax ] = currentMin; - maxs[ dimensionArgMax ] = currentMax; - final IntervalView< T > currentInterval = Views.interval( gradient, new FinalInterval( mins, maxs ) ); - tasks.add( () -> { - gradientCentralDifference( source, currentInterval, dimension ); - return null; - } ); - } - - final List< Future< Void > > futures = es.invokeAll( tasks ); + TaskExecutor taskExecutor = TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ); + Parallelization.runWithExecutor( taskExecutor, () -> { + gradientCentralDerivativeParallel( source, result, dimension ); + } ); + } - for ( final Future< Void > f : futures ) - f.get(); + /** + * @deprecated + * Use {@link #gradientCentralDifference(RandomAccessible, RandomAccessibleInterval, int)} + * instead. + *

+ * Read {@link Parallelization} to learn how to run the method multi-threaded. Here is an example: + *

+ *

+	 * {@code
+	 * TaskExecutor taskExecutor = TaskExecutors.forExecutorServiceAndNumTasks( executorService, numTasks );
+	 * Parallelization.runWithExecutor( taskExecutor, () -> {
+	 *     gradientCentralDerivativeParallel( source, result, dimension );
+	 * } );
+	 * }
+	 * 
+ */ + private static > void gradientCentralDerivativeParallel( RandomAccessible source, + RandomAccessibleInterval result, int dimension ) + { + gradientCentralDifference( source, result, dimension ); } // fast version @@ -181,10 +138,10 @@ public static < T extends NumericType< T > > void gradientCentralDifferenceParal public static < T extends NumericType< T > > void gradientCentralDifference( final RandomAccessible< T > source, final RandomAccessibleInterval< T > result, final int dimension ) { - final RandomAccessibleInterval< T > back = Views.interval( source, Intervals.translate( result, -1, dimension ) ); - final RandomAccessibleInterval< T > front = Views.interval( source, Intervals.translate( result, 1, dimension ) ); + final RandomAccessibleInterval back = Views.interval( source, Intervals.translate( result, -1, dimension ) ); + final RandomAccessibleInterval front = Views.interval( source, Intervals.translate( result, 1, dimension ) ); - LoopBuilder.setImages( result, back, front ).forEachPixel( ( r, b, f ) -> { + LoopBuilder.setImages( result, back, front ).multiThreaded().forEachPixel( ( r, b, f ) -> { r.set( f ); r.sub( b ); r.mul( 0.5 ); @@ -207,7 +164,7 @@ public static < T extends NumericType< T > > void gradientBackwardDifference( fi final RandomAccessibleInterval< T > back = Views.interval( source, Intervals.translate( result, -1, dimension ) ); final RandomAccessibleInterval< T > front = Views.interval( source, result ); - LoopBuilder.setImages( result, back, front ).forEachPixel( ( r, b, f ) -> { + LoopBuilder.setImages( result, back, front ).multiThreaded().forEachPixel( ( r, b, f ) -> { r.set( f ); r.sub( b ); } ); @@ -217,7 +174,7 @@ public static < T extends NumericType< T > > void gradientBackwardDifference( fi * Compute the forward difference of source in a particular dimension: * {@code d_f( x ) = ( f( x + e ) - f( x ) )} * where {@code e} is the unit vector along that dimension - + * @param source source image, has to provide valid data in the interval of * the gradient image plus a one pixel border in dimension. * @param result output image @@ -229,7 +186,7 @@ public static < T extends NumericType< T > > void gradientForwardDifference( fin final RandomAccessibleInterval< T > back = Views.interval( source, result ); final RandomAccessibleInterval< T > front = Views.interval( source, Intervals.translate( result, 1, dimension ) ); - LoopBuilder.setImages( result, back, front ).forEachPixel( ( r, b, f ) -> { + LoopBuilder.setImages( result, back, front ).multiThreaded().forEachPixel( ( r, b, f ) -> { r.set( f ); r.sub( b ); } ); diff --git a/src/main/java/net/imglib2/algorithm/labeling/ConnectedComponents.java b/src/main/java/net/imglib2/algorithm/labeling/ConnectedComponents.java index c578e21eb..95b64bf41 100644 --- a/src/main/java/net/imglib2/algorithm/labeling/ConnectedComponents.java +++ b/src/main/java/net/imglib2/algorithm/labeling/ConnectedComponents.java @@ -39,12 +39,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; -import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import net.imglib2.Cursor; import net.imglib2.FinalInterval; @@ -52,6 +48,7 @@ import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.iterator.IntervalIterator; +import net.imglib2.parallel.Parallelization; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.type.numeric.IntegerType; import net.imglib2.view.Views; @@ -99,17 +96,19 @@ public CollectNeighborLabelsFactory getFactory() * @param se * structuring element to use. 8-connected or 4-connected * (respectively n-dimensional analog) + * @param service + * service providing threads for multi-threading */ public static < T extends IntegerType< T >, L, I extends IntegerType< I > > void labelAllConnectedComponents( final RandomAccessible< T > input, final ImgLabeling< L, I > labeling, final Iterator< L > labelGenerator, - final StructuringElement se ) + final StructuringElement se, + final ExecutorService service ) { - final int numThreads = Runtime.getRuntime().availableProcessors(); - final ExecutorService service = Executors.newFixedThreadPool( numThreads ); - labelAllConnectedComponents( input, labeling, labelGenerator, se, service ); - service.shutdown(); + Parallelization.runWithExecutor( service, + () -> labelAllConnectedComponents( input, labeling, labelGenerator, se ) + ); } /** @@ -130,21 +129,18 @@ public static < T extends IntegerType< T >, L, I extends IntegerType< I > > void * @param se * structuring element to use. 8-connected or 4-connected * (respectively n-dimensional analog) - * @param service - * service providing threads for multi-threading */ public static < T extends IntegerType< T >, L, I extends IntegerType< I > > void labelAllConnectedComponents( final RandomAccessible< T > input, final ImgLabeling< L, I > labeling, final Iterator< L > labelGenerator, - final StructuringElement se, - final ExecutorService service ) + final StructuringElement se ) { final RandomAccessibleInterval< I > output = labeling.getIndexImg(); for ( final I i : Views.iterable( output ) ) i.setZero(); - final int numLabels = labelAllConnectedComponents( input, output, se, service ) + 1; + final int numLabels = labelAllConnectedComponents( input, output, se ) + 1; final ArrayList< Set< L > > labelSets = new ArrayList<>(); labelSets.add( Collections.emptySet() ); @@ -161,7 +157,6 @@ public static < T extends IntegerType< T >, L, I extends IntegerType< I > > void * *

* Note, that the {@code output} image must be cleared to 0! - *

* * @param input * input image with pixels > 0 belonging to foreground. @@ -170,19 +165,20 @@ public static < T extends IntegerType< T >, L, I extends IntegerType< I > > void * @param se * structuring element to use. 8-connected or 4-connected * (respectively n-dimensional analog) + * @param service + * service providing threads for multi-threading * @return the number of connected components (that is, the highest value * occurring in the output image. */ public static < T extends IntegerType< T >, L extends IntegerType< L > > int labelAllConnectedComponents( final RandomAccessible< T > input, final RandomAccessibleInterval< L > output, - final StructuringElement se ) + final StructuringElement se, + final ExecutorService service ) { - final int numThreads = Runtime.getRuntime().availableProcessors(); - final ExecutorService service = Executors.newFixedThreadPool( numThreads ); - final int result = labelAllConnectedComponents( input, output, se, service ); - service.shutdown(); - return result; + return Parallelization.runWithExecutor( service, + () -> labelAllConnectedComponents( input, output, se ) + ); } /** @@ -192,6 +188,7 @@ public static < T extends IntegerType< T >, L extends IntegerType< L > > int lab * *

* Note, that the {@code output} image must be cleared to 0! + *

* * @param input * input image with pixels > 0 belonging to foreground. @@ -200,16 +197,13 @@ public static < T extends IntegerType< T >, L extends IntegerType< L > > int lab * @param se * structuring element to use. 8-connected or 4-connected * (respectively n-dimensional analog) - * @param service - * service providing threads for multi-threading * @return the number of connected components (that is, the highest value * occurring in the output image. */ public static < T extends IntegerType< T >, L extends IntegerType< L > > int labelAllConnectedComponents( final RandomAccessible< T > input, final RandomAccessibleInterval< L > output, - final StructuringElement se, - final ExecutorService service ) + final StructuringElement se ) { final int n = output.numDimensions(); final int splitDim = n - 1; @@ -234,37 +228,14 @@ public static < T extends IntegerType< T >, L extends IntegerType< L > > int lab min[ splitDim ] += taskSize; } - final ArrayList< Future< ? > > futures = new ArrayList< Future< ? > >(); - for ( final Fragment< T, L > fragment : fragments ) - { - futures.add( service.submit( new Runnable() - { - @Override - public void run() - { - fragment.mark(); - } - } ) ); - } - getAllFutures( futures ); + Parallelization.getTaskExecutor().forEach( Arrays.asList( fragments ), Fragment::mark ); final TIntArrayList merged = mergeCanonicalLists( fragments ); for ( int i = 1; i < numTasks; ++i ) fragments[ i ].linkToPreviousFragment( fragments[ i - 1 ], merged ); final int numComponents = splitCanonicalLists( fragments, merged ); - for ( final Fragment< T, L > fragment : fragments ) - { - futures.add( service.submit( new Runnable() - { - @Override - public void run() - { - fragment.relabel(); - } - } ) ); - } - getAllFutures( futures ); + Parallelization.getTaskExecutor().forEach( Arrays.asList( fragments ), Fragment::relabel ); return numComponents; } @@ -481,26 +452,6 @@ private static < T extends IntegerType< T >, L extends IntegerType< L > > int sp return nextLabel - 1; } - private static void getAllFutures( final List< Future< ? > > futures ) - { - for ( final Future< ? > future : futures ) - { - try - { - future.get(); - } - catch ( final InterruptedException e ) - { - e.printStackTrace(); - } - catch ( final ExecutionException e ) - { - e.printStackTrace(); - } - } - futures.clear(); - } - private static interface CollectNeighborLabels< L extends IntegerType< L > > { public void collect( RandomAccess< L > la, final TIntArrayList neighborLabels, final long[] labelsMin, final long[] labelsMax ); diff --git a/src/main/java/net/imglib2/algorithm/linalg/eigen/TensorEigenValues.java b/src/main/java/net/imglib2/algorithm/linalg/eigen/TensorEigenValues.java index 8d3ed143a..3cfc45e20 100644 --- a/src/main/java/net/imglib2/algorithm/linalg/eigen/TensorEigenValues.java +++ b/src/main/java/net/imglib2/algorithm/linalg/eigen/TensorEigenValues.java @@ -34,24 +34,19 @@ package net.imglib2.algorithm.linalg.eigen; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import net.imglib2.Cursor; -import net.imglib2.FinalInterval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.Parallelization; +import net.imglib2.parallel.TaskExecutors; import net.imglib2.type.numeric.ComplexType; import net.imglib2.type.numeric.RealType; -import net.imglib2.view.IntervalView; import net.imglib2.view.Views; -import net.imglib2.view.composite.NumericComposite; -import net.imglib2.view.composite.RealComposite; +import net.imglib2.view.composite.CompositeIntervalView; +import net.imglib2.view.composite.GenericComposite; /** * @@ -286,71 +281,10 @@ public static < T extends RealType< T >, U extends ComplexType< U > > RandomAcce final ExecutorService es ) { - assert nTasks > 0: "Passed nTasks < 1"; - - final int tensorDims = tensor.numDimensions(); - - long dimensionMax = Long.MIN_VALUE; - int dimensionArgMax = -1; - - for ( int d = 0; d < tensorDims - 1; ++d ) - { - final long size = tensor.dimension( d ); - if ( size > dimensionMax ) - { - dimensionMax = size; - dimensionArgMax = d; - } - } - - final long stepSize = Math.max( dimensionMax / nTasks, 1 ); - final long stepSizeMinusOne = stepSize - 1; - final long max = dimensionMax - 1; - - final ArrayList< Callable< RandomAccessibleInterval< U > > > tasks = new ArrayList<>(); - for ( long currentMin = 0; currentMin < dimensionMax; currentMin += stepSize ) - { - final long currentMax = Math.min( currentMin + stepSizeMinusOne, max ); - final long[] minT = new long[ tensorDims ]; - final long[] maxT = new long[ tensorDims ]; - final long[] minE = new long[ tensorDims ]; - final long[] maxE = new long[ tensorDims ]; - tensor.min( minT ); - tensor.max( maxT ); - eigenvalues.min( minE ); - eigenvalues.max( maxE ); - minE[ dimensionArgMax ] = minT[ dimensionArgMax ] = currentMin; - maxE[ dimensionArgMax ] = maxT[ dimensionArgMax ] = currentMax; - final IntervalView< T > currentTensor = Views.interval( tensor, new FinalInterval( minT, maxT ) ); - final IntervalView< U > currentEigenvalues = Views.interval( eigenvalues, new FinalInterval( minE, maxE ) ); - tasks.add( () -> calculateEigenValuesImpl( currentTensor, currentEigenvalues, ev.copy() ) ); - } - - - try - { - final List< Future< RandomAccessibleInterval< U > > > futures = es.invokeAll( tasks ); - for ( final Future< RandomAccessibleInterval< U > > f : futures ) - try - { - f.get(); - } - catch ( final ExecutionException e ) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - catch ( final InterruptedException e ) - { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - return eigenvalues; - - + assert nTasks > 0 : "Passed nTasks < 1"; + return Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> calculateEigenValues( tensor, eigenvalues, ev ) ); } private static < T extends RealType< T >, U extends ComplexType< U > > RandomAccessibleInterval< U > calculateEigenValuesImpl( @@ -358,10 +292,15 @@ private static < T extends RealType< T >, U extends ComplexType< U > > RandomAcc final RandomAccessibleInterval< U > eigenvalues, final EigenValues< T, U > ev ) { - final Cursor< RealComposite< T > > m = Views.iterable( Views.collapseReal( tensor ) ).cursor(); - final Cursor< NumericComposite< U > > e = Views.iterable( Views.collapseNumeric( eigenvalues ) ).cursor(); - while ( m.hasNext() ) - ev.compute( m.next(), e.next() ); + RandomAccessibleInterval< ? extends GenericComposite< T > > tensorVectors = Views.collapse( tensor ); + CompositeIntervalView< U, ? extends GenericComposite< U > > eigenvaluesVectors = Views.collapse( eigenvalues ); + LoopBuilder.setImages( tensorVectors, eigenvaluesVectors ) + .multiThreaded() + .forEachChunk( chunk -> { + EigenValues< T, U > copy = ev.copy(); + chunk.forEachPixel( copy::compute ); + return null; + } ); return eigenvalues; } diff --git a/src/main/java/net/imglib2/algorithm/localextrema/LocalExtrema.java b/src/main/java/net/imglib2/algorithm/localextrema/LocalExtrema.java index f2b1bce5e..af8694fd3 100644 --- a/src/main/java/net/imglib2/algorithm/localextrema/LocalExtrema.java +++ b/src/main/java/net/imglib2/algorithm/localextrema/LocalExtrema.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -42,20 +43,22 @@ import java.util.concurrent.Future; import java.util.stream.IntStream; import java.util.stream.LongStream; - -import net.imglib2.Cursor; -import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.Localizable; import net.imglib2.Point; +import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.Sampler; import net.imglib2.algorithm.neighborhood.Neighborhood; import net.imglib2.algorithm.neighborhood.RectangleShape; import net.imglib2.algorithm.neighborhood.Shape; +import net.imglib2.converter.readwrite.WriteConvertedRandomAccessible; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.Parallelization; +import net.imglib2.parallel.TaskExecutor; +import net.imglib2.parallel.TaskExecutors; import net.imglib2.util.ConstantUtils; -import net.imglib2.util.Intervals; import net.imglib2.util.ValuePair; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; @@ -306,7 +309,7 @@ public static < P, T > List< P > findLocalExtrema( * @param numTasks * Number of tasks for parallel execution * @param splitDim - * Dimension along which input should be split for parallization + * ignored * @return {@link List} of extrema * @throws ExecutionException * @throws InterruptedException @@ -320,38 +323,8 @@ public static < P, T > List< P > findLocalExtrema( final int numTasks, final int splitDim ) throws InterruptedException, ExecutionException { - - final long[] min = Intervals.minAsLongArray( interval ); - final long[] max = Intervals.maxAsLongArray( interval ); - - final long splitDimSize = interval.dimension( splitDim ); - final long splitDimMax = max[ splitDim ]; - final long splitDimMin = min[ splitDim ]; - final long taskSize = Math.max( splitDimSize / numTasks, 1 ); - - final ArrayList< Callable< List< P > > > tasks = new ArrayList<>(); - - for ( long start = splitDimMin, stop = splitDimMin + taskSize - 1; start <= splitDimMax; start += taskSize, stop += taskSize ) - { - final long s = start; - // need max here instead of dimension for constructor of - // FinalInterval - final long S = Math.min( stop, splitDimMax ); - tasks.add( () -> { - final long[] localMin = min.clone(); - final long[] localMax = max.clone(); - localMin[ splitDim ] = s; - localMax[ splitDim ] = S; - return findLocalExtrema( source, new FinalInterval( localMin, localMax ), localNeighborhoodCheck, shape ); - } ); - } - - final ArrayList< P > extrema = new ArrayList<>(); - final List< Future< List< P > > > futures = service.invokeAll( tasks ); - for ( final Future< List< P > > f : futures ) - extrema.addAll( f.get() ); - return extrema; - + TaskExecutor taskExecutor = TaskExecutors.forExecutorServiceAndNumTasks( service, numTasks ); + return Parallelization.runWithExecutor( taskExecutor, () -> findLocalExtrema( source, interval, localNeighborhoodCheck, shape ) ); } /** @@ -470,22 +443,30 @@ public static < P, T > List< P > findLocalExtrema( final LocalNeighborhoodCheck< P, T > localNeighborhoodCheck, final Shape shape ) { + WriteConvertedRandomAccessible< T, RandomAccess< T > > randomAccessible = new WriteConvertedRandomAccessible<>( source, sampler -> (RandomAccess< T >) sampler ); + RandomAccessibleInterval< RandomAccess< T > > centers = Views.interval( randomAccessible, interval); + RandomAccessibleInterval< Neighborhood< T > > neighborhoods = Views.interval( shape.neighborhoodsRandomAccessible( source ), interval ); + List< List< P > > extremas = LoopBuilder.setImages( centers, neighborhoods ).multiThreaded().forEachChunk( chunk -> { + List< P > extrema = new ArrayList<>(); + chunk.forEachPixel( ( center, neighborhood ) -> { + P p = localNeighborhoodCheck.check( center, neighborhood ); + if ( p != null ) + extrema.add( p ); + } ); + return extrema; + } ); + return concatenate( extremas ); + } - final IntervalView< T > sourceInterval = Views.interval( source, interval ); - - final ArrayList< P > extrema = new ArrayList<>(); - - final Cursor< T > center = Views.flatIterable( sourceInterval ).cursor(); - for ( final Neighborhood< T > neighborhood : shape.neighborhoods( sourceInterval ) ) - { - center.fwd(); - final P p = localNeighborhoodCheck.check( center, neighborhood ); - if ( p != null ) - extrema.add( p ); - } - - return extrema; - + private static < P > List

concatenate( List> lists ) + { + if(lists.size() == 1) + return lists.get( 0 ); + int size = lists.stream().mapToInt( List::size ).sum(); + List< P > result = new ArrayList<>( size ); + for ( List< P > list : lists ) + result.addAll( list ); + return result; } /** diff --git a/src/main/java/net/imglib2/algorithm/localextrema/SubpixelLocalization.java b/src/main/java/net/imglib2/algorithm/localextrema/SubpixelLocalization.java index a2168f244..93e497655 100644 --- a/src/main/java/net/imglib2/algorithm/localextrema/SubpixelLocalization.java +++ b/src/main/java/net/imglib2/algorithm/localextrema/SubpixelLocalization.java @@ -45,13 +45,18 @@ import Jama.LUDecomposition; import Jama.Matrix; + import net.imglib2.Interval; +import net.imglib2.FinalInterval; import net.imglib2.Localizable; import net.imglib2.Point; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RealPoint; import net.imglib2.RealPositionable; +import net.imglib2.loops.IntervalChunks; +import net.imglib2.parallel.Parallelization; +import net.imglib2.parallel.TaskExecutor; import net.imglib2.type.numeric.RealType; import net.imglib2.util.Intervals; @@ -60,7 +65,7 @@ * {@link #refinePeaks(List, RandomAccessible, Interval, boolean, int, boolean, float, boolean[], int)} * method to do this, but this has a lot of parameters. Therefore, this class * can also be instantiated to encapsulate the parameter settings. - * + * *

* A List {@link RefinedPeak} for the given list of {@link Localizable} is * computed by, for each peak, fitting a quadratic function to the image and @@ -69,7 +74,7 @@ * repeated at the corresponding integer coordinates. This is repeated to * convergence, for a maximum number of iterations, or until the integer * coordinates move out of the valid image. - * + * * @author Stephan Preibisch * @author Tobias Pietzsch */ @@ -94,8 +99,6 @@ public SubpixelLocalization( final int numDimensions ) // principally one can move in any dimension allowedToMoveInDim = new boolean[ numDimensions ]; Arrays.fill( allowedToMoveInDim, true ); - - numThreads = Runtime.getRuntime().availableProcessors(); } public void setAllowMaximaTolerance( final boolean allowMaximaTolerance ) @@ -165,14 +168,14 @@ public boolean getReturnInvalidPeaks() public int getNumThreads() { - return numThreads; + return numThreads == 0 ? Parallelization.getTaskExecutor().getParallelism() : numThreads; } /** * Refine a set of peaks to subpixel coordinates. Calls * {@link #refinePeaks(List, RandomAccessible, Interval, boolean, int, boolean, float, boolean[], int)} * with the parameters set to this object. - * + * * @param peaks * List of integer peaks. * @param img @@ -185,7 +188,13 @@ public int getNumThreads() */ public ArrayList< RefinedPeak< P > > process( final List< P > peaks, final RandomAccessible< T > img, final Interval validInterval ) { - return refinePeaks( peaks, img, validInterval, returnInvalidPeaks, maxNumMoves, allowMaximaTolerance, maximaTolerance, allowedToMoveInDim, numThreads ); + if ( numThreads != 0 ) + return Parallelization.runWithNumThreads( numThreads, + () -> refinePeaks( peaks, img, validInterval, returnInvalidPeaks, maxNumMoves, + allowMaximaTolerance, maximaTolerance, allowedToMoveInDim ) ); + else + return refinePeaks( peaks, img, validInterval, returnInvalidPeaks, maxNumMoves, + allowMaximaTolerance, maximaTolerance, allowedToMoveInDim ); } /** @@ -198,7 +207,7 @@ public ArrayList< RefinedPeak< P > > process( final List< P > peaks, final Rando * fit is repeated at the corresponding integer coordinates. This is * repeated to convergence, for a maximum number of iterations, or until the * integer coordinates move out of the valid image. - * + * * @param peaks * List of integer peaks. * @param img @@ -232,21 +241,13 @@ public static < T extends RealType< T >, P extends Localizable > ArrayList< Refi final int maxNumMoves, final boolean allowMaximaTolerance, final float maximaTolerance, final boolean[] allowedToMoveInDim, final int numThreads ) { - final int numPeaks = peaks.size(); - final int numTasks = numThreads <= 1 ? 1 : ( int ) Math.min( numPeaks, numThreads * 20 ); - final ExecutorService ex = Executors.newFixedThreadPool( numThreads ); - - // run - final ArrayList< RefinedPeak< P > > allRefinedPeaks = refinePeaks(peaks, img, validInterval, returnInvalidPeaks, maxNumMoves, allowMaximaTolerance, maximaTolerance, allowedToMoveInDim, numTasks, ex); - - // shutdown - ex.shutdown(); - - return allRefinedPeaks; + return Parallelization.runWithNumThreads( numThreads, + () -> refinePeaks( peaks, img, validInterval, returnInvalidPeaks, maxNumMoves, allowMaximaTolerance, maximaTolerance, + allowedToMoveInDim ) ); } /** - * Refine a set of peaks to subpixel coordinates. Multi-threaded version. + * Refine a set of peaks to subpixel coordinates. Single-threaded version. *

* A List {@link RefinedPeak} for the given list of {@link Localizable} is * computed by, for each peak, fitting a quadratic function to the image and @@ -255,7 +256,7 @@ public static < T extends RealType< T >, P extends Localizable > ArrayList< Refi * fit is repeated at the corresponding integer coordinates. This is * repeated to convergence, for a maximum number of iterations, or until the * integer coordinates move out of the valid image. - * + * * @param peaks * List of integer peaks. * @param img @@ -280,91 +281,44 @@ public static < T extends RealType< T >, P extends Localizable > ArrayList< Refi * @param allowedToMoveInDim * specifies, per dimension, whether the base location is allowed * to be moved in the iterative procedure. - * @param numTasks - * How many tasks to use for the computation. - * @param ex - * The executor for the computation. * @return refined list of peaks. */ public static < T extends RealType< T >, P extends Localizable > ArrayList< RefinedPeak< P > > refinePeaks( final List< P > peaks, final RandomAccessible< T > img, final Interval validInterval, final boolean returnInvalidPeaks, - final int maxNumMoves, final boolean allowMaximaTolerance, final float maximaTolerance, final boolean[] allowedToMoveInDim, - final int numTasks, final ExecutorService ex ) + final int maxNumMoves, final boolean allowMaximaTolerance, final float maximaTolerance, final boolean[] allowedToMoveInDim ) { final int numPeaks = peaks.size(); - final ArrayList< RefinedPeak< P > > allRefinedPeaks = new ArrayList<>( numPeaks ); if ( numPeaks == 0 ) - return allRefinedPeaks; + return new ArrayList<>(); - final int taskSize = numPeaks / numTasks; - final List< Callable< ArrayList< RefinedPeak< P > > > > tasks = new ArrayList<>( taskSize ); - for ( int taskNum = 0; taskNum < numTasks; ++taskNum ) - { - final int fromIndex = taskNum * taskSize; - final int toIndex = ( taskNum == numTasks - 1 ) ? numPeaks : fromIndex + taskSize; + TaskExecutor taskExecutor = Parallelization.getTaskExecutor(); - tasks.add( () -> refinePeaks( - peaks.subList( fromIndex, toIndex ), - img, validInterval, returnInvalidPeaks, maxNumMoves, allowMaximaTolerance, maximaTolerance, allowedToMoveInDim ) ); - } + List< Interval > chunks = IntervalChunks.chunkInterval( new FinalInterval( numPeaks ), taskExecutor.suggestNumberOfTasks() ); - try - { - final List< Future< ArrayList< RefinedPeak< P > > > > futures = ex.invokeAll( tasks ); - for ( final Future< ArrayList< RefinedPeak< P > > > future : futures ) - allRefinedPeaks.addAll( future.get() ); - } - catch ( final InterruptedException | ExecutionException e ) - { - e.printStackTrace(); - } + List< ArrayList< RefinedPeak< P > > > result = taskExecutor.forEachApply( chunks, chunk -> + refinePeaksChunk( + peaks.subList( ( int ) chunk.min( 0 ), ( int ) chunk.max( 0 ) + 1 ), + img, validInterval, returnInvalidPeaks, maxNumMoves, allowMaximaTolerance, + maximaTolerance, allowedToMoveInDim ) + ); - return allRefinedPeaks; + return concatenate( result ); } - /** - * Refine a set of peaks to subpixel coordinates. Single-threaded version. - *

- * A List {@link RefinedPeak} for the given list of {@link Localizable} is - * computed by, for each peak, fitting a quadratic function to the image and - * computing the subpixel coordinates of the extremum. This is an iterative - * procedure. If the extremum is shifted more than 0.5 in one or more the - * fit is repeated at the corresponding integer coordinates. This is - * repeated to convergence, for a maximum number of iterations, or until the - * integer coordinates move out of the valid image. - * - * @param peaks - * List of integer peaks. - * @param img - * Pixel values. - * @param validInterval - * In which interval the {@code img} contains valid pixels. - * If null, an infinite {@code img} is assumed. Integer - * peaks must lie within a 1-pixel border of this interval. - * @param returnInvalidPeaks - * Whether (invalid) {@link RefinedPeak} should be created for - * peaks where the fitting procedure did not converge. - * @param maxNumMoves - * maximum number of iterations for each peak. - * @param allowMaximaTolerance - * If we allow an increasing maxima tolerance we will not change - * the base position that easily. Sometimes it simply jumps from - * left to right and back, because it is 4.51 (i.e. goto 5), then - * 4.49 (i.e. goto 4) Then we say, ok, lets keep the base - * position even if the subpixel location is 0.6... - * @param maximaTolerance - * By how much to increase the tolerance per iteration. - * @param allowedToMoveInDim - * specifies, per dimension, whether the base location is allowed - * to be moved in the iterative procedure. - * @return refined list of peaks. - */ - public static < T extends RealType< T >, P extends Localizable > ArrayList< RefinedPeak< P > > refinePeaks( + private static < P > ArrayList< P > concatenate( List< ? extends List< ? extends P > > lists ) + { + int size = lists.stream().mapToInt( List::size ).sum(); + ArrayList< P > result = new ArrayList<>( size ); + lists.forEach( result::addAll ); + return result; + } + + public static < T extends RealType< T >, P extends Localizable > ArrayList< RefinedPeak< P > > refinePeaksChunk( final List< P > peaks, final RandomAccessible< T > img, final Interval validInterval, final boolean returnInvalidPeaks, final int maxNumMoves, final boolean allowMaximaTolerance, final float maximaTolerance, final boolean[] allowedToMoveInDim ) { - final ArrayList< RefinedPeak< P >> refinedPeaks = new ArrayList< RefinedPeak< P > >(); + final ArrayList< RefinedPeak< P > > refinedPeaks = new ArrayList< RefinedPeak< P > >(); final int n = img.numDimensions(); @@ -471,7 +425,7 @@ else if ( returnInvalidPeaks ) /** * Estimate subpixel {@code offset} of extremum of quadratic function * fitted at {@code p}. - * + * * @param p * integer position at which to fit quadratic. * @param access diff --git a/src/main/java/net/imglib2/algorithm/morphology/Dilation.java b/src/main/java/net/imglib2/algorithm/morphology/Dilation.java index 947f93a83..cbdaa7897 100644 --- a/src/main/java/net/imglib2/algorithm/morphology/Dilation.java +++ b/src/main/java/net/imglib2/algorithm/morphology/Dilation.java @@ -34,24 +34,23 @@ package net.imglib2.algorithm.morphology; import java.util.List; -import java.util.Vector; +import java.util.function.BiConsumer; -import net.imglib2.Cursor; import net.imglib2.FinalDimensions; import net.imglib2.Interval; import net.imglib2.IterableInterval; -import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.loops.IterableLoopBuilder; import net.imglib2.algorithm.neighborhood.Neighborhood; import net.imglib2.algorithm.neighborhood.Shape; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; -import net.imglib2.multithreading.Chunk; -import net.imglib2.multithreading.SimpleMultiThreading; +import net.imglib2.parallel.Parallelization; import net.imglib2.type.Type; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; +import net.imglib2.util.Cast; import net.imglib2.util.Util; import net.imglib2.view.ExtendedRandomAccessibleInterval; import net.imglib2.view.IntervalView; @@ -59,6 +58,7 @@ public class Dilation { + /** * Performs the dilation morphological operation, on a {@link RealType} * {@link Img} using a list of {@link Shape}s as a flat structuring element. @@ -449,122 +449,38 @@ public static < T extends RealType< T >> void dilate( final RandomAccessible< T */ public static < T extends Type< T > & Comparable< T > > void dilate( final RandomAccessible< T > source, final IterableInterval< T > target, final Shape strel, final T minVal, int numThreads ) { - numThreads = Math.max( 1, numThreads ); - - /* - * Prepare iteration. - */ - - final RandomAccessible< Neighborhood< T >> accessible = strel.neighborhoodsRandomAccessible( source ); - - /* - * Multithread - */ - - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( target.size(), numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - - - final Object tmp = minVal; - if ( tmp instanceof BitType ) - { - /* - * Optimization for BitType - */ + final RandomAccessible< Neighborhood< T > > neighborhoods = strel.neighborhoodsRandomAccessible( source ); + Parallelization.runWithNumThreads( numThreads, () -> { + IterableLoopBuilder.setImages( target, neighborhoods ).multithreaded().forEachChunk( chunk -> { + chunk.forEachPixel( getDilateAction( minVal ) ); + return null; + } ); + } ); + } - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology dilate thread " + i ) - { - @Override - public void run() + private static < T extends Type< T > & Comparable< T > > BiConsumer< T, Neighborhood< T > > getDilateAction( T minVal ) + { + if ( minVal instanceof BitType ) + return Cast.unchecked( (BiConsumer< BitType, Neighborhood< BitType > > ) ( t, neighborhood ) -> { + for ( BitType val1 : neighborhood ) + if ( val1.get() ) { - final RandomAccess< Neighborhood< T >> randomAccess = accessible.randomAccess( target ); - final Object tmp2 = target.cursor(); - @SuppressWarnings( "unchecked" ) - final Cursor< BitType > cursorDilated = ( Cursor< BitType > ) tmp2; - cursorDilated.jumpFwd( chunk.getStartPosition() ); - - for ( long steps = 0; steps < chunk.getLoopSize(); steps++ ) - { - cursorDilated.fwd(); - randomAccess.setPosition( cursorDilated ); - final Neighborhood< T > neighborhood = randomAccess.get(); - final Object tmp3 = neighborhood.cursor(); - @SuppressWarnings( "unchecked" ) - final Cursor< BitType > nc = ( Cursor< BitType > ) tmp3; - - while ( nc.hasNext() ) - { - nc.fwd(); - final BitType val = nc.get(); - if ( val.get() ) - { - cursorDilated.get().set( true ); - break; - } - } - } - + t.set( true ); + return; } - }; - } - } + t.set( false ); + } ); else { - /* - * All other comparable type. - */ - - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology dilate thread " + i ) - { - @Override - public void run() - { - final RandomAccess< Neighborhood< T >> randomAccess = accessible.randomAccess( target ); - final Cursor< T > cursorDilated = target.cursor(); - cursorDilated.jumpFwd( chunk.getStartPosition() ); - - final T max = MorphologyUtils.createVariable( source, target ); - for ( long steps = 0; steps < chunk.getLoopSize(); steps++ ) - { - cursorDilated.fwd(); - randomAccess.setPosition( cursorDilated ); - final Neighborhood< T > neighborhood = randomAccess.get(); - final Cursor< T > nc = neighborhood.cursor(); - - /* - * Look for max in the neighborhood. - */ - - max.set( minVal ); - while ( nc.hasNext() ) - { - nc.fwd(); - final T val = nc.get(); - // We need only Comparable to do this: - if ( val.compareTo( max ) > 0 ) - { - max.set( val ); - } - } - cursorDilated.get().set( max ); - } - - } - }; - } + T max = minVal.copy(); + return ( t, neighborhood ) -> { + max.set( minVal ); + for ( T val : neighborhood ) + if ( val.compareTo( max ) > 0 ) + max.set( val ); + t.set( max ); + }; } - - /* - * Launch calculation - */ - - SimpleMultiThreading.startAndJoin( threads ); } /** diff --git a/src/main/java/net/imglib2/algorithm/morphology/Erosion.java b/src/main/java/net/imglib2/algorithm/morphology/Erosion.java index aced24af1..d1c0b9294 100644 --- a/src/main/java/net/imglib2/algorithm/morphology/Erosion.java +++ b/src/main/java/net/imglib2/algorithm/morphology/Erosion.java @@ -34,24 +34,23 @@ package net.imglib2.algorithm.morphology; import java.util.List; -import java.util.Vector; +import java.util.function.BiConsumer; -import net.imglib2.Cursor; import net.imglib2.FinalDimensions; import net.imglib2.Interval; import net.imglib2.IterableInterval; -import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.loops.IterableLoopBuilder; import net.imglib2.algorithm.neighborhood.Neighborhood; import net.imglib2.algorithm.neighborhood.Shape; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; -import net.imglib2.multithreading.Chunk; -import net.imglib2.multithreading.SimpleMultiThreading; +import net.imglib2.parallel.Parallelization; import net.imglib2.type.Type; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.RealType; +import net.imglib2.util.Cast; import net.imglib2.util.Util; import net.imglib2.view.ExtendedRandomAccessibleInterval; import net.imglib2.view.IntervalView; @@ -449,122 +448,38 @@ public static < T extends RealType< T >> void erode( final RandomAccessible< T > */ public static < T extends Type< T > & Comparable< T > > void erode( final RandomAccessible< T > source, final IterableInterval< T > target, final Shape strel, final T maxVal, int numThreads ) { - numThreads = Math.max( 1, numThreads ); - - /* - * Prepare iteration. - */ - - final RandomAccessible< Neighborhood< T >> accessible = strel.neighborhoodsRandomAccessible( source ); - - /* - * Multithread - */ - - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( target.size(), numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - - final Object tmp = maxVal; - if ( tmp instanceof BitType ) - { - /* - * Optimization for BitType - */ + final RandomAccessible< Neighborhood< T >> neighborhoods = strel.neighborhoodsRandomAccessible( source ); + Parallelization.runWithNumThreads( numThreads, () -> { + IterableLoopBuilder.setImages( target, neighborhoods ).multithreaded().forEachChunk( chunk -> { + chunk.forEachPixel( getErodeAction( maxVal ) ); + return null; + } ); + } ); + } - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology erode thread " + i ) - { - @Override - public void run() + private static < T extends Type< T > & Comparable< T > > BiConsumer< T, Neighborhood< T > > getErodeAction( T maxVal ) + { + if ( maxVal instanceof BitType ) + return Cast.unchecked( ( BiConsumer< BitType, Neighborhood< BitType > > ) ( t, neighborhood ) -> { + for ( BitType val1 : neighborhood ) + if ( ! val1.get() ) { - final RandomAccess< Neighborhood< T >> randomAccess = accessible.randomAccess( target ); - final Object tmp2 = target.cursor(); - @SuppressWarnings( "unchecked" ) - final Cursor< BitType > cursorTarget = ( Cursor< BitType > ) tmp2; - cursorTarget.jumpFwd( chunk.getStartPosition() ); - - for ( long steps = 0; steps < chunk.getLoopSize(); steps++ ) - { - cursorTarget.fwd(); - randomAccess.setPosition( cursorTarget ); - final Object tmp3 = randomAccess.get(); - @SuppressWarnings( "unchecked" ) - final Neighborhood< BitType > neighborhood = (net.imglib2.algorithm.neighborhood.Neighborhood< BitType > ) tmp3; - final Cursor< BitType > nc = neighborhood.cursor(); - - cursorTarget.get().set( true ); - while ( nc.hasNext() ) - { - nc.fwd(); - final BitType val = nc.get(); - if ( !val.get() ) - { - cursorTarget.get().set( false ); - break; - } - } - } - + t.set( false ); + return; } - }; - } - } + t.set( true ); + } ); else { - /* - * All other comparable type. - */ - - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology erode thread " + i ) - { - @Override - public void run() - { - final RandomAccess< Neighborhood< T >> randomAccess = accessible.randomAccess( target ); - final Cursor< T > cursorTarget = target.cursor(); - cursorTarget.jumpFwd( chunk.getStartPosition() ); - - final T max = MorphologyUtils.createVariable( source, target ); - for ( long steps = 0; steps < chunk.getLoopSize(); steps++ ) - { - cursorTarget.fwd(); - randomAccess.setPosition( cursorTarget ); - final Neighborhood< T > neighborhood = randomAccess.get(); - final Cursor< T > nc = neighborhood.cursor(); - - /* - * Look for max in the neighborhood. - */ - - max.set( maxVal ); - while ( nc.hasNext() ) - { - nc.fwd(); - final T val = nc.get(); - // We need only Comparable to do this: - if ( val.compareTo( max ) < 0 ) - { - max.set( val ); - } - } - cursorTarget.get().set( max ); - } - - } - }; - } + T min = maxVal.copy(); + return ( t, neighborhood ) -> { + min.set( maxVal ); + for ( T val : neighborhood ) + if ( val.compareTo( min ) < 0 ) + min.set( val ); + t.set( min ); + }; } - - /* - * Launch calculation - */ - - SimpleMultiThreading.startAndJoin( threads ); } /** diff --git a/src/main/java/net/imglib2/algorithm/morphology/MorphologyUtils.java b/src/main/java/net/imglib2/algorithm/morphology/MorphologyUtils.java index 82d7c83ba..656d6b4eb 100644 --- a/src/main/java/net/imglib2/algorithm/morphology/MorphologyUtils.java +++ b/src/main/java/net/imglib2/algorithm/morphology/MorphologyUtils.java @@ -33,15 +33,13 @@ */ package net.imglib2.algorithm.morphology; -import java.util.Vector; - -import net.imglib2.Cursor; import net.imglib2.EuclideanSpace; import net.imglib2.Interval; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.loops.IterableLoopBuilder; import net.imglib2.algorithm.neighborhood.Neighborhood; import net.imglib2.algorithm.neighborhood.Shape; import net.imglib2.img.Img; @@ -49,12 +47,11 @@ import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.array.ArrayRandomAccess; import net.imglib2.img.basictypeaccess.array.LongArray; -import net.imglib2.multithreading.Chunk; -import net.imglib2.multithreading.SimpleMultiThreading; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.Parallelization; import net.imglib2.type.Type; import net.imglib2.type.logic.BitType; import net.imglib2.type.operators.Sub; -import net.imglib2.util.Intervals; import net.imglib2.util.Util; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; @@ -66,17 +63,15 @@ public class MorphologyUtils * Static util to compute the final image dimensions and required offset * when performing a full dilation with the specified strel. * - * @param source - * the source image. - * @param strel - * the strel to use for dilation. + * @param source the source image. + * @param strel the strel to use for dilation. * @return a 2-elements {@code long[][]}: - *

    - *
  1. a {@code long[]} array with the final image target - * dimensions. - *
  2. a {@code long[]} array with the offset to apply to the - * source image. - *
+ *
    + *
  1. a {@code long[]} array with the final image target + * dimensions. + *
  2. a {@code long[]} array with the offset to apply to the + * source image. + *
*/ public static final < T > long[][] computeTargetImageDimensionsAndOffset( final Interval source, final Shape strel ) { @@ -291,61 +286,16 @@ private static final void appendSingleSlice( final RandomAccess< BitType > ra, f static < T extends Type< T > > void copy( final IterableInterval< T > source, final RandomAccessible< T > target, final int numThreads ) { - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( source.size(), numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology copy thread " + i ) - { - @Override - public void run() - { - final Cursor< T > sourceCursor = source.localizingCursor(); - sourceCursor.jumpFwd( chunk.getStartPosition() ); - final RandomAccess< T > targetRandomAccess = target.randomAccess(); - - for ( long step = 0; step < chunk.getLoopSize(); step++ ) - { - sourceCursor.fwd(); - targetRandomAccess.setPosition( sourceCursor ); - targetRandomAccess.get().set( sourceCursor.get() ); - } - } - }; - } - - SimpleMultiThreading.startAndJoin( threads ); + Parallelization.runWithNumThreads( numThreads, () -> { + IterableLoopBuilder.setImages( source, target ).multithreaded().forEachPixel( ( s, t ) -> t.set( s ) ); + } ); } static < T extends Type< T > > void copy2( final RandomAccessible< T > source, final IterableInterval< T > target, final int numThreads ) { - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( target.size(), numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology copy2 thread " + i ) - { - @Override - public void run() - { - final Cursor< T > targetCursor = target.localizingCursor(); - targetCursor.jumpFwd( chunk.getStartPosition() ); - final RandomAccess< T > sourceRandomAccess = source.randomAccess(); - - // iterate over the input cursor - for ( long step = 0; step < chunk.getLoopSize(); step++ ) - { - targetCursor.fwd(); - sourceRandomAccess.setPosition( targetCursor ); - targetCursor.get().set( sourceRandomAccess.get() ); - } - } - }; - } - - SimpleMultiThreading.startAndJoin( threads ); + Parallelization.runWithNumThreads( numThreads, () -> { + IterableLoopBuilder.setImages( target, source ).multithreaded().forEachPixel( ( t, s ) -> t.set( s ) ); + } ); } static < T extends Type< T > > Img< T > copyCropped( final Img< T > largeSource, final Interval interval, final int numThreads ) @@ -356,32 +306,8 @@ static < T extends Type< T > > Img< T > copyCropped( final Img< T > largeSource, offset[ d ] = ( largeSource.dimension( d ) - interval.dimension( d ) ) / 2; } final Img< T > create = largeSource.factory().create( interval ); - - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( create.size(), numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology copyCropped thread " + i ) - { - @Override - public void run() - { - final IntervalView< T > intervalView = Views.offset( largeSource, offset ); - final Cursor< T > cursor = create.cursor(); - cursor.jumpFwd( chunk.getStartPosition() ); - final RandomAccess< T > randomAccess = intervalView.randomAccess(); - for ( long step = 0; step < chunk.getLoopSize(); step++ ) - { - cursor.fwd(); - randomAccess.setPosition( cursor ); - cursor.get().set( randomAccess.get() ); - } - } - }; - } - - SimpleMultiThreading.startAndJoin( threads ); + final RandomAccessibleInterval< T > intervalView = Views.translateInverse( largeSource, offset ); + copy2( intervalView, create, numThreads ); return create; } @@ -393,7 +319,7 @@ public void run() * @param interval * @return type instance */ - static < T extends Type< T >> T createVariable( final RandomAccessible< T > accessible, final Interval interval ) + static < T extends Type< T > > T createVariable( final RandomAccessible< T > accessible, final Interval interval ) { final RandomAccess< T > a = accessible.randomAccess(); interval.min( a ); @@ -405,7 +331,7 @@ public static final Neighborhood< BitType > getNeighborhood( final Shape shape, final int numDims = space.numDimensions(); final long[] dimensions = Util.getArrayFromValue( 1l, numDims ); final ArrayImg< BitType, LongArray > img = ArrayImgs.bits( dimensions ); - final IterableInterval< Neighborhood< BitType >> neighborhoods = shape.neighborhoods( img ); + final IterableInterval< Neighborhood< BitType > > neighborhoods = shape.neighborhoods( img ); final Neighborhood< BitType > neighborhood = neighborhoods.cursor().next(); return neighborhood; } @@ -418,12 +344,10 @@ public static final Neighborhood< BitType > getNeighborhood( final Shape shape, * This method only prints the first 3 dimensions of the structuring * element. Dimensions above 3 are skipped. * - * @param shape - * the structuring element to print. - * @param dimensionality - * the dimensionality to cast it over. This is required as - * {@link Shape} does not carry a dimensionality, and we need one - * to generate a neighborhood to iterate. + * @param shape the structuring element to print. + * @param dimensionality the dimensionality to cast it over. This is required as + * {@link Shape} does not carry a dimensionality, and we need one + * to generate a neighborhood to iterate. * @return a string representation of the structuring element. */ public static final String printNeighborhood( final Shape shape, final int dimensionality ) @@ -473,219 +397,88 @@ else if ( neighborhood.numDimensions() > 0 ) /** * Does A = A - B. Writes the results in A. * - * @param A - * A - * @param B - * B + * @param A A + * @param B B * @param numThreads */ static < T extends Sub< T > > void subAAB( final RandomAccessible< T > A, final IterableInterval< T > B, final int numThreads ) { - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( B.size(), numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology subAAB thread " + i ) - { - @Override - public void run() - { - final Cursor< T > Bcursor = B.localizingCursor(); - Bcursor.jumpFwd( chunk.getStartPosition() ); - final RandomAccess< T > Ara = A.randomAccess(); - - for ( long step = 0; step < chunk.getLoopSize(); step++ ) - { - Bcursor.fwd(); - Ara.setPosition( Bcursor ); - Ara.get().sub( Bcursor.get() ); - } - } - }; - } - - SimpleMultiThreading.startAndJoin( threads ); + Parallelization.runWithNumThreads( numThreads, () -> { + IterableLoopBuilder.setImages( B, A ).multithreaded().forEachPixel( ( b, a ) -> a.sub( b ) ); + } ); } - /** * Does A = A - B. Writes the results in A. * - * @param A - * A - * @param B - * B + * @param A A + * @param B B * @param numThreads */ static < T extends Sub< T > > void subAAB2( final IterableInterval< T > A, final RandomAccessible< T > B, final int numThreads ) { - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( A.size(), numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology subAAB2 thread " + i ) - { - @Override - public void run() - { - final Cursor< T > Acursor = A.localizingCursor(); - Acursor.jumpFwd( chunk.getStartPosition() ); - final RandomAccess< T > Bra = B.randomAccess(); // LOL - - for ( long step = 0; step < chunk.getLoopSize(); step++ ) - { - Acursor.fwd(); - Bra.setPosition( Acursor ); - Acursor.get().sub( Bra.get() ); - } - } - }; - } - - SimpleMultiThreading.startAndJoin( threads ); + Parallelization.runWithNumThreads( numThreads, () -> { + IterableLoopBuilder.setImages( A, B ).multithreaded().forEachPixel( ( a, b ) -> a.sub( b ) ); + } ); } - /** * Does A = B - A. Writes the results in A. * - * @param source - * A - * @param target - * B + * @param A A + * @param B B * @param numThreads */ - static < T extends Sub< T > & Type< T >> void subABA( final RandomAccessible< T > source, final IterableInterval< T > target, final int numThreads ) + static < T extends Sub< T > & Type< T > > void subABA( final RandomAccessible< T > A, final IterableInterval< T > B, final int numThreads ) { - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( target.size(), numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology subABA thread " + i ) - { - @Override - public void run() - { - final T tmp = createVariable( source, target ); - final Cursor< T > targetCursor = target.localizingCursor(); - targetCursor.jumpFwd( chunk.getStartPosition() ); - final RandomAccess< T > sourceRandomAccess = source.randomAccess(); - - for ( long step = 0; step < chunk.getLoopSize(); step++ ) - { - targetCursor.fwd(); - sourceRandomAccess.setPosition( targetCursor ); - - tmp.set( targetCursor.get() ); - tmp.sub( sourceRandomAccess.get() ); - - sourceRandomAccess.get().set( tmp ); - } - } - }; - } - - SimpleMultiThreading.startAndJoin( threads ); + Parallelization.runWithNumThreads( numThreads, () -> { + IterableLoopBuilder.setImages( B, A ).multithreaded().forEachChunk( chunk -> { + T tmp = createVariable( A, B ); + chunk.forEachPixel( ( b, a ) -> { + tmp.set( b ); + tmp.sub( a ); + a.set( tmp ); + } ); + return null; + } ); + } ); } /** - * Does A = B - A. Writes the results in A. + * Does B = B - A. Writes the results in B. * - * @param source - * A - * @param target - * B + * @param A A + * @param B B * @param numThreads */ - static < T extends Sub< T > & Type< T >> void subABA2( final RandomAccessibleInterval< T > source, final RandomAccessible< T > target, final int numThreads ) + static < T extends Sub< T > & Type< T > > void subABA2( final RandomAccessibleInterval< T > A, final RandomAccessible< T > B, final int numThreads ) { - final long size = Intervals.numElements( source ); - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( size, numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology subABA2 thread " + i ) - { - @Override - public void run() - { - final T tmp = createVariable( target, source ); - final Cursor< T > sourceCursor = Views.iterable( source ).localizingCursor(); - sourceCursor.jumpFwd( chunk.getStartPosition() ); - final RandomAccess< T > targetRandomAccess = target.randomAccess( source ); - - for ( long step = 0; step < chunk.getLoopSize(); step++ ) - { - - } - while ( sourceCursor.hasNext() ) - { - sourceCursor.fwd(); - targetRandomAccess.setPosition( sourceCursor ); - - tmp.set( targetRandomAccess.get() ); - tmp.sub( sourceCursor.get() ); - - targetRandomAccess.get().set( tmp ); - } - } - }; - } - - SimpleMultiThreading.startAndJoin( threads ); + Parallelization.runWithNumThreads( numThreads, () -> { + IntervalView croppedB = Views.interval( B, A ); + LoopBuilder.setImages( A, croppedB ).forEachPixel( (a, b) -> b.sub( a ) ); + } ); } /** * Does B = A - B. Writes the results in B. * - * @param A - * A - * @param B - * B + * @param A A + * @param B B * @param numThreads */ static < T extends Type< T > & Sub< T > > void subBAB( final RandomAccessible< T > A, final IterableInterval< T > B, final int numThreads ) { - final long size = Intervals.numElements( B ); - final Vector< Chunk > chunks = SimpleMultiThreading.divideIntoChunks( size, numThreads ); - final Thread[] threads = SimpleMultiThreading.newThreads( numThreads ); - - for ( int i = 0; i < threads.length; i++ ) - { - final Chunk chunk = chunks.get( i ); - threads[ i ] = new Thread( "Morphology subBAB thread " + i ) - { - @Override - public void run() - { - final T tmp = createVariable( A, B ); - final Cursor< T > BCursor = B.localizingCursor(); - BCursor.jumpFwd( chunk.getStartPosition() ); - final RandomAccess< T > Ara = A.randomAccess(); - - for ( long step = 0; step < chunk.getLoopSize(); step++ ) - { - BCursor.fwd(); - Ara.setPosition( BCursor ); - - tmp.set( Ara.get() ); - tmp.sub( BCursor.get() ); - - BCursor.get().set( tmp ); - } - } - }; - } - - SimpleMultiThreading.startAndJoin( threads ); + Parallelization.runWithNumThreads( numThreads, () -> { + IterableLoopBuilder.setImages( B, A ).multithreaded().forEachChunk( chunk -> { + T tmp = createVariable( A, B ); + chunk.forEachPixel( ( b, a ) -> { + tmp.set( a ); + tmp.sub( b ); + b.set( tmp ); + } ); + return null; + } ); + } ); } } diff --git a/src/main/java/net/imglib2/algorithm/morphology/distance/DistanceTransform.java b/src/main/java/net/imglib2/algorithm/morphology/distance/DistanceTransform.java index 24f4e2482..66a90e354 100644 --- a/src/main/java/net/imglib2/algorithm/morphology/distance/DistanceTransform.java +++ b/src/main/java/net/imglib2/algorithm/morphology/distance/DistanceTransform.java @@ -34,17 +34,10 @@ package net.imglib2.algorithm.morphology.distance; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; import java.util.stream.DoubleStream; import java.util.stream.IntStream; - -import net.imglib2.Cursor; import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.RandomAccessible; @@ -56,12 +49,14 @@ import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.img.cell.CellImg; import net.imglib2.img.cell.CellImgFactory; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.Parallelization; +import net.imglib2.parallel.TaskExecutors; import net.imglib2.type.BooleanType; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.LongType; import net.imglib2.type.numeric.real.DoubleType; -import net.imglib2.util.Intervals; import net.imglib2.util.Util; import net.imglib2.util.ValuePair; import net.imglib2.view.Views; @@ -169,7 +164,8 @@ public static < T extends RealType< T > > void transform( final int nTasks, final double... weights ) throws InterruptedException, ExecutionException { - transform( source, source, distanceType, es, nTasks, weights ); + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> transform( source, source, distanceType, weights ) ); } /** @@ -248,7 +244,8 @@ public static < T extends RealType< T >, U extends RealType< U > > void transfor final int nTasks, final double... weights ) throws InterruptedException, ExecutionException { - transform( source, target, target, distanceType, es, nTasks, weights ); + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> transform( source, target, distanceType, weights ) ); } /** @@ -353,21 +350,8 @@ public static < T extends RealType< T >, U extends RealType< U >, V extends Real final int nTasks, final double... weights ) throws InterruptedException, ExecutionException { - - final boolean isIsotropic = weights.length <= 1; - final double[] w = weights.length == source.numDimensions() ? weights : DoubleStream.generate( () -> weights.length == 0 ? 1.0 : weights[ 0 ] ).limit( source.numDimensions() ).toArray(); - - switch ( distanceType ) - { - case EUCLIDIAN: - transform( source, tmp, target, isIsotropic ? new EuclidianDistanceIsotropic( w[ 0 ] ) : new EuclidianDistanceAnisotropic( w ), es, nTasks ); - break; - case L1: - transformL1( source, tmp, target, es, nTasks, w ); - break; - default: - break; - } + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> transform( source, tmp, target, distanceType, weights ) ); } /** @@ -422,7 +406,8 @@ public static < T extends RealType< T > > void transform( final ExecutorService es, final int nTasks ) throws InterruptedException, ExecutionException { - transform( source, source, d, es, nTasks ); + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> transform( source, d ) ); } /** @@ -487,7 +472,8 @@ public static < T extends RealType< T >, U extends RealType< U > > void transfor final ExecutorService es, final int nTasks ) throws InterruptedException, ExecutionException { - transform( source, target, target, d, es, nTasks ); + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> transform( source, target, d ) ); } /** @@ -592,37 +578,8 @@ public static < T extends RealType< T >, U extends RealType< U >, V extends Real final ExecutorService es, final int nTasks ) throws InterruptedException, ExecutionException { - - assert source.numDimensions() == target.numDimensions(): "Dimension mismatch"; - final int nDim = source.numDimensions(); - final int lastDim = nDim - 1; - - if ( nDim == 1 ) - { - transformAlongDimensionParallel( - ( RandomAccessible< T > ) Views.addDimension( source ), - Views.interval( Views.addDimension( target ), new FinalInterval( target.dimension( 0 ), 1 ) ), - d, - 0, - es, - nTasks ); - } - else - { - transformAlongDimensionParallel( source, tmp, d, 0, es, nTasks ); - } - - for ( int dim = 1; dim < nDim; ++dim ) - { - if ( dim == lastDim ) - { - transformAlongDimensionParallel( tmp, target, d, dim, es, nTasks ); - } - else - { - transformAlongDimensionParallel( tmp, tmp, d, dim, es, nTasks ); - } - } + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> transform( source, tmp, target, d ) ); } /** @@ -697,7 +654,8 @@ public static < B extends BooleanType< B >, U extends RealType< U > > void binar final int nTasks, final double... weights ) throws InterruptedException, ExecutionException { - binaryTransform( source, target, target, distanceType, es, nTasks, weights ); + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> binaryTransform( source, target, distanceType, weights ) ); } /** @@ -789,11 +747,8 @@ public static < B extends BooleanType< B >, U extends RealType< U >, V extends R final int nTasks, final double... weights ) throws InterruptedException, ExecutionException { - final U maxVal = Util.getTypeFromInterval( tmp ).createVariable(); - maxVal.setReal( maxVal.getMaxValue() ); - final Converter< B, U > converter = new BinaryMaskToCost<>( maxVal ); - final RandomAccessible< U > converted = Converters.convert( source, converter, maxVal.createVariable() ); - transform( converted, tmp, target, distanceType, es, nTasks, weights ); + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> binaryTransform( source, tmp, target, distanceType, weights ) ); } /** @@ -886,7 +841,8 @@ public static < B extends BooleanType< B >, U extends RealType< U > > void binar final ExecutorService es, final int nTasks ) throws InterruptedException, ExecutionException { - binaryTransform( source, target, target, d, es, nTasks ); + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> binaryTransform( source, target, d ) ); } /** @@ -963,11 +919,8 @@ public static < B extends BooleanType< B >, U extends RealType< U >, V extends R final ExecutorService es, final int nTasks ) throws InterruptedException, ExecutionException { - final U maxVal = Util.getTypeFromInterval( tmp ).createVariable(); - maxVal.setReal( maxVal.getMaxValue() ); - final Converter< B, U > converter = new BinaryMaskToCost<>( maxVal ); - final RandomAccessible< U > converted = Converters.convert( source, converter, maxVal.createVariable() ); - transform( converted, tmp, target, d, es, nTasks ); + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> binaryTransform( source, tmp, target, d ) ); } /** @@ -986,7 +939,7 @@ public static < B extends BooleanType< B >, U extends RealType< U >, V extends R * @param weights * Individual weights for each dimension, balancing image values * and L1 distance. - * @param + * @param * {@link BooleanType} binary mask input * @param * {@link RealType} intermediate results @@ -1065,36 +1018,8 @@ private static < T extends RealType< T >, U extends RealType< U >, V extends Rea final int nTasks, final double... weights ) throws InterruptedException, ExecutionException { - assert source.numDimensions() == target.numDimensions(): "Dimension mismatch"; - final int nDim = source.numDimensions(); - final int lastDim = nDim - 1; - - if ( nDim == 1 ) - { - transformL1AlongDimensionParallel( - ( RandomAccessible< T > ) Views.addDimension( source ), - Views.interval( Views.addDimension( target ), new FinalInterval( target.dimension( 0 ), 1 ) ), - 0, - weights[ 0 ], - es, - nTasks ); - } - else - { - transformL1AlongDimensionParallel( source, tmp, 0, weights[ 0 ], es, nTasks ); - } - - for ( int dim = 1; dim < nDim; ++dim ) - { - if ( dim == lastDim ) - { - transformL1AlongDimensionParallel( tmp, target, dim, weights[ dim ], es, nTasks ); - } - else - { - transformL1AlongDimensionParallel( tmp, tmp, dim, weights[ dim ], es, nTasks ); - } - } + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ), + () -> transformL1( source, tmp, target, weights ) ); } private static < T extends RealType< T >, U extends RealType< U > > void transformAlongDimension( @@ -1103,63 +1028,25 @@ private static < T extends RealType< T >, U extends RealType< U > > void transfo final Distance d, final int dim ) { - final int lastDim = target.numDimensions() - 1; final long size = target.dimension( dim ); - final RealComposite< DoubleType > tmp = Views.collapseReal( createAppropriateOneDimensionalImage( size, new DoubleType() ) ).randomAccess().get(); - // do not permute if we already work on last dimension - final Cursor< RealComposite< T > > s = Views.flatIterable( Views.collapseReal( dim == lastDim ? Views.interval( source, target ) : Views.permute( Views.interval( source, target ), dim, lastDim ) ) ).cursor(); - final Cursor< RealComposite< U > > t = Views.flatIterable( Views.collapseReal( dim == lastDim ? target : Views.permute( target, dim, lastDim ) ) ).cursor(); - final RealComposite< LongType > lowerBoundDistanceIndex = Views.collapseReal( createAppropriateOneDimensionalImage( size, new LongType() ) ).randomAccess().get(); - final RealComposite< DoubleType > envelopeIntersectLocation = Views.collapseReal( createAppropriateOneDimensionalImage( size + 1, new DoubleType() ) ).randomAccess().get(); - - while ( s.hasNext() ) - { - final RealComposite< T > sourceComp = s.next(); - final RealComposite< U > targetComp = t.next(); - for ( long i = 0; i < size; ++i ) - { - tmp.get( i ).set( sourceComp.get( i ).getRealDouble() ); - } - transformSingleColumn( tmp, targetComp, lowerBoundDistanceIndex, envelopeIntersectLocation, d, dim, size ); - } - } - - private static < T extends RealType< T >, U extends RealType< U > > void transformAlongDimensionParallel( - final RandomAccessible< T > source, - final RandomAccessibleInterval< U > target, - final Distance d, - final int dim, - final ExecutorService es, - final int nTasks ) throws InterruptedException, ExecutionException - { - int largestDim = getLargestDimension( Views.hyperSlice( target, dim, target.min( dim ) ) ); - // ignore dimension along which we calculate transform - if ( largestDim >= dim ) - { - largestDim += 1; - } - final long size = target.dimension( dim ); - final long stepPerChunk = Math.max( size / nTasks, 1 ); - - final long[] min = Intervals.minAsLongArray( target ); - final long[] max = Intervals.maxAsLongArray( target ); - - final long largestDimMin = target.min( largestDim ); - final long largestDimMax = target.max( largestDim ); - - final ArrayList< Callable< Void > > tasks = new ArrayList<>(); - for ( long m = largestDimMin, M = largestDimMin + stepPerChunk - 1; m <= largestDimMax; m += stepPerChunk, M += stepPerChunk ) - { - min[ largestDim ] = m; - max[ largestDim ] = Math.min( M, largestDimMax ); - final Interval fi = new FinalInterval( min, max ); - tasks.add( () -> { - transformAlongDimension( source, Views.interval( target, fi ), d, dim ); - return null; - } ); - } - - invokeAllAndWait( es, tasks ); + final RandomAccessibleInterval< RealComposite< T > > s = collapseDimensions( Views.interval( source, target ), dim ); + final RandomAccessibleInterval< RealComposite< U > > t = collapseDimensions( target, dim ); + + LoopBuilder.setImages( s, t ).multiThreaded().forEachChunk( chunk -> { + final RealComposite< DoubleType > tmp = Views.collapseReal( createAppropriateOneDimensionalImage( size, new DoubleType() ) ).randomAccess().get(); + final RealComposite< LongType > lowerBoundDistanceIndex = Views.collapseReal( createAppropriateOneDimensionalImage( size, new LongType() ) ).randomAccess().get(); + final RealComposite< DoubleType > envelopeIntersectLocation = Views.collapseReal( createAppropriateOneDimensionalImage( size + 1, new DoubleType() ) ).randomAccess().get(); + chunk.forEachPixel( + ( sourceComp, targetComp ) -> { + for ( long i = 0; i < size; ++i ) + { + tmp.get( i ).set( sourceComp.get( i ).getRealDouble() ); + } + transformSingleColumn( tmp, targetComp, lowerBoundDistanceIndex, envelopeIntersectLocation, d, dim, size ); + } + ); + return null; + } ); } private static < T extends RealType< T >, U extends RealType< U > > void transformSingleColumn( @@ -1216,62 +1103,28 @@ private static < T extends RealType< T >, U extends RealType< U > > void transfo final int dim, final double weight ) { - final int lastDim = target.numDimensions() - 1; final long size = target.dimension( dim ); - final RealComposite< DoubleType > tmp = Views.collapseReal( createAppropriateOneDimensionalImage( size, new DoubleType() ) ).randomAccess().get(); - // do not permute if we already work on last dimension - final Cursor< RealComposite< T > > s = Views.flatIterable( Views.collapseReal( dim == lastDim ? Views.interval( source, target ) : Views.permute( Views.interval( source, target ), dim, lastDim ) ) ).cursor(); - final Cursor< RealComposite< U > > t = Views.flatIterable( Views.collapseReal( dim == lastDim ? target : Views.permute( target, dim, lastDim ) ) ).cursor(); - - while ( s.hasNext() ) - { - final RealComposite< T > sourceComp = s.next(); - final RealComposite< U > targetComp = t.next(); - for ( long i = 0; i < size; ++i ) - { - tmp.get( i ).set( sourceComp.get( i ).getRealDouble() ); - } - transformL1SingleColumn( tmp, targetComp, weight, size ); - } + final RandomAccessibleInterval< RealComposite< T > > s = collapseDimensions( Views.interval( source, target ), dim ); + final RandomAccessibleInterval< RealComposite< U > > t = collapseDimensions( target, dim ); + + LoopBuilder.setImages( s, t ).multiThreaded().forEachChunk( chunk -> { + final RealComposite< DoubleType > tmp = Views.collapseReal( createAppropriateOneDimensionalImage( size, new DoubleType() ) ).randomAccess().get(); + chunk.forEachPixel( ( sourceComp, targetComp ) -> { + for ( long i = 0; i < size; ++i ) + { + tmp.get( i ).set( sourceComp.get( i ).getRealDouble() ); + } + transformL1SingleColumn( tmp, targetComp, weight, size ); + } ); + return null; + } ); } - private static < T extends RealType< T >, U extends RealType< U > > void transformL1AlongDimensionParallel( - final RandomAccessible< T > source, - final RandomAccessibleInterval< U > target, - final int dim, - final double weight, - final ExecutorService es, - final int nTasks ) throws InterruptedException, ExecutionException + private static < U extends RealType< U > > RandomAccessibleInterval< RealComposite< U > > collapseDimensions( RandomAccessibleInterval< U > target, int dim ) { - int largestDim = getLargestDimension( Views.hyperSlice( target, dim, target.min( dim ) ) ); - // ignore dimension along which we calculate transform - if ( largestDim >= dim ) - { - largestDim += 1; - } - final long size = target.dimension( dim ); - final long stepPerChunk = Math.max( size / nTasks, 1 ); - - final long[] min = Intervals.minAsLongArray( target ); - final long[] max = Intervals.maxAsLongArray( target ); - - final long largestDimMin = target.min( largestDim ); - final long largestDimMax = target.max( largestDim ); - - final ArrayList< Callable< Void > > tasks = new ArrayList<>(); - for ( long m = largestDimMin, M = largestDimMin + stepPerChunk - 1; m <= largestDimMax; m += stepPerChunk, M += stepPerChunk ) - { - min[ largestDim ] = m; - max[ largestDim ] = Math.min( M, largestDimMax ); - final Interval fi = new FinalInterval( min, max ); - tasks.add( () -> { - transformL1AlongDimension( source, Views.interval( target, fi ), dim, weight ); - return null; - } ); - } - - invokeAllAndWait( es, tasks ); - + final int lastDim = target.numDimensions() - 1; + // do not permute if we already work on last dimension + return Views.collapseReal( dim == lastDim ? target : Views.permute( target, dim, lastDim ) ); } private static < T extends RealType< T >, U extends RealType< U > > void transformL1SingleColumn( @@ -1298,19 +1151,6 @@ private static < T extends RealType< T >, U extends RealType< U > > void transfo } - /** - * Convenience method to invoke all tasks with a given - * {@link ExecutorService}. - */ - private static < T > void invokeAllAndWait( final ExecutorService es, final Collection< Callable< T > > tasks ) throws InterruptedException, ExecutionException - { - final List< Future< T > > futures = es.invokeAll( tasks ); - for ( final Future< T > f : futures ) - { - f.get(); - } - } - /** * Convenience method for creating an appropriate storage img: * {@link ArrayImg} if size is less than {@link Integer#MAX_VALUE}, diff --git a/src/main/java/net/imglib2/algorithm/stats/ComputeMinMax.java b/src/main/java/net/imglib2/algorithm/stats/ComputeMinMax.java index 009be9d36..7fba1f043 100644 --- a/src/main/java/net/imglib2/algorithm/stats/ComputeMinMax.java +++ b/src/main/java/net/imglib2/algorithm/stats/ComputeMinMax.java @@ -34,21 +34,20 @@ package net.imglib2.algorithm.stats; -import java.util.Vector; -import java.util.concurrent.atomic.AtomicInteger; - -import net.imglib2.Cursor; import net.imglib2.IterableInterval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.Algorithm; import net.imglib2.algorithm.Benchmark; import net.imglib2.algorithm.MultiThreaded; -import net.imglib2.multithreading.Chunk; -import net.imglib2.multithreading.SimpleMultiThreading; +import net.imglib2.loops.IterableLoopBuilder; +import net.imglib2.parallel.Parallelization; import net.imglib2.type.Type; import net.imglib2.util.Util; +import net.imglib2.util.ValuePair; import net.imglib2.view.Views; +import java.util.List; + /** * TODO * @@ -63,7 +62,7 @@ public class ComputeMinMax< T extends Type< T > & Comparable< T >> implements Al * @param min * @param max */ - final public static < T extends Comparable< T > & Type< T > > void computeMinMax( final RandomAccessibleInterval< T > interval, final T min, final T max ) + public static < T extends Comparable< T > & Type< T > > void computeMinMax( final RandomAccessibleInterval< T > interval, final T min, final T max ) { final ComputeMinMax< T > c = new ComputeMinMax< T >( Views.iterable( interval ), min, max ); c.process(); @@ -72,20 +71,18 @@ final public static < T extends Comparable< T > & Type< T > > void computeMinMax max.set( c.getMax() ); } - final IterableInterval< T > image; + private final IterableInterval< T > image; - final T min, max; + private final T min, max; - String errorMessage = ""; + private String errorMessage = ""; - int numThreads; + private int numThreads; - long processingTime; + private long processingTime; public ComputeMinMax( final IterableInterval< T > interval, final T min, final T max ) { - setNumThreads(); - this.image = interval; this.min = min; @@ -107,87 +104,52 @@ public boolean process() { final long startTime = System.currentTimeMillis(); - final long imageSize = image.size(); - - final AtomicInteger ai = new AtomicInteger( 0 ); - final Thread[] threads = SimpleMultiThreading.newThreads( getNumThreads() ); - - final Vector< Chunk > threadChunks = SimpleMultiThreading.divideIntoChunks( imageSize, numThreads ); - final Vector< T > minValues = new Vector< T >(); - final Vector< T > maxValues = new Vector< T >(); - - for ( int ithread = 0; ithread < threads.length; ++ithread ) - { - minValues.add( image.firstElement().createVariable() ); - maxValues.add( image.firstElement().createVariable() ); - - threads[ ithread ] = new Thread( new Runnable() - { - @Override - public void run() - { - // Thread ID - final int myNumber = ai.getAndIncrement(); - - // get chunk of pixels to process - final Chunk myChunk = threadChunks.get( myNumber ); - - // compute min and max - compute( myChunk.getStartPosition(), myChunk.getLoopSize(), minValues.get( myNumber ), maxValues.get( myNumber ) ); - - } - } ); - } - - SimpleMultiThreading.startAndJoin( threads ); - - // compute overall min and max - min.set( minValues.get( 0 ) ); - max.set( maxValues.get( 0 ) ); - - for ( int i = 0; i < threads.length; ++i ) - { - T value = minValues.get( i ); - if ( Util.min( min, value ) == value ) - min.set( value ); - - value = maxValues.get( i ); - if ( Util.max( max, value ) == value ) - max.set( value ); - } + if ( numThreads == 0 ) + computeMinAndMax(); + else + Parallelization.runWithNumThreads( numThreads, () -> computeMinAndMax() ); processingTime = System.currentTimeMillis() - startTime; return true; } - protected void compute( final long startPos, final long loopSize, final T min, final T max ) + private void computeMinAndMax() { - final Cursor< T > cursor = image.cursor(); - - // init min and max - cursor.fwd(); + T firstElement = image.firstElement(); + List< ValuePair< T, T > > listOfMinAndMaxValues = IterableLoopBuilder + .setImages( image ) + .multithreaded() + .forEachChunk( + chunk -> { + T min = firstElement.createVariable(); + T max = firstElement.createVariable(); + min.set( firstElement ); + max.set( firstElement ); + chunk.forEachPixel( value -> { + if ( Util.min( min, value ) == value ) + min.set( value ); + + if ( Util.max( max, value ) == value ) + max.set( value ); + } ); + return new ValuePair<>( min, max ); + } + ); - min.set( cursor.get() ); - max.set( cursor.get() ); - - cursor.reset(); + // compute overall min and max + computeGlobalMinAndMax( listOfMinAndMaxValues ); + } - // move to the starting position of the current thread - cursor.jumpFwd( startPos ); + public void computeGlobalMinAndMax( List< ValuePair< T, T > > listOfMinAndMaxValues ) + { + min.set( listOfMinAndMaxValues.get( 0 ).getA() ); + max.set( listOfMinAndMaxValues.get( 0 ).getB() ); - // do as many pixels as wanted by this thread - for ( long j = 0; j < loopSize; ++j ) + for ( ValuePair< T, T > minAndMax : listOfMinAndMaxValues ) { - cursor.fwd(); - - final T value = cursor.get(); - - if ( Util.min( min, value ) == value ) - min.set( value ); - - if ( Util.max( max, value ) == value ) - max.set( value ); + min.set( Util.min( min, minAndMax.getA() ) ); + max.set( Util.max( max, minAndMax.getB() ) ); } } @@ -216,7 +178,7 @@ public long getProcessingTime() @Override public void setNumThreads() { - this.numThreads = Runtime.getRuntime().availableProcessors(); + this.numThreads = 0; } @Override @@ -228,7 +190,7 @@ public void setNumThreads( final int numThreads ) @Override public int getNumThreads() { - return numThreads; + return numThreads == 0 ? Parallelization.getTaskExecutor().getParallelism() : numThreads; } @Override diff --git a/src/test/java/net/imglib2/algorithm/binary/ThresholderTest.java b/src/test/java/net/imglib2/algorithm/binary/ThresholderTest.java new file mode 100644 index 000000000..b5100a885 --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/binary/ThresholderTest.java @@ -0,0 +1,45 @@ +package net.imglib2.algorithm.binary; + +import net.imglib2.algorithm.util.TestImages; +import net.imglib2.img.Img; +import net.imglib2.test.ImgLib2Assert; +import net.imglib2.type.logic.BitType; +import net.imglib2.type.numeric.real.DoubleType; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + +/** + * Tests {@link Thresholder}. + * + * @author Matthias Arzt + */ +public class ThresholderTest +{ + private final Img input = TestImages.doubles2d( 100, 100, ( x, y ) -> x ); + + private final int numThreads = 4; + + private final double threshold = 50; + + @Test + public void testThresholdAbove() + { + boolean above = true; + Img result = Thresholder.threshold( input, new DoubleType( threshold ), above, numThreads ); + Img expected = TestImages.bits2d( 100, 100, (x, y) -> x > threshold ); + assertNotNull( result ); + ImgLib2Assert.assertImageEquals( expected, result ); + } + + @Test + public void testThresholdBelow() + { + boolean above = false; + Img result = Thresholder.threshold( input, new DoubleType( 50 ), above, numThreads ); + Img expected = TestImages.bits2d( 100, 100, (x, y) -> x < threshold ); + assertNotNull( result ); + ImgLib2Assert.assertImageEquals( expected, result ); + } + +} diff --git a/src/test/java/net/imglib2/algorithm/dog/DifferenceOfGaussianTest.java b/src/test/java/net/imglib2/algorithm/dog/DifferenceOfGaussianTest.java new file mode 100644 index 000000000..3cd4f65b1 --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/dog/DifferenceOfGaussianTest.java @@ -0,0 +1,62 @@ +package net.imglib2.algorithm.dog; + +import net.imglib2.Interval; +import net.imglib2.RandomAccessible; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.util.TestImages; +import net.imglib2.img.Img; +import net.imglib2.test.ImgLib2Assert; +import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.util.Intervals; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Tests {@link DifferenceOfGaussian}. + */ +public class DifferenceOfGaussianTest +{ + + private ExecutorService executorService; + + private final Interval interval = Intervals.createMinMax( -5, -5, 5, 5 ); + + private final RandomAccessible input = TestImages.dirac2d(); + + private final Img expected = TestImages.doubles2d( interval, + (x, y) -> gauss2d( 4, x, y ) - gauss2d( 2, x, y ) ); + + @Before + public void before() { + executorService = Executors.newFixedThreadPool( 8 ); + } + + @After + public void after() { + executorService.shutdown(); + } + + @Test + public void testDoG() { + RandomAccessibleInterval output = TestImages.createImg( new DoubleType(), interval ); + DifferenceOfGaussian.DoG( new double[]{ 2, 2 }, new double[]{4, 4}, input, output, executorService ); + ImgLib2Assert.assertImageEqualsRealType( expected, output, 0.0001 ); + } + + @Test + public void testDoG_WithTmpImage() { + RandomAccessibleInterval output = TestImages.createImg( new DoubleType(), interval ); + RandomAccessibleInterval tmp = TestImages.createImg( new DoubleType(), interval ); + DifferenceOfGaussian.DoG( new double[]{ 2, 2 }, new double[]{4, 4}, input, tmp, output, executorService ); + ImgLib2Assert.assertImageEqualsRealType( expected, output, 0.0001 ); + } + + private double gauss2d( double sigma, int x, int y ) + { + return 1 / (sigma * sigma * 2 * Math.PI ) * Math.exp( - 0.5 * (x * x + y * y) / (sigma * sigma) ); + } +} diff --git a/src/test/java/net/imglib2/algorithm/labeling/ConnectedComponentsTest.java b/src/test/java/net/imglib2/algorithm/labeling/ConnectedComponentsTest.java index b76825213..62d484e83 100644 --- a/src/test/java/net/imglib2/algorithm/labeling/ConnectedComponentsTest.java +++ b/src/test/java/net/imglib2/algorithm/labeling/ConnectedComponentsTest.java @@ -11,13 +11,13 @@ * %% * 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 @@ -34,117 +34,77 @@ package net.imglib2.algorithm.labeling; -import static net.imglib2.algorithm.labeling.ConnectedComponents.StructuringElement.EIGHT_CONNECTED; -import static net.imglib2.algorithm.labeling.ConnectedComponents.StructuringElement.FOUR_CONNECTED; -import static org.junit.Assert.assertEquals; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; - -import net.imglib2.Cursor; -import net.imglib2.algorithm.labeling.ConnectedComponents; +import net.imglib2.Interval; import net.imglib2.algorithm.labeling.ConnectedComponents.StructuringElement; +import net.imglib2.algorithm.util.TestImages; import net.imglib2.img.Img; import net.imglib2.img.array.ArrayImgs; +import net.imglib2.loops.LoopBuilder; import net.imglib2.roi.labeling.ImgLabeling; -import net.imglib2.roi.labeling.LabelingType; import net.imglib2.type.logic.BitType; -import net.imglib2.type.numeric.integer.UnsignedShortType; - +import net.imglib2.type.numeric.IntegerType; +import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.util.Intervals; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import java.util.HashMap; +import java.util.Iterator; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static net.imglib2.algorithm.labeling.ConnectedComponents.StructuringElement.EIGHT_CONNECTED; +import static net.imglib2.algorithm.labeling.ConnectedComponents.StructuringElement.FOUR_CONNECTED; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + /** - * TODO + * Tests {@link ConnectedComponents} * * @author Lee Kamentsky * @author Tobias Pietzsch */ public class ConnectedComponentsTest { - private void test2D( final boolean[][] input, final int[][] expected, final StructuringElement se, final int start, final int background ) - { - final long[] dimensions = new long[] { input.length, input[ 0 ].length }; - final Img< BitType > image = ArrayImgs.bits( dimensions ); - final Img< UnsignedShortType > indexImg = ArrayImgs.unsignedShorts( dimensions ); - final ImgLabeling< Integer, UnsignedShortType > labeling = new ImgLabeling< Integer, UnsignedShortType >( indexImg ); - - /* - * Fill the image. - */ - final Cursor< BitType > c = image.localizingCursor(); - final int[] position = new int[ 2 ]; - while ( c.hasNext() ) - { - final BitType t = c.next(); - c.localize( position ); - t.set( input[ position[ 0 ] ][ position[ 1 ] ] ); - } - /* - * Run the algorithm. - */ - final Iterator< Integer > names = new Iterator< Integer >() - { - private int i = start; - @Override - public boolean hasNext() - { - return true; - } + private ExecutorService executorService; - @Override - public Integer next() - { - return i++; - } - - @Override - public void remove() - {} - }; - ConnectedComponents.labelAllConnectedComponents( image, labeling, names, se ); + @Before + public void before() + { + executorService = Executors.newFixedThreadPool( 4 ); + } - /* - * Check the result - */ - final Cursor< LabelingType< Integer > > lc = labeling.localizingCursor(); - final HashMap< Integer, Integer > map = new HashMap< Integer, Integer >(); - while ( lc.hasNext() ) - { - final LabelingType< Integer > labels = lc.next(); - lc.localize( position ); - final int expectedValue = expected[ ( position[ 0 ] ) ][ ( position[ 1 ] ) ]; - if ( expectedValue == background ) - assertEquals( labels.size(), 0 ); - else - { - assertEquals( labels.size(), 1 ); - final Integer value = labels.iterator().next(); - if ( map.containsKey( value ) ) - assertEquals( expectedValue, map.get( value ).intValue() ); - else - map.put( value, expectedValue ); - } - } + @After + public void after() + { + executorService.shutdown(); } @Test public void testEmpty() { - final boolean[][] input = new boolean[ 3 ][ 3 ]; - final int[][] expected = new int[ 3 ][ 3 ]; - test2D( input, expected, FOUR_CONNECTED, 1, 0 ); - test2D( input, expected, EIGHT_CONNECTED, 1, 0 ); + final Img input = ArrayImgs.bits( 3, 3 ); + final Img expected = ArrayImgs.ints( 3, 3 ); + test( input, expected, FOUR_CONNECTED ); + test( input, expected, EIGHT_CONNECTED ); } @Test public void testOne() { - final boolean[][] input = new boolean[][] { { false, false, false }, { false, true, false }, { false, false, false } }; - final int[][] expected = new int[][] { { 0, 0, 0 }, { 0, 1, 0 }, { 0, 0, 0 } }; - test2D( input, expected, FOUR_CONNECTED, 1, 0 ); - test2D( input, expected, EIGHT_CONNECTED, 1, 0 ); + final Img input = createBitTypeImage( new boolean[][] { // + { false, false, false }, // + { false, true, false }, // + { false, false, false } } ); + final Img expected = ArrayImgs.ints( new int[] { // + 0, 0, 0, // + 0, 1, 0, // + 0, 0, 0 // + }, 3, 3 ); + test( input, expected, FOUR_CONNECTED ); + test( input, expected, EIGHT_CONNECTED ); } @Test @@ -153,79 +113,212 @@ public void testOutOfBounds() /* * Make sure that the labeler can handle out of bounds conditions */ - final long[][] offsets = new long[][] { { -1, -1 }, { 0, -1 }, { 1, -1 }, { -1, 0 }, { 1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 } }; + final long[][] offsets = new long[][] { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 0, 1 }, { 2, 1 }, { 0, 2 }, { 1, 2 }, { 2, 2 } }; for ( final long[] offset : offsets ) { - final boolean[][] input = new boolean[ 3 ][ 3 ]; - final int[][] expected = new int[ 3 ][ 3 ]; - - input[ ( int ) ( offset[ 0 ] ) + 1 ][ ( int ) ( offset[ 1 ] ) + 1 ] = true; - expected[ ( int ) ( offset[ 0 ] ) + 1 ][ ( int ) ( offset[ 1 ] ) + 1 ] = 1; - test2D( input, expected, FOUR_CONNECTED, 1, 0 ); - test2D( input, expected, EIGHT_CONNECTED, 1, 0 ); + final Img input = ArrayImgs.bits( 3, 3 ); + final Img expected = ArrayImgs.ints( 3, 3 ); + input.getAt( offset[ 0 ], offset[ 1 ] ).set( true ); + expected.getAt( offset[ 0 ], offset[ 1 ] ).set( 1 ); + test( input, expected, FOUR_CONNECTED ); + test( input, expected, EIGHT_CONNECTED ); } } @Test public void testOneObject() { - final boolean[][] input = new boolean[][] { { false, false, false, false, false }, { false, true, true, true, false }, { false, true, true, true, false }, { false, true, true, true, false }, { false, false, false, false, false } }; - final int[][] expected = new int[][] { { 0, 0, 0, 0, 0 }, { 0, 1, 1, 1, 0 }, { 0, 1, 1, 1, 0 }, { 0, 1, 1, 1, 0 }, { 0, 0, 0, 0, 0 } }; - test2D( input, expected, FOUR_CONNECTED, 1, 0 ); - test2D( input, expected, EIGHT_CONNECTED, 1, 0 ); + final Img input2 = createBitTypeImage( new boolean[][] { // + { false, false, false, false, false }, // + { false, true, true, true, false }, // + { false, true, true, true, false }, // + { false, true, true, true, false }, // + { false, false, false, false, false } } ); + final Img expected2 = ArrayImgs.ints( new int[] { // + 0, 0, 0, 0, 0, // + 0, 1, 1, 1, 0, // + 0, 1, 1, 1, 0, // + 0, 1, 1, 1, 0, // + 0, 0, 0, 0, 0 // + }, 5, 5 ); + test( input2, expected2, FOUR_CONNECTED ); + test( input2, expected2, EIGHT_CONNECTED ); } @Test public void testTwoObjects() { - final boolean[][] input = new boolean[][] { { false, false, false, false, false }, { false, true, true, true, false }, { false, false, false, false, false }, { false, true, true, true, false }, { false, false, false, false, false } }; - final int[][] expected = new int[][] { { 0, 0, 0, 0, 0 }, { 0, 1, 1, 1, 0 }, { 0, 0, 0, 0, 0 }, { 0, 2, 2, 2, 0 }, { 0, 0, 0, 0, 0 } }; - test2D( input, expected, FOUR_CONNECTED, 1, 0 ); - test2D( input, expected, EIGHT_CONNECTED, 1, 0 ); + final Img input2 = createBitTypeImage( new boolean[][] { // + { false, false, false, false, false }, // + { false, true, true, true, false }, // + { false, false, false, false, false }, // + { false, true, true, true, false }, // + { false, false, false, false, false } } ); + final Img expected2 = ArrayImgs.ints( new int[] { // + 0, 0, 0, 0, 0, // + 0, 1, 1, 1, 0, // + 0, 0, 0, 0, 0, // + 0, 2, 2, 2, 0, // + 0, 0, 0, 0, 0 // + }, 5, 5 ); + test( input2, expected2, FOUR_CONNECTED ); + test( input2, expected2, EIGHT_CONNECTED ); } @Test public void testBigObject() { - final boolean[][] input = new boolean[ 25 ][ 25 ]; - final int[][] expected = new int[ 25 ][ 25 ]; - for ( int i = 0; i < input.length; i++ ) - { - Arrays.fill( input[ i ], true ); - Arrays.fill( expected[ i ], 1 ); - } - test2D( input, expected, FOUR_CONNECTED, 1, 0 ); - test2D( input, expected, EIGHT_CONNECTED, 1, 0 ); + final Img input = TestImages.bits2d( 25, 25, ( x, y ) -> true ); + final Img expected = TestImages.ints2d( 25, 25, ( x, y ) -> 1 ); + test( input, expected, FOUR_CONNECTED ); + test( input, expected, EIGHT_CONNECTED ); } @Test public void testBigBigObject() { - final boolean[][] input = new boolean[ 100 ][ 100 ]; - final int[][] expected = new int[ 100 ][ 100 ]; - for ( int i = 0; i < input.length; i++ ) - { - Arrays.fill( input[ i ], true ); - Arrays.fill( expected[ i ], 1 ); - } - test2D( input, expected, FOUR_CONNECTED, 1, 0 ); - test2D( input, expected, EIGHT_CONNECTED, 1, 0 ); + final Img input = TestImages.bits2d( 100, 100, ( x, y ) -> true ); + final Img expected = TestImages.ints2d( 100, 100, ( x, y ) -> 1 ); + test( input, expected, FOUR_CONNECTED ); + test( input, expected, EIGHT_CONNECTED ); } + final Img input = ArrayImgs.ints( new int[] { // + 0, 0, 0, 0, 0, // + 0, 1, 1, 1, 0, // + 1, 0, 0, 0, 0, // + 0, 1, 1, 1, 0, // + 0, 0, 0, 0, 1 // + }, 5, 5 ); + + final Img expectedFourConnected = ArrayImgs.ints( new int[] { // + 0, 0, 0, 0, 0, // + 0, 1, 1, 1, 0, // + 3, 0, 0, 0, 0, // + 0, 2, 2, 2, 0, // + 0, 0, 0, 0, 4 // + }, 5, 5 ); + + final Img expectedEightConnected = ArrayImgs.ints( new int[] { // + 0, 0, 0, 0, 0, // + 0, 1, 1, 1, 0, // + 1, 0, 0, 0, 0, // + 0, 1, 1, 1, 0, // + 0, 0, 0, 0, 1 // + }, 5, 5 ); + @Test public void testFourConnected() { - final boolean[][] input = new boolean[][] { { false, false, false, false, false }, { false, true, true, true, false }, { true, false, false, false, false }, { false, true, true, true, false }, { false, false, false, false, true } }; - final int[][] expected4 = new int[][] { { 0, 0, 0, 0, 0 }, { 0, 1, 1, 1, 0 }, { 3, 0, 0, 0, 0 }, { 0, 2, 2, 2, 0 }, { 0, 0, 0, 0, 4 } }; - test2D( input, expected4, FOUR_CONNECTED, 1, 0 ); - + test( input, expectedFourConnected, FOUR_CONNECTED ); } @Test public void testEightConnected() { - final boolean[][] input = new boolean[][] { { false, false, false, false, false }, { false, true, true, true, false }, { true, false, false, false, false }, { false, true, true, true, false }, { false, false, false, false, true } }; - final int[][] expected8 = new int[][] { { 0, 0, 0, 0, 0 }, { 0, 1, 1, 1, 0 }, { 1, 0, 0, 0, 0 }, { 0, 1, 1, 1, 0 }, { 0, 0, 0, 0, 1 } }; - test2D( input, expected8, EIGHT_CONNECTED, 1, 0 ); + test( input, expectedEightConnected, EIGHT_CONNECTED ); + } + + private > void test( Img input, Img expected, StructuringElement se ) + { + final ImgLabeling labeling = createLabeling( input ); + ConnectedComponents.labelAllConnectedComponents( input, labeling, new IntegerIterator(), se ); + checkResult( expected, labeling ); + } + + @Test + public void testExecutorService() + { + final ImgLabeling labeling = createLabeling( input ); + ConnectedComponents.labelAllConnectedComponents( input, labeling, new IntegerIterator(), FOUR_CONNECTED, executorService ); + checkResult( expectedFourConnected, labeling ); + } + + @Test + public void testOutputIntType() + { + Img output = ArrayImgs.ints( Intervals.dimensionsAsLongArray( input ) ); + ConnectedComponents.labelAllConnectedComponents( input, output, FOUR_CONNECTED ); + checkResult( expectedFourConnected, output ); + } + + @Test + public void testOutputIntTypeAndExecutorService() + { + Img output = ArrayImgs.ints( Intervals.dimensionsAsLongArray( input ) ); + ConnectedComponents.labelAllConnectedComponents( input, output, FOUR_CONNECTED, executorService ); + checkResult( expectedFourConnected, output ); + } + + private > ImgLabeling createLabeling( Interval interval ) + { + return new ImgLabeling<>( ArrayImgs.unsignedShorts( Intervals.dimensionsAsLongArray( interval ) ) ); + } + + private Img createBitTypeImage( boolean[][] input ) + { + int width = input.length; + int height = input[ 0 ].length; + return TestImages.bits2d( width, height, ( x, y ) -> input[ y ][ x ] ); + } + + private void checkResult( Img expected, ImgLabeling labeling ) + { + final HashMap map = new HashMap<>(); + LoopBuilder.setImages( expected, labeling ).forEachPixel( ( expectedPixel, labels ) -> { + final int expectedValue = expectedPixel.get(); + if ( expectedValue == 0 ) + assertEquals( labels.size(), 0 ); + else + { + assertEquals( labels.size(), 1 ); + final Integer value = labels.iterator().next(); + if ( map.containsKey( value ) ) + assertEquals( expectedValue, map.get( value ).intValue() ); + else + map.put( value, expectedValue ); + } + } ); + } + + private void checkResult( Img expected, Img labeling ) + { + final HashMap map = new HashMap<>(); + LoopBuilder.setImages( expected, labeling ).forEachPixel( ( expectedPixel, labels ) -> { + final int expectedValue = expectedPixel.get(); + final int labelValue = labels.get(); + if ( expectedValue == 0 ) + assertEquals( 0, labelValue ); + else + { + assertNotEquals( 0, labelValue ); + if ( map.containsKey( labelValue ) ) + assertEquals( expectedValue, map.get( labelValue ).intValue() ); + else + map.put( labelValue, expectedValue ); + } + } ); + } + + private static class IntegerIterator implements Iterator + { + private int i = 1; + + @Override + public boolean hasNext() + { + return true; + } + + @Override + public Integer next() + { + return i++; + } + + @Override + public void remove() + { + } } } diff --git a/src/test/java/net/imglib2/algorithm/localextrema/LocalExtremaBenchmark.java b/src/test/java/net/imglib2/algorithm/localextrema/LocalExtremaBenchmark.java new file mode 100644 index 000000000..944d42f7c --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/localextrema/LocalExtremaBenchmark.java @@ -0,0 +1,62 @@ +package net.imglib2.algorithm.localextrema; + +import net.imglib2.Point; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.neighborhood.RectangleShape; +import net.imglib2.parallel.Parallelization; +import net.imglib2.test.RandomImgs; +import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.view.Views; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +/** + * Demonstrates how to use {@link Parallelization} to execute a algorithm + * single threaded / multi threaded .... + * And shows the execution time. + */ +@Fork( 0 ) +@Warmup( iterations = 10, time = 200, timeUnit = TimeUnit.MILLISECONDS ) +@Measurement( iterations = 10, time = 200, timeUnit = TimeUnit.MILLISECONDS ) +@State( Scope.Benchmark ) +@BenchmarkMode( { Mode.AverageTime } ) +public class LocalExtremaBenchmark +{ + + private final RandomAccessibleInterval< IntType > image = RandomImgs.seed( 42 ).nextImage( new IntType(), 100, 100, 100 ); + + @Benchmark + public List< Point > benchmark() throws ExecutionException, InterruptedException + { + LocalExtrema.LocalNeighborhoodCheck< Point, IntType > check = new LocalExtrema.MaximumCheck( new IntType( Integer.MIN_VALUE )); + return LocalExtrema.findLocalExtrema( image, check, new RectangleShape( 5, true ) ); + } + + public static void main( String... args ) throws RunnerException + { + Options options = new OptionsBuilder() + .include( LocalExtremaBenchmark.class.getName() ) + .build(); + new Runner( options ).run(); + } +} diff --git a/src/test/java/net/imglib2/algorithm/morphology/DilationTest.java b/src/test/java/net/imglib2/algorithm/morphology/DilationTest.java index 4e4f5aa0c..aab7d7787 100644 --- a/src/test/java/net/imglib2/algorithm/morphology/DilationTest.java +++ b/src/test/java/net/imglib2/algorithm/morphology/DilationTest.java @@ -33,11 +33,6 @@ */ package net.imglib2.algorithm.morphology; -import static org.junit.Assert.assertEquals; - -import java.util.List; -import java.util.Random; - import net.imglib2.Cursor; import net.imglib2.FinalInterval; import net.imglib2.Interval; @@ -45,17 +40,25 @@ import net.imglib2.algorithm.neighborhood.DiamondShape; import net.imglib2.algorithm.neighborhood.Neighborhood; import net.imglib2.algorithm.neighborhood.Shape; +import net.imglib2.converter.RealTypeConverters; import net.imglib2.img.Img; import net.imglib2.img.array.ArrayImgs; +import net.imglib2.test.ImgLib2Assert; import net.imglib2.type.logic.BitType; +import net.imglib2.type.numeric.integer.IntType; import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.util.Intervals; import net.imglib2.util.Util; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; - import org.junit.Before; import org.junit.Test; +import java.util.List; +import java.util.Random; + +import static org.junit.Assert.assertEquals; + public class DilationTest { @@ -234,4 +237,39 @@ public void testDilateToFull() randomAccess3.get().get(), cursor1.get().get() ); } } + + private final Img input = ArrayImgs.ints( new int[] { // + 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0 // + }, 5, 5 ); + + private final Img expected =ArrayImgs.ints( new int[] { // + 0, 0, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 1, 1, 1, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 0, 0 // + }, 5, 5 ); + + private final Shape structuringElement = new DiamondShape( 1 ); + + @Test + public void testDilate() { + Img output = Dilation.dilate( input, structuringElement, 1 ); + ImgLib2Assert.assertImageEquals( expected, output ); + } + + @Test + public void testDilateBitType() { + // setup + Img inputBits = ArrayImgs.bits( Intervals.dimensionsAsLongArray( input ) ); + RealTypeConverters.copyFromTo( input, inputBits ); + // process + Img output = Dilation.dilate( inputBits, structuringElement, 1 ); + // test + ImgLib2Assert.assertImageEqualsIntegerType( expected, output ); + } } diff --git a/src/test/java/net/imglib2/algorithm/morphology/ErosionTest.java b/src/test/java/net/imglib2/algorithm/morphology/ErosionTest.java index 488b41723..857ed0929 100644 --- a/src/test/java/net/imglib2/algorithm/morphology/ErosionTest.java +++ b/src/test/java/net/imglib2/algorithm/morphology/ErosionTest.java @@ -45,10 +45,14 @@ import net.imglib2.algorithm.neighborhood.DiamondShape; import net.imglib2.algorithm.neighborhood.Neighborhood; import net.imglib2.algorithm.neighborhood.Shape; +import net.imglib2.converter.RealTypeConverters; import net.imglib2.img.Img; import net.imglib2.img.array.ArrayImgs; +import net.imglib2.test.ImgLib2Assert; import net.imglib2.type.logic.BitType; +import net.imglib2.type.numeric.integer.IntType; import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.util.Intervals; import net.imglib2.util.Util; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; @@ -240,4 +244,38 @@ public void testErodeToFull() } } + private final Img input = ArrayImgs.ints( new int[] { // + 0, 0, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 1, 1, 1, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 0, 0 // + }, 5, 5 ); + + private final Img expected =ArrayImgs.ints( new int[] { // + 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0 // + }, 5, 5 ); + + private final Shape structuringElement = new DiamondShape( 1 ); + + @Test + public void testErode() { + Img output = Erosion.erode( input, structuringElement, 1 ); + ImgLib2Assert.assertImageEquals( expected, output ); + } + + @Test + public void testErodeBitType() { + // setup + Img inputBits = ArrayImgs.bits( Intervals.dimensionsAsLongArray( input ) ); + RealTypeConverters.copyFromTo( input, inputBits ); + // process + Img output = Erosion.erode( inputBits, structuringElement, 1 ); + // test + ImgLib2Assert.assertImageEqualsIntegerType( expected, output ); + } } diff --git a/src/test/java/net/imglib2/algorithm/morphology/MorphologyUtilsTest.java b/src/test/java/net/imglib2/algorithm/morphology/MorphologyUtilsTest.java new file mode 100644 index 000000000..4e3207d9d --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/morphology/MorphologyUtilsTest.java @@ -0,0 +1,98 @@ +package net.imglib2.algorithm.morphology; + +import net.imglib2.FinalInterval; +import net.imglib2.IterableInterval; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.test.ImgLib2Assert; +import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.util.Intervals; +import net.imglib2.view.Views; +import org.junit.Test; + +public class MorphologyUtilsTest +{ + + Img input = ArrayImgs.ints(new int[] { // + 1, 2, 3, 4, // + 5, 6, 7, 8, // + 9, 10, 11, 12, // + 13, 14, 15, 16 // + }, 4, 4); + + Img expected = ArrayImgs.ints(new int[] { // + 0, 0, 0, 0, // + 0, 6, 7, 0, // + 0, 10, 11, 0, // + 0, 0, 0, 0 // + }, 4, 4); + + FinalInterval interval = Intervals.createMinSize( 1, 1, 2, 2 ); + + @Test + public void testCopy() { + Img output = ArrayImgs.ints( 4, 4 ); + IterableInterval crop = Views.interval( input, interval ); + MorphologyUtils.copy( crop, Views.extendZero( output ), 4 ); + ImgLib2Assert.assertImageEquals( expected, output ); + } + + @Test + public void testCopy2() { + Img output = ArrayImgs.ints( 4, 4 ); + IterableInterval crop = Views.interval( output, interval ); + MorphologyUtils.copy2( Views.extendZero( input ), crop, 4 ); + ImgLib2Assert.assertImageEquals( expected, output ); + } + + @Test + public void testCopyCropped() { + Img output = MorphologyUtils.copyCropped( input, new FinalInterval( 2, 2 ), 4 ); + ImgLib2Assert.assertImageEquals( Views.zeroMin( Views.interval( expected, interval ) ), output ); + } + + @Test + public void testSubAAB() { + Img a = ArrayImgs.ints( new int[] { 10, 30 }, 2 ); + Img b = ArrayImgs.ints( new int[] { 4, 5 }, 2 ); + Img expected = ArrayImgs.ints( new int[] { 10, 25 }, 2 ); + MorphologyUtils.subAAB( Views.extendZero(a), Views.interval( b, Intervals.createMinSize( 1, 1 ) ), 4 ); + ImgLib2Assert.assertImageEquals( expected, a ); + } + + @Test + public void testSubAAB2() { + Img a = ArrayImgs.ints( new int[] { 10, 30 }, 2 ); + Img b = ArrayImgs.ints( new int[] { 4, 5 }, 2 ); + Img expected = ArrayImgs.ints( new int[] { 10, 25 }, 2 ); + MorphologyUtils.subAAB2( Views.interval(a, Intervals.createMinSize( 1, 1 )), Views.extendZero( b ), 4 ); + ImgLib2Assert.assertImageEquals( expected, a ); + } + + @Test + public void testSubABA() { + Img a = ArrayImgs.ints( new int[] { 10, 30 }, 2 ); + Img b = ArrayImgs.ints( new int[] { 4, 5 }, 2 ); + Img expected = ArrayImgs.ints( new int[] { 10, -25 }, 2 ); + MorphologyUtils.subABA( Views.extendZero(a), Views.interval( b, Intervals.createMinSize( 1, 1 ) ), 4 ); + ImgLib2Assert.assertImageEquals( expected, a ); + } + + @Test + public void testSubABA2() { + Img a = ArrayImgs.ints( new int[] { 10, 30 }, 2 ); + Img b = ArrayImgs.ints( new int[] { 4, 5 }, 2 ); + Img expected = ArrayImgs.ints( new int[] { 4, -25 }, 2 ); + MorphologyUtils.subABA2( Views.interval(a, Intervals.createMinSize( 1, 1 )), Views.extendZero( b ), 4 ); + ImgLib2Assert.assertImageEquals( expected, b ); + } + + @Test + public void testSubABA3() { + Img a = ArrayImgs.ints( new int[] { 10, 30 }, 2 ); + Img b = ArrayImgs.ints( new int[] { 4, 5 }, 2 ); + Img expected = ArrayImgs.ints( new int[] { 4, 25 }, 2 ); + MorphologyUtils.subBAB( Views.extendZero( a ), Views.interval(b, Intervals.createMinSize( 1, 1 )), 4 ); + ImgLib2Assert.assertImageEquals( expected, b ); + } +} diff --git a/src/test/java/net/imglib2/algorithm/stats/ComputeMinMaxTest.java b/src/test/java/net/imglib2/algorithm/stats/ComputeMinMaxTest.java new file mode 100644 index 000000000..0a074876c --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/stats/ComputeMinMaxTest.java @@ -0,0 +1,21 @@ +package net.imglib2.algorithm.stats; + +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.type.numeric.integer.IntType; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ComputeMinMaxTest +{ + @Test + public void testComputeMinMax() { + Img image = ArrayImgs.ints( new int[] { 100, 100, 50, 200 }, 2, 2 ); + IntType min = new IntType( 0 ); + IntType max = new IntType( 0 ); + ComputeMinMax.computeMinMax( image, min, max ); + assertEquals( new IntType(50), min ); + assertEquals( new IntType(200), max ); + } +} diff --git a/src/test/java/net/imglib2/algorithm/util/TestImages.java b/src/test/java/net/imglib2/algorithm/util/TestImages.java new file mode 100644 index 000000000..8d67b05e7 --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/util/TestImages.java @@ -0,0 +1,133 @@ +package net.imglib2.algorithm.util; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.Localizable; +import net.imglib2.RandomAccessible; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.converter.Converter; +import net.imglib2.converter.Converters; +import net.imglib2.img.Img; +import net.imglib2.img.ImgView; +import net.imglib2.img.array.ArrayImgFactory; +import net.imglib2.type.NativeType; +import net.imglib2.type.Type; +import net.imglib2.type.logic.BitType; +import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.util.Cast; +import net.imglib2.util.Intervals; +import net.imglib2.util.Localizables; +import net.imglib2.view.Views; + +/** + * Utility class to help create test images. + */ +public class TestImages +{ + public static > Img createImg( T type, Interval interval ) { + ArrayImgFactory factory = new ArrayImgFactory<>( type ); + Img zeroMinImg = factory.create( interval ); + long[] offset = Intervals.minAsLongArray( interval ); + RandomAccessibleInterval rai = Views.translate( zeroMinImg, offset ); + return ImgView.wrap( rai, factory ); + } + + /** + * @returns an image of given dimensions. The pixel values are set as + * specified in the content function. The image is read only. + */ + public static Img doubles2d( int width, int height, DoubleFunction2d content ) { + Interval interval = new FinalInterval( width, height ); + return doubles2d( interval, content ); + } + + /** + * @returns an image of given dimensions. The pixel values are set as + * specified in the content function. The image is read only. + */ + public static Img doubles2d( Interval interval, DoubleFunction2d content ) + { + return generateImg2d( interval, new DoubleType(), (x, y, pixel) -> pixel.set( content.apply( x, y ) ) ); + } + + /** + * @returns an image of given dimensions. The pixel values are set as + * specified in the content function. The image is read only. + */ + public static Img bits2d( int width, int height, BooleanFunction2d content ) { + Interval interval = new FinalInterval( width, height ); + return bits2d( interval, content ); + } + + /** + * @returns an image of given dimensions. The pixel values are set as + * specified in the content function. The image is read only. + */ + private static Img bits2d( Interval interval, BooleanFunction2d content ) + { + return generateImg2d( interval, new BitType(), (x, y, pixel) -> pixel.set( content.apply( x, y ) ) ); + } + + /** + * @returns an image of given dimensions. The pixel values are set as + * specified in the content function. The image is read only. + */ + public static Img ints2d( int width, int height, IntFunction2d content ) + { + Interval interval = new FinalInterval( width, height ); + return generateImg2d( interval, new IntType(), (x, y, pixel) -> pixel.set( content.apply( x, y ) ) ); + } + + public static > Img generateImg2d( Interval interval, T type, PixelFunction2d function ) + { + Converter converter = (position, pixel) -> { + int x = position.getIntPosition( 0 ); + int y = position.getIntPosition( 1 ); + function.apply( x, y, pixel ); + }; + return ImgView.wrap( Converters.convert( Intervals.positions( interval ), converter, type ), Cast.unchecked( new ArrayImgFactory<>( Cast.unchecked( type ) ) ) ); + } + + /** + * @return a two dimensional infinitely large image. (see {@link RandomAccessible}) + * The pixel a the origin is 1. All other pixels are zero. + */ + public static RandomAccessible dirac2d() + { + return doubles2d( (x, y) -> (x == 0 && y == 0) ? 1 : 0 ); + } + + /** + * @returns a two dimensional infinitely large image. The pixel values are + * as given by the specified content function. + */ + public static RandomAccessible doubles2d( DoubleFunction2d content ) { + Converter converter = (position, pixel) -> { + int x = position.getIntPosition( 0 ); + int y = position.getIntPosition( 1 ); + pixel.setReal( content.apply( x, y ) ); + }; + return Converters.convert( Localizables.randomAccessible( 2 ), converter, new DoubleType() ); + } + + public interface PixelFunction2d + { + void apply( int x, int y, T pixel ); + } + + public interface DoubleFunction2d + { + double apply( int x, int y ); + } + + public interface BooleanFunction2d + { + boolean apply( int x, int y ); + } + + public interface IntFunction2d + { + int apply( int x, int y ); + } +}