Browse Source

added Surface.load_annotation()

Fabian Peter Hammerle 5 years ago
parent
commit
d3568220f5

+ 0 - 1
examples/Pipfile

@@ -7,7 +7,6 @@ name = "pypi"
 freesurfer-surface = {editable = true, path = "./.."}
 pandas = "<1"
 notebook = "<6"
-"ed0a5ba" = {editable = true, path = "./.."}
 
 [dev-packages]
 

+ 1 - 5
examples/Pipfile.lock

@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "c672ad016f8684e7a167c351dd3eaefe3996f1f038281253f8c9209952ebc466"
+            "sha256": "1dbc6b947a8bf266fdc07306defa411b3084dc824a69c66014d10ffe7922382a"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -51,10 +51,6 @@
             ],
             "version": "==0.6.0"
         },
-        "ed0a5ba": {
-            "editable": true,
-            "path": "./.."
-        },
         "entrypoints": {
             "hashes": [
                 "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",

+ 187 - 0
examples/annotation_stats.ipynb

@@ -0,0 +1,187 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "155622"
+      ]
+     },
+     "execution_count": 1,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from freesurfer_surface import Surface\n",
+    "\n",
+    "SUBJECTS_DIR = '../tests/subjects'\n",
+    "surface = Surface.read_triangular(SUBJECTS_DIR + '/fabian/surf/lh.pial')\n",
+    "surface.load_annotation(SUBJECTS_DIR + '/fabian/label/lh.aparc.annot')\n",
+    "len(surface.vertex_annotation_values)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "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>annotation_value</th>\n",
+       "      <th>vertex_index</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>6558940</td>\n",
+       "      <td>0</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>6558940</td>\n",
+       "      <td>1</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>6558940</td>\n",
+       "      <td>2</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>6558940</td>\n",
+       "      <td>3</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>6558940</td>\n",
+       "      <td>4</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "   annotation_value  vertex_index\n",
+       "0           6558940             0\n",
+       "1           6558940             1\n",
+       "2           6558940             2\n",
+       "3           6558940             3\n",
+       "4           6558940             4"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "import pandas\n",
+    "\n",
+    "vertex_frame = pandas.DataFrame({'vertex_index': vertex_index, 'annotation_value': annotation_value}\n",
+    "                                for vertex_index, annotation_value in surface.vertex_annotation_values.items())\n",
+    "vertex_frame.head()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "10542100    12532\n",
+       "9221140      9883\n",
+       "9182740      9792\n",
+       "8204875      9484\n",
+       "1351760      8156\n",
+       "0            8062\n",
+       "14423100     7944\n",
+       "1316060      7095\n",
+       "14474380     7006\n",
+       "14433500     6737\n",
+       "9211105      5636\n",
+       "11832480     5560\n",
+       "3302560      5207\n",
+       "9231540      5003\n",
+       "7874740      4756\n",
+       "3296035      4383\n",
+       "6500         4128\n",
+       "2146559      4117\n",
+       "6558940      3033\n",
+       "4924360      3017\n",
+       "9221340      2839\n",
+       "3957880      2734\n",
+       "3988540      2507\n",
+       "1326300      2460\n",
+       "14464220     2352\n",
+       "9180300      1861\n",
+       "9180240      1711\n",
+       "10511485     1362\n",
+       "2647065      1322\n",
+       "3302420      1078\n",
+       "3988500       991\n",
+       "13145750      990\n",
+       "660700        789\n",
+       "11146310      727\n",
+       "6553700       368\n",
+       "Name: annotation_value, dtype: int64"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "vertex_frame['annotation_value'].value_counts()"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.7"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

+ 19 - 0
freesurfer_surface/__init__.py

@@ -54,11 +54,14 @@ Vertex = collections.namedtuple('Vertex', ['right', 'anterior', 'superior'])
 
 class Surface:
 
+    # pylint: disable=too-many-instance-attributes
+
     _MAGIC_NUMBER = b'\xff\xff\xfe'
 
     _TAG_CMDLINE = b'\x00\x00\x00\x03'
     _TAG_OLD_SURF_GEOM = b'\x00\x00\x00\x14'
     _TAG_OLD_USEREALRAS = b'\x00\x00\x00\x02'
+    _TAG_OLD_COLORTABLE = b'\0\0\0\x01'
 
     _DATETIME_FORMAT = '%a %b %d %H:%M:%S %Y'
 
@@ -70,6 +73,7 @@ class Surface:
         self.using_old_real_ras = False
         self.volume_geometry_info = None
         self.command_lines = []
+        self.vertex_annotation_values = None
 
     @classmethod
     def _read_cmdlines(cls, stream: typing.BinaryIO) -> typing.Iterator[str]:
@@ -151,6 +155,21 @@ class Surface:
                 surface_file.write(self._TAG_CMDLINE + struct.pack('>Q', len(command_line) + 1)
                                    + command_line + b'\0')
 
+    def load_annotation(self, annotation_file_path: str) -> None:
+        # https://surfer.nmr.mgh.harvard.edu/fswiki/LabelsClutsAnnotationFiles
+        with open(annotation_file_path, 'rb') as annotation_file:
+            annotations_num, = struct.unpack('>I', annotation_file.read(4))
+            assert annotations_num <= len(self.vertices)
+            annotations = (struct.unpack('>II', annotation_file.read(4 * 2))
+                           for _ in range(annotations_num))
+            self.vertex_annotation_values = {vertex_index: annotation_value
+                                             for vertex_index, annotation_value in annotations}
+            assert all(0 <= vertex_index < len(self.vertices)
+                       for vertex_index in self.vertex_annotation_values.keys())
+            assert all((annotation_value >> (8 * 3)) == 0
+                       for annotation_value in self.vertex_annotation_values.values())
+            assert annotation_file.read(4) == self._TAG_OLD_COLORTABLE
+
     def add_vertex(self, vertex: Vertex) -> int:
         self.vertices.append(vertex)
         return len(self.vertices) - 1

BIN
tests/subjects/fabian/label/lh.aparc.annot


+ 10 - 0
tests/test_surface.py

@@ -116,6 +116,16 @@ def test_write_triangular_same_locale(tmpdir):
         assert output_file.read().split(b' on ')[1].startswith(b'Mon Dec 31 21:42:00 2018\n')
 
 
+def test_load_annotation():
+    surface = Surface.read_triangular(SURFACE_FILE_PATH)
+    assert not surface.vertex_annotation_values
+    surface.load_annotation(os.path.join(SUBJECTS_DIR, 'fabian', 'label', 'lh.aparc.annot'))
+    assert len(surface.vertex_annotation_values) == 155622
+    assert surface.vertex_annotation_values[0] == (((100 << 8) + 20) << 8) + 220
+    assert surface.vertex_annotation_values[1] == (((100 << 8) + 20) << 8) + 220
+    assert surface.vertex_annotation_values[42] == (((140 << 8) + 30) << 8) + 20
+
+
 def test_add_vertex():
     surface = Surface()
     assert not surface.vertices