From bd6df7851e5edda856ba3e04bc6fe8dd19925007 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 5 Nov 2024 14:00:20 -0500
Subject: [PATCH 01/56] Set up example dataset for background correction
---
.../embackground/CorrectBackground.java | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
new file mode 100644
index 000000000..f56458e74
--- /dev/null
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -0,0 +1,23 @@
+package org.janelia.render.client.embackground;
+
+import ij.ImageJ;
+import net.imglib2.img.Img;
+import net.imglib2.img.display.imagej.ImageJFunctions;
+import net.imglib2.type.numeric.integer.UnsignedByteType;
+import org.janelia.saalfeldlab.n5.N5FSReader;
+import org.janelia.saalfeldlab.n5.N5Reader;
+import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
+
+public class CorrectBackground {
+ private static final String containerPath = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
+ private static final String dataset = "data/s4";
+
+ public static void main(final String[] args) {
+ try (final N5Reader reader = new N5FSReader(containerPath)) {
+ final Img stack = N5Utils.open(reader, dataset);
+
+ new ImageJ();
+ ImageJFunctions.show(stack);
+ }
+ }
+}
From 125207878f3019949baf982763e922b6743feebb Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 5 Nov 2024 14:40:41 -0500
Subject: [PATCH 02/56] Add first proof of concept for background correction
---
.../embackground/CorrectBackground.java | 30 +++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index f56458e74..ffab76f35 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -1,23 +1,49 @@
package org.janelia.render.client.embackground;
import ij.ImageJ;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.RealRandomAccessible;
+import net.imglib2.converter.Converters;
import net.imglib2.img.Img;
import net.imglib2.img.display.imagej.ImageJFunctions;
+import net.imglib2.position.FunctionRealRandomAccessible;
import net.imglib2.type.numeric.integer.UnsignedByteType;
+import net.imglib2.type.numeric.real.FloatType;
+import net.imglib2.view.Views;
import org.janelia.saalfeldlab.n5.N5FSReader;
import org.janelia.saalfeldlab.n5.N5Reader;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
public class CorrectBackground {
private static final String containerPath = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
- private static final String dataset = "data/s4";
+ private static final String dataset = "data";
+ private static final int scale = 4;
public static void main(final String[] args) {
try (final N5Reader reader = new N5FSReader(containerPath)) {
- final Img stack = N5Utils.open(reader, dataset);
+ final Img stack = N5Utils.open(reader, dataset + "/s" + scale);
+
+ final int width = (int) stack.dimension(0);
+ final int height = (int) stack.dimension(1);
+
+ final int midX = width / 2;
+ final int midY = height / 2;
+
+ final RealRandomAccessible background = new FunctionRealRandomAccessible<>(3, (pos, value) -> {
+ final double x = (pos.getDoublePosition(0) - midX) / width;
+ final double y = (pos.getDoublePosition(1) - midY) / height;
+ value.setReal(1 - (x * x + y * y) / 2.0);
+ }, FloatType::new);
+
+ final RandomAccessibleInterval materializedBackground = Views.interval(Views.raster(background), stack);
+
+ final RandomAccessibleInterval corrected = Converters.convert(stack, materializedBackground, (s, b, o) -> {
+ o.set((int) (s.getRealDouble() / b.getRealDouble()));
+ }, new UnsignedByteType());
new ImageJ();
ImageJFunctions.show(stack);
+ ImageJFunctions.show(corrected);
}
}
}
From d5507657ee1ac0d595f3b67a5c27cb72d35e15e6 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 5 Nov 2024 16:51:50 -0500
Subject: [PATCH 03/56] Add quadratic background model
---
.../embackground/QuadraticBackground.java | 114 ++++++++++++++++++
.../embackground/QuadraticBackgroundTest.java | 52 ++++++++
2 files changed, 166 insertions(+)
create mode 100644 render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
create mode 100644 render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
new file mode 100644
index 000000000..f12e029ed
--- /dev/null
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
@@ -0,0 +1,114 @@
+package org.janelia.render.client.embackground;
+
+import mpicbg.models.AbstractModel;
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.NotEnoughDataPointsException;
+import mpicbg.models.PointMatch;
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.factory.LinearSolverFactory_DDRM;
+import org.ejml.interfaces.linsol.LinearSolverDense;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+public class QuadraticBackground extends AbstractModel {
+
+ private final int N_COEFFICIENTS = 6;
+ private final double[] coefficients = new double[N_COEFFICIENTS];
+
+
+ public QuadraticBackground() {
+ Arrays.fill(coefficients, 0);
+ }
+
+ public QuadraticBackground(final double[] coefficients) {
+ System.arraycopy(coefficients, 0, this.coefficients, 0, N_COEFFICIENTS);
+ }
+
+ public QuadraticBackground(final QuadraticBackground background) {
+ this(background.coefficients);
+ }
+
+ @Override
+ public int getMinNumMatches() {
+ return N_COEFFICIENTS;
+ }
+
+ @Override
+ public void fit(final Collection
matches) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ final DMatrixRMaj ATA = new DMatrixRMaj(N_COEFFICIENTS, N_COEFFICIENTS, true, new double[N_COEFFICIENTS * N_COEFFICIENTS]);
+ final DMatrixRMaj ATb = new DMatrixRMaj(N_COEFFICIENTS, 1);
+
+ final double[] rowA = new double[N_COEFFICIENTS];
+
+ for (final P match : matches) {
+ final double x = match.getP1().getL()[0];
+ final double y = match.getP1().getL()[1];
+ final double z = match.getP2().getL()[0];
+
+ // compute one row of the least-squares matrix A
+ rowA[0] = x * x;
+ rowA[1] = x * y;
+ rowA[2] = y * y;
+ rowA[3] = x;
+ rowA[4] = y;
+ rowA[5] = 1;
+
+ // update upper triangle of A^T * A
+ for (int i = 0; i < N_COEFFICIENTS; i++) {
+ for (int j = i; j < N_COEFFICIENTS; j++) {
+ ATA.data[i * N_COEFFICIENTS + j] += rowA[i] * rowA[j];
+ }
+ }
+
+ // update right-hand side A^T * b
+ for (int i = 0; i < N_COEFFICIENTS; i++) {
+ ATb.data[i] += rowA[i] * z;
+ }
+ }
+
+ // set up Cholesky decomposition for A^T * A x = A^T * b (only upper triangle of A^T * A is used)
+ final LinearSolverDense solver = LinearSolverFactory_DDRM.chol(3);
+ solver.setA(ATA);
+
+ // coefficients are modified in place
+ final DMatrixRMaj x = new DMatrixRMaj(N_COEFFICIENTS, 1);
+ x.setData(coefficients);
+ solver.solve(ATb, x);
+ }
+
+ @Override
+ public void set(final QuadraticBackground quadraticBackground) {
+
+ }
+
+ @Override
+ public QuadraticBackground copy() {
+ return null;
+ }
+
+ @Override
+ public double[] apply(final double[] location) {
+ final double[] result = location.clone();
+ applyInPlace(result);
+ return result;
+ }
+
+ @Override
+ public void applyInPlace(final double[] location) {
+ final double x = location[0];
+ final double y = location[1];
+ final double result = coefficients[0] * x * x
+ + coefficients[1] * x * y
+ + coefficients[2] * y * y
+ + coefficients[3] * x
+ + coefficients[4] * y
+ + coefficients[5];
+ location[0] = result;
+ location[1] = 0.0;
+ }
+
+ public double[] getCoefficients() {
+ return coefficients;
+ }
+}
diff --git a/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java b/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
new file mode 100644
index 000000000..b83262cc7
--- /dev/null
+++ b/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
@@ -0,0 +1,52 @@
+package org.janelia.render.client.embackground;
+
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.NotEnoughDataPointsException;
+import mpicbg.models.Point;
+import mpicbg.models.PointMatch;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class QuadraticBackgroundTest {
+
+ @Test
+ public void simpleModelIsComputedCorrectly() throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ final List matches = new ArrayList<>();
+
+ // minimal set of points to fit 0.5 * x^2 + 0.5 * y^2
+ matches.add(new PointMatch(new Point(new double[]{0, 0}), new Point(new double[]{0})));
+ matches.add(new PointMatch(new Point(new double[]{1, 1}), new Point(new double[]{1})));
+ matches.add(new PointMatch(new Point(new double[]{-1, 1}), new Point(new double[]{1})));
+ matches.add(new PointMatch(new Point(new double[]{1, -1}), new Point(new double[]{1})));
+ matches.add(new PointMatch(new Point(new double[]{-1, -1}), new Point(new double[]{1})));
+ matches.add(new PointMatch(new Point(new double[]{0, 1}), new Point(new double[]{0.5})));
+
+ final QuadraticBackground background = new QuadraticBackground();
+ background.fit(matches);
+
+ // order of coefficients: {x^2, xy, y^2, x, y, 1}
+ Assert.assertArrayEquals(new double[]{0.5, 0, 0.5, 0, 0, 0}, background.getCoefficients(), 1e-12);
+ }
+
+ @Test
+ public void modelCanBeRecoveredWithManyPoints() throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ final QuadraticBackground background = new QuadraticBackground(new double[]{1, 2, 3, 4, 5, 6});
+
+ final List matches = new ArrayList<>();
+ for (int i = 0; i < 100; i++) {
+ final double x = Math.random() * 2 - 1;
+ final double y = Math.random() * 2 - 1;
+ final double[] location = new double[]{x, y};
+ background.applyInPlace(location);
+ matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{location[0]})));
+ }
+
+ final QuadraticBackground recovered = new QuadraticBackground();
+ recovered.fit(matches);
+
+ Assert.assertArrayEquals(background.getCoefficients(), recovered.getCoefficients(), 1e-12);
+ }
+}
From 203645627db2ebf73a3585a7392e12a25c085d0d Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 5 Nov 2024 17:27:40 -0500
Subject: [PATCH 04/56] Only use first slice for correction example
---
.../client/embackground/CorrectBackground.java | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index ffab76f35..829f77d22 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -22,6 +22,7 @@ public class CorrectBackground {
public static void main(final String[] args) {
try (final N5Reader reader = new N5FSReader(containerPath)) {
final Img stack = N5Utils.open(reader, dataset + "/s" + scale);
+ final RandomAccessibleInterval firstSlice = Views.hyperSlice(stack, 2, 0);
final int width = (int) stack.dimension(0);
final int height = (int) stack.dimension(1);
@@ -29,21 +30,22 @@ public static void main(final String[] args) {
final int midX = width / 2;
final int midY = height / 2;
- final RealRandomAccessible background = new FunctionRealRandomAccessible<>(3, (pos, value) -> {
+ final RealRandomAccessible background = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
final double x = (pos.getDoublePosition(0) - midX) / width;
final double y = (pos.getDoublePosition(1) - midY) / height;
value.setReal(1 - (x * x + y * y) / 2.0);
}, FloatType::new);
- final RandomAccessibleInterval materializedBackground = Views.interval(Views.raster(background), stack);
- final RandomAccessibleInterval corrected = Converters.convert(stack, materializedBackground, (s, b, o) -> {
+ final RandomAccessibleInterval materializedBackground = Views.interval(Views.raster(background), firstSlice);
+
+ final RandomAccessibleInterval corrected = Converters.convert(firstSlice, materializedBackground, (s, b, o) -> {
o.set((int) (s.getRealDouble() / b.getRealDouble()));
}, new UnsignedByteType());
new ImageJ();
- ImageJFunctions.show(stack);
- ImageJFunctions.show(corrected);
+ ImageJFunctions.show(firstSlice, "Original");
+ ImageJFunctions.show(corrected, "Corrected");
}
}
}
From a22c35943017dfea150ff86b950227233c47c2b8 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 5 Nov 2024 17:42:07 -0500
Subject: [PATCH 05/56] Add toString method for better printing
---
.../client/embackground/QuadraticBackground.java | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
index f12e029ed..ef7f39924 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
@@ -111,4 +111,15 @@ public void applyInPlace(final double[] location) {
public double[] getCoefficients() {
return coefficients;
}
+
+ @Override
+ public String toString() {
+ return "QuadraticBackground{ "
+ + coefficients[0] + " * x^2 + "
+ + coefficients[1] + " * xy + "
+ + coefficients[2] + " * y^2 + "
+ + coefficients[3] + " * x + "
+ + coefficients[4] + " * y + "
+ + coefficients[5] + " }";
+ }
}
From 7c93790801918a3478570df1361c100a65e753e7 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 5 Nov 2024 17:42:30 -0500
Subject: [PATCH 06/56] Fit model from first slice
---
.../embackground/CorrectBackground.java | 38 ++++++++++++++++---
1 file changed, 33 insertions(+), 5 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index 829f77d22..1f96a2f30 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -1,6 +1,11 @@
package org.janelia.render.client.embackground;
import ij.ImageJ;
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.NotEnoughDataPointsException;
+import mpicbg.models.Point;
+import mpicbg.models.PointMatch;
+import net.imglib2.Cursor;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.RealRandomAccessible;
import net.imglib2.converter.Converters;
@@ -14,12 +19,15 @@
import org.janelia.saalfeldlab.n5.N5Reader;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
+import java.util.ArrayList;
+import java.util.List;
+
public class CorrectBackground {
private static final String containerPath = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
private static final String dataset = "data";
private static final int scale = 4;
- public static void main(final String[] args) {
+ public static void main(final String[] args) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
try (final N5Reader reader = new N5FSReader(containerPath)) {
final Img stack = N5Utils.open(reader, dataset + "/s" + scale);
final RandomAccessibleInterval firstSlice = Views.hyperSlice(stack, 2, 0);
@@ -30,13 +38,32 @@ public static void main(final String[] args) {
final int midX = width / 2;
final int midY = height / 2;
+ // fit a quadratic background model with all the points in the first slice
+ final QuadraticBackground backgroundModel = new QuadraticBackground();
+ final List matches = new ArrayList<>();
+ final Cursor cursor = Views.iterable(firstSlice).localizingCursor();
+ while (cursor.hasNext()) {
+ cursor.fwd();
+ final double x = (cursor.getDoublePosition(0) - midX) / width;
+ final double y = (cursor.getDoublePosition(1) - midY) / height;
+ final double z = cursor.get().getRealDouble();
+ matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{z})));
+ }
+ backgroundModel.fit(matches);
+ System.out.println("Fitted background model: " + backgroundModel);
+
+ // we assume that the model is concave, so the offset is the maximum value
+ final double maxValue = backgroundModel.getCoefficients()[5];
+
+ // create a background image from the model
+ final double[] location = new double[2];
final RealRandomAccessible background = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
- final double x = (pos.getDoublePosition(0) - midX) / width;
- final double y = (pos.getDoublePosition(1) - midY) / height;
- value.setReal(1 - (x * x + y * y) / 2.0);
+ location[0] = (pos.getDoublePosition(0) - midX) / width;
+ location[1] = (pos.getDoublePosition(1) - midY) / height;
+ backgroundModel.applyInPlace(location);
+ value.setReal(location[0] / maxValue);
}, FloatType::new);
-
final RandomAccessibleInterval materializedBackground = Views.interval(Views.raster(background), firstSlice);
final RandomAccessibleInterval corrected = Converters.convert(firstSlice, materializedBackground, (s, b, o) -> {
@@ -45,6 +72,7 @@ public static void main(final String[] args) {
new ImageJ();
ImageJFunctions.show(firstSlice, "Original");
+ ImageJFunctions.show(materializedBackground, "Background");
ImageJFunctions.show(corrected, "Corrected");
}
}
From 496e0c98a1bc53892932af70da1d4089831b861c Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 5 Nov 2024 18:04:55 -0500
Subject: [PATCH 07/56] Deal with overflow errors
---
.../janelia/render/client/embackground/CorrectBackground.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index 1f96a2f30..b9e23438c 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -39,6 +39,7 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
final int midY = height / 2;
// fit a quadratic background model with all the points in the first slice
+ final long start = System.currentTimeMillis();
final QuadraticBackground backgroundModel = new QuadraticBackground();
final List matches = new ArrayList<>();
final Cursor cursor = Views.iterable(firstSlice).localizingCursor();
@@ -51,6 +52,7 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
}
backgroundModel.fit(matches);
System.out.println("Fitted background model: " + backgroundModel);
+ System.out.println("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
// we assume that the model is concave, so the offset is the maximum value
final double maxValue = backgroundModel.getCoefficients()[5];
@@ -67,7 +69,7 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
final RandomAccessibleInterval materializedBackground = Views.interval(Views.raster(background), firstSlice);
final RandomAccessibleInterval corrected = Converters.convert(firstSlice, materializedBackground, (s, b, o) -> {
- o.set((int) (s.getRealDouble() / b.getRealDouble()));
+ o.set(UnsignedByteType.getCodedSignedByteChecked((int) (s.getRealDouble() / b.getRealDouble())));
}, new UnsignedByteType());
new ImageJ();
From 3ac4319fab129a458ba4bf8701aa735a6cacc17a Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 11:55:28 -0500
Subject: [PATCH 08/56] Add test for model evaluation
---
.../embackground/QuadraticBackgroundTest.java | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java b/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
index b83262cc7..216bdd8cd 100644
--- a/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
+++ b/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
@@ -12,6 +12,32 @@
public class QuadraticBackgroundTest {
+ @Test
+ public void simpleModelProducesCorrectResults() throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ // 0.5 * x^2 + 0.5 * y^2 + 1
+ final QuadraticBackground background = new QuadraticBackground(new double[]{0.5, 0, 0.5, 0, 0, 1});
+
+ final double[] location1 = new double[]{0, 0};
+ background.applyInPlace(location1);
+ Assert.assertEquals(1, location1[0], 1e-12);
+
+ final double[] location2 = new double[]{1, 1};
+ background.applyInPlace(location2);
+ Assert.assertEquals(2, location2[0], 1e-12);
+
+ final double[] location3 = new double[]{-1, 1};
+ background.applyInPlace(location3);
+ Assert.assertEquals(2, location3[0], 1e-12);
+
+ final double[] location4 = new double[]{1, 0};
+ background.applyInPlace(location4);
+ Assert.assertEquals(1.5, location4[0], 1e-12);
+
+ final double[] location5 = new double[]{0, 1};
+ background.applyInPlace(location5);
+ Assert.assertEquals(1.5, location5[0], 1e-12);
+ }
+
@Test
public void simpleModelIsComputedCorrectly() throws NotEnoughDataPointsException, IllDefinedDataPointsException {
final List matches = new ArrayList<>();
From cbac9d5cc60a9939114d59a88854864b847ecd82 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 12:00:53 -0500
Subject: [PATCH 09/56] Flip order of coefficients in quadratic model
---
.../embackground/CorrectBackground.java | 2 +-
.../embackground/QuadraticBackground.java | 42 ++++++++++---------
.../embackground/QuadraticBackgroundTest.java | 6 +--
3 files changed, 27 insertions(+), 23 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index b9e23438c..99c6d4733 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -55,7 +55,7 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
System.out.println("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
// we assume that the model is concave, so the offset is the maximum value
- final double maxValue = backgroundModel.getCoefficients()[5];
+ final double maxValue = backgroundModel.getCoefficients()[0];
// create a background image from the model
final double[] location = new double[2];
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
index ef7f39924..eac621ac8 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
@@ -11,6 +11,10 @@
import java.util.Arrays;
import java.util.Collection;
+
+/**
+ * A quadratic model for background correction in 2D slices of EM data.
+ */
public class QuadraticBackground extends AbstractModel {
private final int N_COEFFICIENTS = 6;
@@ -47,12 +51,12 @@ public void fit(final Collection
matches) throws NotEn
final double z = match.getP2().getL()[0];
// compute one row of the least-squares matrix A
- rowA[0] = x * x;
- rowA[1] = x * y;
- rowA[2] = y * y;
- rowA[3] = x;
- rowA[4] = y;
- rowA[5] = 1;
+ rowA[0] = 1;
+ rowA[1] = y;
+ rowA[2] = x;
+ rowA[3] = y * y;
+ rowA[4] = x * y;
+ rowA[5] = x * x;
// update upper triangle of A^T * A
for (int i = 0; i < N_COEFFICIENTS; i++) {
@@ -68,7 +72,7 @@ public
void fit(final Collection
matches) throws NotEn
}
// set up Cholesky decomposition for A^T * A x = A^T * b (only upper triangle of A^T * A is used)
- final LinearSolverDense solver = LinearSolverFactory_DDRM.chol(3);
+ final LinearSolverDense solver = LinearSolverFactory_DDRM.chol(N_COEFFICIENTS);
solver.setA(ATA);
// coefficients are modified in place
@@ -98,12 +102,12 @@ public double[] apply(final double[] location) {
public void applyInPlace(final double[] location) {
final double x = location[0];
final double y = location[1];
- final double result = coefficients[0] * x * x
- + coefficients[1] * x * y
- + coefficients[2] * y * y
- + coefficients[3] * x
- + coefficients[4] * y
- + coefficients[5];
+ final double result = coefficients[5] * x * x
+ + coefficients[4] * x * y
+ + coefficients[3] * y * y
+ + coefficients[2] * x
+ + coefficients[1] * y
+ + coefficients[0];
location[0] = result;
location[1] = 0.0;
}
@@ -115,11 +119,11 @@ public double[] getCoefficients() {
@Override
public String toString() {
return "QuadraticBackground{ "
- + coefficients[0] + " * x^2 + "
- + coefficients[1] + " * xy + "
- + coefficients[2] + " * y^2 + "
- + coefficients[3] + " * x + "
- + coefficients[4] + " * y + "
- + coefficients[5] + " }";
+ + coefficients[5] + " x^2 + "
+ + coefficients[4] + " xy + "
+ + coefficients[3] + " y^2 + "
+ + coefficients[2] + " x + "
+ + coefficients[1] + " y + "
+ + coefficients[0] + " }";
}
}
diff --git a/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java b/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
index 216bdd8cd..6c4fc0bc0 100644
--- a/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
+++ b/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
@@ -15,7 +15,7 @@ public class QuadraticBackgroundTest {
@Test
public void simpleModelProducesCorrectResults() throws NotEnoughDataPointsException, IllDefinedDataPointsException {
// 0.5 * x^2 + 0.5 * y^2 + 1
- final QuadraticBackground background = new QuadraticBackground(new double[]{0.5, 0, 0.5, 0, 0, 1});
+ final QuadraticBackground background = new QuadraticBackground(new double[]{1, 0, 0, 0.5, 0, 0.5});
final double[] location1 = new double[]{0, 0};
background.applyInPlace(location1);
@@ -53,8 +53,8 @@ public void simpleModelIsComputedCorrectly() throws NotEnoughDataPointsException
final QuadraticBackground background = new QuadraticBackground();
background.fit(matches);
- // order of coefficients: {x^2, xy, y^2, x, y, 1}
- Assert.assertArrayEquals(new double[]{0.5, 0, 0.5, 0, 0, 0}, background.getCoefficients(), 1e-12);
+ // order of coefficients: {1, y, x, y^2, x*y, x^2}
+ Assert.assertArrayEquals(new double[]{0, 0, 0, 0.5, 0, 0.5}, background.getCoefficients(), 1e-12);
}
@Test
From 040c88406f3860a09c2e3fc7ed846ea5b279c26f Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 12:09:36 -0500
Subject: [PATCH 10/56] Add fourth-order model
---
.../embackground/FourthOrderBackground.java | 143 ++++++++++++++++++
1 file changed, 143 insertions(+)
create mode 100644 render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
new file mode 100644
index 000000000..f0ced78bf
--- /dev/null
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
@@ -0,0 +1,143 @@
+package org.janelia.render.client.embackground;
+
+import mpicbg.models.AbstractModel;
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.NotEnoughDataPointsException;
+import mpicbg.models.PointMatch;
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.factory.LinearSolverFactory_DDRM;
+import org.ejml.interfaces.linsol.LinearSolverDense;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+
+/**
+ * A 4th order model for background correction in 2D slices of EM data.
+ * To ensure that the model is concave, no 3rd order terms are included.
+ */
+public class FourthOrderBackground extends AbstractModel {
+
+ private final int N_COEFFICIENTS = 9;
+ private final double[] coefficients = new double[N_COEFFICIENTS];
+
+
+ public FourthOrderBackground() {
+ Arrays.fill(coefficients, 0);
+ }
+
+ public FourthOrderBackground(final double[] coefficients) {
+ System.arraycopy(coefficients, 0, this.coefficients, 0, N_COEFFICIENTS);
+ }
+
+ public FourthOrderBackground(final FourthOrderBackground background) {
+ this(background.coefficients);
+ }
+
+ @Override
+ public int getMinNumMatches() {
+ return N_COEFFICIENTS;
+ }
+
+ @Override
+ public void fit(final Collection
matches) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ final DMatrixRMaj ATA = new DMatrixRMaj(N_COEFFICIENTS, N_COEFFICIENTS, true, new double[N_COEFFICIENTS * N_COEFFICIENTS]);
+ final DMatrixRMaj ATb = new DMatrixRMaj(N_COEFFICIENTS, 1);
+
+ final double[] rowA = new double[N_COEFFICIENTS];
+
+ for (final P match : matches) {
+ final double x = match.getP1().getL()[0];
+ final double y = match.getP1().getL()[1];
+ final double z = match.getP2().getL()[0];
+
+ // compute one row of the least-squares matrix A
+ final double xx = x * x;
+ final double yy = y * y;
+ rowA[0] = 1;
+ rowA[1] = y;
+ rowA[2] = x;
+ rowA[3] = yy;
+ rowA[4] = x * y;
+ rowA[5] = xx;
+ rowA[6] = yy * yy;
+ rowA[7] = xx * yy;
+ rowA[8] = xx * xx;
+
+ // update upper triangle of A^T * A
+ for (int i = 0; i < N_COEFFICIENTS; i++) {
+ for (int j = i; j < N_COEFFICIENTS; j++) {
+ ATA.data[i * N_COEFFICIENTS + j] += rowA[i] * rowA[j];
+ }
+ }
+
+ // update right-hand side A^T * b
+ for (int i = 0; i < N_COEFFICIENTS; i++) {
+ ATb.data[i] += rowA[i] * z;
+ }
+ }
+
+ // set up Cholesky decomposition for A^T * A x = A^T * b (only upper triangle of A^T * A is used)
+ final LinearSolverDense solver = LinearSolverFactory_DDRM.chol(N_COEFFICIENTS);
+ solver.setA(ATA);
+
+ // coefficients are modified in place
+ final DMatrixRMaj x = new DMatrixRMaj(N_COEFFICIENTS, 1);
+ x.setData(coefficients);
+ solver.solve(ATb, x);
+ }
+
+ @Override
+ public void set(final FourthOrderBackground quadraticBackground) {
+
+ }
+
+ @Override
+ public FourthOrderBackground copy() {
+ return null;
+ }
+
+ @Override
+ public double[] apply(final double[] location) {
+ final double[] result = location.clone();
+ applyInPlace(result);
+ return result;
+ }
+
+ @Override
+ public void applyInPlace(final double[] location) {
+ final double x = location[0];
+ final double y = location[1];
+ final double xx = x * x;
+ final double yy = y * y;
+ final double result = coefficients[8] * xx * xx
+ + coefficients[7] * xx * yy
+ + coefficients[6] * yy * yy
+ + coefficients[5] * xx
+ + coefficients[4] * x * y
+ + coefficients[3] * yy
+ + coefficients[2] * x
+ + coefficients[1] * y
+ + coefficients[0];
+ location[0] = result;
+ location[1] = 0.0;
+ }
+
+ public double[] getCoefficients() {
+ return coefficients;
+ }
+
+ @Override
+ public String toString() {
+ return "FourthOrderBackground{ "
+ + coefficients[8] + " x^4 + "
+ + coefficients[7] + " x^2 y^2 + "
+ + coefficients[6] + " y^4 + "
+ + coefficients[5] + " x^2 + "
+ + coefficients[4] + " xy + "
+ + coefficients[3] + " y^2 + "
+ + coefficients[2] + " x + "
+ + coefficients[1] + " y + "
+ + coefficients[0] + " }";
+ }
+}
From 7d7e9ad75adb5c8616e44335a268ea85036d5ce3 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 14:07:41 -0500
Subject: [PATCH 11/56] Load ROIs from disk
---
.../embackground/CorrectBackground.java | 49 ++++++++++++++++++-
1 file changed, 47 insertions(+), 2 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index 99c6d4733..ba8be0b64 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -1,6 +1,8 @@
package org.janelia.render.client.embackground;
import ij.ImageJ;
+import ij.gui.Roi;
+import ij.io.RoiDecoder;
import mpicbg.models.IllDefinedDataPointsException;
import mpicbg.models.NotEnoughDataPointsException;
import mpicbg.models.Point;
@@ -19,15 +21,20 @@
import org.janelia.saalfeldlab.n5.N5Reader;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.Enumeration;
import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
public class CorrectBackground {
private static final String containerPath = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
+ private static final String roiPath = containerPath + "/roi-set.zip";
private static final String dataset = "data";
private static final int scale = 4;
- public static void main(final String[] args) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ public static void main(final String[] args) throws NotEnoughDataPointsException, IllDefinedDataPointsException, IOException {
try (final N5Reader reader = new N5FSReader(containerPath)) {
final Img stack = N5Utils.open(reader, dataset + "/s" + scale);
final RandomAccessibleInterval firstSlice = Views.hyperSlice(stack, 2, 0);
@@ -38,9 +45,14 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
final int midX = width / 2;
final int midY = height / 2;
+ final List rois = readRois(roiPath);
+ if (rois.isEmpty()) {
+ throw new IllegalArgumentException("No ROIs found in " + roiPath);
+ }
+
// fit a quadratic background model with all the points in the first slice
final long start = System.currentTimeMillis();
- final QuadraticBackground backgroundModel = new QuadraticBackground();
+ final FourthOrderBackground backgroundModel = new FourthOrderBackground();
final List matches = new ArrayList<>();
final Cursor cursor = Views.iterable(firstSlice).localizingCursor();
while (cursor.hasNext()) {
@@ -78,4 +90,37 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
ImageJFunctions.show(corrected, "Corrected");
}
}
+
+ private static List readRois(final String path) throws IOException {
+ if (path.endsWith(".zip")) {
+ return extractRoisFromZip(path);
+ } else {
+ final RoiDecoder rd = new RoiDecoder(path);
+ final Roi roi = rd.getRoi();
+ return List.of(roi);
+ }
+ }
+
+ private static List extractRoisFromZip(final String path) throws IOException {
+ final List rois = new ArrayList<>();
+
+ try (final ZipFile zipFile = new ZipFile(path)) {
+ final Enumeration extends ZipEntry> entries = zipFile.entries();
+
+ while (entries.hasMoreElements()) {
+ final ZipEntry entry = entries.nextElement();
+ final String name = entry.getName();
+
+ if (name.endsWith(".roi")) {
+ final RoiDecoder rd = new RoiDecoder(zipFile.getInputStream(entry).readAllBytes(), entry.getName());
+ final Roi roi = rd.getRoi();
+ if (roi != null) {
+ rois.add(roi);
+ }
+ }
+ }
+ }
+
+ return rois;
+ }
}
From 4731a558ba7305a6998a774db1de2b96ec6fd168 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 14:30:03 -0500
Subject: [PATCH 12/56] Use only points in ROI to fit model
---
.../embackground/CorrectBackground.java | 34 ++++++++++++++-----
1 file changed, 25 insertions(+), 9 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index ba8be0b64..2b8da6f6e 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -7,11 +7,12 @@
import mpicbg.models.NotEnoughDataPointsException;
import mpicbg.models.Point;
import mpicbg.models.PointMatch;
-import net.imglib2.Cursor;
+import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.RealRandomAccessible;
import net.imglib2.converter.Converters;
import net.imglib2.img.Img;
+import net.imglib2.img.array.ArrayImgs;
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.position.FunctionRealRandomAccessible;
import net.imglib2.type.numeric.integer.UnsignedByteType;
@@ -23,8 +24,11 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -42,24 +46,35 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
final int width = (int) stack.dimension(0);
final int height = (int) stack.dimension(1);
- final int midX = width / 2;
- final int midY = height / 2;
+ final double midX = width / 2.0;
+ final double midY = height / 2.0;
final List rois = readRois(roiPath);
if (rois.isEmpty()) {
throw new IllegalArgumentException("No ROIs found in " + roiPath);
}
+ final Set pointsOfInterest = new HashSet<>();
+ for (final Roi roi : rois) {
+ final java.awt.Point[] containedPoints = roi.getContainedPoints();
+ Collections.addAll(pointsOfInterest, containedPoints);
+ }
+
+ final Img roiImg = ArrayImgs.unsignedBytes(width, height);
+ final RandomAccess roiRa = roiImg.randomAccess();
+ for (final java.awt.Point point : pointsOfInterest) {
+ roiRa.setPositionAndGet(point.x, point.y).set(255);
+ }
+
// fit a quadratic background model with all the points in the first slice
final long start = System.currentTimeMillis();
final FourthOrderBackground backgroundModel = new FourthOrderBackground();
final List matches = new ArrayList<>();
- final Cursor cursor = Views.iterable(firstSlice).localizingCursor();
- while (cursor.hasNext()) {
- cursor.fwd();
- final double x = (cursor.getDoublePosition(0) - midX) / width;
- final double y = (cursor.getDoublePosition(1) - midY) / height;
- final double z = cursor.get().getRealDouble();
+ final RandomAccess ra = firstSlice.randomAccess();
+ for (final java.awt.Point point : pointsOfInterest) {
+ final double x = (point.x - midX) / width;
+ final double y = (point.y - midY) / height;
+ final double z = ra.setPositionAndGet(point.x, point.y).getRealDouble();
matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{z})));
}
backgroundModel.fit(matches);
@@ -88,6 +103,7 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
ImageJFunctions.show(firstSlice, "Original");
ImageJFunctions.show(materializedBackground, "Background");
ImageJFunctions.show(corrected, "Corrected");
+ ImageJFunctions.show(roiImg, "ROI");
}
}
From fef0e151ddf7afab64f087d651d562afbe7b0b46 Mon Sep 17 00:00:00 2001
From: Stephan Preibisch
Date: Wed, 6 Nov 2024 15:11:30 -0500
Subject: [PATCH 13/56] added basic layout for IJ plugin with a hack
---
.../render/client/embackground/BG_Plugin.java | 101 ++++++++++++++++++
1 file changed, 101 insertions(+)
create mode 100644 render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
new file mode 100644
index 000000000..11eed30a3
--- /dev/null
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -0,0 +1,101 @@
+package org.janelia.render.client.embackground;
+
+import java.awt.FlowLayout;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+
+import org.janelia.saalfeldlab.n5.N5FSReader;
+import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
+
+import ij.IJ;
+import ij.ImageJ;
+import ij.gui.GenericDialog;
+import ij.gui.Roi;
+import ij.plugin.PlugIn;
+import ij.plugin.frame.RoiManager;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.display.imagej.ImageJFunctions;
+import net.imglib2.view.Views;
+
+public class BG_Plugin implements PlugIn
+{
+ public static int defaultType = 0;
+ public static String[] fitTypes = new String[] { "Quadratic", "Fourth Order" };
+
+ @Override
+ public void run(String arg)
+ {
+ GenericDialog gd = new GenericDialog( "fit..." );
+
+ gd.addChoice( "fit_type", fitTypes, fitTypes[ defaultType ]);
+ gd.showDialog();
+
+ if (gd.wasCanceled() )
+ return;
+
+ int type = defaultType = gd.getNextChoiceIndex();
+
+ fit( type );
+ }
+
+ public static Roi getROIs()
+ {
+ RoiManager rm = RoiManager.getInstance();
+
+ if (rm == null || rm.getCount() == 0)
+ {
+ IJ.log( "please define rois ... " );
+ return null;
+ }
+
+ int numRois = rm.getCount();
+ Roi r = null;
+
+ return r;
+ }
+
+ public static void fit( int type )
+ {
+ IJ.log( "fitting with ... " + fitTypes[ type ] );
+ }
+
+ private static void showNonBlockingDialog() {
+ new Thread(() -> {
+ JDialog dialog = new JDialog((JFrame)null, "Run Background plugin...", false);
+ dialog.setLayout(new FlowLayout());
+
+ JButton closeButton = new JButton("Run Background plugin...");
+ closeButton.addActionListener(e -> {
+ new BG_Plugin().run( null );
+ });
+ dialog.add(closeButton);
+
+ dialog.pack();
+ dialog.setVisible(true);
+ }).start();
+ }
+
+ public static void main( String[] args )
+ {
+ new ImageJ();
+
+ SwingUtilities.invokeLater(() ->
+ {
+ showNonBlockingDialog();
+ });
+
+ // open image
+ String n5Path = "/Volumes/public/ForPreibisch/cerebellum-3.n5";
+
+ RandomAccessibleInterval img = N5Utils.open( new N5FSReader( n5Path ), "data/s4" );
+ int z = 0;
+ img = Views.hyperSlice( img, 2, z );
+ ImageJFunctions.show( img );
+
+ //
+ //new BG_Plugin().run( null );
+ }
+}
From 111b4a94c027ce5c4112ccb85f0e92731a46840f Mon Sep 17 00:00:00 2001
From: Stephan Preibisch
Date: Wed, 6 Nov 2024 15:12:59 -0500
Subject: [PATCH 14/56] add roi call
---
.../org/janelia/render/client/embackground/BG_Plugin.java | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
index 11eed30a3..9f4aa4d86 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -28,6 +28,11 @@ public class BG_Plugin implements PlugIn
@Override
public void run(String arg)
{
+ Roi rois = getROIs();
+
+ if ( rois == null )
+ return;
+
GenericDialog gd = new GenericDialog( "fit..." );
gd.addChoice( "fit_type", fitTypes, fitTypes[ defaultType ]);
From 8e13997a387adedb5b354bebbbaec52a19a0ca26 Mon Sep 17 00:00:00 2001
From: Stephan Preibisch
Date: Wed, 6 Nov 2024 15:15:43 -0500
Subject: [PATCH 15/56] small fixes
---
.../org/janelia/render/client/embackground/BG_Plugin.java | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
index 9f4aa4d86..c3b485cc5 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -57,9 +57,7 @@ public static Roi getROIs()
}
int numRois = rm.getCount();
- Roi r = null;
-
- return r;
+ return rm.getRoi( 0 );
}
public static void fit( int type )
@@ -99,7 +97,8 @@ public static void main( String[] args )
int z = 0;
img = Views.hyperSlice( img, 2, z );
ImageJFunctions.show( img );
-
+
+ new RoiManager(); // Create a new instance
//
//new BG_Plugin().run( null );
}
From df805d8f1a9efb0ffc6d919fbdfea8296d396198 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 16:09:18 -0500
Subject: [PATCH 16/56] Fix IDE warnings
---
.../render/client/embackground/BG_Plugin.java | 73 ++++++++-----------
1 file changed, 31 insertions(+), 42 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
index c3b485cc5..187588473 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -20,60 +20,56 @@
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.view.Views;
-public class BG_Plugin implements PlugIn
-{
+public class BG_Plugin implements PlugIn {
+
public static int defaultType = 0;
public static String[] fitTypes = new String[] { "Quadratic", "Fourth Order" };
@Override
- public void run(String arg)
- {
- Roi rois = getROIs();
+ public void run(final String arg) {
+ final Roi rois = getROIs();
- if ( rois == null )
+ if (rois == null) {
return;
+ }
- GenericDialog gd = new GenericDialog( "fit..." );
+ final GenericDialog gd = new GenericDialog("fit...");
- gd.addChoice( "fit_type", fitTypes, fitTypes[ defaultType ]);
+ gd.addChoice("fit_type", fitTypes, fitTypes[defaultType]);
gd.showDialog();
- if (gd.wasCanceled() )
+ if (gd.wasCanceled()) {
return;
+ }
- int type = defaultType = gd.getNextChoiceIndex();
+ defaultType = gd.getNextChoiceIndex();
+ final int type = gd.getNextChoiceIndex();
- fit( type );
+ fit(type);
}
- public static Roi getROIs()
- {
- RoiManager rm = RoiManager.getInstance();
+ public static Roi getROIs() {
+ final RoiManager rm = RoiManager.getInstance();
- if (rm == null || rm.getCount() == 0)
- {
- IJ.log( "please define rois ... " );
+ if (rm == null || rm.getCount() == 0) {
+ IJ.log("please define rois ... ");
return null;
}
- int numRois = rm.getCount();
return rm.getRoi( 0 );
}
- public static void fit( int type )
- {
- IJ.log( "fitting with ... " + fitTypes[ type ] );
+ public static void fit(final int type) {
+ IJ.log("fitting with ... " + fitTypes[type]);
}
private static void showNonBlockingDialog() {
new Thread(() -> {
- JDialog dialog = new JDialog((JFrame)null, "Run Background plugin...", false);
+ final JDialog dialog = new JDialog((JFrame)null, "Run Background plugin...", false);
dialog.setLayout(new FlowLayout());
- JButton closeButton = new JButton("Run Background plugin...");
- closeButton.addActionListener(e -> {
- new BG_Plugin().run( null );
- });
+ final JButton closeButton = new JButton("Run Background plugin...");
+ closeButton.addActionListener(e -> new BG_Plugin().run(null ));
dialog.add(closeButton);
dialog.pack();
@@ -81,25 +77,18 @@ private static void showNonBlockingDialog() {
}).start();
}
- public static void main( String[] args )
- {
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public static void main(final String[] args) {
new ImageJ();
- SwingUtilities.invokeLater(() ->
- {
- showNonBlockingDialog();
- });
-
- // open image
- String n5Path = "/Volumes/public/ForPreibisch/cerebellum-3.n5";
+ SwingUtilities.invokeLater(BG_Plugin::showNonBlockingDialog);
- RandomAccessibleInterval img = N5Utils.open( new N5FSReader( n5Path ), "data/s4" );
- int z = 0;
- img = Views.hyperSlice( img, 2, z );
- ImageJFunctions.show( img );
+ final String n5Path = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
+ RandomAccessibleInterval img = N5Utils.open(new N5FSReader(n5Path), "data/s4");
+ final int z = 0;
+ img = Views.hyperSlice(img, 2, z);
+ ImageJFunctions.show(img);
- new RoiManager(); // Create a new instance
- //
- //new BG_Plugin().run( null );
+ new RoiManager();
}
}
From fa439c2fc6452bcf6500e60164f0f315092a4359 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 17:58:51 -0500
Subject: [PATCH 17/56] Modularize CorrectBackground
---
.../embackground/CorrectBackground.java | 145 +++++++++++-------
1 file changed, 91 insertions(+), 54 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index 2b8da6f6e..e83f2fed1 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -12,10 +12,12 @@
import net.imglib2.RealRandomAccessible;
import net.imglib2.converter.Converters;
import net.imglib2.img.Img;
-import net.imglib2.img.array.ArrayImgs;
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.position.FunctionRealRandomAccessible;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.RealType;
import net.imglib2.type.numeric.integer.UnsignedByteType;
+import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.view.Views;
import org.janelia.saalfeldlab.n5.N5FSReader;
@@ -33,81 +35,116 @@
import java.util.zip.ZipFile;
public class CorrectBackground {
- private static final String containerPath = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
- private static final String roiPath = containerPath + "/roi-set.zip";
- private static final String dataset = "data";
- private static final int scale = 4;
public static void main(final String[] args) throws NotEnoughDataPointsException, IllDefinedDataPointsException, IOException {
+ final String containerPath = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
+ final String roiPath = containerPath + "/roi-set.zip";
+ final String dataset = "data";
+ final int scale = 4;
+
try (final N5Reader reader = new N5FSReader(containerPath)) {
final Img stack = N5Utils.open(reader, dataset + "/s" + scale);
final RandomAccessibleInterval firstSlice = Views.hyperSlice(stack, 2, 0);
-
- final int width = (int) stack.dimension(0);
- final int height = (int) stack.dimension(1);
-
- final double midX = width / 2.0;
- final double midY = height / 2.0;
-
final List rois = readRois(roiPath);
- if (rois.isEmpty()) {
- throw new IllegalArgumentException("No ROIs found in " + roiPath);
- }
-
- final Set pointsOfInterest = new HashSet<>();
- for (final Roi roi : rois) {
- final java.awt.Point[] containedPoints = roi.getContainedPoints();
- Collections.addAll(pointsOfInterest, containedPoints);
- }
- final Img roiImg = ArrayImgs.unsignedBytes(width, height);
- final RandomAccess roiRa = roiImg.randomAccess();
- for (final java.awt.Point point : pointsOfInterest) {
- roiRa.setPositionAndGet(point.x, point.y).set(255);
+ if (rois.isEmpty()) {
+ throw new IllegalArgumentException("No ROIs found");
}
- // fit a quadratic background model with all the points in the first slice
final long start = System.currentTimeMillis();
- final FourthOrderBackground backgroundModel = new FourthOrderBackground();
- final List matches = new ArrayList<>();
- final RandomAccess ra = firstSlice.randomAccess();
- for (final java.awt.Point point : pointsOfInterest) {
- final double x = (point.x - midX) / width;
- final double y = (point.y - midY) / height;
- final double z = ra.setPositionAndGet(point.x, point.y).getRealDouble();
- matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{z})));
- }
- backgroundModel.fit(matches);
+ final FourthOrderBackground backgroundModel = fitBackgroundModel(rois, firstSlice);
System.out.println("Fitted background model: " + backgroundModel);
System.out.println("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
- // we assume that the model is concave, so the offset is the maximum value
- final double maxValue = backgroundModel.getCoefficients()[0];
+ final RandomAccessibleInterval background = createBackgroundImage(backgroundModel, firstSlice);
+ final RandomAccessibleInterval corrected = correctBackground(firstSlice, background, new UnsignedByteType());
- // create a background image from the model
- final double[] location = new double[2];
- final RealRandomAccessible background = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
- location[0] = (pos.getDoublePosition(0) - midX) / width;
- location[1] = (pos.getDoublePosition(1) - midY) / height;
- backgroundModel.applyInPlace(location);
- value.setReal(location[0] / maxValue);
- }, FloatType::new);
+ new ImageJ();
+ ImageJFunctions.show(firstSlice, "Original");
+ ImageJFunctions.show(background, "Background");
+ ImageJFunctions.show(corrected, "Corrected");
+ }
+ }
- final RandomAccessibleInterval materializedBackground = Views.interval(Views.raster(background), firstSlice);
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static & RealType>
+ RandomAccessibleInterval correctBackground(final RandomAccessibleInterval slice, final RandomAccessibleInterval background, final T type) {
- final RandomAccessibleInterval corrected = Converters.convert(firstSlice, materializedBackground, (s, b, o) -> {
+ final RandomAccessibleInterval corrected;
+ if (type instanceof UnsignedByteType) {
+ corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
o.set(UnsignedByteType.getCodedSignedByteChecked((int) (s.getRealDouble() / b.getRealDouble())));
}, new UnsignedByteType());
+ } else if (type instanceof UnsignedShortType) {
+ corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
+ o.set(UnsignedShortType.getCodedSignedShortChecked((int) (s.getRealDouble() / b.getRealDouble())));
+ }, new UnsignedShortType());
+ } else {
+ throw new IllegalArgumentException("Unsupported type: " + type.getClass());
+ }
- new ImageJ();
- ImageJFunctions.show(firstSlice, "Original");
- ImageJFunctions.show(materializedBackground, "Background");
- ImageJFunctions.show(corrected, "Corrected");
- ImageJFunctions.show(roiImg, "ROI");
+ return corrected;
+ }
+
+
+ public static & RealType>
+ RandomAccessibleInterval createBackgroundImage(final FourthOrderBackground backgroundModel, final RandomAccessibleInterval slice) {
+ final int width = (int) slice.dimension(0);
+ final int height = (int) slice.dimension(1);
+
+ final double midX = width / 2.0;
+ final double midY = height / 2.0;
+
+ // we assume that the model is concave, so the offset is the maximum value
+ final double maxValue = backgroundModel.getCoefficients()[0];
+
+ final double[] location = new double[2];
+ final RealRandomAccessible background = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
+ location[0] = (pos.getDoublePosition(0) - midX) / width;
+ location[1] = (pos.getDoublePosition(1) - midY) / height;
+ backgroundModel.applyInPlace(location);
+ value.setReal(location[0] / maxValue);
+ }, FloatType::new);
+
+ return Views.interval(Views.raster(background), slice);
+ }
+
+ public static & RealType>
+ FourthOrderBackground fitBackgroundModel(final List rois, final RandomAccessibleInterval slice)
+ throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ final int width = (int) slice.dimension(0);
+ final int height = (int) slice.dimension(1);
+
+ final double midX = width / 2.0;
+ final double midY = height / 2.0;
+
+ // fit a quadratic background model with all the points in the first slice
+ final FourthOrderBackground backgroundModel = new FourthOrderBackground();
+ final List matches = new ArrayList<>();
+ final RandomAccess ra = slice.randomAccess();
+
+ for (final java.awt.Point point : extractInterestPoints(rois)) {
+ final double x = (point.x - midX) / width;
+ final double y = (point.y - midY) / height;
+ final double z = ra.setPositionAndGet(point.x, point.y).getRealDouble();
+ matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{z})));
+ }
+ backgroundModel.fit(matches);
+
+ return backgroundModel;
+ }
+
+ private static Set extractInterestPoints(final List rois) {
+
+ final Set pointsOfInterest = new HashSet<>();
+ for (final Roi roi : rois) {
+ final java.awt.Point[] containedPoints = roi.getContainedPoints();
+ Collections.addAll(pointsOfInterest, containedPoints);
}
+ return pointsOfInterest;
}
- private static List readRois(final String path) throws IOException {
+ public static List readRois(final String path) throws IOException {
if (path.endsWith(".zip")) {
return extractRoisFromZip(path);
} else {
From 77db485e91580398d023fc5060576c5a47dfd182 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 18:23:22 -0500
Subject: [PATCH 18/56] Extract abstract BackgroundModel super class
---
.../client/embackground/BackgroundModel.java | 109 ++++++++++++++++
.../embackground/CorrectBackground.java | 4 +-
.../embackground/FourthOrderBackground.java | 117 ++++--------------
.../embackground/QuadraticBackground.java | 104 +++-------------
4 files changed, 154 insertions(+), 180 deletions(-)
create mode 100644 render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
new file mode 100644
index 000000000..e82b12931
--- /dev/null
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
@@ -0,0 +1,109 @@
+package org.janelia.render.client.embackground;
+
+import mpicbg.models.AbstractModel;
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.NotEnoughDataPointsException;
+import mpicbg.models.PointMatch;
+import org.ejml.data.DMatrixRMaj;
+import org.ejml.dense.row.factory.LinearSolverFactory_DDRM;
+import org.ejml.interfaces.linsol.LinearSolverDense;
+
+import java.util.Collection;
+import java.util.List;
+
+
+/**
+ * An abstract base model for background correction in 2D slices of EM data.
+ */
+public abstract class BackgroundModel> extends AbstractModel {
+
+ private final double[] coefficients;
+
+ protected abstract int nCoefficients();
+ protected abstract void fillRowA(final double[] rowA, final double x, final double y);
+ protected abstract List coefficientNames();
+
+
+ public BackgroundModel() {
+ coefficients = new double[nCoefficients()];
+ }
+
+ public BackgroundModel(final double[] coefficients) {
+ this();
+ System.arraycopy(coefficients, 0, this.coefficients, 0, nCoefficients());
+ }
+
+ public double[] getCoefficients() {
+ return coefficients;
+ }
+
+ @Override
+ public int getMinNumMatches() {
+ return nCoefficients();
+ }
+
+ @Override
+ public void fit(final Collection
matches) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ final DMatrixRMaj ATA = new DMatrixRMaj(nCoefficients(), nCoefficients(), true, new double[nCoefficients() * nCoefficients()]);
+ final DMatrixRMaj ATb = new DMatrixRMaj(nCoefficients(), 1);
+
+ final double[] rowA = new double[nCoefficients()];
+
+ for (final P match : matches) {
+ final double x = match.getP1().getL()[0];
+ final double y = match.getP1().getL()[1];
+ final double z = match.getP2().getL()[0];
+
+ // compute one row of the least-squares matrix A
+ fillRowA(rowA, x, y);
+
+ // update upper triangle of A^T * A
+ for (int i = 0; i < nCoefficients(); i++) {
+ for (int j = i; j < nCoefficients(); j++) {
+ ATA.data[i * nCoefficients() + j] += rowA[i] * rowA[j];
+ }
+ }
+
+ // update right-hand side A^T * b
+ for (int i = 0; i < nCoefficients(); i++) {
+ ATb.data[i] += rowA[i] * z;
+ }
+ }
+
+ // set up Cholesky decomposition for A^T * A x = A^T * b (only upper triangle of A^T * A is used)
+ final LinearSolverDense solver = LinearSolverFactory_DDRM.chol(nCoefficients());
+ solver.setA(ATA);
+
+ // coefficients are modified in place
+ final DMatrixRMaj x = new DMatrixRMaj(nCoefficients(), 1);
+ x.setData(coefficients);
+ solver.solve(ATb, x);
+ }
+
+ @Override
+ public double[] apply(final double[] location) {
+ final double[] result = location.clone();
+ applyInPlace(result);
+ return result;
+ }
+
+ @Override
+ public void set(final T model) {
+ System.arraycopy(model.getCoefficients(), 0, coefficients, 0, nCoefficients());
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("BackgroundModel{ ");
+
+ for (int i = nCoefficients() - 1; i > 0; i--) {
+ sb.append(coefficients[i])
+ .append(" ")
+ .append(coefficientNames().get(i))
+ .append(" + ");
+ }
+
+ sb.append(coefficients[0]);
+ return sb.append("}").toString();
+ }
+}
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index e83f2fed1..0662ca528 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -52,7 +52,7 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
}
final long start = System.currentTimeMillis();
- final FourthOrderBackground backgroundModel = fitBackgroundModel(rois, firstSlice);
+ final BackgroundModel> backgroundModel = fitBackgroundModel(rois, firstSlice);
System.out.println("Fitted background model: " + backgroundModel);
System.out.println("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
@@ -88,7 +88,7 @@ RandomAccessibleInterval correctBackground(final RandomAccessibleInterval
public static & RealType>
- RandomAccessibleInterval createBackgroundImage(final FourthOrderBackground backgroundModel, final RandomAccessibleInterval slice) {
+ RandomAccessibleInterval createBackgroundImage(final BackgroundModel> backgroundModel, final RandomAccessibleInterval slice) {
final int width = (int) slice.dimension(0);
final int height = (int) slice.dimension(1);
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
index f0ced78bf..6cf8d9678 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
@@ -1,107 +1,50 @@
package org.janelia.render.client.embackground;
-import mpicbg.models.AbstractModel;
-import mpicbg.models.IllDefinedDataPointsException;
-import mpicbg.models.NotEnoughDataPointsException;
-import mpicbg.models.PointMatch;
-import org.ejml.data.DMatrixRMaj;
-import org.ejml.dense.row.factory.LinearSolverFactory_DDRM;
-import org.ejml.interfaces.linsol.LinearSolverDense;
-
-import java.util.Arrays;
-import java.util.Collection;
+import java.util.List;
/**
* A 4th order model for background correction in 2D slices of EM data.
* To ensure that the model is concave, no 3rd order terms are included.
*/
-public class FourthOrderBackground extends AbstractModel {
-
- private final int N_COEFFICIENTS = 9;
- private final double[] coefficients = new double[N_COEFFICIENTS];
-
+public class FourthOrderBackground extends BackgroundModel {
public FourthOrderBackground() {
- Arrays.fill(coefficients, 0);
+ super();
}
public FourthOrderBackground(final double[] coefficients) {
- System.arraycopy(coefficients, 0, this.coefficients, 0, N_COEFFICIENTS);
+ super(coefficients);
}
public FourthOrderBackground(final FourthOrderBackground background) {
- this(background.coefficients);
- }
-
- @Override
- public int getMinNumMatches() {
- return N_COEFFICIENTS;
+ super(background.getCoefficients());
}
- @Override
- public void fit(final Collection
matches) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
- final DMatrixRMaj ATA = new DMatrixRMaj(N_COEFFICIENTS, N_COEFFICIENTS, true, new double[N_COEFFICIENTS * N_COEFFICIENTS]);
- final DMatrixRMaj ATb = new DMatrixRMaj(N_COEFFICIENTS, 1);
-
- final double[] rowA = new double[N_COEFFICIENTS];
-
- for (final P match : matches) {
- final double x = match.getP1().getL()[0];
- final double y = match.getP1().getL()[1];
- final double z = match.getP2().getL()[0];
-
- // compute one row of the least-squares matrix A
- final double xx = x * x;
- final double yy = y * y;
- rowA[0] = 1;
- rowA[1] = y;
- rowA[2] = x;
- rowA[3] = yy;
- rowA[4] = x * y;
- rowA[5] = xx;
- rowA[6] = yy * yy;
- rowA[7] = xx * yy;
- rowA[8] = xx * xx;
-
- // update upper triangle of A^T * A
- for (int i = 0; i < N_COEFFICIENTS; i++) {
- for (int j = i; j < N_COEFFICIENTS; j++) {
- ATA.data[i * N_COEFFICIENTS + j] += rowA[i] * rowA[j];
- }
- }
-
- // update right-hand side A^T * b
- for (int i = 0; i < N_COEFFICIENTS; i++) {
- ATb.data[i] += rowA[i] * z;
- }
- }
-
- // set up Cholesky decomposition for A^T * A x = A^T * b (only upper triangle of A^T * A is used)
- final LinearSolverDense solver = LinearSolverFactory_DDRM.chol(N_COEFFICIENTS);
- solver.setA(ATA);
-
- // coefficients are modified in place
- final DMatrixRMaj x = new DMatrixRMaj(N_COEFFICIENTS, 1);
- x.setData(coefficients);
- solver.solve(ATb, x);
- }
@Override
- public void set(final FourthOrderBackground quadraticBackground) {
-
+ protected int nCoefficients() {
+ return 9;
}
@Override
- public FourthOrderBackground copy() {
- return null;
+ protected void fillRowA(final double[] rowA, final double x, final double y) {
+ final double xx = x * x;
+ final double yy = y * y;
+ rowA[0] = 1;
+ rowA[1] = y;
+ rowA[2] = x;
+ rowA[3] = yy;
+ rowA[4] = x * y;
+ rowA[5] = xx;
+ rowA[6] = yy * yy;
+ rowA[7] = xx * yy;
+ rowA[8] = xx * xx;
}
@Override
- public double[] apply(final double[] location) {
- final double[] result = location.clone();
- applyInPlace(result);
- return result;
+ protected List coefficientNames() {
+ return List.of("x^4", "x^2 y^2", "y^4", "x^2", "xy", "y^2", "x", "y", "1");
}
@Override
@@ -110,6 +53,7 @@ public void applyInPlace(final double[] location) {
final double y = location[1];
final double xx = x * x;
final double yy = y * y;
+ final double[] coefficients = getCoefficients();
final double result = coefficients[8] * xx * xx
+ coefficients[7] * xx * yy
+ coefficients[6] * yy * yy
@@ -123,21 +67,8 @@ public void applyInPlace(final double[] location) {
location[1] = 0.0;
}
- public double[] getCoefficients() {
- return coefficients;
- }
-
@Override
- public String toString() {
- return "FourthOrderBackground{ "
- + coefficients[8] + " x^4 + "
- + coefficients[7] + " x^2 y^2 + "
- + coefficients[6] + " y^4 + "
- + coefficients[5] + " x^2 + "
- + coefficients[4] + " xy + "
- + coefficients[3] + " y^2 + "
- + coefficients[2] + " x + "
- + coefficients[1] + " y + "
- + coefficients[0] + " }";
+ public FourthOrderBackground copy() {
+ return new FourthOrderBackground(this);
}
}
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
index eac621ac8..38da87e47 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
@@ -1,107 +1,51 @@
package org.janelia.render.client.embackground;
-import mpicbg.models.AbstractModel;
-import mpicbg.models.IllDefinedDataPointsException;
-import mpicbg.models.NotEnoughDataPointsException;
-import mpicbg.models.PointMatch;
-import org.ejml.data.DMatrixRMaj;
-import org.ejml.dense.row.factory.LinearSolverFactory_DDRM;
-import org.ejml.interfaces.linsol.LinearSolverDense;
-
-import java.util.Arrays;
-import java.util.Collection;
+import java.util.List;
/**
* A quadratic model for background correction in 2D slices of EM data.
*/
-public class QuadraticBackground extends AbstractModel {
-
- private final int N_COEFFICIENTS = 6;
- private final double[] coefficients = new double[N_COEFFICIENTS];
-
+public class QuadraticBackground extends BackgroundModel {
public QuadraticBackground() {
- Arrays.fill(coefficients, 0);
+ super();
}
public QuadraticBackground(final double[] coefficients) {
- System.arraycopy(coefficients, 0, this.coefficients, 0, N_COEFFICIENTS);
+ super(coefficients);
}
public QuadraticBackground(final QuadraticBackground background) {
- this(background.coefficients);
- }
-
- @Override
- public int getMinNumMatches() {
- return N_COEFFICIENTS;
+ super(background.getCoefficients());
}
- @Override
- public void fit(final Collection
matches) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
- final DMatrixRMaj ATA = new DMatrixRMaj(N_COEFFICIENTS, N_COEFFICIENTS, true, new double[N_COEFFICIENTS * N_COEFFICIENTS]);
- final DMatrixRMaj ATb = new DMatrixRMaj(N_COEFFICIENTS, 1);
-
- final double[] rowA = new double[N_COEFFICIENTS];
-
- for (final P match : matches) {
- final double x = match.getP1().getL()[0];
- final double y = match.getP1().getL()[1];
- final double z = match.getP2().getL()[0];
-
- // compute one row of the least-squares matrix A
- rowA[0] = 1;
- rowA[1] = y;
- rowA[2] = x;
- rowA[3] = y * y;
- rowA[4] = x * y;
- rowA[5] = x * x;
-
- // update upper triangle of A^T * A
- for (int i = 0; i < N_COEFFICIENTS; i++) {
- for (int j = i; j < N_COEFFICIENTS; j++) {
- ATA.data[i * N_COEFFICIENTS + j] += rowA[i] * rowA[j];
- }
- }
-
- // update right-hand side A^T * b
- for (int i = 0; i < N_COEFFICIENTS; i++) {
- ATb.data[i] += rowA[i] * z;
- }
- }
-
- // set up Cholesky decomposition for A^T * A x = A^T * b (only upper triangle of A^T * A is used)
- final LinearSolverDense solver = LinearSolverFactory_DDRM.chol(N_COEFFICIENTS);
- solver.setA(ATA);
-
- // coefficients are modified in place
- final DMatrixRMaj x = new DMatrixRMaj(N_COEFFICIENTS, 1);
- x.setData(coefficients);
- solver.solve(ATb, x);
- }
@Override
- public void set(final QuadraticBackground quadraticBackground) {
-
+ protected int nCoefficients() {
+ return 6;
}
@Override
- public QuadraticBackground copy() {
- return null;
+ protected void fillRowA(final double[] rowA, final double x, final double y) {
+ rowA[0] = 1;
+ rowA[1] = y;
+ rowA[2] = x;
+ rowA[3] = y * y;
+ rowA[4] = x * y;
+ rowA[5] = x * x;
}
@Override
- public double[] apply(final double[] location) {
- final double[] result = location.clone();
- applyInPlace(result);
- return result;
+ protected List coefficientNames() {
+ return List.of("x^2", "xy", "y^2", "x", "y", "1");
}
@Override
public void applyInPlace(final double[] location) {
final double x = location[0];
final double y = location[1];
+ final double[] coefficients = getCoefficients();
final double result = coefficients[5] * x * x
+ coefficients[4] * x * y
+ coefficients[3] * y * y
@@ -112,18 +56,8 @@ public void applyInPlace(final double[] location) {
location[1] = 0.0;
}
- public double[] getCoefficients() {
- return coefficients;
- }
-
@Override
- public String toString() {
- return "QuadraticBackground{ "
- + coefficients[5] + " x^2 + "
- + coefficients[4] + " xy + "
- + coefficients[3] + " y^2 + "
- + coefficients[2] + " x + "
- + coefficients[1] + " y + "
- + coefficients[0] + " }";
+ public QuadraticBackground copy() {
+ return new QuadraticBackground(this);
}
}
From de6a040b8b3ed3ea7ca15c7fb361c8ea16358cf1 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 18:25:43 -0500
Subject: [PATCH 19/56] Make model fit generic
---
.../render/client/embackground/CorrectBackground.java | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index 0662ca528..d1f9b1b59 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -52,7 +52,8 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
}
final long start = System.currentTimeMillis();
- final BackgroundModel> backgroundModel = fitBackgroundModel(rois, firstSlice);
+ final BackgroundModel> backgroundModel = new FourthOrderBackground();
+ fitBackgroundModel(rois, firstSlice, backgroundModel);
System.out.println("Fitted background model: " + backgroundModel);
System.out.println("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
@@ -110,7 +111,7 @@ RandomAccessibleInterval createBackgroundImage(final BackgroundModel<
}
public static & RealType>
- FourthOrderBackground fitBackgroundModel(final List rois, final RandomAccessibleInterval slice)
+ void fitBackgroundModel(final List rois, final RandomAccessibleInterval slice, final BackgroundModel> backgroundModel)
throws NotEnoughDataPointsException, IllDefinedDataPointsException {
final int width = (int) slice.dimension(0);
final int height = (int) slice.dimension(1);
@@ -119,7 +120,6 @@ FourthOrderBackground fitBackgroundModel(final List rois, final RandomAcces
final double midY = height / 2.0;
// fit a quadratic background model with all the points in the first slice
- final FourthOrderBackground backgroundModel = new FourthOrderBackground();
final List matches = new ArrayList<>();
final RandomAccess ra = slice.randomAccess();
@@ -130,8 +130,6 @@ FourthOrderBackground fitBackgroundModel(final List rois, final RandomAcces
matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{z})));
}
backgroundModel.fit(matches);
-
- return backgroundModel;
}
private static Set extractInterestPoints(final List rois) {
From 863bc3943b6fea72641e718aa29cd9bd626791ca Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 18:40:08 -0500
Subject: [PATCH 20/56] Make plugin actually compute background
---
.../render/client/embackground/BG_Plugin.java | 60 +++++++++++++++----
1 file changed, 47 insertions(+), 13 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
index 187588473..bec18ae4f 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -1,12 +1,18 @@
package org.janelia.render.client.embackground;
import java.awt.FlowLayout;
+import java.util.Arrays;
+import java.util.List;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.NotEnoughDataPointsException;
+import net.imglib2.type.numeric.integer.UnsignedByteType;
+import net.imglib2.type.numeric.real.FloatType;
import org.janelia.saalfeldlab.n5.N5FSReader;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
@@ -25,42 +31,71 @@ public class BG_Plugin implements PlugIn {
public static int defaultType = 0;
public static String[] fitTypes = new String[] { "Quadratic", "Fourth Order" };
+ public static RandomAccessibleInterval img;
+
@Override
public void run(final String arg) {
- final Roi rois = getROIs();
+ final List rois = getROIs();
- if (rois == null) {
+ if (rois == null || rois.isEmpty()) {
+ IJ.log("No ROIs specified.");
return;
}
- final GenericDialog gd = new GenericDialog("fit...");
+ final GenericDialog gd = new GenericDialog("Fit background correction");
- gd.addChoice("fit_type", fitTypes, fitTypes[defaultType]);
+ gd.addChoice("Fit type", fitTypes, fitTypes[defaultType]);
gd.showDialog();
if (gd.wasCanceled()) {
return;
}
- defaultType = gd.getNextChoiceIndex();
final int type = gd.getNextChoiceIndex();
+ defaultType = type;
- fit(type);
+ try {
+ fit(type, rois);
+ } catch (final NotEnoughDataPointsException | IllDefinedDataPointsException e) {
+ IJ.log("Fitting failed: " + e.getMessage());
+ }
}
- public static Roi getROIs() {
+ public static List getROIs() {
final RoiManager rm = RoiManager.getInstance();
if (rm == null || rm.getCount() == 0) {
- IJ.log("please define rois ... ");
+ IJ.log("Please define ROIs first before running background correction.");
return null;
}
- return rm.getRoi( 0 );
+ return Arrays.asList(rm.getRoisAsArray());
}
- public static void fit(final int type) {
- IJ.log("fitting with ... " + fitTypes[type]);
+ public static void fit(final int type, final List rois) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ IJ.log("Fitting with " + fitTypes[type] + " model...");
+
+ final BackgroundModel> backgroundModel;
+ if (fitTypes[type].equals("Quadratic")) {
+ backgroundModel = new QuadraticBackground();
+ } else if (fitTypes[type].equals("Fourth Order")) {
+ backgroundModel = new FourthOrderBackground();
+ } else {
+ throw new IllegalArgumentException("Unknown fit type: " + fitTypes[type]);
+ }
+
+ final long start = System.currentTimeMillis();
+ CorrectBackground.fitBackgroundModel(rois, img, backgroundModel);
+ IJ.log("Fitted background model: " + backgroundModel);
+ IJ.log("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
+ IJ.log("Raw coefficients: " + Arrays.toString(backgroundModel.getCoefficients()));
+
+ final RandomAccessibleInterval background = CorrectBackground.createBackgroundImage(backgroundModel, img);
+ final RandomAccessibleInterval corrected = CorrectBackground.correctBackground(img, background, new UnsignedByteType());
+
+ ImageJFunctions.show(background, "Background");
+ ImageJFunctions.show(corrected, "Corrected");
+
}
private static void showNonBlockingDialog() {
@@ -77,14 +112,13 @@ private static void showNonBlockingDialog() {
}).start();
}
- @SuppressWarnings({"unchecked", "rawtypes"})
public static void main(final String[] args) {
new ImageJ();
SwingUtilities.invokeLater(BG_Plugin::showNonBlockingDialog);
final String n5Path = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
- RandomAccessibleInterval img = N5Utils.open(new N5FSReader(n5Path), "data/s4");
+ img = N5Utils.open(new N5FSReader(n5Path), "data/s4");
final int z = 0;
img = Views.hyperSlice(img, 2, z);
ImageJFunctions.show(img);
From ee0e584a9f758f7e341eadc0789ecc6efdff80b1 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 6 Nov 2024 18:51:56 -0500
Subject: [PATCH 21/56] Add option to show background image or not
---
.../render/client/embackground/BG_Plugin.java | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
index bec18ae4f..77df41bc1 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -29,6 +29,7 @@
public class BG_Plugin implements PlugIn {
public static int defaultType = 0;
+ public static boolean defaultShowBackground = false;
public static String[] fitTypes = new String[] { "Quadratic", "Fourth Order" };
public static RandomAccessibleInterval img;
@@ -45,6 +46,7 @@ public void run(final String arg) {
final GenericDialog gd = new GenericDialog("Fit background correction");
gd.addChoice("Fit type", fitTypes, fitTypes[defaultType]);
+ gd.addCheckbox("Show background image", defaultShowBackground);
gd.showDialog();
if (gd.wasCanceled()) {
@@ -54,8 +56,11 @@ public void run(final String arg) {
final int type = gd.getNextChoiceIndex();
defaultType = type;
+ final boolean showBackground = gd.getNextBoolean();
+ defaultShowBackground = showBackground;
+
try {
- fit(type, rois);
+ fit(type, rois, showBackground);
} catch (final NotEnoughDataPointsException | IllDefinedDataPointsException e) {
IJ.log("Fitting failed: " + e.getMessage());
}
@@ -72,7 +77,7 @@ public static List getROIs() {
return Arrays.asList(rm.getRoisAsArray());
}
- public static void fit(final int type, final List rois) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ public static void fit(final int type, final List rois, final boolean showBackground) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
IJ.log("Fitting with " + fitTypes[type] + " model...");
final BackgroundModel> backgroundModel;
@@ -93,7 +98,9 @@ public static void fit(final int type, final List rois) throws NotEnoughDat
final RandomAccessibleInterval background = CorrectBackground.createBackgroundImage(backgroundModel, img);
final RandomAccessibleInterval corrected = CorrectBackground.correctBackground(img, background, new UnsignedByteType());
- ImageJFunctions.show(background, "Background");
+ if (showBackground) {
+ ImageJFunctions.show(background, "Background");
+ }
ImageJFunctions.show(corrected, "Corrected");
}
From 837b44aa17b470bc7e7e482297891dc302ac2504 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 7 Nov 2024 11:34:43 -0500
Subject: [PATCH 22/56] Fix and improve BackgroundModel::toString
---
.../client/embackground/BackgroundModel.java | 19 ++++++++++++++-----
.../embackground/FourthOrderBackground.java | 2 +-
.../embackground/QuadraticBackground.java | 2 +-
3 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
index e82b12931..5bf922d0e 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
@@ -94,16 +94,25 @@ public void set(final T model) {
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder("BackgroundModel{ ");
+ final StringBuilder sb = new StringBuilder("BackgroundModel{");
+
+ if (coefficients[nCoefficients() - 1] < 0) {
+ sb.append("-");
+ }
for (int i = nCoefficients() - 1; i > 0; i--) {
- sb.append(coefficients[i])
+ sb.append(String.format("%.2f", Math.abs(coefficients[i])))
.append(" ")
- .append(coefficientNames().get(i))
- .append(" + ");
+ .append(coefficientNames().get(i));
+
+ if (coefficients[i - 1] >= 0) {
+ sb.append(" + ");
+ } else {
+ sb.append(" - ");
+ }
}
- sb.append(coefficients[0]);
+ sb.append(String.format("%.2f", Math.abs(coefficients[0])));
return sb.append("}").toString();
}
}
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
index 6cf8d9678..d03278928 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
@@ -44,7 +44,7 @@ protected void fillRowA(final double[] rowA, final double x, final double y) {
@Override
protected List coefficientNames() {
- return List.of("x^4", "x^2 y^2", "y^4", "x^2", "xy", "y^2", "x", "y", "1");
+ return List.of("1", "y", "x", "y^2", "xy", "x^2", "y^4", "x^2 y^2", "x^4");
}
@Override
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
index 38da87e47..b51795315 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
@@ -38,7 +38,7 @@ protected void fillRowA(final double[] rowA, final double x, final double y) {
@Override
protected List coefficientNames() {
- return List.of("x^2", "xy", "y^2", "x", "y", "1");
+ return List.of("1", "y", "x", "y^2", "xy", "x^2");
}
@Override
From 51e95e4eb75637a292a8747de47cd36463090b6a Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 7 Nov 2024 11:50:53 -0500
Subject: [PATCH 23/56] Add normalization and correct background additively
---
.../client/embackground/BackgroundModel.java | 23 +++++++++++++
.../embackground/CorrectBackground.java | 34 ++++++++-----------
2 files changed, 38 insertions(+), 19 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
index 5bf922d0e..4add63c4c 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
@@ -37,6 +37,29 @@ public double[] getCoefficients() {
return coefficients;
}
+ /**
+ * Normalize the model so that it approximately integrates to zero over [-1, 1] x [-1, 1].
+ * This assumes that the constant term is stored in the first coefficient.
+ */
+ public void normalize() {
+ final int N = 10 * nCoefficients();
+ final double[] location = new double[2];
+ double avg = 0;
+
+ // approximate integral over [-1, 1] x [-1, 1] by averaging the function values at a grid of points
+ for (int i = 0; i < N; i++) {
+ for (int j = 0; j < N; j++) {
+ location[0] = 2.0 * i / N - 1.0;
+ location[1] = 2.0 * j / N - 1.0;
+ applyInPlace(location);
+ avg += location[0];
+ }
+ }
+
+ avg /= (N * N);
+ coefficients[0] -= avg;
+ }
+
@Override
public int getMinNumMatches() {
return nCoefficients();
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index d1f9b1b59..2b57cf9dc 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -37,6 +37,7 @@
public class CorrectBackground {
public static void main(final String[] args) throws NotEnoughDataPointsException, IllDefinedDataPointsException, IOException {
+
final String containerPath = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
final String roiPath = containerPath + "/roi-set.zip";
final String dataset = "data";
@@ -74,11 +75,11 @@ RandomAccessibleInterval correctBackground(final RandomAccessibleInterval
final RandomAccessibleInterval corrected;
if (type instanceof UnsignedByteType) {
corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
- o.set(UnsignedByteType.getCodedSignedByteChecked((int) (s.getRealDouble() / b.getRealDouble())));
+ o.set(UnsignedByteType.getCodedSignedByteChecked((int) (s.getRealDouble() - b.getRealDouble())));
}, new UnsignedByteType());
} else if (type instanceof UnsignedShortType) {
corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
- o.set(UnsignedShortType.getCodedSignedShortChecked((int) (s.getRealDouble() / b.getRealDouble())));
+ o.set(UnsignedShortType.getCodedSignedShortChecked((int) (s.getRealDouble() - b.getRealDouble())));
}, new UnsignedShortType());
} else {
throw new IllegalArgumentException("Unsupported type: " + type.getClass());
@@ -87,24 +88,19 @@ RandomAccessibleInterval correctBackground(final RandomAccessibleInterval
return corrected;
}
-
public static & RealType>
RandomAccessibleInterval createBackgroundImage(final BackgroundModel> backgroundModel, final RandomAccessibleInterval slice) {
- final int width = (int) slice.dimension(0);
- final int height = (int) slice.dimension(1);
-
- final double midX = width / 2.0;
- final double midY = height / 2.0;
- // we assume that the model is concave, so the offset is the maximum value
- final double maxValue = backgroundModel.getCoefficients()[0];
+ // transform pixel coordinates into [-1, 1] x [-1, 1]
+ final double scaleX = slice.dimension(0) / 2.0;
+ final double scaleY = slice.dimension(1) / 2.0;
final double[] location = new double[2];
final RealRandomAccessible background = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
- location[0] = (pos.getDoublePosition(0) - midX) / width;
- location[1] = (pos.getDoublePosition(1) - midY) / height;
+ location[0] = (pos.getDoublePosition(0) - scaleX) / scaleX;
+ location[1] = (pos.getDoublePosition(1) - scaleY) / scaleY;
backgroundModel.applyInPlace(location);
- value.setReal(location[0] / maxValue);
+ value.setReal(location[0]);
}, FloatType::new);
return Views.interval(Views.raster(background), slice);
@@ -113,23 +109,23 @@ RandomAccessibleInterval createBackgroundImage(final BackgroundModel<
public static & RealType>
void fitBackgroundModel(final List rois, final RandomAccessibleInterval slice, final BackgroundModel> backgroundModel)
throws NotEnoughDataPointsException, IllDefinedDataPointsException {
- final int width = (int) slice.dimension(0);
- final int height = (int) slice.dimension(1);
- final double midX = width / 2.0;
- final double midY = height / 2.0;
+ // transform pixel coordinates into [-1, 1] x [-1, 1]
+ final double scaleX = slice.dimension(0) / 2.0;
+ final double scaleY = slice.dimension(1) / 2.0;
// fit a quadratic background model with all the points in the first slice
final List matches = new ArrayList<>();
final RandomAccess ra = slice.randomAccess();
for (final java.awt.Point point : extractInterestPoints(rois)) {
- final double x = (point.x - midX) / width;
- final double y = (point.y - midY) / height;
+ final double x = (point.x - scaleX) / scaleX;
+ final double y = (point.y - scaleY) / scaleY;
final double z = ra.setPositionAndGet(point.x, point.y).getRealDouble();
matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{z})));
}
backgroundModel.fit(matches);
+ backgroundModel.normalize();
}
private static Set extractInterestPoints(final List rois) {
From 4c94b426398604a4cd46e56f2de9f17d02e0bc1f Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 7 Nov 2024 11:53:15 -0500
Subject: [PATCH 24/56] Reorder methods in CorrectBackground
---
.../embackground/CorrectBackground.java | 76 +++++++++----------
1 file changed, 38 insertions(+), 38 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index 2b57cf9dc..f6fe61e12 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -68,44 +68,6 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
}
}
- @SuppressWarnings({"rawtypes", "unchecked"})
- public static & RealType>
- RandomAccessibleInterval correctBackground(final RandomAccessibleInterval slice, final RandomAccessibleInterval background, final T type) {
-
- final RandomAccessibleInterval corrected;
- if (type instanceof UnsignedByteType) {
- corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
- o.set(UnsignedByteType.getCodedSignedByteChecked((int) (s.getRealDouble() - b.getRealDouble())));
- }, new UnsignedByteType());
- } else if (type instanceof UnsignedShortType) {
- corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
- o.set(UnsignedShortType.getCodedSignedShortChecked((int) (s.getRealDouble() - b.getRealDouble())));
- }, new UnsignedShortType());
- } else {
- throw new IllegalArgumentException("Unsupported type: " + type.getClass());
- }
-
- return corrected;
- }
-
- public static & RealType>
- RandomAccessibleInterval createBackgroundImage(final BackgroundModel> backgroundModel, final RandomAccessibleInterval slice) {
-
- // transform pixel coordinates into [-1, 1] x [-1, 1]
- final double scaleX = slice.dimension(0) / 2.0;
- final double scaleY = slice.dimension(1) / 2.0;
-
- final double[] location = new double[2];
- final RealRandomAccessible background = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
- location[0] = (pos.getDoublePosition(0) - scaleX) / scaleX;
- location[1] = (pos.getDoublePosition(1) - scaleY) / scaleY;
- backgroundModel.applyInPlace(location);
- value.setReal(location[0]);
- }, FloatType::new);
-
- return Views.interval(Views.raster(background), slice);
- }
-
public static & RealType>
void fitBackgroundModel(final List rois, final RandomAccessibleInterval slice, final BackgroundModel> backgroundModel)
throws NotEnoughDataPointsException, IllDefinedDataPointsException {
@@ -138,6 +100,44 @@ private static Set extractInterestPoints(final List rois) {
return pointsOfInterest;
}
+ public static & RealType>
+ RandomAccessibleInterval createBackgroundImage(final BackgroundModel> backgroundModel, final RandomAccessibleInterval slice) {
+
+ // transform pixel coordinates into [-1, 1] x [-1, 1]
+ final double scaleX = slice.dimension(0) / 2.0;
+ final double scaleY = slice.dimension(1) / 2.0;
+
+ final double[] location = new double[2];
+ final RealRandomAccessible background = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
+ location[0] = (pos.getDoublePosition(0) - scaleX) / scaleX;
+ location[1] = (pos.getDoublePosition(1) - scaleY) / scaleY;
+ backgroundModel.applyInPlace(location);
+ value.setReal(location[0]);
+ }, FloatType::new);
+
+ return Views.interval(Views.raster(background), slice);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static & RealType>
+ RandomAccessibleInterval correctBackground(final RandomAccessibleInterval slice, final RandomAccessibleInterval background, final T type) {
+
+ final RandomAccessibleInterval corrected;
+ if (type instanceof UnsignedByteType) {
+ corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
+ o.set(UnsignedByteType.getCodedSignedByteChecked((int) (s.getRealDouble() - b.getRealDouble())));
+ }, new UnsignedByteType());
+ } else if (type instanceof UnsignedShortType) {
+ corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
+ o.set(UnsignedShortType.getCodedSignedShortChecked((int) (s.getRealDouble() - b.getRealDouble())));
+ }, new UnsignedShortType());
+ } else {
+ throw new IllegalArgumentException("Unsupported type: " + type.getClass());
+ }
+
+ return corrected;
+ }
+
public static List readRois(final String path) throws IOException {
if (path.endsWith(".zip")) {
return extractRoisFromZip(path);
From ed7fcea0b6638b576661760ce50ba8a95e2d7042 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 7 Nov 2024 14:15:17 -0500
Subject: [PATCH 25/56] Make BG_Plugin use command line parameters
---
.../render/client/embackground/BG_Plugin.java | 32 ++++++++++++++++---
1 file changed, 27 insertions(+), 5 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
index 77df41bc1..7c05d4d13 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -9,10 +9,12 @@
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
+import com.beust.jcommander.Parameter;
import mpicbg.models.IllDefinedDataPointsException;
import mpicbg.models.NotEnoughDataPointsException;
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.real.FloatType;
+import org.janelia.render.client.parameter.CommandLineParameters;
import org.janelia.saalfeldlab.n5.N5FSReader;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
@@ -28,6 +30,24 @@
public class BG_Plugin implements PlugIn {
+ private static class Parameters extends CommandLineParameters {
+ @Parameter(names = "--n5Path",
+ description = "Path to the N5 container",
+ required = true)
+ public String n5Path;
+
+ @Parameter(names = "--dataset",
+ description = "Name of the dataset",
+ required = true)
+ public String dataset;
+
+ @Parameter(names = "--z",
+ description = "Z slice to process",
+ required = true)
+ public int z;
+ }
+
+
public static int defaultType = 0;
public static boolean defaultShowBackground = false;
public static String[] fitTypes = new String[] { "Quadratic", "Fourth Order" };
@@ -120,14 +140,16 @@ private static void showNonBlockingDialog() {
}
public static void main(final String[] args) {
- new ImageJ();
+ final Parameters params = new Parameters();
+ params.parse(args);
+ new ImageJ();
SwingUtilities.invokeLater(BG_Plugin::showNonBlockingDialog);
- final String n5Path = System.getenv("HOME") + "/big-data/render-exports/cerebellum-3.n5";
- img = N5Utils.open(new N5FSReader(n5Path), "data/s4");
- final int z = 0;
- img = Views.hyperSlice(img, 2, z);
+ img = N5Utils.open(new N5FSReader(params.n5Path), params.dataset);
+ if (img.numDimensions() > 2) {
+ img = Views.hyperSlice(img, 2, params.z);
+ }
ImageJFunctions.show(img);
new RoiManager();
From 8e5290f5aeb8976812679569d80f3190af0d461a Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 7 Nov 2024 15:02:20 -0500
Subject: [PATCH 26/56] Add skeleton for BackgroundCorrection spark client
---
.../BackgroundCorrectionClient.java | 132 ++++++++++++++++++
1 file changed, 132 insertions(+)
create mode 100644 render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
new file mode 100644
index 000000000..12a693c36
--- /dev/null
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -0,0 +1,132 @@
+package org.janelia.render.client.spark.intensityadjust;
+
+import com.beust.jcommander.Parameter;
+import org.apache.spark.SparkConf;
+import org.apache.spark.api.java.JavaRDD;
+import org.apache.spark.api.java.JavaSparkContext;
+import org.janelia.alignment.util.Grid;
+import org.janelia.render.client.ClientRunner;
+import org.janelia.render.client.embackground.BackgroundModel;
+import org.janelia.render.client.parameter.CommandLineParameters;
+import org.janelia.saalfeldlab.n5.DatasetAttributes;
+import org.janelia.saalfeldlab.n5.N5FSReader;
+import org.janelia.saalfeldlab.n5.N5FSWriter;
+import org.janelia.saalfeldlab.n5.N5Reader;
+import org.janelia.saalfeldlab.n5.N5Writer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Spark client for background correction by a layer-wise quadratic or fourth order model.
+ * The client takes as input an N5 container with a 3D dataset and a parameter file, and writes the corrected data to a
+ * new dataset in a given container.
+ *
+ * The parameter file is a json file containing a list of z values and corresponding models. The model for each z value
+ * is valid for all z layers starting at the given z value until the next z value in the list.
+ * Models are specified by an identifier ("quadratic" or "fourthOrder") and a list of coefficients (6 or 9,
+ * respectively). Coefficients can be found interactively using {@link org.janelia.render.client.embackground.BG_Plugin}.
+ */
+public class BackgroundCorrectionClient implements Serializable {
+
+ public static class Parameters extends CommandLineParameters {
+ @Parameter(names = "--n5In",
+ description = "Path to the input N5 container",
+ required = true)
+ public String n5In;
+
+ @Parameter(names = "--datasetIn",
+ description = "Name of the input dataset",
+ required = true)
+ public String datasetIn;
+
+ @Parameter(names = "--n5Out",
+ description = "Path to the output N5 container",
+ required = true)
+ public String n5Out;
+
+ @Parameter(names = "--datasetOut",
+ description = "Name of the output dataset",
+ required = true)
+ public String datasetOut;
+
+ @Parameter(names = "--parameterFile",
+ description = "Path to the parameter json file containing a list of z values and their corresponding models. See class description for details.",
+ required = true)
+ public String parameterFile;
+ }
+
+ public static void main(final String[] args) {
+ final ClientRunner clientRunner = new ClientRunner(args) {
+ @Override
+ public void runClient(final String[] args) throws Exception {
+ final Parameters parameters = new Parameters();
+ parameters.parse(args);
+ final BackgroundCorrectionClient client = new BackgroundCorrectionClient(parameters);
+ client.run();
+ }
+ };
+ clientRunner.run();
+ }
+
+ private final Parameters parameters;
+
+ public BackgroundCorrectionClient(final Parameters parameters) {
+ LOG.info("init: parameters={}", parameters);
+ this.parameters = parameters;
+ }
+
+ public void run() throws IOException {
+ final SparkConf conf = new SparkConf().setAppName("BackgroundCorrectionClient");
+ try (final JavaSparkContext sparkContext = new JavaSparkContext(conf)) {
+ final String sparkAppId = sparkContext.getConf().getAppId();
+ LOG.info("run: appId is {}", sparkAppId);
+ runWithContext(sparkContext);
+ }
+ }
+
+ public void runWithContext(final JavaSparkContext sparkContext) {
+
+ LOG.info("runWithContext: entry");
+
+ // read parameters
+ final Map> models = null;
+
+ // set up input and output N5 datasets
+ final DatasetAttributes inputAttributes;
+ try (final N5Reader in = new N5FSReader(parameters.n5In)) {
+ inputAttributes = in.getDatasetAttributes(parameters.datasetIn);
+ final Map> otherAttributes = in.listAttributes(parameters.datasetIn);
+
+ try (final N5Writer out = new N5FSWriter(parameters.n5Out)) {
+ if (out.exists(parameters.datasetOut)) {
+ throw new IllegalArgumentException("Output dataset already exists: " + parameters.datasetOut);
+ }
+
+ out.createDataset(parameters.datasetOut, inputAttributes);
+ out.setAttribute(parameters.datasetOut, "BackgroundCorrectionClientParameters", parameters);
+ otherAttributes.forEach((key, clazz) -> {
+ final Object attribute = in.getAttribute(parameters.datasetIn, key, clazz);
+ out.setAttribute(parameters.datasetOut, key, attribute);
+ });
+ }
+ }
+
+ // parallelize computation over blocks of the input/output dataset
+ final List blocks = Grid.create(inputAttributes.getDimensions(), inputAttributes.getBlockSize());
+ final JavaRDD blockRDD = sparkContext.parallelize(blocks, blocks.size());
+ blockRDD.foreach(block -> processSingleBlock(parameters, models));
+
+ LOG.info("runWithContext: exit");
+ }
+
+ private static void processSingleBlock(final Parameters parameters, final Map> models) {
+
+ }
+
+ private static final Logger LOG = LoggerFactory.getLogger(BackgroundCorrectionClient.class);
+}
From 824359701b3ef7cb09bd26a34052a5c05f344f93 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 7 Nov 2024 18:22:52 -0500
Subject: [PATCH 27/56] Store unfinished work on parameter file
---
.../client/embackground/BackgroundModel.java | 3 +-
.../BackgroundCorrectionClient.java | 86 ++++++++++++++++++-
2 files changed, 85 insertions(+), 4 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
index 4add63c4c..fbc41b47f 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
@@ -8,6 +8,7 @@
import org.ejml.dense.row.factory.LinearSolverFactory_DDRM;
import org.ejml.interfaces.linsol.LinearSolverDense;
+import java.io.Serializable;
import java.util.Collection;
import java.util.List;
@@ -15,7 +16,7 @@
/**
* An abstract base model for background correction in 2D slices of EM data.
*/
-public abstract class BackgroundModel> extends AbstractModel {
+public abstract class BackgroundModel> extends AbstractModel implements Serializable {
private final double[] coefficients;
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 12a693c36..3bf575eb3 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -1,12 +1,18 @@
package org.janelia.render.client.spark.intensityadjust;
import com.beust.jcommander.Parameter;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.janelia.alignment.util.Grid;
import org.janelia.render.client.ClientRunner;
import org.janelia.render.client.embackground.BackgroundModel;
+import org.janelia.render.client.embackground.FourthOrderBackground;
+import org.janelia.render.client.embackground.QuadraticBackground;
import org.janelia.render.client.parameter.CommandLineParameters;
import org.janelia.saalfeldlab.n5.DatasetAttributes;
import org.janelia.saalfeldlab.n5.N5FSReader;
@@ -16,8 +22,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -30,6 +38,23 @@
* is valid for all z layers starting at the given z value until the next z value in the list.
* Models are specified by an identifier ("quadratic" or "fourthOrder") and a list of coefficients (6 or 9,
* respectively). Coefficients can be found interactively using {@link org.janelia.render.client.embackground.BG_Plugin}.
+ *
+ * In particular, the parameter file should have the following format. There is one root object with a single key
+ * "models". The value of this key is an array of objects, each with three keys: "z", "model", and "coefficients"s, e.g.:
+ *
+ * {
+ * "models": [ {
+ * "z": 0,
+ * "model": "quadratic",
+ * "coefficients": [1.0, 0.0, 1.0, 0.0, 0.0, 0.0]
+ * }, {
+ * "z": 10,
+ * "model": "fourthOrder",
+ * "coefficients": [1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+ * }
+ * ]
+ * }
+ *
*/
public class BackgroundCorrectionClient implements Serializable {
@@ -89,12 +114,13 @@ public void run() throws IOException {
}
}
- public void runWithContext(final JavaSparkContext sparkContext) {
+ public void runWithContext(final JavaSparkContext sparkContext) throws IOException {
LOG.info("runWithContext: entry");
// read parameters
- final Map> models = null;
+ final BackgroundModelProvider modelProvider = BackgroundModelProvider.fromFile(parameters.parameterFile);
+ System.exit(0);
// set up input and output N5 datasets
final DatasetAttributes inputAttributes;
@@ -119,7 +145,7 @@ public void runWithContext(final JavaSparkContext sparkContext) {
// parallelize computation over blocks of the input/output dataset
final List blocks = Grid.create(inputAttributes.getDimensions(), inputAttributes.getBlockSize());
final JavaRDD blockRDD = sparkContext.parallelize(blocks, blocks.size());
- blockRDD.foreach(block -> processSingleBlock(parameters, models));
+// blockRDD.foreach(block -> processSingleBlock(parameters, models));
LOG.info("runWithContext: exit");
}
@@ -128,5 +154,59 @@ private static void processSingleBlock(final Parameters parameters, final Map zValues;
+ private final List> models;
+
+ private BackgroundModelProvider(final List zValues, final List> models) {
+ this.zValues = zValues;
+ this.models = models;
+ }
+
+ public BackgroundModel> getModel(final int z) {
+ for (int i = 0; i < zValues.size() - 1; i++) {
+ if (z >= zValues.get(i) && z < zValues.get(i + 1)) {
+ return models.get(i);
+ }
+ }
+ return null;
+ }
+
+
+ public static BackgroundModelProvider fromFile(final String fileName) throws IOException {
+
+ final List zValues = new ArrayList<>();
+ final List> models = new ArrayList<>();
+
+ final JsonArray root;
+ try (final FileReader reader = new FileReader(fileName)) {
+ root = JsonParser.parseReader(reader).getAsJsonObject().getAsJsonArray("models");
+ }
+
+ for (final JsonElement jsonElement : root) {
+ final JsonObject parameterSet = jsonElement.getAsJsonObject();
+ final int z = parameterSet.get("fromZ").getAsInt();
+ final String modelType = parameterSet.get("model").getAsString();
+ final double[] coefficients = parameterSet.getAsJsonArray("coefficients").asList().stream()
+ .map(JsonElement::getAsDouble)
+ .mapToDouble(Double::doubleValue).toArray();
+
+ LOG.info("Extract model from parameters file: fromZ={}, modelType={}, coefficients={}", z, modelType, coefficients);
+
+ zValues.add(z);
+ if (modelType.equals("quadratic")) {
+ models.add(new QuadraticBackground(coefficients));
+ } else if (modelType.equals("fourthOrder")) {
+ models.add(new FourthOrderBackground(coefficients));
+ } else {
+ throw new IllegalArgumentException("Unknown model type: " + modelType);
+ }
+ }
+
+ return new BackgroundModelProvider(zValues, models);
+ }
+ }
+
private static final Logger LOG = LoggerFactory.getLogger(BackgroundCorrectionClient.class);
}
From 0054565c0e24472b83c2f640f11c4cb2bcbdafca Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Fri, 8 Nov 2024 09:08:32 -0500
Subject: [PATCH 28/56] Make json deserialization nicer
---
.../BackgroundCorrectionClient.java | 99 +++++++++++--------
1 file changed, 56 insertions(+), 43 deletions(-)
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 3bf575eb3..4fb30b7e3 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -1,9 +1,8 @@
package org.janelia.render.client.spark.intensityadjust;
import com.beust.jcommander.Parameter;
-import com.google.gson.JsonArray;
+import com.google.gson.Gson;
import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
@@ -26,6 +25,9 @@
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -39,21 +41,19 @@
* Models are specified by an identifier ("quadratic" or "fourthOrder") and a list of coefficients (6 or 9,
* respectively). Coefficients can be found interactively using {@link org.janelia.render.client.embackground.BG_Plugin}.
*
- * In particular, the parameter file should have the following format. There is one root object with a single key
- * "models". The value of this key is an array of objects, each with three keys: "z", "model", and "coefficients"s, e.g.:
+ * In particular, the parameter file should have the following format. There is one root array, whose elements have
+ * exactly keys: "fromZ", "modelType", and "coefficients"s, e.g.:
*
- * {
- * "models": [ {
- * "z": 0,
- * "model": "quadratic",
- * "coefficients": [1.0, 0.0, 1.0, 0.0, 0.0, 0.0]
- * }, {
- * "z": 10,
- * "model": "fourthOrder",
- * "coefficients": [1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
- * }
- * ]
- * }
+ * [ {
+ * "fromZ": 1,
+ * "modelType": "quadratic",
+ * "coefficients": [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
+ * }, {
+ * "fromZ": 3,
+ * "modelType": "fourthOrder",
+ * "coefficients": [ 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0
+ * }
+ * ]
*
*/
public class BackgroundCorrectionClient implements Serializable {
@@ -119,7 +119,7 @@ public void runWithContext(final JavaSparkContext sparkContext) throws IOExcepti
LOG.info("runWithContext: entry");
// read parameters
- final BackgroundModelProvider modelProvider = BackgroundModelProvider.fromFile(parameters.parameterFile);
+ final BackgroundModelProvider modelProvider = BackgroundModelProvider.fromJsonFile(parameters.parameterFile);
System.exit(0);
// set up input and output N5 datasets
@@ -156,55 +156,68 @@ private static void processSingleBlock(final Parameters parameters, final Map zValues;
- private final List> models;
+ private final List sortedModelSpecs;
- private BackgroundModelProvider(final List zValues, final List> models) {
- this.zValues = zValues;
- this.models = models;
+ private BackgroundModelProvider(final List modelSpecs) {
+ this.sortedModelSpecs = modelSpecs;
+ this.sortedModelSpecs.sort(Collections.reverseOrder(Comparator.comparingInt(ModelSpec::getZ)));
}
public BackgroundModel> getModel(final int z) {
- for (int i = 0; i < zValues.size() - 1; i++) {
- if (z >= zValues.get(i) && z < zValues.get(i + 1)) {
- return models.get(i);
+ for (final ModelSpec modelSpec : sortedModelSpecs) {
+ if (z >= modelSpec.getZ()) {
+ return modelSpec.getModel();
}
}
return null;
}
+ public static BackgroundModelProvider fromJsonFile(final String fileName) throws IOException {
+ LOG.info("Reading model specs from file: {}", fileName);
+ try (final FileReader reader = new FileReader(fileName)) {
+ return fromJson(JsonParser.parseReader(reader));
+ }
+ }
- public static BackgroundModelProvider fromFile(final String fileName) throws IOException {
+ public static BackgroundModelProvider fromJson(final JsonElement jsonData) throws IOException {
- final List zValues = new ArrayList<>();
- final List> models = new ArrayList<>();
+ final List modelSpecs = new ArrayList<>();
+ Collections.addAll(modelSpecs, new Gson().fromJson(jsonData, ModelSpec[].class));
- final JsonArray root;
- try (final FileReader reader = new FileReader(fileName)) {
- root = JsonParser.parseReader(reader).getAsJsonObject().getAsJsonArray("models");
+ // validation of json data
+ for (final ModelSpec modelSpec : modelSpecs) {
+ LOG.info("Found model spec: {}", modelSpec);
+ final BackgroundModel> ignored = modelSpec.getModel();
}
- for (final JsonElement jsonElement : root) {
- final JsonObject parameterSet = jsonElement.getAsJsonObject();
- final int z = parameterSet.get("fromZ").getAsInt();
- final String modelType = parameterSet.get("model").getAsString();
- final double[] coefficients = parameterSet.getAsJsonArray("coefficients").asList().stream()
- .map(JsonElement::getAsDouble)
- .mapToDouble(Double::doubleValue).toArray();
+ return new BackgroundModelProvider(modelSpecs);
+ }
+
+
+ private static class ModelSpec implements Serializable {
+ private int fromZ;
+ private String modelType;
+ private double[] coefficients;
- LOG.info("Extract model from parameters file: fromZ={}, modelType={}, coefficients={}", z, modelType, coefficients);
+ // no explicit constructor; meant to be deserialized from json
- zValues.add(z);
+ public int getZ() {
+ return fromZ;
+ }
+
+ public BackgroundModel> getModel() {
if (modelType.equals("quadratic")) {
- models.add(new QuadraticBackground(coefficients));
+ return new QuadraticBackground(coefficients);
} else if (modelType.equals("fourthOrder")) {
- models.add(new FourthOrderBackground(coefficients));
+ return new FourthOrderBackground(coefficients);
} else {
throw new IllegalArgumentException("Unknown model type: " + modelType);
}
}
- return new BackgroundModelProvider(zValues, models);
+ public String toString() {
+ return "ModelSpec{fromZ=" + fromZ + ", modelType=" + modelType + ", coefficients=" + Arrays.toString(coefficients) + "}";
+ }
}
}
From ac6aa5a6db56fd1c95594c439308d81e02e1e4a4 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Fri, 8 Nov 2024 09:45:39 -0500
Subject: [PATCH 29/56] Add first complete version of
BackgroundCorrectionClient
---
.../BackgroundCorrectionClient.java | 71 ++++++++++++++++---
1 file changed, 63 insertions(+), 8 deletions(-)
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 4fb30b7e3..1c51e3ad6 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -4,9 +4,15 @@
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
+import net.imglib2.Cursor;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.Img;
+import net.imglib2.type.numeric.integer.UnsignedByteType;
+import net.imglib2.view.Views;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
+import org.apache.spark.broadcast.Broadcast;
import org.janelia.alignment.util.Grid;
import org.janelia.render.client.ClientRunner;
import org.janelia.render.client.embackground.BackgroundModel;
@@ -18,6 +24,7 @@
import org.janelia.saalfeldlab.n5.N5FSWriter;
import org.janelia.saalfeldlab.n5.N5Reader;
import org.janelia.saalfeldlab.n5.N5Writer;
+import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -85,6 +92,10 @@ public static class Parameters extends CommandLineParameters {
public String parameterFile;
}
+ private static final Logger LOG = LoggerFactory.getLogger(BackgroundCorrectionClient.class);
+
+ private final Parameters parameters;
+
public static void main(final String[] args) {
final ClientRunner clientRunner = new ClientRunner(args) {
@Override
@@ -98,7 +109,6 @@ public void runClient(final String[] args) throws Exception {
clientRunner.run();
}
- private final Parameters parameters;
public BackgroundCorrectionClient(final Parameters parameters) {
LOG.info("init: parameters={}", parameters);
@@ -120,7 +130,6 @@ public void runWithContext(final JavaSparkContext sparkContext) throws IOExcepti
// read parameters
final BackgroundModelProvider modelProvider = BackgroundModelProvider.fromJsonFile(parameters.parameterFile);
- System.exit(0);
// set up input and output N5 datasets
final DatasetAttributes inputAttributes;
@@ -143,15 +152,62 @@ public void runWithContext(final JavaSparkContext sparkContext) throws IOExcepti
}
// parallelize computation over blocks of the input/output dataset
+ final Broadcast modelProviderBroadcast = sparkContext.broadcast(modelProvider);
+ final Broadcast parametersBroadcast = sparkContext.broadcast(parameters);
+
final List blocks = Grid.create(inputAttributes.getDimensions(), inputAttributes.getBlockSize());
final JavaRDD blockRDD = sparkContext.parallelize(blocks, blocks.size());
-// blockRDD.foreach(block -> processSingleBlock(parameters, models));
+
+ blockRDD.foreach(block -> processSingleBlock(parametersBroadcast.value(), modelProviderBroadcast.value(), block));
LOG.info("runWithContext: exit");
}
- private static void processSingleBlock(final Parameters parameters, final Map> models) {
+ private static void processSingleBlock(final Parameters parameters, final BackgroundModelProvider modelProvider, final Grid.Block block) {
+ LOG.info("processSingleBlock: block={}", block);
+
+ try (final N5Reader in = new N5FSReader(parameters.n5In);
+ final N5Writer out = new N5FSWriter(parameters.n5Out)) {
+
+ // load block
+ final Img img = N5Utils.open(in, parameters.datasetIn);
+ final RandomAccessibleInterval croppedImg = Views.interval(img, block);
+ final long[] dimensions = in.getDatasetAttributes(parameters.datasetIn).getDimensions();
+
+ // scale pixel coordinates to [-1, 1] x [-1, 1]
+ final double xScale = dimensions[0] / 2.0;
+ final double yScale = dimensions[1] / 2.0;
+
+ // process block z-slice by z-slice
+ final int xShift = (int) block.min(0);
+ final int yShift = (int) block.min(1);
+ final int zShift = (int) block.min(2);
+ for (int z = (int) block.min(2); z <= block.max(2); z++) {
+ final BackgroundModel> model = modelProvider.getModel(z);
+ if (model == null) {
+ LOG.info("No model found for z={}", z);
+ continue;
+ }
+
+ final RandomAccessibleInterval slice = Views.hyperSlice(croppedImg, 2, z - zShift);
+ final Cursor cursor = Views.iterable(slice).localizingCursor();
+ final double[] location = new double[2];
+
+ // apply model to slice
+ while (cursor.hasNext()) {
+ final UnsignedByteType pixel = cursor.next();
+ location[0] = (xShift + cursor.getIntPosition(0) - xScale) / xScale;
+ location[1] = (yShift + cursor.getIntPosition(1) - yScale) / yScale;
+
+ model.applyInPlace(location);
+ pixel.setReal(UnsignedByteType.getCodedSignedByteChecked((int) (pixel.getRealDouble() - location[0])));
+ }
+ }
+
+ // save block
+ N5Utils.saveNonEmptyBlock(croppedImg, out, parameters.datasetOut, block.offset, new UnsignedByteType());
+ }
}
@@ -179,7 +235,7 @@ public static BackgroundModelProvider fromJsonFile(final String fileName) throws
}
}
- public static BackgroundModelProvider fromJson(final JsonElement jsonData) throws IOException {
+ public static BackgroundModelProvider fromJson(final JsonElement jsonData) {
final List modelSpecs = new ArrayList<>();
Collections.addAll(modelSpecs, new Gson().fromJson(jsonData, ModelSpec[].class));
@@ -194,7 +250,8 @@ public static BackgroundModelProvider fromJson(final JsonElement jsonData) throw
}
- private static class ModelSpec implements Serializable {
+ @SuppressWarnings("unused")
+ private static class ModelSpec implements Serializable {
private int fromZ;
private String modelType;
private double[] coefficients;
@@ -220,6 +277,4 @@ public String toString() {
}
}
}
-
- private static final Logger LOG = LoggerFactory.getLogger(BackgroundCorrectionClient.class);
}
From 1848cae8a2749ab94f751730154969c4ba649c22 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Fri, 8 Nov 2024 10:23:31 -0500
Subject: [PATCH 30/56] Fix some bugs to make background correction work
---
.../BackgroundCorrectionClient.java | 23 +++++++++++--------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 1c51e3ad6..376c5f904 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -179,26 +179,27 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
final double yScale = dimensions[1] / 2.0;
// process block z-slice by z-slice
- final int xShift = (int) block.min(0);
- final int yShift = (int) block.min(1);
- final int zShift = (int) block.min(2);
-
for (int z = (int) block.min(2); z <= block.max(2); z++) {
final BackgroundModel> model = modelProvider.getModel(z);
if (model == null) {
- LOG.info("No model found for z={}", z);
+ LOG.warn("No model found for z={}", z);
continue;
}
- final RandomAccessibleInterval slice = Views.hyperSlice(croppedImg, 2, z - zShift);
+ final RandomAccessibleInterval slice = Views.hyperSlice(croppedImg, 2, z - (int) block.min(2));
final Cursor cursor = Views.iterable(slice).localizingCursor();
final double[] location = new double[2];
// apply model to slice
while (cursor.hasNext()) {
final UnsignedByteType pixel = cursor.next();
- location[0] = (xShift + cursor.getIntPosition(0) - xScale) / xScale;
- location[1] = (yShift + cursor.getIntPosition(1) - yScale) / yScale;
+ if (pixel.getInteger() == 0) {
+ // background should not be corrected
+ continue;
+ }
+
+ location[0] = (cursor.getDoublePosition(0) - xScale) / xScale;
+ location[1] = (cursor.getDoublePosition(1) - yScale) / yScale;
model.applyInPlace(location);
pixel.setReal(UnsignedByteType.getCodedSignedByteChecked((int) (pixel.getRealDouble() - location[0])));
@@ -206,7 +207,7 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
}
// save block
- N5Utils.saveNonEmptyBlock(croppedImg, out, parameters.datasetOut, block.offset, new UnsignedByteType());
+ N5Utils.saveNonEmptyBlock(croppedImg, out, parameters.datasetOut, block.gridPosition, new UnsignedByteType());
}
}
@@ -262,6 +263,10 @@ public int getZ() {
return fromZ;
}
+ public void setCoefficients(final double[] coefficients) {
+ this.coefficients = coefficients;
+ }
+
public BackgroundModel> getModel() {
if (modelType.equals("quadratic")) {
return new QuadraticBackground(coefficients);
From 53f9e8d7ea83360536516b11d3497699bf6ff0f3 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 13 Nov 2024 18:01:03 -0500
Subject: [PATCH 31/56] Simplify plugin using ImageJ2 automatic detection
---
render-ws-java-client/pom.xml | 10 ++
.../render/client/embackground/BG_Plugin.java | 157 ------------------
.../BackgroundCorrectionPlugin.java | 124 ++++++++++++++
.../BackgroundCorrectionClient.java | 2 +-
4 files changed, 135 insertions(+), 158 deletions(-)
delete mode 100644 render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
create mode 100644 render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundCorrectionPlugin.java
diff --git a/render-ws-java-client/pom.xml b/render-ws-java-client/pom.xml
index dec2851ef..6632aab59 100644
--- a/render-ws-java-client/pom.xml
+++ b/render-ws-java-client/pom.xml
@@ -163,6 +163,16 @@
bigdataviewer-vistools
+
+ net.imagej
+ imagej
+
+
+
+ net.imagej
+ imagej-legacy
+
+
sc.fiji
z_spacing
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
deleted file mode 100644
index 7c05d4d13..000000000
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ /dev/null
@@ -1,157 +0,0 @@
-package org.janelia.render.client.embackground;
-
-import java.awt.FlowLayout;
-import java.util.Arrays;
-import java.util.List;
-
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JFrame;
-import javax.swing.SwingUtilities;
-
-import com.beust.jcommander.Parameter;
-import mpicbg.models.IllDefinedDataPointsException;
-import mpicbg.models.NotEnoughDataPointsException;
-import net.imglib2.type.numeric.integer.UnsignedByteType;
-import net.imglib2.type.numeric.real.FloatType;
-import org.janelia.render.client.parameter.CommandLineParameters;
-import org.janelia.saalfeldlab.n5.N5FSReader;
-import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
-
-import ij.IJ;
-import ij.ImageJ;
-import ij.gui.GenericDialog;
-import ij.gui.Roi;
-import ij.plugin.PlugIn;
-import ij.plugin.frame.RoiManager;
-import net.imglib2.RandomAccessibleInterval;
-import net.imglib2.img.display.imagej.ImageJFunctions;
-import net.imglib2.view.Views;
-
-public class BG_Plugin implements PlugIn {
-
- private static class Parameters extends CommandLineParameters {
- @Parameter(names = "--n5Path",
- description = "Path to the N5 container",
- required = true)
- public String n5Path;
-
- @Parameter(names = "--dataset",
- description = "Name of the dataset",
- required = true)
- public String dataset;
-
- @Parameter(names = "--z",
- description = "Z slice to process",
- required = true)
- public int z;
- }
-
-
- public static int defaultType = 0;
- public static boolean defaultShowBackground = false;
- public static String[] fitTypes = new String[] { "Quadratic", "Fourth Order" };
-
- public static RandomAccessibleInterval img;
-
- @Override
- public void run(final String arg) {
- final List rois = getROIs();
-
- if (rois == null || rois.isEmpty()) {
- IJ.log("No ROIs specified.");
- return;
- }
-
- final GenericDialog gd = new GenericDialog("Fit background correction");
-
- gd.addChoice("Fit type", fitTypes, fitTypes[defaultType]);
- gd.addCheckbox("Show background image", defaultShowBackground);
- gd.showDialog();
-
- if (gd.wasCanceled()) {
- return;
- }
-
- final int type = gd.getNextChoiceIndex();
- defaultType = type;
-
- final boolean showBackground = gd.getNextBoolean();
- defaultShowBackground = showBackground;
-
- try {
- fit(type, rois, showBackground);
- } catch (final NotEnoughDataPointsException | IllDefinedDataPointsException e) {
- IJ.log("Fitting failed: " + e.getMessage());
- }
- }
-
- public static List getROIs() {
- final RoiManager rm = RoiManager.getInstance();
-
- if (rm == null || rm.getCount() == 0) {
- IJ.log("Please define ROIs first before running background correction.");
- return null;
- }
-
- return Arrays.asList(rm.getRoisAsArray());
- }
-
- public static void fit(final int type, final List rois, final boolean showBackground) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
- IJ.log("Fitting with " + fitTypes[type] + " model...");
-
- final BackgroundModel> backgroundModel;
- if (fitTypes[type].equals("Quadratic")) {
- backgroundModel = new QuadraticBackground();
- } else if (fitTypes[type].equals("Fourth Order")) {
- backgroundModel = new FourthOrderBackground();
- } else {
- throw new IllegalArgumentException("Unknown fit type: " + fitTypes[type]);
- }
-
- final long start = System.currentTimeMillis();
- CorrectBackground.fitBackgroundModel(rois, img, backgroundModel);
- IJ.log("Fitted background model: " + backgroundModel);
- IJ.log("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
- IJ.log("Raw coefficients: " + Arrays.toString(backgroundModel.getCoefficients()));
-
- final RandomAccessibleInterval background = CorrectBackground.createBackgroundImage(backgroundModel, img);
- final RandomAccessibleInterval corrected = CorrectBackground.correctBackground(img, background, new UnsignedByteType());
-
- if (showBackground) {
- ImageJFunctions.show(background, "Background");
- }
- ImageJFunctions.show(corrected, "Corrected");
-
- }
-
- private static void showNonBlockingDialog() {
- new Thread(() -> {
- final JDialog dialog = new JDialog((JFrame)null, "Run Background plugin...", false);
- dialog.setLayout(new FlowLayout());
-
- final JButton closeButton = new JButton("Run Background plugin...");
- closeButton.addActionListener(e -> new BG_Plugin().run(null ));
- dialog.add(closeButton);
-
- dialog.pack();
- dialog.setVisible(true);
- }).start();
- }
-
- public static void main(final String[] args) {
- final Parameters params = new Parameters();
- params.parse(args);
-
- new ImageJ();
- SwingUtilities.invokeLater(BG_Plugin::showNonBlockingDialog);
-
- img = N5Utils.open(new N5FSReader(params.n5Path), params.dataset);
- if (img.numDimensions() > 2) {
- img = Views.hyperSlice(img, 2, params.z);
- }
- ImageJFunctions.show(img);
-
- new RoiManager();
- }
-}
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundCorrectionPlugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundCorrectionPlugin.java
new file mode 100644
index 000000000..5a122c04b
--- /dev/null
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundCorrectionPlugin.java
@@ -0,0 +1,124 @@
+package org.janelia.render.client.embackground;
+
+import ij.ImagePlus;
+import ij.gui.Roi;
+import ij.plugin.frame.RoiManager;
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.NotEnoughDataPointsException;
+import net.imagej.Dataset;
+import net.imagej.ImageJ;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.Img;
+import net.imglib2.img.display.imagej.ImageJFunctions;
+import net.imglib2.type.numeric.integer.UnsignedByteType;
+import net.imglib2.type.numeric.real.FloatType;
+import net.imglib2.view.Views;
+import org.janelia.render.client.parameter.CommandLineParameters;
+import org.janelia.saalfeldlab.n5.N5FSReader;
+import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
+import org.scijava.command.Command;
+import org.scijava.log.LogService;
+import org.scijava.plugin.Parameter;
+import org.scijava.plugin.Plugin;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Plugin(type=Command.class, headless=true, menuPath="Render Tools>Background Correction")
+public class BackgroundCorrectionPlugin implements Command {
+
+ private static class Parameters extends CommandLineParameters {
+ @com.beust.jcommander.Parameter(names = "--n5Path",
+ description = "Path to the N5 container",
+ required = true)
+ public String n5Path;
+
+ @com.beust.jcommander.Parameter(names = "--dataset",
+ description = "Name of the dataset",
+ required = true)
+ public String dataset;
+
+ @com.beust.jcommander.Parameter(names = "--z",
+ description = "Z slice to process",
+ required = true)
+ public int z;
+ }
+
+ @Parameter
+ private Dataset source;
+
+ @Parameter
+ private LogService log;
+
+ @Parameter(label="Fit type:", choices={"Quadratic", "Fourth Order"}, style="radioButtonHorizontal")
+ private String fitType;
+
+ @Parameter(label="Show background:")
+ private boolean showBackground;
+
+ @Override
+ public void run() {
+ final Img img = source.typedImg(new UnsignedByteType());
+
+ log.info("Fitting with " + fitType + " model...");
+
+ final BackgroundModel> backgroundModel;
+ if (fitType.equals("Quadratic")) {
+ backgroundModel = new QuadraticBackground();
+ } else if (fitType.equals("Fourth Order")) {
+ backgroundModel = new FourthOrderBackground();
+ } else {
+ throw new IllegalArgumentException("Unknown fit type: " + fitType);
+ }
+
+ final List rois = getROIs();
+
+ final long start = System.currentTimeMillis();
+ try {
+ CorrectBackground.fitBackgroundModel(rois, img, backgroundModel);
+ } catch (final NotEnoughDataPointsException | IllDefinedDataPointsException e) {
+ log.error("Fitting failed: " + e.getMessage());
+ return;
+ }
+ log.info("Fitted background model: " + backgroundModel);
+ log.info("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
+ log.info("Raw coefficients: " + Arrays.toString(backgroundModel.getCoefficients()));
+
+ final RandomAccessibleInterval background = CorrectBackground.createBackgroundImage(backgroundModel, img);
+ final RandomAccessibleInterval corrected = CorrectBackground.correctBackground(img, background, new UnsignedByteType());
+
+ if (showBackground) {
+ ImageJFunctions.show(background, "Background");
+ }
+ ImageJFunctions.show(corrected, "Corrected");
+
+ }
+
+ private List getROIs() {
+ final RoiManager rm = RoiManager.getInstance();
+
+ if (rm == null || rm.getCount() == 0) {
+ log.error("Please define ROIs first before running background correction.");
+ return null;
+ }
+
+ return Arrays.asList(rm.getRoisAsArray());
+ }
+
+ public static void main(final String[] args) {
+ final Parameters params = new Parameters();
+ params.parse(args);
+
+ RandomAccessibleInterval img = N5Utils.open(new N5FSReader(params.n5Path), params.dataset);
+ if (img.numDimensions() > 2) {
+ img = Views.hyperSlice(img, 2, params.z);
+ }
+
+ // select ROIs and press "t" to add them to the ROI manager
+ // then run the plugin through the "Render Tools" menu
+ final ImageJ ij = new ImageJ();
+ ij.launch();
+ final ImagePlus imgPlus = ImageJFunctions.wrap(img, "Original");
+ imgPlus.show();
+ }
+}
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 376c5f904..88307293f 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -49,7 +49,7 @@
* respectively). Coefficients can be found interactively using {@link org.janelia.render.client.embackground.BG_Plugin}.
*
* In particular, the parameter file should have the following format. There is one root array, whose elements have
- * exactly keys: "fromZ", "modelType", and "coefficients"s, e.g.:
+ * the properties: "fromZ", "modelType", and "coefficients"s, e.g.:
*
* [ {
* "fromZ": 1,
From 8eecf5eb9d4012fe1e296bc8d20afeaeb80c4176 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 14 Nov 2024 10:44:14 -0500
Subject: [PATCH 32/56] Revert "Simplify plugin using ImageJ2 automatic
detection"
This reverts commit 53f9e8d7ea83360536516b11d3497699bf6ff0f3.
The commit used ImageJ2 which clashes with some of the other
dependencies in the project.
---
render-ws-java-client/pom.xml | 10 --
.../render/client/embackground/BG_Plugin.java | 157 ++++++++++++++++++
.../BackgroundCorrectionPlugin.java | 124 --------------
.../BackgroundCorrectionClient.java | 2 +-
4 files changed, 158 insertions(+), 135 deletions(-)
create mode 100644 render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
delete mode 100644 render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundCorrectionPlugin.java
diff --git a/render-ws-java-client/pom.xml b/render-ws-java-client/pom.xml
index 6632aab59..dec2851ef 100644
--- a/render-ws-java-client/pom.xml
+++ b/render-ws-java-client/pom.xml
@@ -163,16 +163,6 @@
bigdataviewer-vistools
-
- net.imagej
- imagej
-
-
-
- net.imagej
- imagej-legacy
-
-
sc.fiji
z_spacing
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
new file mode 100644
index 000000000..7c05d4d13
--- /dev/null
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -0,0 +1,157 @@
+package org.janelia.render.client.embackground;
+
+import java.awt.FlowLayout;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+
+import com.beust.jcommander.Parameter;
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.NotEnoughDataPointsException;
+import net.imglib2.type.numeric.integer.UnsignedByteType;
+import net.imglib2.type.numeric.real.FloatType;
+import org.janelia.render.client.parameter.CommandLineParameters;
+import org.janelia.saalfeldlab.n5.N5FSReader;
+import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
+
+import ij.IJ;
+import ij.ImageJ;
+import ij.gui.GenericDialog;
+import ij.gui.Roi;
+import ij.plugin.PlugIn;
+import ij.plugin.frame.RoiManager;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.display.imagej.ImageJFunctions;
+import net.imglib2.view.Views;
+
+public class BG_Plugin implements PlugIn {
+
+ private static class Parameters extends CommandLineParameters {
+ @Parameter(names = "--n5Path",
+ description = "Path to the N5 container",
+ required = true)
+ public String n5Path;
+
+ @Parameter(names = "--dataset",
+ description = "Name of the dataset",
+ required = true)
+ public String dataset;
+
+ @Parameter(names = "--z",
+ description = "Z slice to process",
+ required = true)
+ public int z;
+ }
+
+
+ public static int defaultType = 0;
+ public static boolean defaultShowBackground = false;
+ public static String[] fitTypes = new String[] { "Quadratic", "Fourth Order" };
+
+ public static RandomAccessibleInterval img;
+
+ @Override
+ public void run(final String arg) {
+ final List rois = getROIs();
+
+ if (rois == null || rois.isEmpty()) {
+ IJ.log("No ROIs specified.");
+ return;
+ }
+
+ final GenericDialog gd = new GenericDialog("Fit background correction");
+
+ gd.addChoice("Fit type", fitTypes, fitTypes[defaultType]);
+ gd.addCheckbox("Show background image", defaultShowBackground);
+ gd.showDialog();
+
+ if (gd.wasCanceled()) {
+ return;
+ }
+
+ final int type = gd.getNextChoiceIndex();
+ defaultType = type;
+
+ final boolean showBackground = gd.getNextBoolean();
+ defaultShowBackground = showBackground;
+
+ try {
+ fit(type, rois, showBackground);
+ } catch (final NotEnoughDataPointsException | IllDefinedDataPointsException e) {
+ IJ.log("Fitting failed: " + e.getMessage());
+ }
+ }
+
+ public static List getROIs() {
+ final RoiManager rm = RoiManager.getInstance();
+
+ if (rm == null || rm.getCount() == 0) {
+ IJ.log("Please define ROIs first before running background correction.");
+ return null;
+ }
+
+ return Arrays.asList(rm.getRoisAsArray());
+ }
+
+ public static void fit(final int type, final List rois, final boolean showBackground) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
+ IJ.log("Fitting with " + fitTypes[type] + " model...");
+
+ final BackgroundModel> backgroundModel;
+ if (fitTypes[type].equals("Quadratic")) {
+ backgroundModel = new QuadraticBackground();
+ } else if (fitTypes[type].equals("Fourth Order")) {
+ backgroundModel = new FourthOrderBackground();
+ } else {
+ throw new IllegalArgumentException("Unknown fit type: " + fitTypes[type]);
+ }
+
+ final long start = System.currentTimeMillis();
+ CorrectBackground.fitBackgroundModel(rois, img, backgroundModel);
+ IJ.log("Fitted background model: " + backgroundModel);
+ IJ.log("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
+ IJ.log("Raw coefficients: " + Arrays.toString(backgroundModel.getCoefficients()));
+
+ final RandomAccessibleInterval background = CorrectBackground.createBackgroundImage(backgroundModel, img);
+ final RandomAccessibleInterval corrected = CorrectBackground.correctBackground(img, background, new UnsignedByteType());
+
+ if (showBackground) {
+ ImageJFunctions.show(background, "Background");
+ }
+ ImageJFunctions.show(corrected, "Corrected");
+
+ }
+
+ private static void showNonBlockingDialog() {
+ new Thread(() -> {
+ final JDialog dialog = new JDialog((JFrame)null, "Run Background plugin...", false);
+ dialog.setLayout(new FlowLayout());
+
+ final JButton closeButton = new JButton("Run Background plugin...");
+ closeButton.addActionListener(e -> new BG_Plugin().run(null ));
+ dialog.add(closeButton);
+
+ dialog.pack();
+ dialog.setVisible(true);
+ }).start();
+ }
+
+ public static void main(final String[] args) {
+ final Parameters params = new Parameters();
+ params.parse(args);
+
+ new ImageJ();
+ SwingUtilities.invokeLater(BG_Plugin::showNonBlockingDialog);
+
+ img = N5Utils.open(new N5FSReader(params.n5Path), params.dataset);
+ if (img.numDimensions() > 2) {
+ img = Views.hyperSlice(img, 2, params.z);
+ }
+ ImageJFunctions.show(img);
+
+ new RoiManager();
+ }
+}
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundCorrectionPlugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundCorrectionPlugin.java
deleted file mode 100644
index 5a122c04b..000000000
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundCorrectionPlugin.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.janelia.render.client.embackground;
-
-import ij.ImagePlus;
-import ij.gui.Roi;
-import ij.plugin.frame.RoiManager;
-import mpicbg.models.IllDefinedDataPointsException;
-import mpicbg.models.NotEnoughDataPointsException;
-import net.imagej.Dataset;
-import net.imagej.ImageJ;
-import net.imglib2.RandomAccessibleInterval;
-import net.imglib2.img.Img;
-import net.imglib2.img.display.imagej.ImageJFunctions;
-import net.imglib2.type.numeric.integer.UnsignedByteType;
-import net.imglib2.type.numeric.real.FloatType;
-import net.imglib2.view.Views;
-import org.janelia.render.client.parameter.CommandLineParameters;
-import org.janelia.saalfeldlab.n5.N5FSReader;
-import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
-import org.scijava.command.Command;
-import org.scijava.log.LogService;
-import org.scijava.plugin.Parameter;
-import org.scijava.plugin.Plugin;
-
-import java.util.Arrays;
-import java.util.List;
-
-@Plugin(type=Command.class, headless=true, menuPath="Render Tools>Background Correction")
-public class BackgroundCorrectionPlugin implements Command {
-
- private static class Parameters extends CommandLineParameters {
- @com.beust.jcommander.Parameter(names = "--n5Path",
- description = "Path to the N5 container",
- required = true)
- public String n5Path;
-
- @com.beust.jcommander.Parameter(names = "--dataset",
- description = "Name of the dataset",
- required = true)
- public String dataset;
-
- @com.beust.jcommander.Parameter(names = "--z",
- description = "Z slice to process",
- required = true)
- public int z;
- }
-
- @Parameter
- private Dataset source;
-
- @Parameter
- private LogService log;
-
- @Parameter(label="Fit type:", choices={"Quadratic", "Fourth Order"}, style="radioButtonHorizontal")
- private String fitType;
-
- @Parameter(label="Show background:")
- private boolean showBackground;
-
- @Override
- public void run() {
- final Img img = source.typedImg(new UnsignedByteType());
-
- log.info("Fitting with " + fitType + " model...");
-
- final BackgroundModel> backgroundModel;
- if (fitType.equals("Quadratic")) {
- backgroundModel = new QuadraticBackground();
- } else if (fitType.equals("Fourth Order")) {
- backgroundModel = new FourthOrderBackground();
- } else {
- throw new IllegalArgumentException("Unknown fit type: " + fitType);
- }
-
- final List rois = getROIs();
-
- final long start = System.currentTimeMillis();
- try {
- CorrectBackground.fitBackgroundModel(rois, img, backgroundModel);
- } catch (final NotEnoughDataPointsException | IllDefinedDataPointsException e) {
- log.error("Fitting failed: " + e.getMessage());
- return;
- }
- log.info("Fitted background model: " + backgroundModel);
- log.info("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
- log.info("Raw coefficients: " + Arrays.toString(backgroundModel.getCoefficients()));
-
- final RandomAccessibleInterval background = CorrectBackground.createBackgroundImage(backgroundModel, img);
- final RandomAccessibleInterval corrected = CorrectBackground.correctBackground(img, background, new UnsignedByteType());
-
- if (showBackground) {
- ImageJFunctions.show(background, "Background");
- }
- ImageJFunctions.show(corrected, "Corrected");
-
- }
-
- private List getROIs() {
- final RoiManager rm = RoiManager.getInstance();
-
- if (rm == null || rm.getCount() == 0) {
- log.error("Please define ROIs first before running background correction.");
- return null;
- }
-
- return Arrays.asList(rm.getRoisAsArray());
- }
-
- public static void main(final String[] args) {
- final Parameters params = new Parameters();
- params.parse(args);
-
- RandomAccessibleInterval img = N5Utils.open(new N5FSReader(params.n5Path), params.dataset);
- if (img.numDimensions() > 2) {
- img = Views.hyperSlice(img, 2, params.z);
- }
-
- // select ROIs and press "t" to add them to the ROI manager
- // then run the plugin through the "Render Tools" menu
- final ImageJ ij = new ImageJ();
- ij.launch();
- final ImagePlus imgPlus = ImageJFunctions.wrap(img, "Original");
- imgPlus.show();
- }
-}
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 88307293f..376c5f904 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -49,7 +49,7 @@
* respectively). Coefficients can be found interactively using {@link org.janelia.render.client.embackground.BG_Plugin}.
*
* In particular, the parameter file should have the following format. There is one root array, whose elements have
- * the properties: "fromZ", "modelType", and "coefficients"s, e.g.:
+ * exactly keys: "fromZ", "modelType", and "coefficients"s, e.g.:
*
* [ {
* "fromZ": 1,
From 3da61060e4b4cabe6fffb714404874e1f4471947 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 14 Nov 2024 11:53:37 -0500
Subject: [PATCH 33/56] Make background correction plugin work more nicely
---
.../render/client/embackground/BG_Plugin.java | 42 ++++++++++---------
1 file changed, 22 insertions(+), 20 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
index 7c05d4d13..563d386d8 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -1,12 +1,10 @@
package org.janelia.render.client.embackground;
-import java.awt.FlowLayout;
+import java.awt.*;
+import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.List;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import com.beust.jcommander.Parameter;
@@ -52,8 +50,6 @@ private static class Parameters extends CommandLineParameters {
public static boolean defaultShowBackground = false;
public static String[] fitTypes = new String[] { "Quadratic", "Fourth Order" };
- public static RandomAccessibleInterval img;
-
@Override
public void run(final String arg) {
final List rois = getROIs();
@@ -110,6 +106,7 @@ public static void fit(final int type, final List rois, final boolean showB
}
final long start = System.currentTimeMillis();
+ final RandomAccessibleInterval img = ImageJFunctions.wrap(IJ.getImage());
CorrectBackground.fitBackgroundModel(rois, img, backgroundModel);
IJ.log("Fitted background model: " + backgroundModel);
IJ.log("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
@@ -125,18 +122,23 @@ public static void fit(final int type, final List rois, final boolean showB
}
- private static void showNonBlockingDialog() {
- new Thread(() -> {
- final JDialog dialog = new JDialog((JFrame)null, "Run Background plugin...", false);
- dialog.setLayout(new FlowLayout());
-
- final JButton closeButton = new JButton("Run Background plugin...");
- closeButton.addActionListener(e -> new BG_Plugin().run(null ));
- dialog.add(closeButton);
-
- dialog.pack();
- dialog.setVisible(true);
- }).start();
+ private static void addKeyListener() {
+ System.out.println("Mapped 'Background Correction' to F1.");
+
+ new Thread(() -> KeyboardFocusManager.getCurrentKeyboardFocusManager()
+ .addKeyEventDispatcher(e -> {
+ if (e.getID() == KeyEvent.KEY_PRESSED) {
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_F1:
+ new BG_Plugin().run(null);
+ break;
+ case KeyEvent.VK_F2:
+ System.out.println("F2 key pressed (not assigned)");
+ break;
+ }
+ }
+ return false;
+ })).start();
}
public static void main(final String[] args) {
@@ -144,9 +146,9 @@ public static void main(final String[] args) {
params.parse(args);
new ImageJ();
- SwingUtilities.invokeLater(BG_Plugin::showNonBlockingDialog);
+ SwingUtilities.invokeLater(BG_Plugin::addKeyListener);
- img = N5Utils.open(new N5FSReader(params.n5Path), params.dataset);
+ RandomAccessibleInterval img = N5Utils.open(new N5FSReader(params.n5Path), params.dataset);
if (img.numDimensions() > 2) {
img = Views.hyperSlice(img, 2, params.z);
}
From 2eeb63fd5decae45a215603b1270a27e8a60389e Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Thu, 14 Nov 2024 15:39:40 -0500
Subject: [PATCH 34/56] Add downscaling to background correction plugin
---
.../render/client/embackground/BG_Plugin.java | 34 ++++++++++++++++---
1 file changed, 29 insertions(+), 5 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
index 563d386d8..d7a63677b 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -10,6 +10,7 @@
import com.beust.jcommander.Parameter;
import mpicbg.models.IllDefinedDataPointsException;
import mpicbg.models.NotEnoughDataPointsException;
+import net.imglib2.algorithm.gauss3.Gauss3;
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.real.FloatType;
import org.janelia.render.client.parameter.CommandLineParameters;
@@ -40,9 +41,12 @@ private static class Parameters extends CommandLineParameters {
public String dataset;
@Parameter(names = "--z",
- description = "Z slice to process",
- required = true)
- public int z;
+ description = "Z slice to process")
+ public int z = 1;
+
+ @Parameter(names = "--downscaleFactor",
+ description = "Downscale factor")
+ public int downscale = 0;
}
@@ -148,12 +152,32 @@ public static void main(final String[] args) {
new ImageJ();
SwingUtilities.invokeLater(BG_Plugin::addKeyListener);
+ IJ.log("Opening " + params.dataset + " from " + params.n5Path);
RandomAccessibleInterval img = N5Utils.open(new N5FSReader(params.n5Path), params.dataset);
if (img.numDimensions() > 2) {
+ IJ.log("Showing slice " + params.z);
img = Views.hyperSlice(img, 2, params.z);
}
- ImageJFunctions.show(img);
+ final RandomAccessibleInterval downscaled = downSample(img, params.downscale);
+ ImageJFunctions.show(downscaled, "Original");
+ }
- new RoiManager();
+ private static RandomAccessibleInterval downSample(final RandomAccessibleInterval img, int downscale) {
+ if (downscale < 1) {
+ // automatically determine downscale factor
+ IJ.log("No downscaling factor given, choosing factor automatically...");
+ final long[] dims = img.dimensionsAsLongArray();
+ final long maxSize = 2000 * 2000;
+ downscale = (int) Math.ceil(Math.sqrt((double) (dims[0] * dims[1]) / maxSize));
+ }
+
+ if (downscale == 1) {
+ IJ.log("No downscaling, showing original image at full size.");
+ return img;
+ } else {
+ IJ.log("Isotropically downscaling by factor of " + downscale);
+ Gauss3.gauss(downscale / 2.0, Views.extendMirrorSingle(img), img);
+ return Views.subsample(img, downscale);
+ }
}
}
From 605e29bd57a00194af35b021181e7cca41ab413c Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 19 Nov 2024 18:07:56 -0500
Subject: [PATCH 35/56] Add mask support to background correction
---
.../BackgroundCorrectionClient.java | 30 ++++++++++++++-----
1 file changed, 22 insertions(+), 8 deletions(-)
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 376c5f904..51d297b1f 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -76,6 +76,11 @@ public static class Parameters extends CommandLineParameters {
required = true)
public String datasetIn;
+ @Parameter(names = "--mask",
+ description = "Name of the mask dataset for the input",
+ required = true)
+ public String mask;
+
@Parameter(names = "--n5Out",
description = "Path to the output N5 container",
required = true)
@@ -137,6 +142,10 @@ public void runWithContext(final JavaSparkContext sparkContext) throws IOExcepti
inputAttributes = in.getDatasetAttributes(parameters.datasetIn);
final Map> otherAttributes = in.listAttributes(parameters.datasetIn);
+ if (!in.exists(parameters.mask)) {
+ throw new IllegalArgumentException("Mask dataset does not exist: " + parameters.mask);
+ }
+
try (final N5Writer out = new N5FSWriter(parameters.n5Out)) {
if (out.exists(parameters.datasetOut)) {
throw new IllegalArgumentException("Output dataset already exists: " + parameters.datasetOut);
@@ -172,6 +181,8 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
// load block
final Img img = N5Utils.open(in, parameters.datasetIn);
final RandomAccessibleInterval croppedImg = Views.interval(img, block);
+ final Img mask = N5Utils.open(in, parameters.mask);
+ final RandomAccessibleInterval croppedMask = Views.interval(mask, block);
final long[] dimensions = in.getDatasetAttributes(parameters.datasetIn).getDimensions();
// scale pixel coordinates to [-1, 1] x [-1, 1]
@@ -186,20 +197,23 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
continue;
}
- final RandomAccessibleInterval slice = Views.hyperSlice(croppedImg, 2, z - (int) block.min(2));
- final Cursor cursor = Views.iterable(slice).localizingCursor();
+ final RandomAccessibleInterval imgSlice = Views.hyperSlice(croppedImg, 2, z - (int) block.min(2));
+ final RandomAccessibleInterval maskSlice = Views.hyperSlice(croppedMask, 2, z - (int) block.min(2));
+ final Cursor imgCursor = Views.iterable(imgSlice).localizingCursor();
+ final Cursor maskCursor = Views.iterable(maskSlice).localizingCursor();
final double[] location = new double[2];
// apply model to slice
- while (cursor.hasNext()) {
- final UnsignedByteType pixel = cursor.next();
- if (pixel.getInteger() == 0) {
- // background should not be corrected
+ while (imgCursor.hasNext()) {
+ final UnsignedByteType pixel = imgCursor.next();
+ final UnsignedByteType maskPixel = maskCursor.next();
+ if (maskPixel.get() == 0) {
+ // don't apply model to background
continue;
}
- location[0] = (cursor.getDoublePosition(0) - xScale) / xScale;
- location[1] = (cursor.getDoublePosition(1) - yScale) / yScale;
+ location[0] = (imgCursor.getDoublePosition(0) - xScale) / xScale;
+ location[1] = (imgCursor.getDoublePosition(1) - yScale) / yScale;
model.applyInPlace(location);
pixel.setReal(UnsignedByteType.getCodedSignedByteChecked((int) (pixel.getRealDouble() - location[0])));
From 4d85665963eaf770f53ff276a28831579b031677 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 19 Nov 2024 18:15:09 -0500
Subject: [PATCH 36/56] Extract static method for coordinate rescaling
---
.../render/client/embackground/BackgroundModel.java | 10 ++++++++++
.../render/client/embackground/CorrectBackground.java | 8 ++++----
.../intensityadjust/BackgroundCorrectionClient.java | 4 ++--
3 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
index fbc41b47f..4a9e60bc1 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
@@ -61,6 +61,16 @@ public void normalize() {
coefficients[0] -= avg;
}
+ /**
+ * Scale the given coordinate to the model coordinates (in [-1, 1]).
+ * @param x the coordinate to scale
+ * @param scale the scale factor (usually half the image size in the respective dimension)
+ * @return the scaled coordinate in the range [-1, 1]
+ */
+ public static double scaleCoordinate(final double x, final double scale) {
+ return (x - scale) / scale;
+ }
+
@Override
public int getMinNumMatches() {
return nCoefficients();
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index f6fe61e12..049141958 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -81,8 +81,8 @@ void fitBackgroundModel(final List rois, final RandomAccessibleInterval
final RandomAccess ra = slice.randomAccess();
for (final java.awt.Point point : extractInterestPoints(rois)) {
- final double x = (point.x - scaleX) / scaleX;
- final double y = (point.y - scaleY) / scaleY;
+ final double x = BackgroundModel.scaleCoordinate(point.x, scaleX);
+ final double y = BackgroundModel.scaleCoordinate(point.x, scaleX);
final double z = ra.setPositionAndGet(point.x, point.y).getRealDouble();
matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{z})));
}
@@ -109,8 +109,8 @@ RandomAccessibleInterval createBackgroundImage(final BackgroundModel<
final double[] location = new double[2];
final RealRandomAccessible background = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
- location[0] = (pos.getDoublePosition(0) - scaleX) / scaleX;
- location[1] = (pos.getDoublePosition(1) - scaleY) / scaleY;
+ location[0] = BackgroundModel.scaleCoordinate(pos.getDoublePosition(0), scaleX);
+ location[1] = BackgroundModel.scaleCoordinate(pos.getDoublePosition(1), scaleY);
backgroundModel.applyInPlace(location);
value.setReal(location[0]);
}, FloatType::new);
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 51d297b1f..216c4f595 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -212,8 +212,8 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
continue;
}
- location[0] = (imgCursor.getDoublePosition(0) - xScale) / xScale;
- location[1] = (imgCursor.getDoublePosition(1) - yScale) / yScale;
+ location[0] = BackgroundModel.scaleCoordinate(imgCursor.getDoublePosition(0), xScale);
+ location[1] = BackgroundModel.scaleCoordinate(imgCursor.getDoublePosition(1), yScale);
model.applyInPlace(location);
pixel.setReal(UnsignedByteType.getCodedSignedByteChecked((int) (pixel.getRealDouble() - location[0])));
From 59b83cada33309067cd53f1004c26adab1a9a3b4 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 20 Nov 2024 17:53:40 -0500
Subject: [PATCH 37/56] Handle multiscale datasets correctly
---
.../BackgroundCorrectionClient.java | 57 ++++++++++++++-----
1 file changed, 42 insertions(+), 15 deletions(-)
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 216c4f595..0599369cd 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -72,12 +72,12 @@ public static class Parameters extends CommandLineParameters {
public String n5In;
@Parameter(names = "--datasetIn",
- description = "Name of the input dataset",
+ description = "Name of the input dataset (or group in case of multiscale pyramid; only s0 is processed)",
required = true)
public String datasetIn;
@Parameter(names = "--mask",
- description = "Name of the mask dataset for the input",
+ description = "Name of the mask dataset for the input (or group in case of multiscale pyramid; only s0 is processed)",
required = true)
public String mask;
@@ -87,7 +87,7 @@ public static class Parameters extends CommandLineParameters {
public String n5Out;
@Parameter(names = "--datasetOut",
- description = "Name of the output dataset",
+ description = "Name of the output dataset (or group in case of multiscale pyramid; only s0 is processed)",
required = true)
public String datasetOut;
@@ -137,13 +137,11 @@ public void runWithContext(final JavaSparkContext sparkContext) throws IOExcepti
final BackgroundModelProvider modelProvider = BackgroundModelProvider.fromJsonFile(parameters.parameterFile);
// set up input and output N5 datasets
- final DatasetAttributes inputAttributes;
+ final int[] blockSize;
+ final long[] dimensions;
try (final N5Reader in = new N5FSReader(parameters.n5In)) {
- inputAttributes = in.getDatasetAttributes(parameters.datasetIn);
- final Map> otherAttributes = in.listAttributes(parameters.datasetIn);
-
- if (!in.exists(parameters.mask)) {
- throw new IllegalArgumentException("Mask dataset does not exist: " + parameters.mask);
+ if (!in.exists(parameters.datasetIn) || !in.exists(parameters.mask)) {
+ throw new IllegalArgumentException("Mask dataset (" + parameters.mask + ") or input dataset (" + parameters.datasetIn + ") do not exist");
}
try (final N5Writer out = new N5FSWriter(parameters.n5Out)) {
@@ -151,12 +149,26 @@ public void runWithContext(final JavaSparkContext sparkContext) throws IOExcepti
throw new IllegalArgumentException("Output dataset already exists: " + parameters.datasetOut);
}
- out.createDataset(parameters.datasetOut, inputAttributes);
+ // create output dataset (or group if input is multiscale)
+ final boolean isMultiScale = in.exists(parameters.datasetIn + "/s0");
+ cloneDatasetOrGroupAttributes(in, out, parameters, isMultiScale);
out.setAttribute(parameters.datasetOut, "BackgroundCorrectionClientParameters", parameters);
- otherAttributes.forEach((key, clazz) -> {
- final Object attribute = in.getAttribute(parameters.datasetIn, key, clazz);
- out.setAttribute(parameters.datasetOut, key, attribute);
- });
+
+ if (isMultiScale) {
+ // since we only write s0, we need to set the scales attribute accordingly
+ out.setAttribute(parameters.datasetOut, "scales", new int[][]{{1, 1, 1}});
+
+ // also set up the output dataset for s0
+ parameters.datasetIn = parameters.datasetIn + "/s0";
+ parameters.mask = parameters.mask + "/s0";
+ parameters.datasetOut = parameters.datasetOut + "/s0";
+ cloneDatasetOrGroupAttributes(in, out, parameters, false);
+ }
+
+ // read block size and dimensions (parameters now refer to the actual dataset to be processed)
+ final DatasetAttributes inputAttributes = in.getDatasetAttributes(parameters.datasetIn);
+ blockSize = inputAttributes.getBlockSize();
+ dimensions = inputAttributes.getDimensions();
}
}
@@ -164,7 +176,7 @@ public void runWithContext(final JavaSparkContext sparkContext) throws IOExcepti
final Broadcast modelProviderBroadcast = sparkContext.broadcast(modelProvider);
final Broadcast parametersBroadcast = sparkContext.broadcast(parameters);
- final List blocks = Grid.create(inputAttributes.getDimensions(), inputAttributes.getBlockSize());
+ final List blocks = Grid.create(dimensions, blockSize);
final JavaRDD blockRDD = sparkContext.parallelize(blocks, blocks.size());
blockRDD.foreach(block -> processSingleBlock(parametersBroadcast.value(), modelProviderBroadcast.value(), block));
@@ -172,6 +184,21 @@ public void runWithContext(final JavaSparkContext sparkContext) throws IOExcepti
LOG.info("runWithContext: exit");
}
+ private static void cloneDatasetOrGroupAttributes(final N5Reader in, final N5Writer out, final Parameters parameters, final boolean isGroup) {
+ if (isGroup) {
+ out.createGroup(parameters.datasetOut);
+ } else {
+ final DatasetAttributes datasetAttributes = in.getDatasetAttributes(parameters.datasetIn);
+ out.createDataset(parameters.datasetOut, datasetAttributes);
+ }
+
+ final Map> attributes = in.listAttributes(parameters.datasetIn);
+ attributes.forEach((key, clazz) -> {
+ final Object attribute = in.getAttribute(parameters.datasetIn, key, clazz);
+ out.setAttribute(parameters.datasetOut, key, attribute);
+ });
+ }
+
private static void processSingleBlock(final Parameters parameters, final BackgroundModelProvider modelProvider, final Grid.Block block) {
LOG.info("processSingleBlock: block={}", block);
From d53c46d3b175e83b6d90b7c3ac849b5325a4cd63 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 20 Nov 2024 18:25:08 -0500
Subject: [PATCH 38/56] Fix bug in background correction code
---
.../janelia/render/client/embackground/BackgroundModel.java | 6 +++---
.../render/client/embackground/CorrectBackground.java | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
index 4a9e60bc1..4dfe81d91 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
@@ -63,12 +63,12 @@ public void normalize() {
/**
* Scale the given coordinate to the model coordinates (in [-1, 1]).
- * @param x the coordinate to scale
+ * @param coordinate the coordinate to scale
* @param scale the scale factor (usually half the image size in the respective dimension)
* @return the scaled coordinate in the range [-1, 1]
*/
- public static double scaleCoordinate(final double x, final double scale) {
- return (x - scale) / scale;
+ public static double scaleCoordinate(final double coordinate, final double scale) {
+ return (coordinate - scale) / scale;
}
@Override
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
index 049141958..f2cb23e75 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
@@ -82,7 +82,7 @@ void fitBackgroundModel(final List rois, final RandomAccessibleInterval
for (final java.awt.Point point : extractInterestPoints(rois)) {
final double x = BackgroundModel.scaleCoordinate(point.x, scaleX);
- final double y = BackgroundModel.scaleCoordinate(point.x, scaleX);
+ final double y = BackgroundModel.scaleCoordinate(point.y, scaleY);
final double z = ra.setPositionAndGet(point.x, point.y).getRealDouble();
matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{z})));
}
From 20f33c18ead6b6d15ac571a2bb4ab2f87d6fa806 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Fri, 22 Nov 2024 15:44:17 -0500
Subject: [PATCH 39/56] Make background correction work on 16bit data
---
.../BackgroundCorrectionClient.java | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 0599369cd..25df8f19f 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -8,6 +8,7 @@
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.img.Img;
import net.imglib2.type.numeric.integer.UnsignedByteType;
+import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.view.Views;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
@@ -40,8 +41,8 @@
/**
* Spark client for background correction by a layer-wise quadratic or fourth order model.
- * The client takes as input an N5 container with a 3D dataset and a parameter file, and writes the corrected data to a
- * new dataset in a given container.
+ * The client takes as input an N5 container with a 3D 16bit dataset and a parameter file, and writes the corrected data
+ * to a new dataset in a given container.
*
* The parameter file is a json file containing a list of z values and corresponding models. The model for each z value
* is valid for all z layers starting at the given z value until the next z value in the list.
@@ -206,8 +207,8 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
final N5Writer out = new N5FSWriter(parameters.n5Out)) {
// load block
- final Img img = N5Utils.open(in, parameters.datasetIn);
- final RandomAccessibleInterval croppedImg = Views.interval(img, block);
+ final Img img = N5Utils.open(in, parameters.datasetIn);
+ final RandomAccessibleInterval croppedImg = Views.interval(img, block);
final Img mask = N5Utils.open(in, parameters.mask);
final RandomAccessibleInterval croppedMask = Views.interval(mask, block);
final long[] dimensions = in.getDatasetAttributes(parameters.datasetIn).getDimensions();
@@ -224,15 +225,15 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
continue;
}
- final RandomAccessibleInterval imgSlice = Views.hyperSlice(croppedImg, 2, z - (int) block.min(2));
+ final RandomAccessibleInterval imgSlice = Views.hyperSlice(croppedImg, 2, z - (int) block.min(2));
final RandomAccessibleInterval maskSlice = Views.hyperSlice(croppedMask, 2, z - (int) block.min(2));
- final Cursor imgCursor = Views.iterable(imgSlice).localizingCursor();
+ final Cursor imgCursor = Views.iterable(imgSlice).localizingCursor();
final Cursor maskCursor = Views.iterable(maskSlice).localizingCursor();
final double[] location = new double[2];
// apply model to slice
while (imgCursor.hasNext()) {
- final UnsignedByteType pixel = imgCursor.next();
+ final UnsignedShortType pixel = imgCursor.next();
final UnsignedByteType maskPixel = maskCursor.next();
if (maskPixel.get() == 0) {
// don't apply model to background
@@ -243,12 +244,12 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
location[1] = BackgroundModel.scaleCoordinate(imgCursor.getDoublePosition(1), yScale);
model.applyInPlace(location);
- pixel.setReal(UnsignedByteType.getCodedSignedByteChecked((int) (pixel.getRealDouble() - location[0])));
+ pixel.setReal(UnsignedShortType.getCodedSignedShortChecked((int) (pixel.getRealDouble() - location[0])));
}
}
// save block
- N5Utils.saveNonEmptyBlock(croppedImg, out, parameters.datasetOut, block.gridPosition, new UnsignedByteType());
+ N5Utils.saveNonEmptyBlock(croppedImg, out, parameters.datasetOut, block.gridPosition, new UnsignedShortType());
}
}
From 213baa74911df682d82deed44a95c96b752bcc10 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Mon, 25 Nov 2024 16:26:54 -0500
Subject: [PATCH 40/56] Pull images from render instead of N5 for BG plugin
---
.../render/client/embackground/BG_Plugin.java | 84 +++++++++++--------
.../BackgroundCorrectionClient.java | 2 +-
2 files changed, 52 insertions(+), 34 deletions(-)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
index d7a63677b..03d896273 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
@@ -2,20 +2,24 @@
import java.awt.*;
import java.awt.event.KeyEvent;
+import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.swing.SwingUtilities;
import com.beust.jcommander.Parameter;
+import ij.ImagePlus;
import mpicbg.models.IllDefinedDataPointsException;
import mpicbg.models.NotEnoughDataPointsException;
-import net.imglib2.algorithm.gauss3.Gauss3;
+import mpicbg.trakem2.transform.TransformMeshMappingWithMasks.ImageProcessorWithMasks;
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.real.FloatType;
+import org.janelia.alignment.spec.Bounds;
+import org.janelia.alignment.util.ImageProcessorCache;
+import org.janelia.render.client.RenderDataClient;
import org.janelia.render.client.parameter.CommandLineParameters;
-import org.janelia.saalfeldlab.n5.N5FSReader;
-import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
+import org.janelia.render.client.solver.visualize.RenderTools;
import ij.IJ;
import ij.ImageJ;
@@ -25,33 +29,40 @@
import ij.plugin.frame.RoiManager;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.img.display.imagej.ImageJFunctions;
-import net.imglib2.view.Views;
public class BG_Plugin implements PlugIn {
+ public static final int MAX_SIZE = 2000 * 2000;
+
private static class Parameters extends CommandLineParameters {
- @Parameter(names = "--n5Path",
- description = "Path to the N5 container",
+ @Parameter(names = "--owner",
+ description = "Name of the owner in the render database",
+ required = true)
+ public String owner;
+
+ @Parameter(names = "--project",
+ description = "Name of the render project",
required = true)
- public String n5Path;
+ public String project;
- @Parameter(names = "--dataset",
- description = "Name of the dataset",
+ @Parameter(names = "--stack",
+ description = "Name of the stack in the render project",
required = true)
- public String dataset;
+ public String stack;
@Parameter(names = "--z",
description = "Z slice to process")
public int z = 1;
- @Parameter(names = "--downscaleFactor",
- description = "Downscale factor")
- public int downscale = 0;
+ @Parameter(names = "--scale",
+ description = "Scale factor for downscaling")
+ public double scale = 0.0;
}
public static int defaultType = 0;
public static boolean defaultShowBackground = false;
+ public static String baseUrl = "http://renderer-dev.int.janelia.org:8080/render-ws/v1";
public static String[] fitTypes = new String[] { "Quadratic", "Fourth Order" };
@Override
@@ -145,39 +156,46 @@ private static void addKeyListener() {
})).start();
}
- public static void main(final String[] args) {
+ public static void main(final String[] args) throws IOException {
final Parameters params = new Parameters();
params.parse(args);
new ImageJ();
SwingUtilities.invokeLater(BG_Plugin::addKeyListener);
- IJ.log("Opening " + params.dataset + " from " + params.n5Path);
- RandomAccessibleInterval img = N5Utils.open(new N5FSReader(params.n5Path), params.dataset);
- if (img.numDimensions() > 2) {
- IJ.log("Showing slice " + params.z);
- img = Views.hyperSlice(img, 2, params.z);
- }
- final RandomAccessibleInterval downscaled = downSample(img, params.downscale);
- ImageJFunctions.show(downscaled, "Original");
+ IJ.log("Opening " + params.owner + "/" + params.project + "/" + params.stack);
+ IJ.log("Showing slice " + params.z);
+
+ final ImagePlus img = renderImage(params);
+ img.show();
}
- private static RandomAccessibleInterval downSample(final RandomAccessibleInterval img, int downscale) {
- if (downscale < 1) {
+ private static ImagePlus renderImage(final Parameters params) throws IOException {
+ final RenderDataClient client = new RenderDataClient(baseUrl, params.owner, params.project);
+ final Bounds layerBounds = client.getLayerBounds(params.stack, (double) params.z);
+ final long x = layerBounds.getMinX().longValue();
+ final long y = layerBounds.getMinY().longValue();
+ final long w = layerBounds.getWidth();
+ final long h = layerBounds.getHeight();
+
+ if (params.scale == 0.0) {
// automatically determine downscale factor
- IJ.log("No downscaling factor given, choosing factor automatically...");
- final long[] dims = img.dimensionsAsLongArray();
- final long maxSize = 2000 * 2000;
- downscale = (int) Math.ceil(Math.sqrt((double) (dims[0] * dims[1]) / maxSize));
+ IJ.log("No scale given, choosing automatically...");
+ params.scale = Math.min(1, Math.sqrt((double) MAX_SIZE / (w * h)));
}
- if (downscale == 1) {
+ if (params.scale == 1) {
IJ.log("No downscaling, showing original image at full size.");
- return img;
} else {
- IJ.log("Isotropically downscaling by factor of " + downscale);
- Gauss3.gauss(downscale / 2.0, Views.extendMirrorSingle(img), img);
- return Views.subsample(img, downscale);
+ IJ.log("Isotropically downscaling by factor of " + params.scale);
}
+
+ final ImageProcessorWithMasks ipwm = RenderTools.renderImage(ImageProcessorCache.DISABLED_CACHE,
+ baseUrl, params.owner, params.project, params.stack,
+ x, y, params.z, w, h,
+ params.scale, false);
+
+ final String title = params.project + "-" + params.stack + "(z=" + params.z + ")";
+ return new ImagePlus(title, ipwm.ip);
}
}
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index 25df8f19f..f30e3f1a2 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -59,7 +59,7 @@
* }, {
* "fromZ": 3,
* "modelType": "fourthOrder",
- * "coefficients": [ 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0
+ * "coefficients": [ 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0 ]
* }
* ]
*
From c201bafdbde3d1704d25fbb8197ac8d40fd459c7 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Tue, 26 Nov 2024 11:01:56 -0500
Subject: [PATCH 41/56] Reduce the number of partitions in spark client
---
.../spark/intensityadjust/BackgroundCorrectionClient.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index f30e3f1a2..f38d3b4e9 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -178,7 +178,7 @@ public void runWithContext(final JavaSparkContext sparkContext) throws IOExcepti
final Broadcast parametersBroadcast = sparkContext.broadcast(parameters);
final List blocks = Grid.create(dimensions, blockSize);
- final JavaRDD blockRDD = sparkContext.parallelize(blocks, blocks.size());
+ final JavaRDD blockRDD = sparkContext.parallelize(blocks);
blockRDD.foreach(block -> processSingleBlock(parametersBroadcast.value(), modelProviderBroadcast.value(), block));
@@ -201,7 +201,7 @@ private static void cloneDatasetOrGroupAttributes(final N5Reader in, final N5Wri
}
private static void processSingleBlock(final Parameters parameters, final BackgroundModelProvider modelProvider, final Grid.Block block) {
- LOG.info("processSingleBlock: block={}", block);
+ LOG.info("processSingleBlock: block={}", block.gridPosition);
try (final N5Reader in = new N5FSReader(parameters.n5In);
final N5Writer out = new N5FSWriter(parameters.n5Out)) {
From a90a7d991319bb775329bf84417625e133d06c90 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 27 Nov 2024 11:07:34 -0500
Subject: [PATCH 42/56] Move BackgroundModel to render-app and rename packages
---
.../alignment/filter/emshading}/BackgroundModel.java | 2 +-
.../filter/emshading}/FourthOrderBackground.java | 2 +-
.../filter/emshading}/QuadraticBackground.java | 2 +-
.../client/{embackground => emshading}/BG_Plugin.java | 5 ++++-
.../{embackground => emshading}/CorrectBackground.java | 4 +++-
.../QuadraticBackgroundTest.java | 3 ++-
.../intensityadjust/BackgroundCorrectionClient.java | 10 +++++-----
7 files changed, 17 insertions(+), 11 deletions(-)
rename {render-ws-java-client/src/main/java/org/janelia/render/client/embackground => render-app/src/main/java/org/janelia/alignment/filter/emshading}/BackgroundModel.java (98%)
rename {render-ws-java-client/src/main/java/org/janelia/render/client/embackground => render-app/src/main/java/org/janelia/alignment/filter/emshading}/FourthOrderBackground.java (97%)
rename {render-ws-java-client/src/main/java/org/janelia/render/client/embackground => render-app/src/main/java/org/janelia/alignment/filter/emshading}/QuadraticBackground.java (96%)
rename render-ws-java-client/src/main/java/org/janelia/render/client/{embackground => emshading}/BG_Plugin.java (96%)
rename render-ws-java-client/src/main/java/org/janelia/render/client/{embackground => emshading}/CorrectBackground.java (97%)
rename render-ws-java-client/src/test/java/org/janelia/render/client/{embackground => emshading}/QuadraticBackgroundTest.java (96%)
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java b/render-app/src/main/java/org/janelia/alignment/filter/emshading/BackgroundModel.java
similarity index 98%
rename from render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
rename to render-app/src/main/java/org/janelia/alignment/filter/emshading/BackgroundModel.java
index 4dfe81d91..946b35943 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BackgroundModel.java
+++ b/render-app/src/main/java/org/janelia/alignment/filter/emshading/BackgroundModel.java
@@ -1,4 +1,4 @@
-package org.janelia.render.client.embackground;
+package org.janelia.alignment.filter.emshading;
import mpicbg.models.AbstractModel;
import mpicbg.models.IllDefinedDataPointsException;
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java b/render-app/src/main/java/org/janelia/alignment/filter/emshading/FourthOrderBackground.java
similarity index 97%
rename from render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
rename to render-app/src/main/java/org/janelia/alignment/filter/emshading/FourthOrderBackground.java
index d03278928..3df90797e 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/FourthOrderBackground.java
+++ b/render-app/src/main/java/org/janelia/alignment/filter/emshading/FourthOrderBackground.java
@@ -1,4 +1,4 @@
-package org.janelia.render.client.embackground;
+package org.janelia.alignment.filter.emshading;
import java.util.List;
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java b/render-app/src/main/java/org/janelia/alignment/filter/emshading/QuadraticBackground.java
similarity index 96%
rename from render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
rename to render-app/src/main/java/org/janelia/alignment/filter/emshading/QuadraticBackground.java
index b51795315..96e806ddf 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/QuadraticBackground.java
+++ b/render-app/src/main/java/org/janelia/alignment/filter/emshading/QuadraticBackground.java
@@ -1,4 +1,4 @@
-package org.janelia.render.client.embackground;
+package org.janelia.alignment.filter.emshading;
import java.util.List;
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/BG_Plugin.java
similarity index 96%
rename from render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
rename to render-ws-java-client/src/main/java/org/janelia/render/client/emshading/BG_Plugin.java
index 03d896273..7c9a4410c 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/BG_Plugin.java
@@ -1,4 +1,4 @@
-package org.janelia.render.client.embackground;
+package org.janelia.render.client.emshading;
import java.awt.*;
import java.awt.event.KeyEvent;
@@ -15,6 +15,9 @@
import mpicbg.trakem2.transform.TransformMeshMappingWithMasks.ImageProcessorWithMasks;
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.real.FloatType;
+import org.janelia.alignment.filter.emshading.BackgroundModel;
+import org.janelia.alignment.filter.emshading.FourthOrderBackground;
+import org.janelia.alignment.filter.emshading.QuadraticBackground;
import org.janelia.alignment.spec.Bounds;
import org.janelia.alignment.util.ImageProcessorCache;
import org.janelia.render.client.RenderDataClient;
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/CorrectBackground.java
similarity index 97%
rename from render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
rename to render-ws-java-client/src/main/java/org/janelia/render/client/emshading/CorrectBackground.java
index f2cb23e75..7485325df 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/embackground/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/CorrectBackground.java
@@ -1,4 +1,4 @@
-package org.janelia.render.client.embackground;
+package org.janelia.render.client.emshading;
import ij.ImageJ;
import ij.gui.Roi;
@@ -20,6 +20,8 @@
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.view.Views;
+import org.janelia.alignment.filter.emshading.BackgroundModel;
+import org.janelia.alignment.filter.emshading.FourthOrderBackground;
import org.janelia.saalfeldlab.n5.N5FSReader;
import org.janelia.saalfeldlab.n5.N5Reader;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
diff --git a/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java b/render-ws-java-client/src/test/java/org/janelia/render/client/emshading/QuadraticBackgroundTest.java
similarity index 96%
rename from render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
rename to render-ws-java-client/src/test/java/org/janelia/render/client/emshading/QuadraticBackgroundTest.java
index 6c4fc0bc0..7a34d6cc4 100644
--- a/render-ws-java-client/src/test/java/org/janelia/render/client/embackground/QuadraticBackgroundTest.java
+++ b/render-ws-java-client/src/test/java/org/janelia/render/client/emshading/QuadraticBackgroundTest.java
@@ -1,9 +1,10 @@
-package org.janelia.render.client.embackground;
+package org.janelia.render.client.emshading;
import mpicbg.models.IllDefinedDataPointsException;
import mpicbg.models.NotEnoughDataPointsException;
import mpicbg.models.Point;
import mpicbg.models.PointMatch;
+import org.janelia.alignment.filter.emshading.QuadraticBackground;
import org.junit.Assert;
import org.junit.Test;
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
index f38d3b4e9..150bbd266 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
@@ -16,9 +16,9 @@
import org.apache.spark.broadcast.Broadcast;
import org.janelia.alignment.util.Grid;
import org.janelia.render.client.ClientRunner;
-import org.janelia.render.client.embackground.BackgroundModel;
-import org.janelia.render.client.embackground.FourthOrderBackground;
-import org.janelia.render.client.embackground.QuadraticBackground;
+import org.janelia.alignment.filter.emshading.BackgroundModel;
+import org.janelia.alignment.filter.emshading.FourthOrderBackground;
+import org.janelia.alignment.filter.emshading.QuadraticBackground;
import org.janelia.render.client.parameter.CommandLineParameters;
import org.janelia.saalfeldlab.n5.DatasetAttributes;
import org.janelia.saalfeldlab.n5.N5FSReader;
@@ -47,7 +47,7 @@
* The parameter file is a json file containing a list of z values and corresponding models. The model for each z value
* is valid for all z layers starting at the given z value until the next z value in the list.
* Models are specified by an identifier ("quadratic" or "fourthOrder") and a list of coefficients (6 or 9,
- * respectively). Coefficients can be found interactively using {@link org.janelia.render.client.embackground.BG_Plugin}.
+ * respectively). Coefficients can be found interactively using {@link org.janelia.render.client.emshading.BG_Plugin}.
*
* In particular, the parameter file should have the following format. There is one root array, whose elements have
* exactly keys: "fromZ", "modelType", and "coefficients"s, e.g.:
@@ -254,7 +254,7 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
}
- private static class BackgroundModelProvider implements Serializable {
+ static class BackgroundModelProvider implements Serializable {
private final List sortedModelSpecs;
private BackgroundModelProvider(final List modelSpecs) {
From c2a1502878273ec0f222e6f79e70577cdd9d9888 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 27 Nov 2024 11:15:37 -0500
Subject: [PATCH 43/56] Rename background -> shading to avoid name clashes
---
...ackground.java => FourthOrderShading.java} | 16 +++----
...cBackground.java => QuadraticShading.java} | 16 +++----
...BackgroundModel.java => ShadingModel.java} | 10 ++--
...ectBackground.java => CorrectShading.java} | 48 +++++++++----------
...gin.java => ShadingCorrection_Plugin.java} | 36 +++++++-------
.../emshading/QuadraticBackgroundTest.java | 10 ++--
...ient.java => ShadingCorrectionClient.java} | 37 +++++++-------
7 files changed, 87 insertions(+), 86 deletions(-)
rename render-app/src/main/java/org/janelia/alignment/filter/emshading/{FourthOrderBackground.java => FourthOrderShading.java} (74%)
rename render-app/src/main/java/org/janelia/alignment/filter/emshading/{QuadraticBackground.java => QuadraticShading.java} (68%)
rename render-app/src/main/java/org/janelia/alignment/filter/emshading/{BackgroundModel.java => ShadingModel.java} (92%)
rename render-ws-java-client/src/main/java/org/janelia/render/client/emshading/{CorrectBackground.java => CorrectShading.java} (78%)
rename render-ws-java-client/src/main/java/org/janelia/render/client/emshading/{BG_Plugin.java => ShadingCorrection_Plugin.java} (83%)
rename render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/{BackgroundCorrectionClient.java => ShadingCorrectionClient.java} (90%)
diff --git a/render-app/src/main/java/org/janelia/alignment/filter/emshading/FourthOrderBackground.java b/render-app/src/main/java/org/janelia/alignment/filter/emshading/FourthOrderShading.java
similarity index 74%
rename from render-app/src/main/java/org/janelia/alignment/filter/emshading/FourthOrderBackground.java
rename to render-app/src/main/java/org/janelia/alignment/filter/emshading/FourthOrderShading.java
index 3df90797e..36978052c 100644
--- a/render-app/src/main/java/org/janelia/alignment/filter/emshading/FourthOrderBackground.java
+++ b/render-app/src/main/java/org/janelia/alignment/filter/emshading/FourthOrderShading.java
@@ -4,21 +4,21 @@
/**
- * A 4th order model for background correction in 2D slices of EM data.
+ * A 4th order model for shading correction in 2D slices of EM data.
* To ensure that the model is concave, no 3rd order terms are included.
*/
-public class FourthOrderBackground extends BackgroundModel {
+public class FourthOrderShading extends ShadingModel {
- public FourthOrderBackground() {
+ public FourthOrderShading() {
super();
}
- public FourthOrderBackground(final double[] coefficients) {
+ public FourthOrderShading(final double[] coefficients) {
super(coefficients);
}
- public FourthOrderBackground(final FourthOrderBackground background) {
- super(background.getCoefficients());
+ public FourthOrderShading(final FourthOrderShading shading) {
+ super(shading.getCoefficients());
}
@@ -68,7 +68,7 @@ public void applyInPlace(final double[] location) {
}
@Override
- public FourthOrderBackground copy() {
- return new FourthOrderBackground(this);
+ public FourthOrderShading copy() {
+ return new FourthOrderShading(this);
}
}
diff --git a/render-app/src/main/java/org/janelia/alignment/filter/emshading/QuadraticBackground.java b/render-app/src/main/java/org/janelia/alignment/filter/emshading/QuadraticShading.java
similarity index 68%
rename from render-app/src/main/java/org/janelia/alignment/filter/emshading/QuadraticBackground.java
rename to render-app/src/main/java/org/janelia/alignment/filter/emshading/QuadraticShading.java
index 96e806ddf..96927ecd4 100644
--- a/render-app/src/main/java/org/janelia/alignment/filter/emshading/QuadraticBackground.java
+++ b/render-app/src/main/java/org/janelia/alignment/filter/emshading/QuadraticShading.java
@@ -4,20 +4,20 @@
/**
- * A quadratic model for background correction in 2D slices of EM data.
+ * A quadratic model for shading correction in 2D slices of EM data.
*/
-public class QuadraticBackground extends BackgroundModel {
+public class QuadraticShading extends ShadingModel {
- public QuadraticBackground() {
+ public QuadraticShading() {
super();
}
- public QuadraticBackground(final double[] coefficients) {
+ public QuadraticShading(final double[] coefficients) {
super(coefficients);
}
- public QuadraticBackground(final QuadraticBackground background) {
- super(background.getCoefficients());
+ public QuadraticShading(final QuadraticShading shading) {
+ super(shading.getCoefficients());
}
@@ -57,7 +57,7 @@ public void applyInPlace(final double[] location) {
}
@Override
- public QuadraticBackground copy() {
- return new QuadraticBackground(this);
+ public QuadraticShading copy() {
+ return new QuadraticShading(this);
}
}
diff --git a/render-app/src/main/java/org/janelia/alignment/filter/emshading/BackgroundModel.java b/render-app/src/main/java/org/janelia/alignment/filter/emshading/ShadingModel.java
similarity index 92%
rename from render-app/src/main/java/org/janelia/alignment/filter/emshading/BackgroundModel.java
rename to render-app/src/main/java/org/janelia/alignment/filter/emshading/ShadingModel.java
index 946b35943..bb7f61684 100644
--- a/render-app/src/main/java/org/janelia/alignment/filter/emshading/BackgroundModel.java
+++ b/render-app/src/main/java/org/janelia/alignment/filter/emshading/ShadingModel.java
@@ -14,9 +14,9 @@
/**
- * An abstract base model for background correction in 2D slices of EM data.
+ * An abstract base model for shading correction in 2D slices of EM data.
*/
-public abstract class BackgroundModel> extends AbstractModel implements Serializable {
+public abstract class ShadingModel> extends AbstractModel implements Serializable {
private final double[] coefficients;
@@ -25,11 +25,11 @@ public abstract class BackgroundModel> extends Abst
protected abstract List coefficientNames();
- public BackgroundModel() {
+ public ShadingModel() {
coefficients = new double[nCoefficients()];
}
- public BackgroundModel(final double[] coefficients) {
+ public ShadingModel(final double[] coefficients) {
this();
System.arraycopy(coefficients, 0, this.coefficients, 0, nCoefficients());
}
@@ -128,7 +128,7 @@ public void set(final T model) {
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder("BackgroundModel{");
+ final StringBuilder sb = new StringBuilder("ShadingModel{");
if (coefficients[nCoefficients() - 1] < 0) {
sb.append("-");
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/CorrectBackground.java b/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/CorrectShading.java
similarity index 78%
rename from render-ws-java-client/src/main/java/org/janelia/render/client/emshading/CorrectBackground.java
rename to render-ws-java-client/src/main/java/org/janelia/render/client/emshading/CorrectShading.java
index 7485325df..8263996e9 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/CorrectBackground.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/CorrectShading.java
@@ -20,8 +20,8 @@
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.view.Views;
-import org.janelia.alignment.filter.emshading.BackgroundModel;
-import org.janelia.alignment.filter.emshading.FourthOrderBackground;
+import org.janelia.alignment.filter.emshading.FourthOrderShading;
+import org.janelia.alignment.filter.emshading.ShadingModel;
import org.janelia.saalfeldlab.n5.N5FSReader;
import org.janelia.saalfeldlab.n5.N5Reader;
import org.janelia.saalfeldlab.n5.imglib2.N5Utils;
@@ -36,7 +36,7 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-public class CorrectBackground {
+public class CorrectShading {
public static void main(final String[] args) throws NotEnoughDataPointsException, IllDefinedDataPointsException, IOException {
@@ -55,41 +55,41 @@ public static void main(final String[] args) throws NotEnoughDataPointsException
}
final long start = System.currentTimeMillis();
- final BackgroundModel> backgroundModel = new FourthOrderBackground();
- fitBackgroundModel(rois, firstSlice, backgroundModel);
- System.out.println("Fitted background model: " + backgroundModel);
+ final ShadingModel> shadingModel = new FourthOrderShading();
+ fitBackgroundModel(rois, firstSlice, shadingModel);
+ System.out.println("Fitted shading model: " + shadingModel);
System.out.println("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
- final RandomAccessibleInterval background = createBackgroundImage(backgroundModel, firstSlice);
- final RandomAccessibleInterval corrected = correctBackground(firstSlice, background, new UnsignedByteType());
+ final RandomAccessibleInterval shading = createBackgroundImage(shadingModel, firstSlice);
+ final RandomAccessibleInterval corrected = correctBackground(firstSlice, shading, new UnsignedByteType());
new ImageJ();
ImageJFunctions.show(firstSlice, "Original");
- ImageJFunctions.show(background, "Background");
+ ImageJFunctions.show(shading, "Shading");
ImageJFunctions.show(corrected, "Corrected");
}
}
public static & RealType>
- void fitBackgroundModel(final List rois, final RandomAccessibleInterval slice, final BackgroundModel> backgroundModel)
+ void fitBackgroundModel(final List rois, final RandomAccessibleInterval slice, final ShadingModel> shadingModel)
throws NotEnoughDataPointsException, IllDefinedDataPointsException {
// transform pixel coordinates into [-1, 1] x [-1, 1]
final double scaleX = slice.dimension(0) / 2.0;
final double scaleY = slice.dimension(1) / 2.0;
- // fit a quadratic background model with all the points in the first slice
+ // fit a quadratic shading model with all the points in the first slice
final List matches = new ArrayList<>();
final RandomAccess ra = slice.randomAccess();
for (final java.awt.Point point : extractInterestPoints(rois)) {
- final double x = BackgroundModel.scaleCoordinate(point.x, scaleX);
- final double y = BackgroundModel.scaleCoordinate(point.y, scaleY);
+ final double x = ShadingModel.scaleCoordinate(point.x, scaleX);
+ final double y = ShadingModel.scaleCoordinate(point.y, scaleY);
final double z = ra.setPositionAndGet(point.x, point.y).getRealDouble();
matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{z})));
}
- backgroundModel.fit(matches);
- backgroundModel.normalize();
+ shadingModel.fit(matches);
+ shadingModel.normalize();
}
private static Set extractInterestPoints(final List rois) {
@@ -103,34 +103,34 @@ private static Set extractInterestPoints(final List rois) {
}
public static & RealType>
- RandomAccessibleInterval createBackgroundImage(final BackgroundModel> backgroundModel, final RandomAccessibleInterval slice) {
+ RandomAccessibleInterval createBackgroundImage(final ShadingModel> shadingModel, final RandomAccessibleInterval slice) {
// transform pixel coordinates into [-1, 1] x [-1, 1]
final double scaleX = slice.dimension(0) / 2.0;
final double scaleY = slice.dimension(1) / 2.0;
final double[] location = new double[2];
- final RealRandomAccessible background = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
- location[0] = BackgroundModel.scaleCoordinate(pos.getDoublePosition(0), scaleX);
- location[1] = BackgroundModel.scaleCoordinate(pos.getDoublePosition(1), scaleY);
- backgroundModel.applyInPlace(location);
+ final RealRandomAccessible shading = new FunctionRealRandomAccessible<>(2, (pos, value) -> {
+ location[0] = ShadingModel.scaleCoordinate(pos.getDoublePosition(0), scaleX);
+ location[1] = ShadingModel.scaleCoordinate(pos.getDoublePosition(1), scaleY);
+ shadingModel.applyInPlace(location);
value.setReal(location[0]);
}, FloatType::new);
- return Views.interval(Views.raster(background), slice);
+ return Views.interval(Views.raster(shading), slice);
}
@SuppressWarnings({"rawtypes", "unchecked"})
public static & RealType>
- RandomAccessibleInterval correctBackground(final RandomAccessibleInterval slice, final RandomAccessibleInterval background, final T type) {
+ RandomAccessibleInterval correctBackground(final RandomAccessibleInterval slice, final RandomAccessibleInterval shading, final T type) {
final RandomAccessibleInterval corrected;
if (type instanceof UnsignedByteType) {
- corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
+ corrected = (RandomAccessibleInterval) Converters.convert(slice, shading, (s, b, o) -> {
o.set(UnsignedByteType.getCodedSignedByteChecked((int) (s.getRealDouble() - b.getRealDouble())));
}, new UnsignedByteType());
} else if (type instanceof UnsignedShortType) {
- corrected = (RandomAccessibleInterval) Converters.convert(slice, background, (s, b, o) -> {
+ corrected = (RandomAccessibleInterval) Converters.convert(slice, shading, (s, b, o) -> {
o.set(UnsignedShortType.getCodedSignedShortChecked((int) (s.getRealDouble() - b.getRealDouble())));
}, new UnsignedShortType());
} else {
diff --git a/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/BG_Plugin.java b/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/ShadingCorrection_Plugin.java
similarity index 83%
rename from render-ws-java-client/src/main/java/org/janelia/render/client/emshading/BG_Plugin.java
rename to render-ws-java-client/src/main/java/org/janelia/render/client/emshading/ShadingCorrection_Plugin.java
index 7c9a4410c..e085d0fea 100644
--- a/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/BG_Plugin.java
+++ b/render-ws-java-client/src/main/java/org/janelia/render/client/emshading/ShadingCorrection_Plugin.java
@@ -15,9 +15,9 @@
import mpicbg.trakem2.transform.TransformMeshMappingWithMasks.ImageProcessorWithMasks;
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.real.FloatType;
-import org.janelia.alignment.filter.emshading.BackgroundModel;
-import org.janelia.alignment.filter.emshading.FourthOrderBackground;
-import org.janelia.alignment.filter.emshading.QuadraticBackground;
+import org.janelia.alignment.filter.emshading.QuadraticShading;
+import org.janelia.alignment.filter.emshading.ShadingModel;
+import org.janelia.alignment.filter.emshading.FourthOrderShading;
import org.janelia.alignment.spec.Bounds;
import org.janelia.alignment.util.ImageProcessorCache;
import org.janelia.render.client.RenderDataClient;
@@ -33,7 +33,7 @@
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.img.display.imagej.ImageJFunctions;
-public class BG_Plugin implements PlugIn {
+public class ShadingCorrection_Plugin implements PlugIn {
public static final int MAX_SIZE = 2000 * 2000;
@@ -77,10 +77,10 @@ public void run(final String arg) {
return;
}
- final GenericDialog gd = new GenericDialog("Fit background correction");
+ final GenericDialog gd = new GenericDialog("Fit shading correction");
gd.addChoice("Fit type", fitTypes, fitTypes[defaultType]);
- gd.addCheckbox("Show background image", defaultShowBackground);
+ gd.addCheckbox("Show shading image", defaultShowBackground);
gd.showDialog();
if (gd.wasCanceled()) {
@@ -104,7 +104,7 @@ public static List getROIs() {
final RoiManager rm = RoiManager.getInstance();
if (rm == null || rm.getCount() == 0) {
- IJ.log("Please define ROIs first before running background correction.");
+ IJ.log("Please define ROIs first before running shading correction.");
return null;
}
@@ -114,27 +114,27 @@ public static List getROIs() {
public static void fit(final int type, final List rois, final boolean showBackground) throws NotEnoughDataPointsException, IllDefinedDataPointsException {
IJ.log("Fitting with " + fitTypes[type] + " model...");
- final BackgroundModel> backgroundModel;
+ final ShadingModel> shadingModel;
if (fitTypes[type].equals("Quadratic")) {
- backgroundModel = new QuadraticBackground();
+ shadingModel = new QuadraticShading();
} else if (fitTypes[type].equals("Fourth Order")) {
- backgroundModel = new FourthOrderBackground();
+ shadingModel = new FourthOrderShading();
} else {
throw new IllegalArgumentException("Unknown fit type: " + fitTypes[type]);
}
final long start = System.currentTimeMillis();
final RandomAccessibleInterval img = ImageJFunctions.wrap(IJ.getImage());
- CorrectBackground.fitBackgroundModel(rois, img, backgroundModel);
- IJ.log("Fitted background model: " + backgroundModel);
+ CorrectShading.fitBackgroundModel(rois, img, shadingModel);
+ IJ.log("Fitted shading model: " + shadingModel);
IJ.log("Fitting took " + (System.currentTimeMillis() - start) + "ms.");
- IJ.log("Raw coefficients: " + Arrays.toString(backgroundModel.getCoefficients()));
+ IJ.log("Raw coefficients: " + Arrays.toString(shadingModel.getCoefficients()));
- final RandomAccessibleInterval background = CorrectBackground.createBackgroundImage(backgroundModel, img);
- final RandomAccessibleInterval corrected = CorrectBackground.correctBackground(img, background, new UnsignedByteType());
+ final RandomAccessibleInterval shading = CorrectShading.createBackgroundImage(shadingModel, img);
+ final RandomAccessibleInterval corrected = CorrectShading.correctBackground(img, shading, new UnsignedByteType());
if (showBackground) {
- ImageJFunctions.show(background, "Background");
+ ImageJFunctions.show(shading, "Shading");
}
ImageJFunctions.show(corrected, "Corrected");
@@ -148,7 +148,7 @@ private static void addKeyListener() {
if (e.getID() == KeyEvent.KEY_PRESSED) {
switch (e.getKeyCode()) {
case KeyEvent.VK_F1:
- new BG_Plugin().run(null);
+ new ShadingCorrection_Plugin().run(null);
break;
case KeyEvent.VK_F2:
System.out.println("F2 key pressed (not assigned)");
@@ -164,7 +164,7 @@ public static void main(final String[] args) throws IOException {
params.parse(args);
new ImageJ();
- SwingUtilities.invokeLater(BG_Plugin::addKeyListener);
+ SwingUtilities.invokeLater(ShadingCorrection_Plugin::addKeyListener);
IJ.log("Opening " + params.owner + "/" + params.project + "/" + params.stack);
IJ.log("Showing slice " + params.z);
diff --git a/render-ws-java-client/src/test/java/org/janelia/render/client/emshading/QuadraticBackgroundTest.java b/render-ws-java-client/src/test/java/org/janelia/render/client/emshading/QuadraticBackgroundTest.java
index 7a34d6cc4..48fa7d31d 100644
--- a/render-ws-java-client/src/test/java/org/janelia/render/client/emshading/QuadraticBackgroundTest.java
+++ b/render-ws-java-client/src/test/java/org/janelia/render/client/emshading/QuadraticBackgroundTest.java
@@ -4,7 +4,7 @@
import mpicbg.models.NotEnoughDataPointsException;
import mpicbg.models.Point;
import mpicbg.models.PointMatch;
-import org.janelia.alignment.filter.emshading.QuadraticBackground;
+import org.janelia.alignment.filter.emshading.QuadraticShading;
import org.junit.Assert;
import org.junit.Test;
@@ -16,7 +16,7 @@ public class QuadraticBackgroundTest {
@Test
public void simpleModelProducesCorrectResults() throws NotEnoughDataPointsException, IllDefinedDataPointsException {
// 0.5 * x^2 + 0.5 * y^2 + 1
- final QuadraticBackground background = new QuadraticBackground(new double[]{1, 0, 0, 0.5, 0, 0.5});
+ final QuadraticShading background = new QuadraticShading(new double[]{1, 0, 0, 0.5, 0, 0.5});
final double[] location1 = new double[]{0, 0};
background.applyInPlace(location1);
@@ -51,7 +51,7 @@ public void simpleModelIsComputedCorrectly() throws NotEnoughDataPointsException
matches.add(new PointMatch(new Point(new double[]{-1, -1}), new Point(new double[]{1})));
matches.add(new PointMatch(new Point(new double[]{0, 1}), new Point(new double[]{0.5})));
- final QuadraticBackground background = new QuadraticBackground();
+ final QuadraticShading background = new QuadraticShading();
background.fit(matches);
// order of coefficients: {1, y, x, y^2, x*y, x^2}
@@ -60,7 +60,7 @@ public void simpleModelIsComputedCorrectly() throws NotEnoughDataPointsException
@Test
public void modelCanBeRecoveredWithManyPoints() throws NotEnoughDataPointsException, IllDefinedDataPointsException {
- final QuadraticBackground background = new QuadraticBackground(new double[]{1, 2, 3, 4, 5, 6});
+ final QuadraticShading background = new QuadraticShading(new double[]{1, 2, 3, 4, 5, 6});
final List matches = new ArrayList<>();
for (int i = 0; i < 100; i++) {
@@ -71,7 +71,7 @@ public void modelCanBeRecoveredWithManyPoints() throws NotEnoughDataPointsExcept
matches.add(new PointMatch(new Point(new double[]{x, y}), new Point(new double[]{location[0]})));
}
- final QuadraticBackground recovered = new QuadraticBackground();
+ final QuadraticShading recovered = new QuadraticShading();
recovered.fit(matches);
Assert.assertArrayEquals(background.getCoefficients(), recovered.getCoefficients(), 1e-12);
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/ShadingCorrectionClient.java
similarity index 90%
rename from render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
rename to render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/ShadingCorrectionClient.java
index 150bbd266..3ca3e7e4c 100644
--- a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/BackgroundCorrectionClient.java
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/ShadingCorrectionClient.java
@@ -16,9 +16,10 @@
import org.apache.spark.broadcast.Broadcast;
import org.janelia.alignment.util.Grid;
import org.janelia.render.client.ClientRunner;
-import org.janelia.alignment.filter.emshading.BackgroundModel;
-import org.janelia.alignment.filter.emshading.FourthOrderBackground;
-import org.janelia.alignment.filter.emshading.QuadraticBackground;
+import org.janelia.alignment.filter.emshading.ShadingModel;
+import org.janelia.alignment.filter.emshading.FourthOrderShading;
+import org.janelia.alignment.filter.emshading.QuadraticShading;
+import org.janelia.render.client.emshading.ShadingCorrection_Plugin;
import org.janelia.render.client.parameter.CommandLineParameters;
import org.janelia.saalfeldlab.n5.DatasetAttributes;
import org.janelia.saalfeldlab.n5.N5FSReader;
@@ -40,14 +41,14 @@
import java.util.Map;
/**
- * Spark client for background correction by a layer-wise quadratic or fourth order model.
+ * Spark client for shading correction by a layer-wise quadratic or fourth order model.
* The client takes as input an N5 container with a 3D 16bit dataset and a parameter file, and writes the corrected data
* to a new dataset in a given container.
*
* The parameter file is a json file containing a list of z values and corresponding models. The model for each z value
* is valid for all z layers starting at the given z value until the next z value in the list.
* Models are specified by an identifier ("quadratic" or "fourthOrder") and a list of coefficients (6 or 9,
- * respectively). Coefficients can be found interactively using {@link org.janelia.render.client.emshading.BG_Plugin}.
+ * respectively). Coefficients can be found interactively using {@link ShadingCorrection_Plugin}.
*
* In particular, the parameter file should have the following format. There is one root array, whose elements have
* exactly keys: "fromZ", "modelType", and "coefficients"s, e.g.:
@@ -64,7 +65,7 @@
* ]
*
*/
-public class BackgroundCorrectionClient implements Serializable {
+public class ShadingCorrectionClient implements Serializable {
public static class Parameters extends CommandLineParameters {
@Parameter(names = "--n5In",
@@ -98,7 +99,7 @@ public static class Parameters extends CommandLineParameters {
public String parameterFile;
}
- private static final Logger LOG = LoggerFactory.getLogger(BackgroundCorrectionClient.class);
+ private static final Logger LOG = LoggerFactory.getLogger(ShadingCorrectionClient.class);
private final Parameters parameters;
@@ -108,7 +109,7 @@ public static void main(final String[] args) {
public void runClient(final String[] args) throws Exception {
final Parameters parameters = new Parameters();
parameters.parse(args);
- final BackgroundCorrectionClient client = new BackgroundCorrectionClient(parameters);
+ final ShadingCorrectionClient client = new ShadingCorrectionClient(parameters);
client.run();
}
};
@@ -116,13 +117,13 @@ public void runClient(final String[] args) throws Exception {
}
- public BackgroundCorrectionClient(final Parameters parameters) {
+ public ShadingCorrectionClient(final Parameters parameters) {
LOG.info("init: parameters={}", parameters);
this.parameters = parameters;
}
public void run() throws IOException {
- final SparkConf conf = new SparkConf().setAppName("BackgroundCorrectionClient");
+ final SparkConf conf = new SparkConf().setAppName("ShadingCorrectionClient");
try (final JavaSparkContext sparkContext = new JavaSparkContext(conf)) {
final String sparkAppId = sparkContext.getConf().getAppId();
LOG.info("run: appId is {}", sparkAppId);
@@ -219,7 +220,7 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
// process block z-slice by z-slice
for (int z = (int) block.min(2); z <= block.max(2); z++) {
- final BackgroundModel> model = modelProvider.getModel(z);
+ final ShadingModel> model = modelProvider.getModel(z);
if (model == null) {
LOG.warn("No model found for z={}", z);
continue;
@@ -240,8 +241,8 @@ private static void processSingleBlock(final Parameters parameters, final Backgr
continue;
}
- location[0] = BackgroundModel.scaleCoordinate(imgCursor.getDoublePosition(0), xScale);
- location[1] = BackgroundModel.scaleCoordinate(imgCursor.getDoublePosition(1), yScale);
+ location[0] = ShadingModel.scaleCoordinate(imgCursor.getDoublePosition(0), xScale);
+ location[1] = ShadingModel.scaleCoordinate(imgCursor.getDoublePosition(1), yScale);
model.applyInPlace(location);
pixel.setReal(UnsignedShortType.getCodedSignedShortChecked((int) (pixel.getRealDouble() - location[0])));
@@ -262,7 +263,7 @@ private BackgroundModelProvider(final List modelSpecs) {
this.sortedModelSpecs.sort(Collections.reverseOrder(Comparator.comparingInt(ModelSpec::getZ)));
}
- public BackgroundModel> getModel(final int z) {
+ public ShadingModel> getModel(final int z) {
for (final ModelSpec modelSpec : sortedModelSpecs) {
if (z >= modelSpec.getZ()) {
return modelSpec.getModel();
@@ -286,7 +287,7 @@ public static BackgroundModelProvider fromJson(final JsonElement jsonData) {
// validation of json data
for (final ModelSpec modelSpec : modelSpecs) {
LOG.info("Found model spec: {}", modelSpec);
- final BackgroundModel> ignored = modelSpec.getModel();
+ final ShadingModel> ignored = modelSpec.getModel();
}
return new BackgroundModelProvider(modelSpecs);
@@ -309,11 +310,11 @@ public void setCoefficients(final double[] coefficients) {
this.coefficients = coefficients;
}
- public BackgroundModel> getModel() {
+ public ShadingModel> getModel() {
if (modelType.equals("quadratic")) {
- return new QuadraticBackground(coefficients);
+ return new QuadraticShading(coefficients);
} else if (modelType.equals("fourthOrder")) {
- return new FourthOrderBackground(coefficients);
+ return new FourthOrderShading(coefficients);
} else {
throw new IllegalArgumentException("Unknown model type: " + modelType);
}
From 7aa7f479ef254a928e9607475ed56a0597c83c36 Mon Sep 17 00:00:00 2001
From: Michael Innerberger
Date: Wed, 27 Nov 2024 11:41:30 -0500
Subject: [PATCH 44/56] Add first version of filter-based shading correction
---
.../filter/ShadingCorrectionFilter.java | 101 ++++++++
.../ShadingCorrectionTileClient.java | 225 ++++++++++++++++++
2 files changed, 326 insertions(+)
create mode 100644 render-app/src/main/java/org/janelia/alignment/filter/ShadingCorrectionFilter.java
create mode 100644 render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/ShadingCorrectionTileClient.java
diff --git a/render-app/src/main/java/org/janelia/alignment/filter/ShadingCorrectionFilter.java b/render-app/src/main/java/org/janelia/alignment/filter/ShadingCorrectionFilter.java
new file mode 100644
index 000000000..98322c07b
--- /dev/null
+++ b/render-app/src/main/java/org/janelia/alignment/filter/ShadingCorrectionFilter.java
@@ -0,0 +1,101 @@
+package org.janelia.alignment.filter;
+
+import ij.process.ImageProcessor;
+import org.janelia.alignment.filter.emshading.FourthOrderShading;
+import org.janelia.alignment.filter.emshading.QuadraticShading;
+import org.janelia.alignment.filter.emshading.ShadingModel;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+public class ShadingCorrectionFilter implements Filter {
+
+ public enum ShadingCorrectionMethod {
+ QUADRATIC(QuadraticShading::new),
+ FOURTH_ORDER(FourthOrderShading::new);
+
+ private final Function> modelFactory;
+
+ ShadingCorrectionMethod(final Function> modelFactory) {
+ this.modelFactory = modelFactory;
+ }
+
+ public ShadingModel> create(final double[] coefficients) {
+ return modelFactory.apply(coefficients);
+ }
+
+ public static ShadingCorrectionMethod fromString(final String method) {
+ for (final ShadingCorrectionMethod shadingCorrectionMethod : values()) {
+ if (shadingCorrectionMethod.name().equalsIgnoreCase(method)) {
+ return shadingCorrectionMethod;
+ }
+ }
+ throw new IllegalArgumentException("Unknown shading correction method: " + method);
+ }
+
+ public static ShadingCorrectionMethod forModel(final ShadingModel> model) {
+ if (model instanceof QuadraticShading) {
+ return QUADRATIC;
+ } else if (model instanceof FourthOrderShading) {
+ return FOURTH_ORDER;
+ } else {
+ throw new IllegalArgumentException("Unknown shading model class: " + model.getClass().getName());
+ }
+ }
+ }
+
+ private ShadingCorrectionMethod correctionMethod;
+ private double[] coefficients;
+
+ // empty constructor required to create instances from specifications
+ @SuppressWarnings("unused")
+ public ShadingCorrectionFilter() {
+ }
+
+ public ShadingCorrectionFilter(final ShadingModel> model) {
+ this(ShadingCorrectionMethod.forModel(model), model.getCoefficients());
+ }
+
+ public ShadingCorrectionFilter(final ShadingCorrectionMethod correctionMethod, final double[] coefficients) {
+ this.correctionMethod = correctionMethod;
+ this.coefficients = coefficients;
+ }
+
+ @Override
+ public void init(final Map params) {
+ this.correctionMethod = ShadingCorrectionMethod.fromString(Filter.getStringParameter("correctionMethod", params));
+ final String[] rawCoefficients = Filter.getCommaSeparatedStringParameter("coefficients", params);
+ this.coefficients = Arrays.stream(rawCoefficients).mapToDouble(Double::parseDouble).toArray();
+ }
+
+ @Override
+ public Map toParametersMap() {
+ final Map map = new LinkedHashMap<>();
+ map.put("correctionMethod", correctionMethod.name());
+ map.put("coefficients", Arrays.toString(coefficients));
+ return map;
+ }
+
+ @Override
+ public void process(final ImageProcessor ip, final double scale) {
+ // transform pixel coordinates into [-1, 1] x [-1, 1]
+ final double scaleX = ip.getWidth() / 2.0;
+ final double scaleY = ip.getHeight() / 2.0;
+ final ShadingModel> shadingModel = correctionMethod.create(coefficients);
+
+ // subtract shading model from image
+ final double[] location = new double[2];
+ for (int i = 0; i < ip.getWidth(); i++) {
+ for (int j = 0; j < ip.getHeight(); j++) {
+ location[0] = ShadingModel.scaleCoordinate(i, scaleX);
+ location[1] = ShadingModel.scaleCoordinate(j, scaleY);
+ shadingModel.applyInPlace(location);
+
+ final double value = ip.getPixelValue(i, j) - location[0];
+ ip.putPixelValue(i, j, value);
+ }
+ }
+ }
+}
diff --git a/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/ShadingCorrectionTileClient.java b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/ShadingCorrectionTileClient.java
new file mode 100644
index 000000000..c592694e2
--- /dev/null
+++ b/render-ws-spark-client/src/main/java/org/janelia/render/client/spark/intensityadjust/ShadingCorrectionTileClient.java
@@ -0,0 +1,225 @@
+package org.janelia.render.client.spark.intensityadjust;
+
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParametersDelegate;
+import mpicbg.models.CoordinateTransform;
+import mpicbg.models.CoordinateTransformList;
+import mpicbg.models.IllDefinedDataPointsException;
+import mpicbg.models.NotEnoughDataPointsException;
+import mpicbg.models.Point;
+import mpicbg.models.PointMatch;
+import org.apache.spark.SparkConf;
+import org.apache.spark.api.java.JavaSparkContext;
+import org.apache.spark.broadcast.Broadcast;
+import org.janelia.alignment.filter.FilterSpec;
+import org.janelia.alignment.filter.ShadingCorrectionFilter;
+import org.janelia.alignment.spec.ResolvedTileSpecCollection;
+import org.janelia.alignment.spec.SectionData;
+import org.janelia.alignment.spec.TileSpec;
+import org.janelia.alignment.spec.stack.StackMetaData;
+import org.janelia.render.client.ClientRunner;
+import org.janelia.render.client.RenderDataClient;
+import org.janelia.alignment.filter.emshading.ShadingModel;
+import org.janelia.render.client.emshading.ShadingCorrection_Plugin;
+import org.janelia.render.client.parameter.CommandLineParameters;
+import org.janelia.render.client.parameter.RenderWebServiceParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.janelia.render.client.spark.intensityadjust.ShadingCorrectionClient.BackgroundModelProvider;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Spark client for shading correction by a layer-wise quadratic or fourth order model.
+ * The client takes as input a render stack and a parameter file and creates a new stack with appropriate shading
+ * correction filters. The shading is assumed to be an artifact in the imaging space, so it is applied by finding
+ * the position of a tile within the z-layer.
+ *
+ * The parameter file is a json file containing a list of z values and corresponding models. The model for each z value
+ * is valid for all z layers starting at the given z value until the next z value in the list.
+ * Models are specified by an identifier ("quadratic" or "fourthOrder") and a list of coefficients (6 or 9,
+ * respectively). Coefficients can be found interactively using {@link ShadingCorrection_Plugin}.
+ *
+ * In particular, the parameter file should have the following format. There is one root array, whose elements have
+ * exactly keys: "fromZ", "modelType", and "coefficients"s, e.g.:
+ *
+ * [ {
+ * "fromZ": 1,
+ * "modelType": "quadratic",
+ * "coefficients": [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]
+ * }, {
+ * "fromZ": 3,
+ * "modelType": "fourthOrder",
+ * "coefficients": [ 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0 ]
+ * }
+ * ]
+ *
+ */
+public class ShadingCorrectionTileClient implements Serializable {
+
+ public static class Parameters extends CommandLineParameters {
+ @ParametersDelegate
+ public RenderWebServiceParameters webservice = new RenderWebServiceParameters();
+
+ @Parameter(names = "--stack",
+ description = "Name of the source stack in the render project",
+ required = true)
+ public String stack;
+
+ @Parameter(names = "--targetStack",
+ description = "Name of the target stack in the render project",
+ required = true)
+ public String targetStack;
+
+ @Parameter(names = "--parameterFile",
+ description = "Path to the parameter json file containing a list of z values and their corresponding models. See class description for details.",
+ required = true)
+ public String parameterFile;
+ }
+
+ private static final Logger LOG = LoggerFactory.getLogger(ShadingCorrectionTileClient.class);
+
+ private final Parameters parameters;
+
+ public static void main(final String[] args) {
+ final ClientRunner clientRunner = new ClientRunner(args) {
+ @Override
+ public void runClient(final String[] args) throws Exception {
+ final Parameters parameters = new Parameters();
+ parameters.parse(args);
+ final ShadingCorrectionTileClient client = new ShadingCorrectionTileClient(parameters);
+ client.run();
+ }
+ };
+ clientRunner.run();
+ }
+
+
+ public ShadingCorrectionTileClient(final Parameters parameters) {
+ LOG.info("init: parameters={}", parameters);
+ this.parameters = parameters;
+ }
+
+ public void run() throws IOException {
+ final SparkConf conf = new SparkConf().setAppName("ShadingCorrectionClient");
+ try (final JavaSparkContext sparkContext = new JavaSparkContext(conf)) {
+ final String sparkAppId = sparkContext.getConf().getAppId();
+ LOG.info("run: appId is {}", sparkAppId);
+ runWithContext(sparkContext);
+ }
+ }
+
+ public void runWithContext(final JavaSparkContext sparkContext) throws IOException {
+
+ LOG.info("runWithContext: entry");
+
+ final BackgroundModelProvider modelProvider = BackgroundModelProvider.fromJsonFile(parameters.parameterFile);
+
+ final RenderDataClient renderClient = parameters.webservice.getDataClient();
+ setUpTargetStack(renderClient);
+
+ final List zValues = renderClient.getStackZValues(parameters.stack);
+ final double minZ = zValues.get(0);
+ final double maxZ = zValues.get(zValues.size() - 1);
+ final ResolvedTileSpecCollection rtsc = renderClient.getResolvedTilesForZRange(parameters.stack, minZ, maxZ);
+
+ final List sectionData = renderClient.getStackSectionData(parameters.stack, minZ, maxZ);
+ final Map sectionDataMap = new HashMap<>();
+ sectionData.forEach(section -> sectionDataMap.put(section.getZ().intValue(), section));
+
+ // parallelize computation over tile specs (broadcasting some data that is needed for all tile specs)
+ final Broadcast modelProviderBroadcast = sparkContext.broadcast(modelProvider);
+ final Broadcast