babyapi / app.py
SwitchAlpha
fix-typo
73388ad
import os
os.environ["NUMBA_DISABLE_CACHING"] = "1"
import torch
import torch.nn as nn
import numpy as np
from fastapi import FastAPI, File, UploadFile, HTTPException, Form
from fastapi.responses import JSONResponse
from typing import Optional
import io
import torchaudio
import librosa
import random
import json
import logging
from contextlib import asynccontextmanager
# [YENİ EKLEME] Hugging Face Transformers kütüphanelerini içe aktarın
from transformers import AutoFeatureExtractor, AutoModelForAudioClassification
# --- 1. Günlük Yapılandırması ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# --- 2. Yapılandırma Seçenekleri ---
class Config:
CLASS_NAMES = ['belly_pain', 'burping', 'discomfort', 'hungry']
NUM_CLASSES = len(CLASS_NAMES)
MODEL_PATH_ON_HF_SPACE = "cry_model_quantized_final.ptl"
TARGET_SAMPLE_RATE = 22050
AUDIO_DURATION_SECONDS = 6.0
TARGET_AUDIO_LENGTH = int(AUDIO_DURATION_SECONDS * TARGET_SAMPLE_RATE)
N_FFT = 400
HOP_LENGTH = 160
N_MELS = 128
N_MFCC = 120
N_CHROMA = 12
MODEL_ARCHITECTURE = 'vanilla'
MODEL_BASE_CHANNELS = 32
MODEL_DROPOUT_RATE = 0.4
FUSION_DIM = 256
DEVICE = torch.device('cpu')
THRESHOLD_HIGH_CONFIDENCE = 0.49
THRESHOLD_TWO_CATEGORIES = 0.40
NUM_CARE_POINTS = 2
FAILURE_REDIRECT_STATUS_TEXT = "redirect_to_failure"
# [YENİ EKLEME] ESC-50 "Kapı Bekçisi" Modeli için yapılandırma
ESC50_MODEL_NAME = "bioamla/ast-esc50"
ESC50_TARGET_SR = 16000 # Bu model 16kHz örnekleme hızı bekler
CRY_CONFIDENCE_THRESHOLD = 0.5 # Ağlama olarak kabul etmek için minimum güven eşiği
cfg = Config()
# --- 3. Bakım Noktaları "Veritabanı" (Çok Dilli) ---
CARE_POINTS_DB_MULTILANG = {
"tr": {
"belly_pain": """Bebek olabilir: Karnı rahatsız. [Ana Neden] Hava yutma. Beslendikten hemen sonra geğirme, muhtemelen şişkinlikle birlikte; [Bakım Noktası] Besledikten sonra bebeği dik tutun ve 10-15 dakika dik pozisyonda tutarak gazını çıkarın.---Bebek olabilir: Karnı rahatsız. [Ana Neden] Olgunlaşmamış sindirim sistemi. Yenidoğanlarda sık geğirme, özellikle beslendikten sonra; [Bakım Noktası] Düzenli beslenmeyi sürdürün, aşırı beslemekten kaçının; gaz çıkarmasına yardımcı olmak için beslendikten sonra sırtına hafifçe vurun.---Bebek olabilir: Karnı rahatsız. [Ana Neden] Çok hızlı veya çok fazla yemek. Geğirme, huysuzluk, kusma eşliğinde; [Bakım Noktası] Besleme hızını kontrol edin, daha küçük, daha sık öğünler benimseyin; besleme sırasında uygun şekilde duraklayın.---Bebek olabilir: Karnı rahatsız. [Ana Neden] Duygusal değişiklikler. Ağladıktan veya güldükten sonra geğirme; [Bakım Noktası] Bebeğin duygularını yatıştırın, yoğun ağlama veya heyecandan kaçının; beslenmeden önce ve sonra sakin bir ortam sağlayın.---Bebek olabilir: Karnı rahatsız. [AnaNeden] Sıcaklık değişiklikleri. Ani ortam sıcaklığı değişiklerinden veya bebek üşüdüğünde geğirme; [Bakım Noktası] Oda sıcaklığını sabit tutun, bebeğin karnının üşümesini önleyin; sıcak tutmaya dikkat edin.""",
"burping": """Besledikten sonra daima bebeğin gazını çıkarın.---Geğirme pozisyonlarını değiştirin, birden fazla yöntem deneyin.---Çok fazla hava yutmasını önlemek için biberon emziği akış hızının uygun olup olmadığını kontrol edin.---Bir seferde yutulan hava miktarını azaltmak için daha küçük, daha sık öğünler sunun.---Eğer bebek sık sık geğiriyorsa, beslenme alışkanlıklarının ayarlanması gerekebilir.""",
"discomfort": """Bezin ıslak veya kirli olup olmadığını kontrol edin.---Oda sıcaklığının rahat olup olmadığını, bebeğin çok sıcak veya çok soğuk giydirilip giydirilmediğini onaylayın.---Bebeğin kıyafetlerinin çok sıkı olup olmadığını veya rahatsızlık verip vermediğini kontrol edin.---Bebeğin böcekler tarafından ısırılmadığından veya cildinin kaşınmadığından emin olun.---Rahatsızlığı hafifletip hafifletmediğini görmek için bebeğin pozisyonunu değiştirmeyi deneyin.""",
"hungry": """Bebek acıkmış olabilir, lütfen zamanında besleyin.---Son beslenme zamanını kontrol edin, yemek zamanı mı?---Biberon veya emzirmeyi deneyin, bebeğin aranma refleksi gösterip göstermediğine bakın.---Bebeğin doyduğundan emin olun, beslendikten sonra bebeğin tepkisini gözlemleyin.---Eğer bebek kontrolsüzce ağlıyorsa, küçük bir ek beslenme gerekebilir."""
},
"en": {
"belly_pain": """Baby may have: Belly pain. [Main Cause] Swallowing air. Burping immediately after feeding, possibly with bloating; [Care Point] Keep the baby upright after feeding and hold in an upright position for 10-15 minutes to help release gas.---Baby may have: Belly pain. [Main Cause] Immature digestive system. Frequent burping in newborns, especially after feeding; [Care Point] Continue regular feeding, avoid overfeeding; gently pat the back after feeding to help release gas.---Baby may have: Belly pain. [Main Cause] Eating too fast or too much. Burping accompanied by fussiness and vomiting; [Care Point] Control feeding pace, adopt smaller, more frequent meals; pause appropriately during feeding.---Baby may have: Belly pain. [Main Cause] Emotional changes. Burping after crying or laughing; [Care Point] Calm the baby's emotions, avoid intense crying or excitement; provide a calm environment before and after feeding.---Baby may have: Belly pain. [Main Cause] Temperature changes. Burping from sudden ambient temperature changes or when baby gets cold; [Care Point] Keep room temperature stable, prevent baby's belly from getting cold; pay attention to keeping warm.""",
"burping": """Always burp the baby after feeding.---Change burping positions, try multiple methods.---Check if the bottle nipple flow rate is appropriate to prevent swallowing too much air.---Offer smaller, more frequent meals to reduce the amount of air swallowed at once.---If the baby burps frequently, feeding habits may need to be adjusted.""",
"discomfort": """Check if the diaper is wet or dirty.---Confirm if the room temperature is comfortable, whether the baby is dressed too warm or too cold.---Check if the baby's clothes are too tight or causing discomfort.---Make sure the baby has not been bitten by insects or has itchy skin.---Try changing the baby's position to see if it relieves the discomfort.""",
"hungry": """Baby may be hungry, please feed in time.---Check the last feeding time, is it mealtime?---Try bottle or breastfeeding, see if the baby shows rooting reflex.---Make sure the baby is full, observe the baby's response after feeding.---If the baby cries uncontrollably, a small supplementary feeding may be needed."""
},
"de": {
"belly_pain": """Baby könnte haben: Bauchschmerzen. [Hauptursache] Luft schlucken. Aufstoßen unmittelbar nach dem Füttern, möglicherweise mit Blähungen; [Pflegepunkt] Halten Sie das Baby nach dem Füttern aufrecht und halten Sie es 10-15 Minuten in aufrechter Position, um Gas freizusetzen.---Baby könnte haben: Bauchschmerzen. [Hauptursache] Unreifes Verdauungssystem. Häufiges Aufstoßen bei Neugeborenen, besonders nach dem Füttern; [Pflegepunkt] Regelmäßige Fütterung fortsetzen, Überfütterung vermeiden; sanft auf den Rücken klopfen nach dem Füttern, um Gas freizusetzen.---Baby könnte haben: Bauchschmerzen. [Hauptursache] Zu schnell oder zu viel essen. Aufstoßen begleitet von Unruhe und Erbrechen; [Pflegepunkt] Fütterungstempo kontrollieren, kleinere, häufigere Mahlzeiten einführen; während des Fütterns angemessen pausieren.---Baby könnte haben: Bauchschmerzen. [Hauptursache] Emotionale Veränderungen. Aufstoßen nach Weinen oder Lachen; [Pflegepunkt] Babys Emotionen beruhigen, intensives Weinen oder Aufregung vermeiden; ruhige Umgebung vor und nach dem Füttern schaffen.---Baby könnte haben: Bauchschmerzen. [Hauptursache] Temperaturveränderungen. Aufstoßen durch plötzliche Umgebungstemperaturänderungen oder wenn Baby friert; [Pflegepunkt] Raumtemperatur stabil halten, Babys Bauch vor Kälte schützen; auf Warmhalten achten.""",
"burping": """Baby nach dem Füttern immer aufstoßen lassen.---Aufstoßpositionen ändern, mehrere Methoden ausprobieren.---Überprüfen, ob die Flaschensauger-Durchflussrate angemessen ist, um zu verhindern, dass zu viel Luft geschluckt wird.---Kleinere, häufigere Mahlzeiten anbieten, um die auf einmal geschluckte Luftmenge zu reduzieren.---Wenn das Baby häufig aufstößt, müssen möglicherweise Fütterungsgewohnheiten angepasst werden.""",
"discomfort": """Überprüfen, ob die Windel nass oder schmutzig ist.---Bestätigen, ob die Raumtemperatur angenehm ist, ob das Baby zu warm oder zu kalt angezogen ist.---Überprüfen, ob die Kleidung des Babys zu eng ist oder Unbehagen verursacht.---Sicherstellen, dass das Baby nicht von Insekten gebissen wurde oder juckende Haut hat.---Versuchen, die Position des Babys zu ändern, um zu sehen, ob es das Unbehagen lindert.""",
"hungry": """Baby könnte hungrig sein, bitte rechtzeitig füttern.---Letzte Fütterungszeit überprüfen, ist es Essenszeit?---Flasche oder Stillen versuchen, sehen, ob Baby Suchreflex zeigt.---Sicherstellen, dass Baby satt ist, Reaktion des Babys nach dem Füttern beobachten.---Wenn Baby unkontrolliert weint, könnte eine kleine Zusatzfütterung erforderlich sein."""
},
"ru": {
"belly_pain": """У ребенка может быть: Боль в животе. [Основная причина] Заглатывание воздуха. Отрыжка сразу после кормления, возможно с вздутием; [Уход] Держите ребенка в вертикальном положении после кормления в течение 10-15 минут, чтобы помочь выпустить газы.---У ребенка может быть: Боль в животе. [Основная причина] Незрелая пищеварительная система. Частая отрыжка у новорожденных, особенно после кормления; [Уход] Продолжайте регулярное кормление, избегайте перекармливания; осторожно похлопывайте по спине после кормления для выпуска газов.---У ребенка может быть: Боль в животе. [Основная причина] Слишком быстрое или чрезмерное питание. Отрыжка, сопровождаемая беспокойством и рвотой; [Уход] Контролируйте темп кормления, вводите меньшие, более частые приемы пищи; делайте соответствующие паузы во время кормления.---У ребенка может быть: Боль в животе. [Основная причина] Эмоциональные изменения. Отрыжка после плача или смеха; [Уход] Успокойте эмоции ребенка, избегайте интенсивного плача или возбуждения; обеспечьте спокойную обстановку до и после кормления.---У ребенка может быть: Боль в животе. [Основная причина] Изменения температуры. Отрыжка от резких изменений температуры окружающей среды или когда ребенок мерзнет; [Уход] Поддерживайте стабильную температуру в комнате, предотвращайте переохлаждение живота ребенка; обращайте внимание на сохранение тепла.""",
"burping": """Всегда помогайте ребенку отрыгнуть после кормления.---Меняйте позиции для отрыжки, пробуйте несколько методов.---Проверьте, подходит ли скорость потока соски бутылочки, чтобы предотвратить заглатывание слишком большого количества воздуха.---Предлагайте меньшие, более частые приемы пищи, чтобы уменьшить количество проглоченного воздуха за раз.---Если ребенок часто отрыгивает, возможно, необходимо скорректировать привычки кормления.""",
"discomfort": """Проверьте, мокрый или грязный подгузник.---Убедитесь, что температура в комнате комфортная, не одет ли ребенок слишком тепло или холодно.---Проверьте, не слишком ли тесная одежда ребенка или вызывает дискомфорт.---Убедитесь, что ребенка не укусили насекомые или у него нет зудящей кожи.---Попробуйте изменить положение ребенка, чтобы увидеть, облегчает ли это дискомфорт.""",
"hungry": """Ребенок может быть голоден, пожалуйста, покормите вовремя.---Проверьте время последнего кормления, не пора ли кормить?---Попробуйте бутылочку или грудное вскармливание, посмотрите, проявляет ли ребенок поисковый рефлекс.---Убедитесь, что ребенок сыт, наблюдайте за реакцией ребенка после кормления.---Если ребенок плачет неконтролируемо, может потребоваться небольшое дополнительное кормление."""
},
"ko": {
"belly_pain": """아기가 겪을 수 있는 증상: 복통. [주요 원因] 공기 삼킴. 수유 직후 트림, 복부 팽만 가능; [돌봄 요령] 수유 후 아기를 세워서 10-15분간 똑바로 세워 가스 배출을 돕습니다.---아기가 겪을 수 있는 증상: 복통. [주요 원因] 미성숙한 소화 시스템. 신생아의 잦은 트림, 특히 수유 후; [돌봄 요령] 규칙적인 수유를 유지하고 과식을 피하며, 수유 후 등을 부드럽게 두드려 가스 배출을 돕습니다.---아기가 겪을 수 있는 증상: 복통. [주요 원因] 너무 빠르거나 과도한 수유. 트림과 함께 보챔과 구토; [돌봄 요령] 수유 속도를 조절하고 소량씩 자주 먹이며, 수유 중 적절히 멈춥니다.---아기가 겪을 수 있는 증상: 복통. [주요 원因] 감정 변화. 울거나 웃은 후 트림; [돌봄 요령] 아기의 감정을 진정시키고 과도한 울음이나 흥분을 피하며, 수유 전후 차분한 환경을 제공합니다.---아기가 겪을 수 있는 증상: 복통. [주요 원因] 온도 변화. 급격한 주변 온도 변화나 아기가 추울 때 트림; [돌봄 요령] 실내 온도를 안정적으로 유지하고 아기 배가 차가워지지 않도록 하며, 보온에 주의합니다.""",
"burping": """수유 후 항상 아기의 트림을 시킵니다.---트림 자세를 바꾸고 여러 방법을 시도합니다.---젖병 젖꼭지 유속이 적절한지 확인하여 공기를 너무 많이 삼키지 않도록 합니다.---한 번에 삼키는 공기량을 줄이기 위해 소량씩 자주 수유합니다.---아기가 자주 트림을 한다면 수유 습관을 조정해야 할 수 있습니다.""",
"discomfort": """기저귀가 젖었거나 더러운지 확인합니다.---실내 온도가 편안한지, 아기가 너무 덥거나 춥게 입혀졌는지 확인합니다.---아기의 옷이 너무 꽉 끼거나 불편함을 주는지 확인합니다.---아기가 벌레에 물리지 않았는지 또는 피부가 가렵지 않은지 확인합니다.---아기의 자세를 바꿔보고 불편함이 완화되는지 확인합니다.""",
"hungry": """아기가 배고플 수 있으니 제때 수유하세요.---마지막 수유 시간을 확인하고 식사 시간인지 확인합니다.---젖병이나 모유 수유를 시도하고 아기가 빨기 반사를 보이는지 확인합니다.---아기가 배부른지 확인하고 수유 후 아기의 반응을 관찰합니다.---아기가 통제할 수 없이 운다면 소량의 추가 수유가 필요할 수 있습니다."""
}
}
# --- 4. Model Tanımı (Değişmedi) ---
# ... (ResidualBlock ve CryNetMultiBranch sınıflarınız burada, değişiklik yok) ...
# Önceki kodunuzdaki Model Tanımı bölümünü buraya kopyalayın
# (Yorum satırı: Sadelik için model tanımını buraya tekrar eklemedim,
# ama sizin kodunuzda tam olarak burada olmalı)
class ResidualBlock(nn.Module): # Kodu tam hale getirmek için ekliyorum
def __init__(self, in_c, out_c, stride, dr):
super().__init__()
self.conv1 = nn.Conv2d(in_c, out_c, 3, stride, 1, bias=False)
self.bn1 = nn.BatchNorm2d(out_c)
self.relu = nn.ReLU(True)
self.conv2 = nn.Conv2d(out_c, out_c, 3, 1, 1, bias=False)
self.bn2 = nn.BatchNorm2d(out_c)
self.downsample = nn.Sequential()
if stride != 1 or in_c != out_c:
self.downsample = nn.Sequential(
nn.Conv2d(in_c, out_c, 1, stride, bias=False),
nn.BatchNorm2d(out_c)
)
self.dropout = nn.Dropout(dr)
def forward(self, x):
res = self.downsample(x)
x = self.relu(self.bn1(self.conv1(x)))
x = self.bn2(self.conv2(x))
x = self.dropout(x)
return self.relu(x + res)
class CryNetMultiBranch(nn.Module):
def __init__(self, num_classes, config):
super().__init__()
self.config = config
self.mel_encoder = self._create_encoder(1)
self.mfcc_encoder = self._create_encoder(1)
self.chroma_encoder = self._create_encoder(1)
encoder_output_dim = config.MODEL_BASE_CHANNELS * 8 * 3
self.fusion_mlp = nn.Sequential(
nn.Linear(encoder_output_dim, config.FUSION_DIM),
nn.LayerNorm(config.FUSION_DIM),
nn.ReLU(True),
nn.Dropout(config.MODEL_DROPOUT_RATE + 0.1),
nn.Linear(config.FUSION_DIM, num_classes)
)
def _make_layer(self, block, in_c, out_c, blocks, stride, dr):
layers = [block(in_c, out_c, stride, dr)]
[layers.append(block(out_c, out_c, 1, dr)) for _ in range(1, blocks)]
return nn.Sequential(*layers)
def _create_encoder(self, in_channels):
base_channels, dr = self.config.MODEL_BASE_CHANNELS, self.config.MODEL_DROPOUT_RATE
blocks_layer4 = 3 if self.config.MODEL_ARCHITECTURE == 'deeper' else 2
return nn.Sequential(
nn.Conv2d(in_channels, base_channels, 7, 2, 3, bias=False), nn.BatchNorm2d(base_channels),
nn.ReLU(True), nn.MaxPool2d(3, 2, 1),
self._make_layer(ResidualBlock, base_channels, base_channels * 2, 2, 2, dr),
self._make_layer(ResidualBlock, base_channels * 2, base_channels * 4, 2, 2, dr),
self._make_layer(ResidualBlock, base_channels * 4, base_channels * 8, blocks_layer4, 2, dr),
nn.AdaptiveAvgPool2d((1, 1))
)
def forward(self, mel, mfcc, chroma):
features = [self.mel_encoder(mel), self.mfcc_encoder(mfcc), self.chroma_encoder(chroma)]
combined = torch.cat([torch.flatten(f, 1) for f in features], dim=1)
return self.fusion_mlp(combined)
# --- 5. Global Kaynak Başlatma ---
# --- 4. Çok Dilli UI Metinleri ---
UI_TEXTS = {
"tr": {
"title_single": "Bebek muhtemelen şundan ağlıyor: {label}",
"title_double": "Bebek muhtemelen şundan ağlıyor: {label1} veya {label2}",
"subtitle": "Ana nedenler ve bakım noktaları:",
"category_names": {
"belly_pain": "Karın Ağrısı",
"burping": "Geğirme",
"discomfort": "Rahatsızlık",
"hungry": "Açlık"
}
},
"en": {
"title_single": "Baby is probably crying from: {label}",
"title_double": "Baby is probably crying from: {label1} or {label2}",
"subtitle": "Main causes and care points:",
"category_names": {
"belly_pain": "Belly Pain",
"burping": "Burping",
"discomfort": "Discomfort",
"hungry": "Hungry"
}
},
"de": {
"title_single": "Baby weint wahrscheinlich wegen: {label}",
"title_double": "Baby weint wahrscheinlich wegen: {label1} oder {label2}",
"subtitle": "Hauptursachen und Pflegepunkte:",
"category_names": {
"belly_pain": "Bauchschmerzen",
"burping": "Aufstoßen",
"discomfort": "Unbehagen",
"hungry": "Hungrig"
}
},
"ru": {
"title_single": "Ребенок, вероятно, плачет от: {label}",
"title_double": "Ребенок, вероятно, плачет от: {label1} или {label2}",
"subtitle": "Основные причины и рекомендации по уходу:",
"category_names": {
"belly_pain": "Боль в животе",
"burping": "Отрыжка",
"discomfort": "Дискомфорт",
"hungry": "Голод"
}
},
"ko": {
"title_single": "아기가 다음 이유로 울고 있을 가능성이 높습니다: {label}",
"title_double": "아기가 다음 이유로 울고 있을 가능성이 높습니다: {label1} 또는 {label2}",
"subtitle": "주요 원인 및 돌봄 요령:",
"category_names": {
"belly_pain": "복통",
"burping": "트림",
"discomfort": "불편함",
"hungry": "배고픔"
}
}
}
app_globals = {}
# --- 6. Kaynakları lifespan olay yöneticisi ile yükleme ---
@asynccontextmanager
async def lifespan(app: FastAPI):
try:
logging.info("Uygulama başlıyor, kaynaklar yükleniyor...")
# Orijinal modelinizi yükleyin
app_globals["loaded_model"] = torch.jit.load(cfg.MODEL_PATH_ON_HF_SPACE, map_location=cfg.DEVICE)
app_globals["loaded_model"].eval()
logging.info(f"✅ Orijinal Model {cfg.MODEL_PATH_ON_HF_SPACE} başarıyla yüklendi!")
# [YENİ EKLEME] ESC-50 "Kapı Bekçisi" modelini ve çıkarıcıyı yükleyin
app_globals["esc50_model"] = AutoModelForAudioClassification.from_pretrained(cfg.ESC50_MODEL_NAME).to(cfg.DEVICE)
app_globals["esc50_model"].eval()
app_globals["esc50_extractor"] = AutoFeatureExtractor.from_pretrained(cfg.ESC50_MODEL_NAME)
logging.info(f"✅ ESC-50 'Kapı Bekçisi' Modeli {cfg.ESC50_MODEL_NAME} başarıyla yüklendi!")
# Orijinal dönüştürücüleriniz
app_globals["mel_spectrogram_transform"] = torchaudio.transforms.MelSpectrogram(
sample_rate=cfg.TARGET_SAMPLE_RATE, n_fft=cfg.N_FFT, hop_length=cfg.HOP_LENGTH, n_mels=cfg.N_MELS
).to(cfg.DEVICE)
app_globals["mfcc_transform"] = torchaudio.transforms.MFCC(
sample_rate=cfg.TARGET_SAMPLE_RATE, n_mfcc=cfg.N_MFCC, melkwargs={"n_fft": cfg.N_FFT, "hop_length": cfg.HOP_LENGTH}
).to(cfg.DEVICE)
logging.info("✅ Ses dönüştürücüleri başarıyla başlatıldı!")
# Çok dilli bakım noktalarını parse et
app_globals["CARE_POINTS_DB_MULTILANG"] = {}
for lang, categories in CARE_POINTS_DB_MULTILANG.items():
app_globals["CARE_POINTS_DB_MULTILANG"][lang] = {}
for category, raw_text in categories.items():
points = [point.strip() for point in raw_text.strip().split('---') if point.strip()]
app_globals["CARE_POINTS_DB_MULTILANG"][lang][category] = points
logging.info("✅ Çok dilli bakım bilgi bankası başarıyla ayrıştırıldı!")
app_globals["resampler_cache"] = {}
# [YENİ EKLEME] ESC-50 modeli için ayrı bir yeniden örnekleyici önbelleği
app_globals["esc50_resampler_cache"] = {}
logging.info("✅ Yeniden örnekleyici önbellekleri başarıyla başlatıldı!")
except Exception as e:
logging.error(f"❌ Uygulama başlatılamadı: Kaynaklar yüklenirken hata oluştu: {e}", exc_info=True)
yield
logging.info("Uygulama kapanıyor, kaynaklar temizleniyor...")
app_globals.clear()
app = FastAPI(lifespan=lifespan)
@app.get("/")
async def read_root():
return {"message": "Bebek Ağlaması Sınıflandırma API'si çalışıyor! /predict_cry_audio adresine ses dosyası POST edin"}
# [YENİ EKLEME] Yeniden örnekleyicileri almak için yardımcı fonksiyon
def get_resampler(cache, orig_freq, new_freq, device):
if orig_freq not in cache:
cache[orig_freq] = {}
if new_freq not in cache[orig_freq]:
cache[orig_freq][new_freq] = torchaudio.transforms.Resample(
orig_freq=orig_freq, new_freq=new_freq
).to(device)
return cache[orig_freq][new_freq]
@app.post("/predict_cry_audio")
async def predict_cry_audio(file: UploadFile = File(...), language: Optional[str] = Form("tr")):
if not app_globals.get("loaded_model") or not app_globals.get("esc50_model"):
raise HTTPException(status_code=503, detail="Modeller yüklenmedi, hizmet geçici olarak kullanılamıyor.")
# Dil validasyonu
if language not in ["en", "de", "tr", "ru", "ko"]:
language = "tr" # Varsayılan dil
try:
audio_bytes = await file.read()
audio_buffer = io.BytesIO(audio_bytes)
waveform, sample_rate = torchaudio.load(audio_buffer)
waveform = waveform.to(cfg.DEVICE)
# [YENİ EKLEME] Sesi monoya dönüştür (her iki model için de en iyisi)
if waveform.shape[0] > 1:
waveform = torch.mean(waveform, dim=0, keepdim=True)
# --- [YENİ ADIM 1: "KAPI BEKÇİSİ" KONTROLÜ] ---
# Sesi ESC-50 modelinin beklediği 16kHz'e yeniden örnekle
esc_resampler = get_resampler(app_globals["esc50_resampler_cache"], sample_rate, cfg.ESC50_TARGET_SR, cfg.DEVICE)
waveform_16k = esc_resampler(waveform)
# Özellikleri çıkar ve modeli çalıştır
inputs = app_globals["esc50_extractor"](
waveform_16k.cpu().numpy().squeeze(),
sampling_rate=cfg.ESC50_TARGET_SR,
return_tensors="pt"
).to(cfg.DEVICE)
with torch.no_grad():
logits = app_globals["esc50_model"](**inputs).logits
probs = logits.softmax(dim=-1)
top_prob, top_class_idx = torch.max(probs, dim=-1)
top_class_name = app_globals["esc50_model"].config.id2label[top_class_idx.item()]
top_prob_val = top_prob.item()
logging.info(f"ESC-50 'Kapı Bekçisi' sonucu: {top_class_name} (Güven: {top_prob_val:.4f})")
# --- [YENİ ADIM 2: KONTROL VE YÖNLENDİRME] ---
if top_class_name != "crying_baby" or top_prob_val < cfg.CRY_CONFIDENCE_THRESHOLD:
# Bu bir bebek ağlaması değil veya güven çok düşük
return JSONResponse(content={
"status": "no_cry_detected",
"message": "AI, yüklenen seste bir bebek ağlaması tespit etmedi.",
"detected_sound": top_class_name,
"confidence": top_prob_val
})
# --- [ORİJİNAL ADIM: AĞLAMA TİPİ SINIFLANDIRMASI] ---
# (Sadece 'crying_baby' tespit edildiyse bu bölüm çalışır)
logging.info("Bebek ağlaması doğrulandı. Ağlama tipi sınıflandırılıyor...")
# Orijinal modeliniz için 22050Hz'e yeniden örnekleyin
resampler_cache = app_globals["resampler_cache"]
if sample_rate != cfg.TARGET_SAMPLE_RATE:
# [GÜNCELLEME] get_resampler fonksiyonunu kullan
resampler = get_resampler(resampler_cache, sample_rate, cfg.TARGET_SAMPLE_RATE, cfg.DEVICE)
waveform_22k = resampler(waveform)
else:
waveform_22k = waveform # Zaten doğru örnekleme hızında
# Orijinal dolgu/kırpma işleminiz
if waveform_22k.shape[1] < cfg.TARGET_AUDIO_LENGTH:
waveform_22k = torch.nn.functional.pad(waveform_22k, (0, cfg.TARGET_AUDIO_LENGTH - waveform_22k.shape[1]))
else:
waveform_22k = waveform_22k[:, :cfg.TARGET_AUDIO_LENGTH]
# Orijinal özellik çıkarımlarınız (waveform_22k kullanarak)
mel_spec = app_globals["mel_spectrogram_transform"](waveform_22k).unsqueeze(0)
mfcc = app_globals["mfcc_transform"](waveform_22k).unsqueeze(0)
waveform_numpy = waveform_22k.cpu().numpy()[0]
chroma_numpy = librosa.feature.chroma_stft(
y=waveform_numpy, sr=cfg.TARGET_SAMPLE_RATE, n_fft=cfg.N_FFT,
hop_length=cfg.HOP_LENGTH, n_chroma=cfg.N_CHROMA
)
chroma = torch.from_numpy(chroma_numpy).to(cfg.DEVICE)
with torch.no_grad():
if chroma.dim() == 2:
chroma = chroma.unsqueeze(0).unsqueeze(0)
# Orijinal modelinizi çalıştırın
probabilities = app_globals["loaded_model"](mel_spec, mfcc, chroma)
probabilities_list = probabilities.squeeze().tolist()
confidences_data = [{"label": cfg.CLASS_NAMES[i], "score": round(score, 4)} for i, score in enumerate(probabilities_list)]
sorted_confidences = sorted(confidences_data, key=lambda x: x['score'], reverse=True)
top1_label = sorted_confidences[0]['label']
top1_score = sorted_confidences[0]['score']
except Exception as e:
logging.error(f"Ses dosyası işlenirken kritik hata: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Ses dosyası işlenemedi, lütfen tekrar deneyin veya dosya formatını kontrol edin.")
# Orijinal JSON yanıt mantığınız (şimdi dile göre bakım noktaları kullanıyor)
response_data = {}
CARE_POINTS_DB = app_globals["CARE_POINTS_DB_MULTILANG"].get(language, app_globals["CARE_POINTS_DB_MULTILANG"]["tr"])
ui_texts = UI_TEXTS.get(language, UI_TEXTS["tr"])
if top1_score > cfg.THRESHOLD_HIGH_CONFIDENCE:
care_points = CARE_POINTS_DB.get(top1_label, [])
selected_points = random.sample(care_points, min(len(care_points), cfg.NUM_CARE_POINTS))
top1_label_translated = ui_texts["category_names"].get(top1_label, top1_label)
response_data = {
"status": "success_single",
"title": ui_texts["title_single"].format(label=top1_label_translated),
"subtitle": ui_texts["subtitle"],
"care_points": selected_points,
"promo_text": "Daha detaylı rehberlik için üyelik satın alabilirsiniz",
"all_scores": sorted_confidences
}
elif len(sorted_confidences) > 1 and sorted_confidences[1]['score'] >= cfg.THRESHOLD_TWO_CATEGORIES:
top2_label = sorted_confidences[1]['label']
combined_points = []
care_points_1 = CARE_POINTS_DB.get(top1_label, [])
if care_points_1: combined_points.append(random.choice(care_points_1))
care_points_2 = CARE_POINTS_DB.get(top2_label, [])
if care_points_2: combined_points.append(random.choice(care_points_2))
top1_label_translated = ui_texts["category_names"].get(top1_label, top1_label)
top2_label_translated = ui_texts["category_names"].get(top2_label, top2_label)
response_data = {
"status": "success_multiple",
"title": ui_texts["title_double"].format(label1=top1_label_translated, label2=top2_label_translated),
"subtitle": ui_texts["subtitle"],
"care_points": combined_points,
"promo_text": "Daha detaylı rehberlik için üyelik satın alabilirsiniz",
"all_scores": sorted_confidences
}
else:
response_data = {
"status": cfg.FAILURE_REDIRECT_STATUS_TEXT,
"message": "AI net bir neden belirleyemedi, sizin için genel yatıştırma yönergeleri hazırladık.",
"all_scores": sorted_confidences
}
return JSONResponse(content=response_data)
# --- 7. Uygulama Başlatıcı ---
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)