hedrekao
HF deploy: clean snapshot without local artifacts
a361db3
Raw
History Blame Contribute Delete
4.85 kB
"""Talker-of-interest (ToI) selection heuristics."""
import logging
from typing import List, Tuple
from .data_structures import TalkerInfo
log = logging.getLogger(__name__)
def select_talker_of_interest(
talkers: List[TalkerInfo],
weights: dict = None,
) -> Tuple[int, TalkerInfo]:
"""
Select the most likely talker of interest using weighted scoring.
Heuristics (in order of importance if using defaults):
1. Front-facing direction (within 45° of 0°) - hearing aid users typically face target
2. English language - contextual heuristic
3. Energy/loudness - louder speakers are more salient
4. Gender clarity - non-ambiguous gender increases confidence
Args:
talkers: List of TalkerInfo objects
weights: Optional dict with keys: 'front_bonus', 'english_bonus',
'energy_weight', 'gender_bonus'
Returns:
Tuple of (selected_index, selected_talker_info)
"""
if not talkers:
raise ValueError("No talkers to select from")
# Default weights
default_weights = {
'front_bonus': 10.0, # strongest factor
'english_bonus': 5.0, # language bonus
'energy_weight': 5.0, # energy multiplier
'gender_bonus': 0.5, # small bonus for clear gender
}
if weights:
default_weights.update(weights)
w = default_weights
best_idx = 0
best_score = -float('inf')
scores = []
for i, talker in enumerate(talkers):
score = 0.0
score_components = {}
# Front-facing bonus
if talker.direction_deg is not None:
angle = talker.direction_deg
angle_from_front = min(angle, 360 - angle)
if angle_from_front < 45: # Within front hemisphere
score += w['front_bonus']
score_components['front'] = w['front_bonus']
# English language bonus
if talker.language == 'en':
score += w['english_bonus']
score_components['english'] = w['english_bonus']
# Energy bonus
if talker.energy is not None:
score += talker.energy * w['energy_weight']
score_components['energy'] = talker.energy * w['energy_weight']
# Gender clarity bonus
if talker.gender and talker.gender != 'ambiguous' and talker.gender != 'unknown':
score += w['gender_bonus']
score_components['gender'] = w['gender_bonus']
# Store score for later reference
talker.selection_score = round(score, 2)
scores.append((i, score, score_components))
log.debug(f"Speaker {i+1}: score={score:.2f}, components={score_components}")
if score > best_score:
best_score = score
best_idx = i
selected_talker = talkers[best_idx]
selected_talker.is_toi = True
# Build reasoning string
reasons = []
best_components = scores[best_idx][2]
if 'front' in best_components:
reasons.append("Front-facing")
if 'english' in best_components:
reasons.append("English speaker")
if 'energy' in best_components and best_components['energy'] > 0:
reasons.append(f"High energy ({selected_talker.energy:.3f})")
if 'gender' in best_components:
reasons.append(f"Clear {selected_talker.gender}")
selected_talker.toi_reason = "; ".join(reasons) if reasons else "Default selection"
log.info(f"Selected talker: Speaker {best_idx + 1} (score: {best_score:.2f})")
log.info(f" Reason: {selected_talker.toi_reason}")
return best_idx, selected_talker
def select_by_energy(talkers: List[TalkerInfo]) -> Tuple[int, TalkerInfo]:
"""Simple heuristic: select talker with highest energy."""
best_idx = 0
best_energy = -float('inf')
for i, talker in enumerate(talkers):
if talker.energy is not None and talker.energy > best_energy:
best_energy = talker.energy
best_idx = i
talkers[best_idx].is_toi = True
talkers[best_idx].toi_reason = f"Highest energy ({best_energy:.3f})"
return best_idx, talkers[best_idx]
def select_by_language(talkers: List[TalkerInfo], target_language: str = 'en') -> Tuple[int, TalkerInfo]:
"""Select first talker with target language, fallback to speaker 0."""
for i, talker in enumerate(talkers):
if talker.language == target_language:
talkers[i].is_toi = True
talkers[i].toi_reason = f"{target_language.upper()} speaker"
return i, talker
# Fallback to first speaker
talkers[0].is_toi = True
talkers[0].toi_reason = f"No {target_language.upper()} speaker found, defaulting to speaker 1"
return 0, talkers[0]