ajustes na calibragem radiometrica, multi_roi
This commit is contained in:
parent
a028c12a3a
commit
b3f7b42f3c
|
|
@ -82,6 +82,15 @@ class RadiometricController:
|
|||
self.reference_patches = cfg.get("reference_patches", []) or []
|
||||
self.patch_aggregation = str(cfg.get("patch_aggregation", "weighted_mean")).lower()
|
||||
|
||||
# Novo contrato de ROIs dinâmicas por patch/cor/câmera.
|
||||
# Compatível com o formato antigo: roi_pct_by_role e roi_pct continuam
|
||||
# servindo como fallback quando a lista dinâmica não existir.
|
||||
self.patch_roi_contract = str(cfg.get("patch_roi_contract", "legacy_single_roi")).lower()
|
||||
self.patch_roi_reduce_method = str(cfg.get("patch_roi_reduce_method", "median_valid_rois")).lower()
|
||||
self.patch_roi_outlier_reject = bool(cfg.get("patch_roi_outlier_reject", True))
|
||||
self.patch_roi_max_p50_delta = float(cfg.get("patch_roi_max_p50_delta", 0.12))
|
||||
self.patch_roi_min_valid_rois = int(cfg.get("patch_roi_min_valid_rois", 1))
|
||||
|
||||
self.patch_control_mode = str(cfg.get("patch_control_mode", "gray_primary")).lower()
|
||||
|
||||
self.patch_require_order = bool(cfg.get("patch_require_order", True))
|
||||
|
|
@ -231,20 +240,82 @@ class RadiometricController:
|
|||
|
||||
return self._safe_roi_pct(self.global_roi_pct)
|
||||
|
||||
def _get_patch_roi_pct_for_role(self, patch: dict, role: str):
|
||||
role = self._normalize_role(role)
|
||||
def _get_patch_roi_items_for_role(self, patch: dict, role: str) -> list[dict]:
|
||||
"""
|
||||
Retorna uma lista normalizada de ROIs para um patch/cor em uma câmera.
|
||||
|
||||
Contrato novo:
|
||||
patch["roi_list_by_role"][role] = [
|
||||
{"name": "gray_rgb_01", "enabled": True, "roi_pct": {...}},
|
||||
...
|
||||
]
|
||||
|
||||
Fallbacks legados:
|
||||
patch["roi_pct_by_role"][role]
|
||||
patch["roi_pct"]
|
||||
"""
|
||||
role = self._normalize_role(role)
|
||||
items: list[dict] = []
|
||||
|
||||
list_by_role = patch.get("roi_list_by_role", {})
|
||||
raw_items = []
|
||||
if isinstance(list_by_role, dict):
|
||||
role_items = list_by_role.get(role)
|
||||
if isinstance(role_items, list):
|
||||
raw_items = role_items
|
||||
|
||||
for idx, item in enumerate(raw_items, start=1):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
if item.get("enabled", True) is False:
|
||||
continue
|
||||
roi = item.get("roi_pct")
|
||||
if not isinstance(roi, dict) or not roi:
|
||||
continue
|
||||
items.append({
|
||||
"name": str(item.get("name") or f"{patch.get('type', 'patch')}_{role}_{idx:02d}"),
|
||||
"enabled": True,
|
||||
"roi_pct": self._safe_roi_pct(roi),
|
||||
"roi_source": "roi_list_by_role",
|
||||
"index": int(idx - 1),
|
||||
})
|
||||
|
||||
if items:
|
||||
return items
|
||||
|
||||
# Fallback 1: contrato antigo por câmera.
|
||||
by_role = patch.get("roi_pct_by_role", {})
|
||||
if isinstance(by_role, dict):
|
||||
roi = by_role.get(role)
|
||||
if isinstance(roi, dict) and roi:
|
||||
return self._safe_roi_pct(roi), "roi_pct_by_role"
|
||||
return [{
|
||||
"name": f"{patch.get('type', 'patch')}_{role}_legacy_by_role",
|
||||
"enabled": True,
|
||||
"roi_pct": self._safe_roi_pct(roi),
|
||||
"roi_source": "roi_pct_by_role",
|
||||
"index": 0,
|
||||
}]
|
||||
|
||||
# Fallback 2: contrato antigo global do patch.
|
||||
legacy = patch.get("roi_pct")
|
||||
if isinstance(legacy, dict) and legacy:
|
||||
return self._safe_roi_pct(legacy), "roi_pct"
|
||||
return [{
|
||||
"name": f"{patch.get('type', 'patch')}_{role}_legacy",
|
||||
"enabled": True,
|
||||
"roi_pct": self._safe_roi_pct(legacy),
|
||||
"roi_source": "roi_pct",
|
||||
"index": 0,
|
||||
}]
|
||||
|
||||
return None, "missing"
|
||||
return []
|
||||
|
||||
def _get_patch_roi_pct_for_role(self, patch: dict, role: str):
|
||||
"""Compatibilidade para código legado: retorna a primeira ROI ativa."""
|
||||
items = self._get_patch_roi_items_for_role(patch, role)
|
||||
if not items:
|
||||
return None, "missing"
|
||||
item = items[0]
|
||||
return item["roi_pct"], item.get("roi_source", "roi_list_by_role")
|
||||
|
||||
def get_patch_target_for_role(self, patch, role, fallback):
|
||||
by_role = patch.get("target_value_by_role", {})
|
||||
|
|
@ -516,20 +587,10 @@ class RadiometricController:
|
|||
if role not in roles and "all" not in roles:
|
||||
continue
|
||||
|
||||
roi_pct, roi_source = self._get_patch_roi_pct_for_role(patch, role)
|
||||
|
||||
if not isinstance(roi_pct, dict):
|
||||
roi_items = self._get_patch_roi_items_for_role(patch, role)
|
||||
if not roi_items:
|
||||
continue
|
||||
|
||||
x0 = float(roi_pct.get("x0", 0.0))
|
||||
y0 = float(roi_pct.get("y0", 0.0))
|
||||
x1 = float(roi_pct.get("x1", 1.0))
|
||||
y1 = float(roi_pct.get("y1", 1.0))
|
||||
|
||||
roi = self._roi_pct_to_pixels(h, w, x0, y0, x1, y1)
|
||||
arr = self._crop_array(img_gray, roi)
|
||||
stats = self.compute_stats(arr)
|
||||
|
||||
target = self.get_patch_target_for_role(
|
||||
patch=patch,
|
||||
role=role,
|
||||
|
|
@ -538,16 +599,59 @@ class RadiometricController:
|
|||
if target is not None:
|
||||
target = float(target)
|
||||
|
||||
roi_results = []
|
||||
for roi_item in roi_items:
|
||||
roi_pct = roi_item.get("roi_pct")
|
||||
if not isinstance(roi_pct, dict):
|
||||
continue
|
||||
|
||||
x0 = float(roi_pct.get("x0", 0.0))
|
||||
y0 = float(roi_pct.get("y0", 0.0))
|
||||
x1 = float(roi_pct.get("x1", 1.0))
|
||||
y1 = float(roi_pct.get("y1", 1.0))
|
||||
|
||||
roi = self._roi_pct_to_pixels(h, w, x0, y0, x1, y1)
|
||||
arr = self._crop_array(img_gray, roi)
|
||||
stats = self.compute_stats(arr)
|
||||
|
||||
roi_results.append({
|
||||
"name": roi_item.get("name"),
|
||||
"enabled": bool(roi_item.get("enabled", True)),
|
||||
"role": role,
|
||||
"roi": list(roi),
|
||||
"roi_pct": {"x0": x0, "y0": y0, "x1": x1, "y1": y1},
|
||||
"roi_source": roi_item.get("roi_source", "roi_list_by_role"),
|
||||
"index": int(roi_item.get("index", len(roi_results))),
|
||||
"stats": stats,
|
||||
})
|
||||
|
||||
if not roi_results:
|
||||
continue
|
||||
|
||||
patch_stats, roi_quality = self.aggregate_roi_metrics(
|
||||
roi_results=roi_results,
|
||||
patch_type=str(patch.get("type", "reference")).lower(),
|
||||
)
|
||||
|
||||
first_roi = next(
|
||||
(r for r in roi_results if r.get("stats", {}).get("valid")),
|
||||
roi_results[0],
|
||||
)
|
||||
|
||||
patch_results.append({
|
||||
"name": patch.get("name", f"patch_{len(patch_results) + 1}"),
|
||||
"type": patch.get("type", "reference"),
|
||||
"role": role,
|
||||
"roi": list(roi),
|
||||
"roi_pct": {"x0": x0, "y0": y0, "x1": x1, "y1": y1},
|
||||
"roi_source": roi_source,
|
||||
"roi": first_roi.get("roi", []),
|
||||
"roi_pct": first_roi.get("roi_pct", {}),
|
||||
"roi_source": "roi_list_by_role" if len(roi_results) > 1 else first_roi.get("roi_source", "roi_list_by_role"),
|
||||
"roi_count": int(len(roi_results)),
|
||||
"roi_valid_count": int(roi_quality.get("valid_count", 0)),
|
||||
"roi_results": roi_results,
|
||||
"roi_quality": roi_quality,
|
||||
"weight": float(patch.get("weight", 1.0)),
|
||||
"target_value": target,
|
||||
"stats": stats,
|
||||
"stats": patch_stats,
|
||||
})
|
||||
|
||||
if not patch_results:
|
||||
|
|
@ -561,6 +665,120 @@ class RadiometricController:
|
|||
|
||||
return metrics
|
||||
|
||||
def aggregate_roi_metrics(self, roi_results: list[dict], patch_type: str = "reference") -> tuple[dict, dict]:
|
||||
"""
|
||||
Agrega várias ROIs da mesma cor/câmera em uma métrica única.
|
||||
|
||||
O controle usa uma estatística robusta, normalmente mediana dos p50.
|
||||
As guardas de proteção usam p95/saturação máximos para não ignorar
|
||||
uma ROI branca estourada, mesmo quando outra ROI está boa.
|
||||
"""
|
||||
valid = [r for r in roi_results if r.get("stats", {}).get("valid")]
|
||||
warnings = []
|
||||
|
||||
if not valid:
|
||||
return {
|
||||
"valid": False,
|
||||
"pixels": 0,
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"p05": 0.0,
|
||||
"p50": 0.0,
|
||||
"p95": 0.0,
|
||||
"sat_pct": 0.0,
|
||||
"dark_pct": 0.0,
|
||||
}, {
|
||||
"valid": False,
|
||||
"warnings": ["no_valid_roi"],
|
||||
"roi_count": len(roi_results),
|
||||
"valid_count": 0,
|
||||
"used_count": 0,
|
||||
"rejected_count": 0,
|
||||
"p50_values": [],
|
||||
}
|
||||
|
||||
p50_values = np.array([float(r["stats"].get("p50", 0.0)) for r in valid], dtype=np.float32)
|
||||
median_p50 = float(np.median(p50_values))
|
||||
spread_p50 = float(np.max(p50_values) - np.min(p50_values)) if len(p50_values) > 1 else 0.0
|
||||
|
||||
used = valid
|
||||
rejected = []
|
||||
|
||||
if self.patch_roi_outlier_reject and len(valid) >= 3:
|
||||
kept = []
|
||||
for r in valid:
|
||||
p50 = float(r["stats"].get("p50", 0.0))
|
||||
if abs(p50 - median_p50) <= self.patch_roi_max_p50_delta:
|
||||
kept.append(r)
|
||||
else:
|
||||
rejected.append(r)
|
||||
if kept:
|
||||
used = kept
|
||||
if rejected:
|
||||
warnings.append(f"roi_outliers_rejected:{len(rejected)}")
|
||||
elif self.patch_roi_outlier_reject and len(valid) == 2 and spread_p50 > self.patch_roi_max_p50_delta:
|
||||
warnings.append(f"roi_p50_spread_high:{spread_p50:.3f}")
|
||||
|
||||
if len(used) < self.patch_roi_min_valid_rois:
|
||||
warnings.append(f"valid_roi_count_low:{len(used)}<{self.patch_roi_min_valid_rois}")
|
||||
|
||||
def arr(key: str) -> np.ndarray:
|
||||
return np.array([float(r["stats"].get(key, 0.0)) for r in used], dtype=np.float32)
|
||||
|
||||
means = arr("mean")
|
||||
stds = arr("std")
|
||||
p05s = arr("p05")
|
||||
p50s = arr("p50")
|
||||
p95s = arr("p95")
|
||||
sats = arr("sat_pct")
|
||||
darks = arr("dark_pct")
|
||||
pixels = int(sum(int(r["stats"].get("pixels", 0)) for r in used))
|
||||
|
||||
reduce_method = self.patch_roi_reduce_method
|
||||
if reduce_method not in ("median_valid_rois", "mean_valid_rois"):
|
||||
reduce_method = "median_valid_rois"
|
||||
|
||||
reducer = np.mean if reduce_method == "mean_valid_rois" else np.median
|
||||
|
||||
# Para brilho/controle usamos redução robusta; para guardas de risco,
|
||||
# mantemos máximos de p95/saturação. Assim um cartão branco saturado
|
||||
# ainda força redução de exposição.
|
||||
stats = {
|
||||
"valid": True,
|
||||
"pixels": pixels,
|
||||
"mean": float(reducer(means)),
|
||||
"std": float(reducer(stds)),
|
||||
"p05": float(reducer(p05s)),
|
||||
"p50": float(reducer(p50s)),
|
||||
"p95": float(np.max(p95s)),
|
||||
"sat_pct": float(np.max(sats)),
|
||||
"dark_pct": float(reducer(darks)),
|
||||
"roi_reduce_method": reduce_method,
|
||||
"roi_count": int(len(roi_results)),
|
||||
"roi_valid_count": int(len(valid)),
|
||||
"roi_used_count": int(len(used)),
|
||||
"roi_rejected_count": int(len(rejected)),
|
||||
"roi_p50_values": [float(v) for v in p50_values.tolist()],
|
||||
"roi_p50_spread": spread_p50,
|
||||
}
|
||||
|
||||
quality = {
|
||||
"valid": bool(len(used) >= self.patch_roi_min_valid_rois),
|
||||
"warnings": warnings,
|
||||
"roi_count": int(len(roi_results)),
|
||||
"valid_count": int(len(valid)),
|
||||
"used_count": int(len(used)),
|
||||
"rejected_count": int(len(rejected)),
|
||||
"p50_values": [float(v) for v in p50_values.tolist()],
|
||||
"p50_spread": spread_p50,
|
||||
"reduce_method": reduce_method,
|
||||
"used_names": [str(r.get("name")) for r in used],
|
||||
"rejected_names": [str(r.get("name")) for r in rejected],
|
||||
"patch_type": str(patch_type).lower(),
|
||||
}
|
||||
|
||||
return stats, quality
|
||||
|
||||
def aggregate_patch_metrics(self, patch_results: list[dict]) -> dict:
|
||||
valid = [p for p in patch_results if p["stats"].get("valid")]
|
||||
|
||||
|
|
@ -588,6 +806,14 @@ class RadiometricController:
|
|||
white = self._find_patch_result(valid, "white")
|
||||
|
||||
warnings = []
|
||||
roi_quality_by_type = {}
|
||||
|
||||
for p in valid:
|
||||
ptype = str(p.get("type", "reference")).lower()
|
||||
rq = p.get("roi_quality", {}) or {}
|
||||
roi_quality_by_type[ptype] = rq
|
||||
for warn in rq.get("warnings", []) or []:
|
||||
warnings.append(f"{ptype}_{warn}")
|
||||
|
||||
# Stats gerais de proteção.
|
||||
p95s = np.array([p["stats"]["p95"] for p in valid], dtype=np.float32)
|
||||
|
|
@ -741,6 +967,7 @@ class RadiometricController:
|
|||
"black_target": black_target,
|
||||
"gray_target": gray_target,
|
||||
"white_target": white_target,
|
||||
"roi_quality_by_type": roi_quality_by_type,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -1187,6 +1414,7 @@ class RadiometricController:
|
|||
f" [PATCH] role={p.get('role', role)} "
|
||||
f"type={p.get('type')} "
|
||||
f"roi_source={p.get('roi_source')} "
|
||||
f"roi_count={p.get('roi_valid_count', p.get('roi_count', 1))}/{p.get('roi_count', 1)} "
|
||||
f"roi_pct={p.get('roi_pct')} "
|
||||
f"p50={st.get('p50', 0):.3f} "
|
||||
f"p95={st.get('p95', 0):.3f} "
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -302,6 +302,24 @@ def default_profile_global():
|
|||
}
|
||||
|
||||
|
||||
def make_default_patch(patch_type: str, target: float, weight: float):
|
||||
return {
|
||||
"name": f"{patch_type}_reference",
|
||||
"type": patch_type,
|
||||
"roles": ROLES[:] if "ROLES" in globals() else ["rgb", "re", "nir"],
|
||||
"target_value": target,
|
||||
"weight": weight,
|
||||
|
||||
# Compatibilidade com o controller antigo: primeira ROI ativa de RGB.
|
||||
"roi_pct": {},
|
||||
"roi_pct_by_role": {"rgb": {}, "re": {}, "nir": {}},
|
||||
|
||||
# Formato novo: lista dinâmica por câmera/role.
|
||||
# Cada item: {name, enabled, roi_pct, created_at, updated_at}
|
||||
"roi_list_by_role": {"rgb": [], "re": [], "nir": []},
|
||||
}
|
||||
|
||||
|
||||
def default_profile_patches():
|
||||
cfg = base_ae_contract()
|
||||
cfg.update({
|
||||
|
|
@ -309,7 +327,6 @@ def default_profile_patches():
|
|||
"spectral_control_mode": "shared",
|
||||
"deadband": 0.035,
|
||||
|
||||
"metering_mode": "reference_patches",
|
||||
"patch_control_mode": "gray_primary",
|
||||
"patch_require_order": True,
|
||||
"patch_min_separation": 0.08,
|
||||
|
|
@ -323,34 +340,16 @@ def default_profile_patches():
|
|||
"patch_gray_min_p50": 0.08,
|
||||
"patch_gray_max_p50": 0.85,
|
||||
|
||||
# Novo contrato: o runtime pode combinar N ROIs por cor/camera.
|
||||
"patch_roi_contract": "multi_roi_by_role_v1",
|
||||
"patch_roi_reduce_method": "median_valid_rois",
|
||||
"patch_roi_outlier_reject": True,
|
||||
"patch_roi_max_p50_delta": 0.12,
|
||||
|
||||
"reference_patches": [
|
||||
{
|
||||
"name": "black_reference",
|
||||
"type": "black",
|
||||
"roles": ["rgb", "re", "nir"],
|
||||
"target_value": 0.06,
|
||||
"weight": 0.25,
|
||||
"roi_pct": {},
|
||||
"roi_pct_by_role": {"rgb": {}, "re": {}, "nir": {}}
|
||||
},
|
||||
{
|
||||
"name": "gray_reference",
|
||||
"type": "gray",
|
||||
"roles": ["rgb", "re", "nir"],
|
||||
"target_value": 0.40,
|
||||
"weight": 1.0,
|
||||
"roi_pct": {},
|
||||
"roi_pct_by_role": {"rgb": {}, "re": {}, "nir": {}}
|
||||
},
|
||||
{
|
||||
"name": "white_reference",
|
||||
"type": "white",
|
||||
"roles": ["rgb", "re", "nir"],
|
||||
"target_value": 0.78,
|
||||
"weight": 0.7,
|
||||
"roi_pct": {},
|
||||
"roi_pct_by_role": {"rgb": {}, "re": {}, "nir": {}}
|
||||
}
|
||||
make_default_patch("black", 0.06, 0.25),
|
||||
make_default_patch("gray", 0.40, 1.0),
|
||||
make_default_patch("white", 0.78, 0.7),
|
||||
],
|
||||
})
|
||||
|
||||
|
|
@ -358,7 +357,6 @@ def default_profile_patches():
|
|||
"radiometric_config": cfg
|
||||
}
|
||||
|
||||
|
||||
def get_active_profile_name(data: dict) -> str:
|
||||
name = str(data.get("active_profile", "global_scene_mode"))
|
||||
if name not in ("global_scene_mode", "three_reference_patches_mode"):
|
||||
|
|
@ -514,36 +512,122 @@ def get_patches(data: dict):
|
|||
)
|
||||
|
||||
|
||||
def ensure_patch_roi_by_role(patch: dict):
|
||||
legacy = patch.get("roi_pct", {})
|
||||
def make_roi_entry(roi_pct: dict, name: str | None = None, enabled: bool = True) -> dict:
|
||||
return {
|
||||
"name": name or "roi_01",
|
||||
"enabled": bool(enabled),
|
||||
"roi_pct": clone_roi(roi_pct),
|
||||
"created_at": now_str(),
|
||||
"updated_at": now_str(),
|
||||
}
|
||||
|
||||
|
||||
def normalize_roi_entry(entry, idx: int) -> dict | None:
|
||||
"""Aceita formatos antigos e novos, devolvendo sempre um item padrão."""
|
||||
if not entry:
|
||||
return None
|
||||
|
||||
if isinstance(entry, dict) and "roi_pct" in entry:
|
||||
roi = entry.get("roi_pct") or {}
|
||||
if not roi:
|
||||
return None
|
||||
out = dict(entry)
|
||||
out["name"] = str(out.get("name") or f"roi_{idx + 1:02d}")
|
||||
out["enabled"] = bool(out.get("enabled", True))
|
||||
out["roi_pct"] = clone_roi(roi)
|
||||
out.setdefault("created_at", now_str())
|
||||
out["updated_at"] = str(out.get("updated_at") or now_str())
|
||||
return out
|
||||
|
||||
if isinstance(entry, dict) and all(k in entry for k in ("x0", "y0", "x1", "y1")):
|
||||
return make_roi_entry(entry, name=f"roi_{idx + 1:02d}", enabled=True)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def sync_patch_legacy_roi_fields(patch: dict):
|
||||
"""Mantém roi_pct e roi_pct_by_role compatíveis com scripts antigos."""
|
||||
roi_lists = patch.setdefault("roi_list_by_role", {})
|
||||
by_role = patch.setdefault("roi_pct_by_role", {})
|
||||
|
||||
for role in ROLES:
|
||||
if role not in by_role or not by_role[role]:
|
||||
by_role[role] = clone_roi(legacy) if legacy else {}
|
||||
entries = roi_lists.setdefault(role, [])
|
||||
first_active = next((e.get("roi_pct") for e in entries if e.get("enabled", True) and e.get("roi_pct")), {})
|
||||
by_role[role] = clone_roi(first_active) if first_active else {}
|
||||
|
||||
return by_role
|
||||
patch["roi_pct"] = by_role.get("rgb", {}) or {}
|
||||
|
||||
|
||||
def ensure_patch_roi_lists_by_role(patch: dict):
|
||||
"""
|
||||
Migra o formato antigo:
|
||||
roi_pct_by_role[role] = {x0,y0,x1,y1}
|
||||
para o formato novo:
|
||||
roi_list_by_role[role] = [{name, enabled, roi_pct, ...}, ...]
|
||||
|
||||
Também aceita, por tolerância, caso alguém já tenha salvo uma lista dentro de roi_pct_by_role.
|
||||
"""
|
||||
legacy_global = patch.get("roi_pct", {}) or {}
|
||||
legacy_by_role = patch.get("roi_pct_by_role", {}) or {}
|
||||
roi_lists = patch.setdefault("roi_list_by_role", {})
|
||||
|
||||
for role in ROLES:
|
||||
raw_list = roi_lists.get(role, [])
|
||||
|
||||
# Caso raro: formato novo foi salvo diretamente em roi_pct_by_role.
|
||||
if not raw_list and isinstance(legacy_by_role.get(role), list):
|
||||
raw_list = legacy_by_role.get(role) or []
|
||||
|
||||
normalized = []
|
||||
if isinstance(raw_list, list):
|
||||
for idx, item in enumerate(raw_list):
|
||||
entry = normalize_roi_entry(item, idx)
|
||||
if entry is not None:
|
||||
normalized.append(entry)
|
||||
elif isinstance(raw_list, dict) and raw_list:
|
||||
entry = normalize_roi_entry(raw_list, 0)
|
||||
if entry is not None:
|
||||
normalized.append(entry)
|
||||
|
||||
# Migração do formato antigo, se ainda não houver lista.
|
||||
if not normalized:
|
||||
old_roi = legacy_by_role.get(role, {}) if isinstance(legacy_by_role, dict) else {}
|
||||
if not old_roi and legacy_global:
|
||||
old_roi = legacy_global
|
||||
if isinstance(old_roi, dict) and old_roi:
|
||||
normalized.append(make_roi_entry(old_roi, name=f"{role}_legacy_01", enabled=True))
|
||||
|
||||
# Garante nomes estáveis e únicos.
|
||||
seen = set()
|
||||
for idx, entry in enumerate(normalized):
|
||||
name = str(entry.get("name") or f"roi_{idx + 1:02d}")
|
||||
if name in seen:
|
||||
name = f"{name}_{idx + 1:02d}"
|
||||
seen.add(name)
|
||||
entry["name"] = name
|
||||
|
||||
roi_lists[role] = normalized
|
||||
|
||||
sync_patch_legacy_roi_fields(patch)
|
||||
return roi_lists
|
||||
|
||||
|
||||
# Alias antigo mantido para não quebrar chamadas existentes.
|
||||
def ensure_patch_roi_by_role(patch: dict):
|
||||
ensure_patch_roi_lists_by_role(patch)
|
||||
return patch.setdefault("roi_pct_by_role", {})
|
||||
|
||||
|
||||
def get_patch_by_type(data: dict, patch_type: str):
|
||||
patch_type = str(patch_type).lower()
|
||||
for p in get_patches(data):
|
||||
if str(p.get("type", "")).lower() == patch_type:
|
||||
ensure_patch_roi_lists_by_role(p)
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def get_patch_roi_for_role(data: dict, patch_type: str, role: str):
|
||||
role = normalize_role(role)
|
||||
patch = get_patch_by_type(data, patch_type)
|
||||
if not patch:
|
||||
return {}
|
||||
|
||||
by_role = ensure_patch_roi_by_role(patch)
|
||||
return by_role.get(role, {}) or patch.get("roi_pct", {}) or {}
|
||||
|
||||
|
||||
def set_patch_roi_for_role(data: dict, patch_type: str, role: str, roi_pct: dict):
|
||||
def ensure_patch_exists(data: dict, patch_type: str):
|
||||
data.setdefault("three_reference_patches_mode", default_profile_patches())
|
||||
cfg = data["three_reference_patches_mode"].setdefault(
|
||||
"radiometric_config",
|
||||
|
|
@ -556,37 +640,114 @@ def set_patch_roi_for_role(data: dict, patch_type: str, role: str, roi_pct: dict
|
|||
)
|
||||
|
||||
patch_type = str(patch_type).lower()
|
||||
role = normalize_role(role)
|
||||
|
||||
target = {"black": 0.06, "gray": 0.40, "white": 0.78}.get(patch_type, 0.40)
|
||||
weight = {"black": 0.25, "gray": 1.0, "white": 0.7}.get(patch_type, 1.0)
|
||||
|
||||
patch = None
|
||||
for p in patches:
|
||||
if str(p.get("type", "")).lower() == patch_type:
|
||||
patch = p
|
||||
break
|
||||
ensure_patch_roi_lists_by_role(p)
|
||||
return p
|
||||
|
||||
if patch is None:
|
||||
patch = {
|
||||
"name": f"{patch_type}_reference",
|
||||
"type": patch_type,
|
||||
"roles": ROLES[:],
|
||||
"target_value": target,
|
||||
"weight": weight,
|
||||
"roi_pct": {},
|
||||
"roi_pct_by_role": {},
|
||||
}
|
||||
patches.append(patch)
|
||||
patch = make_default_patch(patch_type, target, weight)
|
||||
patches.append(patch)
|
||||
ensure_patch_roi_lists_by_role(patch)
|
||||
return patch
|
||||
|
||||
by_role = ensure_patch_roi_by_role(patch)
|
||||
by_role[role] = roi_pct
|
||||
|
||||
# Compatibilidade com formato antigo.
|
||||
# Mantém roi_pct como RGB, para scripts antigos não quebrarem.
|
||||
patch["roi_pct"] = by_role.get("rgb", roi_pct)
|
||||
def get_patch_roi_entries_for_role(data: dict, patch_type: str, role: str, enabled_only: bool = False):
|
||||
role = normalize_role(role)
|
||||
patch = get_patch_by_type(data, patch_type)
|
||||
if not patch:
|
||||
return []
|
||||
roi_lists = ensure_patch_roi_lists_by_role(patch)
|
||||
entries = list(roi_lists.get(role, []) or [])
|
||||
if enabled_only:
|
||||
entries = [e for e in entries if e.get("enabled", True) and e.get("roi_pct")]
|
||||
return entries
|
||||
|
||||
|
||||
def get_patch_roi_for_role(data: dict, patch_type: str, role: str):
|
||||
"""Compatibilidade: retorna a primeira ROI ativa da lista."""
|
||||
entries = get_patch_roi_entries_for_role(data, patch_type, role, enabled_only=True)
|
||||
if entries:
|
||||
return entries[0].get("roi_pct", {}) or {}
|
||||
|
||||
patch = get_patch_by_type(data, patch_type)
|
||||
if not patch:
|
||||
return {}
|
||||
return patch.get("roi_pct_by_role", {}).get(normalize_role(role), {}) or patch.get("roi_pct", {}) or {}
|
||||
|
||||
|
||||
def get_patch_roi_entry(data: dict, patch_type: str, role: str, index: int):
|
||||
entries = get_patch_roi_entries_for_role(data, patch_type, role, enabled_only=False)
|
||||
if not entries:
|
||||
return None, -1
|
||||
index = clamp(int(index), 0, len(entries) - 1)
|
||||
return entries[index], index
|
||||
|
||||
|
||||
def set_patch_roi_for_role(data: dict, patch_type: str, role: str, roi_pct: dict, index: int | None = None, append: bool = False):
|
||||
patch = ensure_patch_exists(data, patch_type)
|
||||
role = normalize_role(role)
|
||||
roi_lists = ensure_patch_roi_lists_by_role(patch)
|
||||
entries = roi_lists.setdefault(role, [])
|
||||
|
||||
if append or index is None or index >= len(entries) or index < 0:
|
||||
entry = make_roi_entry(
|
||||
roi_pct,
|
||||
name=f"{patch_type}_{role}_{len(entries) + 1:02d}",
|
||||
enabled=True,
|
||||
)
|
||||
entries.append(entry)
|
||||
saved_index = len(entries) - 1
|
||||
else:
|
||||
saved_index = int(index)
|
||||
old = entries[saved_index]
|
||||
old["roi_pct"] = clone_roi(roi_pct)
|
||||
old["enabled"] = bool(old.get("enabled", True))
|
||||
old["updated_at"] = now_str()
|
||||
|
||||
sync_patch_legacy_roi_fields(patch)
|
||||
return saved_index
|
||||
|
||||
|
||||
def delete_patch_roi_for_role(data: dict, patch_type: str, role: str, index: int):
|
||||
patch = get_patch_by_type(data, patch_type)
|
||||
if not patch:
|
||||
return False, 0
|
||||
role = normalize_role(role)
|
||||
roi_lists = ensure_patch_roi_lists_by_role(patch)
|
||||
entries = roi_lists.setdefault(role, [])
|
||||
if not entries:
|
||||
return False, 0
|
||||
index = clamp(int(index), 0, len(entries) - 1)
|
||||
entries.pop(index)
|
||||
sync_patch_legacy_roi_fields(patch)
|
||||
return True, len(entries)
|
||||
|
||||
|
||||
def toggle_patch_roi_enabled_for_role(data: dict, patch_type: str, role: str, index: int):
|
||||
entry, idx = get_patch_roi_entry(data, patch_type, role, index)
|
||||
if entry is None:
|
||||
return False, False
|
||||
entry["enabled"] = not bool(entry.get("enabled", True))
|
||||
entry["updated_at"] = now_str()
|
||||
patch = get_patch_by_type(data, patch_type)
|
||||
if patch:
|
||||
sync_patch_legacy_roi_fields(patch)
|
||||
return True, bool(entry["enabled"])
|
||||
|
||||
|
||||
def add_empty_patch_roi_slot(data: dict, patch_type: str, role: str):
|
||||
# Usa uma ROI pequena central como placeholder, para o usuário arrastar por cima depois.
|
||||
return set_patch_roi_for_role(
|
||||
data,
|
||||
patch_type,
|
||||
role,
|
||||
{"x0": 0.45, "y0": 0.45, "x1": 0.55, "y1": 0.55},
|
||||
append=True,
|
||||
)
|
||||
|
||||
def set_shared_mode(data: dict, shared: bool):
|
||||
for profile in ("global_scene_mode", "three_reference_patches_mode"):
|
||||
data.setdefault(profile, default_profile_global() if profile == "global_scene_mode" else default_profile_patches())
|
||||
|
|
@ -624,7 +785,7 @@ def draw_roi_on_panel(panel, roi_pct, label, color, thickness=2):
|
|||
0.55, color, 1, cv2.LINE_AA)
|
||||
|
||||
|
||||
def draw_all_rois(panel, data, selected_target, mode, panel_role, edit_role):
|
||||
def draw_all_rois(panel, data, selected_target, mode, panel_role, edit_role, selected_roi_index=0):
|
||||
panel_role = normalize_role(panel_role)
|
||||
edit_role = normalize_role(edit_role)
|
||||
|
||||
|
|
@ -640,13 +801,22 @@ def draw_all_rois(panel, data, selected_target, mode, panel_role, edit_role):
|
|||
for p in get_patches(data):
|
||||
typ = str(p.get("type", "")).lower()
|
||||
color = PATCH_COLORS.get(typ, (0, 255, 255))
|
||||
roi = get_patch_roi_for_role(data, typ, panel_role)
|
||||
entries = get_patch_roi_entries_for_role(data, typ, panel_role, enabled_only=False)
|
||||
|
||||
label = f"{typ.upper()}/{panel_role.upper()}"
|
||||
selected = typ == selected_target and is_edit_panel
|
||||
thickness = 3 if selected else 2
|
||||
for idx, entry in enumerate(entries):
|
||||
roi = entry.get("roi_pct", {})
|
||||
if not roi:
|
||||
continue
|
||||
|
||||
draw_roi_on_panel(panel, roi, label, color, thickness)
|
||||
enabled = bool(entry.get("enabled", True))
|
||||
selected = typ == selected_target and is_edit_panel and idx == selected_roi_index
|
||||
thickness = 3 if selected else 1 if not enabled else 2
|
||||
|
||||
label = f"{typ.upper()}/{panel_role.upper()}#{idx + 1}"
|
||||
if not enabled:
|
||||
label += " OFF"
|
||||
|
||||
draw_roi_on_panel(panel, roi, label, color, thickness)
|
||||
|
||||
|
||||
def build_board(
|
||||
|
|
@ -655,6 +825,7 @@ def build_board(
|
|||
mode,
|
||||
selected_target,
|
||||
edit_role,
|
||||
selected_roi_index,
|
||||
drag_rect_local,
|
||||
drag_role,
|
||||
panel_rects,
|
||||
|
|
@ -717,9 +888,9 @@ def build_board(
|
|||
nir01_show = resize_if_needed(nir01, (base_h, base_w)) if nir01 is not None else None
|
||||
nir_panel = gray_to_bgr_u8(nir01_show) if nir01_show is not None else np.zeros_like(rgb_panel)
|
||||
|
||||
draw_all_rois(rgb_panel, data, selected_target, mode, "rgb", edit_role)
|
||||
draw_all_rois(re_panel, data, selected_target, mode, "re", edit_role)
|
||||
draw_all_rois(nir_panel, data, selected_target, mode, "nir", edit_role)
|
||||
draw_all_rois(rgb_panel, data, selected_target, mode, "rgb", edit_role, selected_roi_index)
|
||||
draw_all_rois(re_panel, data, selected_target, mode, "re", edit_role, selected_roi_index)
|
||||
draw_all_rois(nir_panel, data, selected_target, mode, "nir", edit_role, selected_roi_index)
|
||||
|
||||
if drag_rect_local is not None:
|
||||
x0, y0, x1, y1 = drag_rect_local
|
||||
|
|
@ -760,8 +931,8 @@ def build_board(
|
|||
board = np.vstack([top, bottom])
|
||||
|
||||
x0, y0, x1, y1 = panel_rects["data"]
|
||||
lines = build_data_lines(decoded, data, mode, selected_target, edit_role, base_w, base_h)
|
||||
overlay_hud(board, lines, x=x0 + 16, y=y0 + 28, font_scale=0.53, line_step=21)
|
||||
lines = build_data_lines(decoded, data, mode, selected_target, edit_role, selected_roi_index, base_w, base_h)
|
||||
overlay_hud(board, lines, x=x0 + 16, y=y0 + 28, font_scale=0.50, line_step=20)
|
||||
|
||||
if preview_scale != 1.0:
|
||||
board = cv2.resize(
|
||||
|
|
@ -773,19 +944,19 @@ def build_board(
|
|||
return board
|
||||
|
||||
|
||||
def build_data_lines(decoded, data, mode, selected_target, edit_role, base_w, base_h):
|
||||
def build_data_lines(decoded, data, mode, selected_target, edit_role, selected_roi_index, base_w, base_h):
|
||||
edit_role = normalize_role(edit_role)
|
||||
active_profile = get_active_profile_name(data)
|
||||
active_cfg = get_active_radiometric_config(data)
|
||||
|
||||
lines = [
|
||||
"RADIOMETRIC CONFIG TOOL",
|
||||
f"modo_edicao={mode.upper()} | camera_editada={edit_role.upper()} | active={active_profile}",
|
||||
f"modo={mode.upper()} | camera={edit_role.upper()} | active={active_profile}",
|
||||
f"spectral={active_cfg.get('spectral_control_mode')} | strategy={active_cfg.get('control_strategy')}",
|
||||
f"interval={active_cfg.get('interval_s')}s | ratio_alpha={active_cfg.get('ratio_alpha')} | ready={active_cfg.get('ready_required_cycles')}",
|
||||
f"roi_contract={active_cfg.get('patch_roi_contract', 'legacy_single_roi')}",
|
||||
"",
|
||||
"Arraste no painel da camera editada para definir a ROI.",
|
||||
"Cada camera salva sua propria ROI: RGB / RE / NIR.",
|
||||
"Arraste no painel da camera editada para definir/atualizar ROI.",
|
||||
"PATCHES agora suportam N ROIs por cor e por camera.",
|
||||
"",
|
||||
]
|
||||
|
||||
|
|
@ -801,39 +972,73 @@ def build_data_lines(decoded, data, mode, selected_target, edit_role, base_w, ba
|
|||
lines.extend(stats_lines_for_mode(data, decoded, mode="global", patch_type=None))
|
||||
|
||||
else:
|
||||
entries_edit = get_patch_roi_entries_for_role(data, selected_target, edit_role, enabled_only=False)
|
||||
n_edit = len(entries_edit)
|
||||
selected_roi_index = clamp(selected_roi_index, 0, max(0, n_edit - 1)) if n_edit else 0
|
||||
|
||||
lines.append(f"PATCH selecionado: {selected_target.upper()}")
|
||||
lines.append("ROIs do patch selecionado:")
|
||||
lines.append(f"ROI selecionada {edit_role.upper()}: #{selected_roi_index + 1 if n_edit else 0}/{n_edit}")
|
||||
lines.append("Contagem de ROIs por camera:")
|
||||
for role in ROLES:
|
||||
roi_pct = get_patch_roi_for_role(data, selected_target, role)
|
||||
entries = get_patch_roi_entries_for_role(data, selected_target, role, enabled_only=False)
|
||||
enabled = sum(1 for e in entries if e.get("enabled", True))
|
||||
marker = "*" if role == edit_role else " "
|
||||
lines.append(f"{marker} {role.upper()}: roi={roi_pct}")
|
||||
lines.append(f"{marker} {role.upper()}: {enabled}/{len(entries)} ativas")
|
||||
|
||||
if n_edit:
|
||||
entry = entries_edit[selected_roi_index]
|
||||
lines.append(f"ROI atual: {entry.get('name')} | enabled={entry.get('enabled', True)}")
|
||||
lines.append(f"rect={entry.get('roi_pct')}")
|
||||
else:
|
||||
lines.append("ROI atual: nenhuma. Arraste para criar a primeira.")
|
||||
|
||||
sel_patch = get_patch_by_type(data, selected_target)
|
||||
if sel_patch:
|
||||
lines.append("")
|
||||
lines.append(
|
||||
f"target={float(sel_patch.get('target_value', 0.0)):.2f} "
|
||||
f"weight={float(sel_patch.get('weight', 1.0)):.2f}"
|
||||
)
|
||||
|
||||
lines.append("")
|
||||
lines.append(f"Stats do patch {selected_target.upper()}:")
|
||||
lines.append(f"Stats robustas {selected_target.upper()}:")
|
||||
lines.extend(stats_lines_for_mode(data, decoded, mode="patches", patch_type=selected_target))
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"M = alterna GLOBAL / 3 PATCHES",
|
||||
"C = alterna camera RGB / RE / NIR",
|
||||
"V = alterna preview bonito / bruto",
|
||||
"1/2/3 = BLACK / GRAY / WHITE",
|
||||
"S = alterna spectral shared/independent",
|
||||
"P ou SPACE = salva JSON",
|
||||
"R = restaura defaults | Q/Esc = sai",
|
||||
"M = GLOBAL/PATCHES | C = camera | V = preview bonito/bruto",
|
||||
"1/2/3 = BLACK/GRAY/WHITE | S = shared/independent",
|
||||
"N = nova ROI | [ ] = troca ROI | D = apaga ROI | T = liga/desliga ROI",
|
||||
"P ou SPACE = salva JSON | R = defaults | Q/Esc = sai",
|
||||
])
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def aggregate_roi_stats(stats_list: list[dict]) -> dict:
|
||||
valid = [s for s in stats_list if s.get("valid")]
|
||||
if not valid:
|
||||
return {"valid": False, "count": 0}
|
||||
|
||||
p50 = np.array([s["p50"] for s in valid], dtype=np.float32)
|
||||
p95 = np.array([s["p95"] for s in valid], dtype=np.float32)
|
||||
sat = np.array([s["sat_pct"] for s in valid], dtype=np.float32)
|
||||
dark = np.array([s["dark_pct"] for s in valid], dtype=np.float32)
|
||||
std = np.array([s["std"] for s in valid], dtype=np.float32)
|
||||
|
||||
return {
|
||||
"valid": True,
|
||||
"count": len(valid),
|
||||
"p50": float(np.median(p50)),
|
||||
"p95": float(np.median(p95)),
|
||||
"sat_pct": float(np.median(sat)),
|
||||
"dark_pct": float(np.median(dark)),
|
||||
"std": float(np.median(std)),
|
||||
"p50_spread": float(p50.max() - p50.min()) if len(p50) > 1 else 0.0,
|
||||
"p50_min": float(p50.min()),
|
||||
"p50_max": float(p50.max()),
|
||||
}
|
||||
|
||||
|
||||
def stats_lines_for_mode(data, decoded, mode: str, patch_type: str | None = None):
|
||||
lines = []
|
||||
|
||||
|
|
@ -843,27 +1048,55 @@ def stats_lines_for_mode(data, decoded, mode: str, patch_type: str | None = None
|
|||
lines.append(f"{role.upper()}: sem frame")
|
||||
continue
|
||||
|
||||
h, w = img.shape[:2]
|
||||
|
||||
if mode == "global":
|
||||
roi_pct = get_global_roi_for_role(data, role)
|
||||
else:
|
||||
roi_pct = get_patch_roi_for_role(data, patch_type, role)
|
||||
|
||||
if not roi_pct:
|
||||
lines.append(f"{role.upper()}: sem ROI")
|
||||
if not roi_pct:
|
||||
lines.append(f"{role.upper()}: sem ROI")
|
||||
continue
|
||||
roi = pct_to_px(roi_pct, w, h)
|
||||
st = compute_stats(img, roi)
|
||||
lines.append(
|
||||
f"{role.upper()}: p50={st['p50']:.3f} p95={st['p95']:.3f} "
|
||||
f"sat={st['sat_pct']:.2f}% dark={st['dark_pct']:.1f}%"
|
||||
)
|
||||
continue
|
||||
|
||||
h, w = img.shape[:2]
|
||||
roi = pct_to_px(roi_pct, w, h)
|
||||
st = compute_stats(img, roi)
|
||||
entries = get_patch_roi_entries_for_role(data, patch_type, role, enabled_only=True)
|
||||
if not entries:
|
||||
lines.append(f"{role.upper()}: sem ROI ativa")
|
||||
continue
|
||||
|
||||
stats = []
|
||||
p50_each = []
|
||||
for entry in entries:
|
||||
roi_pct = entry.get("roi_pct", {})
|
||||
if not roi_pct:
|
||||
continue
|
||||
roi = pct_to_px(roi_pct, w, h)
|
||||
st = compute_stats(img, roi)
|
||||
stats.append(st)
|
||||
if st.get("valid"):
|
||||
p50_each.append(st["p50"])
|
||||
|
||||
ag = aggregate_roi_stats(stats)
|
||||
if not ag.get("valid"):
|
||||
lines.append(f"{role.upper()}: ROIs invalidas")
|
||||
continue
|
||||
|
||||
mini = ",".join(f"{v:.2f}" for v in p50_each[:4])
|
||||
if len(p50_each) > 4:
|
||||
mini += ",..."
|
||||
|
||||
lines.append(
|
||||
f"{role.upper()}: p50={st['p50']:.3f} p95={st['p95']:.3f} "
|
||||
f"sat={st['sat_pct']:.2f}% dark={st['dark_pct']:.1f}%"
|
||||
f"{role.upper()}: n={ag['count']} p50_med={ag['p50']:.3f} "
|
||||
f"spread={ag['p50_spread']:.3f} sat_med={ag['sat_pct']:.2f}%"
|
||||
)
|
||||
lines.append(f" p50_each=[{mini}]")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def rect_inside(rect, x, y):
|
||||
if rect is None:
|
||||
return False
|
||||
|
|
@ -904,6 +1137,7 @@ def main():
|
|||
|
||||
mode = "global"
|
||||
selected_target = "gray"
|
||||
selected_roi_index = 0
|
||||
edit_role = "rgb"
|
||||
drag_role = None
|
||||
beauty_preview = True
|
||||
|
|
@ -923,7 +1157,7 @@ def main():
|
|||
window_name = "Radiometric Config Tool"
|
||||
|
||||
def on_mouse(event, x, y, flags, param):
|
||||
nonlocal dragging, drag_start, drag_rect_local, last_msg, last_msg_t, data, drag_role
|
||||
nonlocal dragging, drag_start, drag_rect_local, last_msg, last_msg_t, data, drag_role, selected_roi_index
|
||||
|
||||
# Coordenadas vêm depois do preview_scale. Reescala para board real.
|
||||
if args.preview_scale != 1.0:
|
||||
|
|
@ -970,8 +1204,13 @@ def main():
|
|||
set_global_roi_for_role(data, role_to_save, roi_pct)
|
||||
last_msg = f"GLOBAL ROI {role_to_save.upper()} atualizada: {roi_pct}"
|
||||
else:
|
||||
set_patch_roi_for_role(data, selected_target, role_to_save, roi_pct)
|
||||
last_msg = f"{selected_target.upper()} ROI {role_to_save.upper()} atualizada: {roi_pct}"
|
||||
selected_roi_index = set_patch_roi_for_role(
|
||||
data, selected_target, role_to_save, roi_pct, index=selected_roi_index, append=False
|
||||
)
|
||||
last_msg = (
|
||||
f"{selected_target.upper()} ROI {role_to_save.upper()} "
|
||||
f"#{selected_roi_index + 1} atualizada: {roi_pct}"
|
||||
)
|
||||
|
||||
drag_role = None
|
||||
last_msg_t = time.time()
|
||||
|
|
@ -1018,6 +1257,7 @@ def main():
|
|||
mode=mode,
|
||||
selected_target=selected_target,
|
||||
edit_role=edit_role,
|
||||
selected_roi_index=selected_roi_index,
|
||||
drag_rect_local=drag_rect_local,
|
||||
drag_role=drag_role,
|
||||
panel_rects=panel_rects,
|
||||
|
|
@ -1052,12 +1292,14 @@ def main():
|
|||
|
||||
update_root_radiometric_config(data)
|
||||
|
||||
selected_roi_index = 0
|
||||
last_msg = f"Modo -> {mode} | active_profile={data['active_profile']}"
|
||||
last_msg_t = time.time()
|
||||
|
||||
elif k == ord("1"):
|
||||
mode = "patches"
|
||||
selected_target = "black"
|
||||
selected_roi_index = 0
|
||||
set_active_profile_name(data, "three_reference_patches_mode")
|
||||
update_root_radiometric_config(data)
|
||||
last_msg = "Selecionado: BLACK"
|
||||
|
|
@ -1066,6 +1308,7 @@ def main():
|
|||
elif k == ord("2"):
|
||||
mode = "patches"
|
||||
selected_target = "gray"
|
||||
selected_roi_index = 0
|
||||
set_active_profile_name(data, "three_reference_patches_mode")
|
||||
update_root_radiometric_config(data)
|
||||
last_msg = "Selecionado: GRAY"
|
||||
|
|
@ -1074,6 +1317,7 @@ def main():
|
|||
elif k == ord("3"):
|
||||
mode = "patches"
|
||||
selected_target = "white"
|
||||
selected_roi_index = 0
|
||||
set_active_profile_name(data, "three_reference_patches_mode")
|
||||
update_root_radiometric_config(data)
|
||||
last_msg = "Selecionado: WHITE"
|
||||
|
|
@ -1131,9 +1375,44 @@ def main():
|
|||
elif k in (ord("c"), ord("C")):
|
||||
idx = ROLES.index(edit_role) if edit_role in ROLES else 0
|
||||
edit_role = ROLES[(idx + 1) % len(ROLES)]
|
||||
selected_roi_index = 0
|
||||
last_msg = f"Camera editada -> {edit_role.upper()}"
|
||||
last_msg_t = time.time()
|
||||
|
||||
elif mode == "patches" and k in (ord("n"), ord("N")):
|
||||
selected_roi_index = add_empty_patch_roi_slot(data, selected_target, edit_role)
|
||||
last_msg = f"Nova ROI {selected_target.upper()}/{edit_role.upper()} #{selected_roi_index + 1}. Arraste para posicionar."
|
||||
last_msg_t = time.time()
|
||||
|
||||
elif mode == "patches" and k in (ord("["), ord(",")):
|
||||
entries = get_patch_roi_entries_for_role(data, selected_target, edit_role, enabled_only=False)
|
||||
if entries:
|
||||
selected_roi_index = (selected_roi_index - 1) % len(entries)
|
||||
last_msg = f"ROI selecionada -> #{selected_roi_index + 1}/{len(entries)}"
|
||||
else:
|
||||
last_msg = "Nenhuma ROI para selecionar"
|
||||
last_msg_t = time.time()
|
||||
|
||||
elif mode == "patches" and k in (ord("]"), ord(".")):
|
||||
entries = get_patch_roi_entries_for_role(data, selected_target, edit_role, enabled_only=False)
|
||||
if entries:
|
||||
selected_roi_index = (selected_roi_index + 1) % len(entries)
|
||||
last_msg = f"ROI selecionada -> #{selected_roi_index + 1}/{len(entries)}"
|
||||
else:
|
||||
last_msg = "Nenhuma ROI para selecionar"
|
||||
last_msg_t = time.time()
|
||||
|
||||
elif mode == "patches" and k in (ord("d"), ord("D")):
|
||||
ok, n_left = delete_patch_roi_for_role(data, selected_target, edit_role, selected_roi_index)
|
||||
selected_roi_index = clamp(selected_roi_index, 0, max(0, n_left - 1))
|
||||
last_msg = f"ROI apagada. Restam {n_left}." if ok else "Nenhuma ROI para apagar"
|
||||
last_msg_t = time.time()
|
||||
|
||||
elif mode == "patches" and k in (ord("t"), ord("T")):
|
||||
ok, enabled = toggle_patch_roi_enabled_for_role(data, selected_target, edit_role, selected_roi_index)
|
||||
last_msg = f"ROI #{selected_roi_index + 1} enabled={enabled}" if ok else "Nenhuma ROI para alternar"
|
||||
last_msg_t = time.time()
|
||||
|
||||
elif k in (ord("v"), ord("V")):
|
||||
beauty_preview = not beauty_preview
|
||||
last_msg = f"Beauty Preview -> {beauty_preview}"
|
||||
|
|
|
|||
Loading…
Reference in New Issue