diff --git a/src/main/java/net/imglib2/algorithm/convolution/AbstractMultiThreadedConvolution.java b/src/main/java/net/imglib2/algorithm/convolution/AbstractMultiThreadedConvolution.java index ff7624189..c9bf4e246 100644 --- a/src/main/java/net/imglib2/algorithm/convolution/AbstractMultiThreadedConvolution.java +++ b/src/main/java/net/imglib2/algorithm/convolution/AbstractMultiThreadedConvolution.java @@ -51,6 +51,7 @@ * * @author Matthias Arzt */ +@Deprecated public abstract class AbstractMultiThreadedConvolution< T > implements Convolution< T > { @@ -61,6 +62,7 @@ abstract protected void process( RandomAccessible< ? extends T > source, ExecutorService executorService, int numThreads ); + @Deprecated @Override public void setExecutor( final ExecutorService executor ) { diff --git a/src/main/java/net/imglib2/algorithm/convolution/Concatenation.java b/src/main/java/net/imglib2/algorithm/convolution/Concatenation.java index 589476490..b897a5341 100644 --- a/src/main/java/net/imglib2/algorithm/convolution/Concatenation.java +++ b/src/main/java/net/imglib2/algorithm/convolution/Concatenation.java @@ -64,10 +64,12 @@ class Concatenation< T > implements Convolution< T > this.steps = new ArrayList<>( steps ); } + @Deprecated @Override - public void setExecutor( final ExecutorService executor ) + public void setExecutor( ExecutorService executor ) { - steps.forEach( step -> step.setExecutor( executor ) ); + for ( Convolution step : steps ) + step.setExecutor( executor ); } @Override diff --git a/src/main/java/net/imglib2/algorithm/convolution/Convolution.java b/src/main/java/net/imglib2/algorithm/convolution/Convolution.java index e5b980f53..b3b1296fe 100644 --- a/src/main/java/net/imglib2/algorithm/convolution/Convolution.java +++ b/src/main/java/net/imglib2/algorithm/convolution/Convolution.java @@ -67,6 +67,7 @@ public interface Convolution< T > /** * Set the {@link ExecutorService} to be used for convolution. */ + @Deprecated default void setExecutor( final ExecutorService executor ) {} diff --git a/src/main/java/net/imglib2/algorithm/convolution/LineConvolution.java b/src/main/java/net/imglib2/algorithm/convolution/LineConvolution.java index 9aca7da82..6666a0228 100644 --- a/src/main/java/net/imglib2/algorithm/convolution/LineConvolution.java +++ b/src/main/java/net/imglib2/algorithm/convolution/LineConvolution.java @@ -33,44 +33,50 @@ */ package net.imglib2.algorithm.convolution; -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 java.util.function.Consumer; -import java.util.function.Supplier; - 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.util.IntervalIndexer; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.Parallelization; +import net.imglib2.parallel.TaskExecutor; +import net.imglib2.parallel.TaskExecutors; +import net.imglib2.util.Cast; import net.imglib2.util.Intervals; +import net.imglib2.util.Localizables; import net.imglib2.view.Views; +import java.util.concurrent.ExecutorService; + /** * This class can be used to implement a separable convolution. It applies a * {@link LineConvolverFactory} on the given images. * * @author Matthias Arzt */ -public class LineConvolution< T > extends AbstractMultiThreadedConvolution< T > +public class LineConvolution< T > implements Convolution { private final LineConvolverFactory< ? super T > factory; private final int direction; + private ExecutorService executor; + public LineConvolution( final LineConvolverFactory< ? super T > factory, final int direction ) { this.factory = factory; this.direction = direction; } + @Deprecated + @Override + public void setExecutor( ExecutorService executor ) + { + this.executor = executor; + } + @Override public Interval requiredSourceInterval( final Interval targetInterval ) { @@ -84,104 +90,38 @@ public Interval requiredSourceInterval( final Interval targetInterval ) @Override public T preferredSourceType( final T targetType ) { - return (T) factory.preferredSourceType( targetType ); + return Cast.unchecked( factory.preferredSourceType( targetType ) ); } @Override - protected void process( final RandomAccessible< ? extends T > source, final RandomAccessibleInterval< ? extends T > target, final ExecutorService executorService, final int numThreads ) + public void process( RandomAccessible< ? extends T > source, RandomAccessibleInterval< ? extends T > target ) { final RandomAccessibleInterval< ? extends T > sourceInterval = Views.interval( source, requiredSourceInterval( target ) ); final long[] sourceMin = Intervals.minAsLongArray( sourceInterval ); final long[] targetMin = Intervals.minAsLongArray( target ); - final Supplier< Consumer< Localizable > > actionFactory = () -> { - - final RandomAccess< ? extends T > in = sourceInterval.randomAccess(); - final RandomAccess< ? extends T > out = target.randomAccess(); - final Runnable convolver = factory.getConvolver( in, out, direction, target.dimension( direction ) ); - - return position -> { - in.setPosition( sourceMin ); - out.setPosition( targetMin ); - in.move( position ); - out.move( position ); - convolver.run(); - }; - }; - final long[] dim = Intervals.dimensionsAsLongArray( target ); dim[ direction ] = 1; - final int numTasks = numThreads > 1 ? timesFourAvoidOverflow(numThreads) : 1; - LineConvolution.forEachIntervalElementInParallel( executorService, numTasks, new FinalInterval( dim ), actionFactory ); - } + RandomAccessibleInterval< Localizable > positions = Localizables.randomAccessibleInterval( new FinalInterval( dim ) ); + TaskExecutor taskExecutor = executor == null ? Parallelization.getTaskExecutor() : TaskExecutors.forExecutorService( executor ); + LoopBuilder.setImages( positions ).multiThreaded(taskExecutor).forEachChunk( + chunk -> { - private int timesFourAvoidOverflow( int x ) - { - return (int) Math.min((long) x * 4, Integer.MAX_VALUE); - } + final RandomAccess< ? extends T > in = sourceInterval.randomAccess(); + final RandomAccess< ? extends T > out = target.randomAccess(); + final Runnable convolver = factory.getConvolver( in, out, direction, target.dimension( direction ) ); - /** - * {@link #forEachIntervalElementInParallel(ExecutorService, int, Interval, Supplier)} - * executes a given action for each position in a given interval. Therefor - * it starts the specified number of tasks. Each tasks calls the action - * factory once, to get an instance of the action that should be executed. - * The action is then called multiple times by the task. - * - * @param service - * {@link ExecutorService} used to create the tasks. - * @param numTasks - * number of tasks to use. - * @param interval - * interval to iterate over. - * @param actionFactory - * factory that returns the action to be executed. - */ - // TODO: move to a better place - public static void forEachIntervalElementInParallel( final ExecutorService service, final int numTasks, final Interval interval, - final Supplier< Consumer< Localizable > > actionFactory ) - { - final long[] min = Intervals.minAsLongArray( interval ); - final long[] dim = Intervals.dimensionsAsLongArray( interval ); - final long size = Intervals.numElements( dim ); - final int boundedNumTasks = (int) Math.max( 1, Math.min(size, numTasks )); - final long taskSize = ( size - 1 ) / boundedNumTasks + 1; // taskSize = roundUp(size / boundedNumTasks); - final ArrayList< Callable< Void > > callables = new ArrayList<>(); + chunk.forEachPixel( position -> { + in.setPosition( sourceMin ); + out.setPosition( targetMin ); + in.move( position ); + out.move( position ); + convolver.run(); + } ); - for ( int taskNum = 0; taskNum < boundedNumTasks; ++taskNum ) - { - final long myStartIndex = taskNum * taskSize; - final long myEndIndex = Math.min( size, myStartIndex + taskSize ); - final Callable< Void > r = () -> { - final Consumer< Localizable > action = actionFactory.get(); - final long[] position = new long[ dim.length ]; - final Localizable localizable = Point.wrap( position ); - for ( long index = myStartIndex; index < myEndIndex; ++index ) - { - IntervalIndexer.indexToPositionWithOffset( index, dim, min, position ); - action.accept( localizable ); + return null; } - return null; - }; - callables.add( r ); - } - execute( service, callables ); - } - - private static void execute( final ExecutorService service, final ArrayList< Callable< Void > > callables ) - { - try - { - final List< Future< Void > > futures = service.invokeAll( callables ); - for ( final Future< Void > future : futures ) - future.get(); - } - catch ( final InterruptedException | ExecutionException e ) - { - final Throwable cause = e.getCause(); - if ( cause instanceof RuntimeException ) - throw ( RuntimeException ) cause; - throw new RuntimeException( e ); - } + ); } } diff --git a/src/main/java/net/imglib2/algorithm/convolution/MultiDimensionConvolution.java b/src/main/java/net/imglib2/algorithm/convolution/MultiDimensionConvolution.java index faef925c7..54a6bc633 100644 --- a/src/main/java/net/imglib2/algorithm/convolution/MultiDimensionConvolution.java +++ b/src/main/java/net/imglib2/algorithm/convolution/MultiDimensionConvolution.java @@ -53,6 +53,7 @@ public class MultiDimensionConvolution< T > implements Convolution< T > { private ExecutorService executor; + @Deprecated @Override public void setExecutor( final ExecutorService executor ) { diff --git a/src/main/java/net/imglib2/algorithm/gauss3/Gauss3.java b/src/main/java/net/imglib2/algorithm/gauss3/Gauss3.java index 0d17f73b7..569effbc1 100644 --- a/src/main/java/net/imglib2/algorithm/gauss3/Gauss3.java +++ b/src/main/java/net/imglib2/algorithm/gauss3/Gauss3.java @@ -34,8 +34,10 @@ package net.imglib2.algorithm.gauss3; +import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; @@ -43,6 +45,7 @@ import net.imglib2.algorithm.convolution.kernel.Kernel1D; import net.imglib2.algorithm.convolution.kernel.SeparableKernelConvolution; import net.imglib2.exception.IncompatibleTypeException; +import net.imglib2.parallel.Parallelization; import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.real.DoubleType; @@ -56,7 +59,7 @@ public final class Gauss3 { /** - * Apply Gaussian convolution to source and write the result to output. + * Apply Gaussian convolution to source and write the result to target. * In-place operation (source==target) is supported. * *

@@ -66,6 +69,11 @@ public final class Gauss3 * in their own precision. The source type S and target type T are either * both {@link RealType RealTypes} or both the same type. * + *

+ * Computation may be multi-threaded, according to the current + * {@link Parallelization} context. (By default, it will use the + * {@link ForkJoinPool#commonPool() common ForkJoinPool}) + * * @param sigma * standard deviation of isotropic Gaussian. * @param source @@ -93,7 +101,7 @@ public static < S extends NumericType< S >, T extends NumericType< T > > void ga } /** - * Apply Gaussian convolution to source and write the result to output. + * Apply Gaussian convolution to source and write the result to target. * In-place operation (source==target) is supported. * *

@@ -104,9 +112,10 @@ public static < S extends NumericType< S >, T extends NumericType< T > > void ga * both {@link RealType RealTypes} or both the same type. * *

- * Computation is multi-threaded with as many threads as processors - * available. - * + * Computation may be multi-threaded, according to the current + * {@link Parallelization} context. (By default, it will use the + * {@link ForkJoinPool#commonPool() common ForkJoinPool}) + * * @param sigma * standard deviation in every dimension. * @param source @@ -126,13 +135,27 @@ public static < S extends NumericType< S >, T extends NumericType< T > > void ga */ public static < S extends NumericType< S >, T extends NumericType< T > > void gauss( final double[] sigma, final RandomAccessible< S > source, final RandomAccessibleInterval< T > target ) throws IncompatibleTypeException { - final int numthreads = Runtime.getRuntime().availableProcessors(); - final ExecutorService service = Executors.newFixedThreadPool( numthreads ); - gauss( sigma, source, target, service ); - service.shutdown(); + final double[][] halfkernels = halfkernels( sigma ); + final Convolution< NumericType< ? > > convolution = SeparableKernelConvolution.convolution( Kernel1D.symmetric( halfkernels ) ); + convolution.process( source, target ); } /** + * @deprecated + * Deprecated. Please use + * {@link Gauss3#gauss(double, RandomAccessible, RandomAccessibleInterval) + * gauss(sigma, source, target)} instead. The number of threads used to + * calculate the Gaussion convolution may by set with the + * {@link Parallelization} context, as show in this example: + *

+	 * {@code
+	 * Parallelization.runWithNumThreads( numThreads,
+	 *    () -> gauss( sigma, source, target )
+	 * );
+	 * }
+	 * 
+ * + *

* Apply Gaussian convolution to source and write the result to output. * In-place operation (source==target) is supported. * @@ -162,14 +185,30 @@ public static < S extends NumericType< S >, T extends NumericType< T > > void ga * if source and target type are not compatible (they must be * either both {@link RealType RealTypes} or the same type). */ + @Deprecated public static < S extends NumericType< S >, T extends NumericType< T > > void gauss( final double[] sigma, final RandomAccessible< S > source, final RandomAccessibleInterval< T > target, final int numThreads ) throws IncompatibleTypeException { - final ExecutorService service = Executors.newFixedThreadPool( numThreads ); - gauss( sigma, source, target, service ); - service.shutdown(); + Parallelization.runWithNumThreads( numThreads, + () -> gauss( sigma, source, target ) + ); } /** + * @deprecated + * Deprecated. Please use + * {@link Gauss3#gauss(double, RandomAccessible, RandomAccessibleInterval) + * gauss(sigma, source, target)} instead. The ExecutorService used to + * calculate the Gaussion convolution may by set with the + * {@link Parallelization} context, as show in this example: + *

+	 * {@code
+	 * Parallelization.runWithExecutor( executorService,
+	 *    () -> gauss( sigma, source, target )
+	 * );
+	 * }
+	 * 
+ * + *

* Apply Gaussian convolution to source and write the result to output. * In-place operation (source==target) is supported. * @@ -199,12 +238,12 @@ public static < S extends NumericType< S >, T extends NumericType< T > > void ga * if source and target type are not compatible (they must be * either both {@link RealType RealTypes} or the same type). */ + @Deprecated public static < S extends NumericType< S >, T extends NumericType< T > > void gauss( final double[] sigma, final RandomAccessible< S > source, final RandomAccessibleInterval< T > target, final ExecutorService service ) throws IncompatibleTypeException { - final double[][] halfkernels = halfkernels( sigma ); - final Convolution< NumericType< ? > > convolution = SeparableKernelConvolution.convolution( Kernel1D.symmetric( halfkernels ) ); - convolution.setExecutor( service ); - convolution.process( source, target ); + Parallelization.runWithExecutor( service, + () -> gauss( sigma, source, target ) + ); } public static double[][] halfkernels( final double[] sigma ) diff --git a/src/test/java/net/imglib2/algorithm/convolution/ConcatenationTest.java b/src/test/java/net/imglib2/algorithm/convolution/ConcatenationTest.java index ab9d78fa8..a893fd563 100644 --- a/src/test/java/net/imglib2/algorithm/convolution/ConcatenationTest.java +++ b/src/test/java/net/imglib2/algorithm/convolution/ConcatenationTest.java @@ -37,11 +37,8 @@ import net.imglib2.Interval; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; -import net.imglib2.algorithm.convolution.kernel.SeparableKernelConvolution; import net.imglib2.img.Img; -import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgs; -import net.imglib2.img.cell.CellImgFactory; import net.imglib2.loops.LoopBuilder; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; @@ -51,6 +48,7 @@ import net.imglib2.util.Intervals; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertArrayEquals; @@ -88,6 +86,7 @@ public void testDifferences2() assertArrayEquals( new int[] { -3, 3 }, targetPixels ); } + @Ignore( "takes to long" ) @Test public void testHugeImage() { @@ -96,7 +95,7 @@ public void testHugeImage() assertTrue( width * height > Integer.MAX_VALUE ); RandomAccessible< UnsignedByteType > source = ConstantUtils.constantRandomAccessible( new UnsignedByteType(), 2 ); RandomAccessibleInterval< UnsignedByteType > target = ConstantUtils.constantRandomAccessibleInterval( - new UnsignedByteType(), 2, new FinalInterval( width, height ) ); + new UnsignedByteType(), new FinalInterval( width, height ) ); double[][] kernels = { { 2 }, { 3 } }; try { diff --git a/src/test/java/net/imglib2/algorithm/convolution/LineConvolutionTest.java b/src/test/java/net/imglib2/algorithm/convolution/LineConvolutionTest.java index c69fd972d..6f76c5930 100644 --- a/src/test/java/net/imglib2/algorithm/convolution/LineConvolutionTest.java +++ b/src/test/java/net/imglib2/algorithm/convolution/LineConvolutionTest.java @@ -37,6 +37,8 @@ import net.imglib2.RandomAccess; import net.imglib2.img.Img; import net.imglib2.img.array.ArrayImgs; +import net.imglib2.parallel.Parallelization; +import net.imglib2.parallel.TaskExecutors; import net.imglib2.type.numeric.integer.UnsignedByteType; import net.imglib2.util.Intervals; import org.junit.Test; @@ -85,8 +87,11 @@ public void testNumTasksEqualsIntegerMaxValue() { byte[] result = new byte[ 1 ]; Img< UnsignedByteType > out = ArrayImgs.unsignedBytes( result, result.length ); Img< UnsignedByteType > in = ArrayImgs.unsignedBytes( new byte[] { 1, 2 }, 2 ); - final LineConvolution< UnsignedByteType > convolution = new LineConvolution<>( new ForwardDifferenceConvolverFactory(), 0 ); - convolution.process( in, out, Executors.newSingleThreadExecutor(), Integer.MAX_VALUE ); + Runnable runnable = () -> { + final LineConvolution< UnsignedByteType > convolution = new LineConvolution<>( new ForwardDifferenceConvolverFactory(), 0 ); + convolution.process( in, out ); + }; + Parallelization.runWithExecutor( TaskExecutors.forExecutorServiceAndNumTasks( Executors.newSingleThreadExecutor(), Integer.MAX_VALUE) , runnable ); assertArrayEquals( new byte[] { 1 }, result ); } diff --git a/src/test/java/net/imglib2/algorithm/gauss3/Gauss3Benchmark.java b/src/test/java/net/imglib2/algorithm/gauss3/Gauss3Benchmark.java new file mode 100644 index 000000000..bd26826af --- /dev/null +++ b/src/test/java/net/imglib2/algorithm/gauss3/Gauss3Benchmark.java @@ -0,0 +1,80 @@ +/*- + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2021 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package net.imglib2.algorithm.gauss3; + +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.*; +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.concurrent.TimeUnit; + +/** + * Measure the time own execution of {@link Gauss3#gauss}. + */ +@Fork( 1 ) +@State( Scope.Benchmark ) +@Warmup( iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement( iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS ) +@BenchmarkMode( Mode.AverageTime ) +@OutputTimeUnit( TimeUnit.MILLISECONDS ) +public class Gauss3Benchmark +{ + + private double sigma = 2.0; + + private Img source = ArrayImgs.doubles( 100, 100 ); + + private Img target = ArrayImgs.doubles( 100, 100 ); + + @Benchmark + public void benchmark() + { + Gauss3.gauss( sigma, Views.extendBorder( source ), target); + } + + public static void main( String[] args ) throws RunnerException + { + Options opt = new OptionsBuilder() + .include( Gauss3Benchmark.class.getSimpleName() ) + .build(); + new Runner( opt ).run(); + } +}