prakman commited on
Commit
3dc96fa
·
verified ·
1 Parent(s): 1640a44

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -169
app.py CHANGED
@@ -1,15 +1,10 @@
1
- # app.py - Adaptive SmartPack app (works on Gradio 3.x or 5.x)
2
- import os
3
- import joblib
4
- import numpy as np
5
- import pandas as pd
6
- import cv2
7
- from PIL import Image
8
  import gradio as gr
 
 
9
 
10
- print("gradio version:", gr.__version__)
11
-
12
- # Feature columns used in training
13
  FEATURE_COLS = [
14
  'mean_R','mean_G','mean_B',
15
  'std_R','std_G','std_B',
@@ -18,59 +13,34 @@ FEATURE_COLS = [
18
  'brightness','ratio_R_G','ratio_R_B'
19
  ]
20
 
21
- # ------------------------------
22
- # Load models (if present)
23
- # ------------------------------
24
  clf = None
25
  rfr = None
26
- try:
27
- if os.path.exists("final_rfc.joblib"):
28
- clf = joblib.load("final_rfc.joblib")
29
- print("[MODEL] Loaded: final_rfc.joblib")
30
- if os.path.exists("final_rfr.joblib"):
31
- rfr = joblib.load("final_rfr.joblib")
32
- print("[MODEL] Loaded: final_rfr.joblib")
33
- except Exception as e:
34
- print("[MODEL] Error loading model(s):", e)
35
-
36
- # ------------------------------
37
- # Utility: literature TVB estimate & mapping
38
- # ------------------------------
39
- def literature_tvb_estimate(time_hr, temp_C):
40
- days = float(time_hr) / 24.0
41
- if temp_C >= 25:
42
- if days <= 0: return 13.0
43
- if days <= 5: return 13.0 + (days/5.0)*(35-13)
44
- return 70.0 + (days-5.0)*3.0
45
- elif temp_C >= 10:
46
- if days <= 0: return 13.0
47
- if days <= 3: return 13.0 + (days/3.0)*(30-13)
48
- return 30.0 + (days-3.0)*1.5
49
- elif temp_C >= 4:
50
- if days <= 0: return 13.0
51
- if days <= 7: return 13.0 + (days/7.0)*(30-13)
52
- return 30.0 + (days-7.0)*0.5
53
- else:
54
- if days <= 0: return 13.0
55
- return 13.0 + days * 0.1
56
 
57
- def tvb_to_class_label(tvb_val):
58
- if tvb_val is None:
59
- return None
60
- if tvb_val < 20:
61
- return "Fresh"
62
- elif tvb_val < 30:
63
- return "Mildly Spoiled"
64
- else:
65
- return "Spoiled"
66
 
67
- # ------------------------------
68
- # Feature extraction (same as training)
69
- # ------------------------------
70
  def extract_features(pil_img):
71
  arr = np.array(pil_img.convert("RGB"))
72
- if arr.ndim != 3:
73
- arr = np.stack([arr]*3, axis=-1)
74
  mean_rgb = arr.mean(axis=(0,1))
75
  std_rgb = arr.std(axis=(0,1))
76
  hsv = cv2.cvtColor(arr, cv2.COLOR_RGB2HSV)
@@ -89,138 +59,89 @@ def extract_features(pil_img):
89
  }
90
  return feats
91
 
92
- # ------------------------------
93
- # Main analyze function
94
- # ------------------------------
95
- def analyze(image, mass_g, time_hr, temperature_C, use_meta_override, use_meta_for_tvb):
96
- """
97
- Returns (class_label, tvb_est, used_image_pil, explanation_str)
98
- """
99
- if image is None:
100
- return "No image uploaded", None, None, "No image provided"
101
-
102
- # Ensure it's PIL
103
- if isinstance(image, np.ndarray):
104
- img_pil = Image.fromarray(image)
 
105
  else:
106
- img_pil = image
107
-
108
- # We assume user provided either pre-cropped image (Gradio 5) or editor-cropped (Gradio 3).
109
- cropped_img = img_pil
110
 
111
- # extract features
112
- try:
113
- feats = extract_features(cropped_img)
114
- except Exception as e:
115
- return "Feature extraction error", None, None, f"Feat err: {e}"
116
 
 
 
 
 
 
 
 
 
117
  X = pd.DataFrame([feats])
118
- missing = [c for c in FEATURE_COLS if c not in X.columns]
119
- if missing:
120
- return "Missing feature cols: " + ",".join(missing), None, cropped_img, "Feature columns missing"
121
-
122
  # classification
123
- cls_pred = None
124
- cls_msg = ""
125
- try:
126
- if clf is not None:
127
  cls_pred = str(clf.predict(X[FEATURE_COLS])[0])
128
- cls_msg = f"Classifier predicted: {cls_pred}. "
129
- else:
130
- cls_msg = "Classifier not available. "
131
- except Exception as e:
132
- cls_msg = f"Classifier error: {e}. "
133
-
134
- # regressor TVB
135
- tvb_est = None
136
- try:
137
- if rfr is not None:
138
- tvb_est = float(rfr.predict(X[FEATURE_COLS])[0])
139
- cls_msg += f"Regressor TVB-N: {tvb_est:.2f}. "
140
- elif use_meta_for_tvb and (time_hr is not None) and (temperature_C is not None):
141
- tvb_est = float(literature_tvb_estimate(time_hr, temperature_C))
142
- cls_msg += f"Literature TVB-N (fallback): {tvb_est:.2f}. "
143
- else:
144
- cls_msg += "No TVB-N available. "
145
- except Exception as e:
146
- cls_msg += f"Regressor error: {e}. "
147
-
148
- # override policy
149
- override_msg = ""
150
- final_class = cls_pred
151
- if use_meta_override and (time_hr is not None) and (temperature_C is not None):
152
- lit_tvb = literature_tvb_estimate(time_hr, temperature_C)
153
- lit_class = tvb_to_class_label(lit_tvb)
154
- override_msg = f"Metadata-based TVB-N={lit_tvb:.1f} => {lit_class}. "
155
- if cls_pred is None:
156
- final_class = lit_class
157
- override_msg += "No classifier prediction — using metadata class."
158
- elif cls_pred != lit_class:
159
- final_class = lit_class
160
- override_msg += f"Classifier ({cls_pred}) disagreed — overridden by metadata."
161
- else:
162
- final_class = cls_pred
163
- override_msg += "Classifier agrees with metadata."
164
- else:
165
- if final_class is None:
166
- if tvb_est is not None:
167
- final_class = tvb_to_class_label(tvb_est)
168
- override_msg = "No classifier — used TVB->class mapping."
169
- else:
170
- final_class = "Unknown"
171
-
172
- meta_info = f"mass_g={mass_g} time_hr={time_hr} temp_C={temperature_C}"
173
- explain_msg = cls_msg + override_msg + " | " + meta_info
174
-
175
- return final_class, (None if tvb_est is None else float(tvb_est)), cropped_img, explain_msg
176
-
177
- # ------------------------------
178
- # Gradio UI (adaptive)
179
- # ------------------------------
180
- # detect major gradio version (major.minor)
181
- try:
182
- GRADIO_V = tuple(int(x) for x in gr.__version__.split(".")[:2])
183
- except Exception:
184
- GRADIO_V = (5, 0)
185
-
186
- use_editor = (GRADIO_V[0] == 3) # editor args exist in 3.x series you used
187
-
188
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
189
- gr.Markdown("# 🐟 SmartPack — Mackerel Freshness Detector")
190
- if use_editor:
191
- gr.Markdown("Upload a RAW photo and **use the crop tool** to select the indicator area (recommended).")
192
  else:
193
- gr.Markdown(
194
- "Editor unavailable in this Gradio version.\n\n"
195
- "- To enable in-browser crop (pencil icon) pin `gradio==3.40.1` in requirements.txt and rebuild the Space.\n"
196
- "- Otherwise upload a pre-cropped image; the app will process the uploaded image."
197
- )
198
 
199
  with gr.Row():
200
  with gr.Column(scale=2):
201
- if use_editor:
202
- # Gradio 3.x style (editor available)
203
- img_in = gr.Image(type="pil", label="Upload image (use crop tool)", source="upload", tool="editor")
204
- gr.Markdown("Tip: click the crop tool icon (square) to select the indicator area, then press Apply.")
205
  else:
206
- # Gradio 5.x: do not pass 'source' or 'tool'
207
  img_in = gr.Image(type="pil", label="Upload pre-cropped indicator image (no editor available)")
208
-
209
  mass_in = gr.Number(label="Mass (g)", value=None, precision=0)
210
  time_in = gr.Number(label="Time (hr)", value=None, precision=1)
211
  temp_in = gr.Number(label="Temperature (°C)", value=None, precision=1)
212
- use_meta_override = gr.Checkbox(label="Override class using metadata TVB-N (if disagree)", value=False)
213
- use_meta_for_tvb = gr.Checkbox(label="Allow metadata fallback for TVB-N if regressor missing", value=True)
214
  submit = gr.Button("Analyze")
215
 
216
  with gr.Column(scale=1):
217
- out_class = gr.Textbox(label="Freshness Class")
218
  out_tvb = gr.Number(label="Estimated TVB-N (mg/100g)")
219
- out_crop = gr.Image(label="Cropped image (used for features)")
220
- out_explain = gr.Textbox(label="Explanation / debug message")
221
 
222
- submit.click(fn=analyze, inputs=[img_in, mass_in, time_in, temp_in, use_meta_override, use_meta_for_tvb],
223
- outputs=[out_class, out_tvb, out_crop, out_explain])
224
 
225
  if __name__ == "__main__":
226
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
+ # app.py - backward-compatible SmartPack launcher (auto-detects Gradio editor support)
2
+ import os, joblib, inspect
 
 
 
 
 
3
  import gradio as gr
4
+ import numpy as np, pandas as pd, cv2
5
+ from PIL import Image
6
 
7
+ # (keep your feature columns / helper functions)
 
 
8
  FEATURE_COLS = [
9
  'mean_R','mean_G','mean_B',
10
  'std_R','std_G','std_B',
 
13
  'brightness','ratio_R_G','ratio_R_B'
14
  ]
15
 
16
+ # load models (example)
 
 
17
  clf = None
18
  rfr = None
19
+ for fname, varname in (("final_rfc.joblib","clf"), ("final_rfr.joblib","rfr")):
20
+ if os.path.exists(fname):
21
+ try:
22
+ loaded = joblib.load(fname)
23
+ if fname.endswith("rfc.joblib"): clf = loaded
24
+ else: rfr = loaded
25
+ print("[MODEL] Loaded:", fname)
26
+ except Exception as e:
27
+ print("[MODEL] Error loading", fname, ":", e)
28
+
29
+ # small helper to detect gr.Image params
30
+ def gr_image_supports_editor():
31
+ try:
32
+ sig = inspect.signature(gr.Image.__init__)
33
+ params = sig.parameters
34
+ return ('tool' in params) and ('source' in params)
35
+ except Exception:
36
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ SUPPORTS_EDITOR = gr_image_supports_editor()
39
+ print("Gradio version:", getattr(gr, "__version__", "unknown"), "Editor support:", SUPPORTS_EDITOR)
 
 
 
 
 
 
 
40
 
41
+ # --- reuse your feature extraction & tvb helper (kept concise here) ---
 
 
42
  def extract_features(pil_img):
43
  arr = np.array(pil_img.convert("RGB"))
 
 
44
  mean_rgb = arr.mean(axis=(0,1))
45
  std_rgb = arr.std(axis=(0,1))
46
  hsv = cv2.cvtColor(arr, cv2.COLOR_RGB2HSV)
 
59
  }
60
  return feats
61
 
62
+ def literature_tvb_estimate(time_hr, temp_C):
63
+ days = float(time_hr) / 24.0
64
+ if temp_C >= 25:
65
+ if days <= 0: return 13.0
66
+ if days <= 5: return 13.0 + (days/5.0)*(35-13)
67
+ return 70.0 + (days-5.0)*3.0
68
+ elif temp_C >= 10:
69
+ if days <= 0: return 13.0
70
+ if days <= 3: return 13.0 + (days/3.0)*(30-13)
71
+ return 30.0 + (days-3.0)*1.5
72
+ elif temp_C >= 4:
73
+ if days <= 0: return 13.0
74
+ if days <= 7: return 13.0 + (days/7.0)*(30-13)
75
+ return 30.0 + (days-7.0)*0.5
76
  else:
77
+ if days <= 0: return 13.0
78
+ return 13.0 + days * 0.1
 
 
79
 
80
+ def tvb_to_class_label(tvb_val):
81
+ if tvb_val is None: return None
82
+ if tvb_val < 20: return "Fresh"
83
+ elif tvb_val < 30: return "Mildly Spoiled"
84
+ else: return "Spoiled"
85
 
86
+ # main analyze function (same outputs as your earlier UI)
87
+ def analyze(image, mass_g, time_hr, temperature_C, use_meta_for_tvb):
88
+ if image is None:
89
+ return "No image uploaded", None, None, "No image"
90
+ # If editor supported, the image is expected to be already cropped by user.
91
+ # Otherwise we attempt naive autocrop center-crop fallback (you can replace with your autocrop).
92
+ img_pil = image if isinstance(image, Image.Image) else Image.fromarray(image)
93
+ feats = extract_features(img_pil)
94
  X = pd.DataFrame([feats])
 
 
 
 
95
  # classification
96
+ cls_pred = "Classifier not available"
97
+ if clf is not None:
98
+ try:
 
99
  cls_pred = str(clf.predict(X[FEATURE_COLS])[0])
100
+ except Exception as e:
101
+ cls_pred = f"Classifier error: {e}"
102
+ # tvb
103
+ tvb = None
104
+ if rfr is not None:
105
+ try:
106
+ tvb = float(rfr.predict(X[FEATURE_COLS])[0])
107
+ except Exception:
108
+ tvb = None
109
+ if (tvb is None) and use_meta_for_tvb and (time_hr is not None) and (temperature_C is not None):
110
+ try:
111
+ tvb = literature_tvb_estimate(time_hr, temperature_C)
112
+ except Exception:
113
+ tvb = None
114
+ meta_msg = f"mass_g={mass_g} time_hr={time_hr} temp_C={temperature_C}"
115
+ return cls_pred, (None if tvb is None else float(tvb)), img_pil, meta_msg
116
+
117
+ # Build UI depending on support
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
119
+ gr.Markdown("# SmartPack — Mackerel Freshness Detector")
120
+ if SUPPORTS_EDITOR:
121
+ gr.Markdown("Editor available: use the crop tool (pencil icon) to crop the indicator.")
122
  else:
123
+ gr.Markdown("**Editor unavailable in this Gradio version.**\n- Either upgrade Gradio (pip install -U gradio) or crop images before upload.\n- App will proceed with provided image (no in-browser crop).")
 
 
 
 
124
 
125
  with gr.Row():
126
  with gr.Column(scale=2):
127
+ if SUPPORTS_EDITOR:
128
+ img_in = gr.Image(type="pil", label="Upload image (use crop tool)", tool="editor", source="upload")
 
 
129
  else:
 
130
  img_in = gr.Image(type="pil", label="Upload pre-cropped indicator image (no editor available)")
 
131
  mass_in = gr.Number(label="Mass (g)", value=None, precision=0)
132
  time_in = gr.Number(label="Time (hr)", value=None, precision=1)
133
  temp_in = gr.Number(label="Temperature (°C)", value=None, precision=1)
134
+ use_meta = gr.Checkbox(label="Use metadata (time/temp) for TVB-N estimate if regressor missing", value=True)
 
135
  submit = gr.Button("Analyze")
136
 
137
  with gr.Column(scale=1):
138
+ out_cls = gr.Textbox(label="Freshness class")
139
  out_tvb = gr.Number(label="Estimated TVB-N (mg/100g)")
140
+ out_crop = gr.Image(label="Cropped indicator (echo)")
141
+ out_meta = gr.Textbox(label="Provided metadata (echo)")
142
 
143
+ submit.click(fn=analyze, inputs=[img_in, mass_in, time_in, temp_in, use_meta],
144
+ outputs=[out_cls, out_tvb, out_crop, out_meta])
145
 
146
  if __name__ == "__main__":
147
+ demo.launch(server_name="0.0.0.0", server_port=7860)