diff --git a/scripts/merge_multi_results.py b/scripts/merge_multi_results.py index 80830aa..3a5626f 100644 --- a/scripts/merge_multi_results.py +++ b/scripts/merge_multi_results.py @@ -101,6 +101,12 @@ def parse_args() -> argparse.Namespace: action="store_true", help="Skip HTML report", ) + p.add_argument( + "--top-k", + type=int, + default=3, + help="Max classes shown per pixel click (rank = mean score + log area). Default 3.", + ) return p.parse_args() @@ -433,14 +439,42 @@ def mask_to_rle(mask: np.ndarray) -> list[int]: return rle +def rank_score_mean_area( + scores_mean: float | None, + scores_max: float | None, + pixels: int, + image_pixels: int, +) -> float: + """Priority for pixel label: mean confidence + log-normalized area. + + rank = mean + log1p(pixels) / log1p(image_pixels) + + - mean: SAM instance conf mean for that prompt (fallback: scores_max) + - area term in [0, 1]: larger masks rank higher, but log softens huge yards + Downstream 3D/point-cloud labeling can reuse the same formula for top-k. + """ + if scores_mean is not None: + mean = float(scores_mean) + elif scores_max is not None: + mean = float(scores_max) + else: + mean = 0.0 + img_px = max(int(image_pixels), 1) + px = max(int(pixels), 0) + area_term = float(np.log1p(px) / np.log1p(img_px)) + return mean + area_term + + def export_hitmasks( out_dir: Path, ordered_items: list[dict[str, Any]], legend: list[dict[str, Any]], size: tuple[int, int], + top_k: int = 3, ) -> list[dict[str, Any]]: """Build interactive class meta with RLE hit data (file:// / no CORS needed).""" w, h = size + image_pixels = int(w * h) classes_meta: list[dict[str, Any]] = [] for i, ent in enumerate(legend): it = None @@ -466,9 +500,14 @@ def export_hitmasks( if smax is None: smax = it.get("scores_max") smean = it.get("scores_mean") + if smean is None: + smean = ent.get("scores_mean") if smean is None and smin is not None and smax is not None: smean = (float(smin) + float(smax)) / 2.0 + pixels = int(ent.get("pixels") or int(mask.sum())) + rank = rank_score_mean_area(smean, smax, pixels, image_pixels) + classes_meta.append( { "id": i, @@ -477,10 +516,11 @@ def export_hitmasks( "n_objects": ent.get("n_objects") if ent.get("n_objects") is not None else it.get("n_objects"), - "pixels": int(ent.get("pixels") or int(mask.sum())), + "pixels": pixels, "scores_min": smin, "scores_max": smax, "scores_mean": smean, + "rank_score": rank, "rle": rle, } ) @@ -490,6 +530,13 @@ def export_hitmasks( "height": h, "overlay": "combined_overlay.png", "hit_mode": "rle", + "top_k": int(top_k), + "ranking": { + "formula": "mean + log1p(pixels)/log1p(W*H)", + "components": ["scores_mean", "log_area_norm"], + "purpose": "pixel prompt label for downstream point cloud", + "top_k": int(top_k), + }, "classes": classes_meta, } meta_path = out_dir / "interactive_meta.json" @@ -505,8 +552,9 @@ def write_html( legend: list[dict[str, Any]], meta: dict[str, Any], interactive_meta: dict[str, Any] | None = None, + top_k: int = 3, ) -> None: - """Write interactive index.html (click image → class list + scores).""" + """Write interactive index.html (click → top-k prompts by mean+area).""" # Embed meta so file:// works without fetch CORS issues imeta = interactive_meta if imeta is None: @@ -521,7 +569,21 @@ def write_html( "overlay": overlay_name, "classes": [], } + # Ensure ranking policy is present for the embedded viewer / 3D export + imeta.setdefault("top_k", int(top_k)) + imeta.setdefault( + "ranking", + { + "formula": "mean + log1p(pixels)/log1p(W*H)", + "components": ["scores_mean", "log_area_norm"], + "purpose": "pixel prompt label for downstream point cloud", + "top_k": int(top_k), + }, + ) + imeta["top_k"] = int(top_k) + imeta["ranking"]["top_k"] = int(top_k) imeta_js = json.dumps(imeta, ensure_ascii=False) + top_k = max(1, int(top_k)) # Static fallback rows still useful if JS fails rows = [] @@ -650,27 +712,32 @@ header p {{ margin:.25rem 0 0; font-size:.85rem; color:rgba(255,255,255,.88); }}
이미지를 클릭하면 해당 픽셀에 겹친 클래스(프롬프트)와 confidence 점수가 오른쪽에 표시됩니다. 겹침 시 점수 높은 순으로 정렬됩니다.
++ 픽셀 클릭 → 그 위치에 겹친 프롬프트 중 + rank = mean conf + log 면적 상위 {top_k}개만 표시합니다. + (3D 포인트 클라우드 라벨용: 픽셀 → prompt) +
클릭: 픽셀 조회 · Shift+클릭: 선택 유지(누적) · Esc: 초기화 · 클래스 {n_cls}개
+클릭: top-{top_k} 조회 · Shift+클릭: 누적 후 재순위 top-{top_k} · Esc: 초기화 · 클래스 {n_cls}개
loading…
{grid_block}