Commit 04adb4db authored by William Clarke's avatar William Clarke
Browse files

Merge branch 'master' into 'master'

Merge for version 1.1.7 - pre FSL Course

Closes #6, #7, #11, #13, and #15

See merge request !25
parents baf4b0cf 8c75ef8f
Pipeline #11097 passed with stages
in 4 minutes and 13 seconds
......@@ -105,7 +105,7 @@ pages:
stage: doc
script:
- git describe --tag --dirty
- pip install -U sphinx sphinx_rtd_theme
- pip install -U sphinx sphinx_rtd_theme==0.5.2
- pip install .
- sphinx-build -b html ./docs/user_docs public
artifacts:
......
This document contains the FSL-MRS release history in reverse chronological order.
1.1.7 (Monday 4th October 2021)
-------------------------------
- Fixed commandline arguments for mrs_tools.
- mrs_tools now handles files with passed without extension.
- Fixed plotting orientation for preprocessing reports.
- CRLB are now reported in scaled absolute and percentage units.
- mrs_tools vis now handles DIM_COIL dimension appropriately with --display_dim command.
- Added a --no_mean command to mrs_tools vis to remove the average signal in multi dimensional data.
1.1.6 (Monday 20th September 2021)
-----------
----------------------------------
- Updates to dynamic MRS fitting in prep for 2021 dwMRS workshop.
- Dynamic MRS fitting beta: pending tests, documentation, and final features.
......
......@@ -88,7 +88,7 @@ def main():
'Or "all" to align over all spectra in higer dimensions.'
'Default = DIM_DYN')
align_group.add_argument('--ppm', type=float, nargs=2,
metavar='<lower-limit upper-limit>',
metavar=('<lower-limit>', '<upper-limit>'),
default=(0.2, 4.2),
help='ppm limits of alignment window'
' (default=0.2->4.2)')
......
......@@ -49,18 +49,20 @@ def main():
visparser.add_argument('--save', default=None, type=str, help='Save fig to path')
visparser.add_argument('--display_dim', default=None, type=str,
help='NIFTI-MRS tag. Do not average across this dimension.')
visparser.add_argument('--no_mean', action="store_false",
help='Do not plot the mean signal line in the case of multiple spectra.')
visparser.set_defaults(func=vis)
# Merge tool - Merge NIfTI MRS along higher dimensions
mergeparser = sp.add_parser(
'merge',
help='Merge NIfTI-MRS along higher dimensions.')
mergeparser.add_argument('--files', type=Path, required=True, nargs='+',
merge_req = mergeparser.add_argument_group('required arguments')
merge_req.add_argument('--files', type=Path, nargs='+', required=True,
help='List of files to merge')
mergeparser.add_argument('--dim', type=str, required=True,
merge_req.add_argument('--dim', type=str, required=True,
help='NIFTI-MRS dimension tag to merge across.')
mergeparser.add_argument('--output',
required=True, type=Path, default=Path('.'),
mergeparser.add_argument('--output', type=Path, default=Path('.'),
help='output folder (defaults to current directory)')
mergeparser.add_argument('--filename', type=str,
help='Override output file name.')
......@@ -70,18 +72,19 @@ def main():
splitparser = sp.add_parser(
'split',
help='Split NIfTI-MRS along higher dimensions.')
splitparser.add_argument('--file', type=Path, required=True,
split_req = splitparser.add_argument_group('required arguments')
split_req.add_argument('--file', type=Path, required=True,
help='File to split')
splitparser.add_argument('--dim', type=str, required=True,
split_req.add_argument('--dim', type=str, required=True,
help='NIFTI-MRS dimension tag to split across.')
group = splitparser.add_mutually_exclusive_group(required=True)
group.add_argument('--indices', type=int, nargs='+',
help='List of indices to extract into second file.'
'All indices are zero-indexed.')
' All indices are zero-indexed.')
group.add_argument('--index', type=int,
help='Index to split at (split after index, zero-indexed).')
splitparser.add_argument('--output',
required=True, type=Path, default=Path('.'),
required=False, type=Path, default=Path('.'),
help='output folder (defaults to current directory)')
splitparser.add_argument('--filename', type=str,
help='Override output file names.')
......@@ -91,14 +94,15 @@ def main():
reorderparser = sp.add_parser(
'reorder',
help='Reorder higher dimensions of NIfTI-MRS.')
reorderparser.add_argument('--file', type=Path, required=True,
reord_req = reorderparser.add_argument_group('required arguments')
reord_req.add_argument('--file', type=Path, required=True,
help='File to reorder')
reorderparser.add_argument('--dim_order', type=str, nargs='+', required=True,
reord_req.add_argument('--dim_order', type=str, nargs='+', required=True,
help='NIFTI-MRS dimension tags in desired order. '
'Enter as strings (min:1, max:3). '
'Can create singleton dimension at end.')
reorderparser.add_argument('--output',
required=True, type=Path, default=Path('.'),
required=False, type=Path, default=Path('.'),
help='output folder (defaults to current directory)')
reorderparser.add_argument('--filename', type=str,
help='Override output file names.')
......@@ -108,10 +112,11 @@ def main():
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,
conj_req = conjparser.add_argument_group('required arguments')
conj_req.add_argument('--file', type=Path, required=True,
help='File to conjugate')
conjparser.add_argument('--output',
required=True, type=Path, default=Path('.'),
required=False, type=Path, default=Path('.'),
help='output folder (defaults to current directory)')
conjparser.add_argument('--filename', type=str,
help='Override output file names.')
......@@ -156,42 +161,18 @@ def vis(args):
"""
from fsl_mrs.utils.plotting import plot_spectrum, plot_spectra
from fsl_mrs.utils.mrs_io import read_FID, read_basis
from fsl_mrs.utils.mrs_io.main import FileNotRecognisedError
import matplotlib.pyplot as plt
import numpy as np
from fsl_mrs.utils.preproc import nifti_mrs_proc
import nibabel as nib
# Some logic to figure out what we are dealing with
p = args.file
nifti_files = list(p.glob('*.nii*'))
# Identify BASIS
if (p.is_dir() and len(nifti_files) == 0)\
or p.suffix.upper() == '.BASIS':
# Some heuristics
if p.is_dir():
conj = True
else:
conj = False
basis = read_basis(args.file)
fig = basis.plot(ppmlim=args.ppmlim, conjugate=conj)
if args.save is not None:
plt.savefig(args.save)
else:
plt.show()
# Identify directory of nifti files
elif p.is_dir() and len(nifti_files) > 0:
raise ValueError('mrs_tools vis should be called on a single'
' NIFTI-MRS file, not a directory (unless'
' it contains basis files).')
# Single nifti file
elif p.is_file():
data = read_FID(args.file)
if data.ndim > 4 and 'DIM_COIL' in data.dim_tags:
def vis_nifti_mrs(file):
data = read_FID(file)
if data.ndim > 4 \
and 'DIM_COIL' in data.dim_tags\
and args.display_dim != 'DIM_COIL':
print('Performing coil combination')
data = nifti_mrs_proc.coilcombine(data)
......@@ -203,7 +184,7 @@ def vis(args):
print(f'Averaging {data.dim_tags[idx]}')
data = nifti_mrs_proc.average(data, data.dim_tags[idx])
fig = plot_spectra(data.mrs(), ppmlim=args.ppmlim)
fig = plot_spectra(data.mrs(), ppmlim=args.ppmlim, plot_avg=args.no_mean)
else:
while data.ndim > 4:
print(f'Averaging {data.dim_tags[0]}')
......@@ -228,6 +209,45 @@ def vis(args):
mrsi.set_mask(mask)
mrsi.plot()
# Some logic to figure out what we are dealing with
p = args.file
nifti_files = list(p.glob('*.nii*'))
# Identify BASIS
if (p.is_dir() and len(nifti_files) == 0)\
or p.suffix.upper() == '.BASIS':
# Some heuristics
if p.is_dir():
conj = True
else:
conj = False
basis = read_basis(args.file)
_ = basis.plot(ppmlim=args.ppmlim, conjugate=conj)
if args.save is not None:
plt.savefig(args.save)
else:
plt.show()
# Identify directory of nifti files
elif p.is_dir() and len(nifti_files) > 0:
raise ValueError('mrs_tools vis should be called on a single'
' NIFTI-MRS file, not a directory (unless'
' it contains basis files).')
elif p.is_file():
vis_nifti_mrs(p)
else:
try:
vis_nifti_mrs(p)
except FileNotRecognisedError as exc:
raise FileNotFoundError(
f"No file or directory '{p}' found."
" Please specify correct file extension (e.g. nii.gz) if there is one.")\
from exc
def merge(args):
"""Merges one or more NIfTI-MRS files along a specified dimension
......
......@@ -14,6 +14,7 @@ testsPath = Path(__file__).parent
# Testing vis option
svs = testsPath / 'testdata/fsl_mrs/metab.nii.gz'
svs_raw = testsPath / 'testdata/fsl_mrs_preproc/metab_raw.nii.gz'
basis = testsPath / 'testdata/fsl_mrs/steam_basis'
......@@ -25,6 +26,30 @@ def test_vis_svs(tmp_path):
assert (tmp_path / 'svs.png').exists()
subprocess.check_call(['mrs_tools', 'vis',
'--ppmlim', '0.2', '4.2',
'--save', str(tmp_path / 'svs2.png'),
svs.with_suffix('').with_suffix('')])
assert (tmp_path / 'svs2.png').exists()
subprocess.check_call(['mrs_tools', 'vis',
'--ppmlim', '0.2', '4.2',
'--display_dim', 'DIM_DYN',
'--save', str(tmp_path / 'svs3.png'),
svs_raw])
assert (tmp_path / 'svs3.png').exists()
subprocess.check_call(['mrs_tools', 'vis',
'--ppmlim', '0.2', '4.2',
'--display_dim', 'DIM_COIL',
'--no_mean',
'--save', str(tmp_path / 'svs4.png'),
svs_raw])
assert (tmp_path / 'svs4.png').exists()
def test_vis_basis(tmp_path):
subprocess.check_call(['mrs_tools', 'vis',
......
......@@ -44,7 +44,7 @@ def _check_datatype(filename):
identify the file type (.nii(.gz),.RAW/.H2O,.txt)
Returns one of: 'NIFTI', 'RAW', 'TXT', 'Unknown'
"""
_, ext = filename.split(os.extsep, 1)
ext = filename.split(os.extsep, 1)[-1]
if 'nii' in ext.lower() or 'nii.gz' in ext.lower():
return 'NIFTI'
elif ext.lower() == 'raw' or ext.lower() == 'h2o':
......
......@@ -553,8 +553,10 @@ def plotly_fit(mrs, res, ppmlim=(.2, 4.2), proj='real', metabs=None, phs=(0, 0))
df['Metab'] = res.metabs
if res.concScalings['molality'] is not None:
df['mMol/kg'] = np.round(res.getConc(scaling='molality'), decimals=2)
df['CRLB'] = np.round(res.getUncertainties(type='molality'), decimals=2)
else:
df['unscaled'] = np.round(res.getConc(), decimals=2)
df['CRLB'] = np.round(res.getUncertainties(type='raw'), decimals=3)
df['%CRLB'] = np.round(res.getUncertainties(), decimals=1)
if res.concScalings['internal'] is not None:
concstr = f'/{res.concScalings["internalRef"]}'
......
......@@ -54,7 +54,8 @@ def plotAxesStyle(fig, ppmlim, title=None):
range=[ppmlim[1], ppmlim[0]])
else:
fig.layout.xaxis.update(title_text='Chemical shift (ppm)',
tick0=2, dtick=.5)
tick0=2, dtick=.5,
autorange="reversed")
fig.layout.yaxis.update(zeroline=True,
zerolinewidth=1,
zerolinecolor='Gray',
......
......@@ -312,12 +312,16 @@ class FitRes(object):
if self.concScalings['internal'] is not None:
concstr = f'/{self.concScalings["internalRef"]}'
df[concstr] = self.getConc(scaling='internal')
df[concstr + ' CRLB'] = self.getUncertainties(type='internal')
else:
df['Raw conc'] = self.getConc()
df['Raw CRLB'] = self.getUncertainties(type='raw')
if self.concScalings['molality'] is not None:
df['mMol/kg'] = self.getConc(scaling='molality')
df['mMol/kg CRLB'] = self.getUncertainties(type='molality')
if self.concScalings['molarity'] is not None:
df['mM'] = self.getConc(scaling='molarity')
df['mM CRLB'] = self.getUncertainties(type='molarity')
df['%CRLB'] = self.getUncertainties()
SNR = self.getQCParams()[0]
......
Markdown is supported
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