diff --git a/spatial_scene.py b/spatial_scene.py index 0e65325..772cf82 100644 --- a/spatial_scene.py +++ b/spatial_scene.py @@ -168,8 +168,8 @@ class SpatialScene: if now - self._last_anomaly.get(category, 0) < 30.0: return None - # Find the usual direction for this category - usual_angle = self.get_usual_direction(category) + # Find the usual direction for this category (already holding lock) + usual_angle = self._usual_direction_unlocked(category) if usual_angle is None: return None @@ -192,34 +192,36 @@ class SpatialScene: return None + def _usual_direction_unlocked(self, category: str) -> Optional[float]: + """Get the most common direction for a category. Caller must hold self._lock.""" + bins = self.scene_map.get(category) + if not bins: + return None + + total_weight = sum(bins.values()) + if total_weight == 0: + return None + + sin_sum = 0.0 + cos_sum = 0.0 + for bin_idx, count in bins.items(): + angle_rad = math.radians(_bin_center(bin_idx)) + sin_sum += count * math.sin(angle_rad) + cos_sum += count * math.cos(angle_rad) + + return math.degrees(math.atan2(sin_sum, cos_sum)) % 360 + def get_usual_direction(self, category: str) -> Optional[float]: - """Get the most common direction for a sound category (weighted average).""" + """Get the most common direction for a sound category (thread-safe).""" with self._lock: - bins = self.scene_map.get(category) - if not bins: - return None - - # Weighted circular mean - total_weight = sum(bins.values()) - if total_weight == 0: - return None - - sin_sum = 0.0 - cos_sum = 0.0 - for bin_idx, count in bins.items(): - angle_rad = math.radians(_bin_center(bin_idx)) - sin_sum += count * math.sin(angle_rad) - cos_sum += count * math.cos(angle_rad) - - mean_angle = math.degrees(math.atan2(sin_sum, cos_sum)) % 360 - return mean_angle + return self._usual_direction_unlocked(category) def get_scene_summary(self) -> dict: """Get a summary of the learned spatial scene.""" with self._lock: summary = {} for category in sorted(self.scene_map.keys()): - usual = self.get_usual_direction(category) + usual = self._usual_direction_unlocked(category) total = self.category_totals[category] if usual is not None: summary[category] = {