diff --git a/src/main/java/net/imagej/ops/geom/GeomNamespace.java b/src/main/java/net/imagej/ops/geom/GeomNamespace.java index 9acd9d7271..c732c3866d 100644 --- a/src/main/java/net/imagej/ops/geom/GeomNamespace.java +++ b/src/main/java/net/imagej/ops/geom/GeomNamespace.java @@ -31,6 +31,7 @@ import java.util.List; +import net.imagej.axis.CalibratedAxis; import net.imagej.mesh.Mesh; import net.imagej.ops.AbstractNamespace; import net.imagej.ops.Namespace; @@ -497,7 +498,18 @@ public > Mesh marchingCubes( net.imagej.ops.Ops.Geometric.MarchingCubes.class, in, isolevel, interpolatorClass); return result; - } + } + + @OpMethod(op = net.imagej.ops.geom.geom3d.DefaultMarchingCubes.class) + public > Mesh marchingCubes( + final RandomAccessibleInterval in, final double isolevel, + final VertexInterpolator interpolatorClass, final List axes) + { + final Mesh result = (Mesh) ops().run( + net.imagej.ops.Ops.Geometric.MarchingCubes.class, in, isolevel, + interpolatorClass, axes); + return result; + } @OpMethod(op = net.imagej.ops.geom.geom3d.DefaultMedianElongation.class) public DoubleType medianElongation(final Mesh in) { diff --git a/src/main/java/net/imagej/ops/geom/geom3d/DefaultMarchingCubes.java b/src/main/java/net/imagej/ops/geom/geom3d/DefaultMarchingCubes.java index e63daaed9f..997b80b4ea 100644 --- a/src/main/java/net/imagej/ops/geom/geom3d/DefaultMarchingCubes.java +++ b/src/main/java/net/imagej/ops/geom/geom3d/DefaultMarchingCubes.java @@ -29,7 +29,11 @@ package net.imagej.ops.geom.geom3d; +import java.util.List; + +import net.imagej.axis.CalibratedAxis; import net.imagej.mesh.Mesh; +import net.imagej.mesh.Triangles; import net.imagej.mesh.naive.NaiveDoubleMesh; import net.imagej.ops.Contingent; import net.imagej.ops.Ops; @@ -55,6 +59,7 @@ * lookup tables are from his implementation. * * @author Tim-Oliver Buchholz (University of Konstanz) + * @author Richard Domander * @param BooleanType */ @Plugin(type = Ops.Geometric.MarchingCubes.class) @@ -70,6 +75,10 @@ public class DefaultMarchingCubes> extends private VertexInterpolator interpolatorClass = new DefaultVertexInterpolator(); + /** If not null, output mesh coordinates are calibrated */ + @Parameter(type = ItemIO.INPUT, required = false) + private List axes; + @SuppressWarnings({ "unchecked" }) @Override public Mesh calculate(final RandomAccessibleInterval input) { @@ -181,7 +190,14 @@ public Mesh calculate(final RandomAccessibleInterval input) { final double v2y = vertlist[TRIANGLE_TABLE[cubeindex][i]][1]; final double v2z = vertlist[TRIANGLE_TABLE[cubeindex][i]][2]; if (positiveArea(v0x, v0y, v0z, v1x, v1y, v1z, v2x, v2y, v2z)) { - output.triangles().add(v0x, v0y, v0z, v1x, v1y, v1z, v2x, v2y, v2z); + if (axes != null) { + addCalibrated(output.triangles(), v0x, v0y, v0z, + v1x, v1y, v1z, v2x, v2y, v2z); + } else { + output.triangles() + .add(v0x, v0y, v0z, v1x, v1y, v1z, v2x, v2y, + v2z); + } } } } @@ -189,6 +205,20 @@ public Mesh calculate(final RandomAccessibleInterval input) { return output; } + private void addCalibrated(final Triangles triangles, final double v0x, + final double v0y, final double v0z, final double v1x, + final double v1y, final double v1z, final double v2x, + final double v2y, final double v2z) { + final CalibratedAxis xAxis = axes.get(0); + final CalibratedAxis yAxis = axes.get(1); + final CalibratedAxis zAxis = axes.get(2); + triangles.add(xAxis.calibratedValue(v0x), yAxis.calibratedValue(v0y), + zAxis.calibratedValue(v0z), xAxis.calibratedValue(v1x), + yAxis.calibratedValue(v1y), zAxis.calibratedValue(v1z), + xAxis.calibratedValue(v2x), yAxis.calibratedValue(v2y), + zAxis.calibratedValue(v2z)); + } + private boolean positiveArea(double v0x, double v0y, double v0z, // double v1x, double v1y, double v1z, // double v2x, double v2y, double v2z) diff --git a/src/test/java/net/imagej/ops/geom/MeshFeatureTests.java b/src/test/java/net/imagej/ops/geom/MeshFeatureTests.java index e972aa4bb9..89e4185915 100644 --- a/src/test/java/net/imagej/ops/geom/MeshFeatureTests.java +++ b/src/test/java/net/imagej/ops/geom/MeshFeatureTests.java @@ -33,6 +33,11 @@ import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.imagej.axis.CalibratedAxis; +import net.imagej.axis.DefaultLinearAxis; import net.imagej.mesh.Mesh; import net.imagej.mesh.Triangle; import net.imagej.ops.Ops; @@ -52,12 +57,20 @@ import net.imagej.ops.geom.geom3d.DefaultVerticesCountMesh; import net.imagej.ops.geom.geom3d.DefaultVolumeConvexHullMesh; import net.imagej.ops.geom.geom3d.DefaultVolumeMesh; +import net.imagej.ops.geom.geom3d.mesh.DefaultVertexInterpolator; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.type.numeric.real.DoubleType; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +/** + * Tests for mesh related ops + * + * @author Tim-Oliver Buchholz (University of Konstanz) + * @author Richard Domander + */ public class MeshFeatureTests extends AbstractFeatureTest { private static final double EPSILON = 10e-12; private static LabelRegion ROI; @@ -69,6 +82,12 @@ public static void setupBefore() { mesh = getMesh(); } + @AfterClass + public static void oneTimeTearDown() { + mesh = null; + ROI = null; + } + @Test public void boxivityMesh() { try { @@ -85,12 +104,11 @@ public void compactness() { ((DoubleType) ops.run(DefaultCompactness.class, mesh)).get(), EPSILON); } + /** + * ConvexHull3D is tested in {@link QuickHull3DTest}. + */ @Test - public void convexHull3D() { - /** - * convexHull3D is tested in {@link QuickHull3DTest}. - */ - } + public void convexHull3D() {} @Test public void convexityMesh() { @@ -128,6 +146,36 @@ public void marchingCubes() { assertTrue(!expectedFacets.hasNext() && !actualFacets.hasNext()); } + @Test + public void marchingCubesCalibratedMesh() { + final double sx = 1.0; + final double sy = 0.5; + final double sz = 0.25; + final List axes = Stream + .of(new DefaultLinearAxis(sx), new DefaultLinearAxis(sy), + new DefaultLinearAxis(sz)).collect(Collectors.toList()); + final Iterator meshFacets = mesh.triangles().iterator(); + + final Mesh result = ops.geom() + .marchingCubes(ROI, 1, new DefaultVertexInterpolator(), axes); + + assertEquals(mesh.triangles().size(), result.triangles().size()); + final Iterator actualFacets = result.triangles().iterator(); + while (meshFacets.hasNext() && actualFacets.hasNext()) { + final Triangle mesh = meshFacets.next(); + final Triangle actual = actualFacets.next(); + assertEquals(mesh.v0x() * sx, actual.v0x(), EPSILON); + assertEquals(mesh.v0y() * sy, actual.v0y(), EPSILON); + assertEquals(mesh.v0z() * sz, actual.v0z(), EPSILON); + assertEquals(mesh.v1x() * sx, actual.v1x(), EPSILON); + assertEquals(mesh.v1y() * sy, actual.v1y(), EPSILON); + assertEquals(mesh.v1z() * sz, actual.v1z(), EPSILON); + assertEquals(mesh.v2x() * sx, actual.v2x(), EPSILON); + assertEquals(mesh.v2y() * sy, actual.v2y(), EPSILON); + assertEquals(mesh.v2z() * sz, actual.v2z(), EPSILON); + } + } + @Test public void medianElongation() { // formula verified and ground truth computed with matlab