# -*- coding: UTF-8 -*-
"""
:filename: sppas.src.anndata.annlocation.interval.py
:author: Brigitte Bigi
:contact: develop@sppas.org
:summary: Represent the interval localization of an annotation.
.. _This file is part of SPPAS: http://www.sppas.org/
..
-------------------------------------------------------------------------
___ __ __ __ ___
/ | \ | \ | \ / the automatic
\__ |__/ |__/ |___| \__ annotation and
\ | | | | \ analysis
___/ | | | | ___/ of speech
Copyright (C) 2011-2021 Brigitte Bigi
Laboratoire Parole et Langage, Aix-en-Provence, France
Use of this software is governed by the GNU Public License, version 3.
SPPAS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SPPAS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SPPAS. If not, see <http://www.gnu.org/licenses/>.
This banner notice must not be removed.
-------------------------------------------------------------------------
"""
import logging
from ...anndataexc import AnnDataTypeError
from ...anndataexc import AnnDataEqTypeError
from ...anndataexc import IntervalBoundsError
from .localization import sppasBaseLocalization
from .point import sppasPoint
from .duration import sppasDuration
# ---------------------------------------------------------------------------
[docs]class sppasInterval(sppasBaseLocalization):
"""Localization of an interval between two sppasPoint instances.
An interval is identified by two sppasPoint objects:
- one is representing the beginning of the interval;
- the other is representing the end of the interval.
"""
[docs] def __init__(self, begin, end):
"""Create a new sppasInterval instance.
:param begin: (sppasPoint)
:param end: (sppasPoint)
Degenerated interval is forbidden, i.e. begin > end.
"""
super(sppasInterval, self).__init__()
if isinstance(begin, sppasPoint) is False:
AnnDataTypeError(begin, "sppasPoint")
if isinstance(end, sppasPoint) is False:
AnnDataTypeError(end, "sppasPoint")
if sppasInterval.check_types(begin, end) is False:
raise AnnDataEqTypeError(begin, end)
if sppasInterval.check_interval_bounds(begin, end) is False:
raise IntervalBoundsError(begin, end)
# we accept some overlap
if begin >= end:
logging.warning('begin ({!s:s} >= end {!s:s})'.format(begin, end))
self.__begin = begin
self.__end = end
# -----------------------------------------------------------------------
[docs] def set(self, other):
"""Set self members from another sppasInterval instance.
:param other: (sppasInterval)
"""
if isinstance(other, sppasInterval) is False:
raise AnnDataTypeError(other, "sppasInterval")
self.__begin = other.get_begin()
self.__end = other.get_end()
# -----------------------------------------------------------------------
[docs] def is_interval(self):
"""Overrides. Return True, because self represents an interval."""
return True
# -----------------------------------------------------------------------
[docs] def copy(self):
"""Return a deep copy of self."""
return sppasInterval(self.__begin.copy(), self.__end.copy())
# -----------------------------------------------------------------------
[docs] def get_begin(self):
"""Return the begin sppasPoint instance."""
return self.__begin
# -----------------------------------------------------------------------
[docs] def set_begin(self, tp):
"""Set the begin of the interval to a new sppasPoint.
Attention: it is a reference assignment.
:param tp: (sppasPoint)
"""
if isinstance(tp, sppasPoint) is False:
raise AnnDataTypeError(tp, "sppasPoint")
if sppasInterval.check_types(tp, self.__end) is False:
raise AnnDataEqTypeError(tp, self.__end)
if sppasInterval.check_interval_bounds(tp, self.__end) is False:
raise IntervalBoundsError(tp, self.__end)
# assign the reference
self.__begin = tp
# -----------------------------------------------------------------------
[docs] def get_end(self):
"""Return the end sppasPoint instance."""
return self.__end
# -----------------------------------------------------------------------
[docs] def set_end(self, tp):
"""Set the end of the interval to a new sppasPoint.
Attention: it is a reference assignment.
:param tp: (sppasPoint)
"""
if isinstance(tp, sppasPoint) is False:
raise AnnDataTypeError(tp, "sppasPoint")
if sppasInterval.check_types(self.__begin, tp) is False:
raise AnnDataEqTypeError(self.__begin, tp)
if sppasInterval.check_interval_bounds(self.__begin, tp) is False:
raise IntervalBoundsError(self.__begin, tp)
# assign the reference
self.__end = tp
# -----------------------------------------------------------------------
[docs] def is_bound(self, point):
"""Return True if point is the begin or the end of the interval."""
return self.__begin == point or self.__end == point
# -----------------------------------------------------------------------
[docs] def combine(self, other):
"""Return a sppasInterval, the combination of two intervals.
:param other: (sppasInterval) the other interval to combine with.
"""
if isinstance(other, sppasInterval) is False:
AnnDataTypeError(other, "sppasInterval")
if self > other:
other, self = self, other
if self.__end <= other.get_begin():
return sppasInterval(self.__begin, other.get_end())
return sppasInterval(other.get_begin(), self.__end)
# -----------------------------------------------------------------------
[docs] def union(self, other):
"""Return a sppasInterval representing the union of two intervals.
:param other: (sppasInterval) the other interval to merge with.
"""
if isinstance(other, sppasInterval) is False:
AnnDataTypeError(other, "sppasInterval")
if self > other:
other, self = self, other
return sppasInterval(self.__begin, other.get_end())
# -----------------------------------------------------------------------
[docs] def duration(self):
"""Overridden. Return the duration of the time interval.
:returns: (sppasDuration) Duration and its vagueness.
"""
# duration is the difference between the midpoints
value = self.__end.get_midpoint() - self.__begin.get_midpoint()
# vagueness of the duration is based on begin/end radius values
vagueness = 0
if self.__begin.get_radius() is not None:
vagueness += self.__begin.get_radius()
if self.__end.get_radius() is not None:
vagueness += self.__end.get_radius()
return sppasDuration(value, vagueness)
# -----------------------------------------------------------------------
[docs] def middle(self):
"""Return a sppasPoint() at the middle of the time interval.
To be tested.
:returns: (sppasPoint)
"""
duration = self.duration()
m = float(self.__begin.get_midpoint()) + (float(duration.get_value()) / 2.)
r = float(duration.get_margin()) / 2.
if self.is_float():
return sppasPoint(m, r)
return sppasPoint(int(m), int(r))
# -----------------------------------------------------------------------
[docs] def middle_value(self):
"""Return the middle value of the time interval.
Return a float value even if points are integers.
:returns: (float) value.
"""
duration = self.__end.get_midpoint() - self.__begin.get_midpoint()
return float(self.__begin.get_midpoint()) + float(duration) / 2.
# -----------------------------------------------------------------------
[docs] def set_radius(self, radius):
"""Set a radius value to begin and end points.
:param radius: (int or float)
:raise: ValueError
"""
duration = self.__end.get_midpoint() - self.__begin.get_midpoint()
if (float(radius)*2.) > float(duration):
raise ValueError(
"The radius value {r} can't be more than half-duration of "
"the interval [{b};{e}]."
"".format(r=radius,
b=self.__begin.get_midpoint(),
e=self.__end.get_midpoint()))
self.__begin.set_radius(radius)
self.__end.set_radius(radius)
# -----------------------------------------------------------------------
[docs] def shift(self, delay):
"""Shift the interval to a given delay.
:param delay: (int, float) delay to shift bounds
:raise: AnnDataTypeError
"""
self.__begin.shift(delay)
self.__end.shift(delay)
# -----------------------------------------------------------------------
[docs] def is_int(self):
return self.__begin.is_int()
[docs] def is_float(self):
return self.__begin.is_float()
# -----------------------------------------------------------------------
[docs] @staticmethod
def check_interval_bounds(begin, end):
"""Check bounds of a virtual interval.
:param begin: (sppasPoint)
:param end: (sppasPoint)
"""
if begin.get_midpoint() >= end.get_midpoint():
return False
if begin.get_radius() is not None and end.get_radius() is not None:
if begin.get_midpoint() - begin.get_radius() > \
end.get_midpoint() - end.get_radius():
return False
return True
# -----------------------------------------------------------------------
[docs] @staticmethod
def check_types(begin, end):
"""True only if begin and end are both the same types of sppasPoint.
:param begin: any kind of data
:param end: any kind of data
:returns: Boolean
"""
try:
begin.get_midpoint()
end.get_midpoint()
except AttributeError:
return False
return isinstance(begin.get_midpoint(), type(end.get_midpoint()))
# -----------------------------------------------------------------------
# Overloads
# -----------------------------------------------------------------------
def __format__(self, fmt):
return str(self).__format__(fmt)
# -----------------------------------------------------------------------
def __repr__(self):
return "sppasInterval: [{!s:s},{!s:s}]".format(self.get_begin(),
self.get_end())
# -----------------------------------------------------------------------
def __str__(self):
return "[{!s:s},{!s:s}]".format(self.get_begin(), self.get_end())
# -----------------------------------------------------------------------
def __contains__(self, other):
"""Return True if the given data is contained in the interval.
:param other: (sppasInterval, sppasPoint, int, float)
"""
if isinstance(other, (sppasInterval,
sppasPoint,
float, int)) is False:
raise AnnDataTypeError(other,
"sppasInterval, sppasPoint, float, int")
if isinstance(other, sppasInterval):
return (self.__begin <= other.get_begin() and
other.get_end() <= self.__end)
return self.__begin <= other <= self.__end
# -----------------------------------------------------------------------
def __eq__(self, other):
"""Equal.
:param other: (sppasInterval) the other interval to compare with.
"""
if isinstance(other, sppasInterval) is False:
return False
return (self.__begin == other.get_begin() and
self.__end == other.get_end())
# -----------------------------------------------------------------------
def __lt__(self, other):
"""LowerThan.
:param other: (sppasInterval, sppasPoint, float, int)
"""
if isinstance(other, (sppasPoint, float, int)):
return self.__end < other
if isinstance(other, sppasInterval) is False:
return False
return self.__begin < other.get_begin()
# -----------------------------------------------------------------------
def __gt__(self, other):
"""GreaterThan.
:param other: (sppasInterval, sppasPoint, float, int)
"""
if isinstance(other, (int, float, sppasPoint)):
return self.__begin > other
if isinstance(other, sppasInterval) is False:
return False
return self.__begin > other.get_begin()