This component comprises the abstract foundation for the collection and use of Ops (see SciJava Ops SPI for the declaration of Ops).
The OpEnvironment is intended to be the main interface for accessing Ops and has API designed to disconnect algorithm from implementation. Instead of identifying a particular implementation, Ops are retrieved through the OpEnvironment by specifying:
- a name
- a set of input types
- an output type
- a functional type
for example, suppose you are looking for a java.util.BiFunction that adds two Numbers. To obtain an Op that can perform this operation, you might specify:
| Parameter | Value |
|---|---|
| Name | "add" |
| Input Types | {Number, Number} |
| Output Type | {Number} |
| Functional Type | BiFunction<Number, Number, Number> |
To create generic typings, OpEnvironment often makes use of the Nil interface of SciJava Types. Anonymous Nils are easily created for generic type X using new Nil<X>() {}. For example, to preserve the functional type in the above table we write new Nil<BiFunction<Number, Number, Number>>() {}.
In situations where the OpEnvironment knows of multiple satisfying Ops, it is up to the OpEnvironment to return the "best Op for the job". By delegating the decision to the OpEnvironment, the user can spend less time drowning in implementation documentation and construct an image processing chain faster.
To match the BiFunction<Number, Number, Number> above, we write the following:
Nil<BiFunction<Number, Number, Number>> funcNil = new Nil<BiFunction<Number, Number, Number>>() {};
Nil<Number> numNil = new Nil<Number>() {};
BiFunction<Number, Number, Number> foo = op("add", funcNil , new Nil[] {numNil, numNil}, numNil);By using OpBuilder, we can make this process much easier to read. All Op calls using the OpBuilder follow a similar pattern:
- Specify the name of the Op using either
new OpBuilder(OpEnvironment env, String name)orOpEnvironment.op(String name) - Specify the input using:
OpBuilder.input(I1 in1, I2 in2, ...)OpBuilder.inType(Nil<I1> in1Type, Nil<I2> in2Type, ...)OpBuilder.inType(Class<I1> in1Class, Class<I2>, ...)
- Specify the output using:
OpBuilder.output(O out)OpBuilder.outType(Nil<O> outType)OpBuilder.outTyep(Class<O> outType)
- Specify the desired return using:
- For computers,
OpBuilder.computer()to get theComputerorcompute()to compute the result directly - For functions,
OpBuilder.function()to get theFunctionorapply()to compute the result directly - For inplaces,
OpBuilder.inplace()to get theInplaceormutateX()to compute the result directly (whereXindicates the index of the argument to be mutated)
Thus we can replace the code:
Nil<BiFunction<Number, Number, Number>> funcNil = new Nil<BiFunction<Number, Number, Number>>() {};
Nil<Number> numNil = new Nil<Number>() {};
BiFunction<Number, Number, Number> foo = op("add", funcNil , new Nil[] {numNil, numNil}, numNil);with the code:
Nil<Number> numNil = new Nil<Number>() {};
BiFunction<Number, Number, Number> foo = op("add") //
.inType(Number.class, Number.class) //
.outType(Number.class) //
.function();Much more readable, no?
OpEnvironment implementations have the ability to respond to a set of Hints triggered by the user or by the Ops known to it.
Suppose, for example, that a subset of the Ops known to your OpEnvironment specialize in performance, making drastic improvements in speed at the cost of computational accuracy. Through the OpHints annotation, these Ops can notify the OpEnvironment of their lossiness:
@OpHints(hints = {Lossiness.LOSSY})
public class FastOp implements BiFunction<Number, Number, Number> {
@Override
public Number apply(Number in1, Number in2) {
...
}
}
@OpHints(hints = {Lossiness.LOSSLESS})
public class PreciseOp implements BiFunction<Number, Number, Number> {
@Override
public Number apply(Number in1, Number in2) {
...
}
}In situations where precision should be prioritized, users can then tell their OpEnvironment to prioritize Ops that do not trade performance for speed.
Hints hints = ...;
OpEnvironment env = ...;
public static void main(String[] args) {
hints.setHint(Lossiness.LOSSLESS);
env.setHints(hints);
}