praatio.data_classes.textgrid API documentation

# praatio.data_classes.textgrid

Functions for reading/writing/manipulating Textgrid classes

This is the 'heart' of praatio.

View Source

      1"""
      2Functions for reading/writing/manipulating Textgrid classes
      3
      4This is the 'heart' of praatio.
      5"""
      6import io
      7import copy
      8from typing import Optional, Tuple, Sequence
      9from typing_extensions import Literal
     10from collections import OrderedDict
     11
     12
     13from praatio.utilities.constants import (
     14    TextgridFormats,
     15    MIN_INTERVAL_LENGTH,
     16    CropCollision,
     17)
     18
     19from praatio.data_classes import textgrid_tier
     20from praatio.data_classes import point_tier
     21from praatio.data_classes import interval_tier
     22from praatio.utilities import constants
     23from praatio.utilities import errors
     24from praatio.utilities import my_math
     25from praatio.utilities import textgrid_io
     26from praatio.utilities import utils
     27
     28
     29class Textgrid:
     30    """A container that stores and operates over interval and point tiers
     31
     32    Textgrids are used by the Praat software to group tiers.  Each tier
     33    contains different annotation information for an audio recording.
     34
     35    Attributes:
     36        tierNames(Tuple[str]): the list of tier names in the textgrid
     37        tiers(Tuple[TextgridTier]): the list of ordered tiers
     38        minTimestamp(float): the minimum allowable timestamp in the textgrid
     39        maxTimestamp(float): the maximum allowable timestamp in the textgrid
     40    """
     41
     42    def __init__(self, minTimestamp: float = None, maxTimestamp: float = None):
     43        """Constructor for Textgrids
     44
     45        Args:
     46            minTimestamp: the minimum allowable timestamp in the textgrid
     47            maxTimestamp: the maximum allowable timestamp in the textgrid
     48        """
     49
     50        self._tierDict: OrderedDict[str, textgrid_tier.TextgridTier] = OrderedDict()
     51
     52        # Timestamps are determined by the first tier added
     53        self.minTimestamp: float = minTimestamp  # type: ignore[assignment]
     54        self.maxTimestamp: float = maxTimestamp  # type: ignore[assignment]
     55
     56    def __len__(self):
     57        return len(self._tierDict)
     58
     59    def __iter__(self):
     60        for entry in self.tiers:
     61            yield entry
     62
     63    def __eq__(self, other):
     64        if not isinstance(other, Textgrid):
     65            return False
     66
     67        isEqual = True
     68        isEqual &= my_math.isclose(self.minTimestamp, other.minTimestamp)
     69        isEqual &= my_math.isclose(self.maxTimestamp, other.maxTimestamp)
     70
     71        isEqual &= self.tierNames == other.tierNames
     72        if isEqual:
     73            for tierName in self.tierNames:
     74                isEqual &= self.getTier(tierName) == other.getTier(tierName)
     75
     76        return isEqual
     77
     78    @property
     79    def tierNames(self) -> Tuple[str, ...]:
     80        return tuple(self._tierDict.keys())
     81
     82    @property
     83    def tiers(self) -> Tuple[textgrid_tier.TextgridTier, ...]:
     84        return tuple(self._tierDict.values())
     85
     86    def addTier(
     87        self,
     88        tier: textgrid_tier.TextgridTier,
     89        tierIndex: Optional[int] = None,
     90        reportingMode: Literal["silence", "warning", "error"] = "warning",
     91    ) -> None:
     92        """Add a tier to this textgrid.
     93
     94        Args:
     95            tier: The tier to add to the textgrid
     96            tierIndex: Insert the tier into the specified position;
     97                if None, the tier will appear after all existing tiers
     98            reportingMode: This flag determines the behavior if there is a size
     99                difference between the maxTimestamp in the tier and the current
    100                textgrid.
    101
    102        Returns:
    103            None
    104
    105        Raises:
    106            TierNameExistsError: The textgrid already contains a tier with the same
    107                name as the tier being added
    108            TextgridStateAutoModified: The minimum or maximum timestamp was changed
    109                when not permitted
    110            IndexError: TierIndex is too large for the size of the existing tier list
    111        """
    112        utils.validateOption(
    113            "reportingMode", reportingMode, constants.ErrorReportingMode
    114        )
    115        errorReporter = utils.getErrorReporter(reportingMode)
    116
    117        if tier.name in self.tierNames:
    118            raise errors.TierNameExistsError("Tier name already in tier")
    119
    120        if tierIndex is None:
    121            self._tierDict[tier.name] = tier
    122        else:  # Need to recreate the tierDict with the new order
    123            newOrderedTierNameList = list(self.tierNames)
    124            newOrderedTierNameList.insert(tierIndex, tier.name)
    125
    126            newTierDict = OrderedDict()
    127            self._tierDict[tier.name] = tier
    128            for tmpName in newOrderedTierNameList:
    129                newTierDict[tmpName] = self.getTier(tmpName)
    130            self._tierDict = newTierDict
    131
    132        minV = tier.minTimestamp
    133        if self.minTimestamp is not None and minV < self.minTimestamp:
    134            errorReporter(
    135                errors.TextgridStateAutoModified,
    136                f"Minimum timestamp in Textgrid changed from ({self.minTimestamp}) to ({minV})",
    137            )
    138        if self.minTimestamp is None or minV < self.minTimestamp:
    139            self.minTimestamp = minV
    140
    141        maxV = tier.maxTimestamp
    142        if self.maxTimestamp is not None and maxV > self.maxTimestamp:
    143            errorReporter(
    144                errors.TextgridStateAutoModified,
    145                f"Maximum timestamp in Textgrid changed from ({self.maxTimestamp}) to ({maxV})",
    146            )
    147        if self.maxTimestamp is None or maxV > self.maxTimestamp:
    148            self.maxTimestamp = maxV
    149
    150    def appendTextgrid(self, tg: "Textgrid", onlyMatchingNames: bool) -> "Textgrid":
    151        """Append one textgrid to the end of this one
    152
    153        Args:
    154            tg: the textgrid to add to this one
    155            onlyMatchingNames: if False, tiers that don't appear in both
    156                textgrids will also appear
    157
    158        Returns:
    159            the modified version of the current textgrid
    160        """
    161        minTime = self.minTimestamp
    162        maxTime = self.maxTimestamp + tg.maxTimestamp
    163        retTG = Textgrid(minTime, maxTime)
    164
    165        # Get all tier names.  Ordered first by this textgrid and
    166        # then by the other textgrid.
    167        combinedTierNames = list(self.tierNames)
    168        for tierName in tg.tierNames:
    169            if tierName not in combinedTierNames:
    170                combinedTierNames.append(tierName)
    171
    172        # Determine the tier names that will be in the final textgrid
    173        finalTierNames = []
    174        if onlyMatchingNames is False:
    175            finalTierNames = combinedTierNames
    176        else:
    177            for tierName in combinedTierNames:
    178                if tierName in self.tierNames:
    179                    if tierName in tg.tierNames:
    180                        finalTierNames.append(tierName)
    181
    182        # Add tiers from this textgrid
    183        for tierName in finalTierNames:
    184            if tierName in self.tierNames:
    185                tier = self.getTier(tierName)
    186                retTG.addTier(tier)
    187
    188        # Add tiers from the given textgrid
    189        for tierName in finalTierNames:
    190            if tierName in tg.tierNames:
    191                appendTier = tg.getTier(tierName)
    192                appendTier = appendTier.new(minTimestamp=minTime, maxTimestamp=maxTime)
    193
    194                appendTier = appendTier.editTimestamps(self.maxTimestamp)
    195
    196                if tierName in retTG.tierNames:
    197                    tier = retTG.getTier(tierName)
    198                    newEntries = retTG.getTier(tierName).entries
    199                    newEntries += appendTier.entries
    200
    201                    tier = tier.new(
    202                        entries=newEntries,
    203                        minTimestamp=minTime,
    204                        maxTimestamp=maxTime,
    205                    )
    206                    retTG.replaceTier(tierName, tier)
    207
    208                else:
    209                    tier = appendTier
    210                    tier = tier.new(minTimestamp=minTime, maxTimestamp=maxTime)
    211                    retTG.addTier(tier)
    212
    213        return retTG
    214
    215    def crop(
    216        self,
    217        cropStart: float,
    218        cropEnd: float,
    219        mode: Literal["strict", "lax", "truncated"],
    220        rebaseToZero: bool,
    221    ) -> "Textgrid":
    222        """Creates a textgrid where all intervals fit within the crop region
    223
    224        Args:
    225            cropStart: The start time of the crop interval
    226            cropEnd: The stop time of the crop interval
    227            mode: Determines the crop behavior
    228                - 'strict', only intervals wholly contained by the crop
    229                    interval will be kept
    230                - 'lax', partially contained intervals will be kept
    231                - 'truncated', partially contained intervals will be
    232                    truncated to fit within the crop region.
    233            rebaseToZero: if True, the cropped textgrid timestamps will be
    234                subtracted by the cropStart; if False, timestamps will not
    235                be changed
    236
    237        Returns:
    238            the modified version of the current textgrid
    239        """
    240        utils.validateOption("mode", mode, CropCollision)
    241
    242        if cropStart >= cropEnd:
    243            raise errors.ArgumentError(
    244                f"Crop error: start time ({cropStart}) must occur before end time ({cropEnd})"
    245            )
    246
    247        if rebaseToZero is True:
    248            minT = 0.0
    249            maxT = cropEnd - cropStart
    250        else:
    251            minT = cropStart
    252            maxT = cropEnd
    253        newTG = Textgrid(minT, maxT)
    254
    255        for tierName in self.tierNames:
    256            tier = self.getTier(tierName)
    257            newTier = tier.crop(cropStart, cropEnd, mode, rebaseToZero)
    258
    259            reportingMode: Literal[
    260                "silence", "warning", "error"
    261            ] = constants.ErrorReportingMode.WARNING
    262            if mode == constants.CropCollision.LAX:
    263                # We expect that there will be changes to the size
    264                # of the textgrid when the mode is LAX
    265                reportingMode = constants.ErrorReportingMode.SILENCE
    266
    267            newTG.addTier(newTier, reportingMode=reportingMode)
    268
    269        return newTG
    270
    271    def eraseRegion(self, start: float, end: float, doShrink: bool) -> "Textgrid":
    272        """Makes a region in a tier blank (removes all contained entries)
    273
    274        Intervals that span the region to erase will be truncated.
    275
    276        Args:
    277            start:
    278            end:
    279            doShrink: if True, all entries appearing after the
    280                erased interval will be shifted to fill the void (ie
    281                the duration of the textgrid will be reduced by
    282                *start* - *end*)
    283
    284        Returns:
    285            the modified version of the current textgrid
    286
    287        Raises:
    288            ArgumentError
    289        """
    290        if start >= end:
    291            raise errors.ArgumentError(
    292                f"EraseRegion error: start time ({start}) must occur before end time ({end})"
    293            )
    294
    295        diff = end - start
    296
    297        maxTimestamp = self.maxTimestamp
    298        if doShrink is True:
    299            maxTimestamp -= diff
    300
    301        newTG = Textgrid(self.minTimestamp, self.maxTimestamp)
    302        for tier in self.tiers:
    303            shrunkTier = tier.eraseRegion(
    304                start, end, constants.EraseCollision.TRUNCATE, doShrink
    305            )
    306            newTG.addTier(shrunkTier)
    307
    308        newTG.maxTimestamp = maxTimestamp
    309
    310        return newTG
    311
    312    def editTimestamps(
    313        self,
    314        offset: float,
    315        reportingMode: Literal["silence", "warning", "error"] = "warning",
    316    ) -> "Textgrid":
    317        """Modifies all timestamps by a constant amount
    318
    319        Args:
    320            offset: the amount to offset in seconds
    321            reportingMode: one of "silence", "warning", or "error". This flag
    322                determines the behavior if there is a size difference between the
    323                maxTimestamp in the tier and the current textgrid.
    324
    325        Returns:
    326            Textgrid: the modified version of the current textgrid
    327        """
    328        utils.validateOption(
    329            "reportingMode", reportingMode, constants.ErrorReportingMode
    330        )
    331
    332        tg = Textgrid(self.minTimestamp, self.maxTimestamp)
    333        for tier in self.tiers:
    334            if len(tier.entries) > 0:
    335                tier = tier.editTimestamps(offset, reportingMode)
    336
    337            tg.addTier(tier, reportingMode=reportingMode)
    338
    339        return tg
    340
    341    def getTier(self, tierName: str) -> textgrid_tier.TextgridTier:
    342        """Get the tier with the specified name"""
    343        return self._tierDict[tierName]
    344
    345    def insertSpace(
    346        self,
    347        start: float,
    348        duration: float,
    349        collisionMode: Literal["stretch", "split", "no_change", "error"] = "error",
    350    ) -> "Textgrid":
    351        """Inserts a blank region into a textgrid
    352
    353        Every item that occurs after *start* will be pushed back by
    354        *duration* seconds
    355
    356        Args:
    357            start:
    358            duration:
    359            collisionMode: Determines behavior in the event that an
    360                interval stradles the starting point.
    361                One of ['stretch', 'split', 'no change', None]
    362                - 'stretch' stretches the interval by /duration/ amount
    363                - 'split' splits the interval into two--everything to the
    364                    right of 'start' will be advanced by 'duration' seconds
    365                - 'no change' leaves the interval as is with no change
    366                - None or any other value throws an AssertionError
    367
    368        Returns:
    369            Textgrid: the modified version of the current textgrid
    370        """
    371        utils.validateOption(
    372            "collisionMode", collisionMode, constants.WhitespaceCollision
    373        )
    374
    375        newTG = Textgrid(self.minTimestamp, self.maxTimestamp)
    376        newTG.minTimestamp = self.minTimestamp
    377        newTG.maxTimestamp = self.maxTimestamp + duration
    378
    379        for tier in self.tiers:
    380            newTier = tier.insertSpace(start, duration, collisionMode)
    381            newTG.addTier(newTier)
    382
    383        return newTG
    384
    385    def mergeTiers(
    386        self, tierNames: Optional[Sequence[str]] = None, preserveOtherTiers: bool = True
    387    ) -> "Textgrid":
    388        """Combine tiers
    389
    390        Args:
    391            tierList: A list of tier names to combine. If none, combine
    392                all tiers.
    393            preserveOtherTiers: If false, uncombined tiers are not
    394                included in the output.
    395
    396        Returns:
    397            Textgrid: the modified version of the current textgrid
    398        """
    399        if tierNames is None:
    400            tierNames = self.tierNames
    401
    402        # Determine the tiers to merge
    403        intervalTierNames = []
    404        pointTierNames = []
    405        for tierName in tierNames:
    406            tier = self.getTier(tierName)
    407            if isinstance(tier, interval_tier.IntervalTier):
    408                intervalTierNames.append(tierName)
    409            elif isinstance(tier, point_tier.PointTier):
    410                pointTierNames.append(tierName)
    411
    412        # Merge the interval tiers
    413        intervalTier = None
    414        if len(intervalTierNames) > 0:
    415            intervalTier = self.getTier(intervalTierNames.pop(0))
    416            for tierName in intervalTierNames:
    417                intervalTier = intervalTier.union(self.getTier(tierName))
    418
    419        # Merge the point tiers
    420        pointTier = None
    421        if len(pointTierNames) > 0:
    422            pointTier = self.getTier(pointTierNames.pop(0))
    423            for tierName in pointTierNames:
    424                pointTier = pointTier.union(self.getTier(tierName))
    425
    426        # Create the final textgrid to output
    427        tg = Textgrid(self.minTimestamp, self.maxTimestamp)
    428
    429        if preserveOtherTiers:
    430            for tier in self.tiers:
    431                if tier.name not in tierNames:
    432                    tg.addTier(tier)
    433
    434        if intervalTier is not None:
    435            tg.addTier(intervalTier)
    436
    437        if pointTier is not None:
    438            tg.addTier(pointTier)
    439
    440        return tg
    441
    442    def new(self) -> "Textgrid":
    443        """Returns a copy of this Textgrid"""
    444        return copy.deepcopy(self)
    445
    446    def save(
    447        self,
    448        fn: str,
    449        format: Literal["short_textgrid", "long_textgrid", "json", "textgrid_json"],
    450        includeBlankSpaces: bool,
    451        minTimestamp: Optional[float] = None,
    452        maxTimestamp: Optional[float] = None,
    453        minimumIntervalLength: float = MIN_INTERVAL_LENGTH,
    454        reportingMode: Literal["silence", "warning", "error"] = "warning",
    455    ) -> None:
    456        """Save the current textgrid to a file
    457
    458        Args:
    459            fn: the fullpath filename of the output
    460            format: one of ['short_textgrid', 'long_textgrid', 'json', 'textgrid_json']
    461                'short_textgrid' and 'long_textgrid' are both used by praat
    462                'json' and 'textgrid_json' are two json variants. 'json' cannot represent
    463                tiers with different min and max timestamps than the textgrid.
    464            includeBlankSpaces: if True, blank sections in interval
    465                tiers will be filled in with an empty interval
    466                (with a label of ""). If you are unsure, True is recommended
    467                as Praat needs blanks to render textgrids properly.
    468            minTimestamp: the minTimestamp of the saved Textgrid;
    469                if None, use whatever is defined in the Textgrid object.
    470                If minTimestamp is larger than timestamps in your textgrid,
    471                an exception will be thrown.
    472            maxTimestamp: the maxTimestamp of the saved Textgrid;
    473                if None, use whatever is defined in the Textgrid object.
    474                If maxTimestamp is smaller than timestamps in your textgrid,
    475                an exception will be thrown.
    476            minimumIntervalLength: any labeled intervals smaller
    477                than this will be removed, useful for removing ultrashort
    478                or fragmented intervals; if None, don't remove any.
    479                Removed intervals are merged (without their label) into
    480                adjacent entries.
    481            reportingMode: one of "silence", "warning", or "error". This flag
    482                determines the behavior if there is a size difference between the
    483                maxTimestamp in the tier and the current textgrid.
    484
    485        Returns:
    486            a string representation of the textgrid
    487        """
    488
    489        utils.validateOption("format", format, TextgridFormats)
    490        utils.validateOption(
    491            "reportingMode", reportingMode, constants.ErrorReportingMode
    492        )
    493
    494        self.validate(reportingMode)
    495
    496        tgAsDict = _tgToDictionary(self)
    497
    498        textgridStr = textgrid_io.getTextgridAsStr(
    499            tgAsDict,
    500            format,
    501            includeBlankSpaces,
    502            minTimestamp,
    503            maxTimestamp,
    504            minimumIntervalLength,
    505        )
    506
    507        with io.open(fn, "w", encoding="utf-8") as fd:
    508            fd.write(textgridStr)
    509
    510    def renameTier(self, oldName: str, newName: str) -> None:
    511        oldTier = self.getTier(oldName)
    512        tierIndex = self.tierNames.index(oldName)
    513        self.removeTier(oldName)
    514        self.addTier(oldTier.new(newName, oldTier.entries), tierIndex)
    515
    516    def removeTier(self, name: str) -> textgrid_tier.TextgridTier:
    517        return self._tierDict.pop(name)
    518
    519    def replaceTier(
    520        self,
    521        name: str,
    522        newTier: textgrid_tier.TextgridTier,
    523        reportingMode: Literal["silence", "warning", "error"] = "warning",
    524    ) -> None:
    525        tierIndex = self.tierNames.index(name)
    526        self.removeTier(name)
    527        self.addTier(newTier, tierIndex, reportingMode)
    528
    529    def validate(
    530        self, reportingMode: Literal["silence", "warning", "error"] = "warning"
    531    ) -> bool:
    532        """Validates this textgrid
    533
    534        Returns whether the textgrid is valid or not. If reportingMode is "warning"
    535        or "error" this will also print on error or stop execution, respectively.
    536
    537        Args:
    538            reportingMode: one of "silence", "warning", or "error". This flag
    539                determines the behavior if there is a size difference between the
    540                maxTimestamp in a tier and the current textgrid.
    541
    542        Returns:
    543            True if this Textgrid is valid; False if not
    544
    545        Raises:
    546            TierNameExistsError: Two tiers have the same name
    547            TextgridStateError: A timestamp fall outside of the allowable range
    548        """
    549        utils.validateOption(
    550            "reportingMode", reportingMode, constants.ErrorReportingMode
    551        )
    552        errorReporter = utils.getErrorReporter(reportingMode)
    553
    554        isValid = True
    555        if len(self.tierNames) != len(set(self.tierNames)):
    556            isValid = False
    557            errorReporter(
    558                errors.TierNameExistsError,
    559                f"Tier names not unique: {self.tierNames}",
    560            )
    561
    562        for tier in self.tiers:
    563            if self.minTimestamp != tier.minTimestamp:
    564                isValid = False
    565                errorReporter(
    566                    errors.TextgridStateError,
    567                    f"Textgrid has a min timestamp of ({self.minTimestamp}) "
    568                    f"but tier has ({tier.minTimestamp})",
    569                )
    570
    571            if self.maxTimestamp != tier.maxTimestamp:
    572                isValid = False
    573                errorReporter(
    574                    errors.TextgridStateError,
    575                    f"Textgrid has a max timestamp of ({self.maxTimestamp}) "
    576                    f"but tier has ({tier.maxTimestamp})",
    577                )
    578
    579            isValid = isValid and tier.validate(reportingMode)
    580
    581        return isValid
    582
    583
    584def _tgToDictionary(tg: Textgrid) -> dict:
    585    tiers = []
    586    for tier in tg.tiers:
    587        tierAsDict = {
    588            "class": tier.tierType,
    589            "name": tier.name,
    590            "xmin": tier.minTimestamp,
    591            "xmax": tier.maxTimestamp,
    592            "entries": tier.entries,
    593        }
    594        tiers.append(tierAsDict)
    595
    596    return {"xmin": tg.minTimestamp, "xmax": tg.maxTimestamp, "tiers": tiers}

class Textgrid: View Source

     30class Textgrid:
     31    """A container that stores and operates over interval and point tiers
     32
     33    Textgrids are used by the Praat software to group tiers.  Each tier
     34    contains different annotation information for an audio recording.
     35
     36    Attributes:
     37        tierNames(Tuple[str]): the list of tier names in the textgrid
     38        tiers(Tuple[TextgridTier]): the list of ordered tiers
     39        minTimestamp(float): the minimum allowable timestamp in the textgrid
     40        maxTimestamp(float): the maximum allowable timestamp in the textgrid
     41    """
     42
     43    def __init__(self, minTimestamp: float = None, maxTimestamp: float = None):
     44        """Constructor for Textgrids
     45
     46        Args:
     47            minTimestamp: the minimum allowable timestamp in the textgrid
     48            maxTimestamp: the maximum allowable timestamp in the textgrid
     49        """
     50
     51        self._tierDict: OrderedDict[str, textgrid_tier.TextgridTier] = OrderedDict()
     52
     53        # Timestamps are determined by the first tier added
     54        self.minTimestamp: float = minTimestamp  # type: ignore[assignment]
     55        self.maxTimestamp: float = maxTimestamp  # type: ignore[assignment]
     56
     57    def __len__(self):
     58        return len(self._tierDict)
     59
     60    def __iter__(self):
     61        for entry in self.tiers:
     62            yield entry
     63
     64    def __eq__(self, other):
     65        if not isinstance(other, Textgrid):
     66            return False
     67
     68        isEqual = True
     69        isEqual &= my_math.isclose(self.minTimestamp, other.minTimestamp)
     70        isEqual &= my_math.isclose(self.maxTimestamp, other.maxTimestamp)
     71
     72        isEqual &= self.tierNames == other.tierNames
     73        if isEqual:
     74            for tierName in self.tierNames:
     75                isEqual &= self.getTier(tierName) == other.getTier(tierName)
     76
     77        return isEqual
     78
     79    @property
     80    def tierNames(self) -> Tuple[str, ...]:
     81        return tuple(self._tierDict.keys())
     82
     83    @property
     84    def tiers(self) -> Tuple[textgrid_tier.TextgridTier, ...]:
     85        return tuple(self._tierDict.values())
     86
     87    def addTier(
     88        self,
     89        tier: textgrid_tier.TextgridTier,
     90        tierIndex: Optional[int] = None,
     91        reportingMode: Literal["silence", "warning", "error"] = "warning",
     92    ) -> None:
     93        """Add a tier to this textgrid.
     94
     95        Args:
     96            tier: The tier to add to the textgrid
     97            tierIndex: Insert the tier into the specified position;
     98                if None, the tier will appear after all existing tiers
     99            reportingMode: This flag determines the behavior if there is a size
    100                difference between the maxTimestamp in the tier and the current
    101                textgrid.
    102
    103        Returns:
    104            None
    105
    106        Raises:
    107            TierNameExistsError: The textgrid already contains a tier with the same
    108                name as the tier being added
    109            TextgridStateAutoModified: The minimum or maximum timestamp was changed
    110                when not permitted
    111            IndexError: TierIndex is too large for the size of the existing tier list
    112        """
    113        utils.validateOption(
    114            "reportingMode", reportingMode, constants.ErrorReportingMode
    115        )
    116        errorReporter = utils.getErrorReporter(reportingMode)
    117
    118        if tier.name in self.tierNames:
    119            raise errors.TierNameExistsError("Tier name already in tier")
    120
    121        if tierIndex is None:
    122            self._tierDict[tier.name] = tier
    123        else:  # Need to recreate the tierDict with the new order
    124            newOrderedTierNameList = list(self.tierNames)
    125            newOrderedTierNameList.insert(tierIndex, tier.name)
    126
    127            newTierDict = OrderedDict()
    128            self._tierDict[tier.name] = tier
    129            for tmpName in newOrderedTierNameList:
    130                newTierDict[tmpName] = self.getTier(tmpName)
    131            self._tierDict = newTierDict
    132
    133        minV = tier.minTimestamp
    134        if self.minTimestamp is not None and minV < self.minTimestamp:
    135            errorReporter(
    136                errors.TextgridStateAutoModified,
    137                f"Minimum timestamp in Textgrid changed from ({self.minTimestamp}) to ({minV})",
    138            )
    139        if self.minTimestamp is None or minV < self.minTimestamp:
    140            self.minTimestamp = minV
    141
    142        maxV = tier.maxTimestamp
    143        if self.maxTimestamp is not None and maxV > self.maxTimestamp:
    144            errorReporter(
    145                errors.TextgridStateAutoModified,
    146                f"Maximum timestamp in Textgrid changed from ({self.maxTimestamp}) to ({maxV})",
    147            )
    148        if self.maxTimestamp is None or maxV > self.maxTimestamp:
    149            self.maxTimestamp = maxV
    150
    151    def appendTextgrid(self, tg: "Textgrid", onlyMatchingNames: bool) -> "Textgrid":
    152        """Append one textgrid to the end of this one
    153
    154        Args:
    155            tg: the textgrid to add to this one
    156            onlyMatchingNames: if False, tiers that don't appear in both
    157                textgrids will also appear
    158
    159        Returns:
    160            the modified version of the current textgrid
    161        """
    162        minTime = self.minTimestamp
    163        maxTime = self.maxTimestamp + tg.maxTimestamp
    164        retTG = Textgrid(minTime, maxTime)
    165
    166        # Get all tier names.  Ordered first by this textgrid and
    167        # then by the other textgrid.
    168        combinedTierNames = list(self.tierNames)
    169        for tierName in tg.tierNames:
    170            if tierName not in combinedTierNames:
    171                combinedTierNames.append(tierName)
    172
    173        # Determine the tier names that will be in the final textgrid
    174        finalTierNames = []
    175        if onlyMatchingNames is False:
    176            finalTierNames = combinedTierNames
    177        else:
    178            for tierName in combinedTierNames:
    179                if tierName in self.tierNames:
    180                    if tierName in tg.tierNames:
    181                        finalTierNames.append(tierName)
    182
    183        # Add tiers from this textgrid
    184        for tierName in finalTierNames:
    185            if tierName in self.tierNames:
    186                tier = self.getTier(tierName)
    187                retTG.addTier(tier)
    188
    189        # Add tiers from the given textgrid
    190        for tierName in finalTierNames:
    191            if tierName in tg.tierNames:
    192                appendTier = tg.getTier(tierName)
    193                appendTier = appendTier.new(minTimestamp=minTime, maxTimestamp=maxTime)
    194
    195                appendTier = appendTier.editTimestamps(self.maxTimestamp)
    196
    197                if tierName in retTG.tierNames:
    198                    tier = retTG.getTier(tierName)
    199                    newEntries = retTG.getTier(tierName).entries
    200                    newEntries += appendTier.entries
    201
    202                    tier = tier.new(
    203                        entries=newEntries,
    204                        minTimestamp=minTime,
    205                        maxTimestamp=maxTime,
    206                    )
    207                    retTG.replaceTier(tierName, tier)
    208
    209                else:
    210                    tier = appendTier
    211                    tier = tier.new(minTimestamp=minTime, maxTimestamp=maxTime)
    212                    retTG.addTier(tier)
    213
    214        return retTG
    215
    216    def crop(
    217        self,
    218        cropStart: float,
    219        cropEnd: float,
    220        mode: Literal["strict", "lax", "truncated"],
    221        rebaseToZero: bool,
    222    ) -> "Textgrid":
    223        """Creates a textgrid where all intervals fit within the crop region
    224
    225        Args:
    226            cropStart: The start time of the crop interval
    227            cropEnd: The stop time of the crop interval
    228            mode: Determines the crop behavior
    229                - 'strict', only intervals wholly contained by the crop
    230                    interval will be kept
    231                - 'lax', partially contained intervals will be kept
    232                - 'truncated', partially contained intervals will be
    233                    truncated to fit within the crop region.
    234            rebaseToZero: if True, the cropped textgrid timestamps will be
    235                subtracted by the cropStart; if False, timestamps will not
    236                be changed
    237
    238        Returns:
    239            the modified version of the current textgrid
    240        """
    241        utils.validateOption("mode", mode, CropCollision)
    242
    243        if cropStart >= cropEnd:
    244            raise errors.ArgumentError(
    245                f"Crop error: start time ({cropStart}) must occur before end time ({cropEnd})"
    246            )
    247
    248        if rebaseToZero is True:
    249            minT = 0.0
    250            maxT = cropEnd - cropStart
    251        else:
    252            minT = cropStart
    253            maxT = cropEnd
    254        newTG = Textgrid(minT, maxT)
    255
    256        for tierName in self.tierNames:
    257            tier = self.getTier(tierName)
    258            newTier = tier.crop(cropStart, cropEnd, mode, rebaseToZero)
    259
    260            reportingMode: Literal[
    261                "silence", "warning", "error"
    262            ] = constants.ErrorReportingMode.WARNING
    263            if mode == constants.CropCollision.LAX:
    264                # We expect that there will be changes to the size
    265                # of the textgrid when the mode is LAX
    266                reportingMode = constants.ErrorReportingMode.SILENCE
    267
    268            newTG.addTier(newTier, reportingMode=reportingMode)
    269
    270        return newTG
    271
    272    def eraseRegion(self, start: float, end: float, doShrink: bool) -> "Textgrid":
    273        """Makes a region in a tier blank (removes all contained entries)
    274
    275        Intervals that span the region to erase will be truncated.
    276
    277        Args:
    278            start:
    279            end:
    280            doShrink: if True, all entries appearing after the
    281                erased interval will be shifted to fill the void (ie
    282                the duration of the textgrid will be reduced by
    283                *start* - *end*)
    284
    285        Returns:
    286            the modified version of the current textgrid
    287
    288        Raises:
    289            ArgumentError
    290        """
    291        if start >= end:
    292            raise errors.ArgumentError(
    293                f"EraseRegion error: start time ({start}) must occur before end time ({end})"
    294            )
    295
    296        diff = end - start
    297
    298        maxTimestamp = self.maxTimestamp
    299        if doShrink is True:
    300            maxTimestamp -= diff
    301
    302        newTG = Textgrid(self.minTimestamp, self.maxTimestamp)
    303        for tier in self.tiers:
    304            shrunkTier = tier.eraseRegion(
    305                start, end, constants.EraseCollision.TRUNCATE, doShrink
    306            )
    307            newTG.addTier(shrunkTier)
    308
    309        newTG.maxTimestamp = maxTimestamp
    310
    311        return newTG
    312
    313    def editTimestamps(
    314        self,
    315        offset: float,
    316        reportingMode: Literal["silence", "warning", "error"] = "warning",
    317    ) -> "Textgrid":
    318        """Modifies all timestamps by a constant amount
    319
    320        Args:
    321            offset: the amount to offset in seconds
    322            reportingMode: one of "silence", "warning", or "error". This flag
    323                determines the behavior if there is a size difference between the
    324                maxTimestamp in the tier and the current textgrid.
    325
    326        Returns:
    327            Textgrid: the modified version of the current textgrid
    328        """
    329        utils.validateOption(
    330            "reportingMode", reportingMode, constants.ErrorReportingMode
    331        )
    332
    333        tg = Textgrid(self.minTimestamp, self.maxTimestamp)
    334        for tier in self.tiers:
    335            if len(tier.entries) > 0:
    336                tier = tier.editTimestamps(offset, reportingMode)
    337
    338            tg.addTier(tier, reportingMode=reportingMode)
    339
    340        return tg
    341
    342    def getTier(self, tierName: str) -> textgrid_tier.TextgridTier:
    343        """Get the tier with the specified name"""
    344        return self._tierDict[tierName]
    345
    346    def insertSpace(
    347        self,
    348        start: float,
    349        duration: float,
    350        collisionMode: Literal["stretch", "split", "no_change", "error"] = "error",
    351    ) -> "Textgrid":
    352        """Inserts a blank region into a textgrid
    353
    354        Every item that occurs after *start* will be pushed back by
    355        *duration* seconds
    356
    357        Args:
    358            start:
    359            duration:
    360            collisionMode: Determines behavior in the event that an
    361                interval stradles the starting point.
    362                One of ['stretch', 'split', 'no change', None]
    363                - 'stretch' stretches the interval by /duration/ amount
    364                - 'split' splits the interval into two--everything to the
    365                    right of 'start' will be advanced by 'duration' seconds
    366                - 'no change' leaves the interval as is with no change
    367                - None or any other value throws an AssertionError
    368
    369        Returns:
    370            Textgrid: the modified version of the current textgrid
    371        """
    372        utils.validateOption(
    373            "collisionMode", collisionMode, constants.WhitespaceCollision
    374        )
    375
    376        newTG = Textgrid(self.minTimestamp, self.maxTimestamp)
    377        newTG.minTimestamp = self.minTimestamp
    378        newTG.maxTimestamp = self.maxTimestamp + duration
    379
    380        for tier in self.tiers:
    381            newTier = tier.insertSpace(start, duration, collisionMode)
    382            newTG.addTier(newTier)
    383
    384        return newTG
    385
    386    def mergeTiers(
    387        self, tierNames: Optional[Sequence[str]] = None, preserveOtherTiers: bool = True
    388    ) -> "Textgrid":
    389        """Combine tiers
    390
    391        Args:
    392            tierList: A list of tier names to combine. If none, combine
    393                all tiers.
    394            preserveOtherTiers: If false, uncombined tiers are not
    395                included in the output.
    396
    397        Returns:
    398            Textgrid: the modified version of the current textgrid
    399        """
    400        if tierNames is None:
    401            tierNames = self.tierNames
    402
    403        # Determine the tiers to merge
    404        intervalTierNames = []
    405        pointTierNames = []
    406        for tierName in tierNames:
    407            tier = self.getTier(tierName)
    408            if isinstance(tier, interval_tier.IntervalTier):
    409                intervalTierNames.append(tierName)
    410            elif isinstance(tier, point_tier.PointTier):
    411                pointTierNames.append(tierName)
    412
    413        # Merge the interval tiers
    414        intervalTier = None
    415        if len(intervalTierNames) > 0:
    416            intervalTier = self.getTier(intervalTierNames.pop(0))
    417            for tierName in intervalTierNames:
    418                intervalTier = intervalTier.union(self.getTier(tierName))
    419
    420        # Merge the point tiers
    421        pointTier = None
    422        if len(pointTierNames) > 0:
    423            pointTier = self.getTier(pointTierNames.pop(0))
    424            for tierName in pointTierNames:
    425                pointTier = pointTier.union(self.getTier(tierName))
    426
    427        # Create the final textgrid to output
    428        tg = Textgrid(self.minTimestamp, self.maxTimestamp)
    429
    430        if preserveOtherTiers:
    431            for tier in self.tiers:
    432                if tier.name not in tierNames:
    433                    tg.addTier(tier)
    434
    435        if intervalTier is not None:
    436            tg.addTier(intervalTier)
    437
    438        if pointTier is not None:
    439            tg.addTier(pointTier)
    440
    441        return tg
    442
    443    def new(self) -> "Textgrid":
    444        """Returns a copy of this Textgrid"""
    445        return copy.deepcopy(self)
    446
    447    def save(
    448        self,
    449        fn: str,
    450        format: Literal["short_textgrid", "long_textgrid", "json", "textgrid_json"],
    451        includeBlankSpaces: bool,
    452        minTimestamp: Optional[float] = None,
    453        maxTimestamp: Optional[float] = None,
    454        minimumIntervalLength: float = MIN_INTERVAL_LENGTH,
    455        reportingMode: Literal["silence", "warning", "error"] = "warning",
    456    ) -> None:
    457        """Save the current textgrid to a file
    458
    459        Args:
    460            fn: the fullpath filename of the output
    461            format: one of ['short_textgrid', 'long_textgrid', 'json', 'textgrid_json']
    462                'short_textgrid' and 'long_textgrid' are both used by praat
    463                'json' and 'textgrid_json' are two json variants. 'json' cannot represent
    464                tiers with different min and max timestamps than the textgrid.
    465            includeBlankSpaces: if True, blank sections in interval
    466                tiers will be filled in with an empty interval
    467                (with a label of ""). If you are unsure, True is recommended
    468                as Praat needs blanks to render textgrids properly.
    469            minTimestamp: the minTimestamp of the saved Textgrid;
    470                if None, use whatever is defined in the Textgrid object.
    471                If minTimestamp is larger than timestamps in your textgrid,
    472                an exception will be thrown.
    473            maxTimestamp: the maxTimestamp of the saved Textgrid;
    474                if None, use whatever is defined in the Textgrid object.
    475                If maxTimestamp is smaller than timestamps in your textgrid,
    476                an exception will be thrown.
    477            minimumIntervalLength: any labeled intervals smaller
    478                than this will be removed, useful for removing ultrashort
    479                or fragmented intervals; if None, don't remove any.
    480                Removed intervals are merged (without their label) into
    481                adjacent entries.
    482            reportingMode: one of "silence", "warning", or "error". This flag
    483                determines the behavior if there is a size difference between the
    484                maxTimestamp in the tier and the current textgrid.
    485
    486        Returns:
    487            a string representation of the textgrid
    488        """
    489
    490        utils.validateOption("format", format, TextgridFormats)
    491        utils.validateOption(
    492            "reportingMode", reportingMode, constants.ErrorReportingMode
    493        )
    494
    495        self.validate(reportingMode)
    496
    497        tgAsDict = _tgToDictionary(self)
    498
    499        textgridStr = textgrid_io.getTextgridAsStr(
    500            tgAsDict,
    501            format,
    502            includeBlankSpaces,
    503            minTimestamp,
    504            maxTimestamp,
    505            minimumIntervalLength,
    506        )
    507
    508        with io.open(fn, "w", encoding="utf-8") as fd:
    509            fd.write(textgridStr)
    510
    511    def renameTier(self, oldName: str, newName: str) -> None:
    512        oldTier = self.getTier(oldName)
    513        tierIndex = self.tierNames.index(oldName)
    514        self.removeTier(oldName)
    515        self.addTier(oldTier.new(newName, oldTier.entries), tierIndex)
    516
    517    def removeTier(self, name: str) -> textgrid_tier.TextgridTier:
    518        return self._tierDict.pop(name)
    519
    520    def replaceTier(
    521        self,
    522        name: str,
    523        newTier: textgrid_tier.TextgridTier,
    524        reportingMode: Literal["silence", "warning", "error"] = "warning",
    525    ) -> None:
    526        tierIndex = self.tierNames.index(name)
    527        self.removeTier(name)
    528        self.addTier(newTier, tierIndex, reportingMode)
    529
    530    def validate(
    531        self, reportingMode: Literal["silence", "warning", "error"] = "warning"
    532    ) -> bool:
    533        """Validates this textgrid
    534
    535        Returns whether the textgrid is valid or not. If reportingMode is "warning"
    536        or "error" this will also print on error or stop execution, respectively.
    537
    538        Args:
    539            reportingMode: one of "silence", "warning", or "error". This flag
    540                determines the behavior if there is a size difference between the
    541                maxTimestamp in a tier and the current textgrid.
    542
    543        Returns:
    544            True if this Textgrid is valid; False if not
    545
    546        Raises:
    547            TierNameExistsError: Two tiers have the same name
    548            TextgridStateError: A timestamp fall outside of the allowable range
    549        """
    550        utils.validateOption(
    551            "reportingMode", reportingMode, constants.ErrorReportingMode
    552        )
    553        errorReporter = utils.getErrorReporter(reportingMode)
    554
    555        isValid = True
    556        if len(self.tierNames) != len(set(self.tierNames)):
    557            isValid = False
    558            errorReporter(
    559                errors.TierNameExistsError,
    560                f"Tier names not unique: {self.tierNames}",
    561            )
    562
    563        for tier in self.tiers:
    564            if self.minTimestamp != tier.minTimestamp:
    565                isValid = False
    566                errorReporter(
    567                    errors.TextgridStateError,
    568                    f"Textgrid has a min timestamp of ({self.minTimestamp}) "
    569                    f"but tier has ({tier.minTimestamp})",
    570                )
    571
    572            if self.maxTimestamp != tier.maxTimestamp:
    573                isValid = False
    574                errorReporter(
    575                    errors.TextgridStateError,
    576                    f"Textgrid has a max timestamp of ({self.maxTimestamp}) "
    577                    f"but tier has ({tier.maxTimestamp})",
    578                )
    579
    580            isValid = isValid and tier.validate(reportingMode)
    581
    582        return isValid

A container that stores and operates over interval and point tiers

Textgrids are used by the Praat software to group tiers. Each tier contains different annotation information for an audio recording.

###### Attributes:

- tierNames(Tuple[str]): the list of tier names in the textgrid
- tiers(Tuple[TextgridTier]): the list of ordered tiers
- minTimestamp(float): the minimum allowable timestamp in the textgrid
- maxTimestamp(float): the maximum allowable timestamp in the textgrid

Textgrid(minTimestamp: float = None, maxTimestamp: float = None) View Source

    43    def __init__(self, minTimestamp: float = None, maxTimestamp: float = None):
    44        """Constructor for Textgrids
    45
    46        Args:
    47            minTimestamp: the minimum allowable timestamp in the textgrid
    48            maxTimestamp: the maximum allowable timestamp in the textgrid
    49        """
    50
    51        self._tierDict: OrderedDict[str, textgrid_tier.TextgridTier] = OrderedDict()
    52
    53        # Timestamps are determined by the first tier added
    54        self.minTimestamp: float = minTimestamp  # type: ignore[assignment]
    55        self.maxTimestamp: float = maxTimestamp  # type: ignore[assignment]

Constructor for Textgrids

###### Arguments:

- minTimestamp: the minimum allowable timestamp in the textgrid
- maxTimestamp: the maximum allowable timestamp in the textgrid

minTimestamp: float
maxTimestamp: float
tierNames: Tuple[str, ...]
tiers: Tuple[praatio.data_classes.textgrid_tier.TextgridTier, ...]
def addTier( self, tier: praatio.data_classes.textgrid_tier.TextgridTier, tierIndex: Optional[int] = None, reportingMode: Literal['silence', 'warning', 'error'] = 'warning') -> None: View Source

     87    def addTier(
     88        self,
     89        tier: textgrid_tier.TextgridTier,
     90        tierIndex: Optional[int] = None,
     91        reportingMode: Literal["silence", "warning", "error"] = "warning",
     92    ) -> None:
     93        """Add a tier to this textgrid.
     94
     95        Args:
     96            tier: The tier to add to the textgrid
     97            tierIndex: Insert the tier into the specified position;
     98                if None, the tier will appear after all existing tiers
     99            reportingMode: This flag determines the behavior if there is a size
    100                difference between the maxTimestamp in the tier and the current
    101                textgrid.
    102
    103        Returns:
    104            None
    105
    106        Raises:
    107            TierNameExistsError: The textgrid already contains a tier with the same
    108                name as the tier being added
    109            TextgridStateAutoModified: The minimum or maximum timestamp was changed
    110                when not permitted
    111            IndexError: TierIndex is too large for the size of the existing tier list
    112        """
    113        utils.validateOption(
    114            "reportingMode", reportingMode, constants.ErrorReportingMode
    115        )
    116        errorReporter = utils.getErrorReporter(reportingMode)
    117
    118        if tier.name in self.tierNames:
    119            raise errors.TierNameExistsError("Tier name already in tier")
    120
    121        if tierIndex is None:
    122            self._tierDict[tier.name] = tier
    123        else:  # Need to recreate the tierDict with the new order
    124            newOrderedTierNameList = list(self.tierNames)
    125            newOrderedTierNameList.insert(tierIndex, tier.name)
    126
    127            newTierDict = OrderedDict()
    128            self._tierDict[tier.name] = tier
    129            for tmpName in newOrderedTierNameList:
    130                newTierDict[tmpName] = self.getTier(tmpName)
    131            self._tierDict = newTierDict
    132
    133        minV = tier.minTimestamp
    134        if self.minTimestamp is not None and minV < self.minTimestamp:
    135            errorReporter(
    136                errors.TextgridStateAutoModified,
    137                f"Minimum timestamp in Textgrid changed from ({self.minTimestamp}) to ({minV})",
    138            )
    139        if self.minTimestamp is None or minV < self.minTimestamp:
    140            self.minTimestamp = minV
    141
    142        maxV = tier.maxTimestamp
    143        if self.maxTimestamp is not None and maxV > self.maxTimestamp:
    144            errorReporter(
    145                errors.TextgridStateAutoModified,
    146                f"Maximum timestamp in Textgrid changed from ({self.maxTimestamp}) to ({maxV})",
    147            )
    148        if self.maxTimestamp is None or maxV > self.maxTimestamp:
    149            self.maxTimestamp = maxV

Add a tier to this textgrid.

###### Arguments:

- tier: The tier to add to the textgrid
- tierIndex: Insert the tier into the specified position; if None, the tier will appear after all existing tiers
- reportingMode: This flag determines the behavior if there is a size difference between the maxTimestamp in the tier and the current textgrid.

###### Returns:

> None

###### Raises:

- TierNameExistsError: The textgrid already contains a tier with the same name as the tier being added
- TextgridStateAutoModified: The minimum or maximum timestamp was changed when not permitted
- IndexError: TierIndex is too large for the size of the existing tier list

def appendTextgrid( self, tg: Textgrid, onlyMatchingNames: bool) -> Textgrid: View Source

    151    def appendTextgrid(self, tg: "Textgrid", onlyMatchingNames: bool) -> "Textgrid":
    152        """Append one textgrid to the end of this one
    153
    154        Args:
    155            tg: the textgrid to add to this one
    156            onlyMatchingNames: if False, tiers that don't appear in both
    157                textgrids will also appear
    158
    159        Returns:
    160            the modified version of the current textgrid
    161        """
    162        minTime = self.minTimestamp
    163        maxTime = self.maxTimestamp + tg.maxTimestamp
    164        retTG = Textgrid(minTime, maxTime)
    165
    166        # Get all tier names.  Ordered first by this textgrid and
    167        # then by the other textgrid.
    168        combinedTierNames = list(self.tierNames)
    169        for tierName in tg.tierNames:
    170            if tierName not in combinedTierNames:
    171                combinedTierNames.append(tierName)
    172
    173        # Determine the tier names that will be in the final textgrid
    174        finalTierNames = []
    175        if onlyMatchingNames is False:
    176            finalTierNames = combinedTierNames
    177        else:
    178            for tierName in combinedTierNames:
    179                if tierName in self.tierNames:
    180                    if tierName in tg.tierNames:
    181                        finalTierNames.append(tierName)
    182
    183        # Add tiers from this textgrid
    184        for tierName in finalTierNames:
    185            if tierName in self.tierNames:
    186                tier = self.getTier(tierName)
    187                retTG.addTier(tier)
    188
    189        # Add tiers from the given textgrid
    190        for tierName in finalTierNames:
    191            if tierName in tg.tierNames:
    192                appendTier = tg.getTier(tierName)
    193                appendTier = appendTier.new(minTimestamp=minTime, maxTimestamp=maxTime)
    194
    195                appendTier = appendTier.editTimestamps(self.maxTimestamp)
    196
    197                if tierName in retTG.tierNames:
    198                    tier = retTG.getTier(tierName)
    199                    newEntries = retTG.getTier(tierName).entries
    200                    newEntries += appendTier.entries
    201
    202                    tier = tier.new(
    203                        entries=newEntries,
    204                        minTimestamp=minTime,
    205                        maxTimestamp=maxTime,
    206                    )
    207                    retTG.replaceTier(tierName, tier)
    208
    209                else:
    210                    tier = appendTier
    211                    tier = tier.new(minTimestamp=minTime, maxTimestamp=maxTime)
    212                    retTG.addTier(tier)
    213
    214        return retTG

Append one textgrid to the end of this one

###### Arguments:

- tg: the textgrid to add to this one
- onlyMatchingNames: if False, tiers that don't appear in both textgrids will also appear

###### Returns:

> the modified version of the current textgrid

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

    216    def crop(
    217        self,
    218        cropStart: float,
    219        cropEnd: float,
    220        mode: Literal["strict", "lax", "truncated"],
    221        rebaseToZero: bool,
    222    ) -> "Textgrid":
    223        """Creates a textgrid where all intervals fit within the crop region
    224
    225        Args:
    226            cropStart: The start time of the crop interval
    227            cropEnd: The stop time of the crop interval
    228            mode: Determines the crop behavior
    229                - 'strict', only intervals wholly contained by the crop
    230                    interval will be kept
    231                - 'lax', partially contained intervals will be kept
    232                - 'truncated', partially contained intervals will be
    233                    truncated to fit within the crop region.
    234            rebaseToZero: if True, the cropped textgrid timestamps will be
    235                subtracted by the cropStart; if False, timestamps will not
    236                be changed
    237
    238        Returns:
    239            the modified version of the current textgrid
    240        """
    241        utils.validateOption("mode", mode, CropCollision)
    242
    243        if cropStart >= cropEnd:
    244            raise errors.ArgumentError(
    245                f"Crop error: start time ({cropStart}) must occur before end time ({cropEnd})"
    246            )
    247
    248        if rebaseToZero is True:
    249            minT = 0.0
    250            maxT = cropEnd - cropStart
    251        else:
    252            minT = cropStart
    253            maxT = cropEnd
    254        newTG = Textgrid(minT, maxT)
    255
    256        for tierName in self.tierNames:
    257            tier = self.getTier(tierName)
    258            newTier = tier.crop(cropStart, cropEnd, mode, rebaseToZero)
    259
    260            reportingMode: Literal[
    261                "silence", "warning", "error"
    262            ] = constants.ErrorReportingMode.WARNING
    263            if mode == constants.CropCollision.LAX:
    264                # We expect that there will be changes to the size
    265                # of the textgrid when the mode is LAX
    266                reportingMode = constants.ErrorReportingMode.SILENCE
    267
    268            newTG.addTier(newTier, reportingMode=reportingMode)
    269
    270        return newTG

Creates a textgrid where all intervals fit within the crop region

###### Arguments:

- cropStart: The start time of the crop interval
- cropEnd: The stop time of the crop interval
- mode: Determines the crop behavior
  - 'strict', only intervals wholly contained by the crop interval will be kept
  - 'lax', partially contained intervals will be kept
  - 'truncated', partially contained intervals will be truncated to fit within the crop region.
- rebaseToZero: if True, the cropped textgrid timestamps will be subtracted by the cropStart; if False, timestamps will not be changed

###### Returns:

> the modified version of the current textgrid

def eraseRegion( self, start: float, end: float, doShrink: bool) -> Textgrid: View Source

    272    def eraseRegion(self, start: float, end: float, doShrink: bool) -> "Textgrid":
    273        """Makes a region in a tier blank (removes all contained entries)
    274
    275        Intervals that span the region to erase will be truncated.
    276
    277        Args:
    278            start:
    279            end:
    280            doShrink: if True, all entries appearing after the
    281                erased interval will be shifted to fill the void (ie
    282                the duration of the textgrid will be reduced by
    283                *start* - *end*)
    284
    285        Returns:
    286            the modified version of the current textgrid
    287
    288        Raises:
    289            ArgumentError
    290        """
    291        if start >= end:
    292            raise errors.ArgumentError(
    293                f"EraseRegion error: start time ({start}) must occur before end time ({end})"
    294            )
    295
    296        diff = end - start
    297
    298        maxTimestamp = self.maxTimestamp
    299        if doShrink is True:
    300            maxTimestamp -= diff
    301
    302        newTG = Textgrid(self.minTimestamp, self.maxTimestamp)
    303        for tier in self.tiers:
    304            shrunkTier = tier.eraseRegion(
    305                start, end, constants.EraseCollision.TRUNCATE, doShrink
    306            )
    307            newTG.addTier(shrunkTier)
    308
    309        newTG.maxTimestamp = maxTimestamp
    310
    311        return newTG

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

Intervals that span the region to erase will be truncated.

###### Arguments:

- start:
- end:
- doShrink: if True, all entries appearing after the erased interval will be shifted to fill the void (ie the duration of the textgrid will be reduced by start - end)

###### Returns:

> the modified version of the current textgrid

###### Raises:

- ArgumentError

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

    313    def editTimestamps(
    314        self,
    315        offset: float,
    316        reportingMode: Literal["silence", "warning", "error"] = "warning",
    317    ) -> "Textgrid":
    318        """Modifies all timestamps by a constant amount
    319
    320        Args:
    321            offset: the amount to offset in seconds
    322            reportingMode: one of "silence", "warning", or "error". This flag
    323                determines the behavior if there is a size difference between the
    324                maxTimestamp in the tier and the current textgrid.
    325
    326        Returns:
    327            Textgrid: the modified version of the current textgrid
    328        """
    329        utils.validateOption(
    330            "reportingMode", reportingMode, constants.ErrorReportingMode
    331        )
    332
    333        tg = Textgrid(self.minTimestamp, self.maxTimestamp)
    334        for tier in self.tiers:
    335            if len(tier.entries) > 0:
    336                tier = tier.editTimestamps(offset, reportingMode)
    337
    338            tg.addTier(tier, reportingMode=reportingMode)
    339
    340        return tg

Modifies all timestamps by a constant amount

###### Arguments:

- offset: the amount to offset in seconds
- reportingMode: one of "silence", "warning", or "error". This flag determines the behavior if there is a size difference between the maxTimestamp in the tier and the current textgrid.

###### Returns:

> Textgrid: the modified version of the current textgrid

def getTier(self, tierName: str) -> praatio.data_classes.textgrid_tier.TextgridTier: View Source

    342    def getTier(self, tierName: str) -> textgrid_tier.TextgridTier:
    343        """Get the tier with the specified name"""
    344        return self._tierDict[tierName]

Get the tier with the specified name

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

    346    def insertSpace(
    347        self,
    348        start: float,
    349        duration: float,
    350        collisionMode: Literal["stretch", "split", "no_change", "error"] = "error",
    351    ) -> "Textgrid":
    352        """Inserts a blank region into a textgrid
    353
    354        Every item that occurs after *start* will be pushed back by
    355        *duration* seconds
    356
    357        Args:
    358            start:
    359            duration:
    360            collisionMode: Determines behavior in the event that an
    361                interval stradles the starting point.
    362                One of ['stretch', 'split', 'no change', None]
    363                - 'stretch' stretches the interval by /duration/ amount
    364                - 'split' splits the interval into two--everything to the
    365                    right of 'start' will be advanced by 'duration' seconds
    366                - 'no change' leaves the interval as is with no change
    367                - None or any other value throws an AssertionError
    368
    369        Returns:
    370            Textgrid: the modified version of the current textgrid
    371        """
    372        utils.validateOption(
    373            "collisionMode", collisionMode, constants.WhitespaceCollision
    374        )
    375
    376        newTG = Textgrid(self.minTimestamp, self.maxTimestamp)
    377        newTG.minTimestamp = self.minTimestamp
    378        newTG.maxTimestamp = self.maxTimestamp + duration
    379
    380        for tier in self.tiers:
    381            newTier = tier.insertSpace(start, duration, collisionMode)
    382            newTG.addTier(newTier)
    383
    384        return newTG

Inserts a blank region into a textgrid

Every item that occurs after start will be pushed back by duration seconds

###### Arguments:

- start:
- duration:
- collisionMode: Determines behavior in the event that an interval stradles the starting point. One of ['stretch', 'split', 'no change', None]
  - 'stretch' stretches the interval by /duration/ amount
  - 'split' splits the interval into two--everything to the right of 'start' will be advanced by 'duration' seconds
  - 'no change' leaves the interval as is with no change
  - None or any other value throws an AssertionError

###### Returns:

> Textgrid: the modified version of the current textgrid

def mergeTiers( self, tierNames: Optional[Sequence[str]] = None, preserveOtherTiers: bool = True) -> Textgrid: View Source

    386    def mergeTiers(
    387        self, tierNames: Optional[Sequence[str]] = None, preserveOtherTiers: bool = True
    388    ) -> "Textgrid":
    389        """Combine tiers
    390
    391        Args:
    392            tierList: A list of tier names to combine. If none, combine
    393                all tiers.
    394            preserveOtherTiers: If false, uncombined tiers are not
    395                included in the output.
    396
    397        Returns:
    398            Textgrid: the modified version of the current textgrid
    399        """
    400        if tierNames is None:
    401            tierNames = self.tierNames
    402
    403        # Determine the tiers to merge
    404        intervalTierNames = []
    405        pointTierNames = []
    406        for tierName in tierNames:
    407            tier = self.getTier(tierName)
    408            if isinstance(tier, interval_tier.IntervalTier):
    409                intervalTierNames.append(tierName)
    410            elif isinstance(tier, point_tier.PointTier):
    411                pointTierNames.append(tierName)
    412
    413        # Merge the interval tiers
    414        intervalTier = None
    415        if len(intervalTierNames) > 0:
    416            intervalTier = self.getTier(intervalTierNames.pop(0))
    417            for tierName in intervalTierNames:
    418                intervalTier = intervalTier.union(self.getTier(tierName))
    419
    420        # Merge the point tiers
    421        pointTier = None
    422        if len(pointTierNames) > 0:
    423            pointTier = self.getTier(pointTierNames.pop(0))
    424            for tierName in pointTierNames:
    425                pointTier = pointTier.union(self.getTier(tierName))
    426
    427        # Create the final textgrid to output
    428        tg = Textgrid(self.minTimestamp, self.maxTimestamp)
    429
    430        if preserveOtherTiers:
    431            for tier in self.tiers:
    432                if tier.name not in tierNames:
    433                    tg.addTier(tier)
    434
    435        if intervalTier is not None:
    436            tg.addTier(intervalTier)
    437
    438        if pointTier is not None:
    439            tg.addTier(pointTier)
    440
    441        return tg

Combine tiers

###### Arguments:

- tierList: A list of tier names to combine. If none, combine all tiers.
- preserveOtherTiers: If false, uncombined tiers are not included in the output.

###### Returns:

> Textgrid: the modified version of the current textgrid

def new(self) -> Textgrid: View Source

    443    def new(self) -> "Textgrid":
    444        """Returns a copy of this Textgrid"""
    445        return copy.deepcopy(self)

Returns a copy of this Textgrid

def save( self, fn: str, format: Literal['short_textgrid', 'long_textgrid', 'json', 'textgrid_json'], includeBlankSpaces: bool, minTimestamp: Optional[float] = None, maxTimestamp: Optional[float] = None, minimumIntervalLength: float = 1e-08, reportingMode: Literal['silence', 'warning', 'error'] = 'warning') -> None: View Source

    447    def save(
    448        self,
    449        fn: str,
    450        format: Literal["short_textgrid", "long_textgrid", "json", "textgrid_json"],
    451        includeBlankSpaces: bool,
    452        minTimestamp: Optional[float] = None,
    453        maxTimestamp: Optional[float] = None,
    454        minimumIntervalLength: float = MIN_INTERVAL_LENGTH,
    455        reportingMode: Literal["silence", "warning", "error"] = "warning",
    456    ) -> None:
    457        """Save the current textgrid to a file
    458
    459        Args:
    460            fn: the fullpath filename of the output
    461            format: one of ['short_textgrid', 'long_textgrid', 'json', 'textgrid_json']
    462                'short_textgrid' and 'long_textgrid' are both used by praat
    463                'json' and 'textgrid_json' are two json variants. 'json' cannot represent
    464                tiers with different min and max timestamps than the textgrid.
    465            includeBlankSpaces: if True, blank sections in interval
    466                tiers will be filled in with an empty interval
    467                (with a label of ""). If you are unsure, True is recommended
    468                as Praat needs blanks to render textgrids properly.
    469            minTimestamp: the minTimestamp of the saved Textgrid;
    470                if None, use whatever is defined in the Textgrid object.
    471                If minTimestamp is larger than timestamps in your textgrid,
    472                an exception will be thrown.
    473            maxTimestamp: the maxTimestamp of the saved Textgrid;
    474                if None, use whatever is defined in the Textgrid object.
    475                If maxTimestamp is smaller than timestamps in your textgrid,
    476                an exception will be thrown.
    477            minimumIntervalLength: any labeled intervals smaller
    478                than this will be removed, useful for removing ultrashort
    479                or fragmented intervals; if None, don't remove any.
    480                Removed intervals are merged (without their label) into
    481                adjacent entries.
    482            reportingMode: one of "silence", "warning", or "error". This flag
    483                determines the behavior if there is a size difference between the
    484                maxTimestamp in the tier and the current textgrid.
    485
    486        Returns:
    487            a string representation of the textgrid
    488        """
    489
    490        utils.validateOption("format", format, TextgridFormats)
    491        utils.validateOption(
    492            "reportingMode", reportingMode, constants.ErrorReportingMode
    493        )
    494
    495        self.validate(reportingMode)
    496
    497        tgAsDict = _tgToDictionary(self)
    498
    499        textgridStr = textgrid_io.getTextgridAsStr(
    500            tgAsDict,
    501            format,
    502            includeBlankSpaces,
    503            minTimestamp,
    504            maxTimestamp,
    505            minimumIntervalLength,
    506        )
    507
    508        with io.open(fn, "w", encoding="utf-8") as fd:
    509            fd.write(textgridStr)

Save the current textgrid to a file

###### Arguments:

- fn: the fullpath filename of the output
- format: one of ['short_textgrid', 'long_textgrid', 'json', 'textgrid_json'] 'short_textgrid' and 'long_textgrid' are both used by praat 'json' and 'textgrid_json' are two json variants. 'json' cannot represent tiers with different min and max timestamps than the textgrid.
- includeBlankSpaces: if True, blank sections in interval tiers will be filled in with an empty interval (with a label of ""). If you are unsure, True is recommended as Praat needs blanks to render textgrids properly.
- minTimestamp: the minTimestamp of the saved Textgrid; if None, use whatever is defined in the Textgrid object. If minTimestamp is larger than timestamps in your textgrid, an exception will be thrown.
- maxTimestamp: the maxTimestamp of the saved Textgrid; if None, use whatever is defined in the Textgrid object. If maxTimestamp is smaller than timestamps in your textgrid, an exception will be thrown.
- minimumIntervalLength: any labeled intervals smaller than this will be removed, useful for removing ultrashort or fragmented intervals; if None, don't remove any. Removed intervals are merged (without their label) into adjacent entries.
- reportingMode: one of "silence", "warning", or "error". This flag determines the behavior if there is a size difference between the maxTimestamp in the tier and the current textgrid.

###### Returns:

> a string representation of the textgrid

def renameTier(self, oldName: str, newName: str) -> None: View Source

    511    def renameTier(self, oldName: str, newName: str) -> None:
    512        oldTier = self.getTier(oldName)
    513        tierIndex = self.tierNames.index(oldName)
    514        self.removeTier(oldName)
    515        self.addTier(oldTier.new(newName, oldTier.entries), tierIndex)

def removeTier(self, name: str) -> praatio.data_classes.textgrid_tier.TextgridTier: View Source

    517    def removeTier(self, name: str) -> textgrid_tier.TextgridTier:
    518        return self._tierDict.pop(name)

def replaceTier( self, name: str, newTier: praatio.data_classes.textgrid_tier.TextgridTier, reportingMode: Literal['silence', 'warning', 'error'] = 'warning') -> None: View Source

    520    def replaceTier(
    521        self,
    522        name: str,
    523        newTier: textgrid_tier.TextgridTier,
    524        reportingMode: Literal["silence", "warning", "error"] = "warning",
    525    ) -> None:
    526        tierIndex = self.tierNames.index(name)
    527        self.removeTier(name)
    528        self.addTier(newTier, tierIndex, reportingMode)

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

    530    def validate(
    531        self, reportingMode: Literal["silence", "warning", "error"] = "warning"
    532    ) -> bool:
    533        """Validates this textgrid
    534
    535        Returns whether the textgrid is valid or not. If reportingMode is "warning"
    536        or "error" this will also print on error or stop execution, respectively.
    537
    538        Args:
    539            reportingMode: one of "silence", "warning", or "error". This flag
    540                determines the behavior if there is a size difference between the
    541                maxTimestamp in a tier and the current textgrid.
    542
    543        Returns:
    544            True if this Textgrid is valid; False if not
    545
    546        Raises:
    547            TierNameExistsError: Two tiers have the same name
    548            TextgridStateError: A timestamp fall outside of the allowable range
    549        """
    550        utils.validateOption(
    551            "reportingMode", reportingMode, constants.ErrorReportingMode
    552        )
    553        errorReporter = utils.getErrorReporter(reportingMode)
    554
    555        isValid = True
    556        if len(self.tierNames) != len(set(self.tierNames)):
    557            isValid = False
    558            errorReporter(
    559                errors.TierNameExistsError,
    560                f"Tier names not unique: {self.tierNames}",
    561            )
    562
    563        for tier in self.tiers:
    564            if self.minTimestamp != tier.minTimestamp:
    565                isValid = False
    566                errorReporter(
    567                    errors.TextgridStateError,
    568                    f"Textgrid has a min timestamp of ({self.minTimestamp}) "
    569                    f"but tier has ({tier.minTimestamp})",
    570                )
    571
    572            if self.maxTimestamp != tier.maxTimestamp:
    573                isValid = False
    574                errorReporter(
    575                    errors.TextgridStateError,
    576                    f"Textgrid has a max timestamp of ({self.maxTimestamp}) "
    577                    f"but tier has ({tier.maxTimestamp})",
    578                )
    579
    580            isValid = isValid and tier.validate(reportingMode)
    581
    582        return isValid

Validates this textgrid

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

###### Arguments:

- reportingMode: one of "silence", "warning", or "error". This flag determines the behavior if there is a size difference between the maxTimestamp in a tier and the current textgrid.

###### Returns:

> True if this Textgrid is valid; False if not

###### Raises:

- TierNameExistsError: Two tiers have the same name
- TextgridStateError: A timestamp fall outside of the allowable range