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

Fix issue with MRSI example fitting and TE in fsl_mrsi.

parent 50f5e3e6
......@@ -2,6 +2,11 @@ This document contains the FSL-MRS release history in reverse chronological orde
1.1.5 (WIP)
-------------------------------
- Updated example MRSI data to conform to NIfTI-MRS standard.
- Quantification will not fail if volume fractions do not sum exactly to 1.0.
- fixed bug in fsl_mrsi looking for TE in wrong header structure.
- New mrs_tools command 'conjugate' to help fix NIfTI-MRS data with the wrong phase/frequency convention.
- basis_tools remove has number of HLSVD components reduced to stop odd broad resonance behaviour.
- fsl_mrs_proc align can now align across all higher dimension FIDs. Pass 'all' as dimension tag.
- New command "fsl_mrs_proc model". HSLVD modelling of peaks in defined region. Number of components settable.
- Updates to basis set simulator. Non-uniform slice select gradients are now handled.
......
%% Cell type:markdown id: tags:
# Example of MRSI fitting on the command line
This notebook demos the process of fitting an MRSI scan using the command line scripts included in FSL-MRS.
### Contents:
- [1. Reconstruction, Processing and Data Conversion](#1.-Reconstruction,-processing-and-data-conversion)
- [2. Segmentation](#2.-Tissue-segmentation)
- [3. Fitting](#3.-Fitting)
- [4. Visualisation of fit](#4.-Visualisation-of-fit)
Will Clarke
June 2020
University of Oxford
%% Cell type:markdown id: tags:
## 1. Reconstruction, processing and data conversion
MRSI reconstruction (from k-space data) can be highly specialised depending on the sequence. For example reconstruction of non-cartesian trajectories (e.g. spirals or concentric-rings) requires regridding or use of the NUFFT. Due to the specialised nature of MRSI reconstruction FSL-MRS does not contain tools for this step.
Simmilarly post-processing of MRSI data is commonly done as part of the reconstruction process. Though many of the processing tools in FSL-MRS can be run on MRSI data they have not been created with MRSI in mind.
This example therefore assumes that you are able to provide reconstructed and processed data ready for fitting. Data can be converted to NIfTI from the Siemens DICOM format using spec2nii. The authors of FSL-MRS and spec2nii are happy to add supported formats if example data and interpretation is provided.
%% Cell type:markdown id: tags:
## 2. Tissue segmentation
Tissue segmentation is required to have meaningful scaling of water referenced metabolite concentrations. mrsi_segment will produce three files, corresponding to the GM, WM and CSF FAST tissue segmentations registered to the MRSI volume.
Run tissue segmentation on the packaged T1 data and mask using the MRSI data file. Here we provide a (partial) .anat file produced by [fsl_anat](https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/fsl_anat) to speed up execution.
This step requires an installation of FSL compatible with fslpy.
%% Cell type:code id: tags:
``` python
#%%capture
%sx mkdir -p MRSI
%sx mrsi_segment -o MRSI -a example_data/example_mrsi/T1.anat example_data/example_mrsi/mrsi.nii.gz
```
%% Cell type:markdown id: tags:
## 3. Fitting
The below is a call to the main MRSI wrapper script. Not the use of the :code:`--add_MM` flag as this metabolite basis does not contain a macromolecule basis. Also note that the mrsi dataset provided is accompagnied by a JSON sidecar file which contains some useful information such as the echo time, the central frequency, and the dwell time.
The below is a call to the main MRSI wrapper script. Note the use of the `--add_MM` flag as this metabolite basis does not contain a macromolecule basis. Also note that the mrsi dataset provided is accompagnied by a JSON sidecar file which contains some useful information such as the echo time, the central frequency, and the dwell time.
The script will by default run in parallel on the available CPU cores. Depending on hardware this should take a few minutes.
%% Cell type:code id: tags:
``` python
%sx fsl_mrsi --data example_data/example_mrsi/mrsi.nii.gz \
--basis example_data/example_mrsi/3T_slaser_32vespa_1250.BASIS \
--output MRSI/example_mrsi_fit \
--mask example_data/example_mrsi/mask.nii.gz \
--h2o example_data/example_mrsi/wref.nii.gz \
--tissue_frac MRSI/mrsi_seg_wm.nii.gz MRSI/mrsi_seg_gm.nii.gz MRSI/mrsi_seg_csf.nii.gz \
--add_MM \
--baseline_order 2 \
--combine PCho GPC --combine Cr PCr --combine NAA NAAG --combine Glu Gln --combine Glc Tau \
--ignore Gly HG \
--ignore Gly Gly_1 HG \
--overwrite
```
%% Cell type:markdown id: tags:
## 4. Visualisation of fit
Now take a look at the outputs. A PNG of the fit to the average of all voxels is provided for a quick sanity check. The folders contain the following:
- concs : concentration for each metabolite or combined metabolites (subfolders contain different types of referencing)
- fit : the model prediction FID, the residual FID, and the baseline (also in the time domain).
- qc : QC parameters split per metabolite
- uncertainties : the std of the fit per metabolite
%% Cell type:code id: tags:
``` python
%sx ls -l MRSI/example_mrsi_fit
```
%% Cell type:markdown id: tags:
Now run the command below in a terminal in order to load the data in FSLeyes. Follow the instructions [here](https://users.fmrib.ox.ac.uk/~saad/fsl_mrs/html/visualisation.html#fsleyes) to set it up in such a way that you can explore each voxel's individual fit.
Now run the command below in a terminal in order to load the data in FSLeyes. You will need to install the NIfTI-MRS plugin for FSLeyes. Instructions for installation are available [online](https://open.win.ox.ac.uk/pages/wclarke/fsleyes-plugin-mrs/install.html).
Then follow the the instructions [here](https://open.win.ox.ac.uk/pages/wclarke/fsleyes-plugin-mrs/mrsi_results.html) to set it up in such a way that you can explore each voxel's individual fit.
To get started, after installing the plugin, run the following command:
```
fsleyes example_data/example_mrsi/T1.anat/T1.nii.gz example_data/example_mrsi/mrsi.nii.gz MRSI/example_mrsi_fit/fit/fit.nii.gz MRSI/example_mrsi_fit/fit/baseline.nii.gz MRSI/example_mrsi_fit/concs/internal/NAA+NAAG.nii.gz
fsleyes -smrs example_data/example_mrsi/T1.anat/T1.nii.gz example_data/example_mrsi/mrsi.nii.gz
```
%% Cell type:code id: tags:
``` python
%sx fsleyes example_data/example_mrsi/T1.anat/T1.nii.gz example_data/example_mrsi/mrsi.nii.gz MRSI/example_mrsi_fit/fit/fit.nii.gz MRSI/example_mrsi_fit/fit/baseline.nii.gz MRSI/example_mrsi_fit/concs/internal/NAA+NAAG.nii.gz
%sx fsleyes -smrs example_data/example_mrsi/T1.anat/T1.nii.gz example_data/example_mrsi/mrsi.nii.gz
```
......
This source diff could not be displayed because it is stored in LFS. You can view the blob instead.
This source diff could not be displayed because it is stored in LFS. You can view the blob instead.
......@@ -234,8 +234,8 @@ def main():
# Echo time
if args.TE is not None:
echotime = args.TE * 1E-3
elif 'TE' in mrsi.header:
echotime = mrsi.header['TE']
elif 'EchoTime' in mrsi_data.hdr_ext:
echotime = mrsi_data.hdr_ext['EchoTime']
else:
echotime = None
# Repetition time
......
......@@ -90,7 +90,7 @@ def main():
# Reorder tool
reorderparser = sp.add_parser(
'reorder',
help='Reorider higher dimensions of NIfTI-MRS.')
help='Reorder higher dimensions of NIfTI-MRS.')
reorderparser.add_argument('--file', type=Path, required=True,
help='File to reorder')
reorderparser.add_argument('--dim_order', type=str, nargs='+', required=True,
......@@ -104,6 +104,19 @@ def main():
help='Override output file names.')
reorderparser.set_defaults(func=reorder)
# conjugate tool
conjparser = sp.add_parser(
'conjugate',
help='Conjugate data to correct phase/frequency convention in a NIfTI-MRS file.')
conjparser.add_argument('--file', type=Path, required=True,
help='File to conjugate')
conjparser.add_argument('--output',
required=True, type=Path, default=Path('.'),
help='output folder (defaults to current directory)')
conjparser.add_argument('--filename', type=str,
help='Override output file names.')
conjparser.set_defaults(func=conj)
# Parse command-line arguments
args = p.parse_args()
......@@ -297,5 +310,28 @@ def reorder(args):
reordered.save(file_out)
def conj(args):
"""Conjugate the data in a nifti-mrs file
:param args: Argparse interpreted arguments
:type args: Namespace
"""
from fsl_mrs.utils import nifti_mrs_tools as nmrs_tools
from fsl_mrs.utils import mrs_io
# 1. Load the file
infile = mrs_io.read_FID(str(args.file))
name = args.file.with_suffix('').with_suffix('').name
# 2. conjugate the file
outfile = nmrs_tools.conjugate(infile)
# 3. Save the output file
if args.filename:
file_out = args.output / args.filename
else:
file_out = args.output / name
outfile.save(file_out)
if __name__ == '__main__':
main()
......@@ -157,3 +157,19 @@ def test_reorder(tmp_path):
'--file', str(test_data_split)])
assert (tmp_path / 'reordered_file.nii.gz').exists()
# Test conjugate option
def test_conjugate(tmp_path):
subprocess.check_call(['mrs_tools', 'conjugate',
'--output', str(tmp_path),
'--filename', 'conj_file',
'--file', str(svs)])
assert (tmp_path / 'conj_file.nii.gz').exists()
subprocess.check_call(['mrs_tools', 'conjugate',
'--output', str(tmp_path),
'--file', str(svs)])
assert (tmp_path / 'metab.nii.gz').exists()
"""Test the miscellaneous functions for NIFTI-MRS tools
from pathlib import Path
Author: Will Clarke <william.clarke@ndcn.ox.ac.uk>
Copyright (C) 2021 University of Oxford
"""
import numpy as np
import fsl_mrs.utils.nifti_mrs_tools.misc as misc
from fsl_mrs.utils import mrs_io
from fsl_mrs.utils import nifti_mrs_tools as nmrs_tools
def test_short_to_long():
dict_repr = misc.dim_n_header_short_to_long({'start': 0.0, 'increment': 0.1}, 3)
assert dict_repr == [0.0, 0.1, 0.2]
testsPath = Path(__file__).parent
test_data = testsPath / 'testdata' / 'fsl_mrs_preproc' / 'metab_raw.nii.gz'
dict_repr = misc.dim_n_header_short_to_long([0.0, 0.1, 0.2], 3)
assert dict_repr == [0.0, 0.1, 0.2]
dict_repr = misc.dim_n_header_short_to_long({'Value': [0.0, 0.1, 0.2], 'description': 'test'}, 3)
assert dict_repr == {'Value': [0.0, 0.1, 0.2], 'description': 'test'}
def test_conjugate():
# Data is (1, 1, 1, 4096, 32, 64) ['DIM_COIL', 'DIM_DYN', None]
nmrs = mrs_io.read_FID(test_data)
dict_repr = misc.dim_n_header_short_to_long({'Value': {'start': 0.0, 'increment': 0.1}, 'description': 'test'}, 3)
assert dict_repr == {'Value': [0.0, 0.1, 0.2], 'description': 'test'}
conjugated = nmrs_tools.conjugate(nmrs)
def test_long_to_short():
dict_repr = misc.dim_n_header_long_to_short([0.0, 0.1, 0.2])
assert dict_repr == {'start': 0.0, 'increment': 0.1}
dict_repr = misc.dim_n_header_long_to_short({'start': 0.0, 'increment': 0.1})
assert dict_repr == {'start': 0.0, 'increment': 0.1}
dict_repr = misc.dim_n_header_long_to_short({'Value': [0.0, 0.1, 0.2], 'description': 'test'})
assert dict_repr == {'Value': {'start': 0.0, 'increment': 0.1}, 'description': 'test'}
dict_repr = misc.dim_n_header_long_to_short({'Value': {'start': 0.0, 'increment': 0.1}, 'description': 'test'})
assert dict_repr == {'Value': {'start': 0.0, 'increment': 0.1}, 'description': 'test'}
def test_dict_to_list():
list_repr = misc._dict_to_list({'start': 0.0, 'increment': 0.1}, 3)
assert list_repr == [0.0, 0.1, 0.2]
list_repr = misc._dict_to_list({'start': 0.0, 'increment': 0.1}, 1)
assert list_repr == [0.0, ]
list_repr = misc._dict_to_list({'start': 1, 'increment': 1}, 3)
assert list_repr == [1, 2, 3]
list_repr = misc._dict_to_list({'start': 1, 'increment': -1}, 3)
assert list_repr == [1, 0, -1]
list_repr = misc._dict_to_list([1, 0, -1], 3)
assert list_repr == [1, 0, -1]
def test_list_to_dict():
dict_repr = misc._list_to_dict([0.0, 0.1, 0.2])
assert dict_repr == {'start': 0.0, 'increment': 0.1}
dict_repr = misc._list_to_dict([1, 2, 3])
assert dict_repr == {'start': 1, 'increment': 1}
dict_repr = misc._list_to_dict([1, 0, -1])
assert dict_repr == {'start': 1, 'increment': -1}
dict_repr = misc._list_to_dict([1, 2, 4])
assert dict_repr == [1, 2, 4]
dict_repr = misc._list_to_dict(['ON', 'OFF'])
assert dict_repr == ['ON', 'OFF']
assert np.allclose(conjugated.data, np.conjugate(nmrs.data))
"""Test the miscellaneous functions for NIFTI-MRS tools
Author: Will Clarke <william.clarke@ndcn.ox.ac.uk>
Copyright (C) 2021 University of Oxford
"""
import fsl_mrs.utils.nifti_mrs_tools.utils as utils
def test_short_to_long():
dict_repr = utils.dim_n_header_short_to_long({'start': 0.0, 'increment': 0.1}, 3)
assert dict_repr == [0.0, 0.1, 0.2]
dict_repr = utils.dim_n_header_short_to_long([0.0, 0.1, 0.2], 3)
assert dict_repr == [0.0, 0.1, 0.2]
dict_repr = utils.dim_n_header_short_to_long({'Value': [0.0, 0.1, 0.2], 'description': 'test'}, 3)
assert dict_repr == {'Value': [0.0, 0.1, 0.2], 'description': 'test'}
dict_repr = utils.dim_n_header_short_to_long({'Value': {'start': 0.0, 'increment': 0.1}, 'description': 'test'}, 3)
assert dict_repr == {'Value': [0.0, 0.1, 0.2], 'description': 'test'}
def test_long_to_short():
dict_repr = utils.dim_n_header_long_to_short([0.0, 0.1, 0.2])
assert dict_repr == {'start': 0.0, 'increment': 0.1}
dict_repr = utils.dim_n_header_long_to_short({'start': 0.0, 'increment': 0.1})
assert dict_repr == {'start': 0.0, 'increment': 0.1}
dict_repr = utils.dim_n_header_long_to_short({'Value': [0.0, 0.1, 0.2], 'description': 'test'})
assert dict_repr == {'Value': {'start': 0.0, 'increment': 0.1}, 'description': 'test'}
dict_repr = utils.dim_n_header_long_to_short({'Value': {'start': 0.0, 'increment': 0.1}, 'description': 'test'})
assert dict_repr == {'Value': {'start': 0.0, 'increment': 0.1}, 'description': 'test'}
def test_dict_to_list():
list_repr = utils._dict_to_list({'start': 0.0, 'increment': 0.1}, 3)
assert list_repr == [0.0, 0.1, 0.2]
list_repr = utils._dict_to_list({'start': 0.0, 'increment': 0.1}, 1)
assert list_repr == [0.0, ]
list_repr = utils._dict_to_list({'start': 1, 'increment': 1}, 3)
assert list_repr == [1, 2, 3]
list_repr = utils._dict_to_list({'start': 1, 'increment': -1}, 3)
assert list_repr == [1, 0, -1]
list_repr = utils._dict_to_list([1, 0, -1], 3)
assert list_repr == [1, 0, -1]
def test_list_to_dict():
dict_repr = utils._list_to_dict([0.0, 0.1, 0.2])
assert dict_repr == {'start': 0.0, 'increment': 0.1}
dict_repr = utils._list_to_dict([1, 2, 3])
assert dict_repr == {'start': 1, 'increment': 1}
dict_repr = utils._list_to_dict([1, 0, -1])
assert dict_repr == {'start': 1, 'increment': -1}
dict_repr = utils._list_to_dict([1, 2, 4])
assert dict_repr == [1, 2, 4]
dict_repr = utils._list_to_dict(['ON', 'OFF'])
assert dict_repr == ['ON', 'OFF']
......@@ -251,7 +251,8 @@ def remove_peak(basis, limits, name=None, all=False):
basis.original_dwell,
basis.cf * 1E6,
limits,
limitUnits='ppm+shift')
limitUnits='ppm+shift',
numSingularValues=5)
basis.update_fid(corrected_obj, n)
else:
index = basis.names.index(name)
......@@ -260,7 +261,8 @@ def remove_peak(basis, limits, name=None, all=False):
basis.original_dwell,
basis.cf * 1E6,
limits,
limitUnits='ppm+shift')
limitUnits='ppm+shift',
numSingularValues=5)
basis.update_fid(corrected_obj, name)
return basis
from .split_merge import split, merge, reorder
from .reshape import reshape
from .misc import conjugate
""" Miscellaneous functions for NIfTI-MRS utilities
""" Miscellaneous tools for NIfTI-MRS
Author: Will Clarke <william.clarke@ndcn.ox.ac.uk>
Copyright (C) 2021 University of Oxford
"""
import json
import numpy as np
from nibabel.nifti1 import Nifti1Extension
from fsl_mrs.core import NIFTI_MRS
def modify_hdr_ext(new_hdr_ext, hdr):
"""Generate a new NIfTI header with a modified header extension.
New header is a copy of the one passed
:param new_hdr_ext: Modified header extension
:type new_hdr_ext: dict
:param hdr: NIfTI header
:type hdr: nibabel.nifti2.Nifti2Header
:return: Copied header with modified hdr extension
:rtype: nibabel.nifti2.Nifti2Header
"""
modded_hdr = hdr.copy()
json_s = json.dumps(new_hdr_ext)
extension = Nifti1Extension(44, json_s.encode('UTF-8'))
modded_hdr.extensions.clear()
modded_hdr.extensions.append(extension)
return modded_hdr
def check_type(in_format):
"""Return type of header: long (list) or short (dict)
:param in_format: Value of header key
:type in_format: list or dict
:return: 'long' or 'short'
:rtype: str
"""
if isinstance(in_format, list):
return 'long'
elif isinstance(in_format, dict)\
and 'start' in in_format:
return 'short'
elif isinstance(in_format, dict)\
and 'Value' in in_format:
return check_type(in_format['Value'])
def dim_n_header_short_to_long(in_format, elements):
if isinstance(in_format, list):
return in_format
elif isinstance(in_format, dict)\
and 'start' in in_format:
return _dict_to_list(in_format, elements)
elif isinstance(in_format, dict)\
and 'Value' in in_format:
out = in_format.copy()
out['Value'] = _dict_to_list(out['Value'], elements)
return out
def conjugate(nmrs):
"""Conjugate a nifti-mrs object.
def dim_n_header_long_to_short(in_format):
if isinstance(in_format, list):
return _list_to_dict(in_format)
elif isinstance(in_format, dict)\
and 'start' in in_format:
return in_format
elif isinstance(in_format, dict)\
and 'Value' in in_format:
out = in_format.copy()
out['Value'] = _list_to_dict(in_format['Value'])
return out
def _dict_to_list(dict_desc, elements):
""" Convert a short dict format to list format
:param dict_desc: dict with start and increment fields
:type dict_desc: dict
:param elements: Number of elements in dimension
:type elements: int
:return: Converted list representation
:rtype: list
:param nmrs: NIFTI_MRS object to conjugate
:type nmrs: NIFTI_MRS
:return: Conjugated NIFTI_MRS
:rtype: NIFTI_MRS
"""
if isinstance(dict_desc, dict):
stop = dict_desc['start'] + dict_desc['increment'] * (elements - 1)
return np.linspace(dict_desc['start'], stop, elements).tolist()
else:
return dict_desc
def _list_to_dict(list_desc):
"""If possible (i.e. vector is monotonic and equally spaced),
convert list to dict (start + increment) representation.
If conversion is not possible return the passed list.
:param list_desc: List of header indicies
:type list_desc: list
:return: Dict representation or unmodified list
:rtype: Dict or list
"""
list_desc = np.asarray(list_desc)
if np.issubdtype(list_desc.dtype, np.number)\
and np.unique(np.diff(list_desc)).size == 1:
return {'start': list_desc[0], 'increment': np.diff(list_desc)[0]}
else:
return list_desc.tolist()
return NIFTI_MRS(np.conjugate(nmrs.data), header=nmrs.header)
......@@ -9,7 +9,7 @@ import json
import numpy as np
from nibabel.nifti1 import Nifti1Extension
from fsl_mrs.core.nifti_mrs import NIFTI_MRS, NIFTIMRS_DimDoesntExist
from fsl_mrs.utils.nifti_mrs_tools import misc
from fsl_mrs.utils.nifti_mrs_tools import utils
class NIfTI_MRSIncompatible(Exception):
......@@ -73,8 +73,8 @@ def split(nmrs, dimension, index_or_indicies):
dim_index + 1,
nmrs.shape[dim_index],
index_or_indicies)
out_hdr_1 = misc.modify_hdr_ext(split_hdr_ext_1, nmrs.header)
out_hdr_2 = misc.modify_hdr_ext(split_hdr_ext_2, nmrs.header)
out_hdr_1 = utils.modify_hdr_ext(split_hdr_ext_1, nmrs.header)
out_hdr_2 = utils.modify_hdr_ext(split_hdr_ext_2, nmrs.header)
nmrs_1 = NIFTI_MRS(np.delete(nmrs.data, index, axis=dim_index), header=out_hdr_1)
nmrs_2 = NIFTI_MRS(np.take(nmrs.data, index, axis=dim_index), header=out_hdr_2)
......@@ -121,13 +121,13 @@ def _split_dim_header(hdr, dimension, dim_length, index):
return split_list(hdr_val)
def split_single(hdr_val):
hdr_type = misc.check_type(hdr_val)
long_fmt = misc.dim_n_header_short_to_long(hdr_val, dim_length)
hdr_type = utils.check_type(hdr_val)
long_fmt = utils.dim_n_header_short_to_long(hdr_val, dim_length)
long_fmt_1, long_fmt_2 = split_user_or_std(long_fmt)
if hdr_type == 'long':
return long_fmt_1, long_fmt_2
else:
return misc.dim_n_header_long_to_short(long_fmt_1), misc.dim_n_header_long_to_short(long_fmt_2)
return utils.dim_n_header_long_to_short(long_fmt_1), utils.dim_n_header_long_to_short(long_fmt_2)
key_str = f'dim_{dimension}_header'
if key_str in hdr:
......@@ -214,7 +214,7 @@ def merge(array_of_nmrs, dimension):
to_concat[-1].shape[dim_index])
merged_length += to_concat[-1].shape[dim_index]
out_hdr = misc.modify_hdr_ext(merged_hdr_ext, array_of_nmrs[0].header)
out_hdr = utils.modify_hdr_ext(merged_hdr_ext, array_of_nmrs[0].header)
return NIFTI_MRS(np.concatenate(to_concat, axis=dim_index), header=out_hdr)
......@@ -252,14 +252,14 @@ def _merge_dim_header(hdr1, hdr2, dimension, dim_length1, dim_length2):
return merge_list(hdr_val1, hdr_val2)
def merge_single(hdr_val1, hdr_val2):
hdr_type = misc.check_type(hdr_val1)
long_fmt_1 = misc.dim_n_header_short_to_long(hdr_val1, dim_length1)
long_fmt_2 = misc.dim_n_header_short_to_long(hdr_val2, dim_length1)
hdr_type = utils.check_type(hdr_val1)
long_fmt_1 = utils.dim_n_header_short_to_long(hdr_val1, dim_length1)
long_fmt_2 = utils.dim_n_header_short_to_long(hdr_val2, dim_length1)
long_fmt = merge_user_or_std(long_fmt_1, long_fmt_2)
if hdr_type == 'long':
return long_fmt
else:
return misc.dim_n_header_long_to_short(long_fmt)
return utils.dim_n_header_long_to_short(long_fmt)
key_str = f'dim_{dimension}_header'
......
""" Utility functions for NIfTI-MRS utilities
Author: Will Clarke <william.clarke@ndcn.ox.ac.uk>
Copyright (C) 2021 University of Oxford
"""
import json
import numpy as np
from nibabel.nifti1 import Nifti1Extension
def modify_hdr_ext(new_hdr_ext, hdr):
"""Generate a new NIfTI header with a modified header extension.
New header is a copy of the one passed