Spaces:
Sleeping
Sleeping
Download utils/inference.py from aryarasyad/GLPFish_old: direct link, hf CLI and curl.
- Browser
- Download file 5.16 kB
-
https://huggingface.co/spaces/aryarasyad/GLPFish_old/resolve/main/utils/inference.py
- Command line
-
hf download hf://spaces/aryarasyad/GLPFish_old/utils/inference.py
-
curl -L -o inference.py https://huggingface.co/spaces/aryarasyad/GLPFish_old/resolve/main/utils/inference.py
5.16 kB
| """ | |
| Inference utilities - Fish Freshness Classifier | |
| """ | |
| import os | |
| import json | |
| import torch | |
| import torch.nn as nn | |
| import numpy as np | |
| from PIL import Image | |
| from torchvision import transforms, models | |
| # ββ Constants ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| IMG_SIZE = 224 | |
| NUM_CLASSES = 4 | |
| MODEL_PATH = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "models", "best_model_fx.pth")) | |
| CLASS_JSON = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "models", "class_names.json")) | |
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| GRADE_RULES = { | |
| # (eye_result, gill_result) -> grade | |
| ("fresh", "fresh"): ("A", "Sangat Segar", "#22c55e"), | |
| ("fresh", "non-fresh"): ("B", "Cukup Segar", "#f59e0b"), | |
| ("non-fresh", "fresh"): ("B", "Cukup Segar", "#f59e0b"), | |
| ("non-fresh", "non-fresh"): ("C", "Tidak Segar", "#ef4444"), | |
| } | |
| GRADE_DESC = { | |
| "A": "Ikan sangat segar, layak ekspor premium. Mata jernih dan insang merah cerah.", | |
| "B": "Ikan cukup segar, layak ekspor standar. Salah satu indikator menunjukkan penurunan kesegaran.", | |
| "C": "Ikan tidak segar, tidak memenuhi syarat ekspor. Mata dan insang menunjukkan tanda tidak segar.", | |
| } | |
| # ββ Transform βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| infer_transform = transforms.Compose([ | |
| transforms.Resize((IMG_SIZE, IMG_SIZE)), | |
| transforms.ToTensor(), | |
| transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), | |
| ]) | |
| # ββ Model loader (singleton cache) βββββββββββββββββββββββββββββββββββββββββββ | |
| _model_cache = None | |
| _classes_cache = None | |
| def load_model(): | |
| global _model_cache, _classes_cache | |
| if _model_cache is not None: | |
| return _model_cache, _classes_cache | |
| if not os.path.exists(MODEL_PATH): | |
| raise FileNotFoundError( | |
| f"Model tidak ditemukan di: {MODEL_PATH}\n" | |
| "Jalankan train.py terlebih dahulu." | |
| ) | |
| # Load class names | |
| with open(CLASS_JSON) as f: | |
| classes = json.load(f) | |
| # Build model | |
| model = models.mobilenet_v3_large(weights=None) # Ganti dengan DEFAULT jika ada akses internet | |
| in_features = model.classifier[3].in_features | |
| model.classifier[3] = nn.Sequential( | |
| nn.Dropout(p=0.4), | |
| nn.Linear(in_features, NUM_CLASSES), | |
| ) | |
| model.load_state_dict(torch.load(MODEL_PATH, map_location=DEVICE)) | |
| model.to(DEVICE) | |
| model.eval() | |
| _model_cache = model | |
| _classes_cache = classes | |
| return model, classes | |
| def predict_single(image_pil): | |
| """ | |
| Predict freshness from a single cropped image (eye OR gill). | |
| Returns: | |
| class_name : str e.g. 'eye-fresh' | |
| confidence : float | |
| all_probs : dict {class_name: prob} | |
| """ | |
| model, classes = load_model() | |
| tensor = infer_transform(image_pil.convert("RGB")).unsqueeze(0).to(DEVICE) | |
| with torch.no_grad(): | |
| logits = model(tensor) | |
| probs = torch.softmax(logits, dim=1)[0].cpu().numpy() | |
| idx = int(probs.argmax()) | |
| class_name = classes[idx] | |
| all_probs = {classes[i]: float(probs[i]) for i in range(len(classes))} | |
| return class_name, float(probs[idx]), all_probs | |
| def determine_grade(eye_class, gill_class): | |
| """ | |
| Map eye + gill prediction to export grade. | |
| eye_class / gill_class: 'eye-fresh', 'eye-non-fresh', 'gill-fresh', 'gill-non-fresh' | |
| Returns: grade, label, color, description | |
| """ | |
| # Extract just 'fresh' or 'non-fresh' | |
| eye_status = "fresh" if "non" not in eye_class else "non-fresh" | |
| gill_status = "fresh" if "non" not in gill_class else "non-fresh" | |
| grade, label, color = GRADE_RULES[(eye_status, gill_status)] | |
| desc = GRADE_DESC[grade] | |
| return grade, label, color, desc, eye_status, gill_status | |
| def classify_fish(eye_image_pil, gill_image_pil): | |
| """ | |
| Full pipeline: predict eye + gill β determine grade. | |
| Returns a result dict. | |
| """ | |
| eye_class, eye_conf, eye_probs = predict_single(eye_image_pil) | |
| gill_class, gill_conf, gill_probs = predict_single(gill_image_pil) | |
| grade, label, color, desc, eye_status, gill_status = determine_grade(eye_class, gill_class) | |
| return { | |
| "grade": grade, | |
| "label": label, | |
| "color": color, | |
| "description": desc, | |
| "eye": { | |
| "class": eye_class, | |
| "status": eye_status, | |
| "confidence": eye_conf, | |
| "probs": eye_probs, | |
| }, | |
| "gill": { | |
| "class": gill_class, | |
| "status": gill_status, | |
| "confidence": gill_conf, | |
| "probs": gill_probs, | |
| }, | |
| } | |
| def is_model_ready(): | |
| return os.path.exists(MODEL_PATH) and os.path.exists(CLASS_JSON) |