cuibinge's picture
Sync YOLO training and evaluation utilities (part 2)
c2b1b26 verified
Raw
History Blame Contribute Delete
6.34 kB
"""Compose task profiles from Markdown capability cards."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
REGISTRY_ROOT = Path(__file__).resolve().parents[1] / "docs" / "registry"
def parse_scalar(value: str) -> Any:
value = value.strip()
if value in {"true", "True"}:
return True
if value in {"false", "False"}:
return False
if value in {"null", "None"}:
return None
if value.startswith("[") and value.endswith("]"):
inner = value[1:-1].strip()
if not inner:
return []
return [parse_scalar(part.strip()) for part in inner.split(",")]
try:
if "." in value:
return float(value)
return int(value)
except ValueError:
return value.strip("\"'")
def parse_front_matter(text: str) -> tuple[dict[str, Any], str]:
lines = text.splitlines()
if not lines or lines[0].strip() != "---":
return {}, text
meta: dict[str, Any] = {}
end = None
for idx, line in enumerate(lines[1:], start=1):
if line.strip() == "---":
end = idx
break
if not line.strip() or line.lstrip().startswith("#"):
continue
if ":" not in line:
continue
key, value = line.split(":", 1)
meta[key.strip()] = parse_scalar(value)
if end is None:
return meta, text
return meta, "\n".join(lines[end + 1 :]).strip()
def load_card(kind: str, card_id: str, registry_root: Path) -> dict[str, Any]:
path = registry_root / kind / f"{card_id}.md"
if not path.exists():
available = sorted(p.stem for p in (registry_root / kind).glob("*.md"))
raise FileNotFoundError(f"Card not found: {path}. Available {kind}: {available}")
text = path.read_text(encoding="utf-8")
meta, body = parse_front_matter(text)
return {"kind": kind, "id": card_id, "path": str(path), "meta": meta, "body": body}
def list_cards(registry_root: Path) -> dict[str, list[str]]:
result: dict[str, list[str]] = {}
for kind in ("elements", "satellites", "sensors", "resolutions"):
folder = registry_root / kind
result[kind] = sorted(p.stem for p in folder.glob("*.md")) if folder.exists() else []
return result
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--element")
parser.add_argument("--satellite")
parser.add_argument("--sensor")
parser.add_argument("--resolution")
parser.add_argument(
"--fusion",
help="Optional sensor/product fusion card, for example FUSED_OPTICAL or STREAM_FUSION.",
)
parser.add_argument("--registry-root", default=str(REGISTRY_ROOT))
parser.add_argument("--output", required=False)
parser.add_argument("--list", action="store_true", help="List available cards and exit.")
return parser.parse_args()
def main() -> None:
args = parse_args()
registry_root = Path(args.registry_root)
if args.list:
print(json.dumps(list_cards(registry_root), indent=2, ensure_ascii=False))
return
required = {
"elements": args.element,
"satellites": args.satellite,
"sensors": args.sensor,
"resolutions": args.resolution,
}
missing = [key for key, value in required.items() if not value]
if missing:
raise SystemExit(f"Missing required cards: {missing}. Use --list to inspect available cards.")
cards = {kind: load_card(kind, card_id, registry_root) for kind, card_id in required.items() if card_id}
if args.fusion:
cards["fusion"] = load_card("sensors", args.fusion, registry_root)
element_meta = cards["elements"]["meta"]
sensor_meta = cards["sensors"]["meta"]
resolution_meta = cards["resolutions"]["meta"]
fusion_meta = cards.get("fusion", {}).get("meta", {})
fusion_state = fusion_meta.get("fusion_state", sensor_meta.get("fusion_state", "none"))
profile = {
"profile_id": "_".join(
part for part in [args.element, args.satellite, args.sensor, args.fusion, args.resolution] if part
),
"cards": cards,
"task": {
"element": args.element,
"task_types": element_meta.get("task_types", []),
"preferred_heads": element_meta.get("preferred_heads", []),
"label_formats": element_meta.get("label_formats", []),
"negative_policy": element_meta.get("negative_policy", "unlabeled_is_ignore"),
},
"input": {
"satellite": args.satellite,
"sensor": args.sensor,
"resolution_m": resolution_meta.get("resolution_m"),
"modalities": sensor_meta.get("modalities", []),
"common_bands": sensor_meta.get("common_bands", []),
"recommended_patch_sizes": resolution_meta.get("recommended_patch_sizes", []),
},
"fusion": {
"card": args.fusion,
"state": fusion_state,
"modalities": fusion_meta.get("modalities", sensor_meta.get("modalities", [])),
"supports_streaming_fusion": fusion_meta.get(
"supports_streaming_fusion", sensor_meta.get("supports_streaming_fusion", False)
),
"requires_fusion_metadata": fusion_meta.get(
"requires_fusion_metadata", sensor_meta.get("requires_fusion_metadata", False)
),
"required_manifest_fields": [
"state",
"method",
"sources",
"target_resolution_m",
"native_multispectral_resolution_m",
"persisted",
"reproducible",
"spectral_preservation",
],
},
"constraints": {
"do_not_assume_external_coastline_or_land_mask": True,
"unlabeled_elements_are_ignore_not_negative": True,
},
}
output = json.dumps(profile, indent=2, ensure_ascii=False)
if args.output:
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(output + "\n", encoding="utf-8")
print(output_path)
else:
print(output)
if __name__ == "__main__":
main()