feat: trace-level matching, health watcher/worker status, timezone config
This commit is contained in:
@@ -13,6 +13,7 @@ Detection cost: near-zero CPU (Vision ANE)
|
||||
Embedding cost: near-zero CPU (CoreML ANE)
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
@@ -29,6 +30,7 @@ from pathlib import Path
|
||||
import coremltools as ct
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from redis_publisher import RedisPublisher
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SWIFT_BIN = os.path.join(SCRIPT_DIR, "swift_processors", ".build", "debug", "swift_face")
|
||||
@@ -49,11 +51,12 @@ def classify_pose(roll: float, yaw: float) -> str:
|
||||
|
||||
class FaceProcessorVision:
|
||||
def __init__(self, video_path: str, output_path: str, uuid: str = "",
|
||||
sample_interval: int = 3):
|
||||
sample_interval: int = 3, publisher: RedisPublisher = None):
|
||||
self.video_path = video_path
|
||||
self.output_path = output_path
|
||||
self.uuid = uuid
|
||||
self.sample_interval = sample_interval
|
||||
self.publisher = publisher
|
||||
|
||||
# Load CoreML FaceNet
|
||||
self.coreml_model = None
|
||||
@@ -127,7 +130,33 @@ class FaceProcessorVision:
|
||||
|
||||
print(f"[FACE_V2] Running: {' '.join(cmd)}")
|
||||
t0 = time.time()
|
||||
subprocess.run(cmd, check=True)
|
||||
log_path = swift_out + ".log"
|
||||
log_f = open(log_path, "w")
|
||||
proc = subprocess.Popen(cmd, stdout=log_f, stderr=subprocess.STDOUT, text=True)
|
||||
last_pct = -1
|
||||
while proc.poll() is None:
|
||||
time.sleep(10)
|
||||
# Read latest log lines
|
||||
try:
|
||||
with open(log_path) as lf:
|
||||
for line in lf:
|
||||
line = line.strip()
|
||||
m = re.search(r'(\d+)% complete', line)
|
||||
if m:
|
||||
pct = int(m.group(1))
|
||||
if pct > last_pct:
|
||||
last_pct = pct
|
||||
if self.publisher:
|
||||
self.publisher.progress("face", pct, 100, f"swift detect {pct}%")
|
||||
except Exception:
|
||||
pass
|
||||
log_f.close()
|
||||
if proc.returncode != 0:
|
||||
stderr_out = proc.stderr.read()
|
||||
if stderr_out:
|
||||
print(stderr_out.strip(), file=sys.stderr)
|
||||
raise RuntimeError(f"swift_face exited with code {proc.returncode}")
|
||||
|
||||
elapsed = time.time() - t0
|
||||
print(f"[FACE_V2] Detection done in {elapsed:.1f}s")
|
||||
|
||||
@@ -156,6 +185,8 @@ class FaceProcessorVision:
|
||||
|
||||
t0 = time.time()
|
||||
embed_count = 0
|
||||
total_face_count = 0
|
||||
last_pct = -1
|
||||
|
||||
for frame_info in frames:
|
||||
frame_num = frame_info["frame"]
|
||||
@@ -220,6 +251,12 @@ class FaceProcessorVision:
|
||||
if len(face_data["frames"]) % 100 == 0:
|
||||
elapsed = time.time() - t0
|
||||
print(f"[FACE_V2] {len(face_data['frames'])} frames, {embed_count} embeddings, {elapsed:.0f}s")
|
||||
if self.publisher:
|
||||
pct = int(len(face_data["frames"]) * 100 / max(len(frames), 1))
|
||||
if pct > last_pct:
|
||||
last_pct = pct
|
||||
self.publisher.progress("face", len(face_data["frames"]), len(frames),
|
||||
f"{embed_count} faces", embed_count, "faces")
|
||||
|
||||
self.video.release()
|
||||
|
||||
@@ -259,19 +296,36 @@ def main():
|
||||
parser.add_argument("--force", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
publisher = RedisPublisher(args.uuid) if args.uuid else None
|
||||
if publisher:
|
||||
publisher.info("face", "FACE_START")
|
||||
|
||||
if args.force and os.path.exists(args.output_path):
|
||||
os.remove(args.output_path)
|
||||
|
||||
processor = FaceProcessorVision(
|
||||
args.video_path, args.output_path,
|
||||
args.uuid, args.sample_interval
|
||||
args.uuid, args.sample_interval, publisher
|
||||
)
|
||||
|
||||
# Step 1: Vision detection (bbox + pose via ANE)
|
||||
detection = processor.process_with_swift()
|
||||
try:
|
||||
detection = processor.process_with_swift()
|
||||
except Exception as e:
|
||||
if publisher:
|
||||
publisher.error("face", f"Detection failed: {e}")
|
||||
raise
|
||||
|
||||
# Step 2: CoreML embedding + save
|
||||
processor.embed_and_save(detection)
|
||||
try:
|
||||
processor.embed_and_save(detection)
|
||||
except Exception as e:
|
||||
if publisher:
|
||||
publisher.error("face", f"Embedding failed: {e}")
|
||||
raise
|
||||
|
||||
if publisher:
|
||||
publisher.complete("face", f"{len(detection.get('frames',[]))} frames")
|
||||
|
||||
# Clean up temp detection file
|
||||
swift_out = args.output_path.replace(".json", "_detect.json")
|
||||
|
||||
Reference in New Issue
Block a user