diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/CreateOutputFFTMethods.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/CreateOutputFFTMethods.java deleted file mode 100644 index 697af5e2c..000000000 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/CreateOutputFFTMethods.java +++ /dev/null @@ -1,77 +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.filter.fft; - -import java.util.function.BiFunction; - -import net.imglib2.Dimensions; -import net.imglib2.img.Img; - -import org.scijava.function.Functions; -import org.scijava.ops.spi.Nullable; -import org.scijava.ops.spi.OpDependency; - -/** - * Function that creates an output for FFTMethods FFT - * - * @author Brian Northan - * @param - * @implNote op names='filter.createFFTOutput' - */ -public class CreateOutputFFTMethods implements - Functions.Arity3> -{ - - @OpDependency(name = "create.img") - private BiFunction> create; - - /** - * TODO - * - * @param paddedDimensions the size of the output image - * @param outType the type of the output image - * @param fast - * @return the output - */ - @Override - public Img apply(Dimensions paddedDimensions, T outType, - @Nullable Boolean fast) - { - if (fast == null) { - fast = true; - } - - var paddedFFTMethodsFFTDimensions = FFTMethodsUtility - .getFFTDimensionsRealToComplex(fast, paddedDimensions); - - return create.apply(paddedFFTMethodsFFTDimensions, outType); - } - -} diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOpC.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOpC.java deleted file mode 100644 index 2d4d52ad3..000000000 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOpC.java +++ /dev/null @@ -1,77 +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.filter.fft; - -import net.imglib2.RandomAccessibleInterval; -import net.imglib2.algorithm.fft2.FFTMethods; -import net.imglib2.type.numeric.ComplexType; -import net.imglib2.type.numeric.RealType; - -import org.scijava.function.Computers; - -/** - * Forward FFT computer that operates on an RAI and wraps FFTMethods. The input - * and output size must conform to a supported FFT size. Use - * {@link org.scijava.ops.image.filter.fftSize.ComputeFFTSize} to calculate the - * supported FFT size. - * - * @author Brian Northan - * @param - * @param - * @implNote op names='filter.fft', priority='0.' - */ -public class FFTMethodsOpC, C extends ComplexType> - implements - Computers.Arity1, RandomAccessibleInterval> -{ - - /** - * Computes an ND FFT using FFTMethods - */ - /** - * TODO - * - * @param input - * @param output - */ - @Override - public void compute(final RandomAccessibleInterval input, - final RandomAccessibleInterval output) - { - - // perform a real to complex FFT in the first dimension - FFTMethods.realToComplex(input, output, 0, false); - - // loop and perform complex to complex FFT in the remaining dimensions - for (var d = 1; d < input.numDimensions(); d++) - FFTMethods.complexToComplex(output, d, true, false); - } - -} diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOpF.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOpF.java deleted file mode 100644 index b08dbbe96..000000000 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOpF.java +++ /dev/null @@ -1,121 +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.filter.fft; - -import net.imglib2.Dimensions; -import net.imglib2.FinalDimensions; -import net.imglib2.RandomAccessibleInterval; -import net.imglib2.outofbounds.OutOfBoundsFactory; -import net.imglib2.type.numeric.ComplexType; -import net.imglib2.type.numeric.RealType; - -import org.scijava.function.Computers; -import org.scijava.function.Functions; -import org.scijava.ops.spi.Nullable; -import org.scijava.ops.spi.OpDependency; - -/** - * Function that uses FFTMethods to perform a forward FFT - * - * @author Brian Northan - * @param TODO Documentation - * @param TODO Documentation - * @implNote op names='filter.fft', priority='100.' - */ -public class FFTMethodsOpF, C extends ComplexType> - implements - Functions.Arity4, C, long[], Boolean, RandomAccessibleInterval> -{ - - @OpDependency(name = "filter.padInputFFTMethods") - private Functions.Arity4, Dimensions, Boolean, OutOfBoundsFactory>, RandomAccessibleInterval> padOp; - - @OpDependency(name = "filter.createFFTOutput") - private Functions.Arity3> createOp; - - @OpDependency(name = "filter.fft") - private Computers.Arity1, RandomAccessibleInterval> fftMethodsOp; - - /** - * Note that if fast is true the input will be extended to the next fast FFT - * size. If false the input will be computed using the original input - * dimensions (if possible). If the input dimensions are not supported by the - * underlying FFT implementation the input will be extended to the nearest - * size that is supported. - */ - /** - * TODO - * - * @param input - * @param fftType the complex type of the output - * @param borderSize the size of border to apply in each dimension - * @param fast whether to perform a fast FFT; default true - * @return the output - */ - @Override - public RandomAccessibleInterval apply( // - final RandomAccessibleInterval input, // - final C fftType, // - @Nullable long[] borderSize, // - @Nullable Boolean fast // - ) { - - if (fast == null) { - fast = true; - } - // calculate the padded size - var paddedSize = new long[input.numDimensions()]; - - for (var d = 0; d < input.numDimensions(); d++) { - paddedSize[d] = input.dimension(d); - - if (borderSize != null) { - paddedSize[d] += borderSize[d]; - } - } - - Dimensions paddedDimensions = new FinalDimensions(paddedSize); - - // create the complex output - var output = createOp.apply(paddedDimensions, - fftType, fast); - - // pad the input - var paddedInput = padOp.apply(input, - paddedDimensions, fast, null); - - // compute and return fft - fftMethodsOp.compute(paddedInput, output); - - return output; - - } - -} diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsUtility.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsUtility.java deleted file mode 100644 index 110731fc9..000000000 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsUtility.java +++ /dev/null @@ -1,107 +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.filter.fft; - -import net.imglib2.Dimensions; -import net.imglib2.FinalDimensions; -import net.imglib2.algorithm.fft2.FFTMethods; - -/** - * Utility class that interacts with FFTMethods - * - * @author Brian Northan - */ -public final class FFTMethodsUtility { - - private FFTMethodsUtility() { - // Prevent instantiation of static utility class - } - - /** - * Calculates padding size and complex FFT size for real to complex FFT - * - * @param fast if true calculate size for fast FFT - * @param inputDimensions original real dimensions - * @param paddedDimensions padded real dimensions - * @param fftDimensions complex FFT dimensions - */ - public static void dimensionsRealToComplex(final boolean fast, - final Dimensions inputDimensions, final long[] paddedDimensions, - final long[] fftDimensions) - { - if (fast) { - FFTMethods.dimensionsRealToComplexFast(inputDimensions, paddedDimensions, - fftDimensions); - } - else { - FFTMethods.dimensionsRealToComplexSmall(inputDimensions, paddedDimensions, - fftDimensions); - } - } - - /** - * Calculates padding size size for real to complex FFT - * - * @param fast if true calculate size for fast FFT - * @param inputDimensions original real dimensions - * @return padded real dimensions - */ - public static Dimensions getPaddedInputDimensionsRealToComplex( - final boolean fast, final Dimensions inputDimensions) - { - final var paddedSize = new long[inputDimensions.numDimensions()]; - final var fftSize = new long[inputDimensions.numDimensions()]; - - dimensionsRealToComplex(fast, inputDimensions, paddedSize, fftSize); - - return new FinalDimensions(paddedSize); - - } - - /** - * Calculates complex FFT size for real to complex FFT - * - * @param fast if true calculate size for fast FFT - * @param inputDimensions original real dimensions - * @return complex FFT dimensions - */ - public static Dimensions getFFTDimensionsRealToComplex(final boolean fast, - final Dimensions inputDimensions) - { - final var paddedSize = new long[inputDimensions.numDimensions()]; - final var fftSize = new long[inputDimensions.numDimensions()]; - - dimensionsRealToComplex(fast, inputDimensions, paddedSize, fftSize); - - return new FinalDimensions(fftSize); - - } - -} diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTs.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTs.java new file mode 100644 index 000000000..77ccbf578 --- /dev/null +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTs.java @@ -0,0 +1,227 @@ +package org.scijava.ops.image.filter.fft; + +import net.imglib2.Dimensions; +import net.imglib2.FinalDimensions; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.fft2.FFTMethods; +import net.imglib2.img.Img; +import net.imglib2.outofbounds.OutOfBoundsFactory; +import net.imglib2.type.numeric.ComplexType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.complex.ComplexFloatType; +import org.scijava.function.Computers; +import org.scijava.function.Functions; +import org.scijava.ops.spi.Nullable; +import org.scijava.ops.spi.OpDependency; + +import java.util.function.BiFunction; + +/** + * Ops performing fast fourier transforms. + *

+ * Note that there are explicit {@link Functions} in this class. These are required + * as adaptation of {@link Computers} into {@link Functions} assumes the computer + * output is the same size as the computer input. For Fourier transforms, this is + * not the case. + *

+ * + * @author Brian Northan + * @author Gabriel Selzer + */ +public class FFTs { + + private FFTs() { + // Prevent instantiation of static utility class + } + + /** + * Forward FFT computer that operates on an RAI and wraps FFTMethods. The input + * and output size must conform to a supported FFT size. Use + * {@link org.scijava.ops.image.filter.fftSize.ComputeFFTSize} to calculate the + * supported FFT size. + * + * @param The data type of elements in the position space + * @param The data type of elements in the fourier space + * @param input the input data + * @param output the output + * @implNote op names='filter.fft', priority='0.', type=Computer + */ + public static , C extends ComplexType> void compute( // + final RandomAccessibleInterval input, // + final RandomAccessibleInterval output // + ) { + + // perform a real to complex FFT in the first dimension + FFTMethods.realToComplex(input, output, 0, false); + + // loop and perform complex to complex FFT in the remaining dimensions + for (var d = 1; d < input.numDimensions(); d++) + FFTMethods.complexToComplex(output, d, true, false); + } + + + /** + * Function that uses FFTMethods to perform a forward FFT + * + * @param The data type of elements in the position space + * @param The data type of elements in the fourier space + * @param padOp pads the boundaries of the input dataset + * @param createOp creates the output dataset + * @param fftOp performs the fast fourier transform + * @param input the input data + * @param fftType the complex type of the output + * @param borderSize the size of border to apply in each dimension + * @param fast whether to perform a fast FFT; default true + * @return the output + * @implNote op names='filter.fft', priority='100.' + */ + public static , C extends ComplexType> RandomAccessibleInterval transformComplexType( // + @OpDependency(name = "filter.padInputFFTMethods") final Functions.Arity4< // + RandomAccessibleInterval, // + Dimensions, // + Boolean, // + OutOfBoundsFactory>, // + RandomAccessibleInterval // + > padOp, // + @OpDependency(name = "filter.createFFTOutput") final Functions.Arity3< // + Dimensions, // + C, // + Boolean, // + RandomAccessibleInterval // + > createOp, // + @OpDependency(name = "filter.fft") final Computers.Arity1< // + RandomAccessibleInterval, // + RandomAccessibleInterval // + > fftOp, // + final RandomAccessibleInterval input, // + final C fftType, // + @Nullable long[] borderSize, // + @Nullable Boolean fast // + ) { + + if (fast == null) { + fast = true; + } + // calculate the padded size + long[] paddedSize = new long[input.numDimensions()]; + + for (int d = 0; d < input.numDimensions(); d++) { + paddedSize[d] = input.dimension(d); + + if (borderSize != null) { + paddedSize[d] += borderSize[d]; + } + } + + Dimensions paddedDimensions = new FinalDimensions(paddedSize); + + // create the complex output + RandomAccessibleInterval output = createOp.apply(paddedDimensions, + fftType, fast); + + // pad the input + RandomAccessibleInterval paddedInput = padOp.apply(input, + paddedDimensions, fast, null); + + // compute and return fft + fftOp.compute(paddedInput, output); + + return output; + + } + + /** + * Function that uses FFTMethods to perform a forward FFT + * + * @param The data type of elements in the position space + * @param fftOp performs the fast fourier transform + * @param input the input data + * @param borderSize the size of border to apply in each dimension + * @param fast whether to perform a fast FFT; default true + * @return the output + * @implNote op names='filter.fft', priority='100.' + */ + public static > RandomAccessibleInterval transformComplexType( // + @OpDependency(name = "filter.fft") final Functions.Arity4< // + RandomAccessibleInterval, // + ComplexFloatType, // + long[], // + Boolean, // + RandomAccessibleInterval // + > fftOp, // + final RandomAccessibleInterval input, // + @Nullable long[] borderSize, // + @Nullable Boolean fast // + ) { + return fftOp.apply(input, new ComplexFloatType(), borderSize, fast); + } + + /** + * Function that creates an output for FFTMethods FFT + * + * @param the type of image elements + * @param creator creates the new image + * @param paddedDimensions the size of the output image + * @param outType the type of the output image + * @param fast whether to perform a fast FFT; default true + * @return the output + * @implNote op names='filter.createFFTOutput' + */ + public static Img createFFTMethodsOutput( + @OpDependency(name="create.img") BiFunction> creator, // + Dimensions paddedDimensions, // + T outType, // + @Nullable Boolean fast + ) { + if (fast == null) { + fast = true; + } + + var dims = getFFTDimensionsRealToComplex(fast, paddedDimensions); + + return creator.apply(dims, outType); + } + + /** + * Calculates padding size and complex FFT size for real to complex FFT + * + * @param fast if true calculate size for fast FFT + * @param inputDimensions original real dimensions + * @param paddedDimensions padded real dimensions + * @param fftDimensions complex FFT dimensions + */ + public static void dimensionsRealToComplex( // + final boolean fast, // + final Dimensions inputDimensions, // + final long[] paddedDimensions, // + final long[] fftDimensions // + ) { + if (fast) { + FFTMethods.dimensionsRealToComplexFast(inputDimensions, paddedDimensions, + fftDimensions); + } + else { + FFTMethods.dimensionsRealToComplexSmall(inputDimensions, paddedDimensions, + fftDimensions); + } + } + + /** + * Calculates complex FFT size for real to complex FFT + * + * @param fast if true calculate size for fast FFT + * @param inputDimensions original real dimensions + * @return complex FFT dimensions + */ + public static Dimensions getFFTDimensionsRealToComplex(final boolean fast, + final Dimensions inputDimensions) + { + final var paddedSize = new long[inputDimensions.numDimensions()]; + final var fftSize = new long[inputDimensions.numDimensions()]; + + dimensionsRealToComplex(fast, inputDimensions, paddedSize, fftSize); + + return new FinalDimensions(fftSize); + + } +} diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/OpRegressionTest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/OpRegressionTest.java index 2329d62f8..5959eefec 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/OpRegressionTest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/OpRegressionTest.java @@ -42,7 +42,7 @@ public class OpRegressionTest { @Test public void testOpDiscoveryRegression() { - long expected = 1964; + long expected = 1967; long actual = ops.infos().size(); assertEquals(expected, actual); } diff --git a/scijava-ops-image/src/test/java/org/scijava/ops/image/filter/FFTTest.java b/scijava-ops-image/src/test/java/org/scijava/ops/image/filter/FFTTest.java index ad36aab3b..35dd2fb0a 100644 --- a/scijava-ops-image/src/test/java/org/scijava/ops/image/filter/FFTTest.java +++ b/scijava-ops-image/src/test/java/org/scijava/ops/image/filter/FFTTest.java @@ -29,7 +29,9 @@ package org.scijava.ops.image.filter; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import org.junit.jupiter.api.Assertions; import org.scijava.ops.image.AbstractOpTest; import org.scijava.ops.image.util.TestImgGeneration; import net.imglib2.Cursor; @@ -209,6 +211,34 @@ public void testPadShiftKernel() { } + @Test + public void testFFTFunctionMatching() { + int i = 9; + final long[] dimensions = new long[] { i, i, i }; + + // create an input with a small sphere at the center + final Img in = TestImgGeneration.floatArray(false, dimensions); + placeSphereInCenter(in); + + // When type not provided, an Img is returned + var out = ops.op("filter.fft").input(in).apply(); + assertInstanceOf(Img.class, out); + var outImg = (Img) out; + assertInstanceOf(ComplexFloatType.class, outImg.firstElement()); + + // This should be identical to what is given when output type provided. + var typedOut = ops.op("filter.fft").input(in, new ComplexFloatType()).apply(); + assertInstanceOf(Img.class, typedOut); + var typedOutImg = (Img) typedOut; + assertInstanceOf(ComplexFloatType.class, typedOutImg.firstElement()); + + assertComplexImagesEqual( // + (Img) outImg, // + (Img) typedOutImg, // + 0f // + ); + } + /** * utility that places a sphere in the center of the image *