Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
import gradio as gr
|
| 4 |
+
from skimage.morphology import convex_hull_image
|
| 5 |
+
|
| 6 |
+
# =========================================================
|
| 7 |
+
# Utility Functions
|
| 8 |
+
# =========================================================
|
| 9 |
+
|
| 10 |
+
def orient_to_portrait(img):
|
| 11 |
+
h, w = img.shape[:2]
|
| 12 |
+
if w > h:
|
| 13 |
+
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
|
| 14 |
+
return img
|
| 15 |
+
|
| 16 |
+
def analyze_cv_only(image, threshold_pct, min_defect_size):
|
| 17 |
+
"""
|
| 18 |
+
Simulates the ML pipeline using traditional image processing.
|
| 19 |
+
"""
|
| 20 |
+
if image is None:
|
| 21 |
+
return None, "0.0%", "0.0%", None, None
|
| 22 |
+
|
| 23 |
+
# 1. Preprocessing
|
| 24 |
+
img_color = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
| 25 |
+
img_color = orient_to_portrait(img_color)
|
| 26 |
+
gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
|
| 27 |
+
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
| 28 |
+
|
| 29 |
+
# 2. Simulate Print Segmentation (Thresholding)
|
| 30 |
+
# Instead of a model, we use simple binary thresholding
|
| 31 |
+
val = int((threshold_pct / 100.0) * 255)
|
| 32 |
+
_, print_mask = cv2.threshold(blurred, val, 255, cv2.THRESH_BINARY_INV)
|
| 33 |
+
|
| 34 |
+
# Find largest contour to represent the print
|
| 35 |
+
contours, _ = cv2.findContours(print_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 36 |
+
if not contours:
|
| 37 |
+
return image, "0.0%", "0.0%", None, None
|
| 38 |
+
|
| 39 |
+
largest_cnt = max(contours, key=cv2.contourArea)
|
| 40 |
+
print_mask_clean = np.zeros_like(print_mask)
|
| 41 |
+
cv2.drawContours(print_mask_clean, [largest_cnt], -1, 255, thickness=-1)
|
| 42 |
+
|
| 43 |
+
# 3. Simulate Defect Detection (Edge Detection within the print)
|
| 44 |
+
# Detects high-contrast areas (cracks/voids) inside the print mask
|
| 45 |
+
edges = cv2.Canny(blurred, 50, 150)
|
| 46 |
+
defect_mask = cv2.bitwise_and(edges, print_mask_clean)
|
| 47 |
+
|
| 48 |
+
# Filter defects by size
|
| 49 |
+
final_defect_mask = np.zeros_like(defect_mask)
|
| 50 |
+
def_cnts, _ = cv2.findContours(defect_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 51 |
+
for cnt in def_cnts:
|
| 52 |
+
if cv2.contourArea(cnt) >= min_defect_size:
|
| 53 |
+
cv2.drawContours(final_defect_mask, [cnt], -1, 255, thickness=-1)
|
| 54 |
+
|
| 55 |
+
# 4. Metrics
|
| 56 |
+
# Integrity: Ratio of print to its convex hull
|
| 57 |
+
try:
|
| 58 |
+
hull = convex_hull_image(print_mask_clean > 0)
|
| 59 |
+
integrity_score = np.sum(print_mask_clean > 0) / np.sum(hull)
|
| 60 |
+
except:
|
| 61 |
+
integrity_score = 0.0
|
| 62 |
+
|
| 63 |
+
# Quality: Inverse of defect area
|
| 64 |
+
print_area = np.sum(print_mask_clean > 0)
|
| 65 |
+
defect_area = np.sum(final_defect_mask > 0)
|
| 66 |
+
quality_score = 100.0 - ((defect_area / print_area) * 100.0) if print_area > 0 else 0.0
|
| 67 |
+
|
| 68 |
+
# 5. Visualization
|
| 69 |
+
vis = img_color.copy()
|
| 70 |
+
cv2.drawContours(vis, [largest_cnt], -1, (255, 0, 0), 2) # Blue outline
|
| 71 |
+
cv2.drawContours(vis, [cv2.convexHull(largest_cnt)], -1, (0, 0, 255), 2) # Red hull
|
| 72 |
+
|
| 73 |
+
# Draw defects in green
|
| 74 |
+
vis[final_defect_mask > 0] = [0, 255, 0]
|
| 75 |
+
|
| 76 |
+
# Save outputs
|
| 77 |
+
mask_path = "combined_mask.png"
|
| 78 |
+
cv2.imwrite(mask_path, final_defect_mask)
|
| 79 |
+
vis_path = "analysis_output.png"
|
| 80 |
+
cv2.imwrite(vis_path, vis)
|
| 81 |
+
|
| 82 |
+
return cv2.cvtColor(vis, cv2.COLOR_BGR2RGB), f"{integrity_score*100:.1f}%", f"{quality_score:.1f}%", vis_path, mask_path
|
| 83 |
+
|
| 84 |
+
# =========================================================
|
| 85 |
+
# UI Layout
|
| 86 |
+
# =========================================================
|
| 87 |
+
custom_css = """
|
| 88 |
+
.score-box { background-color: #f8f9fa; border-radius: 10px; padding: 15px; text-align: center; }
|
| 89 |
+
.score-value { font-size: 2rem !important; font-weight: 700 !important; color: #2b6cb0 !important; }
|
| 90 |
+
"""
|
| 91 |
+
|
| 92 |
+
with gr.Blocks(css=custom_css) as app:
|
| 93 |
+
gr.Markdown("# CV-Only Print Analysis (No ML)")
|
| 94 |
+
|
| 95 |
+
with gr.Row():
|
| 96 |
+
with gr.Column():
|
| 97 |
+
img_input = gr.Image(type="numpy", label="Input Image")
|
| 98 |
+
thresh_slider = gr.Slider(50, 255, value=127, label="Brightness Threshold")
|
| 99 |
+
size_slider = gr.Slider(0, 500, value=20, label="Min Defect Size")
|
| 100 |
+
run_btn = gr.Button("Analyze", variant="primary")
|
| 101 |
+
|
| 102 |
+
with gr.Column():
|
| 103 |
+
img_output = gr.Image(label="Analysis Result")
|
| 104 |
+
with gr.Row():
|
| 105 |
+
area_out = gr.Markdown("0.0%", elem_classes=["score-value"])
|
| 106 |
+
surface_out = gr.Markdown("0.0%", elem_classes=["score-value"])
|
| 107 |
+
|
| 108 |
+
run_btn.click(
|
| 109 |
+
fn=analyze_cv_only,
|
| 110 |
+
inputs=[img_input, thresh_slider, size_slider],
|
| 111 |
+
outputs=[img_output, area_out, surface_out]
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
app.queue().launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)
|