Commit 0715bece authored by William Clarke's avatar William Clarke
Browse files

Reorder functionality and mergin along singleton dimension added.

parent 6408e80a
......@@ -106,12 +106,11 @@ class NIFTI_MRS(Image):
std_tags = ['DIM_COIL', 'DIM_DYN', 'DIM_INDIRECT_0']
for idx in range(3):
curr_dim = idx + 5
if self.ndim >= curr_dim:
curr_tag = f'dim_{curr_dim}'
if curr_tag in self.hdr_ext:
self.dim_tags[idx] = self.hdr_ext[curr_tag]
else:
self.dim_tags[idx] = std_tags[idx]
curr_tag = f'dim_{curr_dim}'
if curr_tag in self.hdr_ext:
self.dim_tags[idx] = self.hdr_ext[curr_tag]
elif curr_dim < self.ndim:
self.dim_tags[idx] = std_tags[idx]
def __getitem__(self, sliceobj):
'''Apply conjugation at use. This swaps from the
......@@ -171,6 +170,7 @@ class NIFTI_MRS(Image):
extension = Nifti1Extension(44, json_s.encode('UTF-8'))
self.header.extensions.clear()
self.header.extensions.append(extension)
self._set_dim_tags()
def dim_position(self, dim_tag):
'''Return position of dim if it exists.'''
......
......@@ -186,3 +186,47 @@ def test_merge():
assert np.allclose(out.data[:, :, :, :, :, 2:], nmrs_2.data)
assert out.hdr_ext == nmrs_1.hdr_ext
assert np.allclose(out.getAffine('voxel', 'world'), nmrs_1.getAffine('voxel', 'world'))
# Merge along squeezed singleton
nmrs_1_e = nmrs_tools.reorder(nmrs_1, ['DIM_COIL', 'DIM_DYN', 'DIM_EDIT'])
nmrs_2_e = nmrs_tools.reorder(nmrs_2, ['DIM_COIL', 'DIM_DYN', 'DIM_EDIT'])
out = nmrs_tools.merge((nmrs_1_e, nmrs_2_e), 'DIM_EDIT')
assert out.data.shape == (1, 1, 1, 4096, 32, 2, 2)
assert out.hdr_ext['dim_7'] == 'DIM_EDIT'
def test_reorder():
"""Test the reorder functionality
"""
nmrs = mrs_io.read_FID(test_data_split)
# Error testing
# Miss existing tag
with pytest.raises(nmrs_tools.NIfTI_MRSIncompatible) as exc_info:
nmrs_tools.reorder(nmrs, ['DIM_COIL', 'DIM_EDIT'])
assert exc_info.type is nmrs_tools.NIfTI_MRSIncompatible
assert exc_info.value.args[0] == "The existing tag (DIM_DYN) does not appear"\
" in the requested tag order (['DIM_COIL', 'DIM_EDIT'])."
# Functionality testing
# Swap order of dimensions
out = nmrs_tools.reorder(nmrs, ['DIM_DYN', 'DIM_COIL'])
assert out.data.shape == (1, 1, 1, 4096, 64, 32)
assert np.allclose(np.swapaxes(nmrs.data, 4, 5), out.data)
assert out.hdr_ext['dim_5'] == 'DIM_DYN'
assert out.hdr_ext['dim_6'] == 'DIM_COIL'
# # Add an additional singleton at end (not reported in shape)
out = nmrs_tools.reorder(nmrs, ['DIM_COIL', 'DIM_DYN', 'DIM_EDIT'])
assert out.data.shape == (1, 1, 1, 4096, 32, 64)
assert out.hdr_ext['dim_5'] == 'DIM_COIL'
assert out.hdr_ext['dim_6'] == 'DIM_DYN'
assert out.hdr_ext['dim_7'] == 'DIM_EDIT'
# Add an additional singleton at 5 (not reported in shape)
out = nmrs_tools.reorder(nmrs, ['DIM_EDIT', 'DIM_COIL', 'DIM_DYN'])
assert out.data.shape == (1, 1, 1, 4096, 1, 32, 64)
assert out.hdr_ext['dim_5'] == 'DIM_EDIT'
assert out.hdr_ext['dim_6'] == 'DIM_COIL'
assert out.hdr_ext['dim_7'] == 'DIM_DYN'
......@@ -3,11 +3,18 @@
Author: Will Clarke <william.clarke@ndcn.ox.ac.uk>
Copyright (C) 2021 University of Oxford
"""
import re
import json
import numpy as np
from nibabel.nifti1 import Nifti1Extension
from fsl_mrs.core.nifti_mrs import NIFTI_MRS, NIFTIMRS_DimDoesntExist
class NIfTI_MRSIncompatible(Exception):
pass
def split(nmrs, dimension, index_or_indicies):
"""Splits, or extracts indices from, a specified dimension of a
NIFTI_MRS object. Output is two NIFTI_MRS objects. Header information preserved.
......@@ -66,10 +73,6 @@ def split(nmrs, dimension, index_or_indicies):
return nmrs_1, nmrs_2
class NIfTI_MRSIncompatible(Exception):
pass
def merge(array_of_nmrs, dimension):
"""Concatenate NIfTI-MRS objects along specified higher dimension
......@@ -119,11 +122,86 @@ def merge(array_of_nmrs, dimension):
raise NIfTI_MRSIncompatible('The shape of all concatentated objects must match.'
f' The shape ({nmrs.shape}) of the {idx} object does'
f' not match that of the first ({array_of_nmrs[0].shape}).')
# Check dim tags for compatibility
if not check_tag(nmrs):
raise NIfTI_MRSIncompatible('The tags of all concatentated objects must match.'
f' The tags ({nmrs.dim_tags}) of the {idx} object does'
f' not match that of the first ({array_of_nmrs[0].dim_tags}).')
# Check dim tags for compatibility
to_concat.append(nmrs.data)
if nmrs.ndim == dim_index:
# If a squeezed singleton on the end.
to_concat.append(np.expand_dims(nmrs.data, -1))
else:
to_concat.append(nmrs.data)
return NIFTI_MRS(np.concatenate(to_concat, axis=dim_index), header=array_of_nmrs[0].header)
def reorder(nmrs, dim_tag_list):
"""Reorder the higher dimensions of a NIfTI-MRS object.
Can force a singleton dimension with new tag.
:param nmrs: NIFTI-MRS object to reorder.
:type nmrs: fsl_mrs.core.nifti_mrs.NIFTI_MRS
:param dim_tag_list: List of dimension tags in desired order
:type dim_tag_list: List of str
:return: Reordered NIfTI-MRS object.
:rtype: fsl_mrs.core.nifti_mrs.NIFTI_MRS
"""
# Check existing tags are in the list of desired tags
for idx, tag in enumerate(nmrs.dim_tags):
if tag not in dim_tag_list\
and tag is not None:
raise NIfTI_MRSIncompatible(f'The existing tag ({tag}) does not appear '
f'in the requested tag order ({dim_tag_list}).')
# Create singleton dimensions if required
original_dims = nmrs.ndim
new_dim = sum(x is not None for x in nmrs.dim_tags) + 4
dims_to_add = tuple(range(original_dims, new_dim + 1))
data_with_singleton = np.expand_dims(nmrs.data, dims_to_add)
# Create list of source indicies
# Create list of destination indicies
# Keep track of singleton tags
source_indicies = []
dest_indicies = []
singleton_tags = {}
counter = 0
for idx, tag in enumerate(dim_tag_list):
if tag is not None:
if tag in nmrs.dim_tags:
source_indicies.append(nmrs.dim_tags.index(tag) + 4)
else:
source_indicies.append(nmrs.ndim + counter)
counter += 1
singleton_tags.update({(idx + 5): tag})
dest_indicies.append(idx + 4)
# Sort header_ext dim_tags
dim_n = re.compile(r'dim_[567].*')
new_hdr_dict = {}
for key in nmrs.hdr_ext:
if dim_n.match(key):
new_index = dest_indicies[source_indicies.index(int(key[4]) - 1)] + 1
new_key = 'dim_' + str(new_index) + key[5:]
new_hdr_dict.update({new_key: nmrs.hdr_ext[key]})
else:
new_hdr_dict.update({key: nmrs.hdr_ext[key]})
# For any singleton dimensions we've added
for dim in singleton_tags:
new_hdr_dict.update({f'dim_{dim}': singleton_tags[dim]})
new_header = nmrs.header.copy()
json_s = json.dumps(new_hdr_dict)
extension = Nifti1Extension(44, json_s.encode('UTF-8'))
new_header.extensions.clear()
new_header.extensions.append(extension)
new_nmrs = NIFTI_MRS(np.moveaxis(data_with_singleton, source_indicies, dest_indicies),
header=new_header)
return new_nmrs
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment