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

Skip to content

Commit ee99cf5

Browse files
committed
Refactored LightSource.shade_rgb into three separate functions
1 parent 48e8052 commit ee99cf5

1 file changed

Lines changed: 135 additions & 38 deletions

File tree

lib/matplotlib/colors.py

Lines changed: 135 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,26 +1463,36 @@ class LightSource(object):
14631463
Create a light source coming from the specified azimuth and elevation.
14641464
Angles are in degrees, with the azimuth measured
14651465
clockwise from north and elevation up from the zero plane of the surface.
1466-
The :meth:`shade` is used to produce rgb values for a shaded relief image
1467-
given a data array.
1466+
1467+
The :meth:`shade` is used to produce "shaded" rgb values for a data array.
1468+
:meth:`shade_rgb` can be used to combine an rgb image with
1469+
The :meth:`shade_rgb`
1470+
The :meth:`hillshade` produces an illumination map of a surface.
14681471
"""
14691472
def __init__(self, azdeg=315, altdeg=45,
14701473
hsv_min_val=0, hsv_max_val=1, hsv_min_sat=1,
14711474
hsv_max_sat=0):
1472-
14731475
"""
14741476
Specify the azimuth (measured clockwise from south) and altitude
14751477
(measured up from the plane of the surface) of the light source
14761478
in degrees.
14771479
1478-
The color of the resulting image will be darkened
1479-
by moving the (s,v) values (in hsv colorspace) toward
1480-
(hsv_min_sat, hsv_min_val) in the shaded regions, or
1481-
lightened by sliding (s,v) toward
1482-
(hsv_max_sat hsv_max_val) in regions that are illuminated.
1483-
The default extremes are chose so that completely shaded points
1484-
are nearly black (s = 1, v = 0) and completely illuminated points
1485-
are nearly white (s = 0, v = 1).
1480+
Parameters
1481+
----------
1482+
azdeg : number, optional
1483+
The azimuth (0-360, degrees clockwise from North) of the light
1484+
source. Defaults to 315 degrees (from the northwest).
1485+
altdeg : number, optional
1486+
The altitude (0-90, degrees up from horizontal) of the light
1487+
source. Defaults to 45 degrees from horizontal.
1488+
1489+
Notes
1490+
-----
1491+
For backwards compatibility, the parameters *hsv_min_val*,
1492+
*hsv_max_val*, *hsv_min_sat*, and *hsv_max_sat* may be supplied at
1493+
initialization as well. However, these parameters will only be used if
1494+
"blend_mode='hsv'" is passed into :meth:`shade` or :meth:`shade_rgb`.
1495+
See the documentation for :meth:`blend_hsv` for more details.
14861496
"""
14871497
self.azdeg = azdeg
14881498
self.altdeg = altdeg
@@ -1491,7 +1501,58 @@ def __init__(self, azdeg=315, altdeg=45,
14911501
self.hsv_min_sat = hsv_min_sat
14921502
self.hsv_max_sat = hsv_max_sat
14931503

1494-
def shade(self, data, cmap, norm=None):
1504+
def hillshade(self, elevation, vert_exag=1, dx=1, dy=1, fraction=1.):
1505+
"""
1506+
Calculates the illumination intensity for a surface using the defined
1507+
azimuth and elevation for the light source.
1508+
1509+
Imagine an artificial sun placed at infinity in some azimuth and
1510+
elevation position illuminating our surface. The parts of the surface
1511+
that slope toward the sun should brighten while those sides facing away
1512+
should become darker.
1513+
1514+
Parameters
1515+
----------
1516+
elevation : array-like
1517+
A 2d array (or equivalent) of the height values used to generate an
1518+
illumination map
1519+
fraction : number, optional
1520+
Increases or decreases the contrast of the hillshade. Values
1521+
greater than one will cause intermediate values to move closer to
1522+
full illumination or shadow (and clipping any values that move
1523+
beyond 0 or 1). Values less than one will cause full shadow or
1524+
full illumination to move closer to a value of 0.5, thereby
1525+
decreasing contrast. Note that this is not mathematically or
1526+
visually the same as increasing/decreasing the vertical
1527+
exaggeration.
1528+
1529+
Returns
1530+
-------
1531+
intensity : ndarray
1532+
A 2d array of illumination values between 0-1, where 0 is
1533+
completely in shadow and 1 is completely illuminated.
1534+
"""
1535+
# Azimuth is in degrees clockwise from North. Convert to radians
1536+
# counterclockwise from East (mathematical notation).
1537+
az = np.radians(90 - self.azdeg)
1538+
alt = np.radians(self.altdeg)
1539+
1540+
dy, dx = np.gradient(elevation)
1541+
slope = 0.5 * np.pi - np.arctan(np.hypot(dx, dy))
1542+
aspect = np.arctan2(dx, dy)
1543+
intensity = (np.sin(alt) * np.sin(slope)
1544+
+ np.cos(alt) * np.cos(slope)
1545+
* np.cos(az - aspect))
1546+
1547+
intensity -= intensity.min()
1548+
intensity /= intensity.ptp()
1549+
if fraction != 1.0:
1550+
intensity = fraction * (intensity - 0.5) + 0.5
1551+
if np.abs(fraction) > 1:
1552+
np.clip(intensity, 0, 1, intensity)
1553+
return intensity
1554+
1555+
def shade(self, data, cmap, norm=None, **kwargs):
14951556
"""
14961557
Take the input data array, convert to HSV values in the
14971558
given colormap, then adjust those color values
@@ -1505,65 +1566,101 @@ def shade(self, data, cmap, norm=None):
15051566
norm = Normalize(vmin=data.min(), vmax=data.max())
15061567

15071568
rgb0 = cmap(norm(data))
1508-
rgb1 = self.shade_rgb(rgb0, elevation=data)
1569+
rgb1 = self.shade_rgb(rgb0, elevation=data, **kwargs)
15091570
rgb0[:, :, 0:3] = rgb1
15101571
return rgb0
15111572

1512-
def shade_rgb(self, rgb, elevation, fraction=1.):
1573+
def shade_rgb(self, rgb, elevation, fraction=1., **kwargs):
15131574
"""
15141575
Take the input RGB array (ny*nx*3) adjust their color values
15151576
to given the impression of a shaded relief map with a
15161577
specified light source using the elevation (ny*nx).
15171578
A new RGB array ((ny*nx*3)) is returned.
15181579
"""
1519-
# imagine an artificial sun placed at infinity in some azimuth and
1520-
# elevation position illuminating our surface. The parts of the
1521-
# surface that slope toward the sun should brighten while those sides
1522-
# facing away should become darker. convert alt, az to radians
1523-
az = self.azdeg * np.pi / 180.0
1524-
alt = self.altdeg * np.pi / 180.0
1525-
# gradient in x and y directions
1526-
dx, dy = np.gradient(elevation)
1527-
slope = 0.5 * np.pi - np.arctan(np.hypot(dx, dy))
1528-
aspect = np.arctan2(dx, dy)
1529-
intensity = (np.sin(alt) * np.sin(slope) + np.cos(alt) *
1530-
np.cos(slope) * np.cos(-az - aspect - 0.5 * np.pi))
1531-
# rescale to interval -1,1
1532-
# +1 means maximum sun exposure and -1 means complete shade.
1533-
intensity = (intensity - intensity.min()) / \
1534-
(intensity.max() - intensity.min())
1535-
intensity = (2. * intensity - 1.) * fraction
1580+
intensity = self.hillshade(elevation, fraction=fraction)
1581+
return self.blend_hsv(rgb, intensity, **kwargs)
1582+
1583+
def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None,
1584+
hsv_min_val=None, hsv_min_sat=None):
1585+
"""
1586+
Take the input data array, convert to HSV values in the given colormap,
1587+
then adjust those color values to give the impression of a shaded
1588+
relief map with a specified light source. RGBA values are returned,
1589+
which can then be used to plot the shaded image with imshow.
1590+
1591+
The color of the resulting image will be darkened by moving the (s,v)
1592+
values (in hsv colorspace) toward (hsv_min_sat, hsv_min_val) in the
1593+
shaded regions, or lightened by sliding (s,v) toward (hsv_max_sat
1594+
hsv_max_val) in regions that are illuminated. The default extremes are
1595+
chose so that completely shaded points are nearly black (s = 1, v = 0)
1596+
and completely illuminated points are nearly white (s = 0, v = 1).
1597+
1598+
Parameters
1599+
----------
1600+
rgb : ndarray
1601+
An MxNx3 RGB array of floats ranging from 0 to 1 (color image).
1602+
intensity : ndarray
1603+
An MxNx1 array of floats ranging from 0 to 1 (grayscale image).
1604+
hsv_max_sat : number, optional
1605+
The maximum saturation value that the *intensity* map can shift the
1606+
output image to. Defaults to 1.
1607+
hsv_min_sat : number, optional
1608+
The minimum saturation value that the *intensity* map can shift the
1609+
output image to. Defaults to 0.
1610+
hsv_max_val : number, optional
1611+
The maximum value ("v" in "hsv") that the *intensity* map can shift
1612+
the output image to. Defaults to 1.
1613+
hsv_min_val: number, optional
1614+
The minimum value ("v" in "hsv") that the *intensity* map can shift
1615+
the output image to. Defaults to 0.
1616+
1617+
Returns
1618+
-------
1619+
rgb : ndarray
1620+
An MxNx3 RGB array representing the combined images.
1621+
"""
1622+
# Backward compatibility...
1623+
if hsv_max_sat is None:
1624+
hsv_max_sat = self.hsv_max_sat
1625+
if hsv_max_val is None:
1626+
hsv_max_val = self.hsv_max_val
1627+
if hsv_min_sat is None:
1628+
hsv_min_sat = self.hsv_min_sat
1629+
if hsv_min_val is None:
1630+
hsv_min_val = self.hsv_min_val
1631+
1632+
# Expects a 2D intensity array scaled between -1 to 1...
1633+
intensity = 2 * intensity - 1
1634+
15361635
# convert to rgb, then rgb to hsv
1537-
#rgb = cmap((data-data.min())/(data.max()-data.min()))
15381636
hsv = rgb_to_hsv(rgb[:, :, 0:3])
1539-
# modify hsv values to simulate illumination.
15401637

1638+
# modify hsv values to simulate illumination.
15411639
hsv[:, :, 1] = np.where(np.logical_and(np.abs(hsv[:, :, 1]) > 1.e-10,
15421640
intensity > 0),
15431641
((1. - intensity) * hsv[:, :, 1] +
1544-
intensity * self.hsv_max_sat),
1642+
intensity * hsv_max_sat),
15451643
hsv[:, :, 1])
15461644

15471645
hsv[:, :, 2] = np.where(intensity > 0,
15481646
((1. - intensity) * hsv[:, :, 2] +
1549-
intensity * self.hsv_max_val),
1647+
intensity * hsv_max_val),
15501648
hsv[:, :, 2])
15511649

15521650
hsv[:, :, 1] = np.where(np.logical_and(np.abs(hsv[:, :, 1]) > 1.e-10,
15531651
intensity < 0),
15541652
((1. + intensity) * hsv[:, :, 1] -
1555-
intensity * self.hsv_min_sat),
1653+
intensity * hsv_min_sat),
15561654
hsv[:, :, 1])
15571655
hsv[:, :, 2] = np.where(intensity < 0,
15581656
((1. + intensity) * hsv[:, :, 2] -
1559-
intensity * self.hsv_min_val),
1657+
intensity * hsv_min_val),
15601658
hsv[:, :, 2])
15611659
hsv[:, :, 1:] = np.where(hsv[:, :, 1:] < 0., 0, hsv[:, :, 1:])
15621660
hsv[:, :, 1:] = np.where(hsv[:, :, 1:] > 1., 1, hsv[:, :, 1:])
15631661
# convert modified hsv back to rgb.
15641662
return hsv_to_rgb(hsv)
15651663

1566-
15671664
def from_levels_and_colors(levels, colors, extend='neither'):
15681665
"""
15691666
A helper routine to generate a cmap and a norm instance which

0 commit comments

Comments
 (0)