Package Bio :: Package Graphics :: Module ColorSpiral
[hide private]
[frames] | no frames]

Source Code for Module Bio.Graphics.ColorSpiral

  1  # This code is part of the Biopython distribution and governed by its 
  2  # license.  Please see the LICENSE file that should have been included 
  3  # as part of this package. 
  4  # 
  5   
  6  """Generate RGB colours suitable for distinguishing categorical data. 
  7   
  8  This module provides a class that implements a spiral 'path' through HSV 
  9  colour space, permitting the selection of a number of points along that path, 
 10  and returning the output in RGB colour space, suitable for use with ReportLab 
 11  and other graphics packages. 
 12   
 13  This approach to colour choice was inspired by Bang Wong's Points of View 
 14  article: Color Coding, in Nature Methods _7_ 573 (doi:10.1038/nmeth0810-573). 
 15   
 16  The module also provides helper functions that return a list for colours, or 
 17  a dictionary of colours (if passed an iterable containing the names of 
 18  categories to be coloured). 
 19  """ 
 20   
 21  # standard library 
 22  import colorsys    # colour format conversions 
 23  from math import log, exp, floor, pi 
 24  import random      # for jitter values 
 25   
 26  __docformat__ = "restructuredtext en" 
 27   
 28   
29 -class ColorSpiral(object):
30 """Implement a spiral path through HSV colour space. 31 32 This class provides functions for sampling points along a logarithmic 33 spiral path through HSV colour space. 34 35 The spiral is described by r = a * exp(b * t) where r is the distance 36 from the axis of the HSV cylinder to the current point in the spiral, 37 and t is the angle through which the spiral has turned to reach the 38 current point. a and b are (positive, real) parameters that control the 39 shape of the spiral. 40 41 - a: the starting direction of the spiral 42 - b: the number of revolutions about the axis made by the spiral 43 44 We permit the spiral to move along the cylinder ('in V-space') between 45 v_init and v_final, to give a gradation in V (essentially, brightness), 46 along the path, where v_init, v_final are in [0,1]. 47 48 A brightness 'jitter' may also be provided as an absolute value in 49 V-space, to aid in distinguishing consecutive colour points on the 50 path. 51 """
52 - def __init__(self, a=1, b=0.33, v_init=0.85, v_final=0.5, 53 jitter=0.05):
54 """Initialise a logarithmic spiral path through HSV colour space 55 56 Arguments: 57 58 - a - Parameter a for the spiral, controls the initial spiral 59 direction. a > 0 60 - b - parameter b for the spiral, controls the rate at which the 61 spiral revolves around the axis. b > 0 62 - v_init - initial value of V (brightness) for the spiral. 63 v_init in [0,1] 64 - v_final - final value of V (brightness) for the spiral 65 v_final in [0,1] 66 - jitter - the degree of V (brightness) jitter to add to each 67 selected colour. The amount of jitter will be selected 68 from a uniform random distribution [-jitter, jitter], 69 and V will be maintained in [0,1]. 70 """ 71 # Initialise attributes 72 self.a = a 73 self.b = b 74 self.v_init = v_init 75 self.v_final = v_final 76 self.jitter = jitter
77
78 - def get_colors(self, k, offset=0.1):
79 """Generate k different RBG colours evenly-space on the spiral. 80 81 A generator returning the RGB colour space values for k 82 evenly-spaced points along the defined spiral in HSV space. 83 84 Arguments: 85 86 - k - the number of points to return 87 - offset - how far along the spiral path to start. 88 """ 89 # We use the offset to skip a number of similar colours near to HSV axis 90 assert offset > 0 and offset < 1, "offset must be in (0,1)" 91 v_rate = (self._v_final - self._v_init) / float(k) 92 # Generator for colours: we have divided the arc length into sections 93 # of equal length, and step along them 94 for n in range(1, k + 1): 95 # For each value of n, t indicates the angle through which the 96 # spiral has turned, to this point 97 t = (1. / self._b) * (log(n + (k * offset)) - 98 log((1 + offset) * k * self._a)) 99 # Put 0 <= h <= 2*pi, where h is the angular part of the polar 100 # co-ordinates for this point on the spiral 101 h = t 102 while h < 0: 103 h += 2 * pi 104 h = (h - (floor(h / (2 * pi)) * pi)) 105 # Now put h in [0, 1] for colorsys conversion 106 h = h / (2 * pi) 107 # r is the radial distance of this point from the centre 108 r = self._a * exp(self._b * t) 109 # v is the brightness of this point, linearly interpolated 110 # from self._v_init to self._v_final. Jitter size is sampled from 111 # a uniform distribution 112 if self._jitter: 113 jitter = random.random() * 2 * self._jitter - self._jitter 114 else: 115 jitter = 0 116 v = self._v_init + (n * v_rate + jitter) 117 # We have arranged the arithmetic such that 0 <= r <= 1, so 118 # we can use this value directly as s in HSV 119 yield colorsys.hsv_to_rgb(h, r, max(0, min(v, 1)))
120
121 - def _get_a(self):
122 return self._a
123
124 - def _set_a(self, value):
125 self._a = max(0, value)
126
127 - def _get_b(self):
128 return self._b
129
130 - def _set_b(self, value):
131 self._b = max(0, value)
132
133 - def _get_v_init(self):
134 return self._v_init
135
136 - def _set_v_init(self, value):
137 self._v_init = max(0, min(1, value))
138
139 - def _get_v_final(self):
140 return self._v_final
141
142 - def _set_v_final(self, value):
143 self._v_final = max(0, min(1, value))
144
145 - def _get_jitter(self):
146 return self._jitter
147
148 - def _set_jitter(self, value):
149 self._jitter = max(0, min(1, value))
150 151 a = property(_get_a, _set_a, 152 doc="Parameter controlling initial spiral direction (a > 0)") 153 b = property(_get_b, _set_b, 154 doc="Parameter controlling rate spiral revolves around axis (b > 0)") 155 v_init = property(_get_v_init, _set_v_init, 156 doc="Initial value of V (brightness) for the spiral (range 0 to 1)") 157 v_final = property(_get_v_final, _set_v_final, 158 doc="Final value of V (brightness) for the spiral (range 0 to 1)") 159 jitter = property(_get_jitter, _set_jitter, 160 doc="Degree of V (brightness) jitter to add to each color (range 0 to 1)")
161 162 163 # Convenience functions for those who don't want to bother with a 164 # ColorSpiral object
165 -def get_colors(k, **kwargs):
166 """Returns k colours selected by the ColorSpiral object, as a generator. 167 168 Arguments: 169 170 - k - the number of colours to return 171 - kwargs - pass-through arguments to the ColorSpiral object 172 """ 173 cs = ColorSpiral(**kwargs) 174 return cs.get_colors(k)
175 176
177 -def get_color_dict(l, **kwargs):
178 """Returns a dictionary of colours using the provided values as keys. 179 180 Returns a dictionary, keyed by the members of iterable l, with a 181 colour assigned to each member. 182 183 Arguments: 184 185 - l - an iterable representing classes to be coloured 186 - kwargs - pass-through arguments to the ColorSpiral object 187 """ 188 cs = ColorSpiral(**kwargs) 189 colors = cs.get_colors(len(l)) 190 dict = {} 191 for item in l: 192 dict[item] = next(colors) 193 return dict
194