diff --git a/imagej/imagej-ops2/src/main/java/module-info.java b/imagej/imagej-ops2/src/main/java/module-info.java index 9c27802f4..20068af3f 100644 --- a/imagej/imagej-ops2/src/main/java/module-info.java +++ b/imagej/imagej-ops2/src/main/java/module-info.java @@ -167,6 +167,7 @@ requires ojalgo; requires jama; requires mines.jtk; + requires net.imglib2.realtransform; provides org.scijava.types.TypeExtractor with net.imagej.ops2.types.ImgFactoryTypeExtractor, diff --git a/imagej/imagej-ops2/src/main/java/net/imagej/ops2/transform/DefaultTransformView.java b/imagej/imagej-ops2/src/main/java/net/imagej/ops2/transform/DefaultTransformView.java new file mode 100644 index 000000000..7ceed3690 --- /dev/null +++ b/imagej/imagej-ops2/src/main/java/net/imagej/ops2/transform/DefaultTransformView.java @@ -0,0 +1,95 @@ +/* + * #%L + * ImageJ software for multidimensional image processing and analysis. + * %% + * Copyright (C) 2014 - 2018 ImageJ 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 net.imagej.ops2.transform; + +import org.scijava.function.Functions; +import org.scijava.ops.spi.Optional; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.RandomAccessible; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.interpolation.InterpolatorFactory; +import net.imglib2.interpolation.randomaccess.LanczosInterpolatorFactory; +import net.imglib2.realtransform.InvertibleRealTransform; +import net.imglib2.realtransform.RealViews; +import net.imglib2.type.numeric.NumericType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.view.Views; + +/** + * Applies an Affine transform to a {@link RandomAccessibleInterval} + * + * @author Brian Northan (True North Intelligent Algorithms) + * @author Martin Horn (University of Konstanz) + * @author Stefan Helfrich (University of Konstanz) + * @implNote op names="transform.realTransform", priority="101.0" + */ +public class DefaultTransformView & RealType> + implements + Functions.Arity4, InvertibleRealTransform, Interval, InterpolatorFactory>, RandomAccessibleInterval> +{ + + /** + * TODO: declare {@code outputInterval}, {@code interpolator} as optional once + * this issue has + * been resolved. Until then, this op must be called as a + * {@link Functions.Arity4} + * + * @param input the input + * @param transform the transform to apply + * @param outputInterval the output interval + * @param interpolator the {@link InterpolatorFactory} delegated to for + * interpolation + * @return the output + */ + @Override + public RandomAccessibleInterval apply( // + RandomAccessibleInterval input, // + InvertibleRealTransform transform, // + @Optional Interval outputInterval, // + @Optional InterpolatorFactory> interpolator // + ) { + if (outputInterval == null) { + outputInterval = new FinalInterval(input); + } + + if (interpolator == null) { + interpolator = new LanczosInterpolatorFactory<>(); + } + + var extended = Views.extendZero(input); + var interpolated = Views.interpolate(extended, interpolator); + var transformed = RealViews.transformReal(interpolated, transform); + var rasterized = Views.raster(transformed); + return Views.interval(rasterized, outputInterval); + } + +} diff --git a/imagej/imagej-ops2/src/test/java/net/imagej/ops2/OpRegressionTest.java b/imagej/imagej-ops2/src/test/java/net/imagej/ops2/OpRegressionTest.java index c80c609d4..db858121d 100644 --- a/imagej/imagej-ops2/src/test/java/net/imagej/ops2/OpRegressionTest.java +++ b/imagej/imagej-ops2/src/test/java/net/imagej/ops2/OpRegressionTest.java @@ -39,7 +39,7 @@ public class OpRegressionTest extends AbstractOpTest { @Test public void opDiscoveryRegressionIT() { - long expected = 1457; + long expected = 1460; long actual = StreamSupport.stream(ops.infos().spliterator(), false).count(); assertEquals(expected, actual); } diff --git a/imagej/imagej-ops2/src/test/java/net/imagej/ops2/transform/realTransform/RealTransformTest.java b/imagej/imagej-ops2/src/test/java/net/imagej/ops2/transform/realTransform/RealTransformTest.java new file mode 100644 index 000000000..3427157b3 --- /dev/null +++ b/imagej/imagej-ops2/src/test/java/net/imagej/ops2/transform/realTransform/RealTransformTest.java @@ -0,0 +1,84 @@ +/*- + * #%L + * ImageJ software for multidimensional image processing and analysis. + * %% + * Copyright (C) 2014 - 2018 ImageJ 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 net.imagej.ops2.transform.realTransform; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.scijava.types.Nil; + +import net.imagej.ops2.AbstractOpTest; +import net.imglib2.Cursor; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.Img; +import net.imglib2.realtransform.AffineTransform2D; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.view.Views; + +public class RealTransformTest extends AbstractOpTest { + + @Test + public void regressionTest() { + + final Img image = openRelativeUnsignedByteImg(getClass(), + "lowresbridge.tif"); + final Img expectedOutput = openRelativeUnsignedByteImg( + getClass(), "rotatedscaledcenter.tif"); + + final AffineTransform2D transform = new AffineTransform2D(); + + double translation = ((double) image.dimension(0) / 2); + transform.translate(-translation, -translation); + transform.rotate(1); + transform.scale(0.5); + transform.translate(translation, translation); + + var outType = new Nil>() {}; + var actualOutput = ops.op("transform.realTransform").arity2() // + .input(image, transform) // + .outType(outType) // + .apply(); + + // compare the output image data to that stored in the file. + final Cursor cursor = Views.iterable(actualOutput) + .localizingCursor(); + final RandomAccess actualRA = actualOutput.randomAccess(); + final RandomAccess expectedRA = expectedOutput + .randomAccess(); + + while (cursor.hasNext()) { + cursor.fwd(); + actualRA.setPosition(cursor); + expectedRA.setPosition(cursor); + assertEquals(expectedRA.get().get(), actualRA.get().get(), 0); + } + + } + +} diff --git a/imagej/imagej-ops2/src/test/resources/net/imagej/ops2/transform/realTransform/lowresbridge.tif b/imagej/imagej-ops2/src/test/resources/net/imagej/ops2/transform/realTransform/lowresbridge.tif new file mode 100644 index 000000000..c42a8febb Binary files /dev/null and b/imagej/imagej-ops2/src/test/resources/net/imagej/ops2/transform/realTransform/lowresbridge.tif differ diff --git a/imagej/imagej-ops2/src/test/resources/net/imagej/ops2/transform/realTransform/rotatedscaledcenter.tif b/imagej/imagej-ops2/src/test/resources/net/imagej/ops2/transform/realTransform/rotatedscaledcenter.tif new file mode 100644 index 000000000..03c4426a5 Binary files /dev/null and b/imagej/imagej-ops2/src/test/resources/net/imagej/ops2/transform/realTransform/rotatedscaledcenter.tif differ