-
Notifications
You must be signed in to change notification settings - Fork 12
BoneJ plug–ins
This page explains the "anatomy" of a BoneJ wrapper plug-in with the help of a simple example. Wrapper plug-ins are Scijava Command
s, 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.