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] Allow setting vmin in plot_glass_brain and plot_stat_map #3993

Merged
merged 25 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9b4ba9e
add vmin to plot_glass_brain
michellewang Sep 12, 2023
943033a
update thresholding
michellewang Sep 12, 2023
e26c79e
add test for plot_glass_brain
michellewang Sep 12, 2023
df374fb
update _get_colorbar_and_data_ranges() and tests
michellewang Sep 12, 2023
bf597fa
use half of colormap if plot_abs is True
michellewang Sep 12, 2023
6d121f6
update docstrings
michellewang Sep 18, 2023
65a518f
fix plot_abs and nan vmin/vmax
michellewang Sep 19, 2023
0f49ef7
minor refactoring
michellewang Sep 19, 2023
7da66f1
add example with vmin
michellewang Sep 19, 2023
070d724
fix flake8 and do thresholding after transform_to_2d
michellewang Sep 20, 2023
174366e
update example
michellewang Sep 20, 2023
751d054
attempt to increase test coverage
michellewang Sep 20, 2023
1c3bb48
Merge branch 'main' into 3084/more_vmin
michellewang Sep 20, 2023
35f73d4
add changelog entry
michellewang Sep 20, 2023
8dff80f
Merge branch 'main' into 3084/more_vmin
michellewang Sep 26, 2023
d420580
move default="auto" for symmetric_cbar to substitution string
michellewang Sep 26, 2023
10c1ff0
remove redundant absolute value transformation
michellewang Sep 26, 2023
fdfbb9f
Merge remote-tracking branch 'upstream/main' into 3084/more_vmin
michellewang Oct 2, 2023
a082c03
attempt to increase test coverage
michellewang Oct 2, 2023
3a9b38e
update docs for symmetric_cbar
michellewang Oct 5, 2023
a49efe8
replace error with warning for plot_abs/vmin case
michellewang Oct 5, 2023
56ab1f9
Merge remote-tracking branch 'upstream/main' into 3084/more_vmin
michellewang Oct 5, 2023
b04548e
allow arbitrary vmin even if plot_abs is True
michellewang Oct 6, 2023
c45e8cd
Apply suggestions from code review
Oct 10, 2023
58f717d
Merge branch 'main' into HEAD
Oct 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/changes/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Enhancements

- Improve ``generate_report`` method of maskers by allowing users to pass a cmap argument for plotting image (:gh:`3897` by `Yasmin Mzayek`_)

- Allow setting ``vmin`` in :func:`~nilearn.plotting.plot_glass_brain` and :func:`~nilearn.plotting.plot_stat_map` (:gh:`3993` by `Michelle Wang`_).

Changes
-------

Expand Down
30 changes: 30 additions & 0 deletions examples/01_plotting/plot_demo_glass_brain_extensive.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,36 @@
stat_img, threshold=3, colorbar=True, plot_abs=False, display_mode="yx"
)

###############################################################################
michellewang marked this conversation as resolved.
Show resolved Hide resolved
# We can control the limits of the colormap and colorbar by setting ``vmin``
# and ``vmax``. Note that we use a non-diverging colormap here since the
# colorbar will not be centered around zero.

# only plot positive values
plotting.plot_glass_brain(
stat_img,
colorbar=True,
plot_abs=False,
display_mode="yz",
vmin=0,
threshold=2,
symmetric_cbar=False,
cmap="viridis",
)

###############################################################################
michellewang marked this conversation as resolved.
Show resolved Hide resolved
# Here we set ``vmin`` to the threshold to use the full color range instead of
# losing colours due to the thresholding.
plotting.plot_glass_brain(
stat_img,
colorbar=True,
plot_abs=False,
display_mode="yz",
vmin=2,
threshold=2,
symmetric_cbar=False,
cmap="viridis",
)

###############################################################################
# Different projections for the left and right hemispheres
Expand Down
15 changes: 6 additions & 9 deletions nilearn/_utils/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -879,15 +879,12 @@ def custom_function(vertices):
"symmetric_cbar"
] = """
symmetric_cbar : :obj:`bool`, or "auto", optional
michellewang marked this conversation as resolved.
Show resolved Hide resolved
Specifies whether the colorbar should range from `-vmax` to `vmax`
or from `vmin` to `vmax`.
Setting to `"auto"` will select the latter
if the range of the whole image is either positive or negative.

.. note::

The colormap will always range from `-vmax` to `vmax`.

Specifies whether the colorbar and colormap should range from `-vmax` to
`vmax` (or from `vmin` to `-vmin` if `-vmin` is greater than `vmax`) or
from `vmin` to `vmax`.
Setting to `"auto"` will select the former if either 1) `vmin` or `vmax` is
None and the image has both positive and negative values, or 2) `vmin` is
equal to `-vmax`.
ymzayek marked this conversation as resolved.
Show resolved Hide resolved
"""

# t_r
Expand Down
4 changes: 4 additions & 0 deletions nilearn/plotting/displays/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,10 @@
else:
data_selection = data

# take absolute value if needed
if self._plot_abs:
data_selection = np.abs(data_selection)

Check warning on line 436 in nilearn/plotting/displays/_axes.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/displays/_axes.py#L436

Added line #L436 was not covered by tests

# We need to make sure data_selection is not empty in the x axis
# This should be the case since we expect images in MNI space
if data_selection.shape[0] == 0:
Expand Down
35 changes: 23 additions & 12 deletions nilearn/plotting/displays/_slicers.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,16 +397,6 @@
img = reorder_img(img, resample=resampling_interpolation)
threshold = float(threshold) if threshold is not None else None

if threshold is not None:
data = _safe_get_data(img, ensure_finite=True)
if threshold == 0:
data = np.ma.masked_equal(data, 0, copy=False)
else:
data = np.ma.masked_inside(
data, -threshold, threshold, copy=False
)
img = new_img_like(img, data, img.affine)

Comment on lines -401 to -410
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

affine = img.affine
data = _safe_get_data(img, ensure_finite=True)
data_bounds = get_bounds(data.shape, affine)
Expand Down Expand Up @@ -461,15 +451,36 @@
ims = []
to_iterate_over = zip(self.axes.values(), data_2d_list)
for display_ax, data_2d in to_iterate_over:
# If data_2d is completely masked, then there is nothing to
# plot. Hence, no point to do imshow().
if data_2d is not None:
# If data_2d is completely masked, then there is nothing to
# plot. Hence, no point to do imshow().
data_2d = self._threshold(

Check warning on line 457 in nilearn/plotting/displays/_slicers.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/displays/_slicers.py#L457

Added line #L457 was not covered by tests
data_2d,
threshold,
vmin=kwargs.get("vmin"),
vmax=kwargs.get("vmax"),
)

im = display_ax.draw_2d(
data_2d, data_bounds, bounding_box, type=type, **kwargs
)
ims.append(im)
return ims

def _threshold(self, data, threshold=None, vmin=None, vmax=None):

Check warning on line 470 in nilearn/plotting/displays/_slicers.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/displays/_slicers.py#L470

Added line #L470 was not covered by tests
ymzayek marked this conversation as resolved.
Show resolved Hide resolved
"""Threshold the data."""
if threshold is not None:
data = np.ma.masked_where(

Check warning on line 473 in nilearn/plotting/displays/_slicers.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/displays/_slicers.py#L473

Added line #L473 was not covered by tests
np.abs(data) <= threshold,
data,
copy=False,
)
if vmin is not None and vmin >= -threshold:
data = np.ma.masked_where(data < vmin, data, copy=False)

Check warning on line 479 in nilearn/plotting/displays/_slicers.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/displays/_slicers.py#L479

Added line #L479 was not covered by tests
if vmax is not None and vmax <= threshold:
data = np.ma.masked_where(data > vmax, data, copy=False)
return data

Check warning on line 482 in nilearn/plotting/displays/_slicers.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/displays/_slicers.py#L481-L482

Added lines #L481 - L482 were not covered by tests

@fill_doc
def _show_colorbar(
self, cmap, norm, cbar_vmin=None, cbar_vmax=None, threshold=None
Expand Down
88 changes: 52 additions & 36 deletions nilearn/plotting/img_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
# delayed, so that the part module can be used without them).
import numpy as np
from matplotlib import gridspec as mgs
from matplotlib.colors import LinearSegmentedColormap

Check warning on line 25 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L25

Added line #L25 was not covered by tests
from nibabel.spatialimages import SpatialImage
from scipy import stats
from scipy.ndimage import binary_fill_holes
Expand Down Expand Up @@ -62,22 +63,20 @@

def _get_colorbar_and_data_ranges(
stat_map_data, vmin=None, vmax=None, symmetric_cbar=True,
force_min_stat_map_value=None, symmetric_data_range=True,
force_min_stat_map_value=None,
):
"""Set colormap and colorbar limits.

Used by plot_stat_map, plot_glass_brain and plot_img_on_surf.

If symmetric_data_range is True, the limits for the colormap will
always be set to range from -vmax to vmax. The limits for the colorbar
depend on the symmetric_cbar argument, please refer to docstring of
plot_stat_map.
The limits for the colorbar depend on the symmetric_cbar argument. Please
refer to docstring of plot_stat_map.
"""
if symmetric_data_range and (vmin is not None):
raise ValueError('this function does not accept a "vmin" '
'argument, as it uses a symmetrical range '
'defined via the vmax argument. To threshold '
'the plotted map, use the "threshold" argument')
# handle invalid vmin/vmax inputs
if (not isinstance(vmin, numbers.Number)) or (not np.isfinite(vmin)):
vmin = None

Check warning on line 77 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L77

Added line #L77 was not covered by tests
if (not isinstance(vmax, numbers.Number)) or (not np.isfinite(vmax)):
vmax = None

Check warning on line 79 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L79

Added line #L79 was not covered by tests

# avoid dealing with masked_array:
if hasattr(stat_map_data, '_mask'):
Expand All @@ -91,13 +90,13 @@
stat_map_max = np.nanmax(stat_map_data)

if symmetric_cbar == "auto":
if symmetric_data_range or (vmin is None) or (vmax is None):
if (vmin is None) or (vmax is None):
symmetric_cbar = stat_map_min < 0 and stat_map_max > 0
else:
symmetric_cbar = np.isclose(vmin, -vmax)

# check compatibility between vmin, vmax and symmetric_cbar
if symmetric_cbar or symmetric_data_range:
if symmetric_cbar:
if vmin is None and vmax is None:
vmax = max(-stat_map_min, stat_map_max)
vmin = -vmax
Expand All @@ -109,28 +108,35 @@
raise ValueError(
"vmin must be equal to -vmax unless symmetric_cbar is False."
)

# set vmin/vmax based on data if they are not already set
if vmin is None:
vmin = stat_map_min
if vmax is None:
vmax = stat_map_max
cbar_vmin = vmin
cbar_vmax = vmax

Check warning on line 112 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L111-L112

Added lines #L111 - L112 were not covered by tests

# set colorbar limits
if not symmetric_cbar:
else:
negative_range = stat_map_max <= 0
positive_range = stat_map_min >= 0
if positive_range:
cbar_vmin = 0
cbar_vmax = None
if vmin is None:
cbar_vmin = 0

Check warning on line 120 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L120

Added line #L120 was not covered by tests
else:
cbar_vmin = vmin
cbar_vmax = vmax

Check warning on line 123 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L122-L123

Added lines #L122 - L123 were not covered by tests
elif negative_range:
cbar_vmax = 0
cbar_vmin = None
if vmax is None:
cbar_vmax = 0

Check warning on line 126 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L126

Added line #L126 was not covered by tests
else:
cbar_vmax = vmax
cbar_vmin = vmin

Check warning on line 129 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L128-L129

Added lines #L128 - L129 were not covered by tests
else:
cbar_vmin = stat_map_min
cbar_vmax = stat_map_max
else:
cbar_vmin, cbar_vmax = None, None
# limit colorbar to plotted values
cbar_vmin = vmin
cbar_vmax = vmax

Check warning on line 133 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L132-L133

Added lines #L132 - L133 were not covered by tests

# set vmin/vmax based on data if they are not already set
if vmin is None:
vmin = stat_map_min

Check warning on line 137 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L137

Added line #L137 was not covered by tests
if vmax is None:
vmax = stat_map_max

Check warning on line 139 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L139

Added line #L139 was not covered by tests

return cbar_vmin, cbar_vmax, vmin, vmax

Expand Down Expand Up @@ -1073,20 +1079,30 @@
"""
if cmap is None:
cmap = cm.cold_hot if black_bg else cm.cold_white_hot
# use only positive half of colormap if plotting absolute values
if plot_abs:
cmap = LinearSegmentedColormap.from_list(

Check warning on line 1084 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L1084

Added line #L1084 was not covered by tests
'cmap_pos', cmap(np.linspace(0.5, 1, 256)),
)

if stat_map_img:
stat_map_img = _utils.check_niimg_3d(stat_map_img, dtype='auto')
if plot_abs:
cbar_vmin, cbar_vmax, vmin, vmax = _get_colorbar_and_data_ranges(
_safe_get_data(stat_map_img, ensure_finite=True),
vmax=vmax,
symmetric_cbar=symmetric_cbar,
force_min_stat_map_value=0)
if vmin is not None and vmin < 0:
raise ValueError(

Check warning on line 1092 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L1092

Added line #L1092 was not covered by tests
ymzayek marked this conversation as resolved.
Show resolved Hide resolved
'vmin cannot be negative if plot_abs is True'
)
force_min_stat_map_value = 0

Check warning on line 1095 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L1095

Added line #L1095 was not covered by tests
else:
cbar_vmin, cbar_vmax, vmin, vmax = _get_colorbar_and_data_ranges(
_safe_get_data(stat_map_img, ensure_finite=True),
vmax=vmax,
symmetric_cbar=symmetric_cbar)
force_min_stat_map_value = None

Check warning on line 1097 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L1097

Added line #L1097 was not covered by tests

cbar_vmin, cbar_vmax, vmin, vmax = _get_colorbar_and_data_ranges(

Check warning on line 1099 in nilearn/plotting/img_plotting.py

View check run for this annotation

Codecov / codecov/patch

nilearn/plotting/img_plotting.py#L1099

Added line #L1099 was not covered by tests
_safe_get_data(stat_map_img, ensure_finite=True),
vmin=vmin,
vmax=vmax,
symmetric_cbar=symmetric_cbar,
force_min_stat_map_value=force_min_stat_map_value,
)
else:
cbar_vmin, cbar_vmax = None, None

Expand Down
10 changes: 2 additions & 8 deletions nilearn/plotting/surf_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -1142,7 +1142,6 @@ def plot_surf_stat_map(surf_mesh, stat_map, bg_map=None,
vmin=vmin,
vmax=vmax,
symmetric_cbar=symmetric_cbar,
symmetric_data_range=False,
)

display = plot_surf(
Expand Down Expand Up @@ -1264,7 +1263,6 @@ def _colorbar_from_array(array, vmin, vmax, threshold, symmetric_cbar=True,
vmin=vmin,
vmax=vmax,
symmetric_cbar=symmetric_cbar,
symmetric_data_range=False,
)
norm = Normalize(vmin=vmin, vmax=vmax)
cmaplist = [cmap(i) for i in range(cmap.N)]
Expand Down Expand Up @@ -1350,11 +1348,8 @@ def plot_img_on_surf(stat_map, surf_mesh='fsaverage5', mask_img=None,
%(vmin)s
%(vmax)s
%(threshold)s
symmetric_cbar : :obj:`bool`, or "auto", optional
Specifies whether the colorbar should range from `-vmax` to `vmax`
(or from `vmin` to `-vmin` if `-vmin` is greater than `vmax`) or
from `vmin` to `vmax`.
Default=True.
%(symmetric_cbar)s
Default='auto'.
%(cmap)s
Default='cold_hot'.
kwargs : dict, optional
Expand Down Expand Up @@ -1412,7 +1407,6 @@ def plot_img_on_surf(stat_map, surf_mesh='fsaverage5', mask_img=None,
vmin=vmin,
vmax=vmax,
symmetric_cbar=symmetric_cbar,
symmetric_data_range=False,
)

for i, (mode, hemi) in enumerate(itertools.product(modes, hemis)):
Expand Down