From 5d837cc944e2e2d745ca96051a2a8a56c35fe459 Mon Sep 17 00:00:00 2001 From: wtclarke Date: Wed, 23 Oct 2024 22:10:24 +0100 Subject: [PATCH] Allow trailing singleton dimensions to be set to None tag --- CHANGELOG.md | 1 + src/nifti_mrs/hdr_ext.py | 6 +++--- src/nifti_mrs/nifti_mrs.py | 8 +++++++- tests/test_nifti_mrs.py | 40 +++++++++++++++++++++++++++++++++++++- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52faf41..dcbe8cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ This document contains the nifti_mrs_tools release history in reverse chronologi 1.3.2 (Wednesday 23rd October 2024) ----------------------------------- - Better visualisation using `mrs_tools vis` for data containing ISIS, metabolite cycling or editing. +- Dimension tags can now be set to `None` to remove trailing singleton dimension. 1.3.1 (Monday 21st October 2024) -------------------------------- diff --git a/src/nifti_mrs/hdr_ext.py b/src/nifti_mrs/hdr_ext.py index 9c9cc81..a1ef85d 100644 --- a/src/nifti_mrs/hdr_ext.py +++ b/src/nifti_mrs/hdr_ext.py @@ -136,15 +136,15 @@ def set_dim_info(self, dim, tag, info=None, hdr=None): :param dim: May be (0,1,2) or ("5th","6th","7th") :type dim: str or int - :param tag: Must be one of the defined dimension tag strings. E.g. DIM_DYN + :param tag: Must be one of the defined dimension tag strings. E.g. DIM_DYN or None :type tag: str :param info: Optional, free-form for documentation, defaults to None :type info: str, optional :param hdr: Dict containing relevant header value names and values. Defaults to None :type hdr: dict, optional """ - if tag not in dimension_tags: - raise ValueError("tag must be one of the defined dimension tag.") + if tag is not None and tag not in dimension_tags: + raise ValueError("tag must be one of the defined dimension tags or None.") new_info = {"tag": tag, "info": info, diff --git a/src/nifti_mrs/nifti_mrs.py b/src/nifti_mrs/nifti_mrs.py index a9d09a6..16f35c6 100644 --- a/src/nifti_mrs/nifti_mrs.py +++ b/src/nifti_mrs/nifti_mrs.py @@ -425,11 +425,17 @@ def set_dim_tag(self, dim, tag, info=None, header=None): :param header: dict containing the dimension headers :type header: dict """ - if tag not in dimension_tags.keys(): + if tag is not None and tag not in dimension_tags.keys(): raise ValueError(f'Tag must be one of: {", ".join(list(dimension_tags.keys()))}.') dim = self._dim_tag_to_index(dim) + # If tag is None, check that the dimension is singleton + if tag is None and self.ndim > dim and self.shape[dim] > 1: + raise ValueError('Tag cannot be set to None for non-singleton dimension.') + if tag is None and self.ndim > dim: + raise ValueError('Tag can only be set to None for trailing singleton dimension.') + if header is not None: # Check size def size_chk(obj): diff --git a/tests/test_nifti_mrs.py b/tests/test_nifti_mrs.py index bb3488a..fbd403f 100644 --- a/tests/test_nifti_mrs.py +++ b/tests/test_nifti_mrs.py @@ -11,9 +11,10 @@ import numpy as np from nibabel.nifti1 import Nifti1Extension -from nifti_mrs.nifti_mrs import NIFTI_MRS +from nifti_mrs.nifti_mrs import NIFTI_MRS, NIFTIMRS_DimDoesntExist from nifti_mrs.validator import headerExtensionError from nifti_mrs.create_nmrs import gen_nifti_mrs, gen_nifti_mrs_hdr_ext +from nifti_mrs import tools # Files testsPath = Path(__file__).parent @@ -172,6 +173,43 @@ def test_set_dim_tag(): header={'EchoTime': np.arange(16).tolist()}) assert nmrs.hdr_ext['dim_6_header'] == {'EchoTime': np.arange(16).tolist()} + # Produce singleton data + _, singleton = tools.split(nmrs, 'DIM_DYN', [0,]) + assert singleton.shape == (1, 1, 1, 4096, 4, 1) + + singleton.set_dim_tag(5, None) + assert singleton.shape == (1, 1, 1, 4096, 4) + + # Run twice to check setting None on None is fine + singleton.set_dim_tag(5, None) + assert singleton.shape == (1, 1, 1, 4096, 4) + + # Restore + singleton.set_dim_tag(5, "DIM_DYN") + assert singleton.shape == (1, 1, 1, 4096, 4, 1) + + # Run using string + singleton.set_dim_tag("DIM_DYN", None) + assert singleton.shape == (1, 1, 1, 4096, 4) + + with pytest.raises( + NIFTIMRS_DimDoesntExist, + match="DIM_DYN doesn't exist in list of tags: ['DIM_COIL', None, None]"): + singleton.set_dim_tag("DIM_DYN", None) + + with pytest.raises( + ValueError, + match="Tag cannot be set to None for non-singleton dimension"): + singleton.set_dim_tag("DIM_COIL", None) + + _, singleton_non_final = tools.split(nmrs, 'DIM_COIL', [0,]) + assert singleton_non_final.shape == (1, 1, 1, 4096, 1, 16) + + with pytest.raises( + ValueError, + match="Tag can only be set to None for trailing singleton dimension."): + singleton_non_final.set_dim_tag("DIM_COIL", None) + def test_nifti_mrs_filename(): obj = NIFTI_MRS(data['unprocessed'])