Skip to content

Commit 51e9a6c

Browse files
committed
Added tests for LightSource functionality
1 parent 3c9cacf commit 51e9a6c

File tree

2 files changed

+101
-26
lines changed

2 files changed

+101
-26
lines changed

lib/matplotlib/tests/test_colors.py

Lines changed: 101 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
unicode_literals)
33

44
import six
5+
import itertools
56

67
from nose.tools import assert_raises
78

@@ -10,6 +11,7 @@
1011

1112
import matplotlib.colors as mcolors
1213
import matplotlib.cm as cm
14+
import matplotlib.cbook as cbook
1315
import matplotlib.pyplot as plt
1416
from matplotlib.testing.decorators import image_comparison, cleanup
1517

@@ -228,32 +230,105 @@ def gray_from_float_rgba():
228230
assert_raises(ValueError, gray_from_float_rgba)
229231

230232

231-
def test_light_source_shading_color_range():
232-
# see also
233-
#http://matplotlib.org/examples/pylab_examples/shading_example.html
234-
235-
from matplotlib.colors import LightSource
236-
from matplotlib.colors import Normalize
237-
238-
refinput = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
239-
norm = Normalize(vmin=0, vmax=50)
240-
ls = LightSource(azdeg=0, altdeg=65)
241-
testoutput = ls.shade(refinput, plt.cm.jet, norm=norm)
242-
refoutput = np.array([
243-
[[0., 0., 0.58912656, 1.],
244-
[0., 0., 0.67825312, 1.],
245-
[0., 0., 0.76737968, 1.],
246-
[0., 0., 0.85650624, 1.]],
247-
[[0., 0., 0.9456328, 1.],
248-
[0., 0., 1., 1.],
249-
[0., 0.04901961, 1., 1.],
250-
[0., 0.12745098, 1., 1.]],
251-
[[0., 0.22156863, 1., 1.],
252-
[0., 0.3, 1., 1.],
253-
[0., 0.37843137, 1., 1.],
254-
[0., 0.45686275, 1., 1.]]
255-
])
256-
assert_array_almost_equal(refoutput, testoutput)
233+
@image_comparison(baseline_images=['light_source_shading_topo'],
234+
extensions=['png'])
235+
def test_light_source_topo_surface():
236+
"""Shades a DEM using different v.e.'s and blend modes."""
237+
dem = np.load(cbook.get_sample_data('jacksboro_fault_dem.npz'))
238+
239+
# Get the true cellsize in meters for accurate vertical exaggeration
240+
dx, dy = dem['dx'], dem['dy'] # In decimal degrees
241+
# Convert to meters...
242+
dx = 111320.0 * dx * np.cos(dem['ymin'])
243+
dy = 111320.0 * dy
244+
245+
ls = mcolors.LightSource(315, 45)
246+
elev = dem['elevation']
247+
cmap = cm.gist_earth
248+
249+
fig, axes = plt.subplots(nrows=3, ncols=3)
250+
for row, mode in zip(axes, ['hsv', 'overlay', 'soft']):
251+
for ax, ve in zip(row, [0.1, 1, 10]):
252+
rgb = ls.shade(elev, cmap, vert_exag=ve, dx=dx, dy=dy,
253+
blend_mode=mode)
254+
ax.imshow(rgb)
255+
ax.set(xticks=[], yticks=[])
256+
257+
258+
def test_light_source_hillshading():
259+
"""Compare the current hillshading method against one that should be
260+
mathematically equivalent. Illuminates a cone from a range of angles."""
261+
262+
def alternative_hillshade(azimuth, elev, z):
263+
illum = _sph2cart(*_azimuth2math(azimuth, elev))
264+
illum = np.array(illum)
265+
266+
dy, dx = np.gradient(-z)
267+
dy = -dy
268+
dz = np.ones_like(dy)
269+
normals = np.dstack([dx, dy, dz])
270+
normals /= np.linalg.norm(normals, axis=2)[..., None]
271+
272+
intensity = np.tensordot(normals, illum, axes=(2, 0))
273+
intensity -= intensity.min()
274+
intensity /= intensity.ptp()
275+
return intensity
276+
277+
y, x = np.mgrid[5:0:-1, :5]
278+
z = -np.hypot(x - x.mean(), y - y.mean())
279+
280+
for az, elev in itertools.product(range(0, 390, 30), range(0, 105, 15)):
281+
ls = mcolors.LightSource(az, elev)
282+
h1 = ls.hillshade(z)
283+
h2 = alternative_hillshade(az, elev, z)
284+
assert_array_almost_equal(h1, h2)
285+
286+
287+
def test_light_source_planar_hillshading():
288+
"""Ensure that the illumination intensity is correct for planar
289+
surfaces."""
290+
291+
def plane(azimuth, elevation, x, y):
292+
"""Create a plane whose normal vector is at the given azimuth and
293+
elevation."""
294+
theta, phi = _azimuth2math(azimuth, elevation)
295+
a, b, c = _sph2cart(theta, phi)
296+
z = -(a*x + b*y) / c
297+
return z
298+
299+
def angled_plane(azimuth, elevation, angle, x, y):
300+
"""Create a plane whose normal vector is at an angle from the given
301+
azimuth and elevation."""
302+
elevation = elevation + angle
303+
if elevation > 90:
304+
azimuth = (azimuth + 180) % 360
305+
elevation = (90 - elevation) % 90
306+
return plane(azimuth, elevation, x, y)
307+
308+
y, x = np.mgrid[5:0:-1, :5]
309+
for az, elev in itertools.product(range(0, 390, 30), range(0, 105, 15)):
310+
ls = mcolors.LightSource(az, elev)
311+
312+
# Make a plane at a range of angles to the illumination
313+
for angle in range(0, 105, 15):
314+
z = angled_plane(az, elev, angle, x, y)
315+
h = ls.hillshade(z)
316+
assert_array_almost_equal(h, np.cos(np.radians(angle)))
317+
318+
319+
def _sph2cart(theta, phi):
320+
x = np.cos(theta) * np.sin(phi)
321+
y = np.sin(theta) * np.sin(phi)
322+
z = np.cos(phi)
323+
return x, y, z
324+
325+
326+
def _azimuth2math(azimuth, elevation):
327+
"""Converts from clockwise-from-north and up-from-horizontal to
328+
mathematical conventions."""
329+
theta = np.radians((90 - azimuth) % 360)
330+
phi = np.radians(90 - elevation)
331+
return theta, phi
257332

258333

259334
if __name__ == '__main__':

0 commit comments

Comments
 (0)