har1zarD commited on
Commit
a986fa9
·
1 Parent(s): 2936e62

Update app.py with latest changes

Browse files
Files changed (1) hide show
  1. app.py +95 -28
app.py CHANGED
@@ -16,12 +16,17 @@ from PIL import Image
16
  import torch
17
  import torch.nn.functional as F
18
  from transformers import CLIPProcessor, CLIPModel
 
19
 
20
  # --- Configuration ---
21
  # LITE varijanta: CLIP zero-shot klasifikacija nad Food-101 labelama (CPU-friendly)
22
  # Zadano koristi ViT-L/14 model; može se promijeniti preko env varijable MODEL_NAME
23
  MODEL_NAME = os.environ.get("MODEL_NAME", "openai/clip-vit-large-patch14")
24
 
 
 
 
 
25
  # --- Helper Functions ---
26
  def select_device() -> str:
27
  """Odabire najbolji dostupni uređaj: CUDA > MPS (Apple) > CPU."""
@@ -103,6 +108,26 @@ def load_model():
103
  print(f"❌ Greška pri učitavanju CLIP modela: {e}")
104
  raise
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  def is_image_file(file: UploadFile):
107
  """Provjerava da li je fajl podržani format slike."""
108
  return file.content_type in ["image/jpeg", "image/png", "image/jpg", "image/webp"]
@@ -342,6 +367,27 @@ def classify_image_with_clip(image: Image.Image, processor: CLIPProcessor, model
342
  "top5": list(zip(top_labels, top_probs))
343
  }
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  def extract_clip_food_info(classification: Dict[str, Any]) -> Dict[str, Any]:
346
  """Formatira rezultat CLIP klasifikacije u zajedničku strukturu."""
347
  primary = classification["primary_label"]
@@ -363,16 +409,17 @@ def extract_clip_food_info(classification: Dict[str, Any]) -> Dict[str, Any]:
363
  }
364
 
365
  # --- Učitaj Model pri Pokretanju Aplikacije ---
366
- print("🚀 Pokrećem LITE Food Scanner API (CLIP)...")
367
  processor, model, device, dtype = load_model()
368
  CURRENT_DTYPE = dtype
369
  TEXT_LABELS = get_food101_labels()
370
  TEXT_FEATURES = build_text_cache(TEXT_LABELS, processor, model, device, dtype)
371
  warmup_model(processor, model, device, dtype)
 
372
 
373
  # --- FastAPI Aplikacija ---
374
  app = FastAPI(
375
- title="🍎 LITE Food Scanner API - Nutrition Edition (CLIP)",
376
  description="""
377
  **🏆 Lako i brzo prepoznavanje hrane + Nutrition Lookup (CPU-friendly)**
378
 
@@ -412,7 +459,7 @@ app = FastAPI(
412
  - 🤖 Inteligentna procjena za nepoznatu hranu
413
  - ✅ Production-ready i stabilan
414
  """,
415
- version="9.0.0 - LITE (CLIP)"
416
  )
417
 
418
  # Dodaj CORS middleware za web aplikacije
@@ -426,7 +473,7 @@ app.add_middleware(
426
 
427
  @app.post("/analyze",
428
  summary="Analiziraj Food Sliku",
429
- description="Upload-uj sliku da dobiješ food label + nutritivne podatke (CLIP LITE)",
430
  response_description="Rezultati food recognition i nutritivnih podataka"
431
  )
432
  async def analyze(file: UploadFile = File(...)):
@@ -464,10 +511,26 @@ async def analyze(file: UploadFile = File(...)):
464
  raise HTTPException(status_code=500, detail=f"Greška pri čitanju slike: {e}")
465
 
466
  try:
467
- # Zero-shot klasifikacija sa CLIP-om
468
- print("🔍 Analiziram sliku sa CLIP (zero-shot Food-101)...")
469
- classification = classify_image_with_clip(image, processor, model, device)
470
- food_info = extract_clip_food_info(classification)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  except Exception as e:
472
  print(f"Greška tokom analize: {e}")
473
  raise HTTPException(status_code=500, detail=f"Greška tokom analize: {e}")
@@ -515,11 +578,15 @@ async def analyze(file: UploadFile = File(...)):
515
  },
516
 
517
  "model_info": {
518
- "vision_model": MODEL_NAME,
519
  "nutrition_source": nutrition_data["source"],
520
- "type": "CLIP Zero-shot Classifier + Nutrition Database",
 
 
 
 
521
  "capabilities": [
522
- "Food Recognition (Food-101)",
523
  "Nutrition Data Lookup"
524
  ]
525
  }
@@ -591,20 +658,20 @@ async def search_nutrition(food_name: str):
591
  def root():
592
  """Root endpoint sa API informacijama."""
593
  return {
594
- "message": "🍎 LITE Food Scanner API v9.0 - CLIP + Nutrition Edition",
595
  "status": "🟢 Online",
596
  "tagline": "🏆 Najbolji Self-Hosted Food Recognition + Nutrition API",
597
  "model": {
598
- "vision_model": MODEL_NAME,
599
  "nutrition_source": "Open Food Facts + AI Estimation",
600
- "type": "CLIP Zero-shot Classifier + Nutrition Database",
601
- "provider": "OpenAI CLIP + Open Food Facts",
602
- "generation": "CLIP (ViT-L/14)",
603
  "device": device.upper(),
604
  "rank": "🥇 LITE rješenje za Food Recognition"
605
  },
606
  "capabilities": {
607
- "food_recognition": "✅ Food-101 Zero-shot (CLIP)",
608
  "nutrition_data": "✅ Realne Nutritivne Vrijednosti",
609
  "nutrition_lookup": "✅ Manual Search po Imenu",
610
  "ingredient_analysis": "❌ (LITE)",
@@ -643,7 +710,7 @@ def root():
643
  )
644
  def health_check():
645
  """Health check endpoint za monitoring i load balancere."""
646
- model_status = model is not None and processor is not None
647
 
648
  # Test nutrition API
649
  nutrition_api_status = "unknown"
@@ -656,12 +723,12 @@ def health_check():
656
  return {
657
  "status": "healthy" if model_status else "unhealthy",
658
  "model_loaded": model_status,
659
- "vision_model": MODEL_NAME,
660
  "nutrition_api": nutrition_api_status,
661
- "model_type": "CLIP Zero-shot Classifier + Nutrition Database",
662
  "device": device,
663
  "device_available": torch.cuda.is_available() if device == "cuda" else True,
664
- "version": "9.0.0 - LITE (CLIP)",
665
  "timestamp": "2025-10-08",
666
  "ranking": "🥇 LITE Food Recognition + Nutrition Rješenje"
667
  }
@@ -675,7 +742,7 @@ def get_capabilities():
675
  return {
676
  "vision_model": MODEL_NAME,
677
  "nutrition_source": "Open Food Facts",
678
- "generation": "CLIP (ViT-L/14) + Nutrition Database",
679
  "release": "2024 (Stable)",
680
  "vision_tasks": {
681
  "food_recognition": {
@@ -725,8 +792,8 @@ def get_capabilities():
725
  "🔬 700,000+ proizvoda u bazi"
726
  ],
727
  "technical_specs": {
728
- "parameters": "~427M",
729
- "architecture": "CLIP (ViT-L/14)",
730
  "training_data": "WIT + zero-shot na Food-101",
731
  "supported_formats": ["JPEG", "PNG", "WebP"],
732
  "max_resolution": "Podrška za visoke rezolucije",
@@ -741,12 +808,12 @@ def get_capabilities():
741
  # --- Pokreni API ---
742
  if __name__ == "__main__":
743
  print("=" * 80)
744
- print("🍎 LITE FOOD SCANNER API v9.0 - NUTRITION EDITION (CLIP)")
745
  print("=" * 80)
746
- print(f"🤖 Vision Model: {MODEL_NAME}")
747
  print(f"📊 Nutrition Source: Open Food Facts + AI Estimation")
748
- print(f"🏢 Provider: OpenAI CLIP + Open Food Facts")
749
- print(f"🔧 Type: CLIP Zero-shot Classifier + Nutrition Database")
750
  print(f"💻 Device: {device.upper()}")
751
  print(f"🎯 Rank: LITE Food Recognition + Nutrition Rješenje")
752
  print(f"✨ Status: Production Ready - NUTRITION EDITION")
 
16
  import torch
17
  import torch.nn.functional as F
18
  from transformers import CLIPProcessor, CLIPModel
19
+ from transformers import pipeline as hf_pipeline
20
 
21
  # --- Configuration ---
22
  # LITE varijanta: CLIP zero-shot klasifikacija nad Food-101 labelama (CPU-friendly)
23
  # Zadano koristi ViT-L/14 model; može se promijeniti preko env varijable MODEL_NAME
24
  MODEL_NAME = os.environ.get("MODEL_NAME", "openai/clip-vit-large-patch14")
25
 
26
+ # Opcioni HF klasifikator (supervizirani model za Food-101). Preporučeni default: nateraw/food
27
+ USE_HF_CLASSIFIER = os.environ.get("USE_HF_CLASSIFIER", "1") == "1"
28
+ HF_FOOD_MODEL_NAME = os.environ.get("FOOD_CLASSIFIER_MODEL", "nateraw/food")
29
+
30
  # --- Helper Functions ---
31
  def select_device() -> str:
32
  """Odabire najbolji dostupni uređaj: CUDA > MPS (Apple) > CPU."""
 
108
  print(f"❌ Greška pri učitavanju CLIP modela: {e}")
109
  raise
110
 
111
+ def load_hf_food_classifier(device: str):
112
+ """Pokušava učitati HF image-classification pipeline (npr. nateraw/food)."""
113
+ if not USE_HF_CLASSIFIER:
114
+ return None
115
+ try:
116
+ print(f"Loading HF Food Classifier: {HF_FOOD_MODEL_NAME} ...")
117
+ # Map device string -> pipeline device index
118
+ device_index = 0 if device in ("cuda", "mps") else -1
119
+ clf = hf_pipeline(
120
+ task="image-classification",
121
+ model=HF_FOOD_MODEL_NAME,
122
+ device=device_index,
123
+ top_k=5,
124
+ )
125
+ print("✅ HF Food Classifier učitan uspješno!")
126
+ return clf
127
+ except Exception as e:
128
+ print(f"⚠️ HF Food Classifier nije moguće učitati ('{HF_FOOD_MODEL_NAME}'): {e}. Nastavljam sa CLIP LITE.")
129
+ return None
130
+
131
  def is_image_file(file: UploadFile):
132
  """Provjerava da li je fajl podržani format slike."""
133
  return file.content_type in ["image/jpeg", "image/png", "image/jpg", "image/webp"]
 
367
  "top5": list(zip(top_labels, top_probs))
368
  }
369
 
370
+ def classify_image_with_hf(image: Image.Image, clf) -> Dict[str, Any]:
371
+ """Klasifikacija slike preko HF pipeline image-classification (top-5)."""
372
+ preds = clf(image)
373
+ # preds je lista dict-ova: {label, score}
374
+ if not preds:
375
+ return {
376
+ "primary_label": "Unknown",
377
+ "alternatives": [],
378
+ "confidence": 0.0,
379
+ "top5": []
380
+ }
381
+ top_labels = [p.get("label", "Unknown") for p in preds]
382
+ top_probs = [float(p.get("score", 0.0)) for p in preds]
383
+ primary_label = top_labels[0]
384
+ return {
385
+ "primary_label": primary_label,
386
+ "alternatives": top_labels[1:],
387
+ "confidence": top_probs[0] if top_probs else 0.0,
388
+ "top5": list(zip(top_labels, top_probs))
389
+ }
390
+
391
  def extract_clip_food_info(classification: Dict[str, Any]) -> Dict[str, Any]:
392
  """Formatira rezultat CLIP klasifikacije u zajedničku strukturu."""
393
  primary = classification["primary_label"]
 
409
  }
410
 
411
  # --- Učitaj Model pri Pokretanju Aplikacije ---
412
+ print("🚀 Pokrećem LITE Food Scanner API (CLIP + opcioni HF classifier)...")
413
  processor, model, device, dtype = load_model()
414
  CURRENT_DTYPE = dtype
415
  TEXT_LABELS = get_food101_labels()
416
  TEXT_FEATURES = build_text_cache(TEXT_LABELS, processor, model, device, dtype)
417
  warmup_model(processor, model, device, dtype)
418
+ HF_CLASSIFIER = load_hf_food_classifier(device)
419
 
420
  # --- FastAPI Aplikacija ---
421
  app = FastAPI(
422
+ title="🍎 LITE Food Scanner API - Nutrition Edition (HF+CLIP)",
423
  description="""
424
  **🏆 Lako i brzo prepoznavanje hrane + Nutrition Lookup (CPU-friendly)**
425
 
 
459
  - 🤖 Inteligentna procjena za nepoznatu hranu
460
  - ✅ Production-ready i stabilan
461
  """,
462
+ version="9.1.0 - LITE (HF+CLIP)"
463
  )
464
 
465
  # Dodaj CORS middleware za web aplikacije
 
473
 
474
  @app.post("/analyze",
475
  summary="Analiziraj Food Sliku",
476
+ description="Upload-uj sliku da dobiješ food label + nutritivne podatke (HF classifier ako je dostupan, inače CLIP LITE)",
477
  response_description="Rezultati food recognition i nutritivnih podataka"
478
  )
479
  async def analyze(file: UploadFile = File(...)):
 
511
  raise HTTPException(status_code=500, detail=f"Greška pri čitanju slike: {e}")
512
 
513
  try:
514
+ # Ako postoji nadzirani HF classifier, koristi njega; inače CLIP zero-shot
515
+ if HF_CLASSIFIER is not None:
516
+ print("🔍 Analiziram sliku sa HF image-classification modelom ...")
517
+ classification = classify_image_with_hf(image, HF_CLASSIFIER)
518
+ # Prebaci u zajednički format
519
+ detailed = "Top-5: " + ", ".join([f"{l} ({p:.2f})" for l, p in classification["top5"]])
520
+ food_info = {
521
+ "primary_label": classification["primary_label"],
522
+ "alternative_labels": classification["alternatives"],
523
+ "detailed_analysis": detailed,
524
+ "food_items": f"1) {classification['primary_label']}",
525
+ "nutritional_context": "",
526
+ "ocr_text": "",
527
+ "has_food": True,
528
+ "confidence": classification["confidence"],
529
+ }
530
+ else:
531
+ print("🔍 Analiziram sliku sa CLIP (zero-shot Food-101)...")
532
+ classification = classify_image_with_clip(image, processor, model, device)
533
+ food_info = extract_clip_food_info(classification)
534
  except Exception as e:
535
  print(f"Greška tokom analize: {e}")
536
  raise HTTPException(status_code=500, detail=f"Greška tokom analize: {e}")
 
578
  },
579
 
580
  "model_info": {
581
+ "vision_model": HF_FOOD_MODEL_NAME if HF_CLASSIFIER is not None else MODEL_NAME,
582
  "nutrition_source": nutrition_data["source"],
583
+ "type": (
584
+ "HF Image Classification + Nutrition Database"
585
+ if HF_CLASSIFIER is not None else
586
+ "CLIP Zero-shot Classifier + Nutrition Database"
587
+ ),
588
  "capabilities": [
589
+ "Food Recognition (supervised Food-101)" if HF_CLASSIFIER is not None else "Food Recognition (Food-101)",
590
  "Nutrition Data Lookup"
591
  ]
592
  }
 
658
  def root():
659
  """Root endpoint sa API informacijama."""
660
  return {
661
+ "message": "🍎 LITE Food Scanner API v9.1 - HF+CLIP + Nutrition Edition",
662
  "status": "🟢 Online",
663
  "tagline": "🏆 Najbolji Self-Hosted Food Recognition + Nutrition API",
664
  "model": {
665
+ "vision_model": HF_FOOD_MODEL_NAME if HF_CLASSIFIER is not None else MODEL_NAME,
666
  "nutrition_source": "Open Food Facts + AI Estimation",
667
+ "type": "HF Image Classification + Nutrition Database" if HF_CLASSIFIER is not None else "CLIP Zero-shot Classifier + Nutrition Database",
668
+ "provider": "Hugging Face + Open Food Facts" if HF_CLASSIFIER is not None else "OpenAI CLIP + Open Food Facts",
669
+ "generation": "ViT/ConvNeXt (supervised Food-101)" if HF_CLASSIFIER is not None else "CLIP (ViT-L/14)",
670
  "device": device.upper(),
671
  "rank": "🥇 LITE rješenje za Food Recognition"
672
  },
673
  "capabilities": {
674
+ "food_recognition": "✅ Food-101 (HF supervised)" if HF_CLASSIFIER is not None else "✅ Food-101 Zero-shot (CLIP)",
675
  "nutrition_data": "✅ Realne Nutritivne Vrijednosti",
676
  "nutrition_lookup": "✅ Manual Search po Imenu",
677
  "ingredient_analysis": "❌ (LITE)",
 
710
  )
711
  def health_check():
712
  """Health check endpoint za monitoring i load balancere."""
713
+ model_status = (model is not None and processor is not None) or (HF_CLASSIFIER is not None)
714
 
715
  # Test nutrition API
716
  nutrition_api_status = "unknown"
 
723
  return {
724
  "status": "healthy" if model_status else "unhealthy",
725
  "model_loaded": model_status,
726
+ "vision_model": HF_FOOD_MODEL_NAME if HF_CLASSIFIER is not None else MODEL_NAME,
727
  "nutrition_api": nutrition_api_status,
728
+ "model_type": "HF Image Classification + Nutrition Database" if HF_CLASSIFIER is not None else "CLIP Zero-shot Classifier + Nutrition Database",
729
  "device": device,
730
  "device_available": torch.cuda.is_available() if device == "cuda" else True,
731
+ "version": "9.1.0 - LITE (HF+CLIP)",
732
  "timestamp": "2025-10-08",
733
  "ranking": "🥇 LITE Food Recognition + Nutrition Rješenje"
734
  }
 
742
  return {
743
  "vision_model": MODEL_NAME,
744
  "nutrition_source": "Open Food Facts",
745
+ "generation": ("HF (supervised Food-101) + Nutrition Database" if HF_CLASSIFIER is not None else "CLIP (ViT-L/14) + Nutrition Database"),
746
  "release": "2024 (Stable)",
747
  "vision_tasks": {
748
  "food_recognition": {
 
792
  "🔬 700,000+ proizvoda u bazi"
793
  ],
794
  "technical_specs": {
795
+ "parameters": "~86M-200M (ovisno o HF modelu)" if HF_CLASSIFIER is not None else "~427M",
796
+ "architecture": "HF Vision Classifier (npr. ViT-B/16)" if HF_CLASSIFIER is not None else "CLIP (ViT-L/14)",
797
  "training_data": "WIT + zero-shot na Food-101",
798
  "supported_formats": ["JPEG", "PNG", "WebP"],
799
  "max_resolution": "Podrška za visoke rezolucije",
 
808
  # --- Pokreni API ---
809
  if __name__ == "__main__":
810
  print("=" * 80)
811
+ print("🍎 LITE FOOD SCANNER API v9.1 - NUTRITION EDITION (HF+CLIP)")
812
  print("=" * 80)
813
+ print(f"🤖 Vision Model: {HF_FOOD_MODEL_NAME if HF_CLASSIFIER is not None else MODEL_NAME}")
814
  print(f"📊 Nutrition Source: Open Food Facts + AI Estimation")
815
+ print(f"🏢 Provider: {'Hugging Face + Open Food Facts' if HF_CLASSIFIER is not None else 'OpenAI CLIP + Open Food Facts'}")
816
+ print(f"🔧 Type: {'HF Image Classification + Nutrition Database' if HF_CLASSIFIER is not None else 'CLIP Zero-shot Classifier + Nutrition Database'}")
817
  print(f"💻 Device: {device.upper()}")
818
  print(f"🎯 Rank: LITE Food Recognition + Nutrition Rješenje")
819
  print(f"✨ Status: Production Ready - NUTRITION EDITION")