From 91148e641f2224eb2b004001755987d2f2433261 Mon Sep 17 00:00:00 2001
From: Paul McCarthy <pauldmccarthy@gmail.com>
Date: Sun, 21 Jan 2018 12:35:39 +0000
Subject: [PATCH] Mesh unit tests updated

---
 tests/test_mesh.py | 251 ++++++++++++++++++++++-----------------------
 1 file changed, 121 insertions(+), 130 deletions(-)

diff --git a/tests/test_mesh.py b/tests/test_mesh.py
index ce47c09fe..bfbffafaf 100644
--- a/tests/test_mesh.py
+++ b/tests/test_mesh.py
@@ -6,10 +6,6 @@
 #
 
 
-import os.path as op
-import            shutil
-import            tempfile
-
 import numpy   as np
 import            mock
 import            pytest
@@ -18,9 +14,6 @@ import fsl.utils.transform as transform
 import fsl.data.mesh       as fslmesh
 
 
-datadir = op.join(op.dirname(__file__), 'testdata')
-
-
 # vertices of a cube
 CUBE_VERTICES = np.array([
     [-1, -1, -1],
@@ -49,119 +42,96 @@ CUBE_TRIANGLES_CW = np.array([
 CUBE_TRIANGLES_CCW = np.array(CUBE_TRIANGLES_CW)
 CUBE_TRIANGLES_CCW[:, [1, 2]] = CUBE_TRIANGLES_CCW[:, [2, 1]]
 
+CUBE_CCW_FACE_NORMALS = np.array([
+    [ 0,  0, -1], [ 0,  0, -1],
+    [ 0,  0,  1], [ 0,  0,  1],
+    [ 0, -1,  0], [ 0, -1,  0],
+    [ 0,  1,  0], [ 0,  1,  0],
+    [-1,  0,  0], [-1,  0,  0],
+    [ 1,  0,  0], [ 1,  0,  0],
+])
 
-def test_create_mesh():
-
-    # Test:
-    #  - create from file
-    #  - create from inmem data
-    testbase = 'test_mesh.vtk'
-    testfile = op.join(datadir, testbase)
-
-    verts, lens, indices = fslmesh.loadVTKPolydataFile(testfile)
-
-    mesh1 = fslmesh.TriangleMesh(testfile)
-    mesh2 = fslmesh.TriangleMesh(verts, indices)
-
-    assert mesh1.name       == testbase
-    assert mesh2.name       == 'TriangleMesh'
-    assert mesh1.dataSource == testfile
-    assert mesh2.dataSource is None
-
-    assert mesh1.vertices.shape == (642,  3)
-    assert mesh2.vertices.shape == (642,  3)
-    assert mesh1.indices.shape  == (1280, 3)
-    assert mesh2.indices.shape  == (1280, 3)
-
-    minbounds = np.array([ 59.50759888,  88.43039703,  72.10890198])
-    maxbounds = np.array([ 77.72619629, 128.40600586,  94.82050323])
-
-    mesh1Bounds = mesh1.getBounds()
-    mesh2Bounds = mesh2.getBounds()
-
-    assert np.all(np.isclose(mesh1Bounds[0], minbounds))
-    assert np.all(np.isclose(mesh1Bounds[1], maxbounds))
-    assert np.all(np.isclose(mesh2Bounds[0], minbounds))
-    assert np.all(np.isclose(mesh2Bounds[1], maxbounds))
-
+CUBE_CCW_VERTEX_NORMALS = np.zeros((8, 3))
+for i in range(8):
+    faces = np.where(CUBE_TRIANGLES_CCW == i)[0]
+    CUBE_CCW_VERTEX_NORMALS[i] = CUBE_CCW_FACE_NORMALS[faces].sum(axis=0)
+CUBE_CCW_VERTEX_NORMALS = transform.normalise(CUBE_CCW_VERTEX_NORMALS)
 
-def test_mesh_loadVertexData():
 
-    meshfile = op.join(datadir, 'test_mesh.vtk')
-    datafile = op.join(datadir, 'test_mesh_data.txt')
-    memdata  = np.random.randint(1, 100, 642)
-    mesh     = fslmesh.TriangleMesh(meshfile)
+def test_mesh_create():
 
-    assert mesh.loadVertexData(datafile).shape == (642,)
-    assert np.all(mesh.loadVertexData('inmemdata', memdata) == memdata)
+    verts = np.array(CUBE_VERTICES)
+    tris  = np.array(CUBE_TRIANGLES_CCW)
 
-    assert mesh.getVertexData(datafile).shape == (642,)
-    assert np.all(mesh.getVertexData('inmemdata') == memdata)
+    mesh = fslmesh.Mesh(tris, vertices=verts)
 
-    mesh.clearVertexData()
+    assert mesh.name       == 'mesh'
+    assert mesh.dataSource is None
+    assert np.all(np.isclose(mesh.vertices, verts))
+    assert np.all(np.isclose(mesh.indices,  tris))
 
-    assert mesh.getVertexData(datafile).shape == (642,)
-    assert np.all(mesh.loadVertexData('inmemdata', memdata) == memdata)
+    blo, bhi = mesh.bounds
 
+    assert np.all(np.isclose(blo, verts.min(axis=0)))
+    assert np.all(np.isclose(bhi, verts.max(axis=0)))
 
-def test_loadVTKPolydataFile():
 
-    testfile = op.join(datadir, 'test_mesh.vtk')
-    verts, lens, indices = fslmesh.loadVTKPolydataFile(testfile)
+def test_mesh_addVertices():
 
-    assert verts.shape   == (642, 3)
-    assert indices.shape == (3840, )
-    assert lens.shape    == (1280, )
-    assert np.all(lens == 3)
+    tris   = np.array(CUBE_TRIANGLES_CCW)
+    verts  = np.array(CUBE_VERTICES)
+    verts2 = np.array(CUBE_VERTICES) * 2
+    verts3 = np.array(CUBE_VERTICES) * 3
 
+    mesh = fslmesh.Mesh(tris, vertices=verts)
 
-def test_getFIRSTPrefix():
+    assert mesh.selectedVertices() == 'default'
+    assert np.all(np.isclose(mesh.vertices, verts))
 
-    failures = [
-        'blob.txt',
-        'blob.vtk',
-        'blob.nii.gz']
+    mesh.addVertices(verts2, 'twotimes')
 
-    passes = [
-        ('blurgh-L_Thal_first.vtk', 'blurgh'),
-        ('blurgh-L_Accu_first.vtk', 'blurgh'),
-        ('blurgh_bufuu-R_Hipp_first.vtk', 'blurgh_bufuu'),
-    ]
+    assert mesh.selectedVertices() == 'twotimes'
+    assert np.all(np.isclose(mesh.vertices, verts2))
 
-    for f in failures:
-        with pytest.raises(ValueError):
-            fslmesh.getFIRSTPrefix(f)
+    mesh.addVertices(verts3, 'threetimes', select=False)
 
-    for fname, expected in passes:
-        assert fslmesh.getFIRSTPrefix(fname) == expected
+    assert mesh.selectedVertices() == 'twotimes'
+    assert np.all(np.isclose(mesh.vertices, verts2))
 
+    mesh.vertices = 'threetimes'
 
+    assert mesh.selectedVertices() == 'threetimes'
+    assert np.all(np.isclose(mesh.vertices, verts3))
 
-def test_findReferenceImage():
 
-    testdir  = tempfile.mkdtemp()
-    vtkfiles = ['struc-L_Thal_first.vtk',
-                'struc_first-L_Thal_first.vtk']
+def test_mesh_addVertexData():
 
-    assert fslmesh.findReferenceImage('nofile') is None
+    mesh = fslmesh.Mesh(np.array(CUBE_TRIANGLES_CCW),
+                        vertices=np.array(CUBE_VERTICES))
 
-    try:
+    nverts = CUBE_VERTICES.shape[0]
 
-        for fname in vtkfiles:
+    data3D   = np.random.randint(1, 100,  nverts)
+    data3_1D = np.random.randint(1, 100, (nverts, 1))
+    data4D   = np.random.randint(1, 100, (nverts, 20))
 
-            assert fslmesh.findReferenceImage(fname) is None
+    mesh.addVertexData('3d',   data3D)
+    mesh.addVertexData('3_1d', data3_1D)
+    mesh.addVertexData('4d',   data4D)
 
-            prefix   = fslmesh.getFIRSTPrefix(fname)
-            imgfname = op.join(testdir, '{}.nii.gz'.format(prefix))
-            fname    = op.join(testdir, fname)
+    assert mesh.getVertexData('3d')  .shape == (nverts, 1)
+    assert mesh.getVertexData('3_1d').shape == (nverts, 1)
+    assert mesh.getVertexData('4d')  .shape == (nverts, 20)
 
-            with open(fname,    'wt') as f: f.write(fname)
-            with open(imgfname, 'wt') as f: f.write(imgfname)
+    assert np.all(np.isclose(data3D.reshape(-1, 1), mesh.getVertexData('3d')))
+    assert np.all(np.isclose(data3_1D,              mesh.getVertexData('3_1d')))
+    assert np.all(np.isclose(data4D,                mesh.getVertexData('4d')))
 
-            assert fslmesh.findReferenceImage(fname) == imgfname
+    mesh.clearVertexData()
 
-    finally:
-        shutil.rmtree(testdir)
+    with pytest.raises(KeyError): mesh.getVertexData('3d')
+    with pytest.raises(KeyError): mesh.getVertexData('3_1d')
+    with pytest.raises(KeyError): mesh.getVertexData('4d')
 
 
 def test_normals():
@@ -170,34 +140,18 @@ def test_normals():
     verts         = np.array(CUBE_VERTICES)
     triangles_cw  = np.array(CUBE_TRIANGLES_CW)
     triangles_ccw = np.array(CUBE_TRIANGLES_CCW)
+    fnormals      = np.array(CUBE_CCW_FACE_NORMALS)
+    vnormals      = np.array(CUBE_CCW_VERTEX_NORMALS)
+
+    cw_nofix  = fslmesh.Mesh(np.array(triangles_cw))
+    cw_fix    = fslmesh.Mesh(np.array(triangles_cw))
+    ccw_nofix = fslmesh.Mesh(np.array(triangles_ccw))
+    ccw_fix   = fslmesh.Mesh(np.array(triangles_ccw))
 
-    # face normals
-    fnormals = np.array([
-        [ 0,  0, -1], [ 0,  0, -1],
-        [ 0,  0,  1], [ 0,  0,  1],
-        [ 0, -1,  0], [ 0, -1,  0],
-        [ 0,  1,  0], [ 0,  1,  0],
-        [-1,  0,  0], [-1,  0,  0],
-        [ 1,  0,  0], [ 1,  0,  0],
-    ])
-
-    # vertex normals
-    vnormals = np.zeros((8, 3))
-    for i in range(8):
-        faces = np.where(triangles_cw == i)[0]
-        vnormals[i] = fnormals[faces].sum(axis=0)
-    vnormals = transform.normalise(vnormals)
-
-    cw_nofix  = fslmesh.TriangleMesh(np.array(verts),
-                                     np.array(triangles_cw))
-    cw_fix    = fslmesh.TriangleMesh(np.array(verts),
-                                     np.array(triangles_cw),
-                                     fixWinding=True)
-    ccw_nofix = fslmesh.TriangleMesh(np.array(verts),
-                                     np.array(triangles_ccw))
-    ccw_fix   = fslmesh.TriangleMesh(np.array(verts),
-                                     np.array(triangles_ccw),
-                                     fixWinding=True)
+    cw_nofix .addVertices(np.array(verts))
+    cw_fix   .addVertices(np.array(verts), fixWinding=True)
+    ccw_nofix.addVertices(np.array(verts))
+    ccw_fix  .addVertices(np.array(verts), fixWinding=True)
 
     # ccw triangles should give correct
     # normals without unwinding
@@ -206,29 +160,54 @@ def test_normals():
     assert np.all(np.isclose(cw_fix   .normals,   fnormals))
     assert np.all(np.isclose(cw_fix   .vnormals,  vnormals))
     assert np.all(np.isclose(ccw_nofix.normals,   fnormals))
-    assert np.all(np.isclose(ccw_nofix.vnormals, vnormals))
+    assert np.all(np.isclose(ccw_nofix.vnormals,  vnormals))
     assert np.all(np.isclose(ccw_fix  .normals,   fnormals))
     assert np.all(np.isclose(ccw_fix  .vnormals,  vnormals))
 
+    # Test standalone calcFaceNormals/
+    # calcVertexNormals functions
+    assert np.all(np.isclose(
+        -fnormals, fslmesh.calcFaceNormals(verts, triangles_cw)))
+    assert np.all(np.isclose(
+        fnormals, fslmesh.calcFaceNormals(verts, triangles_ccw)))
+    assert np.all(np.isclose(
+        -vnormals, fslmesh.calcVertexNormals(verts, triangles_cw, -fnormals)))
+    assert np.all(np.isclose(
+        vnormals, fslmesh.calcVertexNormals(verts, triangles_ccw, fnormals)))
+
+
+def test_needsFixing():
+
+    verts    = np.array(CUBE_VERTICES)
+    tris_cw  = np.array(CUBE_TRIANGLES_CW)
+    tris_ccw = np.array(CUBE_TRIANGLES_CCW)
+    fnormals = np.array(CUBE_CCW_VERTEX_NORMALS)
+    blo      = verts.min(axis=0)
+    bhi      = verts.max(axis=0)
+
+    assert not fslmesh.needsFixing(verts, tris_ccw, fnormals, blo, bhi)
+    assert     fslmesh.needsFixing(verts, tris_cw, -fnormals, blo, bhi)
 
 
 def test_trimesh_no_trimesh():
 
-    testbase = 'test_mesh.vtk'
-    testfile = op.join(datadir, testbase)
+    verts = np.array(CUBE_VERTICES)
+    tris  = np.array(CUBE_TRIANGLES_CCW)
 
     mods = ['trimesh', 'rtree']
 
     for mod in mods:
         with mock.patch.dict('sys.modules', **{mod : None}):
-            mesh = fslmesh.TriangleMesh(testfile)
+
+            mesh = fslmesh.Mesh(tris, vertices=verts)
+
             assert mesh.trimesh() is None
             locs, tris = mesh.rayIntersection([[0, 0, 0]], [[0, 0, 1]])
             assert locs.size == 0
             assert tris.size == 0
 
-            verts, idxs, dists = mesh.nearestVertex([[0, 0, 0]])
-            assert verts.size == 0
+            nverts, idxs, dists = mesh.nearestVertex([[0, 0, 0]])
+            assert nverts.size == 0
             assert idxs.size  == 0
             assert dists.size == 0
 
@@ -241,9 +220,10 @@ def test_trimesh():
 
     import trimesh
 
-    testbase = 'test_mesh.vtk'
-    testfile = op.join(datadir, testbase)
-    mesh = fslmesh.TriangleMesh(testfile)
+    verts = np.array(CUBE_VERTICES)
+    tris  = np.array(CUBE_TRIANGLES_CCW)
+
+    mesh = fslmesh.Mesh(tris, vertices=verts)
     assert isinstance(mesh.trimesh(), trimesh.Trimesh)
 
 
@@ -251,7 +231,7 @@ def test_rayIntersection():
 
     verts     = np.array(CUBE_VERTICES)
     triangles = np.array(CUBE_TRIANGLES_CCW)
-    mesh      = fslmesh.TriangleMesh(verts, triangles)
+    mesh      = fslmesh.Mesh(triangles, vertices=verts)
 
     for axis in range(3):
         rayOrigin       = [0, 0, 0]
@@ -279,7 +259,7 @@ def test_nearestVertex():
 
     verts     = np.array(CUBE_VERTICES)
     triangles = np.array(CUBE_TRIANGLES_CCW)
-    mesh      = fslmesh.TriangleMesh(verts, triangles)
+    mesh      = fslmesh.Mesh(triangles, vertices=verts)
 
     nverts, nidxs, ndists = mesh.nearestVertex(verts * 2)
 
@@ -292,7 +272,7 @@ def test_planeIntersection():
 
     verts     = np.array(CUBE_VERTICES)
     triangles = np.array(CUBE_TRIANGLES_CCW)
-    mesh      = fslmesh.TriangleMesh(verts, triangles)
+    mesh      = fslmesh.Mesh(triangles, vertices=verts)
 
     normal = [0, 0, 1]
     origin = [0, 0, 0]
@@ -357,3 +337,14 @@ def test_planeIntersection():
     assert np.all(np.isclose(lines, expLines))
     assert np.all(np.isclose(faces, expFaces))
     assert np.all(np.isclose(dists, expDists))
+
+    normal = [0, 0, 1]
+    origin = [3, 3, 3]
+
+    lines, faces, dists = mesh.planeIntersection(normal,
+                                                 origin,
+                                                 distances=True)
+
+    assert lines.shape  == (0, 2, 3)
+    assert faces.shape  == (0, )
+    assert dists.shape  == (0, 2, 3)
-- 
GitLab