Spaces:
Running
Running
| 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 --- | |
| 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) | |
| 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] | |
| 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) |