Skip to content

Commit de30b1a

Browse files
authored
Merge pull request #46 from maarzt/seperable-convolution
Seperable Convolution with Asymmetric Kernels + Fast Gauss
2 parents 78e4b57 + d17d33e commit de30b1a

37 files changed

+2757
-4
lines changed

pom.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ Jean-Yves Tinevez and Michael Zinsmaier.</license.copyrightOwners>
200200
<!-- NB: Deploy releases to the ImageJ Maven repository. -->
201201
<releaseProfiles>deploy-to-imagej</releaseProfiles>
202202

203-
<imglib2.version>5.1.0</imglib2.version>
203+
<imglib2.version>5.6.0</imglib2.version>
204204
<imglib2-realtransform.version>2.0.0-beta-39</imglib2-realtransform.version>
205205
<jitk-tps.version>3.0.0</jitk-tps.version>
206206
</properties>
@@ -248,6 +248,18 @@ Jean-Yves Tinevez and Michael Zinsmaier.</license.copyrightOwners>
248248
<artifactId>junit</artifactId>
249249
<scope>test</scope>
250250
</dependency>
251+
<dependency>
252+
<groupId>org.openjdk.jmh</groupId>
253+
<artifactId>jmh-core</artifactId>
254+
<version>1.19</version>
255+
<scope>test</scope>
256+
</dependency>
257+
<dependency>
258+
<groupId>org.openjdk.jmh</groupId>
259+
<artifactId>jmh-generator-annprocess</artifactId>
260+
<version>1.19</version>
261+
<scope>test</scope>
262+
</dependency>
251263
</dependencies>
252264

253265
<build>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package net.imglib2.algorithm.convolution;
2+
3+
import java.util.concurrent.ExecutorService;
4+
import java.util.concurrent.Executors;
5+
import java.util.concurrent.ThreadPoolExecutor;
6+
7+
import net.imglib2.RandomAccessible;
8+
import net.imglib2.RandomAccessibleInterval;
9+
10+
/**
11+
* Abstract class to help implementing a Convolution, that is multi threaded
12+
* using an {@link ExecutorService}. This implements the method
13+
* {@link Convolution#setExecutor(ExecutorService)}.
14+
* <p>
15+
* Classes that derive from {@link AbstractMultiThreadedConvolution} must
16+
* override
17+
* {@link AbstractMultiThreadedConvolution#process(RandomAccessible, RandomAccessibleInterval, ExecutorService, int)}
18+
*
19+
* @author Matthias Arzt
20+
*/
21+
public abstract class AbstractMultiThreadedConvolution< T > implements Convolution< T >
22+
{
23+
24+
private ExecutorService executor;
25+
26+
abstract protected void process( RandomAccessible< ? extends T > source,
27+
RandomAccessibleInterval< ? extends T > target,
28+
ExecutorService executorService,
29+
int numThreads );
30+
31+
@Override
32+
public void setExecutor( final ExecutorService executor )
33+
{
34+
this.executor = executor;
35+
}
36+
37+
@Override
38+
final public void process( final RandomAccessible< ? extends T > source, final RandomAccessibleInterval< ? extends T > target )
39+
{
40+
if ( executor == null )
41+
{
42+
final int numThreads = suggestNumThreads();
43+
final ExecutorService executor = Executors.newFixedThreadPool( numThreads );
44+
try
45+
{
46+
process( source, target, executor, numThreads );
47+
}
48+
finally
49+
{
50+
executor.shutdown();
51+
}
52+
}
53+
else
54+
{
55+
process( source, target, executor, getNumThreads( executor ) );
56+
}
57+
}
58+
59+
private int getNumThreads( final ExecutorService executor )
60+
{
61+
if ( executor instanceof ThreadPoolExecutor )
62+
return ( ( ThreadPoolExecutor ) executor ).getMaximumPoolSize();
63+
return suggestNumThreads();
64+
}
65+
66+
private int suggestNumThreads()
67+
{
68+
return Runtime.getRuntime().availableProcessors();
69+
}
70+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package net.imglib2.algorithm.convolution;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
6+
import java.util.concurrent.ExecutorService;
7+
8+
import net.imglib2.Interval;
9+
import net.imglib2.RandomAccessible;
10+
import net.imglib2.RandomAccessibleInterval;
11+
import net.imglib2.img.Img;
12+
import net.imglib2.type.NativeType;
13+
import net.imglib2.util.Intervals;
14+
import net.imglib2.util.Pair;
15+
import net.imglib2.util.Util;
16+
import net.imglib2.util.ValuePair;
17+
import net.imglib2.view.Views;
18+
19+
/**
20+
* Helper to implement {@link Convolution#concat}.
21+
*
22+
* @author Matthias Arzt
23+
*/
24+
class Concatenation< T > implements Convolution< T >
25+
{
26+
27+
private final List< Convolution< T > > steps;
28+
29+
Concatenation( final List< ? extends Convolution< T > > steps )
30+
{
31+
this.steps = new ArrayList<>( steps );
32+
}
33+
34+
@Override
35+
public void setExecutor( final ExecutorService executor )
36+
{
37+
steps.forEach( step -> step.setExecutor( executor ) );
38+
}
39+
40+
@Override
41+
public Interval requiredSourceInterval( final Interval targetInterval )
42+
{
43+
Interval result = targetInterval;
44+
for ( int i = steps.size() - 1; i >= 0; i-- )
45+
result = steps.get( i ).requiredSourceInterval( result );
46+
return result;
47+
}
48+
49+
@Override
50+
public T preferredSourceType( T targetType )
51+
{
52+
for ( int i = steps.size() - 1; i >= 0; i-- )
53+
targetType = steps.get( i ).preferredSourceType( targetType );
54+
return targetType;
55+
}
56+
57+
@Override
58+
public void process( final RandomAccessible< ? extends T > source, final RandomAccessibleInterval< ? extends T > target )
59+
{
60+
final List< Pair< T, Interval > > srcIntervals = tmpIntervals( Util.getTypeFromInterval( target ), target );
61+
RandomAccessibleInterval< ? extends T > currentSource = Views.interval( source, srcIntervals.get( 0 ).getB() );
62+
RandomAccessibleInterval< ? extends T > available = null;
63+
64+
for ( int i = 0; i < steps.size(); i++ )
65+
{
66+
final Convolution< T > step = steps.get( i );
67+
final T targetType = srcIntervals.get( i + 1 ).getA();
68+
final Interval targetInterval = srcIntervals.get( i + 1 ).getB();
69+
RandomAccessibleInterval< ? extends T > currentTarget =
70+
( i == steps.size() - 1 ) ? target : null;
71+
72+
if ( currentTarget == null && available != null &&
73+
Intervals.contains( available, targetInterval ) &&
74+
Util.getTypeFromInterval( available ).getClass().equals( targetType.getClass() ) )
75+
currentTarget = Views.interval( available, targetInterval );
76+
77+
if ( currentTarget == null )
78+
currentTarget = createImage( uncheckedCast( targetType ), targetInterval );
79+
80+
step.process( currentSource, currentTarget );
81+
82+
if ( i > 0 )
83+
available = currentSource;
84+
currentSource = currentTarget;
85+
}
86+
}
87+
88+
private static < T extends NativeType< T > > RandomAccessibleInterval< T > createImage( final T targetType, final Interval targetInterval )
89+
{
90+
final long[] dimensions = Intervals.dimensionsAsLongArray( targetInterval );
91+
final Img< T > ts = Util.getArrayOrCellImgFactory( targetInterval, targetType ).create( dimensions );
92+
return Views.translate( ts, Intervals.minAsLongArray( targetInterval ) );
93+
}
94+
95+
private static < T > T uncheckedCast( final Object in )
96+
{
97+
@SuppressWarnings( "unchecked" )
98+
final
99+
T in1 = ( T ) in;
100+
return in1;
101+
}
102+
103+
private List< Pair< T, Interval > > tmpIntervals( T type, Interval interval )
104+
{
105+
final List< Pair< T, Interval > > result = new ArrayList<>( Collections.nCopies( steps.size() + 1, null ) );
106+
result.set( steps.size(), new ValuePair<>( type, interval ) );
107+
for ( int i = steps.size() - 1; i >= 0; i-- )
108+
{
109+
final Convolution< T > step = steps.get( i );
110+
interval = step.requiredSourceInterval( interval );
111+
type = step.preferredSourceType( type );
112+
result.set( i, new ValuePair<>( type, interval ) );
113+
}
114+
return result;
115+
}
116+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package net.imglib2.algorithm.convolution;
2+
3+
import java.util.Arrays;
4+
import java.util.List;
5+
import java.util.concurrent.ExecutorService;
6+
7+
import net.imglib2.Interval;
8+
import net.imglib2.RandomAccessible;
9+
import net.imglib2.RandomAccessibleInterval;
10+
11+
/**
12+
* This interface allows the client to perform a convolution. But also to query
13+
* for the required input image size and preferred input image type. The
14+
* ExectorService can be set, to allow multi or single threaded operation.
15+
* <p>
16+
* Very importantly, multiple {@link Convolution}s can be easily concatenated.
17+
*
18+
* @author Matthias Arzt
19+
*/
20+
public interface Convolution< T >
21+
{
22+
/**
23+
* Returns the required size for source image, to calculate the given target
24+
* interval.
25+
*/
26+
Interval requiredSourceInterval( Interval targetInterval );
27+
28+
/**
29+
* What's the preferred type for the source image, when target should have
30+
* the specified type?
31+
*/
32+
T preferredSourceType( T targetType );
33+
34+
/**
35+
* Set the {@link ExecutorService} to be used for convolution.
36+
*/
37+
default void setExecutor( final ExecutorService executor )
38+
{}
39+
40+
/**
41+
* Fills the target image, with the convolution result.
42+
*
43+
* @param source
44+
* Source image. It must allow pixel access in the interval
45+
* returned by {@code requiredSourceInterval(target)}
46+
* @param target
47+
* Target image.
48+
*/
49+
void process( RandomAccessible< ? extends T > source, RandomAccessibleInterval< ? extends T > target );
50+
51+
/**
52+
* Concatenate multiple {@link Convolution}s to one convolution. (e.g.
53+
* Concatenation of a gauss convolution in X, and a gauss convolution in Y
54+
* will be a 2d gauss convolution).
55+
*/
56+
static < T > Convolution< T > concat( final Convolution< T >... steps )
57+
{
58+
return concat( Arrays.asList( steps ) );
59+
}
60+
61+
static < T > Convolution< T > concat( final List< ? extends Convolution< T > > steps )
62+
{
63+
if ( steps.isEmpty() )
64+
throw new IllegalArgumentException( "Concat requires at least one convolution operation." );
65+
if ( steps.size() == 1 )
66+
return steps.get( 0 );
67+
return new Concatenation<>( steps );
68+
}
69+
}

0 commit comments

Comments
 (0)