Scripting the Trainable Weka Segmentation

Revision as of 12:49, 17 April 2013 by Iarganda (talk | contribs) (corrected text to link to Trainable Segmentation package)

Scripting is one of the reasons Fiji is so powerful, and the Trainable Segmentation library (that includes the Advanced Weka Segmentation plugin methods) is one of the best examples for scriptable Fiji components.

Getting started

The first thing you need to start scripting the is to know which methods you can use. For that, please have a look at the API of the Trainable Segmentation library, which is available here.

Define your own features

Here is a little Javascript that makes two features from the Clown example and uses them to train a classifier (see the inline comments for more information):

importClass(Packages.ij.IJ);
importClass(Packages.ij.ImagePlus);
importClass(Packages.ij.ImageStack);
importClass(Packages.ij.gui.PolygonRoi);
importClass(Packages.ij.plugin.Duplicator);
importClass(Packages.ij.process.FloatPolygon);
importClass(Packages.ij.process.StackConverter);
importClass(Packages.trainableSegmentation.FeatureStack);
importClass(Packages.trainableSegmentation.FeatureStackArray);
importClass(Packages.trainableSegmentation.WekaSegmentation);

var image = IJ.openImage(System.getProperty("ij.dir") + "/samples/clown.jpg");
if (image.getStackSize() > 1)
        new StackConverter(image).convertToGray32();
else
        image.setProcessor(image.getProcessor().convertToFloat());

var duplicator = new Duplicator();

// process the image into different stacks, one per feature:

var smoothed = duplicator.run(image);
IJ.run(smoothed, "Gaussian Blur...", "radius=20");

var medianed = duplicator.run(image);
IJ.run(medianed, "Median...", "radius=10");

// add new feature here (1/2)

// the FeatureStackArray contains a FeatureStack for every slice in our original image
var featuresArray = new FeatureStackArray(image.getStackSize(), 1, 16, false,
        1, 19, null);

// turn the list of stacks into FeatureStack instances, one per original
// slice. Each FeatureStack contains exactly one slice per feature.
for (var slice = 1; slice <= image.getStackSize(); slice++) {
        var stack = new ImageStack(image.getWidth(), image.getHeight());
        stack.addSlice("smoothed", smoothed.getStack().getProcessor(slice));
        stack.addSlice("medianed", medianed.getStack().getProcessor(slice));

        // add new feature here (2/2) and do not forget to add it with a
        // unique slice label!

        var featuresImage = new ImagePlus("slice " + slice, stack);
        var features = new FeatureStack(featuresImage);
        featuresArray.set(features, slice - 1);
        featuresArray.setEnabledFeatures(features.getEnabledFeatures());
}

var wekaSegmentation = new WekaSegmentation(image);
wekaSegmentation.setFeatureStackArray(featuresArray);

// set examples for class 1 (= foreground) and 0 (= background))
function addExample(classNum, slice, xArray, yArray) {
        var polygon = new FloatPolygon(xArray, yArray);
        var roi = new PolygonRoi(polygon, PolygonRoi.FREELINE);
        IJ.log("roi: " + roi);
        wekaSegmentation.addExample(classNum, roi, slice);
}

/*
 * generate these with the macro:

        getSelectionCoordinates(x, y);

        print('['); Array.print(x); print('],");
        print('['); Array.print(y); print(']");
 */
addExample(1, 1,
        [ 82,85,85,86,87,87,87,88,88,88,88,88,88,88,88,86,86,84,83,82,81,
          80,80,78,76,75,74,74,73,72,71,70,70,68,65,63,62,60,58,57,55,55,
          54,53,51,50,49,49,49,51,52,53,54,55,55,56,56 ],
        [ 141,137,136,134,133,132,130,129,128,127,126,125,124,123,122,121,
          120,119,118,118,116,116,115,115,114,114,113,112,111,111,111,111,
          110,110,110,110,111,112,113,114,114,115,116,117,118,119,119,120,
          121,123,125,126,128,128,129,129,130 ]
);
addExample(0, 1,
        [ 167,165,163,161,158,157,157,157,157,157,157,157,158 ],
        [ 30,29,29,29,29,29,28,26,25,24,23,22,21 ]
);

// train classifier
if (!wekaSegmentation.trainClassifier())
        throw new RuntimeException("Uh oh! No training today.");

output = wekaSegmentation.applyClassifier(image);
output.show();

Define binary labels programmatically

Here is a simple script in Beanshell doing the following:

  1. It takes one image (2D or stack) as training input image and a binary image as the corresponding labels.
  2. Train a classifier (in this case a random forest, but it can be changed) based on randomly selected pixels of the training image. The number of samples (pixels to use for training) is also a parameter, and it will be the same for each class.
  3. Apply the trained classifier to a test image (2D or stack).
import ij.*;
import ij.process.*;
import trainableSegmentation.*;
import hr.irb.fastRandomForest.*;

// training input image (it could be a stack or a single 2D image)
image = IJ.openImage("train-volume.tif");
// corresponding binary labels
labels = IJ.openImage("train-labels.tif");

// create Weka segmentator
seg = new WekaSegmentation(image);

// number of samples to use per slice
nSamplesToUse = 2000;


// Classifier
// In this case we use a Fast Random Forest
rf = new FastRandomForest();
// Number of trees in the forest
rf.setNumTrees(100);		
// Number of features per tree
rf.setNumFeatures(0);
// Seed
rf.setSeed( (new java.util.Random()).nextInt() );

// set classifier
seg.setClassifier(rf);

// Parameters 
// membrane patch size
seg.setMembranePatchSize(11);
// maximum radius of the filters
seg.setMaximumSigma(16.0f);

// Selected attributes
enableFeatures = new boolean[]{
			true, 	/* Gaussian_blur */
			true, 	/* Sobel_filter */
			true, 	/* Hessian */
			true, 	/* Difference_of_gaussians */
			true, 	/* Membrane_projections */
			false, 	/* Variance */
			false, 	/* Mean */
			false, 	/* Minimum */
			false, 	/* Maximum */
			false, 	/* Median */
			false,	/* Anisotropic_diffusion */
			false, 	/* Bilateral */
			false, 	/* Lipschitz */
			false, 	/* Kuwahara */
			false,	/* Gabor */
			false, 	/* Derivatives */
			false, 	/* Laplacian */
			false,	/* Structure */
			false,	/* Entropy */
			false	/* Neighbors */
};

// Enable features in the segmentator
seg.setEnabledFeatures( enableFeatures );

// Add balanced labels
seg.addRandomBalancedBinaryData(image, labels, "class 2", "class 1", nSamplesToUse);

// Train classifier
seg.trainClassifier();


// Un-comment next 7 lines to segment and display training image
/*
// Apply classifier to current image
seg.applyClassifier( true );

// Display classified image
prob = seg.getClassifiedImage();
prob.setTitle( "Probability maps of train image" );
prob.show();
*/

// Open test image
image = IJ.openImage("test-volume.tif");

// Apply trained classifier to test image and get probabilities
prob = seg.applyClassifier(image, 0, true );

// Display results
prob.setTitle( "Probability maps of test image" );
prob.show();

image.show();

IJ.log("---");