From acd024ac5d5e453340e5ea591bb54049477e558d Mon Sep 17 00:00:00 2001 From: William T Clarke Date: Wed, 15 Mar 2023 12:04:50 +0000 Subject: [PATCH] ENH: spec2nii insert handles non-compliant NIfTI files. (#70) * spec2nii insert handles non-compliant nifti files. * Correct date in changelog. --- CHANGELOG.md | 5 ++++ spec2nii/other.py | 26 ++++++++++++++++--- spec2nii/spec2nii.py | 2 ++ tests/test_other_func.py | 56 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a91454..302152f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ This document contains the Spec2nii release history in reverse chronological order. +0.6.7 (Wednesday 15th March 2023) +--------------------------------- +- `spec2nii insert` can now insert a compliant header object into a non-compliant NIfTI file to generate a new NIfTI-MRS file. +- `spec2nii insert` can now update dwelltime using the optional `--dwelltime` argument. + 0.6.6 (Thursday 9th March 2023) ------------------------------- - Added in ability to generate empty NIfTI-MRS for twix-pathway MRSI scans. diff --git a/spec2nii/other.py b/spec2nii/other.py index 7861943..7a1010e 100644 --- a/spec2nii/other.py +++ b/spec2nii/other.py @@ -6,7 +6,7 @@ import json import pprint -from nifti_mrs.nifti_mrs import NIFTI_MRS +from nifti_mrs.nifti_mrs import NIFTI_MRS, NotNIFTI_MRS def dump_headers(args): @@ -52,13 +52,31 @@ def insert_hdr_ext(args): :return: List of output names :rtype: [str,] """ - # Load data - nifti_mrs_img = NIFTI_MRS(args.file) with open(args.json_file) as jf: new_hdr = json.load(jf) - nifti_mrs_img.hdr_ext = new_hdr + # Load data + try: + nifti_mrs_img = NIFTI_MRS(args.file) + nifti_mrs_img.hdr_ext = new_hdr + + except NotNIFTI_MRS: + print(f'{args.file} is not compliant with the NIfTI-MRS standard, attempting to convert') + from fsl.data.image import Image + from nifti_mrs.create_nmrs import gen_nifti_mrs_hdr_ext + from nifti_mrs.hdr_ext import Hdr_Ext + + nimg = Image(args.file) + hdr_ext = Hdr_Ext.from_header_ext(new_hdr, dimensions=nimg.ndim) + nifti_mrs_img = gen_nifti_mrs_hdr_ext( + nimg[:], + nimg.header['pixdim'][4], + hdr_ext, + affine=nimg.getAffine('voxel', 'world')) + + if args.dwelltime is not None: + nifti_mrs_img.dwelltime = args.dwelltime # Process output name. if args.fileout: diff --git a/spec2nii/spec2nii.py b/spec2nii/spec2nii.py index 48195d6..e999032 100644 --- a/spec2nii/spec2nii.py +++ b/spec2nii/spec2nii.py @@ -252,6 +252,8 @@ def add_common_parameters(subparser): parser_insert = subparsers.add_parser('insert', help='Insert json formatted file into existing NIfTI-MRS file.') parser_insert.add_argument('file', help='NIFTI-MRS file', type=Path) parser_insert.add_argument('json_file', help='JSON file to insert', type=Path) + parser_insert.add_argument("--dwelltime", type=float, + help="Specify a new dwelltime (1/bandwidth, pixdim[4]) value in seconds.") parser_insert.add_argument("-f", "--fileout", type=str, help="Output file base name (default = input file name)") parser_insert.add_argument("-o", "--outdir", type=Path, diff --git a/tests/test_other_func.py b/tests/test_other_func.py index 169189c..3d0772b 100644 --- a/tests/test_other_func.py +++ b/tests/test_other_func.py @@ -5,10 +5,14 @@ ''' import subprocess -from .io_for_tests import read_nifti_mrs from pathlib import Path import json +import numpy as np +from fsl.data.image import Image + +from .io_for_tests import read_nifti_mrs + # Data paths siemens_path = Path(__file__).parent / 'spec2nii_test_data' / 'Siemens' data_path = siemens_path / 'VBData' / 'Twix' / 'meas_MID151_svs_se_C_T15_S10_10_FID108741.dat' @@ -74,6 +78,7 @@ def test_insert(tmp_path): subprocess.check_call(['spec2nii', 'insert', '-o', tmp_path, '-f', 'new', + '--dwelltime', '0.0005', str(tmp_path / 'original.nii.gz'), str(tmp_path / 'new.json')]) @@ -82,3 +87,52 @@ def test_insert(tmp_path): hdr_ext = json.loads(img.header.extensions[hdr_ext_codes.index(44)].get_content()) assert extracted_hdr == hdr_ext + assert np.isclose(img.header['pixdim'][4], 0.0005) + + +def test_insert_not_nmrs(tmp_path): + # Create a small dummy file that doesn't conform to the standard + + affine = np.random.random((4, 4)) + affine[3, 0] = 0.0 + affine[3, 1] = 0.0 + affine[3, 2] = 0.0 + affine[3, 3] = 1.0 + + data = np.ones((3, 3, 3, 256), dtype=float) + 1j * np.ones((3, 3, 3, 256), dtype=float) + + non_compliant = Image(data, header=None, xform=affine) + non_compliant.header['pixdim'][4] = 1 / 1000 + non_compliant.save(tmp_path / 'noncompliant.nii.gz') + + newhd = { + 'SpectrometerFrequency': 123.456789, + 'ResonantNucleus': '1H'} + + with open(tmp_path / 'new.json', 'w') as jf: + json.dump(newhd, jf) + + subprocess.check_call(['spec2nii', 'insert', + '-o', tmp_path, + '-f', 'compliant', + str(tmp_path / 'noncompliant.nii.gz'), + str(tmp_path / 'new.json')]) + + img = read_nifti_mrs(tmp_path / 'compliant.nii.gz') + hdr_ext_codes = img.header.extensions.get_codes() + hdr_ext = json.loads(img.header.extensions[hdr_ext_codes.index(44)].get_content()) + + assert hdr_ext['ResonantNucleus'][0] == '1H' + assert img.header.get_intent()[2].split('_')[0] == 'mrs' + assert np.allclose(img.affine, affine) + assert np.isclose(img.header['pixdim'][4], 1 / 1000) + + subprocess.check_call(['spec2nii', 'insert', + '-o', tmp_path, + '-f', 'compliant_dt', + '--dwelltime', '0.0005', + str(tmp_path / 'noncompliant.nii.gz'), + str(tmp_path / 'new.json')]) + + img = read_nifti_mrs(tmp_path / 'compliant_dt.nii.gz') + assert np.isclose(img.header['pixdim'][4], 0.0005)