120 lines
4.7 KiB
Python
120 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Verify visual/OCR/face GPU indexing and named-person search on live data."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import time
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
|
|
def call(client: httpx.Client, method: str, path: str, **kwargs: Any) -> httpx.Response:
|
|
response = client.request(method, path, **kwargs)
|
|
if response.is_error:
|
|
raise RuntimeError(f"{method} {path} -> {response.status_code}: {response.text[:1000]}")
|
|
return response
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--base-url", required=True)
|
|
parser.add_argument("--token-file", type=Path, required=True)
|
|
parser.add_argument("--run-dir", type=Path, required=True)
|
|
parser.add_argument("--case-id", default="face")
|
|
parser.add_argument("--timeout", type=int, default=600)
|
|
args = parser.parse_args()
|
|
state = json.loads((args.run_dir / "state.json").read_text())
|
|
token = args.token_file.read_text().strip()
|
|
client = httpx.Client(
|
|
base_url=args.base_url.rstrip("/"),
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
timeout=httpx.Timeout(60, connect=10),
|
|
)
|
|
filename = f"{state['run_id']}-{args.case_id}-e2e-face.mp4"
|
|
observed: dict[str, list[str]] = {name: [] for name in ("visual", "ocr", "faces")}
|
|
deadline = time.monotonic() + args.timeout
|
|
video: dict[str, Any] | None = None
|
|
while time.monotonic() < deadline:
|
|
models = call(client, "GET", "/api/v1/models").json()
|
|
components = models.get("accelerator", {}).get("components", {})
|
|
for component in observed:
|
|
device = components.get(component, {}).get("actual_device")
|
|
if device and device not in observed[component]:
|
|
observed[component].append(device)
|
|
videos = call(client, "GET", "/api/v1/videos?limit=500").json()
|
|
video = next(
|
|
(
|
|
item
|
|
for item in videos
|
|
if item.get("source_id") == state["source_id"]
|
|
and str(item.get("source_key") or "").rsplit("/", 1)[-1] == filename
|
|
),
|
|
None,
|
|
)
|
|
if video and video.get("index_state", {}).get("faces") in {"ready", "failed"}:
|
|
break
|
|
time.sleep(1)
|
|
if not video:
|
|
raise AssertionError("face fixture never appeared in the video list")
|
|
if video.get("index_state", {}).get("faces") != "ready":
|
|
raise AssertionError(f"face indexing failed: {video.get('index_state')} {video.get('error')}")
|
|
|
|
matched_person: dict[str, Any] | None = None
|
|
matched_faces: list[dict[str, Any]] = []
|
|
for person in call(client, "GET", "/api/v1/people").json():
|
|
faces = call(client, "GET", f"/api/v1/people/{person['id']}/faces?limit=500").json()
|
|
owned = [face for face in faces if face.get("video_id") == video["id"]]
|
|
if owned:
|
|
matched_person = person
|
|
matched_faces = owned
|
|
break
|
|
if not matched_person:
|
|
raise AssertionError("the official OpenCV face fixture did not produce a person cluster")
|
|
person_name = f"{state['run_id']}-人物-729"
|
|
call(client, "PATCH", f"/api/v1/people/{matched_person['id']}", json={"name": person_name})
|
|
positive = call(
|
|
client,
|
|
"POST",
|
|
"/api/v1/search",
|
|
json={"text": person_name, "recognition_types": ["person"], "limit": 100},
|
|
).json()
|
|
if video["id"] not in {item["video_id"] for item in positive.get("items", [])}:
|
|
raise AssertionError("named-person search omitted the face fixture video")
|
|
negative = call(
|
|
client,
|
|
"POST",
|
|
"/api/v1/search",
|
|
json={
|
|
"text": f"{state['run_id']}-不存在人物-9834721",
|
|
"recognition_types": ["person"],
|
|
"limit": 100,
|
|
},
|
|
).json()
|
|
if video["id"] in {item["video_id"] for item in negative.get("items", [])}:
|
|
raise AssertionError("non-matching person search returned the face fixture video")
|
|
non_gpu = {name: devices for name, devices in observed.items() if not any("GPU" in d.upper() for d in devices)}
|
|
if non_gpu:
|
|
raise AssertionError(f"GPU was not observed for all frame components: {non_gpu}")
|
|
|
|
report = {
|
|
"video_id": video["id"],
|
|
"person_id": matched_person["id"],
|
|
"person_name": person_name,
|
|
"face_count": len(matched_faces),
|
|
"observed_devices": observed,
|
|
"positive_matches": len(positive.get("items", [])),
|
|
"negative_matches": len(negative.get("items", [])),
|
|
}
|
|
(args.run_dir / "face-report.json").write_text(
|
|
json.dumps(report, ensure_ascii=False, indent=2) + "\n"
|
|
)
|
|
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|