diff --git a/src/main/java/net/imagej/ops/transform/TransformNamespace.java b/src/main/java/net/imagej/ops/transform/TransformNamespace.java index 6c74e84421..96dd59dbcc 100644 --- a/src/main/java/net/imagej/ops/transform/TransformNamespace.java +++ b/src/main/java/net/imagej/ops/transform/TransformNamespace.java @@ -50,6 +50,7 @@ import net.imglib2.img.array.ArrayImg; import net.imglib2.interpolation.InterpolatorFactory; import net.imglib2.outofbounds.OutOfBoundsFactory; +import net.imglib2.realtransform.InvertibleRealTransform; import net.imglib2.transform.integer.shear.InverseShearTransform; import net.imglib2.transform.integer.shear.ShearTransform; import net.imglib2.type.Type; @@ -173,7 +174,8 @@ public MixedTransformView addDimensionView( @OpMethod( op = net.imagej.ops.transform.collapseNumericView.DefaultCollapseNumeric2CompositeView.class) public > CompositeView> - collapseNumericView(final RandomAccessible input, final int numChannels) + collapseNumericView(final RandomAccessible input, + final int numChannels) { return (CompositeView>) ops().run( Ops.Transform.CollapseNumericView.class, input, numChannels); @@ -335,7 +337,8 @@ ExtendedRandomAccessibleInterval extendBorderView(final F input) @OpMethod( op = net.imagej.ops.transform.extendMirrorDoubleView.DefaultExtendMirrorDoubleView.class) public > - ExtendedRandomAccessibleInterval extendMirrorDoubleView(final F input) + ExtendedRandomAccessibleInterval extendMirrorDoubleView( + final F input) { return (ExtendedRandomAccessibleInterval) ops().run( Ops.Transform.ExtendMirrorDoubleView.class, input); @@ -354,7 +357,8 @@ ExtendedRandomAccessibleInterval extendMirrorDoubleView(final F input) @OpMethod( op = net.imagej.ops.transform.extendMirrorSingleView.DefaultExtendMirrorSingleView.class) public , F extends RandomAccessibleInterval> - ExtendedRandomAccessibleInterval extendMirrorSingleView(final F input) + ExtendedRandomAccessibleInterval extendMirrorSingleView( + final F input) { return (ExtendedRandomAccessibleInterval) ops().run( Ops.Transform.ExtendMirrorSingleView.class, input); @@ -1203,4 +1207,46 @@ public > RandomAccessibleInterval concatenateView( ConcatenateViewWithAccessMode.class, source, concatenationAxis, mode); } + @OpMethod( + op = net.imagej.ops.transform.realTransform.DefaultTransformView.class) + public > RandomAccessibleInterval realTransform( + final RandomAccessibleInterval in, + final InvertibleRealTransform transform) + { + final RandomAccessibleInterval result = + (RandomAccessibleInterval) ops().run( + Ops.Transform.RealTransform.class, in, + transform); + return result; + } + + @OpMethod( + op = net.imagej.ops.transform.realTransform.DefaultTransformView.class) + public > RandomAccessibleInterval realTransform( + final RandomAccessibleInterval in, + final InvertibleRealTransform transform, final Interval outputInterval) + { + final RandomAccessibleInterval result = + (RandomAccessibleInterval) ops().run( + Ops.Transform.RealTransform.class, in, + transform, outputInterval); + return result; + } + + @OpMethod( + op = net.imagej.ops.transform.realTransform.DefaultTransformView.class) + public > RandomAccessibleInterval realTransform( + final RandomAccessibleInterval in, + final InvertibleRealTransform transform, final Interval outputInterval, + final InterpolatorFactory> interpolator) + { + final RandomAccessibleInterval result = + (RandomAccessibleInterval) ops().run( + Ops.Transform.RealTransform.class, in, + transform, outputInterval, interpolator); + return result; + } + + + } diff --git a/src/main/java/net/imagej/ops/transform/realTransform/DefaultTransformView.java b/src/main/java/net/imagej/ops/transform/realTransform/DefaultTransformView.java new file mode 100644 index 0000000000..c0f0c59358 --- /dev/null +++ b/src/main/java/net/imagej/ops/transform/realTransform/DefaultTransformView.java @@ -0,0 +1,99 @@ +/* + * #%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.ops.transform.realTransform; + +import net.imagej.ops.Ops; +import net.imagej.ops.special.function.AbstractUnaryFunctionOp; +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.outofbounds.OutOfBoundsFactory; +import net.imglib2.outofbounds.OutOfBoundsMirrorFactory; +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.IntervalView; +import net.imglib2.view.Views; + +import org.scijava.Priority; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; + +/** + * 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) + */ +@Plugin(type = Ops.Transform.RealTransform.class, priority = Priority.HIGH + 1) +public class DefaultTransformView & RealType> + extends + AbstractUnaryFunctionOp, RandomAccessibleInterval> + implements Ops.Transform.RealTransform +{ + + /** + * The transform to apply + */ + @Parameter + InvertibleRealTransform transform; + + @Parameter(required = false) + private Interval outputInterval; + + @Parameter(required = false) + private InterpolatorFactory> interpolator; + + @Override + public RandomAccessibleInterval calculate( + final RandomAccessibleInterval input) + { + + if (outputInterval == null) { + outputInterval = new FinalInterval(input); + } + + if (interpolator == null) { + interpolator = new LanczosInterpolatorFactory<>(); + } + + final IntervalView interval = Views.interval(Views.raster(RealViews + .transformReal(Views.interpolate(Views.extendZero(input), interpolator), + transform)), outputInterval); + + return interval; + } + +} diff --git a/src/main/templates/net/imagej/ops/Ops.list b/src/main/templates/net/imagej/ops/Ops.list index e38e8c72f6..a5b1e1a112 100644 --- a/src/main/templates/net/imagej/ops/Ops.list +++ b/src/main/templates/net/imagej/ops/Ops.list @@ -448,6 +448,7 @@ namespaces = ``` [name: "translateView", iface: "TranslateView"], [name: "unshearView", iface: "UnshearView"], [name: "zeroMinView", iface: "ZeroMinView"], + [name: "realTransform", iface: "RealTransform"], ]], [name: "zernike", iface: "Zernike", ops: [ [name: "magnitude", iface: "Magnitude"], diff --git a/src/test/java/net/imagej/ops/transform/realTransform/RealTransformTest.java b/src/test/java/net/imagej/ops/transform/realTransform/RealTransformTest.java new file mode 100644 index 0000000000..f4cf8e4c97 --- /dev/null +++ b/src/test/java/net/imagej/ops/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.ops.transform.realTransform; + +import static org.junit.Assert.assertEquals; + +import net.imagej.ops.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; + +import org.junit.Test; + +public class RealTransformTest extends AbstractOpTest { + + @Test + public void regressionTest() throws Exception { + + final Img image = openUnsignedByteType(getClass(), + "lowresbridge.tif"); + final Img expectedOutput = openUnsignedByteType( + getClass(), "rotatedscaledcenter.tif"); + + final AffineTransform2D transform = new AffineTransform2D(); + + transform.translate(-image.dimension(0) / 2, -image.dimension(0) / 2); + transform.rotate(1); + transform.scale(0.5); + transform.translate(image.dimension(0) / 2, image.dimension(0) / 2); + + @SuppressWarnings("unchecked") + final RandomAccessibleInterval actualOutput = + (RandomAccessibleInterval) ops.run( + net.imagej.ops.transform.realTransform.DefaultTransformView.class, + image, transform); + + // 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/src/test/resources/net/imagej/ops/transform/realTransform/lowresbridge.tif b/src/test/resources/net/imagej/ops/transform/realTransform/lowresbridge.tif new file mode 100644 index 0000000000..c42a8febbe Binary files /dev/null and b/src/test/resources/net/imagej/ops/transform/realTransform/lowresbridge.tif differ diff --git a/src/test/resources/net/imagej/ops/transform/realTransform/rotatedscaledcenter.tif b/src/test/resources/net/imagej/ops/transform/realTransform/rotatedscaledcenter.tif new file mode 100644 index 0000000000..03c4426a5f Binary files /dev/null and b/src/test/resources/net/imagej/ops/transform/realTransform/rotatedscaledcenter.tif differ