From 426552b174a11142472b9573dc6b9edc28e60352 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Thu, 13 Aug 2026 17:13:11 -0500 Subject: [PATCH 1/6] 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/6] 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) { From 2138118cc3f746ae09f2a17c7bafd587810f17e9 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 7 Sep 2026 11:34:05 -0500 Subject: [PATCH 3/6] Expand on Project Ops --- .../project/DefaultProjectParallel.java | 10 +- .../project/ProjectParallelFunction.java | 58 +++++++++ .../project/ProjectRAIToIterableInterval.java | 122 ------------------ .../image/transform/project/ProjectTest.java | 60 ++++++++- 4 files changed, 120 insertions(+), 130 deletions(-) create mode 100644 scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelFunction.java delete mode 100644 scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectRAIToIterableInterval.java 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 4494dc47d..2f8eabec1 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 @@ -40,9 +40,13 @@ import org.scijava.function.Computers; /** - * @param - * @param - * @implNote op name='transform.project', priority='99.' + * Projection is the act of creating 1-dimensional slices of an n-dimensional image, + * reducing that slice down to a single value, and combining those images back into a (n-1)-dimensional array + * + * @param the type of input image elements + * @param the type of output image elements + * @implNote op name='transform.project', priority='99.', hints="adaptation.FORBIDDEN" + * @see ProjectParallelFunction for an Op that creates its own output */ public class DefaultProjectParallel implements Computers.Arity3, Computers.Arity1, V>, Integer, RandomAccessibleInterval> diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelFunction.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelFunction.java new file mode 100644 index 000000000..1d242d335 --- /dev/null +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelFunction.java @@ -0,0 +1,58 @@ +package org.scijava.ops.image.transform.project.project; + +import net.imglib2.FinalDimensions; +import net.imglib2.RandomAccessibleInterval; +import org.scijava.function.Computers; +import org.scijava.function.Functions; +import org.scijava.function.Producer; +import org.scijava.ops.spi.OpDependency; + +import java.util.function.BiFunction; + +/** + * Wraps {@link DefaultProjectParallel}, but creates a new output image in the process. + * @param the type of input image elements + * @param the type of output image elements + * @author Gabriel Selzer + * @implNote op name='transform.project', priority='99.' + */ +public class ProjectParallelFunction implements + Functions.Arity3, Computers.Arity1, V>, Integer, RandomAccessibleInterval> +{ + @OpDependency(name="transform.project") + Computers.Arity3, Computers.Arity1, V>, Integer, RandomAccessibleInterval> projector; + + @OpDependency(name="transform.translateView") + BiFunction, long[], RandomAccessibleInterval> translator; + + @OpDependency(name="create.type") + Producer typeCreator; + + @OpDependency(name="create.img") + BiFunction> creator; + + + /** + * + * @param input the input {@code n}-dimensional dataset + * @param op the Op to project over {@code dim} + * @param dim the dimension along {@code input} to project + * @return a {@code n-1}-dimensional dataset + */ + @Override + public RandomAccessibleInterval apply(RandomAccessibleInterval input, Computers.Arity1, V> op, Integer dim) { + var dims = new long[input.numDimensions() - 1]; + var min = new long[input.numDimensions() - 1]; + for(int i = 0; i < input.numDimensions() - 1; i++) { + dims[i] = input.dimension(i >= dim ? i+1 : i); + min[i] = input.min(i >= dim ? i+1 : i); + } + // Get an arbitrary instance of the output type + var outImg = creator.apply(new FinalDimensions(dims), typeCreator.create()); + // translate by the minimum of the input img + var translated = translator.apply(outImg, min); + + projector.compute(input, op, dim, translated); + return translated; + } +} diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectRAIToIterableInterval.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectRAIToIterableInterval.java deleted file mode 100644 index 12b31fb84..000000000 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectRAIToIterableInterval.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * #%L - * Image processing operations for SciJava Ops. - * %% - * Copyright (C) 2014 - 2025 SciJava developers. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package org.scijava.ops.image.transform.project.project; - -import java.util.Iterator; - -import net.imglib2.Cursor; -import net.imglib2.IterableInterval; -import net.imglib2.RandomAccess; -import net.imglib2.RandomAccessibleInterval; - -import org.scijava.function.Computers; - -/** - * @implNote op names='transform.project', priority='-100.' - */ -public class ProjectRAIToIterableInterval implements - Computers.Arity3, Computers.Arity1, V>, Integer, IterableInterval> -{ - - /** - * TODO - * - * @param input - * @param op - * @param dim - * @param output - */ - @Override - public void compute(final RandomAccessibleInterval input, - final Computers.Arity1, V> op, final Integer dim, - final IterableInterval output) - { - if (input.numDimensions() != output.numDimensions() + 1) - throw new IllegalArgumentException( - "Input must have one more dimension than output!"); - if (dim >= input.numDimensions()) throw new IllegalArgumentException( - "The dimension provided to compute over does not exist in the input!"); - - final var cursor = output.localizingCursor(); - final var access = input.randomAccess(); - - while (cursor.hasNext()) { - cursor.fwd(); - for (var d = 0; d < input.numDimensions(); d++) { - if (d != dim) { - access.setPosition(cursor.getIntPosition(d - (d > dim ? -1 : 0)), d); - } - } - - op.compute(new DimensionIterable(input.dimension(dim), access, dim), - cursor.get()); - } - } - - final class DimensionIterable implements Iterable { - - private final long size; - private final RandomAccess access; - private final int dim; - - public DimensionIterable(final long size, final RandomAccess access, - final int dim) - { - this.size = size; - this.access = access; - this.dim = dim; - } - - @Override - public Iterator iterator() { - return new Iterator() { - - int k = -1; - - @Override - public boolean hasNext() { - return k < size - 1; - } - - @Override - public T next() { - k++; - access.setPosition(k, dim); - return access.get(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Not supported"); - } - }; - } - } -} 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 18fa3a6e6..a67ffe18e 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 @@ -29,8 +29,6 @@ package org.scijava.ops.image.transform.project; -import static org.junit.jupiter.api.Assertions.assertEquals; - import net.imglib2.FinalInterval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.array.ArrayImgs; @@ -47,6 +45,11 @@ import org.scijava.ops.api.OpBuilder; import org.scijava.types.Nil; +import java.util.Random; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.*; + public class ProjectTest extends AbstractOpTest { private final int PROJECTION_DIM = 2; @@ -62,9 +65,9 @@ public void initImg() { final RandomAccess randomAccess = in.randomAccess(); // at each x,y,z fill with x+y - for (int x = 0; x < 10; x++) { - for (int y = 0; y < 10; y++) { - for (int z = 0; z < 10; z++) { + for (int x = 0; x < in.dimension(0); x++) { + for (int y = 0; y < in.dimension(1); y++) { + for (int z = 0; z < in.dimension(2); z++) { randomAccess.setPosition(new long[] { x, y, z }); randomAccess.get().setReal(x + y); } @@ -114,7 +117,54 @@ public void testInterval() { // 2 + 3 + 4 = 9 assertEquals(9, outCursor.next().get()); } + } + + /** + * Ensures {@code "transform.project"} can run as a {@link Function} + */ + @Test + public void testFunction() { + var op = ops.op("stats.sum").input(in).outType(UnsignedByteType.class).computer(); + + RandomAccessibleInterval output = ops.op("transform.project").input(in, op, 2).outType(new Nil>() {}).apply(); + + for (int x = 0; x < output.dimension(0); x++) { + for (int y = 0; y < output.dimension(1); y++) { + assertEquals(in.dimension(2) * (x + y), output.getAt(x, y).getIntegerLong()); + } + } + } + /** + * Ensures {@code "transform.project"} runs only within the passed interval. + */ + @Test + public void testFunctionInterval() { + // 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 1<=x<=3, 1<=y<=4, 2<=z<=4 + var intervaled = Views.interval(input, new FinalInterval(new long[] {1, 1, 2}, new long[] {3, 4, 4})); + + // Project on the interval + var op = ops.op("stats.sum").input(intervaled).outType(UnsignedByteType.class).computer(); + var out = ops.op("transform.project").input(intervaled, op, PROJECTION_DIM) + .outType(new Nil>() {}).apply(); + + // 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()); + } + assertArrayEquals(new long[] {1, 1}, out.minAsLongArray()); + assertArrayEquals(new long[] {3, 4}, out.maxAsLongArray()); } private void testEquality(final Img img1, From 1e5c4e3389d91e0bec2b72260b51f80ca0f4c45d Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 7 Sep 2026 11:57:53 -0500 Subject: [PATCH 4/6] Clean up comments --- ...ectParallel.java => ProjectParallelComputer.java} | 12 ++++++------ .../project/project/ProjectParallelFunction.java | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) rename scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/{DefaultProjectParallel.java => ProjectParallelComputer.java} (91%) 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/ProjectParallelComputer.java similarity index 91% rename from scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/DefaultProjectParallel.java rename to scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelComputer.java index 2f8eabec1..d3a2a48dd 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/ProjectParallelComputer.java @@ -48,17 +48,17 @@ * @implNote op name='transform.project', priority='99.', hints="adaptation.FORBIDDEN" * @see ProjectParallelFunction for an Op that creates its own output */ -public class DefaultProjectParallel implements +public class ProjectParallelComputer implements Computers.Arity3, Computers.Arity1, V>, Integer, RandomAccessibleInterval> { /** - * TODO + * Projects {@code op} along 1-dimensional slices (along dimension {@code dim}) of {@code input} * - * @param input - * @param op - * @param dim - * @param output + * @param input the input {@code n}-dimensional dataset + * @param op the Op to project over {@code dim} + * @param dim the dimension along {@code input} to project + * @param output the output {@code n-1}-dimensional dataset */ @Override public void compute(final RandomAccessibleInterval input, diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelFunction.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelFunction.java index 1d242d335..5780c3ee0 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelFunction.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelFunction.java @@ -10,7 +10,7 @@ import java.util.function.BiFunction; /** - * Wraps {@link DefaultProjectParallel}, but creates a new output image in the process. + * Wraps {@link ProjectParallelComputer}, but creates a new output image in the process. * @param the type of input image elements * @param the type of output image elements * @author Gabriel Selzer @@ -33,6 +33,7 @@ public class ProjectParallelFunction implements /** + * Projects {@code op} along 1-dimensional slices (along dimension {@code dim}) of {@code input} * * @param input the input {@code n}-dimensional dataset * @param op the Op to project over {@code dim} From 52c11f460ee16c06fff2d5d6798d0fae5fea05ae Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 7 Sep 2026 12:06:18 -0500 Subject: [PATCH 5/6] Use Ops for slicing --- .../transform/project/project/ProjectParallelComputer.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelComputer.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelComputer.java index d3a2a48dd..f307abba4 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelComputer.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelComputer.java @@ -38,6 +38,8 @@ import net.imglib2.view.Views; import org.scijava.function.Computers; +import org.scijava.function.Functions; +import org.scijava.ops.spi.OpDependency; /** * Projection is the act of creating 1-dimensional slices of an n-dimensional image, @@ -52,6 +54,9 @@ public class ProjectParallelComputer implements Computers.Arity3, Computers.Arity1, V>, Integer, RandomAccessibleInterval> { + @OpDependency(name="transform.hyperSliceView") + Functions.Arity3, Integer, Long, RandomAccessibleInterval> slicer; + /** * Projects {@code op} along 1-dimensional slices (along dimension {@code dim}) of {@code input} * @@ -76,7 +81,7 @@ public void compute(final RandomAccessibleInterval input, .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)); + ra = slicer.apply(ra, d < dim ? 0 : 1, position.getLongPosition(d)); } op.compute(ra, pixel); }); From cad5161516b677d544540c0ebf0d5f9db4af8ae7 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 7 Sep 2026 12:25:33 -0500 Subject: [PATCH 6/6] Use interval instead Only one function call per pixel now --- .../project/ProjectParallelComputer.java | 78 ++++++------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelComputer.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelComputer.java index f307abba4..a7ebfd7a5 100644 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelComputer.java +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/transform/project/project/ProjectParallelComputer.java @@ -29,21 +29,21 @@ package org.scijava.ops.image.transform.project.project; -import java.util.Iterator; -import net.imglib2.RandomAccess; +import net.imglib2.FinalInterval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.loops.LoopBuilder; import net.imglib2.util.Intervals; import net.imglib2.view.Views; import org.scijava.function.Computers; -import org.scijava.function.Functions; -import org.scijava.ops.spi.OpDependency; /** * Projection is the act of creating 1-dimensional slices of an n-dimensional image, * reducing that slice down to a single value, and combining those images back into a (n-1)-dimensional array + *

+ * Note that this Op cannot be adapted because the output is necessarily of different dimensionality than the input. + *

* * @param the type of input image elements * @param the type of output image elements @@ -51,12 +51,13 @@ * @see ProjectParallelFunction for an Op that creates its own output */ public class ProjectParallelComputer implements - Computers.Arity3, Computers.Arity1, V>, Integer, RandomAccessibleInterval> + Computers.Arity3< + RandomAccessibleInterval, + Computers.Arity1, V>, + Integer, + RandomAccessibleInterval + > { - - @OpDependency(name="transform.hyperSliceView") - Functions.Arity3, Integer, Long, RandomAccessibleInterval> slicer; - /** * Projects {@code op} along 1-dimensional slices (along dimension {@code dim}) of {@code input} * @@ -78,52 +79,19 @@ public void compute(final RandomAccessibleInterval input, "ERROR: input image must contain dimension " + dim); LoopBuilder.setImages(output, Intervals.positions(output)).multiThreaded() - .forEachPixel((pixel, position) -> { - var ra = input; - for (var d = 0; d < position.numDimensions(); d++) { - ra = slicer.apply(ra, d < dim ? 0 : 1, position.getLongPosition(d)); - } - op.compute(ra, pixel); + .forEachChunk(chunk -> { + var min = new long[input.numDimensions()]; + var max = new long[input.numDimensions()]; + min[dim] = input.min(dim); + max[dim] = input.max(dim); + chunk.forEachPixel((pixel, position) -> { + for (var d = 0; d < position.numDimensions(); d++) { + min[d >= dim ? d+1 : d] = position.getLongPosition(d); + max[d >= dim ? d+1 : d] = position.getLongPosition(d); + } + op.compute(Views.interval(input, new FinalInterval(min, max)), pixel); + }); + return chunk; }); } - - final class DimensionIterable implements Iterable { - - private final long size; - private final int dim; - private final RandomAccess access; - - public DimensionIterable(final long size, final int dim, - final RandomAccess access) - { - this.size = size; - this.dim = dim; - this.access = access; - } - - @Override - public Iterator iterator() { - return new Iterator() { - - int k = -1; - - @Override - public boolean hasNext() { - return k < size - 1; - } - - @Override - public T next() { - k++; - access.setPosition(k, dim); - return access.get(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Not supported"); - } - }; - } - } }