diff --git a/src/main/java/net/imglib2/algorithm/gradient/PartialDerivative.java b/src/main/java/net/imglib2/algorithm/gradient/PartialDerivative.java index 7a10407d2..35a439204 100644 --- a/src/main/java/net/imglib2/algorithm/gradient/PartialDerivative.java +++ b/src/main/java/net/imglib2/algorithm/gradient/PartialDerivative.java @@ -34,21 +34,18 @@ 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; /** @@ -98,7 +95,7 @@ public static < T extends NumericType< T > > void gradientCentralDifference2( fi * @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 + * @param result * output image * @param dimension * along which dimension the partial derivatives are computed @@ -110,57 +107,42 @@ public static < T extends NumericType< T > > void gradientCentralDifference2( fi */ 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 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; - } ); - } + TaskExecutor taskExecutor = TaskExecutors.forExecutorServiceAndNumTasks( es, nTasks ); + Parallelization.runWithExecutor( taskExecutor, () -> { + gradientCentralDerivativeParallel( source, result, dimension ); + } ); + } - final List< Future< Void > > futures = es.invokeAll( tasks ); + /** + * 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 result + * output image + * @param dimension + * along which dimension the partial derivatives are computed + */ + private static > void gradientCentralDerivativeParallel( RandomAccessible source, + RandomAccessibleInterval result, int dimension ) + { + final RandomAccessibleInterval back = Views.interval( source, Intervals.translate( result, -1, dimension ) ); + final RandomAccessibleInterval front = Views.interval( source, Intervals.translate( result, 1, dimension ) ); - for ( final Future< Void > f : futures ) - f.get(); + LoopBuilder.setImages( result, back, front ).multiThreaded().forEachPixel( ( r, b, f ) -> { + r.set( f ); + r.sub( b ); + r.mul( 0.5 ); + } ); } // fast version @@ -181,13 +163,8 @@ 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 ) ); - - LoopBuilder.setImages( result, back, front ).forEachPixel( ( r, b, f ) -> { - r.set( f ); - r.sub( b ); - r.mul( 0.5 ); + Parallelization.runSingleThreaded( () -> { + gradientCentralDerivativeParallel( source, result, dimension ); } ); } diff --git a/src/main/java/net/imglib2/algorithm/localextrema/LocalExtrema.java b/src/main/java/net/imglib2/algorithm/localextrema/LocalExtrema.java index 15ad1bedf..2e5a76386 100644 --- a/src/main/java/net/imglib2/algorithm/localextrema/LocalExtrema.java +++ b/src/main/java/net/imglib2/algorithm/localextrema/LocalExtrema.java @@ -33,33 +33,35 @@ */ package net.imglib2.algorithm.localextrema; -import java.util.ArrayList; -import java.util.Arrays; -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.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; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.stream.IntStream; +import java.util.stream.LongStream; + /** * Provides {@link #findLocalExtrema} to find pixels that are extrema in their * local neighborhood. @@ -320,38 +322,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 +442,28 @@ 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( Collection> lists ) + { + 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 19040654f..d59b1ef88 100644 --- a/src/main/java/net/imglib2/algorithm/localextrema/SubpixelLocalization.java +++ b/src/main/java/net/imglib2/algorithm/localextrema/SubpixelLocalization.java @@ -34,14 +34,9 @@ package net.imglib2.algorithm.localextrema; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - +import Jama.LUDecomposition; +import Jama.Matrix; +import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.Localizable; import net.imglib2.Point; @@ -49,17 +44,22 @@ 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; -import Jama.LUDecomposition; -import Jama.Matrix; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * Refine a set of peaks to subpixel coordinates. This class provides the static * {@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 @@ -68,7 +68,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 */ @@ -93,8 +93,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 ) @@ -164,14 +162,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 @@ -184,7 +182,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 ); } /** @@ -197,7 +201,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 @@ -231,45 +235,9 @@ 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 ArrayList< RefinedPeak< P > > allRefinedPeaks = new ArrayList< RefinedPeak< P > >( numPeaks ); - - if ( numPeaks == 0 ) - return allRefinedPeaks; - - final int numTasks = numThreads <= 1 ? 1 : ( int ) Math.min( numPeaks, numThreads * 20 ); - final int taskSize = numPeaks / numTasks; - - final ExecutorService ex = Executors.newFixedThreadPool( numThreads ); - final List< RefinedPeak< P > > synchronizedAllRefinedPeaks = Collections.synchronizedList( allRefinedPeaks ); - for ( int taskNum = 0; taskNum < numTasks; ++taskNum ) - { - final int fromIndex = taskNum * taskSize; - final int toIndex = ( taskNum == numTasks - 1 ) ? numPeaks : fromIndex + taskSize; - final Runnable r = new Runnable() - { - @Override - public void run() - { - final ArrayList< RefinedPeak< P > > refinedPeaks = refinePeaks( - peaks.subList( fromIndex, toIndex ), - img, validInterval, returnInvalidPeaks, maxNumMoves, allowMaximaTolerance, maximaTolerance, allowedToMoveInDim ); - synchronizedAllRefinedPeaks.addAll( refinedPeaks ); - } - }; - ex.execute( r ); - } - ex.shutdown(); - try - { - ex.awaitTermination( 1000, TimeUnit.DAYS ); - } - catch ( final InterruptedException e ) - { - e.printStackTrace(); - } - - return allRefinedPeaks; + return Parallelization.runWithNumThreads( numThreads, + () -> refinePeaks( peaks, img, validInterval, returnInvalidPeaks, maxNumMoves, allowMaximaTolerance, maximaTolerance, + allowedToMoveInDim ) ); } /** @@ -282,7 +250,7 @@ public void run() * 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 @@ -313,7 +281,36 @@ public static < T extends RealType< T >, P extends Localizable > ArrayList< Refi 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 int numPeaks = peaks.size(); + + if ( numPeaks == 0 ) + return new ArrayList<>(); + + TaskExecutor taskExecutor = Parallelization.getTaskExecutor(); + + List< Interval > chunks = IntervalChunks.chunkInterval( new FinalInterval( numPeaks ), taskExecutor.suggestNumberOfTasks() ); + + 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 concatenate( result ); + } + + 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; + } + + private static < T extends RealType< T >, P extends Localizable > ArrayList< RefinedPeak< P > > refinePeaksChunk( List< P > peaks, RandomAccessible< T > img, Interval validInterval, boolean returnInvalidPeaks, int maxNumMoves, boolean allowMaximaTolerance, float maximaTolerance, boolean[] allowedToMoveInDim ) + { + final ArrayList< RefinedPeak< P > > refinedPeaks = new ArrayList< RefinedPeak< P > >(); final int n = img.numDimensions(); @@ -420,7 +417,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 28b7adb7a..f544cdaeb 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,37 @@ 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 ); + break; } - }; - } - } + } ); 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 6a33b1e31..8dd67653b 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,37 @@ 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( getDilateAction( 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 > > getDilateAction( 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( true ); + break; } - }; - } - } + } ); 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 0144a4a37..cfb838135 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,14 +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.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; public class MorphologyUtils @@ -66,17 +61,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 +284,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 +304,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 +317,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 +329,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 +342,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 +395,85 @@ 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. * - * @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 ); + subABA( A, Views.interval( B, A ), numThreads ); } /** * 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 ); + a.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 89a399b33..41f5936ee 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 a7ea72cbd..24e7fecb1 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,49 @@ 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(); + List< ValuePair< T, T > > listOfMinAndMaxValues = IterableLoopBuilder + .setImages( image ) + .multithreaded() + .forEachChunk( + chunk -> { + T minValue = image.firstElement().createVariable(); + T maxValue = image.firstElement().createVariable(); + chunk.forEachPixel( value -> { + if ( Util.min( min, value ) == value ) + min.set( value ); + + if ( Util.max( max, value ) == value ) + max.set( value ); + } ); + return new ValuePair<>( minValue, maxValue ); + } + ); - 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 +175,7 @@ public long getProcessingTime() @Override public void setNumThreads() { - this.numThreads = Runtime.getRuntime().availableProcessors(); + this.numThreads = 0; } @Override @@ -228,7 +187,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/gauss3/Gauss3Benchmark.java b/src/test/java/net/imglib2/algorithm/gauss3/Gauss3Benchmark.java index bd26826af..351ca46a9 100644 --- a/src/test/java/net/imglib2/algorithm/gauss3/Gauss3Benchmark.java +++ b/src/test/java/net/imglib2/algorithm/gauss3/Gauss3Benchmark.java @@ -35,7 +35,6 @@ import net.imglib2.img.Img; import net.imglib2.img.array.ArrayImgs; -import net.imglib2.parallel.Parallelization; import net.imglib2.type.numeric.real.DoubleType; import net.imglib2.view.Views; import org.openjdk.jmh.annotations.*; 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(); + } +}