From dff774e2d89b07a0b40b44219391e906616ef5f1 Mon Sep 17 00:00:00 2001 From: mathiasg <mathiasg@stanford.edu> Date: Mon, 6 Jul 2020 09:32:33 -0400 Subject: [PATCH 1/9] RF: Allow kwargs within ``to_filename`` and ``instance_to_filename`` methods --- nibabel/filebasedimages.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nibabel/filebasedimages.py b/nibabel/filebasedimages.py index 006b70d615..f610e87543 100644 --- a/nibabel/filebasedimages.py +++ b/nibabel/filebasedimages.py @@ -315,7 +315,7 @@ def filespec_to_file_map(klass, filespec): def filespec_to_files(klass, filespec): return klass.filespec_to_file_map(filespec) - def to_filename(self, filename): + def to_filename(self, filename, **kwargs): """ Write image to files implied by filename string Parameters @@ -381,7 +381,7 @@ def make_file_map(klass, mapping=None): load = from_filename @classmethod - def instance_to_filename(klass, img, filename): + def instance_to_filename(klass, img, filename, **kwargs): """ Save `img` in our own format, to name implied by `filename` This is a class method @@ -394,7 +394,7 @@ def instance_to_filename(klass, img, filename): Filename, implying name to which to save image. """ img = klass.from_image(img) - img.to_filename(filename) + img.to_filename(filename, **kwargs) @classmethod def from_image(klass, img): From f78d7f1b54b2be05bf543152f7fe406b978c3bf8 Mon Sep 17 00:00:00 2001 From: mathiasg <mathiasg@stanford.edu> Date: Mon, 6 Jul 2020 09:33:46 -0400 Subject: [PATCH 2/9] ENH: Add option to allow inferring of intent code based on output file suffixes --- nibabel/cifti2/cifti2.py | 50 ++++++++++++++++++++++--- nibabel/cifti2/tests/test_new_cifti2.py | 34 ++++++----------- 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/nibabel/cifti2/cifti2.py b/nibabel/cifti2/cifti2.py index 0b2b7f2a9a..2cc6931de7 100644 --- a/nibabel/cifti2/cifti2.py +++ b/nibabel/cifti2/cifti2.py @@ -89,6 +89,23 @@ class Cifti2HeaderError(Exception): 'CIFTI_STRUCTURE_THALAMUS_LEFT', 'CIFTI_STRUCTURE_THALAMUS_RIGHT') +# "Standard CIFTI Mapping Combinations" within CIFTI-2 spec +# https://www.nitrc.org/forum/attachment.php?attachid=341&group_id=454&forum_id=1955 +CIFTI_EXTENSIONS_TO_INTENTS = { + '.dconn': 'NIFTI_INTENT_CONNECTIVITY_DENSE', + '.dtseries': 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', + '.pconn': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED', + '.ptseries': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES', + '.dscalar': 'NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS', + '.dlabel': 'NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS', + '.pscalar': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR', + '.pdconn': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE', + '.dpconn': 'NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED', + '.pconnseries': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES', + '.pconnscalar': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR', + '.dfan': 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', +} + def _value_if_klass(val, klass): if val is None or isinstance(val, klass): @@ -1466,11 +1483,7 @@ def to_file_map(self, file_map=None): raise ValueError( f"Dataobj shape {self._dataobj.shape} does not match shape " f"expected from CIFTI-2 header {self.header.matrix.get_data_shape()}") - # if intent code is not set, default to unknown CIFTI - if header.get_intent()[0] == 'none': - header.set_intent('NIFTI_INTENT_CONNECTIVITY_UNKNOWN') - data = reshape_dataobj(self.dataobj, - (1, 1, 1, 1) + self.dataobj.shape) + data = reshape_dataobj(self.dataobj, (1, 1, 1, 1) + self.dataobj.shape) # If qform not set, reset pixdim values so Nifti2 does not complain if header['qform_code'] == 0: header['pixdim'][:4] = 1 @@ -1509,6 +1522,33 @@ def get_data_dtype(self): def set_data_dtype(self, dtype): self._nifti_header.set_data_dtype(dtype) + def to_filename(self, filename, infer_intent=False): + """ + Ensures NIfTI header intent code is set prior to saving. + + Parameters + ---------- + infer_intent : boolean, optional + If ``True``, attempt to infer and set intent code based on filename suffix. + """ + header = self._nifti_header + if infer_intent: + # try to infer intent code based on filename suffix + intent = _infer_intent_from_filename(filename) + if intent is not None: + header.set_intent(intent) + # if intent code is not set, default to unknown + if header.get_intent()[0] == 'none': + header.set_intent('NIFTI_INTENT_CONNECTIVITY_UNKNOWN') + super().to_filename(filename) + + +def _infer_intent_from_filename(filename): + """Parses output filename for common suffixes and fetches corresponding intent code""" + from pathlib import Path + ext = Path(filename).suffixes[0] + return CIFTI_EXTENSIONS_TO_INTENTS.get(ext) + load = Cifti2Image.from_filename save = Cifti2Image.instance_to_filename diff --git a/nibabel/cifti2/tests/test_new_cifti2.py b/nibabel/cifti2/tests/test_new_cifti2.py index 65ef95c316..5ee6f4a7c3 100644 --- a/nibabel/cifti2/tests/test_new_cifti2.py +++ b/nibabel/cifti2/tests/test_new_cifti2.py @@ -237,10 +237,9 @@ def test_dtseries(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(13, 10) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES') with InTemporaryDirectory(): - ci.save(img, 'test.dtseries.nii') + ci.save(img, 'test.dtseries.nii', infer_intent=True) img2 = nib.load('test.dtseries.nii') assert img2.nifti_header.get_intent()[0] == 'ConnDenseSeries' assert isinstance(img2, ci.Cifti2Image) @@ -281,10 +280,9 @@ def test_dlabel(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(2, 10) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS') with InTemporaryDirectory(): - ci.save(img, 'test.dlabel.nii') + ci.save(img, 'test.dlabel.nii', infer_intent=True) img2 = nib.load('test.dlabel.nii') assert img2.nifti_header.get_intent()[0] == 'ConnDenseLabel' assert isinstance(img2, ci.Cifti2Image) @@ -301,10 +299,9 @@ def test_dconn(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(10, 10) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_DENSE') with InTemporaryDirectory(): - ci.save(img, 'test.dconn.nii') + ci.save(img, 'test.dconn.nii', infer_intent=True) img2 = nib.load('test.dconn.nii') assert img2.nifti_header.get_intent()[0] == 'ConnDense' assert isinstance(img2, ci.Cifti2Image) @@ -323,10 +320,9 @@ def test_ptseries(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(13, 4) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES') with InTemporaryDirectory(): - ci.save(img, 'test.ptseries.nii') + ci.save(img, 'test.ptseries.nii', infer_intent=True) img2 = nib.load('test.ptseries.nii') assert img2.nifti_header.get_intent()[0] == 'ConnParcelSries' assert isinstance(img2, ci.Cifti2Image) @@ -345,10 +341,9 @@ def test_pscalar(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(2, 4) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR') with InTemporaryDirectory(): - ci.save(img, 'test.pscalar.nii') + ci.save(img, 'test.pscalar.nii', infer_intent=True) img2 = nib.load('test.pscalar.nii') assert img2.nifti_header.get_intent()[0] == 'ConnParcelScalr' assert isinstance(img2, ci.Cifti2Image) @@ -367,10 +362,9 @@ def test_pdconn(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(10, 4) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE') with InTemporaryDirectory(): - ci.save(img, 'test.pdconn.nii') + ci.save(img, 'test.pdconn.nii', infer_intent=True) img2 = ci.load('test.pdconn.nii') assert img2.nifti_header.get_intent()[0] == 'ConnParcelDense' assert isinstance(img2, ci.Cifti2Image) @@ -389,10 +383,9 @@ def test_dpconn(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(4, 10) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED') with InTemporaryDirectory(): - ci.save(img, 'test.dpconn.nii') + ci.save(img, 'test.dpconn.nii', infer_intent=True) img2 = ci.load('test.dpconn.nii') assert img2.nifti_header.get_intent()[0] == 'ConnDenseParcel' assert isinstance(img2, ci.Cifti2Image) @@ -430,10 +423,9 @@ def test_pconn(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(4, 4) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_PARCELLATED') with InTemporaryDirectory(): - ci.save(img, 'test.pconn.nii') + ci.save(img, 'test.pconn.nii', infer_intent=True) img2 = ci.load('test.pconn.nii') assert img.nifti_header.get_intent()[0] == 'ConnParcels' assert isinstance(img2, ci.Cifti2Image) @@ -453,11 +445,9 @@ def test_pconnseries(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(4, 4, 13) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_PARCELLATED_' - 'PARCELLATED_SERIES') with InTemporaryDirectory(): - ci.save(img, 'test.pconnseries.nii') + ci.save(img, 'test.pconnseries.nii', infer_intent=True) img2 = ci.load('test.pconnseries.nii') assert img.nifti_header.get_intent()[0] == 'ConnPPSr' assert isinstance(img2, ci.Cifti2Image) @@ -478,11 +468,9 @@ def test_pconnscalar(): hdr = ci.Cifti2Header(matrix) data = np.random.randn(4, 4, 2) img = ci.Cifti2Image(data, hdr) - img.nifti_header.set_intent('NIFTI_INTENT_CONNECTIVITY_PARCELLATED_' - 'PARCELLATED_SCALAR') with InTemporaryDirectory(): - ci.save(img, 'test.pconnscalar.nii') + ci.save(img, 'test.pconnscalar.nii', infer_intent=True) img2 = ci.load('test.pconnscalar.nii') assert img.nifti_header.get_intent()[0] == 'ConnPPSc' assert isinstance(img2, ci.Cifti2Image) @@ -517,7 +505,7 @@ def test_wrong_shape(): ci.Cifti2Image(data, hdr) with suppress_warnings(): img = ci.Cifti2Image(data, hdr) - + with pytest.raises(ValueError): img.to_file_map() From 2b6b328173d52b8909c005bfba64ea60330ce4c1 Mon Sep 17 00:00:00 2001 From: mathiasg <mathiasg@stanford.edu> Date: Fri, 2 Oct 2020 16:25:58 -0400 Subject: [PATCH 3/9] 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 --- nibabel/cifti2/cifti2.py | 107 +++++++++++++++++------- nibabel/cifti2/tests/test_new_cifti2.py | 62 +++++++++++--- 2 files changed, 131 insertions(+), 38 deletions(-) diff --git a/nibabel/cifti2/cifti2.py b/nibabel/cifti2/cifti2.py index 2cc6931de7..a6ec086718 100644 --- a/nibabel/cifti2/cifti2.py +++ b/nibabel/cifti2/cifti2.py @@ -25,6 +25,7 @@ from ..nifti1 import Nifti1Extensions from ..nifti2 import Nifti2Image, Nifti2Header from ..arrayproxy import reshape_dataobj +from ..volumeutils import Recoder from warnings import warn @@ -91,20 +92,50 @@ class Cifti2HeaderError(Exception): # "Standard CIFTI Mapping Combinations" within CIFTI-2 spec # https://www.nitrc.org/forum/attachment.php?attachid=341&group_id=454&forum_id=1955 -CIFTI_EXTENSIONS_TO_INTENTS = { - '.dconn': 'NIFTI_INTENT_CONNECTIVITY_DENSE', - '.dtseries': 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', - '.pconn': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED', - '.ptseries': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES', - '.dscalar': 'NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS', - '.dlabel': 'NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS', - '.pscalar': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR', - '.pdconn': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE', - '.dpconn': 'NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED', - '.pconnseries': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES', - '.pconnscalar': 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR', - '.dfan': 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', -} +CIFTI_CODES = Recoder(( + ('dconn', 'NIFTI_INTENT_CONNECTIVITY_DENSE', ( + 'CIFTI_INDEX_TYPE_BRAIN_MODELS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', + )), + ('dtseries', 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', ( + 'CIFTI_INDEX_TYPE_SERIES', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', + )), + ('pconn', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED', ( + 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_PARCELS', + )), + ('ptseries', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES', ( + 'CIFTI_INDEX_TYPE_SERIES', 'CIFTI_INDEX_TYPE_PARCELS', + )), + ('dscalar', 'NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS', ( + 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', + )), + ('dlabel', 'NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS', ( + 'CIFTI_INDEX_TYPE_LABELS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', + )), + ('pscalar', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR', ( + 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_PARCELS', + )), + ('pdconn', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE', ( + 'CIFTI_INDEX_TYPE_BRAIN_MODELS', 'CIFTI_INDEX_TYPE_PARCELS', + )), + ('dpconn', 'NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED', ( + 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', + )), + ('pconnseries', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES', ( + 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_SERIES', + )), + ('pconnscalar', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR', ( + 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_SCALARS', + )), + ('dfan', 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', ( + 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', + )), + ('dfibersamp', 'NIFTI_INTENT_CONNECTIVITY_UNKNOWN', ( + 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', + )), + ('dfansamp', 'NIFTI_INTENT_CONNECTIVITY_UNKNOWN', ( + 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', + )), +), fields=('extension', 'niistring', 'map_types')) def _value_if_klass(val, klass): @@ -1522,32 +1553,52 @@ def get_data_dtype(self): def set_data_dtype(self, dtype): self._nifti_header.set_data_dtype(dtype) - def to_filename(self, filename, infer_intent=False): + def to_filename(self, filename, validate=True): """ Ensures NIfTI header intent code is set prior to saving. Parameters ---------- - infer_intent : boolean, optional - If ``True``, attempt to infer and set intent code based on filename suffix. + validate : boolean, optional + If ``True``, infer and validate CIFTI type based on filename suffix. + This includes the setting of the NIfTI intent code and checking the ``CIFTI2Matrix`` + for the expected IndicesMaps attributes. + If validation fails, an error will be raised instead. """ - header = self._nifti_header - if infer_intent: - # try to infer intent code based on filename suffix - intent = _infer_intent_from_filename(filename) - if intent is not None: - header.set_intent(intent) + nheader = self._nifti_header + # try to infer intent code based on filename suffix + if validate: + ext = _extract_cifti_extension(filename) + try: + CIFTI_CODES.extension[ext] + except KeyError as err: + raise KeyError( + f"Validation failed: No information for extension {ext} available" + ) from err + intent = CIFTI_CODES.niistring[ext] + nheader.set_intent(intent) + # validate matrix indices + for idx, mtype in enumerate(CIFTI_CODES.map_types[ext]): + try: + assert self.header.matrix.get_index_map(idx).indices_map_to_data_type == mtype + except Exception: + raise Cifti2HeaderError( + f"Validation failed: Cifti2Matrix index map {idx} does " + f"not match expected type {mtype}" + ) # if intent code is not set, default to unknown - if header.get_intent()[0] == 'none': - header.set_intent('NIFTI_INTENT_CONNECTIVITY_UNKNOWN') + if nheader.get_intent()[0] == 'none': + nheader.set_intent('NIFTI_INTENT_CONNECTIVITY_UNKNOWN') super().to_filename(filename) -def _infer_intent_from_filename(filename): +def _extract_cifti_extension(filename): """Parses output filename for common suffixes and fetches corresponding intent code""" from pathlib import Path - ext = Path(filename).suffixes[0] - return CIFTI_EXTENSIONS_TO_INTENTS.get(ext) + _suf = Path(filename).suffixes + # select second to last if possible (.<suffix>.nii) + ext = _suf[-2] if len(_suf) >= 2 else _suf[0] + return ext.lstrip('.') load = Cifti2Image.from_filename diff --git a/nibabel/cifti2/tests/test_new_cifti2.py b/nibabel/cifti2/tests/test_new_cifti2.py index 5ee6f4a7c3..948e1aca41 100644 --- a/nibabel/cifti2/tests/test_new_cifti2.py +++ b/nibabel/cifti2/tests/test_new_cifti2.py @@ -239,7 +239,7 @@ def test_dtseries(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.dtseries.nii', infer_intent=True) + ci.save(img, 'test.dtseries.nii') img2 = nib.load('test.dtseries.nii') assert img2.nifti_header.get_intent()[0] == 'ConnDenseSeries' assert isinstance(img2, ci.Cifti2Image) @@ -282,7 +282,7 @@ def test_dlabel(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.dlabel.nii', infer_intent=True) + ci.save(img, 'test.dlabel.nii') img2 = nib.load('test.dlabel.nii') assert img2.nifti_header.get_intent()[0] == 'ConnDenseLabel' assert isinstance(img2, ci.Cifti2Image) @@ -301,7 +301,7 @@ def test_dconn(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.dconn.nii', infer_intent=True) + ci.save(img, 'test.dconn.nii') img2 = nib.load('test.dconn.nii') assert img2.nifti_header.get_intent()[0] == 'ConnDense' assert isinstance(img2, ci.Cifti2Image) @@ -322,7 +322,7 @@ def test_ptseries(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.ptseries.nii', infer_intent=True) + ci.save(img, 'test.ptseries.nii') img2 = nib.load('test.ptseries.nii') assert img2.nifti_header.get_intent()[0] == 'ConnParcelSries' assert isinstance(img2, ci.Cifti2Image) @@ -343,7 +343,7 @@ def test_pscalar(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.pscalar.nii', infer_intent=True) + ci.save(img, 'test.pscalar.nii') img2 = nib.load('test.pscalar.nii') assert img2.nifti_header.get_intent()[0] == 'ConnParcelScalr' assert isinstance(img2, ci.Cifti2Image) @@ -364,7 +364,7 @@ def test_pdconn(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.pdconn.nii', infer_intent=True) + ci.save(img, 'test.pdconn.nii') img2 = ci.load('test.pdconn.nii') assert img2.nifti_header.get_intent()[0] == 'ConnParcelDense' assert isinstance(img2, ci.Cifti2Image) @@ -385,7 +385,7 @@ def test_dpconn(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.dpconn.nii', infer_intent=True) + ci.save(img, 'test.dpconn.nii') img2 = ci.load('test.dpconn.nii') assert img2.nifti_header.get_intent()[0] == 'ConnDenseParcel' assert isinstance(img2, ci.Cifti2Image) @@ -425,7 +425,7 @@ def test_pconn(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.pconn.nii', infer_intent=True) + ci.save(img, 'test.pconn.nii') img2 = ci.load('test.pconn.nii') assert img.nifti_header.get_intent()[0] == 'ConnParcels' assert isinstance(img2, ci.Cifti2Image) @@ -447,7 +447,7 @@ def test_pconnseries(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.pconnseries.nii', infer_intent=True) + ci.save(img, 'test.pconnseries.nii') img2 = ci.load('test.pconnseries.nii') assert img.nifti_header.get_intent()[0] == 'ConnPPSr' assert isinstance(img2, ci.Cifti2Image) @@ -470,7 +470,7 @@ def test_pconnscalar(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.pconnscalar.nii', infer_intent=True) + ci.save(img, 'test.pconnscalar.nii') img2 = ci.load('test.pconnscalar.nii') assert img.nifti_header.get_intent()[0] == 'ConnPPSc' assert isinstance(img2, ci.Cifti2Image) @@ -509,3 +509,45 @@ def test_wrong_shape(): with pytest.raises(ValueError): img.to_file_map() + +def test_cifti_validation(): + # flip label / brain_model index maps + geometry_map = create_geometry_map((0, )) + label_map = create_label_map((1, )) + matrix = ci.Cifti2Matrix() + matrix.append(label_map) + matrix.append(geometry_map) + hdr = ci.Cifti2Header(matrix) + data = np.random.randn(10, 2) + img = ci.Cifti2Image(data, hdr) + + # attempt to save and validate with an invalid extension + with pytest.raises(KeyError): + ci.save(img, 'test.dlabelz.nii') + # even with a proper extension, flipped index maps will fail + with pytest.raises(ci.Cifti2HeaderError): + ci.save(img, 'test.dlabel.nii') + + label_map = create_label_map((0, )) + geometry_map = create_geometry_map((1, )) + matrix = ci.Cifti2Matrix() + matrix.append(label_map) + matrix.append(geometry_map) + hdr = ci.Cifti2Header(matrix) + data = np.random.randn(2, 10) + img = ci.Cifti2Image(data, hdr) + + with InTemporaryDirectory(): + # still fail with invalid extension and validation + with pytest.raises(KeyError): + ci.save(img, 'test.dlabelz.nii') + # but removing validation should work (though intent code will be unknown) + ci.save(img, 'test.dlabelz.nii', validate=False) + + img2 = nib.load('test.dlabelz.nii') + assert img2.nifti_header.get_intent()[0] == 'ConnUnknown' + assert isinstance(img2, ci.Cifti2Image) + assert_array_equal(img2.get_fdata(), data) + check_label_map(img2.header.matrix.get_index_map(0)) + check_geometry_map(img2.header.matrix.get_index_map(1)) + del img2 From ad6adfa5b30e317939990962f188aaf72aca8161 Mon Sep 17 00:00:00 2001 From: mathiasg <mathiasg@stanford.edu> Date: Fri, 2 Oct 2020 18:57:59 -0400 Subject: [PATCH 4/9] RF: Avoid validation when testing CIFTI saving --- nibabel/cifti2/tests/test_cifti2.py | 15 +++++++++++++++ nibabel/cifti2/tests/test_cifti2io_axes.py | 2 +- nibabel/cifti2/tests/test_new_cifti2.py | 2 +- nibabel/tests/test_image_api.py | 20 ++++++++++---------- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/nibabel/cifti2/tests/test_cifti2.py b/nibabel/cifti2/tests/test_cifti2.py index ea571065de..f64e11c5df 100644 --- a/nibabel/cifti2/tests/test_cifti2.py +++ b/nibabel/cifti2/tests/test_cifti2.py @@ -427,3 +427,18 @@ def make_imaker(self, arr, header=None, ni_header=None): ) header.matrix.append(mim) return lambda: self.image_maker(arr.copy(), header, ni_header) + + def validate_filenames(self, imaker, params, validate=False): + super().validate_filenames(imaker, params, validate=validate) + + def validate_mmap_parameter(self, imaker, params, validate=False): + super().validate_mmap_parameter(imaker, params, validate=validate) + + def validate_to_bytes(self, imaker, params, validate=False): + super().validate_to_bytes(imaker, params, validate=validate) + + def validate_from_bytes(self, imaker, params, validate=False): + super().validate_from_bytes(imaker, params, validate=validate) + + def validate_to_from_bytes(self, imaker, params, validate=False): + super().validate_to_from_bytes(imaker, params, validate=validate) diff --git a/nibabel/cifti2/tests/test_cifti2io_axes.py b/nibabel/cifti2/tests/test_cifti2io_axes.py index c237e3c61a..1abd129ae5 100644 --- a/nibabel/cifti2/tests/test_cifti2io_axes.py +++ b/nibabel/cifti2/tests/test_cifti2io_axes.py @@ -91,7 +91,7 @@ def check_rewrite(arr, axes, extension='.nii'): custom extension to use """ (fd, name) = tempfile.mkstemp(extension) - cifti2.Cifti2Image(arr, header=axes).to_filename(name) + cifti2.Cifti2Image(arr, header=axes).to_filename(name, validate=False) img = nib.load(name) arr2 = img.get_fdata() assert np.allclose(arr, arr2) diff --git a/nibabel/cifti2/tests/test_new_cifti2.py b/nibabel/cifti2/tests/test_new_cifti2.py index 948e1aca41..2bc6da3b47 100644 --- a/nibabel/cifti2/tests/test_new_cifti2.py +++ b/nibabel/cifti2/tests/test_new_cifti2.py @@ -406,7 +406,7 @@ def test_plabel(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - ci.save(img, 'test.plabel.nii') + ci.save(img, 'test.plabel.nii', validate=False) img2 = ci.load('test.plabel.nii') assert img.nifti_header.get_intent()[0] == 'ConnUnknown' assert isinstance(img2, ci.Cifti2Image) diff --git a/nibabel/tests/test_image_api.py b/nibabel/tests/test_image_api.py index 392a493e53..9a9ae040c4 100644 --- a/nibabel/tests/test_image_api.py +++ b/nibabel/tests/test_image_api.py @@ -123,7 +123,7 @@ def validate_header_deprecated(self, imaker, params): hdr = img.get_header() assert hdr is img.header - def validate_filenames(self, imaker, params): + def validate_filenames(self, imaker, params, **kwargs): # Validate the filename, file_map interface if not self.can_save: @@ -160,7 +160,7 @@ def validate_filenames(self, imaker, params): warnings.filterwarnings('error', category=DeprecationWarning, module=r"nibabel.*") - img.to_filename(path) + img.to_filename(path, **kwargs) rt_img = img.__class__.from_filename(path) assert_array_equal(img.shape, rt_img.shape) assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) @@ -456,7 +456,7 @@ def validate_shape_deprecated(self, imaker, params): with pytest.raises(ExpiredDeprecationError): img.get_shape() - def validate_mmap_parameter(self, imaker, params): + def validate_mmap_parameter(self, imaker, params, **kwargs): img = imaker() fname = img.get_filename() with InTemporaryDirectory(): @@ -468,7 +468,7 @@ def validate_mmap_parameter(self, imaker, params): if not img.rw or not img.valid_exts: return fname = 'image' + img.valid_exts[0] - img.to_filename(fname) + img.to_filename(fname, **kwargs) rt_img = img.__class__.from_filename(fname, mmap=True) assert_almost_equal(img.get_fdata(), rt_img.get_fdata()) rt_img = img.__class__.from_filename(fname, mmap=False) @@ -533,22 +533,22 @@ def validate_affine_deprecated(self, imaker, params): class SerializeMixin(object): - def validate_to_bytes(self, imaker, params): + def validate_to_bytes(self, imaker, params, **kwargs): img = imaker() serialized = img.to_bytes() with InTemporaryDirectory(): fname = 'img' + self.standard_extension - img.to_filename(fname) + img.to_filename(fname, **kwargs) with open(fname, 'rb') as fobj: file_contents = fobj.read() assert serialized == file_contents - def validate_from_bytes(self, imaker, params): + def validate_from_bytes(self, imaker, params, **kwargs): img = imaker() klass = getattr(self, 'klass', img.__class__) with InTemporaryDirectory(): fname = 'img' + self.standard_extension - img.to_filename(fname) + img.to_filename(fname, **kwargs) all_images = list(getattr(self, 'example_images', [])) + [{'fname': fname}] for img_params in all_images: @@ -561,12 +561,12 @@ def validate_from_bytes(self, imaker, params): del img_a del img_b - def validate_to_from_bytes(self, imaker, params): + def validate_to_from_bytes(self, imaker, params, **kwargs): img = imaker() klass = getattr(self, 'klass', img.__class__) with InTemporaryDirectory(): fname = 'img' + self.standard_extension - img.to_filename(fname) + img.to_filename(fname, **kwargs) all_images = list(getattr(self, 'example_images', [])) + [{'fname': fname}] for img_params in all_images: From 2a20433db3f58c5fc30bc316c066fec661be0167 Mon Sep 17 00:00:00 2001 From: mathiasg <mathiasg@stanford.edu> Date: Mon, 5 Oct 2020 11:05:49 -0400 Subject: [PATCH 5/9] RF: Extract nifti header update from ``to_filename`` --- nibabel/cifti2/cifti2.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/nibabel/cifti2/cifti2.py b/nibabel/cifti2/cifti2.py index a6ec086718..98e4a83a89 100644 --- a/nibabel/cifti2/cifti2.py +++ b/nibabel/cifti2/cifti2.py @@ -1545,7 +1545,11 @@ def update_headers(self): >>> img.shape == (2, 3, 4) True """ - self._nifti_header.set_data_shape((1, 1, 1, 1) + self._dataobj.shape) + header = self._nifti_header + header.set_data_shape((1, 1, 1, 1) + self._dataobj.shape) + # if intent code is not set, default to unknown + if header.get_intent()[0] == 'none': + header.set_intent('NIFTI_INTENT_CONNECTIVITY_UNKNOWN') def get_data_dtype(self): return self._nifti_header.get_data_dtype() @@ -1565,8 +1569,6 @@ def to_filename(self, filename, validate=True): for the expected IndicesMaps attributes. If validation fails, an error will be raised instead. """ - nheader = self._nifti_header - # try to infer intent code based on filename suffix if validate: ext = _extract_cifti_extension(filename) try: @@ -1576,7 +1578,7 @@ def to_filename(self, filename, validate=True): f"Validation failed: No information for extension {ext} available" ) from err intent = CIFTI_CODES.niistring[ext] - nheader.set_intent(intent) + self._nifti_header.set_intent(intent) # validate matrix indices for idx, mtype in enumerate(CIFTI_CODES.map_types[ext]): try: @@ -1586,9 +1588,6 @@ def to_filename(self, filename, validate=True): f"Validation failed: Cifti2Matrix index map {idx} does " f"not match expected type {mtype}" ) - # if intent code is not set, default to unknown - if nheader.get_intent()[0] == 'none': - nheader.set_intent('NIFTI_INTENT_CONNECTIVITY_UNKNOWN') super().to_filename(filename) From 619295e9c6990f0ce6cbeb08f266a562b9da8d86 Mon Sep 17 00:00:00 2001 From: mathiasg <mathiasg@stanford.edu> Date: Mon, 5 Oct 2020 11:37:23 -0400 Subject: [PATCH 6/9] FIX: remove validation for remaining tests --- nibabel/cifti2/tests/test_cifti2io_header.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/cifti2/tests/test_cifti2io_header.py b/nibabel/cifti2/tests/test_cifti2io_header.py index df4fe10fcd..f1f1f6c811 100644 --- a/nibabel/cifti2/tests/test_cifti2io_header.py +++ b/nibabel/cifti2/tests/test_cifti2io_header.py @@ -83,7 +83,7 @@ def test_readwritedata(): with InTemporaryDirectory(): for name in datafiles: img = ci.load(name) - ci.save(img, 'test.nii') + ci.save(img, 'test.nii', validate=False) img2 = ci.load('test.nii') assert len(img.header.matrix) == len(img2.header.matrix) # Order should be preserved in load/save @@ -109,7 +109,7 @@ def test_nibabel_readwritedata(): with InTemporaryDirectory(): for name in datafiles: img = nib.load(name) - nib.save(img, 'test.nii') + nib.save(img, 'test.nii', validate=False) img2 = nib.load('test.nii') assert len(img.header.matrix) == len(img2.header.matrix) # Order should be preserved in load/save From 62be507ccd01f3568dd52ea3d0649440e1a75913 Mon Sep 17 00:00:00 2001 From: mathiasg <mathiasg@stanford.edu> Date: Mon, 5 Oct 2020 12:53:25 -0400 Subject: [PATCH 7/9] ENH: Allow kwargs in generic save method --- nibabel/loadsave.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nibabel/loadsave.py b/nibabel/loadsave.py index f3a2b5876b..eb112b158c 100644 --- a/nibabel/loadsave.py +++ b/nibabel/loadsave.py @@ -78,7 +78,7 @@ def guessed_image_type(filename): raise ImageFileError(f'Cannot work out file type of "{filename}"') -def save(img, filename): +def save(img, filename, **kwargs): """ Save an image to file adapting format to `filename` Parameters @@ -96,7 +96,7 @@ def save(img, filename): # Save the type as expected try: - img.to_filename(filename) + img.to_filename(filename, **kwargs) except ImageFileError: pass else: @@ -144,7 +144,7 @@ def save(img, filename): # Here, we either have a klass or a converted image. if converted is None: converted = klass.from_image(img) - converted.to_filename(filename) + converted.to_filename(filename, **kwargs) @deprecate_with_version('read_img_data deprecated. ' From 03ef43d65f497d2a16facc0262d3b823e677ab24 Mon Sep 17 00:00:00 2001 From: mathiasg <mathiasg@stanford.edu> Date: Mon, 19 Oct 2020 13:33:45 -0400 Subject: [PATCH 8/9] FIX: Lower severity of failed validation checks --- nibabel/cifti2/cifti2.py | 87 ++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/nibabel/cifti2/cifti2.py b/nibabel/cifti2/cifti2.py index 98e4a83a89..a0cd22fa58 100644 --- a/nibabel/cifti2/cifti2.py +++ b/nibabel/cifti2/cifti2.py @@ -93,46 +93,46 @@ class Cifti2HeaderError(Exception): # "Standard CIFTI Mapping Combinations" within CIFTI-2 spec # https://www.nitrc.org/forum/attachment.php?attachid=341&group_id=454&forum_id=1955 CIFTI_CODES = Recoder(( - ('dconn', 'NIFTI_INTENT_CONNECTIVITY_DENSE', ( + ('.dconn.nii', 'NIFTI_INTENT_CONNECTIVITY_DENSE', ( 'CIFTI_INDEX_TYPE_BRAIN_MODELS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', )), - ('dtseries', 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', ( + ('.dtseries.nii', 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', ( 'CIFTI_INDEX_TYPE_SERIES', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', )), - ('pconn', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED', ( + ('.pconn.nii', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED', ( 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_PARCELS', )), - ('ptseries', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES', ( + ('.ptseries.nii', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES', ( 'CIFTI_INDEX_TYPE_SERIES', 'CIFTI_INDEX_TYPE_PARCELS', )), - ('dscalar', 'NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS', ( + ('.dscalar.nii', 'NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS', ( 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', )), - ('dlabel', 'NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS', ( + ('.dlabel.nii', 'NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS', ( 'CIFTI_INDEX_TYPE_LABELS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', )), - ('pscalar', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR', ( + ('.pscalar.nii', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR', ( 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_PARCELS', )), - ('pdconn', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE', ( + ('.pdconn.nii', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE', ( 'CIFTI_INDEX_TYPE_BRAIN_MODELS', 'CIFTI_INDEX_TYPE_PARCELS', )), - ('dpconn', 'NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED', ( + ('.dpconn.nii', 'NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED', ( 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', )), - ('pconnseries', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES', ( + ('.pconnseries.nii', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES', ( 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_SERIES', )), - ('pconnscalar', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR', ( + ('.pconnscalar.nii', 'NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR', ( 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_PARCELS', 'CIFTI_INDEX_TYPE_SCALARS', )), - ('dfan', 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', ( + ('.dfan.nii', 'NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES', ( 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', )), - ('dfibersamp', 'NIFTI_INTENT_CONNECTIVITY_UNKNOWN', ( + ('.dfibersamp.nii', 'NIFTI_INTENT_CONNECTIVITY_UNKNOWN', ( 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', )), - ('dfansamp', 'NIFTI_INTENT_CONNECTIVITY_UNKNOWN', ( + ('.dfansamp.nii', 'NIFTI_INTENT_CONNECTIVITY_UNKNOWN', ( 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_SCALARS', 'CIFTI_INDEX_TYPE_BRAIN_MODELS', )), ), fields=('extension', 'niistring', 'map_types')) @@ -1564,40 +1564,39 @@ def to_filename(self, filename, validate=True): Parameters ---------- validate : boolean, optional - If ``True``, infer and validate CIFTI type based on filename suffix. - This includes the setting of the NIfTI intent code and checking the ``CIFTI2Matrix`` - for the expected IndicesMaps attributes. - If validation fails, an error will be raised instead. + If ``True``, infer and validate CIFTI type based on MatrixIndicesMap values. + This includes the setting of the relevant intent code within the NIfTI header. + If validation fails, a UserWarning is issued and saving continues. """ if validate: - ext = _extract_cifti_extension(filename) - try: - CIFTI_CODES.extension[ext] - except KeyError as err: - raise KeyError( - f"Validation failed: No information for extension {ext} available" - ) from err - intent = CIFTI_CODES.niistring[ext] - self._nifti_header.set_intent(intent) - # validate matrix indices - for idx, mtype in enumerate(CIFTI_CODES.map_types[ext]): - try: - assert self.header.matrix.get_index_map(idx).indices_map_to_data_type == mtype - except Exception: - raise Cifti2HeaderError( - f"Validation failed: Cifti2Matrix index map {idx} does " - f"not match expected type {mtype}" - ) - super().to_filename(filename) + # Determine CIFTI type via index maps + from .parse_cifti2 import intent_codes + matrix = self.header.matrix + map_types = tuple( + matrix.get_index_map(idx).indices_map_to_data_type for idx + in sorted(matrix.mapped_indices) + ) + try: + expected_intent = CIFTI_CODES.niistring[map_types] + expected_ext = CIFTI_CODES.extension[map_types] + except KeyError: # unknown + expected_intent = "NIFTI_INTENT_CONNECTIVITY_UNKNOWN" + expected_ext = None + warn( + "No information found for matrix containing the following index maps:" + f"{map_types}, defaulting to unknown." + ) -def _extract_cifti_extension(filename): - """Parses output filename for common suffixes and fetches corresponding intent code""" - from pathlib import Path - _suf = Path(filename).suffixes - # select second to last if possible (.<suffix>.nii) - ext = _suf[-2] if len(_suf) >= 2 else _suf[0] - return ext.lstrip('.') + orig_intent = self._nifti_header.get_intent()[0] + if expected_intent != intent_codes.niistring[orig_intent]: + warn( + f"Expected NIfTI intent: {expected_intent} has been automatically set." + ) + self._nifti_header.set_intent(expected_intent) + if expected_ext is not None and not filename.endswith(expected_ext): + warn(f"Filename does not end with expected extension: {expected_ext}") + super().to_filename(filename) load = Cifti2Image.from_filename From af7265ceee16cbdfa753355908eca8c0505bd988 Mon Sep 17 00:00:00 2001 From: mathiasg <mathiasg@stanford.edu> Date: Mon, 19 Oct 2020 13:34:33 -0400 Subject: [PATCH 9/9] TST: Clean up test --- nibabel/cifti2/tests/test_new_cifti2.py | 31 +++++++++++-------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/nibabel/cifti2/tests/test_new_cifti2.py b/nibabel/cifti2/tests/test_new_cifti2.py index 2bc6da3b47..026fc920f0 100644 --- a/nibabel/cifti2/tests/test_new_cifti2.py +++ b/nibabel/cifti2/tests/test_new_cifti2.py @@ -7,12 +7,11 @@ scratch. """ import numpy as np - import nibabel as nib from nibabel import cifti2 as ci from nibabel.tmpdirs import InTemporaryDirectory - import pytest + from ...testing import ( clear_and_catch_warnings, error_warnings, suppress_warnings, assert_array_equal) @@ -515,17 +514,13 @@ def test_cifti_validation(): geometry_map = create_geometry_map((0, )) label_map = create_label_map((1, )) matrix = ci.Cifti2Matrix() - matrix.append(label_map) matrix.append(geometry_map) + matrix.append(label_map) hdr = ci.Cifti2Header(matrix) data = np.random.randn(10, 2) img = ci.Cifti2Image(data, hdr) - - # attempt to save and validate with an invalid extension - with pytest.raises(KeyError): - ci.save(img, 'test.dlabelz.nii') - # even with a proper extension, flipped index maps will fail - with pytest.raises(ci.Cifti2HeaderError): + # flipped index maps will warn + with InTemporaryDirectory(), pytest.warns(UserWarning): ci.save(img, 'test.dlabel.nii') label_map = create_label_map((0, )) @@ -538,16 +533,16 @@ def test_cifti_validation(): img = ci.Cifti2Image(data, hdr) with InTemporaryDirectory(): - # still fail with invalid extension and validation - with pytest.raises(KeyError): - ci.save(img, 'test.dlabelz.nii') - # but removing validation should work (though intent code will be unknown) - ci.save(img, 'test.dlabelz.nii', validate=False) - - img2 = nib.load('test.dlabelz.nii') - assert img2.nifti_header.get_intent()[0] == 'ConnUnknown' + ci.save(img, 'test.validate.nii', validate=False) + ci.save(img, 'test.dlabel.nii') + + img2 = nib.load('test.dlabel.nii') + img3 = nib.load('test.validate.nii') + assert img2.nifti_header.get_intent()[0] == 'ConnDenseLabel' + assert img3.nifti_header.get_intent()[0] == 'ConnUnknown' assert isinstance(img2, ci.Cifti2Image) + assert isinstance(img3, ci.Cifti2Image) assert_array_equal(img2.get_fdata(), data) check_label_map(img2.header.matrix.get_index_map(0)) check_geometry_map(img2.header.matrix.get_index_map(1)) - del img2 + del img2, img3 \ No newline at end of file