Examples of usage of imglib2-roi

Hi @NicoKiaru,

In short, this mouthful:

inside = Regions.iterable(
           Views.interval(
             Views.raster(Masks.toRealRandomAccessible(sphere)),
             Intervals.largestContainedInterval(sphere)))

… now becomes:

inside = Masks.toIterableRegion(sphere)

That’s all.

3 Likes

Nice! However it’s not working yet for me. Has the PR reached FIJI yet ?

Possibly not. Before, it was merged with a different method name. Try using Masks.toIterableInterval instead of Masks.toIterableRegion for the time being.

Albert

1 Like

Hmm still failing. I’ll keep the long version for now.

This means @tpietzsch or @ctrueden haven’t yet cut a release of imglib2-roi with the new changes.

I released imglib2-roi-0.8.1, but the method has been renamed to Masks.toIterableRegion because that reflects more accurately what it does.

1 Like

Here’s the repo of a roi-course by @maarzt.

1 Like

I’m playing around with this and have a question. I am iterating over the pixels of a polygon and the number of pixels does not match my expectations. Here is my example:

ImageJ ij = new ImageJ();

//create image, set all pixels to one
ArrayImg<BitType, ?> image2d = new ArrayImgFactory<>(new BitType()).create(3, 3);
image2d.forEach(pixel -> pixel.setOne());

//get contour
Polygon2D poly = ij.op().geom().contour( image2d, false );

System.out.println("Contour: ");
for (RealLocalizable vertex : poly.vertices()) {
	System.out.println(vertex);
}

System.out.println("\nPixels of iterable region: ");

Cursor<BitType> cursor = Regions.sample(Masks.toIterableRegion(poly), image2d).localizingCursor();
while(cursor.hasNext()) {
	cursor.next();
	System.out.println(Arrays.toString(Localizables.asLongArray(cursor)));
}

This is the output:

Contour: 
(0.0,0.0)
(1.0,0.0)
(2.0,0.0)
(2.0,1.0)
(2.0,2.0)
(1.0,2.0)
(0.0,2.0)
(0.0,1.0)

Pixels of iterable region: 
[0, 0]
[1, 0]
[0, 1]
[1, 1]

I somehow would have expected that the contour is either part of the region or not part of the region, but it’s partly part of the region. Is that just the convention? Is it documented somewhere? Or is something wrong with my code?

EDIT since the contour coming from ops sits on the border pixels belonging to the foreground, the contour pixels should be part of the region, no?

2 Likes

To put things into context, I link to some related discussions here:

I fully agree that we should try to make the behavior of the contour op consistent with the imglib2-roi behavior. :slightly_smiling_face:

2 Likes

Did you check the ROI’s boundary type?

poly.boundaryType()

If you need a polygon excluding the boundary, use GeomMasks.openPolygon2D. If you need one including the boundary, use GeomMasks.closedPolygon2D. If you don’t care, use GeomMasks.polygon2D, which has the mixed boundary behavior you observed (because it has convenient mathematical properties I think—@awalter17 correct me if I’m wrong).

Be aware of the following from the open and closed 2D polygon implementations:

This implementation of a polygon does not support creating a single polygon object which is actually multiple polygons. It does support self-intersecting polygons with even-odd winding.

The DefaultContour implementation in ImageJ Ops currently uses the unspecified boundary type of Polygon2D:

@awalter17 It looks like you updated ImageJ Ops to use the new imglib2-roi with this commit:

Did you choose the unspecified boundary polygon on purpose? Or would a different one be better? What do you think?

1 Like

Hello!

You are correct! The Polygon2D implementation with unspecified boundary behavior is also more efficient then the open or closed implementations.

The unspecified boundary behavior was intentional. When I updated imagej-ops to use the new ROIs, I wanted to preserve the functionality of any Ops using the “old” ROIs. And the contour(...) Op was previously using the “old” imglib2-roi Polygon, which had unspecified boundary behavior.

I would leave the current DefaultContour implementation as is, since the resulting Polygon2D is the most efficient implementation. But I would modify the Java doc to state that the resulting polygon has unspecified boundary behavior.

And then I think it would be good to have a second *Contour Op which accepts either a BoundaryType or an instance of WritablePolygon2D (though we’d probably want to add a setVertices(...) method to WritablePolygon2D if we went this route). That way users have the option of specifying the boundary behavior.

Thoughts?

Cheers,
Alison

2 Likes

@awalter17 Thanks for your reply!

The new SciJava Ops has the power to make any computer op into a function op automatically, as long as a factory is available for constructing new objects of the given type. We can leverage this in the new versions of the geom2d ops to make all those ops computers only, with the Polygon2D factory producing a DefaultWritablePolygon2D.

@gselzer Here are the ops currently hardcoding construction of Polygon2D objects:

git grep 'new DefaultWritablePolygon'
src/main/java/net/imagej/ops/geom/geom2d/DefaultBoundingBox.java:               return new DefaultWritablePolygon2D(bounds);
src/main/java/net/imagej/ops/geom/geom2d/DefaultContour.java:           return new DefaultWritablePolygon2D(p);
src/main/java/net/imagej/ops/geom/geom2d/DefaultConvexHull2D.java:              return new DefaultWritablePolygon2D(L);
src/main/java/net/imagej/ops/geom/geom2d/DefaultSmallestEnclosingRectangle.java:                return new DefaultWritablePolygon2D(out);
src/test/java/net/imagej/ops/features/AbstractFeatureTest.java:         return new DefaultWritablePolygon2D(vertices);

Please change those all to computers typed on WritablePolygon2D.

We can look at the efficiency of calling addVertex repeatedly to build up polygons. If it’s bad, then I agree that adding a mass vertex insertion method makes sense.

1 Like

Hello,
I really appreciate the ongoing discussion and would like to add a question for the usage of net.imglib2.roi. How to do an operation similar to IJ1 /Edit/Clear Outside with a LabelRegion?

My initial approach was to use Views.interval(image, region). However this does not exactly what I want. I would like to blank all pixels outside the region. In fact even keep the size of the original image.

Second option would be to use a cursor an go through all points that do not belong to region and set them to 0. This seems not very elegant and what is the best way to do a NOT operation with a region?

Greetings

@apoliti Sorry for the late reply. All ImgLib2 shape ROIs are Predicates, so you can use all those methods including and, or, xor and negate. E.g.:

final WritablePolygon2D polygon = GeomMasks.polygon2D(new double[] {2, 3, 4, 5, 6, 1, 9, 9}, new double[] {7, 5, 3, 4, 6, 1, 1, 9});
final RealMask outsidePolygon = polygon.negate();

And then bound the outsidePolygon to an interval etc. as before.

2 Likes

Hmm, @ctrueden, how should these Ops treat these preallocated WritablePolygon2Ds? I am a bit naive about their usage. What do we do if there are already vertices present in the Polygon? Do we delete all vertices in the Polygon beforehand?

Great question. I think yes, the result of a computer needs to be deterministic regardless of the container’s current contents (as long as the container is structurally correct, which here just means “is an object of the right type”—but e.g. for ImgLib2 images also means “has the needed dimensions and type”). We may need to enrich the polygon API to support: A) clearing/deleting vertices; and/or B) preallocating a particular capacity a la the ArrayList.ensureCapacity method.

On the other hand: where I grow concerned is for operations where ensuring this deterministic behavior creates a performance issue. Suppose you have an op that writes some data sparsely into a RandomAccessibleInterval. It probably should not be responsible for zeroing out all the other locations. It is more sane to state in the preconditions that the container should be empty, and then define what empty means for each kind of object. If a non-empty container is given, determinism is not guaranteed. But a serious downside of this “container must be empty” constraint is that it makes buffer reuse more complicated.

What do you think? @dietzc @MarcelWiedenmann Thoughts?

I do think that we should be deterministic here. If there are other vertices in the Polygon, then for example, DefaultBoundingBox will not be a box. So naively I would say we need to delete those vertices (assuming my intuition is correct).

If I am correct above then we need to determine whether or not it is more efficient to delete all vertices, or if it is more efficient to keep the Op as a Function (i.e. whether it costs more to delete all vertices or to make a new Polygon2D)

1 Like

Thanks @albertcardona
It’s like you read my mind a year ahead!

@imagejan @albertcardona
Just a quick question,

Any idea how I should define a thickness for Line ROIs?

One solution that comes to mind is to draw a Polygon, and then rotate it.

In ImageJ2 ops. I see a LineOverlay that does so, but LineRegionOfInterest is deprecated.
This implies there’s a better approach I guess.