Migrating from ImageJ Opsο
The ImageJ Ops framework is the predecessor of the SciJava Ops framework, and many steps were taken to make expressing Ops much easier. This document shows ImageJ Ops developers how to convert their Ops to SciJava Ops - weβll convert DefaultGaussRAI
from the ImageJ Ops repository as an example.
Upgrading pom-scijavaο
Before doing anything else, you should ensure that your version of pom-scijava is new enough to make use of SciJava Ops goodness.
TODO: Replace with the pom-scijava version needed to grab this annotation processor.
Until the SciJava Ops annotation processor is integrated into pom-scijava, developers must add the following block of code to the build
section of their project POM:
TODO: Replace with the pom-scijava version needed to grab this annotation processor. TODO: Replace the SciJava Ops Indexer version with the correct initial version
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.scijava</groupId>
<artifactId>scijava-ops-indexer</artifactId>
<version>1.0.0</version>
</path>
</annotationProcessorPaths>
<fork>true</fork>
<showWarnings>true</showWarnings>
<compilerArgs>
<arg>-Aparse.ops=true</arg>
<arg>-Aop.version="${project.version}"</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
The other step recommended in your POM is to remove the dependency on ImageJ Ops!
Removing all the boilerplateο
In ImageJ Ops, all Ops were written as Class
es, with SciJava @Plugin
and @Parameter
annotations everywhere - most of these annotations can be removed in SciJava Ops!
Replace the @Plugin
annotation with an @implNote
in the javadocο
SciJava Ops uses a separate annotation processor to record plugins at compile time. This allows SciJava Ops to discover Ops without any runtime dependencies! Thus, instead of using an annotation, plugin declaration is performed within the javadoc of all plugins, using the @implNote
tag new to Java 9. The general schema for op declaration is as follows:
/**
* My sweet Op
*
* @implNote op names='name1'
*/
Additional options include:
Providing multiple names by replacing
names='name1'
withnames='name1,name2,...'
to allow your Op to be discoverable under multiple namesProviding the Opβs priority by appending
priority=<the priority>
. SciJava Ops uses the same priority values as ImageJ Ops, and relative priorities can be retained by using the new SciJava Priority library.
The following diff shows the changes needed to replace the @Plugin
annotation:
* @author Christian Dietz (University of Konstanz)
* @author Stephan Saalfeld
* @param <T> type of input and output
+ * @implNote op names='filter.gauss', priority='1.0'
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
-@Plugin(type = Ops.Filter.Gauss.class, priority = 1.0)
public class DefaultGaussRAI<T extends NumericType<T> & NativeType<T>> extends
AbstractUnaryHybridCF<RandomAccessibleInterval<T>, RandomAccessibleInterval<T>>
implements Ops.Filter.Gauss
Moving parameters to the functional methodο
SciJava Ops no longer uses the @Parameter
annotation to declare parameters - all Op parameters are instead passed through the functional method.
To conform DefaultGaussRAI
to this schema, the following diff should be applied:
public class DefaultGaussRAI<T extends NumericType<T> & NativeType<T>> extends
AbstractUnaryHybridCF<RandomAccessibleInterval<T>, RandomAccessibleInterval<T>>
implements Ops.Filter.Gauss
{
- @Parameter
- private ThreadService threads;
-
- @Parameter
- private double[] sigmas;
-
- @Parameter(required = false)
- private OutOfBoundsFactory<T, RandomAccessibleInterval<T>> outOfBounds;
-
@Override
public void compute(final RandomAccessibleInterval<T> input,
+ final ThreadService threads,
+ final double[] sigmas,
+ @Nullable OutOfBoundsFactory<T, RandomAccessibleInterval<T>> outOfBounds,
final RandomAccessibleInterval<T> output)
{
Note: nullable parameters are denoted using the org.scijava.ops.spi.Nullable
annotation. Just like in ImageJ Ops, if the user does not decide to pass that parameter, it will be assigned to null
, so leave in any null-checks for nullable parameters.
Implementing the right functional interfaceο
Ops in the SciJava Ops framework implement a new set of Op types, designed for functional simplicity. These functional interfaces are built off of the java.util.function.Function
interface, and are housed in the SciJava Functions library.
The first step to finding the right functional interface for the Op being converted. While the Computer
, Function
, and Inplace
Op interfaces persist in SciJava Ops, there are many more interfaces because all parameters are defined in the functional method.
If your Op was originally a Function
, then the Op should implement one of the following interfaces:
java.util.function.Function
, if your Op has only one parameterjava.util.function.BiFunction
, if your Op has only two parametersorg.scijava.function.Functions.ArityX
, if your Op hasX>2
parameters
If your Op was originally a Computer
and now has X
parameters in its functional method, then the Op should implement org.scijava.function.Computers.ArityX
.
Finally, if your Op was originally an Inplace
and now has X
parameters in its functional method, with parameter Y
being the one to mutate, then the Op should implement org.scijava.function.Inplace.ArityX_Y
.
After the last step, we see that our example Op (a Computer
) has 4 inputs - thus it should implement org.scijava.function.Computers.Arity4
- below is the diff of the change necessary:
* @author Christian Dietz (University of Konstanz)
* @author Stephan Saalfeld
* @param <T> type of input and output
* @implNote op names='filter.gauss', priority='1.0'
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
-public class DefaultGaussRAI<T extends NumericType<T> & NativeType<T>> extends
- AbstractUnaryHybridCF<RandomAccessibleInterval<T>, RandomAccessibleInterval<T>>
- implements Ops.Filter.Gauss
+public class DefaultGaussRAI<T extends NumericType<T> & NativeType<T>> implements
+ Computers.Arity4<RandomAccessibleInterval<T>, ThreadSerice, double[], OutOfBoundsFactory<T, RandomAccessibleInterval<T>>, RandomAccessibleInterval<T>>
{
Note two things when you change the interface:
The interface
Ops.Filter.Gauss
is no longer necessary - if you are implementing some such interface, you no longer need it!Additional interface methods, including hybrid Op method like
createOutput
are no longer necessary, and can also be deleted. Below is the diff of removingcreateOutput
from our example Op:} } - @Override - public RandomAccessibleInterval<T> createOutput( - final RandomAccessibleInterval<T> input) - { - return ops().create().img(input); - } - }
The Example Op, configured for SciJava Opsο
And thatβs it! Your Op is now ready to be used in SciJava Ops!
/**
* Gaussian filter, wrapping {@link Gauss3} of imglib2-algorithms.
*
* @author Christian Dietz (University of Konstanz)
* @author Stephan Saalfeld
* @param <T> type of input and output
* @implNote op names='filter.gauss', priority='1.0'
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class DefaultGaussRAI<T extends NumericType<T> & NativeType<T>> implements
Computers.Arity4<RandomAccessibleInterval<T>, ThreadSerice, double[], OutOfBoundsFactory<T, RandomAccessibleInterval<T>>, RandomAccessibleInterval<T>>
{
@Override
public void compute(final RandomAccessibleInterval<T> input,
final ThreadService threads,
final double[] sigmas,
@Nullable OutOfBoundsFactory<T, RandomAccessibleInterval<T>> outOfBounds,
final RandomAccessibleInterval<T> output)
{
if (outOfBounds == null) {
outOfBounds = new OutOfBoundsMirrorFactory<>(Boundary.SINGLE);
}
final RandomAccessible<FloatType> eIn = //
(RandomAccessible) Views.extend(input, outOfBounds);
try {
SeparableSymmetricConvolution.convolve(Gauss3.halfkernels(sigmas), eIn,
output, threads.getExecutorService());
}
catch (final IncompatibleTypeException e) {
throw new RuntimeException(e);
}
}
}