Files
imagefind/scripts/live-face-acceptance.py
T

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()