diff --git a/src/test/java/net/imagej/ops/geom/MeshFeatureTests.java b/src/test/java/net/imagej/ops/geom/MeshFeatureTests.java index 620ef36ed..56292cb96 100644 --- a/src/test/java/net/imagej/ops/geom/MeshFeatureTests.java +++ b/src/test/java/net/imagej/ops/geom/MeshFeatureTests.java @@ -37,24 +37,18 @@ import net.imagej.mesh.Triangle; import net.imagej.ops.Ops; import net.imagej.ops.features.AbstractFeatureTest; -import net.imagej.ops.geom.geom3d.DefaultBoxivityMesh; -import net.imagej.ops.geom.geom3d.DefaultCompactness; -import net.imagej.ops.geom.geom3d.DefaultConvexityMesh; -import net.imagej.ops.geom.geom3d.DefaultMainElongation; -import net.imagej.ops.geom.geom3d.DefaultMarchingCubes; -import net.imagej.ops.geom.geom3d.DefaultMedianElongation; -import net.imagej.ops.geom.geom3d.DefaultSolidityMesh; -import net.imagej.ops.geom.geom3d.DefaultSparenessMesh; -import net.imagej.ops.geom.geom3d.DefaultSphericity; -import net.imagej.ops.geom.geom3d.DefaultSurfaceArea; -import net.imagej.ops.geom.geom3d.DefaultSurfaceAreaConvexHullMesh; -import net.imagej.ops.geom.geom3d.DefaultVerticesCountConvexHullMesh; -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.*; +import net.imagej.ops.morphology.fillHoles.DefaultFillHoles; +import net.imagej.ops.morphology.floodFill.DefaultFloodFill; +import net.imglib2.Cursor; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImgs; import net.imglib2.roi.labeling.LabelRegion; +import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.view.Views; import org.junit.BeforeClass; import org.junit.Test; @@ -199,8 +193,88 @@ public void verticesCountMesh() { } + /** + * Creates a 3D binary image of a sphere. + * + * @param r The radius of the sphere. + * @return A RandomAccessibleInterval representing the sphere. + */ + public RandomAccessibleInterval generateSphere(int r) { + long[] dims = new long[] {-r, r, -r, r, -r, r}; // Dimensions of the bounding box of the sphere + Img sphereImg = ArrayImgs.bits(dims); + + Cursor cursor = sphereImg.localizingCursor(); + + // Center of the sphere + int cx = r; + int cy = r; + int cz = r; + + while (cursor.hasNext()) { + cursor.fwd(); + int x = cursor.getIntPosition(0) - cx; + int y = cursor.getIntPosition(1) - cy; + int z = cursor.getIntPosition(2) - cz; + + if (x * x + y * y + z * z <= r * r) { + cursor.get().set(true); + } + } + + return sphereImg; + } + + public long compareImages(RandomAccessibleInterval img1, RandomAccessibleInterval img2) { + long diff = 0; + Cursor cursor1 = Views.iterable(img1).cursor(); + Cursor cursor2 = Views.iterable(img2).cursor(); + + while (cursor1.hasNext() && cursor2.hasNext()) { + cursor1.fwd(); + cursor2.fwd(); + + if (!cursor1.get().valueEquals(cursor2.get())) { + diff++; + } + } + return diff; + } + @Test public void voxelization3D() { // https://github.com/imagej/imagej-ops/issues/422 + RandomAccessibleInterval sphere = generateSphere(20); + final Mesh result = (Mesh) ops.run(DefaultMarchingCubes.class, sphere); + assertEquals(mesh.triangles().size(), result.triangles().size()); + final Iterator expectedFacets = mesh.triangles().iterator(); + final Iterator actualFacets = result.triangles().iterator(); + while (expectedFacets.hasNext() && actualFacets.hasNext()) { + final Triangle expected = expectedFacets.next(); + final Triangle actual = actualFacets.next(); + assertEquals(expected.v0x(), actual.v0x(), EPSILON); + assertEquals(expected.v0y(), actual.v0y(), EPSILON); + assertEquals(expected.v0z(), actual.v0z(), EPSILON); + assertEquals(expected.v1x(), actual.v1x(), EPSILON); + assertEquals(expected.v1y(), actual.v1y(), EPSILON); + assertEquals(expected.v1z(), actual.v1z(), EPSILON); + assertEquals(expected.v2x(), actual.v2x(), EPSILON); + assertEquals(expected.v2y(), actual.v2y(), EPSILON); + assertEquals(expected.v2z(), actual.v2z(), EPSILON); + } + assertTrue(!expectedFacets.hasNext() && !actualFacets.hasNext()); + + // The mesh is good by now, let's check the voxelization + RandomAccessibleInterval voxelization = (RandomAccessibleInterval) ops.run(DefaultVoxelization3D.class, result); + + // Flood fill (ops implementation starts from borders) + RandomAccessibleInterval filledVoxelization = (RandomAccessibleInterval) ops.run(DefaultFillHoles.class, voxelization); + + // Compare inverted image + // Comparison + long diff = compareImages(sphere, filledVoxelization); + long total = ROI.size(); + + assertTrue("Voxelization does not match the original image closely enough.", diff / (double) total < 0.07); + } }