class FeatureBase(object): nodes: 'LCMSFeatureTreeList' def __init__(self, nodes): self.nodes = LCMSFeatureTreeList(nodes) def find_time(self, time_point): return self.nodes.find_time(time_point)
class LCMSFeature(FeatureBase): created_at = "new" def __init__(self, nodes=None, adducts=None, used_as_adduct=None, feature_id=None): if nodes is None: nodes = [] if adducts is None: adducts = [] if used_as_adduct is None: used_as_adduct = [] if feature_id is None: feature_id = uid() FeatureBase.__init__(self, nodes) self._total_intensity = None self._mz = None self._last_mz = 0.0 self._times = None self._peaks = None self._start_time = None self._end_time = None self.adducts = adducts self.used_as_adduct = used_as_adduct self.feature_id = feature_id self._peak_averager = RunningWeightedAverage() if len(self) > 0: self._feed_peak_averager() def _feed_peak_averager(self): for node in self: self._peak_averager.update(node.members) def invalidate(self, reaverage=False): self._invalidate(reaverage) def _invalidate(self, reaverage=False): self._total_intensity = None self._last_mz = self._mz if self._mz is not None else 0. self._mz = None self._times = None self._peaks = None self._start_time = None self._end_time = None if reaverage: self._peak_averager = RunningWeightedAverage() if len(self) > 0: self._feed_peak_averager() @property def total_signal(self): if self._total_intensity is None: total = 0. for node in self.nodes: total += node.total_intensity() self._total_intensity = total return self._total_intensity @property def intensity(self): return self.total_signal @property def neutral_mass(self): return self.mz @property def mz(self): if self._mz is None: best_mz = self._peak_averager.current_mean self._last_mz = self._mz = best_mz return self._mz @property def times(self): if self._times is None: self._times = np.array( [node.time for node in self.nodes]) return self._times @property def peaks(self): if self._peaks is None: self._peaks = tuple(node.peaks for node in self.nodes) return self._peaks @property def start_time(self): if self._start_time is None: self._start_time = self.nodes[0].time return self._start_time @property def end_time(self): if self._end_time is None: self._end_time = self.nodes[-1].time return self._end_time def overlaps_in_time(self, interval): cond = ((self.start_time <= interval.start_time and self.end_time >= interval.end_time) or ( self.start_time >= interval.start_time and self.end_time <= interval.end_time) or ( self.start_time >= interval.start_time and self.end_time >= interval.end_time and self.start_time <= interval.end_time) or ( self.start_time <= interval.start_time and self.end_time >= interval.start_time) or ( self.start_time <= interval.end_time and self.end_time >= interval.end_time)) return cond def spans_in_time(self, time): return self.start_time <= time <= self.end_time def as_arrays(self): rts = np.array( [node.time for node in self.nodes], dtype=np.float64) signal = np.array([node.total_intensity() for node in self.nodes], dtype=np.float64) return rts, signal def __len__(self): return len(self.nodes) def __repr__(self): return "%s(%0.4f, %0.2f, %0.2f)" % ( self.__class__.__name__, self.mz, self.start_time, self.end_time) def _copy_chunk(self, nodes, *args, **kwargs): x = self.__class__(LCMSFeatureTreeList(nodes)) x.used_as_adduct = list(self.used_as_adduct) return x def split_at(self, time): _, i = self.nodes.find_time(time) if self.nodes[i].time < time: i += 1 return LCMSFeature(self.nodes[:i]), LCMSFeature(self.nodes[i:]) def split_sparse(self, delta_rt=1.): chunks = [] current_chunk = [] last_rt = self.nodes[0].time for node in self.nodes: if (node.time - last_rt) > delta_rt: x = self._copy_chunk(current_chunk) chunks.append(x) current_chunk = [] last_rt = node.time current_chunk.append(node) x = self._copy_chunk(current_chunk) chunks.append(x) for chunk in chunks: chunk.created_at = self.created_at for member in chunks: for other in chunks: if member == other: continue assert not member.overlaps_in_time(other) return chunks def truncate_before(self, time): _, i = self.nodes.find_time(time) if self.nodes[i].time < time: i += 1 self.nodes = LCMSFeatureTreeList(self.nodes[i:]) self._invalidate() def truncate_after(self, time): _, i = self.nodes.find_time(time) if self.nodes[i].time < time: i += 1 self.nodes = LCMSFeatureTreeList(self.nodes[:i]) self._invalidate() def clone(self, deep=False, cls=None): if cls is None: cls = self.__class__ c = cls(self.nodes.clone(deep=deep), list( self.adducts), list(self.used_as_adduct)) c.feature_id = self.feature_id c.created_at = self.created_at return c def insert_node(self, node): self._peak_averager.update(node.members) self.nodes.insert_node(node) self._invalidate() def insert(self, peak, time): self._peak_averager.add(peak) self.nodes.insert(time, [peak]) self._invalidate() def merge(self, other): new = self.clone() for node in other.nodes: node = node.clone() new.insert_node(node) new.created_at = "merge" return new @property def apex_time(self): rt, intensity = self.as_arrays() return rt[np.argmax(intensity)] def __iter__(self): return iter(self.nodes) def __eq__(self, other): if other is None: return False if len(self) != len(other): return False else: if abs(self.start_time - other.start_time) > 1e-4: return False elif abs(self.end_time - other.end_time) > 1e-4: return False else: for a, b in zip(self, other): if a != b: return False return True def __ne__(self, other): return not (self == other) def __hash__(self): return hash((self.mz, self.start_time, self.end_time)) def __getitem__(self, i): return self.nodes[i]
class FeatureBase(object): def __init__(self, nodes): self.nodes = LCMSFeatureTreeList(nodes) def find_time(self, time_point): return self.nodes.find_time(time_point)
class LCMSFeature(FeatureBase): created_at = "new" def __init__(self, nodes=None, adducts=None, used_as_adduct=None, feature_id=None): if nodes is None: nodes = [] if adducts is None: adducts = [] if used_as_adduct is None: used_as_adduct = [] if feature_id is None: feature_id = uid() FeatureBase.__init__(self, nodes) self._total_intensity = None self._mz = None self._last_mz = 0.0 self._times = None self._peaks = None self._start_time = None self._end_time = None self.adducts = adducts self.used_as_adduct = used_as_adduct self.feature_id = feature_id self._peak_averager = RunningWeightedAverage() if len(self) > 0: self._feed_peak_averager() def _feed_peak_averager(self): for node in self: self._peak_averager.update(node.members) def invalidate(self, reaverage=False): self._invalidate(reaverage) def _invalidate(self, reaverage=False): self._total_intensity = None self._last_mz = self._mz if self._mz is not None else 0. self._mz = None self._times = None self._peaks = None self._start_time = None self._end_time = None if reaverage: self._peak_averager = RunningWeightedAverage() if len(self) > 0: self._feed_peak_averager() @property def total_signal(self): if self._total_intensity is None: total = 0. for node in self.nodes: total += node.total_intensity() self._total_intensity = total return self._total_intensity @property def intensity(self): return self.total_signal @property def neutral_mass(self): return self.mz @property def mz(self): if self._mz is None: best_mz = self._peak_averager.current_mean self._last_mz = self._mz = best_mz return self._mz @property def times(self): if self._times is None: self._times = np.array( [node.time for node in self.nodes]) return self._times @property def peaks(self): if self._peaks is None: self._peaks = tuple(node.peaks for node in self.nodes) return self._peaks @property def start_time(self): if self._start_time is None: self._start_time = self.nodes[0].time return self._start_time @property def end_time(self): if self._end_time is None: self._end_time = self.nodes[-1].time return self._end_time def overlaps_in_time(self, interval): cond = ((self.start_time <= interval.start_time and self.end_time >= interval.end_time) or ( self.start_time >= interval.start_time and self.end_time <= interval.end_time) or ( self.start_time >= interval.start_time and self.end_time >= interval.end_time and self.start_time <= interval.end_time) or ( self.start_time <= interval.start_time and self.end_time >= interval.start_time) or ( self.start_time <= interval.end_time and self.end_time >= interval.end_time)) return cond def as_arrays(self): rts = np.array( [node.time for node in self.nodes], dtype=np.float64) signal = np.array([node.total_intensity() for node in self.nodes], dtype=np.float64) return rts, signal def __len__(self): return len(self.nodes) def __repr__(self): return "%s(%0.4f, %0.2f, %0.2f)" % ( self.__class__.__name__, self.mz, self.start_time, self.end_time) def _copy_chunk(self, nodes, *args, **kwargs): x = self.__class__(LCMSFeatureTreeList(nodes)) x.used_as_adduct = list(self.used_as_adduct) return x def split_at(self, time): _, i = self.nodes.find_time(time) if self.nodes[i].time < time: i += 1 return LCMSFeature(self.nodes[:i]), LCMSFeature(self.nodes[i:]) def split_sparse(self, delta_rt=1.): chunks = [] current_chunk = [] last_rt = self.nodes[0].time for node in self.nodes: if (node.time - last_rt) > delta_rt: x = self._copy_chunk(current_chunk) chunks.append(x) current_chunk = [] last_rt = node.time current_chunk.append(node) x = self._copy_chunk(current_chunk) chunks.append(x) for chunk in chunks: chunk.created_at = self.created_at for member in chunks: for other in chunks: if member == other: continue assert not member.overlaps_in_time(other) return chunks def truncate_before(self, time): _, i = self.nodes.find_time(time) if self.nodes[i].time < time: i += 1 self.nodes = LCMSFeatureTreeList(self.nodes[i:]) self._invalidate() def truncate_after(self, time): _, i = self.nodes.find_time(time) if self.nodes[i].time < time: i += 1 self.nodes = LCMSFeatureTreeList(self.nodes[:i]) self._invalidate() def clone(self, deep=False, cls=None): if cls is None: cls = self.__class__ c = cls(self.nodes.clone(deep=deep), list( self.adducts), list(self.used_as_adduct)) c.feature_id = self.feature_id c.created_at = self.created_at return c def insert_node(self, node): self._peak_averager.update(node.members) self.nodes.insert_node(node) self._invalidate() def insert(self, peak, time): self._peak_averager.add(peak) self.nodes.insert(time, [peak]) self._invalidate() def merge(self, other): new = self.clone() for node in other.nodes: node = node.clone() new.insert_node(node) new.created_at = "merge" return new @property def apex_time(self): rt, intensity = self.as_arrays() return rt[np.argmax(intensity)] def __iter__(self): return iter(self.nodes) def __eq__(self, other): if other is None: return False if len(self) != len(other): return False else: if abs(self.start_time - other.start_time) > 1e-4: return False elif abs(self.end_time - other.end_time) > 1e-4: return False else: for a, b in zip(self, other): if a != b: return False return True def __ne__(self, other): return not (self == other) def __hash__(self): return hash((self.mz, self.start_time, self.end_time)) def __getitem__(self, i): return self.nodes[i]