점수 추가
This commit is contained in:
+149
-27
@@ -101,6 +101,12 @@ def parse_args() -> argparse.Namespace:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Skip HTML report",
|
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()
|
return p.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -433,14 +439,42 @@ def mask_to_rle(mask: np.ndarray) -> list[int]:
|
|||||||
return rle
|
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(
|
def export_hitmasks(
|
||||||
out_dir: Path,
|
out_dir: Path,
|
||||||
ordered_items: list[dict[str, Any]],
|
ordered_items: list[dict[str, Any]],
|
||||||
legend: list[dict[str, Any]],
|
legend: list[dict[str, Any]],
|
||||||
size: tuple[int, int],
|
size: tuple[int, int],
|
||||||
|
top_k: int = 3,
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""Build interactive class meta with RLE hit data (file:// / no CORS needed)."""
|
"""Build interactive class meta with RLE hit data (file:// / no CORS needed)."""
|
||||||
w, h = size
|
w, h = size
|
||||||
|
image_pixels = int(w * h)
|
||||||
classes_meta: list[dict[str, Any]] = []
|
classes_meta: list[dict[str, Any]] = []
|
||||||
for i, ent in enumerate(legend):
|
for i, ent in enumerate(legend):
|
||||||
it = None
|
it = None
|
||||||
@@ -466,9 +500,14 @@ def export_hitmasks(
|
|||||||
if smax is None:
|
if smax is None:
|
||||||
smax = it.get("scores_max")
|
smax = it.get("scores_max")
|
||||||
smean = it.get("scores_mean")
|
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:
|
if smean is None and smin is not None and smax is not None:
|
||||||
smean = (float(smin) + float(smax)) / 2.0
|
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(
|
classes_meta.append(
|
||||||
{
|
{
|
||||||
"id": i,
|
"id": i,
|
||||||
@@ -477,10 +516,11 @@ def export_hitmasks(
|
|||||||
"n_objects": ent.get("n_objects")
|
"n_objects": ent.get("n_objects")
|
||||||
if ent.get("n_objects") is not None
|
if ent.get("n_objects") is not None
|
||||||
else it.get("n_objects"),
|
else it.get("n_objects"),
|
||||||
"pixels": int(ent.get("pixels") or int(mask.sum())),
|
"pixels": pixels,
|
||||||
"scores_min": smin,
|
"scores_min": smin,
|
||||||
"scores_max": smax,
|
"scores_max": smax,
|
||||||
"scores_mean": smean,
|
"scores_mean": smean,
|
||||||
|
"rank_score": rank,
|
||||||
"rle": rle,
|
"rle": rle,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -490,6 +530,13 @@ def export_hitmasks(
|
|||||||
"height": h,
|
"height": h,
|
||||||
"overlay": "combined_overlay.png",
|
"overlay": "combined_overlay.png",
|
||||||
"hit_mode": "rle",
|
"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,
|
"classes": classes_meta,
|
||||||
}
|
}
|
||||||
meta_path = out_dir / "interactive_meta.json"
|
meta_path = out_dir / "interactive_meta.json"
|
||||||
@@ -505,8 +552,9 @@ def write_html(
|
|||||||
legend: list[dict[str, Any]],
|
legend: list[dict[str, Any]],
|
||||||
meta: dict[str, Any],
|
meta: dict[str, Any],
|
||||||
interactive_meta: dict[str, Any] | None = None,
|
interactive_meta: dict[str, Any] | None = None,
|
||||||
|
top_k: int = 3,
|
||||||
) -> None:
|
) -> 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
|
# Embed meta so file:// works without fetch CORS issues
|
||||||
imeta = interactive_meta
|
imeta = interactive_meta
|
||||||
if imeta is None:
|
if imeta is None:
|
||||||
@@ -521,7 +569,21 @@ def write_html(
|
|||||||
"overlay": overlay_name,
|
"overlay": overlay_name,
|
||||||
"classes": [],
|
"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)
|
imeta_js = json.dumps(imeta, ensure_ascii=False)
|
||||||
|
top_k = max(1, int(top_k))
|
||||||
|
|
||||||
# Static fallback rows still useful if JS fails
|
# Static fallback rows still useful if JS fails
|
||||||
rows = []
|
rows = []
|
||||||
@@ -650,27 +712,32 @@ header p {{ margin:.25rem 0 0; font-size:.85rem; color:rgba(255,255,255,.88); }}
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>Merged multi-prompt — click to inspect</h1>
|
<h1>Merged multi-prompt — top-{top_k} pixel labels</h1>
|
||||||
<p>이미지를 클릭하면 해당 픽셀에 겹친 클래스(프롬프트)와 confidence 점수가 오른쪽에 표시됩니다. 겹침 시 점수 높은 순으로 정렬됩니다.</p>
|
<p>
|
||||||
|
픽셀 클릭 → 그 위치에 겹친 프롬프트 중
|
||||||
|
<strong>rank = mean conf + log 면적</strong> 상위 <strong>{top_k}</strong>개만 표시합니다.
|
||||||
|
(3D 포인트 클라우드 라벨용: 픽셀 → prompt)
|
||||||
|
</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<div class="stage">
|
<div class="stage">
|
||||||
<div class="canvas-wrap" id="wrap">
|
<div class="canvas-wrap" id="wrap">
|
||||||
<canvas id="view"></canvas>
|
<canvas id="view"></canvas>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint">클릭: 픽셀 조회 · Shift+클릭: 선택 유지(누적) · Esc: 초기화 · 클래스 {n_cls}개</p>
|
<p class="hint">클릭: top-{top_k} 조회 · Shift+클릭: 누적 후 재순위 top-{top_k} · Esc: 초기화 · 클래스 {n_cls}개</p>
|
||||||
<p class="status" id="status">loading…</p>
|
<p class="status" id="status">loading…</p>
|
||||||
{grid_block}
|
{grid_block}
|
||||||
</div>
|
</div>
|
||||||
<aside class="side">
|
<aside class="side">
|
||||||
<div>
|
<div>
|
||||||
<h2>Selection</h2>
|
<h2>Top-{top_k} labels</h2>
|
||||||
<div class="pick-meta" id="pickMeta">클릭하여 조회</div>
|
<div class="pick-meta" id="pickMeta">클릭하여 조회</div>
|
||||||
<ul class="hit-list" id="hitList"></ul>
|
<ul class="hit-list" id="hitList"></ul>
|
||||||
<div class="empty-hit" id="emptyHit" hidden>이 픽셀에 마스크 없음</div>
|
<div class="empty-hit" id="emptyHit" hidden>이 픽셀에 마스크 없음</div>
|
||||||
<p class="priority-note">
|
<p class="priority-note">
|
||||||
겹침 우선순위 참고: 목록은 <strong>scores_max</strong> 내림차순입니다.
|
우선순위: <code>rank = scores_mean + log1p(pixels)/log1p(W×H)</code><br/>
|
||||||
점수가 비슷하면 픽셀 면적(n_objects)과 장면 문맥으로 판단하세요.
|
mean 없을 때 scores_max 사용. 상위 <strong>{top_k}</strong>개만 표시 (나머지 중복 숨김).
|
||||||
|
이후 포인트클라우드에서 이 prompt를 픽셀 라벨로 씁니다.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -703,7 +770,9 @@ header p {{ margin:.25rem 0 0; font-size:.85rem; color:rgba(255,255,255,.88); }}
|
|||||||
let overlayImg = null;
|
let overlayImg = null;
|
||||||
let classes = [];
|
let classes = [];
|
||||||
let lastHits = [];
|
let lastHits = [];
|
||||||
|
let lastAllCount = 0;
|
||||||
let marker = null;
|
let marker = null;
|
||||||
|
const TOP_K = {top_k};
|
||||||
|
|
||||||
function setStatus(msg, ok) {{
|
function setStatus(msg, ok) {{
|
||||||
statusEl.textContent = msg;
|
statusEl.textContent = msg;
|
||||||
@@ -731,6 +800,32 @@ header p {{ margin:.25rem 0 0; font-size:.85rem; color:rgba(255,255,255,.88); }}
|
|||||||
return false;
|
return false;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
/** rank = mean + log1p(pixels)/log1p(W*H) — same as Python rank_score_mean_area */
|
||||||
|
function computeRankScore(cls) {{
|
||||||
|
if (cls.rank_score != null && Number.isFinite(Number(cls.rank_score))) {{
|
||||||
|
return Number(cls.rank_score);
|
||||||
|
}}
|
||||||
|
let mean = 0;
|
||||||
|
if (cls.scores_mean != null) mean = Number(cls.scores_mean);
|
||||||
|
else if (cls.scores_max != null) mean = Number(cls.scores_max);
|
||||||
|
const imgPx = Math.max(1, (meta.width || 1) * (meta.height || 1));
|
||||||
|
const px = Math.max(0, Number(cls.pixels) || 0);
|
||||||
|
const areaTerm = Math.log1p(px) / Math.log1p(imgPx);
|
||||||
|
return mean + areaTerm;
|
||||||
|
}}
|
||||||
|
|
||||||
|
function sortByRank(hits) {{
|
||||||
|
return hits.slice().sort((a, b) => {{
|
||||||
|
const d = b.rank_score - a.rank_score;
|
||||||
|
if (d !== 0) return d;
|
||||||
|
// tie-break: higher mean, then larger area
|
||||||
|
const ma = a.scores_mean != null ? Number(a.scores_mean) : -1;
|
||||||
|
const mb = b.scores_mean != null ? Number(b.scores_mean) : -1;
|
||||||
|
if (mb !== ma) return mb - ma;
|
||||||
|
return (b.pixels || 0) - (a.pixels || 0);
|
||||||
|
}});
|
||||||
|
}}
|
||||||
|
|
||||||
function draw() {{
|
function draw() {{
|
||||||
if (!overlayImg || !meta) return;
|
if (!overlayImg || !meta) return;
|
||||||
const w = meta.width, h = meta.height;
|
const w = meta.width, h = meta.height;
|
||||||
@@ -797,32 +892,38 @@ header p {{ margin:.25rem 0 0; font-size:.85rem; color:rgba(255,255,255,.88); }}
|
|||||||
scores_mean: cls.scores_mean,
|
scores_mean: cls.scores_mean,
|
||||||
n_objects: cls.n_objects,
|
n_objects: cls.n_objects,
|
||||||
pixels: cls.pixels,
|
pixels: cls.pixels,
|
||||||
rank_score: (cls.scores_max != null ? Number(cls.scores_max) : -1),
|
rank_score: computeRankScore(cls),
|
||||||
}});
|
}});
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
hits.sort((a, b) => b.rank_score - a.rank_score || (b.pixels || 0) - (a.pixels || 0));
|
let ranked = sortByRank(hits);
|
||||||
|
lastAllCount = ranked.length;
|
||||||
|
|
||||||
if (accumulate && lastHits.length) {{
|
if (accumulate && lastHits.length) {{
|
||||||
const map = new Map(lastHits.map(h => [h.id, h]));
|
const map = new Map(lastHits.map(h => [h.id, h]));
|
||||||
for (const h of hits) map.set(h.id, h);
|
for (const h of ranked) map.set(h.id, h);
|
||||||
lastHits = Array.from(map.values()).sort(
|
ranked = sortByRank(Array.from(map.values()));
|
||||||
(a, b) => b.rank_score - a.rank_score || (b.pixels || 0) - (a.pixels || 0)
|
lastAllCount = ranked.length;
|
||||||
);
|
|
||||||
}} else {{
|
|
||||||
lastHits = hits;
|
|
||||||
}}
|
}}
|
||||||
|
// Only top-K labels for UI and for downstream "pixel → prompt" selection
|
||||||
|
lastHits = ranked.slice(0, TOP_K);
|
||||||
marker = {{ x, y }};
|
marker = {{ x, y }};
|
||||||
renderHits();
|
renderHits();
|
||||||
draw();
|
draw();
|
||||||
}}
|
}}
|
||||||
|
|
||||||
function fmtScore(h) {{
|
function fmtScore(h) {{
|
||||||
if (h.scores_max == null && h.scores_min == null) return 'score n/a';
|
if (h.scores_max == null && h.scores_min == null && h.scores_mean == null) {{
|
||||||
const a = h.scores_min != null ? Number(h.scores_min).toFixed(3) : '?';
|
return 'score n/a';
|
||||||
const b = h.scores_max != null ? Number(h.scores_max).toFixed(3) : '?';
|
}}
|
||||||
const m = h.scores_mean != null ? Number(h.scores_mean).toFixed(3) : null;
|
const m = h.scores_mean != null ? Number(h.scores_mean).toFixed(3) : null;
|
||||||
return m != null ? `max ${{b}} · mean ${{m}}` : `${{a}} ~ ${{b}}`;
|
const b = h.scores_max != null ? Number(h.scores_max).toFixed(3) : null;
|
||||||
|
const r = h.rank_score != null ? Number(h.rank_score).toFixed(3) : null;
|
||||||
|
const parts = [];
|
||||||
|
if (m != null) parts.push('mean ' + m);
|
||||||
|
if (b != null) parts.push('max ' + b);
|
||||||
|
if (r != null) parts.push('rank ' + r);
|
||||||
|
return parts.join(' · ') || 'score n/a';
|
||||||
}}
|
}}
|
||||||
|
|
||||||
function renderHits() {{
|
function renderHits() {{
|
||||||
@@ -837,18 +938,21 @@ header p {{ margin:.25rem 0 0; font-size:.85rem; color:rgba(255,255,255,.88); }}
|
|||||||
return;
|
return;
|
||||||
}}
|
}}
|
||||||
emptyHit.hidden = true;
|
emptyHit.hidden = true;
|
||||||
pickMeta.textContent = `pixel (${{marker.x}}, ${{marker.y}}) — ${{lastHits.length}} class(es) overlapping`;
|
const hidden = Math.max(0, lastAllCount - lastHits.length);
|
||||||
|
pickMeta.textContent = hidden > 0
|
||||||
|
? `pixel (${{marker.x}}, ${{marker.y}}) — top ${{lastHits.length}} / ${{lastAllCount}} overlapping (hidden ${{hidden}})`
|
||||||
|
: `pixel (${{marker.x}}, ${{marker.y}}) — top ${{lastHits.length}} / ${{lastAllCount}} overlapping`;
|
||||||
|
|
||||||
lastHits.forEach((h, i) => {{
|
lastHits.forEach((h, i) => {{
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
if (i === 0) li.classList.add('top');
|
if (i === 0) li.classList.add('top');
|
||||||
li.style.borderLeftColor = `rgb(${{h.color[0]}},${{h.color[1]}},${{h.color[2]}})`;
|
li.style.borderLeftColor = `rgb(${{h.color[0]}},${{h.color[1]}},${{h.color[2]}})`;
|
||||||
const rank = i === 0 ? '<span class="rank-badge">priority #1</span>' : `<span class="rank-badge">#${{i+1}}</span>`;
|
const rank = `<span class="rank-badge">#${{i+1}}</span>`;
|
||||||
li.innerHTML = `
|
li.innerHTML = `
|
||||||
<span class="sw" style="background:rgb(${{h.color[0]}},${{h.color[1]}},${{h.color[2]}})"></span>
|
<span class="sw" style="background:rgb(${{h.color[0]}},${{h.color[1]}},${{h.color[2]}})"></span>
|
||||||
<div><div class="name">${{h.prompt}}${{rank}}</div></div>
|
<div><div class="name">${{h.prompt}}${{rank}}</div></div>
|
||||||
<div class="score">${{fmtScore(h)}}</div>
|
<div class="score">${{fmtScore(h)}}</div>
|
||||||
<div class="sub">n_objects=${{h.n_objects ?? '-'}} · pixels=${{(h.pixels != null ? Number(h.pixels).toLocaleString() : '-')}}</div>
|
<div class="sub">n_objects=${{h.n_objects ?? '-'}} · pixels=${{(h.pixels != null ? Number(h.pixels).toLocaleString() : '-')}} · rank=${{Number(h.rank_score).toFixed(3)}}</div>
|
||||||
`;
|
`;
|
||||||
hitList.appendChild(li);
|
hitList.appendChild(li);
|
||||||
|
|
||||||
@@ -875,11 +979,16 @@ header p {{ margin:.25rem 0 0; font-size:.85rem; color:rgba(255,255,255,.88); }}
|
|||||||
scores_mean: c.scores_mean,
|
scores_mean: c.scores_mean,
|
||||||
n_objects: c.n_objects,
|
n_objects: c.n_objects,
|
||||||
pixels: c.pixels,
|
pixels: c.pixels,
|
||||||
|
rank_score: c.rank_score,
|
||||||
}}));
|
}}));
|
||||||
setStatus('loading overlay…');
|
setStatus('loading overlay…');
|
||||||
overlayImg = await loadImage(meta.overlay || '{overlay_name}');
|
overlayImg = await loadImage(meta.overlay || '{overlay_name}');
|
||||||
draw();
|
draw();
|
||||||
setStatus('ready — ' + classes.length + ' classes · click image (RLE hit-test)', true);
|
setStatus(
|
||||||
|
'ready — ' + classes.length + ' classes · click → top-' + TOP_K +
|
||||||
|
' by mean+log(area) (RLE)',
|
||||||
|
true
|
||||||
|
);
|
||||||
}} catch (e) {{
|
}} catch (e) {{
|
||||||
setStatus(String(e.message || e), false);
|
setStatus(String(e.message || e), false);
|
||||||
try {{
|
try {{
|
||||||
@@ -1042,8 +1151,11 @@ def main() -> int:
|
|||||||
print(f"saved: {grid_path}")
|
print(f"saved: {grid_path}")
|
||||||
|
|
||||||
# RLE hit data for interactive click query (file:// safe, no getImageData)
|
# RLE hit data for interactive click query (file:// safe, no getImageData)
|
||||||
print("exporting RLE hit data for interactive HTML...")
|
top_k = max(1, int(args.top_k))
|
||||||
classes_meta = export_hitmasks(out_dir, ordered, legend, base.size)
|
print(f"exporting RLE hit data for interactive HTML (top_k={top_k})...")
|
||||||
|
classes_meta = export_hitmasks(
|
||||||
|
out_dir, ordered, legend, base.size, top_k=top_k
|
||||||
|
)
|
||||||
print(f"saved: {out_dir / 'interactive_meta.json'} ({len(classes_meta)} classes, RLE)")
|
print(f"saved: {out_dir / 'interactive_meta.json'} ({len(classes_meta)} classes, RLE)")
|
||||||
|
|
||||||
merge_summary = {
|
merge_summary = {
|
||||||
@@ -1052,6 +1164,13 @@ def main() -> int:
|
|||||||
"n_classes": len(legend),
|
"n_classes": len(legend),
|
||||||
"max_side": args.max_side,
|
"max_side": args.max_side,
|
||||||
"alpha": args.alpha,
|
"alpha": args.alpha,
|
||||||
|
"top_k": 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": top_k,
|
||||||
|
},
|
||||||
"overview_size": list(base.size),
|
"overview_size": list(base.size),
|
||||||
"classes": legend,
|
"classes": legend,
|
||||||
"outputs": {
|
"outputs": {
|
||||||
@@ -1075,6 +1194,8 @@ def main() -> int:
|
|||||||
"width": base.size[0],
|
"width": base.size[0],
|
||||||
"height": base.size[1],
|
"height": base.size[1],
|
||||||
"overlay": overlay_path.name,
|
"overlay": overlay_path.name,
|
||||||
|
"top_k": top_k,
|
||||||
|
"ranking": merge_summary["ranking"],
|
||||||
"classes": classes_meta,
|
"classes": classes_meta,
|
||||||
}
|
}
|
||||||
# Use overlay without side legend so click coords map 1:1 to hitmasks
|
# Use overlay without side legend so click coords map 1:1 to hitmasks
|
||||||
@@ -1085,8 +1206,9 @@ def main() -> int:
|
|||||||
legend=legend,
|
legend=legend,
|
||||||
meta=merge_summary,
|
meta=merge_summary,
|
||||||
interactive_meta=interactive_meta,
|
interactive_meta=interactive_meta,
|
||||||
|
top_k=top_k,
|
||||||
)
|
)
|
||||||
print(f"saved: {html_path} (interactive click inspect)")
|
print(f"saved: {html_path} (interactive top-{top_k} by mean+log area)")
|
||||||
|
|
||||||
print("\nClasses merged:")
|
print("\nClasses merged:")
|
||||||
for ent in legend:
|
for ent in legend:
|
||||||
|
|||||||
Reference in New Issue
Block a user