Download app.py from Swaroop05/Breaker: direct link, hf CLI and curl.
- Browser
- Download file 7.74 kB
-
https://huggingface.co/spaces/Swaroop05/Breaker/resolve/main/app.py
- Command line
-
hf download hf://spaces/Swaroop05/Breaker/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Swaroop05/Breaker/resolve/main/app.py
7.74 kB
| import os, cv2, re, base64 | |
| import numpy as np | |
| import pandas as pd | |
| import gradio as gr | |
| from roboflow import Roboflow | |
| from openai import OpenAI | |
| from openpyxl import load_workbook | |
| # ================= CONFIG ================= | |
| ROBOFLOW_API_KEY = "uP19IAi98TqwLvHmNB8V" | |
| ROBOFLOW_PROJECT = "braker3" | |
| ROBOFLOW_VERSION = 6 | |
| CONF_THRESHOLD = 0.35 | |
| IOU_THRESHOLD = 0.4 | |
| client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) | |
| rf = Roboflow(api_key=ROBOFLOW_API_KEY) | |
| model = rf.workspace().project(ROBOFLOW_PROJECT).version(ROBOFLOW_VERSION).model | |
| # ================= CONSTANTS ================= | |
| SPEC_JP = { | |
| "Manufacture Name": "メーカー", | |
| "Circuit Name": "回路番号", | |
| "Load Name": "負荷名称", | |
| "Breaking Capacity": "遮断容量", | |
| "AF": "フレーム(AF)", | |
| "AT": "トリップ(AT)" | |
| } | |
| MANUFACTURER_MAP = { | |
| "MITSUBISHI ELECTRIC": "三菱電機", | |
| "SIEMENS": "SIEMENS", | |
| "SCHNEIDER ELECTRIC": "SCHNEIDER ELECTRIC", | |
| "ABB": "ABB", | |
| "LS ELECTRIC": "LS ELECTRIC" | |
| } | |
| VALID_BREAKING_CAPACITY = {"6","10","15","25","36","50","65","85"} | |
| DEFAULT_BREAKING_CAPACITY = "85" # ← your dataset truth | |
| # ================= IMAGE ================= | |
| def prepare_for_roboflow(img, max_side=1024): | |
| h,w = img.shape[:2] | |
| scale = min(max_side/max(h,w),1.0) | |
| return cv2.resize(img,(int(w*scale),int(h*scale))) if scale<1 else img | |
| def crop(img,x1,y1,x2,y2,pad=20): | |
| h,w = img.shape[:2] | |
| return img[max(0,y1-pad):min(h,y2+pad), max(0,x1-pad):min(w,x2+pad)] | |
| def enhance(img): | |
| g = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) | |
| g = cv2.resize(g,None,fx=3,fy=3,interpolation=cv2.INTER_CUBIC) | |
| clahe = cv2.createCLAHE(2.0,(8,8)) | |
| return cv2.cvtColor(clahe.apply(g), cv2.COLOR_GRAY2BGR) | |
| def enhance_breaking_capacity(img): | |
| g = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) | |
| g = cv2.resize(g,None,fx=4,fy=4,interpolation=cv2.INTER_CUBIC) | |
| g = cv2.adaptiveThreshold( | |
| g,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, | |
| cv2.THRESH_BINARY,31,2 | |
| ) | |
| return cv2.cvtColor(g, cv2.COLOR_GRAY2BGR) | |
| def img_to_base64(img): | |
| _,buf = cv2.imencode(".jpg",img) | |
| return base64.b64encode(buf).decode() | |
| # ================= TEXT ================= | |
| def remove_spaces_only(text): | |
| return re.sub(r"\s+", "", str(text)) if text else "" | |
| def extract_digits(text): | |
| nums = re.findall(r"\d+",str(text)) | |
| return nums[-1] if nums else "" | |
| def clean_manufacturer(text): | |
| t=text.upper() | |
| for k in MANUFACTURER_MAP: | |
| if k in t: | |
| return k | |
| return "" | |
| def normalize_breaking_capacity(text): | |
| nums = re.findall(r"\d+",str(text)) | |
| for n in nums: | |
| if n in VALID_BREAKING_CAPACITY: | |
| return n | |
| return DEFAULT_BREAKING_CAPACITY | |
| # ================= GPT OCR ================= | |
| def gpt_ocr(label,img): | |
| if label == "Breaking Capacity": | |
| img = enhance_breaking_capacity(img) | |
| else: | |
| img = enhance(img) | |
| b64 = img_to_base64(img) | |
| rules={ | |
| "Manufacture Name":"Return ONLY manufacturer name in English.", | |
| "Circuit Name":"Return EXACT text.", | |
| "Load Name":"Return EXACT text.", | |
| "AF":"Return ONLY number.", | |
| "AT":"Return ONLY number.", | |
| "Breaking Capacity":"Return ONLY kA number." | |
| } | |
| r = client.chat.completions.create( | |
| model="gpt-5.2", | |
| messages=[ | |
| {"role":"system","content":"Strict OCR engine"}, | |
| {"role":"user","content":[ | |
| {"type":"text","text":rules[label]}, | |
| {"type":"image_url","image_url":{"url":f"data:image/jpeg;base64,{b64}"}} | |
| ]} | |
| ], | |
| temperature=0 | |
| ) | |
| raw = r.choices[0].message.content.strip() | |
| if label == "Manufacture Name": | |
| return clean_manufacturer(raw) | |
| if label in ["Circuit Name","Load Name"]: | |
| return remove_spaces_only(raw) | |
| if label in ["AF","AT"]: | |
| return extract_digits(raw) | |
| if label == "Breaking Capacity": | |
| return normalize_breaking_capacity(raw) | |
| return raw | |
| # ================= EXCEL ================= | |
| def normalize_header(s): | |
| return str(s).replace("\n","").replace(" ","") | |
| def find_column(df,keys): | |
| for c in df.columns: | |
| for k in keys: | |
| if k in normalize_header(c): | |
| return c | |
| return None | |
| def verify_excel(excel,det): | |
| wb=load_workbook(excel,data_only=True) | |
| ws=wb["MCB"] | |
| raw=pd.DataFrame([list(r) for r in ws.iter_rows(values_only=True)]) | |
| raw.dropna(how="all",inplace=True) | |
| hdr=None | |
| for i in range(len(raw)): | |
| if "回路" in "".join(map(str,raw.iloc[i].values)): | |
| hdr=i; break | |
| if hdr is None: | |
| return pd.DataFrame([["回路番号","", "NO","ヘッダー不明"]], | |
| columns=["仕様","検出値","Excelに存在?","備考"]) | |
| df=raw.iloc[hdr+1:].copy() | |
| df.columns=raw.iloc[hdr] | |
| df.dropna(how="all",inplace=True) | |
| ccol=find_column(df,["回路番号","回路"]) | |
| target=None | |
| for _,r in df.iterrows(): | |
| if remove_spaces_only(r[ccol])==det.get("Circuit Name",""): | |
| target=r; break | |
| if target is None: | |
| return pd.DataFrame([["回路番号",det.get("Circuit Name",""),"NO","Excelに存在しない"]], | |
| columns=["仕様","検出値","Excelに存在?","備考"]) | |
| rows=[] | |
| for k,jp in SPEC_JP.items(): | |
| detv=det.get(k,"") | |
| col=find_column(df,[jp.replace("(","").replace(")",""),jp[:2]]) | |
| excelv=str(target[col]) if col else "" | |
| if k in ["Circuit Name","Load Name"]: | |
| ok=remove_spaces_only(detv)==remove_spaces_only(excelv) | |
| elif k=="Manufacture Name": | |
| ok=MANUFACTURER_MAP.get(detv,detv)==excelv | |
| else: | |
| ok=detv==excelv | |
| rows.append([jp,detv,"YES" if ok else "NO","" if ok else f"Excel値: {excelv}"]) | |
| return pd.DataFrame(rows,columns=["仕様","検出値","Excelに存在?","備考"]) | |
| # ================= PIPELINE ================= | |
| def run_pipeline(image,excel): | |
| if image is None: | |
| return None,pd.DataFrame(),pd.DataFrame(),None | |
| img=prepare_for_roboflow(image) | |
| preds=model.predict( | |
| img, | |
| confidence=int(CONF_THRESHOLD*100), | |
| overlap=int(IOU_THRESHOLD*100) | |
| ).json()["predictions"] | |
| best={} | |
| vis=img.copy() | |
| for p in preds: | |
| lab=p["class"] | |
| x,y,w,h=map(int,[p["x"],p["y"],p["width"],p["height"]]) | |
| x1,y1,x2,y2=x-w//2,y-h//2,x+w//2,y+h//2 | |
| cv2.rectangle(vis,(x1,y1),(x2,y2),(0,255,0),2) | |
| c=crop(img,x1,y1,x2,y2) | |
| if lab not in best or p["confidence"]>best[lab][0]: | |
| best[lab]=(p["confidence"],c) | |
| det={} | |
| rows=[] | |
| for lab,(_,c) in best.items(): | |
| v=gpt_ocr(lab,c) | |
| if v: | |
| det[lab]=v | |
| rows.append([lab,v]) | |
| return vis, pd.DataFrame(rows,columns=["Field","Extracted Text"]), verify_excel(excel,det), "verification_result.xlsx" | |
| # ================= UI ================= | |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# ⚡ Breaker Panel OCR & Verification") | |
| with gr.Row(): | |
| img_in=gr.Image(type="numpy",label="Upload Image") | |
| img_out=gr.Image(label="Detected Image") | |
| excel_in=gr.File(label="Upload Excel (MCB)",file_types=[".xlsx",".xlsm"]) | |
| btn=gr.Button("Run Verification",variant="primary") | |
| t1=gr.Dataframe(label="OCR Output") | |
| t2=gr.Dataframe(label="Verification Result") | |
| f=gr.File(label="Download Result") | |
| btn.click(run_pipeline,[img_in,excel_in],[img_out,t1,t2,f]) | |
| demo.launch() | |