Ayesha352 commited on
Commit
fc690df
·
verified ·
1 Parent(s): bca3724

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -193
app.py CHANGED
@@ -5,225 +5,139 @@ import gradio as gr
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 resize_to_height(img, target_height):
80
- """Resize image to target height while maintaining aspect ratio"""
81
- h, w = img.shape[:2]
82
- ratio = target_height / h
83
- new_width = int(w * ratio)
84
- return cv2.resize(img, (new_width, target_height))
85
-
86
- def parse_xml_roi_points(xml_path):
87
- """Parse your XML structure, return list of polygons (Nx2 points)."""
88
- if xml_path is None:
89
- return None
90
-
91
- polys = []
92
- try:
93
- tree = ET.parse(xml_path)
94
- root = tree.getroot()
95
- # Transform ROI points (FourPoint)
96
- for tr in root.findall(".//transform"):
97
- pts = []
98
- for p in tr.findall("point"):
99
- x = float(p.get("x")); y = float(p.get("y"))
100
- pts.append([x, y])
101
- if len(pts) >= 3:
102
- polys.append(np.array(pts, dtype=np.float32))
103
- # Overlay polygons
104
- for ov in root.findall(".//overlay"):
105
- pts = []
106
- for p in ov.findall("point"):
107
- x = float(p.get("x")); y = float(p.get("y"))
108
- pts.append([x, y])
109
- if len(pts) >= 3:
110
- polys.append(np.array(pts, dtype=np.float32))
111
- except Exception as e:
112
- print("XML parse error:", e)
113
- return polys if polys else None
114
 
115
  # ---------------- Main Function ----------------
116
  def homography_all_detectors(flat_file, persp_file, json_file, xml_file):
117
  flat_img = cv2.imread(flat_file)
118
  persp_img = cv2.imread(persp_file)
119
-
120
- # Use the file paths directly (no .name needed)
121
- mockup = json.load(open(json_file))
122
- roi = mockup["printAreas"][0]
123
- roi_x, roi_y = roi["position"]["x"], roi["position"]["y"]
124
- roi_w, roi_h = roi["width"], roi["height"]
125
- roi_rot_deg = roi["rotation"]
126
-
127
- xml_polys = parse_xml_roi_points(xml_file) if xml_file else None
128
 
129
  flat_gray = preprocess_gray_clahe(flat_img)
130
  persp_gray = preprocess_gray_clahe(persp_img)
 
131
 
132
- results = []
 
133
  download_files = []
134
 
135
- for method in ["SIFT","ORB","BRISK","KAZE","AKAZE"]:
136
- try:
137
- kp1, kp2, good, matches_img = detect_and_match(flat_gray, persp_gray, method)
138
- if kp1 is None or len(good) < 4:
139
- continue
140
-
141
- src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)
142
- dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)
143
- H, _ = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
144
-
145
- # top-left = flat
146
- top_left = add_title(flat_img.copy(), "Flat Image")
147
-
148
- # top-right = feature matches
149
- if matches_img is None:
150
- matches_img = flat_img.copy()
151
- top_right = add_title(matches_img, "Feature Matches (Flat→Perspective)")
152
-
153
- # bottom-left = homography ROI
154
- bottom_left = persp_img.copy()
155
- if H is not None:
156
- roi_corners = get_rotated_rect_corners(roi_x, roi_y, roi_w, roi_h, roi_rot_deg)
157
- roi_persp = cv2.perspectiveTransform(roi_corners.reshape(-1,1,2), H).reshape(-1,2)
158
- cv2.polylines(bottom_left, [roi_persp.astype(int)], True, (0,255,0), 3)
159
- bottom_left = add_title(bottom_left, "ROI via Homography (from JSON)")
160
-
161
- # bottom-right = XML ROI
162
- bottom_right = persp_img.copy()
163
- if xml_polys:
164
- for poly in xml_polys:
165
- cv2.polylines(bottom_right, [poly.astype(int)], True, (0,0,255), 3)
166
- else:
167
- cv2.putText(bottom_right, "No XML ROI", (50,100),
168
- cv2.FONT_HERSHEY_SIMPLEX, 2, (0,0,255), 3)
169
- bottom_right = add_title(bottom_right, "Ground Truth ROI (XML)")
170
-
171
- # Resize all images to the same height before stacking
172
- target_height = max(top_left.shape[0], top_right.shape[0],
173
- bottom_left.shape[0], bottom_right.shape[0])
174
-
175
- top_left_resized = resize_to_height(top_left, target_height)
176
- top_right_resized = resize_to_height(top_right, target_height)
177
- bottom_left_resized = resize_to_height(bottom_left, target_height)
178
- bottom_right_resized = resize_to_height(bottom_right, target_height)
179
-
180
- # stack horizontally
181
- top = np.hstack([top_left_resized, top_right_resized])
182
- bottom = np.hstack([bottom_left_resized, bottom_right_resized])
183
-
184
- # Make sure top and bottom have the same width
185
- min_width = min(top.shape[1], bottom.shape[1])
186
- top = cv2.resize(top, (min_width, top.shape[0]))
187
- bottom = cv2.resize(bottom, (min_width, bottom.shape[0]))
188
-
189
- composite = np.vstack([top, bottom])
190
-
191
- base_name = os.path.splitext(os.path.basename(persp_file))[0]
192
- file_name = f"{base_name}_{method.lower()}_grid.png"
193
- cv2.imwrite(file_name, composite)
194
- results.append((cv2.cvtColor(composite, cv2.COLOR_BGR2RGB), f"{method} Grid"))
195
- download_files.append(file_name)
196
- except Exception as e:
197
- print(f"Error in {method}: {str(e)}")
198
- continue
199
-
200
- while len(download_files) < 5:
201
- download_files.append(None)
202
-
203
- # Return the results in the correct format
204
- gallery_output = results
205
- file_outputs = download_files[:5]
206
-
207
- return [gallery_output] + file_outputs
208
 
209
  # ---------------- Gradio UI ----------------
210
  iface = gr.Interface(
211
  fn=homography_all_detectors,
212
  inputs=[
213
- gr.Image(label="Upload Flat Image", type="filepath"),
214
- gr.Image(label="Upload Perspective Image", type="filepath"),
215
- gr.File(label="Upload mockup.json", file_types=[".json"]),
216
- gr.File(label="Upload Groundtruth.xml", file_types=[".xml"])
217
  ],
218
  outputs=[
219
- gr.Gallery(label="Composite Grids (per Detector)", show_label=True),
220
- gr.File(label="Download SIFT Grid"),
221
- gr.File(label="Download ORB Grid"),
222
- gr.File(label="Download BRISK Grid"),
223
- gr.File(label="Download KAZE Grid"),
224
- gr.File(label="Download AKAZE Grid")
225
  ],
226
- title="Homography ROI vs XML ROI",
227
- description="Each detector produces one 2×2 grid: Flat, Matches, Homography ROI, Ground Truth ROI."
228
  )
229
- 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, sin_r = np.cos(rot_rad), np.sin(rot_rad)
12
+ R = np.array([[cos_r, -sin_r], [sin_r, cos_r]])
13
+ cx, cy = x + w/2, y + h/2
14
+ local_corners = np.array([[-w/2,-h/2],[w/2,-h/2],[w/2,h/2],[-w/2,h/2]])
 
 
 
 
 
 
 
 
15
  rotated_corners = np.dot(local_corners, R.T)
16
+ return (rotated_corners + np.array([cx,cy])).astype(np.float32)
 
17
 
18
  def preprocess_gray_clahe(img):
19
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
20
+ clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
21
  return clahe.apply(gray)
22
 
23
  def detect_and_match(img1_gray, img2_gray, method="SIFT", ratio_thresh=0.78):
24
+ if method=="SIFT": detector=cv2.SIFT_create(nfeatures=5000); matcher=cv2.BFMatcher(cv2.NORM_L2)
25
+ elif method=="ORB": detector=cv2.ORB_create(5000); matcher=cv2.BFMatcher(cv2.NORM_HAMMING)
26
+ elif method=="BRISK": detector=cv2.BRISK_create(); matcher=cv2.BFMatcher(cv2.NORM_HAMMING)
27
+ elif method=="KAZE": detector=cv2.KAZE_create(); matcher=cv2.BFMatcher(cv2.NORM_L2)
28
+ elif method=="AKAZE": detector=cv2.AKAZE_create(); matcher=cv2.BFMatcher(cv2.NORM_HAMMING)
29
+ else: return None,None,[]
30
+
31
+ kp1, des1 = detector.detectAndCompute(img1_gray,None)
32
+ kp2, des2 = detector.detectAndCompute(img2_gray,None)
33
+ if des1 is None or des2 is None: return None,None,[]
34
+
35
+ raw_matches = matcher.knnMatch(des1,des2,k=2)
36
+ good = [m for m,n in raw_matches if m.distance < ratio_thresh*n.distance]
37
+ return kp1, kp2, good
38
+
39
+ def parse_xml_points(xml_file):
40
+ tree = ET.parse(xml_file)
41
+ root = tree.getroot()
42
+ points=[]
43
+ for pt_type in ["TopLeft","TopRight","BottomLeft","BottomRight"]:
44
+ elem=root.find(f".//point[@type='{pt_type}']")
45
+ points.append([float(elem.get("x")), float(elem.get("y"))])
46
+ return np.array(points,dtype=np.float32).reshape(-1,2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  # ---------------- Main Function ----------------
49
  def homography_all_detectors(flat_file, persp_file, json_file, xml_file):
50
  flat_img = cv2.imread(flat_file)
51
  persp_img = cv2.imread(persp_file)
52
+ mockup = json.load(open(json_file.name))
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
+ xml_points = parse_xml_points(xml_file.name)
61
 
62
+ methods = ["SIFT","ORB","BRISK","KAZE","AKAZE"]
63
+ gallery_images = []
64
  download_files = []
65
 
66
+ for method in methods:
67
+ kp1,kp2,good_matches = detect_and_match(flat_gray,persp_gray,method)
68
+ if kp1 is None or kp2 is None or len(good_matches)<4: continue
69
+
70
+ # Feature matching
71
+ match_img = cv2.drawMatches(flat_img,kp1,persp_img,kp2,good_matches,None,flags=2)
72
+
73
+ # Homography & ROI
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
+ # --------- For display only: resize to match grid ---------
90
+ def resize_to_height(img, target_h):
91
+ h, w = img.shape[:2]
92
+ scale = target_h / h
93
+ new_w = int(w * scale)
94
+ return cv2.resize(img, (new_w, target_h))
95
+
96
+ target_h = 300 # temporary display height for gallery
97
+ flat_disp = resize_to_height(flat_img, target_h)
98
+ match_disp = resize_to_height(match_img, target_h)
99
+ roi_disp = resize_to_height(persp_roi, target_h)
100
+ xml_disp = resize_to_height(xml_gt_img, target_h)
101
+
102
+ # Merge 2x2 grid for gallery display
103
+ top = np.hstack([flat_disp, match_disp])
104
+ bottom = np.hstack([roi_disp, xml_disp])
105
+ combined_grid = np.vstack([top, bottom])
106
+ gallery_images.append((combined_grid,f"{method} Detector"))
107
+
108
+ # Save original resolution for download
109
+ base_name = os.path.splitext(os.path.basename(persp_file))[0]
110
+ file_name = f"{base_name}_{method.lower()}.png"
111
+ # Merge original images for download
112
+ top_orig = np.hstack([flat_img, match_img])
113
+ bottom_orig = np.hstack([persp_roi, xml_gt_img])
114
+ combined_orig = np.vstack([top_orig, bottom_orig])
115
+ cv2.imwrite(file_name, combined_orig)
116
+ download_files.append(file_name)
117
+
118
+ # Ensure 5 outputs
119
+ while len(download_files)<5: download_files.append(None)
120
+ return gallery_images, download_files[0], download_files[1], download_files[2], download_files[3], download_files[4]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  # ---------------- Gradio UI ----------------
123
  iface = gr.Interface(
124
  fn=homography_all_detectors,
125
  inputs=[
126
+ gr.Image(label="Upload Flat Image",type="filepath"),
127
+ gr.Image(label="Upload Perspective Image",type="filepath"),
128
+ gr.File(label="Upload mockup.json",file_types=[".json"]),
129
+ gr.File(label="Upload XML file",file_types=[".xml"])
130
  ],
131
  outputs=[
132
+ gr.Gallery(label="Results per Detector",show_label=True),
133
+ gr.File(label="Download SIFT Result"),
134
+ gr.File(label="Download ORB Result"),
135
+ gr.File(label="Download BRISK Result"),
136
+ gr.File(label="Download KAZE Result"),
137
+ gr.File(label="Download AKAZE Result")
138
  ],
139
+ title="Homography ROI Projection with Feature Matching & XML GT",
140
+ description="Shows 4 views per detector. Grid resized for display, original resolution saved for download."
141
  )
142
+
143
+ iface.launch()