Skip to content

Commit 770d565

Browse files
committed
Convert SACA QNorm helper class into an Op
This commit converts the QNorm.java helper class for SACA into an Op. This commit also includes a test for this new Op.
1 parent 5d06e5d commit 770d565

File tree

4 files changed

+101
-16
lines changed

4 files changed

+101
-16
lines changed

scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/SACASigMask.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import net.imglib2.util.Intervals;
3636

3737
import org.scijava.function.Computers;
38+
import org.scijava.function.Functions;
3839
import org.scijava.ops.spi.Nullable;
3940
import org.scijava.ops.spi.OpDependency;
4041

@@ -53,6 +54,9 @@ public class SACASigMask implements
5354
@OpDependency(name = "threshold.apply")
5455
private Computers.Arity2<RandomAccessibleInterval<DoubleType>, DoubleType, RandomAccessibleInterval<BitType>> thresOp;
5556

57+
@OpDependency(name = "stats.qnorm")
58+
private Functions.Arity5<Double, Double, Double, Boolean, Boolean, Double> qnormOp;
59+
5660
/**
5761
* Spatially Adaptive Colocalization Analysis (SACA) significant pixel mask.
5862
* This Op returns a binary mask of the significantly colocalized pixels from
@@ -85,7 +89,7 @@ public void compute(final RandomAccessibleInterval<DoubleType> heatmap,
8589
if (logP == null) logP = false;
8690

8791
// compute QNorm
88-
double thres = QNorm.compute(alpha / Intervals.numElements(heatmap), mean,
92+
double thres = qnormOp.apply(alpha / Intervals.numElements(heatmap), mean,
8993
sd, lowerTail, logP);
9094

9195
// apply QNorm thres and create significant pixel mask

scijava-ops-image/src/main/java/org/scijava/ops/image/coloc/saca/QNorm.java renamed to scijava-ops-image/src/main/java/org/scijava/ops/image/stats/DefaultQNorm.java

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,41 +27,65 @@
2727
* #L%
2828
*/
2929

30-
package org.scijava.ops.image.coloc.saca;
30+
package org.scijava.ops.image.stats;
31+
32+
import org.scijava.function.Computers;
33+
import org.scijava.function.Functions;
34+
import org.scijava.ops.spi.Nullable;
3135

3236
import org.apache.commons.math3.distribution.NormalDistribution;
3337

3438
/**
35-
* Helper class for Spatially Adaptive Colocalization Analysis (SACA) framework.
36-
* This class is used by the "coloc.saca.sigMask" Op to produce a binary mask of
37-
* significantly colocalized pixels. This class replicates R's qnorm function.
38-
*
3939
* @author Shulei Wang
4040
* @author Ellen TA Dobson
4141
* @author Curtis Rueden
42+
* @author Edward Evans
43+
* @implNote op names='stats.qnorm', priority='100.'
4244
*/
4345

44-
public final class QNorm {
46+
public final class DefaultQNorm implements
47+
Functions.Arity5<Double, Double, Double, Boolean, Boolean, Double>
48+
{
49+
50+
/**
51+
* Op to calculate the quantile function for a normal distribution.
52+
*
53+
* @param p
54+
* @param mean
55+
* @param sd
56+
* @param lowerTail
57+
* @param logP
58+
* @return quantiles for input probaility
59+
*/
60+
@Override
61+
public Double apply(Double p, @Nullable Double mean, @Nullable Double sd,
62+
@Nullable Boolean lowerTail, @Nullable Boolean logP)
63+
{
64+
// set mean if necessary
65+
if (mean == null) mean = 0.0;
66+
67+
// set sd if necessary
68+
if (sd == null) sd = 1.0;
4569

46-
private QNorm() {}
70+
// set lowerTail if necessary
71+
if (lowerTail == null) lowerTail = true;
4772

48-
public static double compute(final double p) {
73+
// set logP if necessary
74+
if (logP == null) logP = false;
75+
76+
// check the bounds of p
4977
if (p < 0 || p > 1) {
5078
return Double.NaN;
5179
}
5280
if (p == 0 || p == 1) {
5381
return Double.POSITIVE_INFINITY;
5482
}
5583

56-
return compute(p, 0, 1, true, false);
57-
}
58-
59-
public static double compute(double p, final double mean, final double sd,
60-
final boolean lowerTail, final boolean logP)
61-
{
84+
// compute QNorm
6285
final NormalDistribution dist = new NormalDistribution(mean, sd);
6386
if (logP) p = Math.exp(p);
6487
final double q = dist.inverseCumulativeProbability(p);
6588
return lowerTail ? q : -q;
89+
6690
}
6791
}

scijava-ops-image/src/test/java/org/scijava/ops/image/OpRegressionTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class OpRegressionTest {
4242

4343
@Test
4444
public void opDiscoveryRegressionIT() {
45-
long expected = 1937;
45+
long expected = 1942;
4646
long actual = ops.infos().size();
4747
assertEquals(expected, actual);
4848
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*-
2+
* #%L
3+
* Image processing operations for SciJava Ops.
4+
* %%
5+
* Copyright (C) 2014 - 2024 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package org.scijava.ops.image.stats;
31+
32+
import org.scijava.ops.image.AbstractOpTest;
33+
34+
import org.junit.jupiter.api.Test;
35+
import static org.junit.jupiter.api.Assertions.assertEquals;
36+
37+
/**
38+
* Test {@code stats.qnorm} op.
39+
*
40+
* @author Edward Evans
41+
*/
42+
43+
public class DefaultQNormTest extends AbstractOpTest {
44+
45+
@Test
46+
public void testCalculation() {
47+
final double[] input = { 0.05, 0.001, 1e-7, 1, 0, 0.99, -0.99, 1.1 };
48+
final double[] expected = { -1.644853626951473, -3.0902323061678136,
49+
-5.199337582187473, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY,
50+
2.326347874040841, Double.NaN, Double.NaN };
51+
52+
// assert calculations are equal
53+
for (int i = 0; i < input.length; i++) {
54+
assertEquals(expected[i], ops.op("stats.qnorm").input(input[i]).apply());
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)