Skip to content

Commit 6321479

Browse files
committed
RF - moved extensions into header; fixed __eq__ bug with tests
1 parent 1f7c121 commit 6321479

File tree

2 files changed

+115
-57
lines changed

2 files changed

+115
-57
lines changed

nibabel/nifti1.py

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,8 @@ def __repr__(self):
366366
return s
367367

368368
def __eq__(self, other):
369+
if len(self) != len(other):
370+
return False
369371
for i, e in enumerate(self):
370372
if not e == other[i]:
371373
return False
@@ -389,8 +391,9 @@ def write_to(self, fileobj, byteswap):
389391
'''
390392
# not extensions -> nothing to do
391393
if not len(self):
394+
# no extensions: be nice and write appropriate flag
395+
fileobj.write(np.array((0, 0, 0, 0), dtype=np.int8).tostring())
392396
return
393-
394397
# since we have extensions write the appropriate flag
395398
fileobj.write(np.array((1, 0, 0, 0), dtype=np.int8).tostring())
396399
# and now each extension
@@ -428,12 +431,10 @@ def from_fileobj(klass, fileobj, size, byteswap):
428431
extension_status = np.fromstring(extension_status, dtype=np.int8)
429432
if byteswap:
430433
extension_status = extension_status.byteswap()
431-
432434
# NIfTI1 says: if first element is non-zero there are extensions present
433435
# if not there is nothing left to do
434436
if not extension_status[0]:
435437
return extensions
436-
437438
# note that we read the extension flag
438439
if not size < 0:
439440
size = size - 4
@@ -481,7 +482,11 @@ def from_fileobj(klass, fileobj, size, byteswap):
481482

482483

483484
class Nifti1Header(SpmAnalyzeHeader):
484-
''' Class for NIFTI1 header '''
485+
''' Class for NIFTI1 header
486+
487+
The NIFTI1 header has many more coded fields than the simpler Analyze
488+
variants. Analyze headers also have extensions
489+
'''
485490
# Copies of module level definitions
486491
_dtype = header_dtype
487492
_data_type_codes = data_type_codes
@@ -497,6 +502,60 @@ class Nifti1Header(SpmAnalyzeHeader):
497502
has_data_slope = True
498503
has_data_intercept = True
499504

505+
# Extension class; should implement __call__ for contruction, and
506+
# ``from_fileobj`` for reading from file
507+
exts_klass = Nifti1Extensions
508+
509+
def __init__(self,
510+
binaryblock=None,
511+
endianness=None,
512+
check=True,
513+
extensions=()):
514+
''' Initialize header from binary data block and extensions
515+
'''
516+
super(Nifti1Header, self).__init__(binaryblock,
517+
endianness,
518+
check)
519+
self.extensions = self.exts_klass(extensions)
520+
521+
def copy(self):
522+
''' Return copy of header
523+
524+
Take extensions as well as header
525+
'''
526+
return self.__class__(
527+
self.binaryblock,
528+
self.endianness,
529+
False,
530+
self.extensions)
531+
532+
@classmethod
533+
def from_fileobj(klass, fileobj, endianness=None, check=True):
534+
raw_str = fileobj.read(klass._dtype.itemsize)
535+
hdr = klass(raw_str, endianness, check)
536+
hdr_len = hdr._header_len()
537+
if hdr_len == -1:
538+
extsize = -1
539+
else:
540+
extsize = hdr_len - fileobj.tell()
541+
byteswap = endian_codes['native'] != hdr.endianness
542+
hdr.extensions = klass.exts_klass.from_fileobj(fileobj, extsize, byteswap)
543+
return hdr
544+
545+
def write_to(self, fileobj):
546+
super(Nifti1Header, self).write_to(fileobj)
547+
byteswap = endian_codes['native'] != self.endianness
548+
self.extensions.write_to(fileobj, byteswap)
549+
550+
def _header_len(self):
551+
''' Return header length in bytes or -1 for unknown
552+
553+
This will be -1 for headers that are their own files, as in the .hdr
554+
file of a nifti pair, or the same as the start of the data (vox_offset)
555+
in single file niftis
556+
'''
557+
return self._header_data['vox_offset']
558+
500559
def get_best_affine(self):
501560
''' Select best of available transforms '''
502561
hdr = self._header_data
@@ -1278,6 +1337,14 @@ def _set_format_specifics(self):
12781337
''' Utility routine to set format specific header stuff '''
12791338
self._header_data['magic'] = 'ni1'
12801339

1340+
def _header_len(self):
1341+
''' Return header length in bytes or -1 for unknown
1342+
1343+
This will be -1 for headers that are their own files, as in the .hdr
1344+
file of a nifti pair, or the same as the start of the data (vox_offset)
1345+
in single file niftis
1346+
'''
1347+
return -1
12811348

12821349
class Nifti1Pair(analyze.AnalyzeImage):
12831350
header_class = Nifti1PairHeader
@@ -1287,20 +1354,6 @@ def from_file_map(klass, file_map):
12871354
hdrf, imgf = klass._get_open_files(file_map, 'rb')
12881355
header = klass.header_class.from_fileobj(hdrf)
12891356
extra = None
1290-
# handle extensions
1291-
# assume the fileptr is just after header (magic field)
1292-
# determine how much to read when parsing the extensions
1293-
if header['vox_offset'] == 0:
1294-
# read till the end of the header
1295-
extsize = -1
1296-
else:
1297-
extsize = header['vox_offset'] - hdrf.tell()
1298-
extensions = Nifti1Extensions.from_fileobj(
1299-
hdrf, extsize,
1300-
endian_codes['native'] != header.endianness)
1301-
# XXX maybe always do that?
1302-
if len(extensions):
1303-
extra = {'extensions': extensions}
13041357
affine = header.get_best_affine()
13051358
hdr_copy = header.copy()
13061359
data = klass.ImageArrayProxy(imgf, hdr_copy)
@@ -1315,13 +1368,6 @@ def _write_header(self, header_file, header, slope, inter):
13151368
header,
13161369
slope,
13171370
inter)
1318-
if not self.extra.has_key('extensions'):
1319-
# no extensions: be nice and write appropriate flag
1320-
header_file.write(np.array((0, 0, 0, 0), dtype=np.int8).tostring())
1321-
else:
1322-
self.extra['extensions'].write_to(
1323-
header_file,
1324-
endian_codes['native'] != header.endianness)
13251371

13261372
def update_header(self):
13271373
''' Harmonize header with image data and affine

nibabel/tests/test_nifti1.py

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
from ..spatialimages import HeaderDataError
1919
from .. import nifti1 as nifti1
2020
from ..nifti1 import (load, Nifti1Header, Nifti1Image,
21-
Nifti1Pair, Nifti1Extension, data_type_codes,
22-
extension_codes, slice_order_codes)
21+
Nifti1Pair, Nifti1Extension, Nifti1Extensions,
22+
data_type_codes, extension_codes, slice_order_codes)
2323

2424
from numpy.testing import assert_array_equal, assert_array_almost_equal
2525
from nose.tools import (assert_true, assert_false, assert_equal,
@@ -387,73 +387,85 @@ def test_extension_codes():
387387
ext = Nifti1Extension(k, 'somevalue')
388388

389389

390-
@parametric
390+
def test_extension_list():
391+
ext_c0 = Nifti1Extensions()
392+
ext_c1 = Nifti1Extensions()
393+
assert_equal(ext_c0, ext_c1)
394+
ext = Nifti1Extension('comment', '123')
395+
ext_c1.append(ext)
396+
assert_false(ext_c0 == ext_c1)
397+
ext_c0.append(ext)
398+
assert_true(ext_c0 == ext_c1)
399+
400+
391401
def test_nifti_extensions():
392402
nim = load(image_file)
393403
# basic checks of the available extensions
394-
ext = nim.extra['extensions']
395-
yield assert_true(len(ext) == 2)
396-
yield assert_true(ext.count('comment') == 2)
397-
yield assert_true(ext.count('afni') == 0)
398-
yield assert_true(ext.get_codes() == [6, 6])
399-
yield assert_true((ext.get_sizeondisk() - 4) % 16 == 0)
404+
hdr = nim.get_header()
405+
exts_container = hdr.extensions
406+
assert_true(len(exts_container) == 2)
407+
assert_true(exts_container.count('comment') == 2)
408+
assert_true(exts_container.count('afni') == 0)
409+
assert_true(exts_container.get_codes() == [6, 6])
410+
assert_true((exts_container.get_sizeondisk() - 4) % 16 == 0)
400411
# first extension should be short one
401-
yield assert_true(ext[0].get_content() == 'extcomment1')
412+
assert_true(exts_container[0].get_content() == 'extcomment1')
402413
# add one
403414
afniext = Nifti1Extension('afni', '<xml></xml>')
404-
ext.append(afniext)
405-
yield assert_true(ext.get_codes() == [6, 6, 4])
406-
yield assert_true(ext.count('comment') == 2)
407-
yield assert_true(ext.count('afni') == 1)
408-
yield assert_true((ext.get_sizeondisk() - 4) % 16 == 0)
415+
exts_container.append(afniext)
416+
assert_true(exts_container.get_codes() == [6, 6, 4])
417+
assert_true(exts_container.count('comment') == 2)
418+
assert_true(exts_container.count('afni') == 1)
419+
assert_true((exts_container.get_sizeondisk() - 4) % 16 == 0)
409420
# delete one
410-
del ext[1]
411-
yield assert_true(ext.get_codes() == [6, 4])
412-
yield assert_true(ext.count('comment') == 1)
413-
yield assert_true(ext.count('afni') == 1)
421+
del exts_container[1]
422+
assert_true(exts_container.get_codes() == [6, 4])
423+
assert_true(exts_container.count('comment') == 1)
424+
assert_true(exts_container.count('afni') == 1)
414425

415426

416-
@parametric
417427
def test_loadsave_cycle():
418428
nim = load(image_file)
419429
# ensure we have extensions
420-
yield assert_true(nim.extra.has_key('extensions'))
421-
yield assert_true(len(nim.extra['extensions']))
430+
hdr = nim.get_header()
431+
exts_container = hdr.extensions
432+
assert_true(len(exts_container) > 0)
422433
# write into the air ;-)
423434
stio = StringIO()
424435
nim.file_map['image'].fileobj = stio
425436
nim.to_file_map()
426437
stio.seek(0)
427438
# reload
428439
lnim = Nifti1Image.from_file_map(nim.file_map)
429-
yield assert_true(lnim.extra.has_key('extensions'))
430-
yield assert_equal(nim.extra['extensions'],
431-
lnim.extra['extensions'])
440+
hdr = lnim.get_header()
441+
lexts_container = hdr.extensions
442+
assert_equal(exts_container,
443+
lexts_container)
432444
# build int16 image
433445
data = np.ones((2,3,4,5), dtype='int16')
434446
img = Nifti1Image(data, np.eye(4))
435447
hdr = img.get_header()
436-
yield assert_equal(hdr.get_data_dtype(), np.int16)
448+
assert_equal(hdr.get_data_dtype(), np.int16)
437449
# default should have no scaling
438-
yield assert_equal(hdr.get_slope_inter(), (1.0, 0.0))
450+
assert_equal(hdr.get_slope_inter(), (1.0, 0.0))
439451
# set scaling
440452
hdr.set_slope_inter(2, 8)
441-
yield assert_equal(hdr.get_slope_inter(), (2, 8))
453+
assert_equal(hdr.get_slope_inter(), (2, 8))
442454
# now build new image with updated header
443455
wnim = Nifti1Image(data, np.eye(4), header=hdr)
444-
yield assert_equal(wnim.get_data_dtype(), np.int16)
445-
yield assert_equal(wnim.get_header().get_slope_inter(), (2, 8))
456+
assert_equal(wnim.get_data_dtype(), np.int16)
457+
assert_equal(wnim.get_header().get_slope_inter(), (2, 8))
446458
# write into the air again ;-)
447459
stio = StringIO()
448460
wnim.file_map['image'].fileobj = stio
449461
wnim.to_file_map()
450462
stio.seek(0)
451463
lnim = Nifti1Image.from_file_map(wnim.file_map)
452-
yield assert_equal(lnim.get_data_dtype(), np.int16)
464+
assert_equal(lnim.get_data_dtype(), np.int16)
453465
# the test below does not pass, because the slope and inter are
454466
# always reset from the data, by the image write
455467
raise SkipTest
456-
yield assert_equal(lnim.get_header().get_slope_inter(), (2, 8))
468+
assert_equal(lnim.get_header().get_slope_inter(), (2, 8))
457469

458470

459471
@parametric

0 commit comments

Comments
 (0)