Add audio labelmap grouping (#24004)

Allow audio classes to be grouped under a shared configured label.

Keep audio overrides separate from object labels and retain only the highest-scoring grouped detection.

Refs #23967
This commit is contained in:
Ersa Oktavian Ramadan
2026-09-12 07:30:04 -06:00
committed by Nicolas Mowen
parent 6ba9dd92e4
commit fd76eb6c6f
9 changed files with 166 additions and 8 deletions
+29 -7
View File
@@ -210,7 +210,11 @@ class AudioEventMaintainer(threading.Thread):
# per-camera stop signal so a single maintainer can be torn down at
# runtime (e.g. on camera removal) without stopping the whole process
self.camera_stop_event = threading.Event()
self.detector = AudioTfl(stop_event, self.camera_config.audio.num_threads)
self.detector = AudioTfl(
stop_event,
self.camera_config.audio.num_threads,
self.camera_config.audio.labelmap,
)
self.shape = (int(round(AUDIO_DURATION * AUDIO_SAMPLE_RATE)),)
self.chunk_size = int(round(AUDIO_DURATION * AUDIO_SAMPLE_RATE * 2))
self.logger = logging.getLogger(f"audio.{self.camera_config.name}")
@@ -392,7 +396,10 @@ class AudioEventMaintainer(threading.Thread):
while not self.stop_event.is_set() and not self.camera_stop_event.is_set():
# check if there is an updated config
self.config_subscriber.check_for_updates()
updated_topics = self.config_subscriber.check_for_updates()
if CameraConfigUpdateEnum.audio.name in updated_topics:
self.detector.update_labelmap(self.camera_config.audio.labelmap)
enabled = self.camera_config.enabled
if enabled != self.was_enabled:
@@ -451,10 +458,17 @@ class AudioEventMaintainer(threading.Thread):
class AudioTfl:
def __init__(self, stop_event: threading.Event, num_threads: int = 2) -> None:
def __init__(
self,
stop_event: threading.Event,
num_threads: int = 2,
labelmap: dict[int, str] | None = None,
) -> None:
self.stop_event = stop_event
self.num_threads = num_threads
self.labels = load_labels("/audio-labelmap.txt", prefill=521)
self._default_labels = load_labels("/audio-labelmap.txt", prefill=521)
self.labels: dict[int, str] = {}
self.update_labelmap(labelmap or {})
# Suppress TFLite delegate creation messages that bypass Python logging
with suppress_stderr_during("tflite_interpreter_init"):
self.interpreter = Interpreter(
@@ -466,6 +480,10 @@ class AudioTfl:
self.tensor_input_details = self.interpreter.get_input_details()
self.tensor_output_details = self.interpreter.get_output_details()
def update_labelmap(self, labelmap: dict[int, str]) -> None:
"""Merge configured label overrides into the default audio labelmap."""
self.labels = {**self._default_labels, **labelmap}
def _detect_raw(self, tensor_input: np.ndarray) -> np.ndarray:
self.interpreter.set_tensor(self.tensor_input_details[0]["index"], tensor_input)
self.interpreter.invoke()
@@ -504,10 +522,14 @@ class AudioTfl:
raw_detections = self._detect_raw(tensor_input)
detected_labels: set[str] = set()
for d in raw_detections:
if d[1] < threshold:
break
detections.append(
(self.labels[int(d[0])], float(d[1]), (d[2], d[3], d[4], d[5]))
)
label = self.labels[int(d[0])]
if label in detected_labels:
continue
detected_labels.add(label)
detections.append((label, float(d[1]), (d[2], d[3], d[4], d[5])))
return detections