GitHub PullRequestReviewCommentEvent at 2014-02-28 06:27:00
Repository: vispy
Actor: rougier
Payload: {"comment":{"url":"https://api.github.com/repos/vispy/vispy/pulls/comments/10155885","id":10155885,"diff_hunk":"@@ -0,0 +1,440 @@\n+# -*- coding: utf-8 -*-\n+# Copyright (c) 2014, Vispy Development Team.\n+# Distributed under the (new) BSD License. See LICENSE.txt for more info.\n+\n+import numpy as np\n+\n+from ..util.six import string_types\n+from ..util import logger\n+\n+\n+def get_color_names():\n+    \"\"\"Get the known color names\n+\n+    Returns\n+    -------\n+    names : list\n+        List of color names known by vispy.\n+    \"\"\"\n+    return list(_color_dict.keys())\n+\n+\n+def _string_to_rgb(color):\n+    \"\"\"Convert user string or hex color to color array (length 3 or 4)\"\"\"\n+    if not color.startswith('#'):\n+        if color.lower() not in _color_dict:\n+            raise ValueError('Color \"%s\" unknown' % color)\n+        color = _color_dict[color]\n+        assert color[0] == '#'\n+    # hex color\n+    color = color[1:]\n+    lc = len(color)\n+    if lc not in (6, 8):\n+        raise ValueError('Hex color must have exactly six or eight '\n+                         'elements following the # sign')\n+    color = np.array([int(color[i:i+2], 16) / 255. for i in range(0, lc, 2)])\n+    return color\n+\n+\n+def _user_to_rgba(color, expand=True):\n+    \"\"\"Convert color(s) from any set of fmts (str/hex/arr) to RGB(A) array\"\"\"\n+    if isinstance(color, string_types):\n+        color = _string_to_rgb(color)\n+    elif isinstance(color, ColorArray):\n+        color = color.rgba\n+    # We have to treat this specially\n+    elif isinstance(color, (list, tuple)):\n+        if any(isinstance(c, string_types) for c in color):\n+            color = [_user_to_rgba(c) for c in color]\n+            if any(len(c) > 1 for c in color):\n+                raise RuntimeError('could not parse colors, are they nested?')\n+            color = [c[0] for c in color]\n+    color = np.atleast_2d(color).astype(np.float64)\n+    if color.shape[1] not in (3, 4):\n+        raise ValueError('color must have three or four elements')\n+    if expand and color.shape[1] == 3:  # only expand if requested\n+        color = np.concatenate((color, np.ones((color.shape[0], 1))),\n+                               axis=1)\n+    if color.min() < 0 or color.max() > 1:\n+        logger.warn('Color will be clamped between 0 and 1: %s' % color)\n+        color = np.clip(color, 0, 1)\n+    return color\n+\n+\n+def _check_color_dim(val):\n+    \"\"\"Ensure val is Nx(n_col), usually Nx3\"\"\"\n+    val = np.atleast_2d(val)\n+    if val.shape[1] not in (3, 4):\n+        raise RuntimeError('Value must have second dimension of size 3 or 4')\n+    return val, val.shape[1]\n+\n+\n+def _rgb_to_hsv(rgbs):\n+    \"\"\"Convert Nx3 or Nx4 rgb to hsv\"\"\"\n+    rgbs, n_dim = _check_color_dim(rgbs)\n+    hsvs = list()\n+    for rgb in rgbs:\n+        rgb = rgb[:3]  # don't use alpha here\n+        idx = np.argmax(rgb)\n+        val = rgb[idx]\n+        c = val - np.min(rgb)\n+        if c == 0:\n+            hue = 0\n+            sat = 0\n+        else:\n+            if idx == 0:  # R == max\n+                hue = ((rgb[1] - rgb[2]) / c) % 6\n+            elif idx == 1:  # G == max\n+                hue = (rgb[2] - rgb[0]) / c + 2\n+            else:  # B == max\n+                hue = (rgb[0] - rgb[1]) / c + 4\n+            hue *= 60\n+            sat = c / val\n+        hsv = [hue, sat, val]\n+        hsvs.append(hsv)\n+    hsvs = np.array(hsvs, dtype=np.float64)\n+    if n_dim == 4:\n+        hsvs = np.concatenate((hsvs, rgbs[:, 3]), axis=1)\n+    return hsvs\n+\n+\n+def _hsv_to_rgb(hsvs):\n+    \"\"\"Convert Nx3 or Nx4 hsv to rgb\"\"\"\n+    hsvs, n_dim = _check_color_dim(hsvs)\n+    # In principle, we *might* be able to vectorize this, but might as well\n+    # wait until a compelling use case appears\n+    rgbs = list()\n+    for hsv in hsvs:\n+        c = hsv[1] * hsv[2]\n+        m = hsv[2] - c\n+        hp = hsv[0] / 60\n+        x = c * (1 - abs(hp % 2 - 1))\n+        if 0 <= hp < 1:\n+            r, g, b = c, x, 0\n+        elif 1 <= hp < 2:\n+            r, g, b = x, c, 0\n+        elif 2 <= hp < 3:\n+            r, g, b = 0, c, x\n+        elif 3 <= hp < 4:\n+            r, g, b = 0, x, c\n+        elif 4 <= hp < 5:\n+            r, g, b = x, 0, c\n+        else:\n+            r, g, b = c, 0, x\n+        rgb = [r + m, g + m, b + m]\n+        rgbs.append(rgb)\n+    rgbs = np.array(rgbs, dtype=np.float64)\n+    if n_dim == 4:\n+        rgbs = np.concatenate((rgbs, hsvs[:, 3]), axis=1)\n+    return rgbs\n+\n+\n+# Let's build the white normalization that would happen into the matrix:\n+#_rgb2xyz = np.array([[0.412453, 0.357580, 0.180423],\n+#                     [0.212671, 0.715160, 0.072169],\n+#                     [0.019334, 0.119193, 0.950227]])\n+#_white_norm = np.array([0.950456, 1.0, 1.088754])\n+#_rgb2xyz /= _white_norm[:, np.newaxis]\n+#_rgb2xyz_norm = _rgb2xyz.T\n+_rgb2xyz_norm = np.array([[0.43395276, 0.212671, 0.01775791],\n+                         [0.37621941, 0.71516, 0.10947652],\n+                         [0.18982783, 0.072169, 0.87276557]])\n+\n+\n+def _rgb_to_lab(rgbs):\n+    rgbs, n_dim = _check_color_dim(rgbs)\n+    # convert RGB->XYZ\n+    xyz = rgbs[:, :3].copy()  # a misnomer for now but will end up being XYZ\n+    over = xyz > 0.04045\n+    xyz[over] = ((xyz[over] + 0.055) / 1.055) ** 2.4\n+    xyz[~over] /= 12.92\n+    xyz = np.dot(xyz, _rgb2xyz_norm)\n+    over = xyz > 0.008856\n+    xyz[over] = xyz[over] ** (1. / 3.)\n+    xyz[~over] = 7.787 * xyz[~over] + 0.13793103448275862\n+\n+    # Convert XYZ->LAB\n+    L = (116. * xyz[:, 1]) - 16\n+    a = 500 * (xyz[:, 0] - xyz[:, 1])\n+    b = 200 * (xyz[:, 1] - xyz[:, 2])\n+    labs = [L, a, b]\n+    # Append alpha if necessary\n+    if n_dim == 4:\n+        labs.append(np.atleast1d(rgbs[:, 3]))\n+    labs = np.array(labs, order='F').T  # Becomes 'C' order b/c of .T\n+    return labs\n+\n+\n+# XYZ to RGB conversion matrix, w/D65 white point normalization\n+#_xyz2rgb = np.array([[3.240479, -1.537150, -0.498535],\n+#                     [-0.969256, 1.875992, 0.041556],\n+#                     [0.055648, -0.204043, 1.057311]])\n+#_white_norm = np.array([0.950456, 1., 1.088754])\n+#_xyz2rgb *= _white_norm[np.newaxis, :]\n+_xyz2rgb_norm = np.array([[3.07993271, -1.53715, -0.54278198],\n+                          [-0.92123518, 1.875992, 0.04524426],\n+                          [0.05289098, -0.204043, 1.15115158]])\n+\n+\n+def _lab_to_rgb(labs):\n+    \"\"\"Convert Nx3 or Nx4 lab to rgb\"\"\"\n+    # adapted from BSD-licensed work in MATLAB by Mark Ruzon\n+    # Based on ITU-R Recommendation BT.709 using the D65\n+    labs, n_dim = _check_color_dim(labs)\n+\n+    # Convert Lab->XYZ (silly indexing used to preserve dimensionality)\n+    y = (labs[:, 0] + 16.) / 116.\n+    x = (labs[:, 1] / 500.) + y\n+    z = y - (labs[:, 2] / 200.)\n+    xyz = np.concatenate(([x], [y], [z]))  # 3xN\n+    over = xyz > 0.2068966\n+    xyz[over] = xyz[over] ** 3.\n+    xyz[~over] = (xyz[~over] - 0.13793103448275862) / 7.787\n+\n+    # Convert XYZ->LAB\n+    rgbs = np.dot(_xyz2rgb_norm, xyz).T\n+    over = rgbs > 0.0031308\n+    rgbs[over] = 1.055 * (rgbs[over] ** (1. / 2.4)) - 0.055\n+    rgbs[~over] *= 12.92\n+    if n_dim == 4:\n+        rgbs = np.concatenate((rgbs, labs[:, 3]), axis=1)\n+    rgbs = np.clip(rgbs, 0., 1.)\n+    return rgbs\n+\n+\n+class ColorArray(object):\n+    \"\"\"An array of colors\n+\n+    Parameters\n+    ----------\n+    color : str | tuple | list of colors\n+        If str, can be any of the names in ``vispy.color.get_color_names``.\n+        Can also be a hex value if it starts with ``'#'`` as ``'#ff0000'``.\n+        If array-like, it must be an Nx3 or Nx4 array-like object.\n+        Can also be a list of colors, such as\n+        ``['red', '#00ff00', ColorArray('blue')]``.\n+    alpha : float | None\n+        If no alpha is not supplied in ``color`` entry and ``alpha`` is None,\n+        then this will default to 1.0 (opaque). If float, it will override\n+        any alpha values in ``color``, if provided.\n+\n+    Examples\n+    --------\n+    There are many ways to define colors. Here are some basic cases:\n+\n+        >>> from vispy.color import ColorArray\n+        >>> r = ColorArray('red')  # using string name\n+        >>> r\n+        <ColorArray: 1 color ((1.0, 0.0, 0.0, 1.0))>\n+        >>> g = ColorArray((0, 1, 0, 1))  # RGBA tuple\n+        >>> b = ColorArray('#0000ff')  # hex color\n+        >>> w = ColorArray()  # defaults to black\n+        >>> w.rgb = r.rgb + g.rgb + b.rgb\n+        >>> w == ColorArray('white')\n+        True\n+        >>> w.alpha = 0\n+        >>> w\n+        <ColorArray: 1 color ((1.0, 1.0, 1.0, 0.0))>\n+        >>> rgb = ColorArray(['r', (0, 1, 0), '#0000FFFF'])\n+        >>> rgb\n+        <ColorArray: 3 colors ((1.0, 0.0, 0.0, 1.0) ... (1.0, 0.0, 0.0, 1.0))>\n+        >>> rgb == ColorArray(['red', '#00ff00', ColorArray('blue')])\n+        True\n+\n+    Notes\n+    -----\n+    Under the hood, this class stores data in RGBA format suitable for use\n+    on the GPU.\n+    \"\"\"\n+    def __init__(self, color=(0., 0., 0., 1.), alpha=None):\n+        \"\"\"Parse input type, and set attribute\"\"\"\n+        rgba = _user_to_rgba(color)\n+        if alpha is not None:\n+            rgba[:, 3] = alpha\n+        self._rgba = None\n+        self.rgba = rgba\n+\n+    ###########################################################################\n+    # Builtins and utilities\n+    def copy(self):\n+        \"\"\"Return a copy of the Color instance\"\"\"\n+        return ColorArray(self)\n+\n+    @classmethod\n+    def _name(cls):\n+        return cls.__name__\n+\n+    def __len__(self):\n+        return self._rgba.shape[0]\n+\n+    def __repr__(self):\n+        nice_str = str(tuple(self._rgba[0]))\n+        plural = ''\n+        if len(self) > 1:\n+            plural = 's'\n+            nice_str += ' ... ' + str(tuple(self.rgba[-1]))\n+        # use self._name() here instead of hard-coding name in case\n+        # we eventually subclass this class\n+        return ('<%s: %i color%s (%s)>' % (self._name(), len(self),\n+                                           plural, nice_str))\n+\n+    def __eq__(self, other):\n+        return np.array_equal(self._rgba, other._rgba)\n+\n+    ###########################################################################\n+    # RGB(A)\n+    @property\n+    def rgba(self):\n+        return self._rgba.copy()\n+\n+    @rgba.setter\n+    def rgba(self, val):\n+        \"\"\"Note: all other attribute sets get routed here!\"\"\"\n+        # This method is meant to do the heavy lifting of setting data\n+        rgba = _user_to_rgba(val, expand=False)\n+        if self._rgba is None:\n+            self._rgba = rgba  # only on init\n+        else:\n+            self._rgba[:, :rgba.shape[1]] = rgba\n+\n+    @property\n+    def rgb(self):\n+        return self._rgba[:, :3].copy()\n+\n+    @rgb.setter\n+    def rgb(self, val):\n+        self.rgba = val\n+\n+    @property\n+    def RGBA(self):\n+        return (self._rgba * 255).astype(int)\n+\n+    @RGBA.setter\n+    def RGBA(self, val):\n+        # need to convert to normalized float\n+        val = np.atleast_1d(val).astype(np.float64) / 255\n+        self.rgba = val\n+\n+    @property\n+    def RGB(self):\n+        return np.round(self._rgba[:, :3] * 255).astype(int)\n+\n+    @RGB.setter\n+    def RGB(self, val):\n+        # need to convert to normalized float\n+        val = np.atleast_1d(val).astype(np.float64) / 255.\n+        self.rgba = val\n+\n+    @property\n+    def alpha(self):\n+        return self._rgba[:, 3]\n+\n+    @alpha.setter\n+    def alpha(self, val):\n+        self._rgba[:, 3] = val\n+\n+    ###########################################################################\n+    # HSV\n+    @property\n+    def hsv(self):\n+        return _rgb_to_hsv(self._rgba[:, :3])\n+\n+    @hsv.setter\n+    def hsv(self, val):\n+        self.rgba = _hsv_to_rgb(val)\n+\n+    @property\n+    def value(self):\n+        return self.hsv[:, 2]\n+\n+    @value.setter\n+    def value(self, val):\n+        hsv = self.hsv\n+        val = np.array(val)\n+        if val.max() > 1 or val.min() < 0:\n+            logger.warn('value will be clipped between 0 and 1')\n+        val = np.clip(val, 0, 1)\n+        hsv[:, 2] = val\n+        self.rgba = _hsv_to_rgb(hsv)\n+\n+    def lighten(self):\n+        \"\"\"Lighten the color\"\"\"\n+        # we might be able to do something perceptually nicer here...\n+        self.value = (self.value + 1) / 2\n+\n+    def darken(self):\n+        \"\"\"Darken the color\"\"\"\n+        # we might be able to do something perceptually nicer here...\n+        self.value /= 2\n+\n+    ###########################################################################\n+    # Lab\n+    @property\n+    def lab(self):\n+        return _rgb_to_lab(self._rgba[:, :3])\n+\n+    @lab.setter\n+    def lab(self, val):\n+        self.rgba = _lab_to_rgb(val)\n+\n+\n+class LinearGradient(ColorArray):\n+    \"\"\"Class to represent linear gradients\n+\n+    Parameters\n+    ----------\n+    colors : ColorArray\n+        The control points to use as colors.\n+    x : array\n+        Array of the same length as ``colors`` that give the x-values\n+        to use along the axis of the array.\n+    \"\"\"\n+    def __init__(self, colors, x):\n+        ColorArray.__init__(self, colors)\n+        self.gradient_x = x\n+\n+    @property\n+    def gradient_x(self):\n+        return self._grad_x.copy()\n+\n+    @gradient_x.setter\n+    def gradient_x(self, val):\n+        x = np.array(val, dtype=np.float64)\n+        if x.ndim != 1 or x.size != len(self):\n+            raise ValueError('x must 1D with the same size as colors (%s), '\n+                             'not %s' % (len(self), x.shape))\n+        self._grad_x = x\n+\n+    def __getitem__(self, loc):\n+        try:\n+            loc = float(loc)\n+        except:\n+            raise RuntimeError('location could not be converted to float: %s'\n+                               % str(loc))\n+        rgba = [np.interp(loc, self._grad_x, rr) for rr in self._rgba.T]\n+        return np.array(rgba)\n+\n+\n+# This is used by color functions to translate user strings to colors\n+# For now, this is web colors, and all in hex. It will take some simple\n+# but annoying refactoring to deal with non-hex entries if we want them.\n+_color_dict = dict(r='#FF0000',","path":"vispy/color/_color.py","position":421,"original_position":421,"commit_id":"1eaa212fb012f449f738f9a86ae8c9d80447e556","original_commit_id":"1eaa212fb012f449f738f9a86ae8c9d80447e556","user":{"login":"rougier","id":327203,"avatar_url":"https://avatars.githubusercontent.com/u/327203","gravatar_id":"05fc6835e821894b1ff75c46391eed7b","url":"https://api.github.com/users/rougier","html_url":"https://github.com/rougier","followers_url":"https://api.github.com/users/rougier/followers","following_url":"https://api.github.com/users/rougier/following{/other_user}","gists_url":"https://api.github.com/users/rougier/gists{/gist_id}","starred_url":"https://api.github.com/users/rougier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rougier/subscriptions","organizations_url":"https://api.github.com/users/rougier/orgs","repos_url":"https://api.github.com/users/rougier/repos","events_url":"https://api.github.com/users/rougier/events{/privacy}","received_events_url":"https://api.github.com/users/rougier/received_events","type":"User","site_admin":false},"body":"there are a lot more web colors, no ?","created_at":"2014-02-28T06:27:00Z","updated_at":"2014-02-28T06:27:00Z","html_url":"https://github.com/vispy/vispy/pull/152#discussion_r10155885","pull_request_url":"https://api.github.com/repos/vispy/vispy/pulls/152","_links":{"self":{"href":"https://api.github.com/repos/vispy/vispy/pulls/comments/10155885"},"html":{"href":"https://github.com/vispy/vispy/pull/152#discussion_r10155885"},"pull_request":{"href":"https://api.github.com/repos/vispy/vispy/pulls/152"}}}}