praatio.data_classes.point_tier API documentation

# praatio.data_classes.point_tier

A PointTier is a tier containing an array of points -- data that exists at a specific point in time

View Source

      1"""
      2A PointTier is a tier containing an array of points -- data that exists at a specific point in time
      3"""
      4from typing import List, Tuple, Optional, Any, Sequence
      5
      6from typing_extensions import Literal
      7
      8from praatio.utilities.constants import (
      9    Point,
     10    POINT_TIER,
     11)
     12from praatio.utilities import constants
     13from praatio.utilities import errors
     14from praatio.utilities import utils
     15from praatio.utilities import my_math
     16
     17from praatio.data_classes import textgrid_tier
     18
     19
     20def _homogenizeEntries(entries):
     21    """
     22    Enforces consistency in points
     23
     24    - converts all entries to points
     25    - removes whitespace in labels
     26    - sorts values by time
     27    """
     28    processedEntries = [Point(float(time), label.strip()) for time, label in entries]
     29    processedEntries.sort()
     30    return processedEntries
     31
     32
     33def _calculateMinAndMaxTime(entries: Sequence[Point], minT=None, maxT=None):
     34    timeList = [time for time, label in entries]
     35    if minT is not None:
     36        timeList.append(float(minT))
     37    if maxT is not None:
     38        timeList.append(float(maxT))
     39
     40    try:
     41        calculatedMinT = min(timeList)
     42        calculatedMaxT = max(timeList)
     43    except ValueError:
     44        raise errors.TimelessTextgridTierException()
     45
     46    return (calculatedMinT, calculatedMaxT)
     47
     48
     49class PointTier(textgrid_tier.TextgridTier):
     50    tierType = POINT_TIER
     51    entryType = Point
     52
     53    def __init__(
     54        self,
     55        name: str,
     56        entries: List[Point],
     57        minT: Optional[float] = None,
     58        maxT: Optional[float] = None,
     59    ):
     60        """A point tier is for annotating instaneous events
     61
     62        The entries is of the form:
     63        [(timeVal1, label1), (timeVal2, label2), ]
     64
     65        The data stored in the labels can be anything but will
     66        be interpreted as text by praatio (the label could be descriptive
     67        text e.g. ('peak point here') or numerical data e.g. (pitch values
     68        like '132'))
     69        """
     70        entries = _homogenizeEntries(entries)
     71        calculatedMinT, calculatedMaxT = _calculateMinAndMaxTime(entries, minT, maxT)
     72
     73        super(PointTier, self).__init__(name, entries, calculatedMinT, calculatedMaxT)
     74
     75    @property
     76    def timestamps(self) -> List[float]:
     77        """All unique timestamps used in this tier"""
     78        tmpTimestamps = [time for time, _ in self.entries]
     79
     80        uniqueTimestamps = list(set(tmpTimestamps))
     81        uniqueTimestamps.sort()
     82
     83        return uniqueTimestamps
     84
     85    def crop(
     86        self,
     87        cropStart: float,
     88        cropEnd: float,
     89        mode: Literal["strict", "lax", "truncated"] = "lax",
     90        rebaseToZero: bool = True,
     91    ) -> "PointTier":
     92        """Creates a new tier containing all entries inside the new interval
     93
     94        Args:
     95            cropStart:
     96            cropEnd:
     97            mode: Mode is ignored.  This parameter is kept for
     98                compatibility with IntervalTier.crop()
     99            rebaseToZero: if True, all entries will have their
    100                timestamps subtracted by *cropStart*.
    101
    102        Returns:
    103            the modified version of the current tier
    104        """
    105        if cropStart >= cropEnd:
    106            raise errors.ArgumentError(
    107                f"Crop error: start time ({cropStart}) must occur before end time ({cropEnd})"
    108            )
    109
    110        newEntries = []
    111
    112        for entry in self.entries:
    113            timestamp = entry.time
    114
    115            if timestamp >= cropStart and timestamp <= cropEnd:
    116                newEntries.append(entry)
    117
    118        if rebaseToZero is True:
    119            newEntries = [
    120                Point(timeV - cropStart, label) for timeV, label in newEntries
    121            ]
    122            minT = 0.0
    123            maxT = cropEnd - cropStart
    124        else:
    125            minT = cropStart
    126            maxT = cropEnd
    127
    128        return PointTier(self.name, newEntries, minT, maxT)
    129
    130    def deleteEntry(self, entry: Point) -> None:
    131        """Removes an entry from the entries"""
    132        self._entries.pop(self._entries.index(entry))
    133
    134    def dejitter(
    135        self, referenceTier: textgrid_tier.TextgridTier, maxDifference: float = 0.001
    136    ) -> "PointTier":
    137        """
    138        Set timestamps in this tier to be the same as values in the reference tier
    139
    140        Timestamps will only be moved if they are less than maxDifference away from the
    141        reference time.
    142
    143        This can be used to correct minor alignment errors between tiers, as made when
    144        annotating files manually, etc.
    145
    146        Args:
    147            referenceTier: the IntervalTier or PointTier to use as a reference
    148            maxDifference: the maximum amount to allow timestamps to be moved by
    149
    150        Returns:
    151            the modified version of the current tier
    152        """
    153        referenceTimestamps = referenceTier.timestamps
    154
    155        newEntries = []
    156        for time, label in self.entries:
    157            timeCompare = min(referenceTimestamps, key=lambda x: abs(x - time))
    158
    159            if my_math.lessThanOrEqual(abs(time - timeCompare), maxDifference):
    160                time = timeCompare
    161            newEntries.append((time, label))
    162
    163        return self.new(entries=newEntries)
    164
    165    def editTimestamps(
    166        self,
    167        offset: float,
    168        reportingMode: Literal["silence", "warning", "error"] = "warning",
    169    ) -> "PointTier":
    170        """Modifies all timestamps by a constant amount
    171
    172        Args:
    173            offset:
    174            reportingMode: one of "silence", "warning", or "error". This flag
    175                determines the behavior if an entries moves outside of minTimestamp
    176                or maxTimestamp after being edited
    177
    178        Returns:
    179            the modified version of the current tier
    180        """
    181        utils.validateOption(
    182            "reportingMode", reportingMode, constants.ErrorReportingMode
    183        )
    184        errorReporter = utils.getErrorReporter(reportingMode)
    185
    186        newEntries: List[Point] = []
    187        for timestamp, label in self.entries:
    188            newTimestamp = timestamp + offset
    189            utils.checkIsUndershoot(newTimestamp, self.minTimestamp, errorReporter)
    190            utils.checkIsOvershoot(newTimestamp, self.maxTimestamp, errorReporter)
    191
    192            if newTimestamp < 0:
    193                continue
    194
    195            newEntries.append(Point(newTimestamp, label))
    196
    197        # Determine new min and max timestamps
    198        timeList = [float(point.time) for point in newEntries]
    199        newMin = min(timeList)
    200        newMax = max(timeList)
    201
    202        if newMin > self.minTimestamp:
    203            newMin = self.minTimestamp
    204
    205        if newMax < self.maxTimestamp:
    206            newMax = self.maxTimestamp
    207
    208        return PointTier(self.name, newEntries, newMin, newMax)
    209
    210    def getValuesAtPoints(
    211        self,
    212        dataTupleList: List[Tuple[float, ...]],
    213        fuzzyMatching: bool = False,
    214    ) -> List[Tuple[Any, ...]]:
    215        """Get the values that occur at points in the point tier
    216
    217        The procedure assumes that all data is ordered in time.
    218        dataTupleList should be in the form
    219        [(t1, v1a, v1b, ..), (t2, v2a, v2b, ..), ..]
    220
    221        It returns the data in the form of
    222        [(t1, v1a, v1b, ..), (t2, v2a, v2b), ..]
    223
    224        The procedure makes one pass through dataTupleList and one
    225        pass through self.entries.  If the data is not sequentially
    226        ordered, the incorrect response will be returned.
    227
    228        Args:
    229            dataTupleList:
    230            fuzzyMatching: if True, if there is not a feature value
    231                at a point, the nearest feature value will be taken.
    232
    233        Returns:
    234            A list of values that exist at the given timepoints
    235        """
    236
    237        currentIndex = 0
    238        retList = []
    239
    240        sortedDataTupleList = sorted(dataTupleList)
    241        for timestamp, label in self.entries:
    242            retTuple = utils.getValueAtTime(
    243                timestamp,
    244                sortedDataTupleList,
    245                fuzzyMatching=fuzzyMatching,
    246                startI=currentIndex,
    247            )
    248            retRow, currentIndex = retTuple
    249            retList.append(retRow)
    250
    251        return retList
    252
    253    def eraseRegion(
    254        self,
    255        start: float,
    256        end: float,
    257        collisionMode: Literal["truncate", "categorical", "error"] = "error",
    258        doShrink: bool = True,
    259    ) -> "PointTier":
    260        """Makes a region in a tier blank (removes all contained entries)
    261
    262        Args:
    263            start: the start of the deletion interval
    264            end: the end of the deletion interval
    265            collisionMode: Ignored for the moment (added for compatibility with
    266                eraseRegion() for Interval Tiers)
    267            doShrink: if True, moves leftward by (/end/ - /start/) all points
    268                to the right of /end/
    269
    270        Returns:
    271            The modified version of the current tier
    272        """
    273
    274        newTier = self.new()
    275        croppedTier = newTier.crop(start, end, constants.CropCollision.TRUNCATED, False)
    276        matchList = croppedTier.entries
    277
    278        if len(matchList) > 0:
    279            # Remove all the matches from the entries
    280            # Go in reverse order because we're destructively altering
    281            # the order of the list (messes up index order)
    282            for point in matchList[::-1]:
    283                newTier.deleteEntry(point)
    284
    285        if doShrink is True:
    286            newEntries = []
    287            diff = end - start
    288            for point in newTier.entries:
    289                if point.time < start:
    290                    newEntries.append(point)
    291                elif point.time > end:
    292                    newEntries.append(Point(point.time - diff, point.label))
    293
    294            newMax = newTier.maxTimestamp - diff
    295            newTier = newTier.new(entries=newEntries, maxTimestamp=newMax)
    296
    297        return newTier
    298
    299    def insertEntry(
    300        self,
    301        entry: Point,
    302        collisionMode: Literal["replace", "merge", "error"] = "error",
    303        collisionReportingMode: Literal["silence", "warning"] = "warning",
    304    ) -> None:
    305        """Inserts an interval into the tier
    306
    307        Args:
    308            entry: the entry to insert
    309            collisionMode: determines the behavior if intervals exist in
    310                the insertion area.
    311                - 'replace', existing items will be removed
    312                - 'merge', inserting item will be fused with existing items
    313                - 'error', will throw TextgridCollisionException
    314            collisionReportingMode:
    315
    316        Returns:
    317            None
    318        """
    319
    320        utils.validateOption(
    321            "collisionMode", collisionMode, constants.IntervalCollision
    322        )
    323        utils.validateOption(
    324            "collisionReportingMode",
    325            collisionReportingMode,
    326            constants.ErrorReportingMode,
    327        )
    328        collisionReporter = utils.getErrorReporter(collisionReportingMode)
    329
    330        if not isinstance(entry, Point):
    331            newPoint = Point(entry[0], entry[1])
    332        else:
    333            newPoint = entry
    334
    335        matchList = []
    336        i = None
    337        for i, point in enumerate(self.entries):
    338            if point.time == newPoint.time:
    339                matchList.append(point)
    340                break
    341
    342        if len(matchList) == 0:
    343            self._entries.append(newPoint)
    344
    345        elif collisionMode == constants.IntervalCollision.REPLACE:
    346            self.deleteEntry(self.entries[i])
    347            self._entries.append(newPoint)
    348
    349        elif collisionMode == constants.IntervalCollision.MERGE:
    350            oldPoint = self.entries[i]
    351            mergedPoint = Point(
    352                newPoint.time, "-".join([oldPoint.label, newPoint.label])
    353            )
    354            self.deleteEntry(self._entries[i])
    355            self._entries.append(mergedPoint)
    356
    357        else:
    358            raise errors.CollisionError(
    359                f"Attempted to insert interval {point} into tier {self.name} "
    360                "of textgrid but overlapping entries "
    361                f"{[tuple(interval) for interval in matchList]} "
    362                "already exist"
    363            )
    364
    365        self.sort()
    366
    367        if len(matchList) != 0:
    368            collisionReporter(
    369                errors.CollisionError,
    370                f"Collision warning for ({point}) with items ({matchList}) of tier '{self.name}'",
    371            )
    372
    373    def insertSpace(
    374        self,
    375        start: float,
    376        duration: float,
    377        _collisionMode: Literal["stretch", "split", "no_change", "error"] = "error",
    378    ) -> "PointTier":
    379        """Inserts a region into the tier
    380
    381        Args:
    382            start: the start time to insert a space at
    383            duration: the duration of the space to insert
    384            collisionMode: Ignored for the moment (added for compatibility
    385                with insertSpace() for Interval Tiers)
    386
    387        Returns:
    388            PointTier: the modified version of the current tier
    389        """
    390
    391        newEntries = []
    392        for point in self.entries:
    393            if point.time <= start:
    394                newEntries.append(point)
    395            elif point.time > start:
    396                newEntries.append(Point(point.time + duration, point.label))
    397
    398        newTier = self.new(
    399            entries=newEntries, maxTimestamp=self.maxTimestamp + duration
    400        )
    401
    402        return newTier
    403
    404    def validate(
    405        self, reportingMode: Literal["silence", "warning", "error"] = "warning"
    406    ) -> bool:
    407        """Validate this tier
    408
    409        Returns whether the tier is valid or not. If reportingMode is "warning"
    410        or "error" this will also print on error or stop execution, respectively.
    411
    412        Args:
    413            reportingMode: Determines the behavior if there is a size difference
    414                between the maxTimestamp in the tier and the current textgrid.
    415
    416        Returns:
    417            True if this tier is valid; False otherwise
    418        """
    419        utils.validateOption(
    420            "reportingMode", reportingMode, constants.ErrorReportingMode
    421        )
    422        errorReporter = utils.getErrorReporter(reportingMode)
    423
    424        isValid = True
    425        previousPoint = None
    426        for point in self.entries:
    427            if previousPoint and previousPoint.time > point.time:
    428                isValid = False
    429                errorReporter(
    430                    errors.TextgridStateError,
    431                    f"Points are not sorted in time: "
    432                    f"[({previousPoint}), ({point})]",
    433                )
    434
    435            if utils.checkIsUndershoot(point.time, self.minTimestamp, errorReporter):
    436                isValid = False
    437
    438            if utils.checkIsOvershoot(point.time, self.maxTimestamp, errorReporter):
    439                isValid = False
    440
    441            previousPoint = point
    442
    443        return isValid

class PointTier(praatio.data_classes.textgrid_tier.TextgridTier): View Source

     50class PointTier(textgrid_tier.TextgridTier):
     51    tierType = POINT_TIER
     52    entryType = Point
     53
     54    def __init__(
     55        self,
     56        name: str,
     57        entries: List[Point],
     58        minT: Optional[float] = None,
     59        maxT: Optional[float] = None,
     60    ):
     61        """A point tier is for annotating instaneous events
     62
     63        The entries is of the form:
     64        [(timeVal1, label1), (timeVal2, label2), ]
     65
     66        The data stored in the labels can be anything but will
     67        be interpreted as text by praatio (the label could be descriptive
     68        text e.g. ('peak point here') or numerical data e.g. (pitch values
     69        like '132'))
     70        """
     71        entries = _homogenizeEntries(entries)
     72        calculatedMinT, calculatedMaxT = _calculateMinAndMaxTime(entries, minT, maxT)
     73
     74        super(PointTier, self).__init__(name, entries, calculatedMinT, calculatedMaxT)
     75
     76    @property
     77    def timestamps(self) -> List[float]:
     78        """All unique timestamps used in this tier"""
     79        tmpTimestamps = [time for time, _ in self.entries]
     80
     81        uniqueTimestamps = list(set(tmpTimestamps))
     82        uniqueTimestamps.sort()
     83
     84        return uniqueTimestamps
     85
     86    def crop(
     87        self,
     88        cropStart: float,
     89        cropEnd: float,
     90        mode: Literal["strict", "lax", "truncated"] = "lax",
     91        rebaseToZero: bool = True,
     92    ) -> "PointTier":
     93        """Creates a new tier containing all entries inside the new interval
     94
     95        Args:
     96            cropStart:
     97            cropEnd:
     98            mode: Mode is ignored.  This parameter is kept for
     99                compatibility with IntervalTier.crop()
    100            rebaseToZero: if True, all entries will have their
    101                timestamps subtracted by *cropStart*.
    102
    103        Returns:
    104            the modified version of the current tier
    105        """
    106        if cropStart >= cropEnd:
    107            raise errors.ArgumentError(
    108                f"Crop error: start time ({cropStart}) must occur before end time ({cropEnd})"
    109            )
    110
    111        newEntries = []
    112
    113        for entry in self.entries:
    114            timestamp = entry.time
    115
    116            if timestamp >= cropStart and timestamp <= cropEnd:
    117                newEntries.append(entry)
    118
    119        if rebaseToZero is True:
    120            newEntries = [
    121                Point(timeV - cropStart, label) for timeV, label in newEntries
    122            ]
    123            minT = 0.0
    124            maxT = cropEnd - cropStart
    125        else:
    126            minT = cropStart
    127            maxT = cropEnd
    128
    129        return PointTier(self.name, newEntries, minT, maxT)
    130
    131    def deleteEntry(self, entry: Point) -> None:
    132        """Removes an entry from the entries"""
    133        self._entries.pop(self._entries.index(entry))
    134
    135    def dejitter(
    136        self, referenceTier: textgrid_tier.TextgridTier, maxDifference: float = 0.001
    137    ) -> "PointTier":
    138        """
    139        Set timestamps in this tier to be the same as values in the reference tier
    140
    141        Timestamps will only be moved if they are less than maxDifference away from the
    142        reference time.
    143
    144        This can be used to correct minor alignment errors between tiers, as made when
    145        annotating files manually, etc.
    146
    147        Args:
    148            referenceTier: the IntervalTier or PointTier to use as a reference
    149            maxDifference: the maximum amount to allow timestamps to be moved by
    150
    151        Returns:
    152            the modified version of the current tier
    153        """
    154        referenceTimestamps = referenceTier.timestamps
    155
    156        newEntries = []
    157        for time, label in self.entries:
    158            timeCompare = min(referenceTimestamps, key=lambda x: abs(x - time))
    159
    160            if my_math.lessThanOrEqual(abs(time - timeCompare), maxDifference):
    161                time = timeCompare
    162            newEntries.append((time, label))
    163
    164        return self.new(entries=newEntries)
    165
    166    def editTimestamps(
    167        self,
    168        offset: float,
    169        reportingMode: Literal["silence", "warning", "error"] = "warning",
    170    ) -> "PointTier":
    171        """Modifies all timestamps by a constant amount
    172
    173        Args:
    174            offset:
    175            reportingMode: one of "silence", "warning", or "error". This flag
    176                determines the behavior if an entries moves outside of minTimestamp
    177                or maxTimestamp after being edited
    178
    179        Returns:
    180            the modified version of the current tier
    181        """
    182        utils.validateOption(
    183            "reportingMode", reportingMode, constants.ErrorReportingMode
    184        )
    185        errorReporter = utils.getErrorReporter(reportingMode)
    186
    187        newEntries: List[Point] = []
    188        for timestamp, label in self.entries:
    189            newTimestamp = timestamp + offset
    190            utils.checkIsUndershoot(newTimestamp, self.minTimestamp, errorReporter)
    191            utils.checkIsOvershoot(newTimestamp, self.maxTimestamp, errorReporter)
    192
    193            if newTimestamp < 0:
    194                continue
    195
    196            newEntries.append(Point(newTimestamp, label))
    197
    198        # Determine new min and max timestamps
    199        timeList = [float(point.time) for point in newEntries]
    200        newMin = min(timeList)
    201        newMax = max(timeList)
    202
    203        if newMin > self.minTimestamp:
    204            newMin = self.minTimestamp
    205
    206        if newMax < self.maxTimestamp:
    207            newMax = self.maxTimestamp
    208
    209        return PointTier(self.name, newEntries, newMin, newMax)
    210
    211    def getValuesAtPoints(
    212        self,
    213        dataTupleList: List[Tuple[float, ...]],
    214        fuzzyMatching: bool = False,
    215    ) -> List[Tuple[Any, ...]]:
    216        """Get the values that occur at points in the point tier
    217
    218        The procedure assumes that all data is ordered in time.
    219        dataTupleList should be in the form
    220        [(t1, v1a, v1b, ..), (t2, v2a, v2b, ..), ..]
    221
    222        It returns the data in the form of
    223        [(t1, v1a, v1b, ..), (t2, v2a, v2b), ..]
    224
    225        The procedure makes one pass through dataTupleList and one
    226        pass through self.entries.  If the data is not sequentially
    227        ordered, the incorrect response will be returned.
    228
    229        Args:
    230            dataTupleList:
    231            fuzzyMatching: if True, if there is not a feature value
    232                at a point, the nearest feature value will be taken.
    233
    234        Returns:
    235            A list of values that exist at the given timepoints
    236        """
    237
    238        currentIndex = 0
    239        retList = []
    240
    241        sortedDataTupleList = sorted(dataTupleList)
    242        for timestamp, label in self.entries:
    243            retTuple = utils.getValueAtTime(
    244                timestamp,
    245                sortedDataTupleList,
    246                fuzzyMatching=fuzzyMatching,
    247                startI=currentIndex,
    248            )
    249            retRow, currentIndex = retTuple
    250            retList.append(retRow)
    251
    252        return retList
    253
    254    def eraseRegion(
    255        self,
    256        start: float,
    257        end: float,
    258        collisionMode: Literal["truncate", "categorical", "error"] = "error",
    259        doShrink: bool = True,
    260    ) -> "PointTier":
    261        """Makes a region in a tier blank (removes all contained entries)
    262
    263        Args:
    264            start: the start of the deletion interval
    265            end: the end of the deletion interval
    266            collisionMode: Ignored for the moment (added for compatibility with
    267                eraseRegion() for Interval Tiers)
    268            doShrink: if True, moves leftward by (/end/ - /start/) all points
    269                to the right of /end/
    270
    271        Returns:
    272            The modified version of the current tier
    273        """
    274
    275        newTier = self.new()
    276        croppedTier = newTier.crop(start, end, constants.CropCollision.TRUNCATED, False)
    277        matchList = croppedTier.entries
    278
    279        if len(matchList) > 0:
    280            # Remove all the matches from the entries
    281            # Go in reverse order because we're destructively altering
    282            # the order of the list (messes up index order)
    283            for point in matchList[::-1]:
    284                newTier.deleteEntry(point)
    285
    286        if doShrink is True:
    287            newEntries = []
    288            diff = end - start
    289            for point in newTier.entries:
    290                if point.time < start:
    291                    newEntries.append(point)
    292                elif point.time > end:
    293                    newEntries.append(Point(point.time - diff, point.label))
    294
    295            newMax = newTier.maxTimestamp - diff
    296            newTier = newTier.new(entries=newEntries, maxTimestamp=newMax)
    297
    298        return newTier
    299
    300    def insertEntry(
    301        self,
    302        entry: Point,
    303        collisionMode: Literal["replace", "merge", "error"] = "error",
    304        collisionReportingMode: Literal["silence", "warning"] = "warning",
    305    ) -> None:
    306        """Inserts an interval into the tier
    307
    308        Args:
    309            entry: the entry to insert
    310            collisionMode: determines the behavior if intervals exist in
    311                the insertion area.
    312                - 'replace', existing items will be removed
    313                - 'merge', inserting item will be fused with existing items
    314                - 'error', will throw TextgridCollisionException
    315            collisionReportingMode:
    316
    317        Returns:
    318            None
    319        """
    320
    321        utils.validateOption(
    322            "collisionMode", collisionMode, constants.IntervalCollision
    323        )
    324        utils.validateOption(
    325            "collisionReportingMode",
    326            collisionReportingMode,
    327            constants.ErrorReportingMode,
    328        )
    329        collisionReporter = utils.getErrorReporter(collisionReportingMode)
    330
    331        if not isinstance(entry, Point):
    332            newPoint = Point(entry[0], entry[1])
    333        else:
    334            newPoint = entry
    335
    336        matchList = []
    337        i = None
    338        for i, point in enumerate(self.entries):
    339            if point.time == newPoint.time:
    340                matchList.append(point)
    341                break
    342
    343        if len(matchList) == 0:
    344            self._entries.append(newPoint)
    345
    346        elif collisionMode == constants.IntervalCollision.REPLACE:
    347            self.deleteEntry(self.entries[i])
    348            self._entries.append(newPoint)
    349
    350        elif collisionMode == constants.IntervalCollision.MERGE:
    351            oldPoint = self.entries[i]
    352            mergedPoint = Point(
    353                newPoint.time, "-".join([oldPoint.label, newPoint.label])
    354            )
    355            self.deleteEntry(self._entries[i])
    356            self._entries.append(mergedPoint)
    357
    358        else:
    359            raise errors.CollisionError(
    360                f"Attempted to insert interval {point} into tier {self.name} "
    361                "of textgrid but overlapping entries "
    362                f"{[tuple(interval) for interval in matchList]} "
    363                "already exist"
    364            )
    365
    366        self.sort()
    367
    368        if len(matchList) != 0:
    369            collisionReporter(
    370                errors.CollisionError,
    371                f"Collision warning for ({point}) with items ({matchList}) of tier '{self.name}'",
    372            )
    373
    374    def insertSpace(
    375        self,
    376        start: float,
    377        duration: float,
    378        _collisionMode: Literal["stretch", "split", "no_change", "error"] = "error",
    379    ) -> "PointTier":
    380        """Inserts a region into the tier
    381
    382        Args:
    383            start: the start time to insert a space at
    384            duration: the duration of the space to insert
    385            collisionMode: Ignored for the moment (added for compatibility
    386                with insertSpace() for Interval Tiers)
    387
    388        Returns:
    389            PointTier: the modified version of the current tier
    390        """
    391
    392        newEntries = []
    393        for point in self.entries:
    394            if point.time <= start:
    395                newEntries.append(point)
    396            elif point.time > start:
    397                newEntries.append(Point(point.time + duration, point.label))
    398
    399        newTier = self.new(
    400            entries=newEntries, maxTimestamp=self.maxTimestamp + duration
    401        )
    402
    403        return newTier
    404
    405    def validate(
    406        self, reportingMode: Literal["silence", "warning", "error"] = "warning"
    407    ) -> bool:
    408        """Validate this tier
    409
    410        Returns whether the tier is valid or not. If reportingMode is "warning"
    411        or "error" this will also print on error or stop execution, respectively.
    412
    413        Args:
    414            reportingMode: Determines the behavior if there is a size difference
    415                between the maxTimestamp in the tier and the current textgrid.
    416
    417        Returns:
    418            True if this tier is valid; False otherwise
    419        """
    420        utils.validateOption(
    421            "reportingMode", reportingMode, constants.ErrorReportingMode
    422        )
    423        errorReporter = utils.getErrorReporter(reportingMode)
    424
    425        isValid = True
    426        previousPoint = None
    427        for point in self.entries:
    428            if previousPoint and previousPoint.time > point.time:
    429                isValid = False
    430                errorReporter(
    431                    errors.TextgridStateError,
    432                    f"Points are not sorted in time: "
    433                    f"[({previousPoint}), ({point})]",
    434                )
    435
    436            if utils.checkIsUndershoot(point.time, self.minTimestamp, errorReporter):
    437                isValid = False
    438
    439            if utils.checkIsOvershoot(point.time, self.maxTimestamp, errorReporter):
    440                isValid = False
    441
    442            previousPoint = point
    443
    444        return isValid

Helper class that provides a standard way to create an ABC using inheritance.

PointTier( name: str, entries: List[praatio.utilities.constants.Point], minT: Optional[float] = None, maxT: Optional[float] = None) View Source

    54    def __init__(
    55        self,
    56        name: str,
    57        entries: List[Point],
    58        minT: Optional[float] = None,
    59        maxT: Optional[float] = None,
    60    ):
    61        """A point tier is for annotating instaneous events
    62
    63        The entries is of the form:
    64        [(timeVal1, label1), (timeVal2, label2), ]
    65
    66        The data stored in the labels can be anything but will
    67        be interpreted as text by praatio (the label could be descriptive
    68        text e.g. ('peak point here') or numerical data e.g. (pitch values
    69        like '132'))
    70        """
    71        entries = _homogenizeEntries(entries)
    72        calculatedMinT, calculatedMaxT = _calculateMinAndMaxTime(entries, minT, maxT)
    73
    74        super(PointTier, self).__init__(name, entries, calculatedMinT, calculatedMaxT)

A point tier is for annotating instaneous events

The entries is of the form: [(timeVal1, label1), (timeVal2, label2), ]

The data stored in the labels can be anything but will be interpreted as text by praatio (the label could be descriptive text e.g. ('peak point here') or numerical data e.g. (pitch values like '132'))

tierType = 'TextTier'
entryType = <class 'praatio.utilities.constants.Point'>
timestamps: List[float]

All unique timestamps used in this tier

def crop( self, cropStart: float, cropEnd: float, mode: Literal['strict', 'lax', 'truncated'] = 'lax', rebaseToZero: bool = True) -> PointTier: View Source

     86    def crop(
     87        self,
     88        cropStart: float,
     89        cropEnd: float,
     90        mode: Literal["strict", "lax", "truncated"] = "lax",
     91        rebaseToZero: bool = True,
     92    ) -> "PointTier":
     93        """Creates a new tier containing all entries inside the new interval
     94
     95        Args:
     96            cropStart:
     97            cropEnd:
     98            mode: Mode is ignored.  This parameter is kept for
     99                compatibility with IntervalTier.crop()
    100            rebaseToZero: if True, all entries will have their
    101                timestamps subtracted by *cropStart*.
    102
    103        Returns:
    104            the modified version of the current tier
    105        """
    106        if cropStart >= cropEnd:
    107            raise errors.ArgumentError(
    108                f"Crop error: start time ({cropStart}) must occur before end time ({cropEnd})"
    109            )
    110
    111        newEntries = []
    112
    113        for entry in self.entries:
    114            timestamp = entry.time
    115
    116            if timestamp >= cropStart and timestamp <= cropEnd:
    117                newEntries.append(entry)
    118
    119        if rebaseToZero is True:
    120            newEntries = [
    121                Point(timeV - cropStart, label) for timeV, label in newEntries
    122            ]
    123            minT = 0.0
    124            maxT = cropEnd - cropStart
    125        else:
    126            minT = cropStart
    127            maxT = cropEnd
    128
    129        return PointTier(self.name, newEntries, minT, maxT)

Creates a new tier containing all entries inside the new interval

###### Arguments:

- cropStart:
- cropEnd:
- mode: Mode is ignored. This parameter is kept for compatibility with IntervalTier.crop()
- rebaseToZero: if True, all entries will have their timestamps subtracted by cropStart.

###### Returns:

> the modified version of the current tier

def deleteEntry(self, entry: praatio.utilities.constants.Point) -> None: View Source

    131    def deleteEntry(self, entry: Point) -> None:
    132        """Removes an entry from the entries"""
    133        self._entries.pop(self._entries.index(entry))

Removes an entry from the entries

def dejitter( self, referenceTier: praatio.data_classes.textgrid_tier.TextgridTier, maxDifference: float = 0.001) -> PointTier: View Source

    135    def dejitter(
    136        self, referenceTier: textgrid_tier.TextgridTier, maxDifference: float = 0.001
    137    ) -> "PointTier":
    138        """
    139        Set timestamps in this tier to be the same as values in the reference tier
    140
    141        Timestamps will only be moved if they are less than maxDifference away from the
    142        reference time.
    143
    144        This can be used to correct minor alignment errors between tiers, as made when
    145        annotating files manually, etc.
    146
    147        Args:
    148            referenceTier: the IntervalTier or PointTier to use as a reference
    149            maxDifference: the maximum amount to allow timestamps to be moved by
    150
    151        Returns:
    152            the modified version of the current tier
    153        """
    154        referenceTimestamps = referenceTier.timestamps
    155
    156        newEntries = []
    157        for time, label in self.entries:
    158            timeCompare = min(referenceTimestamps, key=lambda x: abs(x - time))
    159
    160            if my_math.lessThanOrEqual(abs(time - timeCompare), maxDifference):
    161                time = timeCompare
    162            newEntries.append((time, label))
    163
    164        return self.new(entries=newEntries)

Set timestamps in this tier to be the same as values in the reference tier

Timestamps will only be moved if they are less than maxDifference away from the reference time.

This can be used to correct minor alignment errors between tiers, as made when annotating files manually, etc.

###### Arguments:

- referenceTier: the IntervalTier or PointTier to use as a reference
- maxDifference: the maximum amount to allow timestamps to be moved by

###### Returns:

> the modified version of the current tier

def editTimestamps( self, offset: float, reportingMode: Literal['silence', 'warning', 'error'] = 'warning') -> PointTier: View Source

    166    def editTimestamps(
    167        self,
    168        offset: float,
    169        reportingMode: Literal["silence", "warning", "error"] = "warning",
    170    ) -> "PointTier":
    171        """Modifies all timestamps by a constant amount
    172
    173        Args:
    174            offset:
    175            reportingMode: one of "silence", "warning", or "error". This flag
    176                determines the behavior if an entries moves outside of minTimestamp
    177                or maxTimestamp after being edited
    178
    179        Returns:
    180            the modified version of the current tier
    181        """
    182        utils.validateOption(
    183            "reportingMode", reportingMode, constants.ErrorReportingMode
    184        )
    185        errorReporter = utils.getErrorReporter(reportingMode)
    186
    187        newEntries: List[Point] = []
    188        for timestamp, label in self.entries:
    189            newTimestamp = timestamp + offset
    190            utils.checkIsUndershoot(newTimestamp, self.minTimestamp, errorReporter)
    191            utils.checkIsOvershoot(newTimestamp, self.maxTimestamp, errorReporter)
    192
    193            if newTimestamp < 0:
    194                continue
    195
    196            newEntries.append(Point(newTimestamp, label))
    197
    198        # Determine new min and max timestamps
    199        timeList = [float(point.time) for point in newEntries]
    200        newMin = min(timeList)
    201        newMax = max(timeList)
    202
    203        if newMin > self.minTimestamp:
    204            newMin = self.minTimestamp
    205
    206        if newMax < self.maxTimestamp:
    207            newMax = self.maxTimestamp
    208
    209        return PointTier(self.name, newEntries, newMin, newMax)

Modifies all timestamps by a constant amount

###### Arguments:

- offset:
- reportingMode: one of "silence", "warning", or "error". This flag determines the behavior if an entries moves outside of minTimestamp or maxTimestamp after being edited

###### Returns:

> the modified version of the current tier

def getValuesAtPoints( self, dataTupleList: List[Tuple[float, ...]], fuzzyMatching: bool = False) -> List[Tuple[Any, ...]]: View Source

    211    def getValuesAtPoints(
    212        self,
    213        dataTupleList: List[Tuple[float, ...]],
    214        fuzzyMatching: bool = False,
    215    ) -> List[Tuple[Any, ...]]:
    216        """Get the values that occur at points in the point tier
    217
    218        The procedure assumes that all data is ordered in time.
    219        dataTupleList should be in the form
    220        [(t1, v1a, v1b, ..), (t2, v2a, v2b, ..), ..]
    221
    222        It returns the data in the form of
    223        [(t1, v1a, v1b, ..), (t2, v2a, v2b), ..]
    224
    225        The procedure makes one pass through dataTupleList and one
    226        pass through self.entries.  If the data is not sequentially
    227        ordered, the incorrect response will be returned.
    228
    229        Args:
    230            dataTupleList:
    231            fuzzyMatching: if True, if there is not a feature value
    232                at a point, the nearest feature value will be taken.
    233
    234        Returns:
    235            A list of values that exist at the given timepoints
    236        """
    237
    238        currentIndex = 0
    239        retList = []
    240
    241        sortedDataTupleList = sorted(dataTupleList)
    242        for timestamp, label in self.entries:
    243            retTuple = utils.getValueAtTime(
    244                timestamp,
    245                sortedDataTupleList,
    246                fuzzyMatching=fuzzyMatching,
    247                startI=currentIndex,
    248            )
    249            retRow, currentIndex = retTuple
    250            retList.append(retRow)
    251
    252        return retList

Get the values that occur at points in the point tier

The procedure assumes that all data is ordered in time. dataTupleList should be in the form [(t1, v1a, v1b, ..), (t2, v2a, v2b, ..), ..]

It returns the data in the form of [(t1, v1a, v1b, ..), (t2, v2a, v2b), ..]

The procedure makes one pass through dataTupleList and one pass through self.entries. If the data is not sequentially ordered, the incorrect response will be returned.

###### Arguments:

- dataTupleList:
- fuzzyMatching: if True, if there is not a feature value at a point, the nearest feature value will be taken.

###### Returns:

> A list of values that exist at the given timepoints

def eraseRegion( self, start: float, end: float, collisionMode: Literal['truncate', 'categorical', 'error'] = 'error', doShrink: bool = True) -> PointTier: View Source

    254    def eraseRegion(
    255        self,
    256        start: float,
    257        end: float,
    258        collisionMode: Literal["truncate", "categorical", "error"] = "error",
    259        doShrink: bool = True,
    260    ) -> "PointTier":
    261        """Makes a region in a tier blank (removes all contained entries)
    262
    263        Args:
    264            start: the start of the deletion interval
    265            end: the end of the deletion interval
    266            collisionMode: Ignored for the moment (added for compatibility with
    267                eraseRegion() for Interval Tiers)
    268            doShrink: if True, moves leftward by (/end/ - /start/) all points
    269                to the right of /end/
    270
    271        Returns:
    272            The modified version of the current tier
    273        """
    274
    275        newTier = self.new()
    276        croppedTier = newTier.crop(start, end, constants.CropCollision.TRUNCATED, False)
    277        matchList = croppedTier.entries
    278
    279        if len(matchList) > 0:
    280            # Remove all the matches from the entries
    281            # Go in reverse order because we're destructively altering
    282            # the order of the list (messes up index order)
    283            for point in matchList[::-1]:
    284                newTier.deleteEntry(point)
    285
    286        if doShrink is True:
    287            newEntries = []
    288            diff = end - start
    289            for point in newTier.entries:
    290                if point.time < start:
    291                    newEntries.append(point)
    292                elif point.time > end:
    293                    newEntries.append(Point(point.time - diff, point.label))
    294
    295            newMax = newTier.maxTimestamp - diff
    296            newTier = newTier.new(entries=newEntries, maxTimestamp=newMax)
    297
    298        return newTier

Makes a region in a tier blank (removes all contained entries)

###### Arguments:

- start: the start of the deletion interval
- end: the end of the deletion interval
- collisionMode: Ignored for the moment (added for compatibility with eraseRegion() for Interval Tiers)
- doShrink: if True, moves leftward by (/end/ - /start/) all points to the right of /end/

###### Returns:

> The modified version of the current tier

def insertEntry( self, entry: praatio.utilities.constants.Point, collisionMode: Literal['replace', 'merge', 'error'] = 'error', collisionReportingMode: Literal['silence', 'warning'] = 'warning') -> None: View Source

    300    def insertEntry(
    301        self,
    302        entry: Point,
    303        collisionMode: Literal["replace", "merge", "error"] = "error",
    304        collisionReportingMode: Literal["silence", "warning"] = "warning",
    305    ) -> None:
    306        """Inserts an interval into the tier
    307
    308        Args:
    309            entry: the entry to insert
    310            collisionMode: determines the behavior if intervals exist in
    311                the insertion area.
    312                - 'replace', existing items will be removed
    313                - 'merge', inserting item will be fused with existing items
    314                - 'error', will throw TextgridCollisionException
    315            collisionReportingMode:
    316
    317        Returns:
    318            None
    319        """
    320
    321        utils.validateOption(
    322            "collisionMode", collisionMode, constants.IntervalCollision
    323        )
    324        utils.validateOption(
    325            "collisionReportingMode",
    326            collisionReportingMode,
    327            constants.ErrorReportingMode,
    328        )
    329        collisionReporter = utils.getErrorReporter(collisionReportingMode)
    330
    331        if not isinstance(entry, Point):
    332            newPoint = Point(entry[0], entry[1])
    333        else:
    334            newPoint = entry
    335
    336        matchList = []
    337        i = None
    338        for i, point in enumerate(self.entries):
    339            if point.time == newPoint.time:
    340                matchList.append(point)
    341                break
    342
    343        if len(matchList) == 0:
    344            self._entries.append(newPoint)
    345
    346        elif collisionMode == constants.IntervalCollision.REPLACE:
    347            self.deleteEntry(self.entries[i])
    348            self._entries.append(newPoint)
    349
    350        elif collisionMode == constants.IntervalCollision.MERGE:
    351            oldPoint = self.entries[i]
    352            mergedPoint = Point(
    353                newPoint.time, "-".join([oldPoint.label, newPoint.label])
    354            )
    355            self.deleteEntry(self._entries[i])
    356            self._entries.append(mergedPoint)
    357
    358        else:
    359            raise errors.CollisionError(
    360                f"Attempted to insert interval {point} into tier {self.name} "
    361                "of textgrid but overlapping entries "
    362                f"{[tuple(interval) for interval in matchList]} "
    363                "already exist"
    364            )
    365
    366        self.sort()
    367
    368        if len(matchList) != 0:
    369            collisionReporter(
    370                errors.CollisionError,
    371                f"Collision warning for ({point}) with items ({matchList}) of tier '{self.name}'",
    372            )

Inserts an interval into the tier

###### Arguments:

- entry: the entry to insert
- collisionMode: determines the behavior if intervals exist in the insertion area.
  - 'replace', existing items will be removed
  - 'merge', inserting item will be fused with existing items
  - 'error', will throw TextgridCollisionException
- collisionReportingMode:

###### Returns:

> None

def insertSpace( self, start: float, duration: float, _collisionMode: Literal['stretch', 'split', 'no_change', 'error'] = 'error') -> PointTier: View Source

    374    def insertSpace(
    375        self,
    376        start: float,
    377        duration: float,
    378        _collisionMode: Literal["stretch", "split", "no_change", "error"] = "error",
    379    ) -> "PointTier":
    380        """Inserts a region into the tier
    381
    382        Args:
    383            start: the start time to insert a space at
    384            duration: the duration of the space to insert
    385            collisionMode: Ignored for the moment (added for compatibility
    386                with insertSpace() for Interval Tiers)
    387
    388        Returns:
    389            PointTier: the modified version of the current tier
    390        """
    391
    392        newEntries = []
    393        for point in self.entries:
    394            if point.time <= start:
    395                newEntries.append(point)
    396            elif point.time > start:
    397                newEntries.append(Point(point.time + duration, point.label))
    398
    399        newTier = self.new(
    400            entries=newEntries, maxTimestamp=self.maxTimestamp + duration
    401        )
    402
    403        return newTier

Inserts a region into the tier

###### Arguments:

- start: the start time to insert a space at
- duration: the duration of the space to insert
- collisionMode: Ignored for the moment (added for compatibility with insertSpace() for Interval Tiers)

###### Returns:

> PointTier: the modified version of the current tier

def validate( self, reportingMode: Literal['silence', 'warning', 'error'] = 'warning') -> bool: View Source

    405    def validate(
    406        self, reportingMode: Literal["silence", "warning", "error"] = "warning"
    407    ) -> bool:
    408        """Validate this tier
    409
    410        Returns whether the tier is valid or not. If reportingMode is "warning"
    411        or "error" this will also print on error or stop execution, respectively.
    412
    413        Args:
    414            reportingMode: Determines the behavior if there is a size difference
    415                between the maxTimestamp in the tier and the current textgrid.
    416
    417        Returns:
    418            True if this tier is valid; False otherwise
    419        """
    420        utils.validateOption(
    421            "reportingMode", reportingMode, constants.ErrorReportingMode
    422        )
    423        errorReporter = utils.getErrorReporter(reportingMode)
    424
    425        isValid = True
    426        previousPoint = None
    427        for point in self.entries:
    428            if previousPoint and previousPoint.time > point.time:
    429                isValid = False
    430                errorReporter(
    431                    errors.TextgridStateError,
    432                    f"Points are not sorted in time: "
    433                    f"[({previousPoint}), ({point})]",
    434                )
    435
    436            if utils.checkIsUndershoot(point.time, self.minTimestamp, errorReporter):
    437                isValid = False
    438
    439            if utils.checkIsOvershoot(point.time, self.maxTimestamp, errorReporter):
    440                isValid = False
    441
    442            previousPoint = point
    443
    444        return isValid

Validate this tier

Returns whether the tier is valid or not. If reportingMode is "warning" or "error" this will also print on error or stop execution, respectively.

###### Arguments:

- reportingMode: Determines the behavior if there is a size difference between the maxTimestamp in the tier and the current textgrid.

###### Returns:

> True if this tier is valid; False otherwise

##### Inherited Members

praatio.data_classes.textgrid_tier.TextgridTier
  name
  minTimestamp
  maxTimestamp
  errorReporter
  entries
  appendTier
  find
  new
  sort
  union