Package org.scijava.ops.api

This module contains the API necessary to retrieve, execute, and reason about Ops.

What is the SciJava Ops Library?

SciJava Ops arose from a desire for simplicity - there are lots of different algorithmic libraries, each with their own data structures, algorithm syntax, and other requirements. With all of these differences, library interoperability can be a daunting task. This can lead to algorithm reimplementations, unmaintained code, and a lack of cooperation between open-source communities. SciJava Ops seeks to unify different libraries under a single framework, separating the development of new algorithms from the way they are executed. Put more succinctly, the goals of SciJava Ops are the following:

  • Usability. All Ops should be called in the same way, and implementation details should be hidden - this means that the user should call an Op the same way, whether the algorithm is written in Java, Python, or as a CUDA kernel.
  • Extensibility. New Ops should be accommodated in a variety of programming languages, and each language should provide minimally-invasive methods for declaring code blocks as Ops. This also means that Ops should accommodate writing Ops in one language, and then calling Ops using data structures from a different language.
  • Generality. Ops should allow any number of typed inputs, without restriction on input types. With no restrictions on input types, the library can seamlessly adapt new data structures tailored to performance or expressiveness as they are developed.
  • Speed. Calling Ops should be approximately as performant as invocation in their natural setting (such as Python, C++, Java, or another language entirely).

What is an Op?

An Op is an algorithm adhering to the following traits:

  1. Ops are stateless and deterministic - with no internal state, calling an Op two times on the same inputs will produce the same output.
  2. Ops are named - this name conveys an Op's purpose, and allows us to find all Ops implementing a particular operation

Using the name and the combination of input and output parameters, we can retrieve, or "match", any Op from within an OpEnvironment. Op calls with the same name and specified inputs/outputs will be reproducible within a particular Op environment.

Op Equivalence

To support the Op matching paradigm, we establish three types of equivalence:

  1. Form Equivalence implies that two objects (which could be Ops, or Op parameters) theoretically draw from the same shared idea (such as an addition operation, or an image, etc.)
  2. Structural Equivalence - Structural Equivalence means that two Ops:
    1. Are Form Equivalent
    2. Have the same number of inputs and the same number of outputs
    3. For each input position, accept form-equivalent inputs
    4. Return form-equivalent outputs
  3. Result Equivalence means that o1.equals(o2) for two outputs o1 and o2 from two Ops.

Within the Ops API, each type of equivalence is utilized in the following ways:

  1. Form Equivalence. If two Ops are form-equivalent, they are defined under the same name.
  2. Structural Equivalence. If two Ops are structural-equivalent, they share a common form-reduced description, searchable using OpEnvironment.help(String). For example, an Op "math.add" that produces an ImgLib2 Img from Img operands, and another Op "math.add" that produces a NumPy ndarray from ndarray operands, will reduce to a single description "math.add" that produces an image from image operands.
  3. Result Equivalence. If two Ops are result-equivalent, they produce equivalent values, using the primary language-specific definition of equality (e.g. `Object.equals`, for Java usage).

For example, consider three Ops:

  1. filter.gauss(net.imglib2.img.Img, net.imglib2.type.numeric.real.FloatType) → net.imglib2.img.Img
  2. filter.gauss(ij.ImagePlus, java.lang.Double) → ij.ImagePlus
  3. filter.gauss(net.imglib2.img.Img, net.imglib2.algorithm.neighborhood.Shape) → net.imglib2.img.Img

Ops 1 and 2 are considered form-equivalent, as they have the same name, and structural-equivalent, as they both take in an image data structure and a floating point number and return an image data structure, but may not be result-equivalent due to implementation differences between the two Ops, or precision loss of the data structures.

Ops 1 and 3 are also form-equivalent, as they have the same name, but are not structural-equivalent, as one has an implicit Shape over which to perform a gaussian blur, while the other uses an explicitly specified shape.

These definitions of equivalence provide clarity when articulating the Ops framework's benefits:

  1. Form-equivalence in Ops allows a comprehensive search of the available algorithms for accomplishing a particular algorithm, by searching its name
  2. Form-equivalence in data inputs allows us to consider algorithm inputs in a library-agnostic way, making it easier to understand and use algorithms
  3. Structural-equivalence in Ops allows us to consider similar Ops in different languages as one, and to delegate to the proper Op using user inputs. In other words, you can call structural-equivalent Ops identically, and SciJava Ops will take care to call the correct Op based on the concrete inputs provided.
  4. Result-equivalence, and therefore reproducibility, in Ops, is guaranteed within an OpEnvironment and a set of input objects, but not just for Op calls. This allows us to ensure reproducible pipelines, but also allows us to introduce new Ops into the pipeline or to run pipelines on different inputs without changing the pipeline itself.