Skip to content

Commit

Permalink
ENH: Add raincloud plot capabilities
Browse files Browse the repository at this point in the history
Add raincloud plot capabilities and demonstrate on the FA values of
computed on a DWI volume across different tissue types.

Add the corresponding tests.

Add a `utils.py` helper module to the `tests` module. Contains a
function to create random data used by the added tests.
  • Loading branch information
jhlegarreta authored and oesteban committed Jul 1, 2024
1 parent 5b78e8d commit e6ac5f1
Show file tree
Hide file tree
Showing 8 changed files with 640 additions and 9 deletions.
7 changes: 6 additions & 1 deletion nireports/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@

from nireports.interfaces.fmri import FMRISummary
from nireports.interfaces.mosaic import PlotContours, PlotMosaic, PlotSpikes
from nireports.interfaces.nuisance import CompCorVariancePlot, ConfoundsCorrelationPlot
from nireports.interfaces.nuisance import (
CompCorVariancePlot,
ConfoundsCorrelationPlot,
RaincloudPlot,
)

__all__ = (
"CompCorVariancePlot",
"ConfoundsCorrelationPlot",
"RaincloudPlot",
"FMRISummary",
"PlotContours",
"PlotMosaic",
Expand Down
108 changes: 107 additions & 1 deletion nireports/interfaces/nuisance.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
)
from nipype.utils.filemanip import fname_presuffix

from nireports.reportlets.nuisance import confounds_correlation_plot
from nireports.reportlets.nuisance import confounds_correlation_plot, plot_raincloud
from nireports.reportlets.xca import compcor_variance_plot


Expand Down Expand Up @@ -142,3 +142,109 @@ def _run_interface(self, runtime):
ignore_initial_volumes=self.inputs.ignore_initial_volumes,
)
return runtime


class _RaincloudPlotInputSpec(BaseInterfaceInputSpec):
data_file = File(exists=True, mandatory=True, desc="File containing the data")
out_file = traits.Either(None, File, value=None, usedefault=True, desc="Path to save plot")
group_name = traits.Str(
"group_name",
mandatory=True,
desc="Group name of interest",
)
feature = traits.Str(
"feature",
mandatory=True,
desc="Feature of interest",
)
palette = traits.Str(
"Set2",
usedefault=True,
desc="Color palette name",
)
orient = traits.Str(
"v",
usedefault=True,
desc="Orientation",
)
density = traits.Bool(
True,
usedefault=True,
desc="``True`` to plot the density",
)
upper_limit_value = traits.Float(
None,
usedefault=True,
desc="Upper limit value over which any value in the data will be styled "
"with a different style",
)
upper_limit_color = traits.Str(
"gray",
usedefault=True,
desc="Lower limit value under which any value in the data will be styled "
"with a different style",
)
lower_limit_value = traits.Float(
None,
usedefault=True,
desc="",
)
lower_limit_color = traits.Str(
"gray",
usedefault=True,
desc="Color name to represent values under ``lower_limit_value``",
)
limit_offset = traits.Float(
None,
usedefault=True,
desc="Offset to plot the values over/under the upper/lower limit values",
)
mark_nans = traits.Bool(
True,
usedefault=True,
desc="``True`` to plot NaNs as dots. ``nans_values`` must be provided if True",
)
nans_value = traits.Float(
None,
usedefault=True,
desc="Value to use for NaN values`",
)
nans_color = traits.Str(
"black",
usedefault=True,
desc="Color name to represent NaN values",
)


class _RaincloudPlotOutputSpec(TraitedSpec):
out_file = File(exists=True, desc="Path to saved plot")


class RaincloudPlot(SimpleInterface):
"""Plot a raincloud of values."""

input_spec = _RaincloudPlotInputSpec
output_spec = _RaincloudPlotOutputSpec

def _run_interface(self, runtime, **kwargs):
if self.inputs.out_file is None:
self._results["out_file"] = fname_presuffix(
self.inputs.data_file,
suffix="_raincloud.svg",
use_ext=False,
newpath=runtime.cwd,
)
else:
self._results["out_file"] = self.inputs.out_file
plot_raincloud(
data_file=self.inputs.data_file,
group_name=self.inputs.group_name,
feature=self.inputs.feature,
palette=self.inputs.palette,
orient=self.inputs.orient,
density=self.inputs.density,
mark_nans=self.inputs.mark_nans,
output_file=self._results["out_file"],
**kwargs,
)
return runtime
28 changes: 28 additions & 0 deletions nireports/reportlets/modality/dwi.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
from mpl_toolkits.mplot3d import art3d
from nilearn.plotting import plot_anat

from nireports.reportlets.nuisance import plot_raincloud


def plot_dwi(dataobj, affine, gradient=None, **kwargs):
"""
Expand Down Expand Up @@ -405,3 +407,29 @@ def plot_gradients(
plt.suptitle(title)

return ax


def plot_tissue_values(data_file, group_name, feature, **kwargs):
"""Generate a raincloud plot with the data points corresponding to the
``feature`` value contained in the data file.
Parameters
----------
data_file : :obj:`str`
File containing the data of interest.
group_name : :obj:`str`
The group name of interest to be plot.
feature : :obj:`str`
The feature of interest to be plot.
kwargs : :obj:`dict`
Extra args given to :func:~`nireports.reportlets.nuisance.plot_raincloud`.
Returns
-------
axes and gridspec
Plotting axes and gridspec. Returned only if ``output_file`` is ``None``.
output_file : :obj:`str`
The file where the figure is saved.
"""

return plot_raincloud(data_file, group_name, feature, **kwargs)
Loading

0 comments on commit e6ac5f1

Please sign in to comment.