From dc925828ab4bf2dd504a3895b41146ac4aea4185 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 16 Apr 2018 16:26:09 -0500 Subject: [PATCH 1/9] AddNoise: Rename to AddGaussianNoise This op was renamed to make it clear that the noise generated by this op is NON-uniform. This distinction is being made to separate this op from a uniform noise generation op (coming in a later commit) --- .../imagej/ops/filter/FilterNamespace.java | 22 +++++++++---------- .../AddGaussianNoiseRealType.java} | 8 +++---- .../AddGaussianNoiseRealTypeCFI.java} | 14 ++++++------ src/main/templates/net/imagej/ops/Ops.list | 2 +- .../imagej/ops/convert/ConvertIIsTest.java | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) rename src/main/java/net/imagej/ops/filter/{addNoise/AddNoiseRealType.java => addGaussianNoise/AddGaussianNoiseRealType.java} (93%) rename src/main/java/net/imagej/ops/filter/{addNoise/AddNoiseRealTypeCFI.java => addGaussianNoise/AddGaussianNoiseRealTypeCFI.java} (84%) diff --git a/src/main/java/net/imagej/ops/filter/FilterNamespace.java b/src/main/java/net/imagej/ops/filter/FilterNamespace.java index 7b49291dd1..5cfa3e55d9 100644 --- a/src/main/java/net/imagej/ops/filter/FilterNamespace.java +++ b/src/main/java/net/imagej/ops/filter/FilterNamespace.java @@ -64,36 +64,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; } 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/templates/net/imagej/ops/Ops.list b/src/main/templates/net/imagej/ops/Ops.list index 15a6095fc4..77ab34a013 100644 --- a/src/main/templates/net/imagej/ops/Ops.list +++ b/src/main/templates/net/imagej/ops/Ops.list @@ -97,7 +97,7 @@ namespaces = ``` ]], [name: "filter", iface: "Filter", ops: [ - [name: "addNoise", iface: "AddNoise"], + [name: "addGaussianNoise", iface: "AddGaussianNoise"], [name: "addPoissonNoise", iface: "AddPoissonNoise"], [name: "bilateral", iface: "Bilateral"], [name: "convolve", iface: "Convolve"], 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); } From e2895d917d3c3db50bdf2803454356fb1e9bbc96 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 16 Apr 2018 17:29:58 -0500 Subject: [PATCH 2/9] Add AddUniformNoise Op. --- .../imagej/ops/filter/FilterNamespace.java | 20 ++++ .../AddUniformNoiseRealType.java | 97 +++++++++++++++++++ src/main/templates/net/imagej/ops/Ops.list | 1 + 3 files changed, 118 insertions(+) create mode 100644 src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java diff --git a/src/main/java/net/imagej/ops/filter/FilterNamespace.java b/src/main/java/net/imagej/ops/filter/FilterNamespace.java index 5cfa3e55d9..9f793d3973 100644 --- a/src/main/java/net/imagej/ops/filter/FilterNamespace.java +++ b/src/main/java/net/imagej/ops/filter/FilterNamespace.java @@ -130,6 +130,26 @@ public , O extends RealType> O addPoissonNoise( Ops.Filter.AddPoissonNoise.class, out, in); return result; } + + @OpMethod(op = net.imagej.ops.filter.addUniformNoise.AddUniformNoiseRealType.class) + public , O extends RealType> O addUniformNoise(final O out, + final I in, final double rangeMin, final double rangeMax) + { + @SuppressWarnings("unchecked") + final O result = (O) ops().run(Ops.Filter.AddUniformNoise.class, out, in, rangeMin, + rangeMax); + return result; + } + + @OpMethod(op = net.imagej.ops.filter.addUniformNoise.AddUniformNoiseRealType.class) + public , O extends RealType> O addUniformNoise(final O out, + final I in, final double rangeMin, final double rangeMax, final long seed) + { + @SuppressWarnings("unchecked") + final O result = (O) ops().run(Ops.Filter.AddUniformNoise.class, out, in, rangeMin, + rangeMax, seed); + return result; + } // -- bilateral -- 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..2056e021ef --- /dev/null +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java @@ -0,0 +1,97 @@ +/* + * #%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; + +/** + * Sets the real output value of a {@link Realtype} T to a randomly generated + * value x, bounded by (and including) the {@code rangeMin} and {@code rangeMax} + * parameters, i.e. {@code rangeMin <= x <= rangeMax}. + * + * @author Gabe Selzer + */ +@Plugin(type = Ops.Filter.AddUniformNoise.class) +public class AddUniformNoiseRealType, O extends RealType> + extends AbstractUnaryComputerOp implements Ops.Filter.AddUniformNoise +{ + + @Parameter + private double rangeMin; + + @Parameter + private double rangeMax; + + @Parameter(required = false) + private long seed = 0xabcdef1234567890L; + + private MersenneTwisterFast rng; + + @Override + public void initialize() { + if (rng == null) rng = 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, O output) { + int i = 0; + do { + final double newVal = (rangeMax - rangeMin) * rng.nextDouble(true, true) + + rangeMin; + if ((rangeMin <= newVal) && (newVal <= rangeMax)) { + output.setReal(newVal); + return; + } + if (i++ > 100) { + throw new IllegalArgumentException( + "noise function failing to terminate. probably misconfigured."); + } + } + while (true); + } + +} diff --git a/src/main/templates/net/imagej/ops/Ops.list b/src/main/templates/net/imagej/ops/Ops.list index 77ab34a013..8aad09145e 100644 --- a/src/main/templates/net/imagej/ops/Ops.list +++ b/src/main/templates/net/imagej/ops/Ops.list @@ -99,6 +99,7 @@ namespaces = ``` [name: "filter", iface: "Filter", ops: [ [name: "addGaussianNoise", iface: "AddGaussianNoise"], [name: "addPoissonNoise", iface: "AddPoissonNoise"], + [name: "addUniformNoise", iface: "AddUniformNoise"], [name: "bilateral", iface: "Bilateral"], [name: "convolve", iface: "Convolve"], [name: "correlate", iface: "Correlate"], From 9cd84d10b98a3c05ac8e6b346139c72d9e8f19db Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Thu, 26 Apr 2018 17:30:36 -0500 Subject: [PATCH 3/9] Allow input image to influence noise generation In previous commits the op did not allow input image data to influence the output values. Now the algorithm allows, given a nonzero input image (in which all of the pixels do not have to be the same value), the input image to influence the output noise, i.e. if it is desired to add uniform noise with range [-4, 4] upon an imported image, the algorithm will now add a value x such that -4 <= x <= 4 to each pixel in the image, whatever value that may be, as long as it does not extend beyond the range of the type. --- .../ops/filter/addUniformNoise/AddUniformNoiseRealType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java index 2056e021ef..414649c15b 100644 --- a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java @@ -81,8 +81,8 @@ public void compute(I input, O output) { int i = 0; do { final double newVal = (rangeMax - rangeMin) * rng.nextDouble(true, true) + - rangeMin; - if ((rangeMin <= newVal) && (newVal <= rangeMax)) { + rangeMin + input.getRealDouble(); + if (newVal <= input.getMaxValue() && newVal >= input.getMinValue()) { output.setReal(newVal); return; } From 7121444c07fb15aa2c915525ca5639d1781f56ca Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Thu, 26 Apr 2018 17:35:22 -0500 Subject: [PATCH 4/9] Cleanup / format --- .../filter/addUniformNoise/AddUniformNoiseRealType.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java index 414649c15b..51b87a3101 100644 --- a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java @@ -38,7 +38,7 @@ import org.scijava.util.MersenneTwisterFast; /** - * Sets the real output value of a {@link Realtype} T to a randomly generated + * Sets the real output value of a {@link RealType} T to a randomly generated * value x, bounded by (and including) the {@code rangeMin} and {@code rangeMax} * parameters, i.e. {@code rangeMin <= x <= rangeMax}. * @@ -49,9 +49,15 @@ public class AddUniformNoiseRealType, O extends RealType 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; From 4aa53c64c519ce28caf7a164db42fb7d9b457da7 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 18 May 2020 11:41:24 -0500 Subject: [PATCH 5/9] Update Javadoc for cleaner explanation --- .../ops/filter/addUniformNoise/AddUniformNoiseRealType.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java index 51b87a3101..6c53fc54d9 100644 --- a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java @@ -38,9 +38,9 @@ import org.scijava.util.MersenneTwisterFast; /** - * Sets the real output value of a {@link RealType} T to a randomly generated - * value x, bounded by (and including) the {@code rangeMin} and {@code rangeMax} - * parameters, i.e. {@code rangeMin <= x <= rangeMax}. + * 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 */ From 5440061f7c8d51adec4ae3667b0a152952343023 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Mon, 18 May 2020 12:03:52 -0500 Subject: [PATCH 6/9] Clamp output when outside type bounds Previously the Op would recompute the calculation whenver the Mersenne Twister provided a random value that would result in the input going outside of the type bounds, in the hopes that another computation would provide a result that was inside the type bounds. Not only can this be slow, but also does not line up with what the user might expect. Suppose that the input to the Op is an UnsignedByteType of value 254, with rangeMin=0 and rangeMax=4. Since we have uniform noise, we would expect that the probability that the output has an equal probability of being 254, 255, 256, 257, 258 (with a probability of 0.2 for each). But we know that this UnsignedByteType cannot hold a value greater than 255. If the Op redoes the calculation every time the output is greater than 255, then we know that the output will be 254 half of the time and 255 half of the time. Therefore no probability matches what might be expected. Consider the same example, but instead of a recomputation we clamp the output when it is outside the type bounds. We now have P(output=254)=0.2, P(output=255)=0.8. In this case, all outputs within the type range will have the expected probability of occurring, which makes more sense than the scenario explained above. This is why the change was made. --- .../AddUniformNoiseRealType.java | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java index 6c53fc54d9..98d0f54429 100644 --- a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java @@ -45,8 +45,8 @@ * @author Gabe Selzer */ @Plugin(type = Ops.Filter.AddUniformNoise.class) -public class AddUniformNoiseRealType, O extends RealType> - extends AbstractUnaryComputerOp implements Ops.Filter.AddUniformNoise +public class AddUniformNoiseRealType> + extends AbstractUnaryComputerOp implements Ops.Filter.AddUniformNoise { /** @@ -83,21 +83,15 @@ public void initialize() { } @Override - public void compute(I input, O output) { - int i = 0; - do { - final double newVal = (rangeMax - rangeMin) * rng.nextDouble(true, true) + - rangeMin + input.getRealDouble(); - if (newVal <= input.getMaxValue() && newVal >= input.getMinValue()) { - output.setReal(newVal); - return; - } - if (i++ > 100) { - throw new IllegalArgumentException( - "noise function failing to terminate. probably misconfigured."); - } - } - while (true); + public void compute(I input, I output) { + final double newVal = (rangeMax - rangeMin) * rng.nextDouble(true, true) + + rangeMin + input.getRealDouble(); + if (newVal > input.getMaxValue()) + output.setReal(input.getMaxValue()); + else if (newVal < input.getMinValue()) + output.setReal(input.getMinValue()); + else + output.setReal(newVal); } } From fff0b3d7480ab554ccdc3f0f51bfc9f4db4cac55 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 19 May 2020 11:38:59 -0500 Subject: [PATCH 7/9] Separate Op into integer case and realType case This allows us to handle integer types and realTypes separately. The reason for this is as follows: It seems clear that we would want to allow floating point randomness when our type allows floating points If we always base our calculations on floating points, this results in edge effects when we add noise to integer types. This is due to rounding: suppose we have a pseudorandomly generated double between 0 and 2. Most IntegerTypes simply round that double to a long, integer, etc in setReal. Thus the probability we add 0 to the input is 0.25, 1 to the input is 0.5, and 2 to the input is 0.25. To get around this case we want to generate the random number differently when we have an integer type. For these reasons we have two different Ops to perform the different noise generation and have shared logic for setting the output. N.B. We do not allow wrapping for floating point types. This is because we operate within the following constraint: the number after the max of the type should map to the minimum of the type. In the case of floating points, what then should happen to a number equal to 0.5 + the maximum of the type? In this paradigm, we would expect it to be the minimum of the type - 0.5; this is impossible, however, since this would be outside of the range of the type. For this reason, we always clamp. --- .../imagej/ops/filter/FilterNamespace.java | 41 ++++++- .../AddUniformNoiseIntegerType.java | 100 ++++++++++++++++++ .../AddUniformNoiseRealType.java | 36 +++++-- 3 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java diff --git a/src/main/java/net/imagej/ops/filter/FilterNamespace.java b/src/main/java/net/imagej/ops/filter/FilterNamespace.java index 9f793d3973..b5a09816dc 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; @@ -131,25 +132,57 @@ public , O extends RealType> O addPoissonNoise( return result; } + // -- Uniform Noise -- + @OpMethod(op = net.imagej.ops.filter.addUniformNoise.AddUniformNoiseRealType.class) - public , O extends RealType> O addUniformNoise(final O out, + public > I addUniformNoise(final I out, final I in, final double rangeMin, final double rangeMax) { @SuppressWarnings("unchecked") - final O result = (O) ops().run(Ops.Filter.AddUniformNoise.class, out, in, rangeMin, + 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 , O extends RealType> O addUniformNoise(final O out, + public > I addUniformNoise(final I out, final I in, final double rangeMin, final double rangeMax, final long seed) { @SuppressWarnings("unchecked") - final O result = (O) ops().run(Ops.Filter.AddUniformNoise.class, out, in, rangeMin, + 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/addUniformNoise/AddUniformNoiseIntegerType.java b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java new file mode 100644 index 0000000000..a4e4d55d86 --- /dev/null +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java @@ -0,0 +1,100 @@ +/* + * #%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 = 0xabcdef1234567890L; + private long range; + + private MersenneTwisterFast rng; + + @Override + public void initialize() { + if (rng == null) rng = 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 index 98d0f54429..05f628b635 100644 --- a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java @@ -81,17 +81,39 @@ public void initialize() { 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(); - if (newVal > input.getMaxValue()) - output.setReal(input.getMaxValue()); - else if (newVal < input.getMinValue()) - output.setReal(input.getMinValue()); - else - output.setReal(newVal); + + 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); + } + } } From 0bc3d3dc76b38c212ce2df2ce7574b637ea478aa Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 19 May 2020 11:46:24 -0500 Subject: [PATCH 8/9] Add tests for Uniform Noise op --- .../addUniformNoise/AddUniformNoiseTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/test/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseTest.java 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..022f0893ed --- /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); + + 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); + + 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); + + 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); + + 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); + } + +} From d134a6c5d0a3e2b6f5cbf72afe477d239f98f794 Mon Sep 17 00:00:00 2001 From: Gabriel Selzer Date: Tue, 19 May 2020 13:56:24 -0500 Subject: [PATCH 9/9] Remove default seed The default seed resulted in non-random noise. Removing it ensures that the result is different every time (unless the user passes a seed) --- src/main/java/net/imagej/ops/filter/FilterNamespace.java | 4 ++-- .../addUniformNoise/AddUniformNoiseIntegerType.java | 6 ++++-- .../filter/addUniformNoise/AddUniformNoiseRealType.java | 6 ++++-- .../ops/filter/addUniformNoise/AddUniformNoiseTest.java | 8 ++++---- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/imagej/ops/filter/FilterNamespace.java b/src/main/java/net/imagej/ops/filter/FilterNamespace.java index b5a09816dc..2b7e74cf2c 100644 --- a/src/main/java/net/imagej/ops/filter/FilterNamespace.java +++ b/src/main/java/net/imagej/ops/filter/FilterNamespace.java @@ -146,7 +146,7 @@ public > I addUniformNoise(final I out, @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) + 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, @@ -176,7 +176,7 @@ public > I addUniformNoise(final I out, @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) + 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, diff --git a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java index a4e4d55d86..90d9942ce4 100644 --- a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseIntegerType.java @@ -72,14 +72,16 @@ public class AddUniformNoiseIntegerType> extends private boolean clampOutput = true; @Parameter(required = false) - private long seed = 0xabcdef1234567890L; + private Long seed; private long range; private MersenneTwisterFast rng; @Override public void initialize() { - if (rng == null) rng = new MersenneTwisterFast(seed); + // 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; diff --git a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java index 05f628b635..fa514a1ff6 100644 --- a/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java +++ b/src/main/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseRealType.java @@ -62,13 +62,15 @@ public class AddUniformNoiseRealType> private double rangeMax; @Parameter(required = false) - private long seed = 0xabcdef1234567890L; + private Long seed; private MersenneTwisterFast rng; @Override public void initialize() { - if (rng == null) rng = new MersenneTwisterFast(seed); + // 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; diff --git a/src/test/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseTest.java b/src/test/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseTest.java index 022f0893ed..4b695db048 100644 --- a/src/test/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseTest.java +++ b/src/test/java/net/imagej/ops/filter/addUniformNoise/AddUniformNoiseTest.java @@ -25,7 +25,7 @@ public class AddUniformNoiseTest extends AbstractOpTest { public void realTypeRegressionTest() { UnaryComputerOp noiseFunc = Computers.unary(ops, Ops.Filter.AddUniformNoise.class, DoubleType.class, DoubleType.class, 0d, - 2d); + 2d, 0xabcdef1234567890L); double[] actual = new double[9]; DoubleType temp = new DoubleType(); @@ -49,7 +49,7 @@ public void realTypeRegressionTest() { public void wrappingUpperEndRegressionTest() { UnaryComputerOp noiseFunc = Computers .unary(ops, Ops.Filter.AddUniformNoise.class, UnsignedByteType.class, - UnsignedByteType.class, 0l, 3l, false); + UnsignedByteType.class, 0l, 3l, false, 0xabcdef1234567890L); int[] actual = new int[9]; UnsignedByteType temp = new UnsignedByteType(); @@ -70,7 +70,7 @@ public void wrappingUpperEndRegressionTest() { public void wrappingLowerEndRegressionTest() { UnaryComputerOp noiseFunc = Computers .unary(ops, Ops.Filter.AddUniformNoise.class, UnsignedByteType.class, - UnsignedByteType.class, -3l, 0l, false); + UnsignedByteType.class, -3l, 0l, false, 0xabcdef1234567890L); int[] actual = new int[9]; UnsignedByteType temp = new UnsignedByteType(); @@ -91,7 +91,7 @@ public void wrappingLowerEndRegressionTest() { public void clampingLowerEndRegressionTest() { UnaryComputerOp noiseFunc = Computers .unary(ops, Ops.Filter.AddUniformNoise.class, UnsignedByteType.class, - UnsignedByteType.class, -3l, 0l); + UnsignedByteType.class, -3l, 0l, 0xabcdef1234567890L); int[] actual = new int[9]; UnsignedByteType temp = new UnsignedByteType();