diff --git a/CHANGELOG.md b/CHANGELOG.md index b6442ec..f8904a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ This document contains the Spec2nii release history in reverse chronological order. -0.7.2 (WIP) ---------------------------------- +0.7.2 (Thursday 7th December 2023) +---------------------------------- - SpectralWidth now added to header extension automatically to match bids specification. - NIfTI-MRS V0.8 now generated. +- Better handling of philips spar/sdat tags and singleton dimensions. 0.7.1 (Tuesday 7th November 2023) --------------------------------- diff --git a/spec2nii/Philips/philips.py b/spec2nii/Philips/philips.py index e04fce0..fe1be0b 100644 --- a/spec2nii/Philips/philips.py +++ b/spec2nii/Philips/philips.py @@ -12,6 +12,8 @@ from spec2nii.nifti_orientation import NIFTIOrient, calc_affine from spec2nii import __version__ as spec2nii_ver +default_tag_order = ['DIM_DYN', 'DIM_EDIT', 'DIM_USER_0'] + def read_sdat_spar_pair(sdat_file, spar_file, shape=None, tags=None, fileout=None, special=None): """Read and convert SPAR/SDAT pairs from Philips scanners @@ -55,9 +57,19 @@ def read_sdat_spar_pair(sdat_file, spar_file, shape=None, tags=None, fileout=Non meta = spar_to_nmrs_hdrext(spar_params) meta.set_standard_def('OriginalFile', [sdat_file.name]) - for idx, tag in enumerate(tags): + # Sort dimension tags + # First ensure user defined tags has length of 3. + while len(tags) < 3: + tags.append(None) + + # Set user defined tags, or the default if the dimension exists + # and is larger than 1 + for idx, (tag, default) in enumerate(zip(tags, default_tag_order)): + npdim = idx + 4 if tag is not None: meta.set_dim_info(idx, tag) + elif data.ndim > npdim and data.shape[npdim] > 1: + meta.set_dim_info(idx, default) # Orientation if spar_params["volume_selection_enable"] == "yes": diff --git a/spec2nii/spec2nii.py b/spec2nii/spec2nii.py index 9ea7897..86f78d9 100644 --- a/spec2nii/spec2nii.py +++ b/spec2nii/spec2nii.py @@ -108,8 +108,10 @@ def add_common_parameters(subparser): "-t", "--tags", type=str, nargs='+', - default=["DIM_DYN", None, None], - help="Specify NIfTI MRS tags used for higher (5th-7th) dimensions.") + default=[None, None, None], + help="Specify NIfTI MRS tags used for higher (5th-7th) dimensions. " + "Defaults to DIM_DYN if more than one spectrum is present. " + "Can be used to create singleton higher dimensions.") parser_philips.add_argument( "-s", "--shape", type=int, diff --git a/tests/spec2nii_test_data b/tests/spec2nii_test_data index e0ad587..ca9850c 160000 --- a/tests/spec2nii_test_data +++ b/tests/spec2nii_test_data @@ -1 +1 @@ -Subproject commit e0ad5870cd5891bbe3fdcf9415845cde1f194884 +Subproject commit ca9850cac4f64b4a1f402d58c73a665492f68b9a diff --git a/tests/test_philips_sdat_spar.py b/tests/test_philips_sdat_spar.py index d9a99a7..2f5b8e0 100644 --- a/tests/test_philips_sdat_spar.py +++ b/tests/test_philips_sdat_spar.py @@ -11,12 +11,16 @@ import numpy as np from .io_for_tests import read_nifti_mrs +from nifti_mrs.nifti_mrs import NIFTI_MRS # Data paths philips_path = Path(__file__).parent / 'spec2nii_test_data' / 'philips' svs_path_sdat = philips_path / 'P1' / 'SV_PRESS_sh_6_2_raw_act.SDAT' svs_path_spar = philips_path / 'P1' / 'SV_PRESS_sh_6_2_raw_act.SPAR' +svs_ma_path_sdat = philips_path / 'spar_multi_avg' / 'multi_avg.sdat' +svs_ma_path_spar = philips_path / 'spar_multi_avg' / 'multi_avg.spar' + svs_edit_path_sdat = philips_path / 'HERCULES_spar_sdat' / 'HERCULES_Example_noID.sdat' svs_edit_path_spar = philips_path / 'HERCULES_spar_sdat' / 'HERCULES_Example_noID.spar' @@ -50,6 +54,66 @@ def test_svs(tmp_path): assert hdr_ext['OriginalFile'][0] == svs_path_sdat.name assert hdr_ext['SoftwareVersions'] == '5.5.2 ; .5.2 ;' + # Check no hanging singleton dimension + nmrs_obj = NIFTI_MRS(tmp_path / 'svs.nii.gz') + assert nmrs_obj.shape == (1, 1, 1, 2048) + + subprocess.check_call([ + 'spec2nii', 'philips', + '-t', 'DIM_DYN', + '-f', 'svs_singleton', + '-o', tmp_path, + '-j', + str(svs_path_sdat), + str(svs_path_spar)]) + + img_t = read_nifti_mrs(tmp_path / 'svs_singleton.nii.gz') + assert img_t.shape == (1, 1, 1, 2048) + + nmrs_obj = NIFTI_MRS(tmp_path / 'svs_singleton.nii.gz') + assert nmrs_obj.shape == (1, 1, 1, 2048, 1) + + hdr_ext_codes = img_t.header.extensions.get_codes() + hdr_ext = json.loads(img_t.header.extensions[hdr_ext_codes.index(44)].get_content()) + assert hdr_ext['dim_5'] == 'DIM_DYN' + + +def test_multiavg_svs(tmp_path): + + subprocess.check_call(['spec2nii', 'philips', + '-f', 'svs', + '-o', tmp_path, + '-j', + str(svs_ma_path_sdat), + str(svs_ma_path_spar)]) + + img_t = read_nifti_mrs(tmp_path / 'svs.nii.gz') + + assert img_t.shape == (1, 1, 1, 2048, 10) + assert np.iscomplexobj(img_t.dataobj) + + hdr_ext_codes = img_t.header.extensions.get_codes() + hdr_ext = json.loads(img_t.header.extensions[hdr_ext_codes.index(44)].get_content()) + + assert hdr_ext['dim_5'] == 'DIM_DYN' + + # Check override tags + subprocess.check_call([ + 'spec2nii', 'philips', + '-t', 'DIM_USER_0', + '-f', 'svs_2', + '-o', tmp_path, + '-j', + str(svs_ma_path_sdat), + str(svs_ma_path_spar)]) + + img_t = read_nifti_mrs(tmp_path / 'svs_2.nii.gz') + assert img_t.shape == (1, 1, 1, 2048, 10) + + hdr_ext_codes = img_t.header.extensions.get_codes() + hdr_ext = json.loads(img_t.header.extensions[hdr_ext_codes.index(44)].get_content()) + assert hdr_ext['dim_5'] == 'DIM_USER_0' + def test_svs_edit(tmp_path):