Source code for mavis.illustrate.util

from colour import Color
import svgwrite

from ..error import DrawingFitError
from ..interval import Interval


MIN_PIXEL_ACCURACY = 1


[docs]def dynamic_label_color(color): """ calculates the luminance of a color and determines if a black or white label will be more contrasting """ color = Color(color) if color.get_luminance() < 0.5: return '#FFFFFF' return '#000000'
[docs]class LabelMapping: def __init__(self, **kwargs): self._mapping = dict() self._reverse_mapping = dict() for attr, val in kwargs.items(): self[attr] = val def __setitem__(self, key, value): if key in self._mapping: raise KeyError('duplicate key: keys must be unique', key) if value in self._reverse_mapping: raise KeyError('duplicate value: values must be unique', value) self._mapping[key] = value self._reverse_mapping[value] = key
[docs] def items(self): return self._mapping.items()
def __getitem__(self, key): return self._mapping[key]
[docs] def __len__(self): return len(self._mapping.keys())
[docs] def get_key(self, value): return self._reverse_mapping[value]
[docs] def set_key(self, key, value): if key in self._mapping: current_value = self._mapping[key] if value == current_value: return elif value in self._reverse_mapping: raise KeyError('duplicate value: values must be unique', value) del self._mapping[key] del self._reverse_mapping[current_value] elif value in self._reverse_mapping: raise KeyError('duplicate value: values must be unique', value) self[key] = value
[docs] def add(self, value, prefix=''): if value in self._reverse_mapping: return self._reverse_mapping[value] i = 1 while True: key = '{}{}'.format(prefix, i) if key not in self._mapping: self[key] = value break i += 1 return self._reverse_mapping[value]
[docs]def split_intervals_into_tracks(intervals): tracks = [[]] for itvl in sorted(intervals, key=lambda x: x[0]): added = False for track in tracks: overlaps = False for track_itvl in track: if Interval.overlaps(itvl, track_itvl): overlaps = True break if not overlaps: added = True track.append(itvl) break if not added: tracks.append([itvl]) return tracks
[docs]def generate_interval_mapping( input_intervals, target_width, ratio, min_width, buffer_length=None, start=None, end=None, min_inter_width=None, min_pixel_accuracy=MIN_PIXEL_ACCURACY): min_inter_width = min_width if min_inter_width is None else min_inter_width if all([x is not None for x in [start, end, buffer_length]]): raise AttributeError('buffer_length is a mutually exclusive argument with start/end') intervals = [] for i in Interval.min_nonoverlapping(*input_intervals): if not intervals or abs(Interval.dist(intervals[-1], i)) > 1: intervals.append(i) else: intervals[-1] = intervals[-1] | i # break up the intervals by any intervals of length 1 for itvl_in in input_intervals: if len(itvl_in) > 1: continue # try splitting all current interval temp = [] for itvl in intervals: split = itvl - itvl_in if split is not None: temp.extend(split) intervals = temp for itvl_in in input_intervals: if len(itvl_in) == 1: intervals.append(itvl_in) # now split any intervals by start/end breaks = {} for i in intervals: # split by input intervals breaks[i] = set([i.start, i.end]) for ii in input_intervals: if ii.start >= i.start and ii.start <= i.end: breaks[i].add(ii.start) if ii.end >= i.start and ii.end <= i.end: breaks[i].add(ii.end) temp = [] for itvl, breakpoints in breaks.items(): breakpoints.add(itvl.start) breakpoints.add(itvl.end) pos = sorted(breakpoints) if len(pos) == 1: temp.append(Interval(pos[0])) else: # remove all the single intervals to start? pos[0] -= 1 for i in range(1, len(pos)): temp.append(Interval(pos[i - 1] + 1, pos[i])) intervals = sorted(temp, key=lambda x: x.start) if buffer_length is None: buffer_length = 0 if start is None: start = max(intervals[0].start - buffer_length, 1) elif start <= 0: raise AttributeError('start must be a natural number', start) if end is None: end = intervals[-1].end + buffer_length elif end <= 0: raise AttributeError('end must be a natural number', end) total_length = end - start + 1 genic_length = sum([len(i) for i in intervals]) intergenic_length = total_length - genic_length intermediate_intervals = 0 if start < intervals[0].start: intermediate_intervals += 1 if end > intervals[-1].end: intermediate_intervals += 1 for i in range(1, len(intervals)): if intervals[i].start > intervals[i - 1].end + 1: intermediate_intervals += 1 width = target_width - intermediate_intervals * min_inter_width - len(intervals) * min_width # reserved width if width < 0: raise DrawingFitError('width cannot accommodate the number of expected objects') intergenic_width = width // (ratio + 1) if intergenic_length > 0 else 0 genic_width = width - intergenic_width def intergenic_unit(x): return x * intergenic_width / intergenic_length def genic_unit(x): return x * genic_width / genic_length mapping = [] pos = 0 # do the intergenic region prior to the first genic region if start < intervals[0].start: ifrom = Interval(start, intervals[0].start - 1) s = max(intergenic_unit(len(ifrom)), 0) ito = Interval(pos, pos + min_inter_width + s) mapping.append((ifrom, ito)) pos = ito.end for i, curr in enumerate(intervals): if i > 0 and intervals[i - 1].end + 1 < curr.start: # add between the intervals prev = intervals[i - 1] ifrom = Interval(prev.end + 1, curr.start - 1) s = max(intergenic_unit(len(ifrom)), 0) ito = Interval(pos, pos + min_inter_width + s) mapping.append((ifrom, ito)) pos = ito.end s = max(genic_unit(len(curr)), 0) assert s >= 0 ito = Interval(pos, pos + min_width + s) mapping.append((curr, ito)) pos = ito.end # now the last intergenic region will make up for the rounding error if end > intervals[-1].end: ifrom = Interval(intervals[-1].end + 1, end) s = max(intergenic_unit(len(ifrom)), 0) ito = Interval(pos, pos + min_inter_width + s) mapping.append((ifrom, ito)) pos = ito.end # mapping[-1][1].end = target_width # min(int(target_width), mapping[-1][1].end) if abs(mapping[-1][1].end - target_width) > min_pixel_accuracy: raise AssertionError( 'end is off by more than the expected pixel allowable error', mapping[-1][1].end, target_width, min_pixel_accuracy) temp = mapping mapping = dict() for ifrom, ito in temp: mapping[ifrom] = ito # assert that that mapping is correct for ifrom in input_intervals: ifrom = Interval(ifrom.start, ifrom.end) p1 = Interval.convert_ratioed_pos(mapping, ifrom.start) p2 = Interval.convert_ratioed_pos(mapping, ifrom.end) if ifrom in mapping and ito.end == target_width: continue n = p1 | p2 if n.length() < min_width and abs(n.length() - min_width) > min_pixel_accuracy: # precision error allowable raise AssertionError( 'interval mapping should not map any intervals to less than the minimum required width. Interval {}' ' was mapped to a pixel interval of length {} but the minimum width is {}'.format( ifrom, n.length(), min_width), p1, p2, mapping, input_intervals, target_width, ratio, min_width, buffer_length, start, end, min_inter_width) return mapping
[docs]class Tag(svgwrite.base.BaseElement): def __init__(self, elementname, content='', **kwargs): self.elementname = elementname super(Tag, self).__init__(**kwargs) self.content = content
[docs] def get_xml(self): xml = super(Tag, self).get_xml() xml.text = self.content return xml