ajuste nos scripts de review e auditoria
This commit is contained in:
parent
f48741f92d
commit
ff06844686
|
|
@ -43,7 +43,7 @@ Exemplos:
|
||||||
--save-visuals --visual-every 20
|
--save-visuals --visual-every 20
|
||||||
|
|
||||||
# Revisão manual e criação do fixed/group
|
# Revisão manual e criação do fixed/group
|
||||||
python -m audito.audit_dataset_manual ^
|
python -m audit.audit_dataset_manual ^
|
||||||
--input_path dataset/1024x640/group ^
|
--input_path dataset/1024x640/group ^
|
||||||
--out_dir audit_out_manual ^
|
--out_dir audit_out_manual ^
|
||||||
--manual-review --build-fixed-dataset --save-rejected-previews
|
--manual-review --build-fixed-dataset --save-rejected-previews
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,26 @@ IMPORTANTE: discordância modelo x GT NÃO é tratada automaticamente como erro
|
||||||
modelo. Em um dataset com rótulos imperfeitos, um conflito de alta confiança pode
|
modelo. Em um dataset com rótulos imperfeitos, um conflito de alta confiança pode
|
||||||
ser justamente um forte candidato a máscara incorreta.
|
ser justamente um forte candidato a máscara incorreta.
|
||||||
|
|
||||||
Saída de revisão (quando não usa --report_only):
|
Saída de revisão física RAW-SAFE (quando não usa --report_only):
|
||||||
|
|
||||||
dataset/revisao/group/<grupo>/
|
dataset/revisao/group/<grupo>/
|
||||||
previews/<base>.png
|
raw_previews/<base>.png # PREVIEW ORIGINAL de dataset/brutas, sem warp/resize
|
||||||
masks/<base>.png # GT semantic colorido
|
raw_masks/<base>.png # MASK ORIGINAL de dataset/brutas, sem warp/resize
|
||||||
predictions/<base>.png # predição semantic colorida
|
predictions_raw/<base>.png # prediction já reprojetada para o domínio RAW
|
||||||
panels/<base>.png # opcional, --save_panels
|
panels/<base>.png # opcional, --save_panels
|
||||||
final_masks/<base>.png # NÃO é tocado pelo auditor
|
final_masks/<base>.png # NÃO é tocado pelo auditor
|
||||||
review_order.csv
|
review_order.csv
|
||||||
|
|
||||||
|
dataset/revisao/group/review_domain_manifest.csv
|
||||||
|
dataset/revisao/group/README_DOMAIN_CONTRACT.txt
|
||||||
|
|
||||||
|
IMPORTANTE:
|
||||||
|
- raw_previews/raw_masks pertencem ao espaço de anotação BRUTO.
|
||||||
|
- predictions_raw pertence ao espaço RAW (modelo retroprojetado).
|
||||||
|
- NUNCA faça apenas resize entre esses dois domínios para reinjetar máscaras.
|
||||||
|
- Esta versão deliberadamente NÃO cria os diretórios legados previews/ e masks/,
|
||||||
|
para impedir que um reviewer antigo misture os domínios silenciosamente.
|
||||||
|
|
||||||
Modos de exportação:
|
Modos de exportação:
|
||||||
--export_mode flagged
|
--export_mode flagged
|
||||||
Exporta casos acionados pelos critérios técnicos.
|
Exporta casos acionados pelos critérios técnicos.
|
||||||
|
|
@ -162,7 +172,13 @@ def clear_dir(path: Path) -> None:
|
||||||
|
|
||||||
|
|
||||||
def clear_generated_review(review_group_root: Path) -> None:
|
def clear_generated_review(review_group_root: Path) -> None:
|
||||||
"""Limpa somente artefatos regeneráveis e PRESERVA final_masks/."""
|
"""
|
||||||
|
Limpa somente artefatos regeneráveis e PRESERVA final_masks/.
|
||||||
|
|
||||||
|
Também remove os diretórios legados previews/ e masks/ para evitar que
|
||||||
|
arquivos de domínio final de execuções antigas sejam confundidos com
|
||||||
|
raw_previews/raw_masks desta versão RAW-SAFE.
|
||||||
|
"""
|
||||||
if not review_group_root.exists():
|
if not review_group_root.exists():
|
||||||
review_group_root.mkdir(parents=True, exist_ok=True)
|
review_group_root.mkdir(parents=True, exist_ok=True)
|
||||||
return
|
return
|
||||||
|
|
@ -171,7 +187,11 @@ def clear_generated_review(review_group_root: Path) -> None:
|
||||||
if not group_dir.is_dir():
|
if not group_dir.is_dir():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for name in ("previews", "masks", "predictions", "panels"):
|
for name in (
|
||||||
|
"previews", "masks", # legado perigoso
|
||||||
|
"raw_previews", "raw_masks", "predictions_raw",
|
||||||
|
"aligned_previews", "predictions", "panels", # legados/debug
|
||||||
|
):
|
||||||
p = group_dir / name
|
p = group_dir / name
|
||||||
if p.exists():
|
if p.exists():
|
||||||
shutil.rmtree(p)
|
shutil.rmtree(p)
|
||||||
|
|
@ -180,6 +200,11 @@ def clear_generated_review(review_group_root: Path) -> None:
|
||||||
if order_csv.exists():
|
if order_csv.exists():
|
||||||
order_csv.unlink()
|
order_csv.unlink()
|
||||||
|
|
||||||
|
for name in ("review_domain_manifest.csv", "README_DOMAIN_CONTRACT.txt"):
|
||||||
|
p = review_group_root / name
|
||||||
|
if p.exists():
|
||||||
|
p.unlink()
|
||||||
|
|
||||||
|
|
||||||
def load_test_module(script_path: Path):
|
def load_test_module(script_path: Path):
|
||||||
if not script_path.is_file():
|
if not script_path.is_file():
|
||||||
|
|
@ -611,6 +636,437 @@ def build_reasons(
|
||||||
return reasons
|
return reasons
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Fontes BRUTAS para revisão RAW-SAFE
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
IMAGE_EXTS = (".png", ".jpg", ".jpeg", ".bmp", ".webp", ".tif", ".tiff")
|
||||||
|
|
||||||
|
|
||||||
|
def _image_files_by_stem(directory: Path) -> Dict[str, Path]:
|
||||||
|
out: Dict[str, Path] = {}
|
||||||
|
if not directory.is_dir():
|
||||||
|
return out
|
||||||
|
for p in sorted(directory.iterdir()):
|
||||||
|
if not p.is_file() or p.suffix.lower() not in IMAGE_EXTS:
|
||||||
|
continue
|
||||||
|
# Se houver duas extensões com o mesmo stem na mesma pasta, falha fechado.
|
||||||
|
if p.stem in out and out[p.stem].resolve() != p.resolve():
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Base duplicada em {directory}: {p.stem}\n"
|
||||||
|
f" - {out[p.stem]}\n"
|
||||||
|
f" - {p}"
|
||||||
|
)
|
||||||
|
out[p.stem] = p
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def index_raw_review_sources(raw_group_root: Path):
|
||||||
|
"""
|
||||||
|
Indexa pares preview+mask diretamente de dataset/brutas/group/<grupo>.
|
||||||
|
|
||||||
|
Retorna:
|
||||||
|
by_key[(group, base)] -> dict
|
||||||
|
by_base[base] -> list[dict]
|
||||||
|
|
||||||
|
O par só entra se preview e mask coexistirem DENTRO do mesmo grupo bruto.
|
||||||
|
"""
|
||||||
|
if not raw_group_root.is_dir():
|
||||||
|
raise FileNotFoundError(f"RAW group não encontrado: {raw_group_root}")
|
||||||
|
|
||||||
|
by_key: Dict[Tuple[str, str], dict] = {}
|
||||||
|
by_base: Dict[str, List[dict]] = defaultdict(list)
|
||||||
|
|
||||||
|
for group_dir in sorted(p for p in raw_group_root.iterdir() if p.is_dir()):
|
||||||
|
previews = _image_files_by_stem(group_dir / "previews")
|
||||||
|
masks = _image_files_by_stem(group_dir / "masks")
|
||||||
|
common = sorted(set(previews) & set(masks))
|
||||||
|
|
||||||
|
for base in common:
|
||||||
|
rec = {
|
||||||
|
"group": group_dir.name,
|
||||||
|
"base": base,
|
||||||
|
"preview": previews[base],
|
||||||
|
"mask": masks[base],
|
||||||
|
}
|
||||||
|
key = (group_dir.name, base)
|
||||||
|
if key in by_key:
|
||||||
|
raise RuntimeError(f"Bundle RAW duplicado para {key}: {by_key[key]} vs {rec}")
|
||||||
|
by_key[key] = rec
|
||||||
|
by_base[base].append(rec)
|
||||||
|
|
||||||
|
return by_key, by_base
|
||||||
|
|
||||||
|
|
||||||
|
def _same_image_pixels(path_a: Path, path_b: Path) -> bool:
|
||||||
|
if not path_a.is_file() or not path_b.is_file():
|
||||||
|
return False
|
||||||
|
|
||||||
|
a = imread_raw_required(path_a, cv2.IMREAD_UNCHANGED)
|
||||||
|
b = imread_raw_required(path_b, cv2.IMREAD_UNCHANGED)
|
||||||
|
|
||||||
|
return (
|
||||||
|
a.shape == b.shape
|
||||||
|
and a.dtype == b.dtype
|
||||||
|
and np.array_equal(a, b)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_normalized_meta_path(sample) -> Optional[Path]:
|
||||||
|
# dataset/960x600/group/<grupo>/tensors/<base>.npy
|
||||||
|
# ↓
|
||||||
|
# dataset/960x600/group/<grupo>/metas/<base>.json
|
||||||
|
tensor_path = Path(sample.tensor_path)
|
||||||
|
|
||||||
|
meta_path = (
|
||||||
|
tensor_path.parent.parent
|
||||||
|
/ "metas"
|
||||||
|
/ f"{sample.base}.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
return meta_path if meta_path.is_file() else None
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_source_path_from_normalized_meta(
|
||||||
|
value,
|
||||||
|
normalized_meta_path: Path,
|
||||||
|
dataset_root: Path,
|
||||||
|
) -> Optional[Path]:
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
|
||||||
|
p = Path(str(value))
|
||||||
|
|
||||||
|
if p.is_absolute():
|
||||||
|
return p if p.is_file() else None
|
||||||
|
|
||||||
|
# O normalize grava source_* relativo à raiz dataset/
|
||||||
|
p_dataset = dataset_root / p
|
||||||
|
if p_dataset.is_file():
|
||||||
|
return p_dataset.resolve()
|
||||||
|
|
||||||
|
# fallback conservador
|
||||||
|
p_meta = normalized_meta_path.parent / p
|
||||||
|
if p_meta.is_file():
|
||||||
|
return p_meta.resolve()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_raw_review_source(
|
||||||
|
sample,
|
||||||
|
by_key,
|
||||||
|
by_base,
|
||||||
|
dataset_root: Path,
|
||||||
|
) -> Tuple[dict, str]:
|
||||||
|
"""
|
||||||
|
Resolve fonte RAW de forma fail-closed.
|
||||||
|
|
||||||
|
Ordem:
|
||||||
|
1) mesmo group+base;
|
||||||
|
2) base globalmente único;
|
||||||
|
3) provenance do meta normalizado + comparação pixel-exact;
|
||||||
|
4) ambiguidade continua sendo erro.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = (str(sample.group), str(sample.base))
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# 1. Caminho trivial
|
||||||
|
# --------------------------------------------------------
|
||||||
|
if key in by_key:
|
||||||
|
return by_key[key], "same_group"
|
||||||
|
|
||||||
|
matches = list(by_base.get(str(sample.base), []))
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# 2. Base único em todo RAW
|
||||||
|
# --------------------------------------------------------
|
||||||
|
if len(matches) == 1:
|
||||||
|
return matches[0], "unique_base_fallback"
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Não encontrei preview+mask BRUTOS para "
|
||||||
|
f"{sample.group}/{sample.base}. "
|
||||||
|
"Não vou exportar revisão em domínio errado."
|
||||||
|
)
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# 3. Base duplicado:
|
||||||
|
# usa provenance gravada pelo normalize.
|
||||||
|
# --------------------------------------------------------
|
||||||
|
normalized_meta_path = _resolve_normalized_meta_path(sample)
|
||||||
|
|
||||||
|
if normalized_meta_path is not None:
|
||||||
|
nmeta = load_json(normalized_meta_path)
|
||||||
|
|
||||||
|
source_preview = _resolve_source_path_from_normalized_meta(
|
||||||
|
nmeta.get("source_preview"),
|
||||||
|
normalized_meta_path,
|
||||||
|
dataset_root,
|
||||||
|
)
|
||||||
|
|
||||||
|
source_mask = _resolve_source_path_from_normalized_meta(
|
||||||
|
nmeta.get("source_mask"),
|
||||||
|
normalized_meta_path,
|
||||||
|
dataset_root,
|
||||||
|
)
|
||||||
|
|
||||||
|
provenance_matches = []
|
||||||
|
|
||||||
|
for rec in matches:
|
||||||
|
evidence = []
|
||||||
|
|
||||||
|
if source_preview is not None:
|
||||||
|
evidence.append(
|
||||||
|
_same_image_pixels(
|
||||||
|
source_preview,
|
||||||
|
Path(rec["preview"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if source_mask is not None:
|
||||||
|
evidence.append(
|
||||||
|
_same_image_pixels(
|
||||||
|
source_mask,
|
||||||
|
Path(rec["mask"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Só aceita quando existe evidência e TODA evidência
|
||||||
|
# disponível aponta para esse mesmo bundle.
|
||||||
|
if evidence and all(evidence):
|
||||||
|
provenance_matches.append(rec)
|
||||||
|
|
||||||
|
if len(provenance_matches) == 1:
|
||||||
|
rec = provenance_matches[0]
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"[RAW-SAFE][PROVENANCE] "
|
||||||
|
f"{sample.group}/{sample.base} "
|
||||||
|
f"-> RAW {rec['group']}/{rec['base']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return rec, "provenance_pixel_exact"
|
||||||
|
|
||||||
|
if len(provenance_matches) > 1:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Proveniência ainda ambígua para "
|
||||||
|
f"{sample.group}/{sample.base}: "
|
||||||
|
+ ", ".join(
|
||||||
|
f"{m['group']}:{m['preview']}"
|
||||||
|
for m in provenance_matches
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# 4. Continua fail-closed
|
||||||
|
# --------------------------------------------------------
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Base RAW ambígua e provenance não resolveu para "
|
||||||
|
f"{sample.group}/{sample.base}: "
|
||||||
|
+ ", ".join(
|
||||||
|
f"{m['group']}:{m['preview']}"
|
||||||
|
for m in matches
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def imread_raw_required(path: Path, flags: int) -> np.ndarray:
|
||||||
|
img = cv2.imread(str(path), flags)
|
||||||
|
if img is None or img.size == 0:
|
||||||
|
# Fallback robusto para caminhos Windows/PyInstaller.
|
||||||
|
try:
|
||||||
|
raw = np.fromfile(str(path), dtype=np.uint8)
|
||||||
|
img = cv2.imdecode(raw, flags)
|
||||||
|
except Exception:
|
||||||
|
img = None
|
||||||
|
if img is None or img.size == 0:
|
||||||
|
raise RuntimeError(f"Falha ao abrir imagem RAW: {path}")
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def write_png_required(path: Path, img: np.ndarray) -> None:
|
||||||
|
ensure_dir(path.parent)
|
||||||
|
ok = cv2.imwrite(str(path), img, [cv2.IMWRITE_PNG_COMPRESSION, 1])
|
||||||
|
if not ok:
|
||||||
|
raise RuntimeError(f"Falha ao gravar PNG: {path}")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_raw_source_geometry(raw_source: dict) -> Tuple[int, int]:
|
||||||
|
"""
|
||||||
|
Valida que preview e mask brutos vivem no mesmo espaço espacial.
|
||||||
|
Não faz resize, crop, warp ou qualquer outra transformação.
|
||||||
|
"""
|
||||||
|
preview = imread_raw_required(Path(raw_source["preview"]), cv2.IMREAD_COLOR)
|
||||||
|
mask = imread_raw_required(Path(raw_source["mask"]), cv2.IMREAD_UNCHANGED)
|
||||||
|
if mask.ndim == 2:
|
||||||
|
mh, mw = mask.shape[:2]
|
||||||
|
else:
|
||||||
|
mh, mw = mask.shape[:2]
|
||||||
|
ph, pw = preview.shape[:2]
|
||||||
|
if (ph, pw) != (mh, mw):
|
||||||
|
raise RuntimeError(
|
||||||
|
"Preview/mask BRUTOS não têm a mesma geometria:\n"
|
||||||
|
f" preview={raw_source['preview']} shape={pw}x{ph}\n"
|
||||||
|
f" mask={raw_source['mask']} shape={mw}x{mh}"
|
||||||
|
)
|
||||||
|
return pw, ph
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_bgr3(img: np.ndarray) -> np.ndarray:
|
||||||
|
if img.ndim == 2:
|
||||||
|
return cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
|
||||||
|
if img.ndim == 3 and img.shape[2] == 4:
|
||||||
|
return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
|
||||||
|
if img.ndim == 3 and img.shape[2] >= 3:
|
||||||
|
return img[:, :, :3]
|
||||||
|
raise RuntimeError(f"Imagem inválida para conversão BGR: shape={getattr(img, 'shape', None)}")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_normalized_meta_path(sample) -> Optional[Path]:
|
||||||
|
candidates = []
|
||||||
|
for attr in ("meta_path", "metadata_path"):
|
||||||
|
value = getattr(sample, attr, None)
|
||||||
|
if value:
|
||||||
|
candidates.append(Path(value))
|
||||||
|
|
||||||
|
tensor_path = getattr(sample, "tensor_path", None)
|
||||||
|
if tensor_path:
|
||||||
|
tp = Path(tensor_path)
|
||||||
|
candidates.append(tp.parent.parent / "metas" / f"{sample.base}.json")
|
||||||
|
|
||||||
|
preview_path = getattr(sample, "preview_path", None)
|
||||||
|
if preview_path:
|
||||||
|
pp = Path(preview_path)
|
||||||
|
candidates.append(pp.parent.parent / "metas" / f"{sample.base}.json")
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
for c in candidates:
|
||||||
|
try:
|
||||||
|
rc = c.resolve()
|
||||||
|
except Exception:
|
||||||
|
rc = c
|
||||||
|
key = str(rc)
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
if rc.is_file():
|
||||||
|
return rc
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_fusion_result_for_sample(sample) -> Tuple[dict, Path]:
|
||||||
|
meta_path = resolve_normalized_meta_path(sample)
|
||||||
|
if meta_path is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Meta normalizado não encontrado para {sample.group}/{sample.base}. "
|
||||||
|
"Necessário para reprojetar a prediction para o domínio RAW."
|
||||||
|
)
|
||||||
|
|
||||||
|
meta = load_json(meta_path)
|
||||||
|
processing = meta.get("processing") or {}
|
||||||
|
fusion_result = processing.get("fusion_result") or {}
|
||||||
|
if not isinstance(fusion_result, dict) or not fusion_result:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"fusion_result ausente/inválido em {meta_path} para {sample.group}/{sample.base}"
|
||||||
|
)
|
||||||
|
return fusion_result, meta_path
|
||||||
|
|
||||||
|
|
||||||
|
def compose_prediction_raw_from_fusion(
|
||||||
|
raw_mask_bgr: np.ndarray,
|
||||||
|
pred_bgr: np.ndarray,
|
||||||
|
fusion_result: dict,
|
||||||
|
sample_key: str,
|
||||||
|
) -> Tuple[np.ndarray, dict]:
|
||||||
|
"""
|
||||||
|
Inverso coerente com o normalize atual:
|
||||||
|
raw_mask (espaço RGB/CAM_A bruto) -> crop_box em ref_shape -> resize nearest para target_size
|
||||||
|
|
||||||
|
Aqui fazemos o caminho inverso APENAS para a prediction semantic:
|
||||||
|
pred_sem no target_size -> resize nearest para tamanho do crop_box -> cola sobre a máscara humana RAW,
|
||||||
|
preservando fora do crop.
|
||||||
|
|
||||||
|
Não existe homografia inversa da mask semantic porque a própria GT semantic no normalize
|
||||||
|
nunca foi homografada; ela apenas sofreu crop+resize no espaço de referência RGB/CAM_A.
|
||||||
|
"""
|
||||||
|
raw_mask_bgr = _ensure_bgr3(raw_mask_bgr)
|
||||||
|
pred_bgr = _ensure_bgr3(pred_bgr)
|
||||||
|
|
||||||
|
raw_h, raw_w = raw_mask_bgr.shape[:2]
|
||||||
|
model_h, model_w = pred_bgr.shape[:2]
|
||||||
|
|
||||||
|
ref_shape = fusion_result.get("ref_shape")
|
||||||
|
if isinstance(ref_shape, (list, tuple)) and len(ref_shape) == 2:
|
||||||
|
ref_h, ref_w = int(ref_shape[0]), int(ref_shape[1])
|
||||||
|
else:
|
||||||
|
ref_h, ref_w = raw_h, raw_w
|
||||||
|
|
||||||
|
crop_box = fusion_result.get("crop_box")
|
||||||
|
if crop_box is None or len(crop_box) != 4:
|
||||||
|
x0_ref, y0_ref, x1_ref, y1_ref = 0, 0, ref_w, ref_h
|
||||||
|
else:
|
||||||
|
x0_ref, y0_ref, x1_ref, y1_ref = [int(v) for v in crop_box]
|
||||||
|
|
||||||
|
x0_ref = max(0, min(ref_w - 1, x0_ref)) if ref_w > 0 else 0
|
||||||
|
y0_ref = max(0, min(ref_h - 1, y0_ref)) if ref_h > 0 else 0
|
||||||
|
x1_ref = max(x0_ref + 1, min(ref_w, x1_ref)) if ref_w > 0 else 1
|
||||||
|
y1_ref = max(y0_ref + 1, min(ref_h, y1_ref)) if ref_h > 0 else 1
|
||||||
|
|
||||||
|
crop_w_ref = x1_ref - x0_ref
|
||||||
|
crop_h_ref = y1_ref - y0_ref
|
||||||
|
if crop_w_ref <= 0 or crop_h_ref <= 0:
|
||||||
|
raise RuntimeError(f"crop_box inválido em {sample_key}: {crop_box}")
|
||||||
|
|
||||||
|
target_size = fusion_result.get("target_size")
|
||||||
|
if isinstance(target_size, (list, tuple)) and len(target_size) == 2:
|
||||||
|
target_w, target_h = int(target_size[0]), int(target_size[1])
|
||||||
|
if (target_w, target_h) != (model_w, model_h):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Prediction com shape incompatível em {sample_key}: "
|
||||||
|
f"pred={model_w}x{model_h} fusion.target_size={target_w}x{target_h}"
|
||||||
|
)
|
||||||
|
|
||||||
|
pred_crop_ref = cv2.resize(pred_bgr, (crop_w_ref, crop_h_ref), interpolation=cv2.INTER_NEAREST)
|
||||||
|
|
||||||
|
sx = float(raw_w) / float(ref_w) if ref_w > 0 else 1.0
|
||||||
|
sy = float(raw_h) / float(ref_h) if ref_h > 0 else 1.0
|
||||||
|
x0_raw = int(round(x0_ref * sx))
|
||||||
|
y0_raw = int(round(y0_ref * sy))
|
||||||
|
x1_raw = int(round(x1_ref * sx))
|
||||||
|
y1_raw = int(round(y1_ref * sy))
|
||||||
|
|
||||||
|
x0_raw = max(0, min(raw_w - 1, x0_raw)) if raw_w > 0 else 0
|
||||||
|
y0_raw = max(0, min(raw_h - 1, y0_raw)) if raw_h > 0 else 0
|
||||||
|
x1_raw = max(x0_raw + 1, min(raw_w, x1_raw)) if raw_w > 0 else 1
|
||||||
|
y1_raw = max(y0_raw + 1, min(raw_h, y1_raw)) if raw_h > 0 else 1
|
||||||
|
|
||||||
|
crop_w_raw = x1_raw - x0_raw
|
||||||
|
crop_h_raw = y1_raw - y0_raw
|
||||||
|
if crop_w_raw <= 0 or crop_h_raw <= 0:
|
||||||
|
raise RuntimeError(f"crop_box escalado inválido em {sample_key}: raw=({x0_raw},{y0_raw},{x1_raw},{y1_raw})")
|
||||||
|
|
||||||
|
if (crop_w_raw, crop_h_raw) != (crop_w_ref, crop_h_ref):
|
||||||
|
pred_crop_raw = cv2.resize(pred_crop_ref, (crop_w_raw, crop_h_raw), interpolation=cv2.INTER_NEAREST)
|
||||||
|
else:
|
||||||
|
pred_crop_raw = pred_crop_ref
|
||||||
|
|
||||||
|
composite = raw_mask_bgr.copy()
|
||||||
|
composite[y0_raw:y1_raw, x0_raw:x1_raw] = pred_crop_raw[:crop_h_raw, :crop_w_raw]
|
||||||
|
|
||||||
|
geom = {
|
||||||
|
"ref_shape": [int(ref_w), int(ref_h)],
|
||||||
|
"raw_shape": [int(raw_w), int(raw_h)],
|
||||||
|
"target_size": [int(model_w), int(model_h)],
|
||||||
|
"crop_box_ref": [int(x0_ref), int(y0_ref), int(x1_ref), int(y1_ref)],
|
||||||
|
"crop_box_raw": [int(x0_raw), int(y0_raw), int(x1_raw), int(y1_raw)],
|
||||||
|
}
|
||||||
|
return composite, geom
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Visualização/copiar casos de revisão
|
# Visualização/copiar casos de revisão
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
@ -866,66 +1322,110 @@ def save_review_item(
|
||||||
chw: np.ndarray,
|
chw: np.ndarray,
|
||||||
gt_sem: np.ndarray,
|
gt_sem: np.ndarray,
|
||||||
pred_sem: np.ndarray,
|
pred_sem: np.ndarray,
|
||||||
|
raw_source: dict,
|
||||||
review_group_root: Path,
|
review_group_root: Path,
|
||||||
base_module,
|
base_module,
|
||||||
semantic_cmap: Dict[int, Tuple[int, int, int]],
|
semantic_cmap: Dict[int, Tuple[int, int, int]],
|
||||||
ignore_id: int,
|
ignore_id: int,
|
||||||
input_channel_names: Sequence[str],
|
input_channel_names: Sequence[str],
|
||||||
save_panels: bool,
|
save_panels: bool,
|
||||||
) -> None:
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Exporta exatamente o contrato simplificado para revisão humana:
|
||||||
|
|
||||||
|
raw_previews/
|
||||||
|
preview ORIGINAL em dataset/brutas (domínio real do sensor)
|
||||||
|
|
||||||
|
raw_masks/
|
||||||
|
máscara humana ORIGINAL no domínio real do sensor
|
||||||
|
|
||||||
|
predictions_raw/
|
||||||
|
candidata do modelo já convertida para o domínio RAW:
|
||||||
|
- parte do pred_sem no espaço final do modelo;
|
||||||
|
- desfaz apenas o caminho usado no normalize semantic GT
|
||||||
|
(resize final <- crop_box no espaço RGB/CAM_A);
|
||||||
|
- preserva a máscara humana fora da região válida do crop.
|
||||||
|
|
||||||
|
Assim a ferramenta humana trabalha sempre em UM único preview e DUAS masks,
|
||||||
|
todos no domínio final correto das brutas.
|
||||||
|
"""
|
||||||
group_dir = review_group_root / sample.group
|
group_dir = review_group_root / sample.group
|
||||||
previews_dir = group_dir / "previews"
|
raw_previews_dir = group_dir / "raw_previews"
|
||||||
masks_dir = group_dir / "masks"
|
raw_masks_dir = group_dir / "raw_masks"
|
||||||
predictions_dir = group_dir / "predictions"
|
predictions_raw_dir = group_dir / "predictions_raw"
|
||||||
|
final_masks_dir = group_dir / "final_masks"
|
||||||
panels_dir = group_dir / "panels"
|
panels_dir = group_dir / "panels"
|
||||||
|
|
||||||
ensure_dir(previews_dir)
|
for p in (raw_previews_dir, raw_masks_dir, predictions_raw_dir, final_masks_dir):
|
||||||
ensure_dir(masks_dir)
|
ensure_dir(p)
|
||||||
ensure_dir(predictions_dir)
|
|
||||||
if save_panels:
|
if save_panels:
|
||||||
ensure_dir(panels_dir)
|
ensure_dir(panels_dir)
|
||||||
|
|
||||||
# Revisão humana usa somente PNG. Mesmo se o preview fonte for JPG,
|
raw_preview_bgr = imread_raw_required(Path(raw_source["preview"]), cv2.IMREAD_COLOR)
|
||||||
# a saída é normalizada para <base>.png.
|
raw_mask_native = imread_raw_required(Path(raw_source["mask"]), cv2.IMREAD_UNCHANGED)
|
||||||
preview_rgb = load_preview_rgb(sample, chw, base_module, input_channel_names)
|
raw_mask_bgr = _ensure_bgr3(raw_mask_native)
|
||||||
cv2.imwrite(
|
|
||||||
str(previews_dir / f"{sample.base}.png"),
|
raw_h, raw_w = raw_preview_bgr.shape[:2]
|
||||||
cv2.cvtColor(preview_rgb, cv2.COLOR_RGB2BGR),
|
mask_h, mask_w = raw_mask_bgr.shape[:2]
|
||||||
|
if (raw_h, raw_w) != (mask_h, mask_w):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Geometria RAW inválida em {sample.group}/{sample.base}: "
|
||||||
|
f"preview={raw_w}x{raw_h} mask={mask_w}x{mask_h}"
|
||||||
)
|
)
|
||||||
|
|
||||||
gt_rgb = base_module.ids_to_rgb(gt_sem, semantic_cmap, ignore_id)
|
raw_preview_out = raw_previews_dir / f"{sample.base}.png"
|
||||||
|
raw_mask_out = raw_masks_dir / f"{sample.base}.png"
|
||||||
|
prediction_raw_out = predictions_raw_dir / f"{sample.base}.png"
|
||||||
|
|
||||||
|
write_png_required(raw_preview_out, raw_preview_bgr)
|
||||||
|
write_png_required(raw_mask_out, raw_mask_bgr)
|
||||||
|
|
||||||
pred_rgb = base_module.ids_to_rgb(pred_sem, semantic_cmap, ignore_id)
|
pred_rgb = base_module.ids_to_rgb(pred_sem, semantic_cmap, ignore_id)
|
||||||
|
pred_bgr = cv2.cvtColor(pred_rgb, cv2.COLOR_RGB2BGR)
|
||||||
|
|
||||||
cv2.imwrite(
|
fusion_result, normalized_meta_path = load_fusion_result_for_sample(sample)
|
||||||
str(masks_dir / f"{sample.base}.png"),
|
pred_raw_bgr, geom = compose_prediction_raw_from_fusion(
|
||||||
cv2.cvtColor(gt_rgb, cv2.COLOR_RGB2BGR),
|
raw_mask_bgr=raw_mask_bgr,
|
||||||
)
|
pred_bgr=pred_bgr,
|
||||||
cv2.imwrite(
|
fusion_result=fusion_result,
|
||||||
str(predictions_dir / f"{sample.base}.png"),
|
sample_key=f"{sample.group}/{sample.base}",
|
||||||
cv2.cvtColor(pred_rgb, cv2.COLOR_RGB2BGR),
|
|
||||||
)
|
)
|
||||||
|
write_png_required(prediction_raw_out, pred_raw_bgr)
|
||||||
|
|
||||||
if save_panels:
|
if save_panels:
|
||||||
# Normaliza tamanho do preview para GT/pred.
|
raw_overlay_gt = cv2.addWeighted(raw_preview_bgr, 0.55, raw_mask_bgr, 0.45, 0.0)
|
||||||
h, w = gt_sem.shape[:2]
|
raw_overlay_model = cv2.addWeighted(raw_preview_bgr, 0.55, pred_raw_bgr, 0.45, 0.0)
|
||||||
if preview_rgb.shape[:2] != (h, w):
|
|
||||||
preview_rgb = cv2.resize(preview_rgb, (w, h), interpolation=cv2.INTER_AREA)
|
|
||||||
|
|
||||||
diff = np.zeros_like(gt_rgb)
|
|
||||||
valid = gt_sem != ignore_id
|
|
||||||
bad = valid & (gt_sem != pred_sem)
|
|
||||||
diff[bad] = (255, 40, 40)
|
|
||||||
|
|
||||||
overlay_gt = base_module.overlay_rgb(preview_rgb, gt_rgb, 0.45)
|
|
||||||
overlay_pred = base_module.overlay_rgb(preview_rgb, pred_rgb, 0.45)
|
|
||||||
panels = [
|
panels = [
|
||||||
("Preview", preview_rgb, sample.base),
|
("RAW preview", cv2.cvtColor(raw_preview_bgr, cv2.COLOR_BGR2RGB), f"{raw_w}x{raw_h}"),
|
||||||
("GT semantic", overlay_gt, f"grupo={sample.group}"),
|
("RAW GT overlay", cv2.cvtColor(raw_overlay_gt, cv2.COLOR_BGR2RGB), "human/original"),
|
||||||
("Pred semantic", overlay_pred, f"score={float(row['review_score']):.3f}"),
|
("RAW model candidate", cv2.cvtColor(raw_overlay_model, cv2.COLOR_BGR2RGB), "model reprojected to raw"),
|
||||||
("GT != Pred", diff, str(row.get("review_reasons", ""))),
|
|
||||||
]
|
]
|
||||||
panel = base_module.compose_grid(panels, cols=2, max_width=1600)
|
panel = base_module.compose_grid(panels, cols=2, max_width=1600)
|
||||||
cv2.imwrite(str(panels_dir / f"{sample.base}.png"), cv2.cvtColor(panel, cv2.COLOR_RGB2BGR))
|
write_png_required(panels_dir / f"{sample.base}.png", cv2.cvtColor(panel, cv2.COLOR_RGB2BGR))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"group": str(sample.group),
|
||||||
|
"base": str(sample.base),
|
||||||
|
"raw_source_group": str(raw_source["group"]),
|
||||||
|
"raw_preview_source": str(raw_source["preview"]),
|
||||||
|
"raw_mask_source": str(raw_source["mask"]),
|
||||||
|
"normalized_meta_source": str(normalized_meta_path),
|
||||||
|
"raw_preview_output": str(raw_preview_out),
|
||||||
|
"raw_mask_output": str(raw_mask_out),
|
||||||
|
"prediction_raw_output": str(prediction_raw_out),
|
||||||
|
"raw_width": int(raw_w),
|
||||||
|
"raw_height": int(raw_h),
|
||||||
|
"model_width": int(pred_sem.shape[1]),
|
||||||
|
"model_height": int(pred_sem.shape[0]),
|
||||||
|
"ref_width": int(geom["ref_shape"][0]),
|
||||||
|
"ref_height": int(geom["ref_shape"][1]),
|
||||||
|
"target_size": list(geom["target_size"]),
|
||||||
|
"crop_box_ref": list(geom["crop_box_ref"]),
|
||||||
|
"crop_box_raw": list(geom["crop_box_raw"]),
|
||||||
|
"review_preview_domain": "raw_annotation_space",
|
||||||
|
"review_mask_domain": "raw_annotation_space",
|
||||||
|
"geometry_contract": "PREDICTION_RAW = HUMAN_RAW outside crop + MODEL prediction backprojected via normalize crop_box/resize inverse",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
@ -966,6 +1466,14 @@ def main() -> None:
|
||||||
parser.add_argument("--test_script", default=None, help="Default: _9_test_multihead_v2.py ao lado deste script")
|
parser.add_argument("--test_script", default=None, help="Default: _9_test_multihead_v2.py ao lado deste script")
|
||||||
|
|
||||||
parser.add_argument("--out_root", default="dataset/revisao")
|
parser.add_argument("--out_root", default="dataset/revisao")
|
||||||
|
parser.add_argument(
|
||||||
|
"--raw_root",
|
||||||
|
default=None,
|
||||||
|
help=(
|
||||||
|
"Pasta dataset/brutas/group usada EXCLUSIVAMENTE como fonte de "
|
||||||
|
"raw_previews/raw_masks. Default: <config_dir>/dataset/brutas/group."
|
||||||
|
),
|
||||||
|
)
|
||||||
parser.add_argument("--run_name", default=None, help="Nome do relatório. Default: stem do checkpoint")
|
parser.add_argument("--run_name", default=None, help="Nome do relatório. Default: stem do checkpoint")
|
||||||
parser.add_argument("--report_only", action="store_true", help="Não copia casos para dataset/revisao/group")
|
parser.add_argument("--report_only", action="store_true", help="Não copia casos para dataset/revisao/group")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|
@ -983,14 +1491,14 @@ def main() -> None:
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--clear_review",
|
"--clear_review",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Limpa previews/masks/predictions/panels e PRESERVA final_masks.",
|
help="Limpa artefatos regeneráveis RAW-SAFE/legados e PRESERVA final_masks.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--clear_review_all",
|
"--clear_review_all",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="PERIGOSO: apaga toda a árvore group, inclusive final_masks.",
|
help="PERIGOSO: apaga toda a árvore group, inclusive final_masks.",
|
||||||
)
|
)
|
||||||
parser.add_argument("--save_panels", action="store_true", help="Salva painel preview/GT/pred/diff dos candidatos")
|
parser.add_argument("--save_panels", action="store_true", help="Salva painel RAW GT + domínio alinhado/pred sem misturar geometrias")
|
||||||
|
|
||||||
# Critérios de revisão. São deliberadamente conservadores.
|
# Critérios de revisão. São deliberadamente conservadores.
|
||||||
parser.add_argument("--high_confidence", type=float, default=0.85)
|
parser.add_argument("--high_confidence", type=float, default=0.85)
|
||||||
|
|
@ -1112,6 +1620,48 @@ def main() -> None:
|
||||||
if args.max_samples > 0:
|
if args.max_samples > 0:
|
||||||
samples = samples[: args.max_samples]
|
samples = samples[: args.max_samples]
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# Contrato RAW-SAFE para exportação física.
|
||||||
|
#
|
||||||
|
# As métricas continuam sendo calculadas no domínio normalizado/modelo.
|
||||||
|
# Já previews e masks para EDIÇÃO são buscados diretamente em BRUTAS.
|
||||||
|
# --------------------------------------------------------
|
||||||
|
raw_root: Optional[Path] = None
|
||||||
|
raw_source_map: Dict[Tuple[str, str], dict] = {}
|
||||||
|
raw_source_resolution: Dict[Tuple[str, str], str] = {}
|
||||||
|
|
||||||
|
if not args.report_only:
|
||||||
|
raw_root = (
|
||||||
|
resolve_path(args.raw_root, Path.cwd())
|
||||||
|
if args.raw_root
|
||||||
|
else (dataset_path / "brutas" / "group").resolve()
|
||||||
|
)
|
||||||
|
if raw_root is None or not raw_root.is_dir():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
"RAW root obrigatório para revisão física RAW-SAFE não encontrado: "
|
||||||
|
f"{raw_root}"
|
||||||
|
)
|
||||||
|
|
||||||
|
raw_by_key, raw_by_base = index_raw_review_sources(raw_root)
|
||||||
|
fallback_count = 0
|
||||||
|
for sample in samples:
|
||||||
|
rec, mode = resolve_raw_review_source(
|
||||||
|
sample,
|
||||||
|
raw_by_key,
|
||||||
|
raw_by_base,
|
||||||
|
dataset_path,
|
||||||
|
)
|
||||||
|
key = (str(sample.group), str(sample.base))
|
||||||
|
raw_source_map[key] = rec
|
||||||
|
raw_source_resolution[key] = mode
|
||||||
|
if mode != "same_group":
|
||||||
|
fallback_count += 1
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"[RAW-SAFE] raw_root={raw_root} | resolvidos={len(raw_source_map)}/{len(samples)} "
|
||||||
|
f"| fallback_base_unico={fallback_count}"
|
||||||
|
)
|
||||||
|
|
||||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||||
|
|
||||||
threshold_from_model: Optional[float] = None
|
threshold_from_model: Optional[float] = None
|
||||||
|
|
@ -1215,7 +1765,9 @@ def main() -> None:
|
||||||
n = len(samples)
|
n = len(samples)
|
||||||
print("=" * 72)
|
print("=" * 72)
|
||||||
print("DATASET REVIEW / MODEL MINING")
|
print("DATASET REVIEW / MODEL MINING")
|
||||||
print(f"Root : {root}")
|
print(f"Root model : {root}")
|
||||||
|
if not args.report_only:
|
||||||
|
print(f"Root RAW : {raw_root}")
|
||||||
print(f"Backend : {backend}")
|
print(f"Backend : {backend}")
|
||||||
print(f"Model : {model_path}")
|
print(f"Model : {model_path}")
|
||||||
if backend == "onnx":
|
if backend == "onnx":
|
||||||
|
|
@ -1350,6 +1902,18 @@ def main() -> None:
|
||||||
"tensor_path": str(sample.tensor_path or ""),
|
"tensor_path": str(sample.tensor_path or ""),
|
||||||
"preview_path": str(sample.preview_path or ""),
|
"preview_path": str(sample.preview_path or ""),
|
||||||
"semantic_mask_path": str(sample.masks.get("semantic") or ""),
|
"semantic_mask_path": str(sample.masks.get("semantic") or ""),
|
||||||
|
"raw_preview_path": (
|
||||||
|
str(raw_source_map.get((str(sample.group), str(sample.base)), {}).get("preview", ""))
|
||||||
|
if not args.report_only else ""
|
||||||
|
),
|
||||||
|
"raw_mask_path": (
|
||||||
|
str(raw_source_map.get((str(sample.group), str(sample.base)), {}).get("mask", ""))
|
||||||
|
if not args.report_only else ""
|
||||||
|
),
|
||||||
|
"raw_source_resolution": (
|
||||||
|
raw_source_resolution.get((str(sample.group), str(sample.base)), "")
|
||||||
|
if not args.report_only else ""
|
||||||
|
),
|
||||||
"inference_backend": backend,
|
"inference_backend": backend,
|
||||||
"inference_provider": args.onnx_provider if backend == "onnx" else str(device),
|
"inference_provider": args.onnx_provider if backend == "onnx" else str(device),
|
||||||
"inference_ms": float(t_inf),
|
"inference_ms": float(t_inf),
|
||||||
|
|
@ -1462,8 +2026,12 @@ def main() -> None:
|
||||||
selected_keys = {(r["group"], r["base"]) for r in selected_rows}
|
selected_keys = {(r["group"], r["base"]) for r in selected_rows}
|
||||||
|
|
||||||
# Copia apenas depois de finalizar ranking/caps.
|
# Copia apenas depois de finalizar ranking/caps.
|
||||||
|
domain_manifest_rows: List[dict] = []
|
||||||
|
domain_manifest_path = review_group_root / "review_domain_manifest.csv"
|
||||||
|
domain_readme_path = review_group_root / "README_DOMAIN_CONTRACT.txt"
|
||||||
|
|
||||||
if not args.report_only:
|
if not args.report_only:
|
||||||
print(f"\n[COPY] candidatos finais: {len(selected_rows)}")
|
print(f"\n[COPY RAW-SAFE] candidatos finais: {len(selected_rows)}")
|
||||||
sample_by_key = {(s.group, s.base): s for s in samples}
|
sample_by_key = {(s.group, s.base): s for s in samples}
|
||||||
|
|
||||||
for j, row in enumerate(selected_rows, 1):
|
for j, row in enumerate(selected_rows, 1):
|
||||||
|
|
@ -1489,12 +2057,19 @@ def main() -> None:
|
||||||
pred_sem = preds["semantic"]
|
pred_sem = preds["semantic"]
|
||||||
gt_sem = resize_ids(base.load_mask(sample.masks["semantic"]), pred_sem.shape[:2])
|
gt_sem = resize_ids(base.load_mask(sample.masks["semantic"]), pred_sem.shape[:2])
|
||||||
|
|
||||||
save_review_item(
|
raw_source = raw_source_map.get((str(sample.group), str(sample.base)))
|
||||||
|
if raw_source is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Fonte RAW desapareceu durante exportação: {sample.group}/{sample.base}"
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_row = save_review_item(
|
||||||
row=row,
|
row=row,
|
||||||
sample=sample,
|
sample=sample,
|
||||||
chw=chw,
|
chw=chw,
|
||||||
gt_sem=gt_sem,
|
gt_sem=gt_sem,
|
||||||
pred_sem=pred_sem,
|
pred_sem=pred_sem,
|
||||||
|
raw_source=raw_source,
|
||||||
review_group_root=review_group_root,
|
review_group_root=review_group_root,
|
||||||
base_module=base,
|
base_module=base,
|
||||||
semantic_cmap=semantic_cmap,
|
semantic_cmap=semantic_cmap,
|
||||||
|
|
@ -1502,6 +2077,10 @@ def main() -> None:
|
||||||
input_channel_names=input_channel_names,
|
input_channel_names=input_channel_names,
|
||||||
save_panels=args.save_panels,
|
save_panels=args.save_panels,
|
||||||
)
|
)
|
||||||
|
domain_row["raw_source_resolution"] = raw_source_resolution.get(
|
||||||
|
(str(sample.group), str(sample.base)), ""
|
||||||
|
)
|
||||||
|
domain_manifest_rows.append(domain_row)
|
||||||
|
|
||||||
# Se veio do cache, removemos assim que foi exportado. Se foi reinferencia,
|
# Se veio do cache, removemos assim que foi exportado. Se foi reinferencia,
|
||||||
# liberamos as referencias grandes imediatamente.
|
# liberamos as referencias grandes imediatamente.
|
||||||
|
|
@ -1529,6 +2108,32 @@ def main() -> None:
|
||||||
sorted(grows, key=lambda r: int(r.get("review_rank_group", 999999))),
|
sorted(grows, key=lambda r: int(r.get("review_rank_group", 999999))),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
write_csv(domain_manifest_path, domain_manifest_rows)
|
||||||
|
domain_readme_path.write_text(
|
||||||
|
"\n".join([
|
||||||
|
"REVIEW DOMAIN CONTRACT - RAW SAFE",
|
||||||
|
"",
|
||||||
|
"raw_previews/ + raw_masks/",
|
||||||
|
" -> espaco ORIGINAL de anotacao em dataset/brutas.",
|
||||||
|
" -> nenhum resize, crop, homografia ou warp e aplicado.",
|
||||||
|
"",
|
||||||
|
"predictions_raw/",
|
||||||
|
" -> candidata do modelo ja convertida para o espaco RAW.",
|
||||||
|
"",
|
||||||
|
"REGRA CRITICA:",
|
||||||
|
" NUNCA injete predictions/ alinhadas diretamente em brutas.",
|
||||||
|
" Esta versao ja faz a retroprojecao coerente com o normalize.",
|
||||||
|
"",
|
||||||
|
"Esta versao NAO cria previews/ e masks/ legados de proposito.",
|
||||||
|
"Um MaskReviewer antigo que espera previews/masks NAO deve ser usado neste output.",
|
||||||
|
"O reviewer RAW-safe deve editar/salvar final_masks no espaco RAW.",
|
||||||
|
"",
|
||||||
|
f"RAW root: {raw_root}",
|
||||||
|
f"Model root: {root}",
|
||||||
|
]),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
# ========================================================
|
# ========================================================
|
||||||
# Relatórios globais
|
# Relatórios globais
|
||||||
# ========================================================
|
# ========================================================
|
||||||
|
|
@ -1620,6 +2225,14 @@ def main() -> None:
|
||||||
"run_name": run_name,
|
"run_name": run_name,
|
||||||
"config": str(config_path),
|
"config": str(config_path),
|
||||||
"root": str(root),
|
"root": str(root),
|
||||||
|
"raw_root": str(raw_root) if raw_root is not None else None,
|
||||||
|
"review_domain_contract": {
|
||||||
|
"raw_previews": "raw_annotation_space",
|
||||||
|
"raw_masks": "raw_annotation_space",
|
||||||
|
"predictions_raw": "raw_annotation_space",
|
||||||
|
"prediction_backprojection": "inverse_of_normalize_semantic_path_using_fusion.crop_box_plus_resize",
|
||||||
|
"cross_domain_resize_allowed": False,
|
||||||
|
},
|
||||||
"backend": backend,
|
"backend": backend,
|
||||||
"model_path": str(model_path),
|
"model_path": str(model_path),
|
||||||
"checkpoint": str(ckpt_path) if ckpt_path is not None else None,
|
"checkpoint": str(ckpt_path) if ckpt_path is not None else None,
|
||||||
|
|
@ -1688,6 +2301,8 @@ def main() -> None:
|
||||||
"review_report": str(report_path),
|
"review_report": str(report_path),
|
||||||
"review_candidates": str(candidates_path),
|
"review_candidates": str(candidates_path),
|
||||||
"review_group_root": str(review_group_root),
|
"review_group_root": str(review_group_root),
|
||||||
|
"review_domain_manifest": str(domain_manifest_path) if not args.report_only else None,
|
||||||
|
"review_domain_readme": str(domain_readme_path) if not args.report_only else None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1707,7 +2322,9 @@ def main() -> None:
|
||||||
"- review_score serve apenas para RANKING de suspeitos.",
|
"- review_score serve apenas para RANKING de suspeitos.",
|
||||||
"- suspicion_pct = review_score*100 e NAO e probabilidade de mascara errada.",
|
"- suspicion_pct = review_score*100 e NAO e probabilidade de mascara errada.",
|
||||||
f"- export_mode={args.export_mode} min_suspicion_pct={args.min_suspicion_pct:.1f}",
|
f"- export_mode={args.export_mode} min_suspicion_pct={args.min_suspicion_pct:.1f}",
|
||||||
"- Exportacao fisica usa somente PNG.",
|
"- Exportacao fisica simplificada usa raw_previews + raw_masks + predictions_raw.",
|
||||||
|
"- predictions_raw ja volta no dominio RAW usando o inverso coerente do normalize semantic.",
|
||||||
|
"- NUNCA usar resize simples para converter entre o dominio final e o RAW.",
|
||||||
"- --clear_review PRESERVA final_masks; --clear_review_all apaga tudo.",
|
"- --clear_review PRESERVA final_masks; --clear_review_all apaga tudo.",
|
||||||
"- Para comparar checkpoints, use --report_only e run_name diferentes.",
|
"- Para comparar checkpoints, use --report_only e run_name diferentes.",
|
||||||
"",
|
"",
|
||||||
|
|
@ -1740,7 +2357,9 @@ def main() -> None:
|
||||||
print(f"Candidates : {candidates_path}")
|
print(f"Candidates : {candidates_path}")
|
||||||
print(f"Summary : {summary_path}")
|
print(f"Summary : {summary_path}")
|
||||||
if not args.report_only:
|
if not args.report_only:
|
||||||
print(f"Revisão física : {review_group_root}")
|
print(f"Revisão física RAW-SAFE : {review_group_root}")
|
||||||
|
print(f"Domain manifest : {domain_manifest_path}")
|
||||||
|
print(f"Domain contract : {domain_readme_path}")
|
||||||
|
|
||||||
if directional:
|
if directional:
|
||||||
print("\nConfusão semântica direcional global:")
|
print("\nConfusão semântica direcional global:")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"camera": "oak-fcc-3",
|
"camera": "oak-fcc-3",
|
||||||
"modelo": "segformer_b1",
|
"modelo": "segformer_b1",
|
||||||
"model_name": "2026_09_04",
|
"model_name": "2026_09_08",
|
||||||
"main_class_name": "erva",
|
"main_class_name": "erva",
|
||||||
"es_classes": "",
|
"es_classes": "",
|
||||||
"model_to_use": "geral",
|
"model_to_use": "geral",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue