Source code for mavis.illustrate.util

from ..interval import Interval
from ..error import DrawingFitError
from colour import Color
import svgwrite


[docs]def dynamic_label_color(color): """ calculates the luminance of a color and determines if a black or white label will be more contrasting """ f = Color(color) if f.get_luminance() < 0.5: return '#FFFFFF' else: return '#000000'
[docs]class LabelMapping: def __init__(self, **kwargs): self._mapping = dict() self._reverse_mapping = dict() for k, v in kwargs.items(): self[k] = v 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 i in sorted(intervals, key=lambda x: x[0]): added = False for t in tracks: overlaps = False for og in t: if Interval.overlaps(i, og): overlaps = True break if not overlaps: added = True t.append(i) break if not added: tracks.append([i]) 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_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 len(intervals) == 0 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 intergenic_unit = lambda x: x * intergenic_width / intergenic_length genic_unit = lambda x: x * genic_width / genic_length assert( genic_width + intergenic_width + len(intervals) * min_width + intermediate_intervals * min_inter_width == target_width) mapping = [] pos = 1 # 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.length() 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.length() s = max(genic_unit(len(curr)), 0) ito = Interval(pos, pos + min_width + s) mapping.append((curr, ito)) pos += ito.length() # 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.length() mapping[-1][1].end = target_width # min(int(target_width), mapping[-1][1].end) 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) > 0.01: # 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_inter_width) return mapping
[docs]class Tag(svgwrite.base.BaseElement): def __init__(DS, elementname, content='', **kwargs): DS.elementname = elementname super(Tag, DS).__init__(**kwargs) DS.content = content
[docs] def get_xml(DS): xml = super(Tag, DS).get_xml() xml.text = DS.content return xml