Skip to content

Commit e5c60b5

Browse files
authored
Merge pull request #33 from scijava/scijava/scijava-concurrent/init
Initialize SciJava Concurrent module. Closes #31
2 parents b052d25 + 0218b2c commit e5c60b5

100 files changed

Lines changed: 2925 additions & 1849 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

imagej/imagej-ops2-tutorial/pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,6 @@
300300
<version>${project.version}</version>
301301
<scope>test</scope>
302302
</dependency>
303-
<dependency>
304-
<groupId>org.scijava</groupId>
305-
<artifactId>scijava-threads</artifactId>
306-
<version>${project.version}</version>
307-
<scope>test</scope>
308-
</dependency>
309303
<dependency>
310304
<!-- Do not remove - Maven incorrectly identifies it as an unused dependency! -->
311305
<groupId>org.scijava</groupId>

imagej/imagej-ops2-tutorial/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363

6464
provides org.scijava.ops.spi.OpCollection with
6565
net.imagej.ops2.tutorial.OpAdaptation,
66+
net.imagej.ops2.tutorial.OpParallelization,
6667
net.imagej.ops2.tutorial.OpReduction,
6768
net.imagej.ops2.tutorial.OpSimplification,
6869
net.imagej.ops2.tutorial.ReportingProgress,
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
2+
package net.imagej.ops2.tutorial;
3+
4+
import org.scijava.function.Computers;
5+
import org.scijava.ops.api.OpEnvironment;
6+
import org.scijava.ops.engine.DefaultOpEnvironment;
7+
import org.scijava.ops.spi.OpCollection;
8+
import org.scijava.ops.spi.OpMethod;
9+
10+
import net.imglib2.algorithm.neighborhood.Neighborhood;
11+
import net.imglib2.algorithm.neighborhood.RectangleShape;
12+
import net.imglib2.img.array.ArrayImgs;
13+
import net.imglib2.type.numeric.integer.UnsignedByteType;
14+
15+
/**
16+
* SciJava Ops includes a mechanism for automatically introducing concurrency to
17+
* Ops.
18+
*
19+
* Developers can utilize this mechanism by writing their Ops on the smallest
20+
* element of the computation, be that a single pixel, or a {@link Neighborhood}.
21+
* SciJava Ops will then "lift" these Ops, creating parallelized Ops that run
22+
* on an entire {@link net.imglib2.RandomAccessibleInterval}
23+
*
24+
* This tutorial showcases these lifting mechanisms.
25+
*
26+
* @author Gabriel Selzer
27+
*/
28+
public class OpParallelization implements OpCollection {
29+
30+
/**
31+
* This Op, which is really just a computation on a single pixel, lets the
32+
* framework assume the burden of parallelization
33+
*
34+
* @param input the input pixel
35+
* @param output the preallocated output pixel
36+
*/
37+
@OpMethod(names = "tutorial.invertPerPixel", type = Computers.Arity1.class)
38+
public static void invertOp(UnsignedByteType input, UnsignedByteType output) {
39+
output.set(255 - input.get());
40+
}
41+
42+
/**
43+
* This Op, which computes some algorithm over a neighborhood, also lets the
44+
* framework assume the burden of parallelization
45+
*
46+
* @param input the input pixel
47+
* @param output the preallocated output pixel
48+
*/
49+
@OpMethod(names = "tutorial.neighborhoodAverage",
50+
type = Computers.Arity1.class)
51+
public static void averageNeighborhood(Neighborhood<UnsignedByteType> input,
52+
UnsignedByteType output)
53+
{
54+
long tmp = 0;
55+
var cursor = input.cursor();
56+
while (cursor.hasNext()) {
57+
tmp += cursor.next().getIntegerLong();
58+
}
59+
output.setInteger(tmp / input.size());
60+
}
61+
62+
public static void main(String... args) {
63+
OpEnvironment ops = new DefaultOpEnvironment();
64+
65+
// First, we show parallelization at work for our per-pixel Op.
66+
// SciJava Ops understands how to apply that Op to each pixel of the input
67+
// image
68+
69+
// Fill an input image with a value
70+
var fillValue = new UnsignedByteType(5);
71+
var inImg = ArrayImgs.unsignedBytes(10, 10);
72+
ops.op("image.fill").arity1().input(fillValue).output(inImg).compute();
73+
// Run the Op
74+
var outImg = ArrayImgs.unsignedBytes(10, 10);
75+
ops.op("tutorial.invertPerPixel").arity1().input(inImg).output(outImg)
76+
.compute();
77+
// Get the original value, and the inverted value
78+
var original = inImg.firstElement().get();
79+
var inverted = outImg.firstElement().get();
80+
System.out.println("Original image was filled with value " + original +
81+
", and the inverted image is filled with value (255 - " + original +
82+
") = " + inverted);
83+
84+
// Now, we show parallelization at work for our Parallelization Op.
85+
// For this example, we use a radius-1 rectangle; in other words, the
86+
// neighborhood
87+
// for a given pixel includes all of its immediate neighbors (including
88+
// diagonal)
89+
var shape = new RectangleShape(1, false);
90+
ops.op("tutorial.neighborhoodAverage").arity2().input(inImg, shape)
91+
.output(outImg).compute();
92+
// Get the original value, and the radius-1 neighborhood value
93+
original = inImg.firstElement().get();
94+
var mean = outImg.firstElement().get();
95+
System.out.println("Original image was filled with value " + original +
96+
", and the radius-1 mean at the corner is (4 * " + original + " / 9) = " +
97+
mean);
98+
}
99+
100+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
package net.imagej.ops2.tutorial;
3+
4+
import java.util.Arrays;
5+
import java.util.List;
6+
import java.util.function.Function;
7+
8+
import org.scijava.ops.api.OpEnvironment;
9+
import org.scijava.ops.engine.DefaultOpEnvironment;
10+
import org.scijava.types.Nil;
11+
12+
import net.imglib2.img.Img;
13+
import net.imglib2.img.array.ArrayImgs;
14+
import net.imglib2.loops.LoopBuilder;
15+
import net.imglib2.parallel.Parallelization;
16+
import net.imglib2.type.numeric.integer.UnsignedByteType;
17+
18+
/**
19+
* Using the {@link net.imglib2.parallel.Parallelization} class, we can perform
20+
* independent computations in parallel. This tutorial showcases running many
21+
* Ops in parallel using {@link net.imglib2.parallel.Parallelization}.
22+
*
23+
* @author Gabriel Selzer
24+
*/
25+
public class ParallelComputation {
26+
27+
public static void main(String... args) {
28+
OpEnvironment ops = new DefaultOpEnvironment();
29+
// To compute tasks using Parallelization, we must first gather a list of
30+
// parameters.
31+
List<Double> fillValues = Arrays.asList(1.0, 2.0, 3.0, 4.0);
32+
Img<UnsignedByteType> data = ArrayImgs.unsignedBytes(10, 10, 10);
33+
Nil<Img<UnsignedByteType>> outNil = new Nil<>() {};
34+
35+
// Note that this function will be run many times in parallel
36+
// - it's not terribly complex, but we could do much more
37+
Function<Double, Img<UnsignedByteType>> fillImage = fillValue -> {
38+
// create a new image of the same size as our data
39+
var output = ops.op("create.img").arity1().input(data).outType(outNil)
40+
.apply();
41+
// fill it with the fill value
42+
LoopBuilder.setImages(output).forEachPixel(pixel -> pixel.setReal(
43+
fillValue));
44+
// and return it
45+
return output;
46+
};
47+
48+
// Parallelization.getTaskExecutor().forEachApply() takes a list of
49+
// parameters,
50+
// and a function to apply on each parameter in the list. The function will
51+
// then be applied in parallel on each parameter, and the return is a list,
52+
// with the ith output being the application of the function on the ith
53+
// parameter.
54+
List<Img<UnsignedByteType>> filledImages = //
55+
Parallelization.getTaskExecutor().forEachApply(fillValues, fillImage);
56+
57+
for (int i = 0; i < fillValues.size(); i++) {
58+
if (filledImages.get(i) != null) {
59+
System.out.println("Image " + i + " was filled with value " +
60+
filledImages.get(i).firstElement().get());
61+
}
62+
}
63+
64+
}
65+
66+
}

imagej/imagej-ops2/pom.xml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@
253253
<groupId>org.scijava</groupId>
254254
<artifactId>parsington</artifactId>
255255
</dependency>
256+
<dependency>
257+
<groupId>org.scijava</groupId>
258+
<artifactId>scijava-concurrent</artifactId>
259+
<version>${project.version}</version>
260+
</dependency>
256261
<dependency>
257262
<groupId>org.scijava</groupId>
258263
<artifactId>scijava-ops-api</artifactId>
@@ -325,12 +330,6 @@
325330
<artifactId>scifio</artifactId>
326331
<scope>test</scope>
327332
</dependency>
328-
<dependency>
329-
<groupId>org.scijava</groupId>
330-
<artifactId>scijava-threads</artifactId>
331-
<version>${project.version}</version>
332-
<scope>test</scope>
333-
</dependency>
334333
<dependency>
335334
<!-- Do not remove - Maven incorrectly identifies it as an unused dependency! -->
336335
<groupId>org.scijava</groupId>

imagej/imagej-ops2/src/main/java/module-info.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,10 @@
6565
opens net.imagej.ops2.filter.gauss to therapi.runtime.javadoc, org.scijava.ops.engine;
6666
opens net.imagej.ops2.filter.hessian to therapi.runtime.javadoc, org.scijava.ops.engine;
6767
opens net.imagej.ops2.filter.ifft to therapi.runtime.javadoc, org.scijava.ops.engine;
68-
opens net.imagej.ops2.filter.max to therapi.runtime.javadoc, org.scijava.ops.engine;
69-
opens net.imagej.ops2.filter.mean to therapi.runtime.javadoc, org.scijava.ops.engine;
70-
opens net.imagej.ops2.filter.median to therapi.runtime.javadoc, org.scijava.ops.engine;
71-
opens net.imagej.ops2.filter.min to therapi.runtime.javadoc, org.scijava.ops.engine;
7268
opens net.imagej.ops2.filter.pad to therapi.runtime.javadoc, org.scijava.ops.engine;
7369
opens net.imagej.ops2.filter.sigma to therapi.runtime.javadoc, org.scijava.ops.engine;
7470
opens net.imagej.ops2.filter.sobel to therapi.runtime.javadoc, org.scijava.ops.engine;
7571
opens net.imagej.ops2.filter.tubeness to therapi.runtime.javadoc, org.scijava.ops.engine;
76-
opens net.imagej.ops2.filter.variance to therapi.runtime.javadoc, org.scijava.ops.engine;
7772
opens net.imagej.ops2.filter.vesselness to therapi.runtime.javadoc, org.scijava.ops.engine;
7873
opens net.imagej.ops2.geom to therapi.runtime.javadoc, org.scijava.ops.engine;
7974
opens net.imagej.ops2.geom.geom2d to org.scijava, therapi.runtime.javadoc, org.scijava.ops.engine;
@@ -153,6 +148,7 @@
153148
requires net.imglib2.roi;
154149
requires org.joml;
155150
requires org.scijava.collections;
151+
requires org.scijava.concurrent;
156152
requires org.scijava.function;
157153
requires org.scijava.meta;
158154
requires org.scijava.ops.api;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
2+
package net.imagej.ops2.adapt;
3+
4+
import java.util.function.Function;
5+
6+
import org.scijava.function.Computers;
7+
import org.scijava.ops.spi.OpMethod;
8+
9+
import net.imglib2.RandomAccessibleInterval;
10+
import net.imglib2.algorithm.neighborhood.Neighborhood;
11+
import net.imglib2.algorithm.neighborhood.Shape;
12+
import net.imglib2.loops.LoopBuilder;
13+
import net.imglib2.outofbounds.OutOfBoundsFactory;
14+
import net.imglib2.outofbounds.OutOfBoundsMirrorFactory;
15+
import net.imglib2.view.Views;
16+
17+
public class LiftNeighborhoodComputersToImg {
18+
19+
/**
20+
* @implNote op names='adapt', priority='100.',
21+
* type='java.util.function.Function'
22+
*/
23+
public static <T, U>
24+
Computers.Arity2<RandomAccessibleInterval<T>, Shape, RandomAccessibleInterval<U>>
25+
adapt1UsingShape(Computers.Arity1<Neighborhood<T>, U> op)
26+
{
27+
OutOfBoundsMirrorFactory<T, RandomAccessibleInterval<T>> oobf //
28+
= new OutOfBoundsMirrorFactory<>(
29+
OutOfBoundsMirrorFactory.Boundary.SINGLE);
30+
return (in, shape, out) -> {
31+
var extended = Views.extend(in, oobf);
32+
var neighborhoods = shape.neighborhoodsRandomAccessible(extended);
33+
var intervaled = Views.interval(neighborhoods, in);
34+
LoopBuilder.setImages(intervaled, out).forEachPixel(op);
35+
};
36+
}
37+
38+
/**
39+
* @implNote op names='adapt', priority='100.',
40+
* type='java.util.function.Function'
41+
*/
42+
@OpMethod(names = "adapt", type = Function.class)
43+
public static <T, U, F extends RandomAccessibleInterval<T>>
44+
Computers.Arity3<F, Shape, OutOfBoundsFactory<T, F>, RandomAccessibleInterval<U>>
45+
adapt1UsingShapeAndOOBF(Computers.Arity1<Neighborhood<T>, U> op)
46+
{
47+
return (in, shape, oobf, out) -> {
48+
var extended = Views.extend(in, oobf);
49+
var neighborhoods = shape.neighborhoodsRandomAccessible(extended);
50+
var intervaled = Views.interval(neighborhoods, in);
51+
LoopBuilder.setImages(intervaled, out).forEachPixel(op);
52+
};
53+
}
54+
55+
/**
56+
* @implNote op names='adapt', priority='100.',
57+
* type='java.util.function.Function'
58+
*/
59+
public static <T, U, V>
60+
Computers.Arity3<RandomAccessibleInterval<T>, V, Shape, RandomAccessibleInterval<U>>
61+
adapt2UsingShape(Computers.Arity2<Neighborhood<T>, V, U> op)
62+
{
63+
OutOfBoundsMirrorFactory<T, RandomAccessibleInterval<T>> oobf //
64+
= new OutOfBoundsMirrorFactory<>(
65+
OutOfBoundsMirrorFactory.Boundary.SINGLE);
66+
return (in1, in2, shape, out) -> {
67+
var extended = Views.extend(in1, oobf);
68+
var neighborhoods = shape.neighborhoodsRandomAccessible(extended);
69+
var intervaled = Views.interval(neighborhoods, in1);
70+
LoopBuilder.setImages(intervaled, out).forEachPixel((in, container) -> op
71+
.compute(in, in2, container));
72+
};
73+
}
74+
75+
/**
76+
* @implNote op names='adapt', priority='100.',
77+
* type='java.util.function.Function'
78+
*/
79+
@OpMethod(names = "adapt", type = Function.class)
80+
public static <T, U, V, F extends RandomAccessibleInterval<T>>
81+
Computers.Arity4<F, V, Shape, OutOfBoundsFactory<T, F>, RandomAccessibleInterval<U>>
82+
adapt2UsingShapeAndOOBF(Computers.Arity2<Neighborhood<T>, V, U> op)
83+
{
84+
return (in1, in2, shape, oobf, out) -> {
85+
var extended = Views.extend(in1, oobf);
86+
var neighborhoods = shape.neighborhoodsRandomAccessible(extended);
87+
var intervaled = Views.interval(neighborhoods, in1);
88+
LoopBuilder.setImages(intervaled, out).forEachPixel((in, container) -> op
89+
.compute(in, in2, container));
90+
};
91+
}
92+
93+
/**
94+
* @implNote op names='adapt', priority='100.',
95+
* type='java.util.function.Function'
96+
*/
97+
public static <T, U, V, W>
98+
Computers.Arity4<RandomAccessibleInterval<T>, V, W, Shape, RandomAccessibleInterval<U>>
99+
adapt3UsingShape(Computers.Arity3<Neighborhood<T>, V, W, U> op)
100+
{
101+
OutOfBoundsMirrorFactory<T, RandomAccessibleInterval<T>> oobf //
102+
= new OutOfBoundsMirrorFactory<>(
103+
OutOfBoundsMirrorFactory.Boundary.SINGLE);
104+
return (in1, in2, in3, shape, out) -> {
105+
var extended = Views.extend(in1, oobf);
106+
var neighborhoods = shape.neighborhoodsRandomAccessible(extended);
107+
var intervaled = Views.interval(neighborhoods, in1);
108+
LoopBuilder.setImages(intervaled, out).forEachPixel((in, container) -> op
109+
.compute(in, in2, in3, container));
110+
};
111+
}
112+
113+
/**
114+
* @implNote op names='adapt', priority='100.',
115+
* type='java.util.function.Function'
116+
*/
117+
@OpMethod(names = "adapt", type = Function.class)
118+
public static <T, U, V, W, F extends RandomAccessibleInterval<T>>
119+
Computers.Arity5<F, V, W, Shape, OutOfBoundsFactory<T, F>, RandomAccessibleInterval<U>>
120+
adapt3UsingShapeAndOOBF(Computers.Arity3<Neighborhood<T>, V, W, U> op)
121+
{
122+
return (in1, in2, in3, shape, oobf, out) -> {
123+
var extended = Views.extend(in1, oobf);
124+
var neighborhoods = shape.neighborhoodsRandomAccessible(extended);
125+
var intervaled = Views.interval(neighborhoods, in1);
126+
LoopBuilder.setImages(intervaled, out).forEachPixel((in, container) -> op
127+
.compute(in, in2, in3, container));
128+
};
129+
}
130+
}

0 commit comments

Comments
 (0)