Interesting library: BoofCV

@ctrueden @Nico_Stuurman @fjug @imagejan

Dear Curtis,

Nico pointed me to this library: http://boofcv.org/

I did not try it myself, but the code syntax (java code, example modified from Nico) looks very interesting in a sense that it is almost identical to our Ops:

GrayF32 divided = new GrayF32(image.width, image.height);
GPixelMath.divide(image, image2, divided);
GrayF32 blurred = new GrayF32(image.width, image.height);
BlurImageOps.gaussian(divided, blurred, 3, -1, null);

Just was curious whether that was on your radar.

Cheers from Ostrava,
Christian

2 Likes

@Christian_Tischer Sorry for the long delay in reply. There was also a topic on the ImageJ mailing list discussing BoofCV in March 2019.

Back then I wrote an ImageJ script that uses BoofCV to do a seeded watershed:

boofcv-seeded-watershed.groovy
#@dependency(group="org.boofcv", module="boofcv-core", version="0.33")
#@dependency(group="org.boofcv", module="boofcv-swing", version="0.32")

#@both ImagePlus imp
#@output ImagePlus (label="Watersheds") watersheds
#@output ImagePlus (label="Regions") regions
#@output ImagePlus (label="Seeds") seeds

import boofcv.alg.filter.binary.BinaryImageOps
import boofcv.alg.filter.binary.ThresholdImageOps
import boofcv.alg.misc.ImageStatistics
import boofcv.alg.segmentation.watershed.WatershedVincentSoille1991
import boofcv.factory.segmentation.FactorySegmentationAlg
import boofcv.gui.binary.VisualizeBinaryData
import boofcv.gui.feature.VisualizeRegions
import boofcv.io.image.ConvertBufferedImage
import boofcv.struct.ConnectRule
import boofcv.struct.image.GrayS32
import boofcv.struct.image.GrayU8

// Call BoofCV to perform an in-place seeded watershed segmentation.
// Adapted from:
// https://github.com/lessthanoptimal/BoofCV/blob/v0.33.1/examples/src/main/java/boofcv/examples/segmentation/ExampleWatershedWithSeeds.java
// - Accepts a BufferedImage as input and mutates it.
// - Returns [regions, seeds] as a two-element list.
def seededWatershed = { image ->
		input = ConvertBufferedImage.convertFromSingle(image, null, GrayU8.class)

		// declare working data
		binary = new GrayU8(input.width,input.height)
		label = new GrayS32(input.width,input.height)

		// Try using the mean pixel value to create a binary image then erode it to separate the particles from
		// each other
		mean = ImageStatistics.mean(input)
		ThresholdImageOps.threshold(input, binary, (int) mean, true)
		filtered = BinaryImageOps.erode8(binary, 2, null)
		numRegions = BinaryImageOps.contour(filtered, ConnectRule.EIGHT, label).size() + 1
		// +1 to regions because contour only counts blobs and not the background

		// The labeled image can be used as is.  A precondition for seeded watershed is that all seeds have an
		// ID > 0.  Luckily, a value of 0 was used for background pixels in the contour algorithm.
		watershed = FactorySegmentationAlg.watershed(ConnectRule.FOUR)

		watershed.process(input,label)

		output = watershed.getOutput()

		outLabeled = VisualizeBinaryData.renderLabeledBG(label, numRegions, null)
		VisualizeRegions.watersheds(output,image,1)

		// Removing the watersheds and update the region count
		// NOTE: watershed.getTotalRegions() does not return correct results if seeds are used!
		watershed.removeWatersheds()
		numRegions--
		outRegions = VisualizeRegions.regions(output,numRegions,null)

		return [outRegions, outLabeled]
}

result = seededWatershed(imp.getImage())
regions = new ij.ImagePlus("Regions", result[0])
seeds = new ij.ImagePlus("Seeds", result[1])

Original gist here.

As I wrote to the mailing list then:

BoofCV works with java.awt.image.BufferedImage, so when writing scripts using BoofCV, transformations will be necessary and many of ImageJ’s more complex scientific multidimensional data structures will not be usable directly—you’ll probably have to go plane by plane.

Hi @Christian_Tischer and @ctrueden,

I explored boofcv and bridged to clij at some point.

BoofCV is awesome, also super fast but unfortunately limited to 2D imaging data.

Cheers,
Robert

3 Likes