Skip to content

Commit a7d25be

Browse files
Merge pull request #528 from pauldmccarthy/unknown_intent_bug
MRG: Fix Nifti1Header.get/set_intent for unknown intent codes The `nibabel.nifti1.Nifti1Header.get_intent` and `set_intent` methods used to fail for intent codes which are not listed in `nibabel.nifti1.intent_codes`. * Add some known intent codes from FSL; * Add flag to allow unknown intent codes.
2 parents 3d59146 + c45d213 commit a7d25be

File tree

2 files changed

+66
-9
lines changed

2 files changed

+66
-9
lines changed

nibabel/nifti1.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from __future__ import division, print_function
1414
import warnings
1515
from io import BytesIO
16+
from six import string_types
1617

1718
import numpy as np
1819
import numpy.linalg as npl
@@ -232,6 +233,19 @@
232233
(2003, 'rgb vector', (), "NIFTI_INTENT_RGB_VECTOR"),
233234
(2004, 'rgba vector', (), "NIFTI_INTENT_RGBA_VECTOR"),
234235
(2005, 'shape', (), "NIFTI_INTENT_SHAPE"),
236+
# FSL-specific intent codes - codes used by FNIRT
237+
# ($FSLDIR/warpfns/fnirt_file_reader.h:104)
238+
(2006, 'fnirt disp field', (), 'FSL_FNIRT_DISPLACEMENT_FIELD'),
239+
(2007, 'fnirt cubic spline coef', (), 'FSL_CUBIC_SPLINE_COEFFICIENTS'),
240+
(2008, 'fnirt dct coef', (), 'FSL_DCT_COEFFICIENTS'),
241+
(2009, 'fnirt quad spline coef', (), 'FSL_QUADRATIC_SPLINE_COEFFICIENTS'),
242+
# FSL-specific intent codes - codes used by TOPUP
243+
# ($FSLDIR/topup/topup_file_io.h:104)
244+
(2016, 'topup cubic spline coef ', (),
245+
'FSL_TOPUP_CUBIC_SPLINE_COEFFICIENTS'),
246+
(2017, 'topup quad spline coef', (),
247+
'FSL_TOPUP_QUADRATIC_SPLINE_COEFFICIENTS'),
248+
(2018, 'topup field', (), 'FSL_TOPUP_FIELD'),
235249
), fields=('code', 'label', 'parameters', 'niistring'))
236250

237251

@@ -1301,18 +1315,22 @@ def get_intent(self, code_repr='label'):
13011315
hdr = self._structarr
13021316
recoder = self._field_recoders['intent_code']
13031317
code = int(hdr['intent_code'])
1318+
known_intent = code in recoder
13041319
if code_repr == 'code':
13051320
label = code
13061321
elif code_repr == 'label':
1307-
label = recoder.label[code]
1322+
if known_intent:
1323+
label = recoder.label[code]
1324+
else:
1325+
label = 'unknown code ' + str(code)
13081326
else:
13091327
raise TypeError('repr can be "label" or "code"')
1310-
n_params = len(recoder.parameters[code])
1328+
n_params = len(recoder.parameters[code]) if known_intent else 0
13111329
params = (float(hdr['intent_p%d' % (i + 1)]) for i in range(n_params))
13121330
name = asstr(np.asscalar(hdr['intent_name']))
13131331
return label, tuple(params), name
13141332

1315-
def set_intent(self, code, params=(), name=''):
1333+
def set_intent(self, code, params=(), name='', allow_unknown=False):
13161334
''' Set the intent code, parameters and name
13171335
13181336
If parameters are not specified, assumed to be all zero. Each
@@ -1331,6 +1349,10 @@ def set_intent(self, code, params=(), name=''):
13311349
defaults to (). Unspecified parameters are set to 0.0
13321350
name : string
13331351
intent name (description). Defaults to ''
1352+
allow_unknown : {False, True}, optional
1353+
Allow unknown integer intent codes. If False (the default),
1354+
a KeyError is raised on attempts to set the intent
1355+
to an unknown code.
13341356
13351357
Returns
13361358
-------
@@ -1339,7 +1361,7 @@ def set_intent(self, code, params=(), name=''):
13391361
Examples
13401362
--------
13411363
>>> hdr = Nifti1Header()
1342-
>>> hdr.set_intent(0) # unknown code
1364+
>>> hdr.set_intent(0) # no intent
13431365
>>> hdr.set_intent('z score')
13441366
>>> hdr.get_intent()
13451367
('z score', (), '')
@@ -1354,19 +1376,32 @@ def set_intent(self, code, params=(), name=''):
13541376
>>> hdr.set_intent('f test')
13551377
>>> hdr.get_intent()
13561378
('f test', (0.0, 0.0), '')
1379+
>>> hdr.set_intent(9999, allow_unknown=True) # unknown code
1380+
>>> hdr.get_intent()
1381+
('unknown code 9999', (), '')
13571382
'''
13581383
hdr = self._structarr
1359-
icode = intent_codes.code[code]
1360-
p_descr = intent_codes.parameters[code]
1384+
known_intent = code in intent_codes
1385+
if not known_intent:
1386+
# We can set intent via an unknown integer code, but can't via an
1387+
# unknown string label
1388+
if not allow_unknown or isinstance(code, string_types):
1389+
raise KeyError('Unknown intent code: ' + str(code))
1390+
if known_intent:
1391+
icode = intent_codes.code[code]
1392+
p_descr = intent_codes.parameters[code]
1393+
else:
1394+
icode = code
1395+
p_descr = ('p1', 'p2', 'p3')
13611396
if len(params) and len(params) != len(p_descr):
13621397
raise HeaderDataError('Need params of form %s, or empty'
13631398
% (p_descr,))
1399+
hdr['intent_code'] = icode
1400+
hdr['intent_name'] = name
13641401
all_params = [0] * 3
13651402
all_params[:len(params)] = params[:]
13661403
for i, param in enumerate(all_params):
13671404
hdr['intent_p%d' % (i + 1)] = param
1368-
hdr['intent_code'] = icode
1369-
hdr['intent_name'] = name
13701405

13711406
def get_slice_duration(self):
13721407
''' Get slice duration

nibabel/tests/test_nifti1.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,12 @@ def test_intents(self):
561561
ehdr.set_intent('t test', (10,), name='some score')
562562
assert_equal(ehdr.get_intent(),
563563
('t test', (10.0,), 'some score'))
564-
# invalid intent name
564+
# unknown intent name or code - unknown name will fail even when
565+
# allow_unknown=True
565566
assert_raises(KeyError, ehdr.set_intent, 'no intention')
567+
assert_raises(KeyError, ehdr.set_intent, 'no intention',
568+
allow_unknown=True)
569+
assert_raises(KeyError, ehdr.set_intent, 32767)
566570
# too many parameters
567571
assert_raises(HeaderDataError, ehdr.set_intent, 't test', (10, 10))
568572
# too few parameters
@@ -574,6 +578,24 @@ def test_intents(self):
574578
assert_equal(ehdr['intent_name'], b'')
575579
ehdr.set_intent('t test', (10,))
576580
assert_equal((ehdr['intent_p2'], ehdr['intent_p3']), (0, 0))
581+
# store intent that is not in nifti1.intent_codes recoder
582+
ehdr.set_intent(9999, allow_unknown=True)
583+
assert_equal(ehdr.get_intent(), ('unknown code 9999', (), ''))
584+
assert_equal(ehdr.get_intent('code'), (9999, (), ''))
585+
ehdr.set_intent(9999, name='custom intent', allow_unknown=True)
586+
assert_equal(ehdr.get_intent(),
587+
('unknown code 9999', (), 'custom intent'))
588+
assert_equal(ehdr.get_intent('code'), (9999, (), 'custom intent'))
589+
# store unknown intent with parameters. set_intent will set the
590+
# parameters, but get_intent won't return them
591+
ehdr.set_intent(code=9999, params=(1, 2, 3), allow_unknown=True)
592+
assert_equal(ehdr.get_intent(), ('unknown code 9999', (), ''))
593+
assert_equal(ehdr.get_intent('code'), (9999, (), ''))
594+
# unknown intent requires either zero, or three, parameters
595+
assert_raises(HeaderDataError, ehdr.set_intent, 999, (1,),
596+
allow_unknown=True)
597+
assert_raises(HeaderDataError, ehdr.set_intent, 999, (1,2),
598+
allow_unknown=True)
577599

578600
def test_set_slice_times(self):
579601
hdr = self.header_class()

0 commit comments

Comments
 (0)