praatio.utilities.my_math API documentation

# praatio.utilities.my_math

Various math utilities

View Source

      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: View Source

    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: View Source

    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): View Source

    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]: View Source

    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, ...]]: View Source

     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]: View Source

    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]: View Source

    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]: View Source

    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: View Source

    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