Coverage for nilearn/plotting/cm.py: 0%
135 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-16 12:32 +0200
1"""Matplotlib colormaps useful for neuroimaging."""
3import matplotlib
4import numpy as _np
5from matplotlib import cm as _cm
6from matplotlib import colors as _colors
7from matplotlib import rcParams as _rcParams
9from nilearn._utils import compare_version
11###############################################################################
12# Custom colormaps for two-tailed symmetric statistics
14# mypy: disable_error_code="attr-defined"
16__all__ = ["_cmap_d"]
19def mix_colormaps(fg, bg):
20 """Mixes foreground and background arrays of RGBA colors.
22 Parameters
23 ----------
24 fg : numpy.ndarray
25 Array of shape (n, 4), foreground RGBA colors
26 represented as floats in [0, 1]
27 bg : numpy.ndarray
28 Array of shape (n, 4), background RGBA colors
29 represented as floats in [0, 1]
31 Returns
32 -------
33 mix : numpy.ndarray
34 Array of shape (n, 4), mixed colors
35 represented as floats in [0, 1]
36 """
37 # Adapted from https://stackoverflow.com/questions/726549/algorithm-for-additive-color-mixing-for-rgb-values/727339#727339 # noqa: E501
38 if fg.shape != bg.shape:
39 raise ValueError(
40 "Trying to mix colormaps with different shapes: "
41 f"{fg.shape}, {bg.shape}"
42 )
44 mix = _np.empty_like(fg)
46 mix[:, 3] = 1 - (1 - fg[:, 3]) * (1 - bg[:, 3])
48 for color_index in range(3):
49 mix[:, color_index] = (
50 fg[:, color_index] * fg[:, 3]
51 + bg[:, color_index] * bg[:, 3] * (1 - fg[:, 3])
52 ) / mix[:, 3]
54 return mix
57def _rotate_cmap(cmap, swap_order=("green", "red", "blue")):
58 """Swap the colors of a colormap."""
59 cdict = cmap._segmentdata.copy()
61 return {
62 "green": list(cdict[swap_order[0]]),
63 "blue": list(cdict[swap_order[1]]),
64 "red": list(cdict[swap_order[2]]),
65 }
68def _fill_pigtailed_cmap(cdict, channel1, channel2):
69 return [
70 (0.5 * (1 - p), c1, c2) for (p, c1, c2) in reversed(cdict[channel1])
71 ] + [(0.5 * (1 + p), c1, c2) for (p, c1, c2) in cdict[channel2]]
74def _pigtailed_cmap(cmap, swap_order=("green", "red", "blue")):
75 """Make a new colormap by concatenating a colormap with its reverse."""
76 cdict = cmap._segmentdata.copy()
78 return {
79 "green": _fill_pigtailed_cmap(cdict, swap_order[0], "green"),
80 "blue": _fill_pigtailed_cmap(cdict, swap_order[1], "blue"),
81 "red": _fill_pigtailed_cmap(cdict, swap_order[2], "red"),
82 }
85def _concat_cmap(cmap1, cmap2):
86 """Make a new colormap by concatenating two colormaps."""
87 cdict = {}
89 cdict1 = cmap1._segmentdata.copy()
90 cdict2 = cmap2._segmentdata.copy()
91 if not callable(cdict1["red"]):
92 for c in ["red", "green", "blue"]:
93 cdict[c] = [(0.5 * p, c1, c2) for (p, c1, c2) in cdict1[c]]
94 else:
95 for c in ["red", "green", "blue"]:
96 cdict[c] = []
97 ps = _np.linspace(0, 1, 10)
98 colors = cmap1(ps)
99 for p, (r, g, b, _) in zip(ps, colors):
100 cdict["red"].append((0.5 * p, r, r))
101 cdict["green"].append((0.5 * p, g, g))
102 cdict["blue"].append((0.5 * p, b, b))
103 if not callable(cdict2["red"]):
104 for c in ["red", "green", "blue"]:
105 cdict[c].extend(
106 [(0.5 * (1 + p), c1, c2) for (p, c1, c2) in cdict2[c]]
107 )
108 else:
109 ps = _np.linspace(0, 1, 10)
110 colors = cmap2(ps)
111 for p, (r, g, b, _) in zip(ps, colors):
112 cdict["red"].append((0.5 * (1 + p), r, r))
113 cdict["green"].append((0.5 * (1 + p), g, g))
114 cdict["blue"].append((0.5 * (1 + p), b, b))
116 return cdict
119def alpha_cmap(color, name="", alpha_min=0.5, alpha_max=1.0):
120 """Return a colormap with the given color, and alpha going from zero to 1.
122 Parameters
123 ----------
124 color : (r, g, b), or a :obj:`str`
125 A triplet of floats ranging from 0 to 1, or a matplotlib
126 color string.
128 name : :obj:`str` , default=''
129 Name of the colormap.
131 alpha_min : :obj:`float`, default=0.5
132 Minimum value for alpha.
134 alpha_max : :obj:`float`, default=1.0
135 Maximum value for alpha.
137 """
138 red, green, blue = _colors.colorConverter.to_rgb(color)
139 if name == "" and hasattr(color, "startswith"):
140 name = color
141 cmapspec = [(red, green, blue, 1.0), (red, green, blue, 1.0)]
142 cmap = _colors.LinearSegmentedColormap.from_list(
143 f"{name}_transparent", cmapspec, _rcParams["image.lut"]
144 )
145 cmap._init()
146 cmap._lut[:, -1] = _np.linspace(alpha_min, alpha_max, cmap._lut.shape[0])
147 cmap._lut[-1, -1] = 0
148 return cmap
151###############################################################################
152# Our colormaps definition
155_cmaps_data = {
156 "cold_hot": _pigtailed_cmap(_cm.hot),
157 "cold_white_hot": _pigtailed_cmap(_cm.hot_r),
158 "brown_blue": _pigtailed_cmap(_cm.bone),
159 "cyan_copper": _pigtailed_cmap(_cm.copper),
160 "cyan_orange": _pigtailed_cmap(_cm.YlOrBr_r),
161 "blue_red": _pigtailed_cmap(_cm.Reds_r),
162 "brown_cyan": _pigtailed_cmap(_cm.Blues_r),
163 "purple_green": _pigtailed_cmap(
164 _cm.Greens_r, swap_order=("red", "blue", "green")
165 ),
166 "purple_blue": _pigtailed_cmap(
167 _cm.Blues_r, swap_order=("red", "blue", "green")
168 ),
169 "blue_orange": _pigtailed_cmap(
170 _cm.Oranges_r, swap_order=("green", "red", "blue")
171 ),
172 "black_blue": _rotate_cmap(_cm.hot),
173 "black_purple": _rotate_cmap(_cm.hot, swap_order=("blue", "red", "green")),
174 "black_pink": _rotate_cmap(_cm.hot, swap_order=("blue", "green", "red")),
175 "black_green": _rotate_cmap(_cm.hot, swap_order=("red", "blue", "green")),
176 "black_red": _cm.hot._segmentdata.copy(),
177}
179_cmaps_data["ocean_hot"] = _concat_cmap(_cm.ocean, _cm.hot_r)
180_cmaps_data["hot_white_bone"] = _concat_cmap(_cm.afmhot, _cm.bone_r)
182_cmaps_data["hot_black_bone"] = _concat_cmap(_cm.afmhot_r, _cm.bone)
185###############################################################################
186# Build colormaps and their reverse.
189# backported and adapted from matplotlib since it's deprecated in 3.2
190def _revcmap(data):
191 data_r = {
192 key: [(1.0 - x, y1, y0) for x, y0, y1 in reversed(val)]
193 for key, val in data.items()
194 }
195 return data_r
198_cmap_d = {}
200for _cmapname in list(_cmaps_data.keys()): # needed as dict changes in loop
201 _cmapname_r = f"{_cmapname}_r"
202 _cmapspec = _cmaps_data[_cmapname]
203 _cmaps_data[_cmapname_r] = _revcmap(_cmapspec)
204 _cmap_d[_cmapname] = _colors.LinearSegmentedColormap(
205 _cmapname, _cmapspec, _rcParams["image.lut"]
206 )
207 _cmap_d[_cmapname_r] = _colors.LinearSegmentedColormap(
208 _cmapname_r, _cmaps_data[_cmapname_r], _rcParams["image.lut"]
209 )
211###############################################################################
212# A few transparent colormaps
213for color, name in (
214 ((1, 0, 0), "red"),
215 ((0, 1, 0), "green"),
216 ((0, 0, 1), "blue"),
217):
218 _cmap_d[f"{name}_transparent"] = alpha_cmap(color, name=name)
219 _cmap_d[f"{name}_transparent_full_alpha_range"] = alpha_cmap(
220 color, alpha_min=0, alpha_max=1, name=name
221 )
223###############################################################################
224# HCP Connectome Workbench colormaps
225# As seen in https://github.com/Washington-University/workbench src/Palette
226roy_big_bl = (
227 _np.array(
228 [
229 (255, 255, 0),
230 (255, 200, 0),
231 (255, 120, 0),
232 (255, 0, 0),
233 (200, 0, 0),
234 (150, 0, 0),
235 (100, 0, 0),
236 (60, 0, 0),
237 (0, 0, 0),
238 (0, 0, 80),
239 (0, 0, 170),
240 (75, 0, 125),
241 (125, 0, 160),
242 (75, 125, 0),
243 (0, 200, 0),
244 (0, 255, 0),
245 (0, 255, 255),
246 (0, 255, 255),
247 ][::-1]
248 )
249 / 255
250)
252videen_style = [
253 "#000000",
254 "#bbbbbb",
255 "#dddddd",
256 "#ffffff",
257 "#ff388d",
258 "#e251e2",
259 "#10b010",
260 "#00ff00",
261 "#00ffff",
262 "#000000",
263 "#660033",
264 "#33334c",
265 "#4c4c7f",
266 "#7f7fcc",
267 "#00ff00",
268 "#10b010",
269 "#ffff00",
270 "#ff9900",
271 "#ff6900",
272 "#ff0000",
273]
275_cmap_d["roy_big_bl"] = _colors.LinearSegmentedColormap.from_list(
276 "roy_big_bl", roy_big_bl.tolist()
277)
278_cmap_d["videen_style"] = _colors.LinearSegmentedColormap.from_list(
279 "videen_style", videen_style
280)
282# Save colormaps in the scope of the module
283globals().update(_cmap_d)
284# Register cmaps in matplotlib too
285for k, v in _cmap_d.items():
286 if compare_version(matplotlib.__version__, ">=", "3.5.0"):
287 from matplotlib import colormaps as _colormaps
289 _register_cmap = _colormaps.register
290 else:
291 _register_cmap = _cm.register_cmap
293 _register_cmap(name=k, cmap=v)
296###############################################################################
297# Utility to replace a colormap by another in an interval
300def dim_cmap(cmap, factor=0.3, to_white=True):
301 """Dim a colormap to white, or to black."""
302 assert 0 <= factor <= 1, (
303 "Dimming factor must be larger than 0 and smaller than 1, "
304 f"{factor} was passed."
305 )
306 if to_white:
308 def dimmer(c):
309 return 1 - factor * (1 - c)
311 else:
313 def dimmer(c):
314 return factor * c
316 cdict = cmap._segmentdata.copy()
317 for _, color in enumerate(("red", "green", "blue")):
318 color_lst = []
319 for value, c1, c2 in cdict[color]:
320 color_lst.append((value, dimmer(c1), dimmer(c2)))
321 cdict[color] = color_lst
323 return _colors.LinearSegmentedColormap(
324 f"{cmap.name}_dimmed", cdict, _rcParams["image.lut"]
325 )
328def replace_inside(outer_cmap, inner_cmap, vmin, vmax):
329 """Replace a colormap by another inside a pair of values."""
330 assert vmin < vmax, (
331 f"'vmin' must be smaller than 'vmax'. Got {vmin=} and {vmax=}."
332 )
333 assert vmin >= 0, f"'vmin' must be larger than 0, {vmin=} was passed."
334 assert vmax <= 1, f"'vmax' must be smaller than 1, {vmax=} was passed."
335 outer_cdict = outer_cmap._segmentdata.copy()
336 inner_cdict = inner_cmap._segmentdata.copy()
338 cdict = {}
339 for this_cdict, cmap in [
340 (outer_cdict, outer_cmap),
341 (inner_cdict, inner_cmap),
342 ]:
343 if callable(this_cdict["red"]):
344 ps = _np.linspace(0, 1, 25)
345 colors = cmap(ps)
346 this_cdict["red"] = []
347 this_cdict["green"] = []
348 this_cdict["blue"] = []
349 for p, (r, g, b, _) in zip(ps, colors):
350 this_cdict["red"].append((p, r, r))
351 this_cdict["green"].append((p, g, g))
352 this_cdict["blue"].append((p, b, b))
354 for c_index, color in enumerate(("red", "green", "blue")):
355 color_lst = []
357 for value, c1, c2 in outer_cdict[color]:
358 if value >= vmin:
359 break
360 color_lst.append((value, c1, c2))
362 color_lst.append(
363 (vmin, outer_cmap(vmin)[c_index], inner_cmap(vmin)[c_index])
364 )
366 for value, c1, c2 in inner_cdict[color]:
367 if value <= vmin:
368 continue
369 if value >= vmax:
370 break
371 color_lst.append((value, c1, c2))
373 color_lst.append(
374 (vmax, inner_cmap(vmax)[c_index], outer_cmap(vmax)[c_index])
375 )
377 color_lst.extend(
378 (value, c1, c2)
379 for value, c1, c2 in outer_cdict[color]
380 if value > vmax
381 )
382 cdict[color] = color_lst
384 return _colors.LinearSegmentedColormap(
385 f"{inner_cmap.name}_inside_{outer_cmap.name}",
386 cdict,
387 _rcParams["image.lut"],
388 )