fillHoles (imagej ops)

Hi

I am trying to use some of the morphological operations in the ops library. But FillHoles is behaving a bit weirdly: my BitType mask image contains a white (true) object not touching the border and some black (false) holes in it. now if I call

ij.op().morphology().fillHoles(fill, mask);

nothing happens. I tried using the background=true option of the op and also inverted the image. In this case it’s all black or white.

On another note: ops can be called with an output paramter, but often the output has to be given as input anyway (C-style). Why have an output at all then? I find this a bit confusing.

I also tried

Img<BitType> mask = new ArrayImgFactory<BitType>().create(lblimg, new BitType());
...
Img<BitType> fill = (Img<BitType>) ij.op().morphology().fillHoles(mask);

throws:

Exception in thread "main" java.lang.ClassCastException: net.imglib2.type.numeric.real.DoubleType cannot be cast to     net.imglib2.type.BooleanType
        at net.imagej.ops.morphology.fillHoles.DefaultFillHoles.compute1(DefaultFillHoles.java:94)
        at net.imagej.ops.morphology.fillHoles.DefaultFillHoles.compute1(DefaultFillHoles.java:57)
        at net.imagej.ops.special.hybrid.UnaryHybridCF.compute1(UnaryHybridCF.java:62)
        at net.imagej.ops.special.hybrid.UnaryHybridCF.run(UnaryHybridCF.java:72)
        at net.imagej.ops.special.hybrid.UnaryHybridCF.run(UnaryHybridCF.java:98)
        at org.scijava.command.CommandModule.run(CommandModule.java:205)
        at net.imagej.ops.OpEnvironment.run(OpEnvironment.java:940)
        at net.imagej.ops.OpEnvironment.run(OpEnvironment.java:157)
        at net.imagej.ops.morphology.MorphologyNamespace.fillHoles(MorphologyNamespace.java:405)
        at Test.main(Test.java:137)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

I have no clue where the double comes from.

In addition to ops in general, which can have M inputs and N outputs of whatever type, there are three kinds of “special” ops: computer, function and inplace. See this javadoc for a summary. In the unary case (single primary input), we have:

  • Computer - An op which computes a result from the given input I, storing the result into the specified preallocated output reference O.
  • Function - An op which computes a result from the given input I, returning the result as a newly allocated output O.
  • Inplace - An op which mutates the contents of its argument in-place.

And some “hybrid” ops are capable of operating in more than one of these ways.

In SciJava terms, when you pass a preallocated output to a computer op, for its contents to be filled in, that output is actually a parameter of type “both”—you give it as input, and it is mutated. Same for inplace. Of the three, only function synthesizes a new output object and returns it.

We have all three of these because they fulfill different requirements and use cases for callers. And because different algorithms have different requirements as well, due to space/time complexity tradeoffs, etc.

Apparently FillHoles has no unit tests, which is a bummer. However, its cousin ExtractHoles does; maybe looking at it helps.

@dietzc Any ideas?

Note that for succinctness, you can use:

Img<BitType> mask = ArrayImgs.bits(lblimg);

The short answer is: the DefaultFillHoles function behavior is currently broken.

Your invocation above is an unsafe and unchecked cast, which you should not do, and which was not intended to be needed here. The DefaultFillHoles function method signature should instead have a return type of Img<BitType>. The fact that it does not is a bug in the DefaultFillHoles API:

The current DefaultFillHoles implementation is a hybrid computer/function, but with a generic T parameter in its output type. This is bogus and needs to be changed: the function version of FillHoles needs to return a concrete type such as BitType. Otherwise, there is no way to infer the T at runtime within the implementation. Right now, DefaultFillHoles requests an op which can create the output from the input, performing an unchecked+invalid cast from UnaryFunctionOp<Interval, DoubleType> to UnaryFunctionOp<RandomAccessibleInterval<T>, RandomAccessible<T>> where T extends BooleanType<T>.

Anyway, as a workaround for now, try calling the computer method signature:

<T extends BooleanType<T>> RandomAccessibleInterval<T> fillHoles(RandomAccessibleInterval<T> out, RandomAccessibleInterval<T> in)

You will need to construct a preallocated output image to pass as out.

Hi Curtis

thanks for all the info.
I will have a look at ExtractHoles…

This does not work for me. That’s how I started my attempts to use this op.

Do you by any chance have a 3D dataset? Or the op sees a 2D input as 3D? In that case, the holes in your object would still be connected to the background unless you pad the image with zero-planes at top and bottom. I vaguely remember some discussion about that in the past.

I also ran into similar problems when trying to use ops.convert().bit(img) with a “binary” 8-bit image (the particles sample image) from Fiji.

I tried the following Groovy script:

// @Img img
// @OpService ops
// @OUTPUT Img output

temp = ops.threshold().apply(img, (double) 128.0)
output = ops.morphology().fillHoles(temp);

but that fails with:

No signature of method: net.imagej.ops.threshold.ThresholdNamespace.apply() is applicable for argument types: (net.imagej.DefaultDataset, java.math.BigDecimal) values: [particles.gif, 128.0]
Possible solutions: apply(net.imglib2.IterableInterval, net.imglib2.type.numeric.RealType), apply(java.lang.Iterable, java.lang.Iterable, net.imglib2.type.numeric.RealType), apply(net.imglib2.IterableInterval, net.imglib2.IterableInterval, net.imglib2.type.numeric.RealType), apply(net.imglib2.type.logic.BitType, java.lang.Comparable, java.lang.Object), any(), apply(net.imglib2.type.logic.BitType, java.lang.Object, java.lang.Object, java.util.Comparator)

I wonder why apply(net.imglib2.IterableInterval, net.imglib2.type.numeric.RealType) does not apply here.

1 Like

@tibuch will have a look. Thanks for the detailed debugging!

1 Like

@FelixM thank you for the hint! I just filed this PR.

There was a bug in FloodFill which is used in FillHoles and the exception was triggered by this line, because the default type of createImage is DoubleType.

thanks @tibuch
I will check it out

The error is gone, but the behaviour of the op still puzzles me. Here’s an example of the image:

This is how the op is called:

Img<BitType> fill = ij.op().create().img(fill, new BitType());
ij.op().morphology().fillHoles(fill, mask, ConnectedComponents.StructuringElement.FOUR_CONNECTED, false);

The output is the same as the input. If I call

ij.op().morphology().fillHoles(fill, mask, ConnectedComponents.StructuringElement.FOUR_CONNECTED, true);

Then the output is pitch black. I also tried to invert the mask first, but that should have the same impact as the background flag, right?

Am I using the library wrong? (pom-fiji 26.1.1)

1 Like

Hi @FelixM,

I’m a bit puzzled. The method fill(rai out, rai in, struc struct, _boolean invert_) doesn’t exist anymore in the latest master (see https://github.com/imagej/imagej-ops/blob/master/src/main/java/net/imagej/ops/morphology/MorphologyNamespace.java) and also not in the latest release of imagej-ops. Are you sure, you are using the latest version of ops (https://github.com/imagej/imagej-ops/releases/tag/imagej-ops-0.33.0). Could you try to explicitly override the imagej-ops version in your local pom and run the algorithm again. Just leave the false away.

1 Like

Hi @dietzc

Did that and this works!

I re-checked the poms: So my pom inherits pom-fiji 26.1.1, which in turn inherits from pom-imagej 16.4.1. And that version of the pom-imagj references imagej-ops 0.32.0. So that explains it.

So now I am working with the pom-imagej 16.5.0, given that latest pom-fiji does not reference the latest pom-imagej. Anyway, I don’t need all the other plugins.

Thanks for checking up on this. Should have figured it out earlier.

2 Likes

Glad you figured it out. Just a heads up that we are going to migrate everything to a single pom-scijava bill of materials soon, which should simplify matters and avoid the need to make these sorts of choices in the future.

3 Likes

A post was split to a new topic: op().morphology().fillHoles does nothing