import sys sys.path.insert(0, "services/recolor") import numpy as np from PIL import Image import cv2 from fabric_recolor.segmentation.birefnet import BirefnetMatteSegmenter from fabric_recolor.segmentation.grounded_sam import GroundedSamSegmenter from fabric_recolor.segmentation.prompts import NEGATIVE_PROMPTS, CAVITY_PROMPTS from fabric_recolor.masks import clean_binary, largest_component, fill_small_holes, alpha_hysteresis src = "data/media/originals/fd/fd12e584a23312c7c5edda1015fe9715d3f5fd19a2ef3d375d635cefab91f54b.jpg" img = Image.open(src).convert("RGB") W0, H0 = img.width, img.height scale = min(1.0, 1600 / max(H0, W0)) seg = img.resize((round(W0*scale), round(H0*scale)), Image.BILINEAR) arr = np.asarray(seg) seg_b = BirefnetMatteSegmenter() seg_b._load() matte = seg_b._matte(seg) matte = alpha_hysteresis(matte, ramp_px=max(4, round(8*scale))) mask = clean_binary((matte >= 0.5).astype(np.uint8), min_area_frac=0.002) mask = largest_component(mask) mask = fill_small_holes(mask, 0.01) obj = int(mask.sum()) # convexity defects = wnęki sylwetki (otwarte i zamknięte) cnts, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) hull_img = np.zeros_like(mask) for c in cnts: cv2.fillPoly(hull_img, [cv2.convexHull(c)], 1) defect = (hull_img & (1 - mask)).astype(np.uint8) # podział defectów: połączone z zewnętrzem (otwarte wnęki) vs zamknięte n_d, l_d = cv2.connectedComponents(defect, connectivity=8) bg_reach = np.zeros_like(mask) ff = (1 - mask).astype(np.uint8).copy() cv2.floodFill(ff, np.zeros((ff.shape[0]+2, ff.shape[1]+2), np.uint8), (0,0), 2) open_ids = np.unique(l_d[ff == 2]); open_ids = open_ids[open_ids > 0] bay = np.isin(l_d, open_ids).astype(np.uint8) print("mask:", obj, "hull:", int(hull_img.sum()), "defect:", int(defect.sum()), "bay(open):", int(bay.sum())) # band_out: maska przyległa do tła poza hull (prawdziwy obrys) k6 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (13, 13)) outside = ((1 - mask) & (1 - hull_img)).astype(np.uint8) band_out = (mask > 0) & (cv2.dilate(outside, k6) > 0) viz = arr.copy() viz[mask > 0] = (viz[mask > 0]*0.5 + np.array([0,180,0])*0.5).astype(np.uint8) viz[bay > 0] = (viz[bay > 0]*0.4 + np.array([0,80,255])*0.6).astype(np.uint8) viz[band_out] = [255, 0, 0] Image.fromarray(viz).save("tmp/dbg-bands-6413.png") iso = Image.fromarray(arr * mask[..., None].astype(np.uint8)) gs = GroundedSamSegmenter() gs._load() det = gs._detect(iso, list(NEGATIVE_PROMPTS)) labels = det.get("text_labels") or det.get("labels") or [] boxes = det["boxes"].cpu().numpy() scores = det["scores"].cpu().numpy() masks = gs._boxes_to_masks(iso, boxes) for i, (lab, box, sc) in enumerate(zip(labels, boxes, scores)): if i >= len(masks): break m = masks[i] pm = m & (mask > 0) a = int(pm.sum()) if a == 0: print(f"{sc:.2f} '{lab}' -> 0 inside"); continue bf = float((pm & band_out).sum()) / a bay_adj = bool((cv2.dilate(pm.astype(np.uint8), k6) & bay.astype(bool)).any()) idx = next((j for j,p in enumerate(NEGATIVE_PROMPTS) if p in lab or lab in p), -1) cav = idx >= 0 and NEGATIVE_PROMPTS[idx] in CAVITY_PROMPTS print(f"[{i}] {sc:.2f} '{lab}' inside={a} ({a/obj:.0%}) band={bf:.2f} bay_adj={bay_adj} cav={cav}") v = arr.copy() v[pm] = (v[pm]*0.35 + np.array([255,40,40])*0.65).astype(np.uint8) v[m & (mask == 0)] = (v[m & (mask == 0)]*0.35 + np.array([255,200,40])*0.65).astype(np.uint8) Image.fromarray(v).resize((600, 800)).save(f"tmp/inst-{i}-6413.png") print("saved tmp/inst-*-6413.png, tmp/dbg-bands-6413.png")