Skip to content

Commit

Permalink
updated detect_test.py to reflect new changes.
Browse files Browse the repository at this point in the history
refactored two tone detection and long tone detection to be more precise and allow for user variables.
  • Loading branch information
TheGreatCodeholio committed Apr 16, 2024
1 parent b6f889f commit db99ee3
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 50 deletions.
2 changes: 1 addition & 1 deletion detect_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
print("Requires a audio path provided. Either file path, or URL.")
exit(0)

detect_result = tone_detect(audio_path)
detect_result = tone_detect(audio_path, time_resolution_ms=50, debug=True)

if len(detect_result.two_tone_result) == 0 and len(detect_result.long_result) == 0 and len(detect_result.hi_low_result) == 0:
print("No tones")
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "icad_tone_detection"
version = "0.7"
version = "0.8"
authors = [
{name = "TheGreatCodeholio", email = "[email protected]"},
]
Expand Down
15 changes: 9 additions & 6 deletions src/icad_tone_detection/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .audio_loader import load_audio
from .frequency_extraction import FrequencyExtraction
from .tone_detection import detect_quickcall, detect_long_tones, detect_warble_tones
from .tone_detection import detect_two_tone, detect_long_tones, detect_warble_tones


class ToneDetectionResult:
Expand All @@ -10,8 +10,8 @@ def __init__(self, two_tone_result, long_result, hi_low_result):
self.hi_low_result = hi_low_result


def tone_detect(audio_path, matching_threshold=2, time_resolution_ms=100, hi_low_interval=0.2,
hi_low_min_alternations=2, debug=False):
def tone_detect(audio_path, matching_threshold=2, time_resolution_ms=100, tone_a_min_length=0.8, tone_b_min_length=2.8, hi_low_interval=0.2,
hi_low_min_alternations=3, long_tone_min_length=2.0, debug=False):
"""
Loads audio from various sources including local path, URL, BytesIO object, or a PyDub AudioSegment.
Expand All @@ -21,7 +21,10 @@ def tone_detect(audio_path, matching_threshold=2, time_resolution_ms=100, hi_low
are considered a match. For example, a threshold of 2 means that two frequencies are considered matching
if they are within 2% of each other.
- time_resolution_ms (int): The time resolution in milliseconds for the STFT. Default is 100ms.
- hi_low_interval (float): The maximum allowed interval in seconds between two consecutive alternating tones. Default is 0.2
- tone_a_min_length (float): The minimum length in seconds of an A tone for two tone detections. Default 0.8 Seconds
- tone_b_min_length (float): The minimum length in seconds of a B tone for two tone detections. Default 2.8 Seconds
- long_tone_min_length (float): The minimum length a long tone needs to be to consider it a match. Default 2.0 Seconds
- hi_low_interval (float): The maximum allowed interval in seconds between two consecutive alternating tones. Default is 0.2 Seconds
- hi_low_min_alternations (int): The minimum number of alternations for a hi-low warble tone sequence to be considered valid. Default 2
- debug (bool): If debug is enabled, print all tones found in audio file. Default is False
Expand All @@ -39,7 +42,7 @@ def tone_detect(audio_path, matching_threshold=2, time_resolution_ms=100, hi_low
if debug is True:
print("Matched frequencies: ", matched_frequencies)

two_tone_result = detect_quickcall(matched_frequencies)
long_result = detect_long_tones(matched_frequencies, two_tone_result)
two_tone_result = detect_two_tone(matched_frequencies, tone_a_min_length, tone_b_min_length)
long_result = detect_long_tones(matched_frequencies, two_tone_result, long_tone_min_length)
hi_low_result = detect_warble_tones(matched_frequencies, hi_low_interval, hi_low_min_alternations)
return ToneDetectionResult(two_tone_result, long_result, hi_low_result)
116 changes: 74 additions & 42 deletions src/icad_tone_detection/tone_detection.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,86 @@
def detect_quickcall(frequency_matches):
qc2_matches = []
# def detect_quickcall(frequency_matches):
# qc2_matches = []
# tone_id = 0
# last_set = None
# if not frequency_matches or len(frequency_matches) < 1:
# return qc2_matches
# for x in frequency_matches:
# if last_set is None and len(x[2]) >= 8 and 0 not in x[2] and 0.0 not in x[2]:
# last_set = x
# else:
# if len(x[2]) >= 8 and 0 not in x[2] and 0.0 not in x[2]:
# if len(last_set[2]) <= 12 and len(x[2]) >= 28:
# tone_data = {"tone_id": f'qc_{tone_id + 1}', "detected": [last_set[2][0], x[2][0]],
# "start": last_set[0], "end": x[1]}
# tone_id += 1
# qc2_matches.append(tone_data)
# last_set = x
# else:
# last_set = x
#
# return qc2_matches


def detect_two_tone(frequency_matches, min_tone_a_length=0.8, min_tone_b_length=2.8):
two_tone_matches = []
tone_id = 0
last_set = None
if not frequency_matches or len(frequency_matches) < 1:
return qc2_matches
for x in frequency_matches:
if last_set is None and len(x[2]) >= 8 and 0 not in x[2] and 0.0 not in x[2]:
last_set = x
else:
if len(x[2]) >= 8 and 0 not in x[2] and 0.0 not in x[2]:
if len(last_set[2]) <= 12 and len(x[2]) >= 28:
tone_data = {"tone_id": f'qc_{tone_id + 1}', "detected": [last_set[2][0], x[2][0]],
"start": last_set[0], "end": x[1]}
return two_tone_matches

for current_set in frequency_matches:
if all(f > 0 for f in current_set[2]): # Ensure frequencies are non-zero
current_duration = current_set[1] - current_set[0] # Calculate the duration of the current tone

if last_set is None:
last_set = current_set
else:
last_duration = last_set[1] - last_set[0] # Calculate the duration of the last tone
# Check if the last tone is a valid A tone and the current is a valid B tone
if last_duration >= min_tone_a_length and current_duration >= min_tone_b_length:
tone_data = {
"tone_id": f'qc_{tone_id + 1}',
"detected": [last_set[2][0], current_set[2][0]], # Frequency values of A and B tones
"start": last_set[0], # Start time of tone A
"end": current_set[1] # End time of tone B
}
tone_id += 1
qc2_matches.append(tone_data)
last_set = x
else:
last_set = x
two_tone_matches.append(tone_data)
# Update last_set to current_set for next iteration
last_set = current_set

return qc2_matches
return two_tone_matches


def detect_long_tones(frequency_matches, detected_quickcall):
tone_id = 0
def detect_long_tones(frequency_matches, detected_quickcall, min_duration=2.0):
long_tone_matches = []
excluded_frequencies = set([])
excluded_frequencies = set([0.0]) # Initializing with 0.0 Hz to exclude it

if not frequency_matches or len(frequency_matches) < 1:
return long_tone_matches

last_set = frequency_matches[0]
# add detected quick call tones to a list, so we can exclude them from long tone matches.
for ttd in detected_quickcall:
excluded_frequencies.update(ttd["detected"][:2])

for x in frequency_matches:
if len(x[2]) >= 10:
if 12 >= len(last_set) >= 8 and len(x[2]) >= 20:
last_set = x[2]
elif len(x[2]) >= 15:
if x[2][0] == 0 or x[2][0] == 0.0:
continue
if x[2][0] in excluded_frequencies:
continue

if x[2][0] > 250:
tone_data = {"tone_id": f'lt_{tone_id + 1}', "detected": x[2][0], "start": round(x[0], 3),
"end": round(x[1], 3)}
tone_id += 1
long_tone_matches.append(tone_data)
# Add detected quick call tones to the excluded list
for quickcall in detected_quickcall:
excluded_frequencies.update(quickcall["detected"][:2])

for start, end, frequencies in frequency_matches:
duration = end - start
if not frequencies:
continue

current_frequency = frequencies[0]

# Skip the loop iteration if the current frequency is in the excluded frequencies
if current_frequency in excluded_frequencies or current_frequency <= 500:
continue

# Check if the duration meets the minimum requirement
if duration >= min_duration:
tone_data = {
"tone_id": f"lt_{len(long_tone_matches) + 1}",
"detected": current_frequency,
"start": start,
"end": end,
"length": duration
}
long_tone_matches.append(tone_data)

return long_tone_matches

Expand Down

0 comments on commit db99ee3

Please sign in to comment.