This is an archive of the old MediaWiki-based ImageJ wiki. The current website can be found at imagej.net.

Developing Plugins for ImageJ 1.x

Imagej1-icon.png
This page explains how to develop plugins with the ImageJ 1.x API. If you start developing a new plugin today, it is highly recommended to develop for ImageJ2.



Development
Topics
Overview
Philosophy
Architecture
Source code
Project management
Coding style
Debugging
Tools
GitHub
Git
Maven
IDEs
Travis
AppVeyor
Dotfiles
Guides
Writing plugins
ImageJ Ops
Contributing to a plugin
Distributing your plugins
Development lifecycle
Building a POM
Developing with Eclipse
Hands-on debugging
Adding new ops
Adding new formats
Using native libraries
Tips for developers
Tips for C++ developers
ImageJ 1.x plugins
Versioning
Logging
Uber-JARs

Plugin, script or macro?

If you want to add a new feature to ImageJ, you can either write a script or macro, or do it as a plugin. Scripts and macros are easier to learn, and hence often faster to develop. However, Java offers numerous advantages including better performance in many cases, as well as compile-time safety of the code.

If you are not sure which to choose, take a look at the Scripting Help and try your hand at a script in a language that appeals to you. You can always convert it to a Java plugin later if the execution speed becomes an issue.

If you are certain that you want to write a plugin in Java, keep on reading!

Plugins

What are plugins (in terms of files)?

  • Plugins are a dedicated mechanism for extending ImageJ/Fiji
  • A plugin consists of one or more Java classes living inside a .jar file in plugins/ (exception: a single .class file)
  • All plugin .jar (or .class) files must contain an underscore in their name
  • Plugins can use 3rd party libraries; in Fiji, store them in the jars/ directory (in ImageJ you have to put them into the plugins/ directory, and to avoid funny menu entries in the Plugins menu, you should rename them if their name contains an underscore)

Updating plugins

  • After storing the .jar file(s) into the plugins/ (or jars/) directory, call Help  › Refresh Menus or restart Fiji
  • If the respective plugin is in-use, you might get funny results when refreshing the menus, due to limitations in Sun Java's handling of .jar files.

What are plugins (in terms of menu entries)?

  • If the .jar file contains a file called plugins.config, it determines what menu items are provided by the plugin
  • If the .jar file does not contain a file called plugins.config, all contained classes containing an underscore in their name are added to the Plugins menu

A plugins.config file looks like this:

# Comments (such as title, author, etc)
#
# The other lines have this format:
#  Menu, “Menu Item”, ClassName
# Example:

Plugins > Analyze, “Plot”, fiji.Plot

A class can be reused for multiple menu entries, by passing an optional argument in the plugins.config file:

# Example how to reuse a Java class

Help, “Bug report”, fiji.Send(“bug”)
Help, “Contact”, fiji.Send(“contact”)

What are plugins (in terms of Java code)?

There are two different types of plugins:

  • Plugins which operate on one image
  • All other plugins (including plugins which require more than one input images, or image format loaders)

A filter plugin looks like this:

public class My_Plugin implements PlugInFilter {
    public int setup(String arg, ImagePlus image) {
        return DOES_ALL;
    }
    public void run(ImageProcessor ip) {
        // Here is the action
    }
}

A general plugin looks like this:

public class My_Plugin implements PlugIn {
    public void run(String arg) {
        // Here is the action
    }
}

Note: it is of course possible to implement a filter plugin using the PlugIn interface, but ImageJ will perform more convenience functions if you use the PlugInFilter interface, such as verifying that there is an image and that it is of the correct type, and error handling.

Limitations

  • Plugins can only implement menu entries (in particular, they cannot provide tools in the toolbar)
  • Some functions which are easy to call via macros are not available via the public Java API (e.g. Image  › Stacks  › Plot Z-axis profile...)
  • It is often quicker to write macros

Rapid prototyping with the Script Editor



There a few good reasons why you should try the Script Editor for rapid prototyping of your plugins or scripts:

  • Supports Jython, JRuby, Javascript, Clojure, Java, BeanShell, and ImageJ's Macro Language
  • Syntax highlighting
  • Compile and Run without restarting Fiji (mini-IDE)
  • Export scripts/plugins as .jar files
  • You can compile & run Java classes in the Script Editor which implement neither a plugin nor a plugin filter, but which have a static main() method
  • Provides code templates
  • Convenience functions, (add import, open JavaDoc, for given class, etc)

Quick Start

To plunge into writing plugins, make sure that there is an active image (e.g. a sample image), start the Script Editor ( File  › New  › Script), and select the Process Pixels menu item from the Templates  › Java menu. Then, run the plugin with Run>Run.

Getting started with Maven

The example-legacy-plugin project provides a working example, and documentation, illustrating how an ImageJ plugin should be structured from a "best practices in Maven" point of view.

Using this project requires a basic understanding of Git and Maven; thus if you are already familiar with the ImageJ 1.x API, this is a reasonable starting point to learn the project management tools used in ImageJ2 development.

Basic workflow

All plugin development tends to follow a consistent "Design - Build - Test" workflow. Practically, this looks like:

  • Make changes to the source code (e.g. in Eclipse)
  • Build your plugin's .jar from the source code (e.g. with Maven)
  • Move the plugin to the plugins directory of an existing ImageJ installation
  • (Re)start ImageJ and run the plugin to test the behavior

... and repeat until your plugin is working as intended.

ImageJ's API

The source of the various Fiji-related projects is spread over several source code repositories, and so is their API documentation. An overview of all the javadoc resources can be found on this page with javadoc links.

The class IJ

The class ij.IJ is a convenience class with many static functions. Two of them are particularly useful for debugging:

// output into the Log window
IJ.log(“Hello, World!”);

// Show a message window
IJ.showMessage(“Hello, World!”);

The class ImageJ

The class ij.ImageJ implements the main window of ImageJ / Fiji, and you can access it via ij.IJ's static method getInstance():

// check if ImageJ is used interactively
if (IJ.getInstance() != null)
    IJ.showMessage(“Interactive!”);

Typically, all you do with that instance is to test whether ImageJ is used as a library (in which case the instance is null).

The class WindowManager

Use the class ij.WindowManager to access the ImageJ windows / images:

// how many windows / images are active?
IJ.log(“There are “
    + WindowManager.getNonImageWindows().length
    + “ windows and “
    + WindowManager.getImageCount()
    + “ images!”);

When implementing a filter plugin, you usually do not need to access WindowManager directly.

The hierarchy of the classes representing an image

All images are represented as instances of ij.ImagePlus. This class wraps an ij.ImageStack of slices. Slices are data-type dependent instances of ij.process.ImageProcessor: ij.process.ByteProcessor, ij.process.ShortProcessor, ij.process.FloatProcessor, and ij.process.ColorProcessor. Or graphically:

Image Class Hierarchy.png

Example usage:

// get the current image
ImagePlus image = WindowManager.getCurrentImage();

// get the current slice
ImageProcessor ip = image.getProcessor();
 
// duplicate the slice
ImageProcessor ip2 = ip.duplicate();

Beyond 3D: Hyperstacks

In ImageJ, you can represent more than 3 dimensions in an image: X, Y, Z, channels, frames (time). Internally, these 5-dimensional images are still represented as stacks of images (essentially, a one-dimensional array of ImageProcessor instances). The ImagePlus class knows how to transform (channel, z-slice, frame) triplets into the corresponding index in the ImageStack, though:

// get the n'th slice (1 <= n <= N!)
ImageStack stack = image.getStack();
int size = stack.getSize();
ImageProcessor ip = stack.getProcessor(size);

// get the ImageProcessor for a given
// (channel, slice, frame) triple
int index = image.getStackIndex(channel, slice, frame);
ImageProcessor ip = stack.getProcessor(index);

Note: for historical reasons, slice indices (and channel and frame indices as well) start at 1. This is in contrast, e.g. to the x, y coordinates, which start at 0 (as one might be used to from other computer languages except BASIC, Pascal and MATLAB).

Working with the pixels' values

The subclasses of the ImageProcessor class implement 2-dimensional images for specific data types (8-bit, 16-bit, 32-bit floating point, and RGB color). Let's start with the grayscale ones:

// get one pixel's value (slow)
float value = ip.getf(0, 0);

This would get you the value of the top left pixel of an object ip of type ImageProcessor, as a 32-bit floating point value.

Since the original data type might be 8-bit, that operation can require a cast (type conversion), which can be quite costly when done very often. Therefore, if you know the data type of your images, you can write more efficient (but data type depednent) code:

// get all type-specific pixels (fast)
// in this example, a ByteProcessor
byte[] pixels = (byte[])ip.getPixels();
int w = ip.getWidth(), h = ip.getHeight();
for (int j = 0; j < h; j++)
    for (int i = 0; i < w; i++) {
        // Java has no unsigned 8-bit data type, so we need to perform Boolean arithmetics
        int value = pixels[i + w * j] & 0xff;
        ...
    }

Note: The previous example assumes that your images are 8-bit (unsigned, i.e. values between 0 and 255) images. Since Java has no data type for unsigned 8-bit integers, we have to use the & 0xff dance (a Boolean AND operation) to make sure that the value is treated as unsigned integer.

Accessing the pixels' values gets trickier when it comes to RGB images. These use the native data type int (32-bit signed integer) to encode 3 color channels à 8-bit, packed into the lower 24 bits (note that ImageJ might store things in the upper 8 bits, so you cannot assume them to be 0). Therefore, the getf() method of the ImageProcessor class does not make sense on color images. You have to access the pixels like this:

// get all pixels of a ColorProcessor
int[] pixels = (int[])ip.getPixels();
int w = ip.getWidth(), h = ip.getHeight();
for (int j = 0; j < h; j++)
    for (int i = 0; i < w; i++) {
        int value = pixels[i + w * j];
        // value is a bit-packed RGB value
        int red = value & 0xff;
        int green = (value >> 8) & 0xff;
        int blue = (value >> 16) & 0xff;
}

Making new images

To make a new image - be it 2, 3, 4 or 5 dimensional - you have to create instances of ImageProcessor first. Example:

ImageProcessor gradient(double angle, int w, int h) {
    float c = (float)Math.cos(angle);
    float s = (float)Math.sin(angle);
    float[] p = new float[w * h];
    for (int j = 0; j < h; j++)
        for (int i = 0; i < w; i++)
            p[i + w *j] = (i – w / 2) * c + (j – h / 2) * s;
    return new FloatProcessor(w, h, p, null);
}

This example implements a method that shows a gradient along a given angle. You can use this method to build a 3-dimensional image:

// make a stack of gradients
int w = 512, h = 512;
ImageStack stack = new ImageStack(w, h);
for (int i = 0; i < 180; i++)
    stack.addSlice(“”, gradient(i / 180f * 2 * Math.PI, w, h));
ImagePlus image = new ImagePlus(“stack”, stack);
// you do not need to show intermediate images
image.show();

Informing the user about the progress

This code snippet shows you how to update the progress bar and the status text:

// show a progress bar
for (int i = 0; i < 100; i++) {
    // do something
    IJ.showProgress(i + 1, 100);
}

// show something in the status bar
IJ.showStatus(“Hello, world!”);

Note: Calling IJ.showProgress(n, n); will hide the progress bar; Therefore, it makes sense to update the progress bar at the end of a loop iteration, so that after the last iteration, the progress bar is hidden.

Frequently used operators

The ImageProcessor class has a few methods such as smooth(), sharpen(), findEdges(), etc

Tip: use the Script Editor's functions in the Tools menu:

  • Open Help for Class... (opens the JavaDoc for a class in a browser),
  • Open .java file for class... (requires the respective source files to be present in the Fiji directory, such as after Downloading and Building Fiji From Source, or
  • Open .java file for menu item... (also needs the source files).

Plots

You can show a plot window very easily using the Plot class:

void plot(double[] values) {
    double[] x = new double[values.length];
    for (int i = 0; i < x.length; i++)
        x[i] = i;
    Plot plot = new Plot(“Plot window”, “x”, “values”, x, values);
    plot.show();
}

It is almost as easy to put multiple plots into one window:

void plot(double[] values, double[] values2) {
    double[] x = new double[values.length];
    for (int i = 0; i < x.length; i++)
        x[i] = i;
    Plot plot = new Plot(“Plot window”, “x”, “values”, x, values);
    plot.setColor(Color.RED);
    plot.draw();
    plot.addPoints(x, values2, Plot.LINE);
    plot.show();
}

To update the contents of a plot window, remember the return value of plot.show() which is a PlotWindow, and use its drawPlot() method:

void plot(double[] values) {
    ...
    PlotWindow plotWindow = plot.show();
    ...
    Plot plot = new Plot("Plot window", "x", "values", x, values);
    plotWindow.drawPlot(plot);
}

The results table

Whenever your plugin quantifies things in the images, you might want to output the values in a results table:

ResultsTable rt = Analyzer.getResultsTable();
if (rt == null) {
    rt = new ResultsTable();
    Analyzer.setResultsTable(rt);
}
for (int i = 1; i <= 10; i++) {
    rt.incrementCounter();
    rt.addValue(“i”, i);
    rt.addValue(“log”, Math.log(i));
}
rt.show(“Results”);

Regions of interest

You can access the ROIs in the following fashion:

    // testing ROI type
    if (roi != null && roi.getType() == Roi.POLYGON)
        IJ.log(“This is a polygon!”);
    showCoordinates((PolygonRoi)roi);

...

// get ROI coordinates
void showCoordinates(PolygonRoi polygon) {
    PolygonRoi polygon = (PolygonRoi)roi;
    int[] x = polygon.getXCoordinates();
    int[] y = polygon.getYCoordinates();
    Rectangle bounds = polygon.getBounds();
    for (int i = 0; i < x.length; i++)
        // x, y are relative to the bounds' origin
        IJ.log(“point ” + i + “: “ + (x[i] + bounds.x) + (y[i] + bounds.y));
}

Note: If the image has no ROI set, then getRoi() will return null, so you must check whether roi != null before accessing fields or methods on the object.

Of course, you can also set ROIs programmatically:

// rectangular ROI
Roi roi = new Roi(10, 10, 90, 90);
image.setRoi(roi);

// oval ROI
Roi roi = new OvalRoi(10, 10, 90, 90);
image.setRoi(roi);

Calling ImageJ2 from ImageJ 1.x

You can use ImageJ2-specific functionality from within an ImageJ 1.x plugin. For example, ImageJ2 provides a spreadsheet-like results table that supports string cells. You can write an ImageJ 1.x plugin that produces such a spreadsheet, displaying it onscreen.

See the call-modern-from-legacy example of the ImageJ Tutorials:

DisplayATable.java

Further tips

Please see also the developers tips how to use ImageJ's API effectively.

Next steps

See guides on: