Source code for mmfutils.plot.colors

"""Various tools for displaying information with color."""
import numpy as np

import matplotlib.cm
from matplotlib import pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, Normalize

from .cmaps import cmaps

__all__ = ["MidpointNormalize", "cm", "color_angle", "color_complex"]


[docs] class MidpointNormalize(Normalize): """Colormap normalization that ensures a balanced distribution about the specified midpoint. Use this with a diverging colormap to ensure that the midpoint lies in the middle of the colormap. Examples -------- >>> norm = MidpointNormalize(midpoint=1.0) >>> norm(np.arange(4)) masked_array(data=[0.25, 0.5 , 0.75, 1. ], mask=False, fill_value=1e+20) >>> norm = MidpointNormalize(midpoint=1.0, vmin=-3) >>> norm(np.arange(4)) masked_array(data=[0.375, 0.5 , 0.625, 0.75 ], mask=False, fill_value=1e+20) """ def __init__(self, vmin=None, vmax=None, clip=False, midpoint=0): self.midpoint = midpoint Normalize.__init__(self, vmin=vmin, vmax=vmax, clip=clip)
[docs] def autoscale_None(self, A): """Sets vmin and vmax if they are None.""" if np.size(A) > 0: # Work with midpoint removed vmax = np.ma.max(A) - self.midpoint vmin = np.ma.min(A) - self.midpoint if self.vmin is None: if self.vmax is not None: vmin = -(self.vmax - self.midpoint) else: vmin = min(vmin, -vmax) self.vmin = vmin + self.midpoint if self.vmax is None: if self.vmin is not None: vmax = -(self.vmin - self.midpoint) else: vmax = max(vmax, -vmin) self.vmax = vmax + self.midpoint # These assertions are written this way to allow them to work with # fully masked arrays. See issue 16. assert not self.vmin > self.vmax assert np.ma.allclose(self.midpoint - self.vmin, self.vmax - self.midpoint)
[docs] def color_angle(theta, map="huslp", gamma=1, saturation=100.0, lightness=75.6): """Return an RGB tuple of colors for each angle theta. The colors cycle smoothly through all hues in the order of the rainbow. The default map is luminosity corrected: http://www.husl-colors.org Arguments --------- theta : array Array of angles as returned, for example, by `np.angle`. map : 'husl' or 'hue' Colour map to use. 'huslp' : a luminosity corrected coloring. All angles here have the same perceptual brightness, however only pastel colors are used. 'husl' : a luminosity corrected coloring similar to huslp but allowing for full saturation. Highly saturated colors do not appear to change uniformly though. other : a custom but poor cycling through hue. """ # Convert to same form used by color map which linear maps the # range -pi/2, pi/2 to 0, 360 theta = theta + np.pi if map in ("husl", "huslp"): import husl @np.vectorize def to_rgb(*v, **kw): """Turn output into a tuple so vectorize works.""" return tuple(getattr(husl, map + "_to_rgb")(*v, **kw)) rgb = np.asarray(to_rgb(theta / np.pi * 180 % 360, saturation, lightness)) # Put the rgb axis last so we can pass this to imshow rgb = np.rollaxis(rgb, 0, rgb.ndim) else: r = ((1 + np.cos(theta)) / 2.0) ** gamma g = ((1 + np.cos(theta - 2 * np.pi / 3.0)) / 2.0) ** gamma b = ((1 + np.cos(theta - 4 * np.pi / 3.0)) / 2.0) ** gamma rgb = np.rollaxis(np.array([r, g, b]), 0, 3) # Scale to range 0-1 (in case of round-off errors) rgb = np.minimum(np.maximum(0.0, rgb), 1.0) return rgb # Need to fix broadcasting here. Should make cmap. r = np.array([1.0, 0, 0]) g = np.array([0, 1.0, 0]) b = np.array([0, 0, 1.0]) rgb = ( r * ((1 + np.cos(theta)) / 2.0) ** gamma + g * ((1 + np.cos(theta - 2 * np.pi / 3.0)) / 2.0) ** gamma + b * ((1 + np.cos(theta - 4 * np.pi / 3.0)) / 2.0) ** gamma )
[docs] def color_complex(psi, vmin=None, vmax=None, reversed=False, **kw): """Return RGB tuple of colors for each complex value. Uses `color_angle` but varies the lightness to match the magnitude of `psi`. Arguments --------- vmin, vmax : float Minimum and maximum of magnitude range. Uses min and max of abs(phi) if not provided. reversed : bool If True, then the minimum magnitude is white. """ theta = np.angle(psi) mag = abs(psi) if vmin is None: vmin = mag.min() if vmax is None: vmax = mag.max() lightness = 100.0 * np.ma.divide(mag - vmin, vmax - vmin).filled(0.75) if reversed: lightness = 100.0 - lightness return color_angle(theta, lightness=lightness, **kw)
def make_angle_colormap(map="huslp", gamma=1, saturation=100.0, lightness=75.6): import husl from matplotlib.colors import LinearSegmentedColormap N = 100 rs = [] gs = [] bs = [] for theta in np.linspace(0, 360, N): r, g, b = getattr(husl, map + "_to_rgb")(theta, saturation, lightness) rs.append((theta / 360.0, r, r)) gs.append((theta / 360.0, g, g)) bs.append((theta / 360.0, b, b)) cdict = dict(red=tuple(rs), green=tuple(gs), blue=tuple(bs)) return LinearSegmentedColormap("huslp", cdict) class Colormaps(object): """New colormaps objects""" husl = make_angle_colormap(map="husl") huslp = make_angle_colormap(map="huslp") # Constructed with seaborn # import seaborn as sns # sns.diverging_palette(0, 255, n=4, s=63, l=73, sep=1, center='dark') diverging = LinearSegmentedColormap.from_list( "diverging", np.array( [ [0.62950738, 0.70001025, 0.89382127, 1.0], # [0.13300000, 0.13300000, 0.13300000, 1.], [0.00000000, 0.00000000, 0.00000000, 1.0], [0.90488582, 0.62784940, 0.68104318, 1.0], ] ), ) def __init__(self, **kw): self.__dict__.update(kw) self._add_to_matplotlib() def __iter__(self): for _cm in dir(self): if not _cm.startswith("_"): yield _cm def _add_to_matplotlib(self): # Monkeypatch matplotlib to add the new color maps for cm in self: if not hasattr(matplotlib.cm, cm): cmap = getattr(self, cm) setattr(matplotlib.cm, cm, cmap) # matplotlib.cm.register_cmap(name=cm, cmap=cmap) matplotlib.colormaps.register(cmap=cmap, name=cm) cm = Colormaps(**cmaps)