ochsncon commited on
Commit
bcbe62f
·
verified ·
1 Parent(s): b99d10a

Update src/vision_analyzer.py

Browse files
Files changed (1) hide show
  1. src/vision_analyzer.py +63 -67
src/vision_analyzer.py CHANGED
@@ -1,8 +1,7 @@
1
- """Computer Vision analyzer for coarse vehicle class prediction.
2
 
3
- The module supports two levels:
4
- 1) A local classifier trained on hand-crafted image features.
5
- 2) A deterministic image-signature fallback so the app always runs.
6
 
7
  No damage detection and no technical condition estimation is implemented.
8
  """
@@ -11,63 +10,58 @@ from __future__ import annotations
11
 
12
  import json
13
  from functools import lru_cache
 
14
  from typing import Any
15
 
16
- import joblib
17
  import numpy as np
18
  from PIL import Image
19
 
20
- from src.config import VISION_LABELS_PATH, VISION_METADATA_PATH, VISION_MODEL_PATH
21
- from src.vision_features import extract_image_features, ensure_pil_image
22
-
23
- DEFAULT_CLASSES = [
24
- "Audi",
25
- "Hyundai Creta",
26
- "Mahindra Scorpio",
27
- "Rolls Royce",
28
- "Swift",
29
- "Tata Safari",
30
- "Toyota Innova",
31
- ]
32
-
33
 
34
 
 
 
 
 
 
 
 
 
 
35
 
36
 
37
  @lru_cache(maxsize=1)
38
- def _load_optional_pytorch_model() -> dict[str, Any] | None:
39
- if not VISION_MODEL_PATH.exists():
 
 
 
 
 
40
  return None
41
 
42
  try:
43
- model = joblib.load(VISION_MODEL_PATH)
44
 
45
- classes = DEFAULT_CLASSES
46
- if VISION_LABELS_PATH.exists():
47
- labels_payload = json.loads(VISION_LABELS_PATH.read_text(encoding="utf-8"))
48
- if isinstance(labels_payload, list) and labels_payload:
49
- classes = [str(x) for x in labels_payload]
50
 
 
 
51
  metadata = {}
52
- if VISION_METADATA_PATH.exists():
53
- metadata = json.loads(VISION_METADATA_PATH.read_text(encoding="utf-8"))
54
 
55
- return {"model": model, "classes": classes, "metadata": metadata}
56
  except Exception:
57
  return None
58
 
59
 
60
- def _fallback_predict(image: Image.Image) -> tuple[str, float, str, list[str]]:
61
- notes = [
62
- "Fallback classifier active: no trained local CV model found.",
63
- "The app falls back to a safe Unknown prediction.",
64
- "No damage detection and no technical condition assessment.",
65
- ]
66
- return "Unknown", 0.0, "fallback", notes
67
-
68
-
69
  def analyze_car_image(image: Image.Image | np.ndarray | None) -> dict[str, Any]:
70
- """Analyze uploaded image and return coarse vehicle class/model-group info."""
 
 
 
71
  if image is None:
72
  return {
73
  "predicted_class": "Unknown",
@@ -87,40 +81,42 @@ def analyze_car_image(image: Image.Image | np.ndarray | None) -> dict[str, Any]:
87
  "notes": ["Input is not a valid image format."],
88
  }
89
 
90
- model_bundle = _load_optional_pytorch_model()
91
  if model_bundle is not None:
92
  try:
93
- model = model_bundle["model"]
94
- classes = model_bundle["classes"]
95
  metadata = model_bundle.get("metadata", {})
96
 
97
- features = extract_image_features(ensure_pil_image(image))
98
- if hasattr(model, "predict_proba"):
99
- probs = model.predict_proba([features])[0]
100
- idx = int(np.argmax(probs))
101
- conf = float(probs[idx])
102
- else:
103
- idx = int(model.predict([features])[0])
104
- conf = 0.5
105
-
106
- predicted = classes[idx] if idx < len(classes) else "Unknown"
107
- return {
108
- "predicted_class": predicted,
109
- "confidence": round(conf, 3),
110
- "method": "local_image_classifier",
111
- "notes": [
112
- "Coarse image-based class prediction.",
113
- f"Vision test accuracy: {metadata.get('test_accuracy', 'n/a')}.",
114
- "No damage detection or detailed technical diagnostics.",
115
- ],
116
- }
117
  except Exception:
118
  pass
119
 
120
- predicted, confidence, method, notes = _fallback_predict(ensure_pil_image(image))
121
  return {
122
- "predicted_class": predicted,
123
- "confidence": confidence,
124
- "method": method,
125
- "notes": notes,
 
 
 
126
  }
 
1
+ """Computer Vision analyzer using transfer-learning ResNet-18 for vehicle brand classification.
2
 
3
+ The module loads a Hugging Face fine-tuned image classification model from
4
+ models/car-image-classifier/ and provides vehicle brand predictions.
 
5
 
6
  No damage detection and no technical condition estimation is implemented.
7
  """
 
10
 
11
  import json
12
  from functools import lru_cache
13
+ from pathlib import Path
14
  from typing import Any
15
 
 
16
  import numpy as np
17
  from PIL import Image
18
 
19
+ from src.config import MODEL_DIR
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
 
22
+ def ensure_pil_image(image: Image.Image | np.ndarray | None) -> Image.Image:
23
+ """Convert input to PIL Image."""
24
+ if image is None:
25
+ raise ValueError("No image provided.")
26
+ if isinstance(image, np.ndarray):
27
+ return Image.fromarray(image.astype("uint8"))
28
+ if not isinstance(image, Image.Image):
29
+ raise TypeError("Input is not a valid image.")
30
+ return image
31
 
32
 
33
  @lru_cache(maxsize=1)
34
+ def _load_transfer_model() -> dict[str, Any] | None:
35
+ """Load the transfer-learning model from models/car-image-classifier/.
36
+
37
+ Returns a dict with 'pipeline' and 'metadata' on success, None if model not found.
38
+ """
39
+ model_dir = MODEL_DIR / "car-image-classifier"
40
+ if not model_dir.exists():
41
  return None
42
 
43
  try:
44
+ from transformers import pipeline
45
 
46
+ # Load the image classification pipeline
47
+ clf_pipeline = pipeline("image-classification", model=str(model_dir), device=-1)
 
 
 
48
 
49
+ # Load metadata if it exists
50
+ metadata_path = model_dir / "vision_metadata.json"
51
  metadata = {}
52
+ if metadata_path.exists():
53
+ metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
54
 
55
+ return {"pipeline": clf_pipeline, "metadata": metadata}
56
  except Exception:
57
  return None
58
 
59
 
 
 
 
 
 
 
 
 
 
60
  def analyze_car_image(image: Image.Image | np.ndarray | None) -> dict[str, Any]:
61
+ """Analyze uploaded image and return vehicle brand prediction.
62
+
63
+ Uses a transfer-learning ResNet-18 model to classify vehicle brands.
64
+ """
65
  if image is None:
66
  return {
67
  "predicted_class": "Unknown",
 
81
  "notes": ["Input is not a valid image format."],
82
  }
83
 
84
+ model_bundle = _load_transfer_model()
85
  if model_bundle is not None:
86
  try:
87
+ pipeline = model_bundle["pipeline"]
 
88
  metadata = model_bundle.get("metadata", {})
89
 
90
+ # Run inference
91
+ pil_image = ensure_pil_image(image)
92
+ results = pipeline(pil_image, top_k=1)
93
+
94
+ if results:
95
+ top_result = results[0]
96
+ predicted = top_result["label"]
97
+ confidence = float(top_result["score"])
98
+
99
+ return {
100
+ "predicted_class": predicted,
101
+ "confidence": round(confidence, 3),
102
+ "method": "local_transfer_model",
103
+ "notes": [
104
+ "Vehicle brand classification using transfer learning (ResNet-18).",
105
+ "The classifier can only predict one of the trained classes.",
106
+ f"Model accuracy on test set: {metadata.get('accuracy', 'n/a')}.",
107
+ "No damage detection or technical condition assessment.",
108
+ ],
109
+ }
110
  except Exception:
111
  pass
112
 
113
+ # Fallback if model not found or inference fails
114
  return {
115
+ "predicted_class": "Unknown",
116
+ "confidence": 0.0,
117
+ "method": "fallback",
118
+ "notes": [
119
+ "No trained transfer-learning model found.",
120
+ "Please train the model using: python -m src.train_vision_model",
121
+ ],
122
  }