Spaces:
Runtime error
Runtime error
File size: 6,571 Bytes
34acdd0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
#!/usr/bin/env python3
"""
Copyright (c) 2020, Carleton University Biomedical Informatics Collaboratory
This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
"""
from typing import List
import numpy as np
VALID_FREQUENCIES = [125, 250, 500, 750, 1000, 1500, 2000, 3000, 4000, 6000, 8000, 16000]
VALID_THRESHOLDS = list(range(-10, 135, 5))
THRESHOLDS = list(range(-10, 130, 10))
OCTAVE_FREQS_HZ = [125, 250, 500, 1000, 2000, 4000, 8000]
INTEROCTAVE_FREQS_HZ = [750, 1500, 3000, 6000]
OCTAVE_FREQS_KHZ = [0.125, 0.25, 0.5, 1, 2, 4, 8]
INTEROCTAVE_FREQS_KHZ = [0.750, 1.5, 3, 6]
def round_threshold(threshold: float) -> int:
"""Returns the nearest multiple of 5 for the threshold input.
Parameters
----------
threshold : float
The threshold snapped to the nearest multiple of 5 along the y-axis.
Returns
-------
float
A ``snapped`` threshold value.
"""
return VALID_THRESHOLDS[np.argmin([abs(threshold - t) for t in VALID_THRESHOLDS])]
def round_frequency(frequency: float) -> int:
"""Returns the nearest audiologically meaningful frequency.
Parameters
----------
frequency : float
The frequency to be snapped to the nearest clinically meaningful frequency.
Returns
-------
float
A ``snapped`` frequency value.
"""
return VALID_FREQUENCIES[np.argmin([abs(frequency - f) for f in VALID_FREQUENCIES])]
def round_frequency_bone(frequency: float, direction: str, epsilon: float = 0.15) -> int:
"""Returns the nearest audiologically meaningful frequency.
Parameters
----------
frequency : float
The frequency to be snapped to the nearest clinically meaningful frequency.
epsilon: float
Distance (in octaves) below which a frequency is considered to be
exactly on the nearest valid frequency. (default: 0.15 octaves)
direction: str
This parameter will influence the snapping behavior as some
audiologists draw bone conduction symbols next to the target frequency,
while other draw it right on it.
epsilon: float
The frequency will be snapped in to the nearest frequency in the
provided direction, unless the distance to the nearest
frequency is < ε (some small distance (IN OCTAVE UNITS), in which
case the frequency will be snapped to that value.
Eg:
1K 2K 1K 2K
| | | |
| > | will be snapped to > |
| | | |
but if the threshold fell directly (within a very small distance of
1.5K, it would be snapped to that.
1K 2K 1K 1.5K 2K
| | | |
| > | will be snapped to | > |
| | | |
because it is really close to 1.5 and the audiologist likely
intentionally meant to indicate 1.5K rather than 1K.
Note: ε is a tweakable parameter that can be optimized over the
dataset.
Returns
-------
float
A ``snapped`` frequency value.
"""
assert direction == "left" or direction == "right"
distances = [abs(frequency_to_octave(frequency) - frequency_to_octave(f)) for f in VALID_FREQUENCIES]
nearest_frequency_index = np.argmin(distances)
snapped = None
if distances[nearest_frequency_index] < epsilon:
snapped = VALID_FREQUENCIES[nearest_frequency_index]
elif direction == "left":
if VALID_FREQUENCIES[nearest_frequency_index] > frequency:
snapped = VALID_FREQUENCIES[nearest_frequency_index - 1] if nearest_frequency_index > 0 else VALID_FREQUENCIES[nearest_frequency_index]
else:
snapped = VALID_FREQUENCIES[nearest_frequency_index]
else:
if VALID_FREQUENCIES[nearest_frequency_index] > frequency:
snapped = VALID_FREQUENCIES[nearest_frequency_index]
else:
snapped = VALID_FREQUENCIES[nearest_frequency_index + 1] if nearest_frequency_index < len(VALID_FREQUENCIES) - 1 else VALID_FREQUENCIES[nearest_frequency_index]
return snapped
def frequency_to_octave(frequency: float) -> float:
"""Converts a frequency (in Hz) to an octave value (linear units).
By convention, the 0th octave is 125Hz.
Parameters
----------
frequency : float
The frequency (a positive real) to be converted to an octave value.
Returns
-------
float
The octave corresponding to the input frequency.
"""
return np.log(frequency / 125) / np.log(2)
def octave_to_frequency(octave: float) -> float:
"""Converts an octave to its corresponding frequency value (in Hz).
By convention, the 0th octave is 125Hz.
Parameters
----------
octave : float
The octave to put on a frequency scale.
Returns
-------
float
The frequency value corresponding to the octave.
"""
return 125 * 2 ** octave
def stringify_measurement(measurement: dict) -> str:
"""Returns a string describing the measurement type that is compatible
with the NIHL portal format.
eg. An air conduction threshold for the right ear with no masking
would yield the string `AIR_UNMASKED_RIGHT`.
Parameters
----------
measurement: dict
A dictionary describing a threshold. Should have the keys `ear`,
`conduction` and `masking`.
Returns
-------
str
The string describing the measurement type in the NIHL portal format.
"""
masking = "masked" if measurement["masking"] else "unmasked"
return f"{measurement['conduction']}_{masking}_{measurement['ear']}".upper()
def measurement_string_to_dict(measurement_type: str) -> dict:
"""Converts a measurement type string in the NIHL portal format into
a dictionary with the equivalent information for use with the digitizer.
eg. `AIR_UNMASKED_RIGHT` would be equivalent to the dictionary:
{`ear`: `right`, `conduction`: `air`, `masking`: False}
Parameters
----------
measurement: dict
A dictionary describing a threshold. Should have the keys `ear`,
`conduction` and `masking`.
Returns
-------
str
The string describing the measurement type in the NIHL portal format.
"""
return {
"ear": "left" if "LEFT" in measurement_type else "right",
"conduction": "air" if "AIR" in measurement_type else "bone",
"masking": False if "UNMASKED" in measurement_type else True
}
|