Skip to content

Commit 02d05a9

Browse files
committed
Expand CallingOps guide
- Expand to cover Function and InPlace options - Reword for clarity
1 parent a36b6c1 commit 02d05a9

1 file changed

Lines changed: 56 additions & 14 deletions

File tree

docs/ops/doc/CallingOps.md

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,42 @@
11
# Calling Ops with the `OpBuilder`
22

3-
Ops are designed to be called from the Op matcher, using the `OpBuilder` syntax. OpBuilder chains follow the [builder pattern](https://refactoring.guru/design-patterns/builder), allowing users to create complex Op calls by "chaining" or appending consecutive, simpler method calls.
3+
Use of the Ops framework centers on a process of matching Op requests are to function implementations based on the parameters provided. The easiest way to make these queries is to use the `OpBuilder` syntax, which follows the [builder pattern](https://refactoring.guru/design-patterns/builder) to assemble the required components of an Op matching request from a particular `OpEnvironment`.
44

5-
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`.
5+
In this page we start after having [identified a Gaussian Blur Op](SearchingForOps) that we would like to use. We assume we already have created an `OpEnvironment`, named `ops`, as well as the input image to blur, and a pre-allocated output image for the result - `inImage` and `outImage`, respectively.
6+
7+
**Note:** we are incrementally building one line of code in this example. Running an intermediate step simply returns an appropriate builder that knows what has been set so far, and which step is next. If you're following along in an IDE or script editor, the code you actually *run* would be the last step, once our builder call is fully constructed.
68

79
## Specifying the name with `.op()`
810

9-
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:
11+
From the `OpEnvironment`, an `OpBuilder` chain is initialized with the `OpEnvironment.op(String)` method, which describes the name of the Op that we ultimately want to call:
1012

1113
```groovy
1214
ops.op("filter.gauss")
1315
```
1416

1517
## Passing the inputs with `.input()`
1618

17-
With the name defined in the `OpBuilder` call, we can then chain the inputs with the `input()` method.
19+
With the name established in the `OpBuilder` chain, we can then specify our input(s) with the `.input()` method.
1820

19-
For our gaussian blur, we will pass as inputs our input image `inImage`, and a `double` as our sigma parameter:
21+
For this Gaussian blur, we have two inputs: `inImage` is the image we want to blur, and a `double` as our sigma parameter:
2022

2123
```groovy
2224
ops.op("filter.gauss").input(inImage, 2.0)
2325
```
2426

2527
## Passing an output buffer with `.output()`
2628

27-
Now that the inputs are specified, we can chain the output buffer using the `output()` method.
29+
After specifying inputs, we provide preallocated outputs using the `.output()` method.
2830

29-
For our gaussian blur, we will pass as the output buffer our output image `outImage`:
31+
For our Gaussian blur, we will pass our output image `outImage` as a buffer for the result:
3032

3133
```groovy
3234
ops.op("filter.gauss").input(inImage, 2.0).output(outImage)
3335
```
3436

3537
## Computing with `.compute()`
3638

37-
With all of the components of the needed Op specified, we can begin computation with the `.compute()` method.
39+
With all of our desired Op's inputs and outputs now specified, we can run it with the `.compute()` method.
3840

3941
```groovy
4042
ops.op("filter.gauss").input(inImage, 2.0).output(outImage).compute()
@@ -44,18 +46,51 @@ In the call to `compute()`, the `OpEnvironment` will use the components of the `
4446
* Match an Op based on the name provided, as well as the types of the provided input and output `Object`s
4547
* Execute the Op on the provided input and output `Object`s.
4648

47-
## Additions: Repeating execution
49+
After this step, `outImage` will contain the results of the Gaussian blur on `inImage`.
50+
51+
## Variations on use
52+
53+
### Using `Function` or `InPlace`
4854

49-
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:
55+
Calling our Gaussian blur as a `Computer` above is great when we have pre-allocated output, but for other scenarios we can request Ops as `Functions` or `InPlaces`.
56+
57+
`Functions` are used when we want to *create* the final output, indicated by ending the builder with `.apply()`:
58+
59+
```groovy
60+
var outImage = ops.op("filter.gauss").input(inImage, 2.0).apply()
61+
```
62+
63+
`InPlaces` are used when we want to destructively modify one of the existing inputs (which is explicitly forbidden by `Computers`). We indicate this by the `mutate#()` method, where the `#` corresponds to the *parameter index* that will be modified:
64+
65+
```
66+
# Modify the first input in-place
67+
ops.op("filter.gauss").input(inImage, 2.0).mutate1()
68+
```
69+
70+
Note that although the final method call changes for each mode of operation, *this is based on the path taken through the `OpBuilder` chain*. For example, we cannot call the `compute()` method if we haven't provided an `.output()`:
71+
72+
```
73+
# Does not compute
74+
ops.op("filter.gauss").input(inImage, 2.0).compute()
75+
```
76+
77+
A key takeaway from this section is that how you **request** the Op does not necessarily need to match how the Op is **implemented**. `Functions` and `Computers` should be largely interchangeable. For the 1.0.0 release we do not have the necessary converters to go between `InPlaces` and the other paradigms, but it is on our [development roadmap](https://github.com/scijava/scijava/issues/47)!
78+
79+
### Repeating execution
80+
81+
When you want to call an Op many times on different inputs, the `OpBuilder` syntax can be modified to return the *Op* itself, instead of performing the computation. Instead of calling the `.compute()` function at the end of our `OpBuilder` chain, we can use the `.computer()` method (or `.inplace()` or `.function()`, as appropriate) to get back the matched Op, which can then be reused via its `.compute()` method (or `.apply()`, `.mutate#()`):
5082

5183
```groovy
5284
var gaussOp = ops.op("filter.gauss").input(inImage, 2.0).output(outImage).computer()
53-
gaussOp.compute(inImage, 2.0, outImage)
85+
gaussOp.compute(inImage, 2.0, outImage1)
86+
gaussOp.compute(inImage, 5.0, outImage2)
5487
```
5588

89+
While we do pass concrete inputs and outputs in this example, they are essentially just being used to reason about the desired *types* - which we'll cover in the next section.
90+
5691
*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.
5792

58-
## Additions: Matching with classes
93+
### Matching with classes
5994

6095
In addition to the `.input()` and `.output()` builder steps, there are parallel `.inType()` and `.outType()`
6196
methods. These accept either a `Class` or a `Nil` - the latter allowing retention of generic types.
@@ -64,8 +99,15 @@ methods. These accept either a `Class` or a `Nil` - the latter allowing retentio
6499
var computer = ops.op("filter.gauss").inType(ImgPlus.class, Double.class).outType(ImgPlus.class).computer()
65100
```
66101

67-
When using the `*Type` methods of the builder, the terminal steps will only allow *creation* of the Op, not
68-
direct execution, since the parameters have not been concretely specified yet.
102+
In this case, we *must* use the `computer()` terminal method of the builder: we
103+
can only *create* the Op, not directly execute it, since the parameters have
104+
not been concretely specified yet. This is very sensible when we want to re-use a computer many times.
105+
106+
We can also use the `.outType()` methods to add type safety to our `Function` calls:
107+
108+
```java
109+
Img outImage = ops.op("filter.gauss").input(inImage, 2.0).outType(Img.class).apply();
110+
```
69111

70112
## Common Pitfalls: Wildcards
71113

0 commit comments

Comments
 (0)