Skip to content

BoneJ plug–ins

Richard Domander edited this page Sep 28, 2018 · 5 revisions

This page explains the "anatomy" of a BoneJ wrapper plug-in with the help of a simple example. Wrapper plug-ins are Scijava Commands, more specifically they extend ContextCommand, because they need the different services available in the ImageJ Context. At least they need an OpService to call ops from imagej-ops. The plug-ins are called "wrappers", because the idea is that they just call various ops, and add a little user interaction and skeletal biology on top.

Here's a minimal example of a typical BoneJ wrapper:

@Plugin(type = Command.class, menuPath = "Plugins>BoneJ>Example")
public class ForegroundCountWrapper<T extends RealType<T> & NativeType<T>> extends ContextCommand
{
    @Parameter(validater = "validateImage")
    private ImgPlus<T> image;

    @Parameter(label = "Invert image")
    private boolean invert;

    @Parameter(type = ItemIO.OUTPUT, label = "BoneJ results")
    private Table<DefaultColumn<Double>, Double> resultsTable;

    @Parameter
    private OpService ops;

    @Override
    public void run() {
	final ImgPlus<BitType> bitImage = Common.toBitTypeImgPlus(ops, image);
        if (invert) {
            bitImage.forEach(BitType::not);
        }
        final IntType voxels = ops.stats().sum(bitImage);
        SharedTable.add(bitImage.name(), "#Foreground voxels", voxels);
        resultsTable = SharedTable.getTable();
    }

    private void validateImage() 
    {
	if (image == null) {
	    cancel(NO_IMAGE_OPEN);
	    return;
	}
	if (!ElementUtil.isColorsBinary(image)) {
	    cancel(NOT_BINARY);
	}
    }
}

This plug-in takes in a binary image, sums the number of foreground voxels in it. Now let's look at the example line by line:

@Plugin(type = Command.class, menuPath = "Plugins>BoneJ>Example")

Without the @Plugin annotation the class won't be recognized as a plug-in, and won't be available when you launch ImageJ. All BoneJ plug-ins are Commands instead of Services etc. As the name implies, the menuPath tells in which menu path the user will find the plug-in.

public class ForegroundCountWrapper<T extends RealType<T> & NativeType<T>> extends ContextCommand

As mentioned earlier, all BoneJ commands all extend the ContextCommand class. There other classes like DynamicCommand and InteractiveCommand, but I haven't been able to implement them successfully (see the backlog for more details). Then there's the type parameter <T extends RealType<T> & NativeType<T>> for the input image - why the command doesn't simply take in a ImgPlus<BitType> or <B extends BooleanType<B>> will be explained later. These types come from ImgLib2, and are recursive, i.e. <T extends Type<T>> so that they can be asked to create copies of themselves with the same type. That is, they have the T createVariable() method.

@Parameter(validater = "validateImage")
private ImgPlus<T> image;

This is the most important @Parameter - the input image. If there's an open image, this parameter will be automatically populated by it. The paramter has a validater callback, a method that'll be called when the parameter is populated to check that it's valid (see below).

@Parameter(label = "Invert image")
private boolean invert;

This parameter controls if the voxel values in the image are inverted before foreground values are counted. Since it can't be automatically populated, a dialog will automatically opened when the plug-in is launched. The string in the label property is shown next the the parameter's widget, which for a boolean will be a check-box.

@Parameter(type = ItemIO.OUTPUT, label = "BoneJ results")
private Table<DefaultColumn<Double>, Double> resultsTable;

This is the only output parameter of the plug-in. By default parameters are inputs. If the table is not null, after the plug-in has finished successfully, it's automatically displayed to the user. How an output parameter is displayed depends on its type.

@Parameter
private OpService ops;

An OpService that will be automatically populated from the context when the plug-in is launched.

@Override
public void run() {
    final ImgPlus<BitType> bitImage = Common.toBitTypeImgPlus(ops, image);

Each Command must implement the run() method. The first line is a call to a BoneJ utility method that uses the OpService to convert the input image into a BitType image, and copies the metadata. The reason the plug-in doesn't just have a ImgPlus<BitType> input parameter is because there's difference between a binary type and binary colour image. For example, the sample image Bat Cochlea Volume opens as a ImgPlus<UnsignedByteType> as it is an 8-bit image. For all intents and purposes, it is a binary image. It has only two colours, but since its elements are bytes, they can have values from 0 to 255. Thus, it's not an image, whose elements are binary, i.e. that can only be true or false, 0 or 1. If you try to call a plug-in with a ImgPlus<BitType> input with a ImgPlus<UnsignedByteType> image, it will crash with a class cast exception. Thus in summary, we'll accept any type of ImgPlus<T>, check that it has binary colours (two different pixel values) and then convert it to a binary type image ImgPlus<BitType>.

if (invert) {
    bitImage.forEach(BitType::not);
} 

If user set the invert check-box true, invert all to voxels in the the BitType image.

final IntType voxels = ops.stats().sum(bitImage);

Use the OpService to call a method from that sums the elements of a BitType image. Because in such an image the foreground elements have value 1, summing them effectively counts the number of foreground voxels.

SharedTable.add(bitImage.name(), "#Foreground voxels", voxels);
resultsTable = SharedTable.getTable();

Add the result to the BoneJ SharedTable and populate the output parameter so that the table will be shown.

private void validateImage() 
{
    if (image == null) {
        cancel(NO_IMAGE_OPEN);
        return;
    }

This is the validater method that's called when image is populated. Even without this if ImageJ would show an error if the image is null, but the default message "An ImgPlus is required, but there is none" is not as informative as "No image open". Calling cancel(String) shows an error, and stops plug-in execution. However you still must call return so that method exists ASAP.

if (!ElementUtil.isColorsBinary(image)) {
    cancel(NOT_BINARY);
}

Calls a BoneJ utility method, that checks that the image has only two colour pixels. If it's not, the plug-in cancels.

Clone this wiki locally