From 3d0b19c19fb22df547be6e7168f934c1b61fb5da Mon Sep 17 00:00:00 2001 From: Edward Evans Date: Mon, 19 Feb 2024 14:11:26 -0600 Subject: [PATCH 1/2] Create a proper "filter.fft" function op Running `out = ops.op("filter.fft").arity1().input(img).apply()` returns a blank image. This is because the wrong image dimensions are being used for the FFT result. This is an example of when adaptation fails. SciJava Ops adapts the FFT Op into a function and assumes that the output image dimensions are the same as the input. Generally this is a safe assumption, but this is an example where the output image dimensions are in fact different. This commit adds a new Op that depends on `FFTMethodsOpF` and returns the FFT result as a ComplexFloatType. This is consistent with the old imagej-ops usage. --- .../ops/image/filter/fft/FFTMethodsOp.java | 75 +++++++++++++++++++ .../scijava/ops/image/OpRegressionTest.java | 2 +- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOp.java diff --git a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOp.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOp.java new file mode 100644 index 000000000..f863faf04 --- /dev/null +++ b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOp.java @@ -0,0 +1,75 @@ +/* + * #%L + * ImageJ2 software for multidimensional image processing and analysis. + * %% + * Copyright (C) 2014 - 2023 ImageJ2 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.type.numeric.RealType; +import net.imglib2.type.numeric.complex.ComplexFloatType; +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 + */ +public class 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 FFTMethodsOpF dependency to FFTMethodsOpF + * @param input input image + * @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 run( // + @OpDependency( + name = "filter.fft") Functions.Arity4, ComplexFloatType, long[], Boolean, RandomAccessibleInterval> FFTMethodsOpF, + final RandomAccessibleInterval input, // + @Nullable long[] borderSize, // + @Nullable Boolean fast // + ) { + + return FFTMethodsOpF.apply(input, new ComplexFloatType(), borderSize, fast); + + } + +} 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..5ce090f19 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 = 1965; long actual = ops.infos().size(); assertEquals(expected, actual); } From 7048430e5c74b73783d88f4ea62c5002654882dc Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 10 Feb 2025 14:01:57 -0600 Subject: [PATCH 2/2] Reorganize FFT Ops This commit throws all of them, rewritten as static methods, into one class, and adds a test to ensure proper matching of function FFT Ops --- .../filter/fft/CreateOutputFFTMethods.java | 77 ------ .../ops/image/filter/fft/FFTMethodsOp.java | 75 ------ .../ops/image/filter/fft/FFTMethodsOpC.java | 77 ------ .../ops/image/filter/fft/FFTMethodsOpF.java | 121 ---------- .../image/filter/fft/FFTMethodsUtility.java | 107 --------- .../scijava/ops/image/filter/fft/FFTs.java | 227 ++++++++++++++++++ .../scijava/ops/image/OpRegressionTest.java | 2 +- .../org/scijava/ops/image/filter/FFTTest.java | 30 +++ 8 files changed, 258 insertions(+), 458 deletions(-) delete mode 100644 scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/CreateOutputFFTMethods.java delete mode 100644 scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOp.java delete mode 100644 scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOpC.java delete mode 100644 scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOpF.java delete mode 100644 scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsUtility.java create mode 100644 scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTs.java 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/FFTMethodsOp.java b/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOp.java deleted file mode 100644 index f863faf04..000000000 --- a/scijava-ops-image/src/main/java/org/scijava/ops/image/filter/fft/FFTMethodsOp.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * #%L - * ImageJ2 software for multidimensional image processing and analysis. - * %% - * Copyright (C) 2014 - 2023 ImageJ2 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.type.numeric.RealType; -import net.imglib2.type.numeric.complex.ComplexFloatType; -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 - */ -public class 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 FFTMethodsOpF dependency to FFTMethodsOpF - * @param input input image - * @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 run( // - @OpDependency( - name = "filter.fft") Functions.Arity4, ComplexFloatType, long[], Boolean, RandomAccessibleInterval> FFTMethodsOpF, - final RandomAccessibleInterval input, // - @Nullable long[] borderSize, // - @Nullable Boolean fast // - ) { - - return FFTMethodsOpF.apply(input, new ComplexFloatType(), borderSize, fast); - - } - -} 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 5ce090f19..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 = 1965; + 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 *