From 426552b174a11142472b9573dc6b9edc28e60352 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Thu, 13 Aug 2026 17:13:11 -0500 Subject: [PATCH 1/2] Widen projection op input Now the project Op can project on a RAI input --- .../project/DefaultProjectParallel.java | 30 +++++++------------ .../project/{project => }/ProjectTest.java | 12 ++------ 2 files changed, 12 insertions(+), 30 deletions(-) rename scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/{project => }/ProjectTest.java (87%) diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/DefaultProjectParallel.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/DefaultProjectParallel.java index 77ee0c60f..4494dc47d 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/DefaultProjectParallel.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/DefaultProjectParallel.java @@ -36,6 +36,7 @@ import net.imglib2.loops.LoopBuilder; import net.imglib2.util.Intervals; +import net.imglib2.view.Views; import org.scijava.function.Computers; /** @@ -44,7 +45,7 @@ * @implNote op name='transform.project', priority='99.' */ public class DefaultProjectParallel implements - Computers.Arity3, Computers.Arity1, V>, Integer, RandomAccessibleInterval> + Computers.Arity3, Computers.Arity1, V>, Integer, RandomAccessibleInterval> { /** @@ -57,9 +58,8 @@ public class DefaultProjectParallel implements */ @Override public void compute(final RandomAccessibleInterval input, - Computers.Arity1, V> op, Integer dim, - final RandomAccessibleInterval output) - { + Computers.Arity1, V> op, Integer dim, + final RandomAccessibleInterval output) { // TODO this first check is too simple, but for now ok if (input.numDimensions() != output.numDimensions() + 1) // throw new IllegalArgumentException( @@ -69,22 +69,12 @@ public void compute(final RandomAccessibleInterval input, "ERROR: input image must contain dimension " + dim); LoopBuilder.setImages(output, Intervals.positions(output)).multiThreaded() - .forEachChunk(chunk -> { - var chunkRA = input.randomAccess(); - chunk.forEachPixel((pixel, position) -> { - for (var d = 0; d < input.numDimensions(); d++) { - if (d != dim) { - chunkRA.setPosition(position.getIntPosition(d - (d > dim ? 1 - : 0)), d); - } - } - - op.compute(new DimensionIterable(input.dimension(dim), dim, chunkRA), - pixel); - - }); - - return null; + .forEachPixel((pixel, position) -> { + var ra = input; + for (var d = 0; d < position.numDimensions(); d++) { + ra = Views.hyperSlice(ra, d < dim ? 0 : 1, position.getIntPosition(d)); + } + op.compute(ra, pixel); }); } diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/project/ProjectTest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/ProjectTest.java similarity index 87% rename from scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/project/ProjectTest.java rename to scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/ProjectTest.java index 0bb4e0c47..bfc3df19a 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/project/ProjectTest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/ProjectTest.java @@ -27,7 +27,7 @@ * #L% */ -package org.scijava.ops.image.transform.project.project; +package org.scijava.ops.image.transform.project; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -50,7 +50,6 @@ public class ProjectTest extends AbstractOpTest { private Img in; private Img out1; private Img out2; - private Computers.Arity1, UnsignedByteType> op; @BeforeEach public void initImg() { @@ -70,18 +69,11 @@ public void initImg() { out1 = TestImgGeneration.unsignedByteArray(false, 10, 10); out2 = TestImgGeneration.unsignedByteArray(false, 10, 10); - - op = OpBuilder.matchComputer(ops, "stats.sum", - new Nil>() - {}, new Nil() {}); } @Test public void testProjector() { - // TODO: uncomment when this Op is ported (assuming it will be?) - // ops.run(DefaultProjectParallel.class, out1, in, op, PROJECTION_DIM); - // ops.run(DefaultProjectParallel.class, out2, in, op, PROJECTION_DIM); - // testEquality(out1, out2); + var op = ops.op("stats.sum").input(in).outType(UnsignedByteType.class).computer(); ops.op("transform.project").input(in, op, PROJECTION_DIM).output(out1) .compute(); From e59977d49d7f38d185a095769435a11140487126 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Fri, 14 Aug 2026 15:31:21 -0500 Subject: [PATCH 2/2] Ensure projection within provided interval Before the changes to the Op, this test failed, because the projection operated on the interval [0, max-min] for an image defined on interval [min, max]. With the changes to the Op, this Op works on [min, max] --- .../image/transform/project/ProjectTest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/ProjectTest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/ProjectTest.java index bfc3df19a..18fa3a6e6 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/ProjectTest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/transform/project/ProjectTest.java @@ -31,6 +31,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import net.imglib2.FinalInterval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.view.Views; import org.scijava.ops.image.AbstractOpTest; import org.scijava.ops.image.util.TestImgGeneration; import net.imglib2.RandomAccess; @@ -82,6 +86,37 @@ public void testProjector() { testEquality(out1, out2); } + /** + * Ensures {@code "transform.project"} runs only within the passed interval. + */ + @Test + public void testInterval() { + // Set up img[x, y, z] = z + var input = ArrayImgs.unsignedBytes(10, 10, 10); + for(int x = 0; x < 10; x++) { + for(int y = 0; y < 10; y++) { + for(int z = 0; z < 10; z++) { + input.getAt(x, y, z).set(z); + } + } + } + // Create an interval containing 2<=z<=4 + var intervaled = Views.interval(input, new FinalInterval(new long[] {0, 0, 2}, new long[] {10, 10, 4})); + + // Project on the interval + var out = ArrayImgs.unsignedBytes(10, 10); + var op = ops.op("stats.sum").input(intervaled).outType(UnsignedByteType.class).computer(); + ops.op("transform.project").input(intervaled, op, PROJECTION_DIM).output(out).compute(); + + // Assert that the projection (summation) only covered z=2, z=3, z=4 + var outCursor = out.cursor(); + while (outCursor.hasNext()) { + // 2 + 3 + 4 = 9 + assertEquals(9, outCursor.next().get()); + } + + } + private void testEquality(final Img img1, final Img img2) {