# 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)