Sanjay1905 commited on
Commit
97aea32
Β·
verified Β·
1 Parent(s): ef7b344

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +393 -0
app.py ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import numpy as np
4
+ import json
5
+ import os
6
+ import tempfile
7
+ import shutil
8
+ from PIL import Image
9
+ from collections import Counter
10
+ from inference_sdk import InferenceHTTPClient
11
+ import easyocr
12
+
13
+ # ─────────────────────────────────────────────
14
+ # IMPORTS FROM OUR PIPELINE
15
+ # ─────────────────────────────────────────────
16
+ from detector import run_detection, detect_traces, draw_detections
17
+ from ocr import run_ocr_on_detections, print_ocr_summary
18
+ from netlist import (assign_reference_designators,
19
+ extract_trace_mask,
20
+ find_trace_connections,
21
+ find_proximity_connections,
22
+ build_nets)
23
+ from kicad_writer import generate_kicad_schematic
24
+
25
+ # ─────────────────────────────────────────────
26
+ # GLOBAL OCR READER (load once)
27
+ # ─────────────────────────────────────────────
28
+ print("[->] Loading EasyOCR...")
29
+ ocr_reader = easyocr.Reader(['en'], gpu=False) # CPU for HuggingFace
30
+ print("[OK] EasyOCR ready")
31
+
32
+
33
+ # ─────────────────────────────────────────────
34
+ # HELPER β€” numpy image to PIL
35
+ # ─────────────────────────────────────────────
36
+ def to_pil(img_bgr: np.ndarray) -> Image.Image:
37
+ return Image.fromarray(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))
38
+
39
+
40
+ # ─────────────────────────────────────────────
41
+ # TAB 1 β€” COMPONENT DETECTION
42
+ # ─────────────────────────────────────────────
43
+ def run_detection_tab(image: Image.Image):
44
+ if image is None:
45
+ return None, "❌ Please upload a PCB image first.", "{}"
46
+
47
+ # Save uploaded image to temp file
48
+ tmp_dir = tempfile.mkdtemp()
49
+ img_path = os.path.join(tmp_dir, "input.jpg")
50
+ image.save(img_path)
51
+
52
+ try:
53
+ # Run Roboflow detection
54
+ detections = run_detection(img_path)
55
+
56
+ if not detections:
57
+ return image, "⚠️ No components detected. Try a clearer PCB image.", "{}"
58
+
59
+ # Draw detections
60
+ img_bgr = cv2.imread(img_path)
61
+ annotated = draw_detections(img_bgr, detections)
62
+ result_pil= to_pil(annotated)
63
+
64
+ # Build summary text
65
+ counts = Counter(d['label'] for d in detections)
66
+ summary = f"βœ… **{len(detections)} components detected**\n\n"
67
+ summary += "| Component | Count |\n|-----------|-------|\n"
68
+ for label, count in sorted(counts.items(), key=lambda x: -x[1]):
69
+ summary += f"| {label} | {count} |\n"
70
+
71
+ # Save detections to JSON for next tabs
72
+ det_json = json.dumps({
73
+ "image_path": img_path,
74
+ "components": [
75
+ {**d, "bbox": list(d["bbox"])}
76
+ for d in detections
77
+ ]
78
+ }, indent=2)
79
+
80
+ return result_pil, summary, det_json
81
+
82
+ except Exception as e:
83
+ return image, f"❌ Error: {str(e)}", "{}"
84
+
85
+
86
+ # ─────────────────────────────────────────────
87
+ # TAB 2 β€” OCR / PART NUMBER READING
88
+ # ─────────────────────────────────────────────
89
+ def run_ocr_tab(det_json: str):
90
+ if not det_json or det_json == "{}":
91
+ return "⚠️ Run Detection first!", "{}"
92
+
93
+ try:
94
+ data = json.loads(det_json)
95
+ img_path = data.get("image_path")
96
+ detections = data.get("components", [])
97
+
98
+ for d in detections:
99
+ d['bbox'] = tuple(d['bbox'])
100
+
101
+ IC_LABELS = ['ic', 'transistor', 'clock', 'display']
102
+
103
+ # Run OCR using global reader
104
+ img = cv2.imread(img_path)
105
+ ih, iw = img.shape[:2]
106
+ PADDING = 10
107
+ updated = []
108
+
109
+ for det in detections:
110
+ label = det['label']
111
+ if label not in IC_LABELS:
112
+ det['ocr_text'] = []
113
+ det['part_number'] = "N/A"
114
+ updated.append(det)
115
+ continue
116
+
117
+ x1, y1, x2, y2 = det['bbox']
118
+ x1p = max(0, x1 - PADDING)
119
+ y1p = max(0, y1 - PADDING)
120
+ x2p = min(iw, x2 + PADDING)
121
+ y2p = min(ih, y2 + PADDING)
122
+ patch = img[y1p:y2p, x1p:x2p]
123
+
124
+ if patch.size == 0:
125
+ det['ocr_text'] = []
126
+ det['part_number'] = "unknown"
127
+ updated.append(det)
128
+ continue
129
+
130
+ # Upscale for better OCR
131
+ h, w = patch.shape[:2]
132
+ scale = 3 if max(h, w) < 100 else 2
133
+ patch = cv2.resize(patch, (w*scale, h*scale),
134
+ interpolation=cv2.INTER_CUBIC)
135
+
136
+ results = ocr_reader.readtext(patch)
137
+ texts = [(t.strip(), round(c, 3))
138
+ for _, t, c in results
139
+ if c >= 0.4 and len(t.strip()) >= 2]
140
+
141
+ combined = " ".join(t for t, c in texts).strip()
142
+ det['ocr_text'] = texts
143
+ det['part_number'] = combined if combined else "unknown"
144
+ updated.append(det)
145
+
146
+ # Build output table
147
+ ic_dets = [d for d in updated if d['label'] in IC_LABELS]
148
+ identified = [d for d in ic_dets
149
+ if d.get('part_number', 'unknown') not in
150
+ ('unknown', 'N/A', '')]
151
+
152
+ summary = f"βœ… **OCR complete β€” {len(identified)}/{len(ic_dets)} ICs identified**\n\n"
153
+ summary += "| RefDes | Label | Part Number | Confidence |\n"
154
+ summary += "|--------|-------|-------------|------------|\n"
155
+
156
+ for i, det in enumerate(ic_dets):
157
+ ref = f"U{i+1}"
158
+ part = det.get('part_number', 'unknown')
159
+ conf = det['confidence']
160
+ summary += f"| {ref} | {det['label']} | {part} | {conf:.0%} |\n"
161
+
162
+ # Pass updated detections forward
163
+ out_json = json.dumps({
164
+ "image_path": img_path,
165
+ "components": [
166
+ {**d,
167
+ "bbox": list(d["bbox"]),
168
+ "ocr_text": [[t, c] for t, c in d.get("ocr_text", [])]}
169
+ for d in updated
170
+ ]
171
+ }, indent=2)
172
+
173
+ return summary, out_json
174
+
175
+ except Exception as e:
176
+ return f"❌ Error: {str(e)}", "{}"
177
+
178
+
179
+ # ─────────────────────────────────────────────
180
+ # TAB 3 β€” NETLIST GENERATION
181
+ # ─────────────────────────────────────────────
182
+ def run_netlist_tab(ocr_json: str):
183
+ if not ocr_json or ocr_json == "{}":
184
+ return "⚠️ Run OCR first!", "{}", None
185
+
186
+ try:
187
+ data = json.loads(ocr_json)
188
+ img_path = data.get("image_path")
189
+ detections = data.get("components", [])
190
+
191
+ for d in detections:
192
+ d['bbox'] = tuple(d['bbox'])
193
+
194
+ img = cv2.imread(img_path)
195
+ components = assign_reference_designators(detections)
196
+ trace_mask = extract_trace_mask(img)
197
+ trace_conn = find_trace_connections(components, trace_mask, img.shape)
198
+ prox_conn = find_proximity_connections(components, trace_conn)
199
+ all_conn = trace_conn + prox_conn
200
+ nets = build_nets(all_conn)
201
+
202
+ # Save netlist JSON to temp file
203
+ tmp_dir = os.path.dirname(img_path)
204
+ netlist_path = os.path.join(tmp_dir, "netlist.json")
205
+
206
+ out_data = {
207
+ "total_components": len(components),
208
+ "total_connections": len(all_conn),
209
+ "total_nets": len(nets),
210
+ "components": [
211
+ {**c,
212
+ "bbox": list(c["bbox"]),
213
+ "ocr_text": [[t, conf] for t, conf
214
+ in c.get("ocr_text", [])]}
215
+ for c in components
216
+ ],
217
+ "connections": [
218
+ {"from": a, "to": b, "method": m}
219
+ for a, b, m in all_conn
220
+ ],
221
+ "nets": nets
222
+ }
223
+
224
+ with open(netlist_path, "w") as f:
225
+ json.dump(out_data, f, indent=2)
226
+
227
+ # Build summary
228
+ trace_c = len([c for c in all_conn if c[2] == "trace"])
229
+ prox_c = len([c for c in all_conn if c[2] == "proximity"])
230
+
231
+ summary = f"βœ… **Netlist generated successfully**\n\n"
232
+ summary += f"- **Components:** {len(components)}\n"
233
+ summary += f"- **Connections:** {len(all_conn)} "
234
+ summary += f"({trace_c} via traces, {prox_c} via proximity)\n"
235
+ summary += f"- **Nets:** {len(nets)}\n\n"
236
+ summary += "| Net | Members |\n|-----|--------|\n"
237
+ for net_name, members in nets.items():
238
+ summary += f"| {net_name} | {', '.join(members[:5])}"
239
+ if len(members) > 5:
240
+ summary += f" ... +{len(members)-5} more"
241
+ summary += " |\n"
242
+
243
+ return summary, json.dumps({"netlist_path": netlist_path}), netlist_path
244
+
245
+ except Exception as e:
246
+ return f"❌ Error: {str(e)}", "{}", None
247
+
248
+
249
+ # ────────────────────────��────────────────────
250
+ # TAB 4 β€” KICAD SCHEMATIC OUTPUT
251
+ # ─────────────────────────────────────────────
252
+ def run_kicad_tab(netlist_ref: str):
253
+ if not netlist_ref or netlist_ref == "{}":
254
+ return "⚠️ Run Netlist generation first!", None
255
+
256
+ try:
257
+ data = json.loads(netlist_ref)
258
+ netlist_path = data.get("netlist_path")
259
+
260
+ if not netlist_path or not os.path.exists(netlist_path):
261
+ return "❌ Netlist file not found. Re-run previous steps.", None
262
+
263
+ # Generate KiCAD schematic
264
+ tmp_dir = os.path.dirname(netlist_path)
265
+ sch_path = os.path.join(tmp_dir, "schematic.kicad_sch")
266
+
267
+ generate_kicad_schematic(netlist_path, sch_path)
268
+
269
+ summary = f"βœ… **KiCAD schematic generated!**\n\n"
270
+ summary += f"- File: `schematic.kicad_sch`\n"
271
+ summary += f"- Format: KiCAD 6/7 compatible\n\n"
272
+ summary += "**How to open:**\n"
273
+ summary += "1. Download the file below\n"
274
+ summary += "2. Open KiCAD β†’ File β†’ Open Schematic\n"
275
+ summary += " OR drag into [kicanvas.org](https://kicanvas.org) for instant preview\n"
276
+
277
+ return summary, sch_path
278
+
279
+ except Exception as e:
280
+ return f"❌ Error: {str(e)}", None
281
+
282
+
283
+ # ─────────────────────────────────────────────
284
+ # BUILD GRADIO UI
285
+ # ─────────────────────────────────────────────
286
+ def build_ui():
287
+ with gr.Blocks(
288
+ title="PCB Image β†’ Schematic",
289
+ theme=gr.themes.Soft(),
290
+ css="""
291
+ .tab-header { font-size: 1.1em; font-weight: bold; }
292
+ .output-panel { background: #1a1a2e; border-radius: 8px; }
293
+ """
294
+ ) as demo:
295
+
296
+ # ── Header ──
297
+ gr.Markdown("""
298
+ # πŸ”Œ PCB Image β†’ Schematic
299
+ ### Convert a PCB photo into a KiCAD schematic automatically
300
+ Upload a PCB image and step through each stage of the pipeline.
301
+ """)
302
+
303
+ # ── Shared state between tabs ──
304
+ detection_state = gr.State("{}")
305
+ ocr_state = gr.State("{}")
306
+ netlist_state = gr.State("{}")
307
+
308
+ # ── Tab 1: Detection ──
309
+ with gr.Tab("πŸ“· 1 β€” Component Detection"):
310
+ gr.Markdown("Upload a PCB image or click one of the example images below.")
311
+ with gr.Row():
312
+ with gr.Column(scale=1):
313
+ img_input = gr.Image(type="pil", label="PCB Image")
314
+ detect_btn = gr.Button("πŸ” Detect Components", variant="primary")
315
+ gr.Examples(
316
+ examples=[
317
+ ["sample 1.jpg"],
318
+ ["sample 2.jpg"],
319
+ ["sample 3.jpg"],
320
+ ["sample 4.jpg"],
321
+ ["sample 5.jpg"],
322
+ ],
323
+ inputs=img_input,
324
+ label="πŸ“‚ Example PCB Images β€” click to load",
325
+ examples_per_page=5,
326
+ )
327
+ with gr.Column(scale=1):
328
+ detect_out = gr.Image(label="Detected Components")
329
+ detect_text = gr.Markdown()
330
+
331
+ detect_btn.click(
332
+ fn=run_detection_tab,
333
+ inputs=[img_input],
334
+ outputs=[detect_out, detect_text, detection_state]
335
+ )
336
+
337
+ # ── Tab 2: OCR ──
338
+ with gr.Tab("πŸ”€ 2 β€” Read IC Text"):
339
+ gr.Markdown("Reads part numbers from IC chips using OCR.")
340
+ ocr_btn = gr.Button("πŸ“– Run OCR on ICs", variant="primary")
341
+ ocr_text = gr.Markdown()
342
+
343
+ ocr_btn.click(
344
+ fn=run_ocr_tab,
345
+ inputs=[detection_state],
346
+ outputs=[ocr_text, ocr_state]
347
+ )
348
+
349
+ # ── Tab 3: Netlist ──
350
+ with gr.Tab("πŸ”— 3 β€” Generate Netlist"):
351
+ gr.Markdown("Finds connections between components using trace detection + proximity.")
352
+ netlist_btn = gr.Button("⚑ Generate Netlist", variant="primary")
353
+ netlist_text = gr.Markdown()
354
+ netlist_file = gr.File(label="Download Netlist JSON", visible=False)
355
+
356
+ netlist_btn.click(
357
+ fn=run_netlist_tab,
358
+ inputs=[ocr_state],
359
+ outputs=[netlist_text, netlist_state, netlist_file]
360
+ )
361
+
362
+ # ── Tab 4: KiCAD ──
363
+ with gr.Tab("πŸ“ 4 β€” KiCAD Schematic"):
364
+ gr.Markdown("Generates a KiCAD `.kicad_sch` file you can open in KiCAD or kicanvas.org")
365
+ kicad_btn = gr.Button("πŸ’Ύ Generate KiCAD File", variant="primary")
366
+ kicad_text = gr.Markdown()
367
+ kicad_file = gr.File(label="Download .kicad_sch")
368
+
369
+ kicad_btn.click(
370
+ fn=run_kicad_tab,
371
+ inputs=[netlist_state],
372
+ outputs=[kicad_text, kicad_file]
373
+ )
374
+
375
+ # ── Footer ──
376
+ gr.Markdown("""
377
+ ---
378
+ Built with Roboflow YOLOv8 Β· EasyOCR Β· OpenCV Β· KiCAD
379
+ """)
380
+
381
+ return demo
382
+
383
+
384
+ # ─────────────────────────────────────────────
385
+ # ENTRY POINT
386
+ # ─────────────────────────────────────────────
387
+ if __name__ == "__main__":
388
+ demo = build_ui()
389
+ demo.launch(
390
+ server_name="0.0.0.0",
391
+ server_port=7860,
392
+ share=False
393
+ )