colors.py 76 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151
  1. """
  2. A module for converting numbers or color arguments to *RGB* or *RGBA*.
  3. *RGB* and *RGBA* are sequences of, respectively, 3 or 4 floats in the
  4. range 0-1.
  5. This module includes functions and classes for color specification conversions,
  6. and for mapping numbers to colors in a 1-D array of colors called a colormap.
  7. Mapping data onto colors using a colormap typically involves two steps: a data
  8. array is first mapped onto the range 0-1 using a subclass of `Normalize`,
  9. then this number is mapped to a color using a subclass of `Colormap`. Two
  10. sublasses of `Colormap` provided here: `LinearSegmentedColormap`, which uses
  11. piecewise-linear interpolation to define colormaps, and `ListedColormap`, which
  12. makes a colormap from a list of colors.
  13. .. seealso::
  14. :doc:`/tutorials/colors/colormap-manipulation` for examples of how to
  15. make colormaps and
  16. :doc:`/tutorials/colors/colormaps` for a list of built-in colormaps.
  17. :doc:`/tutorials/colors/colormapnorms` for more details about data
  18. normalization
  19. More colormaps are available at palettable_.
  20. The module also provides functions for checking whether an object can be
  21. interpreted as a color (`is_color_like`), for converting such an object
  22. to an RGBA tuple (`to_rgba`) or to an HTML-like hex string in the
  23. "#rrggbb" format (`to_hex`), and a sequence of colors to an (n, 4)
  24. RGBA array (`to_rgba_array`). Caching is used for efficiency.
  25. Matplotlib recognizes the following formats to specify a color:
  26. * an RGB or RGBA (red, green, blue, alpha) tuple of float values in closed
  27. interval ``[0, 1]`` (e.g., ``(0.1, 0.2, 0.5)`` or ``(0.1, 0.2, 0.5, 0.3)``);
  28. * a hex RGB or RGBA string (e.g., ``'#0f0f0f'`` or ``'#0f0f0f80'``;
  29. case-insensitive);
  30. * a shorthand hex RGB or RGBA string, equivalent to the hex RGB or RGBA
  31. string obtained by duplicating each character, (e.g., ``'#abc'``, equivalent
  32. to ``'#aabbcc'``, or ``'#abcd'``, equivalent to ``'#aabbccdd'``;
  33. case-insensitive);
  34. * a string representation of a float value in ``[0, 1]`` inclusive for gray
  35. level (e.g., ``'0.5'``);
  36. * one of the characters ``{'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'}``, which
  37. are short-hand notations for shades of blue, green, red, cyan, magenta,
  38. yellow, black, and white. Note that the colors ``'g', 'c', 'm', 'y'`` do not
  39. coincide with the X11/CSS4 colors. Their particular shades were chosen for
  40. better visibility of colored lines against typical backgrounds.
  41. * a X11/CSS4 color name (case-insensitive);
  42. * a name from the `xkcd color survey`_, prefixed with ``'xkcd:'`` (e.g.,
  43. ``'xkcd:sky blue'``; case insensitive);
  44. * one of the Tableau Colors from the 'T10' categorical palette (the default
  45. color cycle): ``{'tab:blue', 'tab:orange', 'tab:green', 'tab:red',
  46. 'tab:purple', 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan'}``
  47. (case-insensitive);
  48. * a "CN" color spec, i.e. 'C' followed by a number, which is an index into the
  49. default property cycle (:rc:`axes.prop_cycle`); the indexing is intended to
  50. occur at rendering time, and defaults to black if the cycle does not include
  51. color.
  52. .. _palettable: https://jiffyclub.github.io/palettable/
  53. .. _xkcd color survey: https://xkcd.com/color/rgb/
  54. """
  55. from collections.abc import Sized
  56. import functools
  57. import itertools
  58. from numbers import Number
  59. import re
  60. import numpy as np
  61. import matplotlib.cbook as cbook
  62. from matplotlib import docstring
  63. from ._color_data import BASE_COLORS, TABLEAU_COLORS, CSS4_COLORS, XKCD_COLORS
  64. class _ColorMapping(dict):
  65. def __init__(self, mapping):
  66. super().__init__(mapping)
  67. self.cache = {}
  68. def __setitem__(self, key, value):
  69. super().__setitem__(key, value)
  70. self.cache.clear()
  71. def __delitem__(self, key):
  72. super().__delitem__(key)
  73. self.cache.clear()
  74. _colors_full_map = {}
  75. # Set by reverse priority order.
  76. _colors_full_map.update(XKCD_COLORS)
  77. _colors_full_map.update({k.replace('grey', 'gray'): v
  78. for k, v in XKCD_COLORS.items()
  79. if 'grey' in k})
  80. _colors_full_map.update(CSS4_COLORS)
  81. _colors_full_map.update(TABLEAU_COLORS)
  82. _colors_full_map.update({k.replace('gray', 'grey'): v
  83. for k, v in TABLEAU_COLORS.items()
  84. if 'gray' in k})
  85. _colors_full_map.update(BASE_COLORS)
  86. _colors_full_map = _ColorMapping(_colors_full_map)
  87. def get_named_colors_mapping():
  88. """Return the global mapping of names to named colors."""
  89. return _colors_full_map
  90. def _sanitize_extrema(ex):
  91. if ex is None:
  92. return ex
  93. try:
  94. ret = ex.item()
  95. except AttributeError:
  96. ret = float(ex)
  97. return ret
  98. def _is_nth_color(c):
  99. """Return whether *c* can be interpreted as an item in the color cycle."""
  100. return isinstance(c, str) and re.match(r"\AC[0-9]+\Z", c)
  101. def is_color_like(c):
  102. """Return whether *c* can be interpreted as an RGB(A) color."""
  103. # Special-case nth color syntax because it cannot be parsed during setup.
  104. if _is_nth_color(c):
  105. return True
  106. try:
  107. to_rgba(c)
  108. except ValueError:
  109. return False
  110. else:
  111. return True
  112. def same_color(c1, c2):
  113. """
  114. Return whether the colors *c1* and *c2* are the same.
  115. *c1*, *c2* can be single colors or lists/arrays of colors.
  116. """
  117. c1 = to_rgba_array(c1)
  118. c2 = to_rgba_array(c2)
  119. n1 = max(c1.shape[0], 1) # 'none' results in shape (0, 4), but is 1-elem
  120. n2 = max(c2.shape[0], 1) # 'none' results in shape (0, 4), but is 1-elem
  121. if n1 != n2:
  122. raise ValueError('Different number of elements passed.')
  123. # The following shape test is needed to correctly handle comparisons with
  124. # 'none', which results in a shape (0, 4) array and thus cannot be tested
  125. # via value comparison.
  126. return c1.shape == c2.shape and (c1 == c2).all()
  127. def to_rgba(c, alpha=None):
  128. """
  129. Convert *c* to an RGBA color.
  130. Parameters
  131. ----------
  132. c : Matplotlib color or ``np.ma.masked``
  133. alpha : float, optional
  134. If *alpha* is not ``None``, it forces the alpha value, except if *c* is
  135. ``"none"`` (case-insensitive), which always maps to ``(0, 0, 0, 0)``.
  136. Returns
  137. -------
  138. tuple
  139. Tuple of ``(r, g, b, a)`` scalars.
  140. """
  141. # Special-case nth color syntax because it should not be cached.
  142. if _is_nth_color(c):
  143. from matplotlib import rcParams
  144. prop_cycler = rcParams['axes.prop_cycle']
  145. colors = prop_cycler.by_key().get('color', ['k'])
  146. c = colors[int(c[1:]) % len(colors)]
  147. try:
  148. rgba = _colors_full_map.cache[c, alpha]
  149. except (KeyError, TypeError): # Not in cache, or unhashable.
  150. rgba = None
  151. if rgba is None: # Suppress exception chaining of cache lookup failure.
  152. rgba = _to_rgba_no_colorcycle(c, alpha)
  153. try:
  154. _colors_full_map.cache[c, alpha] = rgba
  155. except TypeError:
  156. pass
  157. return rgba
  158. def _to_rgba_no_colorcycle(c, alpha=None):
  159. """
  160. Convert *c* to an RGBA color, with no support for color-cycle syntax.
  161. If *alpha* is not ``None``, it forces the alpha value, except if *c* is
  162. ``"none"`` (case-insensitive), which always maps to ``(0, 0, 0, 0)``.
  163. """
  164. orig_c = c
  165. if c is np.ma.masked:
  166. return (0., 0., 0., 0.)
  167. if isinstance(c, str):
  168. if c.lower() == "none":
  169. return (0., 0., 0., 0.)
  170. # Named color.
  171. try:
  172. # This may turn c into a non-string, so we check again below.
  173. c = _colors_full_map[c]
  174. except KeyError:
  175. if len(orig_c) != 1:
  176. try:
  177. c = _colors_full_map[c.lower()]
  178. except KeyError:
  179. pass
  180. if isinstance(c, str):
  181. # hex color in #rrggbb format.
  182. match = re.match(r"\A#[a-fA-F0-9]{6}\Z", c)
  183. if match:
  184. return (tuple(int(n, 16) / 255
  185. for n in [c[1:3], c[3:5], c[5:7]])
  186. + (alpha if alpha is not None else 1.,))
  187. # hex color in #rgb format, shorthand for #rrggbb.
  188. match = re.match(r"\A#[a-fA-F0-9]{3}\Z", c)
  189. if match:
  190. return (tuple(int(n, 16) / 255
  191. for n in [c[1]*2, c[2]*2, c[3]*2])
  192. + (alpha if alpha is not None else 1.,))
  193. # hex color with alpha in #rrggbbaa format.
  194. match = re.match(r"\A#[a-fA-F0-9]{8}\Z", c)
  195. if match:
  196. color = [int(n, 16) / 255
  197. for n in [c[1:3], c[3:5], c[5:7], c[7:9]]]
  198. if alpha is not None:
  199. color[-1] = alpha
  200. return tuple(color)
  201. # hex color with alpha in #rgba format, shorthand for #rrggbbaa.
  202. match = re.match(r"\A#[a-fA-F0-9]{4}\Z", c)
  203. if match:
  204. color = [int(n, 16) / 255
  205. for n in [c[1]*2, c[2]*2, c[3]*2, c[4]*2]]
  206. if alpha is not None:
  207. color[-1] = alpha
  208. return tuple(color)
  209. # string gray.
  210. try:
  211. c = float(c)
  212. except ValueError:
  213. pass
  214. else:
  215. if not (0 <= c <= 1):
  216. raise ValueError(
  217. f"Invalid string grayscale value {orig_c!r}. "
  218. f"Value must be within 0-1 range")
  219. return c, c, c, alpha if alpha is not None else 1.
  220. raise ValueError(f"Invalid RGBA argument: {orig_c!r}")
  221. # tuple color.
  222. if not np.iterable(c):
  223. raise ValueError(f"Invalid RGBA argument: {orig_c!r}")
  224. if len(c) not in [3, 4]:
  225. raise ValueError("RGBA sequence should have length 3 or 4")
  226. if not all(isinstance(x, Number) for x in c):
  227. # Checks that don't work: `map(float, ...)`, `np.array(..., float)` and
  228. # `np.array(...).astype(float)` would all convert "0.5" to 0.5.
  229. raise ValueError(f"Invalid RGBA argument: {orig_c!r}")
  230. # Return a tuple to prevent the cached value from being modified.
  231. c = tuple(map(float, c))
  232. if len(c) == 3 and alpha is None:
  233. alpha = 1
  234. if alpha is not None:
  235. c = c[:3] + (alpha,)
  236. if any(elem < 0 or elem > 1 for elem in c):
  237. raise ValueError("RGBA values should be within 0-1 range")
  238. return c
  239. def to_rgba_array(c, alpha=None):
  240. """
  241. Convert *c* to a (n, 4) array of RGBA colors.
  242. If *alpha* is not ``None``, it forces the alpha value. If *c* is
  243. ``"none"`` (case-insensitive) or an empty list, an empty array is returned.
  244. If *c* is a masked array, an ndarray is returned with a (0, 0, 0, 0)
  245. row for each masked value or row in *c*.
  246. """
  247. # Special-case inputs that are already arrays, for performance. (If the
  248. # array has the wrong kind or shape, raise the error during one-at-a-time
  249. # conversion.)
  250. if (isinstance(c, np.ndarray) and c.dtype.kind in "if"
  251. and c.ndim == 2 and c.shape[1] in [3, 4]):
  252. mask = c.mask.any(axis=1) if np.ma.is_masked(c) else None
  253. c = np.ma.getdata(c)
  254. if c.shape[1] == 3:
  255. result = np.column_stack([c, np.zeros(len(c))])
  256. result[:, -1] = alpha if alpha is not None else 1.
  257. elif c.shape[1] == 4:
  258. result = c.copy()
  259. if alpha is not None:
  260. result[:, -1] = alpha
  261. if mask is not None:
  262. result[mask] = 0
  263. if np.any((result < 0) | (result > 1)):
  264. raise ValueError("RGBA values should be within 0-1 range")
  265. return result
  266. # Handle single values.
  267. # Note that this occurs *after* handling inputs that are already arrays, as
  268. # `to_rgba(c, alpha)` (below) is expensive for such inputs, due to the need
  269. # to format the array in the ValueError message(!).
  270. if cbook._str_lower_equal(c, "none"):
  271. return np.zeros((0, 4), float)
  272. try:
  273. return np.array([to_rgba(c, alpha)], float)
  274. except (ValueError, TypeError):
  275. pass
  276. # Convert one at a time.
  277. if isinstance(c, str):
  278. # Single string as color sequence.
  279. # This is deprecated and will be removed in the future.
  280. try:
  281. result = np.array([to_rgba(cc, alpha) for cc in c])
  282. except ValueError as err:
  283. raise ValueError(
  284. "'%s' is neither a valid single color nor a color sequence "
  285. "consisting of single character color specifiers such as "
  286. "'rgb'. Note also that the latter is deprecated." % c) from err
  287. else:
  288. cbook.warn_deprecated(
  289. "3.2", message="Using a string of single character colors as "
  290. "a color sequence is deprecated since %(since)s and will be "
  291. "removed %(removal)s. Use an explicit list instead.")
  292. return result
  293. if len(c) == 0:
  294. return np.zeros((0, 4), float)
  295. else:
  296. return np.array([to_rgba(cc, alpha) for cc in c])
  297. def to_rgb(c):
  298. """Convert *c* to an RGB color, silently dropping the alpha channel."""
  299. return to_rgba(c)[:3]
  300. def to_hex(c, keep_alpha=False):
  301. """
  302. Convert *c* to a hex color.
  303. Uses the ``#rrggbb`` format if *keep_alpha* is False (the default),
  304. ``#rrggbbaa`` otherwise.
  305. """
  306. c = to_rgba(c)
  307. if not keep_alpha:
  308. c = c[:3]
  309. return "#" + "".join(format(int(round(val * 255)), "02x") for val in c)
  310. ### Backwards-compatible color-conversion API
  311. cnames = CSS4_COLORS
  312. hexColorPattern = re.compile(r"\A#[a-fA-F0-9]{6}\Z")
  313. rgb2hex = to_hex
  314. hex2color = to_rgb
  315. class ColorConverter:
  316. """
  317. A class only kept for backwards compatibility.
  318. Its functionality is entirely provided by module-level functions.
  319. """
  320. colors = _colors_full_map
  321. cache = _colors_full_map.cache
  322. to_rgb = staticmethod(to_rgb)
  323. to_rgba = staticmethod(to_rgba)
  324. to_rgba_array = staticmethod(to_rgba_array)
  325. colorConverter = ColorConverter()
  326. ### End of backwards-compatible color-conversion API
  327. def _create_lookup_table(N, data, gamma=1.0):
  328. r"""
  329. Create an *N* -element 1-d lookup table.
  330. This assumes a mapping :math:`f : [0, 1] \rightarrow [0, 1]`. The returned
  331. data is an array of N values :math:`y = f(x)` where x is sampled from
  332. [0, 1].
  333. By default (*gamma* = 1) x is equidistantly sampled from [0, 1]. The
  334. *gamma* correction factor :math:`\gamma` distorts this equidistant
  335. sampling by :math:`x \rightarrow x^\gamma`.
  336. Parameters
  337. ----------
  338. N : int
  339. The number of elements of the created lookup table; at least 1.
  340. data : Mx3 array-like or callable
  341. Defines the mapping :math:`f`.
  342. If a Mx3 array-like, the rows define values (x, y0, y1). The x values
  343. must start with x=0, end with x=1, and all x values be in increasing
  344. order.
  345. A value between :math:`x_i` and :math:`x_{i+1}` is mapped to the range
  346. :math:`y^1_{i-1} \ldots y^0_i` by linear interpolation.
  347. For the simple case of a y-continuous mapping, y0 and y1 are identical.
  348. The two values of y are to allow for discontinuous mapping functions.
  349. E.g. a sawtooth with a period of 0.2 and an amplitude of 1 would be::
  350. [(0, 1, 0), (0.2, 1, 0), (0.4, 1, 0), ..., [(1, 1, 0)]
  351. In the special case of ``N == 1``, by convention the returned value
  352. is y0 for x == 1.
  353. If *data* is a callable, it must accept and return numpy arrays::
  354. data(x : ndarray) -> ndarray
  355. and map values between 0 - 1 to 0 - 1.
  356. gamma : float
  357. Gamma correction factor for input distribution x of the mapping.
  358. See also https://en.wikipedia.org/wiki/Gamma_correction.
  359. Returns
  360. -------
  361. array
  362. The lookup table where ``lut[x * (N-1)]`` gives the closest value
  363. for values of x between 0 and 1.
  364. Notes
  365. -----
  366. This function is internally used for `.LinearSegmentedColormap`.
  367. """
  368. if callable(data):
  369. xind = np.linspace(0, 1, N) ** gamma
  370. lut = np.clip(np.array(data(xind), dtype=float), 0, 1)
  371. return lut
  372. try:
  373. adata = np.array(data)
  374. except Exception as err:
  375. raise TypeError("data must be convertible to an array") from err
  376. shape = adata.shape
  377. if len(shape) != 2 or shape[1] != 3:
  378. raise ValueError("data must be nx3 format")
  379. x = adata[:, 0]
  380. y0 = adata[:, 1]
  381. y1 = adata[:, 2]
  382. if x[0] != 0. or x[-1] != 1.0:
  383. raise ValueError(
  384. "data mapping points must start with x=0 and end with x=1")
  385. if (np.diff(x) < 0).any():
  386. raise ValueError("data mapping points must have x in increasing order")
  387. # begin generation of lookup table
  388. if N == 1:
  389. # convention: use the y = f(x=1) value for a 1-element lookup table
  390. lut = np.array(y0[-1])
  391. else:
  392. x = x * (N - 1)
  393. xind = (N - 1) * np.linspace(0, 1, N) ** gamma
  394. ind = np.searchsorted(x, xind)[1:-1]
  395. distance = (xind[1:-1] - x[ind - 1]) / (x[ind] - x[ind - 1])
  396. lut = np.concatenate([
  397. [y1[0]],
  398. distance * (y0[ind] - y1[ind - 1]) + y1[ind - 1],
  399. [y0[-1]],
  400. ])
  401. # ensure that the lut is confined to values between 0 and 1 by clipping it
  402. return np.clip(lut, 0.0, 1.0)
  403. @cbook.deprecated("3.2",
  404. addendum='This is not considered public API any longer.')
  405. @docstring.copy(_create_lookup_table)
  406. def makeMappingArray(N, data, gamma=1.0):
  407. return _create_lookup_table(N, data, gamma)
  408. def _warn_if_global_cmap_modified(cmap):
  409. if getattr(cmap, '_global', False):
  410. cbook.warn_deprecated(
  411. "3.3",
  412. message="You are modifying the state of a globally registered "
  413. "colormap. In future versions, you will not be able to "
  414. "modify a registered colormap in-place. To remove this "
  415. "warning, you can make a copy of the colormap first. "
  416. f'cmap = copy.copy(mpl.cm.get_cmap("{cmap.name}"))'
  417. )
  418. class Colormap:
  419. """
  420. Baseclass for all scalar to RGBA mappings.
  421. Typically, Colormap instances are used to convert data values (floats)
  422. from the interval ``[0, 1]`` to the RGBA color that the respective
  423. Colormap represents. For scaling of data into the ``[0, 1]`` interval see
  424. `matplotlib.colors.Normalize`. Subclasses of `matplotlib.cm.ScalarMappable`
  425. make heavy use of this ``data -> normalize -> map-to-color`` processing
  426. chain.
  427. """
  428. def __init__(self, name, N=256):
  429. """
  430. Parameters
  431. ----------
  432. name : str
  433. The name of the colormap.
  434. N : int
  435. The number of rgb quantization levels.
  436. """
  437. self.name = name
  438. self.N = int(N) # ensure that N is always int
  439. self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything.
  440. self._rgba_under = None
  441. self._rgba_over = None
  442. self._i_under = self.N
  443. self._i_over = self.N + 1
  444. self._i_bad = self.N + 2
  445. self._isinit = False
  446. #: When this colormap exists on a scalar mappable and colorbar_extend
  447. #: is not False, colorbar creation will pick up ``colorbar_extend`` as
  448. #: the default value for the ``extend`` keyword in the
  449. #: `matplotlib.colorbar.Colorbar` constructor.
  450. self.colorbar_extend = False
  451. def __call__(self, X, alpha=None, bytes=False):
  452. """
  453. Parameters
  454. ----------
  455. X : float or int, ndarray or scalar
  456. The data value(s) to convert to RGBA.
  457. For floats, X should be in the interval ``[0.0, 1.0]`` to
  458. return the RGBA values ``X*100`` percent along the Colormap line.
  459. For integers, X should be in the interval ``[0, Colormap.N)`` to
  460. return RGBA values *indexed* from the Colormap with index ``X``.
  461. alpha : float, None
  462. Alpha must be a scalar between 0 and 1, or None.
  463. bytes : bool
  464. If False (default), the returned RGBA values will be floats in the
  465. interval ``[0, 1]`` otherwise they will be uint8s in the interval
  466. ``[0, 255]``.
  467. Returns
  468. -------
  469. Tuple of RGBA values if X is scalar, otherwise an array of
  470. RGBA values with a shape of ``X.shape + (4, )``.
  471. """
  472. if not self._isinit:
  473. self._init()
  474. mask_bad = X.mask if np.ma.is_masked(X) else np.isnan(X) # Mask nan's.
  475. xa = np.array(X, copy=True)
  476. if not xa.dtype.isnative:
  477. xa = xa.byteswap().newbyteorder() # Native byteorder is faster.
  478. if xa.dtype.kind == "f":
  479. with np.errstate(invalid="ignore"):
  480. xa *= self.N
  481. # Negative values are out of range, but astype(int) would
  482. # truncate them towards zero.
  483. xa[xa < 0] = -1
  484. # xa == 1 (== N after multiplication) is not out of range.
  485. xa[xa == self.N] = self.N - 1
  486. # Avoid converting large positive values to negative integers.
  487. np.clip(xa, -1, self.N, out=xa)
  488. xa = xa.astype(int)
  489. # Set the over-range indices before the under-range;
  490. # otherwise the under-range values get converted to over-range.
  491. xa[xa > self.N - 1] = self._i_over
  492. xa[xa < 0] = self._i_under
  493. xa[mask_bad] = self._i_bad
  494. if bytes:
  495. lut = (self._lut * 255).astype(np.uint8)
  496. else:
  497. lut = self._lut.copy() # Don't let alpha modify original _lut.
  498. if alpha is not None:
  499. alpha = np.clip(alpha, 0, 1)
  500. if bytes:
  501. alpha = int(alpha * 255)
  502. if (lut[-1] == 0).all():
  503. lut[:-1, -1] = alpha
  504. # All zeros is taken as a flag for the default bad
  505. # color, which is no color--fully transparent. We
  506. # don't want to override this.
  507. else:
  508. lut[:, -1] = alpha
  509. # If the bad value is set to have a color, then we
  510. # override its alpha just as for any other value.
  511. rgba = lut[xa]
  512. if not np.iterable(X):
  513. # Return a tuple if the input was a scalar
  514. rgba = tuple(rgba)
  515. return rgba
  516. def __copy__(self):
  517. cls = self.__class__
  518. cmapobject = cls.__new__(cls)
  519. cmapobject.__dict__.update(self.__dict__)
  520. if self._isinit:
  521. cmapobject._lut = np.copy(self._lut)
  522. cmapobject._global = False
  523. return cmapobject
  524. def set_bad(self, color='k', alpha=None):
  525. """Set the color for masked values."""
  526. _warn_if_global_cmap_modified(self)
  527. self._rgba_bad = to_rgba(color, alpha)
  528. if self._isinit:
  529. self._set_extremes()
  530. def set_under(self, color='k', alpha=None):
  531. """
  532. Set the color for low out-of-range values when ``norm.clip = False``.
  533. """
  534. _warn_if_global_cmap_modified(self)
  535. self._rgba_under = to_rgba(color, alpha)
  536. if self._isinit:
  537. self._set_extremes()
  538. def set_over(self, color='k', alpha=None):
  539. """
  540. Set the color for high out-of-range values when ``norm.clip = False``.
  541. """
  542. _warn_if_global_cmap_modified(self)
  543. self._rgba_over = to_rgba(color, alpha)
  544. if self._isinit:
  545. self._set_extremes()
  546. def _set_extremes(self):
  547. if self._rgba_under:
  548. self._lut[self._i_under] = self._rgba_under
  549. else:
  550. self._lut[self._i_under] = self._lut[0]
  551. if self._rgba_over:
  552. self._lut[self._i_over] = self._rgba_over
  553. else:
  554. self._lut[self._i_over] = self._lut[self.N - 1]
  555. self._lut[self._i_bad] = self._rgba_bad
  556. def _init(self):
  557. """Generate the lookup table, ``self._lut``."""
  558. raise NotImplementedError("Abstract class only")
  559. def is_gray(self):
  560. if not self._isinit:
  561. self._init()
  562. return (np.all(self._lut[:, 0] == self._lut[:, 1]) and
  563. np.all(self._lut[:, 0] == self._lut[:, 2]))
  564. def _resample(self, lutsize):
  565. """Return a new color map with *lutsize* entries."""
  566. raise NotImplementedError()
  567. def reversed(self, name=None):
  568. """
  569. Return a reversed instance of the Colormap.
  570. .. note:: This function is not implemented for base class.
  571. Parameters
  572. ----------
  573. name : str, optional
  574. The name for the reversed colormap. If it's None the
  575. name will be the name of the parent colormap + "_r".
  576. See Also
  577. --------
  578. LinearSegmentedColormap.reversed
  579. ListedColormap.reversed
  580. """
  581. raise NotImplementedError()
  582. class LinearSegmentedColormap(Colormap):
  583. """
  584. Colormap objects based on lookup tables using linear segments.
  585. The lookup table is generated using linear interpolation for each
  586. primary color, with the 0-1 domain divided into any number of
  587. segments.
  588. """
  589. def __init__(self, name, segmentdata, N=256, gamma=1.0):
  590. """
  591. Create color map from linear mapping segments
  592. segmentdata argument is a dictionary with a red, green and blue
  593. entries. Each entry should be a list of *x*, *y0*, *y1* tuples,
  594. forming rows in a table. Entries for alpha are optional.
  595. Example: suppose you want red to increase from 0 to 1 over
  596. the bottom half, green to do the same over the middle half,
  597. and blue over the top half. Then you would use::
  598. cdict = {'red': [(0.0, 0.0, 0.0),
  599. (0.5, 1.0, 1.0),
  600. (1.0, 1.0, 1.0)],
  601. 'green': [(0.0, 0.0, 0.0),
  602. (0.25, 0.0, 0.0),
  603. (0.75, 1.0, 1.0),
  604. (1.0, 1.0, 1.0)],
  605. 'blue': [(0.0, 0.0, 0.0),
  606. (0.5, 0.0, 0.0),
  607. (1.0, 1.0, 1.0)]}
  608. Each row in the table for a given color is a sequence of
  609. *x*, *y0*, *y1* tuples. In each sequence, *x* must increase
  610. monotonically from 0 to 1. For any input value *z* falling
  611. between *x[i]* and *x[i+1]*, the output value of a given color
  612. will be linearly interpolated between *y1[i]* and *y0[i+1]*::
  613. row i: x y0 y1
  614. /
  615. /
  616. row i+1: x y0 y1
  617. Hence y0 in the first row and y1 in the last row are never used.
  618. See Also
  619. --------
  620. LinearSegmentedColormap.from_list
  621. Static method; factory function for generating a smoothly-varying
  622. LinearSegmentedColormap.
  623. makeMappingArray
  624. For information about making a mapping array.
  625. """
  626. # True only if all colors in map are identical; needed for contouring.
  627. self.monochrome = False
  628. Colormap.__init__(self, name, N)
  629. self._segmentdata = segmentdata
  630. self._gamma = gamma
  631. def _init(self):
  632. self._lut = np.ones((self.N + 3, 4), float)
  633. self._lut[:-3, 0] = _create_lookup_table(
  634. self.N, self._segmentdata['red'], self._gamma)
  635. self._lut[:-3, 1] = _create_lookup_table(
  636. self.N, self._segmentdata['green'], self._gamma)
  637. self._lut[:-3, 2] = _create_lookup_table(
  638. self.N, self._segmentdata['blue'], self._gamma)
  639. if 'alpha' in self._segmentdata:
  640. self._lut[:-3, 3] = _create_lookup_table(
  641. self.N, self._segmentdata['alpha'], 1)
  642. self._isinit = True
  643. self._set_extremes()
  644. def set_gamma(self, gamma):
  645. """Set a new gamma value and regenerate color map."""
  646. self._gamma = gamma
  647. self._init()
  648. @staticmethod
  649. def from_list(name, colors, N=256, gamma=1.0):
  650. """
  651. Create a `LinearSegmentedColormap` from a list of colors.
  652. Parameters
  653. ----------
  654. name : str
  655. The name of the colormap.
  656. colors : array-like of colors or array-like of (value, color)
  657. If only colors are given, they are equidistantly mapped from the
  658. range :math:`[0, 1]`; i.e. 0 maps to ``colors[0]`` and 1 maps to
  659. ``colors[-1]``.
  660. If (value, color) pairs are given, the mapping is from *value*
  661. to *color*. This can be used to divide the range unevenly.
  662. N : int
  663. The number of rgb quantization levels.
  664. gamma : float
  665. """
  666. if not np.iterable(colors):
  667. raise ValueError('colors must be iterable')
  668. if (isinstance(colors[0], Sized) and len(colors[0]) == 2
  669. and not isinstance(colors[0], str)):
  670. # List of value, color pairs
  671. vals, colors = zip(*colors)
  672. else:
  673. vals = np.linspace(0, 1, len(colors))
  674. cdict = dict(red=[], green=[], blue=[], alpha=[])
  675. for val, color in zip(vals, colors):
  676. r, g, b, a = to_rgba(color)
  677. cdict['red'].append((val, r, r))
  678. cdict['green'].append((val, g, g))
  679. cdict['blue'].append((val, b, b))
  680. cdict['alpha'].append((val, a, a))
  681. return LinearSegmentedColormap(name, cdict, N, gamma)
  682. def _resample(self, lutsize):
  683. """Return a new color map with *lutsize* entries."""
  684. new_cmap = LinearSegmentedColormap(self.name, self._segmentdata,
  685. lutsize)
  686. new_cmap._rgba_over = self._rgba_over
  687. new_cmap._rgba_under = self._rgba_under
  688. new_cmap._rgba_bad = self._rgba_bad
  689. return new_cmap
  690. # Helper ensuring picklability of the reversed cmap.
  691. @staticmethod
  692. def _reverser(func, x):
  693. return func(1 - x)
  694. def reversed(self, name=None):
  695. """
  696. Return a reversed instance of the Colormap.
  697. Parameters
  698. ----------
  699. name : str, optional
  700. The name for the reversed colormap. If it's None the
  701. name will be the name of the parent colormap + "_r".
  702. Returns
  703. -------
  704. LinearSegmentedColormap
  705. The reversed colormap.
  706. """
  707. if name is None:
  708. name = self.name + "_r"
  709. # Using a partial object keeps the cmap picklable.
  710. data_r = {key: (functools.partial(self._reverser, data)
  711. if callable(data) else
  712. [(1.0 - x, y1, y0) for x, y0, y1 in reversed(data)])
  713. for key, data in self._segmentdata.items()}
  714. new_cmap = LinearSegmentedColormap(name, data_r, self.N, self._gamma)
  715. # Reverse the over/under values too
  716. new_cmap._rgba_over = self._rgba_under
  717. new_cmap._rgba_under = self._rgba_over
  718. new_cmap._rgba_bad = self._rgba_bad
  719. return new_cmap
  720. class ListedColormap(Colormap):
  721. """
  722. Colormap object generated from a list of colors.
  723. This may be most useful when indexing directly into a colormap,
  724. but it can also be used to generate special colormaps for ordinary
  725. mapping.
  726. Parameters
  727. ----------
  728. colors : list, array
  729. List of Matplotlib color specifications, or an equivalent Nx3 or Nx4
  730. floating point array (*N* rgb or rgba values).
  731. name : str, optional
  732. String to identify the colormap.
  733. N : int, optional
  734. Number of entries in the map. The default is *None*, in which case
  735. there is one colormap entry for each element in the list of colors.
  736. If ::
  737. N < len(colors)
  738. the list will be truncated at *N*. If ::
  739. N > len(colors)
  740. the list will be extended by repetition.
  741. """
  742. def __init__(self, colors, name='from_list', N=None):
  743. self.monochrome = False # Are all colors identical? (for contour.py)
  744. if N is None:
  745. self.colors = colors
  746. N = len(colors)
  747. else:
  748. if isinstance(colors, str):
  749. self.colors = [colors] * N
  750. self.monochrome = True
  751. elif np.iterable(colors):
  752. if len(colors) == 1:
  753. self.monochrome = True
  754. self.colors = list(
  755. itertools.islice(itertools.cycle(colors), N))
  756. else:
  757. try:
  758. gray = float(colors)
  759. except TypeError:
  760. pass
  761. else:
  762. self.colors = [gray] * N
  763. self.monochrome = True
  764. Colormap.__init__(self, name, N)
  765. def _init(self):
  766. self._lut = np.zeros((self.N + 3, 4), float)
  767. self._lut[:-3] = to_rgba_array(self.colors)
  768. self._isinit = True
  769. self._set_extremes()
  770. def _resample(self, lutsize):
  771. """Return a new color map with *lutsize* entries."""
  772. colors = self(np.linspace(0, 1, lutsize))
  773. new_cmap = ListedColormap(colors, name=self.name)
  774. # Keep the over/under values too
  775. new_cmap._rgba_over = self._rgba_over
  776. new_cmap._rgba_under = self._rgba_under
  777. new_cmap._rgba_bad = self._rgba_bad
  778. return new_cmap
  779. def reversed(self, name=None):
  780. """
  781. Return a reversed instance of the Colormap.
  782. Parameters
  783. ----------
  784. name : str, optional
  785. The name for the reversed colormap. If it's None the
  786. name will be the name of the parent colormap + "_r".
  787. Returns
  788. -------
  789. ListedColormap
  790. A reversed instance of the colormap.
  791. """
  792. if name is None:
  793. name = self.name + "_r"
  794. colors_r = list(reversed(self.colors))
  795. new_cmap = ListedColormap(colors_r, name=name, N=self.N)
  796. # Reverse the over/under values too
  797. new_cmap._rgba_over = self._rgba_under
  798. new_cmap._rgba_under = self._rgba_over
  799. new_cmap._rgba_bad = self._rgba_bad
  800. return new_cmap
  801. class Normalize:
  802. """
  803. A class which, when called, linearly normalizes data into the
  804. ``[0.0, 1.0]`` interval.
  805. """
  806. def __init__(self, vmin=None, vmax=None, clip=False):
  807. """
  808. Parameters
  809. ----------
  810. vmin, vmax : float or None
  811. If *vmin* and/or *vmax* is not given, they are initialized from the
  812. minimum and maximum value, respectively, of the first input
  813. processed; i.e., ``__call__(A)`` calls ``autoscale_None(A)``.
  814. clip : bool, default: False
  815. If ``True`` values falling outside the range ``[vmin, vmax]``,
  816. are mapped to 0 or 1, whichever is closer, and masked values are
  817. set to 1. If ``False`` masked values remain masked.
  818. Clipping silently defeats the purpose of setting the over, under,
  819. and masked colors in a colormap, so it is likely to lead to
  820. surprises; therefore the default is ``clip=False``.
  821. Notes
  822. -----
  823. Returns 0 if ``vmin == vmax``.
  824. """
  825. self.vmin = _sanitize_extrema(vmin)
  826. self.vmax = _sanitize_extrema(vmax)
  827. self.clip = clip
  828. @staticmethod
  829. def process_value(value):
  830. """
  831. Homogenize the input *value* for easy and efficient normalization.
  832. *value* can be a scalar or sequence.
  833. Returns
  834. -------
  835. result : masked array
  836. Masked array with the same shape as *value*.
  837. is_scalar : bool
  838. Whether *value* is a scalar.
  839. Notes
  840. -----
  841. Float dtypes are preserved; integer types with two bytes or smaller are
  842. converted to np.float32, and larger types are converted to np.float64.
  843. Preserving float32 when possible, and using in-place operations,
  844. greatly improves speed for large arrays.
  845. """
  846. is_scalar = not np.iterable(value)
  847. if is_scalar:
  848. value = [value]
  849. dtype = np.min_scalar_type(value)
  850. if np.issubdtype(dtype, np.integer) or dtype.type is np.bool_:
  851. # bool_/int8/int16 -> float32; int32/int64 -> float64
  852. dtype = np.promote_types(dtype, np.float32)
  853. # ensure data passed in as an ndarray subclass are interpreted as
  854. # an ndarray. See issue #6622.
  855. mask = np.ma.getmask(value)
  856. data = np.asarray(value)
  857. result = np.ma.array(data, mask=mask, dtype=dtype, copy=True)
  858. return result, is_scalar
  859. def __call__(self, value, clip=None):
  860. """
  861. Normalize *value* data in the ``[vmin, vmax]`` interval into the
  862. ``[0.0, 1.0]`` interval and return it.
  863. Parameters
  864. ----------
  865. value
  866. Data to normalize.
  867. clip : bool
  868. If ``None``, defaults to ``self.clip`` (which defaults to
  869. ``False``).
  870. Notes
  871. -----
  872. If not already initialized, ``self.vmin`` and ``self.vmax`` are
  873. initialized using ``self.autoscale_None(value)``.
  874. """
  875. if clip is None:
  876. clip = self.clip
  877. result, is_scalar = self.process_value(value)
  878. self.autoscale_None(result)
  879. # Convert at least to float, without losing precision.
  880. (vmin,), _ = self.process_value(self.vmin)
  881. (vmax,), _ = self.process_value(self.vmax)
  882. if vmin == vmax:
  883. result.fill(0) # Or should it be all masked? Or 0.5?
  884. elif vmin > vmax:
  885. raise ValueError("minvalue must be less than or equal to maxvalue")
  886. else:
  887. if clip:
  888. mask = np.ma.getmask(result)
  889. result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax),
  890. mask=mask)
  891. # ma division is very slow; we can take a shortcut
  892. resdat = result.data
  893. resdat -= vmin
  894. resdat /= (vmax - vmin)
  895. result = np.ma.array(resdat, mask=result.mask, copy=False)
  896. if is_scalar:
  897. result = result[0]
  898. return result
  899. def inverse(self, value):
  900. if not self.scaled():
  901. raise ValueError("Not invertible until both vmin and vmax are set")
  902. (vmin,), _ = self.process_value(self.vmin)
  903. (vmax,), _ = self.process_value(self.vmax)
  904. if np.iterable(value):
  905. val = np.ma.asarray(value)
  906. return vmin + val * (vmax - vmin)
  907. else:
  908. return vmin + value * (vmax - vmin)
  909. def autoscale(self, A):
  910. """Set *vmin*, *vmax* to min, max of *A*."""
  911. A = np.asanyarray(A)
  912. self.vmin = A.min()
  913. self.vmax = A.max()
  914. def autoscale_None(self, A):
  915. """If vmin or vmax are not set, use the min/max of *A* to set them."""
  916. A = np.asanyarray(A)
  917. if self.vmin is None and A.size:
  918. self.vmin = A.min()
  919. if self.vmax is None and A.size:
  920. self.vmax = A.max()
  921. def scaled(self):
  922. """Return whether vmin and vmax are set."""
  923. return self.vmin is not None and self.vmax is not None
  924. class TwoSlopeNorm(Normalize):
  925. def __init__(self, vcenter, vmin=None, vmax=None):
  926. """
  927. Normalize data with a set center.
  928. Useful when mapping data with an unequal rates of change around a
  929. conceptual center, e.g., data that range from -2 to 4, with 0 as
  930. the midpoint.
  931. Parameters
  932. ----------
  933. vcenter : float
  934. The data value that defines ``0.5`` in the normalization.
  935. vmin : float, optional
  936. The data value that defines ``0.0`` in the normalization.
  937. Defaults to the min value of the dataset.
  938. vmax : float, optional
  939. The data value that defines ``1.0`` in the normalization.
  940. Defaults to the the max value of the dataset.
  941. Examples
  942. --------
  943. This maps data value -4000 to 0., 0 to 0.5, and +10000 to 1.0; data
  944. between is linearly interpolated::
  945. >>> import matplotlib.colors as mcolors
  946. >>> offset = mcolors.TwoSlopeNorm(vmin=-4000.,
  947. vcenter=0., vmax=10000)
  948. >>> data = [-4000., -2000., 0., 2500., 5000., 7500., 10000.]
  949. >>> offset(data)
  950. array([0., 0.25, 0.5, 0.625, 0.75, 0.875, 1.0])
  951. """
  952. self.vcenter = vcenter
  953. self.vmin = vmin
  954. self.vmax = vmax
  955. if vcenter is not None and vmax is not None and vcenter >= vmax:
  956. raise ValueError('vmin, vcenter, and vmax must be in '
  957. 'ascending order')
  958. if vcenter is not None and vmin is not None and vcenter <= vmin:
  959. raise ValueError('vmin, vcenter, and vmax must be in '
  960. 'ascending order')
  961. def autoscale_None(self, A):
  962. """
  963. Get vmin and vmax, and then clip at vcenter
  964. """
  965. super().autoscale_None(A)
  966. if self.vmin > self.vcenter:
  967. self.vmin = self.vcenter
  968. if self.vmax < self.vcenter:
  969. self.vmax = self.vcenter
  970. def __call__(self, value, clip=None):
  971. """
  972. Map value to the interval [0, 1]. The clip argument is unused.
  973. """
  974. result, is_scalar = self.process_value(value)
  975. self.autoscale_None(result) # sets self.vmin, self.vmax if None
  976. if not self.vmin <= self.vcenter <= self.vmax:
  977. raise ValueError("vmin, vcenter, vmax must increase monotonically")
  978. result = np.ma.masked_array(
  979. np.interp(result, [self.vmin, self.vcenter, self.vmax],
  980. [0, 0.5, 1.]), mask=np.ma.getmask(result))
  981. if is_scalar:
  982. result = np.atleast_1d(result)[0]
  983. return result
  984. @cbook.deprecation.deprecated('3.2', alternative='TwoSlopeNorm')
  985. class DivergingNorm(TwoSlopeNorm):
  986. ...
  987. class LogNorm(Normalize):
  988. """Normalize a given value to the 0-1 range on a log scale."""
  989. def _check_vmin_vmax(self):
  990. if self.vmin > self.vmax:
  991. raise ValueError("minvalue must be less than or equal to maxvalue")
  992. elif self.vmin <= 0:
  993. raise ValueError("minvalue must be positive")
  994. def __call__(self, value, clip=None):
  995. if clip is None:
  996. clip = self.clip
  997. result, is_scalar = self.process_value(value)
  998. result = np.ma.masked_less_equal(result, 0, copy=False)
  999. self.autoscale_None(result)
  1000. self._check_vmin_vmax()
  1001. vmin, vmax = self.vmin, self.vmax
  1002. if vmin == vmax:
  1003. result.fill(0)
  1004. else:
  1005. if clip:
  1006. mask = np.ma.getmask(result)
  1007. result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax),
  1008. mask=mask)
  1009. # in-place equivalent of above can be much faster
  1010. resdat = result.data
  1011. mask = result.mask
  1012. if mask is np.ma.nomask:
  1013. mask = (resdat <= 0)
  1014. else:
  1015. mask |= resdat <= 0
  1016. np.copyto(resdat, 1, where=mask)
  1017. np.log(resdat, resdat)
  1018. resdat -= np.log(vmin)
  1019. resdat /= (np.log(vmax) - np.log(vmin))
  1020. result = np.ma.array(resdat, mask=mask, copy=False)
  1021. if is_scalar:
  1022. result = result[0]
  1023. return result
  1024. def inverse(self, value):
  1025. if not self.scaled():
  1026. raise ValueError("Not invertible until scaled")
  1027. self._check_vmin_vmax()
  1028. vmin, vmax = self.vmin, self.vmax
  1029. if np.iterable(value):
  1030. val = np.ma.asarray(value)
  1031. return vmin * np.ma.power((vmax / vmin), val)
  1032. else:
  1033. return vmin * pow((vmax / vmin), value)
  1034. def autoscale(self, A):
  1035. # docstring inherited.
  1036. super().autoscale(np.ma.masked_less_equal(A, 0, copy=False))
  1037. def autoscale_None(self, A):
  1038. # docstring inherited.
  1039. super().autoscale_None(np.ma.masked_less_equal(A, 0, copy=False))
  1040. class SymLogNorm(Normalize):
  1041. """
  1042. The symmetrical logarithmic scale is logarithmic in both the
  1043. positive and negative directions from the origin.
  1044. Since the values close to zero tend toward infinity, there is a
  1045. need to have a range around zero that is linear. The parameter
  1046. *linthresh* allows the user to specify the size of this range
  1047. (-*linthresh*, *linthresh*).
  1048. """
  1049. def __init__(self, linthresh, linscale=1.0, vmin=None, vmax=None,
  1050. clip=False, *, base=None):
  1051. """
  1052. Parameters
  1053. ----------
  1054. linthresh : float
  1055. The range within which the plot is linear (to avoid having the plot
  1056. go to infinity around zero).
  1057. linscale : float, default: 1
  1058. This allows the linear range (-*linthresh* to *linthresh*)
  1059. to be stretched relative to the logarithmic range. Its
  1060. value is the number of powers of *base* to use for each
  1061. half of the linear range.
  1062. For example, when *linscale* == 1.0 (the default) and
  1063. ``base=10``, then space used for the positive and negative
  1064. halves of the linear range will be equal to a decade in
  1065. the logarithmic.
  1066. base : float, default: None
  1067. If not given, defaults to ``np.e`` (consistent with prior
  1068. behavior) and warns.
  1069. In v3.3 the default value will change to 10 to be consistent with
  1070. `.SymLogNorm`.
  1071. To suppress the warning pass *base* as a keyword argument.
  1072. """
  1073. Normalize.__init__(self, vmin, vmax, clip)
  1074. if base is None:
  1075. self._base = np.e
  1076. cbook.warn_deprecated(
  1077. "3.2", removal="3.4", message="default base will change from "
  1078. "np.e to 10 %(removal)s. To suppress this warning specify "
  1079. "the base keyword argument.")
  1080. else:
  1081. self._base = base
  1082. self._log_base = np.log(self._base)
  1083. self.linthresh = float(linthresh)
  1084. self._linscale_adj = (linscale / (1.0 - self._base ** -1))
  1085. if vmin is not None and vmax is not None:
  1086. self._transform_vmin_vmax()
  1087. def __call__(self, value, clip=None):
  1088. if clip is None:
  1089. clip = self.clip
  1090. result, is_scalar = self.process_value(value)
  1091. self.autoscale_None(result)
  1092. vmin, vmax = self.vmin, self.vmax
  1093. if vmin > vmax:
  1094. raise ValueError("minvalue must be less than or equal to maxvalue")
  1095. elif vmin == vmax:
  1096. result.fill(0)
  1097. else:
  1098. if clip:
  1099. mask = np.ma.getmask(result)
  1100. result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax),
  1101. mask=mask)
  1102. # in-place equivalent of above can be much faster
  1103. resdat = self._transform(result.data)
  1104. resdat -= self._lower
  1105. resdat /= (self._upper - self._lower)
  1106. if is_scalar:
  1107. result = result[0]
  1108. return result
  1109. def _transform(self, a):
  1110. """Inplace transformation."""
  1111. with np.errstate(invalid="ignore"):
  1112. masked = np.abs(a) > self.linthresh
  1113. sign = np.sign(a[masked])
  1114. log = (self._linscale_adj +
  1115. np.log(np.abs(a[masked]) / self.linthresh) / self._log_base)
  1116. log *= sign * self.linthresh
  1117. a[masked] = log
  1118. a[~masked] *= self._linscale_adj
  1119. return a
  1120. def _inv_transform(self, a):
  1121. """Inverse inplace Transformation."""
  1122. masked = np.abs(a) > (self.linthresh * self._linscale_adj)
  1123. sign = np.sign(a[masked])
  1124. exp = np.power(self._base,
  1125. sign * a[masked] / self.linthresh - self._linscale_adj)
  1126. exp *= sign * self.linthresh
  1127. a[masked] = exp
  1128. a[~masked] /= self._linscale_adj
  1129. return a
  1130. def _transform_vmin_vmax(self):
  1131. """Calculate vmin and vmax in the transformed system."""
  1132. vmin, vmax = self.vmin, self.vmax
  1133. arr = np.array([vmax, vmin]).astype(float)
  1134. self._upper, self._lower = self._transform(arr)
  1135. def inverse(self, value):
  1136. if not self.scaled():
  1137. raise ValueError("Not invertible until scaled")
  1138. val = np.ma.asarray(value)
  1139. val = val * (self._upper - self._lower) + self._lower
  1140. return self._inv_transform(val)
  1141. def autoscale(self, A):
  1142. # docstring inherited.
  1143. super().autoscale(A)
  1144. self._transform_vmin_vmax()
  1145. def autoscale_None(self, A):
  1146. # docstring inherited.
  1147. super().autoscale_None(A)
  1148. self._transform_vmin_vmax()
  1149. class PowerNorm(Normalize):
  1150. """
  1151. Linearly map a given value to the 0-1 range and then apply
  1152. a power-law normalization over that range.
  1153. """
  1154. def __init__(self, gamma, vmin=None, vmax=None, clip=False):
  1155. Normalize.__init__(self, vmin, vmax, clip)
  1156. self.gamma = gamma
  1157. def __call__(self, value, clip=None):
  1158. if clip is None:
  1159. clip = self.clip
  1160. result, is_scalar = self.process_value(value)
  1161. self.autoscale_None(result)
  1162. gamma = self.gamma
  1163. vmin, vmax = self.vmin, self.vmax
  1164. if vmin > vmax:
  1165. raise ValueError("minvalue must be less than or equal to maxvalue")
  1166. elif vmin == vmax:
  1167. result.fill(0)
  1168. else:
  1169. if clip:
  1170. mask = np.ma.getmask(result)
  1171. result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax),
  1172. mask=mask)
  1173. resdat = result.data
  1174. resdat -= vmin
  1175. resdat[resdat < 0] = 0
  1176. np.power(resdat, gamma, resdat)
  1177. resdat /= (vmax - vmin) ** gamma
  1178. result = np.ma.array(resdat, mask=result.mask, copy=False)
  1179. if is_scalar:
  1180. result = result[0]
  1181. return result
  1182. def inverse(self, value):
  1183. if not self.scaled():
  1184. raise ValueError("Not invertible until scaled")
  1185. gamma = self.gamma
  1186. vmin, vmax = self.vmin, self.vmax
  1187. if np.iterable(value):
  1188. val = np.ma.asarray(value)
  1189. return np.ma.power(val, 1. / gamma) * (vmax - vmin) + vmin
  1190. else:
  1191. return pow(value, 1. / gamma) * (vmax - vmin) + vmin
  1192. class BoundaryNorm(Normalize):
  1193. """
  1194. Generate a colormap index based on discrete intervals.
  1195. Unlike `Normalize` or `LogNorm`, `BoundaryNorm` maps values to integers
  1196. instead of to the interval 0-1.
  1197. Mapping to the 0-1 interval could have been done via piece-wise linear
  1198. interpolation, but using integers seems simpler, and reduces the number of
  1199. conversions back and forth between integer and floating point.
  1200. """
  1201. def __init__(self, boundaries, ncolors, clip=False, *, extend='neither'):
  1202. """
  1203. Parameters
  1204. ----------
  1205. boundaries : array-like
  1206. Monotonically increasing sequence of boundaries
  1207. ncolors : int
  1208. Number of colors in the colormap to be used
  1209. clip : bool, optional
  1210. If clip is ``True``, out of range values are mapped to 0 if they
  1211. are below ``boundaries[0]`` or mapped to ``ncolors - 1`` if they
  1212. are above ``boundaries[-1]``.
  1213. If clip is ``False``, out of range values are mapped to -1 if
  1214. they are below ``boundaries[0]`` or mapped to *ncolors* if they are
  1215. above ``boundaries[-1]``. These are then converted to valid indices
  1216. by `Colormap.__call__`.
  1217. extend : {'neither', 'both', 'min', 'max'}, default: 'neither'
  1218. Extend the number of bins to include one or both of the
  1219. regions beyond the boundaries. For example, if ``extend``
  1220. is 'min', then the color to which the region between the first
  1221. pair of boundaries is mapped will be distinct from the first
  1222. color in the colormap, and by default a
  1223. `~matplotlib.colorbar.Colorbar` will be drawn with
  1224. the triangle extension on the left or lower end.
  1225. Returns
  1226. -------
  1227. int16 scalar or array
  1228. Notes
  1229. -----
  1230. *boundaries* defines the edges of bins, and data falling within a bin
  1231. is mapped to the color with the same index.
  1232. If the number of bins, including any extensions, is less than
  1233. *ncolors*, the color index is chosen by linear interpolation, mapping
  1234. the ``[0, nbins - 1]`` range onto the ``[0, ncolors - 1]`` range.
  1235. """
  1236. if clip and extend != 'neither':
  1237. raise ValueError("'clip=True' is not compatible with 'extend'")
  1238. self.clip = clip
  1239. self.vmin = boundaries[0]
  1240. self.vmax = boundaries[-1]
  1241. self.boundaries = np.asarray(boundaries)
  1242. self.N = len(self.boundaries)
  1243. self.Ncmap = ncolors
  1244. self.extend = extend
  1245. self._N = self.N - 1 # number of colors needed
  1246. self._offset = 0
  1247. if extend in ('min', 'both'):
  1248. self._N += 1
  1249. self._offset = 1
  1250. if extend in ('max', 'both'):
  1251. self._N += 1
  1252. if self._N > self.Ncmap:
  1253. raise ValueError(f"There are {self._N} color bins including "
  1254. f"extensions, but ncolors = {ncolors}; "
  1255. "ncolors must equal or exceed the number of "
  1256. "bins")
  1257. def __call__(self, value, clip=None):
  1258. if clip is None:
  1259. clip = self.clip
  1260. xx, is_scalar = self.process_value(value)
  1261. mask = np.ma.getmaskarray(xx)
  1262. xx = np.atleast_1d(xx.filled(self.vmax + 1))
  1263. if clip:
  1264. np.clip(xx, self.vmin, self.vmax, out=xx)
  1265. max_col = self.Ncmap - 1
  1266. else:
  1267. max_col = self.Ncmap
  1268. iret = np.digitize(xx, self.boundaries) - 1 + self._offset
  1269. if self.Ncmap > self._N:
  1270. scalefac = (self.Ncmap - 1) / (self._N - 1)
  1271. iret = (iret * scalefac).astype(np.int16)
  1272. iret[xx < self.vmin] = -1
  1273. iret[xx >= self.vmax] = max_col
  1274. ret = np.ma.array(iret, mask=mask)
  1275. if is_scalar:
  1276. ret = int(ret[0]) # assume python scalar
  1277. return ret
  1278. def inverse(self, value):
  1279. """
  1280. Raises
  1281. ------
  1282. ValueError
  1283. BoundaryNorm is not invertible, so calling this method will always
  1284. raise an error
  1285. """
  1286. raise ValueError("BoundaryNorm is not invertible")
  1287. class NoNorm(Normalize):
  1288. """
  1289. Dummy replacement for `Normalize`, for the case where we want to use
  1290. indices directly in a `~matplotlib.cm.ScalarMappable`.
  1291. """
  1292. def __call__(self, value, clip=None):
  1293. return value
  1294. def inverse(self, value):
  1295. return value
  1296. def rgb_to_hsv(arr):
  1297. """
  1298. Convert float rgb values (in the range [0, 1]), in a numpy array to hsv
  1299. values.
  1300. Parameters
  1301. ----------
  1302. arr : (..., 3) array-like
  1303. All values must be in the range [0, 1]
  1304. Returns
  1305. -------
  1306. (..., 3) ndarray
  1307. Colors converted to hsv values in range [0, 1]
  1308. """
  1309. arr = np.asarray(arr)
  1310. # check length of the last dimension, should be _some_ sort of rgb
  1311. if arr.shape[-1] != 3:
  1312. raise ValueError("Last dimension of input array must be 3; "
  1313. "shape {} was found.".format(arr.shape))
  1314. in_shape = arr.shape
  1315. arr = np.array(
  1316. arr, copy=False,
  1317. dtype=np.promote_types(arr.dtype, np.float32), # Don't work on ints.
  1318. ndmin=2, # In case input was 1D.
  1319. )
  1320. out = np.zeros_like(arr)
  1321. arr_max = arr.max(-1)
  1322. ipos = arr_max > 0
  1323. delta = arr.ptp(-1)
  1324. s = np.zeros_like(delta)
  1325. s[ipos] = delta[ipos] / arr_max[ipos]
  1326. ipos = delta > 0
  1327. # red is max
  1328. idx = (arr[..., 0] == arr_max) & ipos
  1329. out[idx, 0] = (arr[idx, 1] - arr[idx, 2]) / delta[idx]
  1330. # green is max
  1331. idx = (arr[..., 1] == arr_max) & ipos
  1332. out[idx, 0] = 2. + (arr[idx, 2] - arr[idx, 0]) / delta[idx]
  1333. # blue is max
  1334. idx = (arr[..., 2] == arr_max) & ipos
  1335. out[idx, 0] = 4. + (arr[idx, 0] - arr[idx, 1]) / delta[idx]
  1336. out[..., 0] = (out[..., 0] / 6.0) % 1.0
  1337. out[..., 1] = s
  1338. out[..., 2] = arr_max
  1339. return out.reshape(in_shape)
  1340. def hsv_to_rgb(hsv):
  1341. """
  1342. Convert hsv values to rgb.
  1343. Parameters
  1344. ----------
  1345. hsv : (..., 3) array-like
  1346. All values assumed to be in range [0, 1]
  1347. Returns
  1348. -------
  1349. (..., 3) ndarray
  1350. Colors converted to RGB values in range [0, 1]
  1351. """
  1352. hsv = np.asarray(hsv)
  1353. # check length of the last dimension, should be _some_ sort of rgb
  1354. if hsv.shape[-1] != 3:
  1355. raise ValueError("Last dimension of input array must be 3; "
  1356. "shape {shp} was found.".format(shp=hsv.shape))
  1357. in_shape = hsv.shape
  1358. hsv = np.array(
  1359. hsv, copy=False,
  1360. dtype=np.promote_types(hsv.dtype, np.float32), # Don't work on ints.
  1361. ndmin=2, # In case input was 1D.
  1362. )
  1363. h = hsv[..., 0]
  1364. s = hsv[..., 1]
  1365. v = hsv[..., 2]
  1366. r = np.empty_like(h)
  1367. g = np.empty_like(h)
  1368. b = np.empty_like(h)
  1369. i = (h * 6.0).astype(int)
  1370. f = (h * 6.0) - i
  1371. p = v * (1.0 - s)
  1372. q = v * (1.0 - s * f)
  1373. t = v * (1.0 - s * (1.0 - f))
  1374. idx = i % 6 == 0
  1375. r[idx] = v[idx]
  1376. g[idx] = t[idx]
  1377. b[idx] = p[idx]
  1378. idx = i == 1
  1379. r[idx] = q[idx]
  1380. g[idx] = v[idx]
  1381. b[idx] = p[idx]
  1382. idx = i == 2
  1383. r[idx] = p[idx]
  1384. g[idx] = v[idx]
  1385. b[idx] = t[idx]
  1386. idx = i == 3
  1387. r[idx] = p[idx]
  1388. g[idx] = q[idx]
  1389. b[idx] = v[idx]
  1390. idx = i == 4
  1391. r[idx] = t[idx]
  1392. g[idx] = p[idx]
  1393. b[idx] = v[idx]
  1394. idx = i == 5
  1395. r[idx] = v[idx]
  1396. g[idx] = p[idx]
  1397. b[idx] = q[idx]
  1398. idx = s == 0
  1399. r[idx] = v[idx]
  1400. g[idx] = v[idx]
  1401. b[idx] = v[idx]
  1402. rgb = np.stack([r, g, b], axis=-1)
  1403. return rgb.reshape(in_shape)
  1404. def _vector_magnitude(arr):
  1405. # things that don't work here:
  1406. # * np.linalg.norm: drops mask from ma.array
  1407. # * np.sum: drops mask from ma.array unless entire vector is masked
  1408. sum_sq = 0
  1409. for i in range(arr.shape[-1]):
  1410. sum_sq += arr[..., i, np.newaxis] ** 2
  1411. return np.sqrt(sum_sq)
  1412. class LightSource:
  1413. """
  1414. Create a light source coming from the specified azimuth and elevation.
  1415. Angles are in degrees, with the azimuth measured
  1416. clockwise from north and elevation up from the zero plane of the surface.
  1417. `shade` is used to produce "shaded" rgb values for a data array.
  1418. `shade_rgb` can be used to combine an rgb image with an elevation map.
  1419. `hillshade` produces an illumination map of a surface.
  1420. """
  1421. def __init__(self, azdeg=315, altdeg=45, hsv_min_val=0, hsv_max_val=1,
  1422. hsv_min_sat=1, hsv_max_sat=0):
  1423. """
  1424. Specify the azimuth (measured clockwise from south) and altitude
  1425. (measured up from the plane of the surface) of the light source
  1426. in degrees.
  1427. Parameters
  1428. ----------
  1429. azdeg : float, default: 315 degrees (from the northwest)
  1430. The azimuth (0-360, degrees clockwise from North) of the light
  1431. source.
  1432. altdeg : float, default: 45 degrees
  1433. The altitude (0-90, degrees up from horizontal) of the light
  1434. source.
  1435. Notes
  1436. -----
  1437. For backwards compatibility, the parameters *hsv_min_val*,
  1438. *hsv_max_val*, *hsv_min_sat*, and *hsv_max_sat* may be supplied at
  1439. initialization as well. However, these parameters will only be used if
  1440. "blend_mode='hsv'" is passed into `shade` or `shade_rgb`.
  1441. See the documentation for `blend_hsv` for more details.
  1442. """
  1443. self.azdeg = azdeg
  1444. self.altdeg = altdeg
  1445. self.hsv_min_val = hsv_min_val
  1446. self.hsv_max_val = hsv_max_val
  1447. self.hsv_min_sat = hsv_min_sat
  1448. self.hsv_max_sat = hsv_max_sat
  1449. @property
  1450. def direction(self):
  1451. """The unit vector direction towards the light source."""
  1452. # Azimuth is in degrees clockwise from North. Convert to radians
  1453. # counterclockwise from East (mathematical notation).
  1454. az = np.radians(90 - self.azdeg)
  1455. alt = np.radians(self.altdeg)
  1456. return np.array([
  1457. np.cos(az) * np.cos(alt),
  1458. np.sin(az) * np.cos(alt),
  1459. np.sin(alt)
  1460. ])
  1461. def hillshade(self, elevation, vert_exag=1, dx=1, dy=1, fraction=1.):
  1462. """
  1463. Calculate the illumination intensity for a surface using the defined
  1464. azimuth and elevation for the light source.
  1465. This computes the normal vectors for the surface, and then passes them
  1466. on to `shade_normals`
  1467. Parameters
  1468. ----------
  1469. elevation : array-like
  1470. A 2d array (or equivalent) of the height values used to generate an
  1471. illumination map
  1472. vert_exag : number, optional
  1473. The amount to exaggerate the elevation values by when calculating
  1474. illumination. This can be used either to correct for differences in
  1475. units between the x-y coordinate system and the elevation
  1476. coordinate system (e.g. decimal degrees vs. meters) or to
  1477. exaggerate or de-emphasize topographic effects.
  1478. dx : number, optional
  1479. The x-spacing (columns) of the input *elevation* grid.
  1480. dy : number, optional
  1481. The y-spacing (rows) of the input *elevation* grid.
  1482. fraction : number, optional
  1483. Increases or decreases the contrast of the hillshade. Values
  1484. greater than one will cause intermediate values to move closer to
  1485. full illumination or shadow (and clipping any values that move
  1486. beyond 0 or 1). Note that this is not visually or mathematically
  1487. the same as vertical exaggeration.
  1488. Returns
  1489. -------
  1490. ndarray
  1491. A 2d array of illumination values between 0-1, where 0 is
  1492. completely in shadow and 1 is completely illuminated.
  1493. """
  1494. # Because most image and raster GIS data has the first row in the array
  1495. # as the "top" of the image, dy is implicitly negative. This is
  1496. # consistent to what `imshow` assumes, as well.
  1497. dy = -dy
  1498. # compute the normal vectors from the partial derivatives
  1499. e_dy, e_dx = np.gradient(vert_exag * elevation, dy, dx)
  1500. # .view is to keep subclasses
  1501. normal = np.empty(elevation.shape + (3,)).view(type(elevation))
  1502. normal[..., 0] = -e_dx
  1503. normal[..., 1] = -e_dy
  1504. normal[..., 2] = 1
  1505. normal /= _vector_magnitude(normal)
  1506. return self.shade_normals(normal, fraction)
  1507. def shade_normals(self, normals, fraction=1.):
  1508. """
  1509. Calculate the illumination intensity for the normal vectors of a
  1510. surface using the defined azimuth and elevation for the light source.
  1511. Imagine an artificial sun placed at infinity in some azimuth and
  1512. elevation position illuminating our surface. The parts of the surface
  1513. that slope toward the sun should brighten while those sides facing away
  1514. should become darker.
  1515. Parameters
  1516. ----------
  1517. fraction : number, optional
  1518. Increases or decreases the contrast of the hillshade. Values
  1519. greater than one will cause intermediate values to move closer to
  1520. full illumination or shadow (and clipping any values that move
  1521. beyond 0 or 1). Note that this is not visually or mathematically
  1522. the same as vertical exaggeration.
  1523. Returns
  1524. -------
  1525. ndarray
  1526. A 2d array of illumination values between 0-1, where 0 is
  1527. completely in shadow and 1 is completely illuminated.
  1528. """
  1529. intensity = normals.dot(self.direction)
  1530. # Apply contrast stretch
  1531. imin, imax = intensity.min(), intensity.max()
  1532. intensity *= fraction
  1533. # Rescale to 0-1, keeping range before contrast stretch
  1534. # If constant slope, keep relative scaling (i.e. flat should be 0.5,
  1535. # fully occluded 0, etc.)
  1536. if (imax - imin) > 1e-6:
  1537. # Strictly speaking, this is incorrect. Negative values should be
  1538. # clipped to 0 because they're fully occluded. However, rescaling
  1539. # in this manner is consistent with the previous implementation and
  1540. # visually appears better than a "hard" clip.
  1541. intensity -= imin
  1542. intensity /= (imax - imin)
  1543. intensity = np.clip(intensity, 0, 1)
  1544. return intensity
  1545. def shade(self, data, cmap, norm=None, blend_mode='overlay', vmin=None,
  1546. vmax=None, vert_exag=1, dx=1, dy=1, fraction=1, **kwargs):
  1547. """
  1548. Combine colormapped data values with an illumination intensity map
  1549. (a.k.a. "hillshade") of the values.
  1550. Parameters
  1551. ----------
  1552. data : array-like
  1553. A 2d array (or equivalent) of the height values used to generate a
  1554. shaded map.
  1555. cmap : `~matplotlib.colors.Colormap`
  1556. The colormap used to color the *data* array. Note that this must be
  1557. a `~matplotlib.colors.Colormap` instance. For example, rather than
  1558. passing in ``cmap='gist_earth'``, use
  1559. ``cmap=plt.get_cmap('gist_earth')`` instead.
  1560. norm : `~matplotlib.colors.Normalize` instance, optional
  1561. The normalization used to scale values before colormapping. If
  1562. None, the input will be linearly scaled between its min and max.
  1563. blend_mode : {'hsv', 'overlay', 'soft'} or callable, optional
  1564. The type of blending used to combine the colormapped data
  1565. values with the illumination intensity. Default is
  1566. "overlay". Note that for most topographic surfaces,
  1567. "overlay" or "soft" appear more visually realistic. If a
  1568. user-defined function is supplied, it is expected to
  1569. combine an MxNx3 RGB array of floats (ranging 0 to 1) with
  1570. an MxNx1 hillshade array (also 0 to 1). (Call signature
  1571. ``func(rgb, illum, **kwargs)``) Additional kwargs supplied
  1572. to this function will be passed on to the *blend_mode*
  1573. function.
  1574. vmin : float or None, optional
  1575. The minimum value used in colormapping *data*. If *None* the
  1576. minimum value in *data* is used. If *norm* is specified, then this
  1577. argument will be ignored.
  1578. vmax : float or None, optional
  1579. The maximum value used in colormapping *data*. If *None* the
  1580. maximum value in *data* is used. If *norm* is specified, then this
  1581. argument will be ignored.
  1582. vert_exag : number, optional
  1583. The amount to exaggerate the elevation values by when calculating
  1584. illumination. This can be used either to correct for differences in
  1585. units between the x-y coordinate system and the elevation
  1586. coordinate system (e.g. decimal degrees vs. meters) or to
  1587. exaggerate or de-emphasize topography.
  1588. dx : number, optional
  1589. The x-spacing (columns) of the input *elevation* grid.
  1590. dy : number, optional
  1591. The y-spacing (rows) of the input *elevation* grid.
  1592. fraction : number, optional
  1593. Increases or decreases the contrast of the hillshade. Values
  1594. greater than one will cause intermediate values to move closer to
  1595. full illumination or shadow (and clipping any values that move
  1596. beyond 0 or 1). Note that this is not visually or mathematically
  1597. the same as vertical exaggeration.
  1598. Additional kwargs are passed on to the *blend_mode* function.
  1599. Returns
  1600. -------
  1601. ndarray
  1602. An MxNx4 array of floats ranging between 0-1.
  1603. """
  1604. if vmin is None:
  1605. vmin = data.min()
  1606. if vmax is None:
  1607. vmax = data.max()
  1608. if norm is None:
  1609. norm = Normalize(vmin=vmin, vmax=vmax)
  1610. rgb0 = cmap(norm(data))
  1611. rgb1 = self.shade_rgb(rgb0, elevation=data, blend_mode=blend_mode,
  1612. vert_exag=vert_exag, dx=dx, dy=dy,
  1613. fraction=fraction, **kwargs)
  1614. # Don't overwrite the alpha channel, if present.
  1615. rgb0[..., :3] = rgb1[..., :3]
  1616. return rgb0
  1617. def shade_rgb(self, rgb, elevation, fraction=1., blend_mode='hsv',
  1618. vert_exag=1, dx=1, dy=1, **kwargs):
  1619. """
  1620. Use this light source to adjust the colors of the *rgb* input array to
  1621. give the impression of a shaded relief map with the given *elevation*.
  1622. Parameters
  1623. ----------
  1624. rgb : array-like
  1625. An (M, N, 3) RGB array, assumed to be in the range of 0 to 1.
  1626. elevation : array-like
  1627. An (M, N) array of the height values used to generate a shaded map.
  1628. fraction : number
  1629. Increases or decreases the contrast of the hillshade. Values
  1630. greater than one will cause intermediate values to move closer to
  1631. full illumination or shadow (and clipping any values that move
  1632. beyond 0 or 1). Note that this is not visually or mathematically
  1633. the same as vertical exaggeration.
  1634. blend_mode : {'hsv', 'overlay', 'soft'} or callable, optional
  1635. The type of blending used to combine the colormapped data values
  1636. with the illumination intensity. For backwards compatibility, this
  1637. defaults to "hsv". Note that for most topographic surfaces,
  1638. "overlay" or "soft" appear more visually realistic. If a
  1639. user-defined function is supplied, it is expected to combine an
  1640. MxNx3 RGB array of floats (ranging 0 to 1) with an MxNx1 hillshade
  1641. array (also 0 to 1). (Call signature
  1642. ``func(rgb, illum, **kwargs)``)
  1643. Additional kwargs supplied to this function will be passed on to
  1644. the *blend_mode* function.
  1645. vert_exag : number, optional
  1646. The amount to exaggerate the elevation values by when calculating
  1647. illumination. This can be used either to correct for differences in
  1648. units between the x-y coordinate system and the elevation
  1649. coordinate system (e.g. decimal degrees vs. meters) or to
  1650. exaggerate or de-emphasize topography.
  1651. dx : number, optional
  1652. The x-spacing (columns) of the input *elevation* grid.
  1653. dy : number, optional
  1654. The y-spacing (rows) of the input *elevation* grid.
  1655. Additional kwargs are passed on to the *blend_mode* function.
  1656. Returns
  1657. -------
  1658. ndarray
  1659. An (m, n, 3) array of floats ranging between 0-1.
  1660. """
  1661. # Calculate the "hillshade" intensity.
  1662. intensity = self.hillshade(elevation, vert_exag, dx, dy, fraction)
  1663. intensity = intensity[..., np.newaxis]
  1664. # Blend the hillshade and rgb data using the specified mode
  1665. lookup = {
  1666. 'hsv': self.blend_hsv,
  1667. 'soft': self.blend_soft_light,
  1668. 'overlay': self.blend_overlay,
  1669. }
  1670. if blend_mode in lookup:
  1671. blend = lookup[blend_mode](rgb, intensity, **kwargs)
  1672. else:
  1673. try:
  1674. blend = blend_mode(rgb, intensity, **kwargs)
  1675. except TypeError as err:
  1676. raise ValueError('"blend_mode" must be callable or one of {}'
  1677. .format(lookup.keys)) from err
  1678. # Only apply result where hillshade intensity isn't masked
  1679. if np.ma.is_masked(intensity):
  1680. mask = intensity.mask[..., 0]
  1681. for i in range(3):
  1682. blend[..., i][mask] = rgb[..., i][mask]
  1683. return blend
  1684. def blend_hsv(self, rgb, intensity, hsv_max_sat=None, hsv_max_val=None,
  1685. hsv_min_val=None, hsv_min_sat=None):
  1686. """
  1687. Take the input data array, convert to HSV values in the given colormap,
  1688. then adjust those color values to give the impression of a shaded
  1689. relief map with a specified light source. RGBA values are returned,
  1690. which can then be used to plot the shaded image with imshow.
  1691. The color of the resulting image will be darkened by moving the (s, v)
  1692. values (in hsv colorspace) toward (hsv_min_sat, hsv_min_val) in the
  1693. shaded regions, or lightened by sliding (s, v) toward (hsv_max_sat,
  1694. hsv_max_val) in regions that are illuminated. The default extremes are
  1695. chose so that completely shaded points are nearly black (s = 1, v = 0)
  1696. and completely illuminated points are nearly white (s = 0, v = 1).
  1697. Parameters
  1698. ----------
  1699. rgb : ndarray
  1700. An MxNx3 RGB array of floats ranging from 0 to 1 (color image).
  1701. intensity : ndarray
  1702. An MxNx1 array of floats ranging from 0 to 1 (grayscale image).
  1703. hsv_max_sat : number, default: 1
  1704. The maximum saturation value that the *intensity* map can shift the
  1705. output image to.
  1706. hsv_min_sat : number, optional
  1707. The minimum saturation value that the *intensity* map can shift the
  1708. output image to. Defaults to 0.
  1709. hsv_max_val : number, optional
  1710. The maximum value ("v" in "hsv") that the *intensity* map can shift
  1711. the output image to. Defaults to 1.
  1712. hsv_min_val : number, optional
  1713. The minimum value ("v" in "hsv") that the *intensity* map can shift
  1714. the output image to. Defaults to 0.
  1715. Returns
  1716. -------
  1717. ndarray
  1718. An MxNx3 RGB array representing the combined images.
  1719. """
  1720. # Backward compatibility...
  1721. if hsv_max_sat is None:
  1722. hsv_max_sat = self.hsv_max_sat
  1723. if hsv_max_val is None:
  1724. hsv_max_val = self.hsv_max_val
  1725. if hsv_min_sat is None:
  1726. hsv_min_sat = self.hsv_min_sat
  1727. if hsv_min_val is None:
  1728. hsv_min_val = self.hsv_min_val
  1729. # Expects a 2D intensity array scaled between -1 to 1...
  1730. intensity = intensity[..., 0]
  1731. intensity = 2 * intensity - 1
  1732. # Convert to rgb, then rgb to hsv
  1733. hsv = rgb_to_hsv(rgb[:, :, 0:3])
  1734. hue, sat, val = np.moveaxis(hsv, -1, 0)
  1735. # Modify hsv values (in place) to simulate illumination.
  1736. # putmask(A, mask, B) <=> A[mask] = B[mask]
  1737. np.putmask(sat, (np.abs(sat) > 1.e-10) & (intensity > 0),
  1738. (1 - intensity) * sat + intensity * hsv_max_sat)
  1739. np.putmask(sat, (np.abs(sat) > 1.e-10) & (intensity < 0),
  1740. (1 + intensity) * sat - intensity * hsv_min_sat)
  1741. np.putmask(val, intensity > 0,
  1742. (1 - intensity) * val + intensity * hsv_max_val)
  1743. np.putmask(val, intensity < 0,
  1744. (1 + intensity) * val - intensity * hsv_min_val)
  1745. np.clip(hsv[:, :, 1:], 0, 1, out=hsv[:, :, 1:])
  1746. # Convert modified hsv back to rgb.
  1747. return hsv_to_rgb(hsv)
  1748. def blend_soft_light(self, rgb, intensity):
  1749. """
  1750. Combine an rgb image with an intensity map using "soft light" blending,
  1751. using the "pegtop" formula.
  1752. Parameters
  1753. ----------
  1754. rgb : ndarray
  1755. An MxNx3 RGB array of floats ranging from 0 to 1 (color image).
  1756. intensity : ndarray
  1757. An MxNx1 array of floats ranging from 0 to 1 (grayscale image).
  1758. Returns
  1759. -------
  1760. ndarray
  1761. An MxNx3 RGB array representing the combined images.
  1762. """
  1763. return 2 * intensity * rgb + (1 - 2 * intensity) * rgb**2
  1764. def blend_overlay(self, rgb, intensity):
  1765. """
  1766. Combines an rgb image with an intensity map using "overlay" blending.
  1767. Parameters
  1768. ----------
  1769. rgb : ndarray
  1770. An MxNx3 RGB array of floats ranging from 0 to 1 (color image).
  1771. intensity : ndarray
  1772. An MxNx1 array of floats ranging from 0 to 1 (grayscale image).
  1773. Returns
  1774. -------
  1775. ndarray
  1776. An MxNx3 RGB array representing the combined images.
  1777. """
  1778. low = 2 * intensity * rgb
  1779. high = 1 - 2 * (1 - intensity) * (1 - rgb)
  1780. return np.where(rgb <= 0.5, low, high)
  1781. def from_levels_and_colors(levels, colors, extend='neither'):
  1782. """
  1783. A helper routine to generate a cmap and a norm instance which
  1784. behave similar to contourf's levels and colors arguments.
  1785. Parameters
  1786. ----------
  1787. levels : sequence of numbers
  1788. The quantization levels used to construct the `BoundaryNorm`.
  1789. Value ``v`` is quantized to level ``i`` if ``lev[i] <= v < lev[i+1]``.
  1790. colors : sequence of colors
  1791. The fill color to use for each level. If *extend* is "neither" there
  1792. must be ``n_level - 1`` colors. For an *extend* of "min" or "max" add
  1793. one extra color, and for an *extend* of "both" add two colors.
  1794. extend : {'neither', 'min', 'max', 'both'}, optional
  1795. The behaviour when a value falls out of range of the given levels.
  1796. See `~.Axes.contourf` for details.
  1797. Returns
  1798. -------
  1799. cmap : `~matplotlib.colors.Normalize`
  1800. norm : `~matplotlib.colors.Colormap`
  1801. """
  1802. slice_map = {
  1803. 'both': slice(1, -1),
  1804. 'min': slice(1, None),
  1805. 'max': slice(0, -1),
  1806. 'neither': slice(0, None),
  1807. }
  1808. cbook._check_in_list(slice_map, extend=extend)
  1809. color_slice = slice_map[extend]
  1810. n_data_colors = len(levels) - 1
  1811. n_expected = n_data_colors + color_slice.start - (color_slice.stop or 0)
  1812. if len(colors) != n_expected:
  1813. raise ValueError(
  1814. f'With extend == {extend!r} and {len(levels)} levels, '
  1815. f'expected {n_expected} colors, but got {len(colors)}')
  1816. cmap = ListedColormap(colors[color_slice], N=n_data_colors)
  1817. if extend in ['min', 'both']:
  1818. cmap.set_under(colors[0])
  1819. else:
  1820. cmap.set_under('none')
  1821. if extend in ['max', 'both']:
  1822. cmap.set_over(colors[-1])
  1823. else:
  1824. cmap.set_over('none')
  1825. cmap.colorbar_extend = extend
  1826. norm = BoundaryNorm(levels, ncolors=n_data_colors)
  1827. return cmap, norm