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

Add LCM raw basis set read/conversion.

parent 70287e9b
Pipeline #14867 passed with stages
in 21 minutes and 14 seconds
......@@ -22,6 +22,7 @@ This document contains the FSL-MRS release history in reverse chronological orde
- Experimental SVS results dashboard - view the results of multiple SVS fits together in a single summary.
- New documentation for dynamic fitting and all new features.
- Refactored imports to improve CLI startup times
- Conversion of LCModel raw formatted basis sets using basis_tools convert.
1.1.14 (Wednesday 29th June)
......@@ -22,7 +22,8 @@ vis
| *Example* :code:`basis_tools convert path/to/my/lcmbasis.BASIS path/to/my/fslbasis`
| Convert LCModel (.Basis) or JMRUI format basis sets to FSL-MRS (.json) format.
| Convert LCModel (.Basis), LCModel (directory of .raw) or JMRUI format basis sets to FSL-MRS (.json) format.
| Note that the bandwidth and fieldstrength must be supplied manually to the CLI for the .raw format.
......@@ -54,6 +54,10 @@ def main():
help='Input basis file or folder')
convertparser.add_argument('output', type=Path,
help='Output fsl formatted folder, will be created if needed.')
convertparser.add_argument('--bandwidth', type=float, default=None,
help='Required for LCModel RAW format only: spectral bandwidth in Hz.')
convertparser.add_argument('--fieldstrength', type=float, default=None,
help='Required for LCModel RAW format only: field strength in tesla.')
convertparser.add_argument('--remove_reference', action="store_true",
help='Remove LCModel reference peak.')
convertparser.add_argument('--hlsvd', action="store_true",
......@@ -240,7 +244,16 @@ def convert(args):
from fsl_mrs.utils import basis_tools
from fsl_mrs.utils.mrs_io import read_basis
basis_tools.convert_lcm_basis(args.input, args.output)
from fsl_mrs.utils.constants import GYRO_MAG_RATIO
if args.input.is_file():
basis_tools.convert_lcm_basis(args.input, args.output)
elif args.input.is_dir():
args.fieldstrength * GYRO_MAG_RATIO['1H'],
if args.remove_reference:
# TODO sort this conjugation mess out.
......@@ -19,6 +19,7 @@ fsl = testsPath / 'testdata/mrs_io/basisset_FSL'
mac = testsPath / 'testdata/basis_tools/macSeed.json'
diff1 = testsPath / 'testdata/basis_tools/low_res_off'
diff2 = testsPath / 'testdata/basis_tools/low_res_on'
raw = testsPath / 'testdata/basis_tools/RawBasis_for_PRESSGE_TE_35_BW_4000_NPts_2048'
def test_info():
......@@ -34,7 +35,7 @@ def test_info():
# '--ppmlim', '0.2', '5.2',
# str(jmrui)])
def test_convert(tmp_path):
def test_convert_lcmbasis(tmp_path):
subprocess.check_call(['basis_tools', 'convert',
str(tmp_path / 'new')])
......@@ -43,6 +44,17 @@ def test_convert(tmp_path):
assert (tmp_path / 'new' / 'NAA.json').is_file()
def test_convert_raw(tmp_path):
subprocess.check_call(['basis_tools', 'convert',
'--bandwidth', '4000',
'--fieldstrength', '3.0',
str(tmp_path / 'new_raw')])
assert (tmp_path / 'new_raw').is_dir()
assert (tmp_path / 'new_raw' / 'NAA.json').is_file()
def test_convert_with_remove(tmp_path):
subprocess.check_call(['basis_tools', 'convert',
......@@ -13,10 +13,12 @@ import numpy as np
from fsl_mrs.utils import mrs_io
from fsl_mrs.utils import basis_tools
from fsl_mrs.utils.mrs_io import fsl_io
from fsl_mrs.utils.constants import GYRO_MAG_RATIO
testsPath = Path(__file__).parent
fsl_basis_path = testsPath / 'testdata' / 'fsl_mrs' / 'steam_basis'
lcm_basis_path = testsPath / 'testdata' / 'basis_tools' / '3T_slaser_32vespa_1250.BASIS'
raw_basis_path = testsPath / 'testdata' / 'basis_tools' / 'RawBasis_for_PRESSGE_TE_35_BW_4000_NPts_2048'
extra_basis = testsPath / 'testdata' / 'basis_tools' / 'macSeed.json'
......@@ -35,6 +37,20 @@ def test_convert_lcmodel(tmp_path):
assert np.isclose(,
def test_convert_raw(tmp_path):
out_loc = tmp_path / 'test_basis_raw'
3.0 * GYRO_MAG_RATIO['1H'],
new_basis = mrs_io.read_basis(out_loc)
assert new_basis.names == ['Cr', 'GPC', 'NAA']
assert new_basis.original_basis_array.shape == (2048, 3)
def test_add_basis():
basis = mrs_io.read_basis(fsl_basis_path)
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.
This source diff could not be displayed because it is stored in LFS. You can view the blob instead.
......@@ -49,6 +49,43 @@ def convert_lcm_basis(path_to_basis, output_location=None):, info_str=sim_info)
def convert_lcm_raw_basis(path_to_basis, bandwidth, central_frequency, output_location=None):
"""Converts an existing LCModel set of .Raw basis files to FSL format (a directory of json files).
The generated FSL format will only contain a subset of the information that it normaly does
(e.g. no sequence info), and won't hold all LCModel fields either.
:param path_to_basis: Directory containing LCModel .raw files.
:type path_to_basis: pathlib.Path
:param bandwidth: Spectral bandwidth in Hz
:type bandwidth: float
:param central_frequency: Spectrometer frequency in MHz
:type central_frequency: float
:param output_location: path to output location + name, defaults to None
:type output_location: _tpathlib.Path or str, optional
from fsl_mrs.utils.mrs_io.lcm_io import read_basis_files
files = [str(x) for x in path_to_basis.glob('*.raw')]
basis_array, names = read_basis_files(files)
basis_array = basis_array.conj()
header = {}
header['dwelltime'] = 1 / bandwidth
header['bandwidth'] = bandwidth
header['centralFrequency'] = central_frequency
header['fwhm'] = None
basis = Basis(basis_array, names, [header, ] * len(names))
sim_info = f'Converted from {str(path_to_basis)}'
if output_location is None:, info_str=sim_info)
else:, info_str=sim_info)
def add_basis(fid, name, cf, bw, target, scale=False, width=None, conj=False, pad=False):
"""Add an additional basis spectrum to an existing FSL formatted basis set.
......@@ -27,17 +27,19 @@ def readLCModelRaw(filename, unpack_header=True, conjugate=True):
header = []
data = []
in_header = False
header_done = False
with open(filename, 'r') as f:
for line in f:
if (line.find('$') > 0):
in_header = True
if in_header:
elif header_done:
data.append(list(map(float, line.split())))
if line.find('$END') > 0:
in_header = False
header_done = True
# Reshape data
data = np.concatenate([np.array(i) for i in data])
......@@ -74,6 +76,12 @@ def read_basis_files(basisfiles, ignore=[]):
Reads basis files and extracts name of metabolite from file name
Assumes .RAW files are FIDs (not spectra)
Comes without any header information unfortunately.
:param basisfiles: List of path strings to raw files
:type basisfiles: list
:param ignore: Optionally ignore files, defaults to []
:type ignore: list, optional
:return: Numpy array of basis spectra and names
basis = []
names = []
......@@ -126,7 +126,7 @@ def read_basis(filename):
elif filename.is_dir():
fslfiles = sorted(list(filename.glob('*.json')))
rawfiles = sorted(list(filename.glob('*.RAW')))
rawfiles = sorted(list(filename.glob('*.RAW')) + list(filename.glob('*.raw')))
txtfiles = sorted(list(filename.glob('*.txt')))
if fslfiles:
basis, names, header = fsl.readFSLBasisFiles(filename)
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