@@ -1457,7 +1457,6 @@ def hsv_to_rgb(hsv):
14571457
14581458 return rgb
14591459
1460-
14611460class LightSource (object ):
14621461 """
14631462 Create a light source coming from the specified azimuth and elevation.
@@ -1469,9 +1468,8 @@ class LightSource(object):
14691468 The :meth:`shade_rgb`
14701469 The :meth:`hillshade` produces an illumination map of a surface.
14711470 """
1472- def __init__ (self , azdeg = 315 , altdeg = 45 ,
1473- hsv_min_val = 0 , hsv_max_val = 1 , hsv_min_sat = 1 ,
1474- hsv_max_sat = 0 ):
1471+ def __init__ (self , azdeg = 315 , altdeg = 45 , hsv_min_val = 0 , hsv_max_val = 1 ,
1472+ hsv_min_sat = 1 , hsv_max_sat = 0 ):
14751473 """
14761474 Specify the azimuth (measured clockwise from south) and altitude
14771475 (measured up from the plane of the surface) of the light source
@@ -1567,8 +1565,8 @@ def hillshade(self, elevation, vert_exag=1, dx=1, dy=1, fraction=1.):
15671565 np .clip (intensity , 0 , 1 , intensity )
15681566 return intensity
15691567
1570- def shade (self , data , cmap , norm = None , vert_exag = 1 , dx = 1 , dy = 1 , fraction = 1 ,
1571- ** kwargs ):
1568+ def shade (self , data , cmap , norm = None , blend_mode = 'hsv' ,
1569+ vert_exag = 1 , dx = 1 , dy = 1 , fraction = 1 , ** kwargs ):
15721570 """
15731571 Combine colormapped data values with an illumination intensity map
15741572 (a.k.a. "hillshade") of the values.
@@ -1586,6 +1584,16 @@ def shade(self, data, cmap, norm=None, vert_exag=1, dx=1, dy=1, fraction=1,
15861584 norm : `~matplotlib.colors.Normalize` instance, optional
15871585 The normalization used to scale values before colormapping. If
15881586 None, the input will be linearly scaled between its min and max.
1587+ blend_mode : {'hsv', 'overlay', 'soft'} or callable, optional
1588+ The type of blending used to combine the colormapped data values
1589+ with the illumination intensity. For backwards compatibility, this
1590+ defaults to "hsv". Note that for most topographic surfaces,
1591+ "overlay" or "soft" appear more visually realistic. If a
1592+ user-defined function is supplied, it is expected to combine an
1593+ MxNx3 RGB array of floats (ranging 0 to 1) with an MxNx1 hillshade
1594+ array (also 0 to 1). (Call signature `func(rgb, illum, **kwargs)`)
1595+ Additional kwargs supplied to this function will be passed on to
1596+ the *blend_mode* function.
15891597 vert_exag : number, optional
15901598 The amount to exaggerate the elevation values by when calculating
15911599 illumination. This can be used either to correct for differences in
@@ -1622,8 +1630,8 @@ def shade(self, data, cmap, norm=None, vert_exag=1, dx=1, dy=1, fraction=1,
16221630 rgb0 [..., :3 ] = rgb1 [..., :3 ]
16231631 return rgb0
16241632
1625- def shade_rgb (self , rgb , elevation , fraction = 1. , vert_exag = 1 , dx = 1 , dy = 1 ,
1626- ** kwargs ):
1633+ def shade_rgb (self , rgb , elevation , fraction = 1. , blend_mode = 'hsv' ,
1634+ vert_exag = 1 , dx = 1 , dy = 1 , ** kwargs ):
16271635 """
16281636 Take the input RGB array (ny*nx*3) adjust their color values
16291637 to given the impression of a shaded relief map with a
@@ -1646,6 +1654,16 @@ def shade_rgb(self, rgb, elevation, fraction=1., vert_exag=1, dx=1, dy=1,
16461654 decreasing contrast. Note that this is not mathematically or
16471655 visually the same as increasing/decreasing the vertical
16481656 exaggeration.
1657+ blend_mode : {'hsv', 'overlay', 'soft'} or callable, optional
1658+ The type of blending used to combine the colormapped data values
1659+ with the illumination intensity. For backwards compatibility, this
1660+ defaults to "hsv". Note that for most topographic surfaces,
1661+ "overlay" or "soft" appear more visually realistic. If a
1662+ user-defined function is supplied, it is expected to combine an
1663+ MxNx3 RGB array of floats (ranging 0 to 1) with an MxNx1 hillshade
1664+ array (also 0 to 1). (Call signature `func(rgb, illum, **kwargs)`)
1665+ Additional kwargs supplied to this function will be passed on to
1666+ the *blend_mode* function.
16491667 vert_exag : number, optional
16501668 The amount to exaggerate the elevation values by when calculating
16511669 illumination. This can be used either to correct for differences in
@@ -1656,15 +1674,31 @@ def shade_rgb(self, rgb, elevation, fraction=1., vert_exag=1, dx=1, dy=1,
16561674 The x-spacing (columns) of the input *elevation* grid.
16571675 dy : number, optional
16581676 The y-spacing (rows) of the input *elevation* grid.
1659- Additional kwargs are passed on to :meth:`blend_hsv` .
1677+ Additional kwargs are passed on to the *blend_mode* function .
16601678
16611679 Returns
16621680 -------
16631681 shaded_rgb : ndarray
16641682 An MxNx3 array of floats ranging between 0-1.
16651683 """
1666- intensity = self .hillshade (elevation , fraction = fraction )
1667- return self .blend_hsv (rgb , intensity , ** kwargs )
1684+ # Calculate the "hillshade" intensity.
1685+ intensity = self .hillshade (elevation , vert_exag , dx , dy , fraction )
1686+ intensity = intensity [..., np .newaxis ]
1687+
1688+ # Blend the hillshade and rgb data using the specified mode
1689+ lookup = {
1690+ 'hsv' :self .blend_hsv ,
1691+ 'soft' :self .blend_soft_light ,
1692+ 'overlay' :self .blend_overlay ,
1693+ }
1694+ if blend_mode in lookup :
1695+ return lookup [blend_mode ](rgb , intensity , ** kwargs )
1696+ else :
1697+ try :
1698+ return blend_mode (rgb , intensity , ** kwargs )
1699+ except TypeError :
1700+ msg = '"blend_mode" must be callable or one of {}'
1701+ raise ValueError (msg .format (lookup .keys ))
16681702
16691703 def blend_hsv (self , rgb , intensity , hsv_max_sat = None , hsv_max_val = None ,
16701704 hsv_min_val = None , hsv_min_sat = None ):
@@ -1716,6 +1750,7 @@ def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None,
17161750 hsv_min_val = self .hsv_min_val
17171751
17181752 # Expects a 2D intensity array scaled between -1 to 1...
1753+ intensity = intensity [..., 0 ]
17191754 intensity = 2 * intensity - 1
17201755
17211756 # convert to rgb, then rgb to hsv
@@ -1747,6 +1782,45 @@ def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None,
17471782 # convert modified hsv back to rgb.
17481783 return hsv_to_rgb (hsv )
17491784
1785+ def blend_soft_light (self , rgb , intensity ):
1786+ """
1787+ Combines an rgb image with an intensity map using "soft light"
1788+ blending. Uses the "pegtop" formula.
1789+
1790+ Parameters
1791+ ----------
1792+ rgb : ndarray
1793+ An MxNx3 RGB array of floats ranging from 0 to 1 (color image).
1794+ intensity : ndarray
1795+ An MxNx1 array of floats ranging from 0 to 1 (grayscale image).
1796+
1797+ Returns
1798+ -------
1799+ rgb : ndarray
1800+ An MxNx3 RGB array representing the combined images.
1801+ """
1802+ return 2 * intensity * rgb + (1 - 2 * intensity ) * rgb ** 2
1803+
1804+ def blend_overlay (self , rgb , intensity ):
1805+ """
1806+ Combines an rgb image with an intensity map using "overlay" blending.
1807+
1808+ Parameters
1809+ ----------
1810+ rgb : ndarray
1811+ An MxNx3 RGB array of floats ranging from 0 to 1 (color image).
1812+ intensity : ndarray
1813+ An MxNx1 array of floats ranging from 0 to 1 (grayscale image).
1814+
1815+ Returns
1816+ -------
1817+ rgb : ndarray
1818+ An MxNx3 RGB array representing the combined images.
1819+ """
1820+ low = 2 * intensity * rgb
1821+ high = 1 - 2 * (1 - intensity ) * (1 - rgb )
1822+ return np .where (rgb <= 0.5 , low , high )
1823+
17501824def from_levels_and_colors (levels , colors , extend = 'neither' ):
17511825 """
17521826 A helper routine to generate a cmap and a norm instance which
0 commit comments