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