Skip to content

Commit 7491470

Browse files
committed
Merge pull request #47 from effigies/master
RF Use nibabel IO functions where available
2 parents ad8bf56 + 3db78a1 commit 7491470

File tree

6 files changed

+20
-220
lines changed

6 files changed

+20
-220
lines changed

examples/plot_foci.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
import os.path as op
1414
from numpy import arange
1515
from numpy.random import permutation
16+
import nibabel as nib
1617
from surfer import Brain
17-
from surfer import io
1818

1919
subject_id = "fsaverage"
2020
subjects_dir = os.environ["SUBJECTS_DIR"]
@@ -61,7 +61,7 @@
6161
and find 10 random vertices within the STS.
6262
"""
6363
annot_path = op.join(subjects_dir, subject_id, "label/lh.aparc.a2009s.annot")
64-
ids, ctab, names = io.read_annot(annot_path)
64+
ids, ctab, names = nib.freesurfer.read_annot(annot_path)
6565
verts = arange(0, len(ids))
6666
coords = permutation(verts[ids == 74])[:10]
6767

examples/plot_parc_values.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
import os
1313
import os.path as op
1414
import numpy as np
15-
from surfer import io, Brain
15+
import nibabel as nib
16+
from surfer import Brain
1617

1718
subject_id = "fsaverage"
1819
hemi = "lh"
@@ -31,7 +32,7 @@
3132
aparc_file = op.join(os.environ["SUBJECTS_DIR"],
3233
subject_id, "label",
3334
hemi + ".aparc.a2009s.annot")
34-
labels, ctab, names = io.read_annot(aparc_file)
35+
labels, ctab, names = nib.freesurfer.read_annot(aparc_file)
3536

3637
"""
3738
Make a random vector of scalar data corresponding to

surfer/io.py

Lines changed: 9 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -40,105 +40,6 @@ def _get_subjects_dir(subjects_dir=None):
4040
return subjects_dir
4141

4242

43-
def _fread3(fobj):
44-
"""Read a 3-byte int from an open binary file object."""
45-
b1, b2, b3 = np.fromfile(fobj, ">u1", 3)
46-
return (b1 << 16) + (b2 << 8) + b3
47-
48-
49-
def _fread3_many(fobj, n):
50-
"""Read 3-byte ints from an open binary file object."""
51-
b1, b2, b3 = np.fromfile(fobj, ">u1", 3 * n).reshape(-1,
52-
3).astype(np.int).T
53-
return (b1 << 16) + (b2 << 8) + b3
54-
55-
56-
def read_geometry(filepath):
57-
"""Read a triangular format Freesurfer surface mesh.
58-
59-
Parameters
60-
----------
61-
filepath : str
62-
Path to surface file
63-
64-
Returns
65-
-------
66-
coords : numpy array
67-
nvtx x 3 array of vertex (x, y, z) coordinates
68-
faces : numpy array
69-
nfaces x 3 array of defining mesh triangles
70-
"""
71-
with open(filepath, "rb") as fobj:
72-
magic = _fread3(fobj)
73-
if magic == 16777215: # Quad file
74-
nvert = _fread3(fobj)
75-
nquad = _fread3(fobj)
76-
coords = np.fromfile(fobj, ">i2", nvert * 3).astype(np.float)
77-
coords = coords.reshape(-1, 3) / 100.0
78-
quads = _fread3_many(fobj, nquad * 4)
79-
quads = quads.reshape(nquad, 4)
80-
#
81-
# Face splitting follows
82-
#
83-
faces = np.zeros((2 * nquad, 3), dtype=np.int)
84-
nface = 0
85-
for quad in quads:
86-
if (quad[0] % 2) == 0:
87-
faces[nface] = quad[0], quad[1], quad[3]
88-
nface += 1
89-
faces[nface] = quad[2], quad[3], quad[1]
90-
nface += 1
91-
else:
92-
faces[nface] = quad[0], quad[1], quad[2]
93-
nface += 1
94-
faces[nface] = quad[0], quad[2], quad[3]
95-
nface += 1
96-
97-
elif magic == 16777214: # Triangle file
98-
create_stamp = fobj.readline()
99-
_ = fobj.readline()
100-
vnum = np.fromfile(fobj, ">i4", 1)[0]
101-
fnum = np.fromfile(fobj, ">i4", 1)[0]
102-
coords = np.fromfile(fobj, ">f4", vnum * 3).reshape(vnum, 3)
103-
faces = np.fromfile(fobj, ">i4", fnum * 3).reshape(fnum, 3)
104-
else:
105-
raise ValueError("File does not appear to be a Freesurfer surface")
106-
107-
coords = coords.astype(np.float) # XXX: due to mayavi bug on mac 32bits
108-
return coords, faces
109-
110-
111-
def read_morph_data(filepath):
112-
"""Read a Freesurfer morphometry data file.
113-
114-
This function reads in what Freesurfer internally calls "curv" file types,
115-
(e.g. ?h. curv, ?h.thickness), but as that has the potential to cause
116-
confusion where "curv" also refers to the surface curvature values,
117-
we refer to these files as "morphometry" files with PySurfer.
118-
119-
Parameters
120-
----------
121-
filepath : str
122-
Path to morphometry file
123-
124-
Returns
125-
-------
126-
curv : numpy array
127-
Vector representation of surface morpometry values
128-
129-
"""
130-
with open(filepath, "rb") as fobj:
131-
magic = _fread3(fobj)
132-
if magic == 16777215:
133-
vnum = np.fromfile(fobj, ">i4", 3)[0]
134-
curv = np.fromfile(fobj, ">f4", vnum)
135-
else:
136-
vnum = magic
137-
_ = _fread3(fobj)
138-
curv = np.fromfile(fobj, ">i2", vnum) / 100
139-
return curv
140-
141-
14243
def read_scalar_data(filepath):
14344
"""Load in scalar data from an image.
14445
@@ -199,76 +100,6 @@ def read_scalar_data(filepath):
199100
return scalar_data
200101

201102

202-
def read_annot(filepath, orig_ids=False):
203-
"""Read in a Freesurfer annotation from a .annot file.
204-
205-
Parameters
206-
----------
207-
filepath : str
208-
Path to annotation file
209-
orig_ids : bool
210-
Whether to return the vertex ids as stored in the annotation
211-
file or the positional colortable ids
212-
213-
Returns
214-
-------
215-
labels : n_vtx numpy array
216-
Annotation id at each vertex
217-
ctab : numpy array
218-
RGBA + label id colortable array
219-
names : numpy array
220-
Array of region names as stored in the annot file
221-
222-
"""
223-
with open(filepath, "rb") as fobj:
224-
dt = ">i4"
225-
vnum = np.fromfile(fobj, dt, 1)[0]
226-
data = np.fromfile(fobj, dt, vnum * 2).reshape(vnum, 2)
227-
labels = data[:, 1]
228-
ctab_exists = np.fromfile(fobj, dt, 1)[0]
229-
if not ctab_exists:
230-
raise Exception('Color table not found in annotation file')
231-
n_entries = np.fromfile(fobj, dt, 1)[0]
232-
if n_entries > 0:
233-
length = np.fromfile(fobj, dt, 1)[0]
234-
orig_tab = np.fromfile(fobj, '>c', length)
235-
orig_tab = orig_tab[:-1]
236-
237-
names = list()
238-
ctab = np.zeros((n_entries, 5), np.int)
239-
for i in xrange(n_entries):
240-
name_length = np.fromfile(fobj, dt, 1)[0]
241-
name = np.fromfile(fobj, "|S%d" % name_length, 1)[0]
242-
names.append(name)
243-
ctab[i, :4] = np.fromfile(fobj, dt, 4)
244-
ctab[i, 4] = (ctab[i, 0] + ctab[i, 1] * (2 ** 8) +
245-
ctab[i, 2] * (2 ** 16) +
246-
ctab[i, 3] * (2 ** 24))
247-
else:
248-
ctab_version = -n_entries
249-
if ctab_version != 2:
250-
raise Exception('Color table version not supported')
251-
n_entries = np.fromfile(fobj, dt, 1)[0]
252-
ctab = np.zeros((n_entries, 5), np.int)
253-
length = np.fromfile(fobj, dt, 1)[0]
254-
_ = np.fromfile(fobj, "|S%d" % length, 1)[0] # Orig table path
255-
entries_to_read = np.fromfile(fobj, dt, 1)[0]
256-
names = list()
257-
for i in xrange(entries_to_read):
258-
_ = np.fromfile(fobj, dt, 1)[0] # Structure
259-
name_length = np.fromfile(fobj, dt, 1)[0]
260-
name = np.fromfile(fobj, "|S%d" % name_length, 1)[0]
261-
names.append(name)
262-
ctab[i, :4] = np.fromfile(fobj, dt, 4)
263-
ctab[i, 4] = (ctab[i, 0] + ctab[i, 1] * (2 ** 8) +
264-
ctab[i, 2] * (2 ** 16))
265-
ctab[:, 3] = 255
266-
if not orig_ids:
267-
ord = np.argsort(ctab[:, -1])
268-
labels = ord[np.searchsorted(ctab[ord, -1], labels)]
269-
return labels, ctab, names
270-
271-
272103
def read_label(filepath, read_scalars=False):
273104
"""Load in a Freesurfer .label file.
274105
@@ -492,7 +323,12 @@ def __init__(self, subject_id, hemi, surf, subjects_dir=None):
492323
def load_geometry(self):
493324
surf_path = pjoin(self.data_path, "surf",
494325
"%s.%s" % (self.hemi, self.surf))
495-
self.coords, self.faces = read_geometry(surf_path)
326+
self.coords, self.faces = nib.freesurfer.read_geometry(surf_path)
327+
328+
def save_geometry(self):
329+
surf_path = pjoin(self.data_path, "surf",
330+
"%s.%s" % (self.hemi, self.surf))
331+
nib.freesurfer.write_geometry(surf_path, self.coords, self.faces)
496332

497333
@property
498334
def x(self):
@@ -509,7 +345,7 @@ def z(self):
509345
def load_curvature(self):
510346
"""Load in curvature values from the ?h.curv file."""
511347
curv_path = pjoin(self.data_path, "surf", "%s.curv" % self.hemi)
512-
self.curv = read_morph_data(curv_path)
348+
self.curv = nib.freesurfer.read_morph_data(curv_path)
513349
self.bin_curv = np.array(self.curv > 0, np.int)
514350

515351
def load_label(self, name):
@@ -521,8 +357,8 @@ def load_label(self, name):
521357
argument.
522358
523359
"""
524-
label = read_label(pjoin(self.data_path, 'label',
525-
'%s.%s.label' % (self.hemi, name)))
360+
label = nib.freesurfer.read_label(pjoin(self.data_path, 'label',
361+
'%s.%s.label' % (self.hemi, name)))
526362
label_array = np.zeros(len(self.x), np.int)
527363
label_array[label] = 1
528364
try:

surfer/tests/test_io.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,6 @@
1515
data_path = pjoin(subj_dir, subject_id)
1616

1717

18-
def test_geometry():
19-
"""Test IO of .surf"""
20-
surf_path = pjoin(data_path, "surf", "%s.%s" % ("lh", "inflated"))
21-
coords, faces = io.read_geometry(surf_path)
22-
assert_equal(0, faces.min())
23-
assert_equal(coords.shape[0], faces.max() + 1)
24-
25-
# Test quad with sphere
26-
surf_path = pjoin(data_path, "surf", "%s.%s" % ("lh", "sphere.reg"))
27-
coords, faces = io.read_geometry(surf_path)
28-
assert_equal(0, faces.min())
29-
assert_equal(coords.shape[0], faces.max() + 1)
30-
31-
32-
def test_morph_data():
33-
"""Test IO of morphometry data file (eg. curvature)."""
34-
curv_path = pjoin(data_path, "surf", "%s.%s" % ("lh", "curv"))
35-
curv = io.read_morph_data(curv_path)
36-
assert -1.0 < curv.min() < 0
37-
assert 0 < curv.max() < 1.0
38-
39-
40-
def test_annot():
41-
"""Test IO of .annot"""
42-
annots = ['aparc', 'aparc.a2005s']
43-
for a in annots:
44-
annot_path = pjoin(data_path, "label", "%s.%s.annot" % ("lh", a))
45-
labels, ctab, names = io.read_annot(annot_path)
46-
assert labels.shape == (163842, )
47-
assert ctab.shape == (len(names), 5)
48-
49-
50-
def test_label():
51-
"""Test IO of .label"""
52-
label_path = pjoin(data_path, "label", "lh.BA1.label")
53-
label = io.read_label(label_path)
54-
# XXX : test more
55-
assert np.all(label > 0)
56-
57-
5818
def test_surface():
5919
"""Test of Surface class"""
6020
for subjects_dir in [None, subj_dir]:

surfer/tests/test_viz.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from os.path import join as pjoin
44
from numpy.testing import assert_raises
55
from tempfile import mktemp
6+
import nibabel as nib
67

78
from surfer import Brain
89
from surfer import io
@@ -99,7 +100,7 @@ def test_foci():
99100
brain.add_foci(coords, map_surface="white", color="gold")
100101

101102
annot_path = pjoin(subj_dir, subject_id, 'label', 'lh.aparc.a2009s.annot')
102-
ids, ctab, names = io.read_annot(annot_path)
103+
ids, ctab, names = nib.freesurfer.read_annot(annot_path)
103104
verts = np.arange(0, len(ids))
104105
coords = np.random.permutation(verts[ids == 74])[:10]
105106
scale_factor = 0.7

surfer/viz.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from scipy import ndimage
88
from matplotlib.colors import colorConverter
99

10+
import nibabel as nib
11+
1012
from . import io
1113
from . import utils
1214
from .io import Surface, _get_subjects_dir
@@ -598,7 +600,7 @@ def add_annotation(self, annot, borders=True, alpha=1):
598600
% filepath)
599601

600602
# Read in the data
601-
labels, cmap, _ = io.read_annot(filepath, orig_ids=True)
603+
labels, cmap, _ = nib.freesurfer.read_annot(filepath, orig_ids=True)
602604

603605
# Maybe zero-out the non-border vertices
604606
if borders:
@@ -828,7 +830,7 @@ def add_morphometry(self, measure, grayscale=False):
828830
view = mlab.view()
829831

830832
# Read in the morphometric data
831-
morph_data = io.read_morph_data(morph_file)
833+
morph_data = nib.freesurfer.read_morph_data(morph_file)
832834

833835
# Get a cortex mask for robust range
834836
self._geo.load_label("cortex")

0 commit comments

Comments
 (0)