| import numpy as np |
| import cv2 |
| import gradio as gr |
| from skimage.morphology import convex_hull_image |
|
|
| |
| |
| |
|
|
| def orient_to_portrait(img): |
| h, w = img.shape[:2] |
| if w > h: |
| img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) |
| return img |
|
|
| def analyze_cv_only(image, threshold_pct, min_defect_size): |
| """ |
| Simulates the ML pipeline using traditional image processing. |
| """ |
| if image is None: |
| return None, "0.0%", "0.0%", None, None |
|
|
| |
| img_color = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) |
| img_color = orient_to_portrait(img_color) |
| gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY) |
| blurred = cv2.GaussianBlur(gray, (5, 5), 0) |
|
|
| |
| |
| val = int((threshold_pct / 100.0) * 255) |
| _, print_mask = cv2.threshold(blurred, val, 255, cv2.THRESH_BINARY_INV) |
|
|
| |
| contours, _ = cv2.findContours(print_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| if not contours: |
| return image, "0.0%", "0.0%", None, None |
| |
| largest_cnt = max(contours, key=cv2.contourArea) |
| print_mask_clean = np.zeros_like(print_mask) |
| cv2.drawContours(print_mask_clean, [largest_cnt], -1, 255, thickness=-1) |
|
|
| |
| |
| edges = cv2.Canny(blurred, 50, 150) |
| defect_mask = cv2.bitwise_and(edges, print_mask_clean) |
| |
| |
| final_defect_mask = np.zeros_like(defect_mask) |
| def_cnts, _ = cv2.findContours(defect_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| for cnt in def_cnts: |
| if cv2.contourArea(cnt) >= min_defect_size: |
| cv2.drawContours(final_defect_mask, [cnt], -1, 255, thickness=-1) |
|
|
| |
| |
| try: |
| hull = convex_hull_image(print_mask_clean > 0) |
| integrity_score = np.sum(print_mask_clean > 0) / np.sum(hull) |
| except: |
| integrity_score = 0.0 |
|
|
| |
| print_area = np.sum(print_mask_clean > 0) |
| defect_area = np.sum(final_defect_mask > 0) |
| quality_score = 100.0 - ((defect_area / print_area) * 100.0) if print_area > 0 else 0.0 |
|
|
| |
| vis = img_color.copy() |
| cv2.drawContours(vis, [largest_cnt], -1, (255, 0, 0), 2) |
| cv2.drawContours(vis, [cv2.convexHull(largest_cnt)], -1, (0, 0, 255), 2) |
| |
| |
| vis[final_defect_mask > 0] = [0, 255, 0] |
|
|
| |
| mask_path = "combined_mask.png" |
| cv2.imwrite(mask_path, final_defect_mask) |
| vis_path = "analysis_output.png" |
| cv2.imwrite(vis_path, vis) |
|
|
| return cv2.cvtColor(vis, cv2.COLOR_BGR2RGB), f"{integrity_score*100:.1f}%", f"{quality_score:.1f}%", vis_path, mask_path |
|
|
| |
| |
| |
| custom_css = """ |
| .score-box { background-color: #f8f9fa; border-radius: 10px; padding: 15px; text-align: center; } |
| .score-value { font-size: 2rem !important; font-weight: 700 !important; color: #2b6cb0 !important; } |
| """ |
|
|
| with gr.Blocks(css=custom_css) as app: |
| gr.Markdown("# CV-Only Print Analysis (No ML)") |
| |
| with gr.Row(): |
| with gr.Column(): |
| img_input = gr.Image(type="numpy", label="Input Image") |
| thresh_slider = gr.Slider(50, 255, value=127, label="Brightness Threshold") |
| size_slider = gr.Slider(0, 500, value=20, label="Min Defect Size") |
| run_btn = gr.Button("Analyze", variant="primary") |
| |
| with gr.Column(): |
| img_output = gr.Image(label="Analysis Result") |
| with gr.Row(): |
| area_out = gr.Markdown("0.0%", elem_classes=["score-value"]) |
| surface_out = gr.Markdown("0.0%", elem_classes=["score-value"]) |
|
|
| run_btn.click( |
| fn=analyze_cv_only, |
| inputs=[img_input, thresh_slider, size_slider], |
| outputs=[img_output, area_out, surface_out] |
| ) |
|
|
| app.queue().launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False) |