diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/morphology/FillHoles.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/morphology/FillHoles.java index bae166e52..9cc0922bd 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/morphology/FillHoles.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/morphology/FillHoles.java @@ -35,11 +35,13 @@ import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.neighborhood.Shape; import net.imglib2.type.BooleanType; +import net.imglib2.util.Util; import net.imglib2.view.Views; - import org.scijava.function.Computers; import org.scijava.ops.spi.OpDependency; +import java.util.ArrayList; + /** * Fills the holes of a BooleanType image. * @@ -56,50 +58,63 @@ public class FillHoles> implements @OpDependency(name = "morphology.floodFill") private Computers.Arity3, Localizable, Shape, RandomAccessibleInterval> floodFillComp; + @OpDependency(name = "image.fill") + private Computers.Arity1> fillConstant; + /** - * TODO + * Fills holes in {@code input} by: + *
    + *
  1. Setting the entire output to be foreground
  2. + *
  3. Iteratively finding all pixels reachable from the edges, setting them to background in the output
  4. + *
* - * @param input - * @param structElement - * @param output + * @param input the input image + * @param shape a {@link Shape} defining the set of neighbors around each pixel. + * @param output a pre-allocated output buffer. */ @Override public void compute(final RandomAccessibleInterval input, - final Shape structElement, final RandomAccessibleInterval output) + final Shape shape, final RandomAccessibleInterval output) { - final var iterOp = Views.flatIterable(input); - final var iterR = Views.flatIterable(output); - - var dim = new long[output.numDimensions()]; + var dim = new long[output.numDimensions()]; output.dimensions(dim); - var rc = iterR.cursor(); - var opc = iterOp.localizingCursor(); - // Fill with non background marker - while (rc.hasNext()) { - rc.next().setOne(); + + // Set entire output to foreground + T one = Util.getTypeFromInterval(output).copy(); + one.setOne(); + fillConstant.compute(one, output); + + // Iterate over each face + for (var i = 0; i < dim.length; i++) { + // top slice + fillHolesInSlice(input, i, 0, shape, output); + // bottom slice + fillHolesInSlice(input, i, dim[i] - 1, shape, output); } - rc.reset(); - boolean border; - // Flood fill from every background border voxel - while (rc.hasNext()) { - rc.next(); - opc.next(); - if (rc.get().get() && !opc.get().get()) { - border = false; - for (var i = 0; i < output.numDimensions(); i++) { - if (rc.getLongPosition(i) == 0 || rc.getLongPosition(i) == dim[i] - - 1) - { - border = true; - break; - } - } - if (border) { - floodFillComp.compute(input, rc, structElement, output); - } + } + + private void fillHolesInSlice(RandomAccessibleInterval input, int i, long pos, Shape shape, RandomAccessibleInterval output) { + var cursor = Views.hyperSlice(input, i, pos).cursor(); + var raOut = output.randomAccess(); + raOut.setPosition(pos, i); + int dims = input.numDimensions(); + while (cursor.hasNext()) { + cursor.next(); + if (cursor.get().get()){ + // this pixel is not a background pixel + continue; + } + // FIXME This is ugly + for(int j = 0; j < dims - 1; j++) { + raOut.setPosition(cursor.getIntPosition(j), j >= i ? j + 1: j); } + if (!raOut.get().get()){ + // a flood fill already took care of this pixel + continue; + } + floodFillComp.compute(input, raOut, shape, output); } - } + } }