https://sppas.org/

SPPAS 4.16

Module sppas.src.anndata

Class sppasTier

Description

Representation of a tier, a structured set of annotations.

Annotations of a tier are sorted depending on their location

(from lowest to highest).

A Tier is made of:

  • a name (used to identify the tier),
  • a set of metadata,
  • an array of annotations,
  • a controlled vocabulary (optional),
  • a media (optional),
  • a parent (optional).

Constructor

Create a new sppasTier instance.

Parameters

  • name: (str) Name of the tier. It is used as identifier.
  • ctrl_vocab: (sppasCtrlVocab)
  • media: (sppasMedia)
  • parent: (sppasTranscription)
def __init__(self, name=None, ctrl_vocab=None, media=None, parent=None):
    """Create a new sppasTier instance.

    :param name: (str) Name of the tier. It is used as identifier.
    :param ctrl_vocab: (sppasCtrlVocab)
    :param media: (sppasMedia)
    :param parent: (sppasTranscription)

    """
    super(sppasTier, self).__init__()
    self.__name = None
    self.__ann = list()
    self.__ctrl_vocab = None
    self.__media = None
    self.__parent = None
    self.set_name(name)
    self.set_ctrl_vocab(ctrl_vocab)
    self.set_media(media)
    self.set_parent(parent)

Public functions

get_name

Return the identifier name of the tier.

def get_name(self):
    """Return the identifier name of the tier."""
    return self.__name
get_ctrl_vocab

Return the controlled vocabulary of the tier.

def get_ctrl_vocab(self):
    """Return the controlled vocabulary of the tier."""
    return self.__ctrl_vocab
get_media

Return the media of the tier.

def get_media(self):
    """Return the media of the tier."""
    return self.__media
get_parent

Return the parent of the tier.

def get_parent(self):
    """Return the parent of the tier."""
    return self.__parent
set_name

Set the name of the tier.

If no name is given, an GUID is randomly assigned.

Important: An empty string is accepted.

Parameters

  • name: (str) The identifier name or None.

Returns

  • the formatted name
def set_name(self, name=None):
    """Set the name of the tier.

        If no name is given, an GUID is randomly assigned.
        Important: An empty string is accepted.

        :param name: (str) The identifier name or None.
        :returns: the formatted name

        """
    if name is None:
        if self.get_id() == '':
            self.gen_id()
        name = self.get_id()
    su = sppasUnicode(name)
    self.__name = su.to_strip()
    return self.__name
set_ctrl_vocab

Set a controlled vocabulary to this tier.

Parameters

  • ctrl_vocab: (sppasCtrlVocab or None)

Raises

AnnDataTypeError, CtrlVocabContainsError

def set_ctrl_vocab(self, ctrl_vocab=None):
    """Set a controlled vocabulary to this tier.

        :param ctrl_vocab: (sppasCtrlVocab or None)
        :raises: AnnDataTypeError, CtrlVocabContainsError

        """
    if ctrl_vocab is not None:
        if isinstance(ctrl_vocab, sppasCtrlVocab) is False:
            raise AnnDataTypeError(ctrl_vocab, 'sppasCtrlVocab')
        for annotation in self.__ann:
            for label in annotation.get_labels():
                annotation.validate_label(label)
        if self.__parent is not None:
            try:
                self.__parent.add_ctrl_vocab(ctrl_vocab)
            except TrsAddError:
                pass
    self.__ctrl_vocab = ctrl_vocab
set_media

Set a media to the tier.

Parameters

  • media: (sppasMedia)

Raises

AnnDataTypeError

def set_media(self, media):
    """Set a media to the tier.

        :param media: (sppasMedia)
        :raises: AnnDataTypeError

        """
    if media is not None:
        if isinstance(media, sppasMedia) is False:
            raise AnnDataTypeError(media, 'sppasMedia')
        if self.__parent is not None:
            try:
                self.__parent.add_media(media)
            except TrsAddError:
                pass
    self.__media = media
set_parent

Set the parent of the tier.

Parameters

  • parent: (sppasTranscription)
def set_parent(self, parent):
    """Set the parent of the tier.

        :param parent: (sppasTranscription)

        """
    self.__parent = parent
    if parent is not None:
        if self.__media is not None:
            try:
                self.__parent.add_media(self.__media)
            except TrsAddError:
                pass
        if self.__ctrl_vocab is not None:
            try:
                self.__parent.add_ctrl_vocab(self.__ctrl_vocab)
            except TrsAddError:
                pass
copy

Return a deep copy of the tier.

Returns

  • (sppasTier) including the 'id'.
def copy(self):
    """Return a deep copy of the tier.

        :return: (sppasTier) including the 'id'.

        """
    new_tier = sppasTier(self.__name)
    my_parent = self.get_parent()
    self.set_parent(None)
    new_tier.set_parent(None)
    new_tier.set_ctrl_vocab(self.__ctrl_vocab)
    new_tier.set_media(self.__media)
    for a in self.__ann:
        new_ann = a.copy()
        new_tier.add(new_ann)
    for key in self.get_meta_keys():
        new_tier.set_meta(key, self.get_meta(key))
    new_tier.set_parent(my_parent)
    self.__parent = my_parent
    return new_tier
create_annotation

Create and add a new annotation into the tier.

Parameters

  • location: (sppasLocation) the location(s) where the annotation happens
  • labels: (sppasLabel, list) the label(s) to stamp this annot.

Returns

  • sppasAnnotation
def create_annotation(self, location, labels=None):
    """Create and add a new annotation into the tier.

        :param location: (sppasLocation) the location(s) where                the annotation happens
        :param labels: (sppasLabel, list) the label(s) to stamp this annot.
        :returns: sppasAnnotation

        """
    ann = sppasAnnotation(location, labels)
    self.add(ann)
    return ann
create_annotation_before

Create and add a new annotation in the hole before idx.

Parameters

  • idx: (int) Index of an existing annotation

Returns

  • sppasAnnotation

Raises

AnnDataTypeError, AnnDataIndexError

def create_annotation_before(self, idx):
    """Create and add a new annotation in the hole before idx.

        :param idx: (int) Index of an existing annotation
        :returns: sppasAnnotation
        :raises AnnDataTypeError, AnnDataIndexError

        """
    if self.is_point():
        raise AnnDataTypeError(self.get_name(), 'Interval, Disjoint')
    try:
        ann = self.__ann[idx]
    except IndexError:
        raise AnnDataIndexError(idx)
    if idx == 0:
        if self.is_int():
            begin = sppasPoint(0)
        else:
            begin = sppasPoint(0.0)
    else:
        prev_ann = self.__ann[idx - 1]
        begin = prev_ann.get_highest_localization()
    end = ann.get_lowest_localization()
    new_ann = self.create_annotation(sppasLocation(sppasInterval(begin, end)))
    return new_ann
create_annotation_after

Create and add a new annotation in the hole after idx.

Parameters

  • idx: (int) Index of an existing annotation

Returns

  • sppasAnnotation

Raises

AnnDataTypeError, AnnDataIndexError

def create_annotation_after(self, idx):
    """Create and add a new annotation in the hole after idx.

        :param idx: (int) Index of an existing annotation
        :returns: sppasAnnotation
        :raises AnnDataTypeError, AnnDataIndexError

        """
    if self.is_point():
        raise AnnDataTypeError(self.get_name(), 'Interval, Disjoint')
    if idx + 1 == len(self.__ann):
        raise AnnDataIndexError(idx + 1)
    try:
        ann = self.__ann[idx]
        next_ann = self.__ann[idx + 1]
    except IndexError:
        raise AnnDataIndexError(idx)
    begin = ann.get_highest_localization()
    end = next_ann.get_lowest_localization()
    new_ann = self.create_annotation(sppasLocation(sppasInterval(begin, end)))
    return new_ann
is_empty

Return True if the tier does not contain annotations.

def is_empty(self):
    """Return True if the tier does not contain annotations."""
    return len(self.__ann) == 0
append

Append the given annotation at the end of the tier.

Assign this tier as parent to the annotation.

Parameters

  • annotation: (sppasAnnotation)

Raises

AnnDataTypeError, CtrlVocabContainsError, HierarchyContainsError, HierarchyTypeError, TierAppendError

def append(self, annotation):
    """Append the given annotation at the end of the tier.

        Assign this tier as parent to the annotation.

        :param annotation: (sppasAnnotation)
        :raises: AnnDataTypeError, CtrlVocabContainsError,         HierarchyContainsError, HierarchyTypeError, TierAppendError

        """
    self.validate_annotation(annotation)
    if len(self.__ann) > 0:
        end = self.__ann[-1].get_highest_localization()
        new = annotation.get_lowest_localization()
        if annotation.location_is_point() and end == new:
            raise TierAppendError(end, new)
        if end > new:
            raise TierAppendError(end, new)
    self.__ann.append(annotation)
add

Add an annotation to the tier in sorted order.

Assign this tier as parent to the annotation.

Parameters

  • annotation: (sppasAnnotation)

Raises

AnnDataTypeError, CtrlVocabContainsError, HierarchyContainsError, HierarchyTypeError

Returns

  • the index of the annotation in the tier
def add(self, annotation):
    """Add an annotation to the tier in sorted order.

        Assign this tier as parent to the annotation.

        :param annotation: (sppasAnnotation)
        :raises: AnnDataTypeError, CtrlVocabContainsError,         HierarchyContainsError, HierarchyTypeError
        :returns: the index of the annotation in the tier

        """
    self.validate_annotation(annotation)
    try:
        self.append(annotation)
    except Exception:
        if annotation.location_is_point():
            index = self.index(annotation.get_lowest_localization())
            if index != -1:
                if self.__ann[index].get_lowest_localization().get_midpoint() == annotation.get_lowest_localization().get_midpoint():
                    raise TierAddError(index)
                self.__ann.insert(index + 1, annotation)
                return index + 1
            else:
                index = self.near(annotation.get_lowest_localization(), direction=-1)
                self.__ann.insert(index, annotation)
                return index
        else:
            index = self.mindex(annotation.get_lowest_localization(), bound=0)
            while index + 1 < len(self.__ann) and self.__ann[index + 1].get_lowest_localization() < annotation.get_lowest_localization():
                index += 1
            while index + 1 < len(self.__ann) and self.__ann[index + 1].get_lowest_localization() == annotation.get_lowest_localization() and (self.__ann[index + 1].get_highest_localization() < annotation.get_highest_localization()):
                index += 1
            if self.__ann[index].get_location() == annotation.get_location():
                raise TierAddError(index)
            if index + 1 < len(self.__ann):
                if self.__ann[index + 1].get_location() == annotation.get_location():
                    raise TierAddError(index + 1)
            self.__ann.insert(index + 1, annotation)
            return index + 1
    return len(self.__ann) - 1
remove

Remove annotation intervals between begin and end.

Parameters

  • begin: (sppasPoint)
  • end: (sppasPoint)
  • overlaps: (bool)

Returns

  • the number of removed annotations

Raises

HierarchyContainsError

def remove(self, begin, end, overlaps=False):
    """Remove annotation intervals between begin and end.

        :param begin: (sppasPoint)
        :param end: (sppasPoint)
        :param overlaps: (bool)
        :returns: the number of removed annotations
        :raises: HierarchyContainsError

        """
    if end < begin:
        raise IntervalBoundsError(begin, end)
    annotations = self.find(begin, end, overlaps)
    copied_anns = list()
    for a in reversed(annotations):
        copied_anns.append(a.copy())
        self.__ann.remove(a)
    if self.__parent is not None:
        try:
            self.validate()
        except:
            for a in copied_anns:
                self.add(a)
    return len(copied_anns)
pop

Remove the annotation at the given position in the tier.

If no index is specified, pop() removes

and returns the last annotation in the tier.

Parameters

  • index: (int) Index of the annotation to remove.

Raises

HierarchyContainsError

def pop(self, index=-1):
    """Remove the annotation at the given position in the tier.

        If no index is specified, pop() removes
        and returns the last annotation in the tier.

        :param index: (int) Index of the annotation to remove.
        :raises: HierarchyContainsError

        """
    try:
        ann = self.__ann[index]
        copied_ann = ann.copy()
    except IndexError:
        raise AnnDataIndexError(index)
    self.__ann.pop(index)
    if self.__parent is not None:
        try:
            self.validate()
        except:
            self.__ann.insert(index, copied_ann)
            raise
remove_unlabelled

Remove annotations without labels.

Do not remove an annotation if it invalidates the hierarchy.

Returns

  • the number of removed annotations
def remove_unlabelled(self):
    """Remove annotations without labels.

        Do not remove an annotation if it invalidates the hierarchy.

        :returns: the number of removed annotations

        """
    nb = 0
    for a in reversed(self.__ann):
        if a.is_labelled() is False:
            try:
                self.__ann.remove(a)
                nb += 1
            except:
                pass
    return nb
split

Split annotation at the given index into 2 annotations.

Example with ann=[1.0;2.0] at index 1:

  • tier.split(1): creates ann1=[1.0;1.5] and ann2=[1.5;2.0]
  • tier.split(1, 1.2): creates ann1=[1.0;1.2] and ann2=[1.2;2.0]
  • tier.split(1, 1.2, 1.5): creates ann1=[1.0;1.2] and ann2=[1.5;2.0]

Parameters

  • idx: (int) Index of the annotation to split.
  • from_time: (int) Time value to split from
  • to_time: (int) Time value to end the cut part

Returns

  • newly created annotation at index idx+1
def split(self, idx, from_time=None, to_time=None):
    """Split annotation at the given index into 2 annotations.

        Example with ann=[1.0;2.0] at index 1:
            - tier.split(1): creates ann1=[1.0;1.5] and ann2=[1.5;2.0]
            - tier.split(1, 1.2): creates ann1=[1.0;1.2] and ann2=[1.2;2.0]
            - tier.split(1, 1.2, 1.5): creates ann1=[1.0;1.2] and ann2=[1.5;2.0]

        :param idx: (int) Index of the annotation to split.
        :param from_time: (int) Time value to split from
        :param to_time: (int) Time value to end the cut part
        :return: newly created annotation at index idx+1

        """
    if self.is_point() is True:
        raise AnnDataTypeError(self.get_name(), 'Interval, Disjoint')
    try:
        ann = self.__ann[idx]
    except IndexError:
        raise AnnDataIndexError(idx)
    localization = ann.get_location().get_best()
    end = localization.get_end().copy()
    if from_time is not None:
        if isinstance(from_time, sppasPoint) is False:
            from_time = sppasPoint(from_time)
        if ann.inside_localization(from_time) is False:
            raise IntervalRangeException(from_time, localization.get_begin(), end)
    if to_time is not None:
        if isinstance(to_time, sppasPoint) is False:
            to_time = sppasPoint(to_time)
        if ann.inside_localization(to_time) is False:
            raise IntervalRangeException(to_time, localization.get_begin(), end)
    if from_time is None:
        cut_point = localization.middle()
    else:
        cut_point = from_time
    try:
        localization.set_end(cut_point)
        self.validate()
    except:
        localization.set_end(end)
        raise
    if to_time is not None:
        cut_point = to_time
    new_ann = self.create_annotation(sppasLocation(sppasInterval(cut_point.copy(), end)))
    return new_ann
merge

Merge the annotation at given index with next or previous one.

if direction > 0:

annidx: [beginidx, endidx, labelsidx]

nextann: [beginn, endn, labelsn]

result: [beginidx, endn, labelsidx + labelsn]

if direction < 0:

prevann: [beginp, endp, labelsp]

annidx: [beginidx, endidx, labelsidx]

result: [beginp, endidx, labelsp + labelsidx]

Parameters

  • idx: (int) Index of the annotation in the list
  • direction: (int) Positive for next, Negative for previous

Returns

  • (bool) False if direction does not match with index

Raises

Exception if merged annotation can't be deleted of the tier

def merge(self, idx, direction):
    """Merge the annotation at given index with next or previous one.

        if direction > 0:
            ann_idx:  [begin_idx, end_idx, labels_idx]
            next_ann: [begin_n, end_n, labels_n]
            result:   [begin_idx, end_n, labels_idx + labels_n]

        if direction < 0:
            prev_ann: [begin_p, end_p, labels_p]
            ann_idx:  [begin_idx, end_idx, labels_idx]
            result:   [begin_p, end_idx, labels_p + labels_idx]

        :param idx: (int) Index of the annotation in the list
        :param direction: (int) Positive for next, Negative for previous
        :return: (bool) False if direction does not match with index
        :raise: Exception if merged annotation can't be deleted of the tier

        """
    if self.is_point() is True:
        raise AnnDataTypeError(self.get_name(), 'Interval, Disjoint')
    try:
        ann = self.__ann[idx]
    except IndexError:
        raise AnnDataIndexError(idx)
    if direction > 0:
        merge_idx = idx + 1
        if merge_idx == len(self.__ann):
            return False
    else:
        if idx == 0:
            return False
        merge_idx = idx - 1
    merge_ann = self.__ann[merge_idx]
    merge_labels = [l.copy() for l in merge_ann.get_labels()]
    merge_loc = merge_ann.get_location().get_best()
    localization = ann.get_location().get_best()
    copied_loc = localization.copy()
    labels = self.__ann[idx].get_labels()
    try:
        if direction > 0:
            localization.set_end(merge_loc.get_end())
            new_labels = labels + merge_labels
        else:
            localization.set_begin(merge_loc.get_begin())
            new_labels = merge_labels + labels
        self.validate()
        ann.set_labels(new_labels)
        self.pop(merge_idx)
    except:
        ann.set_best_localization(copied_loc)
        ann.set_labels(labels)
        raise
    return True
get_all_points

Return the list of all points of the tier.

def get_all_points(self):
    """Return the list of all points of the tier."""
    if len(self.__ann) == 0:
        return []
    points = list()
    for ann in self.__ann:
        points.extend(ann.get_all_points())
    return points
get_first_point

Return the first point of the first annotation.

def get_first_point(self):
    """Return the first point of the first annotation."""
    if len(self.__ann) == 0:
        return None
    return self.__ann[0].get_lowest_localization()
get_last_point

Return the last point of the last location.

def get_last_point(self):
    """Return the last point of the last location."""
    if len(self.__ann) == 0:
        return None
    return self.__ann[-1].get_highest_localization()
has_point

Return True if the tier contains a given point.

Parameters

  • point: (sppasPoint) The point to find in the tier.

Returns

  • (bool)
def has_point(self, point):
    """Return True if the tier contains a given point.

        :param point: (sppasPoint) The point to find in the tier.
        :returns: (bool)

        """
    if isinstance(point, sppasPoint) is False:
        raise AnnDataTypeError(point, 'sppasPoint')
    return point in self.get_all_points()
has_location

Return True if the tier has the given location.

to be tested.

Parameters
  • location
def has_location(self, location):
    """Return True if the tier has the given location.

        to be tested.

        """
    begin = location.get_lowest_localization()
    end = location.get_highest_localization()
    anns = self.find(begin, end, overlaps=False)
    for a in anns:
        if a.get_location() == location:
            return True
    return False
is_disjoint

Return True if the tier is made of disjoint localizations.

def is_disjoint(self):
    """Return True if the tier is made of disjoint localizations."""
    if len(self.__ann) == 0:
        return False
    return self.__ann[0].get_location().is_disjoint()
is_interval

Return True if the tier is made of interval localizations.

def is_interval(self):
    """Return True if the tier is made of interval localizations."""
    if len(self.__ann) == 0:
        return False
    return self.__ann[0].get_location().is_interval()
is_point

Return True if the tier is made of point localizations.

def is_point(self):
    """Return True if the tier is made of point localizations."""
    if len(self.__ann) == 0:
        return False
    return self.__ann[0].get_location().is_point()
get_midpoint_intervals

Return midpoint values of all the intervals.

def get_midpoint_intervals(self):
    """Return midpoint values of all the intervals."""
    units = list()
    if self.is_point() is False:
        for i in range(len(self)):
            b = self.__ann[i].get_lowest_localization().get_midpoint()
            e = self.__ann[i].get_highest_localization().get_midpoint()
            units.append((b, e))
    return units
get_midpoint_points

Return midpoint values of all the points.

def get_midpoint_points(self):
    """Return midpoint values of all the points."""
    units = list()
    if self.is_point() is True:
        for i in range(len(self)):
            m = self.__ann[i].get_lowest_localization().get_midpoint()
            units.append(m)
    return units
set_radius

Fix a radius value to all points of the tier.

Parameters

  • radius: (int, float) New radius value

Raises

AnnDataTypeError, AnnDataNegValueError

def set_radius(self, radius):
    """Fix a radius value to all points of the tier.

        :param radius: (int, float) New radius value
        :raise: AnnDataTypeError, AnnDataNegValueError

        """
    for ann in self.__ann:
        ann.get_location().set_radius(radius)
find

Return a list of annotations between begin and end.

Parameters

  • begin: sppasPoint or None to start from the beginning of the tier
  • end: sppasPoint or None to end at the end of the tier
  • overlaps: (bool) Return also overlapped annotations, using midpoints. Not relevant for tiers with points.
  • indexes: (bool) Return indexes instead of annotations
  • radius_overlaps: (bool) Return also overlapped annotations, using midpoints and radius. Not relevant for tiers with points. overlaps must be true.

Returns

  • List of sppasAnnotation or list of indexes
def find(self, begin, end, overlaps=True, indexes=False, radius_overlaps=False):
    """Return a list of annotations between begin and end.

        :param begin: sppasPoint or None to start from the beginning of the tier
        :param end: sppasPoint or None to end at the end of the tier
        :param overlaps: (bool) Return also overlapped annotations, using midpoints.                   Not relevant for tiers with points.
        :param indexes: (bool) Return indexes instead of annotations
        :param radius_overlaps: (bool) Return also overlapped annotations,                   using midpoints and radius.                   Not relevant for tiers with points. overlaps must be true.
        :returns: List of sppasAnnotation or list of indexes

        """
    if len(self.__ann) == 0:
        return []
    if begin is None:
        begin = self.get_first_point()
    if end is None:
        end = self.get_last_point()
    if begin > self.get_last_point() or end < self.get_first_point():
        return []
    annotations = list()
    if self.is_point() is True:
        lo = self.index(begin)
        if lo == -1:
            lo = self.near(begin, direction=1)
        for i, ann in enumerate(self.__ann[lo:]):
            lowest = ann.get_lowest_localization()
            highest = ann.get_highest_localization()
            if lowest > end and highest > end:
                break
            if lowest >= begin and highest <= end:
                if indexes is True:
                    annotations.append(lo + i)
                else:
                    annotations.append(ann)
    else:
        lo = self.__find(begin, radius_overlaps, closest=True)
        if lo != -1:
            if overlaps is True:
                for i, ann in enumerate(self.__ann[lo:]):
                    b = ann.get_lowest_localization()
                    e = ann.get_highest_localization()
                    if b > end:
                        break
                    if radius_overlaps is False:
                        if end > b and begin < e:
                            if indexes is True:
                                annotations.append(lo + i)
                            else:
                                annotations.append(ann)
                    elif end >= b and begin <= e:
                        if indexes is True:
                            annotations.append(lo + i)
                        else:
                            annotations.append(ann)
            else:
                for i, ann in enumerate(self.__ann[lo:]):
                    b = ann.get_lowest_localization()
                    e = ann.get_highest_localization()
                    if b >= begin and e <= end:
                        if indexes is True:
                            annotations.append(lo + i)
                        else:
                            annotations.append(ann)
                    if b >= end:
                        break
        else:
            logging.info('No annotation is matching begin={}, end={}'.format(begin, end))
    return annotations
index

Return the index of the moment (int), or -1.

Only for tier with points.

Parameters

  • moment: (sppasPoint)
def index(self, moment):
    """Return the index of the moment (int), or -1.

        Only for tier with points.

        :param moment: (sppasPoint)

        """
    if self.is_point() is False:
        return -1
    lo = 0
    hi = len(self.__ann)
    mid = (lo + hi) // 2
    found = False
    while lo < hi:
        mid = (lo + hi) // 2
        a = self.__ann[mid]
        if moment < a.get_lowest_localization():
            hi = mid
        elif moment > a.get_highest_localization():
            lo = mid + 1
        else:
            found = True
            break
    if found is False:
        return -1
    return mid
lindex

Return the index of the interval starting at a given moment, or -1.

Only for tier with intervals or disjoint.

If the tier contains more than one annotation starting at the same

moment, the method returns the first one.

Parameters

  • moment: (sppasPoint)
def lindex(self, moment):
    """Return the index of the interval starting at a given moment, or -1.

        Only for tier with intervals or disjoint.
        If the tier contains more than one annotation starting at the same
        moment, the method returns the first one.

        :param moment: (sppasPoint)

        """
    if self.is_point() is True:
        return -1
    lo = 0
    hi = len(self.__ann)
    mid = (lo + hi) // 2
    found = False
    while lo < hi:
        mid = (lo + hi) // 2
        begin = self.__ann[mid].get_lowest_localization()
        if moment < begin:
            hi = mid
        elif moment > begin:
            lo = mid + 1
        else:
            found = True
            break
    if found is False:
        return -1
    if mid == 0:
        return 0
    while mid >= 0 and self.__ann[mid].get_lowest_localization() == moment:
        mid -= 1
    return mid + 1
mindex

Return index of the interval containing the given moment.

Only for tier with intervals or disjoint.

If the tier contains more than one annotation at the same moment,

the method returns the first one (i.e. the one which started at first).

Parameters

  • moment: (sppasPoint)
  • bound: (int) - 0 to exclude bounds of the interval; - -1 to include begin bound; - +1 to include end bound; - +2 to include both begin/end bounds; - others: the midpoint of moment is strictly inside

Returns

  • (int) Index of the 1st annotation containing moment or -1
def mindex(self, moment, bound=0):
    """Return index of the interval containing the given moment.

        Only for tier with intervals or disjoint.

        If the tier contains more than one annotation at the same moment,
        the method returns the first one (i.e. the one which started at first).

        :param moment: (sppasPoint)
        :param bound: (int)
            - 0 to exclude bounds of the interval;
            - -1 to include begin bound;
            - +1 to include end bound;
            - +2 to include both begin/end bounds;
            - others: the midpoint of moment is strictly inside
        :returns: (int) Index of the 1st annotation containing moment or -1

        """
    if self.is_point() is True:
        return -1
    for i, a in enumerate(self.__ann):
        b = a.get_lowest_localization()
        e = a.get_highest_localization()
        if bound == -1:
            if b <= moment < e:
                return i
        elif bound == 1:
            if b < moment <= e:
                return i
        elif bound == 2:
            if b <= moment <= e:
                return i
        elif bound == 0:
            if b < moment < e:
                return i
        elif b < moment.get_midpoint() < e:
            return i
    return -1
rindex

Return the index of the interval ending at the given moment.

Only for tier with intervals or disjoint.

If the tier contains more than one annotation ending at the same moment,

the method returns the last one.

Parameters

  • moment: (sppasPoint)
def rindex(self, moment):
    """Return the index of the interval ending at the given moment.

        Only for tier with intervals or disjoint.
        If the tier contains more than one annotation ending at the same moment,
        the method returns the last one.

        :param moment: (sppasPoint)

        """
    if self.is_point() is True:
        return -1
    lo = 0
    hi = len(self.__ann)
    mid = (lo + hi) // 2
    found = False
    while lo < hi:
        mid = (lo + hi) // 2
        a = self.__ann[mid]
        if moment < a.get_highest_localization():
            hi = mid
        elif moment > a.get_highest_localization():
            lo = mid + 1
        else:
            found = True
            break
    if found is False:
        return -1
    if mid == len(self.__ann) - 1:
        return mid
    while mid + 1 < len(self.__ann) and self.__ann[mid + 1].get_highest_localization() == moment:
        mid += 1
    return mid
is_superset

Return True if this tier contains all points of the other tier.

Parameters

  • other: (sppasTier)

Returns

  • Boolean
def is_superset(self, other):
    """Return True if this tier contains all points of the other tier.

        :param other: (sppasTier)
        :returns: Boolean

        """
    if len(other) == 0:
        return True
    tier_points = self.get_all_points()
    other_points = other.get_all_points()
    for op in other_points:
        if op not in tier_points:
            return False
    return True
near

Search for the annotation whose localization is closest.

Search for the nearest localization to the given moment into a

given direction.

Parameters

  • moment: (sppasPoint)
  • direction: (int) - nearest 0 - nereast forward 1 - nereast backward -1
def near(self, moment, direction=1):
    """Search for the annotation whose localization is closest.

        Search for the nearest localization to the given moment into a
        given direction.

        :param moment: (sppasPoint)
        :param direction: (int)
                - nearest 0
                - nereast forward 1
                - nereast backward -1

        """
    if isinstance(moment, sppasPoint) is False:
        raise sppasTypeError('moment', 'sppasPoint')
    if len(self.__ann) == 0:
        return -1
    if len(self.__ann) == 1:
        return 0
    index = self.__find(moment, radius_overlaps=False, closest=True)
    if index == -1:
        return -1
    a = self.__ann[index]
    if direction == 1:
        if moment <= a.get_lowest_localization():
            return index
        if index + 1 < len(self.__ann):
            return index + 1
        return -1
    elif direction == -1:
        if moment >= a.get_highest_localization():
            return index
        if index - 1 >= 0:
            return index - 1
        return -1
    a = self.__ann[index]
    if a.get_lowest_localization() <= moment <= a.get_highest_localization():
        return index
    _next = index + 1
    if _next >= len(self.__ann):
        return index
    time = moment.get_midpoint()
    prev_time = self.__ann[index].get_highest_localization().get_midpoint()
    next_time = self.__ann[_next].get_lowest_localization().get_midpoint()
    if abs(time - prev_time) > abs(next_time - time):
        return _next
    return index
is_string

All label tags are string or unicode or None.

def is_string(self):
    """All label tags are string or unicode or None."""
    if len(self.__ann) == 0:
        return False
    for ann in self.__ann:
        if ann.is_labelled() is True:
            return ann.label_is_string()
    return False
is_float

All label tags are float values or None.

def is_float(self):
    """All label tags are float values or None."""
    if len(self.__ann) == 0:
        return False
    for ann in self.__ann:
        if ann.is_labelled() is True:
            return ann.label_is_float()
    return False
is_int

All label tags are integer values or None.

def is_int(self):
    """All label tags are integer values or None."""
    if len(self.__ann) == 0:
        return False
    for ann in self.__ann:
        if ann.is_labelled() is True:
            return ann.label_is_int()
    return False
is_bool

All label tags are boolean values or None.

def is_bool(self):
    """All label tags are boolean values or None."""
    if len(self.__ann) == 0:
        return False
    for ann in self.__ann:
        if ann.is_labelled() is True:
            return ann.label_is_bool()
    return False
is_fuzzypoint

All label tags are fuzzy point values or None.

def is_fuzzypoint(self):
    """All label tags are fuzzy point values or None."""
    if len(self.__ann) == 0:
        return False
    for ann in self.__ann:
        if ann.is_labelled() is True:
            return ann.label_is_point()
    return False
is_fuzzyrect

All label tags are fuzzy rectangle values or None.

def is_fuzzyrect(self):
    """All label tags are fuzzy rectangle values or None."""
    if len(self.__ann) == 0:
        return False
    for ann in self.__ann:
        if ann.is_labelled() is True:
            return ann.label_is_rect()
    return False
get_labels_type

Return the current type of labels, or an empty string.

def get_labels_type(self):
    """Return the current type of labels, or an empty string."""
    if len(self.__ann) == 0:
        return ''
    for ann in self.__ann:
        if ann.is_labelled() is True:
            return ann.get_label_type()
    return ''
get_nb_filled_labels

Return the number annotation with a filled label.

def get_nb_filled_labels(self):
    """Return the number annotation with a filled label."""
    nb = 0
    for ann in self.__ann:
        if ann.is_labelled() is True:
            nb += 1
    return nb
validate
def validate(self):
    if self.__parent is not None:
        self.__parent.validate_hierarchy(self)
validate_annotation

Validate the annotation and set its parent to this tier.

Parameters

  • annotation: (sppasAnnotation)

Raises

AnnDataTypeError, CtrlVocabContainsError, HierarchyContainsError, HierarchyTypeError

def validate_annotation(self, annotation):
    """Validate the annotation and set its parent to this tier.

        :param annotation: (sppasAnnotation)
        :raises: AnnDataTypeError, CtrlVocabContainsError,         HierarchyContainsError, HierarchyTypeError

        """
    if isinstance(annotation, sppasAnnotation) is False:
        raise AnnDataTypeError(annotation, 'sppasAnnotation')
    if len(self.__ann) > 0:
        if annotation.location_is_point() is True and self.is_point() is False:
            raise AnnDataTypeError(str(annotation) + ' (sppasPoint)', 'sppasInterval')
        if annotation.location_is_interval() is True and self.is_interval() is False:
            raise AnnDataTypeError(str(annotation) + ' (sppasInterval)', 'sppasPoint')
        if annotation.location_is_disjoint() is True and self.is_disjoint() is False:
            raise AnnDataTypeError(annotation, 'sppasDisjoint')
    annotation.set_parent(self)
validate_annotation_label

Validate a label.

Parameters

  • label: (sppasLabel)

Raises

CtrlVocabContainsError

def validate_annotation_label(self, label):
    """Validate a label.

        :param label: (sppasLabel)
        :raises: CtrlVocabContainsError

        """
    if self.__ctrl_vocab is not None:
        for tag, score in label:
            if tag.is_empty() is False and self.__ctrl_vocab.contains(tag) is False:
                raise CtrlVocabContainsError(tag)
    if (self.is_bool() or self.is_float() or self.is_int() or self.is_string()) is False:
        return
    if label.is_tagged():
        if label.get_type() != self.get_labels_type():
            raise AnnDataTypeError(label, self.get_labels_type())
validate_annotation_location

Ask the parent to validate a location.

Parameters

  • location: (sppasLocation)

Raises

AnnDataTypeError, HierarchyContainsError, HierarchyTypeError

def validate_annotation_location(self, location):
    """Ask the parent to validate a location.

        :param location: (sppasLocation)
        :raises: AnnDataTypeError, HierarchyContainsError, HierarchyTypeError

        """
    if self.__parent is not None:
        self.__parent.validate_annotation_location(self, location)
get_annotation

Find an annotation from its metadata 'id'.

Parameters

  • identifier: (str) Metadata 'id' of an annotation.

Returns

  • sppasAnnotation or None
def get_annotation(self, identifier):
    """Find an annotation from its metadata 'id'.

        :param identifier: (str) Metadata 'id' of an annotation.
        :returns: sppasAnnotation or None

        """
    for a in self:
        if a.get_meta('id') == identifier:
            return a
    return None
get_annotation_index

Find an annotation.

Parameters

  • ann: (sppasAnnotation)

Returns

  • (int) -1 if not found
def get_annotation_index(self, ann):
    """Find an annotation.

        :param ann: (sppasAnnotation)
        :returns: (int) -1 if not found

        """
    if self.is_point():
        return self.index(ann.get_highest_localization())
    else:
        i1 = self.lindex(ann.get_lowest_localization())
        i2 = self.rindex(ann.get_highest_localization())
        for i in range(i1, i2 + 1):
            ai = self.__ann[i]
            if ai is ann:
                return i
            if ai.get_id() == ann.get_id():
                return i
    return -1
create_ctrl_vocab

Create the controlled vocabulary from annotation labels.

Create (or re-create) the controlled vocabulary from the list of

already existing annotation labels.

The current controlled vocabulary is deleted.

Parameters

  • name: (str) Name of the controlled vocabulary. The name of the tier is used by default.
def create_ctrl_vocab(self, name=None):
    """Create the controlled vocabulary from annotation labels.

        Create (or re-create) the controlled vocabulary from the list of
        already existing annotation labels.
        The current controlled vocabulary is deleted.

        :param name: (str) Name of the controlled vocabulary.         The name of the tier is used by default.

        """
    if name is None:
        name = self.__name
    self.__ctrl_vocab = sppasCtrlVocab(name)
    for ann in self.__ann:
        for label in ann.get_labels():
            if label.is_tagged():
                for tag, score in label:
                    self.__ctrl_vocab.add(tag)
export_to_intervals

Create a tier with the consecutive filled intervals.

Return an empty tier if 'self' is not of type "interval".

The created intervals are not filled.

Parameters

  • separators: (list)

Returns

  • (sppasTier)
def export_to_intervals(self, separators):
    """Create a tier with the consecutive filled intervals.

        Return an empty tier if 'self' is not of type "interval".
        The created intervals are not filled.

        :param separators: (list)
        :returns: (sppasTier)

        """
    intervals = sppasTier('intervals')
    if self.is_interval() is False:
        return intervals
    begin = self.get_first_point()
    end = begin
    prev_ann = None
    for ann in self.__ann:
        tag = None
        if ann.label_is_filled():
            tag = ann.get_best_tag()
        if prev_ann is not None:
            if tag is None or tag.get_typed_content() in separators or prev_ann.get_highest_localization() < ann.get_lowest_localization():
                if end > begin:
                    intervals.create_annotation(sppasLocation(sppasInterval(begin, prev_ann.get_highest_localization())))
                if tag is None or tag.get_typed_content() in separators:
                    begin = ann.get_highest_localization()
                else:
                    begin = ann.get_lowest_localization()
        elif tag is None or tag.get_typed_content() in separators:
            begin = ann.get_highest_localization()
        end = ann.get_highest_localization()
        prev_ann = ann
    if end > begin:
        ann = self.__ann[-1]
        end = ann.get_highest_localization()
        intervals.create_annotation(sppasLocation(sppasInterval(begin, end)))
    return intervals
export_unfilled

Create a tier with the unlabelled/unfilled intervals.

Only for tiers of type Interval.

It represents the "NOT tier", ie where this tier is not annotated.

IMPORTANT: Never tested with overlapped annotations,

actually not tested at all (but used in the plugin StatGroups).

Returns

  • (sppasTier) or None
def export_unfilled(self):
    """Create a tier with the unlabelled/unfilled intervals.

        Only for tiers of type Interval.
        It represents the "NOT tier", ie where this tier is not annotated.

        IMPORTANT: Never tested with overlapped annotations,
        actually not tested at all (but used in the plugin StatGroups).

        :return: (sppasTier) or None

        """
    if self.is_empty() is True:
        return None
    if self.is_interval() is False:
        return None
    intervals = self.export_to_intervals([])
    not_intervals = sppasTier('NotIntervals')
    if intervals.is_empty():
        not_intervals.create_annotation(sppasLocation(sppasInterval(self.get_first_point(), self.get_last_point())))
        return not_intervals
    begin = self.__ann[0].get_lowest_localization()
    prev_ann = intervals[0]
    prev_begin = prev_ann.get_lowest_localization()
    if prev_begin > begin:
        not_intervals.create_annotation(sppasLocation(sppasInterval(begin, prev_begin)))
    for i in range(1, len(intervals)):
        prev_end = prev_ann.get_highest_localization()
        ann = intervals[i]
        begin = ann.get_lowest_localization()
        if begin > prev_end:
            not_intervals.create_annotation(sppasLocation(sppasInterval(prev_end, begin)))
        prev_ann = ann
    end = self.__ann[-1].get_highest_localization()
    prev_end = intervals[-1].get_highest_localization()
    if prev_end < end:
        not_intervals.create_annotation(sppasLocation(sppasInterval(prev_end, end)))
    return not_intervals
fit

Select then slice or extend annotations to fit in other tier.

Keep only the annotations of self that have some overlapping time

with the given other tier and slice the localization of

such selected annotations to exactly match those of the other tier.

Example:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

tier1: |a|b| |c| |d| |e| |f|

tier2: |w| |_x_| |y| |z|

tier1.fit(tier2) result is:

|a|b| |c| |d| |e|

tier2.fit(tier1) result is:

|w|w| |x| |x| |y|

Parameters

  • other: (sppasTier)

Returns

  • (sppasTier)
def fit(self, other):
    """Select then slice or extend annotations to fit in other tier.

        Keep only the annotations of self that have some overlapping time
        with the given other tier and slice the localization of
        such selected annotations to exactly match those of the other tier.

        Example:

                 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14
        tier1:         |_a_|_b_|       |_c_|   |_d_|     |_e_|       |f|
        tier2:           |_w_|       |_______x_______|     |_y_| |_z_|

        tier1.fit(tier2) result is:
                         |a|b|         |_c_|   |_d_|       |e|

        tier2.fit(tier1) result is:
                         |w|w|         |_x_|   |_x_|       |y|

        :param other: (sppasTier)
        :return: (sppasTier)

        """
    ft = sppasTier(self.__name + '-inter-' + other.get_name())
    for ann in other:
        b = ann.get_lowest_localization()
        e = ann.get_highest_localization()
        found_anns = self.find(b, e, overlaps=True)
        if len(found_anns) == 1:
            labels = list()
            for la in found_anns[0].get_labels():
                labels.append(la.copy())
            bf = max(b, found_anns[0].get_lowest_localization())
            ef = min(e, found_anns[0].get_highest_localization())
            ft.create_annotation(sppasLocation(sppasInterval(bf.copy(), ef.copy())), labels)
        elif len(found_anns) > 1:
            for i, ao in enumerate(found_anns):
                labels = list()
                for la in ao.get_labels():
                    labels.append(la.copy())
                if ao is found_anns[0]:
                    bf = max(b, ao.get_lowest_localization())
                    location = sppasLocation(sppasInterval(bf.copy(), ao.get_highest_localization().copy()))
                elif ao is found_anns[-1]:
                    ef = min(e, ao.get_highest_localization())
                    location = sppasLocation(sppasInterval(ao.get_lowest_localization().copy(), ef.copy()))
                else:
                    location = ao.get_location().copy()
                ft.create_annotation(location, labels)
    return ft

Protected functions

__find

Find index of the 1st annotation whose moment value contains x.

Parameters

  • x: (sppasPoint)
  • radius_overlaps: (bool)
  • closest: (bool) return the closest if there's no annotation on x

Returns

  • (int) -1 if not found and closest is False.
def __find(self, x, radius_overlaps=False, closest=False):
    """Find index of the 1st annotation whose moment value contains x.

        :param x: (sppasPoint)
        :param radius_overlaps: (bool)
        :param closest: (bool) return the closest if there's no annotation on x
        :return: (int) -1 if not found and closest is False.

        """
    if len(self.__ann) == 0:
        return -1
    if x < self.__ann[0].get_highest_localization():
        if closest is False:
            return -1
        return 0
    if x > self.__ann[-1].get_highest_localization():
        if closest is False:
            return -1
        return len(self.__ann) - 1
    if len(self.__ann) == 1:
        if closest is True:
            return 0
        if self.__ann[0].get_lowest_localization() <= x < self.__ann[0].get_highest_localization():
            return 0
        return -1
    is_point = self.is_point()
    lo = 0
    hi = len(self.__ann)
    mid = int(float(lo + hi) / 2.0)
    found = False
    while found is False and lo < hi:
        mid = int(float(lo + hi) / 2.0)
        a = self.__ann[mid]
        if is_point is True:
            p = a.get_location().get_best()
            if p == x:
                found = True
            elif x < p:
                hi = mid
            else:
                lo = mid + 1
        else:
            b = a.get_lowest_localization()
            e = a.get_highest_localization()
            if radius_overlaps is False:
                if b.get_midpoint() <= x < e.get_midpoint():
                    found = True
            elif b <= x <= e:
                found = True
            if found is False:
                if x <= e:
                    hi = mid
                else:
                    lo = mid + 1
    if found is True:
        tmp_annotations = list()
        tmp_annotations.append(mid)
        i = mid
        for ann in reversed(self.__ann[:mid]):
            i = i - 1
            b = ann.get_lowest_localization()
            e = ann.get_highest_localization()
            if radius_overlaps is False:
                if b < x < e:
                    tmp_annotations.append(i)
            elif b <= x <= e:
                tmp_annotations.append(i)
            if b > x:
                break
        return tmp_annotations[-1]
    if closest is False:
        return -1
    return mid

Overloads

__iter__
def __iter__(self):
    for a in self.__ann:
        yield a
__getitem__
def __getitem__(self, i):
    return self.__ann[i]
__len__
def __len__(self):
    return len(self.__ann)
__contains__

Value can be either an annotation or an annotation identifier.

Parameters
  • value
def __contains__(self, value):
    """Value can be either an annotation or an annotation identifier."""
    if isinstance(value, sppasAnnotation):
        return value in self.__ann
    else:
        for a in self.__ann:
            if a.get_id() == value:
                return True
    return False
__hash__
def __hash__(self):
    return hash(self.get_id())