Jelajahi Sumber

Annotation.read: read labels

Fabian Peter Hammerle 5 tahun lalu
induk
melakukan
c16dfa4fd1
3 mengubah file dengan 418 tambahan dan 4 penghapusan
  1. 382 3
      examples/annotation_stats.ipynb
  2. 31 1
      freesurfer_surface/__init__.py
  3. 5 0
      tests/test_annotation.py

+ 382 - 3
examples/annotation_stats.ipynb

@@ -49,6 +49,385 @@
    "cell_type": "code",
    "execution_count": 3,
    "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>name</th>\n",
+       "      <th>red</th>\n",
+       "      <th>green</th>\n",
+       "      <th>blue</th>\n",
+       "      <th>transparency</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>unknown</td>\n",
+       "      <td>25</td>\n",
+       "      <td>25</td>\n",
+       "      <td>5</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>bankssts</td>\n",
+       "      <td>25</td>\n",
+       "      <td>40</td>\n",
+       "      <td>100</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>caudalanteriorcingulate</td>\n",
+       "      <td>125</td>\n",
+       "      <td>160</td>\n",
+       "      <td>100</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>caudalmiddlefrontal</td>\n",
+       "      <td>100</td>\n",
+       "      <td>0</td>\n",
+       "      <td>25</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>corpuscallosum</td>\n",
+       "      <td>120</td>\n",
+       "      <td>50</td>\n",
+       "      <td>70</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>5</th>\n",
+       "      <td>cuneus</td>\n",
+       "      <td>220</td>\n",
+       "      <td>100</td>\n",
+       "      <td>20</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>6</th>\n",
+       "      <td>entorhinal</td>\n",
+       "      <td>220</td>\n",
+       "      <td>10</td>\n",
+       "      <td>20</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>7</th>\n",
+       "      <td>fusiform</td>\n",
+       "      <td>180</td>\n",
+       "      <td>140</td>\n",
+       "      <td>220</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>8</th>\n",
+       "      <td>inferiorparietal</td>\n",
+       "      <td>220</td>\n",
+       "      <td>220</td>\n",
+       "      <td>60</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>9</th>\n",
+       "      <td>inferiortemporal</td>\n",
+       "      <td>180</td>\n",
+       "      <td>120</td>\n",
+       "      <td>40</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>10</th>\n",
+       "      <td>isthmuscingulate</td>\n",
+       "      <td>140</td>\n",
+       "      <td>140</td>\n",
+       "      <td>20</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>11</th>\n",
+       "      <td>lateraloccipital</td>\n",
+       "      <td>20</td>\n",
+       "      <td>140</td>\n",
+       "      <td>30</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>12</th>\n",
+       "      <td>lateralorbitofrontal</td>\n",
+       "      <td>35</td>\n",
+       "      <td>50</td>\n",
+       "      <td>75</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>13</th>\n",
+       "      <td>lingual</td>\n",
+       "      <td>225</td>\n",
+       "      <td>140</td>\n",
+       "      <td>140</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>14</th>\n",
+       "      <td>medialorbitofrontal</td>\n",
+       "      <td>200</td>\n",
+       "      <td>75</td>\n",
+       "      <td>35</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>15</th>\n",
+       "      <td>middletemporal</td>\n",
+       "      <td>160</td>\n",
+       "      <td>50</td>\n",
+       "      <td>100</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>16</th>\n",
+       "      <td>parahippocampal</td>\n",
+       "      <td>20</td>\n",
+       "      <td>60</td>\n",
+       "      <td>220</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>17</th>\n",
+       "      <td>paracentral</td>\n",
+       "      <td>60</td>\n",
+       "      <td>60</td>\n",
+       "      <td>220</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>18</th>\n",
+       "      <td>parsopercularis</td>\n",
+       "      <td>220</td>\n",
+       "      <td>140</td>\n",
+       "      <td>180</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>19</th>\n",
+       "      <td>parsorbitalis</td>\n",
+       "      <td>20</td>\n",
+       "      <td>50</td>\n",
+       "      <td>100</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>20</th>\n",
+       "      <td>parstriangularis</td>\n",
+       "      <td>220</td>\n",
+       "      <td>20</td>\n",
+       "      <td>60</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>21</th>\n",
+       "      <td>pericalcarine</td>\n",
+       "      <td>120</td>\n",
+       "      <td>60</td>\n",
+       "      <td>100</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>22</th>\n",
+       "      <td>postcentral</td>\n",
+       "      <td>220</td>\n",
+       "      <td>20</td>\n",
+       "      <td>20</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>23</th>\n",
+       "      <td>posteriorcingulate</td>\n",
+       "      <td>220</td>\n",
+       "      <td>220</td>\n",
+       "      <td>180</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>24</th>\n",
+       "      <td>precentral</td>\n",
+       "      <td>60</td>\n",
+       "      <td>220</td>\n",
+       "      <td>20</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>25</th>\n",
+       "      <td>precuneus</td>\n",
+       "      <td>160</td>\n",
+       "      <td>180</td>\n",
+       "      <td>140</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>26</th>\n",
+       "      <td>rostralanteriorcingulate</td>\n",
+       "      <td>80</td>\n",
+       "      <td>140</td>\n",
+       "      <td>20</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>27</th>\n",
+       "      <td>rostralmiddlefrontal</td>\n",
+       "      <td>75</td>\n",
+       "      <td>125</td>\n",
+       "      <td>50</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>28</th>\n",
+       "      <td>superiorfrontal</td>\n",
+       "      <td>20</td>\n",
+       "      <td>160</td>\n",
+       "      <td>220</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>29</th>\n",
+       "      <td>superiorparietal</td>\n",
+       "      <td>20</td>\n",
+       "      <td>140</td>\n",
+       "      <td>180</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>30</th>\n",
+       "      <td>superiortemporal</td>\n",
+       "      <td>140</td>\n",
+       "      <td>220</td>\n",
+       "      <td>220</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>31</th>\n",
+       "      <td>supramarginal</td>\n",
+       "      <td>80</td>\n",
+       "      <td>20</td>\n",
+       "      <td>160</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>32</th>\n",
+       "      <td>frontalpole</td>\n",
+       "      <td>100</td>\n",
+       "      <td>100</td>\n",
+       "      <td>0</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>33</th>\n",
+       "      <td>temporalpole</td>\n",
+       "      <td>70</td>\n",
+       "      <td>170</td>\n",
+       "      <td>20</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>34</th>\n",
+       "      <td>transversetemporal</td>\n",
+       "      <td>150</td>\n",
+       "      <td>200</td>\n",
+       "      <td>150</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>35</th>\n",
+       "      <td>insula</td>\n",
+       "      <td>255</td>\n",
+       "      <td>32</td>\n",
+       "      <td>192</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "                        name  red  green  blue  transparency\n",
+       "0                    unknown   25     25     5             0\n",
+       "1                   bankssts   25     40   100             0\n",
+       "2    caudalanteriorcingulate  125    160   100             0\n",
+       "3        caudalmiddlefrontal  100      0    25             0\n",
+       "4             corpuscallosum  120     50    70             0\n",
+       "5                     cuneus  220    100    20             0\n",
+       "6                 entorhinal  220     10    20             0\n",
+       "7                   fusiform  180    140   220             0\n",
+       "8           inferiorparietal  220    220    60             0\n",
+       "9           inferiortemporal  180    120    40             0\n",
+       "10          isthmuscingulate  140    140    20             0\n",
+       "11          lateraloccipital   20    140    30             0\n",
+       "12      lateralorbitofrontal   35     50    75             0\n",
+       "13                   lingual  225    140   140             0\n",
+       "14       medialorbitofrontal  200     75    35             0\n",
+       "15            middletemporal  160     50   100             0\n",
+       "16           parahippocampal   20     60   220             0\n",
+       "17               paracentral   60     60   220             0\n",
+       "18           parsopercularis  220    140   180             0\n",
+       "19             parsorbitalis   20     50   100             0\n",
+       "20          parstriangularis  220     20    60             0\n",
+       "21             pericalcarine  120     60   100             0\n",
+       "22               postcentral  220     20    20             0\n",
+       "23        posteriorcingulate  220    220   180             0\n",
+       "24                precentral   60    220    20             0\n",
+       "25                 precuneus  160    180   140             0\n",
+       "26  rostralanteriorcingulate   80    140    20             0\n",
+       "27      rostralmiddlefrontal   75    125    50             0\n",
+       "28           superiorfrontal   20    160   220             0\n",
+       "29          superiorparietal   20    140   180             0\n",
+       "30          superiortemporal  140    220   220             0\n",
+       "31             supramarginal   80     20   160             0\n",
+       "32               frontalpole  100    100     0             0\n",
+       "33              temporalpole   70    170    20             0\n",
+       "34        transversetemporal  150    200   150             0\n",
+       "35                    insula  255     32   192             0"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "import pandas\n",
+    "\n",
+    "labels_frame = pandas.DataFrame((vars(label) for label in surface.annotation.labels),\n",
+    "                                columns=['index', 'name', 'red', 'green', 'blue', 'transparency'])\n",
+    "labels_frame.drop(columns=['index'])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
    "outputs": [
     {
      "data": {
@@ -114,7 +493,7 @@
        "4           6558940             4"
       ]
      },
-     "execution_count": 3,
+     "execution_count": 4,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -129,7 +508,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [
     {
@@ -173,7 +552,7 @@
        "Name: annotation_value, dtype: int64"
       ]
      },
-     "execution_count": 4,
+     "execution_count": 5,
      "metadata": {},
      "output_type": "execute_result"
     }

+ 31 - 1
freesurfer_surface/__init__.py

@@ -15,8 +15,10 @@ https://surfer.nmr.mgh.harvard.edu/
 >>>
 >>> vertex_index = surface.add_vertex(Vertex(0.0, -3.14, 21.42))
 >>> print(surface.vertices[vertex_index])
->>>
 >>> surface.write_triangular('somewhere/else/lh.pial')
+>>>
+>>> surface.load_annotation_file('bert/label/lh.aparc.annot')
+>>> print([label.name for label in surface.annotation.labels])
 """
 
 import collections
@@ -49,9 +51,22 @@ def setlocale(temporary_locale):
     finally:
         locale.setlocale(locale.LC_ALL, primary_locale)
 
+
 Vertex = collections.namedtuple('Vertex', ['right', 'anterior', 'superior'])
 
 
+class Label:
+
+    # pylint: disable=too-few-public-methods
+
+    index: int
+    name: str
+    red: int
+    blue: int
+    green: int
+    transparency: int
+
+
 class Annotation:
 
     # pylint: disable=too-few-public-methods
@@ -60,6 +75,18 @@ class Annotation:
 
     vertex_values: typing.Dict[int, int] = {}
     colortable_path: typing.Optional[bytes] = None
+    # TODO dict
+    labels: typing.List[Label] = None
+
+    @staticmethod
+    def _read_label(stream: typing.BinaryIO) -> Label:
+        label = Label()
+        label.index, name_length = struct.unpack('>II', stream.read(4*2))
+        label.name = stream.read(name_length - 1).decode()
+        assert stream.read(1) == b'\0'
+        label.red, label.blue, label.green, label.transparency \
+            = struct.unpack('>IIII', stream.read(4 * 4))
+        return label
 
     def _read(self, stream: typing.BinaryIO) -> None:
         # https://surfer.nmr.mgh.harvard.edu/fswiki/LabelsClutsAnnotationFiles
@@ -75,6 +102,9 @@ class Annotation:
         assert colortable_version > 0  # new version
         self.colortable_path = stream.read(filename_length - 1)
         assert stream.read(1) == b'\0'
+        labels_num, = struct.unpack('>I', stream.read(4))
+        self.labels = [self._read_label(stream) for _ in range(labels_num)]
+        assert not stream.read(1)
 
     @classmethod
     def read(cls, annotation_file_path: str) -> 'Annotation':

+ 5 - 0
tests/test_annotation.py

@@ -13,3 +13,8 @@ def test_load_annotation():
     assert annotation.vertex_values[42] == (((140 << 8) + 30) << 8) + 20
     assert annotation.colortable_path == b'/autofs/space/tanha_002/users/greve' \
                                          b'/fsdev.build/average/colortable_desikan_killiany.txt'
+    assert len(annotation.labels) == 36
+    assert vars(annotation.labels[0]) == {'index': 0, 'name': 'unknown',
+                                          'red': 25, 'blue': 5, 'green': 25, 'transparency': 0}
+    assert vars(annotation.labels[35]) == {'index': 35, 'name': 'insula',
+                                           'red': 255, 'blue': 192, 'green': 32, 'transparency': 0}