Ops are designed to be called from the Op matcher, using the OpBuilder syntax. OpBuilder chains follow the builder pattern, allowing users to create complex Op calls by "chaining" or appending consecutive, simpler method calls.
On this page, we will be constructing an OpBuilder call on an OpEnvironment ops to execute a Gaussian Blur on an input image inImage, with our output buffer outImage.
From the OpEnvironment, an OpBuilder call is initialized with the method OpEnvironment.op(String), which is used to describe the name of the Op that the OpBuilder must return:
ops.op("filter.gauss")After providing the name, the OpBuilder syntax requires the user to define the number of inputs to the Op call; we do this by adding one of the arity*() methods to the chain - arity1() tells the OpBuilder chain to expect one input, arity2() expects two inputs, and so on.
In doing a basic gaussian blur, we will pass through the input image inImage, and a sigma parameter - therefore, we will call arity2():
ops.op("filter.gauss").arity2()With the arity defined in the OpBuilder call, we can then chain the inputs with the input() method.
For our gaussian blur, we will pass as inputs our input image inImage, and a double as our sigma parameter:
ops.op("filter.gauss").arity2().input(inImage, 2.0)Now that the inputs are specified, we can chain the output buffer using the output() method.
For our gaussian blur, we will pass as the output buffer our output image outImage:
ops.op("filter.gauss").arity2().input(inImage, 2.0).output(outImage)With all of the components of the needed Op specified, we can begin computation with the .compute() method.
ops.op("filter.gauss").arity2().input(inImage, 2.0).output(outImage).compute()In the call to compute(), the OpEnvironment will use the components of the OpBuilder syntax to:
- Match an Op based on the name provided, as well as the types of the provided input and output
Objects - Execute the Op on the provided input and output
Objects.
While the .op().arity*() pattern is very repetitive (and thus easy to remember), OpBuilder chains provide convenience methods to combine them, such as:
.op("my.opName").arity1()can be replaced with.unary("my.opName").op("my.opName").arity2()can be replaced with.binary("my.opName")
Therefore the following OpBuilder call is identical to the previous call:
var gaussOp = ops.binary("filter.gauss").input(inImage, 2.0).output(outImage).computer()
gaussOp.compute(inImage, 2.0, outImage)When an Op should be executed many times on different inputs, the OpBuilder syntax can be modified to return the Op instead. Instead of calling the .compute() function at the end of our OpBuilder call, we can instead call the .computer() method to get back the matched Op:
If, instead of having a single image inImage we had Y images inImage1, inImage2, ..., inImageY, we
var gaussOp = ops.op("filter.gauss").arity2().input(inImage, 2.0).output(outImage).computer()
gaussOp.compute(inImage, 2.0, outImage)Note that the default OpEnvironment implementations cache Op requests - this means that repeated OpBuilder requests targeting the same action will be faster than the original matching call.
Using wildcards, such as Img<?> inImage, can make Op reuse difficult. For example, the following code segment will not compile in a Java runtime:
Img<?> inImage = ...;
var gaussOp = ops.binary("filter.gauss").input(inImage, 2.0).output(outImage).computer();
gaussOp.compute(inImage, 2.0, outImage);If you don't need to save the Op to a variable, just call it directly as shown here. Generally speaking, op requests are cached, meaning repeated OpBuilder calls that directly execute Ops will not significantly increase performance.
If you know that your Img will always contain bytes, define your variable as an Img<ByteType>