Seicas commited on
Commit
41979e6
·
verified ·
1 Parent(s): cf27608

Upload 10 files

Browse files
Files changed (10) hide show
  1. Dockerfile +24 -0
  2. app.py +1 -0
  3. asr.py +93 -0
  4. config.py +10 -0
  5. diarization.py +11 -0
  6. main.py +157 -0
  7. preprocessing.py +41 -0
  8. requirements.txt +19 -0
  9. routes.py +85 -0
  10. 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']}")