ajuste no carregamento do mapa no rover
This commit is contained in:
parent
2fe3fa4f0e
commit
96c5049899
|
|
@ -745,30 +745,44 @@ namespace AgroBase.Services
|
|||
|
||||
private static void NormalizarMapa(MapaFeatureCollectionModel dados)
|
||||
{
|
||||
dados.type = string.IsNullOrWhiteSpace(dados.type) ? "FeatureCollection" : dados.type;
|
||||
dados.type = string.IsNullOrWhiteSpace(dados.type)
|
||||
? "FeatureCollection"
|
||||
: dados.type;
|
||||
|
||||
var idsUtilizados = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
for (int i = 0; i < dados.features.Count; i++)
|
||||
{
|
||||
MapaFeatureModel feature = dados.features[i];
|
||||
|
||||
feature.type = string.IsNullOrWhiteSpace(feature.type) ? "Feature" : feature.type;
|
||||
feature.type = string.IsNullOrWhiteSpace(feature.type)
|
||||
? "Feature"
|
||||
: feature.type;
|
||||
|
||||
if (feature.properties == null)
|
||||
{
|
||||
feature.properties = new MapaFeaturePropertiesModel();
|
||||
}
|
||||
|
||||
/*
|
||||
* Mantemos a compatibilidade com o fluxo atual:
|
||||
* a seleção usa IDs internos começando em zero.
|
||||
*/
|
||||
string id = (i + 1).ToString(CultureInfo.InvariantCulture);
|
||||
feature.geometry.id = id;
|
||||
string id = feature.properties.Id?.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(feature.properties.Id))
|
||||
// Mapa sem ID explícito:
|
||||
// gera um ID numérico sequencial.
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
feature.properties.Id = id;
|
||||
id = (i + 1).ToString(
|
||||
CultureInfo.InvariantCulture
|
||||
);
|
||||
}
|
||||
|
||||
if (!idsUtilizados.Add(id))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"O mapa possui ID de rua duplicado: '{id}'."
|
||||
);
|
||||
}
|
||||
|
||||
// Uma única identidade em todo o sistema.
|
||||
feature.properties.Id = id;
|
||||
feature.geometry.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -899,6 +913,15 @@ namespace AgroBase.Services
|
|||
}
|
||||
|
||||
function obterIdRua(feature, indice) {
|
||||
if (
|
||||
feature &&
|
||||
feature.properties &&
|
||||
feature.properties.Id !== undefined &&
|
||||
feature.properties.Id !== null
|
||||
) {
|
||||
return String(feature.properties.Id);
|
||||
}
|
||||
|
||||
if (
|
||||
feature &&
|
||||
feature.geometry &&
|
||||
|
|
@ -916,7 +939,7 @@ namespace AgroBase.Services
|
|||
return String(feature.id);
|
||||
}
|
||||
|
||||
return String(indice);
|
||||
return String(indice + 1);
|
||||
}
|
||||
|
||||
function ruaSelecionada(id) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
r"""
|
||||
_10_export_onnx.py
|
||||
|
||||
Exporta o checkpoint PyTorch do SegFormer Multi-Head OAK-FCC-3 para ONNX.
|
||||
|
|
@ -10,7 +10,7 @@ Exemplo:
|
|||
|
||||
python .\_10_export_onnx.py --config .\config.json --train-script .\_8_train_multihead_v2.py --checkpoint .\backup\segformer_b1\2026_09_18\stacked_raw5\best_score.pt --out .\backup\segformer_b1\2026_09_18\stacked_raw5\best_score_audit.onnx --opset 17 --device cuda --include-norm --norm-stats .\backup\segformer_b1\2026_09_18\stacked_raw5\norm_stats.json --postprocess none
|
||||
|
||||
python .\_10_export_onnx.py --config .\config.json --train-script .\_8_train_multihead_v2.py --checkpoint .\backup\segformer_b1\2026_09_18\stacked_raw5\best_operational.pt --out .\backup\segformer_b1\2026_09_18\stacked_raw5\best_operation.onnx --opset 17 --device cuda --include-norm --norm-stats .\backup\segformer_b1\2026_09_18\stacked_raw5\norm_stats.json --postprocess argmax_fullres
|
||||
python .\_10_export_onnx.py --config .\config.json --train-script .\_8_train_multihead_v2.py --checkpoint .\backup\segformer_b1\2026_09_18\stacked_raw5\best_operational.pt --out .\backup\segformer_b1\2026_09_18\stacked_raw5\best_operation.onnx --opset 17 --device cuda --include-norm --norm-stats .\backup\segformer_b1\2026_09_18\stacked_raw5\norm_stats.json --postprocess argmax_fullres --target-threshold auto --vegetation-threshold 0.5 --cana-threshold 0.5
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -178,6 +178,95 @@ def load_selected_norm_stats(
|
|||
return path, mean_sel, std_sel, requested
|
||||
|
||||
|
||||
def _coerce_threshold(value):
|
||||
"""Converte float/dict de metadata em threshold [0, 1]."""
|
||||
if isinstance(value, dict):
|
||||
value = value.get("threshold")
|
||||
try:
|
||||
if value is None:
|
||||
return None
|
||||
v = float(value)
|
||||
if math.isfinite(v) and 0.0 <= v <= 1.0:
|
||||
return v
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def extract_operational_threshold_from_checkpoint(ckpt: dict):
|
||||
"""
|
||||
Resolve o threshold operacional da head target salvo pelo trainer.
|
||||
|
||||
Suporta o contrato atual e alguns formatos anteriores usados nos
|
||||
checkpoints/auditores do projeto.
|
||||
"""
|
||||
if not isinstance(ckpt, dict):
|
||||
return None, "none"
|
||||
|
||||
candidates = []
|
||||
|
||||
best = ckpt.get("best", {}) or {}
|
||||
if isinstance(best, dict):
|
||||
candidates.extend([
|
||||
("best.operational_threshold", best.get("operational_threshold")),
|
||||
("best.best_operational_threshold", best.get("best_operational_threshold")),
|
||||
])
|
||||
|
||||
extra = ckpt.get("extra", {}) or {}
|
||||
if isinstance(extra, dict):
|
||||
score_now = extra.get("score_now", {}) or {}
|
||||
if isinstance(score_now, dict):
|
||||
candidates.extend([
|
||||
("extra.score_now.operational_threshold", score_now.get("operational_threshold")),
|
||||
("extra.score_now.best_operational_threshold", score_now.get("best_operational_threshold")),
|
||||
])
|
||||
|
||||
val = extra.get("val", {}) or {}
|
||||
if isinstance(val, dict):
|
||||
agri = val.get("agricultural", {}) or {}
|
||||
if isinstance(agri, dict):
|
||||
candidates.append((
|
||||
"extra.val.agricultural.best_operational_threshold",
|
||||
agri.get("best_operational_threshold"),
|
||||
))
|
||||
|
||||
for source, value in candidates:
|
||||
threshold = _coerce_threshold(value)
|
||||
if threshold is not None:
|
||||
return threshold, source
|
||||
|
||||
return None, "none"
|
||||
|
||||
|
||||
def resolve_threshold_arg(raw_value, *, head_name: str, ckpt: dict, allow_auto: bool):
|
||||
"""Resolve CLI numérica ou `auto` e devolve (valor, fonte)."""
|
||||
raw = str(raw_value).strip().lower()
|
||||
|
||||
if raw == "auto":
|
||||
if not allow_auto:
|
||||
raise RuntimeError(
|
||||
f"--{head_name}-threshold=auto não é suportado para esta head. "
|
||||
"Informe um valor explícito entre 0 e 1."
|
||||
)
|
||||
value, source = extract_operational_threshold_from_checkpoint(ckpt)
|
||||
if value is None:
|
||||
raise RuntimeError(
|
||||
"Não encontrei o operational_threshold no checkpoint para a head target. "
|
||||
"Para export de campo não vou cair silenciosamente para 0.5. "
|
||||
"Informe --target-threshold <valor> explicitamente ou use um checkpoint "
|
||||
"que contenha best.operational_threshold."
|
||||
)
|
||||
return value, f"checkpoint:{source}"
|
||||
|
||||
value = _coerce_threshold(raw)
|
||||
if value is None:
|
||||
raise RuntimeError(
|
||||
f"--{head_name}-threshold inválido: {raw_value!r}. Use valor entre 0 e 1"
|
||||
+ (" ou 'auto'" if allow_auto else "") + "."
|
||||
)
|
||||
return value, "cli"
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Wrapper ONNX
|
||||
# ============================================================
|
||||
|
|
@ -189,8 +278,8 @@ class MultiHeadOnnxWrapper(nn.Module):
|
|||
Pode exportar:
|
||||
- logits crus
|
||||
- logits redimensionados
|
||||
- argmax em baixa resolução
|
||||
- argmax em resolução da entrada
|
||||
- máscara em baixa resolução (threshold explícito nas heads binárias)
|
||||
- máscara em resolução da entrada (threshold explícito nas heads binárias)
|
||||
|
||||
Também pode embutir a normalização:
|
||||
x = (x - mean) / std
|
||||
|
|
@ -205,6 +294,7 @@ class MultiHeadOnnxWrapper(nn.Module):
|
|||
norm_mean: List[float] | None = None,
|
||||
norm_std: List[float] | None = None,
|
||||
postprocess: str = "none",
|
||||
binary_thresholds: Dict[str, float] | None = None,
|
||||
):
|
||||
super().__init__()
|
||||
self.model = model
|
||||
|
|
@ -212,6 +302,17 @@ class MultiHeadOnnxWrapper(nn.Module):
|
|||
self.resize_to_input = bool(resize_to_input)
|
||||
self.include_norm = bool(include_norm)
|
||||
self.postprocess = str(postprocess).lower()
|
||||
self.binary_thresholds = {
|
||||
str(k).strip().lower(): float(v)
|
||||
for k, v in (binary_thresholds or {}).items()
|
||||
}
|
||||
invalid_threshold_heads = sorted(
|
||||
set(self.binary_thresholds) - {"vegetation", "cana", "target"}
|
||||
)
|
||||
if invalid_threshold_heads:
|
||||
raise RuntimeError(
|
||||
f"Threshold binário configurado para heads inválidas: {invalid_threshold_heads}"
|
||||
)
|
||||
|
||||
if self.postprocess not in ("none", "resize_logits", "argmax_lowres", "argmax_fullres"):
|
||||
raise RuntimeError(f"postprocess inválido: {self.postprocess}")
|
||||
|
|
@ -232,6 +333,21 @@ class MultiHeadOnnxWrapper(nn.Module):
|
|||
self.register_buffer("norm_mean", torch.empty(0))
|
||||
self.register_buffer("norm_std", torch.empty(0))
|
||||
|
||||
def _mask_from_logits(self, head_name: str, logits: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
Heads binárias configuradas usam probabilidade explícita da classe 1.
|
||||
Demais heads preservam argmax tradicional.
|
||||
"""
|
||||
threshold = self.binary_thresholds.get(str(head_name).strip().lower())
|
||||
if threshold is not None:
|
||||
# vegetation/cana/target são heads binárias de 2 logits pelo contrato
|
||||
# do modelo. Evitamos branch dependente de shape aqui para manter o
|
||||
# trace/export ONNX totalmente estático.
|
||||
prob_positive = torch.softmax(logits, dim=1)[:, 1, :, :]
|
||||
return (prob_positive >= threshold).to(torch.uint8)
|
||||
|
||||
return torch.argmax(logits, dim=1).to(torch.uint8)
|
||||
|
||||
def forward(self, pixel_values: torch.Tensor):
|
||||
input_hw = pixel_values.shape[-2:]
|
||||
|
||||
|
|
@ -262,7 +378,7 @@ class MultiHeadOnnxWrapper(nn.Module):
|
|||
result.append(logits)
|
||||
|
||||
elif self.postprocess == "argmax_lowres":
|
||||
mask = torch.argmax(logits, dim=1).to(torch.uint8)
|
||||
mask = self._mask_from_logits(head_name, logits)
|
||||
result.append(mask)
|
||||
|
||||
elif self.postprocess == "argmax_fullres":
|
||||
|
|
@ -272,7 +388,7 @@ class MultiHeadOnnxWrapper(nn.Module):
|
|||
mode="bilinear",
|
||||
align_corners=False,
|
||||
)
|
||||
mask = torch.argmax(logits, dim=1).to(torch.uint8)
|
||||
mask = self._mask_from_logits(head_name, logits)
|
||||
result.append(mask)
|
||||
|
||||
return tuple(result)
|
||||
|
|
@ -331,6 +447,32 @@ def main():
|
|||
),
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--target-threshold",
|
||||
default="auto",
|
||||
help=(
|
||||
"Threshold da probabilidade da classe positiva da head target quando o "
|
||||
"postprocess gera máscara. Padrão='auto': lê o operational_threshold "
|
||||
"salvo no checkpoint e falha se ele não existir."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vegetation-threshold",
|
||||
default="0.5",
|
||||
help=(
|
||||
"Threshold da classe vegetation para export mask. Padrão=0.5. "
|
||||
"Use override explícito apenas após calibração específica dessa head."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cana-threshold",
|
||||
default="0.5",
|
||||
help=(
|
||||
"Threshold da classe cana para export mask. Padrão=0.5. "
|
||||
"Use override explícito apenas após calibração específica dessa head."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--dynamic-batch",
|
||||
action="store_true",
|
||||
|
|
@ -462,6 +604,58 @@ def main():
|
|||
model.to(device)
|
||||
model.eval()
|
||||
|
||||
mask_postprocess = args.postprocess in ("argmax_lowres", "argmax_fullres")
|
||||
checkpoint_operational_threshold, checkpoint_operational_threshold_source = (
|
||||
extract_operational_threshold_from_checkpoint(ckpt)
|
||||
)
|
||||
|
||||
binary_thresholds = {}
|
||||
threshold_sources = {}
|
||||
|
||||
if mask_postprocess:
|
||||
target_threshold, target_threshold_source = resolve_threshold_arg(
|
||||
args.target_threshold,
|
||||
head_name="target",
|
||||
ckpt=ckpt,
|
||||
allow_auto=True,
|
||||
)
|
||||
vegetation_threshold, vegetation_threshold_source = resolve_threshold_arg(
|
||||
args.vegetation_threshold,
|
||||
head_name="vegetation",
|
||||
ckpt=ckpt,
|
||||
allow_auto=False,
|
||||
)
|
||||
cana_threshold, cana_threshold_source = resolve_threshold_arg(
|
||||
args.cana_threshold,
|
||||
head_name="cana",
|
||||
ckpt=ckpt,
|
||||
allow_auto=False,
|
||||
)
|
||||
|
||||
binary_thresholds = {
|
||||
"target": target_threshold,
|
||||
"vegetation": vegetation_threshold,
|
||||
"cana": cana_threshold,
|
||||
}
|
||||
threshold_sources = {
|
||||
"target": target_threshold_source,
|
||||
"vegetation": vegetation_threshold_source,
|
||||
"cana": cana_threshold_source,
|
||||
}
|
||||
|
||||
print("[THRESHOLD] Máscaras binárias serão exportadas com thresholds explícitos:")
|
||||
for head_name in ("target", "vegetation", "cana"):
|
||||
print(
|
||||
f" {head_name:10s}: {binary_thresholds[head_name]:.3f} "
|
||||
f"({threshold_sources[head_name]})"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"[THRESHOLD] postprocess retorna logits; thresholds não são aplicados no grafo. "
|
||||
f"Checkpoint operational={checkpoint_operational_threshold} "
|
||||
f"source={checkpoint_operational_threshold_source}"
|
||||
)
|
||||
|
||||
norm_stats_path = None
|
||||
norm_mean = None
|
||||
norm_std = None
|
||||
|
|
@ -488,6 +682,7 @@ def main():
|
|||
norm_mean=norm_mean,
|
||||
norm_std=norm_std,
|
||||
postprocess=args.postprocess,
|
||||
binary_thresholds=binary_thresholds,
|
||||
)
|
||||
wrapper.to(device)
|
||||
wrapper.eval()
|
||||
|
|
@ -564,6 +759,11 @@ def main():
|
|||
"heads_config": heads_config,
|
||||
"checkpoint_epoch": ckpt.get("epoch", None),
|
||||
"checkpoint_best": ckpt.get("best", None),
|
||||
"checkpoint_operational_threshold": checkpoint_operational_threshold,
|
||||
"checkpoint_operational_threshold_source": checkpoint_operational_threshold_source,
|
||||
"thresholds_applied_in_graph": bool(mask_postprocess),
|
||||
"binary_thresholds": binary_thresholds,
|
||||
"binary_threshold_sources": threshold_sources,
|
||||
"include_norm": bool(args.include_norm),
|
||||
"norm_stats_path": str(norm_stats_path) if norm_stats_path is not None else None,
|
||||
"norm_channels": norm_channels if norm_channels else input_channel_names,
|
||||
|
|
@ -588,6 +788,10 @@ def main():
|
|||
"oak.output_heads": json.dumps(output_heads),
|
||||
"oak.output_names": json.dumps(output_names),
|
||||
"oak.stats_source_tag": experiment_tag(config, channels),
|
||||
"oak.thresholds_applied_in_graph": json.dumps(bool(mask_postprocess)),
|
||||
"oak.binary_thresholds": json.dumps(binary_thresholds),
|
||||
"oak.binary_threshold_sources": json.dumps(threshold_sources),
|
||||
"oak.checkpoint_operational_threshold": json.dumps(checkpoint_operational_threshold),
|
||||
}
|
||||
existing = {item.key: item for item in onnx_model.metadata_props}
|
||||
for key, value in metadata_values.items():
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -27,6 +27,15 @@ Exemplo:
|
|||
|
||||
python .\\_9_test_multihead.py --config config.json --split_folder val --ckpt backup\segformer_b1\test_multi\stacked_raw5_multihead\best_score.pt
|
||||
|
||||
|
||||
python .\_9_test_multihead_v2.py `
|
||||
--config .\config_bench_ar0234.json `
|
||||
--test_folder "C:\Users\USER\Desktop\fotos_multiespectrais\empyreo\chao_cana_erva" `
|
||||
--ckpt .\backup\segformer_b1\2026_09_18\stacked_raw5\best_score.pt `
|
||||
--norm_stats .\dataset\960x600\group\norm_stats.json `
|
||||
--runtime_mode all `
|
||||
--target_threshold auto
|
||||
|
||||
Controles:
|
||||
D / seta direita : próxima amostra
|
||||
A / seta esquerda: amostra anterior
|
||||
|
|
|
|||
|
|
@ -1629,6 +1629,10 @@ def main() -> None:
|
|||
raw_root: Optional[Path] = None
|
||||
raw_source_map: Dict[Tuple[str, str], dict] = {}
|
||||
raw_source_resolution: Dict[Tuple[str, str], str] = {}
|
||||
# Amostras normalizadas continuam válidas para auditoria mesmo quando o
|
||||
# bundle RAW original está incompleto/ausente. Nesses casos, só bloqueamos
|
||||
# a exportação física RAW-SAFE daquela amostra e registramos o motivo.
|
||||
raw_source_failures: Dict[Tuple[str, str], str] = {}
|
||||
|
||||
if not args.report_only:
|
||||
raw_root = (
|
||||
|
|
@ -1645,13 +1649,22 @@ def main() -> None:
|
|||
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))
|
||||
try:
|
||||
rec, mode = resolve_raw_review_source(
|
||||
sample,
|
||||
raw_by_key,
|
||||
raw_by_base,
|
||||
dataset_path,
|
||||
)
|
||||
except RuntimeError as exc:
|
||||
raw_source_resolution[key] = "unavailable"
|
||||
raw_source_failures[key] = str(exc)
|
||||
print(
|
||||
f"[RAW-SAFE][SKIP-PHYSICAL] {sample.group}/{sample.base} | {exc}"
|
||||
)
|
||||
continue
|
||||
|
||||
raw_source_map[key] = rec
|
||||
raw_source_resolution[key] = mode
|
||||
if mode != "same_group":
|
||||
|
|
@ -1659,6 +1672,7 @@ def main() -> None:
|
|||
|
||||
print(
|
||||
f"[RAW-SAFE] raw_root={raw_root} | resolvidos={len(raw_source_map)}/{len(samples)} "
|
||||
f"| indisponiveis={len(raw_source_failures)} "
|
||||
f"| fallback_base_unico={fallback_count}"
|
||||
)
|
||||
|
||||
|
|
@ -2029,6 +2043,8 @@ def main() -> None:
|
|||
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"
|
||||
physical_exported_count = 0
|
||||
physical_export_skipped_count = 0
|
||||
|
||||
if not args.report_only:
|
||||
print(f"\n[COPY RAW-SAFE] candidatos finais: {len(selected_rows)}")
|
||||
|
|
@ -2038,6 +2054,23 @@ def main() -> None:
|
|||
key = (str(row["group"]), str(row["base"]))
|
||||
sample = sample_by_key[key]
|
||||
|
||||
# A auditoria/relatório desta amostra continua válido. Só não há
|
||||
# como gerar raw_previews/raw_masks/predictions_raw com segurança.
|
||||
raw_source = raw_source_map.get(key)
|
||||
if raw_source is None:
|
||||
physical_export_skipped_count += 1
|
||||
row["physical_export_skipped"] = 1
|
||||
row["physical_export_skip_reason"] = raw_source_failures.get(
|
||||
key,
|
||||
"Fonte RAW indisponível para exportação física RAW-SAFE",
|
||||
)
|
||||
print(
|
||||
f"[COPY RAW-SAFE][SKIP] {sample.group}/{sample.base} | "
|
||||
f"{row['physical_export_skip_reason']}"
|
||||
)
|
||||
cache_for_copy.pop((sample.group, sample.base), None)
|
||||
continue
|
||||
|
||||
cached = cache_for_copy.get((sample.group, sample.base))
|
||||
if cached is not None and cached[0].group == sample.group:
|
||||
_, chw, gt_sem, pred_sem = cached
|
||||
|
|
@ -2057,26 +2090,42 @@ def main() -> None:
|
|||
pred_sem = preds["semantic"]
|
||||
gt_sem = resize_ids(base.load_mask(sample.masks["semantic"]), pred_sem.shape[:2])
|
||||
|
||||
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}"
|
||||
try:
|
||||
domain_row = save_review_item(
|
||||
row=row,
|
||||
sample=sample,
|
||||
chw=chw,
|
||||
gt_sem=gt_sem,
|
||||
pred_sem=pred_sem,
|
||||
raw_source=raw_source,
|
||||
review_group_root=review_group_root,
|
||||
base_module=base,
|
||||
semantic_cmap=semantic_cmap,
|
||||
ignore_id=ignore_id,
|
||||
input_channel_names=input_channel_names,
|
||||
save_panels=args.save_panels,
|
||||
)
|
||||
except RuntimeError as exc:
|
||||
# Um bundle RAW existente, mas geometricamente inválido/corrompido,
|
||||
# também não deve abortar milhares de amostras já auditáveis.
|
||||
physical_export_skipped_count += 1
|
||||
row["physical_export_skipped"] = 1
|
||||
row["physical_export_skip_reason"] = str(exc)
|
||||
print(
|
||||
f"[COPY RAW-SAFE][SKIP] {sample.group}/{sample.base} | {exc}"
|
||||
)
|
||||
cache_for_copy.pop((sample.group, sample.base), None)
|
||||
del chw, gt_sem, pred_sem
|
||||
if 'preds' in locals():
|
||||
try:
|
||||
del preds
|
||||
except Exception:
|
||||
pass
|
||||
continue
|
||||
|
||||
domain_row = save_review_item(
|
||||
row=row,
|
||||
sample=sample,
|
||||
chw=chw,
|
||||
gt_sem=gt_sem,
|
||||
pred_sem=pred_sem,
|
||||
raw_source=raw_source,
|
||||
review_group_root=review_group_root,
|
||||
base_module=base,
|
||||
semantic_cmap=semantic_cmap,
|
||||
ignore_id=ignore_id,
|
||||
input_channel_names=input_channel_names,
|
||||
save_panels=args.save_panels,
|
||||
)
|
||||
physical_exported_count += 1
|
||||
row["physical_export_skipped"] = 0
|
||||
row["physical_export_skip_reason"] = ""
|
||||
domain_row["raw_source_resolution"] = raw_source_resolution.get(
|
||||
(str(sample.group), str(sample.base)), ""
|
||||
)
|
||||
|
|
@ -2249,6 +2298,10 @@ def main() -> None:
|
|||
"samples": len(rows),
|
||||
"selected_for_review": len(selected_rows),
|
||||
"selected_pct": float(100.0 * len(selected_rows) / max(len(rows), 1)),
|
||||
"raw_source_available": len(raw_source_map) if not args.report_only else None,
|
||||
"raw_source_unavailable": len(raw_source_failures) if not args.report_only else None,
|
||||
"physical_exported": physical_exported_count if not args.report_only else 0,
|
||||
"physical_export_skipped": physical_export_skipped_count if not args.report_only else 0,
|
||||
"export": {
|
||||
"mode": str(args.export_mode),
|
||||
"min_suspicion_pct": float(args.min_suspicion_pct),
|
||||
|
|
@ -2358,6 +2411,10 @@ def main() -> None:
|
|||
print(f"Summary : {summary_path}")
|
||||
if not args.report_only:
|
||||
print(f"Revisão física RAW-SAFE : {review_group_root}")
|
||||
print(f"RAW disponíveis : {len(raw_source_map)}")
|
||||
print(f"RAW indisponíveis : {len(raw_source_failures)}")
|
||||
print(f"Exportados fisicamente : {physical_exported_count}")
|
||||
print(f"Pulados na exportação RAW : {physical_export_skipped_count}")
|
||||
print(f"Domain manifest : {domain_manifest_path}")
|
||||
print(f"Domain contract : {domain_readme_path}")
|
||||
|
||||
|
|
|
|||
|
|
@ -721,7 +721,7 @@
|
|||
}
|
||||
},
|
||||
"flatfield_config": {
|
||||
"enabled": true,
|
||||
"enabled": false,
|
||||
"npz_file": "flatfield_maps_v1.npz",
|
||||
"apply_before_fusion": true,
|
||||
"apply_after_decode": true,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
{
|
||||
"camera": "oak-fcc-3",
|
||||
"modelo": "segformer_b1",
|
||||
"model_name": "2026_09_08",
|
||||
"main_class_name": "erva",
|
||||
"es_classes": "",
|
||||
"model_to_use": "geral",
|
||||
"raw_size": [1280, 800],
|
||||
"resolucao": [960, 600],
|
||||
"roi_inicio": 0.0,
|
||||
"roi_tamanho": 1.0,
|
||||
"shaves": 3,
|
||||
"source_channels": ["R", "G", "B", "RE", "NIR"],
|
||||
"channels": 5,
|
||||
"input_channels": ["R", "G", "B", "RE", "NIR"],
|
||||
"derived_channels": {
|
||||
"epsilon": 1e-6,
|
||||
"clip_min": -1.0,
|
||||
"clip_max": 1.0
|
||||
},
|
||||
"backbone": "nvidia/mit-b1",
|
||||
"fusion_mode": "stacked",
|
||||
"stats_source_tag": "stacked_raw5",
|
||||
"module_params_json": "calibration/mp_ar0234/module_params.json",
|
||||
"ckpt_test": "best_operational",
|
||||
"multi_head": true,
|
||||
"heads": {
|
||||
"semantic": {
|
||||
"enabled": true,
|
||||
"type": "multiclass",
|
||||
"num_classes": 3,
|
||||
"mask_dir": "masks",
|
||||
"classes": {"chao": 0, "cana": 1, "erva": 2},
|
||||
"ignore_index": 255,
|
||||
"loss_weight": 0.20
|
||||
},
|
||||
"vegetation": {
|
||||
"enabled": true,
|
||||
"type": "binary",
|
||||
"num_classes": 2,
|
||||
"mask_dir": "masks_vegetation",
|
||||
"classes": {"background": 0, "vegetation": 1},
|
||||
"ignore_index": 255,
|
||||
"loss_weight": 0.15
|
||||
},
|
||||
"cana": {
|
||||
"enabled": true,
|
||||
"type": "binary",
|
||||
"num_classes": 2,
|
||||
"mask_dir": "masks_cana",
|
||||
"classes": {"not_cana": 0, "cana": 1},
|
||||
"ignore_index": 255,
|
||||
"loss_weight": 0.35
|
||||
},
|
||||
"target": {
|
||||
"enabled": true,
|
||||
"type": "binary",
|
||||
"num_classes": 2,
|
||||
"mask_dir": "__derived_target__",
|
||||
"classes": {"background": 0, "target": 1},
|
||||
"ignore_index": 255,
|
||||
"loss_weight": 0.30,
|
||||
"derived": true
|
||||
}
|
||||
},
|
||||
"training_v2": {
|
||||
"model": {
|
||||
"decoder_mode": "shared_light",
|
||||
"spectral_input_init": "zero_extra"
|
||||
},
|
||||
"augmentation": {
|
||||
"enabled": true,
|
||||
"horizontal_flip_p": 0.5,
|
||||
"vertical_flip_p": 0.0,
|
||||
"affine_p": 0.7,
|
||||
"rotate_deg": 5.0,
|
||||
"scale_min": 0.9,
|
||||
"scale_max": 1.1,
|
||||
"translate_frac": 0.04,
|
||||
"crop_p": 0.45,
|
||||
"crop_scale_min": 0.7,
|
||||
"crop_scale_max": 1.0,
|
||||
"crop_focus_target_p": 0.55,
|
||||
"crop_focus_cana_p": 0.25,
|
||||
"global_gain_p": 0.35,
|
||||
"global_gain_min": 0.92,
|
||||
"global_gain_max": 1.08,
|
||||
"band_gain_p": 0.25,
|
||||
"band_gain_min": 0.96,
|
||||
"band_gain_max": 1.04,
|
||||
"rgb_gamma_p": 0.2,
|
||||
"rgb_gamma_min": 0.94,
|
||||
"rgb_gamma_max": 1.06,
|
||||
"noise_p": 0.2,
|
||||
"noise_sigma_min": 0.001,
|
||||
"noise_sigma_max": 0.008,
|
||||
"blur_p": 0.12,
|
||||
"blur_kernel": 3,
|
||||
"sensor_channel_dropout_p": 0.0,
|
||||
"clip_physical": true
|
||||
},
|
||||
"sampler": {
|
||||
"mode": "diverse",
|
||||
"samples_per_epoch": 0,
|
||||
"tiny_target_pct": 0.005,
|
||||
"small_target_pct": 0.02,
|
||||
"medium_target_pct": 0.1
|
||||
},
|
||||
"class_weighting": {
|
||||
"method": "log_inverse",
|
||||
"log_offset": 1.02,
|
||||
"power": 0.5,
|
||||
"min_weight": 0.25,
|
||||
"max_weight": 4.0
|
||||
},
|
||||
"loss": {
|
||||
"dice_reduction": "per_image",
|
||||
"dice_smooth": 1.0,
|
||||
"boundary_weight": 0.0,
|
||||
"ohem_ratio": 0.0,
|
||||
"safety": {
|
||||
"enabled": true,
|
||||
"weight": 0.08,
|
||||
"cana_weight": 1.0,
|
||||
"ground_weight": 0.2
|
||||
}
|
||||
},
|
||||
"optimizer": {
|
||||
"encoder_lr": null,
|
||||
"patch_lr_mult": 2.0,
|
||||
"heads_lr_mult": 5.0,
|
||||
"weight_decay": null,
|
||||
"no_decay_bias": true,
|
||||
"no_decay_norm": true,
|
||||
"betas": [
|
||||
0.9,
|
||||
0.999
|
||||
],
|
||||
"eps": 1e-08
|
||||
},
|
||||
"scheduler": {
|
||||
"mode": "poly",
|
||||
"warmup_ratio": 0.05,
|
||||
"warmup_start_factor": 0.1,
|
||||
"poly_power": 1.0,
|
||||
"min_lr_ratio": 0.02
|
||||
},
|
||||
"optimization": {
|
||||
"grad_clip_norm": 1.0,
|
||||
"matmul_precision": "high",
|
||||
"cudnn_benchmark": true,
|
||||
"persistent_workers": true,
|
||||
"prefetch_factor": 2
|
||||
},
|
||||
"target_distillation": {
|
||||
"enabled": false,
|
||||
"mode": "cross_head",
|
||||
"start_epoch": 8,
|
||||
"rampup_epochs": 12,
|
||||
"hard_weight": 0.75,
|
||||
"distill_weight": 0.25,
|
||||
"teacher_confidence_min": 0.6,
|
||||
"detach_teacher": true,
|
||||
"w_sem_erva": 0.45,
|
||||
"w_veg_not_cana": 0.35,
|
||||
"w_veg_suppressed": 0.2,
|
||||
"cana_suppression_power": 1.5
|
||||
},
|
||||
"metrics": {
|
||||
"target_thresholds": [
|
||||
0.3,
|
||||
0.4,
|
||||
0.5,
|
||||
0.6,
|
||||
0.7,
|
||||
0.8,
|
||||
0.9
|
||||
],
|
||||
"ece_bins": 15,
|
||||
"scenario_metrics": true,
|
||||
"group_metrics": true,
|
||||
"rich_train_metrics": false,
|
||||
"operational_threshold": {
|
||||
"max_cana_spray_rate": 0.02,
|
||||
"max_ground_spray_rate": 0.03,
|
||||
"max_weed_miss_rate": 0.2,
|
||||
"score_weights": {
|
||||
"target_iou": 0.35,
|
||||
"target_f1": 0.2,
|
||||
"cana_safety": 0.25,
|
||||
"ground_safety": 0.1,
|
||||
"weed_recall": 0.1
|
||||
}
|
||||
}
|
||||
},
|
||||
"selection_score": {
|
||||
"target_iou": 0.4,
|
||||
"cana_iou": 0.2,
|
||||
"target_f1": 0.1,
|
||||
"vegetation_miou": 0.1,
|
||||
"semantic_miou": 0.05,
|
||||
"cana_safety": 0.15
|
||||
},
|
||||
"checkpoint": {
|
||||
"early_stop_min_delta": 0.0005,
|
||||
"save_best_safety": true,
|
||||
"save_best_legacy": true,
|
||||
"save_best_operational": true
|
||||
},
|
||||
"data": {
|
||||
"validate_npy_content": true,
|
||||
"skip_corrupt_samples": true,
|
||||
"max_corrupt_fraction": 0.005
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -872,7 +872,7 @@ class RawProcessorCore:
|
|||
# OAK_CORE_PERF_LOG_INTERVAL_S=1.0
|
||||
# OAK_CORE_SHAPES_LOG_INTERVAL_S=5.0
|
||||
self.core_perf_debug = str(
|
||||
os.getenv("OAK_CORE_PERF_DEBUG", "1")
|
||||
os.getenv("OAK_CORE_PERF_DEBUG", "0")
|
||||
).strip().lower() not in ("0", "false", "no", "off")
|
||||
try:
|
||||
self.core_perf_log_interval_s = max(0.2, float(
|
||||
|
|
|
|||
Loading…
Reference in New Issue