Thursday, January 13, 2011

Images in a jiffy: speeding up Jiffle with Janino

Jiffle is a scripting language for raster algebra dreamed up by myself and Andrea Antonello. You can use it to create, combine and analyse raster (pixel) images. It's based on the r.mapcalc language for map calculations in the GRASS GIS program.

The idea of Jiffle is to let you concentrate on the interesting bits rather than having to write the incredibly tedious boiler plate code that is normally required to work with image data using Java. The image here of a simple trigonometric function was created with this script...
    xc = width() / 2
    yc = height() / 2
    dx = (x()-xc)/xc
    dy = (y()-yc)/yc
    d = sqrt(dx^2 + dy^2)
    outImg = sin(8 * PI * d)

The Jiffle parser, developed using ANTLR, converts a script like the one above into a tree representation of the expressions (Abstract Syntax Tree or AST). Up until now, running the script involved walking this tree multiple times (once per destination image pixel) and executing the expression like an interpreter. While this worked, it was much too slow to process large images.

Enter Janino, which advertises itself (quite accurately) as a "super-small, super-fast Java™ compiler". One of things you can do with Janino is to compile source and load the resulting bytecode in-memory. To apply this to Jiffle, I first replaced the ANTLR grammar for the run-time tree walker with one that translates an AST into a Java method body. For example, the script above is translated to this...

double xc=_width / 2.0;
double yc=_height / 2.0;
double dx=(_x - xc) / xc;
double dy=(_y - yc) / yc;
double d=Math.sqrt(Math.pow(dx, 2.0) + Math.pow(dy, 2.0));
writeToImage("out", _x, _y, _band, Math.sin(25.132741228718345 * d));

Next, I created a new interface JiffleRuntime and a template for an implementing class that looks like this...

import jaitools.jiffle.runtime.JiffleRuntime;

import java.awt.image.RenderedImage;
import java.awt.image.WritableRenderedImage;
import java.util.HashMap;
import java.util.Map;
import javax.media.jai.iterator.RandomIter;
import javax.media.jai.iterator.RandomIterFactory;
import javax.media.jai.iterator.WritableRandomIter;

public class JiffleRuntimeImpl implements JiffleRuntime {

/*
* Note not using generics here because they are not
* supported by Janino.
*/
private Map images = new HashMap();
private Map readers = new HashMap();
private Map writers = new HashMap();

private double _width;
private double _height;

public void evaluate(int _x, int _y, int _band) {
// COMPILER_BREAK

throw new UnsupportedOperationException("Method body to be provided by Jiffle compiler");

// COMPILER_RESUME
}

public double readFromImage(String imageName, int x, int y, int band) {
RandomIter iter = (RandomIter) readers.get(imageName);
return iter.getSampleDouble(x, y, band);
}

public void writeToImage(String imageName, int x, int y, int band, double value) {
WritableRandomIter iter = (WritableRandomIter) writers.get(imageName);
iter.setSample(x, y, band, value);
}

public void setDestinationImage(String imageName, WritableRenderedImage image) {
images.put(imageName, image);

if (images.size() == 1) {
_width = image.getWidth();
_height = image.getHeight();
}

writers.put(imageName, RandomIterFactory.createWritable(image, null));
}

public void setSourceImage(String imageName, RenderedImage image) {
images.put(imageName, image);
readers.put(imageName, RandomIterFactory.create(image, null));
}

}

The Jiffle compiler inserts the generated statements into the evaluate method of this class...

public void evaluate(int _x, int _y, int _band) {
double xc=_width / 2.0;
double yc=_height / 2.0;
double dx=(_x - xc) / xc;
double dy=(_y - yc) / yc;
double d=Math.sqrt(Math.pow(dx, 2.0) + Math.pow(dy, 2.0));
writeToImage("out", _x, _y, _band, Math.sin(25.132741228718345 * d));
}

Now we have the run-time class source as a String in memory, we compile it with Janino's SimpleCompiler class and retrieve the resulting executable bytecode as shown here...

SimpleCompiler compiler = new SimpleCompiler();
compiler.cook(runtimeSource);
Class clazz = compiler.getClassLoader().loadClass(PACKAGE_NAME + "." + RUNTIME_CLASS_NAME);
runtimeInstance = (JiffleRuntime) clazz.newInstance();

And voila ! The Jiffle script has now been compiled into a form that will run at the speed of the JVM, hundreds of time faster than the original tree walking approach.

Janino doesn't handle generic collections or var-arg method calls so some work-arounds were required in the parser grammars and Jiffle's run-time support classes to deal with this. For example, instead of generating this source...

double foo = JiffleFunctions.median(x1, x2, x3, x4, x5);

We change the median method to expect a Double array and have the compiler generate this...

double foo = JiffleFunctions.median(new Double[]{x1, x2, x3, x4, x5});

Friday, April 16, 2010

Version 1.0.1 released

The jai-tools project is pleased to announce the release of version 1.0.1 on April 16, 2010.

This version provides a number of bug fixes and new features in the statistics and numerics classes (utils module) plus new data filtering options with the ZonalStats operator. See the release page for full details of the changes in this version.

You can checkout the sources with your favourite subversion client from:
http://jai-tools.googlecode.com/svn/tags/1.0.1

If you use Maven as your build tool you can download the jai-tools binary, source and javadoc jars from the central Maven repository. See here for details on how to set up your pom.xml file.

If you prefer to add the jars to your project manually they can be downloaded from:
http://repo1.maven.org/maven2/com/googlecode/jaitools/

Updated snapshot jars for the development version (1.1-SNAPSHOT) are also available (see here for details).

Share and enjoy.

Monday, March 15, 2010

New BorderExtender and Number handling classes

Two new BorderExtender classes have been added to jai-tools 1.1-SNAPSHOT:

RandomBorderExtender generates an image border with uniform random pixel values sampled from a user-specified range.

SamplingBorderExtender generates pixel values by randomly sampling the source image within a threshold distance of each border pixel.

You'll find these classes in the utils module, jaitools.imageutils package and you can use them in any JAI operation that accepts a BorderExtender in the RenderingHints. They're brand new so I'd welcome your comments, suggestions, bug reports etc.

Also in that module is the jaitools.numeric.NumberOperations class. This used by the new BorderExtenders but is also available for general use. It provides a number of static helper methods for working with Number objects without having to type-narrow them. For instance:

// Add two Number objects. The return value will be an instance of the
// highest ranking class of the two arguments (e.g. Byte + Integer will
// give an Integer result)
Number n1 = ...
Number n2 = ...
Number sum = NumberOperations.add(n1, n2);

You can checkout the source code for jai-tools 1.1-SNAPSHOT with your favourite subversion client from http://jai-tools.googlecode.com/svn/trunk/

See the jai-tools project site for details on how to use the 1.1-SNAPSHOT jars in your project, either using Maven or manual download.

Version 1.0.0 released

The jai-tools project is pleased to announce the release of version 1.0.0 for your Java programming pleasure.
--- General modules ---
utilsSupport classes including DiskMemTileCache and DiskMemImage
jiffleThe jiffle image scripting language
kernelFactory and utility classes to work with KernelJAI objects
demoA selection of example programs
--- Image operators ---
kernelstatsCalculates neighbourhood statistics
maskedconvolveExtends the standard JAI convolve operator with source and destination masking
rangelookupAn image lookup operator that can handle both integral and floating point data types and work with value ranges
regionalizeIdentifies regions of (sufficiently) uniform value in a source image
zonalstatsCalculates statistics for values in a source image, optionally grouped by zones in a control image


You can checkout the sources with your favourite subversion client from:
http://jai-tools.googlecode.com/svn/tags/1.0.0

If you use Maven as your build tool you can download the jai-tools binary, source and javadoc jars from the central Maven repository.

If you prefer to add the jars to your project manually they can be downloaded from:
http://repo1.maven.org/maven2/com/googlecode/jaitools/