File size: 5,786 Bytes
016e3a4
 
 
 
a3fd7df
016e3a4
 
 
 
 
 
bc98998
6b2ef87
 
 
 
 
 
d8f4143
6b2ef87
 
 
 
 
 
 
 
 
702fbba
6b2ef87
 
 
 
 
 
 
 
db5d973
6b2ef87
702fbba
6b2ef87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3fd7df
702fbba
 
 
6b2ef87
db5d973
6b2ef87
 
 
 
 
 
 
 
 
 
426c5c1
6b2ef87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b145630
 
d06b635
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import gradio as gr
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image
import numpy as np
import pickle
import os
import cv2
import logging
from sklearn.cluster import KMeans

def detect_connect_and_crop(image_source: Image.Image):
    """
    1. Detects bolt holes using Hough Transform.
    2. Separates them into Top and Bottom rows.
    3. Fits a straight horizontal line through the center of each row.
    4. Crops the region strictly between these two lines.

    """
    # Convert PIL Image to OpenCV format (RGB -> BGR usually, but we keep RGB for display)
    img_rgb = np.array(image_source)
    img_h, img_w = img_rgb.shape[:2]
    
    # Convert to Grayscale for detection
    gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
    
    # ── Step 1: Detect Circles (Bolt Holes) ─────────────────────────────────
    circles = cv2.HoughCircles(
        gray,
        cv2.HOUGH_GRADIENT,
        dp=1,            # Resolution ratio
        minDist=50,      # Min distance between holes (prevents double detection)
        param1=100,      # Canny edge threshold (higher = fewer edges)
        param2=30,       # Accumulator threshold (lower = more circles)
        minRadius=10,    # Min size of bolt hole
        maxRadius=40     # Max size of bolt hole
    )
    
    if circles is None:
        return image_source, image_source, "❌ No bolt holes detected. Adjust lighting or focus."
    
    # Convert coordinates to integers
    circles = np.round(circles[0]).astype(int)
    
    # ── Step 2: Separate into Top and Bottom Rows ───────────────────────────
    # We sort all circles by their Y (height) coordinate
    # The "Gap" in Y values tells us where the split between rows is.
    
    ys = [c[1] for c in circles]
    y_median = np.median(ys) # Find the middle point of the image's height logic
    
    # Split circles based on if they are above or below the median height
    top_row = [c for c in circles if c[1] < y_median]
    bot_row = [c for c in circles if c[1] >= y_median]
    
    if len(top_row) < 2 or len(bot_row) < 2:
        return image_source, image_source, "⚠️ Need at least 2 holes in both top and bottom rows to draw lines."

    # ── Step 3: Calculate Straight Lines (Average Y) ────────────────────────
    # We take the average Y of the top row to get a straight horizontal line
    y_top_line = int(np.mean([c[1] for c in top_row]))
    
    # We take the average Y of the bottom row
    y_bot_line = int(np.mean([c[1] for c in bot_row]))


    
    # ── Step 4: Visualization ───────────────────────────────────────────────
    vis_img = img_rgb.copy()
    
    # Colors
    LINE_COLOR = (0, 255, 0)     # Green
    HOLE_COLOR = (255, 0, 0)     # Red
    CENTER_COLOR = (255, 255, 0) # Yellow dots
    
    # Draw the straight horizontal lines across the full width
    cv2.line(vis_img, (0, y_top_line), (img_w, y_top_line), LINE_COLOR, 3)
    cv2.line(vis_img, (0, y_bot_line), (img_w, y_bot_line), LINE_COLOR, 3)
    
    # Draw the holes and their centers to show what we detected
    for (x, y, r) in circles:
        cv2.circle(vis_img, (x, y), r, HOLE_COLOR, 2)       # The hole outline
        cv2.circle(vis_img, (x, y), 3, CENTER_COLOR, -1)    # The center dot

    # ── Step 5: Crop the Region Between Lines ──────────────────────────────
    # Slice the array: img[y_start : y_end, x_start : x_end]
    # We add a tiny padding (e.g., +/- 0) or keep it exact.
    
    # Ensure y_top is actually above y_bot
    y_start, y_end = sorted([y_top_line, y_bot_line])
    
    # Safety check for crop size
    if y_end - y_start < 10:
        return Image.fromarray(vis_img), Image.fromarray(img_rgb), "⚠️ Calculated lines are too close."
        
    cropped_img = img_rgb[y_start:y_end, 0:img_w]
    
    # Generate Stats Text
    stats_text = (
        f"βœ… **Processing Complete**\n"
        f"β€’ Top Row Holes: {len(top_row)}\n"
        f"β€’ Bottom Row Holes: {len(bot_row)}\n"
        f"β€’ Top Line Y-Pos: {y_top_line} px\n"
        f"β€’ Bottom Line Y-Pos: {y_bot_line} px\n"
        f"β€’ Cropped Height: {y_end - y_start} px"
    )



    return Image.fromarray(vis_img), Image.fromarray(cropped_img), stats_text

# ── Gradio Interface ────────────────────────────────────────────────────────
with gr.Blocks(title="Engine Line Connector", theme=gr.themes.Default(primary_hue="blue")) as demo:
    gr.Markdown(
        """
        # πŸ“ Bolt-Center Line Connector
        ### Precision Cropping Tool
        1. Detects bolt holes.
        2. Draws a straight line through the average center of the top & bottom rows.
        3. Crops the saddle area exactly between these two lines.
        """
    )
    
    with gr.Row():
        input_image = gr.Image(type="pil", label="πŸ“· Upload Engine Block Image")




        
    run_button = gr.Button("πŸ”— Connect Centers & Crop", variant="primary")
    
    with gr.Row():
        output_vis = gr.Image(label="✏️ Visualized Lines & Centers")
        output_crop = gr.Image(label="βœ‚οΈ Final Cropped Area")
        
    stats_output = gr.Markdown()
    
    run_button.click(
        fn=detect_connect_and_crop,
        inputs=[input_image],
        outputs=[output_vis, output_crop, stats_output]
    )

if __name__ == "__main__":
    demo.launch()