Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit db7847f

Browse files
authored
Merge pull request #14889 from tillahoffmann/arrayalpha2
Support pixel-by-pixel alpha in imshow.
2 parents bebb206 + bec9259 commit db7847f

File tree

4 files changed

+69
-40
lines changed

4 files changed

+69
-40
lines changed

examples/images_contours_and_fields/image_transparency_blend.py

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@
66
Blend transparency with color to highlight parts of data with imshow.
77
88
A common use for :func:`matplotlib.pyplot.imshow` is to plot a 2-D statistical
9-
map. While ``imshow`` makes it easy to visualize a 2-D matrix as an image,
10-
it doesn't easily let you add transparency to the output. For example, one can
11-
plot a statistic (such as a t-statistic) and color the transparency of
12-
each pixel according to its p-value. This example demonstrates how you can
13-
achieve this effect using :class:`matplotlib.colors.Normalize`. Note that it is
14-
not possible to directly pass alpha values to :func:`matplotlib.pyplot.imshow`.
9+
map. The function makes it easy to visualize a 2-D matrix as an image and add
10+
transparency to the output. For example, one can plot a statistic (such as a
11+
t-statistic) and color the transparency of each pixel according to its p-value.
12+
This example demonstrates how you can achieve this effect.
1513
1614
First we will generate some data, in this case, we'll create two 2-D "blobs"
1715
in a 2-D grid. One blob will be positive, and the other negative.
@@ -50,42 +48,38 @@ def normal_pdf(x, mean, var):
5048
# We'll also create a grey background into which the pixels will fade
5149
greys = np.full((*weights.shape, 3), 70, dtype=np.uint8)
5250

53-
# First we'll plot these blobs using only ``imshow``.
51+
# First we'll plot these blobs using ``imshow`` without transparency.
5452
vmax = np.abs(weights).max()
55-
vmin = -vmax
56-
cmap = plt.cm.RdYlBu
53+
imshow_kwargs = {
54+
'vmax': vmax,
55+
'vmin': -vmax,
56+
'cmap': 'RdYlBu',
57+
'extent': (xmin, xmax, ymin, ymax),
58+
}
5759

5860
fig, ax = plt.subplots()
5961
ax.imshow(greys)
60-
ax.imshow(weights, extent=(xmin, xmax, ymin, ymax), cmap=cmap)
62+
ax.imshow(weights, **imshow_kwargs)
6163
ax.set_axis_off()
6264

6365
###############################################################################
6466
# Blending in transparency
6567
# ========================
6668
#
6769
# The simplest way to include transparency when plotting data with
68-
# :func:`matplotlib.pyplot.imshow` is to convert the 2-D data array to a
69-
# 3-D image array of rgba values. This can be done with
70-
# :class:`matplotlib.colors.Normalize`. For example, we'll create a gradient
70+
# :func:`matplotlib.pyplot.imshow` is to pass an array matching the shape of
71+
# the data to the ``alpha`` argument. For example, we'll create a gradient
7172
# moving from left to right below.
7273

7374
# Create an alpha channel of linearly increasing values moving to the right.
7475
alphas = np.ones(weights.shape)
7576
alphas[:, 30:] = np.linspace(1, 0, 70)
7677

77-
# Normalize the colors b/w 0 and 1, we'll then pass an MxNx4 array to imshow
78-
colors = Normalize(vmin, vmax, clip=True)(weights)
79-
colors = cmap(colors)
80-
81-
# Now set the alpha channel to the one we created above
82-
colors[..., -1] = alphas
83-
8478
# Create the figure and image
8579
# Note that the absolute values may be slightly different
8680
fig, ax = plt.subplots()
8781
ax.imshow(greys)
88-
ax.imshow(colors, extent=(xmin, xmax, ymin, ymax))
82+
ax.imshow(weights, alpha=alphas, **imshow_kwargs)
8983
ax.set_axis_off()
9084

9185
###############################################################################
@@ -102,18 +96,11 @@ def normal_pdf(x, mean, var):
10296
alphas = Normalize(0, .3, clip=True)(np.abs(weights))
10397
alphas = np.clip(alphas, .4, 1) # alpha value clipped at the bottom at .4
10498

105-
# Normalize the colors b/w 0 and 1, we'll then pass an MxNx4 array to imshow
106-
colors = Normalize(vmin, vmax)(weights)
107-
colors = cmap(colors)
108-
109-
# Now set the alpha channel to the one we created above
110-
colors[..., -1] = alphas
111-
11299
# Create the figure and image
113100
# Note that the absolute values may be slightly different
114101
fig, ax = plt.subplots()
115102
ax.imshow(greys)
116-
ax.imshow(colors, extent=(xmin, xmax, ymin, ymax))
103+
ax.imshow(weights, alpha=alphas, **imshow_kwargs)
117104

118105
# Add contour lines to further highlight different levels.
119106
ax.contour(weights[::-1], levels=[-.1, .1], colors='k', linestyles='-')

lib/matplotlib/axes/_axes.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5504,9 +5504,10 @@ def imshow(self, X, cmap=None, norm=None, aspect=None,
55045504
which can be set by *filterrad*. Additionally, the antigrain image
55055505
resize filter is controlled by the parameter *filternorm*.
55065506
5507-
alpha : scalar, optional
5507+
alpha : scalar or array-like, optional
55085508
The alpha blending value, between 0 (transparent) and 1 (opaque).
5509-
This parameter is ignored for RGBA input data.
5509+
If *alpha* is an array, the alpha blending values are applied pixel
5510+
by pixel, and *alpha* must have the same shape as *X*.
55105511
55115512
vmin, vmax : scalar, optional
55125513
When using scalar data and no explicit *norm*, *vmin* and *vmax*

lib/matplotlib/image.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import math
88
import os
99
import logging
10+
from numbers import Number
1011
from pathlib import Path
1112
import urllib.parse
1213

@@ -95,7 +96,7 @@ def composite_images(images, renderer, magnification=1.0):
9596
if data is not None:
9697
x *= magnification
9798
y *= magnification
98-
parts.append((data, x, y, image.get_alpha() or 1.0))
99+
parts.append((data, x, y, image._get_scalar_alpha()))
99100
bboxes.append(
100101
Bbox([[x, y], [x + data.shape[1], y + data.shape[0]]]))
101102

@@ -281,9 +282,29 @@ def set_alpha(self, alpha):
281282
----------
282283
alpha : float
283284
"""
284-
martist.Artist.set_alpha(self, alpha)
285+
if alpha is not None and not isinstance(alpha, Number):
286+
alpha = np.asarray(alpha)
287+
if alpha.ndim != 2:
288+
raise TypeError('alpha must be a float, two-dimensional '
289+
'array, or None')
290+
self._alpha = alpha
291+
self.pchanged()
292+
self.stale = True
285293
self._imcache = None
286294

295+
def _get_scalar_alpha(self):
296+
"""
297+
Get a scalar alpha value to be applied to the artist as a whole.
298+
299+
If the alpha value is a matrix, the method returns 1.0 because pixels
300+
have individual alpha values (see `~._ImageBase._make_image` for
301+
details). If the alpha value is a scalar, the method returns said value
302+
to be applied to the artist as a whole because pixels do not have
303+
individual alpha values.
304+
"""
305+
return 1.0 if self._alpha is None or np.ndim(self._alpha) > 0 \
306+
else self._alpha
307+
287308
def changed(self):
288309
"""
289310
Call this whenever the mappable is changed so observers can
@@ -487,14 +508,17 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
487508
# pixel it will be between [0, 1] (such as a rotated image).
488509
out_mask = np.isnan(out_alpha)
489510
out_alpha[out_mask] = 1
511+
# Apply the pixel-by-pixel alpha values if present
512+
alpha = self.get_alpha()
513+
if alpha is not None and np.ndim(alpha) > 0:
514+
out_alpha *= _resample(self, alpha, out_shape,
515+
t, resample=True)
490516
# mask and run through the norm
491517
output = self.norm(np.ma.masked_array(A_resampled, out_mask))
492518
else:
493519
if A.shape[2] == 3:
494520
A = _rgb_to_rgba(A)
495-
alpha = self.get_alpha()
496-
if alpha is None:
497-
alpha = 1
521+
alpha = self._get_scalar_alpha()
498522
output_alpha = _resample( # resample alpha channel
499523
self, A[..., 3], out_shape, t, alpha=alpha)
500524
output = _resample( # resample rgb channels
@@ -509,9 +533,7 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
509533

510534
# Apply alpha *after* if the input was greyscale without a mask
511535
if A.ndim == 2:
512-
alpha = self.get_alpha()
513-
if alpha is None:
514-
alpha = 1
536+
alpha = self._get_scalar_alpha()
515537
alpha_channel = output[:, :, 3]
516538
alpha_channel[:] = np.asarray(
517539
np.asarray(alpha_channel, np.float32) * out_alpha * alpha,
@@ -593,7 +615,7 @@ def draw(self, renderer, *args, **kwargs):
593615
# actually render the image.
594616
gc = renderer.new_gc()
595617
self._set_gc_clip(gc)
596-
gc.set_alpha(self.get_alpha())
618+
gc.set_alpha(self._get_scalar_alpha())
597619
gc.set_url(self.get_url())
598620
gc.set_gid(self.get_gid())
599621

lib/matplotlib/tests/test_image.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,3 +1118,22 @@ def test_image_cursor_formatting():
11181118

11191119
data = np.nan
11201120
assert im.format_cursor_data(data) == '[nan]'
1121+
1122+
1123+
@check_figures_equal()
1124+
def test_image_array_alpha(fig_test, fig_ref):
1125+
'''per-pixel alpha channel test'''
1126+
x = np.linspace(0, 1)
1127+
xx, yy = np.meshgrid(x, x)
1128+
1129+
zz = np.exp(- 3 * ((xx - 0.5) ** 2) + (yy - 0.7 ** 2))
1130+
alpha = zz / zz.max()
1131+
1132+
cmap = plt.get_cmap('viridis')
1133+
ax = fig_test.add_subplot(111)
1134+
ax.imshow(zz, alpha=alpha, cmap=cmap, interpolation='nearest')
1135+
1136+
ax = fig_ref.add_subplot(111)
1137+
rgba = cmap(colors.Normalize()(zz))
1138+
rgba[..., -1] = alpha
1139+
ax.imshow(rgba, interpolation='nearest')

0 commit comments

Comments
 (0)