diff --git a/src/main/java/net/imagej/ops/AbstractOpEnvironment.java b/src/main/java/net/imagej/ops/AbstractOpEnvironment.java index f237b4dba6..4c66977621 100644 --- a/src/main/java/net/imagej/ops/AbstractOpEnvironment.java +++ b/src/main/java/net/imagej/ops/AbstractOpEnvironment.java @@ -51,10 +51,15 @@ import net.imagej.ops.stats.StatsNamespace; import net.imagej.ops.thread.ThreadNamespace; import net.imagej.ops.threshold.ThresholdNamespace; +import net.imglib2.Interval; import net.imglib2.IterableInterval; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.neighborhood.RectangleShape; import net.imglib2.algorithm.neighborhood.Shape; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.outofbounds.OutOfBoundsFactory; +import net.imglib2.type.NativeType; import net.imglib2.type.Type; import net.imglib2.type.numeric.RealType; @@ -181,8 +186,9 @@ public , O extends RealType> O convert(final O out, { @SuppressWarnings("unchecked") final IterableInterval result = - (IterableInterval) run(net.imagej.ops.convert.ConvertIterableInterval.class, out, - in, pixConvert); + (IterableInterval) run( + net.imagej.ops.convert.ConvertIterableInterval.class, out, in, + pixConvert); return result; } @@ -223,7 +229,6 @@ public String help(final Namespace namespace) { return result; } - @Override public String help() { final String result = @@ -264,8 +269,8 @@ public Object join(final Object... args) { } @Override - public C join(final C out, final A in, final ComputerOp first, - final ComputerOp second) + public C join(final C out, final A in, + final ComputerOp first, final ComputerOp second) { @SuppressWarnings("unchecked") final C result = @@ -275,8 +280,9 @@ public C join(final C out, final A in, final ComputerOp first, } @Override - public C join(final C out, final A in, final ComputerOp first, - final ComputerOp second, final BufferFactory bufferFactory) + public C join(final C out, final A in, + final ComputerOp first, final ComputerOp second, + final BufferFactory bufferFactory) { @SuppressWarnings("unchecked") final C result = @@ -303,8 +309,8 @@ public A join(final A out, final A in, { @SuppressWarnings("unchecked") final A result = - (A) run(net.imagej.ops.join.DefaultJoinComputers.class, out, in, - ops, bufferFactory); + (A) run(net.imagej.ops.join.DefaultJoinComputers.class, out, in, ops, + bufferFactory); return result; } @@ -420,8 +426,7 @@ public > IterableInterval map( @SuppressWarnings("unchecked") final IterableInterval result = (IterableInterval) run( - net.imagej.ops.map.MapIterableIntervalToView.class, input, op, - type); + net.imagej.ops.map.MapIterableIntervalToView.class, input, op, type); return result; } @@ -489,7 +494,8 @@ public RandomAccessibleInterval map( @SuppressWarnings("unchecked") final RandomAccessibleInterval result = (RandomAccessibleInterval) run( - net.imagej.ops.map.neighborhood.MapNeighborhood.class, out, in, op, shape); + net.imagej.ops.map.neighborhood.MapNeighborhood.class, out, in, op, + shape); return result; } @@ -502,7 +508,74 @@ public RandomAccessibleInterval map( @SuppressWarnings("unchecked") final RandomAccessibleInterval result = (RandomAccessibleInterval) run( - net.imagej.ops.map.neighborhood.MapNeighborhoodWithCenter.class, out, in, func, shape); + net.imagej.ops.map.neighborhood.MapNeighborhoodWithCenter.class, out, + in, func, shape); + return result; + } + + @Override + public , O extends NativeType> ArrayImg map( + ArrayImg out, ArrayImg in, ComputerOp, O> op, + RectangleShape shape) + { + @SuppressWarnings("unchecked") + final ArrayImg result = + (ArrayImg) run( + net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeTypeExtended.class, + out, in, op, shape); + return result; + } + + @Override + public , O extends NativeType> ArrayImg map( + ArrayImg out, ArrayImg in, ComputerOp, O> op, + RectangleShape shape, OutOfBoundsFactory oobFactory) + { + @SuppressWarnings("unchecked") + final ArrayImg result = + (ArrayImg) run( + net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeTypeExtended.class, + out, in, op, shape, oobFactory); + return result; + } + + @Override + public , O extends NativeType> ArrayImg map( + final ArrayImg out, final ArrayImg in, + final CenterAwareComputerOp op, final int span) + { + @SuppressWarnings("unchecked") + final ArrayImg result = + (ArrayImg) run( + net.imagej.ops.map.neighborhood.array.MapNeighborhoodWithCenterNativeType.class, + out, in, op, span); + return result; + } + + @Override + public , O extends NativeType> ArrayImg map( + final ArrayImg out, final ArrayImg in, + final ComputerOp, O> op, final int span) + { + @SuppressWarnings("unchecked") + final ArrayImg result = + (ArrayImg) run( + net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeType.class, + out, in, op, span); + return result; + } + + @Override + public , O extends NativeType> ArrayImg + map(final ArrayImg out, final ArrayImg in, + final ComputerOp, O> op, final int span, + final Interval interval) + { + @SuppressWarnings("unchecked") + final ArrayImg result = + (ArrayImg) run( + net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeType.class, + out, in, op, span, interval); return result; } diff --git a/src/main/java/net/imagej/ops/OpEnvironment.java b/src/main/java/net/imagej/ops/OpEnvironment.java index bf4eec3e82..3c0eb9d54f 100644 --- a/src/main/java/net/imagej/ops/OpEnvironment.java +++ b/src/main/java/net/imagej/ops/OpEnvironment.java @@ -48,10 +48,15 @@ import net.imagej.ops.stats.StatsNamespace; import net.imagej.ops.thread.ThreadNamespace; import net.imagej.ops.threshold.ThresholdNamespace; +import net.imglib2.Interval; import net.imglib2.IterableInterval; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.neighborhood.RectangleShape; import net.imglib2.algorithm.neighborhood.Shape; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.outofbounds.OutOfBoundsFactory; +import net.imglib2.type.NativeType; import net.imglib2.type.Type; import net.imglib2.type.numeric.RealType; @@ -292,12 +297,13 @@ public interface OpEnvironment extends Contextual { /** Executes the "join" operation on the given arguments. */ @OpMethod(op = net.imagej.ops.join.DefaultJoinComputerAndComputer.class) - C join(C out, A in, ComputerOp first, ComputerOp second); + C + join(C out, A in, ComputerOp first, ComputerOp second); /** Executes the "join" operation on the given arguments. */ @OpMethod(op = net.imagej.ops.join.DefaultJoinComputerAndComputer.class) - C join(C out, A in, ComputerOp first, ComputerOp second, - BufferFactory bufferFactory); + C join(C out, A in, ComputerOp first, + ComputerOp second, BufferFactory bufferFactory); /** Executes the "join" operation on the given arguments. */ @OpMethod(op = net.imagej.ops.join.DefaultJoinInplaceAndInplace.class) @@ -399,6 +405,45 @@ RandomAccessibleInterval map(RandomAccessibleInterval out, RandomAccessibleInterval in, CenterAwareComputerOp, O> op, Shape shape); + /** Executes the "map" operation on the given arguments. */ + @OpMethod( + op = net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeTypeExtended.class) + public + , O extends NativeType> ArrayImg map( + ArrayImg out, ArrayImg in, ComputerOp, O> op, + RectangleShape shape); + + /** Executes the "map" operation on the given arguments. */ + @OpMethod( + op = net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeTypeExtended.class) + public + , O extends NativeType> ArrayImg map( + ArrayImg out, ArrayImg in, ComputerOp, O> op, + RectangleShape shape, OutOfBoundsFactory oobFactory); + + /** Executes the "map" operation on the given arguments. */ + @OpMethod( + op = net.imagej.ops.map.neighborhood.array.MapNeighborhoodWithCenterNativeType.class) + public + , O extends NativeType> ArrayImg map( + final ArrayImg out, final ArrayImg in, + final CenterAwareComputerOp op, final int span); + + /** Executes the "map" operation on the given arguments. */ + @OpMethod( + op = net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeType.class) + public , O extends NativeType> ArrayImg map( + final ArrayImg out, final ArrayImg in, + final ComputerOp, O> op, final int span); + + /** Executes the "map" operation on the given arguments. */ + @OpMethod( + op = net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeType.class) + public , O extends NativeType> ArrayImg + map(final ArrayImg out, final ArrayImg in, + final ComputerOp, O> op, final int span, + final Interval interval); + /** Executes the "map" operation on the given arguments. */ @OpMethod(op = net.imagej.ops.map.MapIterableToIterable.class) Iterable map(Iterable out, Iterable in, ComputerOp op); diff --git a/src/main/java/net/imagej/ops/map/neighborhood/array/MapNeighborhoodNativeType.java b/src/main/java/net/imagej/ops/map/neighborhood/array/MapNeighborhoodNativeType.java new file mode 100644 index 0000000000..e67a45770c --- /dev/null +++ b/src/main/java/net/imagej/ops/map/neighborhood/array/MapNeighborhoodNativeType.java @@ -0,0 +1,135 @@ + +package net.imagej.ops.map.neighborhood.array; + +import net.imagej.ops.ComputerOp; +import net.imagej.ops.Contingent; +import net.imagej.ops.Op; +import net.imagej.ops.Ops; +import net.imagej.ops.map.AbstractMapComputer; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.type.NativeType; + +import org.scijava.Priority; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +/** + * Optimized neighborhood map implementation for 1D/2D/3D {@link Img}. This + * implementation uses access to the underlying types, which bypasses + * OutOfBounds checks, though. This means that pixels which are out of bounds + * are not considered as belonging to the neighborhood of a pixel. This can + * change results of averages over a neighborhood in comparison to using an out + * of bounds strategy which "creates" pixels in the neighborhood where there are + * none after the bounds of the image. + * + * @author Jonathan Hale + * @param Input {@link NativeType} + * @param Ouput {@link NativeType} + * @see MapNeighborhoodWithCenterNativeType + */ +@Plugin(type = Op.class, name = Ops.Map.NAME, + priority = Priority.LOW_PRIORITY + 10) +public class MapNeighborhoodNativeType, O extends NativeType> + extends AbstractMapComputer, O, ArrayImg, ArrayImg> + implements Contingent +{ + + /** + * Span of the centered rectangle shape to use. + */ + @Parameter + private int span; + + /** + * Interval for input and output images to constrain interation to. + */ + @Parameter(required = false) + private Interval interval; + + @Override + public void compute(final ArrayImg input, final ArrayImg output) { + final I in = input.firstElement(); + final O out = output.firstElement(); + + final int width = (int) input.dimension(0); + final int height = + (input.numDimensions() > 1) ? (int) input.dimension(1) : 1; + final int depth = + (input.numDimensions() > 2) ? (int) input.dimension(2) : 1; + + final Interval i = (interval == null) ? input : interval; + + final int minX = (int) i.min(0); + final int minY = (i.numDimensions() > 1) ? (int) i.min(1) : 0; + final int minZ = (i.numDimensions() > 2) ? (int) i.min(2) : 0; + + final int maxX = (int) i.max(0); + final int maxY = (i.numDimensions() > 1) ? (int) i.max(1) : 0; + final int maxZ = (i.numDimensions() > 2) ? (int) i.max(2) : 0; + + final int skipX = width - (maxX - minX + 1); + final int skipY = width * (height - (maxY - minY + 1)); + + final ComputerOp, O> op = getOp(); + + int index = minX + minY * width + minZ * height * width; + in.updateIndex(index); + out.updateIndex(index); + + for (int z = minZ; z <= maxZ; ++z) { + for (int y = minY; y <= maxY; ++y) { + for (int x = minX; x <= maxX; ++x) { + // save the current index, since it will be changed by the + // NeighborhoodIterable. Increment to save doing that later. + index = in.getIndex() + 1; + + final Iterable neighborhood = + new NeighborhoodIterableNativeType(in, x, y, z, width, height, + depth, span); + + op.compute(neighborhood, out); + + in.updateIndex(index); + out.incIndex(); + } + in.incIndex(skipX); + out.incIndex(skipX); + } + in.incIndex(skipY); + out.incIndex(skipY); + } + } + + /** + * @return The interval which constraints iteration or null, if + * none is set. + */ + public Interval getInterval() { + return interval; + } + + /** + * Set the interval which constraints iteration. + */ + public void setInterval(Interval i) { + interval = i; + } + + /** + * Convenience method to set the interval which constraints iteration. + * + * @see #setInterval(Interval) + */ + public void setInterval(long[] min, long[] max) { + interval = new FinalInterval(min, max); + } + + @Override + public boolean conforms() { + return getInput().numDimensions() > 0 || getInput().numDimensions() <= 3; + } + +} diff --git a/src/main/java/net/imagej/ops/map/neighborhood/array/MapNeighborhoodNativeTypeExtended.java b/src/main/java/net/imagej/ops/map/neighborhood/array/MapNeighborhoodNativeTypeExtended.java new file mode 100644 index 0000000000..83105b6a97 --- /dev/null +++ b/src/main/java/net/imagej/ops/map/neighborhood/array/MapNeighborhoodNativeTypeExtended.java @@ -0,0 +1,233 @@ + +package net.imagej.ops.map.neighborhood.array; + +import java.util.ArrayList; +import java.util.concurrent.Future; + +import net.imagej.ops.ComputerOp; +import net.imagej.ops.Contingent; +import net.imagej.ops.OpService; +import net.imagej.ops.Ops; +import net.imagej.ops.map.AbstractMapComputer; +import net.imagej.ops.map.neighborhood.MapNeighborhood; +import net.imglib2.FinalInterval; +import net.imglib2.algorithm.neighborhood.RectangleShape; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.outofbounds.OutOfBoundsFactory; +import net.imglib2.type.NativeType; +import net.imglib2.view.Views; + +import org.scijava.Cancelable; +import org.scijava.Priority; +import org.scijava.log.LogService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.thread.ThreadService; + +/** + * Optimized implementation of MapNeighborhood which uses + * {@link MapNeighborhoodNativeType} for a center interval which does not + * require out of bounds checks and {@link MapNeighborhood} for external + * intervals. + * + * @author Jonathan Hale + * @param Input {@link NativeType} + * @param Ouput {@link NativeType} + * @see MapNeighborhoodWithCenterNativeType + * @see MapNeighborhood + */ +@Plugin(type = net.imagej.ops.Op.class, name = Ops.Map.NAME, + priority = Priority.LOW_PRIORITY + 10) +public class MapNeighborhoodNativeTypeExtended, O extends NativeType, Op extends ComputerOp> + extends AbstractMapComputer, O, ArrayImg, ArrayImg> + implements Contingent, Cancelable +{ + + @Parameter + protected ThreadService threadService; + + @Parameter + protected OpService ops; + + @Parameter + protected LogService log; + + @Parameter + private RectangleShape shape; + + @Parameter(required = false) + private OutOfBoundsFactory oobFactory; + + @Override + public void compute(final ArrayImg input, final ArrayImg output) { + // all dimensions are equal as ensured in conforms() */ + final int span = shape.getSpan(); + + final int numDimensions = input.numDimensions(); + + // calculate intervals + FinalInterval[] intervals; + FinalInterval center; + final long dim0 = input.dimension(0); + final long max0 = input.max(0); + final long maxSafe0 = max0 - span; // maximal extension of horizontally + // centered intervals + final long spanPlus1 = span + 1; + /* Note about ordering of intervals: + * Since done parallel, the idea is to queue work by decreasing size. + * The center interval is usually the biggest, but can be best optimized. + * Following: front/back, top/bottom, left/right. (Depending on dimensions, + * some might not be available.) + * + * The intervals were chosen intentionally to optimize access to storage, + * which is expected to be in order of dimensions (0 first, then 1, ...). + * + * The order for queuing the tasks is: Unoptimized intervals in order of size, + * then optimized center interval + */ + /* build intervals */ + if (numDimensions == 1) { + intervals = new FinalInterval[] { + /* left */ + new FinalInterval(new long[] { 0 }, new long[] { span }), + /* right */ + new FinalInterval(new long[] { dim0 - span }, new long[] { max0 }) }; + /* center */ + center = + new FinalInterval(new long[] { spanPlus1 }, new long[] { maxSafe0 }); + } + else if (numDimensions == 2) { + final long dim1 = input.dimension(1); + final long max1 = input.max(1); + final long maxSafe1 = max1 - span; // maximal extension of vertically + // centered intervals + + intervals = + new FinalInterval[] { + /* top */ + new FinalInterval(new long[] { 0, 0 }, new long[] { max0, span }), + /* bottom */ + new FinalInterval(new long[] { 0, dim1 - span }, new long[] { max0, + max1 }), + /* left */ + new FinalInterval(new long[] { 0, spanPlus1 }, new long[] { span, + max1 - span }), + /* right */ + new FinalInterval(new long[] { dim0 - span, spanPlus1 }, new long[] { + max0, maxSafe1 }) }; + /* center */ + center = + new FinalInterval(new long[] { spanPlus1, spanPlus1 }, new long[] { + maxSafe0, maxSafe1 }); + } + else { + // numDimensions == 3, guaranteed by conforms() + + final long dim1 = input.dimension(1); + final long dim2 = input.dimension(2); + final long max1 = input.max(1); + final long max2 = input.max(2); + final long maxSafe1 = max1 - span; // maximal extension of vertically + // centered intervals + final long maxSafe2 = max2 - span; // maximal extension of depth + // centered intervals + intervals = + new FinalInterval[] { + /* front */ + new FinalInterval(new long[] { 0, 0, 0 }, new long[] { max0, max1, + span }), + /* back */ + new FinalInterval(new long[] { 0, 0, dim2 - span }, new long[] { + max0, max1, max2 }), + /* top */ + new FinalInterval(new long[] { 0, 0, spanPlus1 }, new long[] { max0, + span, maxSafe2 }), + /* bottom */ + new FinalInterval(new long[] { 0, dim1 - span, spanPlus1 }, + new long[] { max0, max1, maxSafe2 }), + /* left */ + new FinalInterval(new long[] { 0, spanPlus1, spanPlus1 }, new long[] { + span, maxSafe1, maxSafe2 }), + /* right */ + new FinalInterval(new long[] { dim0 - span, spanPlus1, spanPlus1 }, + new long[] { max0, maxSafe1, maxSafe2 }) }; + /* center */ + center = + new FinalInterval(new long[] { spanPlus1, spanPlus1, spanPlus1 }, + new long[] { maxSafe0, maxSafe1, maxSafe2 }); + } + + ArrayList> futures = + new ArrayList>(2 * numDimensions + 1); + + for (final FinalInterval interval : intervals) { + futures.add(threadService.run(new Runnable() { + + @Override + public void run() { + if (oobFactory == null) { + ops.run(MapNeighborhood.class, Views.interval(output, interval), + Views.interval(input, interval), getOp(), shape); + } + else { + ops.run(MapNeighborhood.class, Views.interval(output, interval), + Views.interval(input, interval), getOp(), shape, oobFactory); + } + } + })); + + } + + final FinalInterval finalCenter = center; + futures.add(threadService.run(new Runnable() { + + @Override + public void run() { + ops.run(MapNeighborhoodNativeType.class, output, input, getOp(), span, + finalCenter); + } + })); + + // wait for tasks to complete + for (final Future future : futures) { + try { + if (isCanceled()) { + break; + } + future.get(); + } + catch (final Exception e) { + log.error(e); + cancel(e.getMessage()); + break; + } + } + } + + @Override + public boolean conforms() { + return getInput().numDimensions() > 0 && getInput().numDimensions() <= 3 && + !shape.isSkippingCenter(); + } + + // --- Cancelable methods --- + + private String cancelReason = null; + private boolean canceled; + + @Override + public boolean isCanceled() { + return canceled; + } + + @Override + public void cancel(String reason) { + cancelReason = reason; + canceled = true; + } + + @Override + public String getCancelReason() { + return cancelReason; + } +} diff --git a/src/main/java/net/imagej/ops/map/neighborhood/array/MapNeighborhoodWithCenterNativeType.java b/src/main/java/net/imagej/ops/map/neighborhood/array/MapNeighborhoodWithCenterNativeType.java new file mode 100644 index 0000000000..d730d19031 --- /dev/null +++ b/src/main/java/net/imagej/ops/map/neighborhood/array/MapNeighborhoodWithCenterNativeType.java @@ -0,0 +1,84 @@ + +package net.imagej.ops.map.neighborhood.array; + +import net.imagej.ops.Contingent; +import net.imagej.ops.Op; +import net.imagej.ops.Ops; +import net.imagej.ops.map.neighborhood.AbstractMapCenterAwareComputer; +import net.imagej.ops.map.neighborhood.CenterAwareComputerOp; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImg; +import net.imglib2.type.NativeType; +import net.imglib2.util.ValuePair; + +import org.scijava.Priority; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +/** + * Optimized center aware neighborhood map implementation for 1D/2D/3D + * {@link Img}. This implementation uses access to the underlying types, which + * bypasses OutOfBounds checks, though. This means that pixels which are out of + * bounds are not considered as belonging to the neighborhood of a pixel. This + * can change results of averages over a neighborhood in comparison to using an + * out of bounds strategy which "creates" pixels in the neighborhood where there + * are none after the bounds of the image. + * + * @author Jonathan Hale + * @param Input {@link NativeType} + * @param Ouput {@link NativeType} + * @see MapNeighborhoodNativeType + */ +@Plugin(type = Op.class, name = Ops.Map.NAME, + priority = Priority.LOW_PRIORITY + 10) +public class MapNeighborhoodWithCenterNativeType, O extends NativeType> + extends AbstractMapCenterAwareComputer, ArrayImg> + implements Contingent +{ + + @Parameter + private int span; + + @Override + public void compute(final ArrayImg input, final ArrayImg output) { + final I in = input.firstElement(); + final O out = output.firstElement(); + + final int width = (int) input.dimension(0); + final int height = (int) input.dimension(1); + final int depth = Math.max(1, (int) input.dimension(2)); + + final CenterAwareComputerOp op = getOp(); + + int index; + + for (int z = 0; z < depth; ++z) { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // save the current index, since it will be changed by the + // NeighborhoodIterable. Increment to save doing that later. + index = in.getIndex() + 1; + + // copy for center pixel access, since it will get changed, again, by + // NeighborhoodIterable. + final I center = in.copy(); + + final Iterable neighborhood = + new NeighborhoodIterableNativeType(in, x, y, z, width, height, + depth, span); + + op.compute(new ValuePair>(center, neighborhood), out); + + in.updateIndex(index); + out.incIndex(); + } + } + } + } + + @Override + public boolean conforms() { + return getInput().numDimensions() > 0 || getInput().numDimensions() <= 3; + } + +} diff --git a/src/main/java/net/imagej/ops/map/neighborhood/array/NeighborhoodIterableNativeType.java b/src/main/java/net/imagej/ops/map/neighborhood/array/NeighborhoodIterableNativeType.java new file mode 100644 index 0000000000..6bbdf15fdd --- /dev/null +++ b/src/main/java/net/imagej/ops/map/neighborhood/array/NeighborhoodIterableNativeType.java @@ -0,0 +1,161 @@ + +package net.imagej.ops.map.neighborhood.array; + +import java.util.Iterator; + +import net.imglib2.type.NativeType; + +/** + * Optimized rectangle neighborhood {@link Iterable} for {@link NativeType} + * which ignores out of bounds pixels. + * + * @param Type of the contents of the Iterable. + * @author Jonathan Hale + */ +final class NeighborhoodIterableNativeType> implements + Iterable +{ + + private final I pointer; + private final int neighSize; + private final int hDiameter; + private final int vDiameter; + private final int startIndex; + private final int nextLineSkip; + private final int nextSliceSkip; + + /* whether this is a 3D neighborhood */ + private final boolean volume; + + /** + * Constructor + * + * @param pointer NativeType to use as "cursor" + * @param x Left bounds of the rectangle neighborhood + * @param y Top bounds of the rectangle neighborhood + * @param z Front bounds of the rectangle neighborhood + * @param w Width of the rectangle neighborhood + * @param h Height of the rectangle neighborhood + * @param d Depth of the rectangle neighborhood + * @param span Span of the neighborhood (to avoid redundant calculation) + */ + public NeighborhoodIterableNativeType(final I pointer, final int x, + final int y, final int z, final int w, final int h, final int d, + final int span) + { + // clamp extensions in every direction to ensure we won't go out of bounds + final int left = Math.min(x, span); + final int top = Math.min(y, span); + final int right = Math.min(w - 1 - x, span); + final int bottom = Math.min(h - 1 - y, span); + final int front = Math.min(z, span); + final int back = Math.min(d - 1 - z, span); + + this.hDiameter = left + right + 1; + this.vDiameter = top + bottom + 1; + final int dDiameter = back + front + 1; + this.pointer = pointer; + this.neighSize = hDiameter * vDiameter * dDiameter; + + pointer.decIndex(front * w * h + top * w + left + 1); + + this.startIndex = pointer.getIndex(); + + this.nextLineSkip = w - (hDiameter - 1); + this.nextSliceSkip = (h - vDiameter) * hDiameter; + + this.volume = vDiameter != 1; + } + + @Override + public final Iterator iterator() { + return (volume) ? new MapNeighborhoodIterator3D() + : new MapNeighborhoodIterator2D(); + } + + /** + * Iterator over a rectangular neighborhood. + * + * @author Jonathan Hale + */ + private final class MapNeighborhoodIterator2D implements Iterator { + + public MapNeighborhoodIterator2D() { + pointer.updateIndex(startIndex); + } + + int index = 0; + int x = -1; + + @Override + public final boolean hasNext() { + return index < neighSize; + } + + @Override + public final I next() { + ++index; + ++x; + + if (x == hDiameter) { + // end of line, skip pixels until next line + pointer.incIndex(nextLineSkip); + x = 0; + } + else { + pointer.incIndex(); + } + + return pointer; + } + } + + /** + * Iterator over a rectangular neighborhood. + * + * @author Jonathan Hale + */ + private final class MapNeighborhoodIterator3D implements Iterator { + + public MapNeighborhoodIterator3D() { + pointer.updateIndex(startIndex); + } + + int index = 0; + int x = -1; + int y = -1; + + @Override + public final boolean hasNext() { + return index < neighSize; + } + + @Override + public final I next() { + ++index; + ++x; + + if (x == hDiameter) { + // end of line, skip pixels until next line + pointer.incIndex(nextLineSkip); + x = 0; + ++y; + if (y == vDiameter) { + // end of slice, skip pixels until next slice + pointer.incIndex(nextSliceSkip); + y = 0; + } + } + else { + pointer.incIndex(); + } + + return pointer; + } + + @Override + public final void remove() { + // noop + } + } +} diff --git a/src/test/java/net/imagej/ops/map/neighborhood/MapNeighborhoodTest.java b/src/test/java/net/imagej/ops/map/neighborhood/MapNeighborhoodTest.java index a1d55407b9..aab407e36d 100644 --- a/src/test/java/net/imagej/ops/map/neighborhood/MapNeighborhoodTest.java +++ b/src/test/java/net/imagej/ops/map/neighborhood/MapNeighborhoodTest.java @@ -6,6 +6,9 @@ import net.imagej.ops.AbstractComputerOp; import net.imagej.ops.AbstractOpTest; import net.imagej.ops.Op; +import net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeType; +import net.imagej.ops.map.neighborhood.array.MapNeighborhoodNativeTypeExtended; +import net.imagej.ops.map.neighborhood.array.MapNeighborhoodWithCenterNativeType; import net.imglib2.algorithm.neighborhood.RectangleShape; import net.imglib2.img.Img; import net.imglib2.type.numeric.integer.ByteType; @@ -83,6 +86,179 @@ public void testMapNeighborhoodsWithCenterAccess() { } } + /** + * Test if every neighborhood pixel of the 2D image was really accessed during + * the map operation. + * + * @see MapNeighborhoodNativeType + */ + @Test + public void testMapNeighborhoodsArrayImage2D() { + final Op functional = + ops.op(MapNeighborhoodNativeType.class, out, in, + new CountNeighborsWithAccess(), 1); + functional.run(); + + final byte[] expected = + new byte[] { 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, + 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4 }; + + int index = 0; + for (ByteType t : out) { + assertEquals("Index " + index + ": ", expected[index++], t.get()); + } + } + + /** + * Test if every neighborhood pixel of the 2D image was really accessed during + * the map operation. + * + * @see MapNeighborhoodNativeTypeExtended + */ + @Test + public void testMapNeighborhoodsArrayImageAlias2D() { + in = generateByteTestImg(true, 7, 7); + out = generateByteTestImg(false, 7, 7); + + final Op functional = + ops.op(MapNeighborhoodNativeTypeExtended.class, out, in, + new CountNeighbors(), new RectangleShape(1, false)); + functional.run(); + + int index = 0; + for (ByteType t : out) { + assertEquals("Index " + index + ": ", 9, t.get()); + index++; + } + } + + /** + * Test if every neighborhood pixel of the 1D image was really accessed during + * the map operation. + * + * @see MapNeighborhoodNativeType + */ + @Test + public void testMapNeighborhoodsArrayImage1D() { + in = generateByteTestImg(true, 7); + out = generateByteTestImg(false, 7); + + final Op functional = + ops.op(MapNeighborhoodNativeType.class, out, in, + new CountNeighborsWithAccess(), 1); + functional.run(); + + final byte[] expected = new byte[] { 2, 3, 3, 3, 3, 3, 2 }; + + int index = 0; + for (ByteType t : out) { + assertEquals("Index " + index + ": ", expected[index++], t.get()); + } + } + + /** + * Test if every neighborhood pixel of the 1D image was really accessed during + * the map operation. + * + * @see MapNeighborhoodNativeTypeExtended + */ + @Test + public void testMapNeighborhoodsArrayImageAlias1D() { + in = generateByteTestImg(true, 7); + out = generateByteTestImg(false, 7); + + final Op functional = + ops.op(MapNeighborhoodNativeTypeExtended.class, out, in, + new CountNeighbors(), new RectangleShape(1, false)); + functional.run(); + + int index = 0; + for (ByteType t : out) { + assertEquals("Index " + index + ": ", 3, t.get()); + index++; + } + } + + /** + * Test if every neighborhood pixel of the 3D image was really accessed during + * the map operation. + * + * @see MapNeighborhoodNativeType + */ + @Test + public void testMapNeighborhoodsArrayImage3D() { + in = generateByteTestImg(true, 3, 3, 3); + out = generateByteTestImg(false, 3, 3, 3); + + final Op functional = + ops.op(MapNeighborhoodNativeType.class, out, in, + new CountNeighborsWithAccess(), 1); + functional.run(); + + final byte[] expected = + new byte[] { 8, 12, 8, 12, 18, 12, 8, 12, 8, 12, 18, 12, 18, 27, 18, 12, + 18, 12, 8, 12, 8, 12, 18, 12, 8, 12, 8 }; + + int index = 0; + for (ByteType t : out) { + assertEquals("Index " + index + ": ", expected[index++], t.get()); + } + } + + /** + * Test if every neighborhood pixel of the 3D image was really accessed during + * the map operation. + * + * @see MapNeighborhoodNativeTypeExtended + */ + @Test + public void testMapNeighborhoodsArrayImageAlias3D() { + in = generateByteTestImg(true, 7, 7, 7); + out = generateByteTestImg(false, 7, 7, 7); + + final Op functional = + ops.op(MapNeighborhoodNativeTypeExtended.class, out, in, + new CountNeighbors(), new RectangleShape(1, false)); + functional.run(); + + int index = 0; + for (ByteType t : out) { + assertEquals("Index " + index + ": ", 27, t.get()); + index++; + } + } + + /** + * Test if every neighborhood pixel of the 2D image was really accessed during + * the map operation. + * + * @see MapNeighborhoodWithCenterNativeType + */ + @Test + public void testMapNeighborhoodsWithCenterAccessArrayImage2D() { + final Op functional = + ops.op(MapNeighborhoodWithCenterNativeType.class, out, in, + new CountNeighborsWithAccessWithCenter(), 1); + functional.run(); + + final byte[] expected = + new byte[] { 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 9, 9, 9, 9, 9, 9, 9, 9, + 9, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, + 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 6, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4 }; + + int index = 0; + for (ByteType t : out) { + assertEquals("Index " + index + ": ", expected[index++], t.get()); + } + } + + + /** * Function which increments the output value for every pixel in the * neighborhood.