Tips for developers

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

An unsorted list of hints that you might find useful:

compile & execute a class

You do not need to call javac yourself with a long classpath:

$ ./fiji --javac YourClass.java

and you can call its main() method just as easy:

$ ./fiji YourClass.class argument1 argument2

rapidly prototype a plugin

It is often easier to start out with a Jython, JRuby or BeanShell script, as you do not have to care about strict typing, exceptions or recompiling. Just place your script (with the correct extension -- .py, .rb or .bsh) into the plugins/ folder and execute the script. Fiji will always execute the current version of the script, so you can edit and run the script without restarting Fiji.

Of course, it is even more convenient to use the Script Editor...

Once you have working code, you can turn it into a proper plugin (this is easiest with BeanShell, as its syntax is closest to Java already), adding strict typing and exception handling as needed.

find the .jar file containing a certain class

Sometimes, the compiler complains about a class not having a certain method or interface, but you know it must contain it. More often than not, that class exists in different versions in your classpath. Find out with

$ ./fiji bin/find-jar-for-class.py the.class.youre.looking.For

If you want to do that with an installed Fiji (i.e. when bin/ is missing), you can start the Script Editor and execute a BeanShell like this:

import ij.IJ;
        
print(IJ.getClassLoader()
        .loadClass("the.class.youre.looking.For")
        .getResource("For.class").toString());

This will output the URL to the .class file, including the path to the enclosing .jar file.

Using ImageJ effectively

ImageJ has a simple API, but it is also big, so here are a few pointers to some useful parts.

How to read a file into an ImagePlus

 ImagePlus image = IJ.openImage(path);

How to get the current image

ImagePlus img = WindowManager.getCurrentImage();  // current image
ImageProcessor ip = img.getProcessor();  // current slice

Making a new image stack -- quickly!

 ImagePlus image = IJ.createImage("my image", "RGB", 640, 480, 20);

How to display an exception in a window

This is especially useful on Windows, where you usually do not see the console:

 IJ.handleException(exception);

This is available since ImageJ 1.43g, as well as the option to set a different exception handler using

 IJ.setExceptionHandler(new IJ.ExceptionHandler() {
    public void handle(Throwable exception) {
       // do something
    }
 });

How to show a plot

ImageJ offers the ij.gui.Plot class to make a window showing a plot. Use it like this:

Plot plot = new Plot("The window title", "labels on the x-axis", "labels on the y-axis",
    float_array_of_x_values, float_array_of_y_values);
plot.show();

Instead of float arrays, you can also use double arrays.

If you need to update the plot at some stage, you need to save the return value of show():

Plot plot = new Plot("The window title", "labels on the x-axis", "labels on the y-axis",
    float_array_of_x_values, float_array_of_y_values);
PlotWindow window = plot.show();
// ...
Plot update = new Plot("Dummy", "x labels", "y labels", x_values, y_values);
window.drawPlot(update);

To add another plot to the same window, use the addPoints() method:

plot.addPoints(float_array_of_x_values, float_array_of_y_values, Plot.LINE);

The plots can be drawn in different colors like this:

Plot plot = new Plot("The window title", "labels on the x-axis", "labels on the y-axis",
    float_array_of_x_values, float_array_of_y_values);
plot.setColor(Color.RED);
plot.draw();
plot.addPoints(float_array_of_x_values, another_float_array_of_y_values, Plot.LINE);
plot.setColor(Color.BLUE);
plot.draw();

You might need to adjust the bounding box if the second plot does not match the bounding box of the first one by using the setLimits() method before the call to plot.draw();

Duplicate, or convert between, ImageProcessor types

The ImageProcessor class has several useful methods: duplicate(), convertToByte(), convertToFloat(), convertToRGB(), and convertToShort().

This class also has some other goodies, such as methods for convolution.

How to store settings persistently

ImageJ (and therefore Fiji, too) has a way to store key/value pairs persistently, i.e. they are available even after a restart. The settings are stored in a file called IJ_Prefs.txt in the subdirectory called .imagej/ in your home directory (on Windows, directly in your home directory; on Mac, in ~/Library/Preferences).

Note: the settings are only saved upon regular exit of Fiji; If you kill the process, or if the Java Runtime crashes, they are not saved. You can ask for the settings to be saved explicitly, though.

Example:

import ij.Prefs;

...

    // save key/value pairs
    Prefs.set("my.persistent.name", "Grizzly Adams");
    Prefs.set("my.persistent.number", 10);

    ....

    // retrieve the values (with default values)
    int myNumber = Prefs.get("my.persistent.number", 0);

    // explicitly save the preferences _now_
    Prefs.savePreferences();

Note: do not use the getString() or getInt(); These methods do not have any setter methods, and they do not access the same values as the get() method (get() actually prefixes the keys with a dot)!

How to turn a number into a string, using a given number of decimal places

Use the d2s() method of the ij.IJ class:

String message = "The current temperature is " + IJ.d2s(degrees, 1) + "° Celsius";

How to abort a plugin completely

Sometimes, you want to abort a plugin without catching an exception at the highest level(s), without having ImageJ show an exception window. You can do that:

throw new RuntimeException(Macro.MACRO_CANCELED);

The special message Macro.MACRO_CANCELED will tell ImageJ not to show the exception message and stack trace in a text window.

Interact with the ROI manager

To add ROIs to the ROI manager, do something like this:

RoiManager manager = RoiManager.getInstance();
if (manager == null)
	manager = new RoiManager();
manager.addRoi(roi);

If you want to add the ROI with a slice label, you have to jump through a hoop:

int currentSlice = image.getCurrentSlice();
RoiManager manager = RoiManager.getInstance();
if (manager == null)
	manager = new RoiManager();
// the first slice is 1 (not 0)
image.setSliceWithoutUpdate(slice);
manager.add(image, rois[i], slice);
image.setSlice(currentSlice);

To retrieve all ROIs from the ROI manager, do this:

RoiManager manager = RoiManager.getInstance();
if (manager != null) {
	Hashtable<String, Roi> table =
		(Hashtable<String, Roi>)manager.getROIs();
	for (String label : table.keySet()) {
		int slice = manager.getSliceNumber(label);
		Roi roi = table.get(label);
		... <do something with the ROI and the slice> ...
	}
}

If you need to preserve the order of the ROIs in the ROI manager, unfortunately you'll have to access the AWT listbox:

import java.awt.List;
...
RoiManager manager = RoiManager.getInstance();
if (manager != null) {
	List labels = manager.getList();
	Hashtable<String, Roi> table = (Hashtable<String, Roi>)manager.getROIs();
	for (int i = 0; i < labels.getItemCount(); i++) {
		String label = labels.getItem(i);
		int slice = manager.getSliceNumber(label);
		Roi roi = table.get(label);
		... <do something with the ROI and the slice> ...
	}
}

To get just the selected ROIs, use code similar to this:

import java.awt.List;
...
RoiManager manager = RoiManager.getInstance();
if (manager != null) {
	String[] labels = manager.getList().getSelectedItems();
	Hashtable<String, Roi> table = (Hashtable<String, Roi>)manager.getROIs();
	for (String label : labels) {
		int slice = manager.getSliceNumber(label);
		Roi roi = table.get(label);
		... <do something with the ROI and the slice> ...
	}
}

Show a results table

First you need to get the results table instance (or create one):

ResultsTable rt = Analyzer.getResultsTable();
if (rt == null) {
        rt = new ResultsTable();
        Analyzer.setResultsTable(rt);
}

Then you can add a new row:

rt.incrementCounter();

... and add entries by column label:

rt.addValue("slice", slice);
rt.addValue("bratwurst", -1);

Finally make sure that the result table is displayed/updated:

rt.show("Results");

Tips for Graphical User Interface (GUI) programming

Programming with Swing components

When programming with Swing, beware that Swing is not thread safe. Swing's golden rule states that:

Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread.

When has a Swing component been realized? When it is visible inside Window or JFrame that got a call to setVisible(true). This implies that its paint(Graphics) method has been called or will be called soon.

These are the methods that can realize a component, or rather, methods called on a Window or Frame or JFrame that will realize all its children components:

setVisible(true)
show()
pack() // this might surprise you

There is a class which helps you with all this: SwingUtilities. Example: to call pack() from within the constructor (which might or might not be called from the Event Dispatch Thread):

if (SwingUtilities.isEventDispatchThread())
	pack();
else try {
	SwingUtilities.invokeAndWait(new Runnable() { public void run() {
		pack();
	}});
} catch (Exception e) { /* ignore */ }

Information extracted from the Swing guide (now only available from via web.archive.org as Oracle removed the original docs), additional information can be found in the Concurrency in Swing lesson of The Java Tutorials.