Spaces:
Runtime error
Runtime error
#!/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 | |
} | |