home | lab | find me | science | publications | software | toolbox | site map

ImageJ programming tutorials

How to integrate a new file format reader and writer


There are two components:

So here is how you can write a reader/writer yourself for your own custom image file format:

1 - The reader: write a plugin that (1) extends ImagePlus, (2) reads the file path passed as argument to the run method, or asks for one, and (3) places all the image data in the proper place -a FileInfo object contained within the ImagePlus:

public class Open_Custom_File_Format extends ImagePlus implements PlugIn {
	public void run(String arg) {
		String path = getPath(arg);
		if (null == path) return;
		if (!parse(path)) return;
		if (null == arg || 0 == arg.trim().length()) this.show(); // was opened by direct call to the plugin
					      // not via HandleExtraFileTypes which would
					      // have given a non-null arg.

	/** Accepts URLs as well. */
	private String getPath(String arg) {
		if (null != arg) {
			if (0 == arg.indexOf("http://")
			 || new File(arg).exists()) return arg;
		// else, ask:
		OpenDialog od = new OpenDialog("Choose a .mrc file", null);
		String dir = od.getDirectory();
		if (null == dir) return null; // dialog was canceled
		dir = dir.replace('\\', '/'); // Windows safe
		if (!dir.endsWith("/")) dir += "/";
		return dir + od.getFileName();

	/** Opens URLs as well. */
	private InputStream open(String path) throws Exception {
		if (0 == path.indexOf("http://"))
			return new java.net.URL(path).openStream();
		return new FileInputStream(path);

	private boolean parse(String path) {
		// Open file and read header
		byte[] buf = new byte[136];
		try {
			InputStream stream = open(path);
			is.read(buf, 0, 136);
		} catch (Exception e) {
			return false;
		// Read width,height,slices ... from the header
		int width = readIntLittleEndian(buf, 0);
		int height = readIntLittleEndian(buf, 4);
		int n_slices = readIntLittleEndian(buf, 8);
		int type = readIntLittleEndian(buf, 12);

		// Build a new FileInfo object with all file format parameters and file data
		FileInfo fi = new FileInfo();
		fi.fileType = type;
		fi.fileFormat = fi.RAW;
		int islash = path.lastIndexOf('/');
		if (0 == path.indexOf("http://")) {
			fi.url = path;
		} else {
			fi.directory = path.substring(0, islash+1);
		fi.fileName = path.substring(islash+1);
		fi.width = width;
		fi.height = height;
		fi.nImages = slices;
		fi.gapBetweenImages = 0;
		fi.intelByteOrder = true; // little endian
		fi.whiteIsZero = false; // no inverted LUT
		fi.longOffset = fi.offset = 512; // header size, in bytes

		// Now make a new ImagePlus out of the FileInfo
		// and integrate its data into this PlugIn, which is also an ImagePlus
		try {
			FileOpener fo = new FileOpener(fi);
			ImagePlus imp = fo.open(false);
			this.setStack(imp.getTitle(), imp.getStack());
			Object obinfo = imp.getProperty("Info");
			if (null != obinfo) this.setProperty("Info", obinfo);
		} catch (Exception e) {
			return false;
		return true;

	private final int readIntLittleEndian(byte[] buf, int start) {
		return (buf[start]) + (buf[start+1]<<8) + (buf[start+2]<<16) + (buf[start+3]<<24);

Now you need only edit the HandleExtraFileTypes.java file, find the end of the list of custom file readers, and add your own:


		if (name.endsWith(".myown")) {
			return tryPlugIn("Open_Custom_File_Format", path);

You will need to recompile the HandleExtraFileTypes.java file, and of course your own custom plugin. Remember to place the plugin .java and .class files in the plugins folder, for example under Input-Output subfolder.


2 - The writer: a plugin that gets the current image, or one passed by argument, and saves it to a user-defined file path:

public class Save_Custom_File_Format implements PlugIn {

	public void run(String arg) {
		ImagePlus imp = WindowManager.getCurrentImage();
		if (null == imp) return;
		SaveDialog sd = new SaveDialog("Save custom", "untitled", null);
		String dir = sd.getDirectory();
		if (null == dir) return; // user canceled dialog
		dir = dir.replace('\\', '/'); // Windows safe
		if (!dir.endsWith("/")) dir += "/";
		saveCustom(imp, dir + sd.getFileName());

	static public void saveCustom(ImagePlus imp, String path) {
		File file = new File(path);
		DataOutputStream dos = null;
		try {
			dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));

			// read data:
			FileInfo fi = imp.getFileInfo();
			// HEADER: ... read all header tags and metadata
			dos.write( ... );

			// BODY: ... read all stack slices (or single slice)
			for (int i=1; i<imp.getNSlices(); i++) {
				dos.write( ... );

		} catch (Exception e) {
		} finally {

Note how above I have created a separate, static public method to save the image. In this fashion, ImageJ can save your file from the menus, but also any of your other plugins can save the file without having to invoke the plugin (and thus Thread-safe).

To make ImageJ aware of your file writer in the "File / Save As" menu, write a text file titled "plugins.config" with the contents similar to below:

# Name: Custom File Writer 
# Author: Yourself
# Date: 2008/04/12

File>Save As, "My Custom Writer...", Save_Custom_File_Format


Finally, make a jar file with the Save_Custom_File_Format.java, Save_Custom_File_Format.class and plugins.config files. In a terminal, type:

$ jar cf My_Custom_Writer.jar Save_Custom_File_Format* plugins.config

Final notes:

Multithreaded programming for ImageJ


Multithreading means running processes in parallel. Only completely independent tasks can be run in parallel. What makes sense to multithread?

The good news is that ImageJ will process stack slices in parallel automatically, at least as of version 1.40 and later.

As for processing multiple images in parallel: ImageJ 1.41f and later lets you apply any sequence of plugins (and commands, which are/behave like plugins) to a given image in a thread-safe manner.

The core idea: the concept of current image over which any invoked plugins are applied has now been reduced to the current image of the current thread that runs the plugin. The same encapsulation is provided for Macro options, which provide parameter values for plugins and automatically fill in text fields, checkboxes and pull down menus in dialogs.

The new changes in ij 1.41f are:

  1. ij.Macro options are now thread-bound: For both set methods, if the argument options is null then the key/value entry is removed from the underlying table.
  2. ij.WindowManager's current image is now thread-bound: For both set methods, if the ImagePlus imp is null then the Thread/ImagePlus key/value entry is removed from the underlying table.
  3. In accordance to 1 and 2 above, ij.IJ class has now 3 new static methods for invoking a command or plugin on an image, and saving it:
    • ij.IJ.run(ImagePlus imp, String command, String options) : this static method will both set the given ImagePlus imp and the given String options macro string as the current image and the macro options, respectively, for the calling Thread. This means the plugin implementing the command will only see that ImagePlus and those macro options, no matter what other plugins or the user are concurrently executing.
    • ij.IJ.save(ImagePlus, path) and IJ.saveAs(ImagePlus, format, path) are shortcut calls for ij.IJ.run(imp, format, "save=["+path+"]") (a.k.a. the new thread-safe command/plugin invocation call) with error checking for the format and whether the path is null (the latter would result in a SaveDialog poping up.) These two methods finally solve the problem of trying to save two images in two parallel threads, with random collisions in the form of the same image being saved in two files and one of the two images not being saved at all -- made possible by the new Thread-bound current image concept in the ij.WindowManager.

Armed with the new ij 1.41f functionality, we can finally safely program in a multithreaded way at the high level of invoking commands in an image.

For example: test multiple parameters for segmentation of the dots in the dot blot sample image.

import ij.*;
import ij.plugin.PlugIn;
import ij.process.*;
import ij.io.Opener;
import java.util.concurrent.atomic.AtomicInteger;

public class A_Parallel_High_Level_Plugin implements PlugIn {

    public void run(String arg) {

        final ImagePlus dot_blot = new Opener()

        final int starting_threshold = 190;
        final int ending_threshold = 255;
        final int n_tests = ending_threshold - starting_threshold + 1;
        final AtomicInteger ai = new AtomicInteger(starting_threshold);

        // store all result images here
        final ImageProcessor[] results = new ImageProcessor[n_tests];

        final Thread[] threads = newThreadArray();

        for (int ithread = 0; ithread < threads.length; ithread++) {

            // Concurrently run in as many threads as CPUs

            threads[ithread] = new Thread() {
                { setPriority(Thread.NORM_PRIORITY); }

                public void run() {

                // Each thread processes a few items in the total list
                // Each loop iteration within the run method
                // has a unique 'i' number to work with
                // and to use as index in the results array:

                for (int i = ai.getAndIncrement(); i <= ending_threshold;
                     i = ai.getAndIncrement()) {
                    // 'i' is the lower bound of the threshold window
                    ImageProcessor ip = dot_blot.getProcessor().duplicate();
                    ip.setMinAndMax(i, 255);
                    ImagePlus imp = new ImagePlus("Threshold " + i, ip);
                    // Run the plugins on the new image:
                    IJ.run(imp, "Convert to Mask", "");
                    IJ.run(imp, "Analyze Particles...",
                        "size=800-20000 circularity=0.00-1.00 show=Outlines");
                    // The above results in a newly opened image,
                    // with the unique name "Drawing of Threshold " + i
                    // Cleanup:
                    // Capture and store resulting image
                    // (WindowManager.getImage is a synchronized,
                    // thread-safe method)
                    ImagePlus res = WindowManager.getImage
                        ("Drawing of Threshold " + i);
                    results[i-starting_threshold] = res.getProcessor();


        // now the results array is full. Just show them in a stack:
        final ImageStack stack = new ImageStack(dot_blot.getWidth(),
        for (int i=0; i< results.length; i++) {
            stack.addSlice(Integer.toString(i), results[i]);

        new ImagePlus("Results", stack).show();

    /** Create a Thread[] array as large as the number of processors available.
    * From Stephan Preibisch's Multithreading.java class. See:
    * http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD
    private Thread[] newThreadArray() {
        int n_cpus = Runtime.getRuntime().availableProcessors();
        return new Thread[n_cpus];

    /** Start all given threads and wait on each of them until all are done.
    * From Stephan Preibisch's Multithreading.java class. See:
    * http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD
    public static void startAndJoin(Thread[] threads)
        for (int ithread = 0; ithread < threads.length; ++ithread)

            for (int ithread = 0; ithread < threads.length; ++ithread)
        } catch (InterruptedException ie)
            throw new RuntimeException(ie);

Update 2011-04-28: thanks to Daniela Ushizima for finding and reporting a bug in the example code above.

ImageJ programming basics


Anatomy of an ImageJ image

An ImageJ image consists of three parts:

An ImageJ image stack consists of four parts, very similar to the above:

When using stacks, bear in mind the following:

The java.awt.Image contained in the ImagePlus is re-created new when calling updateAndDraw() on the ImagePlus. You must call updateAndDraw() after modifying the pixels if you want the change to be reflected on an image currently being displayed on the screen.

Creating images

To create an image (long and detailed):
int width = 400;
int height = 400;
ImageProcessor ip = new ByteProcessor(width, height);
String title = "My new image";
ImagePlus imp = new ImagePlus(title, ip);

There are several ImageProcessor classes, each with its own specialized set of constructors. See the ImageJ API at ImageProcessor (the parent abstract class of all processors), ByteProcessor, ShortProcessor, FloatProcessor, and ColorProcessor.

To create an image, concisely:
new ImagePlus("My new image", new ByteProcessor(400, 400)).show();
To create an image of any kind, with any number of slices:

A simple 8-bit grayscale image of 400x400 pixels:

ImagePlus imp = IJ.createImage("My new image", "8-bit black", 400, 400, 1);
// or, without getting back a reference:
IJ.newImage("My new image", "8-bit black", 400, 400, 1);

A stack of 10 color images of 400x400 pixels:

ImagePlus imp = IJ.createImage("My new image", "RGB white", 400, 400, 10);
// again, without getting back a reference:
IJ.newImage("My new image", "RGB white", 400, 400, 10);

Destroying images

A call to flush() will release all memory resources used by the ImagePlus.

ImagePlus imp = ...

WARNING! If you are holding a pointer to the ImageProcessor as obtained from ImagePlus getProcessor() method, the pixel array pointer of that ImageProcessor will be set to null. You should instead call duplicate() on the ImageProcessor, or get its pixels directly via getPixels() and store them in a new ImageProcessor of the same dimensions (setting along the LUT, etc).

Likewise, the java.awt.Image gets its own method flush() invoked as well.

Opening images

All methods revolve around the ij.io.Opener class.

The high-level way, from a file or URL
ImagePlus imp = IJ.openImage("/path/to/image.tif");

ImagePlus imp = IJ.openImage("http://www.example.org/path/to/image.tif");

// Without getting back a pointer, and automatically showing it:
// Same but from an URL

Thanks to Wayne Rasband for the above suggestions.

From a file
Opener opener = new Opener();
ImagePlus imp = opener.openImage("/path/to/image.tif");
From an URL
Opener opener = new Opener();
ImagePlus imp = opener.openImage("http://www.example.org/path/to/image.tif");

Above notice how the URL http:// is automatically detected and properly parsed. If desired, one can directly call:

ImagePlus imp = opener.openURL("http://www.example.org/path/to/image.tif");

Editing pixels

Running ImageJ commands on an image

In a high-level way, pixels may be edited by calling ImageJ commands on an image:

ImagePlus imp = ...

// Making a binary image
IJ.run(imp, "Convert to Mask", ""); // "" means no arguments

// Resizing, opens a copy in a new window (the 'create' command keyword)
IJ.run(imp, "Scale...", "x=0.5 y=0.5 width=344 height=345 interpolate create title=[Scaled version of " + imp.getTitle() + "]");


Any ImageJ command may be applied. You can find out which commands to use and which arguments to give them by running the Plugins - Macros - Recorder, and manually calling ImageJ menu commands on an open image.

Editing at mid-level: the ImageProcessor (and ROIs/selections)

To draw or fill the ROI (Region Of Interest) on an image:

ImagePlus imp = ...
ImageProcessor ip = imp.getProcessor();

// Assuming 8-bit image

// fill a rectangular region with 255 (on grayscale this is white color):
Roi roi = new Roi(30, 40, 100, 100); // x, y, width, height of the rectangle

// fill an oval region with 255 (white color when grayscale LUT):
OvalRoi oroi = new OvalRoi(50, 60, 100, 150); // x, y, width, height of the oval
ip.fill(ip.getMask()); // notice different fill method
                       // regular fill() would fill the entire bounding box rectangle of the OvalRoi
// The method above is valid at least for PolygonRoi and ShapeRoi as well.

// draw the contour of any region with 255 pixel intensity
Roi roi = ...

// update screen view of the image

A few words on ROIs:

There are many selection/ROI types: Roi (the rectangular one, and also parent of all others), Line, OvalRoi, PolygonRoi, PointRoi, FreehandRoi, ShapeRoi, TextRoi. In addition some have subtypes, such as the POLYGON and POLYLINE types for PolygonRoi.

Most ROIs are useful for editing images; a few for image analysis (Line, PointRoi, TextRoi).

The most powerful ROI is the ShapeRoi: backed up by a java.awt.geom.GeneralPath, it's capable of storing any number of disconnected regions of interest of any shape.

The ip.fill(ip.getMask()) method is safest to use in all occasions, needing only a check for whether the ImageProcessor mask returned by getMask() is not null.

To rotate, flip and scale the image (or only its ROI, if any):

ImagePlus imp = ...
ImageProcessor ip = imp.getProcessor();





// rotate WITHOUT enlarging the canvas to fit
double angle = 45;
ip.setInterpolate(true); // bilinear

// rotate ENLARGING the canvas and filling the new areas with background color
double angle = 45;
IJ.run(imp, "Arbitrarily...", "angle=" + angle + " grid=1 interpolate enlarge");

// scale WITHOUT modifying the canvas dimensions
ip.setInterpolate(true); // bilinear
ip.scale(2.0, 2.0); // in X and Y

// scale ENLARGING or SHRINKING the canvas dimensions
double sx = 2.0;
double sy = 0.75;
int new_width = (int)(ip.getWidth() * sx);
int new_height = (int)(ip.getHeight() * sy);
ip.setInterpolate(true); // bilinear
ImageProcesor ip2 = ip.resize(new_width, new_height); // of the same type as the original
imp.setProcessor(imp.getTitle(), ip2); // UPDATE the original ImagePlus

// update screen view of the image

The ImageProcessor class offers methods to draw lines, text and dots, and many more. Have a look at the ImageProcessor API.

Editing at low-level: the pixel array
ImagePlus imp = ...
ImageProcessor ip = imp.getProcessor();

// Editing the pixel array
if (imp.getType() == ImagePlus.GRAY8) {
    byte[] pixels = (byte[])ip.getPixels();
    // ... do whatever operations directly on the pixel array

// Replacing the pixel array: ONLY if same size
if (imp.getType() == ImagePlus.GRAY8) {
    int width = ip.getWidth();
    int height = ip.getHeight();
    byte[] new_pixels = new byte[width * height];
    // set each pixel value to whatever, between -128 and 127
    for (int y=0; y<height; y++) {
        for (int x=0; x<width; x++) {
            // Editing pixel at x,y position
            new_pixels[y * width + x] = ...;
    // update ImageProcessor to new array

// Replacing the pixel array but of different length: for example, to resize 2.5 times in width and height
int new_width = (int)(ip.getWidth() * 2.5);
int new_height = (int)(ip.getHeight() * 2.5);
ImageProcessor ip2 = ip.createProcessor(new_width, new_height); // of same type
imp.setProcessor(imp.getTitle(), ip2);

if (imp.getType() == ImagePlus.GRAY8) {
    byte[] pix = (byte[])imp.getProcessor().getPixels(); // or ip2.getPixels();
    // .. process pixels ...
    for (int y=0; y<height; y++) {
        for (int x=0; x<width; x++) {
            // Editing pixel at x,y position
            new_pixels[y * width + x] = ...;

// DON'T forget to update the screen image!

Of course updating the screen image is only necessary if you are displaying the ImagePlus.

Saving images

The high-level way
ImagePlus imp = ...
IJ.saveAs(imp, "tif", "/path/to/image.tif");

// or by using the file format extension:
IJ.save(imp, "/path/to/image.tif");

Many formats are supported. Search for method "saveAs" in class IJ.

Via FileSaver class
ImagePlus imp = ...
new FileSaver(imp).saveAsTiff("/path/to/image.tif");

The FileSaver class has many more options: saveAsTiffStack, saveAsJpeg, saveAsPng, saveAsGif ... etc.

How to automate an ImageJ dialog


That is, how to fill in automatically the values in a dialog, so that it never shows up. In this way, plugins that require dialogs can be used for batch processing without user intervention.

The key points:

  1. Set the name of the thread in which the dialog will show to "Run$_" plus something else, such as "Run$_my_batch_script".
  2. Set the Macro options for that Thread. The Macro options are a collection of key/value such as: "title='My new image' width=512 height=512 image_type='8-bit add_noise'.
  3. Execute the function normally: dialogs don't show. Any keys not defined in the Macro options will take the default value for that key.

An example in javascript:


// Prepare options for Bandpass filter
options = "filter_large=40 filter_small=3 suppress=None tolerance=5 autoscale saturate";
// Get the current thread
thread = Thread.currentThread();
original_name = thread.getName();
// Rename current thread
// Set the options for the current thread
Macro.setOptions(Thread.currentThread(), options);

// Get the current image
imp = IJ.getImage();
// Finally, run the bandpass filter without dialogs:
IJ.runPlugIn(imp, "ij.plugin.filter.FFTFilter", "");

// Be nice: undo naming, so other scripts may run with dialogs
// Be safe: remove the thread's options from the table
// (which also removes the reference to the thread itself)
Macro.setOptions(thread, null);

To test the above code, just go to menu "PlugIns - New - Javascript" and paste it in the text window that opens. The push control+j, or choose "Macros - Evaluate Javascript".

A detailed example, in java:

/*  An example plugin to illustrate the automation of GenericDialog
 *  i.e. how to call a function that would normally show a dialog
 *  with options, with those options already filled in automatically.
 *  The process consists in:
 *  1 - Renaming the current thread to "Run$_" + any name.
 *  2 - Setting the Macro options String for that thread.
 *  3 - Calling the function normally, but now the dialog never shows
 *      and its options are automatically filled in from the Macro options.
 *  Notice that:
 *   1 - The text label of the option becomes a key in the table of options.
 *   2 - Options with an underscore in their name will show it in the dialog as
 *   a blank space, but still the option name itself (the key) has the
 *   underscore. This enables options like 'the_width' and 'the_height' to be
 *   different (as opposed to the value set for 'the' being given to both).
 *   3 - Boolean options, a.k.a. checkboxes, are true when present and false
 *   otherwise.
 *   4 - Choices, a.k.a. pulldown menus, need the exact String value desired,
 *   not an index.
 *   5 - If a key is absent in the Macro options string, its default value is
 *   taken. The default value is the one that appears in the dialog when
 *   opened.
 *   6 - Text values are set with single quotes: title='this and that'

import ij.plugin.PlugIn;
import ij.gui.GenericDialog;
import ij.ImagePlus;
import ij.process.*;
import ij.IJ;
import ij.Macro;
import java.awt.Color;

public class Create_image_from_dialog implements PlugIn {

	public void run(String arg) {

		// A - Without automation: the dialog shows
		ImagePlus imp = createImage();
		if (null != imp) imp.show();

		// B - With automation: the dialog never shows:

		Thread thread = Thread.currentThread();

		// ... so we can create many images in a loop, for example.
		for (int i=1; i<=3; i++) {
			// Create a 1024x1024 image, 160bit, with noise added
			// and with the title storing the loop index:
			Macro.setOptions(thread, "title='My new image " + i + "'"
			      + " width=1024 height=1024 type='16-bit' add_noise");
			// Above, notice how we do not set the fill_value key,
			//     so that its default value (zero in this case) is taken.
			ImagePlus imp2 = createImage();

		// Cleanup: remove reference to the Thread and its associated options
		Macro.setOptions(thread, null);

	public ImagePlus createImage() {
		final GenericDialog gd = new GenericDialog("Create image");
		gd.addStringField("title:", "new");
		gd.addNumericField("width:", 512, 0);
		gd.addNumericField("height:", 512, 0);
		final String[] types = new String[]{"8-bit", "16-bit", "32-bit", "RGB"};
		gd.addChoice("type:", types, types[0]);
		gd.addSlider("fill_value:", 0, 255, 0); // min, max, default
		gd.addCheckbox("add_noise", false);
		if (gd.wasCanceled()) return null;

		final String title = gd.getNextString();
		final int width = (int)gd.getNextNumber();
		final int height = (int)gd.getNextNumber();
		final int itype = gd.getNextChoiceIndex();
		final double fill_value = gd.getNextNumber();
		final boolean add_noise = gd.getNextBoolean();

		ImageProcessor ip = null;

		switch (itype) {
			case 0: ip = new ByteProcessor(width, height);  break;
			case 1: ip = new ShortProcessor(width, height); break;
			case 2: ip = new FloatProcessor(width, height); break;
			case 3: ip = new ColorProcessor(width, height); break;

		// Color images are created filled with white by default
		if (3 == itype && 255 != fill_value) {
			// color image
			ip.setColor(new Color((int)fill_value, (int)fill_value, (int)fill_value));
		// non-color images
		else if (0 != fill_value) {

		final ImagePlus imp = new ImagePlus(title, ip);

		if (add_noise) IJ.run(imp, "Add Noise", "");

		return imp;

Other resources

Last updated: 2011-04-28 07:25 Zurich time. Copyright Albert Cardona 2007,2008.