Skip to content

Commit 34ed45a

Browse files
committed
Merge pull request matplotlib#3291 from joferkington/lightsource-enhancements
Lightsource enhancements
2 parents 59ebe49 + 9fce26c commit 34ed45a

File tree

9 files changed

+753
-93
lines changed

9 files changed

+753
-93
lines changed

CHANGELOG

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
2014-09-27 Overhauled `colors.LightSource`. Added `LightSource.hillshade` to
2+
allow the independent generation of illumination maps. Added new
3+
types of blending for creating more visually appealing shaded relief
4+
plots (e.g. `blend_mode="overlay"`, etc, in addition to the legacy
5+
"hsv" mode).
6+
17
2014-06-10 Added Colorbar.remove()
28

39
2014-06-07 Fixed bug so radial plots can be saved as ps in py3k.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Demonstrates using custom hillshading in a 3D surface plot.
3+
"""
4+
from mpl_toolkits.mplot3d import Axes3D
5+
from matplotlib import cbook
6+
from matplotlib import cm
7+
from matplotlib.colors import LightSource
8+
import matplotlib.pyplot as plt
9+
import numpy as np
10+
11+
filename = cbook.get_sample_data('jacksboro_fault_dem.npz', asfileobj=False)
12+
with np.load(filename) as dem:
13+
z = dem['elevation']
14+
nrows, ncols = z.shape
15+
x = np.linspace(dem['xmin'], dem['xmax'], ncols)
16+
y = np.linspace(dem['ymin'], dem['ymax'], nrows)
17+
x, y = np.meshgrid(x, y)
18+
19+
region = np.s_[5:50, 5:50]
20+
x, y, z = x[region], y[region], z[region]
21+
22+
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
23+
24+
ls = LightSource(270, 45)
25+
# To use a custom hillshading mode, override the built-in shading and pass
26+
# in the rgb colors of the shaded surface calculated from "shade".
27+
rgb = ls.shade(z, cmap=cm.gist_earth, vert_exag=0.1, blend_mode='soft')
28+
surf = ax.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=rgb,
29+
linewidth=0, antialiased=False, shade=False)
30+
31+
plt.show()
32+
Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,55 @@
11
import numpy as np
22
import matplotlib.pyplot as plt
33
from matplotlib.colors import LightSource
4+
from matplotlib.cbook import get_sample_data
45

5-
# example showing how to make shaded relief plots
6+
# Example showing how to make shaded relief plots
67
# like Mathematica
78
# (http://reference.wolfram.com/mathematica/ref/ReliefPlot.html)
89
# or Generic Mapping Tools
910
# (http://gmt.soest.hawaii.edu/gmt/doc/gmt/html/GMT_Docs/node145.html)
1011

11-
# test data
12-
X,Y=np.mgrid[-5:5:0.05,-5:5:0.05]
13-
Z=np.sqrt(X**2+Y**2)+np.sin(X**2+Y**2)
14-
# create light source object.
15-
ls = LightSource(azdeg=0,altdeg=65)
16-
# shade data, creating an rgb array.
17-
rgb = ls.shade(Z,plt.cm.copper)
18-
# plot un-shaded and shaded images.
19-
plt.figure(figsize=(12,5))
20-
plt.subplot(121)
21-
plt.imshow(Z,cmap=plt.cm.copper)
22-
plt.title('imshow')
23-
plt.xticks([]); plt.yticks([])
24-
plt.subplot(122)
25-
plt.imshow(rgb)
26-
plt.title('imshow with shading')
27-
plt.xticks([]); plt.yticks([])
28-
plt.show()
12+
def main():
13+
# Test data
14+
x, y = np.mgrid[-5:5:0.05, -5:5:0.05]
15+
z = 5 * (np.sqrt(x**2 + y**2) + np.sin(x**2 + y**2))
16+
17+
filename = get_sample_data('jacksboro_fault_dem.npz', asfileobj=False)
18+
with np.load(filename) as dem:
19+
elev = dem['elevation']
20+
21+
fig = compare(z, plt.cm.copper)
22+
fig.suptitle('HSV Blending Looks Best with Smooth Surfaces', y=0.95)
23+
24+
fig = compare(elev, plt.cm.gist_earth, ve=0.05)
25+
fig.suptitle('Overlay Blending Looks Best with Rough Surfaces', y=0.95)
26+
27+
plt.show()
28+
29+
def compare(z, cmap, ve=1):
30+
# Create subplots and hide ticks
31+
fig, axes = plt.subplots(ncols=2, nrows=2)
32+
for ax in axes.flat:
33+
ax.set(xticks=[], yticks=[])
34+
35+
# Illuminate the scene from the northwest
36+
ls = LightSource(azdeg=315, altdeg=45)
37+
38+
axes[0, 0].imshow(z, cmap=cmap)
39+
axes[0, 0].set(xlabel='Colormapped Data')
40+
41+
axes[0, 1].imshow(ls.hillshade(z, vert_exag=ve), cmap='gray')
42+
axes[0, 1].set(xlabel='Illumination Intensity')
43+
44+
rgb = ls.shade(z, cmap=cmap, vert_exag=ve, blend_mode='hsv')
45+
axes[1, 0].imshow(rgb)
46+
axes[1, 0].set(xlabel='Blend Mode: "hsv" (default)')
47+
48+
rgb = ls.shade(z, cmap=cmap, vert_exag=ve, blend_mode='overlay')
49+
axes[1, 1].imshow(rgb)
50+
axes[1, 1].set(xlabel='Blend Mode: "overlay"')
51+
52+
return fig
53+
54+
if __name__ == '__main__':
55+
main()
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
Demonstrates a few common tricks with shaded plots.
3+
"""
4+
import numpy as np
5+
import matplotlib.pyplot as plt
6+
from matplotlib.colors import LightSource, Normalize
7+
8+
def display_colorbar():
9+
"""Display a correct numeric colorbar for a shaded plot."""
10+
y, x = np.mgrid[-4:2:200j, -4:2:200j]
11+
z = 10 * np.cos(x**2 + y**2)
12+
13+
cmap = plt.cm.copper
14+
ls = LightSource(315, 45)
15+
rgb = ls.shade(z, cmap)
16+
17+
fig, ax = plt.subplots()
18+
ax.imshow(rgb)
19+
20+
# Use a proxy artist for the colorbar...
21+
im = ax.imshow(z, cmap=cmap)
22+
im.remove()
23+
fig.colorbar(im)
24+
25+
ax.set_title('Using a colorbar with a shaded plot', size='x-large')
26+
27+
def avoid_outliers():
28+
"""Use a custom norm to control the displayed z-range of a shaded plot."""
29+
y, x = np.mgrid[-4:2:200j, -4:2:200j]
30+
z = 10 * np.cos(x**2 + y**2)
31+
32+
# Add some outliers...
33+
z[100, 105] = 2000
34+
z[120, 110] = -9000
35+
36+
ls = LightSource(315, 45)
37+
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(8, 4.5))
38+
39+
rgb = ls.shade(z, plt.cm.copper)
40+
ax1.imshow(rgb)
41+
ax1.set_title('Full range of data')
42+
43+
rgb = ls.shade(z, plt.cm.copper, vmin=-10, vmax=10)
44+
ax2.imshow(rgb)
45+
ax2.set_title('Manually set range')
46+
47+
fig.suptitle('Avoiding Outliers in Shaded Plots', size='x-large')
48+
49+
def shade_other_data():
50+
"""Demonstrates displaying different variables through shade and color."""
51+
y, x = np.mgrid[-4:2:200j, -4:2:200j]
52+
z1 = np.sin(x**2) # Data to hillshade
53+
z2 = np.cos(x**2 + y**2) # Data to color
54+
55+
norm=Normalize(z2.min(), z2.max())
56+
cmap = plt.cm.jet
57+
58+
ls = LightSource(315, 45)
59+
rgb = ls.shade_rgb(cmap(norm(z2)), z1)
60+
61+
fig, ax = plt.subplots()
62+
ax.imshow(rgb)
63+
ax.set_title('Shade by one variable, color by another', size='x-large')
64+
65+
display_colorbar()
66+
avoid_outliers()
67+
shade_other_data()
68+
plt.show()
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""
2+
Demonstrates the visual effect of varying blend mode and vertical exaggeration
3+
on "hillshaded" plots.
4+
5+
Note that the "overlay" and "soft" blend modes work well for complex surfaces
6+
such as this example, while the default "hsv" blend mode works best for smooth
7+
surfaces such as many mathematical functions.
8+
9+
In most cases, hillshading is used purely for visual purposes, and *dx*/*dy*
10+
can be safely ignored. In that case, you can tweak *vert_exag* (vertical
11+
exaggeration) by trial and error to give the desired visual effect. However,
12+
this example demonstrates how to use the *dx* and *dy* kwargs to ensure that
13+
the *vert_exag* parameter is the true vertical exaggeration.
14+
"""
15+
import numpy as np
16+
import matplotlib.pyplot as plt
17+
from matplotlib.cbook import get_sample_data
18+
from matplotlib.colors import LightSource
19+
20+
dem = np.load(get_sample_data('jacksboro_fault_dem.npz'))
21+
z = dem['elevation']
22+
23+
#-- Optional dx and dy for accurate vertical exaggeration --------------------
24+
# If you need topographically accurate vertical exaggeration, or you don't want
25+
# to guess at what *vert_exag* should be, you'll need to specify the cellsize
26+
# of the grid (i.e. the *dx* and *dy* parameters). Otherwise, any *vert_exag*
27+
# value you specify will be realitive to the grid spacing of your input data
28+
# (in other words, *dx* and *dy* default to 1.0, and *vert_exag* is calculated
29+
# relative to those parameters). Similarly, *dx* and *dy* are assumed to be in
30+
# the same units as your input z-values. Therefore, we'll need to convert the
31+
# given dx and dy from decimal degrees to meters.
32+
dx, dy = dem['dx'], dem['dy']
33+
dy = 111200 * dy
34+
dx = 111200 * dx * np.cos(np.radians(dem['ymin']))
35+
#-----------------------------------------------------------------------------
36+
37+
# Shade from the northwest, with the sun 45 degrees from horizontal
38+
ls = LightSource(azdeg=315, altdeg=45)
39+
cmap = plt.cm.gist_earth
40+
41+
fig, axes = plt.subplots(nrows=4, ncols=3, figsize=(8, 9))
42+
plt.setp(axes.flat, xticks=[], yticks=[])
43+
44+
# Vary vertical exaggeration and blend mode and plot all combinations
45+
for col, ve in zip(axes.T, [0.1, 1, 10]):
46+
# Show the hillshade intensity image in the first row
47+
col[0].imshow(ls.hillshade(z, vert_exag=ve, dx=dx, dy=dy), cmap='gray')
48+
49+
# Place hillshaded plots with different blend modes in the rest of the rows
50+
for ax, mode in zip(col[1:], ['hsv', 'overlay', 'soft']):
51+
rgb = ls.shade(z, cmap=cmap, blend_mode=mode,
52+
vert_exag=ve, dx=dx, dy=dy)
53+
ax.imshow(rgb)
54+
55+
# Label rows and columns
56+
for ax, ve in zip(axes[0], [0.1, 1, 10]):
57+
ax.set_title('{}'.format(ve), size=18)
58+
for ax, mode in zip(axes[:,0], ['Hillshade', 'hsv', 'overlay', 'soft']):
59+
ax.set_ylabel(mode, size=18)
60+
61+
# Group labels...
62+
axes[0,1].annotate('Vertical Exaggeration', (0.5, 1), xytext=(0, 30),
63+
textcoords='offset points', xycoords='axes fraction',
64+
ha='center', va='bottom', size=20)
65+
axes[2,0].annotate('Blend Mode', (0, 0.5), xytext=(-30, 0),
66+
textcoords='offset points', xycoords='axes fraction',
67+
ha='right', va='center', size=20, rotation=90)
68+
fig.subplots_adjust(bottom=0.05, right=0.95)
69+
70+
plt.show()

0 commit comments

Comments
 (0)