JAI-EXT Tutorial

Introduction

From GeoTools 14.x a new JAI extension API has been integrated, this new API is called JAI-EXT.

JAI-EXT is an open-source Project which provides a fast, high-scalability API for image processing. The main feature of this API is the ability to support external ROI objects and Image NoData for most of its processing operations.

This page has been written to describe this new API inside GeoTools, to demonstrate its use, and to explain best practices for working with it.

Usage

JAI-EXT registration

A project which would like to use JAI-EXT operations needs to first register them. This may be accomplished by calling, at the top of your project, the following piece of code:

static {
        JAIExt.initJAIEXT();
}

This registers all of the JAI-EXT operations inside the JAI OperationRegistry in order to use them instead of old JAI operations.

Note

It should be pointed out that if this method is called more than one time; it has no effect since it is only an initialization method.

The above changes can be reverted individually:

JAIExt.registerJAIDescriptor("Warp") --> Replace the JAI-EXT "Warp" operation with the JAI one

JAIExt.registerJAIEXTDescriptor("Warp") --> Replace the JAI "Warp" operation with the JAI-EXT one

These methods allow replacement of the OperationDescriptor associated with an operation with one from JAI or JAI-EXT.

Note

OperationDescriptor is a class describing the parameters to set for executing a JAI/JAI-EXT operation.

In order to avoid exceptions after replacing the OperationDescriptor associated with a JAI/JAI-EXT operation, users should take care on how they launch the operation:

  1. Using a ParameterBlock instance, which does not provide the same checks present in the ParameterBlockJAI class which may lead to unexpected exceptions. Here is an example

    // Inputs
    RenderedImage img; // Example image (256x256)
    double[] scales = new double[]{2.0d};
    double[] offsets = new double[]{1.0d};
    // Input NoData, for example 128. It must be passed as a JAI-EXT "Range"
    Range nodata = RangeFactory.create(128d, 128d);
    // Input ROI
    ROI roi = new ROIShape(new Rectangle(0, 0, 128, 128));
    // Optional input RenderingHints
    RenderingHints hints;
    
    // Creation of the ParameterBlock
    ParameterBlock pb = new ParameterBlock();
    // Setting the parameters
    pb.setSource(img, 0); // The source image.
    pb.set(scales, 0);    // The per-band constants to multiply by.
    pb.set(offsets, 1);   // The per-band offsets to be added.
    pb.set(roi, 2);       // ROI
    pb.set(nodata, 3);    // NoData range
    // Calling the operation
    RenderedOp result = JAI.create("Rescale", pb, hints);
    
  2. Call the related GeoTools ImageWorker method, if present:

    // Same inputs as above
    // Instantiation of the ImageWorker
    ImageWorker w = new ImageWorker(img);
    // Setting RenderingHints
    w.setRenderingHints(img);
    // Setting ROI and NoData
    w.setROI(roi);
    w.setNoData(nodata);
    
    // Executing the operation
    w.rescale(scales, offsets);
    
    // Getting the result
    RenderedOp result = w.getRenderedImage();
    

Note

The main aim of the JAI-EXT project is to completely replace all the JAI framework. In this temporary phase users may notice a few error messages on startup similar to this one:

Error in registry file at line number #5
A descriptor is already registered against the name "OrderedDither" under registry mode "rendered"

These errors are reported by JAI at low level and will be removed when JAI has been totally replaced.

GeoTools registration

Since the majority of the GeoTools operations are internally bound to the JAI operations, users must take care on how they handle them with JAI-EXT.

The first suggestion is to always use a CoverageProcessor instance for getting a GeoTools coverage operation. It should be better to get a new CoverageProcessor instance by using the static factory method CoverageProcessor.getInstance() since this method allows to cache the various CoverageProcessor instances and reuse them if needed.

When an OperationDescriptor is replaced, users should take care to remove the existing associated GeoTools operation from all the CoverageProcessor instances and then to insert it again. This procedure must be done in order to avoid having a GeoTools operation with an internal OperationDescriptor which has been replaced; this situation may lead to wrong parameter initialization which could then lead to exceptions during Coverage processing.

The procedure is described below.

CoverageProcessor.removeOperationFromProcessors("Warp"); // Removal of the operation from the processors
CoverageProcessor.updateProcessors(); // Update of all the processors with the new operation

Best Practice

Below is a simple piece of code for how to handle NoData for a GridCoverage.

// Creation of a new GridCoverage2D from a RenderedImage
RenderedImage img; // Example image (256x256)
// Coverage CRS
CoordinateReferenceSystem crs = CRS.decode("EPSG:4326");
// Coverage Envelope
Envelope envelope = new ReferencedEnvelope(-180, 180, -90, 90, crs);
// Coverage Properties
Map<String, Object> properties = new HashMap<String, Object>();
// NoData definition
double nodata = -9999;
// Wrapping NoData inside a container
NoDataContainer container = new NoDataContainer(nodata);
// Setting NoData as property
CoverageUtilities.setNoDataProperty(properties, container);

// Setting ROI as property
ROI roi = new ROIShape(new Rectangle(0, 0, 128, 128));
CoverageUtilities.setROIProperty(properties, roi);

// Creating the GridCoverage
GridCoverageFactory factory = new GridCoverageFactory();
GridCoverage2D coverage = factory.create("Test", img, envelope);

// Retrieving NoData from the GridCoverage
NoDataContainer newContainer = CoverageUtilities.getNoDataProperty(coverage);

// Retrieving ROI from GridCoverage
ROI newROI = CoverageUtilities.getROIProperty(coverage);

It should be noted that NoData is always returned as NoDataContainer instance. This class provides useful methods for accessing NoData as array, single value or Range. In the following code shows how to change the NoData value after executing a single operation.

// Getting CoverageProcessor
CoverageProcessor processor = CoverageProcessor.getInstance();

// Getting Scale operation
Operation scale = processor.getOperation("Scale");
// Getting scale parameters
ParameterValueGroup params = processor.getParameters();
params.parameter("Source0").setValue(coverage);
// Setting the Background. The first value will be taken as NoData if a NoData was already present in input
params.parameter("backgroundValues").setValue(new double[]{100});

// Executing the operation
GridCoverage2D result = (GridCoverage2D) processor.doOperation(params);
// Getting the new NoData value
NoDataContainer newNoDataContainer = CoverageUtilities.getNoDataProperty(result); // it should have 100 as NoData

Warning

Since the GTCrop operation has been moved to the JAI-EXT project, users should take care that replacing the JAI-EXT Crop with the JAI one will result in the loss of all the fixes provided by GTCrop.