Construct LabelRegions from labelMap

imglib2

#1

@maarzt @haesleinhuepf @ctrueden
Hello!
I have an IntType RandomAccessibleInterval where the pixel values are already a label map.
Now I would like to get the LabelRegions from this image, but I do not manage to construct a valid ImgLabeling from it.

I tried like this:

// labelMap being an RandomAccessibleInterval< IntType >
final ImgLabeling< Integer, IntType > imgLabeling = new ImgLabeling<>( labelMap );
final LabelRegions< Integer > labelRegions = new LabelRegions<>( imgLabeling );
final Set< Integer > existingLabels = labelRegions.getExistingLabels();

But this throws an error:

java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.get(ArrayList.java:433)
	at net.imglib2.roi.labeling.LabelRegions.update(LabelRegions.java:368)
	at net.imglib2.roi.labeling.LabelRegions.getExistingLabels(LabelRegions.java:138)

I know how to make an ImgLabeling from scratch using ConnectedComponents.* but this will generate new label indices and thus change the old ones in my labelMap, which I actually want to keep on purpose.

Any ideas?


Probably related to my issue is that I conceptually do not really understand how one sets the LabelingMapping of an ImgLabeling

https://javadoc.scijava.org/ImgLib2/net/imglib2/roi/labeling/ImgLabeling.html


#2

Hi @Christian_Tischer,

I just digged in old code and found this snippet starting at an Img representing the label map and ending at a list of LabelRegions:

        // input: ImagePlus binaryOrLabelImage
        RandomAccessibleInterval< IntType > img = Converters.convert( (RandomAccessibleInterval<T>) ImageJFunctions.wrapReal(binaryOrLabelImage), realToIntConverter, new IntType() );

        final Dimensions dims = img;
        final IntType t = new IntType();
        final RandomAccessibleInterval<IntType> labelImg = Util.getArrayOrCellImgFactory(dims, t).create(dims, t);
        ImgLabeling<Integer, IntType> labelingImg = new ImgLabeling<Integer, IntType>(labelImg);

        // create labeling image
        if (Utilities.isBinary(img)) {
            ops.labeling().cca(labelingImg, img, ConnectedComponents.StructuringElement.FOUR_CONNECTED);
        } else {
            final Cursor<LabelingType<Integer>> labelCursor = Views.flatIterable(labelingImg).cursor();

            for (final IntType input : Views.flatIterable(img)) {
                final LabelingType<Integer> element = labelCursor.next();
                if (input.getRealFloat() != 0) {
                    element.add((int) input.getRealFloat());
                }
            }
        }

        // create list of regions
        LabelRegions<Integer> labelRegions = new LabelRegions<Integer>(labelingImg);
        ArrayList<RandomAccessibleInterval<BoolType>> regionsList = new  ArrayList<RandomAccessibleInterval<BoolType>>();

        Object[] regionsArr = labelRegions.getExistingLabels().toArray();
        for (int i = 0; i < labelRegions.getExistingLabels().size(); i++)
        {
            LabelRegion<Integer> lr = labelRegions.getLabelRegion((Integer)regionsArr[i]);

            regionsList.add(lr);
        }

        // output: ArrayList<RandomAccessibleInterval<BoolType>> regionsList

I wrote it two years ago, so it might not be state of the art. But I remember that I did it for a reason. There was apparently no way of feeding a labelmap into an ImgLabelling. That’s why… :wink:

I hope that helps.

Cheers,
Robert


#3

There’s a PR by @maarzt in imglib2-roi that might be of interest in this discussion:

In particular the comment by @tpietzsch:

I explicitly made it hard to construct an ImgLabeling in this way by locking it down with the LabelingAccess. The reason is, that it is easy to create a non-functional ImgLabeling if the image and the labelSets don’t match up. The labelSets need to have the empty set at index 0 and there must be no higher value in the image than labelSets.size() . I see that it is convenient to be able to construct ImgLabeling like that and allow people to shoot themselves in the foot if they want to.


#4

Very interesting!

Do I get the logic right?

This loops through the actual pixel values:
for (final IntType input : Views.flatIterable(img))
This somehow gets access to labels:
final LabelingType<Integer> element = labelCursor.next();
…, where element in fact is a Set which could take multiple labels, but in your (and my case) we simply want the label set to be one integer number which is the same as the one on the index image. Thus you set it like this.

Correct?!


#5

Below code also seems to work (“stolen” from ConnectedComponents.*):

public static ImgLabeling< Integer, IntType > labelMapAsImgLabeling( RandomAccessibleInterval< IntType > labelMap )
	{
		final ImgLabeling< Integer, IntType > imgLabeling = new ImgLabeling<>( labelMap );

		final double maximumLabel = Algorithms.getMaximumValue( labelMap );

		final ArrayList< Set< Integer > > labelSets = new ArrayList< >();

		labelSets.add( new HashSet<>() ); // empty 0 label
		for ( int label = 1; label <= maximumLabel; ++label )
		{
			final HashSet< Integer > set = new HashSet< >();
			set.add( label );
			labelSets.add( set );
		}
		
		new LabelingMapping.SerialisationAccess< Integer >( imgLabeling.getMapping() )
		{
			{
				super.setLabelSets( labelSets );
			}
		};

		return imgLabeling;
	}

#6

Hi Christian,

I’m not sure, if I get the question right. Let’s assume, I have a label map image looking like this:

   0 1
   3 4

The cursor will go through from the top left to the bottom right and add the labels 0, 1, 3 and 4 to the respective pixels. Internally, the ImgLabelling might look completely different (e.g. with indices 0, 1, 2 and 3), but I can access the image labelled 4 from the ImgLabelling by asking for the respective LabelRegion:

labelRegions.getLabelRegion(4);

Furthermore, if you ask for the region labelled with 2, you will get null back, which makes sense from my point of view.

Instead of 0, 1, 3 and 4 you could play the same game with A, B, D, F and an ImgLabelling of type ImgLabelling<Integer, String>.

Last but not least: The code snippet I posted, lives deep inside a converter, which takes a label map ImagePlus and hands over an ArrayList<LabelRegion>. All my code either accesses the ImagePlus or the list of regions. I don’t deal with the ImgLabelling at all. IMHO, this is an internal data structure which should be kept away from any API as it is not very convenient to work with.

Cheers,
Robert


#7

I see! I guess that means that the ImgLabeling automatically generates the pixel values of the IndexImg when you call element.add on the labelCursor?! There must be some internal logic that checks whether the specific label Set that you just created by calling element.add (potentially multiple times as there could be multiple labels per pixel) exists already, and if not, generates the next index, right??!