Wednesday, June 1, 2011

Next please ! Image iterators part 2

In the previous article the new SimpleIterator and WritableSimpleIterator classes were introduced. In this article, we'll look at some additional features of those classes and then take a peek at another new class: WindowIterator.

Sometimes size matters. If you are working with massive images it's important to make data access as efficient as possible. This is even more important when working with images over a network. SimpleIterator caters for this with its tile-wise traversal option...


RenderedImage myGreatBigImage = ...
Rectangle iteratorBounds = ...
int outside = 0;

// create an iterator that will visit each tile in turn
SimpleIterator iter = new SimpleIterator(myGreatBigImage, iteratorBounds,
outside, SimpleIterator.Order.TILE_X_Y);

// no special code is required when using the iterator in tile-wise mode
do {
Number sample = iter.getSample();
// do interesting things with data
...
while (iter.next());


With tile-wise traversal, the iterator first divides its bounding rectangle into sub-bounds according to the image's tile structure. When moved with the next() method, as in the above code snippet, the iterator will visit all pixels in a sub-bound before proceeding to the next. This minimizes the need to swap tiles in and out of memory or move them across a network.

You can also use random access in combination with tile-wise traversal as in this example...

SimpleIterator iter = new SimpleIterator(myGreatBigImage, iteratorBounds,
outside, SimpleIterator.Order.TILE_X_Y);

// We first sample a specified position
Number sample = iter.getSample(x, y, band);

// Now if we continue sequentially, the iterator will do tile-wise
// sampling of the image
while (iter.next()) {
sample = iter.getSample(band);
...
}


WindowIterator

Moving window algorithms are common in image processing. Many kernel-based analyses can be implemented as convolutions using JAI's Convolve operator or the more flexible JAI-tools MaskedConvolve operator (see also the JAI-tools KernelFactory class). However, algorithms which are more complex or involve probabilistic sampling of the values in the moving window are better handled by passing the moving window values to the client directly. The new WindowIterator class caters for this.

Consider the Voter Model algorithm in which, over many iterations, the value of each pixel in an image is replaced by a randomly selected value from its neighbourhood. Clint Sprott's excellent website has examples of this algorithm applied to ecological simulation and image reconstruction.

Below is a snippet adapted from a JAI-tools example program showing how WindowIterator can be used with a WritableSimpleIterator to implement the Voter Model algorithm (the complete example is available here).


// A WindowIterator gets values from the source image
Dimension winDim = new Dimension(3, 3);
Point keyElement = new Point(1, 1);
winIter = new WindowIterator(sourceImage, null, winDim, keyElement);
int[][] dataWindow = null;

// A WritableSimpleIterator to set values in the destination image
writeIter = new WritableSimpleIterator(destImage, null, null);

do {
// Get a 3x3 window of values from the source image
dataWindow = winIter.getWindow(dataWindow);

// Call a method which random selects a window position
// other than the key element
Point nbr = getRandomNbr(winDim, keyElement);

// Write the value of the selected neighbour to the
// destination image
writeIter.setSample(dataWindow[nbr.y][nbr.x]);

} while (winIter.next() && writeIter.next());


The bounds of a WindowIterator represent all positions that the data window's key element will traverse. If the bounds are equal to, or extend beyond, the image bounds, then some data window positions can lie outside the image. WindowIterator lets you specify an outside value to be returned for these positions...


// This iterator will return -1 for all cells that lie outside the image
WindowIterator iter = new WindowIterator(image, bounds, winDim, keyElement, -1);


If you're familiar with JAI's BorderExtender classes you'll note that this is equivalent to using a BorderExtenderConstant.

WindowIterator also allows you to set the X and Y step distances. In this example, the step distances are set to the window dimensions to down-sample the source image...

RenderedImage image = ...

Dimension winDim = new Dimension(3, 3);
Point keyElement = new Point(1, 1);
int xstep = 3;
int ystep = 3;

Rectangle iterBounds = new Rectangle(
image.getMinX() + 1, image.getMinY() + 1,
image.getWidth - 2, image.getHeight() - 2);

WindowIterator iter = new WindowIterator(image, iterBounds, winDim, keyElement,
xstep, ystep, 0);

do {
dataWindow = iter.getSample(dataWindow);
// extract desired summary from window and write to
// a destination image
...
} while (iter.next());

3 comments:

  1. Cool stuff Michael!
    Another good thing about iterating tile by tile is when you're doing so against a very deep JAI processing chain, with normal iteration order the first tile accessed might not be in cache anymore once you get back to it for the second row iteration.

    ReplyDelete
  2. Ah yes, that's a good point for the (future) user guide.

    ReplyDelete
  3. Very nice job.

    I try to use the WindowIterator to apply a kernel on a large image (up to 1GO).
    However I have an out of memory. Could please have a look and give me your opinion.
    Thx.

    Below my code

    ImageInputStream iis = ImageIO.createImageInputStream(new File(path));
    ParameterBlockJAI pbj = new ParameterBlockJAI("ImageRead");
    pbj.setParameter("Input", iis);
    ImageLayout layout = new ImageLayout();
    layout.setTileWidth(512);
    layout.setTileHeight(512);

    RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
    RenderedOp image = JAI.create("ImageRead", pbj, hints);

    WindowIterator winIter = new WindowIterator(image, null, winDim,
    keyElement);


    TiledImage tiled = new TiledImage(image, false);

    WritableSimpleIterator writeIter = new WritableSimpleIterator(tiled,null, null);

    do {
    // Get a 3x3 window of values from the source image
    dataWindow = winIter.getWindow(dataWindow);
    //Do the job, create a new pixel value and set it
    writeIter.setSample(pixel);

    } while (winIter.next() && writeIter.next());

    //Save result
    JAI.create("filestore", tiled, "/tmp/test.tif", "TIFF");

    ReplyDelete