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