praatio.data_classes.textgrid_tier
The abstract class used by all textgrid tiers
1""" 2The abstract class used by all textgrid tiers 3""" 4import re 5import copy 6import math 7from typing import ( 8 List, 9 Optional, 10 Type, 11 TypeVar, 12 Union, 13) 14from abc import ABC, abstractmethod 15 16from typing_extensions import Literal 17 18 19from praatio.utilities import constants 20from praatio.utilities import errors 21from praatio.utilities import utils 22 23T = TypeVar("T", bound="TextgridTier") 24 25 26class TextgridTier(ABC): 27 tierType: str 28 entryType: Union[Type[constants.Point], Type[constants.Interval]] 29 30 def __init__( 31 self, 32 name: str, 33 entries: List, 34 minT: float, 35 maxT: float, 36 errorMode: Literal["silence", "warning", "error"] = "warning", 37 ): 38 "A container that stores and operates over interval and point tiers" 39 utils.validateOption("errorMode", errorMode, constants.ErrorReportingMode) 40 41 """See PointTier or IntervalTier""" 42 entries.sort() 43 44 self.name = name 45 self._entries = entries 46 self.minTimestamp = minT 47 self.maxTimestamp = maxT 48 self.errorReporter = utils.getErrorReporter(errorMode) 49 50 def __len__(self): 51 return len(self._entries) 52 53 def __iter__(self): 54 for entry in self.entries: 55 yield entry 56 57 def __eq__(self, other): 58 if not isinstance(self, type(other)): 59 return False 60 61 isEqual = True 62 isEqual &= self.name == other.name 63 isEqual &= math.isclose(self.minTimestamp, other.minTimestamp) 64 isEqual &= math.isclose(self.maxTimestamp, other.maxTimestamp) 65 isEqual &= len(self.entries) == len(other.entries) 66 67 # TODO: Intervals and Points now use isclose, so we can simplify this 68 # logic (selfEntry == otherEntry); however, this will break 69 # things for klattgrids 70 if isEqual: 71 for selfEntry, otherEntry in zip(self.entries, other.entries): 72 for selfSubEntry, otherSubEntry in zip(selfEntry, otherEntry): 73 try: 74 isEqual &= math.isclose(selfSubEntry, otherSubEntry) 75 except TypeError: 76 isEqual &= selfSubEntry == otherSubEntry 77 78 return isEqual 79 80 @property 81 def entries(self): 82 return tuple(self._entries) 83 84 @property 85 @abstractmethod 86 def timestamps(self) -> List[float]: 87 pass 88 89 def appendTier(self, tier: "TextgridTier") -> "TextgridTier": 90 """Append a tier to the end of this one. 91 92 This tier's maxtimestamp will be lengthened by the amount in the passed in tier. 93 """ 94 if self.tierType != tier.tierType: 95 raise errors.ArgumentError( 96 f"Cannot append a tier of type {type(self)} to a tier of type {type(tier)}." 97 ) 98 99 maxTime = self.maxTimestamp + tier.maxTimestamp 100 101 # We're increasing the size of the tier, so of course its intervals 102 # may exceed the bounds of the tier's maxTimestamp, triggering 103 # errors/warnings--we can ignore those 104 appendTier = tier.editTimestamps( 105 self.maxTimestamp, constants.ErrorReportingMode.SILENCE 106 ) 107 108 entries = self._entries + appendTier._entries 109 entries.sort() 110 111 return self.new( 112 self.name, entries, minTimestamp=self.minTimestamp, maxTimestamp=maxTime 113 ) 114 115 def find( 116 self, 117 matchLabel: str, 118 substrMatchFlag: bool = False, 119 usingRE: bool = False, 120 ) -> List[int]: 121 """Returns the index of all intervals that match the given label 122 123 Args: 124 matchLabel: the label to search for 125 substrMatchFlag: if True, match any label containing matchLabel. 126 if False, label must be the same as matchLabel. 127 usingRE: if True, matchLabel is interpreted as a regular expression 128 129 Returns: 130 A list of indicies 131 """ 132 returnList = [] 133 if usingRE is True: 134 for i, entry in enumerate(self.entries): 135 matchList = re.findall(matchLabel, entry.label, re.I) 136 if matchList != []: 137 returnList.append(i) 138 else: 139 for i, entry in enumerate(self.entries): 140 if not substrMatchFlag: 141 if entry.label == matchLabel: 142 returnList.append(i) 143 else: 144 if matchLabel in entry.label: 145 returnList.append(i) 146 147 return returnList 148 149 def new( 150 self: T, 151 name: Optional[str] = None, 152 entries: Optional[list] = None, 153 minTimestamp: Optional[float] = None, 154 maxTimestamp: Optional[float] = None, 155 ) -> T: 156 """Make a new tier derived from the current one""" 157 if name is None: 158 name = self.name 159 if entries is None: 160 entries = copy.deepcopy(self.entries) 161 entries = [ 162 self.entryType(*entry) 163 if isinstance(entry, tuple) or isinstance(entry, list) 164 else entry 165 for entry in entries 166 ] 167 if minTimestamp is None: 168 minTimestamp = self.minTimestamp 169 if maxTimestamp is None: 170 maxTimestamp = self.maxTimestamp 171 return type(self)(name, entries, minTimestamp, maxTimestamp) 172 173 def sort(self) -> None: 174 """Sorts the entries in the list of entries""" 175 # A list containing tuples and lists will be sorted with tuples 176 # first and then lists. To correctly sort, we need to make 177 # sure that all data structures inside the entry list are 178 # of the same data type. The entry list is sorted whenever 179 # the entry list is modified, so this is probably the best 180 # place to enforce the data type 181 self._entries = [ 182 entry if isinstance(entry, self.entryType) else self.entryType(*entry) 183 for entry in self.entries 184 ] 185 self._entries.sort() 186 187 def union(self, tier: "TextgridTier") -> "TextgridTier": 188 """The given tier is set unioned to this tier. 189 190 All entries in the given tier are added to the current tier. 191 Overlapping entries are merged. 192 """ 193 retTier = self.new() 194 195 for entry in tier.entries: 196 retTier.insertEntry( 197 entry, 198 collisionMode=constants.IntervalCollision.MERGE, 199 collisionReportingMode=constants.ErrorReportingMode.SILENCE, 200 ) 201 202 retTier.sort() 203 204 return retTier 205 206 @abstractmethod 207 def editTimestamps( 208 self, 209 offset: float, 210 reportingMode: Literal["silence", "warning", "error"] = "warning", 211 ) -> "TextgridTier": # pragma: no cover 212 pass 213 214 @abstractmethod 215 def insertEntry( 216 self, 217 entry, 218 collisionMode: Literal["replace", "merge", "error"] = "error", 219 collisionReportingMode: Literal["silence", "warning"] = "warning", 220 ) -> None: # pragma: no cover 221 pass 222 223 @abstractmethod 224 def dejitter( 225 self, 226 referenceTier: "TextgridTier", 227 maxDifference: float = 0.001, 228 ) -> "TextgridTier": # pragma: no cover 229 pass 230 231 @abstractmethod 232 def eraseRegion( 233 self, 234 start: float, 235 end: float, 236 collisionMode: Literal["truncate", "categorical", "error"] = "error", 237 doShrink: bool = True, 238 ) -> "TextgridTier": # pragma: no cover 239 pass 240 241 @abstractmethod 242 def crop( 243 self, 244 cropStart: float, 245 cropEnd: float, 246 mode: Literal["strict", "lax", "truncated"], 247 rebaseToZero: bool, 248 ) -> "TextgridTier": # pragma: no cover 249 pass 250 251 @abstractmethod 252 def insertSpace( 253 self, 254 start: float, 255 duration: float, 256 collisionMode: Literal["stretch", "split", "no_change", "error"], 257 ) -> "TextgridTier": # pragma: no cover 258 pass 259 260 @abstractmethod 261 def deleteEntry(self, entry) -> None: # pragma: no cover 262 pass 263 264 @abstractmethod 265 def validate(self, reportingMode) -> bool: # pragma: no cover 266 pass
class
TextgridTier(abc.ABC):
27class TextgridTier(ABC): 28 tierType: str 29 entryType: Union[Type[constants.Point], Type[constants.Interval]] 30 31 def __init__( 32 self, 33 name: str, 34 entries: List, 35 minT: float, 36 maxT: float, 37 errorMode: Literal["silence", "warning", "error"] = "warning", 38 ): 39 "A container that stores and operates over interval and point tiers" 40 utils.validateOption("errorMode", errorMode, constants.ErrorReportingMode) 41 42 """See PointTier or IntervalTier""" 43 entries.sort() 44 45 self.name = name 46 self._entries = entries 47 self.minTimestamp = minT 48 self.maxTimestamp = maxT 49 self.errorReporter = utils.getErrorReporter(errorMode) 50 51 def __len__(self): 52 return len(self._entries) 53 54 def __iter__(self): 55 for entry in self.entries: 56 yield entry 57 58 def __eq__(self, other): 59 if not isinstance(self, type(other)): 60 return False 61 62 isEqual = True 63 isEqual &= self.name == other.name 64 isEqual &= math.isclose(self.minTimestamp, other.minTimestamp) 65 isEqual &= math.isclose(self.maxTimestamp, other.maxTimestamp) 66 isEqual &= len(self.entries) == len(other.entries) 67 68 # TODO: Intervals and Points now use isclose, so we can simplify this 69 # logic (selfEntry == otherEntry); however, this will break 70 # things for klattgrids 71 if isEqual: 72 for selfEntry, otherEntry in zip(self.entries, other.entries): 73 for selfSubEntry, otherSubEntry in zip(selfEntry, otherEntry): 74 try: 75 isEqual &= math.isclose(selfSubEntry, otherSubEntry) 76 except TypeError: 77 isEqual &= selfSubEntry == otherSubEntry 78 79 return isEqual 80 81 @property 82 def entries(self): 83 return tuple(self._entries) 84 85 @property 86 @abstractmethod 87 def timestamps(self) -> List[float]: 88 pass 89 90 def appendTier(self, tier: "TextgridTier") -> "TextgridTier": 91 """Append a tier to the end of this one. 92 93 This tier's maxtimestamp will be lengthened by the amount in the passed in tier. 94 """ 95 if self.tierType != tier.tierType: 96 raise errors.ArgumentError( 97 f"Cannot append a tier of type {type(self)} to a tier of type {type(tier)}." 98 ) 99 100 maxTime = self.maxTimestamp + tier.maxTimestamp 101 102 # We're increasing the size of the tier, so of course its intervals 103 # may exceed the bounds of the tier's maxTimestamp, triggering 104 # errors/warnings--we can ignore those 105 appendTier = tier.editTimestamps( 106 self.maxTimestamp, constants.ErrorReportingMode.SILENCE 107 ) 108 109 entries = self._entries + appendTier._entries 110 entries.sort() 111 112 return self.new( 113 self.name, entries, minTimestamp=self.minTimestamp, maxTimestamp=maxTime 114 ) 115 116 def find( 117 self, 118 matchLabel: str, 119 substrMatchFlag: bool = False, 120 usingRE: bool = False, 121 ) -> List[int]: 122 """Returns the index of all intervals that match the given label 123 124 Args: 125 matchLabel: the label to search for 126 substrMatchFlag: if True, match any label containing matchLabel. 127 if False, label must be the same as matchLabel. 128 usingRE: if True, matchLabel is interpreted as a regular expression 129 130 Returns: 131 A list of indicies 132 """ 133 returnList = [] 134 if usingRE is True: 135 for i, entry in enumerate(self.entries): 136 matchList = re.findall(matchLabel, entry.label, re.I) 137 if matchList != []: 138 returnList.append(i) 139 else: 140 for i, entry in enumerate(self.entries): 141 if not substrMatchFlag: 142 if entry.label == matchLabel: 143 returnList.append(i) 144 else: 145 if matchLabel in entry.label: 146 returnList.append(i) 147 148 return returnList 149 150 def new( 151 self: T, 152 name: Optional[str] = None, 153 entries: Optional[list] = None, 154 minTimestamp: Optional[float] = None, 155 maxTimestamp: Optional[float] = None, 156 ) -> T: 157 """Make a new tier derived from the current one""" 158 if name is None: 159 name = self.name 160 if entries is None: 161 entries = copy.deepcopy(self.entries) 162 entries = [ 163 self.entryType(*entry) 164 if isinstance(entry, tuple) or isinstance(entry, list) 165 else entry 166 for entry in entries 167 ] 168 if minTimestamp is None: 169 minTimestamp = self.minTimestamp 170 if maxTimestamp is None: 171 maxTimestamp = self.maxTimestamp 172 return type(self)(name, entries, minTimestamp, maxTimestamp) 173 174 def sort(self) -> None: 175 """Sorts the entries in the list of entries""" 176 # A list containing tuples and lists will be sorted with tuples 177 # first and then lists. To correctly sort, we need to make 178 # sure that all data structures inside the entry list are 179 # of the same data type. The entry list is sorted whenever 180 # the entry list is modified, so this is probably the best 181 # place to enforce the data type 182 self._entries = [ 183 entry if isinstance(entry, self.entryType) else self.entryType(*entry) 184 for entry in self.entries 185 ] 186 self._entries.sort() 187 188 def union(self, tier: "TextgridTier") -> "TextgridTier": 189 """The given tier is set unioned to this tier. 190 191 All entries in the given tier are added to the current tier. 192 Overlapping entries are merged. 193 """ 194 retTier = self.new() 195 196 for entry in tier.entries: 197 retTier.insertEntry( 198 entry, 199 collisionMode=constants.IntervalCollision.MERGE, 200 collisionReportingMode=constants.ErrorReportingMode.SILENCE, 201 ) 202 203 retTier.sort() 204 205 return retTier 206 207 @abstractmethod 208 def editTimestamps( 209 self, 210 offset: float, 211 reportingMode: Literal["silence", "warning", "error"] = "warning", 212 ) -> "TextgridTier": # pragma: no cover 213 pass 214 215 @abstractmethod 216 def insertEntry( 217 self, 218 entry, 219 collisionMode: Literal["replace", "merge", "error"] = "error", 220 collisionReportingMode: Literal["silence", "warning"] = "warning", 221 ) -> None: # pragma: no cover 222 pass 223 224 @abstractmethod 225 def dejitter( 226 self, 227 referenceTier: "TextgridTier", 228 maxDifference: float = 0.001, 229 ) -> "TextgridTier": # pragma: no cover 230 pass 231 232 @abstractmethod 233 def eraseRegion( 234 self, 235 start: float, 236 end: float, 237 collisionMode: Literal["truncate", "categorical", "error"] = "error", 238 doShrink: bool = True, 239 ) -> "TextgridTier": # pragma: no cover 240 pass 241 242 @abstractmethod 243 def crop( 244 self, 245 cropStart: float, 246 cropEnd: float, 247 mode: Literal["strict", "lax", "truncated"], 248 rebaseToZero: bool, 249 ) -> "TextgridTier": # pragma: no cover 250 pass 251 252 @abstractmethod 253 def insertSpace( 254 self, 255 start: float, 256 duration: float, 257 collisionMode: Literal["stretch", "split", "no_change", "error"], 258 ) -> "TextgridTier": # pragma: no cover 259 pass 260 261 @abstractmethod 262 def deleteEntry(self, entry) -> None: # pragma: no cover 263 pass 264 265 @abstractmethod 266 def validate(self, reportingMode) -> bool: # pragma: no cover 267 pass
Helper class that provides a standard way to create an ABC using inheritance.
TextgridTier( name: str, entries: List, minT: float, maxT: float, errorMode: Literal['silence', 'warning', 'error'] = 'warning')
31 def __init__( 32 self, 33 name: str, 34 entries: List, 35 minT: float, 36 maxT: float, 37 errorMode: Literal["silence", "warning", "error"] = "warning", 38 ): 39 "A container that stores and operates over interval and point tiers" 40 utils.validateOption("errorMode", errorMode, constants.ErrorReportingMode) 41 42 """See PointTier or IntervalTier""" 43 entries.sort() 44 45 self.name = name 46 self._entries = entries 47 self.minTimestamp = minT 48 self.maxTimestamp = maxT 49 self.errorReporter = utils.getErrorReporter(errorMode)
A container that stores and operates over interval and point tiers
entryType: Union[Type[praatio.utilities.constants.Point], Type[praatio.utilities.constants.Interval]]
90 def appendTier(self, tier: "TextgridTier") -> "TextgridTier": 91 """Append a tier to the end of this one. 92 93 This tier's maxtimestamp will be lengthened by the amount in the passed in tier. 94 """ 95 if self.tierType != tier.tierType: 96 raise errors.ArgumentError( 97 f"Cannot append a tier of type {type(self)} to a tier of type {type(tier)}." 98 ) 99 100 maxTime = self.maxTimestamp + tier.maxTimestamp 101 102 # We're increasing the size of the tier, so of course its intervals 103 # may exceed the bounds of the tier's maxTimestamp, triggering 104 # errors/warnings--we can ignore those 105 appendTier = tier.editTimestamps( 106 self.maxTimestamp, constants.ErrorReportingMode.SILENCE 107 ) 108 109 entries = self._entries + appendTier._entries 110 entries.sort() 111 112 return self.new( 113 self.name, entries, minTimestamp=self.minTimestamp, maxTimestamp=maxTime 114 )
Append a tier to the end of this one.
This tier's maxtimestamp will be lengthened by the amount in the passed in tier.
def
find( self, matchLabel: str, substrMatchFlag: bool = False, usingRE: bool = False) -> List[int]:
116 def find( 117 self, 118 matchLabel: str, 119 substrMatchFlag: bool = False, 120 usingRE: bool = False, 121 ) -> List[int]: 122 """Returns the index of all intervals that match the given label 123 124 Args: 125 matchLabel: the label to search for 126 substrMatchFlag: if True, match any label containing matchLabel. 127 if False, label must be the same as matchLabel. 128 usingRE: if True, matchLabel is interpreted as a regular expression 129 130 Returns: 131 A list of indicies 132 """ 133 returnList = [] 134 if usingRE is True: 135 for i, entry in enumerate(self.entries): 136 matchList = re.findall(matchLabel, entry.label, re.I) 137 if matchList != []: 138 returnList.append(i) 139 else: 140 for i, entry in enumerate(self.entries): 141 if not substrMatchFlag: 142 if entry.label == matchLabel: 143 returnList.append(i) 144 else: 145 if matchLabel in entry.label: 146 returnList.append(i) 147 148 return returnList
Returns the index of all intervals that match the given label
Arguments:
- matchLabel: the label to search for
- substrMatchFlag: if True, match any label containing matchLabel. if False, label must be the same as matchLabel.
- usingRE: if True, matchLabel is interpreted as a regular expression
Returns:
A list of indicies
def
new( self: ~T, name: Optional[str] = None, entries: Optional[list] = None, minTimestamp: Optional[float] = None, maxTimestamp: Optional[float] = None) -> ~T:
150 def new( 151 self: T, 152 name: Optional[str] = None, 153 entries: Optional[list] = None, 154 minTimestamp: Optional[float] = None, 155 maxTimestamp: Optional[float] = None, 156 ) -> T: 157 """Make a new tier derived from the current one""" 158 if name is None: 159 name = self.name 160 if entries is None: 161 entries = copy.deepcopy(self.entries) 162 entries = [ 163 self.entryType(*entry) 164 if isinstance(entry, tuple) or isinstance(entry, list) 165 else entry 166 for entry in entries 167 ] 168 if minTimestamp is None: 169 minTimestamp = self.minTimestamp 170 if maxTimestamp is None: 171 maxTimestamp = self.maxTimestamp 172 return type(self)(name, entries, minTimestamp, maxTimestamp)
Make a new tier derived from the current one
def
sort(self) -> None:
174 def sort(self) -> None: 175 """Sorts the entries in the list of entries""" 176 # A list containing tuples and lists will be sorted with tuples 177 # first and then lists. To correctly sort, we need to make 178 # sure that all data structures inside the entry list are 179 # of the same data type. The entry list is sorted whenever 180 # the entry list is modified, so this is probably the best 181 # place to enforce the data type 182 self._entries = [ 183 entry if isinstance(entry, self.entryType) else self.entryType(*entry) 184 for entry in self.entries 185 ] 186 self._entries.sort()
Sorts the entries in the list of entries
188 def union(self, tier: "TextgridTier") -> "TextgridTier": 189 """The given tier is set unioned to this tier. 190 191 All entries in the given tier are added to the current tier. 192 Overlapping entries are merged. 193 """ 194 retTier = self.new() 195 196 for entry in tier.entries: 197 retTier.insertEntry( 198 entry, 199 collisionMode=constants.IntervalCollision.MERGE, 200 collisionReportingMode=constants.ErrorReportingMode.SILENCE, 201 ) 202 203 retTier.sort() 204 205 return retTier
The given tier is set unioned to this tier.
All entries in the given tier are added to the current tier. Overlapping entries are merged.
@abstractmethod
def
editTimestamps( self, offset: float, reportingMode: Literal['silence', 'warning', 'error'] = 'warning') -> TextgridTier:
@abstractmethod
def
insertEntry( self, entry, collisionMode: Literal['replace', 'merge', 'error'] = 'error', collisionReportingMode: Literal['silence', 'warning'] = 'warning') -> None:
@abstractmethod
def
dejitter( self, referenceTier: TextgridTier, maxDifference: float = 0.001) -> TextgridTier:
@abstractmethod
def
eraseRegion( self, start: float, end: float, collisionMode: Literal['truncate', 'categorical', 'error'] = 'error', doShrink: bool = True) -> TextgridTier:
@abstractmethod
def
crop( self, cropStart: float, cropEnd: float, mode: Literal['strict', 'lax', 'truncated'], rebaseToZero: bool) -> TextgridTier:
@abstractmethod
def
insertSpace( self, start: float, duration: float, collisionMode: Literal['stretch', 'split', 'no_change', 'error']) -> TextgridTier: