@@ -366,6 +366,8 @@ def __repr__(self):
366
366
return s
367
367
368
368
def __eq__ (self , other ):
369
+ if len (self ) != len (other ):
370
+ return False
369
371
for i , e in enumerate (self ):
370
372
if not e == other [i ]:
371
373
return False
@@ -387,13 +389,6 @@ def write_to(self, fileobj, byteswap):
387
389
-------
388
390
None
389
391
'''
390
- # not extensions -> nothing to do
391
- if not len (self ):
392
- return
393
-
394
- # since we have extensions write the appropriate flag
395
- fileobj .write (np .array ((1 , 0 , 0 , 0 ), dtype = np .int8 ).tostring ())
396
- # and now each extension
397
392
for e in self :
398
393
e .write_to (fileobj , byteswap )
399
394
@@ -404,43 +399,24 @@ def from_fileobj(klass, fileobj, size, byteswap):
404
399
Parameters
405
400
----------
406
401
fileobj : file-like object
407
- It is assumed to be positions right after the NIfTI magic field.
402
+ We begin reading the extensions at the current file position
408
403
size : int
409
- Number of bytes to read. If negative, fileobj will be read till its
410
- end.
404
+ Number of bytes to read. If negative, fileobj will be read till its
405
+ end.
411
406
byteswap : boolean
412
- Flag if byteswapping the read data is required.
407
+ Flag if byteswapping the read data is required.
413
408
414
409
Returns
415
410
-------
416
- An extension list. This list might be empty in case not extensions
417
- were present in fileobj.
411
+ An extension list. This list might be empty in case not extensions
412
+ were present in fileobj.
418
413
'''
419
414
# make empty extension list
420
415
extensions = klass ()
421
- # assume the fileptr is just after header (magic field)
422
- # try reading the next 4 bytes after the initial header
423
- extension_status = fileobj .read (4 )
424
- if not len (extension_status ):
425
- # if there is nothing the NIfTI standard requires to assume zeros
426
- extension_status = np .zeros ((4 ,), dtype = np .int8 )
427
- else :
428
- extension_status = np .fromstring (extension_status , dtype = np .int8 )
429
- if byteswap :
430
- extension_status = extension_status .byteswap ()
431
-
432
- # NIfTI1 says: if first element is non-zero there are extensions present
433
- # if not there is nothing left to do
434
- if not extension_status [0 ]:
435
- return extensions
436
-
437
- # note that we read the extension flag
438
- if not size < 0 :
439
- size = size - 4
416
+ # assume the file pointer is at the beginning of any extensions.
440
417
# read until the whole header is parsed (each extension is a multiple
441
418
# of 16 bytes) or in case of a separate header file till the end
442
419
# (break inside the body)
443
- # XXX not sure if the separate header behavior is sane
444
420
while size >= 16 or size < 0 :
445
421
# the next 8 bytes should have esize and ecode
446
422
ext_def = fileobj .read (8 )
@@ -481,7 +457,18 @@ def from_fileobj(klass, fileobj, size, byteswap):
481
457
482
458
483
459
class Nifti1Header (SpmAnalyzeHeader ):
484
- ''' Class for NIFTI1 header '''
460
+ ''' Class for NIFTI1 header
461
+
462
+ The NIFTI1 header has many more coded fields than the simpler Analyze
463
+ variants. Nifti1 headers also have extensions.
464
+
465
+ Nifti allows the header to be a separate file, as part of a nifti image /
466
+ header pair, or to precede the data in a single file. The object needs to
467
+ know which type it is, in order to manage the voxel offset pointing to the
468
+ data, extension reading, and writing the correct magic string.
469
+
470
+ This class handles the header-preceding-data case.
471
+ '''
485
472
# Copies of module level definitions
486
473
_dtype = header_dtype
487
474
_data_type_codes = data_type_codes
@@ -497,6 +484,68 @@ class Nifti1Header(SpmAnalyzeHeader):
497
484
has_data_slope = True
498
485
has_data_intercept = True
499
486
487
+ # Extension class; should implement __call__ for contruction, and
488
+ # ``from_fileobj`` for reading from file
489
+ exts_klass = Nifti1Extensions
490
+
491
+ # Signal whether this is single (header + data) file
492
+ is_single = True
493
+
494
+ def __init__ (self ,
495
+ binaryblock = None ,
496
+ endianness = None ,
497
+ check = True ,
498
+ extensions = ()):
499
+ ''' Initialize header from binary data block and extensions
500
+ '''
501
+ super (Nifti1Header , self ).__init__ (binaryblock ,
502
+ endianness ,
503
+ check )
504
+ self .extensions = self .exts_klass (extensions )
505
+
506
+ def copy (self ):
507
+ ''' Return copy of header
508
+
509
+ Take reference to extensions as well as copy of header contents
510
+ '''
511
+ return self .__class__ (
512
+ self .binaryblock ,
513
+ self .endianness ,
514
+ False ,
515
+ self .extensions )
516
+
517
+ @classmethod
518
+ def from_fileobj (klass , fileobj , endianness = None , check = True ):
519
+ raw_str = fileobj .read (klass ._dtype .itemsize )
520
+ hdr = klass (raw_str , endianness , check )
521
+ # Read next 4 bytes to see if we have extensions. The nifti standard
522
+ # has this as a 4 byte string; if the first value is not zero, then we
523
+ # have extensions.
524
+ extension_status = fileobj .read (4 )
525
+ if len (extension_status ) < 4 or extension_status [0 ] == '\x00 ' :
526
+ return hdr
527
+ # If this is a detached header file read to end
528
+ if not klass .is_single :
529
+ extsize = - 1
530
+ else : # otherwise read until the beginning of the data
531
+ extsize = hdr ._header_data ['vox_offset' ] - fileobj .tell ()
532
+ byteswap = endian_codes ['native' ] != hdr .endianness
533
+ hdr .extensions = klass .exts_klass .from_fileobj (fileobj , extsize , byteswap )
534
+ return hdr
535
+
536
+ def write_to (self , fileobj ):
537
+ super (Nifti1Header , self ).write_to (fileobj )
538
+ n_exts = len (self .extensions )
539
+ if n_exts == 0 :
540
+ # If single file, write required 0 stream to signal no extensions
541
+ if self .is_single :
542
+ fileobj .write ('\x00 ' * 4 )
543
+ return
544
+ # Signal there are extensions that follow
545
+ fileobj .write ('\x01 \x00 \x00 \x00 ' )
546
+ byteswap = endian_codes ['native' ] != self .endianness
547
+ self .extensions .write_to (fileobj , byteswap )
548
+
500
549
def get_best_affine (self ):
501
550
''' Select best of available transforms '''
502
551
hdr = self ._header_data
@@ -510,8 +559,12 @@ def _empty_headerdata(self, endianness=None):
510
559
''' Create empty header binary block with given endianness '''
511
560
hdr_data = analyze .AnalyzeHeader ._empty_headerdata (self , endianness )
512
561
hdr_data ['scl_slope' ] = 1
513
- hdr_data ['magic' ] = 'n+1'
514
- hdr_data ['vox_offset' ] = 352
562
+ if self .is_single :
563
+ hdr_data ['magic' ] = 'n+1'
564
+ hdr_data ['vox_offset' ] = 352
565
+ else :
566
+ hdr_data ['magic' ] = 'ni1'
567
+ hdr_data ['vox_offset' ] = 0
515
568
return hdr_data
516
569
517
570
def get_qform_quaternion (self ):
@@ -1149,9 +1202,12 @@ def set_xyzt_units(self, xyz=None, t=None):
1149
1202
1150
1203
def _set_format_specifics (self ):
1151
1204
''' Utility routine to set format specific header stuff '''
1152
- self ._header_data ['magic' ] = 'n+1'
1153
- if self ._header_data ['vox_offset' ] < 352 :
1154
- self ._header_data ['vox_offset' ] = 352
1205
+ if self .is_single :
1206
+ self ._header_data ['magic' ] = 'n+1'
1207
+ if self ._header_data ['vox_offset' ] < 352 :
1208
+ self ._header_data ['vox_offset' ] = 352
1209
+ else :
1210
+ self ._header_data ['magic' ] = 'ni1'
1155
1211
1156
1212
''' Checks only below here '''
1157
1213
@@ -1266,17 +1322,8 @@ def _chk_xform_code(klass, code_type, hdr, fix):
1266
1322
1267
1323
class Nifti1PairHeader (Nifti1Header ):
1268
1324
''' Class for nifti1 pair header '''
1269
- def _empty_headerdata (self , endianness = None ):
1270
- ''' Create empty header binary block with given endianness '''
1271
- hdr_data = analyze .AnalyzeHeader ._empty_headerdata (self , endianness )
1272
- hdr_data ['scl_slope' ] = 1
1273
- hdr_data ['magic' ] = 'ni1'
1274
- hdr_data ['vox_offset' ] = 0
1275
- return hdr_data
1276
-
1277
- def _set_format_specifics (self ):
1278
- ''' Utility routine to set format specific header stuff '''
1279
- self ._header_data ['magic' ] = 'ni1'
1325
+ # Signal whether this is single (header + data) file
1326
+ is_single = False
1280
1327
1281
1328
1282
1329
class Nifti1Pair (analyze .AnalyzeImage ):
@@ -1287,20 +1334,6 @@ def from_file_map(klass, file_map):
1287
1334
hdrf , imgf = klass ._get_open_files (file_map , 'rb' )
1288
1335
header = klass .header_class .from_fileobj (hdrf )
1289
1336
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 }
1304
1337
affine = header .get_best_affine ()
1305
1338
hdr_copy = header .copy ()
1306
1339
data = klass .ImageArrayProxy (imgf , hdr_copy )
@@ -1315,13 +1348,6 @@ def _write_header(self, header_file, header, slope, inter):
1315
1348
header ,
1316
1349
slope ,
1317
1350
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 )
1325
1351
1326
1352
def update_header (self ):
1327
1353
''' Harmonize header with image data and affine
0 commit comments