Files
yolov26_3d/tools/model_inference/adapters/eventid_clip_resolver.py
2026-06-24 09:35:46 +08:00

823 lines
31 KiB
Python
Executable File

from __future__ import annotations
import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
import json
from dataclasses import dataclass
from pathlib import Path
import re
from typing import Any, Callable, Optional
try:
from .get_clip_by_eventid import get_associated_clip_ids
from .pdcl_clip_export_utils import (
build_clip_tasks_from_clip_ids,
run_clip_tasks_inference_exported,
)
except ImportError:
from get_clip_by_eventid import get_associated_clip_ids
from pdcl_clip_export_utils import (
build_clip_tasks_from_clip_ids,
run_clip_tasks_inference_exported,
)
DEFAULT_EVENT_CACHE_FILE = Path(__file__).resolve().parents[1] / ".cache" / "event_clip_cache.json"
@dataclass(frozen=True)
class ResolvedEventRecord:
scene: str
record_index: int
event_id: str
event_id_field_used: str
source_record: dict[str, Any]
clip_ids: list[str]
clip_source: str
@dataclass(frozen=True)
class EventResolutionStats:
total_events: int
cache_hits: int
cache_misses: int
request_workers: int
cache_file: str
direct_clip_records: int = 0
event_lookup_records: int = 0
def _dedupe_preserve_order(values: list[str]) -> list[str]:
ordered: list[str] = []
seen: set[str] = set()
for value in values:
token = str(value).strip()
if not token or token in seen:
continue
seen.add(token)
ordered.append(token)
return ordered
def _sanitize_identifier_for_path(identifier: str, prefix: str = "event_id") -> str:
token = re.sub(r'[\\/:*?"<>|\s]+', "_", str(identifier or "").strip())
token = token.strip("._")
token = token or "unknown_id"
normalized_prefix = re.sub(r"[^0-9A-Za-z]+", "_", str(prefix or "").strip())
normalized_prefix = normalized_prefix.strip("._") or "id"
return f"{normalized_prefix}_{token}"
def _build_resolution_stats_payload(resolution_stats: "EventResolutionStats") -> dict[str, Any]:
return {
"total_events": resolution_stats.total_events,
"cache_hits": resolution_stats.cache_hits,
"cache_misses": resolution_stats.cache_misses,
"request_workers": resolution_stats.request_workers,
"cache_file": resolution_stats.cache_file,
"direct_clip_records": resolution_stats.direct_clip_records,
"event_lookup_records": resolution_stats.event_lookup_records,
}
def _record_key(record: "ResolvedEventRecord") -> tuple[str, int, str]:
return (record.scene, int(record.record_index), str(record.event_id))
def _normalize_direct_clip_ids(raw_value: Any) -> list[str]:
if isinstance(raw_value, list):
return _dedupe_preserve_order([str(item).strip() for item in raw_value if str(item).strip()])
if isinstance(raw_value, str):
text = raw_value.strip()
if not text:
return []
if text.startswith("["):
try:
parsed = json.loads(text)
except Exception:
parsed = None
if isinstance(parsed, list):
return _dedupe_preserve_order([str(item).strip() for item in parsed if str(item).strip()])
return _dedupe_preserve_order([token for token in re.split(r"[\s,]+", text) if token])
if raw_value is None:
return []
token = str(raw_value).strip()
if not token:
return []
return [token]
def _extract_direct_clip_ids(record: dict[str, Any], clip_ids_field: str) -> tuple[bool, list[str]]:
field_name = str(clip_ids_field or "").strip()
if not field_name or field_name not in record:
return False, []
return True, _normalize_direct_clip_ids(record.get(field_name))
def _resolve_record_identifier(
record: dict[str, Any],
preferred_field: str,
*,
allow_direct_clip_fallback: bool = False,
) -> tuple[str, str]:
candidate_fields: list[str] = []
preferred_token = str(preferred_field or "").strip()
if preferred_token:
candidate_fields.append(preferred_token)
if allow_direct_clip_fallback:
for field_name in ("rawid", "event_id", "data_path"):
if field_name not in candidate_fields:
candidate_fields.append(field_name)
for field_name in candidate_fields:
identifier = str(record.get(field_name, "")).strip()
if identifier:
return identifier, field_name
return "", preferred_token
def _extract_condition_values(source_record: dict[str, Any], condition_fields: list[str]) -> dict[str, str]:
return {
str(field): str(source_record.get(field, "")).strip()
for field in condition_fields
}
def _select_event_records_by_condition(
records: list["ResolvedEventRecord"],
*,
condition_fields: list[str],
max_records_per_condition: int,
selection_strategy: str,
) -> tuple[list["ResolvedEventRecord"], dict[str, Any]]:
normalized_fields = [str(field).strip() for field in condition_fields if str(field).strip()]
limit = max(0, int(max_records_per_condition))
selection_enabled = bool(normalized_fields and limit > 0)
summary: dict[str, Any] = {
"enabled": selection_enabled,
"condition_fields": normalized_fields,
"max_records_per_condition": limit,
"selection_strategy": selection_strategy,
"records_before_selection": len(records),
"records_after_selection": len(records),
"group_count": 0,
"groups": [],
}
if not selection_enabled:
return list(records), summary
if selection_strategy != "first":
raise ValueError(f"Unsupported condition selection strategy: {selection_strategy!r}")
selected_records: list[ResolvedEventRecord] = []
selected_counts: dict[tuple[str, tuple[str, ...]], int] = {}
group_summaries: dict[tuple[str, tuple[str, ...]], dict[str, Any]] = {}
for record in records:
condition_values = _extract_condition_values(record.source_record, normalized_fields)
condition_tuple = tuple(condition_values[field] for field in normalized_fields)
group_key = (record.scene, condition_tuple)
group_summary = group_summaries.setdefault(
group_key,
{
"scene": record.scene,
"condition_values": condition_values,
"record_count": 0,
"selected_count": 0,
"skipped_count": 0,
"selected_record_ids": [],
"selected_record_indices": [],
"skipped_record_ids": [],
"skipped_record_indices": [],
},
)
group_summary["record_count"] += 1
current_selected_count = selected_counts.get(group_key, 0)
if current_selected_count < limit:
selected_records.append(record)
selected_counts[group_key] = current_selected_count + 1
group_summary["selected_count"] += 1
group_summary["selected_record_ids"].append(record.event_id)
group_summary["selected_record_indices"].append(record.record_index)
continue
group_summary["skipped_count"] += 1
group_summary["skipped_record_ids"].append(record.event_id)
group_summary["skipped_record_indices"].append(record.record_index)
summary["records_after_selection"] = len(selected_records)
summary["group_count"] = len(group_summaries)
summary["groups"] = list(group_summaries.values())
return selected_records, summary
def _filter_selection_summary_for_scene(selection_summary: dict[str, Any], scene: str) -> dict[str, Any]:
if not selection_summary:
return {}
scene_groups = [
dict(group)
for group in selection_summary.get("groups", [])
if str(group.get("scene", "")) == str(scene)
]
filtered = dict(selection_summary)
filtered["groups"] = scene_groups
filtered["group_count"] = len(scene_groups)
if scene_groups:
filtered["records_before_selection"] = sum(int(group.get("record_count", 0)) for group in scene_groups)
filtered["records_after_selection"] = sum(int(group.get("selected_count", 0)) for group in scene_groups)
else:
filtered["records_before_selection"] = 0
filtered["records_after_selection"] = 0
return filtered
def _format_condition_summary(source_record: dict[str, Any], condition_fields: list[str]) -> str:
normalized_fields = [str(field).strip() for field in condition_fields if str(field).strip()]
if not normalized_fields:
return ""
values = _extract_condition_values(source_record, normalized_fields)
return ", ".join(f"{field}={values.get(field, '')}" for field in normalized_fields)
def _print_event_json_summary(
*,
args: argparse.Namespace,
manifest_path: Path,
resolved_records: list["ResolvedEventRecord"],
selected_records: list["ResolvedEventRecord"],
selection_summary: dict[str, Any],
scene_results: dict[str, dict[str, Any]],
) -> None:
print(f"Event JSON file: {args.event_json_file}")
if args.scene:
print(f"Scene filter: {args.scene}")
print(
"Resolved records: "
f"{len(resolved_records)}, selected records: {len(selected_records)}"
)
print(f"Manifest: {manifest_path}")
if not selected_records:
print("No records selected.")
return
condition_fields = [str(field).strip() for field in getattr(args, "condition_fields", []) if str(field).strip()]
if args.selection_only:
print("Selection-only mode: no clip export or inference was run.")
elif selection_summary.get("enabled"):
print(
"Condition selection: "
f"{selection_summary.get('group_count', 0)} groups, "
f"strategy={selection_summary.get('selection_strategy', '')}, "
f"max_records_per_condition={selection_summary.get('max_records_per_condition', 0)}"
)
for scene in sorted(scene_results):
scene_result = scene_results[scene]
print(
f"Scene {scene}: selected_record_count="
f"{scene_result.get('selected_record_count', len([record for record in selected_records if record.scene == scene]))}"
)
scene_manifest_path = scene_result.get("scene_manifest_path")
if scene_manifest_path:
print(f" Scene manifest: {scene_manifest_path}")
if scene_result.get("scene_output_dir"):
print(f" Output dir: {scene_result['scene_output_dir']}")
scene_records = [record for record in selected_records if record.scene == scene]
for record in scene_records:
condition_summary = _format_condition_summary(record.source_record, condition_fields)
summary_parts = []
if condition_summary:
summary_parts.append(condition_summary)
summary_parts.append(f"record_id={record.event_id}")
summary_parts.append(f"clips={len(record.clip_ids)}")
print(f" - {'; '.join(summary_parts)}")
def _build_scene_manifest_payload(
*,
args: argparse.Namespace,
scene: str,
scene_records: list["ResolvedEventRecord"],
resolution_stats: "EventResolutionStats",
selection_summary: Optional[dict[str, Any]] = None,
) -> dict[str, Any]:
payload = {
"event_json_file": args.event_json_file,
"scene": scene,
"event_id_field": args.event_id_field,
"event_clip_ids_field": args.event_clip_ids_field,
"resolution_stats": _build_resolution_stats_payload(resolution_stats),
"event_record_count": len(scene_records),
"records": [
{
"record_index": record.record_index,
"event_id": record.event_id,
"event_id_field_used": record.event_id_field_used,
"clip_ids": record.clip_ids,
"clip_source": record.clip_source,
"source_record": record.source_record,
}
for record in scene_records
],
}
if selection_summary is not None:
payload["selection"] = selection_summary
return payload
def _build_event_manifest_payload(
*,
args: argparse.Namespace,
scene: str,
event_id: str,
event_records: list["ResolvedEventRecord"],
clip_ids: list[str],
resolution_stats: "EventResolutionStats",
) -> dict[str, Any]:
condition_fields = [str(field).strip() for field in getattr(args, "condition_fields", []) if str(field).strip()]
payload = {
"event_json_file": args.event_json_file,
"scene": scene,
"event_id_field": args.event_id_field,
"event_clip_ids_field": args.event_clip_ids_field,
"event_id": event_id,
"resolution_stats": _build_resolution_stats_payload(resolution_stats),
"event_record_count": len(event_records),
"clip_ids": clip_ids,
"clip_count": len(clip_ids),
"records": [
{
"record_index": record.record_index,
"event_id": record.event_id,
"event_id_field_used": record.event_id_field_used,
"clip_ids": record.clip_ids,
"clip_source": record.clip_source,
"source_record": record.source_record,
}
for record in event_records
],
}
if event_records:
payload["event_id_field_used"] = event_records[0].event_id_field_used
payload["clip_source"] = sorted({record.clip_source for record in event_records})
if condition_fields and event_records:
payload["condition_values"] = _extract_condition_values(event_records[0].source_record, condition_fields)
return payload
def load_event_scene_json(json_file: str) -> dict[str, list[dict[str, Any]]]:
path = Path(json_file)
with path.open("r", encoding="utf-8") as file:
payload = json.load(file)
if not isinstance(payload, dict):
raise ValueError(f"Expected top-level dict in {path}, got {type(payload).__name__}")
normalized: dict[str, list[dict[str, Any]]] = {}
for scene, records in payload.items():
if not isinstance(records, list):
raise ValueError(f"Scene {scene!r} should map to a list, got {type(records).__name__}")
normalized[str(scene)] = [dict(item) if isinstance(item, dict) else {"value": item} for item in records]
return normalized
def load_event_clip_cache(cache_file: str | Path) -> dict[str, list[str]]:
cache_path = Path(cache_file)
if not cache_path.exists():
return {}
try:
payload = json.loads(cache_path.read_text(encoding="utf-8"))
except Exception:
return {}
if not isinstance(payload, dict):
return {}
cache: dict[str, list[str]] = {}
for event_id, clip_ids in payload.items():
if isinstance(clip_ids, list):
cache[str(event_id)] = [str(item).strip() for item in clip_ids if str(item).strip()]
return cache
def save_event_clip_cache(cache_file: str | Path, cache_payload: dict[str, list[str]]) -> Path:
cache_path = Path(cache_file)
cache_path.parent.mkdir(parents=True, exist_ok=True)
normalized = {
str(event_id): [str(item).strip() for item in clip_ids if str(item).strip()]
for event_id, clip_ids in cache_payload.items()
}
with cache_path.open("w", encoding="utf-8") as file:
json.dump(normalized, file, indent=2, ensure_ascii=False)
return cache_path
def resolve_event_clip_ids(
event_ids: list[str],
*,
timeout: float = 60.0,
cache_file: str | Path = DEFAULT_EVENT_CACHE_FILE,
workers: int = 4,
max_retries: int = 3,
retry_backoff_sec: float = 2.0,
) -> tuple[dict[str, list[str]], EventResolutionStats]:
ordered_event_ids: list[str] = []
seen: set[str] = set()
for event_id in event_ids:
normalized = str(event_id).strip()
if not normalized or normalized in seen:
continue
seen.add(normalized)
ordered_event_ids.append(normalized)
cache_payload = load_event_clip_cache(cache_file)
resolved: dict[str, list[str]] = {}
unresolved_event_ids: list[str] = []
cache_hits = 0
cache_misses = 0
for event_id in ordered_event_ids:
if event_id in cache_payload:
resolved[event_id] = list(cache_payload[event_id])
cache_hits += 1
else:
unresolved_event_ids.append(event_id)
cache_misses += 1
if unresolved_event_ids:
max_workers = max(1, min(int(workers), len(unresolved_event_ids)))
if max_workers == 1:
for event_id in unresolved_event_ids:
clip_ids = get_associated_clip_ids(
event_id,
timeout=timeout,
max_retries=max_retries,
retry_backoff_sec=retry_backoff_sec,
)
resolved[event_id] = clip_ids
cache_payload[event_id] = clip_ids
else:
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_event_id = {
executor.submit(
get_associated_clip_ids,
event_id,
timeout,
max_retries,
retry_backoff_sec,
): event_id
for event_id in unresolved_event_ids
}
for future in as_completed(future_to_event_id):
event_id = future_to_event_id[future]
clip_ids = future.result()
resolved[event_id] = clip_ids
cache_payload[event_id] = clip_ids
save_event_clip_cache(cache_file, cache_payload)
worker_count = max(1, min(int(workers), len(unresolved_event_ids)))
else:
worker_count = 0
stats = EventResolutionStats(
total_events=len(ordered_event_ids),
cache_hits=cache_hits,
cache_misses=cache_misses,
request_workers=worker_count,
cache_file=str(Path(cache_file).resolve()),
)
return resolved, stats
def resolve_event_records(
json_file: str,
scene_filter: Optional[str] = None,
event_id_field: str = "data_path",
clip_ids_field: str = "clips",
max_events: int = 0,
timeout: float = 60.0,
cache_file: str | Path = DEFAULT_EVENT_CACHE_FILE,
workers: int = 4,
max_retries: int = 3,
retry_backoff_sec: float = 2.0,
) -> tuple[list[ResolvedEventRecord], EventResolutionStats]:
scene_payload = load_event_scene_json(json_file)
scene_names = [scene_filter] if scene_filter else list(scene_payload)
pending_records: list[tuple[str, int, dict[str, Any], str, str, list[str], str]] = []
lookup_event_ids: list[str] = []
processed = 0
direct_clip_records = 0
event_lookup_records = 0
for scene in scene_names:
records = scene_payload.get(scene)
if records is None:
raise ValueError(f"Scene {scene!r} not found in {json_file}")
for index, record in enumerate(records):
has_direct_clip_ids, direct_clip_ids = _extract_direct_clip_ids(record, clip_ids_field)
event_id, resolved_id_field = _resolve_record_identifier(
record,
event_id_field,
allow_direct_clip_fallback=has_direct_clip_ids,
)
if not event_id:
continue
clip_source = "direct_clip_ids_field" if has_direct_clip_ids else "event_lookup"
if has_direct_clip_ids:
direct_clip_records += 1
else:
event_lookup_records += 1
lookup_event_ids.append(event_id)
pending_records.append(
(scene, index, record, event_id, resolved_id_field, direct_clip_ids, clip_source)
)
processed += 1
if max_events > 0 and processed >= max_events:
break
if max_events > 0 and processed >= max_events:
break
resolved_clip_map: dict[str, list[str]] = {}
lookup_stats = EventResolutionStats(
total_events=0,
cache_hits=0,
cache_misses=0,
request_workers=0,
cache_file=str(Path(cache_file).resolve()),
)
if lookup_event_ids:
resolved_clip_map, lookup_stats = resolve_event_clip_ids(
lookup_event_ids,
timeout=timeout,
cache_file=cache_file,
workers=workers,
max_retries=max_retries,
retry_backoff_sec=retry_backoff_sec,
)
resolved = [
ResolvedEventRecord(
scene=scene,
record_index=index,
event_id=event_id,
event_id_field_used=resolved_id_field,
source_record=record,
clip_ids=direct_clip_ids if clip_source == "direct_clip_ids_field" else resolved_clip_map.get(event_id, []),
clip_source=clip_source,
)
for scene, index, record, event_id, resolved_id_field, direct_clip_ids, clip_source in pending_records
]
stats = EventResolutionStats(
total_events=len(pending_records),
cache_hits=lookup_stats.cache_hits,
cache_misses=lookup_stats.cache_misses,
request_workers=lookup_stats.request_workers,
cache_file=str(Path(cache_file).resolve()),
direct_clip_records=direct_clip_records,
event_lookup_records=event_lookup_records,
)
return resolved, stats
def build_event_resolution_payload(
json_file: str,
resolved_records: list[ResolvedEventRecord],
event_id_field: str,
clip_ids_field: str,
stats: EventResolutionStats,
selection_summary: Optional[dict[str, Any]] = None,
selected_record_keys: Optional[set[tuple[str, int, str]]] = None,
) -> dict[str, Any]:
selection_enabled = bool(selection_summary and selection_summary.get("enabled"))
selected_keys = selected_record_keys or set()
scenes: dict[str, list[dict[str, Any]]] = {}
for record in resolved_records:
scenes.setdefault(record.scene, []).append(
{
"record_index": record.record_index,
"event_id_field": event_id_field,
"event_id_field_used": record.event_id_field_used,
"event_id": record.event_id,
"event_clip_ids_field": clip_ids_field,
"clip_ids": record.clip_ids,
"clip_count": len(record.clip_ids),
"clip_source": record.clip_source,
"selected_for_inference": True if not selection_enabled else _record_key(record) in selected_keys,
"source_record": record.source_record,
}
)
payload = {
"event_json_file": json_file,
"event_id_field": event_id_field,
"event_clip_ids_field": clip_ids_field,
"scene_count": len(scenes),
"record_count": len(resolved_records),
"selected_record_count": len(selected_keys) if selection_enabled else len(resolved_records),
"resolution_stats": _build_resolution_stats_payload(stats),
"scenes": scenes,
}
if selection_summary is not None:
payload["selection"] = selection_summary
return payload
def save_event_resolution_manifest(
output_root: Path,
json_file: str,
resolved_records: list[ResolvedEventRecord],
event_id_field: str,
clip_ids_field: str,
stats: EventResolutionStats,
selection_summary: Optional[dict[str, Any]] = None,
selected_record_keys: Optional[set[tuple[str, int, str]]] = None,
) -> Path:
manifest_path = output_root / "_status" / "event_scene_manifest.json"
manifest_path.parent.mkdir(parents=True, exist_ok=True)
payload = build_event_resolution_payload(
json_file,
resolved_records,
event_id_field,
clip_ids_field,
stats,
selection_summary=selection_summary,
selected_record_keys=selected_record_keys,
)
with manifest_path.open("w", encoding="utf-8") as file:
json.dump(payload, file, indent=2, ensure_ascii=False)
return manifest_path
def run_event_json_inference_exported(
*,
context: Any,
args: argparse.Namespace,
load_env: Callable[[], Any],
run_case_inference: Callable[..., dict[str, Any]],
) -> dict[str, Any]:
resolved_records, resolution_stats = resolve_event_records(
json_file=args.event_json_file,
scene_filter=args.scene,
event_id_field=args.event_id_field,
clip_ids_field=args.event_clip_ids_field,
max_events=args.max_events,
timeout=args.event_request_timeout,
cache_file=args.event_cache_file,
workers=args.event_resolve_workers,
max_retries=args.event_request_retries,
retry_backoff_sec=args.event_request_retry_backoff_sec,
)
selected_records, selection_summary = _select_event_records_by_condition(
resolved_records,
condition_fields=[str(field).strip() for field in getattr(args, "condition_fields", []) if str(field).strip()],
max_records_per_condition=int(getattr(args, "max_records_per_condition", 0)),
selection_strategy=str(getattr(args, "condition_select_strategy", "first")),
)
selected_record_keys = {_record_key(record) for record in selected_records}
output_root = Path(args.output_dir).resolve()
output_root.mkdir(parents=True, exist_ok=True)
manifest_path = save_event_resolution_manifest(
output_root=output_root,
json_file=args.event_json_file,
resolved_records=resolved_records,
event_id_field=args.event_id_field,
clip_ids_field=args.event_clip_ids_field,
stats=resolution_stats,
selection_summary=selection_summary,
selected_record_keys=selected_record_keys,
)
summary_by_scene: dict[str, dict[str, Any]] = {}
for scene in sorted({record.scene for record in selected_records}):
scene_records = [record for record in selected_records if record.scene == scene]
scene_selection_summary = _filter_selection_summary_for_scene(selection_summary, scene)
scene_export_root = Path(args.export_root).resolve() / scene
scene_output_root = output_root / scene
scene_output_root.mkdir(parents=True, exist_ok=True)
scene_manifest_path = scene_output_root / "_status" / "scene_event_manifest.json"
scene_manifest_path.parent.mkdir(parents=True, exist_ok=True)
with scene_manifest_path.open("w", encoding="utf-8") as file:
json.dump(
_build_scene_manifest_payload(
args=args,
scene=scene,
scene_records=scene_records,
resolution_stats=resolution_stats,
selection_summary=scene_selection_summary,
),
file,
indent=2,
ensure_ascii=False,
)
if args.selection_only:
summary_by_scene[scene] = {
"scene_output_dir": str(scene_output_root),
"scene_manifest_path": str(scene_manifest_path),
"selected_record_count": len(scene_records),
"selection_only": True,
}
continue
event_records_by_id: dict[str, list[ResolvedEventRecord]] = {}
event_order: list[str] = []
for record in scene_records:
if record.event_id not in event_records_by_id:
event_order.append(record.event_id)
event_records_by_id.setdefault(record.event_id, []).append(record)
selected_scene_clip_ids: set[str] = set()
event_results: dict[str, dict[str, Any]] = {}
event_dirs: dict[str, str] = {}
event_export_dirs: dict[str, str] = {}
for event_id in event_order:
event_records = event_records_by_id[event_id]
event_clip_ids = _dedupe_preserve_order(
[clip_id for record in event_records for clip_id in record.clip_ids]
)
if args.limit_clips > 0:
limited_clip_ids: list[str] = []
for clip_id in event_clip_ids:
if clip_id in selected_scene_clip_ids:
limited_clip_ids.append(clip_id)
continue
if len(selected_scene_clip_ids) >= args.limit_clips:
continue
selected_scene_clip_ids.add(clip_id)
limited_clip_ids.append(clip_id)
event_clip_ids = limited_clip_ids
clip_tasks = build_clip_tasks_from_clip_ids(event_clip_ids)
dir_prefix = "event_id"
if any(record.clip_source == "direct_clip_ids_field" for record in event_records):
dir_prefix = event_records[0].event_id_field_used or args.event_id_field or "record_id"
event_dir_name = _sanitize_identifier_for_path(event_id, prefix=dir_prefix)
event_export_root = scene_export_root / event_dir_name
event_output_root = scene_output_root / event_dir_name
event_export_dirs[event_id] = str(event_export_root)
event_dirs[event_id] = str(event_output_root)
def on_before_run(export_root: Path, inference_root: Path, tasks: list[Any], *, _event_id: str = event_id, _event_records: list[ResolvedEventRecord] = event_records, _event_clip_ids: list[str] = event_clip_ids) -> None:
payload = _build_event_manifest_payload(
args=args,
scene=scene,
event_id=_event_id,
event_records=_event_records,
clip_ids=_event_clip_ids,
resolution_stats=resolution_stats,
)
manifest_path = inference_root / "_status" / "event_manifest.json"
manifest_path.parent.mkdir(parents=True, exist_ok=True)
with manifest_path.open("w", encoding="utf-8") as file:
json.dump(payload, file, indent=2, ensure_ascii=False)
event_results[event_id] = run_clip_tasks_inference_exported(
context=context,
args=args,
clip_tasks=clip_tasks,
load_env=load_env,
run_case_inference=run_case_inference,
export_root=event_export_root,
output_root=event_output_root,
on_before_run=on_before_run,
)
summary_by_scene[scene] = {
"scene_output_dir": str(scene_output_root),
"scene_manifest_path": str(scene_manifest_path),
"event_export_dirs": event_export_dirs,
"event_dirs": event_dirs,
"event_results": event_results,
}
result = {
"event_json_file": args.event_json_file,
"scene": args.scene or "",
"event_id_field": args.event_id_field,
"event_clip_ids_field": args.event_clip_ids_field,
"manifest_path": str(manifest_path),
"selection": selection_summary,
"selected_record_count": len(selected_records),
"resolution_stats": _build_resolution_stats_payload(resolution_stats),
"scene_results": summary_by_scene,
}
_print_event_json_summary(
args=args,
manifest_path=manifest_path,
resolved_records=resolved_records,
selected_records=selected_records,
selection_summary=selection_summary,
scene_results=summary_by_scene,
)
return result