diff --git a/docs/krea2-pov-pose-atlas.md b/docs/krea2-pov-pose-atlas.md index 2910dbd..a5a848c 100644 --- a/docs/krea2-pov-pose-atlas.md +++ b/docs/krea2-pov-pose-atlas.md @@ -25,7 +25,8 @@ and generator decision behind that variant. For command-line planning, `python tools/krea2_tuning_report.py` shows which catalog variants are proven or pending and which atlas pose folders are still -unmapped by the catalog. +unmapped by the catalog. Unmapped folders include sample pose/control image +paths and a suggested candidate key to start the next catalog entry. ## Inventory diff --git a/krea2_tuning_report.py b/krea2_tuning_report.py index 6f309b7..ad28d0f 100644 --- a/krea2_tuning_report.py +++ b/krea2_tuning_report.py @@ -95,6 +95,12 @@ def _is_background_or_control_folder(folder_name: str) -> bool: ) +def _sample_pngs(folder: Path, limit: int) -> list[str]: + if not folder.is_dir() or limit <= 0: + return [] + return [str(path) for path in sorted(folder.glob("*.png"), key=lambda path: path.name.lower())[:limit]] + + def atlas_folder_rows(atlas_root: str | Path | None = None) -> list[dict[str, Any]]: root = Path(atlas_root) if atlas_root is not None else _catalog_atlas_root() if not root.is_dir(): @@ -137,6 +143,34 @@ def atlas_coverage_summary(atlas_root: str | Path | None = None) -> dict[str, An } +def _suggested_variant_key(folder_name: str) -> str: + normalized = "".join(char if char.isalnum() else "_" for char in folder_name.lower()).strip("_") + while "__" in normalized: + normalized = normalized.replace("__", "_") + return f"pov_{normalized}_candidate" if normalized else "pov_unmapped_candidate" + + +def atlas_gap_plans(atlas_root: str | Path | None = None, sample_limit: int = 3) -> list[dict[str, Any]]: + root = Path(atlas_root) if atlas_root is not None else _catalog_atlas_root() + plans: list[dict[str, Any]] = [] + for row in atlas_folder_rows(atlas_root=root): + if row.get("mapped"): + continue + folder_name = str(row.get("folder") or "") + folder_path = root / folder_name + control_folder = Path(str(row.get("control_folder") or "")) + plans.append( + { + "folder": folder_name, + "suggested_variant_key": _suggested_variant_key(folder_name), + "image_count": int(row.get("image_count") or 0), + "sample_images": _sample_pngs(folder_path, sample_limit), + "control_images": _sample_pngs(control_folder, sample_limit), + } + ) + return plans + + def next_test_plans() -> list[dict[str, Any]]: rows_by_key = {str(row.get("key")): row for row in coverage_rows()} plans: list[dict[str, Any]] = [] @@ -218,4 +252,21 @@ def markdown_report(atlas_root: str | Path | None = None) -> str: ) if unmapped: lines.extend(["", "Unmapped atlas folders:", *[f"- {folder}" for folder in unmapped]]) + gap_plans = atlas_gap_plans(atlas_root=atlas_root) + if gap_plans: + lines.extend(["", "## Atlas Gap Plans"]) + for plan in gap_plans: + sample_images = plan["sample_images"] + control_images = plan["control_images"] + lines.extend( + [ + "", + f"### {plan['folder']}", + "", + f"- Suggested key: {plan['suggested_variant_key']}", + f"- Pose images: {plan['image_count']}", + f"- Samples: {', '.join(sample_images) or 'none'}", + f"- Controls: {', '.join(control_images) or 'none'}", + ] + ) return "\n".join(lines) diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 13a98e5..4c3a190 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -6884,6 +6884,8 @@ def smoke_krea2_tuning_report_policy() -> None: folder_path = atlas_root / folder folder_path.mkdir() (folder_path / f"{folder}_sample.png").write_bytes(b"") + (atlas_root / "custom_pose" / "custom_pose_b.png").write_bytes(b"") + (atlas_root / "custom_pose_control" / "custom_pose_control_b.png").write_bytes(b"") atlas_rows = krea2_tuning_report.atlas_folder_rows(atlas_root=atlas_root) atlas_by_folder = {row.get("folder"): row for row in atlas_rows} _expect(atlas_by_folder.get("doggy", {}).get("mapped") is True, "Atlas report should mark catalog folders as mapped") @@ -6896,9 +6898,16 @@ def smoke_krea2_tuning_report_policy() -> None: _expect(atlas_summary.get("pose_folder_count") == 2, "Atlas report should count only pose folders") _expect(atlas_summary.get("mapped_folder_count") == 1, "Atlas report should count mapped pose folders") _expect(atlas_summary.get("unmapped_folders") == ["custom_pose"], "Atlas report should identify unmapped pose folders") + gap_plans = krea2_tuning_report.atlas_gap_plans(atlas_root=atlas_root, sample_limit=2) + _expect([plan.get("folder") for plan in gap_plans] == ["custom_pose"], "Atlas gap plans should follow unmapped folders") + custom_gap = gap_plans[0] + _expect(custom_gap.get("suggested_variant_key") == "pov_custom_pose_candidate", "Atlas gap plan should suggest stable variant key") + _expect(len(custom_gap.get("sample_images") or []) == 2, "Atlas gap plan should include deterministic sample images") + _expect(len(custom_gap.get("control_images") or []) == 2, "Atlas gap plan should include deterministic control images") atlas_markdown = krea2_tuning_report.markdown_report(atlas_root=atlas_root) _expect("Atlas Folder Coverage" in atlas_markdown, "Krea2 tuning report markdown lost atlas coverage section") _expect("custom_pose" in atlas_markdown, "Krea2 tuning report markdown lost unmapped atlas folder") + _expect("pov_custom_pose_candidate" in atlas_markdown, "Krea2 tuning report markdown lost suggested gap key") markdown = krea2_tuning_report.markdown_report() _expect("pov_ballsucking_low_head" in markdown, "Krea2 tuning report markdown lost candidate variant") _expect("needs_fixed_seed_tests" in markdown, "Krea2 tuning report markdown lost coverage state")