Skeletonization and pruning

I am running skeletonization on a binary image using the following java code:

ByteProcessor processor = new ByteProcessor(heart.getProcessor(), false); processor.skeletonize();

  1. Is there a way to get the coordinates in an array instead of an image?
  2. How do I pick the longest path?

You can use Skeletonize3D, and AnalyzeSkeleton which also work in 2D. Have a look at this script in the wiki.

3 Likes

I’d like to know more about question 2 from @mongoose54. I have a method that is looking at the skeleton of a fruit. Sometimes the fruit is bumpy or the segmentation of the fruit from the backgroud is rough. This leaves me with a skeleton with little branches sometimes. What I really want is just the longest skeleton.

When I use the analyze skeleton plugin it is able to find it, but I can’t figure out how to isolate it. It appears to be a selection, but I can’t use it. Also, in the examples I’ve played with, where the branch connects to the main skeleton the “selection” has a gap in it where the main node is. Also the tips are white as well.

If I have a skeleton like this:

I run skeletonize and I get this:

It has one side branch (I need this to handle more, but it’s an effective example).

How can I use the Analyze plugin to isolate the pixels of the longest skeleton? If I can do this then I can get rid of a lot of error handling code and reduce the amount of blurring and erode/dilate I do to smooth the fruit prior to skeletonization. This is important because it’s affecting the shape of the fruit, particularly the ends which I’m interested in measuring for shape characteristics.
TIA. B

1 Like

To my knowledge, there is no direct way (yet?) to get the continuous longest skeleton. But you can assemble it from its parts by:

  • thresholding the Longest shortest paths image at the range 96-96 (the segments of the longest path)
  • thresholding the Tagged skeleton image at the range 30 (end points) to 70 (branch points)
  • converting both images to binary masks
  • combining both images using AND in the image calculator (Process > Image Calculator…)
  • getting rid of objects less than 4 pixels (e.g. using Analyze > Analyze Particles…)

Maybe @iarganda has some better suggestion?

5 Likes

Using the plugin GUI there’s no direct way to obtain the longest path. Using a script, you can get the graph and work with it on your own.

1 Like

Thanks for your response @iarganda . I’m trying to pull out the branches, but I’m having trouble even running the example script on the wiki. What graph are you referring to?

Started Untitled.ijm.js at Tue May 10 09:44:49 PDT 2016
nashorn:mozilla_compat.js:67 ReferenceError: "AnalyzeSkeleton_" is not defined
	at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:57)
	at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:319)
	at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:291)
	at jdk.nashorn.internal.objects.Global.__noSuchProperty__(Global.java:1428)
	at jdk.nashorn.internal.scripts.Script$Recompilation$24$1362A$\=nashorn\!mozilla_compat.value-1$__noSuchProperty__(nashorn:mozilla_compat.js:67)
	at jdk.nashorn.internal.scripts.Script$25$Untitled_ijm.:program(Untitled.ijm.js:11)
	at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:623)
	at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:494)
	at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:393)
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:446)
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:403)
	at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:399)
	at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:150)
	at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:249)
	at org.scijava.script.ScriptModule.run(ScriptModule.java:174)
	at org.scijava.module.ModuleRunner.run(ModuleRunner.java:167)
	at org.scijava.module.ModuleRunner.call(ModuleRunner.java:126)
	at org.scijava.module.ModuleRunner.call(ModuleRunner.java:65)
	at org.scijava.thread.DefaultThreadService$2.call(DefaultThreadService.java:191)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

The plugin is named AnalyzeSkeleton.-3.0.0.jar in my Fiji install. I’m guessing that NONE is supposed to cover different versions and that’s not the problem.

1 Like

The script wasn’t updated since the recent package change in AnalyzeSkeleton-3.0.0. It’s sufficient to change the import line to:

importPackage(Packages.sc.fiji.analyzeSkeleton);

I just updated the script on the wiki.


I guess @iarganda was referring to the getGraphs() method of AnalyzeSkeleton_ that returns a sc.fiji.analyzeSkeleton.Graph object containing all the edges and vertices. I didn’t investigate how to extract the longest branch information though…

4 Likes

Thanks for updating the script, @imagejan! I’m not sure if someone has already implemented a solution in ImageJ/Fiji to find the longest path. It’s maybe a task to add to my TODO list…

3 Likes

How do I extract the graph elements? I’m looking at the source and am not getting it: https://github.com/fiji/AnalyzeSkeleton/blob/master/src/main/java/sc/fiji/analyzeSkeleton/Graph.java

You can use getVertices(), getEdges() and getRoot() to navigate the graph.

1 Like

Can you give me one more hint? I just want to extract to see what data it contains. I’m used to the macro language and R so I struggle with Java and JS.

// I get the skeleton
var skelResult = skel.run(AnalyzeSkeleton_.NONE, false, true, null, true, false);
// isolate the graph
var graphObj = getGraph(skelResult);
// trying to get the edges as an example
var graphEdge = getEdges(graphObj);

//prints nothing
IJ.log(graphEdge);

Thanks,
B

1 Like

Hi,
as a complement you may have a look at the geodesic diameter. It is implemented in the MorphoLibJ library. It was originally developed to get geodesic diameter (or gedesic longest path) with binary particle, but if you apply it on skeleton I think it can give the result you expect. Result is given as an overlay, so you can extract the list of vertices of the polyline.

hope this helps?

3 Likes

No problem. Let me be more descriptive:

  • The Edge objects contain the information of each branch. Namely, the two extreme vertices (V1 and V2, accessible with getV1() and getV2()) and all the slab voxels in between (accessible with getSlabs())
  • The Vertice objects contain the information about the nodes in the graph. Namely, the edges that exit that vertex and the set of points that belong to the vertex (this is an important detail because following the definition of junction in AnalzyeSkeleton, several voxels can form a junction, therefore a vertex).
2 Likes

@dlegland,

Yes in fact that’s exactly what I’m looking to do. I’m struggling to extract the data points as you say. I found a way to create an ROI from an overlay, but when I do this, it resets the ROI manager (which is something I really don’t want).

run("Skeletonize (2D/3D)");

run("Geodesic Diameter", "label=skeleton distances=[Chessknight (5,7,11)] show image=skeleton");
run("To ROI Manager");
roiManager("Select", fruitnumber);

selectWindow("skeleton");
run("Close");

newImage("skeleton", "black", imgWidth, imgHeight, 1);
selectWindow("HeadLine");
roiManager("Select", 0);
setForegroundColor(255,255,255);
run("Draw", "slice");
run("Select None");

Is there a specific method to get points that make up an overlay? getSelectionCoordinates() complains about a lack of selection. I don’t see a getOverlayCoordinates() in the macro manual. Maybe @Wayne or @ctrueden have an idea.

1 Like

Sorry, I don’t know, and do not have time to research it in coming weeks. I hope someone else knows.

1 Like

Overlay seems to be a container for multiple ROIs.

I do not see an obvious way to access individual ROIs from an Overlay in the macro language, but within scripts it is possible to interrogate an Overlay for its size, to find out how many ROIs it contains. Then one can loop through the ROIs of an Overlay by getting each ROI using its index.

2 Likes

I’m not very familiar with overlay, so I can not help this way.

However, you can call direclty the static method that computes the list of position corresponding to the geodesic path. The method is in class inra.ijpb.binary.geodesic.GeodesicDiameterFloat, its signature is
Map<Integer, List> longestGeodesicPaths(ImageProcessor labelImage).

I think it is possible to call it from a macro, by using the “call” command, but I haven’t tried myself.

1 Like

I’ve given it a try and I’m not sure if it’s my incompetence or other factors.
I’ve tried:
call(“MorphoLibJ.inra.ijpb.binary.geodesic.GeodesicDiameterFloat.longestGeodesicPaths”, “test”);
and many variations on the name of the function. I constantly get:Could not load class where “test” is the name of the window that has a skeleton (as above in this thread).

I know that there are two inputs for the function (imageprocessor and labelImage), but I’m not sure how to give it the imageprocessor. Also, the manual for call() suggests that the method needs to be public static. On the source page I can’t find the word static anywhere.

Any further pointers? I swear I’m trying here… just really weak in Java.

my mistake, the method needs to be static, sorry…
I will consier adding a static version in a future release.

In the meanwhile, I do not know if it is possible to instanciate the class and call a non static method?
In java the code should be something like this:
paths = new GeodesicDiameterFloat(new float[]{3,4}).longestGeodesicPaths(labelImage);

1 Like

Thanks for helping. Geodesic diameter is exactly what I need. I’ve considered rewriting it in the macro language to use it, but I have deadlines that don’t agree with me doing that. :confused:

If I can put in a request, it would be awesome to have an option of outputting a selection or an overlay of the longest branch.