"""Abstract base class and shared types for cry classifiers.""" from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass import numpy as np # ── Label sets ──────────────────────────────────────────────────────────────── LABELS_5CLASS = ["belly_pain", "burping", "discomfort", "hungry", "tired"] LABELS_8CLASS = [ "hungry", "burping", "scared", "belly_pain", "discomfort", "cold_hot", "lonely", "tired", ] LABEL_EMOJI: dict[str, str] = { "belly_pain": "😣", "burping": "🫧", "discomfort": "😖", "hungry": "🍼", "tired": "😴", "scared": "😨", "cold_hot": "🌡️", "lonely": "🥺", "cry": "✅", "not_cry": "❌", } # What each cry label means — shown in the terminal legend LABEL_MEANING: dict[str, str] = { "belly_pain": "Baby has stomach cramps or gas — try gentle tummy massage or bicycle legs", "burping": "Baby needs to burp — hold upright and pat back gently", "discomfort": "General discomfort — check diaper, clothing, temperature, or position", "hungry": "Baby is hungry — time to feed", "tired": "Baby is sleepy or overtired — needs soothing and rest", "scared": "Baby is startled or frightened — comfort and hold close", "cold_hot": "Baby is too cold or too warm — adjust clothing or room temperature", "lonely": "Baby wants attention or closeness — pick up and cuddle", } def display_label(raw: str) -> str: """Return an emoji-prefixed human-friendly label.""" emoji = LABEL_EMOJI.get(raw, "❓") name = raw.replace("_", " ").title() return f"{emoji} {name}" # ── Prediction dataclass ───────────────────────────────────────────────────── @dataclass class CryPrediction: model_name: str label: str # raw label display_label: str # emoji + human name confidence: float # 0.0 – 1.0 latency_ms: float # inference time in ms error: str | None = None # ── Abstract classifier ────────────────────────────────────────────────────── class CryClassifier(ABC): name: str = "unnamed" description: str = "" def __init__(self) -> None: self._loaded = False @abstractmethod def load(self) -> None: """Download weights (if needed) and initialize the model.""" @abstractmethod def predict(self, audio_np: np.ndarray, sr: int) -> CryPrediction: """Run inference on a single audio window and return a prediction.""" def is_loaded(self) -> bool: return self._loaded