Cached ImgLib2 - lazy processing challenge

fiji
imglib2
#1

Hi everybody,

I’m playing with ImgLib2 ‘lazy’ processing. What I would like to have is a way to display the borders of a 3D label image which is:

  • lazy, meaning it’s only computed where I’m looking at,
  • and fast

The ‘fast’ part is my problem now.

To show the issue, I’ve made a self-contained groovy script which:

  • creates a model label Image and displays it in BigDataViewer
  • calls a fonction getBorderLabelImage(RandomAccessibleInterval<T> lblImg), which returns the border image, that’s the function which needs to be changed/optimized
  • displays the border image on BigDataViewer

If you execute this groovy script, you’ll see it in action:

Now the problem is that it takes about 50 seconds to compute and display this border image.
For a correct interactivity, I’d like to shrink this value to about 0.1s.

I’ll try some things, but I’m happy to hear about your insights and the performance you can reach ;-).

So far I didn’t find a way to compute the time it takes to finish the lazy processing. Is there a way to measure it ?

Thanks for reading this long message!

PS : A solution with ‘Converters’ could work, as @Christian_Tischer explained me. I’m interested in knowing this option, but I’m also interested in retrieving a part of the whole 3D image when it’s computed, that’s why the cached image is my preferred one for the moment.

4 Likes
#2

I had a quick look, and I feel something must be wrong with the way you are using the DiskCachedCellImgFactory, but I have no experience here…maybe @maarzt can spot the issue quickly? I can offer to make a version of above using a Converter. Are you interested in that?

3 Likes
#3

I’ve managed to speed up a bit the process, it’s down to about 5 seconds (factor 10), and I know that the way the test image is generated is also downgrading the performance, it’s more in the range of 1 sec if the image is ‘hardcoded’.

The edge is a bit thinner but that’s fine.

Updated gist:

At this point I really don’t know how to make it faster with a cached cell img.

And sure I 'm interested to test the Converter option!

3 Likes
#4

I see now, the input image is also computed on the fly. I think that’s not a good idea if you want to benchmark the border creation, because you don’t know what’s the slow part: the input image creation or the border image creation. This can be hard to disentangle. I would hardcode it. You can copy the “virtual” input label image into an ArrayImg to make sure that the pixels are precomputed.

final RandomAccessibleInterval< T > copy = new ArrayImgFactory( Util.getTypeFromInterval( orig ) ).create( orig );
LoopBuilder.setImages( copy, orig ).forEachPixel( Type::set );
4 Likes
#5

Cool! The less than 1 sec in reached! What takes time now is copying the image, but it’s not really the goal here.

Minor issue : this works within an IDE like intellij, but I cannot find method reference with double colons in groovy.

I’ll update the gist once I find a way or an alternative to this notation.

1 Like
#6

I think this should work (not tested):

LoopBuilder.setImages( copy, orig ).forEachPixel( ( c, o ) -> c.set( o )  );
#7

Maybe like this?

2 Likes
#8

I’ve updated the gist, and now the image is copied before being processed. It’s nicely working and pretty fast.

Just a few extra points:

  • I tried your suggestions @Christian_Tischer and @hanslovsky, but none worked. I am not able to use static method reference with LoopBuilder in groovy. To me it was also not clear what the LoopBuilder was requiring. Digging into the code was not really helpful. Was it requiring a Function, a Consumer, a BiFunction ?

  • Anyway I chose another option to copy the image and thanks to @hanslovsky, I used Grids.collectAllContainedIntervals to speed up the copy with parallelStreams.

  • There’s a weird difference of behaviour between Groovy and Java. This piece of code which tests whether shifted images have the same value:

while ( out.hasNext() ) {
    // Current value
    T v = inNS.next();
    // Equals in all shifted images ?
    int res = abs(v.compareTo(inXS.next()))
            + abs(v.compareTo(inYS.next()))
            + abs(v.compareTo(inZS.next()));
    out.next().set( ( byte ) ( res>0?126:0 ) );
}

Gives a correct image in Java, but not in Groovy (the Y shifted image is somehow ignored, you can see the problem if you zoom on the edge). If I replace this part by this equivalent piece of code :

while ( out.hasNext() ) {
                T v = inNS.next();
                if (v.compareTo(inXS.next())!=0) {
                	out.next().set( (byte) 126 ) 
                	inYS.next()
                	inZS.next()
                } else {
                	if (v.compareTo(inYS.next())!=0) {
	                	out.next().set( (byte) 126 ) 
	                	inZS.next()
	                } else {
	                	if (v.compareTo(inZS.next())!=0) {
		                	out.next().set( (byte) 126 ) 
		                } else {
		                	out.next() 
		                }
	                }
                }
}

Then it’s working as expected in Groovy! It’s weird enough to be mentioned.

1 Like
#9

Interesting! How much faster is it? And how do you specify the number of threads?

#10

Hi Christian,

In my not powerful laptop with a 4 cores CPU, I have a bit better than a factor 2. As far as I understand Streams in java use a common thread pool, but you can specify a different one (link) (Java 8, I don’t know different this could be in upper versions). There was a discussion about this and stories about ExecutorService somewhere in gitter recently (sorry I can’t be more specific).

Best,

Nicolas