Operator Implementation Guidelines

As there are four different methods which you may use to perform the computation of an operator (not counting computePixel() of the PixelOperator, as this is just a replacement of computeTile()), there is sometimes confusion about which one should be used. This Wiki page shall help in determining which methods you need to implement to make your operator run as efficient as possible. This means, we need to specify which contents need to be specified in each of these methods:

  • void initialize(): This method is responsible for validating the input parameters and the source products. It is also responsible for creating the target product and defining its properties. In some cases it is hard to define the target product, because its exact form will depend from the processing. In these cases you should try to create a target product as you expect it. The target product shall change as little as possible. Do not perform any tasks here that consume a lot of time, as the method may be called repeatedly.

  • void doExecute(ProgressMonitor pm): This method can serve two (non-mutually exclusive) purposes: First: Perform a lengthy preparation task before the actual computation takes place, second: Do all the computation instead of computeTile()/computeTileStack(). This method is optional.

  • void computeTile(Tile targetTile, ProgressMonitor pm): Use this method if you want to compute the values for a single target tile. This method is the best choice if

    1. You want to compute the values of the target band in a piece of code written by yourself

    2. The computation of a tile does not depend on the computation of neighbouring tiles.

    3. Each band of the target product is computed independently, i.e., the steps to compute the target values are not repeated for another tile.

    When this method is implemented, computeTileStack() should not be implemented.

  • void computeTileStack(Map<Band, Tile> targetTiles, Rectangle targetTileRectangle, ProgressMonitor pm): This method behaves similar to computeTile, only that it allows to set the values for multiple target values at the same time. Use this method if item 3 in the list above is not the case.

Notes for developing

  • Always implement initialize()

  • Always use the target product that you have defined in initialize(). If you somehow end up with a different product than the target product, fill the defined target product with the values from this other product.

  • If you can, set JAI Images directly to your bands

  • If you cannot set JAI Images directly, use computeTile() / computeTileStack() / computePixel()

  • In case that each pixel can be computed separately it is probably the best choice to implement the computePixel() - method of a PixelOperator(see https://senbox.atlassian.net/wiki/spaces/SNAP/pages/523796481) .

  • If you do not have any reason to implement doExecute() , just leave it as it is: blank.

Examples

In the following we discuss a few cases in order to aid you in finding the best way to implement your operator. Note that as we have discussed the properties of computeTile() vs. computeTileStack() vs. computePixel() above, in the remainder we will refer to them only as computeTile() for reasons of readability, also when computeTileStack() or computePixel() is the best choice.

I just want to use some bands from a source product to create new bands in a target product. The computation of tiles can happen spatially independently.

Recommended Procedure: Implement initialize() to define your target product and computeTile() to compute the values of the bands. Do not implement doExecute() .

I want to create a target product, but before I compute the tiles, I want to read in auxiliary data like a large Look-Up-Table. I only need to read it in once.

Recommended Procedure: Implement initialize() to define your target product and computeTile() to compute the values of the bands. Implement doExecute() to read in your auxiliary data, keep it as a field. Do not forget to release it from the cache when you’re done in dispose() .

I find that I cannot use computeTile() in my case, because I am doing something quite special to create my target product. / I would like to write a sort of wrapper around an external application.

Recommended Procedure: Implement initialize() to define your target product. Try to define it as closely as possible to your expected outcome. Put what you need to do to create the target product in doExecute() . Be sure to “fill“ the target product defined in initialize() with your computed values. You do not need to implement computeTile() .

I find I can do without computeTile(). I am content if I can simply set JAI Images to my target bands.

Recommended Procedure: Just implement initialize(), set the JAI Images there directly. You do not need to implement doExecute() or computeTile().

Setting JAI images is enough, but I need to do something that takes some time to come up with these images.

Recommended Procedure: Define the target product in initialize() , use doExecute() for your task and set the JAI Images there.

I actually do not want to create a target product. I just want to write a file or otherwise take care of the output of my operator myself.

Recommended Procedure: This is a bit of abuse of the GPF, but you can do it. Set up a small “dummy“ target product in initialize() without any bands. You will not need to do anything with it, but the GPF requires it to trigger the correct processing routines. Implement your task in doExecute().

I need to compute the whole product twice! First I need to do one pass over all tiles, then I need to do another one. How can I do this?

Recommended Procedure: We recommend to revise your algorithm, as this might lead to problems with the tile cache, but it is possible. For each ot the two runs, create a dedicated Operator. In the doExceute() - method of the operator that implements the second run, use GPF.getDefaultIntance().createOperator() to create the operator that implements the first run to trigger its initialize() - method. Then call OperatorExecutor.create(firstOperator).execute(ProgressMonitor) to trigger the computation of the first product. You can then get the the target product from the first run with firstOperator.getTargetProduct().