Spaces:
No application file
No application file
| # Copyright 2001 by Brad Chapman. All rights reserved. | |
| # | |
| # This file is part of the Biopython distribution and governed by your | |
| # choice of the "Biopython License Agreement" or the "BSD 3-Clause License". | |
| # Please see the LICENSE file that should have been included as part of this | |
| # package. | |
| """Represent information for graphical display. | |
| Classes in this module are designed to hold information in a way that | |
| makes it easy to draw graphical figures. | |
| """ | |
| # reportlab | |
| from reportlab.lib import colors | |
| # local stuff | |
| from Bio.Graphics.BasicChromosome import ChromosomeSegment | |
| from Bio.Graphics.BasicChromosome import TelomereSegment | |
| # --- constants | |
| # This is a default color scheme based on the light spectrum. | |
| # Based on my vague recollections from biology, this is our friend ROY G. BIV | |
| RAINBOW_COLORS = { | |
| (1, 1): colors.violet, | |
| (2, 2): colors.indigo, | |
| (3, 3): colors.blue, | |
| (4, 4): colors.green, | |
| (5, 5): colors.yellow, | |
| (6, 6): colors.orange, | |
| (7, 20): colors.red, | |
| } | |
| class ChromosomeCounts: | |
| """Represent a chromosome with count information. | |
| This is used to display information about counts along a chromosome. | |
| The segments are expected to have different count information, which | |
| will be displayed using a color scheme. | |
| I envision using this class when you think that certain regions of | |
| the chromosome will be especially abundant in the counts, and you | |
| want to pick those out. | |
| """ | |
| def __init__(self, segment_names, color_scheme=RAINBOW_COLORS): | |
| """Initialize a representation of chromosome counts. | |
| Arguments: | |
| - segment_names - An ordered list of all segment names along | |
| the chromosome. The count and other information will be added | |
| to these. | |
| - color_scheme - A coloring scheme to use in the counts. This | |
| should be a dictionary mapping count ranges to colors (specified | |
| in reportlab.lib.colors). | |
| """ | |
| self._names = segment_names | |
| self._count_info = {} | |
| self._label_info = {} | |
| self._scale_info = {} | |
| for name in self._names: | |
| self._count_info[name] = 0 | |
| self._label_info[name] = None | |
| self._scale_info[name] = 1 | |
| self._color_scheme = color_scheme | |
| def add_count(self, segment_name, count=1): | |
| """Add counts to the given segment name. | |
| Arguments: | |
| - segment_name - The name of the segment we should add counts to. | |
| If the name is not present, a KeyError will be raised. | |
| - count - The counts to add the current segment. This defaults to | |
| a single count. | |
| """ | |
| try: | |
| self._count_info[segment_name] += count | |
| except KeyError: | |
| raise KeyError(f"Segment name {segment_name} not found.") from None | |
| def scale_segment_value(self, segment_name, scale_value=None): | |
| """Divide the counts for a segment by some kind of scale value. | |
| This is useful if segments aren't represented by raw counts, but | |
| are instead counts divided by some number. | |
| """ | |
| try: | |
| self._count_info[segment_name] /= scale_value | |
| except KeyError: | |
| raise KeyError(f"Segment name {segment_name} not found.") from None | |
| def add_label(self, segment_name, label): | |
| """Add a label to a specific segment. | |
| Raises a KeyError is the specified segment name is not found. | |
| """ | |
| if segment_name in self._label_info: | |
| self._label_info[segment_name] = label | |
| else: | |
| raise KeyError(f"Segment name {segment_name} not found.") | |
| def set_scale(self, segment_name, scale): | |
| """Set the scale for a specific chromosome segment. | |
| By default all segments have the same scale -- this allows scaling | |
| by the size of the segment. | |
| Raises a KeyError is the specified segment name is not found. | |
| """ | |
| if segment_name in self._label_info: | |
| self._scale_info[segment_name] = scale | |
| else: | |
| raise KeyError(f"Segment name {segment_name} not found.") | |
| def get_segment_info(self): | |
| """Retrieve the color and label info about the segments. | |
| Returns a list consisting of two tuples specifying the counts and | |
| label name for each segment. The list is ordered according to the | |
| original listing of names. Labels are set as None if no label | |
| was specified. | |
| """ | |
| order_info = [] | |
| for seg_name in self._names: | |
| order_info.append((self._count_info[seg_name], self._label_info[seg_name])) | |
| return order_info | |
| def fill_chromosome(self, chromosome): | |
| """Add the collected segment information to a chromosome for drawing. | |
| Arguments: | |
| - chromosome - A Chromosome graphics object that we can add | |
| chromosome segments to. | |
| This creates ChromosomeSegment (and TelomereSegment) objects to | |
| fill in the chromosome. The information is derived from the | |
| label and count information, with counts transformed to the | |
| specified color map. | |
| Returns the chromosome with all of the segments added. | |
| """ | |
| for seg_num in range(len(self._names)): | |
| is_end_segment = 0 | |
| # make the top and bottom telomeres | |
| if seg_num == 0: | |
| cur_segment = TelomereSegment() | |
| is_end_segment = 1 | |
| elif seg_num == len(self._names) - 1: | |
| cur_segment = TelomereSegment(1) | |
| is_end_segment = 1 | |
| # otherwise, they are just regular segments | |
| else: | |
| cur_segment = ChromosomeSegment() | |
| seg_name = self._names[seg_num] | |
| if self._count_info[seg_name] > 0: | |
| color = self._color_from_count(self._count_info[seg_name]) | |
| cur_segment.fill_color = color | |
| if self._label_info[seg_name] is not None: | |
| cur_segment.label = self._label_info[seg_name] | |
| # give end segments extra size so they look right | |
| if is_end_segment: | |
| cur_segment.scale = 3 | |
| else: | |
| cur_segment.scale = self._scale_info[seg_name] | |
| chromosome.add(cur_segment) | |
| return chromosome | |
| def _color_from_count(self, count): | |
| """Translate the given count into a color using the color scheme (PRIVATE).""" | |
| for count_start, count_end in self._color_scheme: | |
| if count >= count_start and count <= count_end: | |
| return self._color_scheme[(count_start, count_end)] | |
| # if we got here we didn't find a color for the count | |
| raise ValueError(f"Count value {count} was not found in the color scheme.") | |