Fundamentals of ImageJ

This tutorial presents the basic concepts and usage of the ImageJ API.

The ImageJ gateway

The first step when working with ImageJ is to get or create an ImageJ gateway. This gateway provides access to ImageJ operations and data structures.

In [1]:
// Behind a firewall? Configure your proxy settings here.
//System.setProperty("http.proxyHost","myproxy.domain")
//System.setProperty("http.proxyPort","8080")

// Load the ImageJ library from the remote Maven repository.
%classpath config resolver scijava.public https://maven.scijava.org/content/groups/public
%classpath add mvn net.imagej imagej 2.0.0-rc-71

// OR: Load ImageJ with Fiji plugins from the remote Maven repository.
//%classpath config resolver scijava.public https://maven.scijava.org/content/groups/public
//%classpath add mvn sc.fiji fiji 2.0.0-pre-10

// OR: Load ImageJ with Fiji plugins from a local Fiji installation.
//%classpath add jar '/Applications/Fiji.app/jars/*'
//%classpath add jar '/Applications/Fiji.app/jars/bio-formats/*'
//%classpath add jar '/Applications/Fiji.app/plugins/*'

// Create an ImageJ gateway.
ij = new net.imagej.ImageJ()
"ImageJ v${ij.getVersion()} is ready to go."
Added new repo: scijava.public
Out[1]:
ImageJ v2.0.0-rc-71 is ready to go.

Services

ImageJ's functionality is divided into services. Each service provides some API methods for performing related tasks.

The gateway provides easy access to this slew of services. Here are some example service calls:

In [2]:
// The plugin service manages the available plugins (see "Plugins" below).
pluginCount = ij.plugin().getIndex().size()
println("There are " + pluginCount + " plugins available.")

// The log service is used for logging messages.
ij.log().warn("Ignoring negative sigma value.")

// The status service is used to report the current status of operations.
// Within a notebook like this, the call does not do anything visible.
ij.status().showStatus("Processing data file 34 of 97...")

// The menu service organizes a menu hierarchy for modules (see "Modules" below).
menuItemCount = ij.menu().getMenu().size()
println("There are " + menuItemCount + " menu items total.")

// The platform service handles platform-specific functionality.
// E.g., it can open a URL in the default web browser for your system:
// ij.platform().open(new URL("https://imagej.net/"))
There are 1562 plugins available.
[WARNING] Ignoring negative sigma value.
There are 413 menu items total.
Out[2]:
null

Comparison with ImageJ 1.x

ImageJ 1.x has a similar concept with the ij.ImageJ class, which is created using new ImageJ() and cached statically as a singleton. This allows the ImageJ instance to be recovered later by calling IJ.getInstance(), and simplifies the API in some ways. However, the assumption that there will only ever be one ImageJ per JVM limits its flexibility, and the fact that ij.ImageJ extends java.awt.Frame makes ImageJ 1.x difficult to use headless or with user interfaces other than Java AWT. Furthermore, ImageJ 1.x is not service-driven, which makes it less extensible; see the SciJava in Detail tutorial notebook for more information.

How to explore the API

One very easy way to explore the API is via an Integrated Development Environment (IDE) such as Eclipse or IntelliJ IDEA. You can use the IDE's autocomplete feature to list all of the available methods of a particular object. E.g., in Eclipse, if you press ctrl+space after a period (.) character on an object, you will see a list of methods which are available:

Unfortunately, here in this notebook software, the autocompletion logic is not (yet!) advanced enough to fully offer such a feature—there is only rudimentary method completion support by pressing tab. Hence, the NotebookService also includes a handy method for inspecting an object's methods. Here is an example which will show the available methods of a List:

In [3]:
myList = ["quick", "brown", "fox"]
ij.notebook().methods(myList)

Try it yourself on the ImageJ service of your choice!

Complete list of built-in services

Here is the complete list of built-in services accessible from the ImageJ gateway:

In [4]:
ij.notebook().methods(ij).findAll{ it.get("returns").endsWith("Service") }

Plugins

ImageJ is built on the SciJava plugin framework. Essentially everything in ImageJ is a plugin.

There are many kinds of plugins, and you can also define your own new kinds. Some of the most central built-in plugin types are:

  • Service – A collection of related functionality. See "Services" above.
  • Command – A routine which can be executed. See "Modules" below.
  • IOPlugin – A plugin for reading and/or writing data to/from external locations. See "Data I/O" below.

Complete list of built-in plugin types

The PluginService provides access to meta-information about available plugins. Here is a count of plugins organized by kind:

In [5]:
// Gather a count of each kind of plugin.
kindCounts = [:]
ij.plugin().getPlugins().forEach{plugin ->
  kind = plugin.getPluginType().getName()
  // HACK: Report all Op plugin subtypes as simply "Op", to avoid overwhelming the list.
  if (kind.startsWith('net.imagej.ops.Ops') || kind.startsWith('net.imagej.ops.features'))
    kind = 'net.imagej.ops.Op'
  kindCounts.put(kind, kindCounts.getOrDefault(kind, 0) + 1)
}
// Build a table which reports this information nicely.
kindCounts.keySet().sort().stream().map{kind -> [
    "package": (kind =~ /^.*\./)[0][0..-2],
    "class": (kind =~ /\.[^\.]*$/)[0][1..-1],
    "count": kindCounts.get(kind)
]}.collect()

Data I/O

The I/O service provides a generalized API for reading and writing from external sources.

Loading data

Here is an example invocation which opens an image from a remote URL:

In [6]:
snowflake = ij.io().open("https://imagej.net/images/snowflake.gif")
[INFO] Verifying GIF format
[INFO] Reading dimensions
[INFO] Reading data blocks
Out[6]:

Saving data

Similarly, you can use the I/O service to save data to an external destination:

In [7]:
destPath = System.getProperty("user.home") + "/Desktop/snowflake.png"
ij.io().save(snowflake, destPath)
"Saved to '$destPath'; length = ${new File(destPath).length()}"
Out[7]:
Saved to '/Users/curtis/Desktop/snowflake.png'; length = 2965

Bio-Formats

Out of the box, ImageJ does not include the Bio-Formats plugin for reading and writing life sciences images.

One easy way to enable it is to use Fiji, which comes bundled with Bio-Formats and many other plugins useful in the life sciences. To initialize an ImageJ gateway that includes the Fiji plugins, replace the first cell of this notebook with the following code:

%classpath config resolver scijava.public https://maven.scijava.org/content/groups/public
%classpath add mvn sc.fiji fiji 2.0.0-pre-9
ij = new net.imagej.ImageJ()

The ij.io().open(...) command should then be capable of opening any Bio-Formats-supported format.

Modules

A SciJava module is an executable snippet of code with typed inputs and outputs. You can think of them as subroutines, also called functions or methods depending on the programming language.

The two most common flavors of module are commands and scripts. A Command is a plugin written in Java, whereas a script is written in one of the many available SciJava scripting languages. Most users who need to code a module will use a script, because they are simpler to write.

Scripts

Here is a sample module, written as a script:

#@input String name
#@output String greeting
#@output int length
greeting = "Hello, " + name + "!"
length = name.length()

You can write scripts such as the above e.g. into ImageJ's Script Editor, and then run them directly. You can also run them in notebooks using ij.script().run(...).

ImageJ (or more precisely: the SciJava script framework) will harvest the input parameters—in this case, the name string—from the user in a way appropriate to the execution context. For example, when running from the ImageJ user interface, a Swing) dialog box will appear asking the user to type in a name. But when running from the command line, the input values can be passed as arguments to the command line invocation; see the Scripting Headless page for details. The module outputs—in this case, greeting and length—will then be displayed to the user in a manner appropriate to the situation: when running graphically, one or more windows will typically pop up; when running from the CLI, output values will be logged to the standard output stream.

The Script Parameters page provides more information about using these parameters in your scripts.

Now let's run this script here in the notebook, and see what happens:

In [8]:
// Write the script as a string constant, so it can be passed to the script service.
script = """
#@input String name
#@output String greeting
#@output int length
greeting = "Hello, " + name + "!"
length = name.length()
"""

// Run the script, passing input key/value pairs using a map.
inputs = ["name": System.getProperty("user.name")]
module = ij.script().run("greeting.groovy", script, true, inputs).get();

// Extract the module output values.
["greeting" : module.getOutput("greeting"),
 "length"   : module.getOutput("length")]

Commands

Here is the same example module, but written as a command:

In [9]:
// NB: While this is a Groovy cell, the class definition here is valid Java.

import org.scijava.ItemIO;
import org.scijava.command.Command;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;

@Plugin(type = Command.class)
public class Hello implements Command {
  
  @Parameter
  private String name;
  
  @Parameter(type = ItemIO.OUTPUT)
  private String greeting;
  
  @Parameter(type = ItemIO.OUTPUT)
  private int length;
  
  @Override
  public void run() {
    greeting = "Hello, " + name + "!";
    length = name.length();
  }
}

// Save a reference to the class, for use in the next cell.
greetingCommand = Hello.class
Out[9]:
class Hello

As you can see, commands are more verbose. But there are advantages to using Java, such as performance, reusability, type safety, and powerful IDE features like autocompletion.

Let's run this command using the command service:

In [10]:
// Run the command, passing input key/value pairs using a map.
inputs = ["name": "John Jacob Jingleheimer Schmidt"]
module = ij.command().run(greetingCommand, true, inputs).get()

// Extract the module output values.
["greeting" : module.getOutput("greeting"),
 "length"   : module.getOutput("length")]

Complete list of built-in modules

The ModuleService provides access to meta-information about available modules. Here is a list of all modules built in to ImageJ:

In [11]:
modules = []
ij.module().getModules().stream().map{module -> [
  "id": module.getIdentifier(),
  "location": module.getLocation().replaceAll('.*/(.*\\.jar)$', '$1'),
  "version": module.getVersion()
]}.collect()

Further reading

For more information about how to write commands and scripts, see the "Extending ImageJ" tutorial notebooks.

Ops

The true meat of ImageJ is ImageJ Ops, a library for reusable image processing. An Op plugin is a form of Command, and therefore a module.

Please proceed to the ImageJ Ops tutorial notebook for a primer with lots of examples!