Module praatio.tgio
Functions for reading/writing/manipulating textgrid files.
This file contains the main data structures for representing Textgrid data: Textgrid, IntervalTier, and PointTier
A Textgrid is a container for multiple annotation tiers. Tiers can contain either interval data (IntervalTier) or point data (PointTier). Tiers in a Textgrid are ordered and must contain a unique name.
openTextgrid() can be used to open a textgrid file. Textgrid.save() can be used to save a Textgrid object to a file.
see the examples/ directory for lots of examples using tgio.py
Expand source code
"""
Functions for reading/writing/manipulating textgrid files.
This file contains the main data structures for representing Textgrid data:
Textgrid, IntervalTier, and PointTier
A Textgrid is a container for multiple annotation tiers. Tiers can contain
either interval data (IntervalTier) or point data (PointTier).
Tiers in a Textgrid are ordered and must contain a unique name.
openTextgrid() can be used to open a textgrid file.
Textgrid.save() can be used to save a Textgrid object to a file.
see the **examples/** directory for lots of examples using tgio.py
"""
import re
import copy
import io
import wave
from collections import namedtuple
from praatio.utilities import utils
INTERVAL_TIER = "IntervalTier"
POINT_TIER = "TextTier"
MIN_INTERVAL_LENGTH = 0.00000001 # Arbitrary threshold
Interval = namedtuple('Interval', ['start', 'end', 'label']) # interval entry
Point = namedtuple('Point', ['time', 'label']) # point entry
def _isclose(a, b, rel_tol=1e-14, abs_tol=0.0):
return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
def _getWavDuration(wavFN):
"For internal use. See praatio.audioio.WavQueryObj() for general use."
audiofile = wave.open(wavFN, "r")
params = audiofile.getparams()
framerate = params[2]
nframes = params[3]
duration = float(nframes) / framerate
return duration
def _fillInBlanks(tier, blankLabel="", startTime=None, endTime=None):
'''
Fills in the space between intervals with empty space
This is necessary to do when saving to create a well-formed textgrid
'''
if startTime is None:
startTime = tier.minTimestamp
if endTime is None:
endTime = tier.maxTimestamp
# Special case: empty textgrid
if len(tier.entryList) == 0:
tier.entryList.append((startTime, endTime, blankLabel))
# Create a new entry list
entryList = tier.entryList[:]
entry = entryList[0]
prevEnd = float(entry[1])
newEntryList = [entry]
for entry in entryList[1:]:
newStart = float(entry[0])
newEnd = float(entry[1])
if prevEnd < newStart:
newEntryList.append((prevEnd, newStart, blankLabel))
newEntryList.append(entry)
prevEnd = newEnd
# Special case: If there is a gap at the start of the file
assert(float(newEntryList[0][0]) >= float(startTime))
if float(newEntryList[0][0]) > float(startTime):
newEntryList.insert(0, (startTime, newEntryList[0][0], blankLabel))
# Special case -- if there is a gap at the end of the file
if endTime is not None:
assert(float(newEntryList[-1][1]) <= float(endTime))
if float(newEntryList[-1][1]) < float(endTime):
newEntryList.append((newEntryList[-1][1], endTime, blankLabel))
newEntryList.sort()
return IntervalTier(tier.name, newEntryList,
tier.minTimestamp, tier.maxTimestamp)
def _removeUltrashortIntervals(tier, minLength, minTimestamp):
'''
Remove intervals that are very tiny
Doing many small manipulations on intervals can lead to the creation
of ultrashort intervals (e.g. 1*10^-15 seconds long). This function
removes such intervals.
'''
# First, remove tiny intervals
newEntryList = []
j = 0 # index to newEntryList
for start, stop, label in tier.entryList:
if stop - start < minLength:
# Correct ultra-short entries
if len(newEntryList) > 0:
lastStart, _, lastLabel = newEntryList[j - 1]
newEntryList[j - 1] = (lastStart, stop, lastLabel)
else:
# Special case: the first entry in oldEntryList was ultra-short
if len(newEntryList) == 0 and start != minTimestamp:
newEntryList.append((minTimestamp, stop, label))
# Normal case
else:
newEntryList.append((start, stop, label))
j += 1
# Next, shift near equivalent tiny boundaries
# This will link intervals that were connected by an interval
# that was shorter than minLength
j = 0
while j < len(newEntryList) - 1:
diff = abs(newEntryList[j][1] - newEntryList[j + 1][0])
if diff > 0 and diff < minLength:
newEntryList[j] = (newEntryList[j][0],
newEntryList[j + 1][0],
newEntryList[j][2])
j += 1
return tier.new(entryList=newEntryList)
def intervalOverlapCheck(interval, cmprInterval, percentThreshold=0,
timeThreshold=0, boundaryInclusive=False):
'''
Checks whether two intervals overlap
If percentThreshold is greater than 0, then if the intervals overlap, they
must overlap by at least this threshold
If timeThreshold is greater than 0, then if the intervals overlap, they
must overlap by at least this threshold
If boundaryInclusive is true, then two intervals are considered to overlap
if they share a boundary
'''
startTime, endTime = interval[:2]
cmprStartTime, cmprEndTime = cmprInterval[:2]
overlapTime = max(0, min(endTime, cmprEndTime) -
max(startTime, cmprStartTime))
overlapFlag = overlapTime > 0
# Do they share a boundary? Only need to check if one boundary ends
# when another begins (because otherwise, they overlap in other ways)
boundaryOverlapFlag = False
if boundaryInclusive:
boundaryOverlapFlag = (startTime == cmprEndTime or
endTime == cmprStartTime)
# Is the overlap over a certain percent?
percentOverlapFlag = False
if percentThreshold > 0 and overlapFlag:
totalTime = max(endTime, cmprEndTime) - min(startTime, cmprStartTime)
percentOverlap = overlapTime / float(totalTime)
percentOverlapFlag = percentOverlap >= percentThreshold
# Is the overlap more than a certain threshold?
timeOverlapFlag = False
if timeThreshold > 0 and overlapFlag:
timeOverlapFlag = overlapTime > timeThreshold
overlapFlag = (overlapFlag or boundaryOverlapFlag or
percentOverlapFlag or timeOverlapFlag)
return overlapFlag
class TextgridCollisionException(Exception):
def __init__(self, tierName, insertInterval, collisionList):
super(TextgridCollisionException, self).__init__()
self.tierName = tierName
self.insertInterval = insertInterval
self.collisionList = collisionList
def __str__(self):
dataTuple = (str(self.insertInterval),
self.tierName,
str(self.collisionList))
return ("Attempted to insert interval %s into tier %s of textgrid" +
"but overlapping entries %s already exist" % dataTuple)
class TimelessTextgridTierException(Exception):
def __str__(self):
return "All textgrid tiers much have a min and max duration"
class BadIntervalError(Exception):
def __init__(self, start, stop, label):
super(BadIntervalError, self).__init__()
self.start = start
self.stop = stop
self.label = label
def __str__(self):
dataTuple = (self.start, self.stop, self.label)
return ("Problem with interval--could not create textgrid " +
"(%s,%s,%s)" % dataTuple)
class TextgridTier(object):
tierType = None
entryType = Interval
def __init__(self, name, entryList, minT, maxT,
pairedWav=None):
'''See PointTier or IntervalTier'''
entryList.sort()
self.name = name
self.entryList = entryList
self.minTimestamp = minT
self.maxTimestamp = maxT
def __eq__(self, other):
isEqual = True
isEqual &= self.name == other.name
isEqual &= _isclose(self.minTimestamp, other.minTimestamp)
isEqual &= _isclose(self.maxTimestamp, other.maxTimestamp)
isEqual &= len(self.entryList) == len(self.entryList)
if isEqual:
for selfEntry, otherEntry in zip(self.entryList, other.entryList):
for selfSubEntry, otherSubEntry in zip(selfEntry, otherEntry):
try:
isEqual &= _isclose(selfSubEntry, otherSubEntry)
except TypeError:
isEqual &= selfSubEntry == otherSubEntry
return isEqual
def appendTier(self, tier):
'''
Append a tier to the end of this one.
This tier's maxtimestamp will be lengthened by the amount in the passed in tier.
'''
minTime = self.minTimestamp
if tier.minTimestamp < minTime:
minTime = tier.minTimestamp
maxTime = self.maxTimestamp + tier.maxTimestamp
appendTier = tier.editTimestamps(self.maxTimestamp,
allowOvershoot=True)
assert(self.tierType == tier.tierType)
entryList = self.entryList + appendTier.entryList
entryList.sort()
return self.new(self.name,
entryList,
minTimestamp=minTime,
maxTimestamp=maxTime)
def deleteEntry(self, entry):
'''Removes an entry from the entryList'''
self.entryList.pop(self.entryList.index(entry))
def find(self, matchLabel, substrMatchFlag=False, usingRE=False):
'''
Returns the index of all intervals that match the given label
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
'''
returnList = []
if usingRE is True:
for i, entry in enumerate(self.entryList):
matchList = re.findall(matchLabel, entry[-1], re.I)
if matchList != []:
returnList.append(i)
else:
for i, entry in enumerate(self.entryList):
if not substrMatchFlag:
if entry[-1] == matchLabel:
returnList.append(i)
else:
if matchLabel in entry[-1]:
returnList.append(i)
return returnList
def getAsText(self):
'''Prints each entry in the tier on a separate line w/ timing info'''
text = ""
text += '"%s"\n' % self.tierType
text += '"%s"\n' % self.name
text += '%s\n%s\n%s\n' % (numToStr(self.minTimestamp),
numToStr(self.maxTimestamp),
len(self.entryList))
for entry in self.entryList:
entry = [numToStr(val) for val in entry[:-1]] + ['"%s"' % entry[-1], ]
try:
unicode
except NameError:
unicodeFunc = str
else:
unicodeFunc = unicode
text += "\n".join([unicodeFunc(val) for val in entry]) + "\n"
return text
def new(self, name=None, entryList=None, minTimestamp=None,
maxTimestamp=None, pairedWav=None):
'''Make a new tier derived from the current one'''
if name is None:
name = self.name
if entryList is None:
entryList = copy.deepcopy(self.entryList)
entryList = [self.entryType(*entry) if isinstance(entry, tuple)
else entry
for entry in entryList]
if minTimestamp is None:
minTimestamp = self.minTimestamp
if maxTimestamp is None and pairedWav is None:
maxTimestamp = self.maxTimestamp
return type(self)(name, entryList, minTimestamp, maxTimestamp,
pairedWav)
def sort(self):
'''Sorts the entries in the entryList'''
# A list containing tuples and lists will be sorted with tuples
# first and then lists. To correctly sort, we need to make
# sure that all data structures inside the entry list are
# of the same data type. The entry list is sorted whenever
# the entry list is modified, so this is probably the best
# place to enforce the data type
self.entryList = [entry if isinstance(entry, self.entryType) else
self.entryType(*entry) for entry in self.entryList]
self.entryList.sort()
def union(self, tier):
'''
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.
'''
retTier = self.new()
for entry in tier.entryList:
retTier.insertEntry(entry, False, collisionCode='merge')
retTier.sort()
return retTier
class PointTier(TextgridTier):
tierType = POINT_TIER
entryType = Point
def __init__(self, name, entryList, minT=None, maxT=None,
pairedWav=None):
'''
A point tier is for annotating instaneous events
The entryList is of the form:
[(timeVal1, label1), (timeVal2, label2), ]
The data stored in the labels can be anything but will
be interpreted as text by praatio (the label could be descriptive
text e.g. ('peak point here') or numerical data e.g. (pitch values
like '132'))
'''
entryList = [Point(float(time), label) for time, label in entryList]
# Determine the min and max timestamps
timeList = [time for time, label in entryList]
if minT is not None:
timeList.append(float(minT))
if maxT is not None:
timeList.append(float(maxT))
if maxT is None and pairedWav is not None:
maxT = _getWavDuration(pairedWav)
try:
minT = min(timeList)
maxT = max(timeList)
except ValueError:
raise TimelessTextgridTierException()
super(PointTier, self).__init__(name, entryList, minT, maxT)
def crop(self, cropStart, cropEnd, mode=None,
rebaseToZero=True):
'''
Creates a new tier containing all entries inside the new interval
mode is ignored. This parameter is kept for compatibility with
IntervalTier.crop()
'''
newEntryList = []
for entry in self.entryList:
timestamp = entry[0]
if timestamp >= cropStart and timestamp <= cropEnd:
newEntryList.append(entry)
if rebaseToZero is True:
newEntryList = [(timeV - cropStart, label)
for timeV, label in newEntryList]
minT = 0
maxT = cropEnd - cropStart
else:
minT = cropStart
maxT = cropEnd
# Create subtier
subTier = PointTier(self.name, newEntryList, minT, maxT)
return subTier
def editTimestamps(self, offset, allowOvershoot=False):
'''
Modifies all timestamps by a constant amount
If allowOvershoot is True, an interval can go beyond the bounds
of the textgrid
'''
newEntryList = []
for timestamp, label in self.entryList:
newTimestamp = timestamp + offset
if not allowOvershoot:
assert(newTimestamp > self.minTimestamp)
assert(newTimestamp <= self.maxTimestamp)
if newTimestamp < 0:
continue
newEntryList.append((newTimestamp, label))
# Determine new min and max timestamps
timeList = [float(subList[0]) for subList in newEntryList]
newMin = min(timeList)
newMax = max(timeList)
if newMin > self.minTimestamp:
newMin = self.minTimestamp
if newMax < self.maxTimestamp:
newMax = self.maxTimestamp
return PointTier(self.name, newEntryList, newMin, newMax)
def getValuesAtPoints(self, dataTupleList, fuzzyMatching=False):
'''
Get the values that occur at points in the point tier
If fuzzyMatching is True, if there is not a feature value
at a point, the nearest feature value will be taken.
The procedure assumes that all data is ordered in time.
dataTupleList should be in the form
[(t1, v1a, v1b, ..), (t2, v2a, v2b, ..), ..]
The procedure makes one pass through dataTupleList and one
pass through self.entryList. If the data is not sequentially
ordered, the incorrect response will be returned.
'''
i = 0
retList = []
sortedDataTupleList = dataTupleList.sorted()
for timestamp, label in self.entryList:
retTuple = utils.getValueAtTime(timestamp,
sortedDataTupleList,
fuzzyMatching=fuzzyMatching,
startI=i)
retTime, retVal, i = retTuple
retList.append((retTime, label, retVal))
return retList
def eraseRegion(self, start, stop, collisionCode=None, doShrink=True):
'''
Makes a region in a tier blank (removes all contained entries)
collisionCode: Ignored for the moment (added for compatibility
with eraseRegion() for Interval Tiers)
doShrink: if True, moves leftward by (/stop/ - /start/)
all points to the right of /stop/
'''
newTier = self.new()
croppedTier = newTier.crop(start, stop, "truncated", False)
matchList = croppedTier.entryList
if len(matchList) == 0:
pass
else:
# Remove all the matches from the entryList
# Go in reverse order because we're destructively altering
# the order of the list (messes up index order)
for tmpEntry in matchList[::-1]:
newTier.deleteEntry(tmpEntry)
if doShrink is True:
newEntryList = []
diff = stop - start
for timestamp, label in newTier.entryList:
if timestamp < start:
newEntryList.append((timestamp, label))
elif timestamp > stop:
newEntryList.append((timestamp - diff, label))
newMax = newTier.maxTimestamp - diff
newTier = newTier.new(entryList=newEntryList,
maxTimestamp=newMax)
return newTier
def insertEntry(self, entry, warnFlag=True, collisionCode=None):
'''
inserts an interval into the tier
collisionCode: in the event that intervals exist in the insertion area,
one of three things may happen
- 'replace' - existing items will be removed
- 'merge' - inserting item will be fused with existing items
- None or any other value - TextgridCollisionException is thrown
if warnFlag is True and collisionCode is not None,
the user is notified of each collision
'''
timestamp, label = entry
if not isinstance(entry, Point):
entry = Point(timestamp, label)
matchList = []
i = None
for i, searchEntry in self.entryList:
if searchEntry[0] == entry[0]:
matchList.append(searchEntry)
break
if len(matchList) == 0:
self.entryList.append(entry)
elif collisionCode.lower() == "replace":
self.deleteEntry(self.entryList[i])
self.entryList.append(entry)
elif collisionCode.lower() == "merge":
oldEntry = self.entryList[i]
newEntry = Point(timestamp, "-".join([oldEntry[-1], label]))
self.deleteEntry(self.entryList[i])
self.entryList.append(newEntry)
else:
raise TextgridCollisionException(self.name, entry, matchList)
self.sort()
if len(matchList) != 0 and warnFlag is True:
fmtStr = "Collision warning for %s with items %s of tier %s"
print((fmtStr % (str(entry), str(matchList), self.name)))
def insertSpace(self, start, duration, collisionCode=None):
'''
Inserts a region into the tier
collisionCode: Ignored for the moment (added for compatibility
with insertSpace() for Interval Tiers)
'''
newEntryList = []
for entry in self.entryList:
if entry[0] <= start:
newEntryList.append(entry)
elif entry[0] > start:
newEntryList.append((entry[0] + duration, entry[1]))
newTier = self.new(entryList=newEntryList,
maxTimestamp=self.maxTimestamp + duration)
return newTier
class IntervalTier(TextgridTier):
tierType = INTERVAL_TIER
entryType = Interval
def __init__(self, name, entryList, minT=None, maxT=None,
pairedWav=None):
'''
An interval tier is for annotating events that have duration
The entryList is of the form:
[(startTime1, endTime1, label1), (startTime2, endTime2, label2), ]
The data stored in the labels can be anything but will
be interpreted as text by praatio (the label could be descriptive
text e.g. ('erase this region') or numerical data e.g. (average pitch
values like '132'))
'''
entryList = [(float(start), float(stop), label)
for start, stop, label in entryList]
if minT is not None:
minT = float(minT)
if maxT is not None:
maxT = float(maxT)
if maxT is None and pairedWav is not None:
maxT = _getWavDuration(pairedWav)
# Prevent poorly-formed textgrids from being created
for entry in entryList:
if entry[0] >= entry[1]:
fmtStr = "Anomaly: startTime=%f, stopTime=%f, label=%s"
print((fmtStr % (entry[0], entry[1], entry[2])))
assert(entry[0] < entry[1])
# Remove whitespace
tmpEntryList = []
for start, stop, label in entryList:
tmpEntryList.append(Interval(start, stop, label.strip()))
entryList = tmpEntryList
# Determine the minimum and maximum timestampes
minTimeList = [subList[0] for subList in entryList]
maxTimeList = [subList[1] for subList in entryList]
if minT is not None:
minTimeList.append(minT)
if maxT is not None:
maxTimeList.append(maxT)
try:
minT = min(minTimeList)
maxT = max(maxTimeList)
except ValueError:
raise TimelessTextgridTierException()
super(IntervalTier, self).__init__(name, entryList, minT, maxT)
def crop(self, cropStart, cropEnd, mode, rebaseToZero):
'''
Creates a new tier with all entries that fit inside the new interval
mode = {'strict', 'lax', 'truncated'}
If 'strict', only intervals wholly contained by the crop
interval will be kept
If 'lax', partially contained intervals will be kept
If 'truncated', partially contained intervals will be
truncated to fit within the crop region.
If rebaseToZero is True, the cropped textgrid values will be
subtracted by the cropStart
'''
assert(mode in ['strict', 'lax', 'truncated'])
# Debugging variables
cutTStart = 0
cutTWithin = 0
cutTEnd = 0
firstIntervalKeptProportion = 0
lastIntervalKeptProportion = 0
newEntryList = []
for entry in self.entryList:
matchedEntry = None
intervalStart = entry[0]
intervalEnd = entry[1]
intervalLabel = entry[2]
# Don't need to investigate if the interval is before or after
# the crop region
if intervalEnd <= cropStart or intervalStart >= cropEnd:
continue
# Determine if the current subEntry is wholly contained
# within the superEntry
if intervalStart >= cropStart and intervalEnd <= cropEnd:
matchedEntry = entry
# If it is only partially contained within the superEntry AND
# inclusion is 'lax', include it anyways
elif mode == 'lax' and (intervalStart >= cropStart or
intervalEnd <= cropEnd):
matchedEntry = entry
# If not strict, include partial tiers on the edges
# -- regardless, record how much information was lost
# - for strict=True, the total time of the cut interval
# - for strict=False, the portion of the interval that lies
# outside the new interval
# The current interval stradles the end of the new interval
elif intervalStart >= cropStart and intervalEnd > cropEnd:
cutTEnd = intervalEnd - cropEnd
lastIntervalKeptProportion = ((cropEnd - intervalStart) /
(intervalEnd - intervalStart))
if mode == "truncated":
matchedEntry = (intervalStart, cropEnd, intervalLabel)
else:
cutTWithin += cropEnd - cropStart
# The current interval stradles the start of the new interval
elif intervalStart < cropStart and intervalEnd <= cropEnd:
cutTStart = cropStart - intervalStart
firstIntervalKeptProportion = ((intervalEnd - cropStart) /
(intervalEnd - intervalStart))
if mode == "truncated":
matchedEntry = (cropStart, intervalEnd, intervalLabel)
else:
cutTWithin += cropEnd - cropStart
# The current interval contains the new interval completely
elif intervalStart <= cropStart and intervalEnd >= cropEnd:
if mode == "lax":
matchedEntry = entry
elif mode == "truncated":
matchedEntry = (cropStart, cropEnd, intervalLabel)
else:
cutTWithin += cropEnd - cropStart
if matchedEntry is not None:
newEntryList.append(matchedEntry)
if rebaseToZero is True:
newEntryList = [(startT - cropStart, stopT - cropStart, label)
for startT, stopT, label in newEntryList]
minT = 0
maxT = cropEnd - cropStart
else:
minT = cropStart
maxT = cropEnd
# Create subtier
croppedTier = IntervalTier(self.name, newEntryList, minT, maxT)
# DEBUG info
# debugInfo = (subTier, cutTStart, cutTWithin, cutTEnd,
# firstIntervalKeptProportion, lastIntervalKeptProportion)
return croppedTier
def difference(self, tier):
'''
Takes the set difference of this tier and the given one
Any overlapping portions of entries with entries in this textgrid
will be removed from the returned tier.
'''
retTier = self.new()
for entry in tier.entryList:
retTier = retTier.eraseRegion(entry[0],
entry[1],
collisionCode='truncate',
doShrink=False)
return retTier
def editTimestamps(self, offset, allowOvershoot=False):
'''
Modifies all timestamps by a constant amount
Can modify the interval start independent of the interval end
If allowOvershoot is True, an interval can go beyond the bounds
of the textgrid
'''
newEntryList = []
for start, stop, label in self.entryList:
newStart = offset + start
newStop = offset + stop
if allowOvershoot is not True:
assert(newStart >= self.minTimestamp)
assert(newStop <= self.maxTimestamp)
if newStop < 0:
continue
if newStart < 0:
newStart = 0
if newStart < 0:
continue
newEntryList.append((newStart, newStop, label))
# Determine new min and max timestamps
newMin = min([entry[0] for entry in newEntryList])
newMax = max([entry[1] for entry in newEntryList])
if newMin > self.minTimestamp:
newMin = self.minTimestamp
if newMax < self.maxTimestamp:
newMax = self.maxTimestamp
return IntervalTier(self.name, newEntryList, newMin, newMax)
def eraseRegion(self, start, stop, collisionCode=None, doShrink=True):
'''
Makes a region in a tier blank (removes all contained entries)
collisionCode: in the event that intervals exist in the insertion area,
one of three things may happen
- 'truncate' - partially contained entries will have the portion
removed that overlaps with the target entry
- 'categorical' - all entries that overlap, even partially, with the
target entry will be completely removed
- None or any other value - AssertionError is thrown
doShrink: if True, moves leftward by (/stop/ - /start/) amount,
each item that occurs after /stop/
'''
matchList = self.crop(start, stop, 'lax', False).entryList
newTier = self.new()
# if the collisionCode is not properly set it isn't clear what to do
assert(collisionCode == 'truncate' or
collisionCode == 'categorical')
if len(matchList) == 0:
pass
else:
# Remove all the matches from the entryList
# Go in reverse order because we're destructively altering
# the order of the list (messes up index order)
for tmpEntry in matchList[::-1]:
newTier.deleteEntry(tmpEntry)
# If we're only truncating, reinsert entries on the left and
# right edges
if collisionCode == 'truncate':
# Check left edge
if matchList[0][0] < start:
newEntry = (matchList[0][0], start, matchList[0][-1])
newTier.entryList.append(newEntry)
# Check right edge
if matchList[-1][1] > stop:
newEntry = (stop, matchList[-1][1], matchList[-1][-1])
newTier.entryList.append(newEntry)
if doShrink is True:
diff = stop - start
newEntryList = []
for entry in newTier.entryList:
if entry[1] <= start:
newEntryList.append(entry)
elif entry[0] >= stop:
newEntryList.append((entry[0] - diff,
entry[1] - diff,
entry[2]))
# Special case: an interval that spanned the deleted
# section
for i in range(0, len(newEntryList) - 1):
rightEdge = newEntryList[i][1] == start
leftEdge = newEntryList[i + 1][0] == start
sameLabel = (newEntryList[i][2] == newEntryList[i + 1][2])
if rightEdge and leftEdge and sameLabel:
newEntry = (newEntryList[i][0],
newEntryList[i + 1][1],
newEntryList[i][2])
newEntryList.pop(i + 1)
newEntryList.pop(i)
newEntryList.insert(i, newEntry)
# Only one interval can span the deleted section,
# so if we've found it, move on
break
newMax = newTier.maxTimestamp - diff
newTier = newTier.new(entryList=newEntryList,
maxTimestamp=newMax)
return newTier
def getValuesInIntervals(self, dataTupleList):
'''
Returns data from dataTupleList contained in labeled intervals
dataTupleList should be of the form:
[(time1, value1a, value1b,...), (time2, value2a, value2b...), ...]
'''
returnList = []
for interval in self.entryList:
intervalDataList = utils.getValuesInInterval(dataTupleList,
interval[0],
interval[1])
returnList.append((interval, intervalDataList))
return returnList
def getNonEntries(self):
'''
Returns the regions of the textgrid without labels
This can include unlabeled segments and regions marked as silent.
'''
entryList = self.entryList
invertedEntryList = [(entryList[i][1], entryList[i + 1][0], "")
for i in range(len(entryList) - 1)]
# Remove entries that have no duration (ie lie between two entries
# that share a border)
invertedEntryList = [entry for entry in invertedEntryList
if entry[0] < entry[1]]
if entryList[0][0] > 0:
invertedEntryList.insert(0, (0, entryList[0][0], ""))
if entryList[-1][1] < self.maxTimestamp:
invertedEntryList.append((entryList[-1][1], self.maxTimestamp, ""))
invertedEntryList = [entry if isinstance(entry, Interval)
else Interval(*entry)
for entry in invertedEntryList]
return invertedEntryList
def insertEntry(self, entry, warnFlag=True, collisionCode=None):
'''
inserts an interval into the tier
collisionCode: in the event that intervals exist in the insertion area,
one of three things may happen
- 'replace' - existing items will be removed
- 'merge' - inserting item will be fused with existing items
- None or any other value - TextgridCollisionException is thrown
if warnFlag is True and collisionCode is not None,
the user is notified of each collision
'''
startTime, endTime = entry[:2]
matchList = self.crop(startTime, endTime, 'lax', False).entryList
if len(matchList) == 0:
self.entryList.append(entry)
elif collisionCode.lower() == "replace":
for matchEntry in matchList:
self.deleteEntry(matchEntry)
self.entryList.append(entry)
elif collisionCode.lower() == "merge":
for matchEntry in matchList:
self.deleteEntry(matchEntry)
matchList.append(entry)
matchList.sort() # By starting time
newEntry = (min([entry[0] for entry in matchList]),
max([entry[1] for entry in matchList]),
"-".join([entry[2] for entry in matchList]))
self.entryList.append(Interval(*newEntry))
else:
raise TextgridCollisionException(self.name, entry, matchList)
self.sort()
if len(matchList) != 0 and warnFlag is True:
fmtStr = "Collision warning for %s with items %s of tier %s"
print((fmtStr % (str(entry), str(matchList), self.name)))
def insertSpace(self, start, duration, collisionCode=None):
'''
Inserts a blank region into the tier
collisionCode: in the event that an interval stradles the
starting point
- '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 - AssertionError is thrown
'''
# if the collisionCode is not properly set it isn't clear what to do
assert(collisionCode == 'stretch' or
collisionCode == 'split' or
collisionCode == 'no change')
newEntryList = []
for entry in self.entryList:
# Entry exists before the insertion point
if entry[1] <= start:
newEntryList.append(entry)
# Entry exists after the insertion point
elif entry[0] >= start:
newEntryList.append((entry[0] + duration,
entry[1] + duration,
entry[2]))
# Entry straddles the insertion point
elif entry[0] <= start and entry[1] > start:
if collisionCode == 'stretch':
newEntryList.append((entry[0],
entry[1] + duration,
entry[2]))
elif collisionCode == 'split':
# Left side of the split
newEntryList.append((entry[0],
start,
entry[2]))
# Right side of the split
newEntryList.append((start + duration,
start + duration + (entry[1] - start),
entry[2]))
elif collisionCode == 'no change':
newEntryList.append(entry)
newTier = self.new(entryList=newEntryList,
maxTimestamp=self.maxTimestamp + duration)
return newTier
def intersection(self, tier):
'''
Takes the set intersection of this tier and the given one
Only intervals that exist in both tiers will remain in the
returned tier. If intervals partially overlap, only the overlapping
portion will be returned.
'''
retEntryList = []
for start, stop, label in tier.entryList:
subTier = self.crop(start, stop, "truncated", False)
# Combine the labels in the two tiers
stub = "%s-%%s" % label
subEntryList = [(subEntry[0], subEntry[1], stub % subEntry[2])
for subEntry in subTier.entryList]
retEntryList.extend(subEntryList)
newName = "%s-%s" % (self.name, tier.name)
retTier = self.new(newName, retEntryList)
return retTier
def morph(self, targetTier, filterFunc=None):
'''
Morphs the duration of segments in this tier to those in another
This preserves the labels and the duration of silence in
this tier while changing the duration of labeled segments.
'''
cumulativeAdjustAmount = 0
lastFromEnd = 0
newEntryList = []
allPoints = [self.entryList, targetTier.entryList]
for fromEntry, targetEntry in utils.safeZip(allPoints, True):
fromStart, fromEnd, fromLabel = fromEntry
targetStart, targetEnd = targetEntry[:2]
# fromStart - lastFromEnd -> was this interval and the
# last one adjacent?
toStart = (fromStart - lastFromEnd) + cumulativeAdjustAmount
currAdjustAmount = (fromEnd - fromStart)
if filterFunc is None or filterFunc(fromLabel):
currAdjustAmount = (targetEnd - targetStart)
toEnd = cumulativeAdjustAmount = toStart + currAdjustAmount
newEntryList.append((toStart, toEnd, fromLabel))
lastFromEnd = fromEnd
newMin = self.minTimestamp
cumulativeDifference = (newEntryList[-1][1] - self.entryList[-1][1])
newMax = self.maxTimestamp + cumulativeDifference
return IntervalTier(self.name, newEntryList, newMin, newMax)
class Textgrid():
def __init__(self):
'A container that stores and operates over interval and point tiers'
self.tierNameList = [] # Preserves the order of the tiers
self.tierDict = {}
self.minTimestamp = None
self.maxTimestamp = None
def __eq__(self, other):
isEqual = True
isEqual &= _isclose(self.minTimestamp, other.minTimestamp)
isEqual &= _isclose(self.maxTimestamp, other.maxTimestamp)
isEqual &= self.tierNameList == other.tierNameList
if isEqual:
for tierName in self.tierNameList:
isEqual &= self.tierDict[tierName] == other.tierDict[tierName]
return isEqual
def addTier(self, tier, tierIndex=None):
'''
Add a tier to this textgrid.
If tierIndex is specified, insert the tier into the specified position.
'''
assert(tier.name not in list(self.tierDict.keys()))
if tierIndex is None:
self.tierNameList.append(tier.name)
else:
self.tierNameList.insert(tierIndex, tier.name)
self.tierDict[tier.name] = tier
minV = tier.minTimestamp
if self.minTimestamp is None or minV < self.minTimestamp:
self.minTimestamp = minV
maxV = tier.maxTimestamp
if self.maxTimestamp is None or maxV > self.maxTimestamp:
self.maxTimestamp = maxV
def appendTextgrid(self, tg, onlyMatchingNames=True):
'''
Append one textgrid to the end of this one
if onlyMatchingNames is False, tiers that don't appear in both
textgrids will also appear
'''
retTG = Textgrid()
minTime = self.minTimestamp
maxTime = self.maxTimestamp + tg.maxTimestamp
# Get all tier names. Ordered first by this textgrid and
# then by the other textgrid.
combinedTierNameList = self.tierNameList
for tierName in tg.tierNameList:
if tierName not in combinedTierNameList:
combinedTierNameList.append(tierName)
# Determine the tier names that will be in the final textgrid
finalTierNameList = []
if onlyMatchingNames is False:
finalTierNameList = combinedTierNameList
else:
for tierName in combinedTierNameList:
if tierName in self.tierNameList:
if tierName in tg.tierNameList:
finalTierNameList.append(tierName)
# Add tiers from this textgrid
for tierName in self.tierNameList:
if tierName in finalTierNameList:
tier = self.tierDict[tierName]
retTG.addTier(tier)
# Add tiers from the given textgrid
for tierName in tg.tierNameList:
if tierName in finalTierNameList:
appendTier = tg.tierDict[tierName]
appendTier = appendTier.new(minTimestamp=minTime,
maxTimestamp=maxTime)
appendTier = appendTier.editTimestamps(self.maxTimestamp)
if tierName in retTG.tierNameList:
tier = retTG.tierDict[tierName]
newEntryList = retTG.tierDict[tierName].entryList
newEntryList += appendTier.entryList
tier = tier.new(entryList=newEntryList,
minTimestamp=minTime,
maxTimestamp=maxTime)
retTG.replaceTier(tierName, tier)
else:
tier = appendTier
tier = tier.new(minTimestamp=minTime,
maxTimestamp=maxTime)
retTG.addTier(tier)
return retTG
def crop(self, cropStart, cropEnd, mode, rebaseToZero):
'''
Creates a textgrid where all intervals fit within the crop region
mode = {'strict', 'lax', 'truncated'}
If 'strict', only intervals wholly contained by the crop
interval will be kept
If 'lax', partially contained intervals will be kept
If 'truncated', partially contained intervals will be
truncated to fit within the crop region.
If rebaseToZero is True, the cropped textgrid values will be
subtracted by the cropStart
'''
assert(mode in ['strict', 'lax', 'truncated'])
newTG = Textgrid()
if rebaseToZero is True:
minT = 0
maxT = cropEnd - cropStart
else:
minT = cropStart
maxT = cropEnd
newTG.minTimestamp = minT
newTG.maxTimestamp = maxT
for tierName in self.tierNameList:
tier = self.tierDict[tierName]
newTier = tier.crop(cropStart, cropEnd, mode, rebaseToZero)
newTG.addTier(newTier)
return newTG
def eraseRegion(self, start, stop, doShrink=True):
'''
Makes a region in a tier blank (removes all contained entries)
If 'doShrink' is 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 - stop)
'''
diff = stop - start
maxTimestamp = self.maxTimestamp
if doShrink is True:
maxTimestamp -= diff
newTG = Textgrid()
for name in self.tierNameList:
tier = self.tierDict[name]
tier = tier.eraseRegion(start, stop, 'truncate', doShrink)
newTG.addTier(tier)
newTG.maxTimestamp = maxTimestamp
return newTG
def editTimestamps(self, offset, allowOvershoot=False):
'''
Modifies all timestamps by a constant amount
'''
tg = Textgrid()
for tierName in self.tierNameList:
tier = self.tierDict[tierName]
if len(tier.entryList) > 0:
tier = tier.editTimestamps(offset, allowOvershoot)
tg.addTier(tier)
return tg
def insertSpace(self, start, duration, collisionCode=None):
'''
Inserts a blank region into a textgrid
Every item that occurs after /start/ will be pushed back by
/duration/ seconds
collisionCode: in the event that an interval stradles the
starting point
- '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 - AssertionError is thrown
'''
newTG = Textgrid()
newTG.minTimestamp = self.minTimestamp
newTG.maxTimestamp = self.maxTimestamp + duration
for tierName in self.tierNameList:
tier = self.tierDict[tierName]
newTier = tier.insertSpace(start, duration, collisionCode)
newTG.addTier(newTier)
return newTG
def mergeTiers(self, includeFunc=None,
tierList=None, preserveOtherTiers=True):
'''
Combine tiers
/includeFunc/ regulates which intervals to include in the merging
with all others being tossed (default accepts all)
If /tierList/ is none, combine all tiers.
'''
if tierList is None:
tierList = self.tierNameList
if includeFunc is None:
includeFunc = lambda entryList: True
# Determine the tiers to merge
intervalTierNameList = []
pointTierNameList = []
for tierName in tierList:
tier = self.tierDict[tierName]
if isinstance(tier, IntervalTier):
intervalTierNameList.append(tierName)
elif isinstance(tier, PointTier):
pointTierNameList.append(tierName)
# Merge the interval tiers
intervalTier = None
if len(intervalTierNameList) > 0:
intervalTier = self.tierDict[intervalTierNameList.pop(0)]
for tierName in intervalTierNameList:
intervalTier = intervalTier.union(self.tierDict[tierName])
# Merge the point tiers
pointTier = None
if len(pointTierNameList) > 0:
pointTier = self.tierDict[pointTierNameList.pop(0)]
for tierName in pointTierNameList:
pointTier = pointTier.merge(self.tierDict[tierName])
# Create the final textgrid to output
tg = Textgrid()
if intervalTier is not None:
tg.addTier(intervalTier)
if pointTier is not None:
tg.addTier(pointTier)
return tg
def new(self):
'''Returns a copy of this Textgrid'''
return copy.deepcopy(self)
def renameTier(self, oldName, newName):
oldTier = self.tierDict[oldName]
tierIndex = self.tierNameList.index(oldName)
self.removeTier(oldName)
self.addTier(oldTier.new(newName, oldTier.entryList), tierIndex)
def removeTier(self, name):
self.tierNameList.pop(self.tierNameList.index(name))
return self.tierDict.pop(name)
def replaceTier(self, name, newTier):
tierIndex = self.tierNameList.index(name)
self.removeTier(name)
self.addTier(newTier, tierIndex)
def save(self, fn, minimumIntervalLength=MIN_INTERVAL_LENGTH, minTimestamp=None, maxTimestamp=None, useShortForm=True):
'''
To save the current textgrid
fn - the fullpath filename of the output
minimumIntervalLength - any labeled intervals smaller than this will be removed,
useful for removing ultrashort or fragmented intervals; if None, don't remove any.
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.
useShortForm - if True, save the textgrid as a short textgrid. Otherwise, use the
long-form textgrid format. For backwards compatibility, is True by default.
'''
for tier in self.tierDict.values():
tier.sort()
if minTimestamp == None:
minTimestamp = self.minTimestamp
if maxTimestamp == None:
maxTimestamp = self.maxTimestamp
# Fill in the blank spaces for interval tiers
for name in self.tierNameList:
tier = self.tierDict[name]
if isinstance(tier, IntervalTier):
tier = _fillInBlanks(tier,
"",
minTimestamp,
maxTimestamp)
if minimumIntervalLength is not None:
tier = _removeUltrashortIntervals(tier,
minimumIntervalLength,
minTimestamp)
self.tierDict[name] = tier
for tier in self.tierDict.values():
tier.sort()
outputTxt = ""
outputTxt += 'File type = "ooTextFile"\n'
outputTxt += 'Object class = "TextGrid"\n\n'
if useShortForm == True:
# Header
outputTxt += "%s\n%s\n" % (numToStr(minTimestamp),
numToStr(maxTimestamp))
outputTxt += "<exists>\n%d\n" % len(self.tierNameList)
for tierName in self.tierNameList:
outputTxt += self.tierDict[tierName].getAsText()
else:
tab = " " * 4
# Header
outputTxt += "xmin = %s \n" % numToStr(minTimestamp)
outputTxt += "xmax = %s \n" % numToStr(maxTimestamp)
outputTxt += "tiers? <exists> \n"
outputTxt += "size = %d \n" % len(self.tierNameList)
outputTxt += "item []: \n"
for tierNum, tierName in enumerate(self.tierNameList):
tier = self.tierDict[tierName]
# Interval header
outputTxt += tab + "item [%d]:\n" % (tierNum + 1)
outputTxt += tab * 2 + 'class = "%s" \n' % tier.tierType
outputTxt += tab * 2 + 'name = "%s" \n' % tierName
outputTxt += tab * 2 + 'xmin = %s \n' % numToStr(tier.minTimestamp)
outputTxt += tab * 2 + 'xmax = %s \n' % numToStr(tier.maxTimestamp)
if tier.tierType == INTERVAL_TIER:
outputTxt += tab * 2 + 'intervals: size = %d \n' % len(tier.entryList)
for intervalNum, entry in enumerate(tier.entryList):
start, stop, label = entry
outputTxt += tab * 2 + 'intervals [%d]:\n' % (intervalNum + 1)
outputTxt += tab * 3 + 'xmin = %s \n' % numToStr(start)
outputTxt += tab * 3 + 'xmax = %s \n' % numToStr(stop)
outputTxt += tab * 3 + 'text = "%s" \n' % label
else:
outputTxt += tab * 2 + 'points: size = %d\n ' % len(tier.entryList)
for pointNum, entry in enumerate(tier.entryList):
timestamp, label = entry
outputTxt += tab * 2 + 'points [%d]:\n' % (pointNum + 1)
outputTxt += tab * 3 + 'number = %s \n' % numToStr(timestamp)
outputTxt += tab * 3 + 'mark = "%s" \n' % label
with io.open(fn, "w", encoding="utf-8") as fd:
fd.write(outputTxt)
def openTextgrid(fnFullPath):
try:
with io.open(fnFullPath, "r", encoding="utf-16") as fd:
data = fd.read()
except UnicodeError:
with io.open(fnFullPath, "r", encoding="utf-8") as fd:
data = fd.read()
data = data.replace("\r\n", "\n")
caseA = "ooTextFile short" in data
caseB = "item [" not in data
if caseA or caseB:
textgrid = _parseShortTextgrid(data)
else:
textgrid = _parseNormalTextgrid(data)
return textgrid
def _parseNormalTextgrid(data):
'''
Reads a normal textgrid
'''
newTG = Textgrid()
# Toss textgrid header
header, data = data.split("item [", 1)
headerList = header.split("\n")
tgMin = float(headerList[3].split("=")[1].strip())
tgMax = float(headerList[4].split("=")[1].strip())
newTG.minTimestamp = tgMin
newTG.maxTimestamp = tgMax
# Process each tier individually (will be output to separate folders)
tierList = data.split("item [")[1:]
for tierTxt in tierList:
hasData = True
if 'class = "IntervalTier"' in tierTxt:
tierType = INTERVAL_TIER
searchWord = "intervals ["
else:
tierType = POINT_TIER
searchWord = "points ["
# Get tier meta-information
try:
header, tierData = tierTxt.split(searchWord, 1)
except ValueError:
# A tier with no entries
if "size = 0" in tierTxt:
header = tierTxt
tierData = ""
hadData = False
else:
raise
tierName = header.split("name = ")[1].split("\n", 1)[0]
tierStart = header.split("xmin = ")[1].split("\n", 1)[0]
tierStart = strToIntOrFloat(tierStart)
tierEnd = header.split("xmax = ")[1].split("\n", 1)[0]
tierEnd = strToIntOrFloat(tierEnd)
tierName = tierName.strip()[1:-1]
# Get the tier entry list
tierEntryList = []
labelI = 0
if tierType == INTERVAL_TIER:
while True:
try:
timeStart, timeStartI = _fetchRow(tierData,
"xmin = ", labelI)
timeEnd, timeEndI = _fetchRow(tierData,
"xmax = ", timeStartI)
label, labelI = _fetchRow(tierData, "text =", timeEndI)
except (ValueError, IndexError):
break
label = label.strip()
if label == "":
continue
tierEntryList.append((timeStart, timeEnd, label))
tier = IntervalTier(tierName, tierEntryList, tierStart, tierEnd)
else:
while True:
try:
time, timeI = _fetchRow(tierData, "number = ", labelI)
label, labelI = _fetchRow(tierData, "mark =", timeI)
except (ValueError, IndexError):
break
label = label.strip()
if label == "":
continue
tierEntryList.append((time, label))
tier = PointTier(tierName, tierEntryList, tierStart, tierEnd)
newTG.addTier(tier)
return newTG
def _parseShortTextgrid(data):
'''
Reads a short textgrid file
'''
newTG = Textgrid()
intervalIndicies = [(i, True)
for i in utils.findAll(data, '"IntervalTier"')]
pointIndicies = [(i, False) for i in utils.findAll(data, '"TextTier"')]
indexList = intervalIndicies + pointIndicies
indexList.append((len(data), None)) # The 'end' of the file
indexList.sort()
tupleList = [(indexList[i][0], indexList[i + 1][0], indexList[i][1])
for i in range(len(indexList) - 1)]
# Set the textgrid's min and max times
header = data[:tupleList[0][0]]
headerList = header.split("\n")
tgMin = float(headerList[3].strip())
tgMax = float(headerList[4].strip())
newTG.minTimestamp = tgMin
newTG.maxTimestamp = tgMax
# Load the data for each tier
for blockStartI, blockEndI, isInterval in tupleList:
tierData = data[blockStartI:blockEndI]
# First row contains the tier type, which we already know
metaStartI = _fetchRow(tierData, '', 0)[1]
# Tier meta-information
tierName, tierNameEndI = _fetchRow(tierData, '', metaStartI)
tierStartTime, tierStartTimeI = _fetchRow(tierData, '', tierNameEndI)
tierEndTime, tierEndTimeI = _fetchRow(tierData, '', tierStartTimeI)
startTimeI = _fetchRow(tierData, '', tierEndTimeI)[1]
tierStartTime = strToIntOrFloat(tierStartTime)
tierEndTime = strToIntOrFloat(tierEndTime)
# Tier entry data
entryList = []
if isInterval:
while True:
try:
startTime, endTimeI = _fetchRow(tierData, '', startTimeI)
endTime, labelI = _fetchRow(tierData, '', endTimeI)
label, startTimeI = _fetchRow(tierData, '', labelI)
except (ValueError, IndexError):
break
label = label.strip()
if label == "":
continue
entryList.append((startTime, endTime, label))
newTG.addTier(IntervalTier(tierName, entryList,
tierStartTime, tierEndTime))
else:
while True:
try:
time, labelI = _fetchRow(tierData, '', startTimeI)
label, startTimeI = _fetchRow(tierData, '', labelI)
except (ValueError, IndexError):
break
label = label.strip()
if label == "":
continue
entryList.append((time, label))
newTG.addTier(PointTier(tierName, entryList,
tierStartTime, tierEndTime))
return newTG
def numToStr(inputNum):
if _isclose(inputNum, int(inputNum)):
retVal = "%d" % inputNum
else:
retVal = "%s" % repr(inputNum)
return retVal
def strToIntOrFloat(inputStr):
return float(inputStr) if '.' in inputStr else int(inputStr)
def _fetchRow(dataStr, searchStr, index):
startIndex = dataStr.index(searchStr, index) + len(searchStr)
endIndex = dataStr.index("\n", startIndex)
word = dataStr[startIndex:endIndex]
word = word.strip()
if word[0] == '"' and word[-1] == '"':
word = word[1:-1]
word = word.strip()
return word, endIndex + 1
Functions
def intervalOverlapCheck(interval, cmprInterval, percentThreshold=0, timeThreshold=0, boundaryInclusive=False)
-
Checks whether two intervals overlap
If percentThreshold is greater than 0, then if the intervals overlap, they must overlap by at least this threshold
If timeThreshold is greater than 0, then if the intervals overlap, they must overlap by at least this threshold
If boundaryInclusive is true, then two intervals are considered to overlap if they share a boundary
Expand source code
def intervalOverlapCheck(interval, cmprInterval, percentThreshold=0, timeThreshold=0, boundaryInclusive=False): ''' Checks whether two intervals overlap If percentThreshold is greater than 0, then if the intervals overlap, they must overlap by at least this threshold If timeThreshold is greater than 0, then if the intervals overlap, they must overlap by at least this threshold If boundaryInclusive is true, then two intervals are considered to overlap if they share a boundary ''' startTime, endTime = interval[:2] cmprStartTime, cmprEndTime = cmprInterval[:2] overlapTime = max(0, min(endTime, cmprEndTime) - max(startTime, cmprStartTime)) overlapFlag = overlapTime > 0 # Do they share a boundary? Only need to check if one boundary ends # when another begins (because otherwise, they overlap in other ways) boundaryOverlapFlag = False if boundaryInclusive: boundaryOverlapFlag = (startTime == cmprEndTime or endTime == cmprStartTime) # Is the overlap over a certain percent? percentOverlapFlag = False if percentThreshold > 0 and overlapFlag: totalTime = max(endTime, cmprEndTime) - min(startTime, cmprStartTime) percentOverlap = overlapTime / float(totalTime) percentOverlapFlag = percentOverlap >= percentThreshold # Is the overlap more than a certain threshold? timeOverlapFlag = False if timeThreshold > 0 and overlapFlag: timeOverlapFlag = overlapTime > timeThreshold overlapFlag = (overlapFlag or boundaryOverlapFlag or percentOverlapFlag or timeOverlapFlag) return overlapFlag
def numToStr(inputNum)
-
Expand source code
def numToStr(inputNum): if _isclose(inputNum, int(inputNum)): retVal = "%d" % inputNum else: retVal = "%s" % repr(inputNum) return retVal
def openTextgrid(fnFullPath)
-
Expand source code
def openTextgrid(fnFullPath): try: with io.open(fnFullPath, "r", encoding="utf-16") as fd: data = fd.read() except UnicodeError: with io.open(fnFullPath, "r", encoding="utf-8") as fd: data = fd.read() data = data.replace("\r\n", "\n") caseA = "ooTextFile short" in data caseB = "item [" not in data if caseA or caseB: textgrid = _parseShortTextgrid(data) else: textgrid = _parseNormalTextgrid(data) return textgrid
def strToIntOrFloat(inputStr)
-
Expand source code
def strToIntOrFloat(inputStr): return float(inputStr) if '.' in inputStr else int(inputStr)
Classes
class BadIntervalError (start, stop, label)
-
Common base class for all non-exit exceptions.
Expand source code
class BadIntervalError(Exception): def __init__(self, start, stop, label): super(BadIntervalError, self).__init__() self.start = start self.stop = stop self.label = label def __str__(self): dataTuple = (self.start, self.stop, self.label) return ("Problem with interval--could not create textgrid " + "(%s,%s,%s)" % dataTuple)
Ancestors
- builtins.Exception
- builtins.BaseException
class Interval (start, end, label)
-
Interval(start, end, label)
Ancestors
- builtins.tuple
Instance variables
var end
-
Alias for field number 1
var label
-
Alias for field number 2
var start
-
Alias for field number 0
class IntervalTier (name, entryList, minT=None, maxT=None, pairedWav=None)
-
An interval tier is for annotating events that have duration
The entryList is of the form: [(startTime1, endTime1, label1), (startTime2, endTime2, label2), ]
The data stored in the labels can be anything but will be interpreted as text by praatio (the label could be descriptive text e.g. ('erase this region') or numerical data e.g. (average pitch values like '132'))
Expand source code
class IntervalTier(TextgridTier): tierType = INTERVAL_TIER entryType = Interval def __init__(self, name, entryList, minT=None, maxT=None, pairedWav=None): ''' An interval tier is for annotating events that have duration The entryList is of the form: [(startTime1, endTime1, label1), (startTime2, endTime2, label2), ] The data stored in the labels can be anything but will be interpreted as text by praatio (the label could be descriptive text e.g. ('erase this region') or numerical data e.g. (average pitch values like '132')) ''' entryList = [(float(start), float(stop), label) for start, stop, label in entryList] if minT is not None: minT = float(minT) if maxT is not None: maxT = float(maxT) if maxT is None and pairedWav is not None: maxT = _getWavDuration(pairedWav) # Prevent poorly-formed textgrids from being created for entry in entryList: if entry[0] >= entry[1]: fmtStr = "Anomaly: startTime=%f, stopTime=%f, label=%s" print((fmtStr % (entry[0], entry[1], entry[2]))) assert(entry[0] < entry[1]) # Remove whitespace tmpEntryList = [] for start, stop, label in entryList: tmpEntryList.append(Interval(start, stop, label.strip())) entryList = tmpEntryList # Determine the minimum and maximum timestampes minTimeList = [subList[0] for subList in entryList] maxTimeList = [subList[1] for subList in entryList] if minT is not None: minTimeList.append(minT) if maxT is not None: maxTimeList.append(maxT) try: minT = min(minTimeList) maxT = max(maxTimeList) except ValueError: raise TimelessTextgridTierException() super(IntervalTier, self).__init__(name, entryList, minT, maxT) def crop(self, cropStart, cropEnd, mode, rebaseToZero): ''' Creates a new tier with all entries that fit inside the new interval mode = {'strict', 'lax', 'truncated'} If 'strict', only intervals wholly contained by the crop interval will be kept If 'lax', partially contained intervals will be kept If 'truncated', partially contained intervals will be truncated to fit within the crop region. If rebaseToZero is True, the cropped textgrid values will be subtracted by the cropStart ''' assert(mode in ['strict', 'lax', 'truncated']) # Debugging variables cutTStart = 0 cutTWithin = 0 cutTEnd = 0 firstIntervalKeptProportion = 0 lastIntervalKeptProportion = 0 newEntryList = [] for entry in self.entryList: matchedEntry = None intervalStart = entry[0] intervalEnd = entry[1] intervalLabel = entry[2] # Don't need to investigate if the interval is before or after # the crop region if intervalEnd <= cropStart or intervalStart >= cropEnd: continue # Determine if the current subEntry is wholly contained # within the superEntry if intervalStart >= cropStart and intervalEnd <= cropEnd: matchedEntry = entry # If it is only partially contained within the superEntry AND # inclusion is 'lax', include it anyways elif mode == 'lax' and (intervalStart >= cropStart or intervalEnd <= cropEnd): matchedEntry = entry # If not strict, include partial tiers on the edges # -- regardless, record how much information was lost # - for strict=True, the total time of the cut interval # - for strict=False, the portion of the interval that lies # outside the new interval # The current interval stradles the end of the new interval elif intervalStart >= cropStart and intervalEnd > cropEnd: cutTEnd = intervalEnd - cropEnd lastIntervalKeptProportion = ((cropEnd - intervalStart) / (intervalEnd - intervalStart)) if mode == "truncated": matchedEntry = (intervalStart, cropEnd, intervalLabel) else: cutTWithin += cropEnd - cropStart # The current interval stradles the start of the new interval elif intervalStart < cropStart and intervalEnd <= cropEnd: cutTStart = cropStart - intervalStart firstIntervalKeptProportion = ((intervalEnd - cropStart) / (intervalEnd - intervalStart)) if mode == "truncated": matchedEntry = (cropStart, intervalEnd, intervalLabel) else: cutTWithin += cropEnd - cropStart # The current interval contains the new interval completely elif intervalStart <= cropStart and intervalEnd >= cropEnd: if mode == "lax": matchedEntry = entry elif mode == "truncated": matchedEntry = (cropStart, cropEnd, intervalLabel) else: cutTWithin += cropEnd - cropStart if matchedEntry is not None: newEntryList.append(matchedEntry) if rebaseToZero is True: newEntryList = [(startT - cropStart, stopT - cropStart, label) for startT, stopT, label in newEntryList] minT = 0 maxT = cropEnd - cropStart else: minT = cropStart maxT = cropEnd # Create subtier croppedTier = IntervalTier(self.name, newEntryList, minT, maxT) # DEBUG info # debugInfo = (subTier, cutTStart, cutTWithin, cutTEnd, # firstIntervalKeptProportion, lastIntervalKeptProportion) return croppedTier def difference(self, tier): ''' Takes the set difference of this tier and the given one Any overlapping portions of entries with entries in this textgrid will be removed from the returned tier. ''' retTier = self.new() for entry in tier.entryList: retTier = retTier.eraseRegion(entry[0], entry[1], collisionCode='truncate', doShrink=False) return retTier def editTimestamps(self, offset, allowOvershoot=False): ''' Modifies all timestamps by a constant amount Can modify the interval start independent of the interval end If allowOvershoot is True, an interval can go beyond the bounds of the textgrid ''' newEntryList = [] for start, stop, label in self.entryList: newStart = offset + start newStop = offset + stop if allowOvershoot is not True: assert(newStart >= self.minTimestamp) assert(newStop <= self.maxTimestamp) if newStop < 0: continue if newStart < 0: newStart = 0 if newStart < 0: continue newEntryList.append((newStart, newStop, label)) # Determine new min and max timestamps newMin = min([entry[0] for entry in newEntryList]) newMax = max([entry[1] for entry in newEntryList]) if newMin > self.minTimestamp: newMin = self.minTimestamp if newMax < self.maxTimestamp: newMax = self.maxTimestamp return IntervalTier(self.name, newEntryList, newMin, newMax) def eraseRegion(self, start, stop, collisionCode=None, doShrink=True): ''' Makes a region in a tier blank (removes all contained entries) collisionCode: in the event that intervals exist in the insertion area, one of three things may happen - 'truncate' - partially contained entries will have the portion removed that overlaps with the target entry - 'categorical' - all entries that overlap, even partially, with the target entry will be completely removed - None or any other value - AssertionError is thrown doShrink: if True, moves leftward by (/stop/ - /start/) amount, each item that occurs after /stop/ ''' matchList = self.crop(start, stop, 'lax', False).entryList newTier = self.new() # if the collisionCode is not properly set it isn't clear what to do assert(collisionCode == 'truncate' or collisionCode == 'categorical') if len(matchList) == 0: pass else: # Remove all the matches from the entryList # Go in reverse order because we're destructively altering # the order of the list (messes up index order) for tmpEntry in matchList[::-1]: newTier.deleteEntry(tmpEntry) # If we're only truncating, reinsert entries on the left and # right edges if collisionCode == 'truncate': # Check left edge if matchList[0][0] < start: newEntry = (matchList[0][0], start, matchList[0][-1]) newTier.entryList.append(newEntry) # Check right edge if matchList[-1][1] > stop: newEntry = (stop, matchList[-1][1], matchList[-1][-1]) newTier.entryList.append(newEntry) if doShrink is True: diff = stop - start newEntryList = [] for entry in newTier.entryList: if entry[1] <= start: newEntryList.append(entry) elif entry[0] >= stop: newEntryList.append((entry[0] - diff, entry[1] - diff, entry[2])) # Special case: an interval that spanned the deleted # section for i in range(0, len(newEntryList) - 1): rightEdge = newEntryList[i][1] == start leftEdge = newEntryList[i + 1][0] == start sameLabel = (newEntryList[i][2] == newEntryList[i + 1][2]) if rightEdge and leftEdge and sameLabel: newEntry = (newEntryList[i][0], newEntryList[i + 1][1], newEntryList[i][2]) newEntryList.pop(i + 1) newEntryList.pop(i) newEntryList.insert(i, newEntry) # Only one interval can span the deleted section, # so if we've found it, move on break newMax = newTier.maxTimestamp - diff newTier = newTier.new(entryList=newEntryList, maxTimestamp=newMax) return newTier def getValuesInIntervals(self, dataTupleList): ''' Returns data from dataTupleList contained in labeled intervals dataTupleList should be of the form: [(time1, value1a, value1b,...), (time2, value2a, value2b...), ...] ''' returnList = [] for interval in self.entryList: intervalDataList = utils.getValuesInInterval(dataTupleList, interval[0], interval[1]) returnList.append((interval, intervalDataList)) return returnList def getNonEntries(self): ''' Returns the regions of the textgrid without labels This can include unlabeled segments and regions marked as silent. ''' entryList = self.entryList invertedEntryList = [(entryList[i][1], entryList[i + 1][0], "") for i in range(len(entryList) - 1)] # Remove entries that have no duration (ie lie between two entries # that share a border) invertedEntryList = [entry for entry in invertedEntryList if entry[0] < entry[1]] if entryList[0][0] > 0: invertedEntryList.insert(0, (0, entryList[0][0], "")) if entryList[-1][1] < self.maxTimestamp: invertedEntryList.append((entryList[-1][1], self.maxTimestamp, "")) invertedEntryList = [entry if isinstance(entry, Interval) else Interval(*entry) for entry in invertedEntryList] return invertedEntryList def insertEntry(self, entry, warnFlag=True, collisionCode=None): ''' inserts an interval into the tier collisionCode: in the event that intervals exist in the insertion area, one of three things may happen - 'replace' - existing items will be removed - 'merge' - inserting item will be fused with existing items - None or any other value - TextgridCollisionException is thrown if warnFlag is True and collisionCode is not None, the user is notified of each collision ''' startTime, endTime = entry[:2] matchList = self.crop(startTime, endTime, 'lax', False).entryList if len(matchList) == 0: self.entryList.append(entry) elif collisionCode.lower() == "replace": for matchEntry in matchList: self.deleteEntry(matchEntry) self.entryList.append(entry) elif collisionCode.lower() == "merge": for matchEntry in matchList: self.deleteEntry(matchEntry) matchList.append(entry) matchList.sort() # By starting time newEntry = (min([entry[0] for entry in matchList]), max([entry[1] for entry in matchList]), "-".join([entry[2] for entry in matchList])) self.entryList.append(Interval(*newEntry)) else: raise TextgridCollisionException(self.name, entry, matchList) self.sort() if len(matchList) != 0 and warnFlag is True: fmtStr = "Collision warning for %s with items %s of tier %s" print((fmtStr % (str(entry), str(matchList), self.name))) def insertSpace(self, start, duration, collisionCode=None): ''' Inserts a blank region into the tier collisionCode: in the event that an interval stradles the starting point - '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 - AssertionError is thrown ''' # if the collisionCode is not properly set it isn't clear what to do assert(collisionCode == 'stretch' or collisionCode == 'split' or collisionCode == 'no change') newEntryList = [] for entry in self.entryList: # Entry exists before the insertion point if entry[1] <= start: newEntryList.append(entry) # Entry exists after the insertion point elif entry[0] >= start: newEntryList.append((entry[0] + duration, entry[1] + duration, entry[2])) # Entry straddles the insertion point elif entry[0] <= start and entry[1] > start: if collisionCode == 'stretch': newEntryList.append((entry[0], entry[1] + duration, entry[2])) elif collisionCode == 'split': # Left side of the split newEntryList.append((entry[0], start, entry[2])) # Right side of the split newEntryList.append((start + duration, start + duration + (entry[1] - start), entry[2])) elif collisionCode == 'no change': newEntryList.append(entry) newTier = self.new(entryList=newEntryList, maxTimestamp=self.maxTimestamp + duration) return newTier def intersection(self, tier): ''' Takes the set intersection of this tier and the given one Only intervals that exist in both tiers will remain in the returned tier. If intervals partially overlap, only the overlapping portion will be returned. ''' retEntryList = [] for start, stop, label in tier.entryList: subTier = self.crop(start, stop, "truncated", False) # Combine the labels in the two tiers stub = "%s-%%s" % label subEntryList = [(subEntry[0], subEntry[1], stub % subEntry[2]) for subEntry in subTier.entryList] retEntryList.extend(subEntryList) newName = "%s-%s" % (self.name, tier.name) retTier = self.new(newName, retEntryList) return retTier def morph(self, targetTier, filterFunc=None): ''' Morphs the duration of segments in this tier to those in another This preserves the labels and the duration of silence in this tier while changing the duration of labeled segments. ''' cumulativeAdjustAmount = 0 lastFromEnd = 0 newEntryList = [] allPoints = [self.entryList, targetTier.entryList] for fromEntry, targetEntry in utils.safeZip(allPoints, True): fromStart, fromEnd, fromLabel = fromEntry targetStart, targetEnd = targetEntry[:2] # fromStart - lastFromEnd -> was this interval and the # last one adjacent? toStart = (fromStart - lastFromEnd) + cumulativeAdjustAmount currAdjustAmount = (fromEnd - fromStart) if filterFunc is None or filterFunc(fromLabel): currAdjustAmount = (targetEnd - targetStart) toEnd = cumulativeAdjustAmount = toStart + currAdjustAmount newEntryList.append((toStart, toEnd, fromLabel)) lastFromEnd = fromEnd newMin = self.minTimestamp cumulativeDifference = (newEntryList[-1][1] - self.entryList[-1][1]) newMax = self.maxTimestamp + cumulativeDifference return IntervalTier(self.name, newEntryList, newMin, newMax)
Ancestors
Class variables
var tierType
-
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
Methods
def crop(self, cropStart, cropEnd, mode, rebaseToZero)
-
Creates a new tier with all entries that fit inside the new interval
mode = {'strict', 'lax', 'truncated'} If 'strict', only intervals wholly contained by the crop interval will be kept If 'lax', partially contained intervals will be kept If 'truncated', partially contained intervals will be truncated to fit within the crop region.
If rebaseToZero is True, the cropped textgrid values will be subtracted by the cropStart
Expand source code
def crop(self, cropStart, cropEnd, mode, rebaseToZero): ''' Creates a new tier with all entries that fit inside the new interval mode = {'strict', 'lax', 'truncated'} If 'strict', only intervals wholly contained by the crop interval will be kept If 'lax', partially contained intervals will be kept If 'truncated', partially contained intervals will be truncated to fit within the crop region. If rebaseToZero is True, the cropped textgrid values will be subtracted by the cropStart ''' assert(mode in ['strict', 'lax', 'truncated']) # Debugging variables cutTStart = 0 cutTWithin = 0 cutTEnd = 0 firstIntervalKeptProportion = 0 lastIntervalKeptProportion = 0 newEntryList = [] for entry in self.entryList: matchedEntry = None intervalStart = entry[0] intervalEnd = entry[1] intervalLabel = entry[2] # Don't need to investigate if the interval is before or after # the crop region if intervalEnd <= cropStart or intervalStart >= cropEnd: continue # Determine if the current subEntry is wholly contained # within the superEntry if intervalStart >= cropStart and intervalEnd <= cropEnd: matchedEntry = entry # If it is only partially contained within the superEntry AND # inclusion is 'lax', include it anyways elif mode == 'lax' and (intervalStart >= cropStart or intervalEnd <= cropEnd): matchedEntry = entry # If not strict, include partial tiers on the edges # -- regardless, record how much information was lost # - for strict=True, the total time of the cut interval # - for strict=False, the portion of the interval that lies # outside the new interval # The current interval stradles the end of the new interval elif intervalStart >= cropStart and intervalEnd > cropEnd: cutTEnd = intervalEnd - cropEnd lastIntervalKeptProportion = ((cropEnd - intervalStart) / (intervalEnd - intervalStart)) if mode == "truncated": matchedEntry = (intervalStart, cropEnd, intervalLabel) else: cutTWithin += cropEnd - cropStart # The current interval stradles the start of the new interval elif intervalStart < cropStart and intervalEnd <= cropEnd: cutTStart = cropStart - intervalStart firstIntervalKeptProportion = ((intervalEnd - cropStart) / (intervalEnd - intervalStart)) if mode == "truncated": matchedEntry = (cropStart, intervalEnd, intervalLabel) else: cutTWithin += cropEnd - cropStart # The current interval contains the new interval completely elif intervalStart <= cropStart and intervalEnd >= cropEnd: if mode == "lax": matchedEntry = entry elif mode == "truncated": matchedEntry = (cropStart, cropEnd, intervalLabel) else: cutTWithin += cropEnd - cropStart if matchedEntry is not None: newEntryList.append(matchedEntry) if rebaseToZero is True: newEntryList = [(startT - cropStart, stopT - cropStart, label) for startT, stopT, label in newEntryList] minT = 0 maxT = cropEnd - cropStart else: minT = cropStart maxT = cropEnd # Create subtier croppedTier = IntervalTier(self.name, newEntryList, minT, maxT) # DEBUG info # debugInfo = (subTier, cutTStart, cutTWithin, cutTEnd, # firstIntervalKeptProportion, lastIntervalKeptProportion) return croppedTier
def difference(self, tier)
-
Takes the set difference of this tier and the given one
Any overlapping portions of entries with entries in this textgrid will be removed from the returned tier.
Expand source code
def difference(self, tier): ''' Takes the set difference of this tier and the given one Any overlapping portions of entries with entries in this textgrid will be removed from the returned tier. ''' retTier = self.new() for entry in tier.entryList: retTier = retTier.eraseRegion(entry[0], entry[1], collisionCode='truncate', doShrink=False) return retTier
def editTimestamps(self, offset, allowOvershoot=False)
-
Modifies all timestamps by a constant amount
Can modify the interval start independent of the interval end
If allowOvershoot is True, an interval can go beyond the bounds of the textgrid
Expand source code
def editTimestamps(self, offset, allowOvershoot=False): ''' Modifies all timestamps by a constant amount Can modify the interval start independent of the interval end If allowOvershoot is True, an interval can go beyond the bounds of the textgrid ''' newEntryList = [] for start, stop, label in self.entryList: newStart = offset + start newStop = offset + stop if allowOvershoot is not True: assert(newStart >= self.minTimestamp) assert(newStop <= self.maxTimestamp) if newStop < 0: continue if newStart < 0: newStart = 0 if newStart < 0: continue newEntryList.append((newStart, newStop, label)) # Determine new min and max timestamps newMin = min([entry[0] for entry in newEntryList]) newMax = max([entry[1] for entry in newEntryList]) if newMin > self.minTimestamp: newMin = self.minTimestamp if newMax < self.maxTimestamp: newMax = self.maxTimestamp return IntervalTier(self.name, newEntryList, newMin, newMax)
def eraseRegion(self, start, stop, collisionCode=None, doShrink=True)
-
Makes a region in a tier blank (removes all contained entries)
collisionCode: in the event that intervals exist in the insertion area, one of three things may happen - 'truncate' - partially contained entries will have the portion removed that overlaps with the target entry - 'categorical' - all entries that overlap, even partially, with the target entry will be completely removed - None or any other value - AssertionError is thrown
doShrink: if True, moves leftward by (/stop/ - /start/) amount, each item that occurs after /stop/
Expand source code
def eraseRegion(self, start, stop, collisionCode=None, doShrink=True): ''' Makes a region in a tier blank (removes all contained entries) collisionCode: in the event that intervals exist in the insertion area, one of three things may happen - 'truncate' - partially contained entries will have the portion removed that overlaps with the target entry - 'categorical' - all entries that overlap, even partially, with the target entry will be completely removed - None or any other value - AssertionError is thrown doShrink: if True, moves leftward by (/stop/ - /start/) amount, each item that occurs after /stop/ ''' matchList = self.crop(start, stop, 'lax', False).entryList newTier = self.new() # if the collisionCode is not properly set it isn't clear what to do assert(collisionCode == 'truncate' or collisionCode == 'categorical') if len(matchList) == 0: pass else: # Remove all the matches from the entryList # Go in reverse order because we're destructively altering # the order of the list (messes up index order) for tmpEntry in matchList[::-1]: newTier.deleteEntry(tmpEntry) # If we're only truncating, reinsert entries on the left and # right edges if collisionCode == 'truncate': # Check left edge if matchList[0][0] < start: newEntry = (matchList[0][0], start, matchList[0][-1]) newTier.entryList.append(newEntry) # Check right edge if matchList[-1][1] > stop: newEntry = (stop, matchList[-1][1], matchList[-1][-1]) newTier.entryList.append(newEntry) if doShrink is True: diff = stop - start newEntryList = [] for entry in newTier.entryList: if entry[1] <= start: newEntryList.append(entry) elif entry[0] >= stop: newEntryList.append((entry[0] - diff, entry[1] - diff, entry[2])) # Special case: an interval that spanned the deleted # section for i in range(0, len(newEntryList) - 1): rightEdge = newEntryList[i][1] == start leftEdge = newEntryList[i + 1][0] == start sameLabel = (newEntryList[i][2] == newEntryList[i + 1][2]) if rightEdge and leftEdge and sameLabel: newEntry = (newEntryList[i][0], newEntryList[i + 1][1], newEntryList[i][2]) newEntryList.pop(i + 1) newEntryList.pop(i) newEntryList.insert(i, newEntry) # Only one interval can span the deleted section, # so if we've found it, move on break newMax = newTier.maxTimestamp - diff newTier = newTier.new(entryList=newEntryList, maxTimestamp=newMax) return newTier
def getNonEntries(self)
-
Returns the regions of the textgrid without labels
This can include unlabeled segments and regions marked as silent.
Expand source code
def getNonEntries(self): ''' Returns the regions of the textgrid without labels This can include unlabeled segments and regions marked as silent. ''' entryList = self.entryList invertedEntryList = [(entryList[i][1], entryList[i + 1][0], "") for i in range(len(entryList) - 1)] # Remove entries that have no duration (ie lie between two entries # that share a border) invertedEntryList = [entry for entry in invertedEntryList if entry[0] < entry[1]] if entryList[0][0] > 0: invertedEntryList.insert(0, (0, entryList[0][0], "")) if entryList[-1][1] < self.maxTimestamp: invertedEntryList.append((entryList[-1][1], self.maxTimestamp, "")) invertedEntryList = [entry if isinstance(entry, Interval) else Interval(*entry) for entry in invertedEntryList] return invertedEntryList
def getValuesInIntervals(self, dataTupleList)
-
Returns data from dataTupleList contained in labeled intervals
dataTupleList should be of the form: [(time1, value1a, value1b,…), (time2, value2a, value2b…), …]
Expand source code
def getValuesInIntervals(self, dataTupleList): ''' Returns data from dataTupleList contained in labeled intervals dataTupleList should be of the form: [(time1, value1a, value1b,...), (time2, value2a, value2b...), ...] ''' returnList = [] for interval in self.entryList: intervalDataList = utils.getValuesInInterval(dataTupleList, interval[0], interval[1]) returnList.append((interval, intervalDataList)) return returnList
def insertEntry(self, entry, warnFlag=True, collisionCode=None)
-
inserts an interval into the tier
collisionCode: in the event that intervals exist in the insertion area, one of three things may happen - 'replace' - existing items will be removed - 'merge' - inserting item will be fused with existing items - None or any other value - TextgridCollisionException is thrown
if warnFlag is True and collisionCode is not None, the user is notified of each collision
Expand source code
def insertEntry(self, entry, warnFlag=True, collisionCode=None): ''' inserts an interval into the tier collisionCode: in the event that intervals exist in the insertion area, one of three things may happen - 'replace' - existing items will be removed - 'merge' - inserting item will be fused with existing items - None or any other value - TextgridCollisionException is thrown if warnFlag is True and collisionCode is not None, the user is notified of each collision ''' startTime, endTime = entry[:2] matchList = self.crop(startTime, endTime, 'lax', False).entryList if len(matchList) == 0: self.entryList.append(entry) elif collisionCode.lower() == "replace": for matchEntry in matchList: self.deleteEntry(matchEntry) self.entryList.append(entry) elif collisionCode.lower() == "merge": for matchEntry in matchList: self.deleteEntry(matchEntry) matchList.append(entry) matchList.sort() # By starting time newEntry = (min([entry[0] for entry in matchList]), max([entry[1] for entry in matchList]), "-".join([entry[2] for entry in matchList])) self.entryList.append(Interval(*newEntry)) else: raise TextgridCollisionException(self.name, entry, matchList) self.sort() if len(matchList) != 0 and warnFlag is True: fmtStr = "Collision warning for %s with items %s of tier %s" print((fmtStr % (str(entry), str(matchList), self.name)))
def insertSpace(self, start, duration, collisionCode=None)
-
Inserts a blank region into the tier
collisionCode: in the event that an interval stradles the starting point - '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 - AssertionError is thrown
Expand source code
def insertSpace(self, start, duration, collisionCode=None): ''' Inserts a blank region into the tier collisionCode: in the event that an interval stradles the starting point - '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 - AssertionError is thrown ''' # if the collisionCode is not properly set it isn't clear what to do assert(collisionCode == 'stretch' or collisionCode == 'split' or collisionCode == 'no change') newEntryList = [] for entry in self.entryList: # Entry exists before the insertion point if entry[1] <= start: newEntryList.append(entry) # Entry exists after the insertion point elif entry[0] >= start: newEntryList.append((entry[0] + duration, entry[1] + duration, entry[2])) # Entry straddles the insertion point elif entry[0] <= start and entry[1] > start: if collisionCode == 'stretch': newEntryList.append((entry[0], entry[1] + duration, entry[2])) elif collisionCode == 'split': # Left side of the split newEntryList.append((entry[0], start, entry[2])) # Right side of the split newEntryList.append((start + duration, start + duration + (entry[1] - start), entry[2])) elif collisionCode == 'no change': newEntryList.append(entry) newTier = self.new(entryList=newEntryList, maxTimestamp=self.maxTimestamp + duration) return newTier
def intersection(self, tier)
-
Takes the set intersection of this tier and the given one
Only intervals that exist in both tiers will remain in the returned tier. If intervals partially overlap, only the overlapping portion will be returned.
Expand source code
def intersection(self, tier): ''' Takes the set intersection of this tier and the given one Only intervals that exist in both tiers will remain in the returned tier. If intervals partially overlap, only the overlapping portion will be returned. ''' retEntryList = [] for start, stop, label in tier.entryList: subTier = self.crop(start, stop, "truncated", False) # Combine the labels in the two tiers stub = "%s-%%s" % label subEntryList = [(subEntry[0], subEntry[1], stub % subEntry[2]) for subEntry in subTier.entryList] retEntryList.extend(subEntryList) newName = "%s-%s" % (self.name, tier.name) retTier = self.new(newName, retEntryList) return retTier
def morph(self, targetTier, filterFunc=None)
-
Morphs the duration of segments in this tier to those in another
This preserves the labels and the duration of silence in this tier while changing the duration of labeled segments.
Expand source code
def morph(self, targetTier, filterFunc=None): ''' Morphs the duration of segments in this tier to those in another This preserves the labels and the duration of silence in this tier while changing the duration of labeled segments. ''' cumulativeAdjustAmount = 0 lastFromEnd = 0 newEntryList = [] allPoints = [self.entryList, targetTier.entryList] for fromEntry, targetEntry in utils.safeZip(allPoints, True): fromStart, fromEnd, fromLabel = fromEntry targetStart, targetEnd = targetEntry[:2] # fromStart - lastFromEnd -> was this interval and the # last one adjacent? toStart = (fromStart - lastFromEnd) + cumulativeAdjustAmount currAdjustAmount = (fromEnd - fromStart) if filterFunc is None or filterFunc(fromLabel): currAdjustAmount = (targetEnd - targetStart) toEnd = cumulativeAdjustAmount = toStart + currAdjustAmount newEntryList.append((toStart, toEnd, fromLabel)) lastFromEnd = fromEnd newMin = self.minTimestamp cumulativeDifference = (newEntryList[-1][1] - self.entryList[-1][1]) newMax = self.maxTimestamp + cumulativeDifference return IntervalTier(self.name, newEntryList, newMin, newMax)
Inherited members
class Point (time, label)
-
Point(time, label)
Ancestors
- builtins.tuple
Instance variables
var label
-
Alias for field number 1
var time
-
Alias for field number 0
class PointTier (name, entryList, minT=None, maxT=None, pairedWav=None)
-
A point tier is for annotating instaneous events
The entryList is of the form: [(timeVal1, label1), (timeVal2, label2), ]
The data stored in the labels can be anything but will be interpreted as text by praatio (the label could be descriptive text e.g. ('peak point here') or numerical data e.g. (pitch values like '132'))
Expand source code
class PointTier(TextgridTier): tierType = POINT_TIER entryType = Point def __init__(self, name, entryList, minT=None, maxT=None, pairedWav=None): ''' A point tier is for annotating instaneous events The entryList is of the form: [(timeVal1, label1), (timeVal2, label2), ] The data stored in the labels can be anything but will be interpreted as text by praatio (the label could be descriptive text e.g. ('peak point here') or numerical data e.g. (pitch values like '132')) ''' entryList = [Point(float(time), label) for time, label in entryList] # Determine the min and max timestamps timeList = [time for time, label in entryList] if minT is not None: timeList.append(float(minT)) if maxT is not None: timeList.append(float(maxT)) if maxT is None and pairedWav is not None: maxT = _getWavDuration(pairedWav) try: minT = min(timeList) maxT = max(timeList) except ValueError: raise TimelessTextgridTierException() super(PointTier, self).__init__(name, entryList, minT, maxT) def crop(self, cropStart, cropEnd, mode=None, rebaseToZero=True): ''' Creates a new tier containing all entries inside the new interval mode is ignored. This parameter is kept for compatibility with IntervalTier.crop() ''' newEntryList = [] for entry in self.entryList: timestamp = entry[0] if timestamp >= cropStart and timestamp <= cropEnd: newEntryList.append(entry) if rebaseToZero is True: newEntryList = [(timeV - cropStart, label) for timeV, label in newEntryList] minT = 0 maxT = cropEnd - cropStart else: minT = cropStart maxT = cropEnd # Create subtier subTier = PointTier(self.name, newEntryList, minT, maxT) return subTier def editTimestamps(self, offset, allowOvershoot=False): ''' Modifies all timestamps by a constant amount If allowOvershoot is True, an interval can go beyond the bounds of the textgrid ''' newEntryList = [] for timestamp, label in self.entryList: newTimestamp = timestamp + offset if not allowOvershoot: assert(newTimestamp > self.minTimestamp) assert(newTimestamp <= self.maxTimestamp) if newTimestamp < 0: continue newEntryList.append((newTimestamp, label)) # Determine new min and max timestamps timeList = [float(subList[0]) for subList in newEntryList] newMin = min(timeList) newMax = max(timeList) if newMin > self.minTimestamp: newMin = self.minTimestamp if newMax < self.maxTimestamp: newMax = self.maxTimestamp return PointTier(self.name, newEntryList, newMin, newMax) def getValuesAtPoints(self, dataTupleList, fuzzyMatching=False): ''' Get the values that occur at points in the point tier If fuzzyMatching is True, if there is not a feature value at a point, the nearest feature value will be taken. The procedure assumes that all data is ordered in time. dataTupleList should be in the form [(t1, v1a, v1b, ..), (t2, v2a, v2b, ..), ..] The procedure makes one pass through dataTupleList and one pass through self.entryList. If the data is not sequentially ordered, the incorrect response will be returned. ''' i = 0 retList = [] sortedDataTupleList = dataTupleList.sorted() for timestamp, label in self.entryList: retTuple = utils.getValueAtTime(timestamp, sortedDataTupleList, fuzzyMatching=fuzzyMatching, startI=i) retTime, retVal, i = retTuple retList.append((retTime, label, retVal)) return retList def eraseRegion(self, start, stop, collisionCode=None, doShrink=True): ''' Makes a region in a tier blank (removes all contained entries) collisionCode: Ignored for the moment (added for compatibility with eraseRegion() for Interval Tiers) doShrink: if True, moves leftward by (/stop/ - /start/) all points to the right of /stop/ ''' newTier = self.new() croppedTier = newTier.crop(start, stop, "truncated", False) matchList = croppedTier.entryList if len(matchList) == 0: pass else: # Remove all the matches from the entryList # Go in reverse order because we're destructively altering # the order of the list (messes up index order) for tmpEntry in matchList[::-1]: newTier.deleteEntry(tmpEntry) if doShrink is True: newEntryList = [] diff = stop - start for timestamp, label in newTier.entryList: if timestamp < start: newEntryList.append((timestamp, label)) elif timestamp > stop: newEntryList.append((timestamp - diff, label)) newMax = newTier.maxTimestamp - diff newTier = newTier.new(entryList=newEntryList, maxTimestamp=newMax) return newTier def insertEntry(self, entry, warnFlag=True, collisionCode=None): ''' inserts an interval into the tier collisionCode: in the event that intervals exist in the insertion area, one of three things may happen - 'replace' - existing items will be removed - 'merge' - inserting item will be fused with existing items - None or any other value - TextgridCollisionException is thrown if warnFlag is True and collisionCode is not None, the user is notified of each collision ''' timestamp, label = entry if not isinstance(entry, Point): entry = Point(timestamp, label) matchList = [] i = None for i, searchEntry in self.entryList: if searchEntry[0] == entry[0]: matchList.append(searchEntry) break if len(matchList) == 0: self.entryList.append(entry) elif collisionCode.lower() == "replace": self.deleteEntry(self.entryList[i]) self.entryList.append(entry) elif collisionCode.lower() == "merge": oldEntry = self.entryList[i] newEntry = Point(timestamp, "-".join([oldEntry[-1], label])) self.deleteEntry(self.entryList[i]) self.entryList.append(newEntry) else: raise TextgridCollisionException(self.name, entry, matchList) self.sort() if len(matchList) != 0 and warnFlag is True: fmtStr = "Collision warning for %s with items %s of tier %s" print((fmtStr % (str(entry), str(matchList), self.name))) def insertSpace(self, start, duration, collisionCode=None): ''' Inserts a region into the tier collisionCode: Ignored for the moment (added for compatibility with insertSpace() for Interval Tiers) ''' newEntryList = [] for entry in self.entryList: if entry[0] <= start: newEntryList.append(entry) elif entry[0] > start: newEntryList.append((entry[0] + duration, entry[1])) newTier = self.new(entryList=newEntryList, maxTimestamp=self.maxTimestamp + duration) return newTier
Ancestors
Class variables
var entryType
-
Point(time, label)
var tierType
-
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
Methods
def crop(self, cropStart, cropEnd, mode=None, rebaseToZero=True)
-
Creates a new tier containing all entries inside the new interval
mode is ignored. This parameter is kept for compatibility with IntervalTier.crop()
Expand source code
def crop(self, cropStart, cropEnd, mode=None, rebaseToZero=True): ''' Creates a new tier containing all entries inside the new interval mode is ignored. This parameter is kept for compatibility with IntervalTier.crop() ''' newEntryList = [] for entry in self.entryList: timestamp = entry[0] if timestamp >= cropStart and timestamp <= cropEnd: newEntryList.append(entry) if rebaseToZero is True: newEntryList = [(timeV - cropStart, label) for timeV, label in newEntryList] minT = 0 maxT = cropEnd - cropStart else: minT = cropStart maxT = cropEnd # Create subtier subTier = PointTier(self.name, newEntryList, minT, maxT) return subTier
def editTimestamps(self, offset, allowOvershoot=False)
-
Modifies all timestamps by a constant amount
If allowOvershoot is True, an interval can go beyond the bounds of the textgrid
Expand source code
def editTimestamps(self, offset, allowOvershoot=False): ''' Modifies all timestamps by a constant amount If allowOvershoot is True, an interval can go beyond the bounds of the textgrid ''' newEntryList = [] for timestamp, label in self.entryList: newTimestamp = timestamp + offset if not allowOvershoot: assert(newTimestamp > self.minTimestamp) assert(newTimestamp <= self.maxTimestamp) if newTimestamp < 0: continue newEntryList.append((newTimestamp, label)) # Determine new min and max timestamps timeList = [float(subList[0]) for subList in newEntryList] newMin = min(timeList) newMax = max(timeList) if newMin > self.minTimestamp: newMin = self.minTimestamp if newMax < self.maxTimestamp: newMax = self.maxTimestamp return PointTier(self.name, newEntryList, newMin, newMax)
def eraseRegion(self, start, stop, collisionCode=None, doShrink=True)
-
Makes a region in a tier blank (removes all contained entries)
collisionCode: Ignored for the moment (added for compatibility with eraseRegion() for Interval Tiers) doShrink: if True, moves leftward by (/stop/ - /start/) all points to the right of /stop/
Expand source code
def eraseRegion(self, start, stop, collisionCode=None, doShrink=True): ''' Makes a region in a tier blank (removes all contained entries) collisionCode: Ignored for the moment (added for compatibility with eraseRegion() for Interval Tiers) doShrink: if True, moves leftward by (/stop/ - /start/) all points to the right of /stop/ ''' newTier = self.new() croppedTier = newTier.crop(start, stop, "truncated", False) matchList = croppedTier.entryList if len(matchList) == 0: pass else: # Remove all the matches from the entryList # Go in reverse order because we're destructively altering # the order of the list (messes up index order) for tmpEntry in matchList[::-1]: newTier.deleteEntry(tmpEntry) if doShrink is True: newEntryList = [] diff = stop - start for timestamp, label in newTier.entryList: if timestamp < start: newEntryList.append((timestamp, label)) elif timestamp > stop: newEntryList.append((timestamp - diff, label)) newMax = newTier.maxTimestamp - diff newTier = newTier.new(entryList=newEntryList, maxTimestamp=newMax) return newTier
def getValuesAtPoints(self, dataTupleList, fuzzyMatching=False)
-
Get the values that occur at points in the point tier
If fuzzyMatching is True, if there is not a feature value at a point, the nearest feature value will be taken.
The procedure assumes that all data is ordered in time. dataTupleList should be in the form [(t1, v1a, v1b, ..), (t2, v2a, v2b, ..), ..]
The procedure makes one pass through dataTupleList and one pass through self.entryList. If the data is not sequentially ordered, the incorrect response will be returned.
Expand source code
def getValuesAtPoints(self, dataTupleList, fuzzyMatching=False): ''' Get the values that occur at points in the point tier If fuzzyMatching is True, if there is not a feature value at a point, the nearest feature value will be taken. The procedure assumes that all data is ordered in time. dataTupleList should be in the form [(t1, v1a, v1b, ..), (t2, v2a, v2b, ..), ..] The procedure makes one pass through dataTupleList and one pass through self.entryList. If the data is not sequentially ordered, the incorrect response will be returned. ''' i = 0 retList = [] sortedDataTupleList = dataTupleList.sorted() for timestamp, label in self.entryList: retTuple = utils.getValueAtTime(timestamp, sortedDataTupleList, fuzzyMatching=fuzzyMatching, startI=i) retTime, retVal, i = retTuple retList.append((retTime, label, retVal)) return retList
def insertEntry(self, entry, warnFlag=True, collisionCode=None)
-
inserts an interval into the tier
collisionCode: in the event that intervals exist in the insertion area, one of three things may happen - 'replace' - existing items will be removed - 'merge' - inserting item will be fused with existing items - None or any other value - TextgridCollisionException is thrown
if warnFlag is True and collisionCode is not None, the user is notified of each collision
Expand source code
def insertEntry(self, entry, warnFlag=True, collisionCode=None): ''' inserts an interval into the tier collisionCode: in the event that intervals exist in the insertion area, one of three things may happen - 'replace' - existing items will be removed - 'merge' - inserting item will be fused with existing items - None or any other value - TextgridCollisionException is thrown if warnFlag is True and collisionCode is not None, the user is notified of each collision ''' timestamp, label = entry if not isinstance(entry, Point): entry = Point(timestamp, label) matchList = [] i = None for i, searchEntry in self.entryList: if searchEntry[0] == entry[0]: matchList.append(searchEntry) break if len(matchList) == 0: self.entryList.append(entry) elif collisionCode.lower() == "replace": self.deleteEntry(self.entryList[i]) self.entryList.append(entry) elif collisionCode.lower() == "merge": oldEntry = self.entryList[i] newEntry = Point(timestamp, "-".join([oldEntry[-1], label])) self.deleteEntry(self.entryList[i]) self.entryList.append(newEntry) else: raise TextgridCollisionException(self.name, entry, matchList) self.sort() if len(matchList) != 0 and warnFlag is True: fmtStr = "Collision warning for %s with items %s of tier %s" print((fmtStr % (str(entry), str(matchList), self.name)))
def insertSpace(self, start, duration, collisionCode=None)
-
Inserts a region into the tier
collisionCode: Ignored for the moment (added for compatibility with insertSpace() for Interval Tiers)
Expand source code
def insertSpace(self, start, duration, collisionCode=None): ''' Inserts a region into the tier collisionCode: Ignored for the moment (added for compatibility with insertSpace() for Interval Tiers) ''' newEntryList = [] for entry in self.entryList: if entry[0] <= start: newEntryList.append(entry) elif entry[0] > start: newEntryList.append((entry[0] + duration, entry[1])) newTier = self.new(entryList=newEntryList, maxTimestamp=self.maxTimestamp + duration) return newTier
Inherited members
class Textgrid
-
A container that stores and operates over interval and point tiers
Expand source code
class Textgrid(): def __init__(self): 'A container that stores and operates over interval and point tiers' self.tierNameList = [] # Preserves the order of the tiers self.tierDict = {} self.minTimestamp = None self.maxTimestamp = None def __eq__(self, other): isEqual = True isEqual &= _isclose(self.minTimestamp, other.minTimestamp) isEqual &= _isclose(self.maxTimestamp, other.maxTimestamp) isEqual &= self.tierNameList == other.tierNameList if isEqual: for tierName in self.tierNameList: isEqual &= self.tierDict[tierName] == other.tierDict[tierName] return isEqual def addTier(self, tier, tierIndex=None): ''' Add a tier to this textgrid. If tierIndex is specified, insert the tier into the specified position. ''' assert(tier.name not in list(self.tierDict.keys())) if tierIndex is None: self.tierNameList.append(tier.name) else: self.tierNameList.insert(tierIndex, tier.name) self.tierDict[tier.name] = tier minV = tier.minTimestamp if self.minTimestamp is None or minV < self.minTimestamp: self.minTimestamp = minV maxV = tier.maxTimestamp if self.maxTimestamp is None or maxV > self.maxTimestamp: self.maxTimestamp = maxV def appendTextgrid(self, tg, onlyMatchingNames=True): ''' Append one textgrid to the end of this one if onlyMatchingNames is False, tiers that don't appear in both textgrids will also appear ''' retTG = Textgrid() minTime = self.minTimestamp maxTime = self.maxTimestamp + tg.maxTimestamp # Get all tier names. Ordered first by this textgrid and # then by the other textgrid. combinedTierNameList = self.tierNameList for tierName in tg.tierNameList: if tierName not in combinedTierNameList: combinedTierNameList.append(tierName) # Determine the tier names that will be in the final textgrid finalTierNameList = [] if onlyMatchingNames is False: finalTierNameList = combinedTierNameList else: for tierName in combinedTierNameList: if tierName in self.tierNameList: if tierName in tg.tierNameList: finalTierNameList.append(tierName) # Add tiers from this textgrid for tierName in self.tierNameList: if tierName in finalTierNameList: tier = self.tierDict[tierName] retTG.addTier(tier) # Add tiers from the given textgrid for tierName in tg.tierNameList: if tierName in finalTierNameList: appendTier = tg.tierDict[tierName] appendTier = appendTier.new(minTimestamp=minTime, maxTimestamp=maxTime) appendTier = appendTier.editTimestamps(self.maxTimestamp) if tierName in retTG.tierNameList: tier = retTG.tierDict[tierName] newEntryList = retTG.tierDict[tierName].entryList newEntryList += appendTier.entryList tier = tier.new(entryList=newEntryList, minTimestamp=minTime, maxTimestamp=maxTime) retTG.replaceTier(tierName, tier) else: tier = appendTier tier = tier.new(minTimestamp=minTime, maxTimestamp=maxTime) retTG.addTier(tier) return retTG def crop(self, cropStart, cropEnd, mode, rebaseToZero): ''' Creates a textgrid where all intervals fit within the crop region mode = {'strict', 'lax', 'truncated'} If 'strict', only intervals wholly contained by the crop interval will be kept If 'lax', partially contained intervals will be kept If 'truncated', partially contained intervals will be truncated to fit within the crop region. If rebaseToZero is True, the cropped textgrid values will be subtracted by the cropStart ''' assert(mode in ['strict', 'lax', 'truncated']) newTG = Textgrid() if rebaseToZero is True: minT = 0 maxT = cropEnd - cropStart else: minT = cropStart maxT = cropEnd newTG.minTimestamp = minT newTG.maxTimestamp = maxT for tierName in self.tierNameList: tier = self.tierDict[tierName] newTier = tier.crop(cropStart, cropEnd, mode, rebaseToZero) newTG.addTier(newTier) return newTG def eraseRegion(self, start, stop, doShrink=True): ''' Makes a region in a tier blank (removes all contained entries) If 'doShrink' is 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 - stop) ''' diff = stop - start maxTimestamp = self.maxTimestamp if doShrink is True: maxTimestamp -= diff newTG = Textgrid() for name in self.tierNameList: tier = self.tierDict[name] tier = tier.eraseRegion(start, stop, 'truncate', doShrink) newTG.addTier(tier) newTG.maxTimestamp = maxTimestamp return newTG def editTimestamps(self, offset, allowOvershoot=False): ''' Modifies all timestamps by a constant amount ''' tg = Textgrid() for tierName in self.tierNameList: tier = self.tierDict[tierName] if len(tier.entryList) > 0: tier = tier.editTimestamps(offset, allowOvershoot) tg.addTier(tier) return tg def insertSpace(self, start, duration, collisionCode=None): ''' Inserts a blank region into a textgrid Every item that occurs after /start/ will be pushed back by /duration/ seconds collisionCode: in the event that an interval stradles the starting point - '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 - AssertionError is thrown ''' newTG = Textgrid() newTG.minTimestamp = self.minTimestamp newTG.maxTimestamp = self.maxTimestamp + duration for tierName in self.tierNameList: tier = self.tierDict[tierName] newTier = tier.insertSpace(start, duration, collisionCode) newTG.addTier(newTier) return newTG def mergeTiers(self, includeFunc=None, tierList=None, preserveOtherTiers=True): ''' Combine tiers /includeFunc/ regulates which intervals to include in the merging with all others being tossed (default accepts all) If /tierList/ is none, combine all tiers. ''' if tierList is None: tierList = self.tierNameList if includeFunc is None: includeFunc = lambda entryList: True # Determine the tiers to merge intervalTierNameList = [] pointTierNameList = [] for tierName in tierList: tier = self.tierDict[tierName] if isinstance(tier, IntervalTier): intervalTierNameList.append(tierName) elif isinstance(tier, PointTier): pointTierNameList.append(tierName) # Merge the interval tiers intervalTier = None if len(intervalTierNameList) > 0: intervalTier = self.tierDict[intervalTierNameList.pop(0)] for tierName in intervalTierNameList: intervalTier = intervalTier.union(self.tierDict[tierName]) # Merge the point tiers pointTier = None if len(pointTierNameList) > 0: pointTier = self.tierDict[pointTierNameList.pop(0)] for tierName in pointTierNameList: pointTier = pointTier.merge(self.tierDict[tierName]) # Create the final textgrid to output tg = Textgrid() if intervalTier is not None: tg.addTier(intervalTier) if pointTier is not None: tg.addTier(pointTier) return tg def new(self): '''Returns a copy of this Textgrid''' return copy.deepcopy(self) def renameTier(self, oldName, newName): oldTier = self.tierDict[oldName] tierIndex = self.tierNameList.index(oldName) self.removeTier(oldName) self.addTier(oldTier.new(newName, oldTier.entryList), tierIndex) def removeTier(self, name): self.tierNameList.pop(self.tierNameList.index(name)) return self.tierDict.pop(name) def replaceTier(self, name, newTier): tierIndex = self.tierNameList.index(name) self.removeTier(name) self.addTier(newTier, tierIndex) def save(self, fn, minimumIntervalLength=MIN_INTERVAL_LENGTH, minTimestamp=None, maxTimestamp=None, useShortForm=True): ''' To save the current textgrid fn - the fullpath filename of the output minimumIntervalLength - any labeled intervals smaller than this will be removed, useful for removing ultrashort or fragmented intervals; if None, don't remove any. 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. useShortForm - if True, save the textgrid as a short textgrid. Otherwise, use the long-form textgrid format. For backwards compatibility, is True by default. ''' for tier in self.tierDict.values(): tier.sort() if minTimestamp == None: minTimestamp = self.minTimestamp if maxTimestamp == None: maxTimestamp = self.maxTimestamp # Fill in the blank spaces for interval tiers for name in self.tierNameList: tier = self.tierDict[name] if isinstance(tier, IntervalTier): tier = _fillInBlanks(tier, "", minTimestamp, maxTimestamp) if minimumIntervalLength is not None: tier = _removeUltrashortIntervals(tier, minimumIntervalLength, minTimestamp) self.tierDict[name] = tier for tier in self.tierDict.values(): tier.sort() outputTxt = "" outputTxt += 'File type = "ooTextFile"\n' outputTxt += 'Object class = "TextGrid"\n\n' if useShortForm == True: # Header outputTxt += "%s\n%s\n" % (numToStr(minTimestamp), numToStr(maxTimestamp)) outputTxt += "<exists>\n%d\n" % len(self.tierNameList) for tierName in self.tierNameList: outputTxt += self.tierDict[tierName].getAsText() else: tab = " " * 4 # Header outputTxt += "xmin = %s \n" % numToStr(minTimestamp) outputTxt += "xmax = %s \n" % numToStr(maxTimestamp) outputTxt += "tiers? <exists> \n" outputTxt += "size = %d \n" % len(self.tierNameList) outputTxt += "item []: \n" for tierNum, tierName in enumerate(self.tierNameList): tier = self.tierDict[tierName] # Interval header outputTxt += tab + "item [%d]:\n" % (tierNum + 1) outputTxt += tab * 2 + 'class = "%s" \n' % tier.tierType outputTxt += tab * 2 + 'name = "%s" \n' % tierName outputTxt += tab * 2 + 'xmin = %s \n' % numToStr(tier.minTimestamp) outputTxt += tab * 2 + 'xmax = %s \n' % numToStr(tier.maxTimestamp) if tier.tierType == INTERVAL_TIER: outputTxt += tab * 2 + 'intervals: size = %d \n' % len(tier.entryList) for intervalNum, entry in enumerate(tier.entryList): start, stop, label = entry outputTxt += tab * 2 + 'intervals [%d]:\n' % (intervalNum + 1) outputTxt += tab * 3 + 'xmin = %s \n' % numToStr(start) outputTxt += tab * 3 + 'xmax = %s \n' % numToStr(stop) outputTxt += tab * 3 + 'text = "%s" \n' % label else: outputTxt += tab * 2 + 'points: size = %d\n ' % len(tier.entryList) for pointNum, entry in enumerate(tier.entryList): timestamp, label = entry outputTxt += tab * 2 + 'points [%d]:\n' % (pointNum + 1) outputTxt += tab * 3 + 'number = %s \n' % numToStr(timestamp) outputTxt += tab * 3 + 'mark = "%s" \n' % label with io.open(fn, "w", encoding="utf-8") as fd: fd.write(outputTxt)
Subclasses
Methods
def addTier(self, tier, tierIndex=None)
-
Add a tier to this textgrid.
If tierIndex is specified, insert the tier into the specified position.
Expand source code
def addTier(self, tier, tierIndex=None): ''' Add a tier to this textgrid. If tierIndex is specified, insert the tier into the specified position. ''' assert(tier.name not in list(self.tierDict.keys())) if tierIndex is None: self.tierNameList.append(tier.name) else: self.tierNameList.insert(tierIndex, tier.name) self.tierDict[tier.name] = tier minV = tier.minTimestamp if self.minTimestamp is None or minV < self.minTimestamp: self.minTimestamp = minV maxV = tier.maxTimestamp if self.maxTimestamp is None or maxV > self.maxTimestamp: self.maxTimestamp = maxV
def appendTextgrid(self, tg, onlyMatchingNames=True)
-
Append one textgrid to the end of this one
if onlyMatchingNames is False, tiers that don't appear in both textgrids will also appear
Expand source code
def appendTextgrid(self, tg, onlyMatchingNames=True): ''' Append one textgrid to the end of this one if onlyMatchingNames is False, tiers that don't appear in both textgrids will also appear ''' retTG = Textgrid() minTime = self.minTimestamp maxTime = self.maxTimestamp + tg.maxTimestamp # Get all tier names. Ordered first by this textgrid and # then by the other textgrid. combinedTierNameList = self.tierNameList for tierName in tg.tierNameList: if tierName not in combinedTierNameList: combinedTierNameList.append(tierName) # Determine the tier names that will be in the final textgrid finalTierNameList = [] if onlyMatchingNames is False: finalTierNameList = combinedTierNameList else: for tierName in combinedTierNameList: if tierName in self.tierNameList: if tierName in tg.tierNameList: finalTierNameList.append(tierName) # Add tiers from this textgrid for tierName in self.tierNameList: if tierName in finalTierNameList: tier = self.tierDict[tierName] retTG.addTier(tier) # Add tiers from the given textgrid for tierName in tg.tierNameList: if tierName in finalTierNameList: appendTier = tg.tierDict[tierName] appendTier = appendTier.new(minTimestamp=minTime, maxTimestamp=maxTime) appendTier = appendTier.editTimestamps(self.maxTimestamp) if tierName in retTG.tierNameList: tier = retTG.tierDict[tierName] newEntryList = retTG.tierDict[tierName].entryList newEntryList += appendTier.entryList tier = tier.new(entryList=newEntryList, minTimestamp=minTime, maxTimestamp=maxTime) retTG.replaceTier(tierName, tier) else: tier = appendTier tier = tier.new(minTimestamp=minTime, maxTimestamp=maxTime) retTG.addTier(tier) return retTG
def crop(self, cropStart, cropEnd, mode, rebaseToZero)
-
Creates a textgrid where all intervals fit within the crop region
mode = {'strict', 'lax', 'truncated'} If 'strict', only intervals wholly contained by the crop interval will be kept If 'lax', partially contained intervals will be kept If 'truncated', partially contained intervals will be truncated to fit within the crop region.
If rebaseToZero is True, the cropped textgrid values will be subtracted by the cropStart
Expand source code
def crop(self, cropStart, cropEnd, mode, rebaseToZero): ''' Creates a textgrid where all intervals fit within the crop region mode = {'strict', 'lax', 'truncated'} If 'strict', only intervals wholly contained by the crop interval will be kept If 'lax', partially contained intervals will be kept If 'truncated', partially contained intervals will be truncated to fit within the crop region. If rebaseToZero is True, the cropped textgrid values will be subtracted by the cropStart ''' assert(mode in ['strict', 'lax', 'truncated']) newTG = Textgrid() if rebaseToZero is True: minT = 0 maxT = cropEnd - cropStart else: minT = cropStart maxT = cropEnd newTG.minTimestamp = minT newTG.maxTimestamp = maxT for tierName in self.tierNameList: tier = self.tierDict[tierName] newTier = tier.crop(cropStart, cropEnd, mode, rebaseToZero) newTG.addTier(newTier) return newTG
def editTimestamps(self, offset, allowOvershoot=False)
-
Modifies all timestamps by a constant amount
Expand source code
def editTimestamps(self, offset, allowOvershoot=False): ''' Modifies all timestamps by a constant amount ''' tg = Textgrid() for tierName in self.tierNameList: tier = self.tierDict[tierName] if len(tier.entryList) > 0: tier = tier.editTimestamps(offset, allowOvershoot) tg.addTier(tier) return tg
def eraseRegion(self, start, stop, doShrink=True)
-
Makes a region in a tier blank (removes all contained entries)
If 'doShrink' is 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 - stop)
Expand source code
def eraseRegion(self, start, stop, doShrink=True): ''' Makes a region in a tier blank (removes all contained entries) If 'doShrink' is 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 - stop) ''' diff = stop - start maxTimestamp = self.maxTimestamp if doShrink is True: maxTimestamp -= diff newTG = Textgrid() for name in self.tierNameList: tier = self.tierDict[name] tier = tier.eraseRegion(start, stop, 'truncate', doShrink) newTG.addTier(tier) newTG.maxTimestamp = maxTimestamp return newTG
def insertSpace(self, start, duration, collisionCode=None)
-
Inserts a blank region into a textgrid
Every item that occurs after /start/ will be pushed back by /duration/ seconds
collisionCode: in the event that an interval stradles the starting point - '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 - AssertionError is thrown
Expand source code
def insertSpace(self, start, duration, collisionCode=None): ''' Inserts a blank region into a textgrid Every item that occurs after /start/ will be pushed back by /duration/ seconds collisionCode: in the event that an interval stradles the starting point - '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 - AssertionError is thrown ''' newTG = Textgrid() newTG.minTimestamp = self.minTimestamp newTG.maxTimestamp = self.maxTimestamp + duration for tierName in self.tierNameList: tier = self.tierDict[tierName] newTier = tier.insertSpace(start, duration, collisionCode) newTG.addTier(newTier) return newTG
def mergeTiers(self, includeFunc=None, tierList=None, preserveOtherTiers=True)
-
Combine tiers
/includeFunc/ regulates which intervals to include in the merging with all others being tossed (default accepts all)
If /tierList/ is none, combine all tiers.
Expand source code
def mergeTiers(self, includeFunc=None, tierList=None, preserveOtherTiers=True): ''' Combine tiers /includeFunc/ regulates which intervals to include in the merging with all others being tossed (default accepts all) If /tierList/ is none, combine all tiers. ''' if tierList is None: tierList = self.tierNameList if includeFunc is None: includeFunc = lambda entryList: True # Determine the tiers to merge intervalTierNameList = [] pointTierNameList = [] for tierName in tierList: tier = self.tierDict[tierName] if isinstance(tier, IntervalTier): intervalTierNameList.append(tierName) elif isinstance(tier, PointTier): pointTierNameList.append(tierName) # Merge the interval tiers intervalTier = None if len(intervalTierNameList) > 0: intervalTier = self.tierDict[intervalTierNameList.pop(0)] for tierName in intervalTierNameList: intervalTier = intervalTier.union(self.tierDict[tierName]) # Merge the point tiers pointTier = None if len(pointTierNameList) > 0: pointTier = self.tierDict[pointTierNameList.pop(0)] for tierName in pointTierNameList: pointTier = pointTier.merge(self.tierDict[tierName]) # Create the final textgrid to output tg = Textgrid() if intervalTier is not None: tg.addTier(intervalTier) if pointTier is not None: tg.addTier(pointTier) return tg
def new(self)
-
Returns a copy of this Textgrid
Expand source code
def new(self): '''Returns a copy of this Textgrid''' return copy.deepcopy(self)
def removeTier(self, name)
-
Expand source code
def removeTier(self, name): self.tierNameList.pop(self.tierNameList.index(name)) return self.tierDict.pop(name)
def renameTier(self, oldName, newName)
-
Expand source code
def renameTier(self, oldName, newName): oldTier = self.tierDict[oldName] tierIndex = self.tierNameList.index(oldName) self.removeTier(oldName) self.addTier(oldTier.new(newName, oldTier.entryList), tierIndex)
def replaceTier(self, name, newTier)
-
Expand source code
def replaceTier(self, name, newTier): tierIndex = self.tierNameList.index(name) self.removeTier(name) self.addTier(newTier, tierIndex)
def save(self, fn, minimumIntervalLength=1e-08, minTimestamp=None, maxTimestamp=None, useShortForm=True)
-
To save the current textgrid
fn - the fullpath filename of the output minimumIntervalLength - any labeled intervals smaller than this will be removed, useful for removing ultrashort or fragmented intervals; if None, don't remove any. 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. useShortForm - if True, save the textgrid as a short textgrid. Otherwise, use the long-form textgrid format. For backwards compatibility, is True by default.
Expand source code
def save(self, fn, minimumIntervalLength=MIN_INTERVAL_LENGTH, minTimestamp=None, maxTimestamp=None, useShortForm=True): ''' To save the current textgrid fn - the fullpath filename of the output minimumIntervalLength - any labeled intervals smaller than this will be removed, useful for removing ultrashort or fragmented intervals; if None, don't remove any. 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. useShortForm - if True, save the textgrid as a short textgrid. Otherwise, use the long-form textgrid format. For backwards compatibility, is True by default. ''' for tier in self.tierDict.values(): tier.sort() if minTimestamp == None: minTimestamp = self.minTimestamp if maxTimestamp == None: maxTimestamp = self.maxTimestamp # Fill in the blank spaces for interval tiers for name in self.tierNameList: tier = self.tierDict[name] if isinstance(tier, IntervalTier): tier = _fillInBlanks(tier, "", minTimestamp, maxTimestamp) if minimumIntervalLength is not None: tier = _removeUltrashortIntervals(tier, minimumIntervalLength, minTimestamp) self.tierDict[name] = tier for tier in self.tierDict.values(): tier.sort() outputTxt = "" outputTxt += 'File type = "ooTextFile"\n' outputTxt += 'Object class = "TextGrid"\n\n' if useShortForm == True: # Header outputTxt += "%s\n%s\n" % (numToStr(minTimestamp), numToStr(maxTimestamp)) outputTxt += "<exists>\n%d\n" % len(self.tierNameList) for tierName in self.tierNameList: outputTxt += self.tierDict[tierName].getAsText() else: tab = " " * 4 # Header outputTxt += "xmin = %s \n" % numToStr(minTimestamp) outputTxt += "xmax = %s \n" % numToStr(maxTimestamp) outputTxt += "tiers? <exists> \n" outputTxt += "size = %d \n" % len(self.tierNameList) outputTxt += "item []: \n" for tierNum, tierName in enumerate(self.tierNameList): tier = self.tierDict[tierName] # Interval header outputTxt += tab + "item [%d]:\n" % (tierNum + 1) outputTxt += tab * 2 + 'class = "%s" \n' % tier.tierType outputTxt += tab * 2 + 'name = "%s" \n' % tierName outputTxt += tab * 2 + 'xmin = %s \n' % numToStr(tier.minTimestamp) outputTxt += tab * 2 + 'xmax = %s \n' % numToStr(tier.maxTimestamp) if tier.tierType == INTERVAL_TIER: outputTxt += tab * 2 + 'intervals: size = %d \n' % len(tier.entryList) for intervalNum, entry in enumerate(tier.entryList): start, stop, label = entry outputTxt += tab * 2 + 'intervals [%d]:\n' % (intervalNum + 1) outputTxt += tab * 3 + 'xmin = %s \n' % numToStr(start) outputTxt += tab * 3 + 'xmax = %s \n' % numToStr(stop) outputTxt += tab * 3 + 'text = "%s" \n' % label else: outputTxt += tab * 2 + 'points: size = %d\n ' % len(tier.entryList) for pointNum, entry in enumerate(tier.entryList): timestamp, label = entry outputTxt += tab * 2 + 'points [%d]:\n' % (pointNum + 1) outputTxt += tab * 3 + 'number = %s \n' % numToStr(timestamp) outputTxt += tab * 3 + 'mark = "%s" \n' % label with io.open(fn, "w", encoding="utf-8") as fd: fd.write(outputTxt)
class TextgridCollisionException (tierName, insertInterval, collisionList)
-
Common base class for all non-exit exceptions.
Expand source code
class TextgridCollisionException(Exception): def __init__(self, tierName, insertInterval, collisionList): super(TextgridCollisionException, self).__init__() self.tierName = tierName self.insertInterval = insertInterval self.collisionList = collisionList def __str__(self): dataTuple = (str(self.insertInterval), self.tierName, str(self.collisionList)) return ("Attempted to insert interval %s into tier %s of textgrid" + "but overlapping entries %s already exist" % dataTuple)
Ancestors
- builtins.Exception
- builtins.BaseException
class TextgridTier (name, entryList, minT, maxT, pairedWav=None)
-
See PointTier or IntervalTier
Expand source code
class TextgridTier(object): tierType = None entryType = Interval def __init__(self, name, entryList, minT, maxT, pairedWav=None): '''See PointTier or IntervalTier''' entryList.sort() self.name = name self.entryList = entryList self.minTimestamp = minT self.maxTimestamp = maxT def __eq__(self, other): isEqual = True isEqual &= self.name == other.name isEqual &= _isclose(self.minTimestamp, other.minTimestamp) isEqual &= _isclose(self.maxTimestamp, other.maxTimestamp) isEqual &= len(self.entryList) == len(self.entryList) if isEqual: for selfEntry, otherEntry in zip(self.entryList, other.entryList): for selfSubEntry, otherSubEntry in zip(selfEntry, otherEntry): try: isEqual &= _isclose(selfSubEntry, otherSubEntry) except TypeError: isEqual &= selfSubEntry == otherSubEntry return isEqual def appendTier(self, tier): ''' Append a tier to the end of this one. This tier's maxtimestamp will be lengthened by the amount in the passed in tier. ''' minTime = self.minTimestamp if tier.minTimestamp < minTime: minTime = tier.minTimestamp maxTime = self.maxTimestamp + tier.maxTimestamp appendTier = tier.editTimestamps(self.maxTimestamp, allowOvershoot=True) assert(self.tierType == tier.tierType) entryList = self.entryList + appendTier.entryList entryList.sort() return self.new(self.name, entryList, minTimestamp=minTime, maxTimestamp=maxTime) def deleteEntry(self, entry): '''Removes an entry from the entryList''' self.entryList.pop(self.entryList.index(entry)) def find(self, matchLabel, substrMatchFlag=False, usingRE=False): ''' Returns the index of all intervals that match the given label 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 ''' returnList = [] if usingRE is True: for i, entry in enumerate(self.entryList): matchList = re.findall(matchLabel, entry[-1], re.I) if matchList != []: returnList.append(i) else: for i, entry in enumerate(self.entryList): if not substrMatchFlag: if entry[-1] == matchLabel: returnList.append(i) else: if matchLabel in entry[-1]: returnList.append(i) return returnList def getAsText(self): '''Prints each entry in the tier on a separate line w/ timing info''' text = "" text += '"%s"\n' % self.tierType text += '"%s"\n' % self.name text += '%s\n%s\n%s\n' % (numToStr(self.minTimestamp), numToStr(self.maxTimestamp), len(self.entryList)) for entry in self.entryList: entry = [numToStr(val) for val in entry[:-1]] + ['"%s"' % entry[-1], ] try: unicode except NameError: unicodeFunc = str else: unicodeFunc = unicode text += "\n".join([unicodeFunc(val) for val in entry]) + "\n" return text def new(self, name=None, entryList=None, minTimestamp=None, maxTimestamp=None, pairedWav=None): '''Make a new tier derived from the current one''' if name is None: name = self.name if entryList is None: entryList = copy.deepcopy(self.entryList) entryList = [self.entryType(*entry) if isinstance(entry, tuple) else entry for entry in entryList] if minTimestamp is None: minTimestamp = self.minTimestamp if maxTimestamp is None and pairedWav is None: maxTimestamp = self.maxTimestamp return type(self)(name, entryList, minTimestamp, maxTimestamp, pairedWav) def sort(self): '''Sorts the entries in the entryList''' # A list containing tuples and lists will be sorted with tuples # first and then lists. To correctly sort, we need to make # sure that all data structures inside the entry list are # of the same data type. The entry list is sorted whenever # the entry list is modified, so this is probably the best # place to enforce the data type self.entryList = [entry if isinstance(entry, self.entryType) else self.entryType(*entry) for entry in self.entryList] self.entryList.sort() def union(self, tier): ''' 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. ''' retTier = self.new() for entry in tier.entryList: retTier.insertEntry(entry, False, collisionCode='merge') retTier.sort() return retTier
Subclasses
Class variables
var entryType
-
Interval(start, end, label)
var tierType
Methods
def appendTier(self, tier)
-
Append a tier to the end of this one.
This tier's maxtimestamp will be lengthened by the amount in the passed in tier.
Expand source code
def appendTier(self, tier): ''' Append a tier to the end of this one. This tier's maxtimestamp will be lengthened by the amount in the passed in tier. ''' minTime = self.minTimestamp if tier.minTimestamp < minTime: minTime = tier.minTimestamp maxTime = self.maxTimestamp + tier.maxTimestamp appendTier = tier.editTimestamps(self.maxTimestamp, allowOvershoot=True) assert(self.tierType == tier.tierType) entryList = self.entryList + appendTier.entryList entryList.sort() return self.new(self.name, entryList, minTimestamp=minTime, maxTimestamp=maxTime)
def deleteEntry(self, entry)
-
Removes an entry from the entryList
Expand source code
def deleteEntry(self, entry): '''Removes an entry from the entryList''' self.entryList.pop(self.entryList.index(entry))
def find(self, matchLabel, substrMatchFlag=False, usingRE=False)
-
Returns the index of all intervals that match the given label
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
Expand source code
def find(self, matchLabel, substrMatchFlag=False, usingRE=False): ''' Returns the index of all intervals that match the given label 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 ''' returnList = [] if usingRE is True: for i, entry in enumerate(self.entryList): matchList = re.findall(matchLabel, entry[-1], re.I) if matchList != []: returnList.append(i) else: for i, entry in enumerate(self.entryList): if not substrMatchFlag: if entry[-1] == matchLabel: returnList.append(i) else: if matchLabel in entry[-1]: returnList.append(i) return returnList
def getAsText(self)
-
Prints each entry in the tier on a separate line w/ timing info
Expand source code
def getAsText(self): '''Prints each entry in the tier on a separate line w/ timing info''' text = "" text += '"%s"\n' % self.tierType text += '"%s"\n' % self.name text += '%s\n%s\n%s\n' % (numToStr(self.minTimestamp), numToStr(self.maxTimestamp), len(self.entryList)) for entry in self.entryList: entry = [numToStr(val) for val in entry[:-1]] + ['"%s"' % entry[-1], ] try: unicode except NameError: unicodeFunc = str else: unicodeFunc = unicode text += "\n".join([unicodeFunc(val) for val in entry]) + "\n" return text
def new(self, name=None, entryList=None, minTimestamp=None, maxTimestamp=None, pairedWav=None)
-
Make a new tier derived from the current one
Expand source code
def new(self, name=None, entryList=None, minTimestamp=None, maxTimestamp=None, pairedWav=None): '''Make a new tier derived from the current one''' if name is None: name = self.name if entryList is None: entryList = copy.deepcopy(self.entryList) entryList = [self.entryType(*entry) if isinstance(entry, tuple) else entry for entry in entryList] if minTimestamp is None: minTimestamp = self.minTimestamp if maxTimestamp is None and pairedWav is None: maxTimestamp = self.maxTimestamp return type(self)(name, entryList, minTimestamp, maxTimestamp, pairedWav)
def sort(self)
-
Sorts the entries in the entryList
Expand source code
def sort(self): '''Sorts the entries in the entryList''' # A list containing tuples and lists will be sorted with tuples # first and then lists. To correctly sort, we need to make # sure that all data structures inside the entry list are # of the same data type. The entry list is sorted whenever # the entry list is modified, so this is probably the best # place to enforce the data type self.entryList = [entry if isinstance(entry, self.entryType) else self.entryType(*entry) for entry in self.entryList] self.entryList.sort()
def union(self, tier)
-
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.
Expand source code
def union(self, tier): ''' 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. ''' retTier = self.new() for entry in tier.entryList: retTier.insertEntry(entry, False, collisionCode='merge') retTier.sort() return retTier
class TimelessTextgridTierException (...)
-
Common base class for all non-exit exceptions.
Expand source code
class TimelessTextgridTierException(Exception): def __str__(self): return "All textgrid tiers much have a min and max duration"
Ancestors
- builtins.Exception
- builtins.BaseException