Spaces:
Sleeping
Sleeping
| # app.py - backward-compatible SmartPack launcher (auto-detects Gradio editor support) | |
| import os, joblib, inspect | |
| import gradio as gr | |
| import numpy as np, pandas as pd, cv2 | |
| from PIL import Image | |
| # (keep your feature columns / helper functions) | |
| FEATURE_COLS = [ | |
| 'mean_R','mean_G','mean_B', | |
| 'std_R','std_G','std_B', | |
| 'mean_H','mean_S','mean_V', | |
| 'std_H','std_S','std_V', | |
| 'brightness','ratio_R_G','ratio_R_B' | |
| ] | |
| # load models (example) | |
| clf = None | |
| rfr = None | |
| for fname, varname in (("final_rfc.joblib","clf"), ("final_rfr.joblib","rfr")): | |
| if os.path.exists(fname): | |
| try: | |
| loaded = joblib.load(fname) | |
| if fname.endswith("rfc.joblib"): clf = loaded | |
| else: rfr = loaded | |
| print("[MODEL] Loaded:", fname) | |
| except Exception as e: | |
| print("[MODEL] Error loading", fname, ":", e) | |
| # small helper to detect gr.Image params | |
| def gr_image_supports_editor(): | |
| try: | |
| sig = inspect.signature(gr.Image.__init__) | |
| params = sig.parameters | |
| return ('tool' in params) and ('source' in params) | |
| except Exception: | |
| return False | |
| SUPPORTS_EDITOR = gr_image_supports_editor() | |
| print("Gradio version:", getattr(gr, "__version__", "unknown"), "Editor support:", SUPPORTS_EDITOR) | |
| # --- reuse your feature extraction & tvb helper (kept concise here) --- | |
| def extract_features(pil_img): | |
| arr = np.array(pil_img.convert("RGB")) | |
| mean_rgb = arr.mean(axis=(0,1)) | |
| std_rgb = arr.std(axis=(0,1)) | |
| hsv = cv2.cvtColor(arr, cv2.COLOR_RGB2HSV) | |
| mean_hsv = hsv.mean(axis=(0,1)) | |
| std_hsv = hsv.std(axis=(0,1)) | |
| R,G,B = mean_rgb | |
| brightness = 0.299*R + 0.587*G + 0.114*B | |
| feats = { | |
| 'mean_R':float(R),'mean_G':float(G),'mean_B':float(B), | |
| 'std_R':float(std_rgb[0]),'std_G':float(std_rgb[1]),'std_B':float(std_rgb[2]), | |
| 'mean_H':float(mean_hsv[0]),'mean_S':float(mean_hsv[1]),'mean_V':float(mean_hsv[2]), | |
| 'std_H':float(std_hsv[0]),'std_S':float(std_hsv[1]),'std_V':float(std_hsv[2]), | |
| 'brightness':float(brightness), | |
| 'ratio_R_G':float(R/(G+1e-6)), | |
| 'ratio_R_B':float(R/(B+1e-6)) | |
| } | |
| return feats | |
| def literature_tvb_estimate(time_hr, temp_C): | |
| days = float(time_hr) / 24.0 | |
| if temp_C >= 25: | |
| if days <= 0: return 13.0 | |
| if days <= 5: return 13.0 + (days/5.0)*(35-13) | |
| return 70.0 + (days-5.0)*3.0 | |
| elif temp_C >= 10: | |
| if days <= 0: return 13.0 | |
| if days <= 3: return 13.0 + (days/3.0)*(30-13) | |
| return 30.0 + (days-3.0)*1.5 | |
| elif temp_C >= 4: | |
| if days <= 0: return 13.0 | |
| if days <= 7: return 13.0 + (days/7.0)*(30-13) | |
| return 30.0 + (days-7.0)*0.5 | |
| else: | |
| if days <= 0: return 13.0 | |
| return 13.0 + days * 0.1 | |
| def tvb_to_class_label(tvb_val): | |
| if tvb_val is None: return None | |
| if tvb_val < 20: return "Fresh" | |
| elif tvb_val < 30: return "Mildly Spoiled" | |
| else: return "Spoiled" | |
| # main analyze function (same outputs as your earlier UI) | |
| def analyze(image, mass_g, time_hr, temperature_C, use_meta_for_tvb): | |
| if image is None: | |
| return "No image uploaded", None, None, "No image" | |
| # If editor supported, the image is expected to be already cropped by user. | |
| # Otherwise we attempt naive autocrop center-crop fallback (you can replace with your autocrop). | |
| img_pil = image if isinstance(image, Image.Image) else Image.fromarray(image) | |
| feats = extract_features(img_pil) | |
| X = pd.DataFrame([feats]) | |
| # classification | |
| cls_pred = "Classifier not available" | |
| if clf is not None: | |
| try: | |
| cls_pred = str(clf.predict(X[FEATURE_COLS])[0]) | |
| except Exception as e: | |
| cls_pred = f"Classifier error: {e}" | |
| # tvb | |
| tvb = None | |
| if rfr is not None: | |
| try: | |
| tvb = float(rfr.predict(X[FEATURE_COLS])[0]) | |
| except Exception: | |
| tvb = None | |
| if (tvb is None) and use_meta_for_tvb and (time_hr is not None) and (temperature_C is not None): | |
| try: | |
| tvb = literature_tvb_estimate(time_hr, temperature_C) | |
| except Exception: | |
| tvb = None | |
| meta_msg = f"mass_g={mass_g} time_hr={time_hr} temp_C={temperature_C}" | |
| return cls_pred, (None if tvb is None else float(tvb)), img_pil, meta_msg | |
| # Build UI depending on support | |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# SmartPack — Mackerel Freshness Detector") | |
| if SUPPORTS_EDITOR: | |
| gr.Markdown("Editor available: use the crop tool (pencil icon) to crop the indicator.") | |
| else: | |
| 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).") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| if SUPPORTS_EDITOR: | |
| img_in = gr.Image(type="pil", label="Upload image (use crop tool)", tool="editor", source="upload") | |
| else: | |
| img_in = gr.Image(type="pil", label="Upload pre-cropped indicator image (no editor available)") | |
| mass_in = gr.Number(label="Mass (g)", value=None, precision=0) | |
| time_in = gr.Number(label="Time (hr)", value=None, precision=1) | |
| temp_in = gr.Number(label="Temperature (°C)", value=None, precision=1) | |
| use_meta = gr.Checkbox(label="Use metadata (time/temp) for TVB-N estimate if regressor missing", value=True) | |
| submit = gr.Button("Analyze") | |
| with gr.Column(scale=1): | |
| out_cls = gr.Textbox(label="Freshness class") | |
| out_tvb = gr.Number(label="Estimated TVB-N (mg/100g)") | |
| out_crop = gr.Image(label="Cropped indicator (echo)") | |
| out_meta = gr.Textbox(label="Provided metadata (echo)") | |
| submit.click(fn=analyze, inputs=[img_in, mass_in, time_in, temp_in, use_meta], | |
| outputs=[out_cls, out_tvb, out_crop, out_meta]) | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860) |