Commit 612d0914 authored by William Clarke's avatar William Clarke
Browse files

Merge branch 'bf/win_script_support' into 'master'

Enh: MS Windows compatibility and installation verification script

See merge request !52
parents 60322251 7abf9301
Pipeline #14562 passed with stages
in 5 minutes and 59 seconds
This document contains the FSL-MRS release history in reverse chronological order.
1.1.13 (Wednesday 1st June)
---------------------------
- Updated setup script to allow command line scripts to run on MS Windows.
- Any FSL cmd-line scripts used operate through fslpy wrappers (including WSL interface).
- Updated install instructions for Windows.
- Added the fsl_mrs_verify script which can be run to verify correct function of FSL-MRS.
1.1.12 (Wednesday 20th April)
-----------------------------
- Update to fslpy version (to 3.9.0) to substantially speed up MRSI preprocessing.
......
......@@ -3,20 +3,11 @@
Installation Instructions
=========================
FSL-MRS can currently be installed using one of two methods.
FSL-MRS should be installed using the conda package manager (option 1) or via the main FSL installer (option 2). Building from source code is possible using the instructions in option 3. For installation on MS Windows machines please see the specific setup instructions below.
Option 1: From GitLab
~~~~~~~~~~~~~~~~~~~~~
Download or clone from |fslmrs_gitlab|_. To get FSL-MRS with example data and example Jupyter notebooks, download the full package from gitlab. `Git LFS <https://git-lfs.github.com/>`_ must be installed to download package data.
::
git clone --recurse-submodules https://git.fmrib.ox.ac.uk/fsl/fsl_mrs.git
cd fsl_mrs
pip install .
Option 2: From Conda
~~~~~~~~~~~~~~~~~~~~
Option 1: Using Conda
~~~~~~~~~~~~~~~~~~~~~
The primary installation method is via *conda*. After installing conda and creating or activating a suitable you can install FSL-MRS from the FSL conda channel. See our page on setting up a :ref:`conda enviroment
<conda>` for a step by step guide.
......@@ -39,7 +30,41 @@ Example data with conda
-----------------------
Installation with conda is easy, but you won't get the packaged example data and notebooks. This can be downloaded separately here: |fslmrs_pkg_data_notebooks|_.
Option 2: FSL install script
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Follow the instructions on the main `FSL wiki installation page <https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FslInstallation>`_.
Option 3: From GitLab
~~~~~~~~~~~~~~~~~~~~~
Download or clone from |fslmrs_gitlab|_. To get FSL-MRS with example data and example Jupyter notebooks, download the full package from gitlab. `Git LFS <https://git-lfs.github.com/>`_ must be installed to download package data.
::
git clone --recurse-submodules https://git.fmrib.ox.ac.uk/fsl/fsl_mrs.git
cd fsl_mrs
conda install -c conda-forge -c defaults \
-c https://fsl.fmrib.ox.ac.uk/fsldownloads/fslconda/public/ \
--file requirements.txt
pip install --no-deps .
Windows Operating System
~~~~~~~~~~~~~~~~~~~~~~~~
FSL-MRS has been tested thoroughly on Mac and Linux operating systems but is not currently tested on MS Windows. However there are two routes for using FSL-MRS on Windows.
The first option is to install (as above) the FSL-MRS or complete FSL package using `Windows Subsystem for Linux <https://docs.microsoft.com/en-us/windows/wsl/install-win10>`_ (or WSL2). This offers an easy way of running a linux environment on a Windows machine. To install the full FSL package in WSL, follow the `instructions online <https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FslInstallation/Windows#Windows_Subsystem_for_Linux>`_
Alternatively, as of V1.1.13 of FSL-MRS the python-only FSL-MRS package can be run in native Windows alongside a WSL FSL installation. This can be achieved as follows:
1. Enable WSL and install FSL into WSL as described in the `FSL install instructions <https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/FslInstallation/Windows#Windows_Subsystem_for_Linux>`_.
2. Add an :code:`FSLDIR` enviroment variable on the host Windows machine. This should be set to :code:`\\\\wsl$\\usr\\local\\fsl` assuming the default install directory for FSL on the WSL guest. In Powershell this can be done with the command :code:`$env:FSLDIR = "\\\\wsl$\\usr\\local\\fsl"` to set it for a single session or :code:`[System.Environment]::SetEnvironmentVariable("FSLDIR", "\\wsl$\usr\local\fsl", [System.EnvironmentVariableTarget]::User)` to set it permanently.
3. Install FSL-MRS on the native Windows machine by following the conda installation guide in Option 1.
For FSL-MRS to access the FSL scripts installed on the WSL machine, it must be running.
Verifying the installation
~~~~~~~~~~~~~~~~~~~~~~~~~~
Operating systems
~~~~~~~~~~~~~~~~~
FSL-MRS has been tested thoroughly on Mac and Linux operating systems. FSL-MRS dependencies and FSL-MRS is available on native Windows installations, but has not currently been tested. `Windows Subsystem for Linux <https://docs.microsoft.com/en-us/windows/wsl/install-win10>`_ (or WSL2) offers a Linux interface on Windows. FSL-MRS has been tested on WSL.
\ No newline at end of file
Please run the packaged :code:`fsl_mrs_verify` script to confirm that installation has successfully completed.
\ No newline at end of file
......@@ -338,7 +338,13 @@ class MRSI(object):
if indicies is None:
indicies = self.get_indicies_in_order()
nt = data_list[0].size
# Deal with the variable types (float vs np.float64) that pandas
# seems to generate depending on (python?) version.
if isinstance(data_list[0], (float, int)):
nt = 1
else:
nt = data_list[0].size
if nt > 1:
data = np.zeros(self.spatial_shape + (nt,), dtype=dtype)
else:
......
#!/usr/bin/env python
# fsl_mrs_veridy - script to verify fsl_mrs sucessfull installation
#
# Author: William Clarke <william.clarke@ndcn.ox.ac.uk>
# Saad Jbabdi <saad@fmrib.ox.ac.uk>
#
# Copyright (C) 2022 University of Oxford
# SHBASECOPYRIGHT
# Quick imports
import urllib.request
import shutil
import tarfile
from pathlib import Path
import subprocess
import pandas as pd
import numpy as np
from fsl_mrs import __version__
def main():
print(f'FSL-MRS version {__version__}.')
# Verify SVS fitting functionality
print('Verifying SVS fitting:')
print('--> Downloading data.')
data_base_url = 'https://git.fmrib.ox.ac.uk/fsl/fsl_mrs/-/'
svs_data = {
'metab.nii.gz': 'raw/master/example_usage/example_data/metab.nii.gz?inline=false',
'wref.nii.gz': 'raw/master/example_usage/example_data/wref.nii.gz?inline=false',
'steam_11ms.tar.gz': 'archive/master/fsl_mrs-master.tar.gz?path=example_usage/example_data/steam_11ms',
'T1.anat.tar.gz': 'archive/master/fsl_mrs-master.tar.gz?path=example_usage/example_data/T1.anat'}
for file in svs_data:
curr_url = data_base_url + svs_data[file]
with urllib.request.urlopen(curr_url) as response, open(file, 'wb') as f:
shutil.copyfileobj(response, f)
if 'tar.gz' in file:
# extract file
tfile = tarfile.open(file)
for member in tfile.getmembers():
# Remove file structure
if member.isfile():
member.name = Path(member.name).name
tfile.extract(member, path='./' + file.replace('.tar.gz', ''))
tfile.close()
Path(file).unlink()
print('--> Running svs_segment')
subprocess.check_call([
'svs_segment',
'-a', 'T1.anat',
'metab.nii.gz'
], stdout=subprocess.DEVNULL)
print('--> Running fsl_mrs')
t1_path = Path('T1.anat') / 'T1_biascorr.nii.gz'
subprocess.check_call([
'fsl_mrs',
'--data', 'metab.nii.gz',
'--basis', 'steam_11ms',
'--output', 'fsl_mrs_test_results',
'--metab_groups', 'Mac',
'--combine', 'Cr', 'PCr',
'--combine', 'NAA', 'NAAG',
'--combine', 'GPC', 'PCh',
'--t1', str(t1_path),
'--tissue_frac', 'segmentation.json',
'--h2o', 'wref.nii.gz',
'--report',
])
assert (Path('fsl_mrs_test_results') / 'summary.csv').is_file()
assert (Path('fsl_mrs_test_results') / 'report.html').is_file()
assert (Path('fsl_mrs_test_results') / 'voxel_location.png').is_file()
svs_results = pd.read_csv(Path('fsl_mrs_test_results') / 'summary.csv', index_col=0)
assert np.isclose(svs_results.loc['NAA+NAAG', 'mM'], 13.18, atol=1E-2)
print('SVS fitting checks passed')
print('All done')
if __name__ == '__main__':
main()
......@@ -359,6 +359,7 @@ def main():
for metab in metabs:
metab_conc_list = [res[0].getConc(scaling=scale, metab=metab)
for res in results]
file_nm = os.path.join(cur_fldr, metab + '.nii.gz')
save_img_output(file_nm,
mrsi.list_to_matched_array(
......
......@@ -10,8 +10,7 @@
# Quick imports
import argparse
import os.path as op
from os import remove
from pathlib import Path
from fsl.wrappers import fsl_anat
from fsl.wrappers.fnirt import applywarp
import numpy as np
......@@ -27,21 +26,30 @@ def main():
parser.add_argument('mrsi', type=str, metavar='MRSI',
help='MRSI nifti file')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-t', '--t1', type=str, metavar='T1',
group.add_argument('-t', '--t1', type=Path, metavar='T1',
help='T1 nifti file')
group.add_argument('-a', '--anat', type=str,
group.add_argument('-a', '--anat', type=Path,
help='fsl_anat output directory.')
parser.add_argument('-o', '--output', type=str,
parser.add_argument('-o', '--output', type=Path,
help='Output directory', default='.')
parser.add_argument('-f', '--filename', type=str,
help='Output file name', default='mrsi_seg')
args = parser.parse_args()
# For windows implementations we must supply absolute
# paths. This enables conversion to wsl paths.
# The fslpy wrapper code requires a string rather than pathlib Path.
def str_resolve_path(pathlib_path):
return str(pathlib_path.resolve())
# If not prevented run fsl_anat for fast segmentation
if (args.anat is None) and (not args.mask_only):
anat = op.join(args.output, 'fsl_anat')
fsl_anat(args.t1, out=anat, nosubcortseg=True)
anat += '.anat'
anat = args.output / 'fsl_anat'
fsl_anat(
str_resolve_path(args.t1),
out=str_resolve_path(anat),
nosubcortseg=True)
anat = anat.with_suffix('.anat')
else:
anat = args.anat
......@@ -49,27 +57,24 @@ def main():
mrsi_in = Image(args.mrsi)
tmp_img = np.zeros(mrsi_in.shape[0:3])
tmp_img = Image(tmp_img, xform=mrsi_in.voxToWorldMat)
tmp_img.save(op.join(args.output, 'tmp.nii.gz'))
# Register the pvseg to the MRSI data using flirt
def applywarp_func(i, o):
applywarp(i,
op.join(args.output, 'tmp.nii.gz'),
o,
applywarp(str_resolve_path(i),
tmp_img,
str_resolve_path(o),
usesqform=True,
super=True,
superlevel='a')
# T1_fast_pve_0, T1_fast_pve_1, T1_fast_pve_2
# partial volume segmentations (CSF, GM, WM respectively)
applywarp_func(op.join(anat, 'T1_fast_pve_0.nii.gz'),
op.join(args.output, args.filename + '_csf.nii.gz'))
applywarp_func(op.join(anat, 'T1_fast_pve_1.nii.gz'),
op.join(args.output, args.filename + '_gm.nii.gz'))
applywarp_func(op.join(anat, 'T1_fast_pve_2.nii.gz'),
op.join(args.output, args.filename + '_wm.nii.gz'))
remove(op.join(args.output, 'tmp.nii.gz'))
applywarp_func(anat / 'T1_fast_pve_0.nii.gz',
args.output / (args.filename + '_csf.nii.gz'))
applywarp_func(anat / 'T1_fast_pve_1.nii.gz',
args.output / (args.filename + '_gm.nii.gz'))
applywarp_func(anat / 'T1_fast_pve_2.nii.gz',
args.output / (args.filename + '_wm.nii.gz'))
if __name__ == '__main__':
......
......@@ -11,8 +11,7 @@
# Quick imports
import argparse
import os.path as op
from os import remove
from pathlib import Path
import nibabel as nib
import numpy as np
from fsl.wrappers import flirt, fslstats, fsl_anat, fslmaths
......@@ -27,15 +26,15 @@ def main():
" - Construct mask in T1 space of an SVS voxel"
" and generate a tissue segmentation file.")
parser.add_argument('svs', type=str, metavar='SVS',
parser.add_argument('svs', type=Path, metavar='SVS',
help='SVS nifti file')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-t', '--t1', type=str, metavar='T1',
group.add_argument('-t', '--t1', type=Path, metavar='T1',
help='T1 nifti file')
group.add_argument('-a', '--anat', type=str,
group.add_argument('-a', '--anat', type=Path,
help='fsl_anat output directory.')
parser.add_argument('-o', '--output', type=str,
help='Output directory', default='.')
parser.add_argument('-o', '--output', type=Path,
help='Output directory', default=Path.cwd())
parser.add_argument('-f', '--filename', type=str,
help='file name stem. _mask.nii.gz'
' or _segmentation.json will be added.',
......@@ -43,15 +42,22 @@ def main():
parser.add_argument('-m', '--mask_only', action="store_true",
help='Only perform masking stage,'
' do not run fsl_anat if only T1 passed.')
parser.add_argument('--no_clean', action="store_false",
help="Don't clean intermediate output.", dest='clean')
args = parser.parse_args()
# For windows implementations we must supply absolute
# paths. This enables conversion to wsl paths.
# The fslpy wrapper code requires a string rather than pathlib Path.
def str_resolve_path(pathlib_path):
return str(pathlib_path.resolve())
# If not prevented run fsl_anat for fast segmentation
if (args.anat is None) and (not args.mask_only):
anat = op.join(args.output, 'fsl_anat')
fsl_anat(args.t1, out=anat, nosubcortseg=True)
anat += '.anat'
anat = args.output / 'fsl_anat'
fsl_anat(
str_resolve_path(args.t1),
out=str_resolve_path(anat),
nosubcortseg=True)
anat = anat.with_suffix('.anat')
else:
anat = args.anat
......@@ -60,24 +66,22 @@ def main():
# Create 3D mock data
mockData = np.zeros((2, 2, 2))
mockData[0, 0, 0] = 1.0
img = nib.Nifti1Image(mockData, affine=data_hdr.affine)
flirt_in = op.join(args.output, 'tmp_mask.nii')
nib.save(img, flirt_in)
tmp = nib.Nifti1Image(mockData, affine=data_hdr.affine)
# Run flirt
if anat is not None:
flirt_ref = op.join(anat, 'T1_biascorr.nii.gz')
flirt_ref = anat / 'T1_biascorr.nii.gz'
else:
flirt_ref = args.t1
flirt_ref = str_resolve_path(flirt_ref)
if args.filename is None:
mask_name = 'mask.nii.gz'
else:
mask_name = args.filename + '_mask.nii.gz'
flirt_out = op.join(args.output, mask_name)
flirt_out = str_resolve_path(args.output / mask_name)
flirt(flirt_in,
flirt(tmp,
flirt_ref,
out=flirt_out,
usesqform=True,
......@@ -87,29 +91,24 @@ def main():
setbackground=0,
paddingsize=1)
# Clean up
if args.clean:
remove(flirt_in)
# Provide tissue segmentation if anat is available
if anat is not None:
# Check that the svs mask intersects with brain, issue warning if not.
fslmaths(flirt_out).add(
op.join(anat, 'T1_biascorr_brain_mask.nii.gz')) \
.mas(flirt_out).run(op.join(args.output, 'tmp.nii.gz'))
mask_path = str_resolve_path(anat / 'T1_biascorr_brain_mask.nii.gz')
tmp_out = fslmaths(flirt_out)\
.add(mask_path)\
.mas(flirt_out)\
.run()
meanInVox = fslstats(op.join(args.output, 'tmp.nii.gz')).M.run()
meanInVox = fslstats(tmp_out).M.run()
if meanInVox < 2.0:
warnings.warn('The mask does not fully intersect'
' with the brain mask. Check manually.')
if args.clean:
remove(op.join(args.output, 'tmp.nii.gz'))
# Count up segmentation values in mask.
seg_csf = op.join(anat, 'T1_fast_pve_0.nii.gz')
seg_gm = op.join(anat, 'T1_fast_pve_1.nii.gz')
seg_wm = op.join(anat, 'T1_fast_pve_2.nii.gz')
seg_csf = str_resolve_path(anat / 'T1_fast_pve_0.nii.gz')
seg_gm = str_resolve_path(anat / 'T1_fast_pve_1.nii.gz')
seg_wm = str_resolve_path(anat / 'T1_fast_pve_2.nii.gz')
# fslstats -t /fast_output/fast_output_pve_0 -k SVS_mask.nii –m
CSF = fslstats(seg_csf).k(flirt_out).m.run()
......@@ -123,7 +122,7 @@ def main():
else:
seg_name = args.filename + '_segmentation.json'
with open(op.join(args.output, seg_name), 'w', encoding='utf-8') as f:
with open(args.output / seg_name, 'w', encoding='utf-8') as f:
json.dump(segresults, f, ensure_ascii=False, indent='\t')
......
'''FSL-MRS test script
Test the installation verification script
Copyright Will Clarke, University of Oxford, 2022'''
from fsl_mrs.scripts import fsl_mrs_verify
def test_verify(tmp_path):
with tmp_path:
try:
fsl_mrs_verify.main()
except Exception as exc:
assert False, f"'fsl_mrs_verify.main()' fialed and raised an exception {exc}"
......@@ -19,6 +19,7 @@ setup(name='fsl_mrs',
long_description=long_description,
long_description_content_type="text/markdown",
packages=['fsl_mrs',
'fsl_mrs.scripts',
'fsl_mrs.core',
'fsl_mrs.utils',
'fsl_mrs.utils.mrs_io',
......@@ -36,16 +37,21 @@ setup(name='fsl_mrs',
'fsl_mrs.utils.preproc': ['templates/*.html'],
'fsl_mrs': ['pkg_data/mrs_fitting_challenge/*/*']},
install_requires=install_requires,
scripts=['fsl_mrs/scripts/fsl_mrs',
'fsl_mrs/scripts/fsl_mrsi',
'fsl_mrs/scripts/fsl_mrs_preproc',
'fsl_mrs/scripts/fsl_mrs_preproc_edit',
'fsl_mrs/scripts/fsl_mrs_proc',
'fsl_mrs/scripts/fsl_mrs_sim',
'fsl_mrs/scripts/mrs_tools',
'fsl_mrs/scripts/basis_tools',
'fsl_mrs/scripts/merge_mrs_reports',
'fsl_mrs/scripts/svs_segment',
'fsl_mrs/scripts/mrsi_segment',
'fsl_mrs/scripts/results_to_spectrum']
entry_points={
'console_scripts': [
'fsl_mrs = fsl_mrs.scripts.fsl_mrs:main',
'fsl_mrsi = fsl_mrs.scripts.fsl_mrsi:main',
'fsl_mrs_preproc = fsl_mrs.scripts.fsl_mrs_preproc:main',
'fsl_mrs_preproc_edit = fsl_mrs.scripts.fsl_mrs_preproc_edit:main',
'fsl_mrs_proc = fsl_mrs.scripts.fsl_mrs_proc:main',
'fsl_mrs_sim = fsl_mrs.scripts.fsl_mrs_sim:main',
'mrs_tools = fsl_mrs.scripts.mrs_tools:main',
'basis_tools = fsl_mrs.scripts.basis_tools:main',
'merge_mrs_reports = fsl_mrs.scripts.merge_mrs_reports:main',
'svs_segment = fsl_mrs.scripts.svs_segment:main',
'mrsi_segment = fsl_mrs.scripts.mrsi_segment:main',
'results_to_spectrum = fsl_mrs.scripts.results_to_spectrum:main',
'fsl_mrs_verify = fsl_mrs.scripts.fsl_mrs_verify:main'
]
}
)
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