3636 remove_small_regions ,
3737 mask_to_bbox ,
3838 make_split_preview ,
39+ augment_prompts_from_mask ,
40+ mask_to_sam_logits ,
41+ parse_points_json ,
42+ points_to_arrays ,
43+ parse_bbox_input ,
3944)
4045
4146try :
@@ -147,9 +152,9 @@ def execute(self, sam_model, image, points_json, bbox_json,
147152 img_np = (img_tensor .cpu ().numpy () * 255 ).astype (np .uint8 )
148153 H , W = img_np .shape [:2 ]
149154
150- # Parse prompts
151- points_list = self . _parse_points (points_json )
152- box_np = self . _parse_bbox (bbox_json , bbox )
155+ # Parse prompts (shared utilities)
156+ points_list = parse_points_json (points_json )
157+ box_np = parse_bbox_input (bbox_json , bbox )
153158
154159 # ── Stage 1: SAM coarse mask (with iterative refinement) ──────
155160 if offload and hasattr (model , "to" ):
@@ -194,7 +199,7 @@ def execute(self, sam_model, image, points_json, bbox_json,
194199 # ── Outputs ───────────────────────────────────────────────────
195200 edge_mask = torch .abs (refined_mask - (coarse_mask > 0.5 ).float ())
196201 det_bbox = mask_to_bbox (refined_mask , W , H )
197- preview = make_split_preview (img_tensor , refined_mask , coarse_mask )
202+ preview = make_split_preview (img_tensor , coarse_mask , refined_mask )
198203
199204 info = json .dumps ({
200205 "model_type" : model_type ,
@@ -232,7 +237,7 @@ def _iterative_sam(self, model, model_type, img_np, points_list,
232237 if predictor is None :
233238 return torch .zeros ((H , W ), dtype = torch .float32 ), 0.0
234239
235- point_coords , point_labels = self . _points_to_arrays (points_list )
240+ point_coords , point_labels = points_to_arrays (points_list )
236241
237242 # Use existing mask as starting point if provided
238243 current_mask = None
@@ -251,7 +256,7 @@ def _iterative_sam(self, model, model_type, img_np, points_list,
251256 iter_box = box_np
252257
253258 if iteration > 0 and current_mask is not None :
254- aug_coords , aug_labels , aug_box = self . _augment_prompts_from_mask (
259+ aug_coords , aug_labels , aug_box = augment_prompts_from_mask (
255260 current_mask , point_coords , point_labels , box_np , H , W
256261 )
257262 iter_coords = aug_coords
@@ -263,14 +268,7 @@ def _iterative_sam(self, model, model_type, img_np, points_list,
263268 try :
264269 mask_input = None
265270 if iteration > 0 and current_mask is not None :
266- m_logit = np .clip (current_mask , 1e-6 , 1 - 1e-6 )
267- m_logit = np .log (m_logit / (1 - m_logit ))
268- if HAS_CV2 :
269- mask_input = cv2 .resize (m_logit , (256 , 256 ),
270- interpolation = cv2 .INTER_LINEAR )
271- else :
272- mask_input = m_logit
273- mask_input = mask_input [np .newaxis , :, :] # (1, 256, 256)
271+ mask_input = mask_to_sam_logits (current_mask )
274272
275273 masks_np , scores , _ = predictor .predict (
276274 point_coords = iter_coords ,
@@ -310,63 +308,6 @@ def _iterative_sam(self, model, model_type, img_np, points_list,
310308
311309 return torch .from_numpy (current_mask ), best_score
312310
313- def _augment_prompts_from_mask (self , mask , orig_coords , orig_labels ,
314- orig_box , H , W ):
315- """Generate additional prompts from the previous mask iteration."""
316- coords_list = []
317- labels_list = []
318-
319- if orig_coords is not None :
320- coords_list .append (orig_coords )
321- labels_list .append (orig_labels )
322-
323- if not HAS_CV2 :
324- c = np .concatenate (coords_list ) if coords_list else None
325- l = np .concatenate (labels_list ) if labels_list else None
326- return c , l , orig_box
327-
328- binary = (mask > 0.5 ).astype (np .uint8 )
329-
330- # Eroded interior → strong positive points
331- kern = cv2 .getStructuringElement (cv2 .MORPH_ELLIPSE , (15 , 15 ))
332- interior = cv2 .erode (binary , kern , iterations = 1 )
333- interior_pts = np .argwhere (interior > 0 ) # (y, x)
334- if len (interior_pts ) > 0 :
335- n = min (3 , len (interior_pts ))
336- indices = np .linspace (0 , len (interior_pts ) - 1 , n , dtype = int )
337- sampled = interior_pts [indices ]
338- coords_list .append (sampled [:, ::- 1 ].astype (np .float32 ))
339- labels_list .append (np .ones (n , dtype = np .int32 ))
340-
341- # Dilated boundary exterior → negative points
342- dilated = cv2 .dilate (binary , kern , iterations = 1 )
343- exterior = dilated - binary
344- exterior_pts = np .argwhere (exterior > 0 )
345- if len (exterior_pts ) > 0 :
346- n = min (2 , len (exterior_pts ))
347- indices = np .linspace (0 , len (exterior_pts ) - 1 , n , dtype = int )
348- sampled = exterior_pts [indices ]
349- coords_list .append (sampled [:, ::- 1 ].astype (np .float32 ))
350- labels_list .append (np .zeros (n , dtype = np .int32 ))
351-
352- all_coords = np .concatenate (coords_list ).astype (np .float32 ) if coords_list else None
353- all_labels = np .concatenate (labels_list ).astype (np .int32 ) if labels_list else None
354-
355- # Derive tighter bbox from mask
356- ys , xs = np .where (binary > 0 )
357- if len (xs ) > 0 :
358- pad = max (5 , int (min (H , W ) * 0.02 ))
359- box = np .array ([
360- max (0 , xs .min () - pad ),
361- max (0 , ys .min () - pad ),
362- min (W , xs .max () + pad ),
363- min (H , ys .max () + pad ),
364- ], dtype = np .float32 )
365- else :
366- box = orig_box
367-
368- return all_coords , all_labels , box
369-
370311 # ══════════════════════════════════════════════════════════════════
371312 # STAGE 3 – Edge-aware matting (delegates to utils)
372313 # ══════════════════════════════════════════════════════════════════
@@ -456,42 +397,3 @@ def _try_laplacian(mask_np, edge_radius, detail_pres):
456397 return torch .from_numpy (np .clip (result , 0 , 1 ).astype (np .float32 ))
457398 except Exception :
458399 return None
459-
460- # ══════════════════════════════════════════════════════════════════
461- # Prompt parsing
462- # ══════════════════════════════════════════════════════════════════
463-
464- @staticmethod
465- def _parse_points (points_json ):
466- try :
467- return json .loads (points_json ) if isinstance (points_json , str ) else points_json
468- except json .JSONDecodeError :
469- return []
470-
471- @staticmethod
472- def _points_to_arrays (points_list ):
473- if not points_list :
474- return None , None
475- coords = [[float (p ["x" ]), float (p ["y" ])] for p in points_list ]
476- labels = [int (p .get ("label" , 1 )) for p in points_list ]
477- return np .array (coords , dtype = np .float32 ), np .array (labels , dtype = np .int32 )
478-
479- @staticmethod
480- def _parse_bbox (bbox_json , bbox_input ):
481- if bbox_input is not None :
482- bx , by , bw , bh = bbox_input
483- return np .array ([bx , by , bx + bw , by + bh ], dtype = np .float32 )
484- if bbox_json and bbox_json .strip ():
485- try :
486- bdata = json .loads (bbox_json )
487- if isinstance (bdata , list ) and len (bdata ) == 4 :
488- return np .array (bdata , dtype = np .float32 )
489- elif isinstance (bdata , dict ):
490- bx = float (bdata .get ("x" , 0 ))
491- by = float (bdata .get ("y" , 0 ))
492- bw = float (bdata .get ("w" , bdata .get ("width" , 0 )))
493- bh = float (bdata .get ("h" , bdata .get ("height" , 0 )))
494- return np .array ([bx , by , bx + bw , by + bh ], dtype = np .float32 )
495- except json .JSONDecodeError :
496- pass
497- return None
0 commit comments