Difference between revisions of "JavaScript Scripting"

(Native Java arrays)
Line 457: Line 457:
* [http://www.mozilla.org/rhino/perf.html Performance tips].
* [http://www.mozilla.org/rhino/perf.html Performance tips].
See also the [[Scripting_comparisons]].
See also the [[Scripting comparisons]].

Revision as of 03:47, 17 November 2008

Javascript tutorial for ImageJ

Language basics

Importing packages and classes

By default, all ImageJ and java.lang.* classes are imported.

You can specify further imports by:

// Import all classes under java.io.*

// Import a single class instead


There are two ways:

  • Direct: the variable is globally visible (dangerous! Leads to nasty bugs.)
  • With a var declaration: the variable is local, visible only within the innermost code block.

Local variables are also faster, because they can be efficiently optimized for access. Examples:

// global variable 'imp'
imp = IJ.getImage();

// local variable 'i', visible within the loop only:
for (var i = 0; i < 10; i++ ) {
  IJ.log("i is " + i);

Mixing global and local variables in a sensible way:

// global variables 'base_url' and 'names'
// and local variable 'imp', the latter visible within the loop only:

base_url = "http://rsb.info.nih.gov/ij/images/";

names = ["blobs.gif", "boats.gif", "bridge.gif"];

for (var i = 0; i< names.length; i++) {
  var imp = IJ.openImage( base_url + names[i] );
  // process image
  // ...

// ERROR: variable 'imp' is not visible outside the loop
// IJ.log("The last image opened was: " + imp);


There are both Javascript arrays and native java arrays.

Javascript arrays

There are many ways to create a Javascript array. Here are a few:

// One dimensional:
var names = ["blobs.gif", "boats.gif", "bridge.gif"];
IJ.log("We have " + names.length + " names.");

// Two dimensional:
var coords = [[10, 20, 30],   // X coords
              [15, 25, 35]];  // Y coords

IJ.log( "x0, y0 = " + coords[0][0] + ", " + coords[1][0] );
IJ.log( "All X coords: " + coords[0] );

// Uneven dimensions:
var coords = [[10, 20, 30],               // X coords
              [15, 25, 35, 45, 55, 75]];  // Y coords
IJ.log( "Number of X coords: " + coords[0].length );
IJ.log( "Number of Y coords: " + coords[1].length );

Arrays in Javascript are extremely flexible:

// Empty arrays:
var names = new Array();
IJ.log("First name is: " + names[0]); // --> prints "undefined", i.e. null.

// Creating array entries at arbitrary index positions:
names[0] = "Table";
names[5] = "Window";
IJ.log("Number of names: " + names.length); // --> prints 6 ! All other entries are "undefined", null.

// Using the array as a dictionary:
names["one"] = 1;
IJ.log("Number of names: " + names.length); // --> still prints 6! But now the array has a map in it as well.
IJ.log("The 'one' is " + names["one"]);     // --> prints 1

// Array entries can contain anything, including other arrays!
names[3] = new Array();
names[3][0] = "Ok";
names[3][1] = "Good";
names[3][2] = ["Arrays", "are", "very", "flexible"]; // another array!

Native Java arrays

Native java arrays can be passed to java functions and methods directly. For example, to create an array of pixels:

width = 512
height = 512
pixels = new java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, width * height);

To manipulate such arrays, just do so as if they were Javascript arrays. Beware, though, that now you are limited to numerical indices only, and within the array size only!

// Subtract 25 to each pixel:
for (var i = 0; i < pixels.length; i++) {
  pixels[i] -= 25;

Above, beware of all problems derived from manipulating signed byte[] arrays, whose values should be made unsigned first, modified, then signed back into the array.


Simple example:

function invertImage(imp) {
  var ip = imp.getProcessor();

// Obtain the current image:
var imp = IJ.getImage();

// Invoke our function

// Update screen:

The number of arguments you invoke an image with is flexible. All arguments with which a function is invoked are collected in a variable named arguments.

Each function is an object, and has a variable this which provides access to its internal fields, such as name:

For example:

function createImage(width, height) {
  IJ.log( "Function name: " + this.name);
  IJ.log( "Number of arguments: " + arguments.length);
  // Default image type:
  var type = "8-bit";
  // Check if an extra argument for the image type was provided:
  if (arguments.length > 2) {
    // Check that the extra arg makes any sense:
    if ("RGB" == arguments[2]) {
      type = "RGB";
    } else {
      IJ.log("Don't know how to use " + arguments[2]);
      return null;

  var imp = IJ.createImage("New image", type, width, height, 1);
  return imp; 

var imp = createImage(400, 400, "RGB");

For a complex example see the example script Multithreaded Image Processing in Javascript, which, beyond parallelization, illustrates how to pass functions as arguments to other functions, and how to invoke them with variable number of arguments.

Functions as Objects

Any number of variables may be created on the fly on the body of a function, on its own this self reference pointer.

To create an object in Javascript, just declare a function first that stores the object's data:

// Use uppercase, by convention

function Data(image, annotation) {
    this.image = image;
    this.annotation = annotation;

Then just create it:

var data = new Data(IJ.getImage(), "The current image");

IJ.log("data contains: " + data.image + "\n with annotation: " + data.annotation);

To add methods to manipulate the new Data object, create a function that takes the object as an argument:

function annotate(data) {
    var gd = GenericDialog("Annotate");
    gd.addStringField("New annotation:", data.annotation);
    if (gd.wasCanceled()) return;
    // assign new annotation:
    data.annotation = gd.getNextString();

// Invoke on the existing Data object:

Creating import namespaces

var awt_bindings = new JavaImporter(Packages.java.awt.Frame, Packages.java.awt.Button);

with (awt_bindings) {
  // imported classes visible ONLY in this block
  win = new Frame("Title");
  b = new Button("Press me");

Inspecting fields and methods of an object

So you are returned an object for a function and you don't know what is it.

To print its class within the interpreter:

ob = ...

or to the log window:

ob = ...

To print the list of methods it has, with their return types and argument types:

ob = ...
m = ob.getClass().getMethods();
for (var i=0; i<m.length; i++) IJ.log(m[i]);

ImageJ interaction

Opening and creating ImageJ images

imp = new Opener().openImage("/path/to/image.jpg");
// do some processing
// ...

Or easier:

imp = IJ.openImage("/path/to/image.jpg");

Also URLs (in this case, we call directly show(), not keeping the returned image pointer into any variable):


Create an image from scratch, including LUT:

// From scratch:
width = 512
height = 512
pixels = new java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, width * height);
// process the pixels
// ...
// Create LUT:
channel = new java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 256);
for (i=0; i<channel.length; i++)
    channel[i] = Integer(i).byteValue();
cm = new LUT(channel, channel, channel);

// Create the image as 8-bit with the LUT we just created:
imp = new ImagePlus("the title", new ByteProcessor(width, height, pixels, cm));

A more convenient way to create images, with a default grayscale LUT:

imp = ImagePlus("the title", new ByteProcessor(512, 512));
pixels = imp.getProcessor().getPixels();
// do some processing ...
// ...

Running ImageJ commands

To launch a command:

// in a separate thread:

// instead, waiting until it finishes:

To run commands on a specific image:

// Obtain an image to work on:
var imp = IJ.getImage();

// Call the command to add noise to the current image
// which is here the one provided as argument:
IJ.run(imp, "Add Noise", "");

// Subtract 25 to each pixel value:
IJ.run(imp, "Subtract...", "value=25");

Above, you can see what arguments to add to a command by opening the Plugins - Macro Recorder, and then running the command manually. The macro string will print on the recorder window.

Inspect java methods and fields in an object

To print the static fields and methods of ImagePlus class:

s = "";
for (a in ImagePlus) { s += " " + a; }


To print the fields and methods of an instance of ImagePlus (i.e. an image that already exists):

// get the current image
imp = IJ.getImage();
// print fields and methods
s = ""; for (a in imp) { s += " " + a; }

... which prints all method names such as getStatistics isHyperStack etc. and fields like width and height (because there are public 'get' methods for it such as getWidth() and getHeight() .)

Creating a script for ImageJ

Save your javascript script in a text file:

  1. with extension .js
  2. and with an underscore '_' in its name: my_first_script.js

...and just drop it in ImageJ's plugins folder or a subfolder.

On startup, the script will appear in the corresponding menu.

If you add the script after ImageJ was started, just call "Help - Update menus" and it will be picked up.

You can continue modifying and saving the script file. Every time you run it from the menu, it will be read from the file system.

Interfaces and anonymous classes

To create an ImageListener without declaring a new class that implements such java interface, simply use a function that will be mapped to all its methods (as long as they have the same signature, which they do in this case):

ImagePlus.addImageListener( function (imp, name) {
       if (name == "imageOpened") {
              IJ.log("Opened image: " + imp);
      } else if (name == "imageClosed") {
              IJ.log("Closed image: " + imp);
      } else if (name == "imageUpdated") {
              IJ.log("Updated image: " + imp);

Alternatively, one can create an object with declared functions inside to assign then to an anonymous class created on the fly from an interface:

body = {
  run: function () {
// Runnable is an interface
runnable = new Runnable(body);
new Thread(runnable).start();


new Thread( function () { IJ.log("Running!"); } ).start();

What the code above did: to look for an interface that could take a method with no arguments, represented by the function, and instantiate an anonymous class that implements such interface with the function mapped to its method.

See also an example plugin for ImageJ written in javascript.

Multithreaded Image Processing in Javascript

The following example shows how to create a generic function, named multithreader, that accepts another function as argument and executes it in parallel a number of times. As a very simple example, a printer function is passed to the multithreader, and a list of numbers is printed without repeating anyone, and not preserving the order of course.

The multithreader scales up to as many CPU cores as the computer has to offer.

Always remember: only completely independent tasks can be parallelized effectively!

A good strategy for multithreading involves carefully considering the task to parallelize: how small can the chunks be? For an image, a chunk could be a pixel or a line, but often those are too small to overcome the overhead of parallelization.

Despite the simple example below, the multithreader framework function allows variable amount of arguments to be passed, as illustrated in the complete plugin Multithreaded_Image_Processing_in_Javascript.js. The script shows how to generate an image with random pixel values in a multithreaded manner, and how the choice of chunks to process in parallel is made for reasonable effectiveness.

// Now, abstract away the multithreading framework into a function
// that takes another function as argument:

function multithreader(fun, start, end) {
        var threads = new java.lang.reflect.Array.newInstance(java.lang.Thread, Runtime.getRuntime().availableProcessors());
        var ai = new AtomicInteger(start);
        // Prepare arguments: all other arguments passed to this function
        // beyond the mandatory arguments fun, start and end:
        var args = new Array();
        var b = 0;
        IJ.log("Multithreading function \"" + fun.name + "\" with arguments:\n  argument 0 is index from " + start + " to " + end);
        for (var a = 3; a < arguments.length; a++) {
                args[b] = arguments[a];
                IJ.log("  argument " + (b+1) + " is " + args[b]);
        var body = {
                run: function() {
                        for (var i = ai.getAndIncrement(); i <= end; i = ai.getAndIncrement()) {
                                // Execute the function given as argument,
                                // passing to it all optional arguments:
                                fun(i, args);
                                Thread.sleep(100); // NOT NEEDED, just to pretend we are doing something!
        // start all threads
        for (var i = 0; i < threads.length; i++) {
                threads[i] = new Thread(new Runnable(body)); // automatically as Runnable
        // wait until all threads finish
        for (var i = 0; i < threads.length; i++) {

// The actual desired effect: the printer
function printer(i) {
        IJ.log("i is " + i);
 // Execute:
 multithreader(printer, 0, 10);

See the complete file here: Multithreaded_Image_Processing_in_Javascript.js


See also the Scripting comparisons.