@@ -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-
15671664def 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