33from os .path import join as pjoin , isdir
44import getpass
55import time
6+ import struct
67import hashlib
78import warnings
89
1516
1617from .. import (read_geometry , read_morph_data , read_annot , read_label ,
1718 write_geometry , write_morph_data , write_annot )
19+ from ..io import _pack_rgba
1820
1921from ...tests .nibabel_data import get_nibabel_data , needs_nibabel_data
2022from ...fileslice import strided_scalar
@@ -169,7 +171,7 @@ def test_write_morph_data():
169171
170172@freesurfer_test
171173def test_annot ():
172- """Test IO of .annot"""
174+ """Test IO of .annot against freesurfer example data. """
173175 annots = ['aparc' , 'aparc.a2005s' ]
174176 for a in annots :
175177 annot_path = pjoin (data_path , "label" , "%s.%s.annot" % ("lh" , a ))
@@ -210,14 +212,12 @@ def test_annot():
210212
211213
212214def test_read_write_annot ():
213-
215+ """Test generating .annot fiole and reading it back."""
214216 # This annot file will store a LUT for a mesh made of 10 vertices, with
215217 # 3 colours in the LUT.
216218 nvertices = 10
217219 nlabels = 3
218-
219220 names = ['label {}' .format (l ) for l in range (1 , nlabels + 1 )]
220-
221221 # randomly generate a label for each vertex, making sure
222222 # that at least one of each label value is present. Label
223223 # values are in the range (0, nlabels-1) - they are used
@@ -226,24 +226,19 @@ def test_read_write_annot():
226226 list (np .random .randint (0 , nlabels , nvertices - nlabels ))
227227 labels = np .array (labels , dtype = np .int32 )
228228 np .random .shuffle (labels )
229-
230229 # Generate some random colours for the LUT
231230 rgbal = np .zeros ((nlabels , 5 ), dtype = np .int32 )
232231 rgbal [:, :4 ] = np .random .randint (0 , 255 , (nlabels , 4 ))
233-
234232 # But make sure we have at least one large alpha, to make sure that when
235233 # it is packed into a signed 32 bit int, it results in a negative value
236234 # for the annotation value.
237235 rgbal [0 , 3 ] = 255
238-
239236 # Generate the annotation values for each LUT entry
240237 rgbal [:, 4 ] = (rgbal [:, 0 ] +
241238 rgbal [:, 1 ] * (2 ** 8 ) +
242239 rgbal [:, 2 ] * (2 ** 16 ) +
243240 rgbal [:, 3 ] * (2 ** 24 ))
244-
245241 annot_path = 'c.annot'
246-
247242 with InTemporaryDirectory ():
248243 write_annot (annot_path , labels , rgbal , names , fill_ctab = False )
249244 labels2 , rgbal2 , names2 = read_annot (annot_path )
@@ -253,6 +248,7 @@ def test_read_write_annot():
253248
254249
255250def test_write_annot_fill_ctab ():
251+ """Test the `fill_ctab` parameter to :func:`.write_annot`. """
256252 nvertices = 10
257253 nlabels = 3
258254 names = ['label {}' .format (l ) for l in range (1 , nlabels + 1 )]
@@ -262,14 +258,12 @@ def test_write_annot_fill_ctab():
262258 np .random .shuffle (labels )
263259 rgba = np .array (np .random .randint (0 , 255 , (nlabels , 4 )), dtype = np .int32 )
264260 annot_path = 'c.annot'
265-
266261 with InTemporaryDirectory ():
267262 write_annot (annot_path , labels , rgba , names , fill_ctab = True )
268263 labels2 , rgbal2 , names2 = read_annot (annot_path )
269264 assert np .all (np .isclose (rgbal2 [:, :4 ], rgba ))
270265 assert np .all (np .isclose (labels2 , labels ))
271266 assert names2 == names
272-
273267 # make sure a warning is emitted if fill_ctab is False, and the
274268 # annotation values are wrong. Use orig_ids=True so we get those bad
275269 # values back.
@@ -285,7 +279,6 @@ def test_write_annot_fill_ctab():
285279 assert np .all (np .isclose (rgbal2 [:, :4 ], rgba ))
286280 assert np .all (np .isclose (labels2 , badannot [labels ].squeeze ()))
287281 assert names2 == names
288-
289282 # make sure a warning is *not* emitted if fill_ctab is False, but the
290283 # annotation values are correct.
291284 rgbal = np .hstack ((rgba , np .zeros ((nlabels , 1 ), dtype = np .int32 )))
@@ -304,6 +297,49 @@ def test_write_annot_fill_ctab():
304297 assert names2 == names
305298
306299
300+ def test_read_annot_old_format ():
301+ """Test reading an old-style .annot file."""
302+ def gen_old_annot_file (fpath , nverts , labels , rgba , names ):
303+ dt = '>i'
304+ vdata = np .zeros ((nverts , 2 ))
305+ vdata [:, 0 ] = np .arange (nverts )
306+ vdata [:, [1 ]] = _pack_rgba (rgba [labels , :])
307+ fbytes = b''
308+ # number of vertices
309+ fbytes += struct .pack (dt , nverts )
310+ # vertices + annotation values
311+ fbytes += vdata .astype (dt ).tobytes ()
312+ # is there a colour table?
313+ fbytes += struct .pack (dt , 1 )
314+ # number of entries in colour table
315+ fbytes += struct .pack (dt , rgba .shape [0 ])
316+ # length of orig_tab string
317+ fbytes += struct .pack (dt , 5 )
318+ fbytes += b'abcd\00 '
319+ for i in range (rgba .shape [0 ]):
320+ # length of entry name (+1 for terminating byte)
321+ fbytes += struct .pack (dt , len (names [i ]) + 1 )
322+ fbytes += names [i ].encode ('ascii' ) + b'\00 '
323+ fbytes += rgba [i , :].astype (dt ).tobytes ()
324+ with open (fpath , 'wb' ) as f :
325+ f .write (fbytes )
326+ with InTemporaryDirectory ():
327+ nverts = 10
328+ nlabels = 3
329+ names = ['Label {}' .format (l ) for l in range (nlabels )]
330+ labels = np .concatenate ((
331+ np .arange (nlabels ), np .random .randint (0 , 3 , nverts - nlabels )))
332+ np .random .shuffle (labels )
333+ rgba = np .random .randint (0 , 255 , (nlabels , 4 ))
334+ # write an old .annot file
335+ gen_old_annot_file ('blah.annot' , nverts , labels , rgba , names )
336+ # read it back
337+ rlabels , rrgba , rnames = read_annot ('blah.annot' )
338+ assert np .all (np .isclose (labels , rlabels ))
339+ assert np .all (np .isclose (rgba , rrgba [:, :4 ]))
340+ assert names == rnames
341+
342+
307343@freesurfer_test
308344def test_label ():
309345 """Test IO of .label"""
0 commit comments