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

Source Code for Module Bio.Graphics.BasicChromosome

  1  """Draw representations of organism chromosomes with added information. 
  2   
  3  These classes are meant to model the drawing of pictures of chromosomes. 
  4  This can be useful for lots of things, including displaying markers on 
  5  a chromosome (ie. for genetic mapping) and showing syteny between two 
  6  chromosomes. 
  7   
  8  The structure of these classes is intended to be a Composite, so that 
  9  it will be easy to plug in and switch different parts without 
 10  breaking the general drawing capabilities of the system. The 
 11  relationship between classes is that everything derives from 
 12  _ChromosomeComponent, which specifies the overall interface. The parts 
 13  then are related so that an Organism contains Chromosomes, and these 
 14  Chromosomes contain ChromosomeSegments. This representation differents 
 15  from the canonical composite structure in that we don't really have 
 16  'leaf' nodes here -- all components can potentially hold sub-components. 
 17   
 18  Most of the time the ChromosomeSegment class is what you'll want to 
 19  customize for specific drawing tasks. 
 20   
 21  For providing drawing capabilities, these classes use reportlab: 
 22   
 23  http://www.reportlab.com 
 24   
 25  This provides nice output in pdf, which it should be possible to convert 
 26  to a wide variety of other formats. 
 27   
 28  There is also the start of support for other formats (such as eps), but it 
 29  doesn't seem to be quite working right for me yet... 
 30  """ 
 31  # standard library 
 32  import os 
 33   
 34  # reportlab 
 35  from reportlab.pdfgen import canvas 
 36  from reportlab.lib.pagesizes import letter 
 37  from reportlab.lib.units import inch 
 38  from reportlab.lib import colors 
 39   
 40  from reportlab.graphics.shapes import Drawing, String, Line, Rect, Wedge 
 41  from reportlab.graphics import renderPDF, renderPS 
 42  from reportlab.graphics.widgetbase import Widget 
 43   
44 -class _ChromosomeComponent(Widget):
45 """Base class specifying the interface for a component of the system. 46 47 This class should not be instantiated directly, but should be used 48 from derived classes. 49 """
50 - def __init__(self):
51 """Initialize a chromosome component. 52 53 Attributes: 54 55 o _sub_components -- Any components which are contained under 56 this parent component. This attribute should be accessed through 57 the add() and remove() functions. 58 """ 59 self._sub_components = []
60
61 - def add(self, component):
62 """Add a sub_component to the list of components under this item. 63 """ 64 assert isinstance(component, _ChromosomeComponent), \ 65 "Expected a _ChromosomeComponent object, got %s" % component 66 67 self._sub_components.append(component)
68
69 - def remove(self, component):
70 """Remove the specified component from the subcomponents. 71 72 Raises a ValueError if the component is not registered as a 73 sub_component. 74 """ 75 try: 76 self._sub_components.remove(component) 77 except ValueError: 78 raise ValueError("Component %s not found in sub_components." % 79 component)
80
81 - def draw(self):
82 """Draw the specified component. 83 """ 84 raise AssertionError("Subclasses must implement.")
85
86 -class Organism(_ChromosomeComponent):
87 """Top level class for drawing chromosomes. 88 89 This class holds information about an organism and all of it's 90 chromosomes, and provides the top level object which could be used 91 for drawing a chromosome representation of an organism. 92 93 Chromosomes should be added and removed from the Organism via the 94 add and remove functions. 95 """ 96 VALID_FORMATS = ['pdf', 'eps'] 97
98 - def __init__(self, output_format = 'pdf'):
99 _ChromosomeComponent.__init__(self) 100 101 # customizable attributes 102 self.page_size = letter 103 self.title_size = 20 104 105 assert output_format in self.VALID_FORMATS, \ 106 "Unsupported format: %s, choices are: %s" % (output_format, 107 self.VALID_FORMATS) 108 self.output_format = output_format
109
110 - def draw(self, output_file, title):
111 """Draw out the information for the Organism. 112 113 Arguments: 114 115 o output_file -- The name of a file specifying where the pdf 116 document drawn should be saved. 117 118 o title -- The output title of the produced document. 119 """ 120 width, height = self.page_size 121 cur_drawing = Drawing(width, height) 122 123 self._draw_title(cur_drawing, title, width, height) 124 125 cur_x_pos = inch * .5 126 if len(self._sub_components) > 0: 127 x_pos_change = (width - inch) / len(self._sub_components) 128 # no sub_components 129 else: 130 pass 131 132 for sub_component in self._sub_components: 133 # set the drawing location of the chromosome 134 sub_component.start_x_position = cur_x_pos 135 sub_component.end_x_position = cur_x_pos + .9 * x_pos_change 136 sub_component.start_y_position = height - 1.5 * inch 137 sub_component.end_y_position = 3 * inch 138 139 # do the drawing 140 sub_component.draw(cur_drawing) 141 142 # update the locations for the next chromosome 143 cur_x_pos += x_pos_change 144 145 self._draw_legend(cur_drawing, 2.5 * inch, width) 146 147 if self.output_format == 'pdf': 148 out_canvas = canvas.Canvas(output_file, pagesize = self.page_size) 149 renderPDF.draw(cur_drawing, out_canvas, 0, 0) 150 out_canvas.showPage() 151 out_canvas.save() 152 elif self.output_format == 'eps': 153 renderPS.drawToFile(cur_drawing, output_file) 154 else: 155 raise ValueError("Invalid output format %s" % self.output_format)
156
157 - def _draw_title(self, cur_drawing, title, width, height):
158 """Write out the title of the organism figure. 159 """ 160 title_string = String(width / 2, height - inch, title) 161 title_string.fontName = 'Helvetica-Bold' 162 title_string.fontSize = self.title_size 163 title_string.textAnchor = "middle" 164 165 cur_drawing.add(title_string)
166
167 - def _draw_legend(self, cur_drawing, start_y, width):
168 """Draw a legend for the figure. 169 170 Subclasses should implement this to provide specialized legends. 171 """ 172 pass
173
174 -class Chromosome(_ChromosomeComponent):
175 """Class for drawing a chromosome of an organism. 176 177 This organizes the drawing of a single organisms chromosome. This 178 class can be instantiated directly, but the draw method makes the 179 most sense to be called in the context of an organism. 180 """
181 - def __init__(self, chromosome_name):
182 """Initialize a Chromosome for drawing. 183 184 Arguments: 185 186 o chromosome_name - The label for the chromosome. 187 188 Attributes: 189 190 o start_x_position, end_x_position - The x positions on the page 191 where the chromosome should be drawn. This allows multiple 192 chromosomes to be drawn on a single page. 193 194 o start_y_position, end_y_position - The y positions on the page 195 where the chromosome should be contained. 196 197 Configuration Attributes: 198 199 o title_size - The size of the chromosome title. 200 201 o scale_num - A number of scale the drawing by. This is useful if 202 you want to draw multiple chromosomes of different sizes at the 203 same scale. If this is not set, then the chromosome drawing will 204 be scaled by the number of segements in the chromosome (so each 205 chromosome will be the exact same final size). 206 """ 207 _ChromosomeComponent.__init__(self) 208 209 self._name = chromosome_name 210 211 self.start_x_position = -1 212 self.end_x_position = -1 213 self.start_y_position = -1 214 self.end_y_position = -1 215 216 self.title_size = 20 217 self.scale_num = None
218
219 - def subcomponent_size(self):
220 """Return the scaled size of all subcomponents of this component. 221 """ 222 total_sub = 0 223 for sub_component in self._sub_components: 224 total_sub += sub_component.scale 225 226 return total_sub
227
228 - def draw(self, cur_drawing):
229 """Draw a chromosome on the specified template. 230 231 Ideally, the x_position and y_*_position attributes should be 232 set prior to drawing -- otherwise we're going to have some problems. 233 """ 234 for position in (self.start_x_position, self.end_x_position, 235 self.start_y_position, self.end_y_position): 236 assert position != -1, "Need to set drawing coordinates." 237 238 # first draw all of the sub-sections of the chromosome -- this 239 # will actually be the picture of the chromosome 240 cur_y_pos = self.start_y_position 241 if self.scale_num: 242 y_pos_change = ((self.start_y_position * .95 - self.end_y_position) 243 / self.scale_num) 244 elif len(self._sub_components) > 0: 245 y_pos_change = ((self.start_y_position * .95 - self.end_y_position) 246 / self.subcomponent_size()) 247 # no sub_components to draw 248 else: 249 pass 250 251 for sub_component in self._sub_components: 252 this_y_pos_change = sub_component.scale * y_pos_change 253 254 # set the location of the component to draw 255 sub_component.start_x_position = self.start_x_position 256 sub_component.end_x_position = self.end_x_position 257 sub_component.start_y_position = cur_y_pos 258 sub_component.end_y_position = cur_y_pos - this_y_pos_change 259 260 # draw the sub component 261 sub_component.draw(cur_drawing) 262 263 # update the position for the next component 264 cur_y_pos -= this_y_pos_change 265 266 self._draw_label(cur_drawing, self._name)
267
268 - def _draw_label(self, cur_drawing, label_name):
269 """Draw a label for the chromosome. 270 """ 271 x_position = self.start_x_position 272 y_position = self.end_y_position 273 274 label_string = String(x_position, y_position, label_name) 275 label_string.fontName = 'Times-BoldItalic' 276 label_string.fontSize = self.title_size 277 label_string.textAnchor = 'start' 278 279 cur_drawing.add(label_string)
280
281 -class ChromosomeSegment(_ChromosomeComponent):
282 """Draw a segment of a chromosome. 283 284 This class provides the important configurable functionality of drawing 285 a Chromosome. Each segment has some customization available here, or can 286 be subclassed to define additional functionality. Most of the interesting 287 drawing stuff is likely to happen at the ChromosomeSegment level. 288 """
289 - def __init__(self):
290 """Initialize a ChromosomeSegment. 291 292 Attributes: 293 o start_x_position, end_x_position - Defines the x range we have 294 to draw things in. 295 296 o start_y_position, end_y_position - Defines the y range we have 297 to draw things in. 298 299 Configuration Attributes: 300 301 o scale - A scaling value for the component. By default this is 302 set at 1 (ie -- has the same scale as everything else). Higher 303 values give more size to the component, smaller values give less. 304 305 o fill_color - A color to fill in the segment with. Colors are 306 available in reportlab.lib.colors 307 308 o label - A label to place on the chromosome segment. This should 309 be a text string specifying what is to be included in the label. 310 311 o label_size - The size of the label. 312 313 o chr_percent - The percentage of area that the chromosome 314 segment takes up. 315 """ 316 _ChromosomeComponent.__init__(self) 317 318 self.start_x_position = -1 319 self.end_x_position = -1 320 self.start_y_position = -1 321 self.end_y_position = -1 322 323 # --- attributes for configuration 324 self.scale = 1 325 self.fill_color = None 326 self.label = None 327 self.label_size = 6 328 self.chr_percent = .25
329
330 - def draw(self, cur_drawing):
331 """Draw a chromosome segment. 332 333 Before drawing, the range we are drawing in needs to be set. 334 """ 335 for position in (self.start_x_position, self.end_x_position, 336 self.start_y_position, self.end_y_position): 337 assert position != -1, "Need to set drawing coordinates." 338 339 self._draw_subcomponents(cur_drawing) 340 self._draw_segment(cur_drawing) 341 self._draw_label(cur_drawing)
342
343 - def _draw_subcomponents(self, cur_drawing):
344 """Draw any subcomponents of the chromosome segment. 345 346 This should be overridden in derived classes if there are 347 subcomponents to be drawn. 348 """ 349 pass
350
351 - def _draw_segment(self, cur_drawing):
352 """Draw the current chromosome segment. 353 """ 354 # set the coordinates of the segment -- it'll take up the left part 355 # of the space we have. 356 segment_x = self.start_x_position 357 segment_y = self.end_y_position 358 segment_width = (self.end_x_position - self.start_x_position) \ 359 * self.chr_percent 360 segment_height = self.start_y_position - self.end_y_position 361 362 # first draw the sides of the segment 363 right_line = Line(segment_x, segment_y, 364 segment_x, segment_y + segment_height) 365 left_line = Line(segment_x + segment_width, segment_y, 366 segment_x + segment_width, segment_y + segment_height) 367 368 cur_drawing.add(right_line) 369 cur_drawing.add(left_line) 370 371 # now draw the box, if it is filled in 372 if self.fill_color is not None: 373 fill_rectangle = Rect(segment_x, segment_y, 374 segment_width, segment_height) 375 fill_rectangle.fillColor = self.fill_color 376 fill_rectangle.strokeColor = None 377 378 cur_drawing.add(fill_rectangle)
379
380 - def _draw_label(self, cur_drawing):
381 """Add a label to the chromosome segment. 382 """ 383 # the label will be applied to the right of the segment 384 if self.label is not None: 385 386 label_x = self.start_x_position + \ 387 (self.chr_percent + 0.05) * (self.end_x_position - 388 self.start_x_position) 389 label_y = ((self.start_y_position - self.end_y_position) / 2 + 390 self.end_y_position) 391 392 label_string = String(label_x, label_y, self.label) 393 label_string.fontName = 'Helvetica' 394 label_string.fontSize = self.label_size 395 396 cur_drawing.add(label_string)
397
398 -class TelomereSegment(ChromosomeSegment):
399 """A segment that is located at the end of a linear chromosome. 400 401 This is just like a regular segment, but it draws the end of a chromosome 402 which is represented by a half circle. This just overrides the 403 _draw_segment class of ChromosomeSegment to provide that specialized 404 drawing. 405 """
406 - def __init__(self, inverted = 0):
407 """Initialize a segment at the end of a chromosome. 408 409 See ChromosomeSegment for all of the attributes that can be 410 customized in a TelomereSegments. 411 412 Arguments: 413 414 o inverted -- Whether or not the telomere should be inverted 415 (ie. drawn on the bottom of a chromosome) 416 """ 417 ChromosomeSegment.__init__(self) 418 419 self._inverted = inverted
420
421 - def _draw_segment(self, cur_drawing):
422 """Draw a half circle representing the end of a linear chromosome. 423 """ 424 # set the coordinates of the segment -- it'll take up the left part 425 # of the space we have. 426 width = (self.end_x_position - self.start_x_position) \ 427 * self.chr_percent 428 height = self.start_y_position - self.end_y_position 429 430 center_x = self.start_x_position + width / 2 431 if self._inverted: 432 center_y = self.start_y_position 433 start_angle = 180 434 end_angle = 360 435 else: 436 center_y = self.end_y_position 437 start_angle = 0 438 end_angle = 180 439 440 cap_wedge = Wedge(center_x, center_y, width / 2, 441 start_angle, end_angle, height / 2) 442 443 cap_wedge.fillColor = self.fill_color 444 cur_drawing.add(cap_wedge) 445 446 # draw a line to cover up the the bottom part of the wedge 447 if self._inverted: 448 cover_line = Line(self.start_x_position, self.start_y_position, 449 self.start_x_position + width, 450 self.start_y_position) 451 else: 452 cover_line = Line(self.start_x_position, self.end_y_position, 453 self.start_x_position + width, 454 self.end_y_position) 455 456 if self.fill_color is not None: 457 cover_color = self.fill_color 458 else: 459 cover_color = colors.white 460 461 cover_line.strokeColor = cover_color 462 cur_drawing.add(cover_line)
463