Ayesha352 commited on
Commit
9997a8e
·
verified ·
1 Parent(s): 06ffef5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -84
app.py CHANGED
@@ -5,121 +5,187 @@ import gradio as gr
5
  import os
6
  import xml.etree.ElementTree as ET
7
 
 
8
  def get_rotated_rect_corners(x, y, w, h, rotation_deg):
9
  rot_rad = np.deg2rad(rotation_deg)
10
- cos_r, sin_r = np.cos(rot_rad), np.sin(rot_rad)
11
- R = np.array([[cos_r, -sin_r], [sin_r, cos_r]])
12
- cx, cy = x + w/2, y + h/2
13
- local_corners = np.array([[-w/2,-h/2],[w/2,-h/2],[w/2,h/2],[-w/2,h/2]])
 
 
 
 
 
 
 
 
14
  rotated_corners = np.dot(local_corners, R.T)
15
- return (rotated_corners + np.array([cx,cy])).astype(np.float32)
 
16
 
17
  def preprocess_gray_clahe(img):
18
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
19
- clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
20
  return clahe.apply(gray)
21
 
22
  def detect_and_match(img1_gray, img2_gray, method="SIFT", ratio_thresh=0.78):
23
- if method=="SIFT": detector=cv2.SIFT_create(nfeatures=5000); matcher=cv2.BFMatcher(cv2.NORM_L2)
24
- elif method=="ORB": detector=cv2.ORB_create(5000); matcher=cv2.BFMatcher(cv2.NORM_HAMMING)
25
- elif method=="BRISK": detector=cv2.BRISK_create(); matcher=cv2.BFMatcher(cv2.NORM_HAMMING)
26
- elif method=="KAZE": detector=cv2.KAZE_create(); matcher=cv2.BFMatcher(cv2.NORM_L2)
27
- elif method=="AKAZE": detector=cv2.AKAZE_create(); matcher=cv2.BFMatcher(cv2.NORM_HAMMING)
28
- else: return None,None,[]
29
-
30
- kp1, des1 = detector.detectAndCompute(img1_gray,None)
31
- kp2, des2 = detector.detectAndCompute(img2_gray,None)
32
- if des1 is None or des2 is None: return None,None,[]
33
-
34
- raw_matches = matcher.knnMatch(des1,des2,k=2)
35
- good = [m for m,n in raw_matches if m.distance < ratio_thresh*n.distance]
36
- return kp1, kp2, good
37
-
38
- def parse_xml_points(xml_file):
39
- tree = ET.parse(xml_file)
40
- root = tree.getroot()
41
- points=[]
42
- for pt_type in ["TopLeft","TopRight","BottomLeft","BottomRight"]:
43
- elem=root.find(f".//point[@type='{pt_type}']")
44
- points.append([float(elem.get("x")), float(elem.get("y"))])
45
- return np.array(points,dtype=np.float32).reshape(-1,2)
46
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  def homography_all_detectors(flat_file, persp_file, json_file, xml_file):
48
  flat_img = cv2.imread(flat_file)
49
  persp_img = cv2.imread(persp_file)
50
-
51
- # FIX: Use the file paths directly instead of .name
52
- mockup = json.load(open(json_file))
53
- roi_data = mockup["printAreas"][0]["position"]
54
- roi_x, roi_y = roi_data["x"], roi_data["y"]
55
- roi_w, roi_h = mockup["printAreas"][0]["width"], mockup["printAreas"][0]["height"]
56
- roi_rot_deg = mockup["printAreas"][0]["rotation"]
57
 
58
  flat_gray = preprocess_gray_clahe(flat_img)
59
  persp_gray = preprocess_gray_clahe(persp_img)
60
-
61
- # FIX: Use the file path directly instead of .name
62
- xml_points = parse_xml_points(xml_file)
63
 
64
- methods = ["SIFT","ORB","BRISK","KAZE","AKAZE"]
65
- gallery_files = []
66
  download_files = []
67
 
68
- for method in methods:
69
- kp1,kp2,good_matches = detect_and_match(flat_gray,persp_gray,method)
70
- if kp1 is None or kp2 is None or len(good_matches)<4: continue
71
-
72
- match_img = cv2.drawMatches(flat_img,kp1,persp_img,kp2,good_matches,None,flags=2)
73
-
74
- src_pts = np.float32([kp1[m.queryIdx].pt for m in good_matches]).reshape(-1,1,2)
75
- dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1,1,2)
76
- H,_ = cv2.findHomography(src_pts,dst_pts,cv2.RANSAC,5.0)
77
- if H is None: continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- roi_corners_flat = get_rotated_rect_corners(roi_x,roi_y,roi_w,roi_h,roi_rot_deg)
80
- roi_corners_persp = cv2.perspectiveTransform(roi_corners_flat.reshape(-1,1,2),H).reshape(-1,2)
81
- persp_roi = persp_img.copy()
82
- cv2.polylines(persp_roi,[roi_corners_persp.astype(int)],True,(0,255,0),2)
83
- for px,py in roi_corners_persp: cv2.circle(persp_roi,(int(px),int(py)),5,(255,0,0),-1)
84
-
85
- xml_gt_img = persp_img.copy()
86
- xml_mapped = cv2.perspectiveTransform(xml_points.reshape(-1,1,2),H).reshape(-1,2)
87
- for px,py in xml_mapped: cv2.circle(xml_gt_img,(int(px),int(py)),5,(0,0,255),-1)
88
-
89
- # Merge 2x2 grid (original resolution)
90
- top = np.hstack([flat_img, match_img])
91
- bottom = np.hstack([persp_roi, xml_gt_img])
92
- combined_grid = np.vstack([top, bottom])
93
-
94
- # Save combined grid as file
95
  base_name = os.path.splitext(os.path.basename(persp_file))[0]
96
- file_name = f"{base_name}_{method.lower()}.png"
97
- cv2.imwrite(file_name, combined_grid)
98
- gallery_files.append(file_name)
99
  download_files.append(file_name)
100
 
101
- # Ensure 5 download slots
102
  while len(download_files)<5: download_files.append(None)
103
- return gallery_files, download_files[0], download_files[1], download_files[2], download_files[3], download_files[4]
104
 
 
105
  iface = gr.Interface(
106
  fn=homography_all_detectors,
107
  inputs=[
108
  gr.Image(label="Upload Flat Image", type="filepath"),
109
  gr.Image(label="Upload Perspective Image", type="filepath"),
110
  gr.File(label="Upload mockup.json", file_types=[".json"]),
111
- gr.File(label="Upload XML file", file_types=[".xml"])
112
  ],
113
  outputs=[
114
- gr.Gallery(label="Results per Detector", show_label=True),
115
- gr.File(label="Download SIFT Result"),
116
- gr.File(label="Download ORB Result"),
117
- gr.File(label="Download BRISK Result"),
118
- gr.File(label="Download KAZE Result"),
119
- gr.File(label="Download AKAZE Result")
120
  ],
121
- title="Homography ROI Projection with Feature Matching & XML GT",
122
- description="Shows 4 views per detector. Original resolution kept; click on Gallery images to open full."
123
  )
124
-
125
- iface.launch()
 
5
  import os
6
  import xml.etree.ElementTree as ET
7
 
8
+ # ---------------- Helper functions ----------------
9
  def get_rotated_rect_corners(x, y, w, h, rotation_deg):
10
  rot_rad = np.deg2rad(rotation_deg)
11
+ cos_r = np.cos(rot_rad)
12
+ sin_r = np.sin(rot_rad)
13
+ R = np.array([[cos_r, -sin_r],
14
+ [sin_r, cos_r]])
15
+ cx = x + w/2
16
+ cy = y + h/2
17
+ local_corners = np.array([
18
+ [-w/2, -h/2],
19
+ [ w/2, -h/2],
20
+ [ w/2, h/2],
21
+ [-w/2, h/2]
22
+ ])
23
  rotated_corners = np.dot(local_corners, R.T)
24
+ corners = rotated_corners + np.array([cx, cy])
25
+ return corners.astype(np.float32)
26
 
27
  def preprocess_gray_clahe(img):
28
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
29
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
30
  return clahe.apply(gray)
31
 
32
  def detect_and_match(img1_gray, img2_gray, method="SIFT", ratio_thresh=0.78):
33
+ if method == "SIFT":
34
+ detector = cv2.SIFT_create(nfeatures=5000)
35
+ norm = cv2.NORM_L2
36
+ elif method == "ORB":
37
+ detector = cv2.ORB_create(5000)
38
+ norm = cv2.NORM_HAMMING
39
+ elif method == "BRISK":
40
+ detector = cv2.BRISK_create()
41
+ norm = cv2.NORM_HAMMING
42
+ elif method == "KAZE":
43
+ detector = cv2.KAZE_create()
44
+ norm = cv2.NORM_L2
45
+ elif method == "AKAZE":
46
+ detector = cv2.AKAZE_create()
47
+ norm = cv2.NORM_HAMMING
48
+ else:
49
+ return None, None, [], None
50
+
51
+ kp1, des1 = detector.detectAndCompute(img1_gray, None)
52
+ kp2, des2 = detector.detectAndCompute(img2_gray, None)
53
+ if des1 is None or des2 is None:
54
+ return None, None, [], None
55
+
56
+ matcher = cv2.BFMatcher(norm)
57
+ raw_matches = matcher.knnMatch(des1, des2, k=2)
58
+ good = [m for m,n in raw_matches if m.distance < ratio_thresh * n.distance]
59
+
60
+ matches_img = None
61
+ if len(good) >= 4:
62
+ matches_img = cv2.drawMatches(
63
+ cv2.cvtColor(img1_gray, cv2.COLOR_GRAY2BGR),
64
+ kp1,
65
+ cv2.cvtColor(img2_gray, cv2.COLOR_GRAY2BGR),
66
+ kp2,
67
+ good, None,
68
+ flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS
69
+ )
70
+ return kp1, kp2, good, matches_img
71
+
72
+ def add_title(img_bgr, title):
73
+ h, w = img_bgr.shape[:2]
74
+ bar = np.full((40, w, 3), 255, dtype=np.uint8)
75
+ cv2.putText(bar, title, (10, 28), cv2.FONT_HERSHEY_SIMPLEX,
76
+ 0.8, (0,0,0), 2, cv2.LINE_AA)
77
+ return np.vstack([bar, img_bgr])
78
+
79
+ def parse_xml_roi_points(xml_path):
80
+ """Parse your XML structure, return list of polygons (Nx2 points)."""
81
+ polys = []
82
+ try:
83
+ tree = ET.parse(xml_path)
84
+ root = tree.getroot()
85
+ # Transform ROI points (FourPoint)
86
+ for tr in root.findall(".//transform"):
87
+ pts = []
88
+ for p in tr.findall("point"):
89
+ x = float(p.get("x")); y = float(p.get("y"))
90
+ pts.append([x, y])
91
+ if len(pts) >= 3:
92
+ polys.append(np.array(pts, dtype=np.float32))
93
+ # Overlay polygons
94
+ for ov in root.findall(".//overlay"):
95
+ pts = []
96
+ for p in ov.findall("point"):
97
+ x = float(p.get("x")); y = float(p.get("y"))
98
+ pts.append([x, y])
99
+ if len(pts) >= 3:
100
+ polys.append(np.array(pts, dtype=np.float32))
101
+ except Exception as e:
102
+ print("XML parse error:", e)
103
+ return polys if polys else None
104
+
105
+ # ---------------- Main Function ----------------
106
  def homography_all_detectors(flat_file, persp_file, json_file, xml_file):
107
  flat_img = cv2.imread(flat_file)
108
  persp_img = cv2.imread(persp_file)
109
+ mockup = json.load(open(json_file.name))
110
+ roi = mockup["printAreas"][0]
111
+ roi_x, roi_y = roi["position"]["x"], roi["position"]["y"]
112
+ roi_w, roi_h = roi["width"], roi["height"]
113
+ roi_rot_deg = roi["rotation"]
114
+
115
+ xml_polys = parse_xml_roi_points(xml_file.name) if xml_file else None
116
 
117
  flat_gray = preprocess_gray_clahe(flat_img)
118
  persp_gray = preprocess_gray_clahe(persp_img)
 
 
 
119
 
120
+ results = []
 
121
  download_files = []
122
 
123
+ for method in ["SIFT","ORB","BRISK","KAZE","AKAZE"]:
124
+ kp1, kp2, good, matches_img = detect_and_match(flat_gray, persp_gray, method)
125
+ if kp1 is None or len(good) < 4:
126
+ continue
127
+
128
+ src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)
129
+ dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)
130
+ H, _ = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
131
+
132
+ # top-left = flat
133
+ top_left = add_title(flat_img.copy(), "Flat Image")
134
+
135
+ # top-right = feature matches
136
+ top_right = add_title(matches_img if matches_img is not None else flat_img.copy(),
137
+ "Feature Matches (Flat→Perspective)")
138
+
139
+ # bottom-left = homography ROI
140
+ bottom_left = persp_img.copy()
141
+ if H is not None:
142
+ roi_corners = get_rotated_rect_corners(roi_x, roi_y, roi_w, roi_h, roi_rot_deg)
143
+ roi_persp = cv2.perspectiveTransform(roi_corners.reshape(-1,1,2), H).reshape(-1,2)
144
+ cv2.polylines(bottom_left, [roi_persp.astype(int)], True, (0,255,0), 3)
145
+ bottom_left = add_title(bottom_left, "ROI via Homography (from JSON)")
146
+
147
+ # bottom-right = XML ROI
148
+ bottom_right = persp_img.copy()
149
+ if xml_polys:
150
+ for poly in xml_polys:
151
+ cv2.polylines(bottom_right, [poly.astype(int)], True, (0,0,255), 3)
152
+ else:
153
+ cv2.putText(bottom_right, "No XML ROI", (50,100),
154
+ cv2.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), 3)
155
+ bottom_right = add_title(bottom_right, "Ground Truth ROI (XML)")
156
+
157
+ # stack horizontally
158
+ top = np.hstack([top_left, top_right])
159
+ bottom = np.hstack([bottom_left, bottom_right])
160
+ composite = np.vstack([top, bottom])
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  base_name = os.path.splitext(os.path.basename(persp_file))[0]
163
+ file_name = f"{base_name}_{method.lower()}_grid.png"
164
+ cv2.imwrite(file_name, composite)
165
+ results.append((cv2.cvtColor(composite, cv2.COLOR_BGR2RGB), f"{method} Grid"))
166
  download_files.append(file_name)
167
 
 
168
  while len(download_files)<5: download_files.append(None)
169
+ return [results]+download_files[:5]
170
 
171
+ # ---------------- Gradio UI ----------------
172
  iface = gr.Interface(
173
  fn=homography_all_detectors,
174
  inputs=[
175
  gr.Image(label="Upload Flat Image", type="filepath"),
176
  gr.Image(label="Upload Perspective Image", type="filepath"),
177
  gr.File(label="Upload mockup.json", file_types=[".json"]),
178
+ gr.File(label="Upload Groundtruth.xml", file_types=[".xml"])
179
  ],
180
  outputs=[
181
+ gr.Gallery(label="Composite Grids (per Detector)", show_label=True),
182
+ gr.File(label="Download SIFT Grid"),
183
+ gr.File(label="Download ORB Grid"),
184
+ gr.File(label="Download BRISK Grid"),
185
+ gr.File(label="Download KAZE Grid"),
186
+ gr.File(label="Download AKAZE Grid")
187
  ],
188
+ title="Homography ROI vs XML ROI",
189
+ description="Each detector produces one 2×2 grid: Flat, Matches, Homography ROI, Ground Truth ROI."
190
  )
191
+ iface.launch()