Examples of usage of imglib2-roi

fiji
imglib2
imagej
imglib2-roi

#1

Hi all,

I am looking for real-world examples of usage of imglib2-roi. In particular, I have the following image processing task: a set of 3D RealPoint with a radius (same radius for all), and a 4D data set.

What I envision is the following:

img4D = ... # a 4-dimensional RandomAccessibleInterval
points = ... # list of 3-dimensional RealPoint
table = [[] for p in points] # list of lists: one list per point, each list as long as the time axis

for t in xrange(img4D.dimension(3)):
  img3D = Views.hyperSlice(img4D, 3, t)
  for i, point in enumerate(points):
    # TODO: measure average pixel value at point + radius
    average = ...
    table[i].append(average)

How would one accomplish this with imglib2-roi?

The trivial way would be using the reverse: build a KDTree with the points, and iterate over all pixels in img3D asking, with a radius neighbor search, for the nearest point, and summing pixel readings at each point and then dividing by the total to get the average. The disadvantage is that all pixels need to be iterated. I’d rather only iterate the pixels within radius distance of each RealPoint of interest.

Thanks for any suggestions.


#2

I’ve gotten as far as finding the class ClosedWritableSphere, and the static utility methods in GeomMasks to construct them. But how do I get the Iterable over all the positions that are inside the ClosedWritableSphere? Google says: nothing, no examples of usage of this anywhere.

The IterableRandomAccessibleRegion reads promising:

Make a boolean {@link RandomAccessibleInterval} iterable. The resulting
 * {@link IterableInterval} contains all samples of the source interval that
 * evaluate to {@code true}.

… but can’t find a way to relate it to the ClosedWritableSphere yet.

Looks like imglib2-roi is in dire need of more publicly discoverable examples. Are they somewhere and I just can’t stumble upon them?


#3

Hi @tpietzsch, how does one get an integer mask, whose positions are iterable, from a ClosedWritableSphere? So that I can use to read the pixel values inside the sphere in a RealRandomAccessible?


#4

It’s surprising that none of the tests in imglib2-roi include any use of RandomAccessibleRegionCursor, which I gather is what I’d want, if only I knew how to get one for a ClosedWritableSphere.


#5

Was happy to have found a class that looks usable, like subclasses of AbstractIterableRegionOfInterest, but they are unfortunately labeled as @Deprecated. Bummer.


#6

Eureka: looks like net.imglib2.roi.Masks has static methods to wrap a RealMaskRealInterval like a ClosedWritableSphere into a RealRandomAccessibleRealInterval. The latter can be rasterized with Views.raster into a RandomAccessibleInterval, because a RealRandomAccessibleRealInterval is just a RealRandomAccessible that is defined within a RealInterval. Finally.

The Masks class should appear in github and in the javadoc as Masks, or similarly, perhaps all other classes of that package should go into a subpackage to highlight the top-level entry point that Masks is. At least now I found it. Onward to writing test code.


#7

By the way, I’d am thankful for Masks.toRealMaskRealInterval hiding the monster class RealRandomAccessibleRealIntervalAsRealMaskRealInterval. At least the name is self-documenting.


#8

On the rasterizing to get an iterable mask, surely there is an easier way than this?

sphere = GeomMasks.closedSphere(center, radius)
mask = Views.iterable(
         Views.interval(
           Views.raster(Masks.toRealRandomAccessible(sphere)),
            Intervals.largestContainedInterval(sphere)))

#9

Almost. There’s net.imglib2.roi.Regions, in particular Regions.iterable, which will return an iterable over the true values of a given RandomAccessibleInterval of boolean type like a mask. Basically, it uses the very desirable IterableRandomAccessibleRegion.

Except, the Masks.toRealRandomAccessible(sphere) is a RealRandomAccessible and not a RandomAccessibleInterval, so conversions are still needed via Views or some other yet-to-be-discovered method.


#10

So this works, except the result is slightly different than ImageJ’s:

from net.imglib2.roi import Masks, Regions
from net.imglib2.roi.geom import GeomMasks
from net.imglib2.img.display.imagej import ImageJFunctions as IL
from net.imglib2.view import Views
from net.imglib2.util import Intervals
from ij import IJ

# Open the embryos sample image, and turn it into 16-bit grayscale
imp = IJ.getImage()
IJ.run(imp, "16-bit", "")
IJ.run(imp, "Specify...", "width=61 height=61 x=1037 y=83 oval")
circle = imp.getRoi()
bounds = circle.getBounds()
center = bounds.x + bounds.width / 2.0, bounds.y + bounds.height / 2.0
print center

img = IL.wrap(imp)
sphere = GeomMasks.closedSphere(center, 61 / 2.0)
inside = Regions.iterable(
           Views.interval(
             Views.raster(Masks.toRealRandomAccessible(sphere)),
             Intervals.largestContainedInterval(sphere)))

pixels = []
ra = img.randomAccess()
cursor = inside.cursor() # only True pixels of the sphere mask

while cursor.hasNext():
  cursor.fwd()
  ra.setPosition(cursor)
  pixels.append(ra.get().get())

print "Average:", sum(pixels) / float(len(pixels))

# prints: 68.91
# whereas ImageJ "measure" prints: 69.285 for the EllipseRoi

#11

Turns out @imagejan had provided a similar solution for @Christian_Tischer : https://github.com/imagej/i2k-2018/blob/af72ef16984a8c16d965b5e1bec98613e90b9d01/ImageJ_Scripting.groovy#L194-L241

To the authors of imglib2-roi newest classes, including Regions and Masks: would you welcome a PR with this:

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

… packed into a convenient static method, e.g. something like Regions.iterable(RealMask), and also parts of it into a Masks.toRandomAccessibleInterval(RealMask) ? Would ease the pain a lot.


#12

@albertcardona Did you see Regions.sample() ?
It binds an IterableRegion to a RandomAccessible<T>.
The result is an IterableInterval<T> that iterates the img pixels “under the region”.

So the above would not need the RandomAccess and could be something like this:

...
cursor = Regions.sample(inside,img).cursor()
while cursor.hasNext():
  pixels.append(cursor.next().get())
...

#13

Thanks @tpietzsch, but Regions.sample(IterableInterval, RandomAccessible) doesn’t quite do it–unless I am misunderstanding something–because ClosedWritableSphere doesn’t implement IterableInterval:

net.imglib2.roi.geom.real
Class ClosedWritableSphere
java.lang.Object
  net.imglib2.AbstractEuclideanSpace
    net.imglib2.roi.geom.real.AbstractWritableSphere
      net.imglib2.roi.geom.real.ClosedWritableSphere
All Implemented Interfaces:
Predicate<RealLocalizable>, EuclideanSpace, RealInterval, Ellipsoid,
Sphere, SuperEllipsoid, WritableEllipsoid, WritableSphere,
WritableSuperEllipsoid, MaskPredicate<RealLocalizable>, RealMask,
RealMaskRealInterval

So I am not sure how I would use Regions.sample?

Also, where is the IterableRegion? It’s not an interface that ClosedWritableSphere implements. Did you mean IterableInterval (which it doesn’t implement either)?

It is not helping here that Region's methods have zero documentation: https://javadoc.scijava.org/ImgLib2/net/imglib2/roi/Regions.html#sample-net.imglib2.IterableInterval-net.imglib2.RandomAccessible-


#14

Wait: I finally got it. You mean, I can iterate directly the pixels of the target RandomAccessibleInterval using Region.sample, rather than having to use a RandomAccess and its setPosition from the Cursor over the ClosedWritableSphere.

Thanks! It does remove some noise:

from net.imglib2.roi import Masks, Regions
from net.imglib2.roi.geom import GeomMasks
from net.imglib2.img.display.imagej import ImageJFunctions as IL
from net.imglib2.view import Views
from net.imglib2.util import Intervals
from ij import IJ

# Open the embryos sample image, and turn it into 16-bit grayscale
imp = IJ.getImage()
IJ.run(imp, "16-bit", "")
IJ.run(imp, "Specify...", "width=61 height=61 x=1037 y=83 oval")
circle = imp.getRoi()
bounds = circle.getBounds()
center = bounds.x + bounds.width / 2.0, bounds.y + bounds.height / 2.0
print center

img = IL.wrap(imp)
sphere = GeomMasks.closedSphere(center, 61 / 2.0)
inside = Regions.iterable(
           Views.interval(
             Views.raster(Masks.toRealRandomAccessible(sphere)),
             Intervals.largestContainedInterval(sphere)))

# Try with Region.sample
cursor = Regions.sample(inside, img).cursor()
pixels = []
while cursor.hasNext():
  pixels.append(cursor.next().get())

print "Average:", sum(pixels) / float(len(pixels))

Notice, though:
Still missing a way to simplify the generation of the inside from a ClosedWritableSphere, or any RealMask. A method in Masks would be most welcome. What would you call it? Happy to write it myself.


#15

Also: can I translate these spheres, of do I need to generate a sphere for every point in space I want to measure? What is the most efficient way, given that there are thousands of such points + radius, and the radius is identical for all?


#16

Also missing from Masks or Regions: statistics. Methods similar to Regions.sample (taking as arguments a RealMask and a RandomAccessibleInterval) that digest the pixel data inside the ROI. Like “mean”, “sum”, “max”, “min”, “stdDev”, and another one named e.g. “statistics” that returns them all as a Map, so as to traverse the data only once.

If I add these, @tpietzsch, would you merge them?


#17

Yes, Regions is still work in progress. I have more stuff in this direction lying around for > two years now, that I work on a bit at hackathons every year. Documentation will come eventually


#18

I think these belong in algorithm, not here. And I would just make them take one IterabelInterval<T> parameter. I would prefer writing Statistics.max(Regions.sample(region, img)) to Statistics.max(region, img).


#19

IterableRegion Masks.toIterableRegion(RealMaskRealInterval)?
@awalter17 any ideas/preferences?

You can translate the spheres. They have center() which is Localizable and Positionable.
I’m not sure whether the updates carry through rasterization etc. To find out, I would have to try / dig through the code myself…


#20

@albertcardona I agree, a method which converts/wraps a RealMaskRealInterval to an IterableRegion would be very helpful! Thank you for bringing this up, and volunteering to write it :smile_cat:

Regarding the naming, I’m fine with Masks.toIterableRegion. But I could also see another method in Regions i.e. IterableRegion Regions.iterable(RealMaskRealInterval). This is kind of nice because it keeps all the “iterable” methods together.