Commit e07fc8a1 authored by William Clarke's avatar William Clarke
Browse files

Add new module and tests for nifti-mrs operations. Implemented split.

parent 68b29d1a
"""Test the split, merge and reorder tools for NIFTI-MRS
Author: Will Clarke <william.clarke@ndcn.ox.ac.uk>
Copyright (C) 2021 University of Oxford
"""
from pathlib import Path
import pytest
import numpy as np
from fsl_mrs.utils import mrs_io
from fsl_mrs.utils.preproc import nifti_mrs_tools as nmrs_tools
testsPath = Path(__file__).parent
test_data_split = testsPath / 'testdata' / 'fsl_mrs_preproc' / 'metab_raw.nii.gz'
test_data_merge = testsPath / 'testdata' / 'fsl_mrs_preproc' / 'wref_raw.nii.gz'
def test_split():
"""Test the split functionality
"""
nmrs = mrs_io.read_FID(test_data_split)
# Error testing
# Wrong dim tag
with pytest.raises(ValueError) as exc_info:
nmrs_tools.split(nmrs, 'DIM_EDIT', 1)
assert exc_info.type is ValueError
assert exc_info.value.args[0] == "DIM_EDIT not found as dimension tag."\
" This data contains ['DIM_COIL', 'DIM_DYN', None]."
# Wrong dim index (no dim in this data)
with pytest.raises(ValueError) as exc_info:
nmrs_tools.split(nmrs, 6, 1)
assert exc_info.type is ValueError
assert exc_info.value.args[0] == "Dimension must be one of 4, 5, or 6 (or DIM_TAG string)."\
" This data has 6 dimensions,"\
" i.e. a maximum dimension value of 5."
# Wrong dim index (too low)
with pytest.raises(ValueError) as exc_info:
nmrs_tools.split(nmrs, 3, 1)
assert exc_info.type is ValueError
assert exc_info.value.args[0] == "Dimension must be one of 4, 5, or 6 (or DIM_TAG string)."\
" This data has 6 dimensions,"\
" i.e. a maximum dimension value of 5."
# Wrong dim index type
with pytest.raises(TypeError) as exc_info:
nmrs_tools.split(nmrs, [3, ], 1)
assert exc_info.type is TypeError
assert exc_info.value.args[0] == "Dimension must be an int (4, 5, or 6) or string (DIM_TAG string)."
# Single index - out of range low
with pytest.raises(ValueError) as exc_info:
nmrs_tools.split(nmrs, 'DIM_DYN', -1)
assert exc_info.type is ValueError
assert exc_info.value.args[0] == "index_or_indicies must be between 0 and N-1,"\
" where N is the size of the specified dimension (64)."
# Single index - out of range high
with pytest.raises(ValueError) as exc_info:
nmrs_tools.split(nmrs, 'DIM_DYN', 64)
assert exc_info.type is ValueError
assert exc_info.value.args[0] == "index_or_indicies must be between 0 and N-1,"\
" where N is the size of the specified dimension (64)."
# List of indicies - out of range low
with pytest.raises(ValueError) as exc_info:
nmrs_tools.split(nmrs, 'DIM_DYN', [-1, 0, 1])
assert exc_info.type is ValueError
assert exc_info.value.args[0] == "index_or_indicies must have elements between 0 and N,"\
" where N is the size of the specified dimension (64)."
# List of indicies - out of range high
with pytest.raises(ValueError) as exc_info:
nmrs_tools.split(nmrs, 'DIM_DYN', [0, 65])
assert exc_info.type is ValueError
assert exc_info.value.args[0] == "index_or_indicies must have elements between 0 and N,"\
" where N is the size of the specified dimension (64)."
# List of indicies - wrong type
with pytest.raises(TypeError) as exc_info:
nmrs_tools.split(nmrs, 'DIM_DYN', '1')
assert exc_info.type is TypeError
assert exc_info.value.args[0] == "index_or_indicies must be single index or list of indicies"
# Functionality testing
out_1, out_2 = nmrs_tools.split(nmrs, 'DIM_DYN', 32)
assert out_1.data.shape == (1, 1, 1, 4096, 32, 32)
assert out_2.data.shape == (1, 1, 1, 4096, 32, 32)
assert np.allclose(out_1.data, nmrs.data[:, :, :, :, :, 0:32])
assert np.allclose(out_2.data, nmrs.data[:, :, :, :, :, 32:])
assert out_1.hdr_ext == nmrs.hdr_ext
assert out_1.hdr_ext == nmrs.hdr_ext
assert np.allclose(out_1.getAffine('voxel', 'world'), nmrs.getAffine('voxel', 'world'))
assert np.allclose(out_2.getAffine('voxel', 'world'), nmrs.getAffine('voxel', 'world'))
out_1, out_2 = nmrs_tools.split(nmrs, 'DIM_DYN', [0, 32, 63])
assert out_1.data.shape == (1, 1, 1, 4096, 32, 61)
assert out_2.data.shape == (1, 1, 1, 4096, 32, 3)
test_list = np.arange(0, 64)
test_list = np.delete(test_list, [0, 32, 63])
assert np.allclose(out_1.data, nmrs.data[:, :, :, :, :, test_list])
assert np.allclose(out_2.data, nmrs.data[:, :, :, :, :, [0, 32, 63]])
"""Tools for merging, splitting and reordering the dimensions of NIfTI-MRS
Author: Will Clarke <william.clarke@ndcn.ox.ac.uk>
Copyright (C) 2021 University of Oxford
"""
import numpy as np
from fsl_mrs.core.nifti_mrs import NIFTI_MRS, NIFTIMRS_DimDoesntExist
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.
:param nmrs: Input nifti_mrs object to split
:type nmrs: fsl_mrs.core.nifti_mrs.NIFTI_MRS
:param dimension: Dimension tag or one of 4, 5, 6 (for 0-indexed 5th, 6th, and 7th)
:type dimension: str or int
:param index_or_indicies: Single integer index to split after,
or list of interger indices to insert into second array.
E.g. '0' will place the first index into the first output
and 1 -> N in the second.
'[1, 5, 10]' will place 1, 5 and 10 into the second output
and all other will remain in the first.
:type index_or_indicies: int or [int]
:return: Two NIFTI_MRS object containing the split files
:rtype: fsl_mrs.core.nifti_mrs.NIFTI_MRS
"""
if isinstance(dimension, str):
try:
dim_index = nmrs.dim_position(dimension)
except NIFTIMRS_DimDoesntExist:
raise ValueError(f'{dimension} not found as dimension tag. This data contains {nmrs.dim_tags}.')
elif isinstance(dimension, int):
if dimension > (nmrs.ndim - 1) or dimension < 4:
raise ValueError('Dimension must be one of 4, 5, or 6 (or DIM_TAG string).'
f' This data has {nmrs.ndim} dimensions,'
f' i.e. a maximum dimension value of {nmrs.ndim-1}.')
dim_index = dimension
else:
raise TypeError('Dimension must be an int (4, 5, or 6) or string (DIM_TAG string).')
# Construct indexing
if isinstance(index_or_indicies, int):
if index_or_indicies < 0\
or index_or_indicies >= nmrs.shape[dim_index]:
raise ValueError('index_or_indicies must be between 0 and N-1,'
f' where N is the size of the specified dimension ({nmrs.shape[dim_index]}).')
index = np.arange(index_or_indicies, nmrs.shape[dim_index])
elif isinstance(index_or_indicies, list):
if not np.logical_and(np.asarray(index_or_indicies) >= 0,
np.asarray(index_or_indicies) <= nmrs.shape[dim_index]).all():
raise ValueError('index_or_indicies must have elements between 0 and N,'
f' where N is the size of the specified dimension ({nmrs.shape[dim_index]}).')
index = index_or_indicies
else:
raise TypeError('index_or_indicies must be single index or list of indicies')
nmrs_1 = NIFTI_MRS(np.delete(nmrs.data, index, axis=dim_index), header=nmrs.header)
nmrs_2 = NIFTI_MRS(np.take(nmrs.data, index, axis=dim_index), header=nmrs.header)
return nmrs_1, nmrs_2
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