Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit71f6526

Browse files
committed
ENH: Add validation when saving CIFTI2 images
- Enabled by default, validation will parse the output filename for a valid CIFTI2 extension.- If found, the intent code of the image will be set. Also, the CIFTI2Header will be check for compliant index maps for the intent code
1 parentd0bbcc7 commit71f6526

File tree

2 files changed

+131
-38
lines changed

2 files changed

+131
-38
lines changed

‎nibabel/cifti2/cifti2.py‎

Lines changed: 79 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from ..dataobj_imagesimportDataobjImage
2525
from ..nifti2importNifti2Image,Nifti2Header
2626
from ..arrayproxyimportreshape_dataobj
27+
from ..volumeutilsimportRecoder
2728
fromwarningsimportwarn
2829

2930

@@ -90,20 +91,50 @@ class Cifti2HeaderError(Exception):
9091

9192
# "Standard CIFTI Mapping Combinations" within CIFTI-2 spec
9293
# https://www.nitrc.org/forum/attachment.php?attachid=341&group_id=454&forum_id=1955
93-
CIFTI_EXTENSIONS_TO_INTENTS= {
94-
'.dconn':'NIFTI_INTENT_CONNECTIVITY_DENSE',
95-
'.dtseries':'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES',
96-
'.pconn':'NIFTI_INTENT_CONNECTIVITY_PARCELLATED',
97-
'.ptseries':'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES',
98-
'.dscalar':'NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS',
99-
'.dlabel':'NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS',
100-
'.pscalar':'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR',
101-
'.pdconn':'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE',
102-
'.dpconn':'NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED',
103-
'.pconnseries':'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES',
104-
'.pconnscalar':'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR',
105-
'.dfan':'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES',
106-
}
94+
CIFTI_CODES=Recoder((
95+
('dconn','NIFTI_INTENT_CONNECTIVITY_DENSE', (
96+
'CIFTI_INDEX_TYPE_BRAIN_MODELS','CIFTI_INDEX_TYPE_BRAIN_MODELS',
97+
)),
98+
('dtseries','NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', (
99+
'CIFTI_INDEX_TYPE_SERIES','CIFTI_INDEX_TYPE_BRAIN_MODELS',
100+
)),
101+
('pconn','NIFTI_INTENT_CONNECTIVITY_PARCELLATED', (
102+
'CIFTI_INDEX_TYPE_PARCELS','CIFTI_INDEX_TYPE_PARCELS',
103+
)),
104+
('ptseries','NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES', (
105+
'CIFTI_INDEX_TYPE_SERIES','CIFTI_INDEX_TYPE_PARCELS',
106+
)),
107+
('dscalar','NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS', (
108+
'CIFTI_INDEX_TYPE_SCALARS','CIFTI_INDEX_TYPE_BRAIN_MODELS',
109+
)),
110+
('dlabel','NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS', (
111+
'CIFTI_INDEX_TYPE_LABELS','CIFTI_INDEX_TYPE_BRAIN_MODELS',
112+
)),
113+
('pscalar','NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR', (
114+
'CIFTI_INDEX_TYPE_SCALARS','CIFTI_INDEX_TYPE_PARCELS',
115+
)),
116+
('pdconn','NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE', (
117+
'CIFTI_INDEX_TYPE_BRAIN_MODELS','CIFTI_INDEX_TYPE_PARCELS',
118+
)),
119+
('dpconn','NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED', (
120+
'CIFTI_INDEX_TYPE_PARCELS','CIFTI_INDEX_TYPE_BRAIN_MODELS',
121+
)),
122+
('pconnseries','NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES', (
123+
'CIFTI_INDEX_TYPE_PARCELS','CIFTI_INDEX_TYPE_PARCELS','CIFTI_INDEX_TYPE_SERIES',
124+
)),
125+
('pconnscalar','NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR', (
126+
'CIFTI_INDEX_TYPE_PARCELS','CIFTI_INDEX_TYPE_PARCELS','CIFTI_INDEX_TYPE_SCALARS',
127+
)),
128+
('dfan','NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', (
129+
'CIFTI_INDEX_TYPE_SCALARS','CIFTI_INDEX_TYPE_BRAIN_MODELS',
130+
)),
131+
('dfibersamp','NIFTI_INTENT_CONNECTIVITY_UNKNOWN', (
132+
'CIFTI_INDEX_TYPE_SCALARS','CIFTI_INDEX_TYPE_SCALARS','CIFTI_INDEX_TYPE_BRAIN_MODELS',
133+
)),
134+
('dfansamp','NIFTI_INTENT_CONNECTIVITY_UNKNOWN', (
135+
'CIFTI_INDEX_TYPE_SCALARS','CIFTI_INDEX_TYPE_SCALARS','CIFTI_INDEX_TYPE_BRAIN_MODELS',
136+
)),
137+
),fields=('extension','niistring','map_types'))
107138

108139

109140
def_value_if_klass(val,klass):
@@ -1503,32 +1534,52 @@ def get_data_dtype(self):
15031534
defset_data_dtype(self,dtype):
15041535
self._nifti_header.set_data_dtype(dtype)
15051536

1506-
defto_filename(self,filename,infer_intent=False):
1537+
defto_filename(self,filename,validate=True):
15071538
"""
15081539
Ensures NIfTI header intent code is set prior to saving.
15091540
15101541
Parameters
15111542
----------
1512-
infer_intent : boolean, optional
1513-
If ``True``, attempt to infer and set intent code based on filename suffix.
1543+
validate : boolean, optional
1544+
If ``True``, infer and validate CIFTI type based on filename suffix.
1545+
This includes the setting of the NIfTI intent code and checking the ``CIFTI2Matrix``
1546+
for the expected IndicesMaps attributes.
1547+
If validation fails, an error will be raised instead.
15141548
"""
1515-
header=self._nifti_header
1516-
ifinfer_intent:
1517-
# try to infer intent code based on filename suffix
1518-
intent=_infer_intent_from_filename(filename)
1519-
ifintentisnotNone:
1520-
header.set_intent(intent)
1549+
nheader=self._nifti_header
1550+
# try to infer intent code based on filename suffix
1551+
ifvalidate:
1552+
ext=_extract_cifti_extension(filename)
1553+
try:
1554+
CIFTI_CODES.extension[ext]
1555+
exceptKeyErroraserr:
1556+
raiseKeyError(
1557+
f"Validation failed: No information for extension{ext} available"
1558+
)fromerr
1559+
intent=CIFTI_CODES.niistring[ext]
1560+
nheader.set_intent(intent)
1561+
# validate matrix indices
1562+
foridx,mtypeinenumerate(CIFTI_CODES.map_types[ext]):
1563+
try:
1564+
assertself.header.matrix.get_index_map(idx).indices_map_to_data_type==mtype
1565+
exceptException:
1566+
raiseCifti2HeaderError(
1567+
f"Validation failed: Cifti2Matrix index map{idx} does "
1568+
f"not match expected type{mtype}"
1569+
)
15211570
# if intent code is not set, default to unknown
1522-
ifheader.get_intent()[0]=='none':
1523-
header.set_intent('NIFTI_INTENT_CONNECTIVITY_UNKNOWN')
1571+
ifnheader.get_intent()[0]=='none':
1572+
nheader.set_intent('NIFTI_INTENT_CONNECTIVITY_UNKNOWN')
15241573
super().to_filename(filename)
15251574

15261575

1527-
def_infer_intent_from_filename(filename):
1576+
def_extract_cifti_extension(filename):
15281577
"""Parses output filename for common suffixes and fetches corresponding intent code"""
15291578
frompathlibimportPath
1530-
ext=Path(filename).suffixes[0]
1531-
returnCIFTI_EXTENSIONS_TO_INTENTS.get(ext)
1579+
_suf=Path(filename).suffixes
1580+
# select second to last if possible (.<suffix>.nii)
1581+
ext=_suf[-2]iflen(_suf)>=2else_suf[0]
1582+
returnext.lstrip('.')
15321583

15331584

15341585
load=Cifti2Image.from_filename

‎nibabel/cifti2/tests/test_new_cifti2.py‎

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def test_dtseries():
239239
img=ci.Cifti2Image(data,hdr)
240240

241241
withInTemporaryDirectory():
242-
ci.save(img,'test.dtseries.nii',infer_intent=True)
242+
ci.save(img,'test.dtseries.nii')
243243
img2=nib.load('test.dtseries.nii')
244244
assertimg2.nifti_header.get_intent()[0]=='ConnDenseSeries'
245245
assertisinstance(img2,ci.Cifti2Image)
@@ -282,7 +282,7 @@ def test_dlabel():
282282
img=ci.Cifti2Image(data,hdr)
283283

284284
withInTemporaryDirectory():
285-
ci.save(img,'test.dlabel.nii',infer_intent=True)
285+
ci.save(img,'test.dlabel.nii')
286286
img2=nib.load('test.dlabel.nii')
287287
assertimg2.nifti_header.get_intent()[0]=='ConnDenseLabel'
288288
assertisinstance(img2,ci.Cifti2Image)
@@ -301,7 +301,7 @@ def test_dconn():
301301
img=ci.Cifti2Image(data,hdr)
302302

303303
withInTemporaryDirectory():
304-
ci.save(img,'test.dconn.nii',infer_intent=True)
304+
ci.save(img,'test.dconn.nii')
305305
img2=nib.load('test.dconn.nii')
306306
assertimg2.nifti_header.get_intent()[0]=='ConnDense'
307307
assertisinstance(img2,ci.Cifti2Image)
@@ -322,7 +322,7 @@ def test_ptseries():
322322
img=ci.Cifti2Image(data,hdr)
323323

324324
withInTemporaryDirectory():
325-
ci.save(img,'test.ptseries.nii',infer_intent=True)
325+
ci.save(img,'test.ptseries.nii')
326326
img2=nib.load('test.ptseries.nii')
327327
assertimg2.nifti_header.get_intent()[0]=='ConnParcelSries'
328328
assertisinstance(img2,ci.Cifti2Image)
@@ -343,7 +343,7 @@ def test_pscalar():
343343
img=ci.Cifti2Image(data,hdr)
344344

345345
withInTemporaryDirectory():
346-
ci.save(img,'test.pscalar.nii',infer_intent=True)
346+
ci.save(img,'test.pscalar.nii')
347347
img2=nib.load('test.pscalar.nii')
348348
assertimg2.nifti_header.get_intent()[0]=='ConnParcelScalr'
349349
assertisinstance(img2,ci.Cifti2Image)
@@ -364,7 +364,7 @@ def test_pdconn():
364364
img=ci.Cifti2Image(data,hdr)
365365

366366
withInTemporaryDirectory():
367-
ci.save(img,'test.pdconn.nii',infer_intent=True)
367+
ci.save(img,'test.pdconn.nii')
368368
img2=ci.load('test.pdconn.nii')
369369
assertimg2.nifti_header.get_intent()[0]=='ConnParcelDense'
370370
assertisinstance(img2,ci.Cifti2Image)
@@ -385,7 +385,7 @@ def test_dpconn():
385385
img=ci.Cifti2Image(data,hdr)
386386

387387
withInTemporaryDirectory():
388-
ci.save(img,'test.dpconn.nii',infer_intent=True)
388+
ci.save(img,'test.dpconn.nii')
389389
img2=ci.load('test.dpconn.nii')
390390
assertimg2.nifti_header.get_intent()[0]=='ConnDenseParcel'
391391
assertisinstance(img2,ci.Cifti2Image)
@@ -425,7 +425,7 @@ def test_pconn():
425425
img=ci.Cifti2Image(data,hdr)
426426

427427
withInTemporaryDirectory():
428-
ci.save(img,'test.pconn.nii',infer_intent=True)
428+
ci.save(img,'test.pconn.nii')
429429
img2=ci.load('test.pconn.nii')
430430
assertimg.nifti_header.get_intent()[0]=='ConnParcels'
431431
assertisinstance(img2,ci.Cifti2Image)
@@ -447,7 +447,7 @@ def test_pconnseries():
447447
img=ci.Cifti2Image(data,hdr)
448448

449449
withInTemporaryDirectory():
450-
ci.save(img,'test.pconnseries.nii',infer_intent=True)
450+
ci.save(img,'test.pconnseries.nii')
451451
img2=ci.load('test.pconnseries.nii')
452452
assertimg.nifti_header.get_intent()[0]=='ConnPPSr'
453453
assertisinstance(img2,ci.Cifti2Image)
@@ -470,7 +470,7 @@ def test_pconnscalar():
470470
img=ci.Cifti2Image(data,hdr)
471471

472472
withInTemporaryDirectory():
473-
ci.save(img,'test.pconnscalar.nii',infer_intent=True)
473+
ci.save(img,'test.pconnscalar.nii')
474474
img2=ci.load('test.pconnscalar.nii')
475475
assertimg.nifti_header.get_intent()[0]=='ConnPPSc'
476476
assertisinstance(img2,ci.Cifti2Image)
@@ -509,3 +509,45 @@ def test_wrong_shape():
509509
withpytest.raises(ValueError):
510510
img.to_file_map()
511511

512+
513+
deftest_cifti_validation():
514+
# flip label / brain_model index maps
515+
geometry_map=create_geometry_map((0, ))
516+
label_map=create_label_map((1, ))
517+
matrix=ci.Cifti2Matrix()
518+
matrix.append(label_map)
519+
matrix.append(geometry_map)
520+
hdr=ci.Cifti2Header(matrix)
521+
data=np.random.randn(10,2)
522+
img=ci.Cifti2Image(data,hdr)
523+
524+
# attempt to save and validate with an invalid extension
525+
withpytest.raises(KeyError):
526+
ci.save(img,'test.dlabelz.nii')
527+
# even with a proper extension, flipped index maps will fail
528+
withpytest.raises(ci.Cifti2HeaderError):
529+
ci.save(img,'test.dlabel.nii')
530+
531+
label_map=create_label_map((0, ))
532+
geometry_map=create_geometry_map((1, ))
533+
matrix=ci.Cifti2Matrix()
534+
matrix.append(label_map)
535+
matrix.append(geometry_map)
536+
hdr=ci.Cifti2Header(matrix)
537+
data=np.random.randn(2,10)
538+
img=ci.Cifti2Image(data,hdr)
539+
540+
withInTemporaryDirectory():
541+
# still fail with invalid extension and validation
542+
withpytest.raises(KeyError):
543+
ci.save(img,'test.dlabelz.nii')
544+
# but removing validation should work (though intent code will be unknown)
545+
ci.save(img,'test.dlabelz.nii',validate=False)
546+
547+
img2=nib.load('test.dlabelz.nii')
548+
assertimg2.nifti_header.get_intent()[0]=='ConnUnknown'
549+
assertisinstance(img2,ci.Cifti2Image)
550+
assert_array_equal(img2.get_fdata(),data)
551+
check_label_map(img2.header.matrix.get_index_map(0))
552+
check_geometry_map(img2.header.matrix.get_index_map(1))
553+
delimg2

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp