Skip to content

Commit

Permalink
[ENH]: Move func _mix_colormaps (#3919)
Browse files Browse the repository at this point in the history
* Move func _mix_colormaps

* Fix isort and black

* Add to latest

* Clean commit

* Fix doc

---------

Co-authored-by: achamma <ahmad.chamma@inria.fr>
  • Loading branch information
achamma723 and achamma committed Aug 22, 2023
1 parent 84984de commit 0d70562
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 87 deletions.
1 change: 1 addition & 0 deletions doc/changes/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ Changes
- Refactor error raising tests using context managers (:gh:`3854` BY `François Paugam`_)
- Added warning to deprecate `darkness` in ``surf_plotting._compute_facecolors_matplotlib`` and ``html_surface._get_vertexcolor`` (:gh`3855` by `Alisha Kodibagkar`_)
- :bdg-secondary:`Doc` Replace skipped doctests with default code-blocks (:gh:`3681` in by `Patrick Sadil`_)
- Move the `~nilearn.plotting.html_surface._mix_colormaps` to `cm.py` in :mod:`~nilearn.plotting` (:gh:`3919` by `Ahmad Chamma`_)
38 changes: 38 additions & 0 deletions nilearn/plotting/cm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,44 @@
# Custom colormaps for two-tailed symmetric statistics


def _mix_colormaps(fg, bg):
"""Mixes foreground and background arrays of RGBA colors.
Parameters
----------
fg : numpy.ndarray
Array of shape (n, 4), foreground RGBA colors
represented as floats in [0, 1]
bg : numpy.ndarray
Array of shape (n, 4), background RGBA colors
represented as floats in [0, 1]
Returns
-------
mix : numpy.ndarray
Array of shape (n, 4), mixed colors
represented as floats in [0, 1]
"""
# Adapted from https://stackoverflow.com/questions/726549/algorithm-for-additive-color-mixing-for-rgb-values/727339#727339 # noqa: E501
if fg.shape != bg.shape:
raise ValueError(
"Trying to mix colormaps with different shapes: "
f"{fg.shape}, {bg.shape}"
)

mix = _np.empty_like(fg)

mix[:, 3] = 1 - (1 - fg[:, 3]) * (1 - bg[:, 3])

for color_index in range(0, 3):
mix[:, color_index] = (
fg[:, color_index] * fg[:, 3]
+ bg[:, color_index] * bg[:, 3] * (1 - fg[:, 3])
) / mix[:, 3]

return mix


def _rotate_cmap(cmap, swap_order=("green", "red", "blue")):
"""Swap the colors of a colormap."""
orig_cdict = cmap._segmentdata.copy()
Expand Down
40 changes: 1 addition & 39 deletions nilearn/plotting/html_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,44 +26,6 @@ class SurfaceView(HTMLDocument): # noqa: D101
pass


def _mix_colormaps(fg, bg):
"""Mixes foreground and background arrays of RGBA colors.
Parameters
----------
fg : numpy.ndarray
Array of shape (n, 4), foreground RGBA colors
represented as floats in [0, 1]
bg : numpy.ndarray
Array of shape (n, 4), background RGBA colors
represented as floats in [0, 1]
Returns
-------
mix : numpy.ndarray
Array of shape (n, 4), mixed colors
represented as floats in [0, 1]
"""
# Adapted from https://stackoverflow.com/questions/726549/algorithm-for-additive-color-mixing-for-rgb-values/727339#727339 # noqa: E501
if fg.shape != bg.shape:
raise ValueError(
"Trying to mix colormaps with different shapes: "
f"{fg.shape}, {bg.shape}"
)

mix = np.empty_like(fg)

mix[:, 3] = 1 - (1 - fg[:, 3]) * (1 - bg[:, 3])

for color_index in range(0, 3):
mix[:, color_index] = (
fg[:, color_index] * fg[:, 3]
+ bg[:, color_index] * bg[:, 3] * (1 - fg[:, 3])
) / mix[:, 3]

return mix


def _get_vertexcolor(surf_map, cmap, norm,
absolute_threshold=None, bg_map=None,
bg_on_data=None, darkness=None):
Expand Down Expand Up @@ -105,7 +67,7 @@ def _get_vertexcolor(surf_map, cmap, norm,
# so that background map becomes visible
surf_colors[~under_threshold, 3] = 0.7

vertex_colors = _mix_colormaps(surf_colors, bg_colors)
vertex_colors = cm._mix_colormaps(surf_colors, bg_colors)

return to_color_strings(vertex_colors)

Expand Down
4 changes: 2 additions & 2 deletions nilearn/plotting/surf_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

from nilearn import image, surface
from nilearn._utils import check_niimg_3d, fill_doc
from nilearn.plotting.cm import cold_hot
from nilearn.plotting.html_surface import _get_vertexcolor, _mix_colormaps
from nilearn.plotting.cm import _mix_colormaps, cold_hot
from nilearn.plotting.html_surface import _get_vertexcolor
from nilearn.plotting.img_plotting import _get_colorbar_and_data_ranges
from nilearn.plotting.js_plotting_utils import colorscale
from nilearn.surface import load_surf_data, load_surf_mesh, vol_to_surf
Expand Down
49 changes: 48 additions & 1 deletion nilearn/plotting/tests/test_cm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
# vi: set ft=python sts=4 ts=4 sw=4 et:
"""Smoke testing the cm module."""
import matplotlib.pyplot as plt
import numpy as np
import pytest

from nilearn.plotting.cm import dim_cmap, replace_inside
from nilearn.plotting.cm import _mix_colormaps, dim_cmap, replace_inside


def test_dim_cmap():
Expand All @@ -22,3 +24,48 @@ def test_replace_inside():

def test_cm_preload():
plt.imshow([list(range(10))], cmap="cold_hot")


def test_mix_colormaps():
n = 100

# Mixin map's shape should be equal to that of
# the foreground and background maps
foreground_map = np.random.rand(n, 4)
background_map = np.random.rand(n, 4)
mix_map = _mix_colormaps(foreground_map, background_map)
assert mix_map.shape == (n, 4)
# Transparency of mixin map should be higher
# than that of both the background and the foreground maps
assert np.all(mix_map[:, 3] >= foreground_map[:, 3])
assert np.all(mix_map[:, 3] >= background_map[:, 3])

# If foreground and background maps' shapes are different,
# an Exception should be raised
background_map = np.random.rand(n - 1, 4)
with pytest.raises(Exception):
_mix_colormaps(foreground_map, background_map)

# If foreground map is transparent,
# mixin should be equal to background map
foreground_map = np.random.rand(n, 4)
background_map = np.random.rand(n, 4)
foreground_map[:, 3] = 0
mix_map = _mix_colormaps(foreground_map, background_map)
assert np.allclose(mix_map, background_map)

# If background map is transparent,
# mixin should be equal to foreground map
foreground_map = np.random.rand(n, 4)
background_map = np.random.rand(n, 4)
background_map[:, 3] = 0
mix_map = _mix_colormaps(foreground_map, background_map)
assert np.allclose(mix_map, foreground_map)

# If foreground and background maps are equal,
# RBG values of the mixin map should be equal
# to that of the foreground and background maps
foreground_map = np.random.rand(n, 4)
background_map = foreground_map
mix_map = _mix_colormaps(foreground_map, background_map)
assert np.allclose(mix_map[:, :3], foreground_map[:, :3])
45 changes: 0 additions & 45 deletions nilearn/plotting/tests/test_html_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,48 +224,3 @@ def test_view_img_on_surf():
"radius": 0.,
"interpolation": "nearest"})
check_html(html)


def test_mix_colormaps():
n = 100

# Mixin map's shape should be equal to that of
# the foreground and background maps
foreground_map = np.random.rand(n, 4)
background_map = np.random.rand(n, 4)
mix_map = html_surface._mix_colormaps(foreground_map, background_map)
assert mix_map.shape == (n, 4)
# Transparency of mixin map should be higher
# than that of both the background and the foreground maps
assert np.all(mix_map[:, 3] >= foreground_map[:, 3])
assert np.all(mix_map[:, 3] >= background_map[:, 3])

# If foreground and background maps' shapes are different,
# an Exception should be raised
background_map = np.random.rand(n - 1, 4)
with pytest.raises(Exception):
html_surface._mix_colormaps(foreground_map, background_map)

# If foreground map is transparent,
# mixin should be equal to background map
foreground_map = np.random.rand(n, 4)
background_map = np.random.rand(n, 4)
foreground_map[:, 3] = 0
mix_map = html_surface._mix_colormaps(foreground_map, background_map)
assert np.allclose(mix_map, background_map)

# If background map is transparent,
# mixin should be equal to foreground map
foreground_map = np.random.rand(n, 4)
background_map = np.random.rand(n, 4)
background_map[:, 3] = 0
mix_map = html_surface._mix_colormaps(foreground_map, background_map)
assert np.allclose(mix_map, foreground_map)

# If foreground and background maps are equal,
# RBG values of the mixin map should be equal
# to that of the foreground and background maps
foreground_map = np.random.rand(n, 4)
background_map = foreground_map
mix_map = html_surface._mix_colormaps(foreground_map, background_map)
assert np.allclose(mix_map[:, :3], foreground_map[:, :3])

0 comments on commit 0d70562

Please sign in to comment.