Cursor getting lost inside an Interval with only one voxel

Hey guys,

I’m having a really strange issue with ImgLib2 Cursors in combination with Intervals. Assume I have an Interval going from position 5/5/5 to position 5/5/5. Thus, it includes just a single voxel. Then, I would like to get a cursor to navigate through this interval. In some special cases, this easy thing fails. It took me hours to find roughly out, where this bugs comes from, so I would like to share this knowledge with you :wink: . When I call Views.interval(Image, Interval), the error appears only if the image was converted/wrapped from an ImagePlus. However, this is a thinkable situation in case of my software, so it would be cool if one knows where the bug originates and how it can be prevented…

Here is some source code to reproduce it:

    import static org.junit.Assert.assertTrue;
    import ij.ImagePlus;
    import ij.gui.NewImage;
    
    import java.util.Arrays;
    
    import net.imglib2.Cursor;
    import net.imglib2.Interval;
    import net.imglib2.img.Img;
    import net.imglib2.img.array.ArrayImgs;
    import net.imglib2.img.display.imagej.ImageJFunctions;
    import net.imglib2.type.numeric.real.FloatType;
    import net.imglib2.util.Intervals;
    import net.imglib2.view.Views;
    
    import org.junit.Test;
    
    public class CursorIntervalTest {
        
        
        @Test
        public void testIfCursorsOnIntervalsWorkFine() {
            // First test: using a native ImgLib2 image
            Img<FloatType> img1 = ArrayImgs.floats(new long[]{10, 10, 10});
            testIfBoundingBoxCursorCreationWorks(img1); // passes
            
            // Second test: using a wrapped/converted ImagePlus
            ImagePlus imp = NewImage.createImage("test", 10, 10, 10, 32, NewImage.FILL_BLACK);
            Img<FloatType> img2 = ImageJFunctions.convertFloat(imp);
            testIfBoundingBoxCursorCreationWorks(img2); // fails
        } 
        
        public void testIfBoundingBoxCursorCreationWorks(Img<FloatType> img)
        {
            // Create bounding box with width = height = depth = 1
            Interval boundingBox = Intervals.createMinMax(new long[]{5,5,5,5,5,5});
            
            //
            Cursor<FloatType> cursor = Views.interval(img, boundingBox).cursor();
            cursor.next();
    
            long[] pos = new long[img.numDimensions()];
            cursor.localize(pos);
            System.out.println("initial cursor position:" + Arrays.toString(pos));    
    
            assertTrue(pos[0] == 5);
            assertTrue(pos[1] == 5);
            assertTrue(pos[2] == 5);
        }
    }

The programs output looks like this:

initial cursor position:[5, 5, 5]
initial cursor position:[0, 0, 555]

And the second line makes absolutely no sense to me :frowning:

Oh and: I’m working in Eclipse on MacOS using Java 1.6 for compilation :slight_smile:

All hints are welcome.

Thanks,
Robert

Ok… this is a tricky one. First of all: Can you try https://github.com/imglib/imglib2/tree/planar-subimg-bug and see if it works?

Explanation: If possible, imglib2 uses an optimized Cursor instead of a Cursor based on the RandomAccess obtained from a IntervalView or MixedTransformView which are both RandomAccessibleIntervals. Such a Cursor would simply set the position of the RandomAccess in each position and somehow simulate the behaviour of a Cursor. However, in certain situations we can be smarter: If we know what we are about to iterate (e.g. a connected Interval in an ArrayImg or PlanarImg, we can use a native Cursor, even if we are in the View-world.

I think I found a bug in one of these optimzed Cursors, the PlanarSubsetCursor. The size of the plane was calculated wrong.

So if you can check if it works now, I will open a PR and ask @tpietzsch to review.

Cheers and thanks for finding this :wink:

2 Likes
import java.util.Arrays;

import org.junit.Test;

import net.imglib2.Cursor;
import net.imglib2.FinalInterval;
import net.imglib2.Interval;
import net.imglib2.img.Img;
import net.imglib2.img.array.ArrayImgs;
import net.imglib2.img.planar.PlanarImgFactory;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.view.Views;

public class CursorIntervalTest {

    @Test
    public void testIfCursorsOnIntervalsWorkFine() {
        // First test: using a native ImgLib2 image
        Img<FloatType> img1 = ArrayImgs.floats(new long[] { 10, 10, 10 });
        testIfBoundingBoxCursorCreationWorks(img1); // passes

        // Second test: using a wrapped/converted ImagePlus
        Img<FloatType> img2 = new PlanarImgFactory<FloatType>().create( new long[]{ 10, 10, 10}, new FloatType() );
        testIfBoundingBoxCursorCreationWorks(img2); // fails
    }

    public void testIfBoundingBoxCursorCreationWorks(Img<FloatType> img) {
        // Create bounding box with width = height = depth = 1
        Interval boundingBox = new FinalInterval(new long[] { 5, 5, 5 },new long[] { 5, 5, 5 });

        //
        Cursor<FloatType> cursor = Views.interval(img, boundingBox).cursor();
        cursor.next();

        long[] pos = new long[img.numDimensions()];
        cursor.localize(pos);
        System.out.println("initial cursor position:" + Arrays.toString(pos));

        org.junit.Assert.assertTrue(pos[0] == 5);
        org.junit.Assert.assertTrue(pos[1] == 5);
        org.junit.Assert.assertTrue(pos[2] == 5);
    }
}

this test works for me.

1 Like

First of all, thanks for the support! Sounds like I really found something. :slight_smile:

But well,… changing the test case doesn’t solve my issue. I have no new PlanarImgFactory<FloatType>() in my software, i’ve got an ‘ImagePlus’ and I cannot change it - it’s the input variable :wink:

So, I downloaded the source from git as you mentioned. Afterwards, I added my test class to it. Furthermore, I added two dependencies to make the test class compile:

    <dependency>
        <groupId>net.imagej</groupId>
        <artifactId>ij</artifactId>
    </dependency>
    <dependency>
        <groupId>net.imglib2</groupId>
        <artifactId>imglib2-ij</artifactId>
    </dependency>

And the output remains

initial cursor position:[5, 5, 5]
initial cursor position:[0, 0, 555]

So, I cannot confirm that it’s working. Or did I do something wrong?

Best,
Robert

Did you switch to my branch? I just simulated the problem using a PlanarImgFactory as in the back you also have a PlanarSubsetCursor with an ImagePlus.

1 Like

Ah sorry, my fault. So yes, I can confirm: it runs in your branch, also with ImagePlusses :slight_smile:

Thanks!
Robert

2 Likes

cool! I will open a PR

1 Like