feat: add ImageFind application and release pipelines
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user