import collections
import heapq
from functools import total_ordering
[docs]@total_ordering
class Label(object):
"""
Represents a label that describes some part of an utterance.
Args:
value (str): The text of the label.
start (float): Start of the label within the utterance in seconds. (default: 0)
end (float): End of the label within the utterance in seconds. (default: -1) (-1 defines
the end of the utterance)
meta (dict): A dictionary containing additional information for the label.
Attributes:
label_list (LabelList): The label-list this label is belonging to.
"""
__slots__ = ['value', 'start', 'end', 'label_list', 'meta']
def __init__(self, value, start=0, end=-1, meta=None):
self.value = value
self.start = start
self.end = end
self.meta = meta or {}
self.label_list = None
def __eq__(self, other):
return (self.start, self.end, self.value.lower()) == (other.start, other.end, other.value.lower())
def __lt__(self, other):
self_end = float('inf') if self.end == -1 else self.end
other_end = float('inf') if other.end == -1 else other.end
return (self.start, self_end, self.value.lower()) < (other.start, other_end, other.value.lower())
def __repr__(self) -> str:
return 'Label({}, {}, {})'.format(self.value, self.start, self.end)
@property
def start_abs(self):
"""
Return the absolute start of the label in seconds relative to the signal.
If the label isn't linked to any utterance via label-list,
it is assumed ``self.start`` is relative to the start of the signal, hence ``self.start`` == ``self.start_abs``.
"""
if self.label_list is None or self.label_list.utterance is None:
return self.start
return self.label_list.utterance.start + self.start
@property
def end_abs(self):
"""
Return the absolute end of the label in seconds relative to the signal.
If the label isn't linked to any utterance via label-list,
it is assumed ``self.end`` is relative to the start of the signal, hence ``self.end`` == ``self.end_abs``.
"""
if self.label_list is None or self.label_list.utterance is None:
return self.end
elif self.end == -1:
return self.label_list.utterance.end_abs
else:
return self.end + self.label_list.utterance.start
@property
def duration(self):
"""
Return the duration of the label in seconds.
"""
return self.end_abs - self.start_abs
[docs] def read_samples(self, sr=None):
"""
Read the samples of the utterance.
Args:
sr (int): If None uses the sampling rate given by the file, otherwise resamples to the given sampling rate.
Returns:
np.ndarray: A numpy array containing the samples as a floating point (numpy.float32) time series.
"""
duration = None
if self.end >= 0 or self.label_list.utterance.end >= 0:
duration = self.duration
return self.label_list.utterance.file.read_samples(sr=sr, offset=self.start_abs, duration=duration)
[docs]class LabelList(object):
"""
Represents a list of labels which describe an utterance.
An utterance can have multiple label-lists.
Args:
idx (str): An unique identifier for the label-list within a corpus for one utterance.
labels (list): The list containing the :py:class:`audiomate.corpus.assets.Label`.
Attributes:
utterance (Utterance): The utterance this label-list is belonging to.
Example:
>>> label_list = LabelList(idx='transcription', labels=[
>>> Label('this', 0, 2),
>>> Label('is', 2, 4),
>>> Label('timmy', 4, 8)
>>> ])
"""
__slots__ = ['idx', 'labels', 'utterance']
def __init__(self, idx='default', labels=[]):
self.idx = idx
self.utterance = None
self.labels = []
self.extend(labels)
[docs] def append(self, label):
"""
Add a label to the end of the list.
Args:
label (Label): The label to add.
"""
label.label_list = self
self.labels.append(label)
[docs] def extend(self, labels):
"""
Add a list of labels to the end of the list.
Args:
labels (list): Labels to add.
"""
for label in labels:
self.append(label)
[docs] def ranges(self, yield_ranges_without_labels=False, include_labels=None):
"""
Generate all ranges of the label-list. A range is defined as a part of the label-list for
which the same labels are defined.
Args:
yield_ranges_without_labels (bool): If True also yields ranges for which no labels are
defined.
include_labels (list): If not empty, only the label values in the list will be
considered.
Returns:
generator: A generator which yields one range (tuple start/end/list-of-labels) at a
time.
Example:
>>> ll = LabelList(labels=[
>>> Label('a', 3.2, 4.5),
>>> Label('b', 5.1, 8.9),
>>> Label('c', 7.2, 10.5),
>>> Label('d', 10.5, 14)
>>> ])
>>> ranges = ll.ranges()
>>> next(ranges)
(3.2, 4.5, [<audiomate.corpus.assets.label.Label at 0x1090527c8>])
>>> next(ranges)
(4.5, 5.1, [])
>>> next(ranges)
(5.1, 7.2, [<audiomate.corpus.assets.label.Label at 0x1090484c8>])
"""
# all label start events
events = [(l.start, 1, l) for l in self.labels]
labels_to_end = False
heapq.heapify(events)
current_range_labels = []
current_range_start = -123
while len(events) > 0:
next_event = heapq.heappop(events)
label = next_event[2]
# Return current range if its not the first event and not the same time as the previous
# event
if -1 < current_range_start < next_event[0]:
if len(current_range_labels) > 0 or yield_ranges_without_labels:
yield (current_range_start, next_event[0], list(current_range_labels))
# Update labels and add the "end" event
if next_event[1] == 1:
if include_labels is None or label.value in include_labels:
current_range_labels.append(label)
if label.end == -1:
labels_to_end = True
else:
heapq.heappush(events, (label.end, -1, label))
else:
current_range_labels.remove(label)
current_range_start = next_event[0]
if labels_to_end and len(current_range_labels) > 0:
yield (current_range_start, -1, list(current_range_labels))
[docs] def label_values(self):
"""
Return a list of all occuring label values.
Returns:
list: Lexicographically sorted list (str) of label values.
Example:
>>> ll = LabelList(labels=[
>>> Label('a', 3.2, 4.5),
>>> Label('b', 5.1, 8.9),
>>> Label('c', 7.2, 10.5),
>>> Label('d', 10.5, 14),
>>> Label('d', 15, 18)
>>> ])
>>> ll.label_values()
['a', 'b', 'c', 'd']
"""
all_labels = set([l.value for l in self])
return sorted(all_labels)
[docs] def label_count(self):
"""
Return for each label the number of occurrences within the list.
Returns:
dict: A dictionary containing for every label-value (key) the number of occurrences (value).
Example:
>>> ll = LabelList(labels=[
>>> Label('a', 3.2, 4.5),
>>> Label('b', 5.1, 8.9),
>>> Label('a', 7.2, 10.5),
>>> Label('b', 10.5, 14),
>>> Label('a', 15, 18)
>>> ])
>>> ll.label_count()
{'a': 3 'b': 2}
"""
occurrences = collections.defaultdict(int)
for label in self:
occurrences[label.value] += 1
return occurrences
[docs] def label_total_duration(self):
"""
Return for each distinct label value the total duration of all occurrences.
Returns:
dict: A dictionary containing for every label-value (key) the total duration in seconds (value).
Example:
>>> ll = LabelList(labels=[
>>> Label('a', 3, 5),
>>> Label('b', 5, 8),
>>> Label('a', 8, 10),
>>> Label('b', 10, 14),
>>> Label('a', 15, 18.5)
>>> ])
>>> ll.label_total_duration()
{'a': 7.5 'b': 7.0}
"""
durations = collections.defaultdict(float)
for label in self:
durations[label.value] += label.duration
return durations
[docs] def apply(self, fn):
"""
Apply the given function `fn` to every label in this label list. `fn` is a function of one argument that
receives the current label which can then be edited in place.
Args:
fn (func): Function to apply to every label
Example:
>>> ll = LabelList(labels=[
... Label('a_label', 1.0, 2.0),
... Label('another_label', 2.0, 3.0)
... ])
>>> def shift_labels(label):
... label.start += 1.0
... label.end += 1.0
...
>>> ll.apply(shift_labels)
>>> ll.labels
[Label(a_label, 2.0, 3.0), Label(another_label, 3.0, 4.0)]
"""
for label in self.labels:
fn(label)
def __getitem__(self, item):
return self.labels.__getitem__(item)
def __iter__(self):
return self.labels.__iter__()
def __len__(self):
return self.labels.__len__()