Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REF] Remove FSL from dependencies #528

Merged
merged 16 commits into from
Oct 3, 2022
Merged
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2.1

.dockersetup: &dockersetup
docker:
- image: pennbbl/xcpd_build:0.0.5
- image: pennbbl/xcpd_build:0.0.6rc1
working_directory: /src/xcp_d

runinstall: &runinstall
Expand Down
7 changes: 4 additions & 3 deletions .circleci/tests/test_smoothing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import tempfile

import numpy as np
from nipype.interfaces.fsl import Smooth
from nipype.pipeline import engine as pe

from xcp_d.interfaces.nilearn import Smooth


def test_smoothing_Nifti(data_dir):
"""Test NIFTI smoothing."""
Expand Down Expand Up @@ -40,11 +41,11 @@ def test_smoothing_Nifti(data_dir):
os.system('rm -rf 3dFWHMx.1D test_fwhm.out test_file.out')

# Smooth the data
smooth_data = pe.Node(Smooth(output_type='NIFTI_GZ', fwhm=6), # FWHM = kernel size
smooth_data = pe.Node(Smooth(fwhm=6), # FWHM = kernel size
name="nifti_smoothing") # Use fslmaths to smooth the image
smooth_data.inputs.in_file = in_file
results = smooth_data.run()
out_file = results.outputs.smoothed_file
out_file = results.outputs.out_file

# Run AFNI'S FWHMx via CLI, the nipype interface doesn't have what we need
# i.e : the "ShowMeClassicFWHM" option
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

FROM pennbbl/xcpd_build:0.0.5
FROM pennbbl/xcpd_build:0.0.6rc1

# Installing xcp_d
COPY . /src/xcp_d
Expand Down
1 change: 0 additions & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ nipype_, and requires some other neuroimaging software tools that are
not handled by the Python's packaging system (Pypi) used to deploy
the ``xcp_d`` package:

- FSL_ (version 5.0.9 or higher)
- ANTs_ (version 2.2.0 - or higher)
- AFNI_ (version Debian-16.2.07)
- `bids-validator <https://github.com/bids-standard/bids-validator>`_ (version 1.6.0)
Expand Down
1 change: 0 additions & 1 deletion docs/links.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
.. _Installation: installation.html
.. _Task Regression: taskregression.html
.. _workflows: workflows.html
.. _FSL: https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/
.. _ANTs: https://stnava.github.io/ANTs/
.. _AFNI: https://afni.nimh.nih.gov/
.. _`Connectome Workbench`: https://www.humanconnectome.org/software/connectome-workbench.html
Expand Down
136 changes: 136 additions & 0 deletions xcp_d/interfaces/nilearn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""Interfaces for Nilearn code."""
import os

from nipype.interfaces.base import (
BaseInterfaceInputSpec,
File,
InputMultiPath,
SimpleInterface,
TraitedSpec,
traits,
)
from nipype.interfaces.nilearn import NilearnBaseInterface


class _MergeInputSpec(BaseInterfaceInputSpec):
in_files = InputMultiPath(
File(exists=True),
mandatory=True,
desc="A list of images to concatenate.",
)
out_file = File(
"concat_4d.nii.gz",
usedefault=True,
exists=False,
desc="The name of the concatenated file to write out. concat_4d.nii.gz by default.",
)


class _MergeOutputSpec(TraitedSpec):
out_file = File(
exists=True,
desc="Concatenated output file.",
)


class Merge(NilearnBaseInterface, SimpleInterface):
"""Merge images."""

input_spec = _MergeInputSpec
output_spec = _MergeOutputSpec

def _run_interface(self, runtime):
from nilearn.image import concat_imgs

img_4d = concat_imgs(self.inputs.in_files)
self._results["out_file"] = os.path.join(runtime.cwd, self.inputs.out_file)
img_4d.to_filename(self._results["out_file"])

return runtime


class _SmoothInputSpec(BaseInterfaceInputSpec):
in_file = File(
exists=True,
mandatory=True,
desc="An image to smooth.",
)
fwhm = traits.Either(
traits.Float(),
traits.List(
traits.Float(),
minlen=3,
maxlen=3,
),
desc="Smoothing strength, as a full-width at half maximum, in millimeters.",
)
out_file = File(
"smooth_img.nii.gz",
usedefault=True,
exists=False,
desc="The name of the smoothed file to write out. smooth_img.nii.gz by default.",
)


class _SmoothOutputSpec(TraitedSpec):
out_file = File(
exists=True,
desc="Smoothed output file.",
)


class Smooth(NilearnBaseInterface, SimpleInterface):
"""Smooth image."""

input_spec = _SmoothInputSpec
output_spec = _SmoothOutputSpec

def _run_interface(self, runtime):
from nilearn.image import smooth_img

img_smoothed = smooth_img(self.inputs.in_file, fwhm=self.inputs.fwhm)
self._results["out_file"] = os.path.join(runtime.cwd, self.inputs.out_file)
img_smoothed.to_filename(self._results["out_file"])

return runtime


class _BinaryMathInputSpec(BaseInterfaceInputSpec):
in_file = File(
exists=True,
mandatory=True,
desc="An image to do math on.",
)
expression = traits.String(
mandatory=True,
desc="A mathematical expression to apply to the image. Must have 'img' in it."
)
out_file = File(
"out_img.nii.gz",
usedefault=True,
exists=False,
desc="The name of the mathified file to write out. out_img.nii.gz by default.",
)


class _BinaryMathOutputSpec(TraitedSpec):
out_file = File(
exists=True,
desc="Mathified output file.",
)


class BinaryMath(NilearnBaseInterface, SimpleInterface):
"""Do math on an image."""

input_spec = _BinaryMathInputSpec
output_spec = _BinaryMathOutputSpec

def _run_interface(self, runtime):
from nilearn.image import math_img

img_mathed = math_img(self.inputs.expression, img=self.inputs.in_file)
self._results["out_file"] = os.path.join(runtime.cwd, self.inputs.out_file)
img_mathed.to_filename(self._results["out_file"])

return runtime
10 changes: 8 additions & 2 deletions xcp_d/utils/concantenation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import nibabel as nb
import numpy as np
from natsort import natsorted
from nilearn.image import concat_imgs
from nipype.interfaces.ants import ApplyTransforms
from templateflow.api import get as get_template

Expand Down Expand Up @@ -248,7 +249,10 @@ def concatenate_nifti(subid, fmridir, outputdir, ses=None, work_dir=None):
glob.glob(fmri_files + os.path.basename(res.split('run-')[0])
+ '*' + resid.partition('_desc')[0]
+ '*_desc-brain_mask.nii.gz'))[0]
os.system('fslmerge -t ' + outfile + ' ' + combinefile)

combine_img = concat_imgs(combinefile)
combine_img.to_filename(outfile)

for b in filex:
dvar = compute_dvars(read_ndata(b, mask))
dvar[0] = np.mean(dvar)
Expand All @@ -269,7 +273,9 @@ def concatenate_nifti(subid, fmridir, outputdir, ses=None, work_dir=None):

combinefiley = " ".join(filey)
rawdata = tempfile.mkdtemp() + '/rawdata.nii.gz'
os.system('fslmerge -t ' + rawdata + ' ' + combinefiley)

combine_img = concat_imgs(combinefiley)
combine_img.to_filename(rawdata)

precarpet = figure_files + os.path.basename(
fileid) + '_desc-precarpetplot_bold.svg'
Expand Down
19 changes: 9 additions & 10 deletions xcp_d/workflow/anatomical.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
from nipype.interfaces.ants import CompositeTransformUtil # MB
from nipype.interfaces.ants.resampling import ApplyTransforms # TM
from nipype.interfaces.freesurfer import MRIsConvert
from nipype.interfaces.fsl import Merge as FSLMerge # TM
from nipype.interfaces.fsl.maths import BinaryMaths # TM
from nipype.pipeline import engine as pe
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
from templateflow.api import get as get_template
Expand All @@ -20,6 +18,7 @@
from xcp_d.interfaces.bids import DerivativesDataSink
from xcp_d.interfaces.c3 import C3d # TM
from xcp_d.interfaces.connectivity import ApplyTransformsx
from xcp_d.interfaces.nilearn import BinaryMath, Merge
from xcp_d.interfaces.surfplotting import BrainPlotx, RibbontoStatmap
from xcp_d.interfaces.workbench import ( # MB,TM
ApplyAffine,
Expand Down Expand Up @@ -533,13 +532,13 @@ def init_anatomical_wf(
# for use with wb_command -surface-apply-warpfield)

reverse_y_component = pe.Node(
BinaryMaths(operation="mul", operand_value=-1.0),
BinaryMath(expression="img * -1"),
name="reverse_y_component",
mem_gb=mem_gb,
n_procs=omp_nthreads,
)
reverse_inv_y_component = pe.Node(
BinaryMaths(operation="mul", operand_value=-1.0),
BinaryMath(expression="img * -1"),
name="reverse_inv_y_component",
mem_gb=mem_gb,
n_procs=omp_nthreads,
Expand All @@ -561,13 +560,13 @@ def init_anatomical_wf(

# re-merge warpfield in FSL FNIRT format, with the reversed y-component from above
remerge_warpfield = pe.Node(
FSLMerge(dimension="t"),
Merge(),
name="remerge_warpfield",
mem_gb=mem_gb,
n_procs=omp_nthreads,
)
remerge_inv_warpfield = pe.Node(
FSLMerge(dimension="t"),
Merge(),
name="remerge_inv_warpfield",
mem_gb=mem_gb,
n_procs=omp_nthreads,
Expand Down Expand Up @@ -965,12 +964,12 @@ def init_anatomical_wf(
(
remerge_warpfield,
lh_surface_apply_warpfield,
[("merged_file", "forward_warp")],
[("out_file", "forward_warp")],
),
(
remerge_inv_warpfield,
lh_surface_apply_warpfield,
[("merged_file", "warpfield")],
[("out_file", "warpfield")],
),
(
left_sphere_raw_mris,
Expand Down Expand Up @@ -1012,12 +1011,12 @@ def init_anatomical_wf(
(
remerge_warpfield,
rh_surface_apply_warpfield,
[("merged_file", "forward_warp")],
[("out_file", "forward_warp")],
),
(
remerge_inv_warpfield,
rh_surface_apply_warpfield,
[("merged_file", "warpfield")],
[("out_file", "warpfield")],
),
(
right_sphere_raw_mris,
Expand Down
18 changes: 8 additions & 10 deletions xcp_d/workflow/postprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import numpy as np
import sklearn
from nipype.interfaces import utility as niu
from nipype.interfaces.fsl import Smooth
from nipype.interfaces.workbench import CiftiSmooth
from nipype.pipeline import engine as pe
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
from pkg_resources import resource_filename as pkgrf
from templateflow.api import get as get_template

from xcp_d.interfaces.filtering import FilteringData
from xcp_d.interfaces.nilearn import Smooth
from xcp_d.utils.doc import fill_doc
from xcp_d.utils.utils import fwhm2sigma, stringforparams

Expand Down Expand Up @@ -166,16 +166,15 @@ def init_post_process_wf(

else:
workflow.__desc__ = workflow.__desc__ + f"""
The processed bold was smoothed with FSL and kernel size (FWHM) of {str(smoothing)} mm.
The processed bold was smoothed with Nilearn and kernel size (FWHM) of {str(smoothing)} mm.
"""
smooth_data = pe.Node(Smooth(output_type='NIFTI_GZ',
fwhm=smoothing),
smooth_data = pe.Node(Smooth(fwhm=smoothing),
name="nifti_smoothing",
mem_gb=mem_gb)

workflow.connect([
(filtering_wf, smooth_data, [('filtered_file', 'in_file')]),
(smooth_data, outputnode, [('smoothed_file', 'smoothed_bold')])
(smooth_data, outputnode, [('out_file', 'smoothed_bold')])
])

return workflow
Expand Down Expand Up @@ -227,16 +226,15 @@ def init_resd_smoothing(

else: # for Nifti
workflow.__desc__ = f""" \
The processed BOLD was smoothed using FSL with a gaussian kernel size of {str(smoothing)} mm
The processed BOLD was smoothed using Nilearn with a gaussian kernel size of {str(smoothing)} mm
(FWHM).
"""
smooth_data = pe.Node(Smooth(output_type='NIFTI_GZ', fwhm=smoothing), # FWHM = kernel size
smooth_data = pe.Node(Smooth(fwhm=smoothing), # FWHM = kernel size
name="nifti_smoothing",
mem_gb=mem_gb,
n_procs=omp_nthreads) # Use fslmaths to smooth the image
n_procs=omp_nthreads) # Use nilearn to smooth the image

# Connect to workflow
workflow.connect([(inputnode, smooth_data, [('bold_file', 'in_file')]),
(smooth_data, outputnode, [('smoothed_file',
'smoothed_bold')])])
(smooth_data, outputnode, [('out_file', 'smoothed_bold')])])
return workflow
Loading