Browse Source

subclass Vertex from numpy.ndarray (previously collections.namedtuple)

Fabian Peter Hammerle 5 years ago
parent
commit
512705b0e4

+ 4 - 5
examples/precentral_gyrus_border.ipynb

@@ -75,13 +75,12 @@
     "BORDER_WIDTH = 0.8 # mm\n",
     "\n",
     "for vertex_triplet_indices in zip(*(slice_offset(border_chain.vertex_indices, o) for o in range(3))):\n",
-    "    vertex_triplet_coords = tuple(numpy.array(surface.vertices[idx])\n",
-    "                                  for idx in vertex_triplet_indices)\n",
-    "    backward_vector = vertex_triplet_coords[0] - vertex_triplet_coords[1]\n",
-    "    forward_vector = unit_vector(vertex_triplet_coords[2] - vertex_triplet_coords[1]) * BORDER_WIDTH / 2\n",
+    "    vertex_triplet = [surface.vertices[idx] for idx in vertex_triplet_indices]\n",
+    "    backward_vector = vertex_triplet[0] - vertex_triplet[1]\n",
+    "    forward_vector = unit_vector(vertex_triplet[2] - vertex_triplet[1]) * BORDER_WIDTH / 2\n",
     "    upward_vector = unit_vector(numpy.cross(backward_vector, forward_vector)) * BORDER_WIDTH / 2\n",
     "    sideward_vector = unit_vector(numpy.cross(upward_vector, forward_vector)) * BORDER_WIDTH / 2\n",
-    "    cross_section_corners = [vertex_triplet_coords[1] + v\n",
+    "    cross_section_corners = [vertex_triplet[1] + v\n",
     "                             for v in [-upward_vector -sideward_vector,\n",
     "                                       -upward_vector +sideward_vector,\n",
     "                                       +upward_vector +sideward_vector,\n",

+ 29 - 29
examples/vertex_coordinate_stats.ipynb

@@ -49,40 +49,40 @@
        "  <thead>\n",
        "    <tr style=\"text-align: right;\">\n",
        "      <th></th>\n",
-       "      <th>right</th>\n",
        "      <th>anterior</th>\n",
+       "      <th>right</th>\n",
        "      <th>superior</th>\n",
        "    </tr>\n",
        "  </thead>\n",
        "  <tbody>\n",
        "    <tr>\n",
        "      <th>0</th>\n",
-       "      <td>-2.126251</td>\n",
        "      <td>-93.136818</td>\n",
+       "      <td>-2.126251</td>\n",
        "      <td>-8.335812</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>1</th>\n",
-       "      <td>-2.696686</td>\n",
        "      <td>-93.194458</td>\n",
+       "      <td>-2.696686</td>\n",
        "      <td>-8.206024</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>2</th>\n",
-       "      <td>-3.738874</td>\n",
        "      <td>-93.369530</td>\n",
+       "      <td>-3.738874</td>\n",
        "      <td>-8.548558</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>3</th>\n",
-       "      <td>-4.594223</td>\n",
        "      <td>-93.183983</td>\n",
+       "      <td>-4.594223</td>\n",
        "      <td>-8.173017</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>4</th>\n",
-       "      <td>-2.160212</td>\n",
        "      <td>-93.190521</td>\n",
+       "      <td>-2.160212</td>\n",
        "      <td>-8.865552</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
@@ -90,12 +90,12 @@
        "</div>"
       ],
       "text/plain": [
-       "      right   anterior  superior\n",
-       "0 -2.126251 -93.136818 -8.335812\n",
-       "1 -2.696686 -93.194458 -8.206024\n",
-       "2 -3.738874 -93.369530 -8.548558\n",
-       "3 -4.594223 -93.183983 -8.173017\n",
-       "4 -2.160212 -93.190521 -8.865552"
+       "    anterior     right  superior\n",
+       "0 -93.136818 -2.126251 -8.335812\n",
+       "1 -93.194458 -2.696686 -8.206024\n",
+       "2 -93.369530 -3.738874 -8.548558\n",
+       "3 -93.183983 -4.594223 -8.173017\n",
+       "4 -93.190521 -2.160212 -8.865552"
       ]
      },
      "execution_count": 2,
@@ -106,7 +106,7 @@
    "source": [
     "import pandas\n",
     "\n",
-    "vertex_frame = pandas.DataFrame(surface.vertices)\n",
+    "vertex_frame = pandas.DataFrame(map(vars, surface.vertices))\n",
     "vertex_frame.head()"
    ]
   },
@@ -136,8 +136,8 @@
        "  <thead>\n",
        "    <tr style=\"text-align: right;\">\n",
        "      <th></th>\n",
-       "      <th>right</th>\n",
        "      <th>anterior</th>\n",
+       "      <th>right</th>\n",
        "      <th>superior</th>\n",
        "    </tr>\n",
        "  </thead>\n",
@@ -150,44 +150,44 @@
        "    </tr>\n",
        "    <tr>\n",
        "      <th>mean</th>\n",
-       "      <td>-26.856146</td>\n",
        "      <td>-20.509144</td>\n",
+       "      <td>-26.856146</td>\n",
        "      <td>24.335409</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>std</th>\n",
-       "      <td>18.796259</td>\n",
        "      <td>39.092622</td>\n",
+       "      <td>18.796259</td>\n",
        "      <td>28.195845</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>min</th>\n",
-       "      <td>-68.530327</td>\n",
        "      <td>-93.548004</td>\n",
+       "      <td>-68.530327</td>\n",
        "      <td>-39.114269</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>25%</th>\n",
-       "      <td>-41.877634</td>\n",
        "      <td>-53.572824</td>\n",
+       "      <td>-41.877634</td>\n",
        "      <td>1.600599</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>50%</th>\n",
-       "      <td>-26.917927</td>\n",
        "      <td>-23.102324</td>\n",
+       "      <td>-26.917927</td>\n",
        "      <td>24.000984</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>75%</th>\n",
-       "      <td>-9.776343</td>\n",
        "      <td>10.147226</td>\n",
+       "      <td>-9.776343</td>\n",
        "      <td>47.299869</td>\n",
        "    </tr>\n",
        "    <tr>\n",
        "      <th>max</th>\n",
-       "      <td>7.291560</td>\n",
        "      <td>70.085968</td>\n",
+       "      <td>7.291560</td>\n",
        "      <td>86.737892</td>\n",
        "    </tr>\n",
        "  </tbody>\n",
@@ -195,15 +195,15 @@
        "</div>"
       ],
       "text/plain": [
-       "               right       anterior       superior\n",
+       "            anterior          right       superior\n",
        "count  155622.000000  155622.000000  155622.000000\n",
-       "mean      -26.856146     -20.509144      24.335409\n",
-       "std        18.796259      39.092622      28.195845\n",
-       "min       -68.530327     -93.548004     -39.114269\n",
-       "25%       -41.877634     -53.572824       1.600599\n",
-       "50%       -26.917927     -23.102324      24.000984\n",
-       "75%        -9.776343      10.147226      47.299869\n",
-       "max         7.291560      70.085968      86.737892"
+       "mean      -20.509144     -26.856146      24.335409\n",
+       "std        39.092622      18.796259      28.195845\n",
+       "min       -93.548004     -68.530327     -39.114269\n",
+       "25%       -53.572824     -41.877634       1.600599\n",
+       "50%       -23.102324     -26.917927      24.000984\n",
+       "75%        10.147226      -9.776343      47.299869\n",
+       "max        70.085968       7.291560      86.737892"
       ]
      },
      "execution_count": 3,

+ 30 - 1
freesurfer_surface/__init__.py

@@ -70,7 +70,36 @@ def setlocale(temporary_locale):
         locale.setlocale(locale.LC_ALL, primary_locale)
 
 
-Vertex = collections.namedtuple('Vertex', ['right', 'anterior', 'superior'])
+class Vertex(numpy.ndarray):
+
+    def __new__(cls, right: float, anterior: float, superior: float):
+        return numpy.array((right, anterior, superior),
+                           dtype=float).view(cls)
+
+    @property
+    def right(self) -> float:
+        return self[0]
+
+    @property
+    def anterior(self) -> float:
+        return self[1]
+
+    @property
+    def superior(self) -> float:
+        return self[2]
+
+    @property
+    def __dict__(self) -> typing.Dict[str, float]:
+        return {'right': self.right,
+                'anterior': self.anterior,
+                'superior': self.superior}
+
+    def __format_coords(self) -> str:
+        return ', '.join('{}={}'.format(name, getattr(self, name))
+                         for name in ['right', 'anterior', 'superior'])
+
+    def __repr__(self) -> str:
+        return '{}({})'.format(type(self).__name__, self.__format_coords())
 
 
 class _PolygonalCircuit:

+ 4 - 0
tests/test_surface.py

@@ -1,5 +1,6 @@
 import datetime
 
+import numpy
 import pytest
 
 from freesurfer_surface import setlocale, Vertex, Triangle, _LineSegment, \
@@ -110,6 +111,9 @@ def test_write_read_triangular_same(tmpdir):
     expected_surface.write_triangular(output_path,
                                       creation_datetime=expected_surface.creation_datetime)
     resulted_surface = Surface.read_triangular(output_path)
+    assert numpy.array_equal(expected_surface.vertices,
+                             resulted_surface.vertices)
+    expected_surface.vertices = resulted_surface.vertices = []
     assert vars(expected_surface) == vars(resulted_surface)
 
 

+ 31 - 0
tests/test_vertex.py

@@ -1,3 +1,4 @@
+import numpy
 import pytest
 
 from freesurfer_surface import Vertex
@@ -5,6 +6,36 @@ from freesurfer_surface import Vertex
 
 def test_init():
     vertex = Vertex(-4.0, 0.5, 21.42)
+    assert isinstance(vertex, numpy.ndarray)
     assert vertex.right == pytest.approx(-4.0)
     assert vertex.anterior == pytest.approx(0.5)
     assert vertex.superior == pytest.approx(21.42)
+
+
+def test_init_kwargs():
+    vertex = Vertex(right=-4.0, superior=21.42, anterior=0.5)
+    assert vertex.right == pytest.approx(-4.0)
+    assert vertex.anterior == pytest.approx(0.5)
+    assert vertex.superior == pytest.approx(21.42)
+
+
+def test_repr():
+    vertex = Vertex(right=-4.0, superior=21.42, anterior=0.5)
+    assert repr(vertex) == 'Vertex(right=-4.0, anterior=0.5, superior=21.42)'
+
+
+def test_add():
+    assert Vertex(-1.5, 4, 2) + Vertex(2, -4.5, 3) \
+        == pytest.approx(Vertex(0.5, -0.5, 5))
+
+
+def test_mult():
+    assert Vertex(-1.5, 4, 2) * -3 == pytest.approx(Vertex(4.5, -12, -6))
+
+
+def test_vars():
+    attrs = vars(Vertex(-1.5, 4, 2))
+    assert len(attrs) == 3
+    assert attrs['right'] == pytest.approx(-1.5)
+    assert attrs['anterior'] == pytest.approx(4)
+    assert attrs['superior'] == pytest.approx(2)