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

ENH: Add DWI volume plot method #101

Merged
merged 6 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ jobs:
name: Install package
command: pip install .[test]

- restore_cache:
keys:
- apt-v0
paths:
- /var/lib/apt

- run:
name: Install texlive
command: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends dvipng texlive texlive-latex-extra cm-super

- save_cache:
key: apt-v0
paths:
- /var/lib/apt

- restore_cache:
keys:
- tf-v0
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/build_test_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ jobs:
- name: Display Python version
run: python -c "import sys; print(sys.version)"

- uses: actions/cache@v4
with:
path: /var/lib/apt
key: apt-cache-v0
restore-keys: |
apt-cache-v0
- name: Install tex
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends dvipng texlive texlive-latex-extra cm-super

- name: Install package
run: pip install $PACKAGE
- name: Verify installation
Expand Down
56 changes: 56 additions & 0 deletions nireports/reportlets/modality/dwi.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,66 @@
# https://www.nipreps.org/community/licensing/
#
"""Visualizations for diffusion MRI data."""
import nibabel as nb
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.pyplot import cm
from mpl_toolkits.mplot3d import art3d
from nilearn.plotting import plot_anat


def plot_dwi(dataobj, affine, gradient=None, **kwargs):
"""
Plot orthogonal (axial, coronal, sagittal) slices of a given DWI volume. The
slices displayed are determined by a tuple contained in the ``cut_coords``
keyword argument.

Parameters
----------
dataobj : :obj:`numpy.ndarray`
DWI volume data: a single 3D volume from a given gradient direction.
affine : :obj:`numpy.ndarray`
Affine transformation matrix.
gradient : :obj:`numpy.ndarray`
Gradient values in RAS+b format at the chosen gradient direction.
kwargs : :obj:`dict`
Extra args given to :obj:`nilearn.plotting.plot_anat()`.

Returns
-------
:class:`nilearn.plotting.displays.OrthoSlicer` or None
An instance of the OrthoSlicer class. If ``output_file`` is defined,
None is returned.

"""

plt.rcParams.update(
{
"text.usetex": True,
"font.family": "sans-serif",
"font.sans-serif": ["Helvetica"],
}
)

affine = np.diag(nb.affines.voxel_sizes(affine).tolist() + [1])
affine[:3, 3] = -1.0 * (affine[:3, :3] @ ((np.array(dataobj.shape) - 1) * 0.5))

vmax = kwargs.pop("vmax", None) or np.percentile(dataobj, 98)
cut_coords = kwargs.pop("cut_coords", None) or (0, 0, 0)

return plot_anat(
nb.Nifti1Image(dataobj, affine, None),
vmax=vmax,
cut_coords=cut_coords,
title=(
r"Reference $b$=0"
if gradient is None
else f"""\
$b$={gradient[3].astype(int)}, \
$\\vec{{b}}$ = ({', '.join(str(v) for v in gradient[:3])})"""
),
**kwargs,
)


def plot_heatmap(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0 0 0 0 0 0 0 1000 1000 1000 1000 1000 1000 1000 1000 1000 1000 1000 1000 1000
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
0 0 0 0 0 0 0 -1 -0.002 0.026 -0.591 0.236 0.893 -0.796 -0.234 -0.936 -0.506 -0.346 -0.457 0.487
0 0 0 0 0 0 0 0 1 0.649 -0.766 -0.524 -0.259 0.129 0.93 0.14 -0.845 -0.847 -0.631 -0.389
0 0 0 0 0 0 0 0 0 0.76 0.252 0.818 0.368 0.591 0.284 0.324 -0.175 -0.402 -0.627 0.782
Binary file not shown.
26 changes: 25 additions & 1 deletion nireports/tests/test_dwi.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,35 @@
"""Test DWI reportlets."""

import pytest
from pathlib import Path

import nibabel as nb
import numpy as np
from matplotlib import pyplot as plt

from nireports.reportlets.modality.dwi import plot_gradients
from nireports.reportlets.modality.dwi import plot_dwi, plot_gradients


def test_plot_dwi(tmp_path, testdata_path, outdir):
"""Check the plot of DWI data."""

stem = 'ds000114_sub-01_ses-test_desc-trunc_dwi'
dwi_img = nb.load(testdata_path / f'{stem}.nii.gz')
affine = dwi_img.affine

bvecs = np.loadtxt(testdata_path / f'{stem}.bvec').T
bvals = np.loadtxt(testdata_path / f'{stem}.bval')

gradients = np.hstack([bvecs, bvals[:, None]])

# Pick a random volume to show
rng = np.random.default_rng(1234)
idx = rng.integers(low=0, high=dwi_img.shape[-1], size=1).item()

_ = plot_dwi(dwi_img.get_fdata()[..., idx], affine, gradient=gradients[idx])

if outdir is not None:
plt.savefig(outdir / f'{stem}.svg', bbox_inches='tight')


@pytest.mark.parametrize(
Expand Down
Loading