praatio.utilities.my_math

Various math utilities

  1"""
  2Various math utilities
  3"""
  4
  5import math
  6import statistics
  7from typing import Callable, List, Tuple
  8
  9from praatio.utilities import errors
 10
 11
 12def numToStr(inputNum: float) -> str:
 13    if isclose(inputNum, int(inputNum)):
 14        retVal = "%d" % inputNum
 15    else:
 16        retVal = "%s" % repr(inputNum)
 17    return retVal
 18
 19
 20def isclose(a: float, b: float, rel_tol: float = 1e-14, abs_tol: float = 0.0) -> bool:
 21    return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
 22
 23
 24def lessThanOrEqual(a: float, b: float):
 25    return isclose(a, b) or a < b
 26
 27
 28def filterTimeSeriesData(
 29    filterFunc: Callable[[List[float], int, bool], List[float]],
 30    featureTimeList: List[list],
 31    windowSize: int,
 32    index: int,
 33    useEdgePadding: bool,
 34) -> List[list]:
 35    """Filter time-stamped data values within a window
 36
 37    filterFunc could be medianFilter() or znormFilter()
 38
 39    It's ok to have other values in the list. eg
 40    featureTimeList: [(time_0, .., featureA_0, ..),
 41                      (time_1, .., featureA_1, ..),
 42                      ..]
 43    """
 44    featureTimeList = [list(row) for row in featureTimeList]
 45    featValues = [row[index] for row in featureTimeList]
 46    featValues = filterFunc(featValues, windowSize, useEdgePadding)
 47
 48    if len(featureTimeList) != len(featValues):
 49        errors.ArgumentError(
 50            "The length of the time values {len(featureTimeList)} does not "
 51            "match the length of the data values {len(featValues)}"
 52        )
 53    outputList = [
 54        [*piRow[:index], f0Val, *piRow[index + 1 :]]
 55        for piRow, f0Val in zip(featureTimeList, featValues)
 56    ]
 57
 58    return outputList
 59
 60
 61def znormalizeSpeakerData(
 62    featureTimeList: List[Tuple[float, ...]], index: int, filterZeroValues: bool
 63) -> List[Tuple[float, ...]]:
 64    """znormalize time series data
 65
 66    The idea is to normalize each speaker separately to be able
 67    to compare data across several speakers for speaker-dependent
 68    data like pitch range
 69
 70    To normalize a speakers data within a local window, use filterTimeSeriesData()
 71
 72    filterZeroValues: if True, don't consider zero values in the mean and stdDev
 73      (recommended value for data like pitch or intensity, where zero values
 74       are not expected)
 75    """
 76    featValues = [row[index] for row in featureTimeList]
 77
 78    if not filterZeroValues:
 79        featValues = znormalizeData(featValues)
 80    else:
 81        featValuesNoZeroes = [val for val in featValues if val != ""]
 82        meanVal = statistics.mean(featValuesNoZeroes)
 83        stdDevVal = statistics.stdev(featValuesNoZeroes)
 84
 85        featValues = [
 86            (val - meanVal) / stdDevVal if val > 0 else 0 for val in featValues
 87        ]
 88
 89    if len(featureTimeList) != len(featValues):
 90        errors.ArgumentError(
 91            "The length of the time values {len(featureTimeList)} does not "
 92            "match the length of the data values {len(featValues)}"
 93        )
 94    outputList = [
 95        tuple([*piRow[:index], val, *piRow[index + 1 :]])
 96        for piRow, val in zip(featureTimeList, featValues)
 97    ]
 98
 99    return outputList
100
101
102def medianFilter(dist: List[float], window: int, useEdgePadding: bool) -> List[float]:
103    """median filter each value in a dataset; filtering occurs within a given window
104
105    Median filtering is used to "smooth" out extreme values.  It can be useful if
106    your data has lots of quick spikes.  The larger the window, the flatter the output
107    becomes.
108    Given:
109    x = [1 1 1 9 5 2 4 7 4 5 1 5]
110    medianFilter(x, 5, False)
111    >> [1 1 1 2 4 5 4 4 4 5 1 5]
112    """
113    return _stepFilter(statistics.median, dist, window, useEdgePadding)
114
115
116def znormWindowFilter(
117    dist: List[float], window: int, useEdgePadding: bool, filterZeroValues: bool
118) -> List[float]:
119    """z-normalize each value in a dataset; normalization occurs within a given window
120
121    If you suspect that events are sensitive to local changes, (e.g. local changes in pitch
122    are more important absolute differences in pitch) then using windowed
123    znormalization is appropriate.
124
125    See znormalizeData() for more information on znormalization.
126    """
127
128    def znormalizeCenterVal(valList):
129        valToNorm = valList[int(len(valList) / 2.0)]
130        return (valToNorm - statistics.mean(valList)) / statistics.stdev(valList)
131
132    if not filterZeroValues:
133        filteredOutput = _stepFilter(znormalizeCenterVal, dist, window, useEdgePadding)
134    else:
135        zeroIndexList = []
136        nonzeroValList = []
137        for i, val in enumerate(dist):
138            if val > 0.0:
139                nonzeroValList.append(val)
140            else:
141                zeroIndexList.append(i)
142
143        filteredOutput = _stepFilter(
144            znormalizeCenterVal, nonzeroValList, window, useEdgePadding
145        )
146
147        for i in zeroIndexList:
148            filteredOutput.insert(i, 0.0)
149
150    return filteredOutput
151
152
153def _stepFilter(
154    filterFunc, dist: List[float], window: int, useEdgePadding: bool
155) -> List[float]:
156
157    offset = int(math.floor(window / 2.0))
158    length = len(dist)
159
160    returnList = []
161    for x in range(length):
162        dataToFilter = []
163        # If using edge padding or if 0 <= context <= length
164        if useEdgePadding or (((0 <= x - offset) and (x + offset < length))):
165
166            preContext: List[float] = []
167            currentContext = [
168                dist[x],
169            ]
170            postContext = []
171
172            lastKnownLargeIndex = 0
173            for y in range(1, offset + 1):  # 1-based
174                if x + y >= length:
175                    if lastKnownLargeIndex == 0:
176                        largeIndexValue = x
177                    else:
178                        largeIndexValue = lastKnownLargeIndex
179                else:
180                    largeIndexValue = x + y
181                    lastKnownLargeIndex = x + y
182
183                postContext.append(dist[largeIndexValue])
184
185                if x - y < 0:
186                    smallIndexValue = 0
187                else:
188                    smallIndexValue = x - y
189
190                preContext.insert(0, dist[smallIndexValue])
191
192            dataToFilter = preContext + currentContext + postContext
193            value = filterFunc(dataToFilter)
194        else:
195            value = dist[x]
196        returnList.append(value)
197
198    return returnList
199
200
201def znormalizeData(valList: List[float]) -> List[float]:
202    """Given a list of floats, return the z-normalized values of the floats
203
204    The formula is: z(v) = (v - mean) / stdDev
205    In effect, this scales all values to the range [-4, 4].
206    It can be used, for example, to compare the pitch values of different speakers who
207    naturally have different pitch ranges.
208    """
209    valList = valList[:]
210    meanVal = statistics.mean(valList)
211    stdDevVal = statistics.stdev(valList)
212
213    return [(val - meanVal) / stdDevVal for val in valList]
214
215
216def rms(intensityValues: List[float]) -> float:
217    """Return the root mean square for the input set of values"""
218    intensityValues = [val ** 2 for val in intensityValues]
219    meanVal = sum(intensityValues) / len(intensityValues)
220    return math.sqrt(meanVal)
def numToStr(inputNum: float) -> str:
13def numToStr(inputNum: float) -> str:
14    if isclose(inputNum, int(inputNum)):
15        retVal = "%d" % inputNum
16    else:
17        retVal = "%s" % repr(inputNum)
18    return retVal
def isclose(a: float, b: float, rel_tol: float = 1e-14, abs_tol: float = 0.0) -> bool:
21def isclose(a: float, b: float, rel_tol: float = 1e-14, abs_tol: float = 0.0) -> bool:
22    return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
def lessThanOrEqual(a: float, b: float):
25def lessThanOrEqual(a: float, b: float):
26    return isclose(a, b) or a < b
def filterTimeSeriesData( filterFunc: Callable[[List[float], int, bool], List[float]], featureTimeList: List[list], windowSize: int, index: int, useEdgePadding: bool) -> List[list]:
29def filterTimeSeriesData(
30    filterFunc: Callable[[List[float], int, bool], List[float]],
31    featureTimeList: List[list],
32    windowSize: int,
33    index: int,
34    useEdgePadding: bool,
35) -> List[list]:
36    """Filter time-stamped data values within a window
37
38    filterFunc could be medianFilter() or znormFilter()
39
40    It's ok to have other values in the list. eg
41    featureTimeList: [(time_0, .., featureA_0, ..),
42                      (time_1, .., featureA_1, ..),
43                      ..]
44    """
45    featureTimeList = [list(row) for row in featureTimeList]
46    featValues = [row[index] for row in featureTimeList]
47    featValues = filterFunc(featValues, windowSize, useEdgePadding)
48
49    if len(featureTimeList) != len(featValues):
50        errors.ArgumentError(
51            "The length of the time values {len(featureTimeList)} does not "
52            "match the length of the data values {len(featValues)}"
53        )
54    outputList = [
55        [*piRow[:index], f0Val, *piRow[index + 1 :]]
56        for piRow, f0Val in zip(featureTimeList, featValues)
57    ]
58
59    return outputList

Filter time-stamped data values within a window

filterFunc could be medianFilter() or znormFilter()

It's ok to have other values in the list. eg featureTimeList: [(time_0, .., featureA_0, ..), (time_1, .., featureA_1, ..), ..]

def znormalizeSpeakerData( featureTimeList: List[Tuple[float, ...]], index: int, filterZeroValues: bool) -> List[Tuple[float, ...]]:
 62def znormalizeSpeakerData(
 63    featureTimeList: List[Tuple[float, ...]], index: int, filterZeroValues: bool
 64) -> List[Tuple[float, ...]]:
 65    """znormalize time series data
 66
 67    The idea is to normalize each speaker separately to be able
 68    to compare data across several speakers for speaker-dependent
 69    data like pitch range
 70
 71    To normalize a speakers data within a local window, use filterTimeSeriesData()
 72
 73    filterZeroValues: if True, don't consider zero values in the mean and stdDev
 74      (recommended value for data like pitch or intensity, where zero values
 75       are not expected)
 76    """
 77    featValues = [row[index] for row in featureTimeList]
 78
 79    if not filterZeroValues:
 80        featValues = znormalizeData(featValues)
 81    else:
 82        featValuesNoZeroes = [val for val in featValues if val != ""]
 83        meanVal = statistics.mean(featValuesNoZeroes)
 84        stdDevVal = statistics.stdev(featValuesNoZeroes)
 85
 86        featValues = [
 87            (val - meanVal) / stdDevVal if val > 0 else 0 for val in featValues
 88        ]
 89
 90    if len(featureTimeList) != len(featValues):
 91        errors.ArgumentError(
 92            "The length of the time values {len(featureTimeList)} does not "
 93            "match the length of the data values {len(featValues)}"
 94        )
 95    outputList = [
 96        tuple([*piRow[:index], val, *piRow[index + 1 :]])
 97        for piRow, val in zip(featureTimeList, featValues)
 98    ]
 99
100    return outputList

znormalize time series data

The idea is to normalize each speaker separately to be able to compare data across several speakers for speaker-dependent data like pitch range

To normalize a speakers data within a local window, use filterTimeSeriesData()

filterZeroValues: if True, don't consider zero values in the mean and stdDev (recommended value for data like pitch or intensity, where zero values are not expected)

def medianFilter(dist: List[float], window: int, useEdgePadding: bool) -> List[float]:
103def medianFilter(dist: List[float], window: int, useEdgePadding: bool) -> List[float]:
104    """median filter each value in a dataset; filtering occurs within a given window
105
106    Median filtering is used to "smooth" out extreme values.  It can be useful if
107    your data has lots of quick spikes.  The larger the window, the flatter the output
108    becomes.
109    Given:
110    x = [1 1 1 9 5 2 4 7 4 5 1 5]
111    medianFilter(x, 5, False)
112    >> [1 1 1 2 4 5 4 4 4 5 1 5]
113    """
114    return _stepFilter(statistics.median, dist, window, useEdgePadding)

median filter each value in a dataset; filtering occurs within a given window

Median filtering is used to "smooth" out extreme values. It can be useful if your data has lots of quick spikes. The larger the window, the flatter the output becomes. Given: x = [1 1 1 9 5 2 4 7 4 5 1 5] medianFilter(x, 5, False)

[1 1 1 2 4 5 4 4 4 5 1 5]

def znormWindowFilter( dist: List[float], window: int, useEdgePadding: bool, filterZeroValues: bool) -> List[float]:
117def znormWindowFilter(
118    dist: List[float], window: int, useEdgePadding: bool, filterZeroValues: bool
119) -> List[float]:
120    """z-normalize each value in a dataset; normalization occurs within a given window
121
122    If you suspect that events are sensitive to local changes, (e.g. local changes in pitch
123    are more important absolute differences in pitch) then using windowed
124    znormalization is appropriate.
125
126    See znormalizeData() for more information on znormalization.
127    """
128
129    def znormalizeCenterVal(valList):
130        valToNorm = valList[int(len(valList) / 2.0)]
131        return (valToNorm - statistics.mean(valList)) / statistics.stdev(valList)
132
133    if not filterZeroValues:
134        filteredOutput = _stepFilter(znormalizeCenterVal, dist, window, useEdgePadding)
135    else:
136        zeroIndexList = []
137        nonzeroValList = []
138        for i, val in enumerate(dist):
139            if val > 0.0:
140                nonzeroValList.append(val)
141            else:
142                zeroIndexList.append(i)
143
144        filteredOutput = _stepFilter(
145            znormalizeCenterVal, nonzeroValList, window, useEdgePadding
146        )
147
148        for i in zeroIndexList:
149            filteredOutput.insert(i, 0.0)
150
151    return filteredOutput

z-normalize each value in a dataset; normalization occurs within a given window

If you suspect that events are sensitive to local changes, (e.g. local changes in pitch are more important absolute differences in pitch) then using windowed znormalization is appropriate.

See znormalizeData() for more information on znormalization.

def znormalizeData(valList: List[float]) -> List[float]:
202def znormalizeData(valList: List[float]) -> List[float]:
203    """Given a list of floats, return the z-normalized values of the floats
204
205    The formula is: z(v) = (v - mean) / stdDev
206    In effect, this scales all values to the range [-4, 4].
207    It can be used, for example, to compare the pitch values of different speakers who
208    naturally have different pitch ranges.
209    """
210    valList = valList[:]
211    meanVal = statistics.mean(valList)
212    stdDevVal = statistics.stdev(valList)
213
214    return [(val - meanVal) / stdDevVal for val in valList]

Given a list of floats, return the z-normalized values of the floats

The formula is: z(v) = (v - mean) / stdDev In effect, this scales all values to the range [-4, 4]. It can be used, for example, to compare the pitch values of different speakers who naturally have different pitch ranges.

def rms(intensityValues: List[float]) -> float:
217def rms(intensityValues: List[float]) -> float:
218    """Return the root mean square for the input set of values"""
219    intensityValues = [val ** 2 for val in intensityValues]
220    meanVal = sum(intensityValues) / len(intensityValues)
221    return math.sqrt(meanVal)

Return the root mean square for the input set of values