Dilate and erode label images in CLIJ

Hello @haesleinhuepf, is there a way to dilate a label image with CLIJ ?

1 Like

Hi @NicoKiaru,

by chance, yes! The operation you are looking for is a local maximum filter which does not overwrite other labels and only background (zero) a.k.a. onlyzeroOverwriteMaximumBox.

If you want to do this over longer ranges, feel free to give extendLabelsWithMaximumRadius (CLIJx) a try.

Better name suggestions for the next release are welcome :wink:

Cheers,
Robert

2 Likes

Ahh, but why are there 3 or 4 arguments with clij2 ?

 default boolean onlyzeroOverwriteMaximumBox(ClearCLImageInterface arg1, ClearCLImageInterface arg2, ClearCLImageInterface arg3) {
    

I’m using this dependency:

<dependency>
			<groupId>net.haesleinhuepf</groupId>
			<artifactId>clij2_</artifactId>
			<version>2.2.0.0</version>
</dependency>
1 Like

Hey @NicoKiaru,

good catch! That’s obviously an API-bug. I will fix in the next minor release. In the meantime, please hand over an 1x1x1 image as second parameter.

Maybe irrelevant side note: This “flag” image is used internally to communicate to the outside that something changed while applying the filter, allowing to run it in a loop and stopping the loop if the operation converges. The fourth parameter allows to call the operation subsequently wiht improved performance by reusing its opencl-kernel. These special cases for CLIJs API shouldn’t be expose to the outside. They are just necessary, to implement funcitons such as connected component labelling and voronoi label maps with optimal performance. See code here and here.

Cheers,
Robert

1 Like

@haesleinhuepf @NicoKiaru
I am trying to get this to work in Java, but I am stuck:

		ImagePlus nucleiLabelMask = segmenter.segment( nuclei );
		CLIJ2 clij2 = CLIJ2.getInstance();
		ClearCLBuffer flag = clij2.create(1,1,1);
		clij2.onlyzeroOverwriteMaximumBox( ???, flag, ??? );
  1. I want to dilate the nucleiLabelMask
  2. Does that make sense so far?
  3. What do I have to put where the ??? are?
  4. Thanks :slightly_smiling_face:
1 Like

Hey Tischi @Christian_Tischer,

welcome to the clijiverse :slight_smile:

Answering your questions:

I’m sure you know, for a non-binary dilation, you can use a maxmimum filter (but the method you tried is just a special maximum filter, as explaind above). Please note that the API reference on the website provides example code for specific languages:

Yes, absolutely, you’re almost there!

Typically, ClearCLBuffer is the image type used in CLIJ. There are also ClearCLImages. Both implement the ClearCLImageInterface which is often specified as type of the parameters. In routine, everyone uses ClearCLBuffers and one day I’ll show you why ClearCLImages bring additional speedup in specific situations :wink:

You are most welcome :slight_smile: Let me guide you a bit:

In order to push an ImagePlus to GPU memory, you need to call clijs push() method:

ClearCLBuffer gpu_input = clij2.push(nucleiLabelMask);

To reserve memory for the result image, you can call clijs create() method. If you hand over an image, it will create a new image of the same type and size:

ClearCLBuffer gpu_output = clij2.create(gpu_input);

Then, you can call the method of interest:

clij2.onlyzeroOverwriteMaximumBox(gpu_input, flag, gpu_output);

And in order to get it back as ImagePlus you, pull() it from the GPU:

ImagePlus result = clij2.pull(gpu_output);

Don’t forget to cleanup memory in the GPU by closing the images you don’t need anymore:

gpu_input.close();
gpu_output.close();
flag.close();

Or the lazy way:

clij2.clear(); // don't do this inside subroutines.

Thus, your code should look like this:

// init GPU
CLIJ2 clij2 = CLIJ2.getInstance("RTX");

// get image and push it to GPU
ImagePlus nucleiLabelMask = segmenter.segment( nuclei );
ClearCLBuffer gpu_input = clij2.push(nucleiLabelMask);

// allocate more memory
ClearCLBuffer flag = clij2.create(1,1,1);
ClearCLBuffer gpu_output = clij2.create(gpu_input);

// process the image
clij2.onlyzeroOverwriteMaximumBox(gpu_input, flag, gpu_output);

// get result back
ImagePlus result = clij2.pull(gpu_output);
result.show();

// clean up
gpu_input.close();
gpu_output.close();
flag.close();

(Disclosure: There may be typos. I’m writing this in the browser).

Online examples

Furthermore, you may want to check out the Java/Jython/Groovy basics tutorial which explains how to call clij methods. Furhtermore, I’m sure you will love the Jython and/or Java example folders, which contains similar stuff, e.g. for binary post-processing:

Last but not least: Perfomance gain comes from calling multiple methods in a row without pushing and pulling in between.

Let me know if you need anything else :wink:

Cheers,
Robert

2 Likes

Addendum: push() takes also RandomAccessibleIntervals as parameter and pullRAI() gives you those back. Just in case you want to integrate it with imglib2 magic :wink:

Cheers,
Robert

1 Like

Thanks a lot!
I even tried to look for java code examples before asking, but could not find it:

Am I overlooking it?

1 Like

No, that is related to the bug Nico reported. The documentation generator can’t find the right method with the right interface and thus, doesn’t offer Java examples. I’ll fix that asap.

1 Like

Is that call expensive? Should I do it only once in my code or is it ok to do it more often?

CLIJ2 clij2 = CLIJ2.getInstance();
1 Like

It’s super cheap. Do it as often as you like :wink:

Thanks :slight_smile:
…and if I want to dilate by more than one pixel I need to create a ClearCLKernel?

1 Like

I hope you will make your own kernels one day, but for now: An iterative approach to this exists as extendLabelsWithMaximumRadius in CLIJx because also others asked for it.

CLIJx clijx = CLIJx.getInstance();
int radius = 5;
clijx.extendLabelsWithMaximumRadius(label_in, label_out, radius);

:slight_smile:

1 Like

Thanks so much for the excellent help!

In case some people (e.g., @NicoKiaru) want to reuse it, here is my code:

/**
 * 		<dependency>
 * 			<groupId>net.haesleinhuepf</groupId>
 * 			<artifactId>clijx_</artifactId>
 * 			<version>0.30.0.2</version>
 * 		</dependency>
 */
public class LabelMaskCLIJXExtender
{
	public ImagePlus extendLabelMask( ImagePlus labelMask, int radius )
	{
		// init GPU
		CLIJx clijx = CLIJx.getInstance();

		// get image and push it to GPU
		ClearCLBuffer gpu_input = clijx.push(labelMask);

		// allocate output memory
		ClearCLBuffer gpu_output = clijx.create(gpu_input);

		// process the image
		clijx.extendLabelsWithMaximumRadius(gpu_input, gpu_output, radius );

		// get result back
		ImagePlus result = clijx.pull(gpu_output);

		// clean up
		gpu_input.close();
		gpu_output.close();
		
		return result;
	}
}
2 Likes

Awesome, thanks for sharing @Christian_Tischer. Is it faster than a pure ImageJ-implementation? Did you measure by chance?

I would measure it if I knew one :wink:

1 Like

Hi Robert,

Just a related question: I have used extendLabelsWithMaximumRadius() a bunch of times to expand labels, and this works really great (and fast).
Now I wanted to shrink the labels, and navily I put a negative radius (in the spirit of the old ImageJ command ‘Edit -> Selection -> Enlarge’. Unfortunately that doesn’t do anything. I also cannot find a function for it in the CLIJ2 reference docs.
Is there another solution? Or has nobody ever asked this?

Thanks!
Bram

2 Likes

Oh, I guess Ext.CLIJ2_minimum2DBox() does the trick, right? :upside_down_face:

Hey Bram @bramvdbroek,

I’m not sure. I think it’s not exactly what you want. Imagine there are two labels with values 5 and 4 touching. If you call a minimum-filter, label 4 will increase in size where they touch while label 5 descreases.

I’ve done something similar in the tribolium morphometry tutorial: You binarize the labelmap, shrink it using binary erosion (or a minimum filter) and then mask the label image with the shrinked binary image:

Ext.CLIJ2_threshold(flip, flap, 1);
for (i = 0; i < number_of_erosions; i++) {
	Ext.CLIJ2_erodeBox(flap, flop);
	Ext.CLIJ2_erodeBox(flop, flap);
}
Ext.CLIJ2_mask(flip, flap, labels);

Let me know if this does the job!

Cheers,
Robert

1 Like

Thanks for the quick answer @haesleinhuepf!

Ah yes, you are right about the touching labels. However, the method you’re proposing also suffers from this, though the results differ slightly. The images below are, from left to right: original labelmap, minimum-filtered result, erode-and-mask result:

image image image
(The values of the touching labels are 56,55 and 49. Here’s the original labelmap image: labelmap_crop.tif (39.0 KB) )

I guess we need a function like onlyselfOverwriteMinimumBox() :slight_smile: Probably more complicated than thinking of the name, isn’t it?

Best regards,
Bram