Skip to content

Commit 9c67036

Browse files
committed
RF: Reducing read_annot function size. .annot data type is now
module-level. Other small doc adjustments.
1 parent 728b016 commit 9c67036

File tree

1 file changed

+124
-46
lines changed

1 file changed

+124
-46
lines changed

nibabel/freesurfer/io.py

Lines changed: 124 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
from ..openers import Opener
1313

1414

15+
_ANNOT_DT = ">i4"
16+
"""Data type for Freesurfer `.annot` files.
17+
18+
Used by :func:`read_annot` and :func:`write_annot`. All data (apart from
19+
strings) in an `.annot` file is stored as big-endian int32.
20+
"""
21+
22+
1523
def _fread3(fobj):
1624
"""Read a 3-byte int from an open binary file object
1725
@@ -74,8 +82,10 @@ def _read_volume_info(fobj):
7482

7583

7684
def _pack_rgba(rgba):
77-
"""Used by :meth:`read_annot` and :meth:`write_annot` to pack an RGBA
78-
sequence into a single integer.
85+
"""Pack an RGBA sequence into a single integer.
86+
87+
Used by :func:`read_annot` and :func:`write_annot` to generate
88+
"annotation values" for a Freesuerfer `.annot` file.
7989
8090
Parameters
8191
----------
@@ -86,7 +96,7 @@ def _pack_rgba(rgba):
8696
Returns
8797
-------
8898
89-
out : ndarray, shape (n, )
99+
out : ndarray, shape (n, 1)
90100
Annotation values for each colour.
91101
"""
92102
bitshifts = 2 ** np.array([[0], [8], [16], [24]], dtype=rgba.dtype)
@@ -316,7 +326,20 @@ def write_morph_data(file_like, values, fnum=0):
316326

317327

318328
def read_annot(filepath, orig_ids=False):
319-
"""Read in a Freesurfer annotation from a .annot file.
329+
"""Read in a Freesurfer annotation from a `.annot` file.
330+
331+
An `.annot` file contains a sequence of vertices with a label (also known
332+
as an "annotation value") associated with each vertex, and then a sequence
333+
of colours corresponding to each label.
334+
335+
The colour table itself may be stored in either an "old-style" format, or
336+
a "new-style" format - the :func:`_read_annot_ctab_old_format` and
337+
:func:`_read_annot_ctab_new_format` functions are respectively used to
338+
read in the colour table.
339+
340+
See:
341+
https://surfer.nmr.mgh.harvard.edu/fswiki/LabelsClutsAnnotationFiles#Annotation
342+
https://github.com/freesurfer/freesurfer/blob/dev/matlab/read_annotation.m
320343
321344
Parameters
322345
----------
@@ -336,11 +359,10 @@ def read_annot(filepath, orig_ids=False):
336359
RGBA + label id colortable array.
337360
names : list of str
338361
The names of the labels. The length of the list is n_labels.
362+
339363
"""
340364
with open(filepath, "rb") as fobj:
341-
# all data (apart from strings) in an .annot file is stored as
342-
# big-endian int32
343-
dt = ">i4"
365+
dt = _ANNOT_DT
344366

345367
# number of vertices
346368
vnum = np.fromfile(fobj, dt, 1)[0]
@@ -355,50 +377,16 @@ def read_annot(filepath, orig_ids=False):
355377
raise Exception('Color table not found in annotation file')
356378

357379
# in old-format files, the next field will contain the number of
358-
# entries in the colour table. In new-format files, this will be
380+
# entries in the colour table. In new-format files, this must be
359381
# equal to -2
360382
n_entries = np.fromfile(fobj, dt, 1)[0]
361383

362384
# We've got an old-format .annot file.
363385
if n_entries > 0:
364-
365-
# orig_tab string length + string
366-
length = np.fromfile(fobj, dt, 1)[0]
367-
orig_tab = np.fromfile(fobj, '>c', length)
368-
orig_tab = orig_tab[:-1]
369-
names = list()
370-
ctab = np.zeros((n_entries, 5), dt)
371-
for i in xrange(n_entries):
372-
# structure name length + string
373-
name_length = np.fromfile(fobj, dt, 1)[0]
374-
name = np.fromfile(fobj, "|S%d" % name_length, 1)[0]
375-
names.append(name)
376-
# read RGBA for this entry
377-
ctab[i, :4] = np.fromfile(fobj, dt, 4)
386+
ctab, names = _read_annot_ctab_old_format(fobj, n_entries)
378387
# We've got a new-format .annot file
379388
else:
380-
# file version number
381-
ctab_version = -n_entries
382-
if ctab_version != 2:
383-
raise Exception('Color table version not supported')
384-
# maximum LUT index present in the file
385-
max_index = np.fromfile(fobj, dt, 1)[0]
386-
ctab = np.zeros((max_index, 5), dt)
387-
# orig_tab string length + string
388-
length = np.fromfile(fobj, dt, 1)[0]
389-
np.fromfile(fobj, "|S%d" % length, 1)[0] # Orig table path
390-
# number of LUT entries present in the file
391-
entries_to_read = np.fromfile(fobj, dt, 1)[0]
392-
names = list()
393-
for _ in xrange(entries_to_read):
394-
# index of this entry
395-
idx = np.fromfile(fobj, dt, 1)[0]
396-
# structure name length + string
397-
name_length = np.fromfile(fobj, dt, 1)[0]
398-
name = np.fromfile(fobj, "|S%d" % name_length, 1)[0]
399-
names.append(name)
400-
# RGBA
401-
ctab[idx, :4] = np.fromfile(fobj, dt, 4)
389+
ctab, names = _read_annot_ctab_new_format(fobj, -n_entries)
402390

403391
# generate annotation values for each LUT entry
404392
ctab[:, [4]] = _pack_rgba(ctab[:, :4])
@@ -414,11 +402,101 @@ def read_annot(filepath, orig_ids=False):
414402
return labels, ctab, names
415403

416404

405+
def _read_annot_ctab_old_format(fobj, n_entries):
406+
"""Read in an old-style Freesurfer colour table from `fobj`.
407+
408+
This function is used by :func:`read_annot`.
409+
410+
Parameters
411+
----------
412+
413+
fobj : file-like
414+
Open file handle to a Freesurfer `.annot` file, with seek point
415+
at the beginning of the colour table data.
416+
n_entries : int
417+
Number of entries in the colour table.
418+
419+
Returns
420+
-------
421+
422+
ctab : ndarray, shape (n_entries, 5)
423+
RGBA colortable array - the last column contains all zeros.
424+
names : list of str
425+
The names of the labels. The length of the list is n_entries.
426+
"""
427+
dt = _ANNOT_DT
428+
429+
# orig_tab string length + string
430+
length = np.fromfile(fobj, dt, 1)[0]
431+
orig_tab = np.fromfile(fobj, '>c', length)
432+
orig_tab = orig_tab[:-1]
433+
names = list()
434+
ctab = np.zeros((n_entries, 5), dt)
435+
for i in xrange(n_entries):
436+
# structure name length + string
437+
name_length = np.fromfile(fobj, dt, 1)[0]
438+
name = np.fromfile(fobj, "|S%d" % name_length, 1)[0]
439+
names.append(name)
440+
# read RGBA for this entry
441+
ctab[i, :4] = np.fromfile(fobj, dt, 4)
442+
443+
return ctab, names
444+
445+
446+
def _read_annot_ctab_new_format(fobj, ctab_version):
447+
"""Read in a new-style Freesurfer colour table from `fobj`.
448+
449+
This function is used by :func:`read_annot`.
450+
451+
Parameters
452+
----------
453+
454+
fobj : file-like
455+
Open file handle to a Freesurfer `.annot` file, with seek point
456+
at the beginning of the colour table data.
457+
ctab_version : int
458+
Colour table format version - must be equal to 2
459+
460+
Returns
461+
-------
462+
463+
ctab : ndarray, shape (n_labels, 5)
464+
RGBA colortable array - the last column contains all zeros.
465+
names : list of str
466+
The names of the labels. The length of the list is n_labels.
467+
"""
468+
dt = _ANNOT_DT
469+
# This code works with a file version == 2, nothing else
470+
if ctab_version != 2:
471+
raise Exception('Unrecognised .annot file version (%i)', ctab_version)
472+
# maximum LUT index present in the file
473+
max_index = np.fromfile(fobj, dt, 1)[0]
474+
ctab = np.zeros((max_index, 5), dt)
475+
# orig_tab string length + string
476+
length = np.fromfile(fobj, dt, 1)[0]
477+
np.fromfile(fobj, "|S%d" % length, 1)[0] # Orig table path
478+
# number of LUT entries present in the file
479+
entries_to_read = np.fromfile(fobj, dt, 1)[0]
480+
names = list()
481+
for _ in xrange(entries_to_read):
482+
# index of this entry
483+
idx = np.fromfile(fobj, dt, 1)[0]
484+
# structure name length + string
485+
name_length = np.fromfile(fobj, dt, 1)[0]
486+
name = np.fromfile(fobj, "|S%d" % name_length, 1)[0]
487+
names.append(name)
488+
# RGBA
489+
ctab[idx, :4] = np.fromfile(fobj, dt, 4)
490+
491+
return ctab, names
492+
493+
417494
def write_annot(filepath, labels, ctab, names, fill_ctab=True):
418495
"""Write out a Freesurfer annotation file.
419496
420497
See:
421498
https://surfer.nmr.mgh.harvard.edu/fswiki/LabelsClutsAnnotationFiles#Annotation
499+
https://github.com/freesurfer/freesurfer/blob/dev/matlab/write_annotation.m
422500
423501
Parameters
424502
----------
@@ -430,14 +508,14 @@ def write_annot(filepath, labels, ctab, names, fill_ctab=True):
430508
RGBA + label id colortable array.
431509
names : list of str
432510
The names of the labels. The length of the list is n_labels.
433-
fill_ctab : bool
511+
fill_ctab : {True, False} optional
434512
If True, the annotation values for each vertex are automatically
435513
generated. In this case, the provided `ctab` may have shape
436514
(n_labels, 4) or (n_labels, 5) - if the latter, the final column is
437515
ignored.
438516
"""
439517
with open(filepath, "wb") as fobj:
440-
dt = ">i4"
518+
dt = _ANNOT_DT
441519
vnum = len(labels)
442520

443521
def write(num, dtype=dt):

0 commit comments

Comments
 (0)