Spaces:
Sleeping
Sleeping
| # app.py | |
| import os | |
| from dotenv import load_dotenv | |
| # ----------------------------- | |
| # ÖNEMLİ: HuggingFace / cache ortam değişkenlerini | |
| # importlardan ÖNCE ayarlıyoruz | |
| # ----------------------------- | |
| os.environ["HF_HOME"] = "/app/data/hf_cache" if os.path.exists("/app/data/hf_cache") else "/tmp/hf_cache" | |
| os.environ["TRANSFORMERS_CACHE"] = os.environ["HF_HOME"] | |
| os.environ["BERT_SCORE_CACHE"] = "/tmp/bert_cache" | |
| os.environ["MPLCONFIGDIR"] = "/app/data/matplotlib_config" if os.path.exists("/app/data/matplotlib_config") else "/tmp/matplotlib_config" | |
| # Şimdi dotenv ve diğer importlar | |
| load_dotenv() | |
| from fastapi import FastAPI, File, UploadFile, HTTPException | |
| from fastapi.responses import JSONResponse | |
| from PIL import Image | |
| from ultralytics import YOLO | |
| from groq import Groq | |
| import json | |
| from datetime import datetime | |
| from io import BytesIO | |
| import sacrebleu | |
| from rouge_score import rouge_scorer | |
| from nltk.translate.meteor_score import meteor_score | |
| import nltk | |
| from bert_score import score as bert_score | |
| import matplotlib.pyplot as plt | |
| from functools import lru_cache | |
| from huggingface_hub import HfApi | |
| import unicodedata | |
| # ----------------------------- | |
| # Ortam değişkenleri | |
| # ----------------------------- | |
| GROQ_API_KEY = os.getenv("GROQ_API_KEY") | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| HF_USERNAME = os.getenv("HF_USERNAME") | |
| DATASET_NAME = os.getenv("HF_DATASET_NAME") | |
| BASE_SAVE_DIR = os.getenv("BASE_SAVE_DIR", "/app/data/result2") | |
| # ----------------------------- | |
| # Cache dizinleri | |
| # ----------------------------- | |
| os.makedirs(BASE_SAVE_DIR, exist_ok=True) | |
| os.makedirs(os.environ.get("HF_HOME", "/tmp/hf_cache"), exist_ok=True) | |
| os.makedirs(os.environ.get("BERT_SCORE_CACHE", "/tmp/bert_cache"), exist_ok=True) | |
| os.makedirs(os.environ.get("MPLCONFIGDIR", "/tmp/matplotlib_config"), exist_ok=True) | |
| # NLTK veri yolu | |
| nltk_data_dir = "/tmp/nltk_data" | |
| os.makedirs(nltk_data_dir, exist_ok=True) | |
| nltk.data.path.append(nltk_data_dir) | |
| try: | |
| nltk.data.find("corpora/wordnet") | |
| except LookupError: | |
| nltk.download("wordnet", download_dir=nltk_data_dir, quiet=True) | |
| try: | |
| nltk.data.find("tokenizers/punkt") | |
| except LookupError: | |
| nltk.download("punkt", download_dir=nltk_data_dir, quiet=True) | |
| # ----------------------------- | |
| # FastAPI app | |
| # ----------------------------- | |
| app = FastAPI(title="Hayvan Tespit API") | |
| # ----------------------------- | |
| # YOLO modelleri | |
| # ----------------------------- | |
| def get_surungen_model(): | |
| return YOLO("SurungenBocek_best.pt") | |
| def get_keciler_model(): | |
| return YOLO("keciler_best2.pt") | |
| CONF_THRESHOLD = 0.74 | |
| GROUND_TRUTH_PATH = os.path.join(os.getcwd(), "ground_truth.json") | |
| # ----------------------------- | |
| # Ground truth yükleme ve normalize etme | |
| # ----------------------------- | |
| def normalize_label(label: str) -> str: | |
| """Küçük harf, boşluk temizleme ve Türkçe karakterleri basitleştirme""" | |
| label = label.strip().lower() | |
| # Türkçe karakterleri ascii benzerine çevir | |
| mapping = str.maketrans("çğıöşü", "cgiosu") | |
| label = label.translate(mapping) | |
| label = " ".join(label.split()) | |
| return label | |
| with open(GROUND_TRUTH_PATH, "r", encoding="utf-8") as f: | |
| ground_truth_raw = json.load(f) | |
| ground_truth = {normalize_label(k): v for k, v in ground_truth_raw.items()} | |
| # ----------------------------- | |
| # Hugging Face Dataset upload fonksiyonu | |
| # ----------------------------- | |
| def upload_to_hf(result_json, image: Image.Image, metrics_plot_path=None): | |
| api = HfApi(token=HF_TOKEN) | |
| now = datetime.now() | |
| file_datetime = now.strftime("%Y%m%d_%H%M%S") | |
| safe_label = result_json["detected_animal"].replace(" ", "_") | |
| # JSON upload | |
| json_bytes = json.dumps(result_json, ensure_ascii=False, indent=4).encode("utf-8") | |
| api.upload_file( | |
| path_or_fileobj=BytesIO(json_bytes), | |
| path_in_repo=f"{file_datetime}_{safe_label}.json", | |
| repo_id=f"{HF_USERNAME}/{DATASET_NAME}", | |
| repo_type="dataset", | |
| token=HF_TOKEN | |
| ) | |
| # Görsel upload | |
| img_bytes = BytesIO() | |
| image.save(img_bytes, format="PNG") | |
| img_bytes.seek(0) | |
| api.upload_file( | |
| path_or_fileobj=img_bytes, | |
| path_in_repo=f"{file_datetime}_{safe_label}.png", | |
| repo_id=f"{HF_USERNAME}/{DATASET_NAME}", | |
| repo_type="dataset", | |
| token=HF_TOKEN | |
| ) | |
| # Metrik grafiği upload | |
| if metrics_plot_path and os.path.exists(metrics_plot_path): | |
| with open(metrics_plot_path, "rb") as f: | |
| api.upload_file( | |
| path_or_fileobj=f, | |
| path_in_repo=f"{file_datetime}_{safe_label}_metrics.png", | |
| repo_id=f"{HF_USERNAME}/{DATASET_NAME}", | |
| repo_type="dataset", | |
| token=HF_TOKEN | |
| ) | |
| # Confidence vs BERTScore F1 grafiği upload | |
| conf_bert_plot_path = result_json.get("conf_bert_plot_path") | |
| if conf_bert_plot_path and os.path.exists(conf_bert_plot_path): | |
| with open(conf_bert_plot_path, "rb") as f: | |
| api.upload_file( | |
| path_or_fileobj=f, | |
| path_in_repo=f"{file_datetime}_{safe_label}_confidence_vs_bertf1.png", | |
| repo_id=f"{HF_USERNAME}/{DATASET_NAME}", | |
| repo_type="dataset", | |
| token=HF_TOKEN | |
| ) | |
| # ----------------------------- | |
| # Genel metrik grafiği | |
| # ----------------------------- | |
| def plot_and_save_metrics(scores, save_dir, label): | |
| if not scores or "error" in scores: | |
| return None | |
| metrics = ["BLEU", "ROUGE-1", "ROUGE-2", "ROUGE-L", | |
| "BERTScore Precision", "BERTScore Recall", "BERTScore F1", "METEOR"] | |
| values = [scores.get(m, 0) or 0 for m in metrics] | |
| plt.figure(figsize=(12, 6)) | |
| bars = plt.bar(metrics, values, edgecolor='black') | |
| plt.title(f"Metrik Karşılaştırması – {label}", fontsize=13) | |
| plt.ylabel("Skor (0-1 veya 0-100 BLEU_raw aralığı)", fontsize=11) | |
| plt.ylim(0, max(values) * 1.2 if values else 1) | |
| plt.xticks(rotation=45, ha="right") | |
| plt.grid(axis='y', linestyle='--', alpha=0.6) | |
| for bar, metric in zip(bars, metrics): | |
| val = scores.get(metric, 0) | |
| text = f"{val:.3f}" | |
| if metric == "BLEU": | |
| raw_val = scores.get("BLEU_raw", 0) | |
| text = f"{raw_val:.1f} ({val:.3f})" | |
| plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.02, text, | |
| ha='center', va='bottom', fontsize=9) | |
| plt.tight_layout() | |
| plot_path = os.path.join(save_dir, f"{label}_metrics.png") | |
| plt.savefig(plot_path, dpi=300) | |
| plt.close() | |
| return plot_path | |
| # ----------------------------- | |
| # Confidence & BERTScore F1 grafiği | |
| # ----------------------------- | |
| def plot_confidence_bertf1(confidence, bert_f1, save_dir, label): | |
| try: | |
| plt.figure(figsize=(6, 5)) | |
| metrics = ["Confidence", "BERTScore F1"] | |
| values = [confidence, bert_f1] | |
| bars = plt.bar(metrics, values, color=["#1f77b4", "#ff7f0e"], edgecolor="black") | |
| plt.title(f"Confidence vs. BERTScore F1 – {label}", fontsize=13) | |
| plt.ylabel("Skor (0 - 1)", fontsize=11) | |
| plt.ylim(0, 1) | |
| plt.grid(axis='y', linestyle='--', alpha=0.6) | |
| for bar, val in zip(bars, values): | |
| plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.02, f"{val:.3f}", | |
| ha='center', va='bottom', fontsize=10) | |
| plt.tight_layout() | |
| plot_path = os.path.join(save_dir, f"{label}_confidence_vs_bertf1.png") | |
| plt.savefig(plot_path, dpi=300) | |
| plt.close() | |
| return plot_path | |
| except Exception as e: | |
| print(f"⚠️ Confidence-BERTScore grafiği hatası: {e}") | |
| return None | |
| # ----------------------------- | |
| # Metrik hesaplama helper | |
| # ----------------------------- | |
| def compute_text_metrics(predicted_text: str, reference_text: str): | |
| score_summary = { | |
| "BLEU_raw": 0, | |
| "BLEU": 0, | |
| "ROUGE-1": 0, | |
| "ROUGE-2": 0, | |
| "ROUGE-L": 0, | |
| "BERTScore Precision": 0, | |
| "BERTScore Recall": 0, | |
| "BERTScore F1": 0, | |
| "METEOR": 0 | |
| } | |
| try: | |
| bleu_raw = sacrebleu.corpus_bleu([predicted_text], [[reference_text]], lowercase=True).score | |
| score_summary["BLEU_raw"] = float(bleu_raw) | |
| score_summary["BLEU"] = float(bleu_raw / 100) | |
| except Exception as e: | |
| print(f"BLEU hesaplama hatası: {e}") | |
| try: | |
| rouge = rouge_scorer.RougeScorer(['rouge1','rouge2','rougeL'], use_stemmer=True) | |
| r_scores = rouge.score(reference_text, predicted_text) | |
| score_summary["ROUGE-1"] = float(r_scores['rouge1'].fmeasure) | |
| score_summary["ROUGE-2"] = float(r_scores['rouge2'].fmeasure) | |
| score_summary["ROUGE-L"] = float(r_scores['rougeL'].fmeasure) | |
| except Exception as e: | |
| print(f"ROUGE hesaplama hatası: {e}") | |
| try: | |
| meteor = meteor_score([reference_text.split()], predicted_text.split()) | |
| score_summary["METEOR"] = float(meteor) | |
| except Exception as e: | |
| print(f"METEOR hesaplama hatası: {e}") | |
| try: | |
| if predicted_text.strip() and reference_text.strip(): | |
| P, R, F1 = bert_score( | |
| [predicted_text], | |
| [reference_text], | |
| lang="tr", | |
| model_type="dbmdz/bert-base-turkish-cased", | |
| rescale_with_baseline=True | |
| ) | |
| score_summary["BERTScore Precision"] = float(P.mean()) | |
| score_summary["BERTScore Recall"] = float(R.mean()) | |
| score_summary["BERTScore F1"] = float(F1.mean()) | |
| except Exception as e: | |
| print(f"BERTScore hata: {e}") | |
| return score_summary | |
| # ----------------------------- | |
| # Endpoints | |
| # ----------------------------- | |
| def root(): | |
| return {"status": "ok", "message": "Hayvan Tespit API çalışıyor"} | |
| async def detect_animal(image: UploadFile = File(...)): | |
| try: | |
| img = Image.open(image.file).convert("RGB") | |
| except Exception: | |
| raise HTTPException(status_code=400, detail="Resim açılamadı") | |
| results1 = get_surungen_model().predict(img) | |
| results2 = get_keciler_model().predict(img) | |
| best_conf, best_label = 0, None | |
| for r in [results1[0], results2[0]]: | |
| for box in getattr(r, "boxes", []): | |
| try: | |
| conf = float(box.conf[0].item()) | |
| label = r.names[int(box.cls[0].item())] | |
| except Exception: | |
| continue | |
| if conf > best_conf: | |
| best_conf, best_label = conf, label | |
| if not best_label or best_conf < CONF_THRESHOLD: | |
| return JSONResponse(content={"error": "Hayvan tespit edilemedi"}, status_code=200) | |
| client = Groq(api_key=GROQ_API_KEY) | |
| prompt = f""" | |
| Provide scientific and interesting information about '{best_label}'. | |
| - Explain characteristics, habitat, and behavior in bullet points. | |
| - Include effects on agriculture. | |
| - Include interactions with humans: is it dangerous, aggressive, or harmless? | |
| - Respond in Turkish. | |
| """ | |
| info_answer = client.chat.completions.create( | |
| model="meta-llama/Llama-4-Scout-17B-16E-Instruct", | |
| messages=[{"role": "system", "content": "You are a biology expert."}, | |
| {"role": "user", "content": prompt}], | |
| max_tokens=800, | |
| temperature=0.7 | |
| ).choices[0].message.content.strip() | |
| # Normalize edilmiş label ile ground truth eşleşmesi | |
| label_norm = normalize_label(best_label) | |
| ref_text = ground_truth.get(label_norm) | |
| if not ref_text: | |
| score_summary = {"error": f"'{best_label}' ground_truth içinde yok"} | |
| else: | |
| score_summary = compute_text_metrics(info_answer, ref_text) | |
| save_dir = os.path.join(BASE_SAVE_DIR, f"result_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}") | |
| os.makedirs(save_dir, exist_ok=True) | |
| img_path = os.path.join(save_dir, f"{best_label.replace(' ','_')}.png") | |
| img.save(img_path) | |
| metrics_plot_path = plot_and_save_metrics(score_summary, save_dir, best_label.replace(' ', '_')) | |
| # Yeni grafik: confidence + BERTScore F1 | |
| bert_f1 = score_summary.get("BERTScore F1", 0) | |
| conf_bert_plot_path = plot_confidence_bertf1(best_conf, bert_f1, save_dir, best_label.replace(' ', '_')) | |
| result_json = { | |
| "detected_animal": best_label, | |
| "confidence": best_conf, | |
| "info": info_answer, | |
| "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), | |
| "image_path": img_path, | |
| "scores": score_summary, | |
| "metrics_plot_path": metrics_plot_path, | |
| "conf_bert_plot_path": conf_bert_plot_path | |
| } | |
| try: | |
| upload_to_hf(result_json, img, metrics_plot_path) | |
| except Exception as e: | |
| print(f"⚠️ HF upload hatası: {e}") | |
| return JSONResponse(content=result_json) | |