Difference between revisions of "JavaScript Scripting"

(Added functions)
(Functions)
Line 167: Line 167:
 
imp.show();
 
imp.show();
 
</source>
 
</source>
 +
 +
For a complex example see the example script [http://pacific.mpi-cbg.de/cgi-bin/gitweb.cgi?p=fiji.git;a=blob;f=plugins/Examples/Multithreaded_Image_Processing_in_Javascript.js;hb=HEAD 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.
  
 
=== Creating import namespaces ===
 
=== Creating import namespaces ===

Revision as of 06:28, 16 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.*
importPackage(Packages.java.io)

// Import a single class instead
importClass(Packages.java.io.File)

Variables

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
  // ...
  imp.show();
}

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

Arrays

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 unsigned back into the array.

Functions

Simple example:

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

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

// Invoke our function
invertImage(imp);

// Update screen:
imp.updateAndDraw();

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");
imp.show();

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.

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");
  win.add(b);
  win.pack();
  win.setVisible(true);
}

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 = ...
 ob.getClass();

or to the log window:

 ob = ...
 IJ.log(ob.getClass());

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

 ob = ...
 m = ob.getClass().getMethods();
 for (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
// ...
imp.show();

Or easier:

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

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

IJ.openImage("http://rsb.info.nih.gov/ij/images/blobs.gif").show();

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));
imp.show();

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 ...
// ...
imp.show();

Running ImageJ commands

To launch a command:

// in a separate thread:
IJ.doCommand("FFT");

// instead, waiting until it finishes:
IJ.run("FFT");

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; }

... which prints INTEGRATED_DENSITY AREA_FRACTION etc.

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() .)

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 () {
     IJ.log("Running!");
   }
 }
 // Runnable is an interface
 runnable = new Runnable(body);
 new Thread(runnable).start();

Simplified:

 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]);
                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
                threads[i].start();
        }
        // wait until all threads finish
        for (var i = 0; i < threads.length; i++) {
                threads[i].join();
        }
}

// 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

Links

See also the Scripting_comparisons.