1111 Detection ,
1212 ImageMetadata ,
1313 Metric ,
14- PolygonPoint ,
1514 PipelineSummary ,
15+ PolygonPoint ,
1616 SegmentationRegion ,
1717)
1818from app .vision .pipelines import PipelineDefinition
@@ -45,6 +45,40 @@ def _contour_to_polygon(contour: np.ndarray) -> list[PolygonPoint]:
4545 ]
4646
4747
48+ def _partition_significant_contours (
49+ contours : list [np .ndarray ],
50+ image_area : int ,
51+ ) -> tuple [list [np .ndarray ], list [np .ndarray ]]:
52+ significant : list [np .ndarray ] = []
53+ near_full_frame : list [np .ndarray ] = []
54+
55+ for contour in contours :
56+ area_ratio = cv2 .contourArea (contour ) / image_area
57+ if area_ratio < 0.02 :
58+ continue
59+
60+ x , y , width , height = cv2 .boundingRect (contour )
61+ box_area_ratio = (width * height ) / image_area
62+ if area_ratio >= 0.9 or box_area_ratio >= 0.94 :
63+ near_full_frame .append (contour )
64+ continue
65+
66+ significant .append (contour )
67+
68+ return significant , near_full_frame
69+
70+
71+ def _select_significant_contours (
72+ contours : list [np .ndarray ],
73+ image_area : int ,
74+ ) -> list [np .ndarray ]:
75+ significant , near_full_frame = _partition_significant_contours (
76+ contours ,
77+ image_area ,
78+ )
79+ return significant or near_full_frame [:1 ]
80+
81+
4882def _starter_detection (image : np .ndarray ) -> tuple [list [dict ], list [dict ], list [dict ]]:
4983 grayscale = cv2 .cvtColor (image , cv2 .COLOR_BGR2GRAY )
5084 blurred = cv2 .GaussianBlur (grayscale , (5 , 5 ), 0 )
@@ -151,33 +185,43 @@ def _foreground_segmentation(
151185
152186 best_mask = thresholded
153187 best_score = - 1.0
188+ best_has_non_full_contours = False
154189
155190 for candidate in candidates :
156191 cleaned = cv2 .morphologyEx (
157192 candidate ,
158193 cv2 .MORPH_OPEN ,
159194 np .ones ((5 , 5 ), dtype = np .uint8 ),
160195 )
161- contours , _ = cv2 .findContours (
196+ raw_contours , _ = cv2 .findContours (
162197 cleaned ,
163198 cv2 .RETR_EXTERNAL ,
164199 cv2 .CHAIN_APPROX_SIMPLE ,
165200 )
166- significant = [
167- contour
168- for contour in contours
169- if cv2 .contourArea (contour ) / image_area >= 0.02
170- ]
171- score = sum (cv2 .contourArea (contour ) for contour in significant ) / image_area
172- if score > best_score :
201+ significant , near_full_frame = _partition_significant_contours (
202+ raw_contours ,
203+ image_area ,
204+ )
205+ candidate_contours = significant or near_full_frame [:1 ]
206+ has_non_full_contours = bool (significant )
207+ score = (
208+ sum (cv2 .contourArea (contour ) for contour in candidate_contours ) / image_area
209+ )
210+ if (
211+ has_non_full_contours and not best_has_non_full_contours
212+ ) or (
213+ has_non_full_contours == best_has_non_full_contours and score > best_score
214+ ):
173215 best_score = score
174216 best_mask = cleaned
217+ best_has_non_full_contours = has_non_full_contours
175218
176- contours , _ = cv2 .findContours (
219+ raw_contours , _ = cv2 .findContours (
177220 best_mask ,
178221 cv2 .RETR_EXTERNAL ,
179222 cv2 .CHAIN_APPROX_SIMPLE ,
180223 )
224+ contours = _select_significant_contours (raw_contours , image_area )
181225
182226 detections = []
183227 segmentations = []
@@ -242,7 +286,10 @@ def _dominant_color(image: np.ndarray) -> tuple[list[dict], list[dict], list[dic
242286 "starter-detection" : PipelineDefinition (
243287 id = "starter-detection" ,
244288 name = "Starter Detection" ,
245- summary = "Detection-first sample pipeline that returns object-style boxes and confidence scores." ,
289+ summary = (
290+ "Detection-first sample pipeline that returns object-style boxes "
291+ "and confidence scores."
292+ ),
246293 tags = ["detection" , "default" , "cpu" ],
247294 runtime = "opencv-cpu" ,
248295 sample_outputs = ["object boxes" , "confidence scores" , "coverage metrics" ],
@@ -251,7 +298,10 @@ def _dominant_color(image: np.ndarray) -> tuple[list[dict], list[dict], list[dic
251298 "document-layout" : PipelineDefinition (
252299 id = "document-layout" ,
253300 name = "Document Layout" ,
254- summary = "Document-oriented box extraction for capture, scanning, and kiosk workflows." ,
301+ summary = (
302+ "Document-oriented box extraction for capture, scanning, and kiosk "
303+ "workflows."
304+ ),
255305 tags = ["detection" , "document" , "cpu" ],
256306 runtime = "opencv-cpu" ,
257307 sample_outputs = ["quadrilateral candidates" , "layout blocks" ],
@@ -260,7 +310,10 @@ def _dominant_color(image: np.ndarray) -> tuple[list[dict], list[dict], list[dic
260310 "foreground-segmentation" : PipelineDefinition (
261311 id = "foreground-segmentation" ,
262312 name = "Foreground Segmentation" ,
263- summary = "Segmentation extension pipeline that returns region polygons plus detection-style boxes." ,
313+ summary = (
314+ "Segmentation extension pipeline that returns region polygons plus "
315+ "detection-style boxes."
316+ ),
264317 tags = ["segmentation" , "extension" , "cpu" ],
265318 runtime = "opencv-cpu" ,
266319 sample_outputs = ["region polygons" , "mask coverage" , "derived boxes" ],
@@ -269,7 +322,10 @@ def _dominant_color(image: np.ndarray) -> tuple[list[dict], list[dict], list[dic
269322 "dominant-color" : PipelineDefinition (
270323 id = "dominant-color" ,
271324 name = "Dominant Color" ,
272- summary = "Metrics-only extension pipeline for quality and image analytics workflows." ,
325+ summary = (
326+ "Metrics-only extension pipeline for quality and image analytics "
327+ "workflows."
328+ ),
273329 tags = ["analytics" , "extension" , "cpu" ],
274330 runtime = "opencv-cpu" ,
275331 sample_outputs = ["channel metrics" , "brightness" ],
0 commit comments