Spaces:
Running
Running
Upload 10 files
Browse files- Dockerfile +24 -0
- app.py +1 -0
- asr.py +93 -0
- config.py +10 -0
- diarization.py +11 -0
- main.py +157 -0
- preprocessing.py +41 -0
- requirements.txt +19 -0
- routes.py +85 -0
- test_asr.py +29 -0
Dockerfile
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.9-slim
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
# Sistem bağımlılıklarını yükle
|
6 |
+
RUN apt-get update && apt-get install -y \
|
7 |
+
build-essential \
|
8 |
+
libsndfile1 \
|
9 |
+
ffmpeg \
|
10 |
+
&& rm -rf /var/lib/apt/lists/*
|
11 |
+
|
12 |
+
# Python bağımlılıklarını yükle
|
13 |
+
COPY requirements.txt .
|
14 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
15 |
+
|
16 |
+
# Uygulama kodunu kopyala
|
17 |
+
COPY . /app
|
18 |
+
|
19 |
+
# Ortam değişkenleri
|
20 |
+
ENV PYTHONPATH=/app
|
21 |
+
ENV ENVIRONMENT=production
|
22 |
+
|
23 |
+
# Uygulamayı başlat
|
24 |
+
CMD ["python", "app_ui.py"]
|
app.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
|
asr.py
ADDED
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import whisper
|
3 |
+
from ..config import settings
|
4 |
+
from typing import Dict, Any, Optional
|
5 |
+
import os
|
6 |
+
|
7 |
+
_model = whisper.load_model(settings.ASR_MODEL)
|
8 |
+
|
9 |
+
class MedicalASR:
|
10 |
+
def __init__(self, config: Dict[str, Any]):
|
11 |
+
"""
|
12 |
+
Tıbbi konuşma tanıma için ASR modülü
|
13 |
+
|
14 |
+
Args:
|
15 |
+
config: Yapılandırma parametreleri
|
16 |
+
- language: Dil kodu (örn. "tr")
|
17 |
+
- model: Kullanılacak model adı
|
18 |
+
- domain: Alan adı (tıp için "medical")
|
19 |
+
"""
|
20 |
+
self.config = config
|
21 |
+
self.language = config.get("language", "tr")
|
22 |
+
self.model_name = config.get("model", "whisper-large-v3")
|
23 |
+
|
24 |
+
# CUDA kullanılabilirse tercih et
|
25 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
26 |
+
|
27 |
+
# Modeli yükle
|
28 |
+
self.model = whisper.load_model(self.model_name, device=self.device)
|
29 |
+
|
30 |
+
# Türkçe tıbbi terim sözlüğü - gerçek bir uygulama için genişletilebilir
|
31 |
+
self.medical_terms = self._load_medical_terms()
|
32 |
+
|
33 |
+
def _load_medical_terms(self) -> Dict[str, str]:
|
34 |
+
"""Türkçe tıbbi terim sözlüğünü yükler"""
|
35 |
+
# Örnek: Bu fonksiyon bir dosyadan ya da veritabanından tıbbi terimleri yükleyebilir
|
36 |
+
return {
|
37 |
+
"ateş": "ateş",
|
38 |
+
"hipertansiyon": "hipertansiyon",
|
39 |
+
"miyokard infarktüsü": "miyokard infarktüsü",
|
40 |
+
# ... daha fazla tıbbi terim
|
41 |
+
}
|
42 |
+
|
43 |
+
def transcribe(self, audio_file: str, speaker_diarization: bool = True) -> Dict[str, Any]:
|
44 |
+
"""
|
45 |
+
Ses dosyasını transkribe eder
|
46 |
+
|
47 |
+
Args:
|
48 |
+
audio_file: Ses dosyasının yolu
|
49 |
+
speaker_diarization: Konuşmacı diyarizasyonu yapılsın mı
|
50 |
+
|
51 |
+
Returns:
|
52 |
+
Transkripsiyon sonuçları
|
53 |
+
"""
|
54 |
+
# Transkripsiyon için Whisper modelini kullan
|
55 |
+
transcribe_options = {
|
56 |
+
"language": self.language,
|
57 |
+
"task": "transcribe",
|
58 |
+
}
|
59 |
+
|
60 |
+
result = self.model.transcribe(audio_file, **transcribe_options)
|
61 |
+
|
62 |
+
# Tıbbi terimleri düzelt
|
63 |
+
corrected_text = self._correct_medical_terms(result["text"])
|
64 |
+
result["text"] = corrected_text
|
65 |
+
|
66 |
+
# Diyarizasyon isteniyorsa ekle
|
67 |
+
if speaker_diarization:
|
68 |
+
# Burada bir diyarizasyon kütüphanesi kullanılabilir (pyannote.audio gibi)
|
69 |
+
# Bu örnekte yapmıyoruz
|
70 |
+
pass
|
71 |
+
|
72 |
+
return result
|
73 |
+
|
74 |
+
def _correct_medical_terms(self, text: str) -> str:
|
75 |
+
"""
|
76 |
+
Transkribe edilmiş metindeki tıbbi terimleri düzeltir
|
77 |
+
|
78 |
+
Args:
|
79 |
+
text: Düzeltilecek metin
|
80 |
+
|
81 |
+
Returns:
|
82 |
+
Düzeltilmiş metin
|
83 |
+
"""
|
84 |
+
# Bu basit bir örnek - daha gelişmiş NLP teknikleri kullanılabilir
|
85 |
+
for term, correct_form in self.medical_terms.items():
|
86 |
+
# Basit string değiştirme - gerçek uygulamada daha sofistike olmalı
|
87 |
+
text = text.replace(term, correct_form)
|
88 |
+
|
89 |
+
return text
|
90 |
+
|
91 |
+
def transcribe_file(file_path: str) -> str:
|
92 |
+
result = _model.transcribe(file_path)
|
93 |
+
return result.get('text', '').strip()
|
config.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
class Settings:
|
4 |
+
ASR_MODEL = os.getenv("ASR_MODEL", "openai/whisper-small")
|
5 |
+
UPLOAD_DIR = os.getenv("UPLOAD_DIR", "./data/uploads")
|
6 |
+
OUTPUT_DIR = os.getenv("OUTPUT_DIR", "./data/outputs")
|
7 |
+
SAMPLE_RATE = int(os.getenv("SAMPLE_RATE", 16000))
|
8 |
+
DIAR_MODEL = os.getenv("DIAR_MODEL", "pyannote/speaker-diarization")
|
9 |
+
|
10 |
+
settings = Settings()
|
diarization.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pyannote.audio import Pipeline
|
2 |
+
from ..config import settings
|
3 |
+
|
4 |
+
_diar_pipeline = Pipeline.from_pretrained(settings.DIAR_MODEL)
|
5 |
+
|
6 |
+
def diarize_segments(file_path: str) -> list:
|
7 |
+
diarization = _diar_pipeline(file_path)
|
8 |
+
segments = []
|
9 |
+
for turn, _, speaker in diarization.itertracks(yield_label=True):
|
10 |
+
segments.append((turn.start, turn.end, speaker))
|
11 |
+
return segments
|
main.py
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, Depends, HTTPException, Security, status
|
2 |
+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
4 |
+
from jose import JWTError, jwt
|
5 |
+
from passlib.context import CryptContext
|
6 |
+
from datetime import datetime, timedelta
|
7 |
+
from typing import Optional, Dict, Any
|
8 |
+
import os
|
9 |
+
from pydantic import BaseModel
|
10 |
+
from .routes import router
|
11 |
+
|
12 |
+
# Güvenlik yapılandırması
|
13 |
+
SECRET_KEY = os.environ.get("SECRET_KEY", "güvenli_bir_anahtar_oluşturun")
|
14 |
+
ALGORITHM = "HS256"
|
15 |
+
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
16 |
+
|
17 |
+
# Şifre hashleme
|
18 |
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
19 |
+
|
20 |
+
# Token doğrulama
|
21 |
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
22 |
+
|
23 |
+
# Kullanıcı modeli
|
24 |
+
class User(BaseModel):
|
25 |
+
username: str
|
26 |
+
full_name: str
|
27 |
+
email: str
|
28 |
+
role: str # "admin", "doctor", "specialist"
|
29 |
+
disabled: bool = False
|
30 |
+
|
31 |
+
# Token modeli
|
32 |
+
class Token(BaseModel):
|
33 |
+
access_token: str
|
34 |
+
token_type: str
|
35 |
+
|
36 |
+
# Örnek kullanıcı veritabanı (gerçek uygulamada güvenli bir veritabanı kullanın)
|
37 |
+
fake_users_db = {
|
38 |
+
"doktor": {
|
39 |
+
"username": "doktor",
|
40 |
+
"full_name": "Doktor Kullanıcı",
|
41 |
+
"email": "doktor@example.com",
|
42 |
+
"hashed_password": pwd_context.hash("gizlisifre"),
|
43 |
+
"role": "doctor",
|
44 |
+
"disabled": False
|
45 |
+
},
|
46 |
+
"bölüm_başkanı": {
|
47 |
+
"username": "bölüm_başkanı",
|
48 |
+
"full_name": "Bölüm Başkanı",
|
49 |
+
"email": "bolum@example.com",
|
50 |
+
"hashed_password": pwd_context.hash("gizlisifre2"),
|
51 |
+
"role": "specialist",
|
52 |
+
"disabled": False
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
# Uygulama
|
57 |
+
app = FastAPI(
|
58 |
+
title="Tıbbi Konuşma Transkripsiyon Servisi",
|
59 |
+
description="Doktor viziteleri sırasında konuşmaları transkribe eden ve diyarize eden API",
|
60 |
+
version="0.1.0"
|
61 |
+
)
|
62 |
+
|
63 |
+
# CORS ayarları - sadece güvenilir kaynaklar
|
64 |
+
app.add_middleware(
|
65 |
+
CORSMiddleware,
|
66 |
+
allow_origins=["https://sizin-guvenli-web-siteniz.com"], # Üretimde belirli bir domain listesi kullanın
|
67 |
+
allow_credentials=True,
|
68 |
+
allow_methods=["GET", "POST"],
|
69 |
+
allow_headers=["Authorization", "Content-Type"],
|
70 |
+
)
|
71 |
+
|
72 |
+
# Yetkilendirme fonksiyonları
|
73 |
+
def verify_password(plain_password, hashed_password):
|
74 |
+
return pwd_context.verify(plain_password, hashed_password)
|
75 |
+
|
76 |
+
def get_user(db, username: str):
|
77 |
+
if username in db:
|
78 |
+
user_dict = db[username]
|
79 |
+
return User(**user_dict)
|
80 |
+
|
81 |
+
def authenticate_user(db, username: str, password: str):
|
82 |
+
user = get_user(db, username)
|
83 |
+
if not user:
|
84 |
+
return False
|
85 |
+
if not verify_password(password, db[username]["hashed_password"]):
|
86 |
+
return False
|
87 |
+
return user
|
88 |
+
|
89 |
+
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
90 |
+
to_encode = data.copy()
|
91 |
+
if expires_delta:
|
92 |
+
expire = datetime.utcnow() + expires_delta
|
93 |
+
else:
|
94 |
+
expire = datetime.utcnow() + timedelta(minutes=15)
|
95 |
+
to_encode.update({"exp": expire})
|
96 |
+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
97 |
+
return encoded_jwt
|
98 |
+
|
99 |
+
async def get_current_user(token: str = Depends(oauth2_scheme)):
|
100 |
+
credentials_exception = HTTPException(
|
101 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
102 |
+
detail="Geçersiz kimlik bilgileri",
|
103 |
+
headers={"WWW-Authenticate": "Bearer"},
|
104 |
+
)
|
105 |
+
try:
|
106 |
+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
107 |
+
username: str = payload.get("sub")
|
108 |
+
if username is None:
|
109 |
+
raise credentials_exception
|
110 |
+
except JWTError:
|
111 |
+
raise credentials_exception
|
112 |
+
user = get_user(fake_users_db, username)
|
113 |
+
if user is None:
|
114 |
+
raise credentials_exception
|
115 |
+
return user
|
116 |
+
|
117 |
+
async def get_current_active_user(current_user: User = Depends(get_current_user)):
|
118 |
+
if current_user.disabled:
|
119 |
+
raise HTTPException(status_code=400, detail="Inactive user")
|
120 |
+
return current_user
|
121 |
+
|
122 |
+
# Doktor yetkisi kontrolü
|
123 |
+
def doctor_required(current_user: User = Depends(get_current_active_user)):
|
124 |
+
if current_user.role not in ["doctor", "specialist"]:
|
125 |
+
raise HTTPException(
|
126 |
+
status_code=status.HTTP_403_FORBIDDEN,
|
127 |
+
detail="Bu işlem için doktor yetkisi gereklidir"
|
128 |
+
)
|
129 |
+
return current_user
|
130 |
+
|
131 |
+
# Token endpoint
|
132 |
+
@app.post("/token", response_model=Token)
|
133 |
+
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
134 |
+
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
|
135 |
+
if not user:
|
136 |
+
raise HTTPException(
|
137 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
138 |
+
detail="Kullanıcı adı veya şifre hatalı",
|
139 |
+
headers={"WWW-Authenticate": "Bearer"},
|
140 |
+
)
|
141 |
+
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
142 |
+
access_token = create_access_token(
|
143 |
+
data={"sub": user.username}, expires_delta=access_token_expires
|
144 |
+
)
|
145 |
+
return {"access_token": access_token, "token_type": "bearer"}
|
146 |
+
|
147 |
+
# Türkçe dil desteği yapılandırması
|
148 |
+
app.state.asr_config = {
|
149 |
+
"language": "tr",
|
150 |
+
"model": "whisper-large-v3",
|
151 |
+
"domain": "medical",
|
152 |
+
# Güvenlik ayarları
|
153 |
+
"anonymize_data": True # Varsayılan olarak veri anonimleştirme aktif
|
154 |
+
}
|
155 |
+
|
156 |
+
# Router'ı ekle - doktor yetkisi gerektir
|
157 |
+
app.include_router(router, prefix="/api", dependencies=[Depends(doctor_required)])
|
preprocessing.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from pydub import AudioSegment
|
3 |
+
import noisereduce as nr
|
4 |
+
import webrtcvad
|
5 |
+
from ..config import settings
|
6 |
+
|
7 |
+
def clean_audio(input_path: str) -> str:
|
8 |
+
audio = AudioSegment.from_file(input_path)
|
9 |
+
samples = audio.get_array_of_samples()
|
10 |
+
reduced = nr.reduce_noise(
|
11 |
+
y=samples, sr=settings.SAMPLE_RATE
|
12 |
+
)
|
13 |
+
cleaned = AudioSegment(
|
14 |
+
reduced.tobytes(),
|
15 |
+
frame_rate=settings.SAMPLE_RATE,
|
16 |
+
sample_width=audio.sample_width,
|
17 |
+
channels=audio.channels
|
18 |
+
)
|
19 |
+
vad = webrtcvad.Vad(2)
|
20 |
+
trimmed = _apply_vad(cleaned, vad)
|
21 |
+
clean_path = input_path.replace('.wav', '_clean.wav')
|
22 |
+
trimmed.export(clean_path, format='wav')
|
23 |
+
return clean_path
|
24 |
+
|
25 |
+
def _apply_vad(audio: AudioSegment, vad: webrtcvad.Vad) -> AudioSegment:
|
26 |
+
frame_duration = 30
|
27 |
+
frames = []
|
28 |
+
samples = audio.get_array_of_samples()
|
29 |
+
for i in range(0, len(samples), int(settings.SAMPLE_RATE * frame_duration / 1000)):
|
30 |
+
frame = samples[i:i + int(settings.SAMPLE_RATE * frame_duration / 1000)]
|
31 |
+
is_speech = vad.is_speech(
|
32 |
+
frame.tobytes(), sample_rate=settings.SAMPLE_RATE
|
33 |
+
)
|
34 |
+
if is_speech:
|
35 |
+
frames.extend(frame)
|
36 |
+
return AudioSegment(
|
37 |
+
data=bytes(frames),
|
38 |
+
sample_width=audio.sample_width,
|
39 |
+
frame_rate=settings.SAMPLE_RATE,
|
40 |
+
channels=audio.channels
|
41 |
+
)
|
requirements.txt
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fastapi==0.103.1
|
2 |
+
uvicorn==0.23.2
|
3 |
+
pydantic==2.3.0
|
4 |
+
python-multipart==0.0.6
|
5 |
+
numpy==1.26.0
|
6 |
+
librosa==0.10.1
|
7 |
+
noisereduce==2.0.1
|
8 |
+
soundfile==0.12.1
|
9 |
+
pyannote.audio==3.0.0
|
10 |
+
torch==2.0.1
|
11 |
+
whisper==1.1.10
|
12 |
+
spacy==3.6.1
|
13 |
+
fuzzywuzzy==0.18.0
|
14 |
+
python-Levenshtein==0.21.1
|
15 |
+
python-jose==3.3.0
|
16 |
+
passlib==1.7.4
|
17 |
+
cryptography==41.0.4
|
18 |
+
python-dotenv==1.0.0
|
19 |
+
gradio==3.50.2
|
routes.py
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, UploadFile, File, Depends, HTTPException, Query
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
+
from .services.preprocessing import clean_audio
|
4 |
+
from .services.asr import transcribe_file, MedicalASR
|
5 |
+
from .services.diarization import diarize_segments
|
6 |
+
from .services.privacy import MedicalPrivacyProcessor
|
7 |
+
from .config import settings
|
8 |
+
from typing import Optional
|
9 |
+
import tempfile, os, uuid
|
10 |
+
from fastapi.concurrency import run_in_threadpool
|
11 |
+
|
12 |
+
router = APIRouter()
|
13 |
+
|
14 |
+
# Gizlilik işlemcisi
|
15 |
+
privacy_processor = MedicalPrivacyProcessor()
|
16 |
+
|
17 |
+
def get_asr_model():
|
18 |
+
"""ASR modelini oluşturur ve döndürür"""
|
19 |
+
from .main import app
|
20 |
+
# Ana uygulamadan konfigürasyonu al
|
21 |
+
config = app.state.asr_config
|
22 |
+
return MedicalASR(config)
|
23 |
+
|
24 |
+
@router.post("/transcribe")
|
25 |
+
async def transcribe_audio(
|
26 |
+
file: UploadFile = File(...),
|
27 |
+
diarize: bool = True,
|
28 |
+
enhance_audio: bool = True,
|
29 |
+
anonymize: Optional[bool] = Query(None, description="Kişisel verileri anonimleştir"),
|
30 |
+
asr_model: MedicalASR = Depends(get_asr_model)
|
31 |
+
):
|
32 |
+
"""Ses dosyasını transkribe eder"""
|
33 |
+
try:
|
34 |
+
# Geçici dosya oluştur
|
35 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_file:
|
36 |
+
temp_file.write(await file.read())
|
37 |
+
temp_file_path = temp_file.name
|
38 |
+
|
39 |
+
# CPU/GPU yoğun işlemi thread pool'da çalıştır
|
40 |
+
result = await run_in_threadpool(
|
41 |
+
asr_model.transcribe,
|
42 |
+
temp_file_path,
|
43 |
+
speaker_diarization=diarize,
|
44 |
+
enhance_audio=enhance_audio
|
45 |
+
)
|
46 |
+
|
47 |
+
# Geçici dosyayı temizle
|
48 |
+
os.unlink(temp_file_path)
|
49 |
+
|
50 |
+
# Kişisel veri anonimleştirme
|
51 |
+
# Eğer açıkça belirtilmediyse uygulama konfigürasyonuna göre davran
|
52 |
+
from .main import app
|
53 |
+
should_anonymize = anonymize if anonymize is not None else app.state.asr_config.get("anonymize_data", True)
|
54 |
+
|
55 |
+
identified_data = {}
|
56 |
+
if should_anonymize:
|
57 |
+
# Ana metni anonimleştir
|
58 |
+
anonymized_text, main_data = privacy_processor.anonymize_text(result["text"])
|
59 |
+
result["text"] = anonymized_text
|
60 |
+
identified_data.update(main_data)
|
61 |
+
|
62 |
+
# Diyarizasyon segmentlerini anonimleştir
|
63 |
+
if "diarization" in result:
|
64 |
+
for segment in result["diarization"]:
|
65 |
+
if segment["text"]:
|
66 |
+
anonymized_segment, segment_data = privacy_processor.anonymize_text(segment["text"])
|
67 |
+
segment["text"] = anonymized_segment
|
68 |
+
# Her segment için tespit edilen verileri güncelle
|
69 |
+
for key, values in segment_data.items():
|
70 |
+
identified_data.setdefault(key, []).extend(values)
|
71 |
+
|
72 |
+
# Tespit edilen verileri güvenli şekilde sakla
|
73 |
+
# NOT: Gerçek uygulamada bu verileri şifreli bir veritabanında saklayın
|
74 |
+
session_id = str(uuid.uuid4())
|
75 |
+
# Burada verileri güvenli bir şekilde saklama kodunuz olacak
|
76 |
+
|
77 |
+
# Yanıtta tespit edilen veri tipleri hakkında bilgi ver
|
78 |
+
result["anonymized"] = True
|
79 |
+
result["identified_data_types"] = {k: len(v) for k, v in identified_data.items() if v}
|
80 |
+
result["session_id"] = session_id
|
81 |
+
|
82 |
+
return JSONResponse(content=result)
|
83 |
+
|
84 |
+
except Exception as e:
|
85 |
+
raise HTTPException(status_code=500, detail=f"Transkripsiyon hatası: {str(e)}")
|
test_asr.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from app.services.asr import MedicalASR
|
2 |
+
import os
|
3 |
+
|
4 |
+
# Test yapılandırması
|
5 |
+
config = {
|
6 |
+
"language": "tr",
|
7 |
+
"model": "whisper-base", # Daha hızlı test için daha küçük model
|
8 |
+
"domain": "medical"
|
9 |
+
}
|
10 |
+
|
11 |
+
# MedicalASR sınıfını başlat
|
12 |
+
asr = MedicalASR(config)
|
13 |
+
|
14 |
+
# Test ses dosyası (örnek bir wav dosyası yolu verin)
|
15 |
+
test_file = "test_audio.wav"
|
16 |
+
|
17 |
+
# Transkripsiyon yap
|
18 |
+
result = asr.transcribe(
|
19 |
+
test_file,
|
20 |
+
speaker_diarization=True,
|
21 |
+
enhance_audio=True
|
22 |
+
)
|
23 |
+
|
24 |
+
# Sonuçları yazdır
|
25 |
+
print("Tam Transkripsiyon:")
|
26 |
+
print(result["text"])
|
27 |
+
print("\nKonuşmacı Ayrımı:")
|
28 |
+
for segment in result.get("diarization", []):
|
29 |
+
print(f"{segment['speaker']} ({segment['start']:.1f}s - {segment['end']:.1f}s): {segment['text']}")
|