CLIJ_label map to ROI fast version

With the walkers, I don’t know how it works, but can each walker have a different or unique intensity value?

Thanks for this Robert.
The visualise outlines is extremely helpful… makes a big difference when fine tuning parameters…

1 Like

Nice workaround @pr4deepr, even though some issues remain with non-round labeling.
Amazing to see that the magic wand is soo much quicker than the current method, where converting threshold to selection is the actual slow step. (@haesleinhuepf This is also carried out on the CPU, right?)

I tested it on an image of 3000x3000 pixels with 1104 ROIs. Processing is >100-fold faster.
However, the new workaround still misses 9 out of 1104 (semi-roundish) ROIs in this case.

Ah, found it: your line for(j=x1[i];j<x_b;j+=2) should be for(j=x1[i];j<x2[i];j+=2).
Is there a specific reason why you step with 2 pixels?
Also, doWand(j, y_temp); can be moved inside the if(intensity>0 && intensity==identifier[i]) {... statement., to save some extra time.

To further speed up things, after the centroid, but before the real brute force search method, one could first look for nonzero pixels in a spiral fashion, e.g. like these:
image image
I created the first spiral with:

newImage("spiral", "16-bit black", 200, 200, 1);
getDimensions(width, height, channels, slices, frames);

pitch = 1;
omega = 15 * (PI/180);	//Angle, converted to radians

x = 0; y = 0; i = 0;
while (x <= width/2 || y <= height/2) {
	x = i*pitch*omega*sin(omega*i);
	y = i*pitch*omega*cos(omega*i);
	setPixel(x + width/2, y + height/2, i);
	i++;
}
resetMinAndMax;
run("Fire");

And the second (Archimedean) spiral using:

newImage("spiral", "16-bit black", 200, 200, 1);
getDimensions(width, height, channels, slices, frames);
size = 100;
pitch = 4;
angle = 0; r = 0; i = 0;

while(r <= width/2 || r <= height/2) {
    r = sqrt(i)*pitch;
    angle += atan(1/r);
    x = (r)*cos(angle*pitch);
    y = (r)*sin(angle*pitch);
    setPixel(x + width/2, y + height/2, i);
    i++;
}
resetMinAndMax;
run("Fire");

Best regards,
Bram

3 Likes

Thanks for the feedback and picking up my mistake @bramvdbroek . Appreciate it as its a learning experience for me.

That was just for speed really. My idea was to search every second pixel than every pixel. Its a bit arbitrary; could be higher if needed.

Good point!

Just so I understand this, this search strategy is for within the bounding box, right? The idea is that this search is more efficient than a brute-force search every pixel in bounding box, thus reducing time taken to find a nonzero pixel within the box?
Any preference over which search method?
Thanks for including the code…

BTW, I found that @romainGuiet has included the doWand option for label to ROI in his plugin LaRoMe, specifically here.

2 Likes

Don’t know if my 2 cents are helpful here and if that is implementable in CLIJ2 and makes sense for GPU processing. So,…
Some time ago, I was using @pr4deepr’s method with the centroid as well until I came across the problem of a center outside. The solution in a standard ImageJ Macro is to use the X_Start and Y_Start points for the wand selection, since those are always part of the object, independent of its shape.
That works perfectly. I just don’t know if CLIJ2 is reading those start coordinate in the same way ImageJ’s “record start” does in the Analyze Particle… function. Having the X-Start and Y-Start is generally a good backup to have an inside point at hand.
I can further elaborate on that if needed.

2 Likes

Thanks @biovoxxel
Had no idea about this.
Found this documentation:

The Record Starts option allows plugins and macros to recreate particle outlines using the doWand(x,y) function. The CircularParticles macro demonstrates how to use this feature.

https://imagej.nih.gov/ij/docs/menus/analyze.html

In the example macro above, if you run this command (with display results ticked):

run(“Analyze Particles…”, “minimum=1 maximum=999999 display clear record”);

It will show results table with these options:
image

where XStart and YStart only appear if “Record starts” is ticked in “Analyze Particles”. These correspond

I had a look and could not find this option in CLIJ. @haesleinhuepf , is there a similar functionality? If not, is it easy to add this (when you have time)? Based on what @biovoxxel is saying, then it wouldn’t matter what shape the object is… you get the XStart and YStart and run doWand on each x,y value.

Pradeep

2 Likes

Something like this doesn’t exist yet in CLIJ. Does anybody know how XStart and YStart are mathematically defined? Side note: This will only work for connected labels, but not for labels in general as mentioned above.

CLIJ in very general hardly supports ImageJ ROIs. I prefer working with label images because these are platform independent (in python for example) and also work in 3D (and in napari for example :wink: ). So better support for ROIs is not on the roadmap.

However, I’m happy to support anybody who plans to work on it. Just posting the link to the code again:

And a link to the clij plugin template:

And a link to the developer documentation:
https://clij.github.io/clij2-docs/development

Let me know how I can help.

Cheers,
Robert

2 Likes

That is true and it will not work in a 3D case (at least not in the sense of the original idea

3 Likes

Btw, the ImageJ 3D Suite by @ThomasBoudier can turn label images into ROIs as well. Did anybody try how fast it performs?

1 Like

As far as I tested that, it was pretty fast. However, I think it depends strongly on the amount of labels and stack thickness.

Here a small scale testing:

run("Particles");
part = getTitle();
run("Invert LUT");

for (i = 0; i < 10; i++) {
	run("Duplicate...", " ");
}
run("Images to Stack", "name=Stack title=particles use");
stack = getTitle();

run("3D Manager");
selectWindow(stack);
start = getTime();
Ext.Manager3D_Segment(128, 255);
afterLabel = getTime();
Ext.Manager3D_AddImage();
afterRoiCreation = getTime();
labelingTime = afterLabel - start;
roiCreationTime = afterRoiCreation - afterLabel;

print("labeling time = " + (afterLabel - start) + " ms");
print("Roi from label time = " + (afterRoiCreation - afterLabel) + " ms");

For me it took 600ms to get 5097 ROIs from the labels created on a 10 slice stack, while in a 30 slice stack with the same number of objects it took already 2220ms. For 50 slices it was 3325ms.
Looks pretty much linear to me following the slice number. Might be similar for object numbers (?)

Still a pretty short time for a coffee break :wink:

3 Likes

Hi Pradeep @pr4deepr and others,

Indeed, I thought of the spiral probing as intermediate search possibility within the ROI bounds, in case the centroid pixel is zero. If no pixel belonging to the correct label is found after doing the spiral you could still probe every pixel in the bounding box.
I had fun making a macro for the spirals yesterday, but didn’t get to implement it in your code.
Anyway, thanks to Jan’s @biovoxxel suggestion using X_start and Y_start that may not be necessary any more.

The ImageJ 3D Suite option indeed seems to be a great and fast choice as well (although it sends the ROIs to the 3D Manager of course).

Generally, Robert @haesleinhuepf has me convinced that working with label maps is harder, better, faster and stronger than working with ROIs. Until recently I was missing some visualization functionality, but that has at least partially been solved, see for instance this tutorial macro to generate outlines and ROI numbers.

3 Likes

Hi,

Just a quick note about 3D Manager and ROIs :

  • within the 3D Suite, we distinguish 3D objects that are lists of voxels (and can be seen as 3D ROIs)
  • the 3D Manager (or any 3D analysis plugin within the suite) will convert a 3D label image to a list of 3D objects, and this is quite fast. Plus the 3D boundaries are also computed.
  • within the 3D Manager and the 3D Draw Roi plugin, actually the selected objects are converted into a mask then to a list of 2D boundaries using ImageJ function Mask2Roi, this may not be optimal.

Best,

Thomas

2 Likes

Thanks for the extensive developer documentation. For a non-JAVA person, it is starting to make sense…
It may not be what you are after, but can we use groovy to develop plugins?

1 Like

Good question! Let me forward this to our groovy expert @imagejan : Does the scijava-plugin mechanism work with groovy classes? :upside_down_face:

If doing that with groovy is not possible, you may find this how-to-make-fiji-plugins-guide helpful:

1 Like

Wow, thanks for this. Super helpful

1 Like

Then, you may also enjoy this mini tutorial:
https://haesleinhuepf.github.io/run_jython_scripts_from_ide/

:upside_down_face:

1 Like

gaah!!!
How have I not seen these before…!!
Thanks again!

1 Like

Just posting the complete code using an Archimidean spiral search as recommended:

	//check this post: https://forum.image.sc/t/clij-label-map-to-roi-fast-version/51356
	//faster method for converting labelmap to ROI in FIJI
	//uses doWand function and CLIJ for this.
	//uses CLIJ to get the centroids and bounding boxes for each label
	//use doWand at centroid of the label. If a selection is made, add to ROI Manager
	//if not, it could be a non-circular object as centroids do not lie within the label
	//uses (Archimedean) spiral search within the bounding box to get a selection and then add to ROI Manager
	
	roiManager("reset");
	label_image=getTitle();
	run("CLIJ2 Macro Extensions", "cl_device=");
	Ext.CLIJ2_push(label_image);
	//reindex the labels to make labels sequential
	Ext.CLIJ2_closeIndexGapsInLabelMap(label_image, reindex);
		//statistics of labelled pixels
	Ext.CLIJ2_statisticsOfLabelledPixels(reindex, reindex);
	Ext.CLIJ2_release(label_image);
	Ext.CLIJ2_pull(reindex);
	Ext.CLIJ2_clear();


	//get centroid of each label
	selectWindow("Results");
	x=Table.getColumn("CENTROID_X");
	y=Table.getColumn("CENTROID_Y");

	//getting the identifiers as the values correspond to the label values
	identifier=Table.getColumn("IDENTIFIER");
	
	x1=Table.getColumn("BOUNDING_BOX_X");
	y1=Table.getColumn("BOUNDING_BOX_Y");
	x2=Table.getColumn("BOUNDING_BOX_END_X");
	y2=Table.getColumn("BOUNDING_BOX_END_Y");

	//use wand tool to create selection at each label centroid and add the selection to ROI manager
	//will not add it if there is no selection or if the background is somehow selected
	selectWindow(reindex);
	for(i=0;i<x.length;i++)
	{
		//use wand tool; quicker than the threshold and selection method
		doWand(x[i], y[i]);	
		intensity=getValue(x[i], y[i]);
		//if there is a selection and if intensity >0 (not background), add ROI
		if(selectionType()>0 && intensity>0) { roiManager("add"); }
		//if there is no intensity value at the centroid, its probably coz the object is not circular
		// and centroid is not in the object
		else{
			//get the width of the bounding box
			x_b=x2[i]-x1[i];
			//get the height of the bounding box
			y_b=y2[i]-y1[i];
			//get y coordinate
			//y_temp=y1[i];
			
			//parameters for  (Archimedean) spiral 
			pitch = 4;
			angle = 0; 
			r = 0; 
			a=0;
			//https://forum.image.sc/t/clij-label-map-to-roi-fast-version/51356/11
			//spiral search instead brute force search of every pixel
			while(r <= x_b/2 || r <= y_b/2) 
			{
			    r = sqrt(a)*pitch;
			    angle += atan(1/r);
			    x_spiral = (r)*cos(angle*pitch);
			    y_spiral = (r)*sin(angle*pitch);
			    intensity=getValue(x[i] + x_spiral, y[i] + y_spiral);
			    a++;

			    if(intensity>0 && intensity==identifier[i])
				{
					doWand(x[i] + x_spiral, y[i] + y_spiral);
					roiManager("add");
					r = x_b+1000; //escape "while" condition as the label has been found
				}
			}
			if(r!=x_b+1000) print("search not successful for "+i);  //not the most elegant way to do this

		}
	}
close("Results");

3 Likes

Hi,

I found another fast alternative: the PTBIOP Update site (managed by @oburri and @romainGuiet) has a function Label image to ROIs.
Just tried it on a 2048 x 2048 pixels image with ~5000 labels. When starting with an empty ROI Manager it takes only 0.1 sec!

A few notes:

  • If the ROI Manager window is open and already contains many ROIs it takes 5-10 times longer (but still very doable).
  • Applying the function on (time-lapse) stacks somehow is much slower (~100 times), because it swaps between the slices/frames. Hiding the image in batch mode results in zero ROIs.
  • Like many other fast solutions, composite ROIs will be split into multiple ROIs.

All the best,
Bram

3 Likes

Awesome. Thanks for reporting this @bramvdbroek, especially the notes about when it is slower.

1 Like