# vision_face_preview.py — face detection on an in-flash model, watched live in
# the Tendrl dev UI (Contact / dev-mcp: Files -> Run).
#
# The boxes and keypoints are drawn into the *pixels* before the frame is
# previewed, so what you see in the browser is exactly what the board decided.
#
# The model (`/rom/blazeface_front_128.tflite`) ships in OpenMV flash — nothing
# to train, nothing to upload. Its weights are read in place, but loading it
# still allocates a tensor arena on the heap; print `gc.mem_free()` before and
# after if you're tight on memory. At 128x128 this is the cheapest of the three
# MediaPipe models and the one to try first on a board without an NPU.
#
# Adapted from OpenMV's MediaPipe face detection example:
#   https://github.com/openmv/openmv/blob/master/scripts/examples/
#
# Requires: OpenMV cam with `ml`, the vision add-on (package-vision.json),
# and firmware that ships `ml.postprocessing.mediapipe`.
import time

from tendrl import preview
from tendrl.vision import Camera, draw_boxes, draw_keypoints, face_boxes

# BlazeFace wants a SQUARE input — VGA windowed to 400x400. A non-square frame
# still runs, it just detects worse.
cam = Camera(framesize="VGA", window=(400, 400))

detect = face_boxes(min_conf=0.4)

while True:
    img = cam.snapshot()

    # [(x, y, w, h, score, keypoints), ...] — keypoints is a (6, 2) array:
    # 0 right eye, 1 left eye, 2 nose, 3 mouth, 4 right ear, 5 left ear.
    faces = detect(img)

    for x, y, w, h, score, kps in faces:
        draw_boxes(img, [(x, y, w, h)], label="face %.2f" % score, color=(0, 0, 255))
        draw_keypoints(img, kps, color=(255, 0, 0))

    if faces:
        print("%d face(s), best %.2f" % (len(faces), max(f[4] for f in faces)))

    # Draw FIRST, preview second: this JPEG-compresses img in place.
    preview.frame(img)
    time.sleep_ms(50)


# --- To publish instead of preview -------------------------------------------
# face() is the same model reduced to a state, which drops straight into a Watch
# so the device sends ~200 bytes on a transition instead of a video feed:
#
#     from tendrl import Client
#     from tendrl.vision import Watch, face
#
#     watch = Watch(Client(mode="sync"), detect=face(min_conf=0.4), debounce=2)
#     while True:
#         watch.update(cam.snapshot())     # publishes only when it changes
