Source code for sleap.rangelist

"""
Module with RangeList class for manipulating a list of range intervals.

This is used to cache the track occupancy so we can keep cache updating
when user manipulates tracks for a range of instances.
"""

from typing import List, Tuple


[docs]class RangeList: """ Class for manipulating a list of range intervals. Each range interval in the list is a [start, end)-tuple. """ def __init__(self, range_list: List[Tuple[int]] = None): self.list = range_list if range_list is not None else [] def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self._list) @property def list(self): """Returns the list of ranges.""" return self._list @list.setter def list(self, val): """Sets the list of ranges.""" self._list = val @property def is_empty(self): """Returns True if the list is empty.""" return len(self.list) == 0 @property def start(self): """Return the start value of range (or None if empty).""" if self.is_empty: return None return self.list[0][0] @property def end(self): """Returns the end value of range (or None if empty).""" if self.is_empty: return None return self.list[-1][1]
[docs] def add(self, val, tolerance=0): """Add a single value, merges to last range if contiguous.""" if self.list and self.list[-1][1] + tolerance >= val: self.list[-1] = (self.list[-1][0], val + 1) else: self.list.append((val, val + 1))
[docs] def insert(self, new_range: tuple): """Add a new range, merging to adjacent/overlapping ranges as appropriate.""" new_range = self._as_tuple(new_range) pre, _, post = self.cut_range(new_range) self.list = self.join_([pre, [new_range], post]) return self.list
[docs] def insert_list(self, range_list: List[Tuple[int]]): """Add each range from a list of ranges.""" for range_ in range_list: self.insert(range_) return self.list
[docs] def remove(self, remove: tuple): """Remove everything that overlaps with given range.""" pre, _, post = self.cut_range(remove) self.list = pre + post
[docs] def cut(self, cut: int): """Return a pair of lists with everything before/after cut.""" return self.cut_(self.list, cut)
[docs] def cut_range(self, cut: tuple): """Return three lists, everthing before/within/after cut range.""" if not self.list: return [], [], [] cut = self._as_tuple(cut) a, r = self.cut_(self.list, cut[0]) b, c = self.cut_(r, cut[1]) return a, b, c
@staticmethod def _as_tuple(x): """Return tuple (converting from range if necessary).""" if isinstance(x, range): return x.start, x.stop return x
[docs] @staticmethod def cut_(range_list: List[Tuple[int]], cut: int): """Return a pair of lists with everything before/after cut. Args: range_list: the list to cut cut: the value at which to cut list Returns: (pre-cut list, post-cut list)-tuple """ pre = [] post = [] for range_ in range_list: if range_[1] <= cut: pre.append(range_) elif range_[0] >= cut: post.append(range_) elif range_[0] < cut < range_[1]: # two new ranges, split at cut a = (range_[0], cut) b = (cut, range_[1]) pre.append(a) post.append(b) return pre, post
[docs] @classmethod def join_(cls, list_list: List[List[Tuple[int]]]): """Return a single list that includes all lists in input list. Args: list_list: a list of range lists Returns: range list that joins all of the lists in list_list """ if len(list_list) == 1: return list_list[0] if len(list_list) == 2: return cls.join_pair_(list_list[0], list_list[1]) return cls.join_pair_(list_list[0], cls.join_(list_list[1:]))
[docs] @staticmethod def join_pair_(list_a: List[Tuple[int]], list_b: List[Tuple[int]]): """Return a single pair of lists that joins two input lists.""" if not list_a or not list_b: return list_a + list_b last_a = list_a[-1] first_b = list_b[0] if last_a[1] >= first_b[0]: return list_a[:-1] + [(last_a[0], first_b[1])] + list_b[1:] return list_a + list_b