Skip to content

Commit 4ac16d5

Browse files
authored
Merge pull request #57 from oesteban/enh/plotting-transparent
ENH: Transparency on fieldmap plots!
2 parents 065a2c4 + 930bf35 commit 4ac16d5

File tree

4 files changed

+66
-20
lines changed

4 files changed

+66
-20
lines changed

sdcflows/interfaces/reportlets.py

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
22
# vi: set ft=python sts=4 ts=4 sw=4 et:
33
"""Interfaces to generate speciality reportlets."""
4+
import numpy as np
45
from nilearn.image import threshold_img, load_img
56
from niworkflows import NIWORKFLOWS_LOG
67
from niworkflows.viz.utils import cuts_from_bbox, compose_view
7-
from nipype.interfaces.base import File, isdefined
8+
from nipype.interfaces.base import File, isdefined, traits
89
from nipype.interfaces.mixins import reporting
910

1011
from ..viz.utils import plot_registration, coolwarm_transparent
1112

1213

1314
class _FieldmapReportletInputSpec(reporting.ReportCapableInputSpec):
1415
reference = File(exists=True, mandatory=True, desc='input reference')
16+
moving = File(exists=True, desc='input moving')
1517
fieldmap = File(exists=True, mandatory=True, desc='input fieldmap')
18+
max_alpha = traits.Float(0.7, usedefault=True, desc='maximum alpha channel')
1619
mask = File(exists=True, desc='brain mask')
1720
out_report = File('report.svg', usedefault=True,
1821
desc='filename for the visual report')
22+
show = traits.Enum(1, 0, 'both', usedefault=True,
23+
desc='where the fieldmap should be shown')
24+
reference_label = traits.Str('Reference', usedefault=True,
25+
desc='a label name for the reference mosaic')
26+
moving_label = traits.Str('Fieldmap (Hz)', usedefault=True,
27+
desc='a label name for the reference mosaic')
1928

2029

2130
class FieldmapReportlet(reporting.ReportCapableInterface):
@@ -37,30 +46,51 @@ def _generate_report(self):
3746
"""Generate a reportlet."""
3847
NIWORKFLOWS_LOG.info('Generating visual report')
3948

40-
refnii = load_img(self.inputs.reference)
49+
movnii = refnii = load_img(self.inputs.reference)
4150
fmapnii = load_img(self.inputs.fieldmap)
42-
contour_nii = load_img(self.inputs.mask) if isdefined(self.inputs.mask) else None
43-
mask_nii = threshold_img(refnii, 1e-3)
51+
52+
if isdefined(self.inputs.moving):
53+
movnii = load_img(self.inputs.moving)
54+
55+
contour_nii = mask_nii = None
56+
if isdefined(self.inputs.mask):
57+
contour_nii = load_img(self.inputs.mask)
58+
maskdata = contour_nii.get_fdata() > 0
59+
else:
60+
mask_nii = threshold_img(refnii, 1e-3)
61+
maskdata = mask_nii.get_fdata() > 0
4462
cuts = cuts_from_bbox(contour_nii or mask_nii, cuts=self._n_cuts)
4563
fmapdata = fmapnii.get_fdata()
46-
vmax = max(fmapdata.max(), abs(fmapdata.min()))
64+
vmax = max(abs(np.percentile(fmapdata[maskdata], 99.8)),
65+
abs(np.percentile(fmapdata[maskdata], 0.2)))
66+
67+
fmap_overlay = [{
68+
'overlay': fmapnii,
69+
'overlay_params': {
70+
'cmap': coolwarm_transparent(max_alpha=self.inputs.max_alpha),
71+
'vmax': vmax,
72+
'vmin': -vmax,
73+
}
74+
}] * 2
75+
76+
if self.inputs.show != 'both':
77+
fmap_overlay[not self.inputs.show] = {}
4778

4879
# Call composer
4980
compose_view(
50-
plot_registration(refnii, 'fixed-image',
81+
plot_registration(movnii, 'moving-image',
5182
estimate_brightness=True,
5283
cuts=cuts,
53-
label='reference',
84+
label=self.inputs.moving_label,
5485
contour=contour_nii,
55-
compress=False),
56-
plot_registration(fmapnii, 'moving-image',
86+
compress=False,
87+
**fmap_overlay[1]),
88+
plot_registration(refnii, 'fixed-image',
5789
estimate_brightness=True,
5890
cuts=cuts,
59-
label='fieldmap (Hz)',
91+
label=self.inputs.reference_label,
6092
contour=contour_nii,
6193
compress=False,
62-
plot_params={'cmap': coolwarm_transparent(),
63-
'vmax': vmax,
64-
'vmin': -vmax}),
94+
**fmap_overlay[0]),
6595
out_file=self._out_report
6696
)

sdcflows/viz/utils.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
def plot_registration(anat_nii, div_id, plot_params=None,
77
order=('z', 'x', 'y'), cuts=None,
88
estimate_brightness=False, label=None, contour=None,
9-
compress='auto'):
9+
compress='auto', overlay=None, overlay_params=None):
1010
"""
1111
Plot the foreground and background views.
1212
@@ -15,6 +15,7 @@ def plot_registration(anat_nii, div_id, plot_params=None,
1515
from uuid import uuid4
1616

1717
from lxml import etree
18+
import matplotlib.pyplot as plt
1819
from nilearn.plotting import plot_anat
1920
from svgutils.transform import SVGFigure
2021
from niworkflows.viz.utils import robust_set_limits, extract_svg, SVGNS
@@ -41,6 +42,15 @@ def plot_registration(anat_nii, div_id, plot_params=None,
4142

4243
# Generate nilearn figure
4344
display = plot_anat(anat_nii, **plot_params)
45+
if overlay is not None:
46+
_overlay_params = {
47+
'vmin': overlay.get_fdata().min(),
48+
'vmax': overlay.get_fdata().max(),
49+
'cmap': plt.cm.gray,
50+
'interpolation': 'nearest',
51+
}
52+
_overlay_params.update(overlay_params)
53+
display.add_overlay(overlay, **_overlay_params)
4454
if contour is not None:
4555
display.add_contours(contour, colors='g', levels=[0.5],
4656
linewidths=0.5)
@@ -60,7 +70,7 @@ def plot_registration(anat_nii, div_id, plot_params=None,
6070
return out_files
6171

6272

63-
def coolwarm_transparent():
73+
def coolwarm_transparent(max_alpha=0.7, opaque_perc=30, transparent_perc=8):
6474
"""Modify the coolwarm color scale to have full transparency around the middle."""
6575
import numpy as np
6676
import matplotlib.pylab as pl
@@ -72,9 +82,15 @@ def coolwarm_transparent():
7282
# Get the colormap colors
7383
my_cmap = cmap(np.arange(cmap.N))
7484

85+
_20perc = (cmap.N * opaque_perc) // 100
86+
midpoint = cmap.N // 2 + 1
87+
_10perc = (cmap.N * transparent_perc) // 100
7588
# Set alpha
76-
alpha = np.ones(cmap.N)
77-
alpha[128:160] = np.linspace(0, 1, len(alpha[128:160]))
78-
alpha[96:128] = np.linspace(1, 0, len(alpha[96:128]))
89+
alpha = np.ones(cmap.N) * max_alpha
90+
alpha[midpoint - _10perc:midpoint + _10perc] = 0
91+
alpha[_20perc:midpoint - _10perc - 1] = np.linspace(
92+
max_alpha, 0, len(alpha[_20perc:midpoint - _10perc - 1]))
93+
alpha[midpoint + _10perc:-_20perc] = np.linspace(
94+
0, max_alpha, len(alpha[midpoint + _10perc:-_20perc]))
7995
my_cmap[:, -1] = alpha
8096
return ListedColormap(my_cmap)

sdcflows/workflows/tests/test_pepolar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def test_pepolar_wf1(bids_layouts, output_path, dataset, workdir):
157157
wf.inputs.inputnode.in_reference_brain = boldref.path
158158
wf.inputs.inputnode.in_reference = boldref.path
159159

160-
rep = pe.Node(FieldmapReportlet(), 'simple_report')
160+
rep = pe.Node(FieldmapReportlet(reference_label='EPI Reference'), 'simple_report')
161161
rep.interface._always_run = True
162162
dsink = pe.Node(DerivativesDataSink(
163163
base_directory=str(output_path), keep_dtype=True,

sdcflows/workflows/tests/test_phdiff.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def test_workflow(bids_layouts, tmpdir, output_path, dataset, workdir):
3131

3232
if output_path:
3333
from ...interfaces.reportlets import FieldmapReportlet
34-
rep = pe.Node(FieldmapReportlet(), 'simple_report')
34+
rep = pe.Node(FieldmapReportlet(reference_label='Magnitude'), 'simple_report')
3535
rep.interface._always_run = True
3636

3737
dsink = pe.Node(DerivativesDataSink(

0 commit comments

Comments
 (0)