Scripting Graph Cut

beanshell
scripting
Tags: #<Tag:0x00007fb87d6b4fd0> #<Tag:0x00007fb87d6b4e90>

#1

Hi guys,

I was having a look on the GraphCut plugin on the wiki and source code on github and I would like to use it via beanshell scripting. I have some questions about that and I would really appreciate sharing your experience with this.

  1. Given an image stack as input. Am I correct, that the current version of GraphCut builds an isolated graph for every slice of the image stack (like a batch process)? or performs it a 3D graphcut?

  2. In this mailing list conversation it was said to be a one-liner. I’m relatively new to to beanshell scripting and couldn’t figure out how this one-liner would look like, can someone point me out how I can call the public function ‘processSingleImageChannel’ from the file Graph_Cut.java and which classes I have to import in the script?

I guess I have to define an object first, right?, and then run the new command. However, this gives me an error that the command could not be found.

gc = processSingleChannelImage(image, edge, dataWeight, pottsWeight, edgeWeight, seg);
gc.runCommand(image, edge, dataWeight, pottsWeight, edgeWeight, seg);

Thanks for your help!
Cheers,
Chris


#2

Hi @cbe,

  1. You seem to be right that this plugin performs many 2D graph cuts rather than one 3D graph cut, since the code for an image stack repeatedly calls the method for 2d.. (I think this is incorrect, and that it does a 3D graph cut, see below).

  2. Your issue seems to be that you need to call processSingleChannelImage on a Graph_Cut object. I think this will do the trick (but havn’t tested):

import graphcut.Graph_Cut;

gc = new Graph_Cut();
gc.processSingleChannelImage(image, edge, dataWeight, pottsWeight, edgeWeight, seg);

This will write the result into the ImagePlus that you pass as the final argument (“seg”).

Let us know how it goes,
John


Editing of thread-contributions after replies
#3

Hi John,

thanks for your helpful comments :slight_smile: Your suggestion + a look on the class page of Graph_Cut helped a lot to make it work now with all the correct arguments! See the attached script… if anyone may find it useful sometime.

One additional question to 1.:

  • given an input stack, why do I get “num Neighbors of pixels = 7” in the log, and why “… = 4” for 2D image? This doesn’t make sense to me, as I suppose it should be 8 in both cases, right? Does someone know about the ideas and further effects of those numNeighbors in this particular implementation?

Cheers,
Chris

import ij.IJ;
import ij.ImagePlus;
import graphcut.Graph_Cut;

// define starting time
startTime = System.currentTimeMillis();
IJ.log( "** Start of Graph_Cut test **" );

// input variables
filepath = "/my/file/path/insert/here.tif"
image = IJ.openImage(filepath);
// dataWeight - weight of data term (t-links) [0,1]
float dataWeight = 0.9;
// pottsWeight - or smoothness, weight of smoothness term (n-links) [0,10]
float pottsWeight = 1;
// optional input, else intialise to null
ImagePlus edge = null;
edgeWeight = 0;

// create new Graph_Cut object
gc = new Graph_Cut();
// run command on new object, seg gives the resulting binary image
seg = gc.processSingleChannelImage(image, edge, dataWeight, pottsWeight, edgeWeight);

// save stack
IJ.saveAsTiff(seg, filepath.replaceFirst("[.][^.]+$", "") + "_GRAPHCUT_dW" + dataWeight + "_pW" + pottsWeight + ".tif");
IJ.log( "GraphCut result saved to directory" );

// Print elapsed time
estimatedTime = System.currentTimeMillis() - startTime;
IJ.log( "** Finished script in " + estimatedTime + " ms **" );

#4

No problem, glad that helped.

Your pointing out that log message was helpful, since:
I now think I was wrong above, and this code does indeed do 3d convolution.
What I pointed to above shows that it works on each channel separately, but it seems to treat slices as part of a 3d volume.

Why are there 7 neighbors though?
In 3D, a voxel can have 8, 16, or 32 26 neighbors. When this code visits a voxel, it adds edges to its neighbors, but it avoids adding duplicate edges ( e.g. edge from A->B is the same as B->A ). It does this (maybe, I think ) by adding edges only to voxels with positions that are lexicographically greater than the position “here.”

Glad your code works, but sorry for this confusion :confused:
John


For a given voxel marked by *, the seven neighbors that are “lexicographically greater than” it might be:
{ E, N, NE, U, UE, NU, NUE}

          x  NU NUE
 z+1      x  U  UE
          x  x  x

          x  N  NE
 z        x  *  E
          x  x  x
 
          x  x  x 
z-1       x  x  x
          x  x  x

where:
W/E - west/east
N/S - north/south
U/D - up/down


#5

@bogovicj: okay, so you are pointing on the difference between graph theory vs. (something like an) “edge growing process” in the implementation?

I have to admit that I’m just starting to have a look on maxflow/mincut implementations and can’t really discuss about this. Assume that there is something like an edge growing process I think the number of new edges the code is taking into account for sure depends on the spatial directions of progress (one implemented) and the neighburhood (which one defined by the implementation). In this context I was just wondering how you came to this statement? [quote=“bogovicj, post:4, topic:5728”]
In 3D, a voxel can have 8, 16, or 32 neighbors.
[/quote]

What basics I know about terms of a regular graph with nodes(=pixel) and edges(=connections between pixels/nodes) so far (see Wiki):

  • in 2D you can have horizontal+vertical edges (4-connectivity), or inclusive diagonal edges (8-connectivity). - btw in Graph_Cut.java there is a private boolean eightconnect which is set as true.
  • in 3D you can have 6-connectivity or 26-connectivity (in the same fashion as 2D)

So in this context I was just wondering how it makes sense, that one gets this confusing output in the log.

But as you pointed out, this output might be just the additional number of connection/edges which are added to those which were already gathered.

So what do you think about what I concluded for myself? …

  • in general the plugin builds an undirected graph, which means the edges are bidirectional
  • given a 2D image: the plugin assumes an 8-connectivity between the nodes/pixels
  • given a stack: the plugin assumes an ???-connectivity

Is it possible that some functionalities are implemented as possible to work in 3D but some functionalitiies (e.g. eightconnect) are just supposed to be used for 2D images exclusively?

What do you reckon? Sorry, when I didn’t get your point correctly. Any suggestions?

For now, I will simply test the difference between 2D vs 3D results, but some insights would be cool.

Thanks a lot for the disucssion :slight_smile:


#6

Very happy to discuss with you @cbe, thanks for noticing this stuff…

Total typo on my part - it’s 26-connectivity in 3d, sorry!! :scream:

Yes, agreed.

Agreed - because of that private eightConnect boolean you noticed.

I think it uses 26 connectivity - in part because of the log message mentioning that there are 7 neighbors, and the stuff in my previous post.

Certainly possible, but I think the main issue is just that eightConnect is not a great name for that variable in this particular code and that it should be something like useDiagonals, or even better, explicitly let you specify the connectivity. The code uses Imglib2 which is great for writing code in a way that is mostly independent of the image dimension. That misleading variable name may be because this was ported from 2d code and was just “left over.”

I happen to know the author of this plugin and will ask about it some time. I may try digging into the code and try cleaning it up too…

John


#7

Hi John,

nice, thanks for sorting this out! I found some differences between 2D vs 3D application of graphcut on my data sets, so I already presumed something like a misleading terminology due to ‘porting’ the 2D implementation to 3D - as you mentioned it.

I think it’s great to note that this plugin is capable of performing a 3D graphcut and if the author could confirm this, I’d suggest to include it in the Wiki. Also to help others not misunderstanding the source code.

Cheers,
Chris