12
12
from ..openers import Opener
13
13
14
14
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
+
15
23
def _fread3 (fobj ):
16
24
"""Read a 3-byte int from an open binary file object
17
25
@@ -74,8 +82,10 @@ def _read_volume_info(fobj):
74
82
75
83
76
84
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.
79
89
80
90
Parameters
81
91
----------
@@ -86,7 +96,7 @@ def _pack_rgba(rgba):
86
96
Returns
87
97
-------
88
98
89
- out : ndarray, shape (n, )
99
+ out : ndarray, shape (n, 1 )
90
100
Annotation values for each colour.
91
101
"""
92
102
bitshifts = 2 ** np .array ([[0 ], [8 ], [16 ], [24 ]], dtype = rgba .dtype )
@@ -316,7 +326,20 @@ def write_morph_data(file_like, values, fnum=0):
316
326
317
327
318
328
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
320
343
321
344
Parameters
322
345
----------
@@ -336,11 +359,10 @@ def read_annot(filepath, orig_ids=False):
336
359
RGBA + label id colortable array.
337
360
names : list of str
338
361
The names of the labels. The length of the list is n_labels.
362
+
339
363
"""
340
364
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
344
366
345
367
# number of vertices
346
368
vnum = np .fromfile (fobj , dt , 1 )[0 ]
@@ -355,50 +377,16 @@ def read_annot(filepath, orig_ids=False):
355
377
raise Exception ('Color table not found in annotation file' )
356
378
357
379
# 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
359
381
# equal to -2
360
382
n_entries = np .fromfile (fobj , dt , 1 )[0 ]
361
383
362
384
# We've got an old-format .annot file.
363
385
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 )
378
387
# We've got a new-format .annot file
379
388
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 )
402
390
403
391
# generate annotation values for each LUT entry
404
392
ctab [:, [4 ]] = _pack_rgba (ctab [:, :4 ])
@@ -414,11 +402,101 @@ def read_annot(filepath, orig_ids=False):
414
402
return labels , ctab , names
415
403
416
404
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
+
417
494
def write_annot (filepath , labels , ctab , names , fill_ctab = True ):
418
495
"""Write out a Freesurfer annotation file.
419
496
420
497
See:
421
498
https://surfer.nmr.mgh.harvard.edu/fswiki/LabelsClutsAnnotationFiles#Annotation
499
+ https://github.com/freesurfer/freesurfer/blob/dev/matlab/write_annotation.m
422
500
423
501
Parameters
424
502
----------
@@ -430,14 +508,14 @@ def write_annot(filepath, labels, ctab, names, fill_ctab=True):
430
508
RGBA + label id colortable array.
431
509
names : list of str
432
510
The names of the labels. The length of the list is n_labels.
433
- fill_ctab : bool
511
+ fill_ctab : {True, False} optional
434
512
If True, the annotation values for each vertex are automatically
435
513
generated. In this case, the provided `ctab` may have shape
436
514
(n_labels, 4) or (n_labels, 5) - if the latter, the final column is
437
515
ignored.
438
516
"""
439
517
with open (filepath , "wb" ) as fobj :
440
- dt = ">i4"
518
+ dt = _ANNOT_DT
441
519
vnum = len (labels )
442
520
443
521
def write (num , dtype = dt ):
0 commit comments