Skip to content

Commit

Permalink
Merge pull request #51 from nipreps/enh/46-interfaces-api
Browse files Browse the repository at this point in the history
ENH: API refactor of *Nipype* interfaces
  • Loading branch information
oesteban authored Mar 8, 2023
2 parents 5254abc + 9e4b610 commit fcd724c
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 111 deletions.
35 changes: 35 additions & 0 deletions nireports/interfaces/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
#
# Copyright 2023 The NiPreps Developers <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# We support and encourage derived works from this project, please read
# about our expectations at
#
# https://www.nipreps.org/community/licensing/
#
"""NiPype interfaces to generate reportlets."""
from nireports.interfaces.fmri import FMRISummary
from nireports.interfaces.nuisance import CompCorVariancePlot, ConfoundsCorrelationPlot
from nireports.interfaces.mosaic import PlotContours, PlotMosaic, PlotSpikes

__all__ = (
"CompCorVariancePlot",
"ConfoundsCorrelationPlot",
"FMRISummary",
"PlotContours",
"PlotMosaic",
"PlotSpikes",
)
49 changes: 49 additions & 0 deletions nireports/interfaces/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
#
# Copyright 2023 The NiPreps Developers <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# We support and encourage derived works from this project, please read
# about our expectations at
#
# https://www.nipreps.org/community/licensing/
#
# STATEMENT OF CHANGES: This file was ported carrying over full git history from MRIQC,
# another NiPreps project licensed under the Apache-2.0 terms, and has been changed since.
# The original file this work derives from is found at:
# https://github.com/nipreps/mriqc/blob/1ffd4c8d1a20b44ebfea648a7b12bb32a425d4ec/
# mriqc/interfaces/viz.py
"""NiPype interface -- basic tooling."""
from nipype.interfaces.base import (
BaseInterfaceInputSpec,
File,
traits,
)


class _PlotBaseInputSpec(BaseInterfaceInputSpec):
in_file = File(exists=True, mandatory=True, desc="File to be plotted")
title = traits.Str(desc="a title string for the plot")
annotate = traits.Bool(True, usedefault=True, desc="annotate left/right")
figsize = traits.Tuple(
(11.69, 8.27),
traits.Float,
traits.Float,
usedefault=True,
desc="Figure size",
)
dpi = traits.Int(300, usedefault=True, desc="Desired DPI of figure")
out_file = File("mosaic.svg", usedefault=True, desc="output file name")
cmap = traits.Str("Greys_r", usedefault=True)
112 changes: 112 additions & 0 deletions nireports/interfaces/fmri.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
#
# Copyright 2023 The NiPreps Developers <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# We support and encourage derived works from this project, please read
# about our expectations at
#
# https://www.nipreps.org/community/licensing/
#
"""Functional MRI -specific visualization."""
import numpy as np
import nibabel as nb

from nipype.utils.filemanip import fname_presuffix
from nipype.interfaces.base import (
File,
BaseInterfaceInputSpec,
TraitedSpec,
SimpleInterface,
traits,
isdefined,
)
from nireports.tools.timeseries import cifti_timeseries, get_tr, nifti_timeseries
from nireports.reportlets.modality.func import fMRIPlot


class _FMRISummaryInputSpec(BaseInterfaceInputSpec):
in_func = File(exists=True, mandatory=True, desc="")
in_spikes_bg = File(exists=True, desc="")
fd = File(exists=True, desc="")
dvars = File(exists=True, desc="")
outliers = File(exists=True, desc="")
in_segm = File(exists=True, desc="")
tr = traits.Either(None, traits.Float, usedefault=True, desc="the TR")
fd_thres = traits.Float(0.2, usedefault=True, desc="")
drop_trs = traits.Int(0, usedefault=True, desc="dummy scans")


class _FMRISummaryOutputSpec(TraitedSpec):
out_file = File(exists=True, desc="written file path")


class FMRISummary(SimpleInterface):
"""Prepare an fMRI summary plot for the report."""

input_spec = _FMRISummaryInputSpec
output_spec = _FMRISummaryOutputSpec

def _run_interface(self, runtime):
import pandas as pd

self._results["out_file"] = fname_presuffix(
self.inputs.in_func,
suffix="_fmriplot.svg",
use_ext=False,
newpath=runtime.cwd,
)

dataframe = pd.DataFrame({
"outliers": np.loadtxt(self.inputs.outliers, usecols=[0]).tolist(),
# Pick non-standardize dvars (col 1)
# First timepoint is NaN (difference)
"DVARS": [np.nan]
+ np.loadtxt(self.inputs.dvars, skiprows=1, usecols=[1]).tolist(),
# First timepoint is zero (reference volume)
"FD": [0.0]
+ np.loadtxt(self.inputs.fd, skiprows=1, usecols=[0]).tolist(),
}) if (
isdefined(self.inputs.outliers)
and isdefined(self.inputs.dvars)
and isdefined(self.inputs.fd)
) else None

input_data = nb.load(self.inputs.in_func)
seg_file = self.inputs.in_segm if isdefined(self.inputs.in_segm) else None
dataset, segments = (
cifti_timeseries(input_data)
if isinstance(input_data, nb.Cifti2Image) else
nifti_timeseries(input_data, seg_file)
)

fig = fMRIPlot(
dataset,
segments=segments,
spikes_files=(
[self.inputs.in_spikes_bg]
if isdefined(self.inputs.in_spikes_bg) else None
),
tr=(
self.inputs.tr if isdefined(self.inputs.tr) else
get_tr(input_data)
),
confounds=dataframe,
units={"outliers": "%", "FD": "mm"},
vlines={"FD": [self.inputs.fd_thres]},
nskip=self.inputs.drop_trs,
).plot()
fig.savefig(self._results["out_file"], bbox_inches="tight")
return runtime
43 changes: 14 additions & 29 deletions nireports/interfaces/viz.py → nireports/interfaces/mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# The original file this work derives from is found at:
# https://github.com/nipreps/mriqc/blob/1ffd4c8d1a20b44ebfea648a7b12bb32a425d4ec/
# mriqc/interfaces/viz.py
"""Visualization interfaces."""
"""Visualization of n-D images with mosaics cutting through planes."""
from pathlib import Path

import numpy as np
Expand All @@ -38,10 +38,11 @@
traits,
)

from nireports.interfaces.base import _PlotBaseInputSpec
from nireports.reportlets.mosaic import plot_mosaic, plot_segmentation, plot_spikes


class PlotContoursInputSpec(BaseInterfaceInputSpec):
class _PlotContoursInputSpec(BaseInterfaceInputSpec):
in_file = File(exists=True, mandatory=True, desc="File to be plotted")
in_contours = File(exists=True, mandatory=True, desc="file to pick the contours from")
cut_coords = traits.Int(8, usedefault=True, desc="number of slices")
Expand Down Expand Up @@ -69,15 +70,15 @@ class PlotContoursInputSpec(BaseInterfaceInputSpec):
vmax = traits.Float(desc="maximum intensity")


class PlotContoursOutputSpec(TraitedSpec):
class _PlotContoursOutputSpec(TraitedSpec):
out_file = File(exists=True, desc="output svg file")


class PlotContours(SimpleInterface):
"""Plot contours"""

input_spec = PlotContoursInputSpec
output_spec = PlotContoursOutputSpec
input_spec = _PlotContoursInputSpec
output_spec = _PlotContoursOutputSpec

def _run_interface(self, runtime):
in_file_ref = Path(self.inputs.in_file)
Expand Down Expand Up @@ -108,28 +109,12 @@ def _run_interface(self, runtime):
return runtime


class PlotBaseInputSpec(BaseInterfaceInputSpec):
in_file = File(exists=True, mandatory=True, desc="File to be plotted")
title = traits.Str(desc="a title string for the plot")
annotate = traits.Bool(True, usedefault=True, desc="annotate left/right")
figsize = traits.Tuple(
(11.69, 8.27),
traits.Float,
traits.Float,
usedefault=True,
desc="Figure size",
)
dpi = traits.Int(300, usedefault=True, desc="Desired DPI of figure")
out_file = File("mosaic.svg", usedefault=True, desc="output file name")
cmap = traits.Str("Greys_r", usedefault=True)


class PlotMosaicInputSpec(PlotBaseInputSpec):
class _PlotMosaicInputSpec(_PlotBaseInputSpec):
bbox_mask_file = File(exists=True, desc="brain mask")
only_noise = traits.Bool(False, desc="plot only noise")


class PlotMosaicOutputSpec(TraitedSpec):
class _PlotMosaicOutputSpec(TraitedSpec):
out_file = File(exists=True, desc="output pdf file")


Expand All @@ -139,8 +124,8 @@ class PlotMosaic(SimpleInterface):
Plots slices of a 3D volume into a pdf file
"""

input_spec = PlotMosaicInputSpec
output_spec = PlotMosaicOutputSpec
input_spec = _PlotMosaicInputSpec
output_spec = _PlotMosaicOutputSpec

def _run_interface(self, runtime):
mask = None
Expand All @@ -164,20 +149,20 @@ def _run_interface(self, runtime):
return runtime


class PlotSpikesInputSpec(PlotBaseInputSpec):
class _PlotSpikesInputSpec(_PlotBaseInputSpec):
in_spikes = File(exists=True, mandatory=True, desc="tsv file of spikes")
in_fft = File(exists=True, mandatory=True, desc="nifti file with the 4D FFT")


class PlotSpikesOutputSpec(TraitedSpec):
class _PlotSpikesOutputSpec(TraitedSpec):
out_file = File(exists=True, desc="output svg file")


class PlotSpikes(SimpleInterface):
"""Plot slices of a dataset with spikes."""

input_spec = PlotSpikesInputSpec
output_spec = PlotSpikesOutputSpec
input_spec = _PlotSpikesInputSpec
output_spec = _PlotSpikesOutputSpec

def _run_interface(self, runtime):
out_file = str((Path(runtime.cwd) / self.inputs.out_file).resolve())
Expand Down
Loading

0 comments on commit fcd724c

Please sign in to comment.