diff --git a/src/main/java/net/imagej/ops/filter/FilterNamespace.java b/src/main/java/net/imagej/ops/filter/FilterNamespace.java index 7b49291dd1..2b7e74cf2c 100644 --- a/src/main/java/net/imagej/ops/filter/FilterNamespace.java +++ b/src/main/java/net/imagej/ops/filter/FilterNamespace.java @@ -46,6 +46,7 @@ import net.imglib2.outofbounds.OutOfBoundsFactory; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.ComplexType; +import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.real.DoubleType; @@ -64,36 +65,36 @@ public class FilterNamespace extends AbstractNamespace { // -- addNoise -- - @OpMethod(ops = { net.imagej.ops.filter.addNoise.AddNoiseRealType.class, - net.imagej.ops.filter.addNoise.AddNoiseRealTypeCFI.class }) - public , O extends RealType> O addNoise(final O out, + @OpMethod(ops = { net.imagej.ops.filter.addGaussianNoise.AddGaussianNoiseRealType.class, + net.imagej.ops.filter.addGaussianNoise.AddGaussianNoiseRealTypeCFI.class }) + public , O extends RealType> O addGaussianNoise(final O out, final I in, final double rangeMin, final double rangeMax, final double rangeStdDev) { @SuppressWarnings("unchecked") - final O result = (O) ops().run(Ops.Filter.AddNoise.class, out, in, rangeMin, + final O result = (O) ops().run(Ops.Filter.AddGaussianNoise.class, out, in, rangeMin, rangeMax, rangeStdDev); return result; } - @OpMethod(ops = { net.imagej.ops.filter.addNoise.AddNoiseRealType.class, - net.imagej.ops.filter.addNoise.AddNoiseRealTypeCFI.class }) - public , O extends RealType> O addNoise(final O out, + @OpMethod(ops = { net.imagej.ops.filter.addGaussianNoise.AddGaussianNoiseRealType.class, + net.imagej.ops.filter.addGaussianNoise.AddGaussianNoiseRealTypeCFI.class }) + public , O extends RealType> O addGaussianNoise(final O out, final I in, final double rangeMin, final double rangeMax, final double rangeStdDev, final long seed) { @SuppressWarnings("unchecked") - final O result = (O) ops().run(Ops.Filter.AddNoise.class, out, in, rangeMin, + final O result = (O) ops().run(Ops.Filter.AddGaussianNoise.class, out, in, rangeMin, rangeMax, rangeStdDev, seed); return result; } - @OpMethod(op = net.imagej.ops.filter.addNoise.AddNoiseRealTypeCFI.class) - public > T addNoise(final T in, final double rangeMin, + @OpMethod(op = net.imagej.ops.filter.addGaussianNoise.AddGaussianNoiseRealTypeCFI.class) + public > T addGaussianNoise(final T in, final double rangeMin, final double rangeMax, final double rangeStdDev) { @SuppressWarnings("unchecked") - final T result = (T) ops().run(Ops.Filter.AddNoise.class, in, rangeMin, + final T result = (T) ops().run(Ops.Filter.AddGaussianNoise.class, in, rangeMin, rangeMax, rangeStdDev); return result; } @@ -130,6 +131,58 @@ public , O extends RealType> O addPoissonNoise( Ops.Filter.AddPoissonNoise.class, out, in); return result; } + + // -- Uniform Noise -- + + @OpMethod(op = net.imagej.ops.filter.addUniformNoise.AddUniformNoiseRealType.class) + public > I addUniformNoise(final I out, + final I in, final double rangeMin, final double rangeMax) + { + @SuppressWarnings("unchecked") + final I result = (I) ops().run(Ops.Filter.AddUniformNoise.class, out, in, rangeMin, + rangeMax); + return result; + } + + @OpMethod(op = net.imagej.ops.filter.addUniformNoise.AddUniformNoiseRealType.class) + public > I addUniformNoise(final I out, + final I in, final double rangeMin, final double rangeMax, final Long seed) + { + @SuppressWarnings("unchecked") + final I result = (I) ops().run(Ops.Filter.AddUniformNoise.class, out, in, rangeMin, + rangeMax, seed); + return result; + } + + @OpMethod(op = net.imagej.ops.filter.addUniformNoise.AddUniformNoiseIntegerType.class) + public > I addUniformNoise(final I out, + final I in, final long rangeMin, final long rangeMax) + { + @SuppressWarnings("unchecked") + final I result = (I) ops().run(Ops.Filter.AddUniformNoise.class, out, in, rangeMin, + rangeMax); + return result; + } + + @OpMethod(op = net.imagej.ops.filter.addUniformNoise.AddUniformNoiseIntegerType.class) + public > I addUniformNoise(final I out, + final I in, final long rangeMin, final long rangeMax, final boolean clampOutput) + { + @SuppressWarnings("unchecked") + final I result = (I) ops().run(Ops.Filter.AddUniformNoise.class, out, in, rangeMin, + rangeMax, clampOutput); + return result; + } + + @OpMethod(op = net.imagej.ops.filter.addUniformNoise.AddUniformNoiseIntegerType.class) + public > I addUniformNoise(final I out, + final I in, final long rangeMin, final long rangeMax, final boolean clampOutput, final Long seed) + { + @SuppressWarnings("unchecked") + final I result = (I) ops().run(Ops.Filter.AddUniformNoise.class, out, in, rangeMin, + rangeMax, clampOutput, seed); + return result; + } // -- bilateral -- diff --git a/src/main/java/net/imagej/ops/filter/addNoise/AddNoiseRealType.java b/src/main/java/net/imagej/ops/filter/addGaussianNoise/AddGaussianNoiseRealType.java similarity index 93% rename from src/main/java/net/imagej/ops/filter/addNoise/AddNoiseRealType.java rename to src/main/java/net/imagej/ops/filter/addGaussianNoise/AddGaussianNoiseRealType.java index 462948879b..ae37a1e8de 100644 --- a/src/main/java/net/imagej/ops/filter/addNoise/AddNoiseRealType.java +++ b/src/main/java/net/imagej/ops/filter/addGaussianNoise/AddGaussianNoiseRealType.java @@ -27,7 +27,7 @@ * #L% */ -package net.imagej.ops.filter.addNoise; +package net.imagej.ops.filter.addGaussianNoise; import java.util.Random; @@ -42,9 +42,9 @@ * Sets the real component of an output real number to the addition of the real * component of an input real number with an amount of Gaussian noise. */ -@Plugin(type = Ops.Filter.AddNoise.class) -public class AddNoiseRealType, O extends RealType> - extends AbstractUnaryComputerOp implements Ops.Filter.AddNoise +@Plugin(type = Ops.Filter.AddGaussianNoise.class) +public class AddGaussianNoiseRealType, O extends RealType> + extends AbstractUnaryComputerOp implements Ops.Filter.AddGaussianNoise { @Parameter diff --git a/src/main/java/net/imagej/ops/filter/addNoise/AddNoiseRealTypeCFI.java b/src/main/java/net/imagej/ops/filter/addGaussianNoise/AddGaussianNoiseRealTypeCFI.java similarity index 84% rename from src/main/java/net/imagej/ops/filter/addNoise/AddNoiseRealTypeCFI.java rename to src/main/java/net/imagej/ops/filter/addGaussianNoise/AddGaussianNoiseRealTypeCFI.java index edd29a24b9..cbf9bd60f1 100644 --- a/src/main/java/net/imagej/ops/filter/addNoise/AddNoiseRealTypeCFI.java +++ b/src/main/java/net/imagej/ops/filter/addGaussianNoise/AddGaussianNoiseRealTypeCFI.java @@ -27,7 +27,7 @@ * #L% */ -package net.imagej.ops.filter.addNoise; +package net.imagej.ops.filter.addGaussianNoise; import java.util.Random; @@ -44,12 +44,12 @@ *

* This op is a hybrid computer/function/inplace, since input and output types * are the same. For input and output of different types, see - * {@link AddNoiseRealType}. + * {@link AddGaussianNoiseRealType}. *

*/ -@Plugin(type = Ops.Filter.AddNoise.class, priority = Priority.HIGH) -public class AddNoiseRealTypeCFI> extends - AbstractUnaryHybridCFI implements Ops.Filter.AddNoise +@Plugin(type = Ops.Filter.AddGaussianNoise.class, priority = Priority.HIGH) +public class AddGaussianNoiseRealTypeCFI> extends + AbstractUnaryHybridCFI implements Ops.Filter.AddGaussianNoise { @Parameter @@ -71,7 +71,7 @@ public class AddNoiseRealTypeCFI> extends @Override public void compute(final T input, final T output) { if (rng == null) rng = new Random(seed); - AddNoiseRealType.addNoise(input, output, rangeMin, rangeMax, rangeStdDev, + AddGaussianNoiseRealType.addNoise(input, output, rangeMin, rangeMax, rangeStdDev, rng); } @@ -80,7 +80,7 @@ public void compute(final T input, final T output) { @Override public void mutate(final T arg) { if (rng == null) rng = new Random(seed); - AddNoiseRealType.addNoise(arg, arg, rangeMin, rangeMax, rangeStdDev, rng); + AddGaussianNoiseRealType.addNoise(arg, arg, rangeMin, rangeMax, rangeStdDev, rng); } // -- UnaryOutputFactory methods -- diff --git a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java new file mode 100644 index 0000000000..90d9942ce4 --- /dev/null +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java @@ -0,0 +1,102 @@ +/* + * #%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.filter.addUniformNoise; + +import java.math.BigInteger; + +import net.imagej.ops.Ops; +import net.imagej.ops.special.computer.AbstractUnaryComputerOp; +import net.imglib2.type.numeric.IntegerType; + +import org.scijava.Priority; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.util.MersenneTwisterFast; + +/** + * Adds a pseudorandomly generated value {@code x} to a {@link IntegerType} + * {@code I}, such that {@code x} is (inclusively) bounded by {@code rangeMin} + * and {@code rangeMax} parameters, i.e. {@code rangeMin <= x <= rangeMax}. + * + * @author Gabe Selzer + */ +@Plugin(type = Ops.Filter.AddUniformNoise.class, priority = Priority.HIGH) +public class AddUniformNoiseIntegerType> extends + AbstractUnaryComputerOp implements Ops.Filter.AddUniformNoise +{ + + /** + * The greatest that an input value can be decreased. + */ + @Parameter + private long rangeMin; + + /** + * The greatest that an input value can be increased + */ + @Parameter + private long rangeMax; + + /** + * If false, the Op will wrap outputs that are outside of the type bounds, + * instead of clamping them + */ + @Parameter(required = false) + private boolean clampOutput = true; + + @Parameter(required = false) + private Long seed; + private long range; + + private MersenneTwisterFast rng; + + @Override + public void initialize() { + // setup rng with seed only if one was provided. + if (rng == null) rng = seed == null ? new MersenneTwisterFast() + : new MersenneTwisterFast(seed); + if (rangeMax < rangeMin) { + long temp = rangeMax; + rangeMax = rangeMin; + rangeMin = temp; + } + // MersenneTwister can only generate numbers that can fit into a long. + range = Math.subtractExact(rangeMax + 1, rangeMin); + } + + @Override + public void compute(I input, I output) { + final double newVal = rng.nextLong(range) + rangeMin + input + .getRealDouble(); + + AddUniformNoiseRealType.setOutput(newVal, clampOutput, output); + } + +} diff --git a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java new file mode 100644 index 0000000000..fa514a1ff6 --- /dev/null +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java @@ -0,0 +1,121 @@ +/* + * #%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.filter.addUniformNoise; + +import net.imagej.ops.Ops; +import net.imagej.ops.special.computer.AbstractUnaryComputerOp; +import net.imglib2.type.numeric.RealType; + +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.util.MersenneTwisterFast; + +/** + * Adds a pseudorandomly generated value {@code x} to a {@link RealType} + * {@code I}, such that {@code x} is (inclusively) bounded by {@code rangeMin} + * and {@code rangeMax} parameters, i.e. {@code rangeMin <= x <= rangeMax}. + * + * @author Gabe Selzer + */ +@Plugin(type = Ops.Filter.AddUniformNoise.class) +public class AddUniformNoiseRealType> + extends AbstractUnaryComputerOp implements Ops.Filter.AddUniformNoise +{ + + /** + * The greatest that an input value can be decreased. + */ + @Parameter + private double rangeMin; + + /** + * The greatest that an input value can be increased + */ + @Parameter + private double rangeMax; + + @Parameter(required = false) + private Long seed; + + private MersenneTwisterFast rng; + + @Override + public void initialize() { + // setup rng with seed only if one was provided. + if (rng == null) rng = seed == null ? new MersenneTwisterFast() + : new MersenneTwisterFast(seed); + if (rangeMax < rangeMin) { + double temp = rangeMax; + rangeMax = rangeMin; + rangeMin = temp; + } + + // if an unacceptable value is passed for either range param (i.e. a NaN + // value or a Double so large that the maths will become infected by + // INFINITY, do not proceed. + if (!Double.isFinite(rangeMax - rangeMin)) + throw new IllegalArgumentException("range not allowed by op."); + } + + @Override + public void compute(I input, I output) { + final double newVal = (rangeMax - rangeMin) * rng.nextDouble(true, true) + + rangeMin + input.getRealDouble(); + + setOutput(newVal, true, output); + } + + protected static > void setOutput(double newVal, boolean clampOutput, I output) { + // clamp output + if (clampOutput) { + output.setReal(Math.max(output.getMinValue(), Math.min(output.getMaxValue(), + newVal))); + } + + // wrap output + else { + double outVal = newVal; + // when output larger than max value, add difference of output and max + // value to the min value + while (outVal > output.getMaxValue()) { + outVal = output.getMinValue() + (outVal - output.getMaxValue() - 1); + } + // when output smaller than min value, subtract difference of output and + // min value from the max value + while (outVal < output.getMinValue()) { + outVal = output.getMaxValue() - (output.getMinValue() - outVal - 1); + } + + output.setReal(outVal); + } + + } + +} diff --git a/src/main/templates/net/imagej/ops/Ops.list b/src/main/templates/net/imagej/ops/Ops.list index 15a6095fc4..8aad09145e 100644 --- a/src/main/templates/net/imagej/ops/Ops.list +++ b/src/main/templates/net/imagej/ops/Ops.list @@ -97,8 +97,9 @@ namespaces = ``` ]], [name: "filter", iface: "Filter", ops: [ - [name: "addNoise", iface: "AddNoise"], + [name: "addGaussianNoise", iface: "AddGaussianNoise"], [name: "addPoissonNoise", iface: "AddPoissonNoise"], + [name: "addUniformNoise", iface: "AddUniformNoise"], [name: "bilateral", iface: "Bilateral"], [name: "convolve", iface: "Convolve"], [name: "correlate", iface: "Correlate"], diff --git a/src/test/java/net/imagej/ops/convert/ConvertIIsTest.java b/src/test/java/net/imagej/ops/convert/ConvertIIsTest.java index dea677e924..b17dd0a48e 100644 --- a/src/test/java/net/imagej/ops/convert/ConvertIIsTest.java +++ b/src/test/java/net/imagej/ops/convert/ConvertIIsTest.java @@ -143,7 +143,7 @@ private byte normalizeScale(final short value) { private void addNoise(final Iterable image) { // NB: Need "? super ShortType" instead of "?" to make javac happy. final UnaryInplaceOp noiseOp = Inplaces.unary( - ops, Ops.Filter.AddNoise.class, ShortType.class, -32768, 32767, 10000); + ops, Ops.Filter.AddGaussianNoise.class, ShortType.class, -32768, 32767, 10000); ops.map(image, noiseOp); } diff --git a/src/test/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseTest.java b/src/test/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseTest.java new file mode 100644 index 0000000000..4b695db048 --- /dev/null +++ b/src/test/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseTest.java @@ -0,0 +1,107 @@ + +package net.imagej.ops.filter.addUniformNoise; + +import net.imagej.ops.AbstractOpTest; +import net.imagej.ops.Ops; +import net.imagej.ops.special.computer.Computers; +import net.imagej.ops.special.computer.UnaryComputerOp; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.type.numeric.real.DoubleType; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests {@link AddUniformNoiseRealType}. + * + * @author Gabriel Selzer + */ +public class AddUniformNoiseTest extends AbstractOpTest { + + /** + * Regression test for floating point values + */ + @Test + public void realTypeRegressionTest() { + UnaryComputerOp noiseFunc = Computers.unary(ops, + Ops.Filter.AddUniformNoise.class, DoubleType.class, DoubleType.class, 0d, + 2d, 0xabcdef1234567890L); + + double[] actual = new double[9]; + DoubleType temp = new DoubleType(); + for (int i = 0; i < actual.length; i++) { + noiseFunc.compute(new DoubleType(254), temp); + actual[i] = temp.getRealDouble(); + } + + double[] expected = { 254.10073053454738, 255.88056371733813, + 255.15987104925694, 255.32176185269049, 254.92408976012726, + 255.04150474558148, 255.4924837511821, 254.66400205149753, + 254.45238896293924 }; + Assert.assertArrayEquals(expected, actual, 1e-6); + } + + /** + * Ensures that the Op wraps correctly to the minimum of the data type when + * clampOutput is set to false + */ + @Test + public void wrappingUpperEndRegressionTest() { + UnaryComputerOp noiseFunc = Computers + .unary(ops, Ops.Filter.AddUniformNoise.class, UnsignedByteType.class, + UnsignedByteType.class, 0l, 3l, false, 0xabcdef1234567890L); + + int[] actual = new int[9]; + UnsignedByteType temp = new UnsignedByteType(); + for (int i = 0; i < actual.length; i++) { + noiseFunc.compute(new UnsignedByteType(254), temp); + actual[i] = temp.get(); + } + + int[] expected = { 0, 0, 1, 0, 255, 254, 0, 0, 0 }; + Assert.assertArrayEquals(expected, actual); + } + + /** + * Ensures that the Op wraps correctly to the maximum of the data type when + * clampOutput is set to false + */ + @Test + public void wrappingLowerEndRegressionTest() { + UnaryComputerOp noiseFunc = Computers + .unary(ops, Ops.Filter.AddUniformNoise.class, UnsignedByteType.class, + UnsignedByteType.class, -3l, 0l, false, 0xabcdef1234567890L); + + int[] actual = new int[9]; + UnsignedByteType temp = new UnsignedByteType(); + for (int i = 0; i < actual.length; i++) { + noiseFunc.compute(new UnsignedByteType(0), temp); + actual[i] = temp.get(); + } + + int[] expected = { 255, 255, 0, 255, 254, 253, 255, 255, 255 }; + Assert.assertArrayEquals(expected, actual); + } + + /** + * Ensures that the Op clamps to the minimum of the data type then clampOutput + * is set to true + */ + @Test + public void clampingLowerEndRegressionTest() { + UnaryComputerOp noiseFunc = Computers + .unary(ops, Ops.Filter.AddUniformNoise.class, UnsignedByteType.class, + UnsignedByteType.class, -3l, 0l, 0xabcdef1234567890L); + + int[] actual = new int[9]; + UnsignedByteType temp = new UnsignedByteType(); + for (int i = 0; i < actual.length; i++) { + noiseFunc.compute(new UnsignedByteType(0), temp); + actual[i] = temp.get(); + } + + int[] expected = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + Assert.assertArrayEquals(expected, actual); + } + +}