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)
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, ..), ..]
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)
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]
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.
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.
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