diff --git a/.gitattributes b/.gitattributes index d5df454628f82dcec8320484769dc3036ef90eb0..6918df289b10c92750ed6f252b49b75c75f8f596 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,38 +1,37 @@ -*.7z filter=lfs diff=lfs merge=lfs -text -*.arrow filter=lfs diff=lfs merge=lfs -text -*.bin filter=lfs diff=lfs merge=lfs -text -*.bz2 filter=lfs diff=lfs merge=lfs -text -*.ckpt filter=lfs diff=lfs merge=lfs -text -*.ftz filter=lfs diff=lfs merge=lfs -text -*.gz filter=lfs diff=lfs merge=lfs -text -*.h5 filter=lfs diff=lfs merge=lfs -text -*.joblib filter=lfs diff=lfs merge=lfs -text -*.lfs.* filter=lfs diff=lfs merge=lfs -text -*.mlmodel filter=lfs diff=lfs merge=lfs -text -*.model filter=lfs diff=lfs merge=lfs -text -*.msgpack filter=lfs diff=lfs merge=lfs -text -*.npy filter=lfs diff=lfs merge=lfs -text -*.npz filter=lfs diff=lfs merge=lfs -text -*.onnx filter=lfs diff=lfs merge=lfs -text -*.ot filter=lfs diff=lfs merge=lfs -text -*.parquet filter=lfs diff=lfs merge=lfs -text -*.pb filter=lfs diff=lfs merge=lfs -text -*.pickle filter=lfs diff=lfs merge=lfs -text -*.pkl filter=lfs diff=lfs merge=lfs -text -*.pt filter=lfs diff=lfs merge=lfs -text -*.pth filter=lfs diff=lfs merge=lfs -text -*.rar filter=lfs diff=lfs merge=lfs -text -*.safetensors filter=lfs diff=lfs merge=lfs -text -saved_model/**/* filter=lfs diff=lfs merge=lfs -text -*.tar.* filter=lfs diff=lfs merge=lfs -text -*.tar filter=lfs diff=lfs merge=lfs -text -*.tflite filter=lfs diff=lfs merge=lfs -text -*.tgz filter=lfs diff=lfs merge=lfs -text -*.wasm filter=lfs diff=lfs merge=lfs -text -*.xz filter=lfs diff=lfs merge=lfs -text -*.zip filter=lfs diff=lfs merge=lfs -text -*.zst filter=lfs diff=lfs merge=lfs -text -*tfevents* filter=lfs diff=lfs merge=lfs -text -*.mp4 filter=lfs diff=lfs merge=lfs -text -*.deb filter=lfs diff=lfs merge=lfs -text -examples_fid/example_couple.jpg filter=lfs diff=lfs merge=lfs -text +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.mlmodel filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.deb filter=lfs diff=lfs merge=lfs -text diff --git a/Dockerfile b/Dockerfile index d8c88a3df4116b7e552b183562ecc58582bea378..dbf1d8b72fb6d3241efe615433b0f8dea5b03f8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,27 @@ -# syntax=docker/dockerfile:1 -FROM python:3.11 - -RUN apt-get update && apt-get install -y ffmpeg libopenblas-dev libopencv-dev qt6-base-dev qt6-multimedia-dev libssl-dev mesa-opencl-icd libboost-all-dev libturbojpeg0-dev ocl-icd-opencl-dev clinfo ocl-icd-libopencl1 && rm -rf /var/lib/apt/lists - -ARG HF_TOKEN="" - -RUN pip install --no-cache-dir huggingface-hub -COPY install_eve.py /tmp/install_eve.py -RUN --mount=type=secret,id=MODEL_ACCESS_TOKEN \ - HF_TOKEN="$HF_TOKEN" python /tmp/install_eve.py && rm /tmp/install_eve.py - -RUN useradd -m -u 1000 user -USER user -ENV PATH="/home/user/.local/bin:$PATH" - -WORKDIR /app - -COPY --chown=user ./requirements.txt requirements.txt -RUN pip install --no-cache-dir --upgrade -r requirements.txt - -RUN mkdir -p /home/user/.config - -COPY --chown=user . /app -CMD ["python", "app.py"] +# syntax=docker/dockerfile:1 +FROM python:3.11 + +RUN apt-get update && apt-get install -y ffmpeg libopenblas-dev libopencv-dev qt6-base-dev qt6-multimedia-dev libssl-dev mesa-opencl-icd libboost-all-dev libturbojpeg0-dev ocl-icd-opencl-dev clinfo ocl-icd-libopencl1 && rm -rf /var/lib/apt/lists + +ARG HF_TOKEN="" + +RUN pip install --no-cache-dir huggingface-hub +COPY install_eve.py /tmp/install_eve.py +RUN --mount=type=secret,id=MODEL_ACCESS_TOKEN \ + HF_TOKEN="$HF_TOKEN" python /tmp/install_eve.py && rm /tmp/install_eve.py + +RUN useradd -m -u 1000 user +USER user +ENV PATH="/home/user/.local/bin:$PATH" + +WORKDIR /app + +COPY --chown=user ./requirements.txt requirements.txt +RUN pip install --no-cache-dir --upgrade -r requirements.txt + +RUN mkdir -p /home/user/.config + +COPY --chown=user . /app +RUN --mount=type=secret,id=MODEL_ACCESS_TOKEN,uid=1000 \ + HF_TOKEN="$HF_TOKEN" python download_examples.py +CMD ["python", "app.py"] diff --git a/app.py b/app.py index 96b6ca0779857db07ea559e112d5859de600c44a..ea32a13698f82082c84db9801d1bb3e1c38742bc 100644 --- a/app.py +++ b/app.py @@ -1,301 +1,302 @@ -import atexit -import os -import sys -from pathlib import Path - -import gradio as gr - -sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent / "shared")) -sys.path.insert(0, str(Path(__file__).resolve().parent / "shared")) - -from env_utils import load_dotenv_if_present, require_secrets -from eula_tab import build_eula_tab -from eve_app_tabs import build_live_inference_tab, build_offline_inference_tab -from eve_inference_handlers import EveAppHandlers, patch_video_for_external_urls -from eve_worker_pool import EveWorkerPool -from face_id_tab import FaceIdTab -from live_inference import ( - TAB_SWITCH_AUTO_STOP_JS, - RtcConfigProvider, - patch_aioice_stun_transaction, - patch_aiortc_h264_nvenc, - patch_fastrtc_frame_queue, - patch_fastrtc_yuv420p_output, -) -from live_stream_manager import LiveStreamManager -from log_utils import log_cpu_info, setup_logger -from session_tracker import SessionTracker -from usage_analytics import UsageTracker -from video_file_server import VideoFileServer -from video_processing import VideoLimits, get_example_videos, wire_video_upload - - -def _build_feature_checkboxes( - hint: str = "", -) -> tuple[gr.Checkbox, gr.Checkbox, gr.Checkbox, gr.Checkbox]: - """Build the EVE feature checkbox group used by both tabs. - - Must be called inside a Gradio layout context. - """ - with gr.Group(): - label = "

Features" - if hint: - label += f" — {hint}" - label += "

" - gr.HTML(label) - cb_face = gr.Checkbox(label="Face Detection", value=True) - cb_person = gr.Checkbox(label="Person Detection", value=True) - cb_face_id = gr.Checkbox(label="Face Identification", value=False) - cb_hand_gesture = gr.Checkbox(label="Hand Detection", value=False) - return cb_face, cb_person, cb_face_id, cb_hand_gesture - - -if __name__ == "__main__": - - patch_fastrtc_frame_queue() - patch_fastrtc_yuv420p_output() - patch_aioice_stun_transaction() - patch_aiortc_h264_nvenc() - - load_dotenv_if_present() - logger = setup_logger(name="app") - log_cpu_info(logger) - require_secrets("MODEL_ACCESS_TOKEN") - - tracker = UsageTracker( - repo_id=os.environ.get("ANALYTICS_REPO_ID", "LatticeSemi/PRIVATE-Demo-Analytics-v1.0"), - ) - tracker.log("server", "server_start") - - max_workers = int(os.environ.get("MAX_WORKERS", os.cpu_count())) - max_ram_gb = float(os.environ.get("MAX_RAM_GB", 32)) - pool = EveWorkerPool(max_workers=max_workers, max_ram_gb=max_ram_gb, ram_headroom_gb=2.0) - - # FPS cap: each live stream runs between MAX_TARGET_FPS (idle) and - # MIN_TARGET_FPS (full load). Set either to 0 to disable the cap. - max_fps_raw = float(os.environ.get("MAX_TARGET_FPS", "24")) - min_fps_raw = float(os.environ.get("MIN_TARGET_FPS", "15")) - max_fps: float | None = max_fps_raw if max_fps_raw > 0 else None - min_fps: float | None = min_fps_raw if min_fps_raw > 0 else None - - camera_width = int(os.environ.get("CAMERA_WIDTH", 640)) - camera_height = int(os.environ.get("CAMERA_HEIGHT", 360)) - - stream_manager = LiveStreamManager( - pool, - session_lifetime_seconds=60 * 4, - max_fps=max_fps, - min_fps=min_fps, - tracker=tracker, - ) - - session_tracker = SessionTracker(pool=pool, tracker=tracker, logger=logger) - - # Separate HTTP server for video output — bypasses Chrome's per-origin - # connection limit that blocks /file= requests while SSE connections are open. - video_server: VideoFileServer | None = None - # TODO: Put that under an env var, for debug purposes, locally, for more than 2 tabs. - """if not os.environ.get("SPACE_ID"): - gradio_cache = os.path.join(tempfile.gettempdir(), "gradio") - os.makedirs(gradio_cache, exist_ok=True) - video_server = VideoFileServer(root_dir=gradio_cache, logger=logger) - video_server.start()""" - - handlers = EveAppHandlers( - pool=pool, - stream_manager=stream_manager, - sessions=session_tracker, - logger=logger, - max_fps=max_fps, - min_fps=min_fps, - video_server=video_server, - ) - - def _shutdown() -> None: - session_tracker.shutdown() - stream_manager.shutdown() - tracker.log("server", "server_stop") - tracker.shutdown() - - atexit.register(_shutdown) - - EXAMPLE_VIDEOS = get_example_videos() - VIDEO_LIMITS = VideoLimits() - rtc_config_provider = RtcConfigProvider() - - face_id_tab = FaceIdTab( - pool, - max_users=2, - examples_dir="./examples_fid", - accept_video=True, - video_limits=VideoLimits(max_file_size_mb=150, max_duration_seconds=60), - tracker=tracker, - ) - - with gr.Blocks( - title="Eve HMI Demo", - theme=gr.themes.Default( - text_size=gr.themes.sizes.text_lg, - primary_hue=gr.themes.colors.yellow, - ), - css=( - f"#webrtc-stream-col {{ max-width: {camera_width}px !important; margin: 0 auto; }}" - " .gradio-container h1, .gradio-container .md h1 { font-size: 2.25rem !important; }" - " .gradio-container h2, .gradio-container .md h2 { font-size: 1.75rem !important; }" - " .gradio-container h3, .gradio-container .md h3 { font-size: 1.4rem !important; }" - " .gradio-container button[role='tab']," - " .gradio-container button[role='tab'] *" - " { text-decoration: underline !important; }" - " .gradio-container .tab-container {" - " height: auto !important;" - " overflow: visible !important;" - " gap: 4px !important;" - " border-bottom: 2px solid var(--border-color-primary) !important; }" - " .gradio-container .tab-container::after { display: none !important; }" - " .gradio-container button[role='tab'] {" - " height: auto !important;" - " padding: 10px 20px !important;" - " border: 1px solid var(--border-color-primary) !important;" - " border-bottom: none !important;" - " border-radius: 8px 8px 0 0 !important;" - " background: var(--background-fill-secondary) !important;" - " margin-bottom: -2px !important; }" - " .gradio-container button[role='tab'].selected {" - " background: var(--primary-500) !important;" - " color: var(--neutral-950) !important;" - " border-color: var(--primary-500) !important;" - " font-weight: 600 !important; }" - " .gradio-container button[role='tab'].selected::after { display: none !important; }" - ), - head=TAB_SWITCH_AUTO_STOP_JS, - ) as demo: - gr.Markdown("# Lattice sensAI Edge Vision Engine SDK") - gr.Markdown( - "Our SDK solves the human sensing challenges by outputting ready-to-use data." - " Our models have a low computation footprint and are ideal for 0.5-2 TOPS devices" - " like FPGAs, SOCs and small NPUs.\n\n" - # TODO: Insert performance summary table - "To access the EVE SDK, fill the form on this [page](https://huggingface.co/LatticeSemi/sensAI-Edge-Vision-Engine-SDK-Packages) and follow the instructions for download." - " For any questions or support, please reach out to us at evehelp@latticesemi.com.\n\n" - "You can also preview the EVE SDK with the following tabs:\n\n" - "- Live Inference to run it live from your webcam\n" - "- Offline Inference to test it with videos you can upload\n" - "- Use the Face ID Registration tab to register face(s) you can use in either Live or Offline Inference to test the Face ID model.\n\n" - "> Note: For demo purposes, execution of the AI pipeline and image draw operations are all performed on a Hugging Face CPU server. Performance may vary based on the number of concurrent users.\n" - "\n\n" - ) - - session_registry = gr.State(value={}) - session_hash_state = gr.State(value="unknown") - - with gr.Tabs() as tabs: - ( - _live_tab, - webrtc_stream, - (live_cb_face, live_cb_person, live_cb_face_id, live_cb_hand_gesture), - ) = build_live_inference_tab( - rtc_configuration=lambda: rtc_config_provider.get(), - feature_checkbox_builder=_build_feature_checkboxes, - extras_builder=lambda: face_id_tab.build_summary(height=80, scale=1), - max_fps=int(max_fps) if max_fps else 30, - width=camera_width, - height=camera_height, - ) - - ( - video_tab, - input_video, - output_video, - (cb_face, cb_person, cb_face_id, cb_hand_gesture), - process_btn, - example_dataset, - ) = build_offline_inference_tab( - feature_checkbox_builder=_build_feature_checkboxes, - example_videos=EXAMPLE_VIDEOS, - video_limits=VIDEO_LIMITS, - extras_builder=lambda: face_id_tab.build_summary(height=80, scale=1), - ) - patch_video_for_external_urls(output_video) - - face_id_tab.build() - - build_eula_tab() - - if os.environ.get("ENABLE_PROFILER", "").strip() not in ("", "0", "false"): - from profiler_tab import build_profiler_tab - - build_profiler_tab(pool) - - # --- Offline Inference wiring --- - wire_video_upload(input_video, output_video, process_btn, example_dataset, VIDEO_LIMITS) - - process_btn.click( - fn=handlers.run_eve_inference, - inputs=[ - input_video, - cb_face, - cb_person, - cb_face_id, - cb_hand_gesture, - session_registry, - ], - outputs=[output_video, session_registry], - concurrency_limit=pool.worker_count, - ).then( - fn=face_id_tab.refresh_all, - inputs=[session_registry], - outputs=face_id_tab.all_slot_components, - ) - - # --- Face ID wiring (self-contained in FaceIdTab) --- - face_id_tab.wire(session_registry) - - # Refresh summary thumbnails when switching to tabs that show them - for tab in (video_tab, _live_tab): - tab.select( - fn=face_id_tab.refresh_summary, - inputs=[session_registry], - outputs=face_id_tab.summary_components, - ) - - # --- Usage analytics --- - tabs.select(fn=session_tracker.on_tab_switch, inputs=[], outputs=[]) - - live_feature_inputs = [live_cb_face, live_cb_person, live_cb_face_id, live_cb_hand_gesture] - for cb in live_feature_inputs: - cb.change(fn=handlers.on_live_feature_change, inputs=live_feature_inputs, outputs=[]) - - # --- Live Inference wiring --- - # FastRTC defaults concurrency_limit to 1 which causes Gradio's queue - # to reject new WebRTC connections with "Too many concurrent connections". - # Since process_live_frame is non-blocking (shows an overlay while - # waiting for a worker), we allow MORE streams than workers so that - # queued users see a "waiting" overlay instead of a connection error. - webrtc_stream.stream( - fn=handlers.process_live_frame, - inputs=[ - webrtc_stream, - live_cb_face, - live_cb_person, - live_cb_face_id, - live_cb_hand_gesture, - session_registry, - session_hash_state, - ], - outputs=[webrtc_stream], - concurrency_limit=pool.worker_count + 8, - ) - - # --- Session tracking --- - demo.load(session_tracker.on_load, outputs=[session_hash_state]) - demo.unload(handlers.cleanup_session) - - if tracker.enabled: - gr.HTML( - "

" - "This demo collects anonymous usage data (session activity, feature usage) " - "to improve the experience. No personal information is stored.

" - ) - - demo.queue() - demo.launch(server_name="0.0.0.0", server_port=7860, share=False) +import atexit +import os +import sys +from pathlib import Path + +import gradio as gr + +APP_DIR = Path(__file__).resolve().parent +sys.path.insert(0, str(APP_DIR.parent.parent / "shared")) +sys.path.insert(0, str(APP_DIR / "shared")) + +from env_utils import load_dotenv_if_present, require_secrets +from eula_tab import build_eula_tab +from eve_app_tabs import build_live_inference_tab, build_offline_inference_tab +from eve_inference_handlers import EveAppHandlers, patch_video_for_external_urls +from eve_worker_pool import EveWorkerPool +from face_id_tab import FaceIdTab +from live_inference import ( + TAB_SWITCH_AUTO_STOP_JS, + RtcConfigProvider, + patch_aioice_stun_transaction, + patch_aiortc_h264_nvenc, + patch_fastrtc_frame_queue, + patch_fastrtc_yuv420p_output, +) +from live_stream_manager import LiveStreamManager +from log_utils import log_cpu_info, setup_logger +from session_tracker import SessionTracker +from usage_analytics import UsageTracker +from video_file_server import VideoFileServer +from video_processing import VideoLimits, get_example_videos, wire_video_upload + + +def _build_feature_checkboxes( + hint: str = "", +) -> tuple[gr.Checkbox, gr.Checkbox, gr.Checkbox, gr.Checkbox]: + """Build the EVE feature checkbox group used by both tabs. + + Must be called inside a Gradio layout context. + """ + with gr.Group(): + label = "

Features" + if hint: + label += f" — {hint}" + label += "

" + gr.HTML(label) + cb_face = gr.Checkbox(label="Face Detection", value=True) + cb_person = gr.Checkbox(label="Person Detection", value=True) + cb_face_id = gr.Checkbox(label="Face Identification", value=False) + cb_hand_gesture = gr.Checkbox(label="Hand Detection", value=False) + return cb_face, cb_person, cb_face_id, cb_hand_gesture + + +if __name__ == "__main__": + + patch_fastrtc_frame_queue() + patch_fastrtc_yuv420p_output() + patch_aioice_stun_transaction() + patch_aiortc_h264_nvenc() + + load_dotenv_if_present() + logger = setup_logger(name="app") + log_cpu_info(logger) + require_secrets("MODEL_ACCESS_TOKEN") + + tracker = UsageTracker( + repo_id=os.environ.get("ANALYTICS_REPO_ID", "LatticeSemi/PRIVATE-Demo-Analytics-v1.0"), + ) + tracker.log("server", "server_start") + + max_workers = int(os.environ.get("MAX_WORKERS", os.cpu_count())) + max_ram_gb = float(os.environ.get("MAX_RAM_GB", 32)) + pool = EveWorkerPool(max_workers=max_workers, max_ram_gb=max_ram_gb, ram_headroom_gb=2.0) + + # FPS cap: each live stream runs between MAX_TARGET_FPS (idle) and + # MIN_TARGET_FPS (full load). Set either to 0 to disable the cap. + max_fps_raw = float(os.environ.get("MAX_TARGET_FPS", "24")) + min_fps_raw = float(os.environ.get("MIN_TARGET_FPS", "15")) + max_fps: float | None = max_fps_raw if max_fps_raw > 0 else None + min_fps: float | None = min_fps_raw if min_fps_raw > 0 else None + + camera_width = int(os.environ.get("CAMERA_WIDTH", 640)) + camera_height = int(os.environ.get("CAMERA_HEIGHT", 360)) + + stream_manager = LiveStreamManager( + pool, + session_lifetime_seconds=60 * 4, + max_fps=max_fps, + min_fps=min_fps, + tracker=tracker, + ) + + session_tracker = SessionTracker(pool=pool, tracker=tracker, logger=logger) + + # Separate HTTP server for video output — bypasses Chrome's per-origin + # connection limit that blocks /file= requests while SSE connections are open. + video_server: VideoFileServer | None = None + # TODO: Put that under an env var, for debug purposes, locally, for more than 2 tabs. + """if not os.environ.get("SPACE_ID"): + gradio_cache = os.path.join(tempfile.gettempdir(), "gradio") + os.makedirs(gradio_cache, exist_ok=True) + video_server = VideoFileServer(root_dir=gradio_cache, logger=logger) + video_server.start()""" + + handlers = EveAppHandlers( + pool=pool, + stream_manager=stream_manager, + sessions=session_tracker, + logger=logger, + max_fps=max_fps, + min_fps=min_fps, + video_server=video_server, + ) + + def _shutdown() -> None: + session_tracker.shutdown() + stream_manager.shutdown() + tracker.log("server", "server_stop") + tracker.shutdown() + + atexit.register(_shutdown) + + EXAMPLE_VIDEOS = get_example_videos(str(APP_DIR / "examples")) + VIDEO_LIMITS = VideoLimits() + rtc_config_provider = RtcConfigProvider() + + face_id_tab = FaceIdTab( + pool, + max_users=2, + examples_dir=str(APP_DIR / "examples_fid"), + accept_video=True, + video_limits=VideoLimits(max_file_size_mb=150, max_duration_seconds=60), + tracker=tracker, + ) + + with gr.Blocks( + title="Eve HMI Demo", + theme=gr.themes.Default( + text_size=gr.themes.sizes.text_lg, + primary_hue=gr.themes.colors.yellow, + ), + css=( + f"#webrtc-stream-col {{ max-width: {camera_width}px !important; margin: 0 auto; }}" + " .gradio-container h1, .gradio-container .md h1 { font-size: 2.25rem !important; }" + " .gradio-container h2, .gradio-container .md h2 { font-size: 1.75rem !important; }" + " .gradio-container h3, .gradio-container .md h3 { font-size: 1.4rem !important; }" + " .gradio-container button[role='tab']," + " .gradio-container button[role='tab'] *" + " { text-decoration: underline !important; }" + " .gradio-container .tab-container {" + " height: auto !important;" + " overflow: visible !important;" + " gap: 4px !important;" + " border-bottom: 2px solid var(--border-color-primary) !important; }" + " .gradio-container .tab-container::after { display: none !important; }" + " .gradio-container button[role='tab'] {" + " height: auto !important;" + " padding: 10px 20px !important;" + " border: 1px solid var(--border-color-primary) !important;" + " border-bottom: none !important;" + " border-radius: 8px 8px 0 0 !important;" + " background: var(--background-fill-secondary) !important;" + " margin-bottom: -2px !important; }" + " .gradio-container button[role='tab'].selected {" + " background: var(--primary-500) !important;" + " color: var(--neutral-950) !important;" + " border-color: var(--primary-500) !important;" + " font-weight: 600 !important; }" + " .gradio-container button[role='tab'].selected::after { display: none !important; }" + ), + head=TAB_SWITCH_AUTO_STOP_JS, + ) as demo: + gr.Markdown("# Lattice sensAI Edge Vision Engine SDK") + gr.Markdown( + "Our SDK solves the human sensing challenges by outputting ready-to-use data." + " Our models have a low computation footprint and are ideal for 0.5-2 TOPS devices" + " like FPGAs, SOCs and small NPUs.\n\n" + # TODO: Insert performance summary table + "To access the EVE SDK, fill the form on this [page](https://huggingface.co/LatticeSemi/sensAI-Edge-Vision-Engine-SDK-Packages) and follow the instructions for download." + " For any questions or support, please reach out to us at evehelp@latticesemi.com.\n\n" + "You can also preview the EVE SDK with the following tabs:\n\n" + "- Live Inference to run it live from your webcam\n" + "- Offline Inference to test it with videos you can upload\n" + "- Use the Face ID Registration tab to register face(s) you can use in either Live or Offline Inference to test the Face ID model.\n\n" + "> Note: For demo purposes, execution of the AI pipeline and image draw operations are all performed on a Hugging Face CPU server. Performance may vary based on the number of concurrent users.\n" + "\n\n" + ) + + session_registry = gr.State(value={}) + session_hash_state = gr.State(value="unknown") + + with gr.Tabs() as tabs: + ( + _live_tab, + webrtc_stream, + (live_cb_face, live_cb_person, live_cb_face_id, live_cb_hand_gesture), + ) = build_live_inference_tab( + rtc_configuration=lambda: rtc_config_provider.get(), + feature_checkbox_builder=_build_feature_checkboxes, + extras_builder=lambda: face_id_tab.build_summary(height=80, scale=1), + max_fps=int(max_fps) if max_fps else 30, + width=camera_width, + height=camera_height, + ) + + ( + video_tab, + input_video, + output_video, + (cb_face, cb_person, cb_face_id, cb_hand_gesture), + process_btn, + example_dataset, + ) = build_offline_inference_tab( + feature_checkbox_builder=_build_feature_checkboxes, + example_videos=EXAMPLE_VIDEOS, + video_limits=VIDEO_LIMITS, + extras_builder=lambda: face_id_tab.build_summary(height=80, scale=1), + ) + patch_video_for_external_urls(output_video) + + face_id_tab.build() + + build_eula_tab() + + if os.environ.get("ENABLE_PROFILER", "").strip() not in ("", "0", "false"): + from profiler_tab import build_profiler_tab + + build_profiler_tab(pool) + + # --- Offline Inference wiring --- + wire_video_upload(input_video, output_video, process_btn, example_dataset, VIDEO_LIMITS) + + process_btn.click( + fn=handlers.run_eve_inference, + inputs=[ + input_video, + cb_face, + cb_person, + cb_face_id, + cb_hand_gesture, + session_registry, + ], + outputs=[output_video, session_registry], + concurrency_limit=pool.worker_count, + ).then( + fn=face_id_tab.refresh_all, + inputs=[session_registry], + outputs=face_id_tab.all_slot_components, + ) + + # --- Face ID wiring (self-contained in FaceIdTab) --- + face_id_tab.wire(session_registry) + + # Refresh summary thumbnails when switching to tabs that show them + for tab in (video_tab, _live_tab): + tab.select( + fn=face_id_tab.refresh_summary, + inputs=[session_registry], + outputs=face_id_tab.summary_components, + ) + + # --- Usage analytics --- + tabs.select(fn=session_tracker.on_tab_switch, inputs=[], outputs=[]) + + live_feature_inputs = [live_cb_face, live_cb_person, live_cb_face_id, live_cb_hand_gesture] + for cb in live_feature_inputs: + cb.change(fn=handlers.on_live_feature_change, inputs=live_feature_inputs, outputs=[]) + + # --- Live Inference wiring --- + # FastRTC defaults concurrency_limit to 1 which causes Gradio's queue + # to reject new WebRTC connections with "Too many concurrent connections". + # Since process_live_frame is non-blocking (shows an overlay while + # waiting for a worker), we allow MORE streams than workers so that + # queued users see a "waiting" overlay instead of a connection error. + webrtc_stream.stream( + fn=handlers.process_live_frame, + inputs=[ + webrtc_stream, + live_cb_face, + live_cb_person, + live_cb_face_id, + live_cb_hand_gesture, + session_registry, + session_hash_state, + ], + outputs=[webrtc_stream], + concurrency_limit=pool.worker_count + 8, + ) + + # --- Session tracking --- + demo.load(session_tracker.on_load, outputs=[session_hash_state]) + demo.unload(handlers.cleanup_session) + + if tracker.enabled: + gr.HTML( + "

" + "This demo collects anonymous usage data (session activity, feature usage) " + "to improve the experience. No personal information is stored.

" + ) + + demo.queue() + demo.launch(server_name="0.0.0.0", server_port=7860, share=False) diff --git a/download_examples.py b/download_examples.py index bb44d8020d9483add30650f3472eb94a9bd30110..9535b9b8706f2d0686993e95e9359df001e45152 100644 --- a/download_examples.py +++ b/download_examples.py @@ -1,124 +1,124 @@ -"""Download example media from the LatticeSemi/Demo-Examples-v1.0 dataset. - -Runs at Docker build time (after ``COPY --chown=user . /app``) to populate the -demo's ``examples*/`` subfolders from the shared HF dataset. Mirrors the -dataset layout: every file under ``/`` in the dataset is copied to the -same relative path locally (``/examples/foo.mp4`` -> ``./examples/foo.mp4``). - -Auth order — same as ``install_eve.py``: - -1. Docker BuildKit secret ``MODEL_ACCESS_TOKEN`` -2. ``HF_TOKEN`` env / build arg -3. No token — works only if the dataset is public. - -Local-dev fallback: if the download fails (offline, no token, dataset private), -but a sibling ``examples*/`` folder already contains files, succeed with a -warning so manual placement / pre-existing files keep working. If neither -the download nor any local file is present, crash loud with the dataset URL -and the token-fix hint. -""" - -import os -import shutil -import sys -from pathlib import Path - -from huggingface_hub import HfApi, hf_hub_download - -DEMO = "eve_hmi" -DATASET = "LatticeSemi/Demo-Examples-v1.0" -SECRET_PATH = "/run/secrets/MODEL_ACCESS_TOKEN" - - -def get_token() -> tuple[str | None, str]: - """Return an HF token from the first available source, or None. - - Falls through past the secret file on PermissionError — BuildKit mounts - the secret as root by default; the Dockerfile must pass ``uid=1000`` for - a non-root user to read it. Falling through lets ``HF_TOKEN`` still work - if the mount permissions are misconfigured. - """ - if os.path.isfile(SECRET_PATH): - try: - with open(SECRET_PATH) as f: - token = f.read().strip() - if token: - return token, "build secret (MODEL_ACCESS_TOKEN)" - except PermissionError as exc: - print( - f"[download_examples] WARNING: cannot read {SECRET_PATH}: {exc}. " - f"Add ``uid=1000`` to the secret mount in the Dockerfile.", - file=sys.stderr, - ) - token = os.environ.get("HF_TOKEN", "").strip() - if token: - return token, "build arg (HF_TOKEN)" - return None, "no auth" - - -def has_local_examples() -> bool: - """True iff any ``examples*`` sibling dir contains at least one file.""" - cwd = Path(".") - if not cwd.is_dir(): - return False - for entry in cwd.iterdir(): - if entry.is_dir() and entry.name.startswith("examples"): - if any(p.is_file() for p in entry.rglob("*")): - return True - return False - - -def main() -> int: - token, source = get_token() - print(f"[download_examples] demo={DEMO} dataset={DATASET} auth={source}") - - download_error: str | None = None - downloaded = 0 - try: - api = HfApi() - files = api.list_repo_files(DATASET, repo_type="dataset", token=token) - prefix = f"{DEMO}/" - wanted = [f for f in files if f.startswith(prefix)] - if not wanted: - download_error = f"dataset has no files under '{prefix}'" - for remote in wanted: - cached = hf_hub_download( - repo_id=DATASET, - repo_type="dataset", - filename=remote, - token=token, - ) - local = Path(remote[len(prefix) :]) - local.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(cached, local) - downloaded += 1 - except Exception as exc: # noqa: BLE001 — broad catch by design at build time - download_error = f"{type(exc).__name__}: {exc}" - - if downloaded > 0: - print(f"[download_examples] OK: downloaded {downloaded} files") - return 0 - - if has_local_examples(): - print( - f"[download_examples] WARNING: download failed — {download_error}.\n" - f" Falling back to existing local example files.", - file=sys.stderr, - ) - return 0 - - print( - f"FATAL: no example files for '{DEMO}'.\n" - f" Download from {DATASET} failed: {download_error or 'no files retrieved'}\n" - f" Fix one of:\n" - f" - HF Space: set MODEL_ACCESS_TOKEN secret with read access to the dataset.\n" - f" - Local dev: run `hf auth login`, or manually drop files into the demo's\n" - f" examples*/ subfolders from\n" - f" https://huggingface.co/datasets/{DATASET}/tree/main/{DEMO}", - file=sys.stderr, - ) - return 1 - - -if __name__ == "__main__": - sys.exit(main()) +"""Download example media from the LatticeSemi/Demo-Examples-v1.0 dataset. + +Runs at Docker build time (after ``COPY --chown=user . /app``) to populate the +demo's ``examples*/`` subfolders from the shared HF dataset. Mirrors the +dataset layout: every file under ``/`` in the dataset is copied to the +same relative path locally (``/examples/foo.mp4`` -> ``./examples/foo.mp4``). + +Auth order — same as ``install_eve.py``: + +1. Docker BuildKit secret ``MODEL_ACCESS_TOKEN`` +2. ``HF_TOKEN`` env / build arg +3. No token — works only if the dataset is public. + +Local-dev fallback: if the download fails (offline, no token, dataset private), +but a sibling ``examples*/`` folder already contains files, succeed with a +warning so manual placement / pre-existing files keep working. If neither +the download nor any local file is present, crash loud with the dataset URL +and the token-fix hint. +""" + +import os +import shutil +import sys +from pathlib import Path + +from huggingface_hub import HfApi, hf_hub_download + +DEMO = "eve_hmi" +DATASET = "LatticeSemi/Demo-Examples-v1.0" +SECRET_PATH = "/run/secrets/MODEL_ACCESS_TOKEN" + + +def get_token() -> tuple[str | None, str]: + """Return an HF token from the first available source, or None. + + Falls through past the secret file on PermissionError — BuildKit mounts + the secret as root by default; the Dockerfile must pass ``uid=1000`` for + a non-root user to read it. Falling through lets ``HF_TOKEN`` still work + if the mount permissions are misconfigured. + """ + if os.path.isfile(SECRET_PATH): + try: + with open(SECRET_PATH) as f: + token = f.read().strip() + if token: + return token, "build secret (MODEL_ACCESS_TOKEN)" + except PermissionError as exc: + print( + f"[download_examples] WARNING: cannot read {SECRET_PATH}: {exc}. " + f"Add ``uid=1000`` to the secret mount in the Dockerfile.", + file=sys.stderr, + ) + token = os.environ.get("HF_TOKEN", "").strip() + if token: + return token, "build arg (HF_TOKEN)" + return None, "no auth" + + +def has_local_examples() -> bool: + """True iff any ``examples*`` sibling dir contains at least one file.""" + cwd = Path(".") + if not cwd.is_dir(): + return False + for entry in cwd.iterdir(): + if entry.is_dir() and entry.name.startswith("examples"): + if any(p.is_file() for p in entry.rglob("*")): + return True + return False + + +def main() -> int: + token, source = get_token() + print(f"[download_examples] demo={DEMO} dataset={DATASET} auth={source}") + + download_error: str | None = None + downloaded = 0 + try: + api = HfApi() + files = api.list_repo_files(DATASET, repo_type="dataset", token=token) + prefix = f"{DEMO}/" + wanted = [f for f in files if f.startswith(prefix)] + if not wanted: + download_error = f"dataset has no files under '{prefix}'" + for remote in wanted: + cached = hf_hub_download( + repo_id=DATASET, + repo_type="dataset", + filename=remote, + token=token, + ) + local = Path(remote[len(prefix) :]) + local.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(cached, local) + downloaded += 1 + except Exception as exc: # noqa: BLE001 — broad catch by design at build time + download_error = f"{type(exc).__name__}: {exc}" + + if downloaded > 0: + print(f"[download_examples] OK: downloaded {downloaded} files") + return 0 + + if has_local_examples(): + print( + f"[download_examples] WARNING: download failed — {download_error}.\n" + f" Falling back to existing local example files.", + file=sys.stderr, + ) + return 0 + + print( + f"FATAL: no example files for '{DEMO}'.\n" + f" Download from {DATASET} failed: {download_error or 'no files retrieved'}\n" + f" Fix one of:\n" + f" - HF Space: set MODEL_ACCESS_TOKEN secret with read access to the dataset.\n" + f" - Local dev: run `hf auth login`, or manually drop files into the demo's\n" + f" examples*/ subfolders from\n" + f" https://huggingface.co/datasets/{DATASET}/tree/main/{DEMO}", + file=sys.stderr, + ) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/install_eve.py b/install_eve.py index 2e26154dc46f5a10c84027ed5d4a6ad2808cae09..dfbe476b3df8553eac1d622b9d9a1982b0c17899 100644 --- a/install_eve.py +++ b/install_eve.py @@ -1,104 +1,104 @@ -"""Download and install the Eve SDK .deb package from HuggingFace Hub. - -Called during Docker build. Tries authentication in order: - -1. Docker BuildKit secret ``MODEL_ACCESS_TOKEN`` (HF Spaces — automatic) -2. ``HF_TOKEN`` build arg (local builds) -3. No token (public repos only) - -Local usage:: - - docker build --build-arg HF_TOKEN=$(cat ~/.cache/huggingface/token) \ - -t eve ./src/demos/eve_hmi -""" - -import os -import re -import shutil -import subprocess -import sys - -from huggingface_hub import hf_hub_download - -EVE_REPO = "LatticeSemi/PRIVATE-Edge-Vision-Engine-EVE-v7.1" -EVE_DEB = "LINUX_X86-15-7.1-eve-sensai_7.1.15~git20260424.20d1a1a_amd64.deb" -EVE_LICENSE_REPO = "LatticeSemi/PRIVATE-Edge-Vision-Engine-EVE-v7.0-License" -EVE_LICENSE = "libEveDevLicense.so" -SECRET_PATH = "/run/secrets/MODEL_ACCESS_TOKEN" -DOWNLOAD_DIR = "/tmp/eve" - - -def get_token(): - """Return an HF token from the first available source, or None.""" - # 1. Docker BuildKit secret (HF Spaces injects MODEL_ACCESS_TOKEN automatically) - if os.path.isfile(SECRET_PATH): - with open(SECRET_PATH) as f: - token = f.read().strip() - if token: - return token, "build secret (MODEL_ACCESS_TOKEN)" - - # 2. HF_TOKEN build arg forwarded as env var - token = os.environ.get("HF_TOKEN", "").strip() - if token: - return token, "build arg (HF_TOKEN)" - - # 3. No token — will only work for public repos - return None, "no auth (will fail for private repos)" - - -def get_license_destination_path() -> str: - """Parse the .deb package of EVE to extract the version.""" - # Alright so regexes are fun, what we want here is to extract the version - # of EVE's package since we need to copy the license into EVE's install - # folder which is /opt/EVE-version-Source/lib. - # For example, in LINUX_X86-531-dev-eve-development_7.0.531~git20260309.c5f1ee6_amd64.deb, - # we want the version extracted to be 7.0.531. - match = re.search(r"(?<=_)\d+(?:\.\d+)+(?=~)", EVE_DEB) - if not match: - raise RuntimeError("Could not parse EVE's package version.") - - version = match.group() - - return f"/opt/EVE-{version}-Source/lib" - - -def main(): - token, auth_source = get_token() - - if token is None and "PRIVATE" in EVE_REPO: - print( - f"ERROR: No authentication token found.\n" - f" Repo '{EVE_REPO}' is private and requires a token.\n" - f" On HF Spaces: set MODEL_ACCESS_TOKEN as a Space secret.\n" - f" Locally: docker build --build-arg HF_TOKEN=$(cat ~/.cache/huggingface/token) ...", - file=sys.stderr, - ) - sys.exit(1) - - print(f"Downloading Eve SDK from {EVE_REPO} using {auth_source}...") - - deb_path = hf_hub_download( - repo_id=EVE_REPO, - filename=EVE_DEB, - local_dir=DOWNLOAD_DIR, - token=token, - ) - - print(f"Installing {deb_path}...") - subprocess.run(["apt-get", "install", "-y", deb_path], check=True) - - license_path = hf_hub_download( - repo_id=EVE_LICENSE_REPO, filename=EVE_LICENSE, local_dir=DOWNLOAD_DIR, token=token - ) - - destination_path = get_license_destination_path() - - print(f"Installing {license_path}...") - subprocess.run(["mv", license_path, destination_path], check=True) - - shutil.rmtree(DOWNLOAD_DIR, ignore_errors=True) - print("Eve SDK installed successfully.") - - -if __name__ == "__main__": - main() +"""Download and install the Eve SDK .deb package from HuggingFace Hub. + +Called during Docker build. Tries authentication in order: + +1. Docker BuildKit secret ``MODEL_ACCESS_TOKEN`` (HF Spaces — automatic) +2. ``HF_TOKEN`` build arg (local builds) +3. No token (public repos only) + +Local usage:: + + docker build --build-arg HF_TOKEN=$(cat ~/.cache/huggingface/token) \ + -t eve ./src/demos/eve_hmi +""" + +import os +import re +import shutil +import subprocess +import sys + +from huggingface_hub import hf_hub_download + +EVE_REPO = "LatticeSemi/PRIVATE-Edge-Vision-Engine-EVE-v7.2" +EVE_DEB = "LINUX_X86-10-7.2-eve-sensai_7.2.10~git20260515.8139998_amd64.deb" +EVE_LICENSE_REPO = "LatticeSemi/PRIVATE-Edge-Vision-Engine-EVE-v7.0-License" +EVE_LICENSE = "libEveDevLicense.so" +SECRET_PATH = "/run/secrets/MODEL_ACCESS_TOKEN" +DOWNLOAD_DIR = "/tmp/eve" + + +def get_token(): + """Return an HF token from the first available source, or None.""" + # 1. Docker BuildKit secret (HF Spaces injects MODEL_ACCESS_TOKEN automatically) + if os.path.isfile(SECRET_PATH): + with open(SECRET_PATH) as f: + token = f.read().strip() + if token: + return token, "build secret (MODEL_ACCESS_TOKEN)" + + # 2. HF_TOKEN build arg forwarded as env var + token = os.environ.get("HF_TOKEN", "").strip() + if token: + return token, "build arg (HF_TOKEN)" + + # 3. No token — will only work for public repos + return None, "no auth (will fail for private repos)" + + +def get_license_destination_path() -> str: + """Parse the .deb package of EVE to extract the version.""" + # Alright so regexes are fun, what we want here is to extract the version + # of EVE's package since we need to copy the license into EVE's install + # folder which is /opt/EVE-version-Source/lib. + # For example, in LINUX_X86-531-dev-eve-development_7.0.531~git20260309.c5f1ee6_amd64.deb, + # we want the version extracted to be 7.0.531. + match = re.search(r"(?<=_)\d+(?:\.\d+)+(?=~)", EVE_DEB) + if not match: + raise RuntimeError("Could not parse EVE's package version.") + + version = match.group() + + return f"/opt/EVE-{version}-Source/lib" + + +def main(): + token, auth_source = get_token() + + if token is None and "PRIVATE" in EVE_REPO: + print( + f"ERROR: No authentication token found.\n" + f" Repo '{EVE_REPO}' is private and requires a token.\n" + f" On HF Spaces: set MODEL_ACCESS_TOKEN as a Space secret.\n" + f" Locally: docker build --build-arg HF_TOKEN=$(cat ~/.cache/huggingface/token) ...", + file=sys.stderr, + ) + sys.exit(1) + + print(f"Downloading Eve SDK from {EVE_REPO} using {auth_source}...") + + deb_path = hf_hub_download( + repo_id=EVE_REPO, + filename=EVE_DEB, + local_dir=DOWNLOAD_DIR, + token=token, + ) + + print(f"Installing {deb_path}...") + subprocess.run(["apt-get", "install", "-y", deb_path], check=True) + + license_path = hf_hub_download( + repo_id=EVE_LICENSE_REPO, filename=EVE_LICENSE, local_dir=DOWNLOAD_DIR, token=token + ) + + destination_path = get_license_destination_path() + + print(f"Installing {license_path}...") + subprocess.run(["mv", license_path, destination_path], check=True) + + shutil.rmtree(DOWNLOAD_DIR, ignore_errors=True) + print("Eve SDK installed successfully.") + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt index 4758e135441b08c57a850132c9f1eebf1d88ee03..9b94437d7b394377341a49a9ce14760e0ac25883 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ -av -gradio==5.50.0 -fastrtc==0.0.34 -huggingface-hub==1.5.0 -opencv-python -dotenv -twilio -psutil +av +gradio==5.50.0 +fastrtc==0.0.34 +huggingface-hub==1.5.0 +opencv-python +dotenv +twilio +psutil diff --git a/shared/assets/EULA.md b/shared/assets/EULA.md index 24a9d1d28a6c10beb3d311eb2cac886965a898f9..724229e5c4998834c5672607265af7685b0fc66d 100644 --- a/shared/assets/EULA.md +++ b/shared/assets/EULA.md @@ -1,21 +1,21 @@ -# DEMO EVALUATION END USER LICENSE AGREEMENT - -IMPORTANT: BY DOWNLOADING, INSTALLING, ACTIVATING, ACCESSING, OR USING THE SOFTWARE, YOU AGREE TO THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT USE THE SOFTWARE. - -This Demo Evaluation End User License Agreement ("Agreement") is between Lattice Semiconductor Corporation ("Lattice") and the person or entity using the Software ("Licensee"). The individual accepting this Agreement represents and warrants that they have authority to bind Licensee. - -**1. Software.** "Software" means the demo version of the software, in object code form only, together with related documentation, materials, updates, license keys, and any output or data generated by the Software, provided by Lattice. - -**2. License Grant.** Subject to this Agreement, Lattice grants Licensee a limited, non-exclusive, non-transferable, non-sublicensable, revocable license to use the Software solely for Licensee's internal, non-commercial evaluation and demonstration purposes, only to assess the Software and determine whether to request a longer-term testing license from Lattice. The Software may be used only by Licensee's employees at a single site. No other rights are granted by implication, estoppel, or otherwise. - -**3. License Key; Revocation.** Use of the Software is controlled by a license key or similar activation mechanism. Lattice may revoke, suspend, disable, or refuse to renew any license key at any time, with or without cause, and without notice or liability. Upon expiration, revocation, suspension, or disablement of the license key, Licensee's right to use the Software immediately terminates and Licensee must comply with Section 8(b). - -**4. Restrictions.** Licensee may not, and may not permit or enable any third party to: (a) use the Software for production, commercial, or revenue-generating purposes; (b) sell, license, sublicense, rent, lease, lend, distribute, transfer, or disclose the Software or any portion thereof to any third party; (c) copy, modify, adapt, translate, or create derivative works of the Software; (d) reverse engineer, decompile, disassemble, or otherwise attempt to discover the source code, algorithms, data structures, or underlying ideas of the Software, except to the limited extent such restriction is expressly prohibited by applicable law; (e) remove, alter, or obscure any proprietary, copyright, trademark, or other notices; (f) use the Software or any information derived from it to develop, improve, train, or benchmark any competing product, service, or technology; (g) publish or disclose any benchmark, test, performance, or evaluation results related to the Software without Lattice's prior written consent; (h) use the Software in violation of any applicable law or regulation; or (i) circumvent or attempt to circumvent any technical protection measures in the Software. - -**5. Ownership; Confidentiality; Feedback.** The Software is licensed, not sold. Lattice and its licensors retain all right, title, and interest in and to the Software, all derivatives and improvements thereof, and all related intellectual property rights worldwide. No license or right is granted to any Lattice patent, trade secret, or trademark. The Software, its features, performance characteristics, and all information relating thereto constitute confidential and proprietary trade secrets of Lattice. Licensee will protect the Software using at least the same degree of care it uses for its own confidential information, but no less than reasonable care, and will not disclose or provide access to the Software except to employees or contractors with a need to know who are bound by written confidentiality obligations no less protective than this Agreement. Any suggestions, ideas, feedback, evaluation results, or other input provided by Licensee regarding the Software ("Feedback") are assigned to Lattice and may be used by Lattice for any purpose without restriction, compensation, or obligation. To the extent such assignment is not enforceable, Licensee grants Lattice a perpetual, irrevocable, worldwide, royalty-free, fully sublicensable license to use and exploit the Feedback. - -**6. Disclaimer.** THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE," WITHOUT WARRANTY OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY LAW, LATTICE DISCLAIMS ALL EXPRESS, IMPLIED, STATUTORY, AND OTHER WARRANTIES, INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND ACCURACY. LATTICE DOES NOT WARRANT THAT THE SOFTWARE WILL BE ERROR-FREE, UNINTERRUPTED, SECURE, OR FREE OF HARMFUL COMPONENTS. - -**7. Limitation of Liability.** TO THE MAXIMUM EXTENT PERMITTED BY LAW, LATTICE AND ITS LICENSORS, DIRECTORS, OFFICERS, AND EMPLOYEES WILL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR ANY LOSS OF PROFITS, REVENUE, DATA, BUSINESS, GOODWILL, OR USE, ARISING OUT OF OR RELATED TO THIS AGREEMENT OR THE SOFTWARE, REGARDLESS OF THE THEORY OF LIABILITY AND EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. LATTICE'S TOTAL AGGREGATE LIABILITY ARISING OUT OF OR RELATED TO THIS AGREEMENT OR THE SOFTWARE WILL NOT EXCEED US$100. THESE LIMITATIONS APPLY NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF ANY LIMITED REMEDY. - -**8. Termination.** (a) This Agreement begins upon first use of the Software and terminates automatically upon breach by Licensee, expiration or revocation of the license key, or upon thirty (30) days' notice by Lattice. Lattice may terminate immediately if Licensee breaches Sections 4 or 5. (b) Upon termination, Licensee must immediately cease all use of the Software, permanently delete or destroy all copies (including backups), and certify such destruction in writing to Lattice within five (5) business days. +# DEMO EVALUATION END USER LICENSE AGREEMENT + +IMPORTANT: BY DOWNLOADING, INSTALLING, ACTIVATING, ACCESSING, OR USING THE SOFTWARE, YOU AGREE TO THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT USE THE SOFTWARE. + +This Demo Evaluation End User License Agreement ("Agreement") is between Lattice Semiconductor Corporation ("Lattice") and the person or entity using the Software ("Licensee"). The individual accepting this Agreement represents and warrants that they have authority to bind Licensee. + +**1. Software.** "Software" means the demo version of the software, in object code form only, together with related documentation, materials, updates, license keys, and any output or data generated by the Software, provided by Lattice. + +**2. License Grant.** Subject to this Agreement, Lattice grants Licensee a limited, non-exclusive, non-transferable, non-sublicensable, revocable license to use the Software solely for Licensee's internal, non-commercial evaluation and demonstration purposes, only to assess the Software and determine whether to request a longer-term testing license from Lattice. The Software may be used only by Licensee's employees at a single site. No other rights are granted by implication, estoppel, or otherwise. + +**3. License Key; Revocation.** Use of the Software is controlled by a license key or similar activation mechanism. Lattice may revoke, suspend, disable, or refuse to renew any license key at any time, with or without cause, and without notice or liability. Upon expiration, revocation, suspension, or disablement of the license key, Licensee's right to use the Software immediately terminates and Licensee must comply with Section 8(b). + +**4. Restrictions.** Licensee may not, and may not permit or enable any third party to: (a) use the Software for production, commercial, or revenue-generating purposes; (b) sell, license, sublicense, rent, lease, lend, distribute, transfer, or disclose the Software or any portion thereof to any third party; (c) copy, modify, adapt, translate, or create derivative works of the Software; (d) reverse engineer, decompile, disassemble, or otherwise attempt to discover the source code, algorithms, data structures, or underlying ideas of the Software, except to the limited extent such restriction is expressly prohibited by applicable law; (e) remove, alter, or obscure any proprietary, copyright, trademark, or other notices; (f) use the Software or any information derived from it to develop, improve, train, or benchmark any competing product, service, or technology; (g) publish or disclose any benchmark, test, performance, or evaluation results related to the Software without Lattice's prior written consent; (h) use the Software in violation of any applicable law or regulation; or (i) circumvent or attempt to circumvent any technical protection measures in the Software. + +**5. Ownership; Confidentiality; Feedback.** The Software is licensed, not sold. Lattice and its licensors retain all right, title, and interest in and to the Software, all derivatives and improvements thereof, and all related intellectual property rights worldwide. No license or right is granted to any Lattice patent, trade secret, or trademark. The Software, its features, performance characteristics, and all information relating thereto constitute confidential and proprietary trade secrets of Lattice. Licensee will protect the Software using at least the same degree of care it uses for its own confidential information, but no less than reasonable care, and will not disclose or provide access to the Software except to employees or contractors with a need to know who are bound by written confidentiality obligations no less protective than this Agreement. Any suggestions, ideas, feedback, evaluation results, or other input provided by Licensee regarding the Software ("Feedback") are assigned to Lattice and may be used by Lattice for any purpose without restriction, compensation, or obligation. To the extent such assignment is not enforceable, Licensee grants Lattice a perpetual, irrevocable, worldwide, royalty-free, fully sublicensable license to use and exploit the Feedback. + +**6. Disclaimer.** THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE," WITHOUT WARRANTY OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY LAW, LATTICE DISCLAIMS ALL EXPRESS, IMPLIED, STATUTORY, AND OTHER WARRANTIES, INCLUDING ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, NON-INFRINGEMENT, AND ACCURACY. LATTICE DOES NOT WARRANT THAT THE SOFTWARE WILL BE ERROR-FREE, UNINTERRUPTED, SECURE, OR FREE OF HARMFUL COMPONENTS. + +**7. Limitation of Liability.** TO THE MAXIMUM EXTENT PERMITTED BY LAW, LATTICE AND ITS LICENSORS, DIRECTORS, OFFICERS, AND EMPLOYEES WILL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR ANY LOSS OF PROFITS, REVENUE, DATA, BUSINESS, GOODWILL, OR USE, ARISING OUT OF OR RELATED TO THIS AGREEMENT OR THE SOFTWARE, REGARDLESS OF THE THEORY OF LIABILITY AND EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. LATTICE'S TOTAL AGGREGATE LIABILITY ARISING OUT OF OR RELATED TO THIS AGREEMENT OR THE SOFTWARE WILL NOT EXCEED US$100. THESE LIMITATIONS APPLY NOTWITHSTANDING ANY FAILURE OF ESSENTIAL PURPOSE OF ANY LIMITED REMEDY. + +**8. Termination.** (a) This Agreement begins upon first use of the Software and terminates automatically upon breach by Licensee, expiration or revocation of the license key, or upon thirty (30) days' notice by Lattice. Lattice may terminate immediately if Licensee breaches Sections 4 or 5. (b) Upon termination, Licensee must immediately cease all use of the Software, permanently delete or destroy all copies (including backups), and certify such destruction in writing to Lattice within five (5) business days. diff --git a/shared/eula_tab.py b/shared/eula_tab.py index 1fb7c7044794ad04dff2a60dc69347b07061b6e8..62f2e93702cd34c35daf83ba1e59bf79381f2184 100644 --- a/shared/eula_tab.py +++ b/shared/eula_tab.py @@ -1,47 +1,47 @@ -"""EULA tab for Gradio demos. - -Displays the End User License Agreement as a read-only Markdown tab. -The EULA text is loaded from ``shared/assets/EULA.md`` so it can be -updated without changing any Python code. - -Usage:: - - with gr.Blocks() as demo: - with gr.Tabs(): - build_eula_tab() -""" - -from pathlib import Path - -import gradio as gr - -_ASSETS_DIR = Path(__file__).resolve().parent / "assets" -_DEFAULT_EULA_PATH = _ASSETS_DIR / "EULA.md" - - -def build_eula_tab( - eula_path: str | Path | None = None, - tab_label: str = "EULA", -) -> gr.TabItem: - """Create a tab displaying the EULA as Markdown. - - Must be called inside a ``gr.Tabs()`` context. - - Args: - eula_path: Path to a Markdown file. Defaults to ``shared/assets/EULA.md``. - tab_label: Label shown on the tab. Defaults to ``"EULA"``. - - Returns: - The ``gr.TabItem`` component. - """ - path = Path(eula_path) if eula_path is not None else _DEFAULT_EULA_PATH - - if path.is_file(): - content = path.read_text(encoding="utf-8") - else: - content = f"_EULA file not found. Expected location:_ `{path}`" - - with gr.TabItem(tab_label) as tab: - gr.Markdown(content) - - return tab +"""EULA tab for Gradio demos. + +Displays the End User License Agreement as a read-only Markdown tab. +The EULA text is loaded from ``shared/assets/EULA.md`` so it can be +updated without changing any Python code. + +Usage:: + + with gr.Blocks() as demo: + with gr.Tabs(): + build_eula_tab() +""" + +from pathlib import Path + +import gradio as gr + +_ASSETS_DIR = Path(__file__).resolve().parent / "assets" +_DEFAULT_EULA_PATH = _ASSETS_DIR / "EULA.md" + + +def build_eula_tab( + eula_path: str | Path | None = None, + tab_label: str = "EULA", +) -> gr.TabItem: + """Create a tab displaying the EULA as Markdown. + + Must be called inside a ``gr.Tabs()`` context. + + Args: + eula_path: Path to a Markdown file. Defaults to ``shared/assets/EULA.md``. + tab_label: Label shown on the tab. Defaults to ``"EULA"``. + + Returns: + The ``gr.TabItem`` component. + """ + path = Path(eula_path) if eula_path is not None else _DEFAULT_EULA_PATH + + if path.is_file(): + content = path.read_text(encoding="utf-8") + else: + content = f"_EULA file not found. Expected location:_ `{path}`" + + with gr.TabItem(tab_label) as tab: + gr.Markdown(content) + + return tab diff --git a/shared/eve_app_tabs.py b/shared/eve_app_tabs.py index b30dfd16db537ee697e549fb9734e59e736736d4..c93f36b277ced85ceecfb35f91012fc6abb4f69d 100644 --- a/shared/eve_app_tabs.py +++ b/shared/eve_app_tabs.py @@ -1,276 +1,276 @@ -"""Tab builders for EVE-based Gradio demos. - -Both tabs (Offline Inference, Live Inference) follow the same pattern: - -- Outer ``gr.TabItem`` with the demo-supplied label. -- Demo-specific feature checkboxes built via a caller-provided callable so - each demo defines its own feature set (Face Detection / Person / - Hand Detection / Face ID / …). -- Optional ``extras_builder`` hook for demo-specific extras inside the tab - (e.g. eve_hmi's Face ID summary thumbnails). - -The tab builders return the underlying components so the caller can wire -events (``process_btn.click``, ``webrtc_stream.stream``, etc.) outside. -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Callable, TypeVar - -import gradio as gr - -from live_inference import RtcConfigurationInput, build_webrtc_stream -from video_processing import VideoLimits, build_video_constraints_accordion - -# The exact tuple of Checkbox components is demo-specific. We let the -# caller's checkbox-builder define the shape and return it transparently. -TCheckboxes = TypeVar("TCheckboxes") - - -def build_offline_inference_tab( - *, - feature_checkbox_builder: Callable[..., TCheckboxes], - example_videos: list, - video_limits: VideoLimits, - extras_builder: Callable[[], None] | None = None, - tab_label: str = "Offline Inference", - feature_hint: str = "applied when processing starts", -) -> tuple[ - gr.TabItem, - gr.Video, - gr.Video, - TCheckboxes, - gr.Button, - gr.Dataset, -]: - """Build the Offline Inference tab. Must be called inside a ``gr.Tabs`` context. - - Args: - feature_checkbox_builder: Callable that builds the demo's feature - checkboxes inside the tab. Receives the keyword ``hint`` (a - short string shown next to the "Features" heading). Returns a - tuple of ``gr.Checkbox`` instances — the same tuple is returned - unchanged so the caller can wire events. - example_videos: Pre-loaded example videos (list of ``[path]`` rows). - video_limits: Upload constraints rendered in the accordion. - extras_builder: Optional callable invoked after the input/output - video columns to add demo-specific widgets (e.g. a Face ID - summary). Called inside the same row, so it shares horizontal - space with the videos. - tab_label: Tab label text (default ``"Offline Inference"``). - feature_hint: Short hint shown next to the Features heading. - - Returns: - ``(tab, input_video, output_video, checkboxes, process_btn, example_dataset)``. - ``checkboxes`` is exactly what ``feature_checkbox_builder`` returned. - """ - with gr.TabItem(tab_label) as video_tab: - with gr.Accordion("Instructions", open=False): - gr.Markdown( - "1. Select the features that will be processed on the video\n" - "2. Select a video (or upload your own in the Input Video frame)\n" - "3. Press the **Process Video** button\n\n" - "Once the video has been processed, you can play the video in the " - "Output Video frame" - ) - - checkboxes = feature_checkbox_builder(hint=feature_hint) - - with gr.Accordion("Video Examples", open=True): - example_dataset = gr.Dataset( - components=[gr.Video(visible=False)], - samples=example_videos, - show_label=False, - ) - - process_btn = gr.Button("Process Video", variant="primary", interactive=False) - - build_video_constraints_accordion(video_limits) - - with gr.Row(equal_height=True): - with gr.Column(scale=5): - input_video = gr.Video(label="Input Video", sources=["upload", "webcam"]) - with gr.Column(scale=5): - output_video = gr.Video(label="Output Video") - if extras_builder is not None: - extras_builder() - - return video_tab, input_video, output_video, checkboxes, process_btn, example_dataset - - -@dataclass -class ImageOrVideoOfflineTab: - """Components returned by :func:`build_image_or_video_offline_tab`. - - The image/video accordions are exposed so the caller can wire mutual - exclusion (expanding one collapses the other) — same pattern as - :class:`face_id_tab.FaceIdTab`. - """ - - tab: gr.TabItem - image_input: gr.Image - video_input: gr.Video - image_output: gr.Image - video_output: gr.Video - checkboxes: object # demo-specific (radio, checkbox tuple, etc.) - process_btn: gr.Button - image_example_dataset: gr.Dataset | None - video_example_dataset: gr.Dataset | None - image_accordion: gr.Accordion - video_accordion: gr.Accordion - - -def build_image_or_video_offline_tab( - *, - feature_checkbox_builder: Callable[..., TCheckboxes], - image_examples: list | None, - video_examples: list | None, - video_limits: VideoLimits, - tab_label: str = "Offline Inference", - feature_hint: str = "applied when processing starts", -) -> ImageOrVideoOfflineTab: - """Build an Offline Inference tab that accepts image OR video. - - Pattern: two mutually-exclusive accordions on the input side ("Input - from an Image" / "Input from a Video"), one Process button, two - output components (image + video) shown side-by-side. Caller is - responsible for: - - - Wiring ``image_accordion`` / ``video_accordion`` mutual exclusion - (one-liner per side, see ``face_id_tab.FaceIdTab.wire``). - - Routing ``process_btn.click`` to a handler that dispatches by - which input is populated. - - Toggling output visibility based on which branch ran. - - Args: - feature_checkbox_builder: Callable that builds the demo's feature - checkboxes/radio inside the tab. Called with keyword - ``hint=feature_hint``. - image_examples: Pre-loaded image examples (list of ``[path]`` - rows) or ``None`` to skip the examples accordion. - video_examples: Same for videos. - video_limits: Upload constraints rendered inside the video - accordion. - tab_label: Tab label text. - feature_hint: Short hint shown next to the Features heading. - """ - with gr.TabItem(tab_label) as tab: - with gr.Accordion("Instructions", open=False): - gr.Markdown( - "1. Select the model that will be used for object detection\n" - "2. Choose between processing an **Image** or a **Video**:\n" - " - For an image: select an example or upload your own, then press " - "**Process**.\n" - " - For a video: expand the video section, select an example or " - "upload your own, then press **Process**.\n\n" - "Once processing is complete, the annotated result appears on the " - "right (image or video, depending on the input)." - ) - - checkboxes = feature_checkbox_builder(hint=feature_hint) - - with gr.Row(): - # --- Left column: input --- - with gr.Column(scale=5): - image_example_dataset: gr.Dataset | None = None - video_example_dataset: gr.Dataset | None = None - - with gr.Accordion( - "Input from an Image", open=True - ) as image_accordion: - if image_examples: - with gr.Accordion("Image Examples", open=True): - image_example_dataset = gr.Dataset( - components=[gr.Image(visible=False)], - samples=image_examples, - show_label=False, - ) - image_input = gr.Image( - label="Input Image", - sources=["upload", "webcam"], - type="filepath", - ) - - with gr.Accordion( - "Input from a Video", open=False - ) as video_accordion: - if video_examples: - with gr.Accordion("Video Examples", open=True): - video_example_dataset = gr.Dataset( - components=[gr.Video(visible=False)], - samples=video_examples, - show_label=False, - ) - build_video_constraints_accordion(video_limits) - video_input = gr.Video( - label="Input Video", sources=["upload", "webcam"] - ) - - process_btn = gr.Button( - "Process", variant="primary", interactive=False - ) - - # --- Right column: output (image OR video, toggled by handler) --- - with gr.Column(scale=5): - image_output = gr.Image(label="Output Image", visible=True) - video_output = gr.Video(label="Output Video", visible=False) - - return ImageOrVideoOfflineTab( - tab=tab, - image_input=image_input, - video_input=video_input, - image_output=image_output, - video_output=video_output, - checkboxes=checkboxes, - process_btn=process_btn, - image_example_dataset=image_example_dataset, - video_example_dataset=video_example_dataset, - image_accordion=image_accordion, - video_accordion=video_accordion, - ) - - -def build_live_inference_tab( - *, - rtc_configuration: RtcConfigurationInput, - feature_checkbox_builder: Callable[..., TCheckboxes], - extras_builder: Callable[[], None] | None = None, - max_fps: int = 15, - width: int = 640, - height: int = 360, - tab_label: str = "Live Inference", - description_html: str = ( - "

Use your webcam for real-time inference. " - "Select features below, then grant camera access when prompted.

" - ), -) -> tuple[gr.TabItem, object, TCheckboxes]: - """Build the Live Inference tab with feature checkboxes + WebRTC stream. - - Args: - rtc_configuration: ICE configuration dict, callable that returns - one, or ``None`` for direct connection. A callable is invoked - per-connection by FastRTC, allowing credential refresh. - feature_checkbox_builder: Callable that builds the demo's feature - checkboxes (same shape as in the offline tab). - extras_builder: Optional callable invoked after the WebRTC stream - for demo-specific widgets (e.g. Face ID summary). - max_fps: Maximum frame rate requested from the browser camera. - width / height: Camera frame dimensions in pixels. - tab_label: Tab label text. - description_html: Optional HTML shown above the stream. - - Returns: - ``(tab, webrtc_stream, checkboxes)``. - """ - with gr.TabItem(tab_label) as tab: - if description_html: - gr.HTML(description_html) - checkboxes = feature_checkbox_builder() - webrtc_stream = build_webrtc_stream( - rtc_configuration, max_fps=max_fps, width=width, height=height - ) - if extras_builder is not None: - extras_builder() - - return tab, webrtc_stream, checkboxes +"""Tab builders for EVE-based Gradio demos. + +Both tabs (Offline Inference, Live Inference) follow the same pattern: + +- Outer ``gr.TabItem`` with the demo-supplied label. +- Demo-specific feature checkboxes built via a caller-provided callable so + each demo defines its own feature set (Face Detection / Person / + Hand Detection / Face ID / …). +- Optional ``extras_builder`` hook for demo-specific extras inside the tab + (e.g. eve_hmi's Face ID summary thumbnails). + +The tab builders return the underlying components so the caller can wire +events (``process_btn.click``, ``webrtc_stream.stream``, etc.) outside. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import Callable, TypeVar + +import gradio as gr + +from live_inference import RtcConfigurationInput, build_webrtc_stream +from video_processing import VideoLimits, build_video_constraints_accordion + +# The exact tuple of Checkbox components is demo-specific. We let the +# caller's checkbox-builder define the shape and return it transparently. +TCheckboxes = TypeVar("TCheckboxes") + + +def build_offline_inference_tab( + *, + feature_checkbox_builder: Callable[..., TCheckboxes], + example_videos: list, + video_limits: VideoLimits, + extras_builder: Callable[[], None] | None = None, + tab_label: str = "Offline Inference", + feature_hint: str = "applied when processing starts", +) -> tuple[ + gr.TabItem, + gr.Video, + gr.Video, + TCheckboxes, + gr.Button, + gr.Dataset, +]: + """Build the Offline Inference tab. Must be called inside a ``gr.Tabs`` context. + + Args: + feature_checkbox_builder: Callable that builds the demo's feature + checkboxes inside the tab. Receives the keyword ``hint`` (a + short string shown next to the "Features" heading). Returns a + tuple of ``gr.Checkbox`` instances — the same tuple is returned + unchanged so the caller can wire events. + example_videos: Pre-loaded example videos (list of ``[path]`` rows). + video_limits: Upload constraints rendered in the accordion. + extras_builder: Optional callable invoked after the input/output + video columns to add demo-specific widgets (e.g. a Face ID + summary). Called inside the same row, so it shares horizontal + space with the videos. + tab_label: Tab label text (default ``"Offline Inference"``). + feature_hint: Short hint shown next to the Features heading. + + Returns: + ``(tab, input_video, output_video, checkboxes, process_btn, example_dataset)``. + ``checkboxes`` is exactly what ``feature_checkbox_builder`` returned. + """ + with gr.TabItem(tab_label) as video_tab: + with gr.Accordion("Instructions", open=False): + gr.Markdown( + "1. Select the features that will be processed on the video\n" + "2. Select a video (or upload your own in the Input Video frame)\n" + "3. Press the **Process Video** button\n\n" + "Once the video has been processed, you can play the video in the " + "Output Video frame" + ) + + checkboxes = feature_checkbox_builder(hint=feature_hint) + + with gr.Accordion("Video Examples", open=True): + example_dataset = gr.Dataset( + components=[gr.Video(visible=False)], + samples=example_videos, + show_label=False, + ) + + process_btn = gr.Button("Process Video", variant="primary", interactive=False) + + build_video_constraints_accordion(video_limits) + + with gr.Row(equal_height=True): + with gr.Column(scale=5): + input_video = gr.Video(label="Input Video", sources=["upload", "webcam"]) + with gr.Column(scale=5): + output_video = gr.Video(label="Output Video") + if extras_builder is not None: + extras_builder() + + return video_tab, input_video, output_video, checkboxes, process_btn, example_dataset + + +@dataclass +class ImageOrVideoOfflineTab: + """Components returned by :func:`build_image_or_video_offline_tab`. + + The image/video accordions are exposed so the caller can wire mutual + exclusion (expanding one collapses the other) — same pattern as + :class:`face_id_tab.FaceIdTab`. + """ + + tab: gr.TabItem + image_input: gr.Image + video_input: gr.Video + image_output: gr.Image + video_output: gr.Video + checkboxes: object # demo-specific (radio, checkbox tuple, etc.) + process_btn: gr.Button + image_example_dataset: gr.Dataset | None + video_example_dataset: gr.Dataset | None + image_accordion: gr.Accordion + video_accordion: gr.Accordion + + +def build_image_or_video_offline_tab( + *, + feature_checkbox_builder: Callable[..., TCheckboxes], + image_examples: list | None, + video_examples: list | None, + video_limits: VideoLimits, + tab_label: str = "Offline Inference", + feature_hint: str = "applied when processing starts", +) -> ImageOrVideoOfflineTab: + """Build an Offline Inference tab that accepts image OR video. + + Pattern: two mutually-exclusive accordions on the input side ("Input + from an Image" / "Input from a Video"), one Process button, two + output components (image + video) shown side-by-side. Caller is + responsible for: + + - Wiring ``image_accordion`` / ``video_accordion`` mutual exclusion + (one-liner per side, see ``face_id_tab.FaceIdTab.wire``). + - Routing ``process_btn.click`` to a handler that dispatches by + which input is populated. + - Toggling output visibility based on which branch ran. + + Args: + feature_checkbox_builder: Callable that builds the demo's feature + checkboxes/radio inside the tab. Called with keyword + ``hint=feature_hint``. + image_examples: Pre-loaded image examples (list of ``[path]`` + rows) or ``None`` to skip the examples accordion. + video_examples: Same for videos. + video_limits: Upload constraints rendered inside the video + accordion. + tab_label: Tab label text. + feature_hint: Short hint shown next to the Features heading. + """ + with gr.TabItem(tab_label) as tab: + with gr.Accordion("Instructions", open=False): + gr.Markdown( + "1. Select the model that will be used for object detection\n" + "2. Choose between processing an **Image** or a **Video**:\n" + " - For an image: select an example or upload your own, then press " + "**Process**.\n" + " - For a video: expand the video section, select an example or " + "upload your own, then press **Process**.\n\n" + "Once processing is complete, the annotated result appears on the " + "right (image or video, depending on the input)." + ) + + checkboxes = feature_checkbox_builder(hint=feature_hint) + + with gr.Row(): + # --- Left column: input --- + with gr.Column(scale=5): + image_example_dataset: gr.Dataset | None = None + video_example_dataset: gr.Dataset | None = None + + with gr.Accordion( + "Input from an Image", open=True + ) as image_accordion: + if image_examples: + with gr.Accordion("Image Examples", open=True): + image_example_dataset = gr.Dataset( + components=[gr.Image(visible=False)], + samples=image_examples, + show_label=False, + ) + image_input = gr.Image( + label="Input Image", + sources=["upload", "webcam"], + type="filepath", + ) + + with gr.Accordion( + "Input from a Video", open=False + ) as video_accordion: + if video_examples: + with gr.Accordion("Video Examples", open=True): + video_example_dataset = gr.Dataset( + components=[gr.Video(visible=False)], + samples=video_examples, + show_label=False, + ) + build_video_constraints_accordion(video_limits) + video_input = gr.Video( + label="Input Video", sources=["upload", "webcam"] + ) + + process_btn = gr.Button( + "Process", variant="primary", interactive=False + ) + + # --- Right column: output (image OR video, toggled by handler) --- + with gr.Column(scale=5): + image_output = gr.Image(label="Output Image", visible=True) + video_output = gr.Video(label="Output Video", visible=False) + + return ImageOrVideoOfflineTab( + tab=tab, + image_input=image_input, + video_input=video_input, + image_output=image_output, + video_output=video_output, + checkboxes=checkboxes, + process_btn=process_btn, + image_example_dataset=image_example_dataset, + video_example_dataset=video_example_dataset, + image_accordion=image_accordion, + video_accordion=video_accordion, + ) + + +def build_live_inference_tab( + *, + rtc_configuration: RtcConfigurationInput, + feature_checkbox_builder: Callable[..., TCheckboxes], + extras_builder: Callable[[], None] | None = None, + max_fps: int = 15, + width: int = 640, + height: int = 360, + tab_label: str = "Live Inference", + description_html: str = ( + "

Use your webcam for real-time inference. " + "Select features below, then grant camera access when prompted.

" + ), +) -> tuple[gr.TabItem, object, TCheckboxes]: + """Build the Live Inference tab with feature checkboxes + WebRTC stream. + + Args: + rtc_configuration: ICE configuration dict, callable that returns + one, or ``None`` for direct connection. A callable is invoked + per-connection by FastRTC, allowing credential refresh. + feature_checkbox_builder: Callable that builds the demo's feature + checkboxes (same shape as in the offline tab). + extras_builder: Optional callable invoked after the WebRTC stream + for demo-specific widgets (e.g. Face ID summary). + max_fps: Maximum frame rate requested from the browser camera. + width / height: Camera frame dimensions in pixels. + tab_label: Tab label text. + description_html: Optional HTML shown above the stream. + + Returns: + ``(tab, webrtc_stream, checkboxes)``. + """ + with gr.TabItem(tab_label) as tab: + if description_html: + gr.HTML(description_html) + checkboxes = feature_checkbox_builder() + webrtc_stream = build_webrtc_stream( + rtc_configuration, max_fps=max_fps, width=width, height=height + ) + if extras_builder is not None: + extras_builder() + + return tab, webrtc_stream, checkboxes diff --git a/shared/eve_inference_handlers.py b/shared/eve_inference_handlers.py index 282777c4e4c2c1553242cf5f2eccad342406a451..b0f485286631e9365743706372a84f6ef1f1627a 100644 --- a/shared/eve_inference_handlers.py +++ b/shared/eve_inference_handlers.py @@ -1,485 +1,509 @@ -"""Offline + live inference handlers shared by EVE-based Gradio demos. - -The two main handlers (``run_eve_inference`` for the Offline Inference tab, -``process_live_frame`` for the Live Inference tab) used to live in -``eve_hmi/app.py``. They are bundled on an ``EveAppHandlers`` instance so -demos can wire them with one ``handlers = EveAppHandlers(...)`` line and -re-use them as Gradio callbacks. - -Face ID is opt-in: pass an empty / ``None`` ``registry`` and Face ID -behaviour is bypassed (gallery restore is skipped, the ``face_id`` flag -still travels through ``FeatureFlags`` so the EVE SDK can act on it). -""" - -from __future__ import annotations - -import logging -import os -import shutil -import tempfile -import threading -import time -import uuid -from typing import TYPE_CHECKING - -import cv2 -import gradio as gr -import numpy as np - -from eve_messages import FeatureFlags -from eve_worker_pool import log_worker_activity -from face_id_tab import FaceEntry # runtime import: Gradio resolves type hints at wire time -from frame_drawing import draw_countdown_banner, draw_overlay, draw_session_timer - -if TYPE_CHECKING: - from eve_worker_pool import EveWorkerPool - from live_stream_manager import LiveStreamManager - from session_tracker import SessionTracker - from video_file_server import VideoFileServer - - -def patch_video_for_external_urls(video_component: gr.Video) -> None: - """Patch a ``gr.Video`` so HTTP(S) URLs bypass Gradio's safehttpx download. - - Gradio's default postprocessing fetches HTTP URLs via ``safehttpx``, - which refuses localhost / private IPs. Setting both ``FileData.path`` - and ``FileData.url`` short-circuits the cache logic in - ``async_move_files_to_cache`` so the browser plays the URL directly. - """ - from gradio.components.video import VideoData - from gradio.data_classes import FileData - - original = video_component.postprocess - - def _postprocess(value): # type: ignore[no-untyped-def] - if isinstance(value, str) and value.startswith(("http://", "https://")): - return VideoData(video=FileData(path=value, url=value)) - return original(value) - - video_component.postprocess = _postprocess # type: ignore[assignment] - - -class EveAppHandlers: - """Gradio-callable handlers for EVE offline + live inference. - - Args: - pool: The shared worker pool. - stream_manager: ``LiveStreamManager`` driving WebRTC streams. - sessions: ``SessionTracker`` for analytics + idle reaping. - logger: Logger used for per-session log lines. - max_fps / min_fps: FPS bounds forwarded to ``send_process_video`` - (controls the worker-side encoding rate). - video_server: Optional local HTTP server for processed videos. - When set, processed clips are returned as URLs instead of raw - paths so Chrome's per-origin connection limit doesn't block - playback while SSE is open. - """ - - def __init__( - self, - pool: EveWorkerPool, - stream_manager: LiveStreamManager, - sessions: SessionTracker, - logger: logging.Logger, - max_fps: float | None, - min_fps: float | None, - video_server: VideoFileServer | None = None, - ) -> None: - self._pool = pool - self._stream = stream_manager - self._sessions = sessions - self._logger = logger - self._max_fps = max_fps - self._min_fps = min_fps - self._video_server = video_server - self._live_logged: set[str] = set() - self._live_logged_lock = threading.Lock() - - # ----- offline inference ------------------------------------------------- - - def run_eve_inference( - self, - input_video: str, - face_detection: bool, - person_detection: bool, - face_id: bool, - hand_gesture: bool, - registry: dict[int, FaceEntry] | None, - mod_model: str | None = None, - request: gr.Request | None = None, - progress: gr.Progress = gr.Progress(), - ) -> tuple[str | None, dict[int, FaceEntry]]: - """Process an uploaded video through one EVE worker, end-to-end. - - The whole read → infer → encode → write loop happens inside the - worker process so the asyncio event loop stays free for SSE - delivery. The main thread only forwards the path/config and - relays small progress dicts. - - Returns ``(output_url_or_path, registry)``. ``registry`` is - returned unchanged when Face ID is disabled, or with ``sdk_id`` - fields refreshed from the worker's gallery-restore results. - """ - registry = registry or {} - session = request.session_hash[:8] - self._sessions.track( - request.session_hash, - "video_process", - face_detection=face_detection, - person_detection=person_detection, - face_id=face_id, - hand_gesture=hand_gesture, - mod_model=mod_model, - ) - - fps, width, height, total_frames = _read_video_metadata(input_video) - if total_frames <= 0: - raise gr.Error("Could not read any frames from the video.") - - features = FeatureFlags( - face_detection=face_detection, - person_detection=person_detection, - face_id=face_id, - hand_gesture=hand_gesture, - mod_model=mod_model, - ) - - gallery_paths: list[str] = [] - remove_all_users = False - if face_id and registry: - gallery_paths = [entry.path for entry in registry.values()] - elif face_id: - remove_all_users = True - - # Write directly into Gradio's cache so postprocessing skips the - # expensive hash_file + shutil.copy2 that would block the asyncio - # event loop. - gradio_cache = os.path.join(tempfile.gettempdir(), "gradio") - session_out = os.path.join(gradio_cache, f"eve_{request.session_hash}") - os.makedirs(session_out, exist_ok=True) - output_path = os.path.join(session_out, f"output_{uuid.uuid4().hex[:8]}.mp4") - - self._logger.info(f"[{session}] run_eve_inference: waiting for worker...") - # Only emit queue events when we actually have to wait — matches the - # live queue behaviour so dashboards don't show phantom peaks. - will_wait = self._pool.idle_count == 0 - if will_wait: - self._sessions.track(request.session_hash, "offline_queue_enter") - t0 = time.monotonic() - worker = self._pool.acquire( - request.session_hash, - timeout=300.0, - progress=progress, - eta_fn=self._stream.estimated_wait, - ) - if will_wait: - self._sessions.track( - request.session_hash, - "offline_queue_exit", - wait_seconds=round(time.monotonic() - t0, 1), - ) - log_worker_activity( - self._logger, "acquired", "video-processing", self._pool, worker.worker_id - ) - try: - frames_processed, gallery_results = worker.send_process_video( - input_path=input_video, - output_path=output_path, - features=features, - gallery_paths=gallery_paths, - remove_all_users=remove_all_users, - fps=fps, - width=width, - height=height, - total_frames=total_frames, - progress=progress, - max_fps=self._max_fps, - min_fps=self._min_fps, - ) - self._logger.info( - f"[{session}] run_eve_inference: done, {frames_processed} frames processed" - ) - finally: - self._pool.release(worker) - log_worker_activity( - self._logger, "released", "video-processing", self._pool, worker.worker_id - ) - self._sessions.track( - request.session_hash, - "video_process_complete", - duration_seconds=round(time.monotonic() - t0, 1), - ) - - if gallery_results: - for entry, r in zip(registry.values(), gallery_results): - entry.sdk_id = r.user_id if r.success else None - - if self._video_server is not None: - rel_path = os.path.relpath(output_path, gradio_cache) - video_result: str | None = self._video_server.build_url(rel_path, request) - else: - video_result = output_path - - return video_result, registry - - # ----- offline image inference ------------------------------------------- - - def run_eve_image_inference( - self, - input_image: str | None, - face_detection: bool, - person_detection: bool, - face_id: bool, - hand_gesture: bool, - registry: dict[int, FaceEntry] | None, - mod_model: str | None = None, - request: gr.Request | None = None, - progress: gr.Progress = gr.Progress(), - ) -> tuple[np.ndarray | None, dict[int, FaceEntry]]: - """Run a single image through one EVE worker, end-to-end. - - Single-frame variant of :meth:`run_eve_inference`. Loads the image - as BGR, sends one ``InferenceCmd`` to the worker (the same path - that powers live inference), and returns the annotated result as - an RGB ``np.ndarray`` ready for ``gr.Image``. - - Returns ``(annotated_image_rgb_or_None, registry)``. Registry is - returned unchanged — single-image inference does not refresh - Face ID gallery state. - """ - if not input_image: - raise gr.Error("Please upload an image.") - - registry = registry or {} - session = request.session_hash[:8] if request is not None else "?" - if request is not None: - self._sessions.track( - request.session_hash, - "image_process", - face_detection=face_detection, - person_detection=person_detection, - face_id=face_id, - hand_gesture=hand_gesture, - mod_model=mod_model, - ) - - frame = cv2.imread(input_image) - if frame is None: - raise gr.Error(f"Could not read image: {input_image}") - - features = FeatureFlags( - face_detection=face_detection, - person_detection=person_detection, - face_id=face_id, - hand_gesture=hand_gesture, - mod_model=mod_model, - ) - - self._logger.info(f"[{session}] run_eve_image_inference: waiting for worker...") - will_wait = self._pool.idle_count == 0 - if will_wait and request is not None: - self._sessions.track(request.session_hash, "offline_queue_enter") - t0 = time.monotonic() - worker = self._pool.acquire( - request.session_hash if request is not None else "image", - timeout=300.0, - progress=progress, - eta_fn=self._stream.estimated_wait, - ) - if will_wait and request is not None: - self._sessions.track( - request.session_hash, - "offline_queue_exit", - wait_seconds=round(time.monotonic() - t0, 1), - ) - log_worker_activity( - self._logger, "acquired", "image-processing", self._pool, worker.worker_id - ) - try: - result = worker.send_inference(frame, features) - self._logger.info(f"[{session}] run_eve_image_inference: done") - finally: - self._pool.release(worker) - log_worker_activity( - self._logger, "released", "image-processing", self._pool, worker.worker_id - ) - if request is not None: - self._sessions.track( - request.session_hash, - "image_process_complete", - duration_seconds=round(time.monotonic() - t0, 1), - ) - - # Worker returns BGR (matches live-inference frame format); Gradio's - # gr.Image expects RGB. - result_rgb = cv2.cvtColor(result, cv2.COLOR_BGR2RGB) - return result_rgb, registry - - # ----- live inference ---------------------------------------------------- - - def process_live_frame( - self, - frame: np.ndarray, - face_detection: bool, - person_detection: bool, - face_id: bool, - hand_gesture: bool, - registry: dict[int, FaceEntry] | None, - session_hash: str, - mod_model: str | None = None, - ): - # Return type intentionally unannotated: actual return is - # ``np.ndarray | fastrtc.CloseStream | None`` but ``CloseStream`` is - # only imported lazily inside the function (avoids pulling fastrtc - # into module scope), and Gradio/FastRTC may run ``get_type_hints`` - # on bound handlers — a forward-ref string would break that. - """Run a single WebRTC frame through an EVE worker. - - A worker is acquired on the first frame and held for the lifetime of - the stream. Returns the frame with an overlay while waiting for a - worker, or a ``CloseStream`` when the session ends so the UI can - reset to ``Start Inference``. - - ``gr.Request`` is not available inside FastRTC stream handlers, so - the Gradio session hash is passed through ``gr.State``. FastRTC's - per-connection ``webrtc_id`` is still pulled from - ``current_context`` to track worker assignment per peer. - """ - if frame is None: - return None - - from fastrtc.utils import current_context - - connection_id = current_context.get().webrtc_id - worker, reason = self._stream.get_or_acquire(connection_id, session_hash, registry or {}) - if worker is None: - if reason == "waiting": - pos, total = self._stream.waiting_position(connection_id) - eta = self._stream.estimated_wait(pos) - eta_text = "" - if eta is not None: - eta_mins = max(0.5, round(eta / 30) * 0.5) - eta_text = f"\nest. wait ~{eta_mins:g} minutes" - if total > 1: - return draw_overlay(frame, f"In queue {eta_text}") - return draw_overlay(frame, f"Waiting for available worker...{eta_text}") - with self._live_logged_lock: - self._live_logged.discard(connection_id) - from fastrtc import CloseStream - - return CloseStream(reason or "Stream ended") - - bridge = self._stream.get_bridge(connection_id) - if bridge is None or not bridge.is_alive: - self._stream.release(connection_id) - return draw_overlay(frame, "Inference error - retrying...") - - with self._live_logged_lock: - first_frame = connection_id not in self._live_logged - if first_frame: - self._live_logged.add(connection_id) - if first_frame: - self._sessions.track( - session_hash, - "live_start", - face_detection=face_detection, - person_detection=person_detection, - face_id=face_id, - hand_gesture=hand_gesture, - mod_model=mod_model, - ) - - features = FeatureFlags( - face_detection=face_detection, - person_detection=person_detection, - face_id=face_id, - hand_gesture=hand_gesture, - mod_model=mod_model, - ) - try: - result = bridge.submit_and_get_latest(frame, features) - if result is None: - # First frame — no inference result yet, show camera feed - return frame - - remaining = self._stream.countdown_remaining(connection_id) - if remaining is not None: - secs = int(remaining) + 1 - result = draw_countdown_banner(result, f"Other users waiting - stopping in {secs}s") - - session_left = self._stream.session_remaining(connection_id) - if session_left is not None: - result = draw_session_timer(result, session_left) - - return result - except (BrokenPipeError, EOFError, OSError): - # Worker pipe is gone (shutdown or crash) — release quietly - self._stream.release(connection_id) - return None - except Exception as exc: - self._logger.error(f"Live inference error: {exc}") - self._stream.release(connection_id) - return draw_overlay(frame, "Inference error - retrying...") - - def on_live_feature_change( - self, - face_detection: bool, - person_detection: bool, - face_id: bool, - hand_gesture: bool, - mod_model: str | None = None, - request: gr.Request | None = None, - ) -> None: - """Track changes to the Live tab's feature checkboxes.""" - self._sessions.track( - request.session_hash, - "live_feature_change", - face_detection=face_detection, - person_detection=person_detection, - face_id=face_id, - hand_gesture=hand_gesture, - mod_model=mod_model, - ) - - # ----- session cleanup --------------------------------------------------- - - def cleanup_session(self, request: gr.Request) -> None: - """Gradio ``demo.unload`` handler — close the session + delete cache.""" - self._sessions.on_unload(request) - - gradio_cache = os.path.join(tempfile.gettempdir(), "gradio") - session_out = os.path.join(gradio_cache, f"eve_{request.session_hash}") - if os.path.isdir(session_out): - shutil.rmtree(session_out, ignore_errors=True) - self._logger.debug(f"Session cleanup: removed {session_out}") - - -def _read_video_metadata(path: str) -> tuple[float, int, int, int]: - """Return ``(fps, width, height, total_frames)`` for a video file. - - Webcam-recorded WebM blobs frequently lie in their headers (fps=0, - fps=1000 from ms timestamps, or no frame count) so we fall back to a - full decode pass when the headers look implausible. - """ - cap = cv2.VideoCapture(path) - fps = cap.get(cv2.CAP_PROP_FPS) - width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) - - if fps <= 0 or fps > 240 or total_frames <= 0 or width <= 0 or height <= 0: - frame_count = 0 - duration_ms = 0.0 - while True: - ret, frame = cap.read() - if not ret: - break - if frame_count == 0: - height, width = frame.shape[:2] - frame_count += 1 - duration_ms = cap.get(cv2.CAP_PROP_POS_MSEC) - total_frames = frame_count - if fps <= 0 or fps > 240: - fps = total_frames / (duration_ms / 1000.0) if duration_ms > 0 else 30.0 - cap.release() - return fps, width, height, total_frames +"""Offline + live inference handlers shared by EVE-based Gradio demos. + +The two main handlers (``run_eve_inference`` for the Offline Inference tab, +``process_live_frame`` for the Live Inference tab) used to live in +``eve_hmi/app.py``. They are bundled on an ``EveAppHandlers`` instance so +demos can wire them with one ``handlers = EveAppHandlers(...)`` line and +re-use them as Gradio callbacks. + +Face ID is opt-in: pass an empty / ``None`` ``registry`` and Face ID +behaviour is bypassed (gallery restore is skipped, the ``face_id`` flag +still travels through ``FeatureFlags`` so the EVE SDK can act on it). +""" + +from __future__ import annotations + +import logging +import os +import shutil +import tempfile +import threading +import time +import uuid +from collections.abc import Mapping +from typing import TYPE_CHECKING + +import cv2 +import gradio as gr +import numpy as np +from eve_messages import FeatureFlags, ModelConfig +from eve_worker_pool import log_worker_activity +from face_id_tab import ( + FaceEntry, # runtime import: Gradio resolves type hints at wire time +) +from frame_drawing import draw_countdown_banner, draw_overlay, draw_session_timer + +if TYPE_CHECKING: + from eve_worker_pool import EveWorkerPool + from live_stream_manager import LiveStreamManager + from session_tracker import SessionTracker + from video_file_server import VideoFileServer + + +def patch_video_for_external_urls(video_component: gr.Video) -> None: + """Patch a ``gr.Video`` so HTTP(S) URLs bypass Gradio's safehttpx download. + + Gradio's default postprocessing fetches HTTP URLs via ``safehttpx``, + which refuses localhost / private IPs. Setting both ``FileData.path`` + and ``FileData.url`` short-circuits the cache logic in + ``async_move_files_to_cache`` so the browser plays the URL directly. + """ + from gradio.components.video import VideoData + from gradio.data_classes import FileData + + original = video_component.postprocess + + def _postprocess(value): # type: ignore[no-untyped-def] + if isinstance(value, str) and value.startswith(("http://", "https://")): + return VideoData(video=FileData(path=value, url=value)) + return original(value) + + video_component.postprocess = _postprocess # type: ignore[assignment] + + +class EveAppHandlers: + """Gradio-callable handlers for EVE offline + live inference. + + Args: + pool: The shared worker pool. + stream_manager: ``LiveStreamManager`` driving WebRTC streams. + sessions: ``SessionTracker`` for analytics + idle reaping. + logger: Logger used for per-session log lines. + max_fps / min_fps: FPS bounds forwarded to ``send_process_video`` + (controls the worker-side encoding rate). + video_server: Optional local HTTP server for processed videos. + When set, processed clips are returned as URLs instead of raw + paths so Chrome's per-origin connection limit doesn't block + playback while SSE is open. + mod_model_registry: Maps a model-name string (the value carried by a + demo's MOD radio) to the ``ModelConfig`` the worker should load. + Demos that do not use MOD (e.g. eve_hmi) omit it; in that case a + model name resolves to ``None`` and MOD stays off. + """ + + def __init__( + self, + pool: EveWorkerPool, + stream_manager: LiveStreamManager, + sessions: SessionTracker, + logger: logging.Logger, + max_fps: float | None, + min_fps: float | None, + video_server: VideoFileServer | None = None, + mod_model_registry: Mapping[str, ModelConfig] | None = None, + ) -> None: + self._pool = pool + self._stream = stream_manager + self._sessions = sessions + self._logger = logger + self._max_fps = max_fps + self._min_fps = min_fps + self._video_server = video_server + self._mod_model_registry = mod_model_registry or {} + self._live_logged: set[str] = set() + self._live_logged_lock = threading.Lock() + + def _resolve_mod_model(self, mod_model: str | None) -> ModelConfig | None: + """Resolve a MOD radio value to the ``ModelConfig`` the worker loads. + + ``None`` / empty (MOD off, or a demo with no MOD radio) yields + ``None``. An unknown name with no registry entry falls back to a + bare ``ModelConfig`` (no path) so the SDK's bundled default model is + used rather than failing. + """ + if not mod_model: + return None + config = self._mod_model_registry.get(mod_model) + if config is None: + self._logger.warning(f"Unknown MOD model {mod_model!r}; using bundled default") + return ModelConfig(name=mod_model) + return config + + # ----- offline inference ------------------------------------------------- + + def run_eve_inference( + self, + input_video: str, + face_detection: bool, + person_detection: bool, + face_id: bool, + hand_gesture: bool, + registry: dict[int, FaceEntry] | None, + mod_model: str | None = None, + request: gr.Request | None = None, + progress: gr.Progress = gr.Progress(), + ) -> tuple[str | None, dict[int, FaceEntry]]: + """Process an uploaded video through one EVE worker, end-to-end. + + The whole read → infer → encode → write loop happens inside the + worker process so the asyncio event loop stays free for SSE + delivery. The main thread only forwards the path/config and + relays small progress dicts. + + Returns ``(output_url_or_path, registry)``. ``registry`` is + returned unchanged when Face ID is disabled, or with ``sdk_id`` + fields refreshed from the worker's gallery-restore results. + """ + registry = registry or {} + session = request.session_hash[:8] + self._sessions.track( + request.session_hash, + "video_process", + face_detection=face_detection, + person_detection=person_detection, + face_id=face_id, + hand_gesture=hand_gesture, + mod_model=mod_model, + ) + + fps, width, height, total_frames = _read_video_metadata(input_video) + if total_frames <= 0: + raise gr.Error("Could not read any frames from the video.") + + features = FeatureFlags( + face_detection=face_detection, + person_detection=person_detection, + face_id=face_id, + hand_gesture=hand_gesture, + mod_model_config=self._resolve_mod_model(mod_model), + ) + + gallery_paths: list[str] = [] + remove_all_users = False + if face_id and registry: + gallery_paths = [entry.path for entry in registry.values()] + elif face_id: + remove_all_users = True + + # Write directly into Gradio's cache so postprocessing skips the + # expensive hash_file + shutil.copy2 that would block the asyncio + # event loop. + gradio_cache = os.path.join(tempfile.gettempdir(), "gradio") + session_out = os.path.join(gradio_cache, f"eve_{request.session_hash}") + os.makedirs(session_out, exist_ok=True) + output_path = os.path.join(session_out, f"output_{uuid.uuid4().hex[:8]}.mp4") + + self._logger.info(f"[{session}] run_eve_inference: waiting for worker...") + # Only emit queue events when we actually have to wait — matches the + # live queue behaviour so dashboards don't show phantom peaks. + will_wait = self._pool.idle_count == 0 + if will_wait: + self._sessions.track(request.session_hash, "offline_queue_enter") + t0 = time.monotonic() + worker = self._pool.acquire( + request.session_hash, + timeout=300.0, + progress=progress, + eta_fn=self._stream.estimated_wait, + ) + if will_wait: + self._sessions.track( + request.session_hash, + "offline_queue_exit", + wait_seconds=round(time.monotonic() - t0, 1), + ) + log_worker_activity( + self._logger, "acquired", "video-processing", self._pool, worker.worker_id + ) + try: + frames_processed, gallery_results = worker.send_process_video( + input_path=input_video, + output_path=output_path, + features=features, + gallery_paths=gallery_paths, + remove_all_users=remove_all_users, + fps=fps, + width=width, + height=height, + total_frames=total_frames, + progress=progress, + max_fps=self._max_fps, + min_fps=self._min_fps, + ) + self._logger.info( + f"[{session}] run_eve_inference: done, {frames_processed} frames processed" + ) + finally: + self._pool.release(worker) + log_worker_activity( + self._logger, "released", "video-processing", self._pool, worker.worker_id + ) + self._sessions.track( + request.session_hash, + "video_process_complete", + duration_seconds=round(time.monotonic() - t0, 1), + ) + + if gallery_results: + for entry, r in zip(registry.values(), gallery_results): + entry.sdk_id = r.user_id if r.success else None + + if self._video_server is not None: + rel_path = os.path.relpath(output_path, gradio_cache) + video_result: str | None = self._video_server.build_url(rel_path, request) + else: + video_result = output_path + + return video_result, registry + + # ----- offline image inference ------------------------------------------- + + def run_eve_image_inference( + self, + input_image: str | None, + face_detection: bool, + person_detection: bool, + face_id: bool, + hand_gesture: bool, + registry: dict[int, FaceEntry] | None, + mod_model: str | None = None, + request: gr.Request | None = None, + progress: gr.Progress = gr.Progress(), + ) -> tuple[np.ndarray | None, dict[int, FaceEntry]]: + """Run a single image through one EVE worker, end-to-end. + + Single-frame variant of :meth:`run_eve_inference`. Loads the image + as BGR, sends one ``InferenceCmd`` to the worker (the same path + that powers live inference), and returns the annotated result as + an RGB ``np.ndarray`` ready for ``gr.Image``. + + Returns ``(annotated_image_rgb_or_None, registry)``. Registry is + returned unchanged — single-image inference does not refresh + Face ID gallery state. + """ + if not input_image: + raise gr.Error("Please upload an image.") + + registry = registry or {} + session = request.session_hash[:8] if request is not None else "?" + if request is not None: + self._sessions.track( + request.session_hash, + "image_process", + face_detection=face_detection, + person_detection=person_detection, + face_id=face_id, + hand_gesture=hand_gesture, + mod_model=mod_model, + ) + + frame = cv2.imread(input_image) + if frame is None: + raise gr.Error(f"Could not read image: {input_image}") + + features = FeatureFlags( + face_detection=face_detection, + person_detection=person_detection, + face_id=face_id, + hand_gesture=hand_gesture, + mod_model_config=self._resolve_mod_model(mod_model), + ) + + self._logger.info(f"[{session}] run_eve_image_inference: waiting for worker...") + will_wait = self._pool.idle_count == 0 + if will_wait and request is not None: + self._sessions.track(request.session_hash, "offline_queue_enter") + t0 = time.monotonic() + worker = self._pool.acquire( + request.session_hash if request is not None else "image", + timeout=300.0, + progress=progress, + eta_fn=self._stream.estimated_wait, + ) + if will_wait and request is not None: + self._sessions.track( + request.session_hash, + "offline_queue_exit", + wait_seconds=round(time.monotonic() - t0, 1), + ) + log_worker_activity( + self._logger, "acquired", "image-processing", self._pool, worker.worker_id + ) + try: + result = worker.send_inference(frame, features) + self._logger.info(f"[{session}] run_eve_image_inference: done") + finally: + self._pool.release(worker) + log_worker_activity( + self._logger, "released", "image-processing", self._pool, worker.worker_id + ) + if request is not None: + self._sessions.track( + request.session_hash, + "image_process_complete", + duration_seconds=round(time.monotonic() - t0, 1), + ) + + # Worker returns BGR (matches live-inference frame format); Gradio's + # gr.Image expects RGB. + result_rgb = cv2.cvtColor(result, cv2.COLOR_BGR2RGB) + return result_rgb, registry + + # ----- live inference ---------------------------------------------------- + + def process_live_frame( + self, + frame: np.ndarray, + face_detection: bool, + person_detection: bool, + face_id: bool, + hand_gesture: bool, + registry: dict[int, FaceEntry] | None, + session_hash: str, + mod_model: str | None = None, + ): + # Return type intentionally unannotated: actual return is + # ``np.ndarray | fastrtc.CloseStream | None`` but ``CloseStream`` is + # only imported lazily inside the function (avoids pulling fastrtc + # into module scope), and Gradio/FastRTC may run ``get_type_hints`` + # on bound handlers — a forward-ref string would break that. + """Run a single WebRTC frame through an EVE worker. + + A worker is acquired on the first frame and held for the lifetime of + the stream. Returns the frame with an overlay while waiting for a + worker, or a ``CloseStream`` when the session ends so the UI can + reset to ``Start Inference``. + + ``gr.Request`` is not available inside FastRTC stream handlers, so + the Gradio session hash is passed through ``gr.State``. FastRTC's + per-connection ``webrtc_id`` is still pulled from + ``current_context`` to track worker assignment per peer. + """ + if frame is None: + return None + + from fastrtc.utils import current_context + + connection_id = current_context.get().webrtc_id + worker, reason = self._stream.get_or_acquire(connection_id, session_hash, registry or {}) + if worker is None: + if reason == "waiting": + pos, total = self._stream.waiting_position(connection_id) + eta = self._stream.estimated_wait(pos) + eta_text = "" + if eta is not None: + eta_mins = max(0.5, round(eta / 30) * 0.5) + eta_text = f"\nest. wait ~{eta_mins:g} minutes" + if total > 1: + return draw_overlay(frame, f"In queue {eta_text}") + return draw_overlay(frame, f"Waiting for available worker...{eta_text}") + with self._live_logged_lock: + self._live_logged.discard(connection_id) + from fastrtc import CloseStream + + return CloseStream(reason or "Stream ended") + + bridge = self._stream.get_bridge(connection_id) + if bridge is None or not bridge.is_alive: + self._stream.release(connection_id) + return draw_overlay(frame, "Inference error - retrying...") + + with self._live_logged_lock: + first_frame = connection_id not in self._live_logged + if first_frame: + self._live_logged.add(connection_id) + if first_frame: + self._sessions.track( + session_hash, + "live_start", + face_detection=face_detection, + person_detection=person_detection, + face_id=face_id, + hand_gesture=hand_gesture, + mod_model=mod_model, + ) + + features = FeatureFlags( + face_detection=face_detection, + person_detection=person_detection, + face_id=face_id, + hand_gesture=hand_gesture, + mod_model_config=self._resolve_mod_model(mod_model), + ) + try: + result = bridge.submit_and_get_latest(frame, features) + if result is None: + # First frame — no inference result yet, show camera feed + return frame + + remaining = self._stream.countdown_remaining(connection_id) + if remaining is not None: + secs = int(remaining) + 1 + result = draw_countdown_banner(result, f"Other users waiting - stopping in {secs}s") + + session_left = self._stream.session_remaining(connection_id) + if session_left is not None: + result = draw_session_timer(result, session_left) + + return result + except (BrokenPipeError, EOFError, OSError): + # Worker pipe is gone (shutdown or crash) — release quietly + self._stream.release(connection_id) + return None + except Exception as exc: + self._logger.error(f"Live inference error: {exc}") + self._stream.release(connection_id) + return draw_overlay(frame, "Inference error - retrying...") + + def on_live_feature_change( + self, + face_detection: bool, + person_detection: bool, + face_id: bool, + hand_gesture: bool, + mod_model: str | None = None, + request: gr.Request | None = None, + ) -> None: + """Track changes to the Live tab's feature checkboxes.""" + self._sessions.track( + request.session_hash, + "live_feature_change", + face_detection=face_detection, + person_detection=person_detection, + face_id=face_id, + hand_gesture=hand_gesture, + mod_model=mod_model, + ) + + # ----- session cleanup --------------------------------------------------- + + def cleanup_session(self, request: gr.Request) -> None: + """Gradio ``demo.unload`` handler — close the session + delete cache.""" + self._sessions.on_unload(request) + + gradio_cache = os.path.join(tempfile.gettempdir(), "gradio") + session_out = os.path.join(gradio_cache, f"eve_{request.session_hash}") + if os.path.isdir(session_out): + shutil.rmtree(session_out, ignore_errors=True) + self._logger.debug(f"Session cleanup: removed {session_out}") + + +def _read_video_metadata(path: str) -> tuple[float, int, int, int]: + """Return ``(fps, width, height, total_frames)`` for a video file. + + Webcam-recorded WebM blobs frequently lie in their headers (fps=0, + fps=1000 from ms timestamps, or no frame count) so we fall back to a + full decode pass when the headers look implausible. + """ + cap = cv2.VideoCapture(path) + fps = cap.get(cv2.CAP_PROP_FPS) + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + + if fps <= 0 or fps > 240 or total_frames <= 0 or width <= 0 or height <= 0: + frame_count = 0 + duration_ms = 0.0 + while True: + ret, frame = cap.read() + if not ret: + break + if frame_count == 0: + height, width = frame.shape[:2] + frame_count += 1 + duration_ms = cap.get(cv2.CAP_PROP_POS_MSEC) + total_frames = frame_count + if fps <= 0 or fps > 240: + fps = total_frames / (duration_ms / 1000.0) if duration_ms > 0 else 30.0 + cap.release() + return fps, width, height, total_frames diff --git a/shared/eve_messages.py b/shared/eve_messages.py index 5525e0da530129675d748b7ba7b7318b2f6d16cb..634eda55efcf07eba46d6fda5682ca30394ae9d0 100644 --- a/shared/eve_messages.py +++ b/shared/eve_messages.py @@ -1,231 +1,257 @@ -"""IPC message dataclasses for the Eve worker pool. - -All types are picklable and sent over ``multiprocessing.Pipe`` between the -main Gradio process and Eve SDK worker processes. -""" - -from dataclasses import dataclass - -# --------------------------------------------------------------------------- -# Shared data types -# --------------------------------------------------------------------------- - - -@dataclass -class CalibrationResultMsg: - """Picklable mirror of ``eve_wrapper.CalibrationResult``.""" - - success: bool - user_id: int - message: str - - -@dataclass -class FeatureFlags: - """Feature toggle bundle sent with inference / configure commands.""" - - face_detection: bool = True - person_detection: bool = True - face_id: bool = False - hand_gesture: bool = False - # None = MOD off. Otherwise a model-name string (e.g. "GMOD-80", - # "AMOD-8", "OMOD") that the worker resolves to the EVE SDK MOD model. - # Defaulted so existing demos keep their pickle shape unchanged. - mod_model: str | None = None - - -@dataclass -class SerializedFrame: - """Picklable representation of a numpy ndarray frame.""" - - data: bytes - shape: tuple[int, ...] - dtype: str - - -# --------------------------------------------------------------------------- -# Commands (main process → worker process) -# --------------------------------------------------------------------------- - - -@dataclass -class InferenceCmd: - frame_bytes: bytes - shape: tuple[int, ...] - dtype: str - features: FeatureFlags - - -@dataclass -class ConfigureFeaturesCmd: - features: FeatureFlags - - -@dataclass -class CalibrateNewUserCmd: - frames_data: list[SerializedFrame] - - -@dataclass -class RemoveAllUsersCmd: - pass - - -@dataclass -class RestoreGalleryCmd: - frames_per_user_data: list[list[SerializedFrame]] - - -@dataclass -class EnableFaceIdCmd: - enabled: bool - - -@dataclass -class ProcessVideoCmd: - input_path: str - output_path: str - features: FeatureFlags - gallery_paths: list[str] - remove_all_users: bool - fps: float - width: int - height: int - total_frames: int - max_fps: float | None = None - min_fps: float | None = None - - -@dataclass -class ShutdownCmd: - pass - - -@dataclass -class StartProfilingCmd: - pass - - -@dataclass -class StopProfilingCmd: - pass - - -@dataclass -class GetProfileStatsCmd: - pass - - -@dataclass -class GetTimingStatsCmd: - reset: bool = True - - -WorkerCmd = ( - InferenceCmd - | ConfigureFeaturesCmd - | CalibrateNewUserCmd - | RemoveAllUsersCmd - | RestoreGalleryCmd - | EnableFaceIdCmd - | ProcessVideoCmd - | ShutdownCmd - | StartProfilingCmd - | StopProfilingCmd - | GetProfileStatsCmd - | GetTimingStatsCmd -) - - -# --------------------------------------------------------------------------- -# Responses (worker process → main process) -# --------------------------------------------------------------------------- - - -@dataclass -class ReadyResponse: - pid: int - - -@dataclass -class ErrorResponse: - error: str - - -@dataclass -class HeartbeatResponse: - pass - - -@dataclass -class OkResponse: - pass - - -@dataclass -class InferenceResponse: - frame_bytes: bytes - shape: tuple[int, ...] - dtype: str - - -@dataclass -class CalibrateOkResponse: - result: CalibrationResultMsg - - -@dataclass -class RemoveUsersOkResponse: - result: bool - - -@dataclass -class RestoreGalleryOkResponse: - results: list[CalibrationResultMsg] - - -@dataclass -class GalleryRestoredResponse: - results: list[CalibrationResultMsg] - - -@dataclass -class ProgressResponse: - current: int - total: int - - -@dataclass -class VideoProcessingDoneResponse: - frames_processed: int - gallery_results: list[CalibrationResultMsg] - recycle: bool - - -@dataclass -class ProfileStatsResponse: - stats_data: bytes # marshalled pstats data - - -@dataclass -class TimingStatsResponse: - """Per-SDK-call timing data from EveWrapper.""" - - stats: dict[str, tuple[int, float]] # {name: (call_count, total_seconds)} - - -WorkerResponse = ( - ReadyResponse - | ErrorResponse - | HeartbeatResponse - | OkResponse - | InferenceResponse - | CalibrateOkResponse - | RemoveUsersOkResponse - | RestoreGalleryOkResponse - | GalleryRestoredResponse - | ProgressResponse - | VideoProcessingDoneResponse - | ProfileStatsResponse - | TimingStatsResponse -) +"""IPC message dataclasses for the Eve worker pool. + +All types are picklable and sent over ``multiprocessing.Pipe`` between the +main Gradio process and Eve SDK worker processes. +""" + +from dataclasses import dataclass + +# --------------------------------------------------------------------------- +# Shared data types +# --------------------------------------------------------------------------- + + +@dataclass +class CalibrationResultMsg: + """Picklable mirror of ``eve_wrapper.CalibrationResult``.""" + + success: bool + user_id: int + message: str + + +@dataclass +class ModelConfig: + """Resolved EVE object-detection (MOD) model description. + + Carries everything ``eve_wrapper.enable_object_detection`` needs to + configure one MOD model. ``model_path`` of ``None`` / ``""`` selects the + EVE SDK's bundled default model (GMOD-80); a path loads that ``.tflite`` + and applies ``class_names``. ``None`` thresholds fall back to the SDK + defaults. Picklable so it can ride inside ``FeatureFlags`` to a worker. + + Attributes: + name: Human-facing model name (e.g. ``"AMOD-8"``); used for logging. + model_path: Absolute path to the model file, or ``None`` for bundled. + class_names: Ordered class labels matching the model's output indices, + or ``None`` to keep the SDK's default class table. + nms_threshold: Optional non-max-suppression score threshold override. + iou_threshold: Optional IoU threshold override. + """ + + name: str + model_path: str | None = None + class_names: tuple[str, ...] | None = None + nms_threshold: float | None = None + iou_threshold: float | None = None + + +@dataclass +class FeatureFlags: + """Feature toggle bundle sent with inference / configure commands.""" + + face_detection: bool = True + person_detection: bool = True + face_id: bool = False + hand_gesture: bool = False + # None = MOD off. Otherwise the resolved MOD model the worker should load. + # Defaulted so demos that don't use MOD (e.g. eve_hmi) keep their pickle + # shape unchanged. + mod_model_config: ModelConfig | None = None + + +@dataclass +class SerializedFrame: + """Picklable representation of a numpy ndarray frame.""" + + data: bytes + shape: tuple[int, ...] + dtype: str + + +# --------------------------------------------------------------------------- +# Commands (main process → worker process) +# --------------------------------------------------------------------------- + + +@dataclass +class InferenceCmd: + frame_bytes: bytes + shape: tuple[int, ...] + dtype: str + features: FeatureFlags + + +@dataclass +class ConfigureFeaturesCmd: + features: FeatureFlags + + +@dataclass +class CalibrateNewUserCmd: + frames_data: list[SerializedFrame] + + +@dataclass +class RemoveAllUsersCmd: + pass + + +@dataclass +class RestoreGalleryCmd: + frames_per_user_data: list[list[SerializedFrame]] + + +@dataclass +class EnableFaceIdCmd: + enabled: bool + + +@dataclass +class ProcessVideoCmd: + input_path: str + output_path: str + features: FeatureFlags + gallery_paths: list[str] + remove_all_users: bool + fps: float + width: int + height: int + total_frames: int + max_fps: float | None = None + min_fps: float | None = None + + +@dataclass +class ShutdownCmd: + pass + + +@dataclass +class StartProfilingCmd: + pass + + +@dataclass +class StopProfilingCmd: + pass + + +@dataclass +class GetProfileStatsCmd: + pass + + +@dataclass +class GetTimingStatsCmd: + reset: bool = True + + +WorkerCmd = ( + InferenceCmd + | ConfigureFeaturesCmd + | CalibrateNewUserCmd + | RemoveAllUsersCmd + | RestoreGalleryCmd + | EnableFaceIdCmd + | ProcessVideoCmd + | ShutdownCmd + | StartProfilingCmd + | StopProfilingCmd + | GetProfileStatsCmd + | GetTimingStatsCmd +) + + +# --------------------------------------------------------------------------- +# Responses (worker process → main process) +# --------------------------------------------------------------------------- + + +@dataclass +class ReadyResponse: + pid: int + + +@dataclass +class ErrorResponse: + error: str + + +@dataclass +class HeartbeatResponse: + pass + + +@dataclass +class OkResponse: + pass + + +@dataclass +class InferenceResponse: + frame_bytes: bytes + shape: tuple[int, ...] + dtype: str + + +@dataclass +class CalibrateOkResponse: + result: CalibrationResultMsg + + +@dataclass +class RemoveUsersOkResponse: + result: bool + + +@dataclass +class RestoreGalleryOkResponse: + results: list[CalibrationResultMsg] + + +@dataclass +class GalleryRestoredResponse: + results: list[CalibrationResultMsg] + + +@dataclass +class ProgressResponse: + current: int + total: int + + +@dataclass +class VideoProcessingDoneResponse: + frames_processed: int + gallery_results: list[CalibrationResultMsg] + recycle: bool + + +@dataclass +class ProfileStatsResponse: + stats_data: bytes # marshalled pstats data + + +@dataclass +class TimingStatsResponse: + """Per-SDK-call timing data from EveWrapper.""" + + stats: dict[str, tuple[int, float]] # {name: (call_count, total_seconds)} + + +WorkerResponse = ( + ReadyResponse + | ErrorResponse + | HeartbeatResponse + | OkResponse + | InferenceResponse + | CalibrateOkResponse + | RemoveUsersOkResponse + | RestoreGalleryOkResponse + | GalleryRestoredResponse + | ProgressResponse + | VideoProcessingDoneResponse + | ProfileStatsResponse + | TimingStatsResponse +) diff --git a/shared/eve_python/ctypes_enum.py b/shared/eve_python/ctypes_enum.py new file mode 100644 index 0000000000000000000000000000000000000000..d764ee18bb82e3f2d1af7b504aaf384be07d7693 --- /dev/null +++ b/shared/eve_python/ctypes_enum.py @@ -0,0 +1,10 @@ +from enum import IntEnum + + +# Taken from https://v4.chriskrycho.com/2015/ctypes-structures-and-dll-exports.html +class CtypesEnum(IntEnum): + """A ctypes-compatible IntEnum superclass.""" + + @classmethod + def from_param(cls, obj): + return int(obj) diff --git a/shared/eve_python/eve_sdk.py b/shared/eve_python/eve_sdk.py index 8cc118a96fc94c95f3e8389cf79cf10a644af8f9..d25a5925e87700a8f75f16b34b6ac90942ec354e 100644 --- a/shared/eve_python/eve_sdk.py +++ b/shared/eve_python/eve_sdk.py @@ -1,297 +1,388 @@ -import ctypes -from . import eve_sdk_structs as structs - -EveProcessingCallbackFn = ctypes.CFUNCTYPE(None, ctypes.POINTER(structs.EveProcessingCallbackReturnData)) - -class EveSDK: - def __init__(self, dll_path: str): - self.cdll = ctypes.CDLL(dll_path) - # EveCameraApi.h - self.cdll.EveGetFormats.restype = structs.EveCameraFormats - self.cdll.EveGetFormats.argtypes = [ctypes.c_uint, structs.CCameraFormat] - self.cdll.EveGetCamera.restype = structs.EveCamera - self.cdll.EveGetCamera.argtypes = [ctypes.c_uint] - self.cdll.EveSetCamera.restype = structs.EveError - self.cdll.EveSetCamera.argtypes = [ctypes.c_uint, structs.CCameraFormat] - # EveControlInterface.h - self.cdll.CreateEve.restype = structs.EveError - self.cdll.CreateEve.argtypes = [structs.EveStartupParameters] - self.cdll.EveRegisterDataCallback.restype = structs.EveError - self.cdll.EveRegisterDataCallback.argtypes = [EveProcessingCallbackFn] - self.cdll.StartEve.restype = structs.EveError - self.cdll.StartEve.argtypes = [] - self.cdll.StartEveWithParameters.restype = structs.EveError - self.cdll.StartEveWithParameters.argtypes = [structs.EveProcessingParameters] - self.cdll.EveSendImageForProcessing.restype = structs.EveError - self.cdll.EveSendImageForProcessing.argtypes = [structs.EveInputImage] - self.cdll.EveSendImageForProcessingWithParams.restype = structs.EveError - self.cdll.EveSendImageForProcessingWithParams.argtypes = [structs.EveInputImage, structs.CCameraParameters] - self.cdll.ShutdownEve.restype = structs.EveError - self.cdll.ShutdownEve.argtypes = [] - - # EveKarolinska.h - self.cdll.EveConfigureKarolinska.restype = structs.EveKarolinskaOptions - self.cdll.EveConfigureKarolinska.argtypes = [structs.EveKarolinskaOptions] - - self.cdll.EveGetKarolinskaData.restype = structs.EveKarolinskaData - self.cdll.EveGetKarolinskaData.argtypes = [] - - # EveFaceId.h - self.cdll.EveConfigureFaceId.restype = structs.EveFaceIdOptions - self.cdll.EveConfigureFaceId.argtypes = [structs.EveFaceIdOptions] - self.cdll.EveFaceIdCalibrateCurrent.restype = structs.EveError - self.cdll.EveFaceIdCalibrateCurrent.argtypes = [] - self.cdll.EveFaceIdCalibrateNew.restype = structs.EveError - self.cdll.EveFaceIdCalibrateNew.argtypes = [] - self.cdll.EveFaceIdForceIdentify.restype = structs.EveError - self.cdll.EveFaceIdForceIdentify.argtypes = [] - self.cdll.EveFaceIdRemoveCurrent.restype = structs.EveError - self.cdll.EveFaceIdRemoveCurrent.argtypes = [] - self.cdll.EveFaceIdRemoveAll.restype = structs.EveError - self.cdll.EveFaceIdRemoveAll.argtypes = [] - self.cdll.EveFaceIdReloadGallery.restype = structs.EveError - self.cdll.EveFaceIdReloadGallery.argtypes = [] - self.cdll.EveFaceIdCommandWaiting.restype = ctypes.c_uint - self.cdll.EveFaceIdCommandWaiting.argtypes = [] - self.cdll.EveGetFaceIdData.restype = structs.EveFaceIdData - self.cdll.EveGetFaceIdData.argtypes = [] - self.cdll.EveSendFaceIdCommand.restype = structs.EveFaceIdCommandStruct - self.cdll.EveSendFaceIdCommand.argtypes = [structs.EveFaceIdCommandStruct] - # EveFaceTracker.h - self.cdll.EveConfigureFaceTracker.restype = structs.EveFaceTrackerOptions - self.cdll.EveConfigureFaceTracker.argtypes = [structs.EveFaceTrackerOptions] - self.cdll.EveGetAllFaceData.restype = structs.EveAllFacesData - self.cdll.EveGetAllFaceData.argtypes = [] - # EveFpga.h - self.cdll.EveConfigureFpga.restype = structs.EveFpgaOptions - self.cdll.EveConfigureFpga.argtypes = [structs.EveFpgaOptions] - self.cdll.EveConfigureFpgaDebug.restype = structs.EveFpgaDebugOptions - self.cdll.EveConfigureFpgaDebug.argtypes = [structs.EveFpgaDebugOptions] - self.cdll.QueryFpgaSetting.restype = structs.EveError - self.cdll.QueryFpgaSetting.argtypes = [structs.pipeline_config_t, ctypes.c_bool] - self.cdll.QueryFpgaSettings.restype = structs.EveError - self.cdll.QueryFpgaSettings.argtypes = [ctypes.c_uint16, ctypes.c_uint32, ctypes.c_bool] - self.cdll.SendSetSetting.restype = structs.EveError - self.cdll.SendSetSetting.argtypes = [structs.pipeline_config_t] - self.cdll.PopQueuedSetting.restype = structs.CFpgaGetSetting - self.cdll.PopQueuedSetting.argtypes = [] - self.cdll.EveGetFpgaData.restype = structs.EveFpgaData - self.cdll.EveGetFpgaData.argtypes = [] - self.cdll.FpgaReadJson.restype = structs.EveFpgaJsonMetadata - self.cdll.FpgaReadJson.argtypes = [] - self.cdll.EveSendImageForProcessingWithFpgaData.restype = structs.EveError - self.cdll.EveSendImageForProcessingWithFpgaData.argtypes = [structs.EveInputImage, structs.EveFpgaManualData] - - # EveImageManipulation.h - self.cdll.EveConfigureImageManipulation.restype = structs.EveImageManipulationOptions - self.cdll.EveConfigureImageManipulation.argtypes = [structs.EveImageManipulationOptions] - - # EveImage.h - self.cdll.EveGetProcessedImage.restype = structs.EveProcessedImage - self.cdll.EveGetProcessedImage.argtypes = [] - self.cdll.EveGetProcessedFrameTime.restype = structs.EveProcessedFrameTime - self.cdll.EveGetProcessedFrameTime.argtypes = [] - self.cdll.EveConfigureProcessedImage.restype = structs.EveImageFormatRequest - self.cdll.EveConfigureProcessedImage.argtypes = [structs.EveImageFormatRequest] - - # EveObjectDetection.h - self.cdll.EveConfigureObjectDetection.restype = structs.EveObjectDetectionOptions - self.cdll.EveConfigureObjectDetection.argtypes = [] - self.cdll.EveConfigurePersonDetection.restype = structs.EvePersonDetectionOptions - self.cdll.EveConfigurePersonDetection.argtypes = [] - self.cdll.EveGetObjectDetectionData.restype = structs.EveDetectionData - self.cdll.EveGetObjectDetectionData.argtypes = [] - self.cdll.EveCopyObjectDetectionData.restype = structs.EveDetectionData - self.cdll.EveCopyObjectDetectionData.argtypes = [] - self.cdll.EveGetPersonDetectionData.restype = structs.EveDetectionData - self.cdll.EveGetPersonDetectionData.argtypes = [] - self.cdll.EveCopyPersonDetectionData.restype = structs.EveDetectionData - self.cdll.EveCopyPersonDetectionData.argtypes = [] - self.cdll.DeleteDetectionData.restype = structs.EveError - self.cdll.DeleteDetectionData.argtypes = [structs.EveDetectionData] - # EveROI.h - self.cdll.EveConfigureROIs.restype = structs.EveROIOptions - self.cdll.EveConfigureROIs.argtypes = [structs.EveROIOptions] - self.cdll.EveGetROIScoreData.restype = structs.EveROIScoreData - self.cdll.EveGetROIScoreData.argtypes = [] - # EveHandGesture.h - self.cdll.EveConfigureHandGesture.restype = structs.EveHandGestureOptions - self.cdll.EveConfigureHandGesture.argtypes = [structs.EveHandGestureOptions] - self.cdll.EveGetHandGestureData.restype = structs.EveHandGestureData - self.cdll.EveGetHandGestureData.argtypes = [] - self.cdll.EveCopyHandGestureData.restype = structs.EveHandGestureData - self.cdll.EveCopyHandGestureData.argtypes = [] - self.cdll.EveDeleteHandGestureData.restype = structs.EveError - self.cdll.EveDeleteHandGestureData.argtypes = [structs.EveHandGestureData] - self.cdll.EveGetStaticGestureDetections.restype = structs.EveStaticGestureData - self.cdll.EveGetStaticGestureDetections.argtypes = [] - self.cdll.EveGetDynamicGestureDetections.restype = structs.EveDynamicGestureData - self.cdll.EveGetDynamicGestureDetections.argtypes = [] - - - # EveCamera.h - def EveGetFormats(self, cameraId: ctypes.c_uint, filter: structs.CCameraFormat) -> structs.EveCameraFormats: - return self.cdll.EveGetFormats(cameraId, filter) - - def EveGetCamera(self, cameraId: ctypes.c_uint) -> structs.EveCamera: - return self.cdll.EveGetCamera(cameraId) - - def EveSetCamera(self, cameraId: ctypes.c_uint, filter: structs.CCameraFormat) -> structs.EveError: - return self.cdll.EveSetCamera(cameraId, filter) - - # EveControlInterface.h - def CreateEve(self, options: structs.EveStartupParameters) -> structs.EveError: - return self.cdll.CreateEve(options) - - def EveRegisterDataCallback(self, callback) -> structs.EveError: - return self.cdll.EveRegisterDataCallback(callback) - - def StartEve(self) -> structs.EveError: - return self.cdll.StartEve() - - def StartEveWithParameters(self, parameters: structs.EveProcessingParameters) -> structs.EveError: - return self.cdll.StartEveWithParameters(parameters) - - def EveSendImageForProcessing(self, image: structs.EveInputImage) -> structs.EveError: - return self.cdll.EveSendImageForProcessing(image) - - def EveSendImageForProcessingWithParams(self, image: structs.EveInputImage, params: structs.CCameraParameters) -> structs.EveError: - return self.cdll.EveSendImageForProcessingWithParams(image, params) - - def EveSendFpgaDataManually(self, image: structs.EveFpgaManualData) -> structs.EveError: - return self.cdll.EveSendFpgaDataManually(image) - - def ShutdownEve(self) -> structs.EveError: - return self.cdll.ShutdownEve() - - # EveFaceId.h - def EveConfigureFaceId(self, options: structs.EveFaceIdOptions) -> structs.EveFaceIdOptions: - return self.cdll.EveConfigureFaceId(options) - - def EveFaceIdCalibrateCurrent(self) -> structs.EveError: - return self.cdll.EveFaceIdCalibrateCurrent() - - def EveFaceIdCalibrateNew(self) -> structs.EveError: - return self.cdll.EveFaceIdCalibrateNew() - - def EveFaceIdForceIdentify(self) -> structs.EveError: - return self.cdll.EveFaceIdForceIdentify() - - def EveFaceIdRemoveCurrent(self) -> structs.EveError: - return self.cdll.EveFaceIdRemoveCurrent() - - def EveFaceIdRemoveAll(self) -> structs.EveError: - return self.cdll.EveFaceIdRemoveAll() - - def EveFaceIdReloadGallery(self) -> structs.EveError: - return self.cdll.EveFaceIdReloadGallery() - - def EveFaceIdCommandWaiting(self) -> ctypes.c_uint: - return self.cdll.EveFaceIdCommandWaiting() - - def EveGetFaceIdData(self) -> structs.EveFaceIdData: - return self.cdll.EveGetFaceIdData() - - def EveSendFaceIdCommand(self, command: structs.EveFaceIdCommandStruct) -> structs.EveFaceIdCommandStruct: - return self.cdll.EveSendFaceIdCommand(command) - - # EveFaceTracker.h - def EveConfigureFaceTracker(self, options: structs.EveFaceTrackerOptions) -> structs.EveFaceTrackerOptions: - return self.cdll.EveConfigureFaceTracker(options) - - def EveGetAllFaceData(self) -> structs.EveAllFacesData: - return self.cdll.EveGetAllFaceData() - - # EveFpga.h - def EveGetFpgaData(self) -> structs.EveFpgaData: - return self.cdll.EveGetFpgaData() - - def EveConfigureFpga(self, options: structs.EveFpgaOptions) -> structs.EveFpgaOptions: - return self.cdll.EveConfigureFpga(options) - - def EveConfigureFpgaDebug(self, options: structs.EveFpgaDebugOptions) -> structs.EveFpgaDebugOptions: - return self.cdll.EveConfigureFpgaDebug(options) - - def QueryFpgaSetting(self, command: structs.pipeline_config_t, notify: ctypes.c_bool) -> structs.EveError: - return self.cdll.QueryFpgaSetting(command, notify) - - def QueryFpgaSettings(self, typeMask: ctypes.c_uint16, settingsMask: ctypes.c_uint32, notify: ctypes.c_bool) -> structs.EveError: - return self.cdll.QueryFpgaSettings(typeMask, settingsMask, notify) - - def SendSetSetting(self, command: structs.pipeline_config_t) -> structs.EveError: - return self.cdll.SendSetSetting(command) - - def PopQueuedSetting(self) -> structs.CFpgaGetSetting: - return self.cdll.PopQueuedSetting() - - def FpgaReadJson(self) -> structs.EveFpgaJsonMetadata: - return self.cdll.FpgaReadJson() - - # EveKarolinksa.h - def EveConfigureKarolinska(self, parameters: structs.EveKarolinskaOptions) -> structs.EveKarolinskaOptions: - return self.cdll.EveConfigureKarolinska(parameters) - - def EveGetKarolinskaData(self) -> structs.EveKarolinskaData: - return self.cdll.EveGetKarolinskaData() - - # EveImageManipulation.h - def EveConfigureImageManipulation(self, options: structs.EveImageManipulationOptions) -> structs.EveImageManipulationOptions: - return self.cdll.EveConfigureImageManipulation(options) - - # EveImage.h - def EveGetProcessedImage(self) -> structs.EveProcessedImage: - return self.cdll.EveGetProcessedImage() - - def EveGetProcessedFrameTime(self) -> structs.EveProcessedFrameTime: - return self.cdll.EveGetProcessedFrameTime() - - def EveConfigureProcessedImage(self, fmt: structs.EveImageFormatRequest) -> structs.EveProcessedFrameTime: - return self.cdll.EveConfigureProcessedImage(fmt) - - # EveObjectDetection.h - def EveConfigureObjectDetection(self, enabled: structs.EveObjectDetectionOptions) -> structs.EveObjectDetectionOptions: - return self.cdll.EveConfigureObjectDetection(enabled) - - def EveConfigurePersonDetection(self, enabled: structs.EvePersonDetectionOptions) -> structs.EvePersonDetectionOptions: - return self.cdll.EveConfigurePersonDetection(enabled) - - def EveGetObjectDetectionData(self) -> structs.EveDetectionData: - return self.cdll.EveGetObjectDetectionData() - - def EveCopyObjectDetectionData(self) -> structs.EveDetectionData: - return self.cdll.EveCopyObjectDetectionData() - - def EveGetPersonDetectionData(self) -> structs.EveDetectionData: - return self.cdll.EveGetPersonDetectionData() - - def EveCopyPersonDetectionData(self) -> structs.EveDetectionData: - return self.cdll.EveCopyPersonDetectionData() - - def DeleteDetectionData(self, data: structs.EveDetectionData) -> structs.EveError: - return self.cdll.DeleteDetectionData(data) - - # EveROI.h - def EveConfigureROIs(self, options: structs.EveROIOptions) -> structs.EveError: - return self.cdll.EveConfigureROIs(options) - - def EveGetROIScoreData(self) -> structs.EveROIScoreData: - return self.cdll.EveGetROIScoreData() - - # EveHandGesture.h - def EveConfigureHandGesture(self, options: structs.EveHandGestureOptions) -> structs.EveHandGestureOptions: - return self.cdll.EveConfigureHandGesture(options) - - def EveGetHandGestureData(self) -> structs.EveHandGestureData: - return self.cdll.EveGetHandGestureData() - - def EveCopyHandGestureData(self) -> structs.EveHandGestureData: - return self.cdll.EveGetHandGestureData() - - def EveDeleteHandGestureData(self, data: structs.EveHandGestureData) -> structs.EveError: - return self.cdll.EveGetHandGestureData(data) - - def EveGetStaticGestureDetections(self) -> structs.EveStaticGestureData: - return self.cdll.EveGetStaticGestureDetections() - - def EveGetDynamicGestureDetections(self) -> structs.EveDynamicGestureData: - return self.cdll.EveGetDynamicGestureDetections() \ No newline at end of file +import ctypes + +from . import eve_sdk_structs as structs + +EveProcessingCallbackFn = ctypes.CFUNCTYPE(None, ctypes.POINTER(structs.EveProcessingCallbackReturnData)) + +class EveSDK: + def __init__(self, dll_path: str): + self.cdll = ctypes.CDLL(dll_path) + # EveCamera.h + self.cdll.EveGetFormats.restype = structs.EveCameraFormats + self.cdll.EveGetFormats.argtypes = [ctypes.c_uint, structs.CCameraFormat] + self.cdll.EveGetCamera.restype = structs.EveCamera + self.cdll.EveGetCamera.argtypes = [ctypes.c_uint] + self.cdll.EveSetCamera.restype = structs.EveError + self.cdll.EveSetCamera.argtypes = [ctypes.c_uint, structs.CCameraFormat] + self.cdll.EveGetNumberOfCameras.restype = structs.EveNumberOfCameras + self.cdll.EveGetNumberOfCameras.argtypes = [] + self.cdll.EveGetMonitors.restype = structs.EveMonitors + self.cdll.EveGetMonitors.argtypes = [] + self.cdll.EveChangeCameraLocation.restype = structs.EveCameraLocation + self.cdll.EveChangeCameraLocation.argtypes = [structs.EveCameraLocation] + self.cdll.EveGetLiveCameraParameters.restype = structs.EveCameraParameters + self.cdll.EveGetLiveCameraParameters.argtypes = [] + # EveControlInterface.h + self.cdll.CreateEve.restype = structs.EveError + self.cdll.CreateEve.argtypes = [structs.EveStartupParameters] + self.cdll.EveRegisterDataCallback.restype = structs.EveError + self.cdll.EveRegisterDataCallback.argtypes = [EveProcessingCallbackFn] + self.cdll.StartEve.restype = structs.EveError + self.cdll.StartEve.argtypes = [] + self.cdll.StartEveWithParameters.restype = structs.EveError + self.cdll.StartEveWithParameters.argtypes = [structs.EveProcessingParameters] + self.cdll.EveSendImageForProcessing.restype = structs.EveError + self.cdll.EveSendImageForProcessing.argtypes = [structs.EveInputImage] + self.cdll.EveSendImageForProcessingWithParams.restype = structs.EveError + self.cdll.EveSendImageForProcessingWithParams.argtypes = [structs.EveInputImage, structs.CCameraParameters] + self.cdll.ShutdownEve.restype = structs.EveError + self.cdll.ShutdownEve.argtypes = [] + self.cdll.EveCheckForLicense.restype = structs.EveLicenseResult + self.cdll.EveCheckForLicense.argtypes = [] + self.cdll.EveResetPipeline.restype = structs.EveError + self.cdll.EveResetPipeline.argtypes = [] + + # EveKarolinska.h + self.cdll.EveConfigureKarolinska.restype = structs.EveKarolinskaOptions + self.cdll.EveConfigureKarolinska.argtypes = [structs.EveKarolinskaOptions] + + self.cdll.EveGetKarolinskaData.restype = structs.EveKarolinskaData + self.cdll.EveGetKarolinskaData.argtypes = [] + + # EveFaceId.h + self.cdll.EveConfigureFaceId.restype = structs.EveFaceIdOptions + self.cdll.EveConfigureFaceId.argtypes = [structs.EveFaceIdOptions] + self.cdll.EveFaceIdCalibrateCurrent.restype = structs.EveError + self.cdll.EveFaceIdCalibrateCurrent.argtypes = [] + self.cdll.EveFaceIdCalibrateNew.restype = structs.EveError + self.cdll.EveFaceIdCalibrateNew.argtypes = [] + self.cdll.EveFaceIdForceIdentify.restype = structs.EveError + self.cdll.EveFaceIdForceIdentify.argtypes = [] + self.cdll.EveFaceIdRemoveCurrent.restype = structs.EveError + self.cdll.EveFaceIdRemoveCurrent.argtypes = [] + self.cdll.EveFaceIdRemoveAll.restype = structs.EveError + self.cdll.EveFaceIdRemoveAll.argtypes = [] + self.cdll.EveFaceIdReloadGallery.restype = structs.EveError + self.cdll.EveFaceIdReloadGallery.argtypes = [] + self.cdll.EveFaceIdCommandWaiting.restype = ctypes.c_uint + self.cdll.EveFaceIdCommandWaiting.argtypes = [] + self.cdll.EveGetFaceIdData.restype = structs.EveFaceIdData + self.cdll.EveGetFaceIdData.argtypes = [] + self.cdll.EveSendFaceIdCommand.restype = structs.EveFaceIdCommandStruct + self.cdll.EveSendFaceIdCommand.argtypes = [structs.EveFaceIdCommandStruct] + # EveFaceTracker.h + self.cdll.EveConfigureFaceTracker.restype = structs.EveFaceTrackerOptions + self.cdll.EveConfigureFaceTracker.argtypes = [structs.EveFaceTrackerOptions] + self.cdll.EveGetEyesData.restype = structs.EveEyes + self.cdll.EveGetEyesData.argtypes = [] + self.cdll.EveGetPupilsData.restype = structs.EvePupils + self.cdll.EveGetPupilsData.argtypes = [] + self.cdll.EveGetAllFaceData.restype = structs.EveAllFacesData + self.cdll.EveGetAllFaceData.argtypes = [] + # EveFpga.h + self.cdll.EveConfigureFpga.restype = structs.EveFpgaOptions + self.cdll.EveConfigureFpga.argtypes = [structs.EveFpgaOptions] + self.cdll.EveConfigureFpgaDebug.restype = structs.EveFpgaDebugOptions + self.cdll.EveConfigureFpgaDebug.argtypes = [structs.EveFpgaDebugOptions] + self.cdll.QueryFpgaSetting.restype = structs.EveError + self.cdll.QueryFpgaSetting.argtypes = [structs.pipeline_config_t, ctypes.c_bool] + self.cdll.QueryFpgaSettings.restype = structs.EveError + self.cdll.QueryFpgaSettings.argtypes = [ctypes.c_uint16, ctypes.c_uint32, ctypes.c_bool] + self.cdll.SendSetSetting.restype = structs.EveError + self.cdll.SendSetSetting.argtypes = [structs.pipeline_config_t] + self.cdll.PopQueuedSetting.restype = structs.CFpgaGetSetting + self.cdll.PopQueuedSetting.argtypes = [] + self.cdll.EveGetFpgaData.restype = structs.EveFpgaData + self.cdll.EveGetFpgaData.argtypes = [] + self.cdll.FpgaReadJson.restype = structs.EveFpgaJsonMetadata + self.cdll.FpgaReadJson.argtypes = [] + self.cdll.FpgaReadData.restype = structs.EveFpgaData + self.cdll.FpgaReadData.argtypes = [] + self.cdll.EveSendImageForProcessingWithFpgaData.restype = structs.EveError + self.cdll.EveSendImageForProcessingWithFpgaData.argtypes = [structs.EveInputImage, structs.EveFpgaManualData] + + # EveImageManipulation.h + self.cdll.EveConfigureImageManipulation.restype = structs.EveImageManipulationOptions + self.cdll.EveConfigureImageManipulation.argtypes = [structs.EveImageManipulationOptions] + + # EveImage.h + self.cdll.EveGetProcessedImage.restype = structs.EveProcessedImage + self.cdll.EveGetProcessedImage.argtypes = [] + self.cdll.EveGetProcessedFrameTime.restype = structs.EveProcessedFrameTime + self.cdll.EveGetProcessedFrameTime.argtypes = [] + self.cdll.EveConfigureProcessedImage.restype = structs.EveImageFormatRequest + self.cdll.EveConfigureProcessedImage.argtypes = [structs.EveImageFormatRequest] + self.cdll.EveConfigureRawImage.restype = structs.EveRawImageRequest + self.cdll.EveConfigureRawImage.argtypes = [structs.EveRawImageRequest] + self.cdll.EveGetRawImage.restype = structs.EveProcessedImage + self.cdll.EveGetRawImage.argtypes = [] + self.cdll.EveGetJsonData.restype = structs.EveJsonData + self.cdll.EveGetJsonData.argtypes = [] + + # EveObjectDetection.h + self.cdll.EveConfigureObjectDetection.restype = structs.EveObjectDetectionOptions + self.cdll.EveConfigureObjectDetection.argtypes = [structs.EveObjectDetectionOptions] + self.cdll.EveGetDefaultObjectDetectionOptions.restype = structs.EveObjectDetectionOptions + self.cdll.EveGetDefaultObjectDetectionOptions.argtypes = [] + self.cdll.EveConfigurePersonDetection.restype = structs.EvePersonDetectionOptions + self.cdll.EveConfigurePersonDetection.argtypes = [structs.EvePersonDetectionOptions] + self.cdll.EveGetObjectDetectionData.restype = structs.EveDetectionData + self.cdll.EveGetObjectDetectionData.argtypes = [] + self.cdll.EveCopyObjectDetectionData.restype = structs.EveDetectionData + self.cdll.EveCopyObjectDetectionData.argtypes = [] + self.cdll.EveGetPersonDetectionData.restype = structs.EveDetectionData + self.cdll.EveGetPersonDetectionData.argtypes = [] + self.cdll.EveCopyPersonDetectionData.restype = structs.EveDetectionData + self.cdll.EveCopyPersonDetectionData.argtypes = [] + self.cdll.DeleteDetectionData.restype = structs.EveError + self.cdll.DeleteDetectionData.argtypes = [structs.EveDetectionData] + # EveROI.h + self.cdll.EveConfigureROIs.restype = structs.EveROIOptions + self.cdll.EveConfigureROIs.argtypes = [structs.EveROIOptions] + self.cdll.EveGetROIScoreData.restype = structs.EveROIScoreData + self.cdll.EveGetROIScoreData.argtypes = [] + # EveHandGesture.h + self.cdll.EveConfigureHandGesture.restype = structs.EveHandGestureOptions + self.cdll.EveConfigureHandGesture.argtypes = [structs.EveHandGestureOptions] + self.cdll.EveGetHandGestureData.restype = structs.EveHandGestureData + self.cdll.EveGetHandGestureData.argtypes = [] + self.cdll.EveCopyHandGestureData.restype = structs.EveHandGestureData + self.cdll.EveCopyHandGestureData.argtypes = [] + self.cdll.EveDeleteHandGestureData.restype = structs.EveError + self.cdll.EveDeleteHandGestureData.argtypes = [structs.EveHandGestureData] + self.cdll.EveGetStaticGestureDetections.restype = structs.EveStaticGestureData + self.cdll.EveGetStaticGestureDetections.argtypes = [] + self.cdll.EveGetStaticGestureDefinitions.restype = structs.EveStaticGestureDefinitions + self.cdll.EveGetStaticGestureDefinitions.argtypes = [] + self.cdll.EveAddStaticGesture.restype = structs.EveError + self.cdll.EveAddStaticGesture.argtypes = [structs.EveStaticGestureDefinition] + self.cdll.EveDeleteStaticGesture.restype = structs.EveError + self.cdll.EveDeleteStaticGesture.argtypes = [structs.EveStaticGestureDefinition] + # EveAlgorithm.h + self.cdll.EveGetAlgorithms.restype = structs.EveSupportedAlgorithms + self.cdll.EveGetAlgorithms.argtypes = [] + + # EveTiming.h + self.cdll.EveGetTimingInformation.restype = structs.EveTiming + self.cdll.EveGetTimingInformation.argtypes = [] + + + # EveCamera.h + def EveGetFormats(self, cameraId: ctypes.c_uint, filter: structs.CCameraFormat) -> structs.EveCameraFormats: + return self.cdll.EveGetFormats(cameraId, filter) + + def EveGetCamera(self, cameraId: ctypes.c_uint) -> structs.EveCamera: + return self.cdll.EveGetCamera(cameraId) + + def EveSetCamera(self, cameraId: ctypes.c_uint, filter: structs.CCameraFormat) -> structs.EveError: + return self.cdll.EveSetCamera(cameraId, filter) + + def EveGetNumberOfCameras(self) -> structs.EveNumberOfCameras: + return self.cdll.EveGetNumberOfCameras() + + def EveGetMonitors(self) -> structs.EveMonitors: + return self.cdll.EveGetMonitors() + + def EveChangeCameraLocation(self, location: structs.EveCameraLocation) -> structs.EveCameraLocation: + return self.cdll.EveChangeCameraLocation(location) + + def EveGetLiveCameraParameters(self) -> structs.EveCameraParameters: + return self.cdll.EveGetLiveCameraParameters() + + # EveControlInterface.h + def CreateEve(self, options: structs.EveStartupParameters) -> structs.EveError: + return self.cdll.CreateEve(options) + + def EveRegisterDataCallback(self, callback) -> structs.EveError: + return self.cdll.EveRegisterDataCallback(callback) + + def StartEve(self) -> structs.EveError: + return self.cdll.StartEve() + + def StartEveWithParameters(self, parameters: structs.EveProcessingParameters) -> structs.EveError: + return self.cdll.StartEveWithParameters(parameters) + + def EveSendImageForProcessing(self, image: structs.EveInputImage) -> structs.EveError: + return self.cdll.EveSendImageForProcessing(image) + + def EveSendImageForProcessingWithParams(self, image: structs.EveInputImage, params: structs.CCameraParameters) -> structs.EveError: + return self.cdll.EveSendImageForProcessingWithParams(image, params) + + def EveSendImageForProcessingWithFpgaData(self, image: structs.EveInputImage, data: structs.EveFpgaManualData) -> structs.EveError: + return self.cdll.EveSendImageForProcessingWithFpgaData(image, data) + + def ShutdownEve(self) -> structs.EveError: + return self.cdll.ShutdownEve() + + def EveCheckForLicense(self) -> structs.EveLicenseResult: + return self.cdll.EveCheckForLicense() + + def EveResetPipeline(self) -> structs.EveError: + return self.cdll.EveResetPipeline() + + # EveFaceId.h + def EveConfigureFaceId(self, options: structs.EveFaceIdOptions) -> structs.EveFaceIdOptions: + return self.cdll.EveConfigureFaceId(options) + + def EveFaceIdCalibrateCurrent(self) -> structs.EveError: + return self.cdll.EveFaceIdCalibrateCurrent() + + def EveFaceIdCalibrateNew(self) -> structs.EveError: + return self.cdll.EveFaceIdCalibrateNew() + + def EveFaceIdForceIdentify(self) -> structs.EveError: + return self.cdll.EveFaceIdForceIdentify() + + def EveFaceIdRemoveCurrent(self) -> structs.EveError: + return self.cdll.EveFaceIdRemoveCurrent() + + def EveFaceIdRemoveAll(self) -> structs.EveError: + return self.cdll.EveFaceIdRemoveAll() + + def EveFaceIdReloadGallery(self) -> structs.EveError: + return self.cdll.EveFaceIdReloadGallery() + + def EveFaceIdCommandWaiting(self) -> ctypes.c_uint: + return self.cdll.EveFaceIdCommandWaiting() + + def EveGetFaceIdData(self) -> structs.EveFaceIdData: + return self.cdll.EveGetFaceIdData() + + def EveSendFaceIdCommand(self, command: structs.EveFaceIdCommandStruct) -> structs.EveFaceIdCommandStruct: + return self.cdll.EveSendFaceIdCommand(command) + + # EveFaceTracker.h + def EveConfigureFaceTracker(self, options: structs.EveFaceTrackerOptions) -> structs.EveFaceTrackerOptions: + return self.cdll.EveConfigureFaceTracker(options) + + def EveGetEyesData(self) -> structs.EveEyes: + return self.cdll.EveGetEyesData() + + def EveGetPupilsData(self) -> structs.EvePupils: + return self.cdll.EveGetPupilsData() + + def EveGetAllFaceData(self) -> structs.EveAllFacesData: + return self.cdll.EveGetAllFaceData() + + # EveFpga.h + def EveGetFpgaData(self) -> structs.EveFpgaData: + return self.cdll.EveGetFpgaData() + + def EveConfigureFpga(self, options: structs.EveFpgaOptions) -> structs.EveFpgaOptions: + return self.cdll.EveConfigureFpga(options) + + def EveConfigureFpgaDebug(self, options: structs.EveFpgaDebugOptions) -> structs.EveFpgaDebugOptions: + return self.cdll.EveConfigureFpgaDebug(options) + + def QueryFpgaSetting(self, command: structs.pipeline_config_t, notify: ctypes.c_bool) -> structs.EveError: + return self.cdll.QueryFpgaSetting(command, notify) + + def QueryFpgaSettings(self, typeMask: ctypes.c_uint16, settingsMask: ctypes.c_uint32, notify: ctypes.c_bool) -> structs.EveError: + return self.cdll.QueryFpgaSettings(typeMask, settingsMask, notify) + + def SendSetSetting(self, command: structs.pipeline_config_t) -> structs.EveError: + return self.cdll.SendSetSetting(command) + + def PopQueuedSetting(self) -> structs.CFpgaGetSetting: + return self.cdll.PopQueuedSetting() + + def FpgaReadJson(self) -> structs.EveFpgaJsonMetadata: + return self.cdll.FpgaReadJson() + + def FpgaReadData(self) -> structs.EveFpgaData: + return self.cdll.FpgaReadData() + + # EveKarolinska.h + def EveConfigureKarolinska(self, parameters: structs.EveKarolinskaOptions) -> structs.EveKarolinskaOptions: + return self.cdll.EveConfigureKarolinska(parameters) + + def EveGetKarolinskaData(self) -> structs.EveKarolinskaData: + return self.cdll.EveGetKarolinskaData() + + # EveImageManipulation.h + def EveConfigureImageManipulation(self, options: structs.EveImageManipulationOptions) -> structs.EveImageManipulationOptions: + return self.cdll.EveConfigureImageManipulation(options) + + # EveImage.h + def EveGetProcessedImage(self) -> structs.EveProcessedImage: + return self.cdll.EveGetProcessedImage() + + def EveGetProcessedFrameTime(self) -> structs.EveProcessedFrameTime: + return self.cdll.EveGetProcessedFrameTime() + + def EveConfigureProcessedImage(self, fmt: structs.EveImageFormatRequest) -> structs.EveImageFormatRequest: + return self.cdll.EveConfigureProcessedImage(fmt) + + def EveConfigureRawImage(self, fmt: structs.EveRawImageRequest) -> structs.EveRawImageRequest: + return self.cdll.EveConfigureRawImage(fmt) + + def EveGetRawImage(self) -> structs.EveProcessedImage: + return self.cdll.EveGetRawImage() + + def EveGetJsonData(self) -> structs.EveJsonData: + return self.cdll.EveGetJsonData() + + # EveObjectDetection.h + def EveConfigureObjectDetection(self, options: structs.EveObjectDetectionOptions) -> structs.EveObjectDetectionOptions: + return self.cdll.EveConfigureObjectDetection(options) + + def EveGetDefaultObjectDetectionOptions(self) -> structs.EveObjectDetectionOptions: + return self.cdll.EveGetDefaultObjectDetectionOptions() + + def EveConfigurePersonDetection(self, options: structs.EvePersonDetectionOptions) -> structs.EvePersonDetectionOptions: + return self.cdll.EveConfigurePersonDetection(options) + + def EveGetObjectDetectionData(self) -> structs.EveDetectionData: + return self.cdll.EveGetObjectDetectionData() + + def EveCopyObjectDetectionData(self) -> structs.EveDetectionData: + return self.cdll.EveCopyObjectDetectionData() + + def EveGetPersonDetectionData(self) -> structs.EveDetectionData: + return self.cdll.EveGetPersonDetectionData() + + def EveCopyPersonDetectionData(self) -> structs.EveDetectionData: + return self.cdll.EveCopyPersonDetectionData() + + def DeleteDetectionData(self, data: structs.EveDetectionData) -> structs.EveError: + return self.cdll.DeleteDetectionData(data) + + # EveROI.h + def EveConfigureROIs(self, options: structs.EveROIOptions) -> structs.EveROIOptions: + return self.cdll.EveConfigureROIs(options) + + def EveGetROIScoreData(self) -> structs.EveROIScoreData: + return self.cdll.EveGetROIScoreData() + + # EveHandGesture.h + def EveConfigureHandGesture(self, options: structs.EveHandGestureOptions) -> structs.EveHandGestureOptions: + return self.cdll.EveConfigureHandGesture(options) + + def EveGetHandGestureData(self) -> structs.EveHandGestureData: + return self.cdll.EveGetHandGestureData() + + def EveCopyHandGestureData(self) -> structs.EveHandGestureData: + return self.cdll.EveCopyHandGestureData() + + def EveDeleteHandGestureData(self, data: structs.EveHandGestureData) -> structs.EveError: + return self.cdll.EveDeleteHandGestureData(data) + + def EveGetStaticGestureDetections(self) -> structs.EveStaticGestureData: + return self.cdll.EveGetStaticGestureDetections() + + def EveGetStaticGestureDefinitions(self) -> structs.EveStaticGestureDefinitions: + return self.cdll.EveGetStaticGestureDefinitions() + + def EveAddStaticGesture(self, definition: structs.EveStaticGestureDefinition) -> structs.EveError: + return self.cdll.EveAddStaticGesture(definition) + + def EveDeleteStaticGesture(self, definition: structs.EveStaticGestureDefinition) -> structs.EveError: + return self.cdll.EveDeleteStaticGesture(definition) + + # EveAlgorithm.h + def EveGetAlgorithms(self) -> structs.EveSupportedAlgorithms: + return self.cdll.EveGetAlgorithms() + + # EveTiming.h + def EveGetTimingInformation(self) -> structs.EveTiming: + return self.cdll.EveGetTimingInformation() diff --git a/shared/eve_python/eve_sdk_structs.py b/shared/eve_python/eve_sdk_structs.py index 01d5977656b8b8fdca372a65f67e572c8298ff0f..2d78a07020f7dcc8b5894c26da9281c53b0d7911 100644 --- a/shared/eve_python/eve_sdk_structs.py +++ b/shared/eve_python/eve_sdk_structs.py @@ -11,8 +11,9 @@ from .structs.CFpgaData import * from .structs.CHandGesture import * from .structs.CImageManipulation import * from .structs.CKarolinska import * +from .structs.CLandmarkMaps import * +from .structs.CLicenseResult import * from .structs.CROIStructs import * -from .structs.CScreenLocation import * from .structs.CVisualSpeechStructs import * from .structs.EveProcessingStatus import * from .structs.EveAlgorithm import * @@ -42,7 +43,5 @@ from .structs.EveObjectDetection import * from .structs.EveObjectDetectionStructs import * from .structs.EveROI import * from .structs.EveROIStructs import * -from .structs.EveScreenLocation import * -from .structs.EveScreenLocationStructs import * from .structs.EveTiming import * from .structs.EveTimingStructs import * diff --git a/shared/eve_python/structs/CAlgorithms.py b/shared/eve_python/structs/CAlgorithms.py index cd9b4bcda53ee7d8aac734340299c51957bcb37c..9038d43071259c7ce1370767c34446ce6b6c8c5a 100644 --- a/shared/eve_python/structs/CAlgorithms.py +++ b/shared/eve_python/structs/CAlgorithms.py @@ -1,27 +1,27 @@ -import ctypes -from ctypes_enum import CtypesEnum - - -class EveAlgorithms(CtypesEnum): - EVE_ALGO_NONE = 0 - EVE_ALGO_BACKGROUND_SEGMENTATION = 1 - EVE_ALGO_OBJECT_DETECTION = 2 - EVE_ALGO_PERSON_DETECTION = 3 - EVE_ALGO_HEADPOSE_3D = 4 - EVE_ALGO_HAND_GESTURE = 5 - EVE_ALGO_DEPTH = 6 - EVE_ALGO_EYEWEAR_DETECTION = 7 - EVE_ALGO_KAROLINSKA = 8 - EVE_ALGO_GAZE = 9 - EVE_ALGO_GAZE_SINGLE_OUTPUT = 10 - EVE_ALGO_FACE_ID = 11 - EVE_ALGO_ROI_SELECTION = 12 - EVE_ALGO_FACE_ENHANCEMENT = 13 - EVE_ALGO_VISUAL_SPEECH_DETECTION = 14 - EVE_ALGO_BACKGROUND_BLUR = 15 - EVE_ALGO_BACKGROUND_REPLACEMENT = 16 - EVE_ALGO_USER_HILIGHT = 17 - EVE_ALGO_USER_FRAMING = 18 - EVE_ALGO_MIRROR_IMAGE = 19 - EVE_ALGO_FPGA_DRAWING = 20 - +import ctypes +from ctypes_enum import CtypesEnum + + +class EveAlgorithms(CtypesEnum): + EVE_ALGO_NONE = 0 + EVE_ALGO_BACKGROUND_SEGMENTATION = 1 + EVE_ALGO_OBJECT_DETECTION = 2 + EVE_ALGO_PERSON_DETECTION = 3 + EVE_ALGO_HEADPOSE_3D = 4 + EVE_ALGO_HAND_GESTURE = 5 + EVE_ALGO_DEPTH = 6 + EVE_ALGO_EYEWEAR_DETECTION = 7 + EVE_ALGO_KAROLINSKA = 8 + EVE_ALGO_GAZE = 9 + EVE_ALGO_GAZE_SINGLE_OUTPUT = 10 + EVE_ALGO_FACE_ID = 11 + EVE_ALGO_ROI_SELECTION = 12 + EVE_ALGO_FACE_ENHANCEMENT = 13 + EVE_ALGO_VISUAL_SPEECH_DETECTION = 14 + EVE_ALGO_BACKGROUND_BLUR = 15 + EVE_ALGO_BACKGROUND_REPLACEMENT = 16 + EVE_ALGO_USER_HILIGHT = 17 + EVE_ALGO_USER_FRAMING = 18 + EVE_ALGO_MIRROR_IMAGE = 19 + EVE_ALGO_FPGA_DRAWING = 20 + diff --git a/shared/eve_python/structs/CBasicStructs.py b/shared/eve_python/structs/CBasicStructs.py index f6bb776edc75c60512dea6bd6f1c90c684d159a1..2f8f711c1defff67c829e4c0f4f4cf5e3437b84d 100644 --- a/shared/eve_python/structs/CBasicStructs.py +++ b/shared/eve_python/structs/CBasicStructs.py @@ -1,86 +1,92 @@ -import ctypes -from ctypes_enum import CtypesEnum - -EVE_ENCODING_SIZE = 9 -EVE_LOCATION_SIZE = 2 - -class EveVideoFormat(CtypesEnum): - EVE_NONE = 0 - EVE_BGRA = 1 - EVE_YUY2 = 2 - EVE_NV12 = 3 - EVE_MJPG = 4 - EVE_BGR = 5 - EVE_GRAYSCALE = 6 - EVE_RGBA = 7 - EVE_RGB = 8 - EVE_ENCODING_SIZE = 9 - -class EveImageLocation(CtypesEnum): - EVE_CPU = 0 - EVE_GPU = 1 - EVE_LOCATION_SIZE = 2 - -class CPoint2i(ctypes.Structure): - _fields_ = [ - ("x", ctypes.c_int), - ("y", ctypes.c_int), - ] - -class CPoint2f(ctypes.Structure): - _fields_ = [ - ("x", ctypes.c_float), - ("y", ctypes.c_float), - ] - -class CPoint3i(ctypes.Structure): - _fields_ = [ - ("x", ctypes.c_int), - ("y", ctypes.c_int), - ("z", ctypes.c_int), - ] - -class CPoint3f(ctypes.Structure): - _fields_ = [ - ("x", ctypes.c_float), - ("y", ctypes.c_float), - ("z", ctypes.c_float), - ] - -class CAngles3f(ctypes.Structure): - _fields_ = [ - ("pitch", ctypes.c_float), - ("yaw", ctypes.c_float), - ("roll", ctypes.c_float), - ] - -class CRect2i(ctypes.Structure): - _fields_ = [ - ("left", ctypes.c_int), - ("top", ctypes.c_int), - ("right", ctypes.c_int), - ("bottom", ctypes.c_int), - ] - -class CRect2iWH(ctypes.Structure): - _fields_ = [ - ("x", ctypes.c_int), - ("y", ctypes.c_int), - ("width", ctypes.c_int), - ("height", ctypes.c_int), - ] - -class CRect2fWH(ctypes.Structure): - _fields_ = [ - ("x", ctypes.c_float), - ("y", ctypes.c_float), - ("width", ctypes.c_float), - ("height", ctypes.c_float), - ] - -class CResolution(ctypes.Structure): - _fields_ = [ - ("width", ctypes.c_uint), - ("height", ctypes.c_uint), - ] - +import ctypes +from ctypes_enum import CtypesEnum + +EVE_ENCODING_SIZE = 9 +EVE_LOCATION_SIZE = 2 + +class EveVideoFormat(CtypesEnum): + EVE_NONE = 0 + EVE_BGRA = 1 + EVE_YUY2 = 2 + EVE_NV12 = 3 + EVE_MJPG = 4 + EVE_BGR = 5 + EVE_GRAYSCALE = 6 + EVE_RGBA = 7 + EVE_RGB = 8 + EVE_ENCODING_SIZE = 9 + +class EveImageLocation(CtypesEnum): + EVE_CPU = 0 + EVE_GPU = 1 + EVE_LOCATION_SIZE = 2 + +class CPoint2i(ctypes.Structure): + _fields_ = [ + ("x", ctypes.c_int), + ("y", ctypes.c_int), + ] + +class CPoint2f(ctypes.Structure): + _fields_ = [ + ("x", ctypes.c_float), + ("y", ctypes.c_float), + ] + +class CPoint3i(ctypes.Structure): + _fields_ = [ + ("x", ctypes.c_int), + ("y", ctypes.c_int), + ("z", ctypes.c_int), + ] + +class CPoint3f(ctypes.Structure): + _fields_ = [ + ("x", ctypes.c_float), + ("y", ctypes.c_float), + ("z", ctypes.c_float), + ] + +class CAngles3f(ctypes.Structure): + _fields_ = [ + ("pitch", ctypes.c_float), + ("yaw", ctypes.c_float), + ("roll", ctypes.c_float), + ] + +class CRect2i(ctypes.Structure): + _fields_ = [ + ("left", ctypes.c_int), + ("top", ctypes.c_int), + ("right", ctypes.c_int), + ("bottom", ctypes.c_int), + ] + +class CRect2iWH(ctypes.Structure): + _fields_ = [ + ("x", ctypes.c_int), + ("y", ctypes.c_int), + ("width", ctypes.c_int), + ("height", ctypes.c_int), + ] + +class CRect2fWH(ctypes.Structure): + _fields_ = [ + ("x", ctypes.c_float), + ("y", ctypes.c_float), + ("width", ctypes.c_float), + ("height", ctypes.c_float), + ] + +class CResolution(ctypes.Structure): + _fields_ = [ + ("width", ctypes.c_uint), + ("height", ctypes.c_uint), + ] + +class CStringData(ctypes.Structure): + _fields_ = [ + ("data", ctypes.POINTER(ctypes.c_byte)), + ("size", ctypes.c_ulonglong), + ] + diff --git a/shared/eve_python/structs/CCameraStructs.py b/shared/eve_python/structs/CCameraStructs.py index d100e5251d96b2c4826cf09fea9195dc040b9d89..975a79687343039a2353332f69a34d3728c60f4f 100644 --- a/shared/eve_python/structs/CCameraStructs.py +++ b/shared/eve_python/structs/CCameraStructs.py @@ -1,48 +1,66 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CBasicStructs import * - -CAMERA_PID_VID_SIZE = 8 -CAMERA_NAME_SIZE = 64 - -class EveCompare(CtypesEnum): - EVE_EQUAL = 0 - EVE_AT_MOST = 1 - EVE_AT_LEAST = 2 - -class CCameraFormat(ctypes.Structure): - _fields_ = [ - ("resolution", CResolution), - ("format", ctypes.c_int), - ("fps", ctypes.c_float), - ("compareResolution", ctypes.c_int), - ("compareFps", ctypes.c_int), - ] - -class CCamera(ctypes.Structure): - _fields_ = [ - ("id", ctypes.c_int), - ("pid", ctypes.c_byte * CAMERA_PID_VID_SIZE), - ("vid", ctypes.c_byte * CAMERA_PID_VID_SIZE), - ("name", ctypes.c_byte * CAMERA_NAME_SIZE), - ("isHardwareCamera", ctypes.c_uint), - ("isFpgaCamera", ctypes.c_uint), - ("isIrCamera", ctypes.c_uint), - ] - -class CCameraParameters(ctypes.Structure): - _fields_ = [ - ("width", ctypes.c_int), - ("height", ctypes.c_int), - ("focalLength", ctypes.c_double), - ("pixelSizeX", ctypes.c_double), - ("pixelSizeY", ctypes.c_double), - ("principalPointX", ctypes.c_double), - ("principalPointY", ctypes.c_double), - ("depthMin", ctypes.c_double), - ("depthMax", ctypes.c_double), - ("screenLocationXinMM", ctypes.c_float), - ("screenLocationYinMM", ctypes.c_float), - ("isInfrared", ctypes.c_uint), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CBasicStructs import * + +CAMERA_PID_VID_SIZE = 8 +CAMERA_NAME_SIZE = 64 + +class EveCompare(CtypesEnum): + EVE_EQUAL = 0 + EVE_AT_MOST = 1 + EVE_AT_LEAST = 2 + +class CCameraFormat(ctypes.Structure): + _fields_ = [ + ("resolution", CResolution), + ("format", ctypes.c_int), + ("fps", ctypes.c_float), + ("compareResolution", ctypes.c_int), + ("compareFps", ctypes.c_int), + ] + +class CCamera(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_int), + ("pid", ctypes.c_byte * CAMERA_PID_VID_SIZE), + ("vid", ctypes.c_byte * CAMERA_PID_VID_SIZE), + ("name", ctypes.c_byte * CAMERA_NAME_SIZE), + ("isHardwareCamera", ctypes.c_uint), + ("isFpgaCamera", ctypes.c_uint), + ("isIrCamera", ctypes.c_uint), + ("screenLocationXinMM", ctypes.c_float), + ("screenLocationYinMM", ctypes.c_float), + ] + +class CCameraParameters(ctypes.Structure): + _fields_ = [ + ("width", ctypes.c_int), + ("height", ctypes.c_int), + ("focalLength", ctypes.c_double), + ("pixelSizeX", ctypes.c_double), + ("pixelSizeY", ctypes.c_double), + ("principalPointX", ctypes.c_double), + ("principalPointY", ctypes.c_double), + ("depthMin", ctypes.c_double), + ("depthMax", ctypes.c_double), + ("screenLocationXinMM", ctypes.c_float), + ("screenLocationYinMM", ctypes.c_float), + ("isInfrared", ctypes.c_uint), + ] + +class CMonitor(ctypes.Structure): + _fields_ = [ + ("rectangle", CRect2iWH), + ("dpiRatio", ctypes.c_float), + ("dpiX", ctypes.c_float), + ("dpiY", ctypes.c_float), + ("primary", ctypes.c_uint), + ] + +class CCameraLocation(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_int), + ("screenLocationXinMM", ctypes.c_float), + ("screenLocationYinMM", ctypes.c_float), + ] + diff --git a/shared/eve_python/structs/CDetectionStructs.py b/shared/eve_python/structs/CDetectionStructs.py index 1cc8771bcbe5a32c639e2ce440ccdaf51ca65f4a..eaaeb970ceadf98f2bfe142895a7e550b6fa748f 100644 --- a/shared/eve_python/structs/CDetectionStructs.py +++ b/shared/eve_python/structs/CDetectionStructs.py @@ -1,38 +1,44 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveProcessingStatus import * - -EVE_DETECTIONS_SIZE = 256 -EVE_CLASS_ID_NAME_SIZE = 32 - -class EveActionStatus(CtypesEnum): - EVE_IDLE = 0 - EVE_INTERPOLATED = 1 - EVE_COMPUTED = 2 - EVE_NO_OUTPUT = 3 - -class FrontalStatus(CtypesEnum): - UNKNOWN = 0 - FRONTAL = 1 - NON_FRONTAL = 2 - -class CSingleDetectionData(ctypes.Structure): - _fields_ = [ - ("topLeftX", ctypes.c_int), - ("topLeftY", ctypes.c_int), - ("bottomRightX", ctypes.c_int), - ("bottomRightY", ctypes.c_int), - ("classScore", ctypes.c_float), - ("classId", ctypes.c_int), - ("classIdName", ctypes.c_byte * EVE_CLASS_ID_NAME_SIZE), - ("frontalStatus", ctypes.c_int), - ] - -class CDetectionData(ctypes.Structure): - _fields_ = [ - ("processingStatus", ctypes.c_int), - ("actionStatus", ctypes.c_int), - ("numberOfDetections", ctypes.c_int), - ("detections", CSingleDetectionData * EVE_DETECTIONS_SIZE), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .EveProcessingStatus import * + +EVE_DETECTIONS_SIZE = 256 +EVE_CLASS_ID_NAME_SIZE = 32 + +class EveActionStatus(CtypesEnum): + EVE_IDLE = 0 + EVE_INTERPOLATED = 1 + EVE_COMPUTED = 2 + EVE_NO_OUTPUT = 3 + +class FrontalStatus(CtypesEnum): + UNKNOWN = 0 + FRONTAL = 1 + NON_FRONTAL = 2 + +class PPEStatus(CtypesEnum): + PPE_UNKNOWN = 0 + PPE_HARD_HAT = 1 + NO_PPE = 2 + +class CSingleDetectionData(ctypes.Structure): + _fields_ = [ + ("topLeftX", ctypes.c_int), + ("topLeftY", ctypes.c_int), + ("bottomRightX", ctypes.c_int), + ("bottomRightY", ctypes.c_int), + ("classScore", ctypes.c_float), + ("classId", ctypes.c_int), + ("classIdName", ctypes.c_byte * EVE_CLASS_ID_NAME_SIZE), + ("frontalStatus", ctypes.c_int), + ("ppeStatus", ctypes.c_int), + ] + +class CDetectionData(ctypes.Structure): + _fields_ = [ + ("processingStatus", ctypes.c_int), + ("actionStatus", ctypes.c_int), + ("numberOfDetections", ctypes.c_int), + ("detections", CSingleDetectionData * EVE_DETECTIONS_SIZE), + ] + diff --git a/shared/eve_python/structs/CFaceData.py b/shared/eve_python/structs/CFaceData.py index d8e1be7edcea3a28b8e63c90a51564dfaaa30c81..a548d7ec69d92f52d46a6db1bdef628dccaa820f 100644 --- a/shared/eve_python/structs/CFaceData.py +++ b/shared/eve_python/structs/CFaceData.py @@ -1,55 +1,61 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CBasicStructs import * -from .CFaceIdStructs import * - -EVE_MAX_FACES = 10 -EVE_EYE_LANDMARK_SIZE = 14 -EVE_PUPIL_LANDMARK_SIZE = 2 - -class EveEyeLandmark(CtypesEnum): - EVE_EYE_RIGHT_CORNER_TEMPORAL = 0 - EVE_EYE_RIGHT_EYELID_UPPER_1 = 1 - EVE_EYE_RIGHT_EYELID_UPPER_2 = 2 - EVE_EYE_RIGHT_CORNER_NASAL = 3 - EVE_EYE_RIGHT_EYELID_LOWER_1 = 4 - EVE_EYE_RIGHT_EYELID_LOWER_2 = 5 - EVE_EYE_LEFT_CORNER_NASAL = 6 - EVE_EYE_LEFT_EYELID_UPPER_2 = 7 - EVE_EYE_LEFT_EYELID_UPPER_1 = 8 - EVE_EYE_LEFT_CORNER_TEMPORAL = 9 - EVE_EYE_LEFT_EYELID_LOWER_2 = 10 - EVE_EYE_LEFT_EYELID_LOWER_1 = 11 - EVE_EYE_RIGHT_PUPIL_CENTER = 12 - EVE_EYE_LEFT_PUPIL_CENTER = 13 - EVE_EYE_LANDMARK_SIZE = 14 - -class EvePupilLandmark(CtypesEnum): - EVE_RIGHT_PUPIL_CENTER = 0 - EVE_LEFT_PUPIL_CENTER = 1 - EVE_PUPIL_LANDMARK_SIZE = 2 - -class CEyeLandmarks(ctypes.Structure): - _fields_ = [ - ("landmarks", CPoint3f * EVE_EYE_LANDMARK_SIZE), - ] - -class CPupilLandmarks(ctypes.Structure): - _fields_ = [ - ("landmarks", CPoint3f * EVE_PUPIL_LANDMARK_SIZE), - ] - -class CFaceData(ctypes.Structure): - _fields_ = [ - ("angles", CAngles3f), - ("faceId", CFaceIdentityData), - ("depth", ctypes.c_float), - ("trackNumber", ctypes.c_int), - ] - -class CAllFaces(ctypes.Structure): - _fields_ = [ - ("detectedFacesCount", ctypes.c_uint), - ("faces", CFaceData * EVE_MAX_FACES), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CBasicStructs import * +from .CFaceIdStructs import * + +EVE_MAX_FACE_LANDMARKS = 68 +EVE_MAX_FACES = 10 +EVE_EYE_LANDMARK_SIZE = 14 +EVE_PUPIL_LANDMARK_SIZE = 2 + +class EveEyeLandmark(CtypesEnum): + EVE_EYE_RIGHT_CORNER_TEMPORAL = 0 + EVE_EYE_RIGHT_EYELID_UPPER_1 = 1 + EVE_EYE_RIGHT_EYELID_UPPER_2 = 2 + EVE_EYE_RIGHT_CORNER_NASAL = 3 + EVE_EYE_RIGHT_EYELID_LOWER_1 = 4 + EVE_EYE_RIGHT_EYELID_LOWER_2 = 5 + EVE_EYE_LEFT_CORNER_NASAL = 6 + EVE_EYE_LEFT_EYELID_UPPER_2 = 7 + EVE_EYE_LEFT_EYELID_UPPER_1 = 8 + EVE_EYE_LEFT_CORNER_TEMPORAL = 9 + EVE_EYE_LEFT_EYELID_LOWER_2 = 10 + EVE_EYE_LEFT_EYELID_LOWER_1 = 11 + EVE_EYE_RIGHT_PUPIL_CENTER = 12 + EVE_EYE_LEFT_PUPIL_CENTER = 13 + EVE_EYE_LANDMARK_SIZE = 14 + +class EvePupilLandmark(CtypesEnum): + EVE_RIGHT_PUPIL_CENTER = 0 + EVE_LEFT_PUPIL_CENTER = 1 + EVE_PUPIL_LANDMARK_SIZE = 2 + +class CEyeLandmarks(ctypes.Structure): + _fields_ = [ + ("landmarks", CPoint3f * EVE_EYE_LANDMARK_SIZE), + ] + +class CPupilLandmarks(ctypes.Structure): + _fields_ = [ + ("landmarks", CPoint3f * EVE_PUPIL_LANDMARK_SIZE), + ] + +class CFaceData(ctypes.Structure): + _fields_ = [ + ("angles", CAngles3f), + ("faceId", CFaceIdentityData), + ("depth", ctypes.c_float), + ("trackNumber", ctypes.c_int), + ("box", CRect2iWH), + ("landmarksCount", ctypes.c_uint), + ("landmarksICS", CPoint2f * EVE_MAX_FACE_LANDMARKS), + ("landmarksCCS", CPoint3f * EVE_MAX_FACE_LANDMARKS), + ("midEyeCCS", CPoint3f), + ] + +class CAllFaces(ctypes.Structure): + _fields_ = [ + ("detectedFacesCount", ctypes.c_uint), + ("faces", CFaceData * EVE_MAX_FACES), + ] + diff --git a/shared/eve_python/structs/CFaceIdStructs.py b/shared/eve_python/structs/CFaceIdStructs.py index 38c3a0fed2c54028341271b715d2e5426cf82a92..3efaa8c2c459508522f473c5c1136958c64aafa3 100644 --- a/shared/eve_python/structs/CFaceIdStructs.py +++ b/shared/eve_python/structs/CFaceIdStructs.py @@ -1,68 +1,68 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveProcessingStatus import * - -EVE_FACE_ID_MAX_MISSING_CALIBRATION_POSES = 5 - -class EveFaceIdActionStatus(CtypesEnum): - EVE_FACE_ID_ACTION_IDLE = 0 - EVE_FACE_ID_ACTION_CALIBRATING = 1 - EVE_FACE_ID_ACTION_CALIBRATED = 2 - EVE_FACE_ID_ACTION_IDENTIFIED = 3 - -class EveFaceIdCalibrationStatus(CtypesEnum): - EVE_FACE_ID_CALIB_NONE = 0 - EVE_FACE_ID_CALIB_RUNNING = 1 - EVE_FACE_ID_CALIB_SUCCESS = 2 - EVE_FACE_ID_CALIB_FAILURE_LACK_POSE_MOTION = 3 - EVE_FACE_ID_CALIB_FAILURE_OTHER = 4 - -class EveFaceIdIdentificationStatus(CtypesEnum): - EVE_FACE_ID_NONE = 0 - EVE_FACE_ID_SUCCESS = 1 - EVE_FACE_ID_FAILURE_VERIFICATION = 2 - EVE_FACE_ID_FAILURE_ANGLE_PITCH = 3 - EVE_FACE_ID_FAILURE_ANGLE_YAW = 4 - EVE_FACE_ID_FAILURE_ANGLE_ROLL = 5 - EVE_FACE_ID_FAILURE_ANGLE_BOTH = 6 - EVE_FACE_ID_FAILURE_NO_GALLERY = 7 - EVE_FACE_ID_FAILURE_EXP_SMILE = 8 - EVE_FACE_ID_FAILURE_EXP_SQUINT = 9 - EVE_FACE_ID_FAILURE_EXP_EYES_CLOSED = 10 - EVE_FACE_ID_FAILURE_DEPTH = 11 - EVE_FACE_ID_FAILURE_OTHER = 12 - -class EveFaceIdPose(CtypesEnum): - EVE_FACE_ID_POSE_FRONTAL = 0 - EVE_FACE_ID_POSE_LEFT = 1 - EVE_FACE_ID_POSE_RIGHT = 2 - EVE_FACE_ID_POSE_UP = 3 - EVE_FACE_ID_POSE_DOWN = 4 - -class EveFaceIdCommand(CtypesEnum): - EVE_FACE_ID_COMMAND_NONE = 0 - EVE_FACE_ID_COMMAND_ADD_NEW_USER = 1 - EVE_FACE_ID_COMMAND_CALIBRATE_CURRENT_USER = 2 - EVE_FACE_ID_COMMAND_FORCE_ID = 3 - EVE_FACE_ID_COMMAND_REMOVE_CURRENT_USER = 4 - EVE_FACE_ID_COMMAND_REMOVE_ALL_USERS = 5 - EVE_FACE_ID_COMMAND_RELOAD_GALLERY = 6 - -class CFaceIdentity(ctypes.Structure): - _fields_ = [ - ("id", ctypes.c_longlong), - ("confidence", ctypes.c_float), - ("similarity", ctypes.c_float), - ] - -class CFaceIdentityData(ctypes.Structure): - _fields_ = [ - ("processingStatus", ctypes.c_int), - ("actionStatus", ctypes.c_int), - ("calibrationStatus", ctypes.c_int), - ("identificationStatus", ctypes.c_int), - ("faceIdentity", CFaceIdentity), - ("missingCalibrationPosesCount", ctypes.c_uint), - ("missingCalibrationPoses", ctypes.c_int * EVE_FACE_ID_MAX_MISSING_CALIBRATION_POSES), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .EveProcessingStatus import * + +EVE_FACE_ID_MAX_MISSING_CALIBRATION_POSES = 5 + +class EveFaceIdActionStatus(CtypesEnum): + EVE_FACE_ID_ACTION_IDLE = 0 + EVE_FACE_ID_ACTION_CALIBRATING = 1 + EVE_FACE_ID_ACTION_CALIBRATED = 2 + EVE_FACE_ID_ACTION_IDENTIFIED = 3 + +class EveFaceIdCalibrationStatus(CtypesEnum): + EVE_FACE_ID_CALIB_NONE = 0 + EVE_FACE_ID_CALIB_RUNNING = 1 + EVE_FACE_ID_CALIB_SUCCESS = 2 + EVE_FACE_ID_CALIB_FAILURE_LACK_POSE_MOTION = 3 + EVE_FACE_ID_CALIB_FAILURE_OTHER = 4 + +class EveFaceIdIdentificationStatus(CtypesEnum): + EVE_FACE_ID_NONE = 0 + EVE_FACE_ID_SUCCESS = 1 + EVE_FACE_ID_FAILURE_VERIFICATION = 2 + EVE_FACE_ID_FAILURE_ANGLE_PITCH = 3 + EVE_FACE_ID_FAILURE_ANGLE_YAW = 4 + EVE_FACE_ID_FAILURE_ANGLE_ROLL = 5 + EVE_FACE_ID_FAILURE_ANGLE_BOTH = 6 + EVE_FACE_ID_FAILURE_NO_GALLERY = 7 + EVE_FACE_ID_FAILURE_EXP_SMILE = 8 + EVE_FACE_ID_FAILURE_EXP_SQUINT = 9 + EVE_FACE_ID_FAILURE_EXP_EYES_CLOSED = 10 + EVE_FACE_ID_FAILURE_DEPTH = 11 + EVE_FACE_ID_FAILURE_OTHER = 12 + +class EveFaceIdPose(CtypesEnum): + EVE_FACE_ID_POSE_FRONTAL = 0 + EVE_FACE_ID_POSE_LEFT = 1 + EVE_FACE_ID_POSE_RIGHT = 2 + EVE_FACE_ID_POSE_UP = 3 + EVE_FACE_ID_POSE_DOWN = 4 + +class EveFaceIdCommand(CtypesEnum): + EVE_FACE_ID_COMMAND_NONE = 0 + EVE_FACE_ID_COMMAND_ADD_NEW_USER = 1 + EVE_FACE_ID_COMMAND_CALIBRATE_CURRENT_USER = 2 + EVE_FACE_ID_COMMAND_FORCE_ID = 3 + EVE_FACE_ID_COMMAND_REMOVE_CURRENT_USER = 4 + EVE_FACE_ID_COMMAND_REMOVE_ALL_USERS = 5 + EVE_FACE_ID_COMMAND_RELOAD_GALLERY = 6 + +class CFaceIdentity(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_longlong), + ("confidence", ctypes.c_float), + ("similarity", ctypes.c_float), + ] + +class CFaceIdentityData(ctypes.Structure): + _fields_ = [ + ("processingStatus", ctypes.c_int), + ("actionStatus", ctypes.c_int), + ("calibrationStatus", ctypes.c_int), + ("identificationStatus", ctypes.c_int), + ("faceIdentity", CFaceIdentity), + ("missingCalibrationPosesCount", ctypes.c_uint), + ("missingCalibrationPoses", ctypes.c_int * EVE_FACE_ID_MAX_MISSING_CALIBRATION_POSES), + ] + diff --git a/shared/eve_python/structs/CFpgaData.py b/shared/eve_python/structs/CFpgaData.py index ea12acf45b52a81fe935f584ad58d34787d47c10..e5d45c04b09617712cdceadb0a6491f1dd44bbb8 100644 --- a/shared/eve_python/structs/CFpgaData.py +++ b/shared/eve_python/structs/CFpgaData.py @@ -1,350 +1,350 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CBasicStructs import * - -EVE_FPGA_MAX_USERS = 10 -EVE_FPGA_MAX_PERSONS = 5 -EVE_FPGA_MAX_HAND_LANDMARKS = 11 -EVE_FPGA_HAND_LANDMARKS = 10 -EVE_FPGA_MAX_OBJECT_DETECTION = 50 -PT_SIZE = 6 -MT_SIZE = 4 -RT_SIZE = 4 - -class EveFpgaConnectionType(CtypesEnum): - EVE_FPGA_AUTO_SELECT = 0 - EVE_FPGA_UART = 1 - EVE_FPGA_I2C = 2 - EVE_FPGA_HUB = 3 - EVE_FPGA_MANUAL = 4 - -class EveFpgaConnectionRequest(CtypesEnum): - EVE_FPGA_STOP = 0 - EVE_FPGA_CONTINUE = 1 - -class pipeline_config_type_t(CtypesEnum): - PT_FD = 0 - PT_LM_FV = 1 - PT_FID = 2 - PT_PD = 3 - PT_HD = 4 - PT_HLMV = 5 - PT_SIZE = 6 - -class setting_type_t(CtypesEnum): - CS_ENABLED = 0x00 - CS_IPS = 0x01 - CS_RESERVED_2_7 = 0x02 - CS_COMMAND = 0x08 - CS_CUSTOM = 0x10 - CS_MAX = 0x11 - -class message_type_t(CtypesEnum): - MT_NONE = 0 - MT_SET = 1 - MT_GET = 2 - MT_GET_BATCH = 3 - MT_SIZE = 4 - -class response_type_t(CtypesEnum): - RT_NONE = 0 - RT_DATA = 1 - RT_GET = 2 - RT_ACK = 3 - RT_SIZE = 4 - -class EveFpgaSerialStatus(CtypesEnum): - EVE_FPGA_SUCCESS = 0 - EVE_FPGA_NO_DATA = 1 - EVE_FPGA_READ_START_MARKER_FAILED = 2 - EVE_FPGA_FIND_START_MARKER_FAILED = 3 - EVE_FPGA_READ_DATA_LENGTH_FAILED = 4 - EVE_FPGA_READ_DATA_FAILED = 5 - EVE_FPGA_CORRUPTED_DATA = 6 - EVE_FPGA_UNEXPECTED_RESPONSE_TYPE = 7 - EVE_FPGA_API_ERROR_START = 8 - EVE_FPGA_NO_CALLBACK = 9 - EVE_FPGA_DATA_ACCESSED_OUTSIDE_CALLBACK = 10 - EVE_FPGA_INIT_FAILED = 11 - EVE_FPGA_NOT_INIT = 12 - EVE_FPGA_NOT_IMPLEMENTED = 13 - EVE_FPGA_API_ERROR_END = 14 - -class EveWakeupDetectionType(CtypesEnum): - EVE_USER_DETECTION = 0 - EVE_STRANGER_DETECTION = 1 - -class EveFpgaPipelineType(CtypesEnum): - EVE_UNKNOWN_PIPELINE = 0 - EVE_HEAD_POSE_PIPELINE = 1 - EVE_FACE_ID_PIPELINE = 2 - EVE_HAND_GESTURE_PIPELINE = 3 - EVE_COMPACT_HEAD_POSE_PIPELINE = 4 - EVE_HMI_PIPELINE = 5 - EVE_STANDALONE_HAND_GESTURE_PIPELINE = 6 - -class EvePersonBodyPose(CtypesEnum): - EVE_FRONT = 0 - EVE_NOT_FRONT = 1 - -class EveDistanceFromCamera(CtypesEnum): - EVE_DISTANCE_CLOSE = 0 - EVE_DISTANCE_MID = 1 - EVE_DISTANCE_FAR = 2 - -class EvePersonRegistrationStatus(CtypesEnum): - EVE_REGISTERED = 0 - EVE_UNREGISTERED = 1 - EVE_UNKNOWN = 2 - EVE_REQUIREMENTS_UNMET = 3 - EVE_DISABLED = 4 - EVE_NO_GALLERY = 5 - -class EveFpgaHandGesture(CtypesEnum): - EVE_FPGA_HAND_GESTURE_NO_GESTURE = 0 - EVE_FPGA_HAND_GESTURE_CLOSE = 1 - EVE_FPGA_HAND_GESTURE_OPEN = 2 - EVE_FPGA_HAND_GESTURE_OPEN_LEFT = 3 - EVE_FPGA_HAND_GESTURE_OPEN_RIGHT = 4 - EVE_FPGA_HAND_GESTURE_INDEX_UP = 5 - EVE_FPGA_HAND_GESTURE_INDEX_DOWN = 6 - EVE_FPGA_HAND_GESTURE_TIP_LEFT = 7 - EVE_FPGA_HAND_GESTURE_TIP_RIGHT = 8 - EVE_FPGA_HAND_GESTURE_UNKNOWN = 9 - -class EveFpgaObjectClass(CtypesEnum): - EVE_FPGA_OBJECT_CLASS_PERSON = 0 - EVE_FPGA_OBJECT_CLASS_BICYCLE = 1 - EVE_FPGA_OBJECT_CLASS_CAR = 2 - EVE_FPGA_OBJECT_CLASS_MOTORCYCLE = 3 - EVE_FPGA_OBJECT_CLASS_BUS = 4 - EVE_FPGA_OBJECT_CLASS_TRUCK = 5 - EVE_FPGA_OBJECT_CLASS_TRAFFIC_LIGHT = 6 - EVE_FPGA_OBJECT_CLASS_STOP_SIGN = 7 - -class pipeline_setting_t(ctypes.Structure): - _fields_ = [ - ("settingType", ctypes.c_int), - ("value", ctypes.c_uint32), - ] - -class pipeline_config_t(ctypes.Structure): - _fields_ = [ - ("type", ctypes.c_int), - ("setting", pipeline_setting_t), - ] - -class CFpgaIdealPersonData(ctypes.Structure): - _fields_ = [ - ("valid", ctypes.c_uint), - ("index", ctypes.c_uint), - ("status", ctypes.c_int), - ("faceAngles", CAngles3f), - ("faceLandmarksConfidence", ctypes.c_float), - ("isFaceLandmarksConfidenceValid", ctypes.c_bool), - ] - -class CFpgaImageDimensions(ctypes.Structure): - _fields_ = [ - ("width", ctypes.c_int), - ("height", ctypes.c_int), - ("cropArea", CRect2i), - ("reserved1", ctypes.c_int), - ("reserved2", ctypes.c_int), - ] - -class CFpgaDataContent(ctypes.Structure): - _fields_ = [ - ("numberOfUsers", ctypes.c_int16), - ("idealUserIndex", ctypes.c_int16), - ("numberOfDetectedFaces", ctypes.c_int16), - ("numberOfFacesConfidence", ctypes.c_float), - ("numberOfDetectedPersons", ctypes.c_int16), - ("numberOfPersonsConfidence", ctypes.c_float), - ("isIdealUserDataAvailable", ctypes.c_bool), - ("idealUserDetected", ctypes.c_bool), - ("isIdealUserIndexValid", ctypes.c_bool), - ("isNumberOfDetectedFacesAvailable", ctypes.c_bool), - ("isNumberOfFacesConfidenceAvailable", ctypes.c_bool), - ("isNumberOfDetectedPersonsAvailable", ctypes.c_bool), - ("isNumberOfPersonsConfidenceAvailable", ctypes.c_bool), - ("isUsersDataAvilable", ctypes.c_bool), - ("isFaceIdDataAvailable", ctypes.c_bool), - ("isObjectDetectionAvailable", ctypes.c_bool), - ("isCameraStreaming", ctypes.c_bool), - ("isHandGestureDataAvailable", ctypes.c_bool), - ("isDefectDetectionAvailable", ctypes.c_bool), - ] - -class CFpgaHandData(ctypes.Structure): - _fields_ = [ - ("validationScore", ctypes.c_float), - ("handBox", CRect2i), - ("landmarks", CPoint3f * EVE_FPGA_MAX_HAND_LANDMARKS), - ] - -class CFpgaHandsData(ctypes.Structure): - _fields_ = [ - ("numberOfHandLandmarkPoints", ctypes.c_int16), - ("handData", CFpgaHandData), - ("gesture", ctypes.c_int), - ("isHandBoxAvailable", ctypes.c_bool), - ("isHandLandmark3D", ctypes.c_bool), - ] - -class CFpgaDefectData(ctypes.Structure): - _fields_ = [ - ("defectBox", CRect2i), - ("width", ctypes.c_int), - ("height", ctypes.c_int), - ("similarity", ctypes.c_float), - ("isDefective", ctypes.c_bool), - ] - -class CFpgaFaceData(ctypes.Structure): - _fields_ = [ - ("faceConfidence", ctypes.c_float), - ("faceDistance", ctypes.c_int16), - ("faceCenter", CPoint3i), - ("anglesICS", CAngles3f), - ("anglesCCS", CAngles3f), - ("faceLandmarksConfidence", ctypes.c_float), - ("faceBox", CRect2i), - ("faceIDStatus", ctypes.c_int), - ("faceID", ctypes.c_int16), - ("isFaceConfidenceAvailable", ctypes.c_bool), - ("isFaceDistanceAvailable", ctypes.c_bool), - ("isFacePositionAvailable", ctypes.c_bool), - ("isEulerAnglesIcsAvailable", ctypes.c_bool), - ("isEulerAnglesCcsAvailable", ctypes.c_bool), - ("isFaceLandmark3D", ctypes.c_bool), - ("isFaceLandmarksConfidenceAvailable", ctypes.c_bool), - ("isFaceGeometricBoxAvailable", ctypes.c_bool), - ("isStatusAvailable", ctypes.c_bool), - ] - -class CFpgaPersonData(ctypes.Structure): - _fields_ = [ - ("personConfidence", ctypes.c_float), - ("personDistance", ctypes.c_int), - ("personPosture", ctypes.c_int), - ("personFrontalPostureConfidence", ctypes.c_float), - ("personNotFrontalPostureConfidence", ctypes.c_float), - ("position", CPoint3i), - ("personBox", CRect2i), - ("isPersonDataAvailable", ctypes.c_bool), - ] - -class CFpgaObjectDetection(ctypes.Structure): - _fields_ = [ - ("objectClass", ctypes.c_int), - ("objectConfidence", ctypes.c_float), - ("objectBox", CRect2i), - ] - -class CFpgaObjectData(ctypes.Structure): - _fields_ = [ - ("numberOfObjects", ctypes.c_int16), - ("objects", CFpgaObjectDetection * EVE_FPGA_MAX_OBJECT_DETECTION), - ] - -class CFpgaUserData(ctypes.Structure): - _fields_ = [ - ("id", ctypes.c_int16), - ("status", ctypes.c_int), - ("scale", ctypes.c_float), - ("faceData", CFpgaFaceData), - ("personData", CFpgaPersonData), - ("isIdealUser", ctypes.c_bool), - ("isIdValid", ctypes.c_bool), - ("isStatusAvailable", ctypes.c_bool), - ("isScaleAvailable", ctypes.c_bool), - ] - -class CFpgaFaceIdData(ctypes.Structure): - _fields_ = [ - ("command", ctypes.c_int16), - ("userId", ctypes.c_int16), - ("freeEntry", ctypes.c_int16), - ("statusCode", ctypes.c_int16), - ("faceId", ctypes.c_int16), - ("lastRegisteredFaceID", ctypes.c_int16), - ("usersInGallery", ctypes.c_int16), - ("gallerySize", ctypes.c_int16), - ] - -class CFpgaPipelineData(ctypes.Structure): - _fields_ = [ - ("pipelineType", ctypes.c_int), - ("imageDimensions", CFpgaImageDimensions), - ("dataContent", CFpgaDataContent), - ("userData", CFpgaUserData * EVE_FPGA_MAX_USERS), - ("objectData", CFpgaObjectData), - ("faceId", CFpgaFaceIdData), - ("handsData", CFpgaHandsData), - ("defectData", CFpgaDefectData), - ] - -class CFpgaMessage(ctypes.Structure): - _fields_ = [ - ("responseType", ctypes.c_int), - ("responseVersion", ctypes.c_uint8), - ("serialStatus", ctypes.c_int), - ("serialReadTimeNano", ctypes.c_longlong), - ] - -class CFpgaData(ctypes.Structure): - _fields_ = [ - ("message", CFpgaMessage), - ("pipelineData", CFpgaPipelineData), - ] - -class CFpgaGetSetting(ctypes.Structure): - _fields_ = [ - ("message", CFpgaMessage), - ("type", ctypes.c_int), - ("setting", ctypes.c_int), - ("value", ctypes.c_uint32), - ] - -class CFpgaParameters(ctypes.Structure): - _fields_ = [ - ("comport", ctypes.c_uint), - ("socWakeupDelay", ctypes.c_uint), - ("wakeupType", ctypes.c_int), - ("forceCameraOn", ctypes.c_ubyte), - ("registerNewFace", ctypes.c_ubyte), - ("clearCurrentFace", ctypes.c_ubyte), - ("enableFaceId", ctypes.c_ubyte), - ("allPipelinesSupported", ctypes.c_ubyte), - ("pipelineVersion", ctypes.c_uint), - ("connection", ctypes.c_int), - ("i2cAdapterNumber", ctypes.c_uint), - ("i2cDeviceNumber", ctypes.c_uint), - ("i2cIRQPin", ctypes.c_uint), - ] - -class CFpgaCallbackControl(ctypes.Structure): - _fields_ = [ - ("request", ctypes.c_int), - ] - -class EveFpgaMetadata(ctypes.Structure): - _fields_ = [ - ("data", ctypes.POINTER(CFpgaData)), - ("errorCode", ctypes.c_int), - ] - -class EveFpgaManualData(ctypes.Structure): - _fields_ = [ - ("data", ctypes.POINTER(ctypes.c_ubyte)), - ("size", ctypes.c_int), - ] - -class EveFpgaJsonMetadata(ctypes.Structure): - _fields_ = [ - ("textStart", ctypes.POINTER(ctypes.c_byte)), - ("textSize", ctypes.c_uint), - ("errorCode", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CBasicStructs import * + +EVE_FPGA_MAX_USERS = 10 +EVE_FPGA_MAX_PERSONS = 5 +EVE_FPGA_MAX_HAND_LANDMARKS = 11 +EVE_FPGA_HAND_LANDMARKS = 10 +EVE_FPGA_MAX_OBJECT_DETECTION = 50 +PT_SIZE = 6 +MT_SIZE = 4 +RT_SIZE = 4 + +class EveFpgaConnectionType(CtypesEnum): + EVE_FPGA_AUTO_SELECT = 0 + EVE_FPGA_UART = 1 + EVE_FPGA_I2C = 2 + EVE_FPGA_HUB = 3 + EVE_FPGA_MANUAL = 4 + +class EveFpgaConnectionRequest(CtypesEnum): + EVE_FPGA_STOP = 0 + EVE_FPGA_CONTINUE = 1 + +class pipeline_config_type_t(CtypesEnum): + PT_FD = 0 + PT_LM_FV = 1 + PT_FID = 2 + PT_PD = 3 + PT_HD = 4 + PT_HLMV = 5 + PT_SIZE = 6 + +class setting_type_t(CtypesEnum): + CS_ENABLED = 0x00 + CS_IPS = 0x01 + CS_RESERVED_2_7 = 0x02 + CS_COMMAND = 0x08 + CS_CUSTOM = 0x10 + CS_MAX = 0x11 + +class message_type_t(CtypesEnum): + MT_NONE = 0 + MT_SET = 1 + MT_GET = 2 + MT_GET_BATCH = 3 + MT_SIZE = 4 + +class response_type_t(CtypesEnum): + RT_NONE = 0 + RT_DATA = 1 + RT_GET = 2 + RT_ACK = 3 + RT_SIZE = 4 + +class EveFpgaSerialStatus(CtypesEnum): + EVE_FPGA_SUCCESS = 0 + EVE_FPGA_NO_DATA = 1 + EVE_FPGA_READ_START_MARKER_FAILED = 2 + EVE_FPGA_FIND_START_MARKER_FAILED = 3 + EVE_FPGA_READ_DATA_LENGTH_FAILED = 4 + EVE_FPGA_READ_DATA_FAILED = 5 + EVE_FPGA_CORRUPTED_DATA = 6 + EVE_FPGA_UNEXPECTED_RESPONSE_TYPE = 7 + EVE_FPGA_API_ERROR_START = 8 + EVE_FPGA_NO_CALLBACK = 9 + EVE_FPGA_DATA_ACCESSED_OUTSIDE_CALLBACK = 10 + EVE_FPGA_INIT_FAILED = 11 + EVE_FPGA_NOT_INIT = 12 + EVE_FPGA_NOT_IMPLEMENTED = 13 + EVE_FPGA_API_ERROR_END = 14 + +class EveWakeupDetectionType(CtypesEnum): + EVE_USER_DETECTION = 0 + EVE_STRANGER_DETECTION = 1 + +class EveFpgaPipelineType(CtypesEnum): + EVE_UNKNOWN_PIPELINE = 0 + EVE_HEAD_POSE_PIPELINE = 1 + EVE_FACE_ID_PIPELINE = 2 + EVE_HAND_GESTURE_PIPELINE = 3 + EVE_COMPACT_HEAD_POSE_PIPELINE = 4 + EVE_HMI_PIPELINE = 5 + EVE_STANDALONE_HAND_GESTURE_PIPELINE = 6 + +class EvePersonBodyPose(CtypesEnum): + EVE_FRONT = 0 + EVE_NOT_FRONT = 1 + +class EveDistanceFromCamera(CtypesEnum): + EVE_DISTANCE_CLOSE = 0 + EVE_DISTANCE_MID = 1 + EVE_DISTANCE_FAR = 2 + +class EvePersonRegistrationStatus(CtypesEnum): + EVE_REGISTERED = 0 + EVE_UNREGISTERED = 1 + EVE_UNKNOWN = 2 + EVE_REQUIREMENTS_UNMET = 3 + EVE_DISABLED = 4 + EVE_NO_GALLERY = 5 + +class EveFpgaHandGesture(CtypesEnum): + EVE_FPGA_HAND_GESTURE_NO_GESTURE = 0 + EVE_FPGA_HAND_GESTURE_CLOSE = 1 + EVE_FPGA_HAND_GESTURE_OPEN = 2 + EVE_FPGA_HAND_GESTURE_OPEN_LEFT = 3 + EVE_FPGA_HAND_GESTURE_OPEN_RIGHT = 4 + EVE_FPGA_HAND_GESTURE_INDEX_UP = 5 + EVE_FPGA_HAND_GESTURE_INDEX_DOWN = 6 + EVE_FPGA_HAND_GESTURE_TIP_LEFT = 7 + EVE_FPGA_HAND_GESTURE_TIP_RIGHT = 8 + EVE_FPGA_HAND_GESTURE_UNKNOWN = 9 + +class EveFpgaObjectClass(CtypesEnum): + EVE_FPGA_OBJECT_CLASS_PERSON = 0 + EVE_FPGA_OBJECT_CLASS_BICYCLE = 1 + EVE_FPGA_OBJECT_CLASS_CAR = 2 + EVE_FPGA_OBJECT_CLASS_MOTORCYCLE = 3 + EVE_FPGA_OBJECT_CLASS_BUS = 4 + EVE_FPGA_OBJECT_CLASS_TRUCK = 5 + EVE_FPGA_OBJECT_CLASS_TRAFFIC_LIGHT = 6 + EVE_FPGA_OBJECT_CLASS_STOP_SIGN = 7 + +class pipeline_setting_t(ctypes.Structure): + _fields_ = [ + ("settingType", ctypes.c_int), + ("value", ctypes.c_uint32), + ] + +class pipeline_config_t(ctypes.Structure): + _fields_ = [ + ("type", ctypes.c_int), + ("setting", pipeline_setting_t), + ] + +class CFpgaIdealPersonData(ctypes.Structure): + _fields_ = [ + ("valid", ctypes.c_uint), + ("index", ctypes.c_uint), + ("status", ctypes.c_int), + ("faceAngles", CAngles3f), + ("faceLandmarksConfidence", ctypes.c_float), + ("isFaceLandmarksConfidenceValid", ctypes.c_bool), + ] + +class CFpgaImageDimensions(ctypes.Structure): + _fields_ = [ + ("width", ctypes.c_int), + ("height", ctypes.c_int), + ("cropArea", CRect2i), + ("reserved1", ctypes.c_int), + ("reserved2", ctypes.c_int), + ] + +class CFpgaDataContent(ctypes.Structure): + _fields_ = [ + ("numberOfUsers", ctypes.c_int16), + ("idealUserIndex", ctypes.c_int16), + ("numberOfDetectedFaces", ctypes.c_int16), + ("numberOfFacesConfidence", ctypes.c_float), + ("numberOfDetectedPersons", ctypes.c_int16), + ("numberOfPersonsConfidence", ctypes.c_float), + ("isIdealUserDataAvailable", ctypes.c_bool), + ("idealUserDetected", ctypes.c_bool), + ("isIdealUserIndexValid", ctypes.c_bool), + ("isNumberOfDetectedFacesAvailable", ctypes.c_bool), + ("isNumberOfFacesConfidenceAvailable", ctypes.c_bool), + ("isNumberOfDetectedPersonsAvailable", ctypes.c_bool), + ("isNumberOfPersonsConfidenceAvailable", ctypes.c_bool), + ("isUsersDataAvilable", ctypes.c_bool), + ("isFaceIdDataAvailable", ctypes.c_bool), + ("isObjectDetectionAvailable", ctypes.c_bool), + ("isCameraStreaming", ctypes.c_bool), + ("isHandGestureDataAvailable", ctypes.c_bool), + ("isDefectDetectionAvailable", ctypes.c_bool), + ] + +class CFpgaHandData(ctypes.Structure): + _fields_ = [ + ("validationScore", ctypes.c_float), + ("handBox", CRect2i), + ("landmarks", CPoint3f * EVE_FPGA_MAX_HAND_LANDMARKS), + ] + +class CFpgaHandsData(ctypes.Structure): + _fields_ = [ + ("numberOfHandLandmarkPoints", ctypes.c_int16), + ("handData", CFpgaHandData), + ("gesture", ctypes.c_int), + ("isHandBoxAvailable", ctypes.c_bool), + ("isHandLandmark3D", ctypes.c_bool), + ] + +class CFpgaDefectData(ctypes.Structure): + _fields_ = [ + ("defectBox", CRect2i), + ("width", ctypes.c_int), + ("height", ctypes.c_int), + ("similarity", ctypes.c_float), + ("isDefective", ctypes.c_bool), + ] + +class CFpgaFaceData(ctypes.Structure): + _fields_ = [ + ("faceConfidence", ctypes.c_float), + ("faceDistance", ctypes.c_int16), + ("faceCenter", CPoint3i), + ("anglesICS", CAngles3f), + ("anglesCCS", CAngles3f), + ("faceLandmarksConfidence", ctypes.c_float), + ("faceBox", CRect2i), + ("faceIDStatus", ctypes.c_int), + ("faceID", ctypes.c_int16), + ("isFaceConfidenceAvailable", ctypes.c_bool), + ("isFaceDistanceAvailable", ctypes.c_bool), + ("isFacePositionAvailable", ctypes.c_bool), + ("isEulerAnglesIcsAvailable", ctypes.c_bool), + ("isEulerAnglesCcsAvailable", ctypes.c_bool), + ("isFaceLandmark3D", ctypes.c_bool), + ("isFaceLandmarksConfidenceAvailable", ctypes.c_bool), + ("isFaceGeometricBoxAvailable", ctypes.c_bool), + ("isStatusAvailable", ctypes.c_bool), + ] + +class CFpgaPersonData(ctypes.Structure): + _fields_ = [ + ("personConfidence", ctypes.c_float), + ("personDistance", ctypes.c_int), + ("personPosture", ctypes.c_int), + ("personFrontalPostureConfidence", ctypes.c_float), + ("personNotFrontalPostureConfidence", ctypes.c_float), + ("position", CPoint3i), + ("personBox", CRect2i), + ("isPersonDataAvailable", ctypes.c_bool), + ] + +class CFpgaObjectDetection(ctypes.Structure): + _fields_ = [ + ("objectClass", ctypes.c_int), + ("objectConfidence", ctypes.c_float), + ("objectBox", CRect2i), + ] + +class CFpgaObjectData(ctypes.Structure): + _fields_ = [ + ("numberOfObjects", ctypes.c_int16), + ("objects", CFpgaObjectDetection * EVE_FPGA_MAX_OBJECT_DETECTION), + ] + +class CFpgaUserData(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_int16), + ("status", ctypes.c_int), + ("scale", ctypes.c_float), + ("faceData", CFpgaFaceData), + ("personData", CFpgaPersonData), + ("isIdealUser", ctypes.c_bool), + ("isIdValid", ctypes.c_bool), + ("isStatusAvailable", ctypes.c_bool), + ("isScaleAvailable", ctypes.c_bool), + ] + +class CFpgaFaceIdData(ctypes.Structure): + _fields_ = [ + ("command", ctypes.c_int16), + ("userId", ctypes.c_int16), + ("freeEntry", ctypes.c_int16), + ("statusCode", ctypes.c_int16), + ("faceId", ctypes.c_int16), + ("lastRegisteredFaceID", ctypes.c_int16), + ("usersInGallery", ctypes.c_int16), + ("gallerySize", ctypes.c_int16), + ] + +class CFpgaPipelineData(ctypes.Structure): + _fields_ = [ + ("pipelineType", ctypes.c_int), + ("imageDimensions", CFpgaImageDimensions), + ("dataContent", CFpgaDataContent), + ("userData", CFpgaUserData * EVE_FPGA_MAX_USERS), + ("objectData", CFpgaObjectData), + ("faceId", CFpgaFaceIdData), + ("handsData", CFpgaHandsData), + ("defectData", CFpgaDefectData), + ] + +class CFpgaMessage(ctypes.Structure): + _fields_ = [ + ("responseType", ctypes.c_int), + ("responseVersion", ctypes.c_uint8), + ("serialStatus", ctypes.c_int), + ("serialReadTimeNano", ctypes.c_longlong), + ] + +class CFpgaData(ctypes.Structure): + _fields_ = [ + ("message", CFpgaMessage), + ("pipelineData", CFpgaPipelineData), + ] + +class CFpgaGetSetting(ctypes.Structure): + _fields_ = [ + ("message", CFpgaMessage), + ("type", ctypes.c_int), + ("setting", ctypes.c_int), + ("value", ctypes.c_uint32), + ] + +class CFpgaParameters(ctypes.Structure): + _fields_ = [ + ("comport", ctypes.c_uint), + ("socWakeupDelay", ctypes.c_uint), + ("wakeupType", ctypes.c_int), + ("forceCameraOn", ctypes.c_ubyte), + ("registerNewFace", ctypes.c_ubyte), + ("clearCurrentFace", ctypes.c_ubyte), + ("enableFaceId", ctypes.c_ubyte), + ("allPipelinesSupported", ctypes.c_ubyte), + ("pipelineVersion", ctypes.c_uint), + ("connection", ctypes.c_int), + ("i2cAdapterNumber", ctypes.c_uint), + ("i2cDeviceNumber", ctypes.c_uint), + ("i2cIRQPin", ctypes.c_uint), + ] + +class CFpgaCallbackControl(ctypes.Structure): + _fields_ = [ + ("request", ctypes.c_int), + ] + +class EveFpgaMetadata(ctypes.Structure): + _fields_ = [ + ("data", ctypes.POINTER(CFpgaData)), + ("errorCode", ctypes.c_int), + ] + +class EveFpgaManualData(ctypes.Structure): + _fields_ = [ + ("data", ctypes.POINTER(ctypes.c_ubyte)), + ("size", ctypes.c_int), + ] + +class EveFpgaJsonMetadata(ctypes.Structure): + _fields_ = [ + ("textStart", ctypes.POINTER(ctypes.c_byte)), + ("textSize", ctypes.c_uint), + ("errorCode", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/CHandGesture.py b/shared/eve_python/structs/CHandGesture.py index c4b589d7b3c35e06122ce6bc0b282d6f04ae71bd..bef4291100942581c4b1c2e4ad9710e4c6c26f0a 100644 --- a/shared/eve_python/structs/CHandGesture.py +++ b/shared/eve_python/structs/CHandGesture.py @@ -1,144 +1,105 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CBasicStructs import * -from .EveProcessingStatus import * - -EVE_MAX_HAND_DETECTIONS = 8 -EVE_MAX_DYNAMIC_GESTURE_SEQUENCE = 8 -EVE_MAX_CUSTOM_STATIC_GESTURES = 20 -EVE_MAX_STATIC_GESTURES = 40 -EVE_HAND_LANDMARKS_SIZE = 2 -EVE_HAND_LANDMARK_SIZE = 11 -EVE_STATIC_GESTURE_SIZE = 22 -EVE_DYNAMIC_GESTURE_SIZE = 12 - -class EveRunHandLandmarks(CtypesEnum): - EVE_HAND_LANDMARKS_ALL_HANDS = 0 - EVE_HAND_LANDMARKS_MAIN_HAND_ONLY = 1 - EVE_HAND_LANDMARKS_SIZE = 2 - -class EveHandLandmark(CtypesEnum): - EVE_WRIST = 0 - EVE_THUMB_IP = 1 - EVE_THUMB_TIP = 2 - EVE_INDEX_MCP = 3 - EVE_INDEX_TIP = 4 - EVE_MIDDLE_MCP = 5 - EVE_MIDDLE_TIP = 6 - EVE_RING_MCP = 7 - EVE_RING_TIP = 8 - EVE_PINKY_MCP = 9 - EVE_PINKY_TIP = 10 - EVE_HAND_LANDMARK_SIZE = 11 - -class EveGestureQuality(CtypesEnum): - EVE_POOR_QUALITY_LOW_CONFIDENCE = 0 - EVE_POOR_QUALITY_HAND_OVER_FACE = 1 - EVE_GOOD_QUALITY = 2 - -class EveStaticGestureType(CtypesEnum): - EVE_STATIC_GESTURE_NONE = 0 - EVE_OPEN_HAND = 1 - EVE_OPEN_HAND_LEFT = 2 - EVE_OPEN_HAND_RIGHT = 3 - EVE_CLOSED_HAND = 4 - EVE_THUMBS_LEFT = 5 - EVE_THUMBS_RIGHT = 6 - EVE_RESERVED_STATIC_GESTURE_1 = 7 - EVE_RESERVED_STATIC_GESTURE_2 = 8 - EVE_RESERVED_STATIC_GESTURE_3 = 9 - EVE_RESERVED_STATIC_GESTURE_4 = 10 - EVE_RESERVED_STATIC_GESTURE_5 = 11 - EVE_CUSTOM_STATIC_GESTURE_1 = 12 - EVE_CUSTOM_STATIC_GESTURE_2 = 13 - EVE_CUSTOM_STATIC_GESTURE_3 = 14 - EVE_CUSTOM_STATIC_GESTURE_4 = 15 - EVE_CUSTOM_STATIC_GESTURE_5 = 16 - EVE_CUSTOM_STATIC_GESTURE_6 = 17 - EVE_CUSTOM_STATIC_GESTURE_7 = 18 - EVE_CUSTOM_STATIC_GESTURE_8 = 19 - EVE_CUSTOM_STATIC_GESTURE_9 = 20 - EVE_CUSTOM_STATIC_GESTURE_10 = 21 - EVE_STATIC_GESTURE_SIZE = 22 - -class EveDynamicGestureType(CtypesEnum): - EVE_DYNAMIC_GESTURE_NONE = 0 - EVE_GRAB = 1 - EVE_RESERVED_DYNAMIC_GESTURE_1 = 2 - EVE_RESERVED_DYNAMIC_GESTURE_2 = 3 - EVE_RESERVED_DYNAMIC_GESTURE_3 = 4 - EVE_RESERVED_DYNAMIC_GESTURE_4 = 5 - EVE_RESERVED_DYNAMIC_GESTURE_5 = 6 - EVE_CUSTOM_DYNAMIC_GESTURE_1 = 7 - EVE_CUSTOM_DYNAMIC_GESTURE_2 = 8 - EVE_CUSTOM_DYNAMIC_GESTURE_3 = 9 - EVE_CUSTOM_DYNAMIC_GESTURE_4 = 10 - EVE_CUSTOM_DYNAMIC_GESTURE_5 = 11 - EVE_DYNAMIC_GESTURE_SIZE = 12 - -class EveStaticGesture(ctypes.Structure): - _fields_ = [ - ("handId", ctypes.c_int), - ("isMainUserHand", ctypes.c_int), - ("type", ctypes.c_int), - ("confidence", ctypes.c_float), - ("quality", ctypes.c_int), - ] - -class EveStaticGestures(ctypes.Structure): - _fields_ = [ - ("count", ctypes.c_uint), - ("gestures", EveStaticGesture * EVE_MAX_HAND_DETECTIONS), - ] - -class EveStaticGestureDefinition(ctypes.Structure): - _fields_ = [ - ("gestureType", ctypes.c_int), - ("id", ctypes.c_uint), - ("landmarksMap", CPoint2f * EVE_HAND_LANDMARK_SIZE), - ] - -class EveDynamicGesture(ctypes.Structure): - _fields_ = [ - ("handId", ctypes.c_int), - ("isMainUserHand", ctypes.c_int), - ("type", ctypes.c_int), - ("quality", ctypes.c_int), - ] - -class EveDynamicGestures(ctypes.Structure): - _fields_ = [ - ("count", ctypes.c_uint), - ("gestures", EveDynamicGesture * EVE_MAX_HAND_DETECTIONS), - ] - -class EveDynamicGestureDefinition(ctypes.Structure): - _fields_ = [ - ("gestureType", ctypes.c_int), - ("sequenceCount", ctypes.c_uint), - ("gestureSequence", ctypes.c_int * EVE_MAX_DYNAMIC_GESTURE_SEQUENCE), - ] - -class EveSingleHandDetection(ctypes.Structure): - _fields_ = [ - ("id", ctypes.c_int), - ("isHandValid", ctypes.c_int), - ("boundingBox", CRect2iWH), - ("boundingBoxScore", ctypes.c_float), - ("landmarksICS", CPoint2f * EVE_HAND_LANDMARK_SIZE), - ("validationScore", ctypes.c_float), - ("inPlaneAngle", ctypes.c_float), - ("depth", ctypes.c_float), - ("isMainUserHand", ctypes.c_int), - ("isInCurrentFrame", ctypes.c_int), - ] - -class EveHandDetections(ctypes.Structure): - _fields_ = [ - ("status", ctypes.c_int), - ("hasFaceROI", ctypes.c_int), - ("faceROI", CRect2fWH), - ("detectedHandCount", ctypes.c_uint), - ("hands", EveSingleHandDetection * EVE_MAX_HAND_DETECTIONS), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CBasicStructs import * +from .EveProcessingStatus import * + +EVE_MAX_HAND_DETECTIONS = 8 +EVE_MAX_CUSTOM_STATIC_GESTURES = 20 +EVE_MAX_STATIC_GESTURES = 40 +EVE_HAND_LANDMARKS_SIZE = 2 +EVE_HAND_LANDMARK_SIZE = 11 +EVE_STATIC_GESTURE_SIZE = 22 + +class EveRunHandLandmarks(CtypesEnum): + EVE_HAND_LANDMARKS_ALL_HANDS = 0 + EVE_HAND_LANDMARKS_MAIN_HAND_ONLY = 1 + EVE_HAND_LANDMARKS_SIZE = 2 + +class EveHandLandmark(CtypesEnum): + EVE_WRIST = 0 + EVE_THUMB_IP = 1 + EVE_THUMB_TIP = 2 + EVE_INDEX_MCP = 3 + EVE_INDEX_TIP = 4 + EVE_MIDDLE_MCP = 5 + EVE_MIDDLE_TIP = 6 + EVE_RING_MCP = 7 + EVE_RING_TIP = 8 + EVE_PINKY_MCP = 9 + EVE_PINKY_TIP = 10 + EVE_HAND_LANDMARK_SIZE = 11 + +class EveGestureQuality(CtypesEnum): + EVE_POOR_QUALITY_LOW_CONFIDENCE = 0 + EVE_POOR_QUALITY_HAND_OVER_FACE = 1 + EVE_GOOD_QUALITY = 2 + +class EveStaticGestureType(CtypesEnum): + EVE_STATIC_GESTURE_NONE = 0 + EVE_OPEN_HAND = 1 + EVE_OPEN_HAND_LEFT = 2 + EVE_OPEN_HAND_RIGHT = 3 + EVE_CLOSED_HAND = 4 + EVE_THUMBS_LEFT = 5 + EVE_THUMBS_RIGHT = 6 + EVE_RESERVED_STATIC_GESTURE_1 = 7 + EVE_RESERVED_STATIC_GESTURE_2 = 8 + EVE_RESERVED_STATIC_GESTURE_3 = 9 + EVE_RESERVED_STATIC_GESTURE_4 = 10 + EVE_RESERVED_STATIC_GESTURE_5 = 11 + EVE_CUSTOM_STATIC_GESTURE_1 = 12 + EVE_CUSTOM_STATIC_GESTURE_2 = 13 + EVE_CUSTOM_STATIC_GESTURE_3 = 14 + EVE_CUSTOM_STATIC_GESTURE_4 = 15 + EVE_CUSTOM_STATIC_GESTURE_5 = 16 + EVE_CUSTOM_STATIC_GESTURE_6 = 17 + EVE_CUSTOM_STATIC_GESTURE_7 = 18 + EVE_CUSTOM_STATIC_GESTURE_8 = 19 + EVE_CUSTOM_STATIC_GESTURE_9 = 20 + EVE_CUSTOM_STATIC_GESTURE_10 = 21 + EVE_STATIC_GESTURE_SIZE = 22 + +class EveStaticGesture(ctypes.Structure): + _fields_ = [ + ("handId", ctypes.c_int), + ("isMainUserHand", ctypes.c_int), + ("type", ctypes.c_int), + ("confidence", ctypes.c_float), + ("quality", ctypes.c_int), + ] + +class EveStaticGestures(ctypes.Structure): + _fields_ = [ + ("count", ctypes.c_uint), + ("gestures", EveStaticGesture * EVE_MAX_HAND_DETECTIONS), + ] + +class EveStaticGestureDefinition(ctypes.Structure): + _fields_ = [ + ("gestureType", ctypes.c_int), + ("id", ctypes.c_uint), + ("landmarksMap", CPoint2f * EVE_HAND_LANDMARK_SIZE), + ] + +class EveSingleHandDetection(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_int), + ("isHandValid", ctypes.c_int), + ("boundingBox", CRect2iWH), + ("boundingBoxScore", ctypes.c_float), + ("landmarksICS", CPoint2f * EVE_HAND_LANDMARK_SIZE), + ("validationScore", ctypes.c_float), + ("inPlaneAngle", ctypes.c_float), + ("depth", ctypes.c_float), + ("isMainUserHand", ctypes.c_int), + ] + +class EveHandDetections(ctypes.Structure): + _fields_ = [ + ("status", ctypes.c_int), + ("hasFaceROI", ctypes.c_int), + ("faceROI", CRect2fWH), + ("detectedHandCount", ctypes.c_uint), + ("hands", EveSingleHandDetection * EVE_MAX_HAND_DETECTIONS), + ] + diff --git a/shared/eve_python/structs/CImageManipulation.py b/shared/eve_python/structs/CImageManipulation.py index 89ebfe21ee394e86c3a5a62126fe27bd68db3010..17bafc2c99fa00a36c6d84a03e4d6c1715d36876 100644 --- a/shared/eve_python/structs/CImageManipulation.py +++ b/shared/eve_python/structs/CImageManipulation.py @@ -1,12 +1,12 @@ -import ctypes -from ctypes_enum import CtypesEnum - - -class CImageManipulationSettings(ctypes.Structure): - _fields_ = [ - ("mirrorImage", ctypes.c_uint), - ("reserved1", ctypes.c_uint), - ("reserved2", ctypes.c_uint), - ("reserved3", ctypes.c_uint), - ] - +import ctypes +from ctypes_enum import CtypesEnum + + +class CImageManipulationSettings(ctypes.Structure): + _fields_ = [ + ("mirrorImage", ctypes.c_uint), + ("reserved1", ctypes.c_uint), + ("reserved2", ctypes.c_uint), + ("reserved3", ctypes.c_uint), + ] + diff --git a/shared/eve_python/structs/CKarolinska.py b/shared/eve_python/structs/CKarolinska.py index e19807667ad7f80bf3086928200e5b44a5a3e55e..9abf6b338c31c3b61fd9cdf6c925a8979a9237ef 100644 --- a/shared/eve_python/structs/CKarolinska.py +++ b/shared/eve_python/structs/CKarolinska.py @@ -1,57 +1,57 @@ -import ctypes -from ctypes_enum import CtypesEnum - - -class EveKarolinskaSleepiness(CtypesEnum): - EVE_KAROLINSKA_DISABLED = 0 - EVE_KAROLINSKA_1 = 1 - EVE_KAROLINSKA_2 = 2 - EVE_KAROLINSKA_3 = 3 - EVE_KAROLINSKA_4 = 4 - EVE_KAROLINSKA_5 = 5 - EVE_KAROLINSKA_6 = 6 - EVE_KAROLINSKA_7 = 7 - EVE_KAROLINSKA_8 = 8 - EVE_KAROLINSKA_9 = 9 - EVE_KAROLINSKA_MAX = 10 - -class EveEyeClosureState(CtypesEnum): - EVE_EYE_STATE_UNKNOWN = 0 - EVE_EYE_OPEN = 1 - EVE_EYE_CLOSED = 2 - -class EveKarolinskaStatus(CtypesEnum): - EVE_KAROLINSKA_OFF = 0 - EVE_KAROLINSKA_NO_FACE = 1 - EVE_KAROLINSKA_BLINKS_ONLY = 2 - EVE_KAROLINSKA_ON = 3 - -class CEyeState(ctypes.Structure): - _fields_ = [ - ("state", ctypes.c_int), - ("closure", ctypes.c_float), - ("confidence", ctypes.c_float), - ("eyelidDistanceMM", ctypes.c_float), - ] - -class CEyeStates(ctypes.Structure): - _fields_ = [ - ("left", CEyeState), - ("right", CEyeState), - ("fused", CEyeState), - ("blinkCount", ctypes.c_uint), - ] - -class CKarolinskaData(ctypes.Structure): - _fields_ = [ - ("status", ctypes.c_int), - ("scale", ctypes.c_int), - ("headPitchScale", ctypes.c_int), - ("yawnScale", ctypes.c_int), - ("blinkDurationScale", ctypes.c_int), - ("yawn", ctypes.c_float), - ("yawnConfidence", ctypes.c_float), - ("yawnCount", ctypes.c_uint), - ("eyes", CEyeStates), - ] - +import ctypes +from ctypes_enum import CtypesEnum + + +class EveKarolinskaSleepiness(CtypesEnum): + EVE_KAROLINSKA_DISABLED = 0 + EVE_KAROLINSKA_1 = 1 + EVE_KAROLINSKA_2 = 2 + EVE_KAROLINSKA_3 = 3 + EVE_KAROLINSKA_4 = 4 + EVE_KAROLINSKA_5 = 5 + EVE_KAROLINSKA_6 = 6 + EVE_KAROLINSKA_7 = 7 + EVE_KAROLINSKA_8 = 8 + EVE_KAROLINSKA_9 = 9 + EVE_KAROLINSKA_MAX = 10 + +class EveEyeClosureState(CtypesEnum): + EVE_EYE_STATE_UNKNOWN = 0 + EVE_EYE_OPEN = 1 + EVE_EYE_CLOSED = 2 + +class EveKarolinskaStatus(CtypesEnum): + EVE_KAROLINSKA_OFF = 0 + EVE_KAROLINSKA_NO_FACE = 1 + EVE_KAROLINSKA_BLINKS_ONLY = 2 + EVE_KAROLINSKA_ON = 3 + +class CEyeState(ctypes.Structure): + _fields_ = [ + ("state", ctypes.c_int), + ("closure", ctypes.c_float), + ("confidence", ctypes.c_float), + ("eyelidDistanceMM", ctypes.c_float), + ] + +class CEyeStates(ctypes.Structure): + _fields_ = [ + ("left", CEyeState), + ("right", CEyeState), + ("fused", CEyeState), + ("blinkCount", ctypes.c_uint), + ] + +class CKarolinskaData(ctypes.Structure): + _fields_ = [ + ("status", ctypes.c_int), + ("scale", ctypes.c_int), + ("headPitchScale", ctypes.c_int), + ("yawnScale", ctypes.c_int), + ("blinkDurationScale", ctypes.c_int), + ("yawn", ctypes.c_float), + ("yawnConfidence", ctypes.c_float), + ("yawnCount", ctypes.c_uint), + ("eyes", CEyeStates), + ] + diff --git a/shared/eve_python/structs/CLandmarkMaps.py b/shared/eve_python/structs/CLandmarkMaps.py index 75adb9a72bc3426e5b80f54a0fba7092ee353198..6e7bc37b14471f2df9f8ff0113f500aad473defe 100644 --- a/shared/eve_python/structs/CLandmarkMaps.py +++ b/shared/eve_python/structs/CLandmarkMaps.py @@ -1,40 +1,40 @@ -import ctypes -from ctypes_enum import CtypesEnum - -EVE_HMI_P_L_SIZE = 23 -EVE_HMI_S_L_SIZE = 5 - -class EveHmiPrimaryLandmarks(CtypesEnum): - EVE_HMI_P_L_RIGHT_TEMPLE = 0 - EVE_HMI_P_L_RIGHT_JAW = 1 - EVE_HMI_P_L_CENTER_JAW = 2 - EVE_HMI_P_L_LEFT_JAW = 3 - EVE_HMI_P_L_LEFT_TEMPLE = 4 - EVE_HMI_P_L_RIGHT_EYEBROW = 5 - EVE_HMI_P_L_LEFT_EYEBROW = 6 - EVE_HMI_P_L_NOSE_BRIDGE = 7 - EVE_HMI_P_L_NOSE_TIP_HIGH = 8 - EVE_HMI_P_L_NOSE_TIP = 9 - EVE_HMI_P_L_NOSE_TIP_LOW = 10 - EVE_HMI_P_L_RIGHT_EYE_1 = 11 - EVE_HMI_P_L_RIGHT_EYE_2 = 12 - EVE_HMI_P_L_LEFT_EYE_1 = 13 - EVE_HMI_P_L_LEFT_EYE_2 = 14 - EVE_HMI_P_L_MOUTH_RIGHT_CORNER = 15 - EVE_HMI_P_L_MOUTH_UPPER_LIP = 16 - EVE_HMI_P_L_MOUTH_LEFT_CORNER = 17 - EVE_HMI_P_L_MOUTH_LOWER_LIP = 18 - EVE_HMI_P_L_MOUTH_OPEN_TOP = 19 - EVE_HMI_P_L_MOUTH_OPEN_BOTTOM = 20 - EVE_HMI_P_L_RIGHT_PUPIL = 21 - EVE_HMI_P_L_LEFT_PUPIL = 22 - EVE_HMI_P_L_SIZE = 23 - -class EveHmiSecondaryLandmarks(CtypesEnum): - EVE_HMI_S_L_RIGHT_PUPIL = 0 - EVE_HMI_S_L_LEFT_PUPIL = 1 - EVE_HMI_S_L_NOSE_TIP_LOW = 2 - EVE_HMI_S_L_RIGHT_MOUTH_CORNER = 3 - EVE_HMI_S_L_LEFT_MOUTH_CORNER = 4 - EVE_HMI_S_L_SIZE = 5 - +import ctypes +from ctypes_enum import CtypesEnum + +EVE_HMI_P_L_SIZE = 23 +EVE_HMI_S_L_SIZE = 5 + +class EveHmiPrimaryLandmarks(CtypesEnum): + EVE_HMI_P_L_RIGHT_TEMPLE = 0 + EVE_HMI_P_L_RIGHT_JAW = 1 + EVE_HMI_P_L_CENTER_JAW = 2 + EVE_HMI_P_L_LEFT_JAW = 3 + EVE_HMI_P_L_LEFT_TEMPLE = 4 + EVE_HMI_P_L_RIGHT_EYEBROW = 5 + EVE_HMI_P_L_LEFT_EYEBROW = 6 + EVE_HMI_P_L_NOSE_BRIDGE = 7 + EVE_HMI_P_L_NOSE_TIP_HIGH = 8 + EVE_HMI_P_L_NOSE_TIP = 9 + EVE_HMI_P_L_NOSE_TIP_LOW = 10 + EVE_HMI_P_L_RIGHT_EYE_1 = 11 + EVE_HMI_P_L_RIGHT_EYE_2 = 12 + EVE_HMI_P_L_LEFT_EYE_1 = 13 + EVE_HMI_P_L_LEFT_EYE_2 = 14 + EVE_HMI_P_L_MOUTH_RIGHT_CORNER = 15 + EVE_HMI_P_L_MOUTH_UPPER_LIP = 16 + EVE_HMI_P_L_MOUTH_LEFT_CORNER = 17 + EVE_HMI_P_L_MOUTH_LOWER_LIP = 18 + EVE_HMI_P_L_MOUTH_OPEN_TOP = 19 + EVE_HMI_P_L_MOUTH_OPEN_BOTTOM = 20 + EVE_HMI_P_L_RIGHT_PUPIL = 21 + EVE_HMI_P_L_LEFT_PUPIL = 22 + EVE_HMI_P_L_SIZE = 23 + +class EveHmiSecondaryLandmarks(CtypesEnum): + EVE_HMI_S_L_RIGHT_PUPIL = 0 + EVE_HMI_S_L_LEFT_PUPIL = 1 + EVE_HMI_S_L_NOSE_TIP_LOW = 2 + EVE_HMI_S_L_RIGHT_MOUTH_CORNER = 3 + EVE_HMI_S_L_LEFT_MOUTH_CORNER = 4 + EVE_HMI_S_L_SIZE = 5 + diff --git a/shared/eve_python/structs/CLicenseResult.py b/shared/eve_python/structs/CLicenseResult.py new file mode 100644 index 0000000000000000000000000000000000000000..9bc26747a9296580aba24b94674e79b41793fd0b --- /dev/null +++ b/shared/eve_python/structs/CLicenseResult.py @@ -0,0 +1,10 @@ +import ctypes +from ctypes_enum import CtypesEnum + + +class EveLicenseResult(ctypes.Structure): + _fields_ = [ + ("licenseReturnCode", ctypes.c_uint), + ("licenseWarnings", ctypes.c_byte * 256), + ] + diff --git a/shared/eve_python/structs/CROIStructs.py b/shared/eve_python/structs/CROIStructs.py index b5d7d214c7e7aa51c5e10532c53da98be2e42a8d..db88f1d22d8304d593f138621e017b9130b0fc1f 100644 --- a/shared/eve_python/structs/CROIStructs.py +++ b/shared/eve_python/structs/CROIStructs.py @@ -1,29 +1,29 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveProcessingStatus import * - -EVE_ROI_MAX_SCORE_COUNT = 20 - -class EveROIState(CtypesEnum): - EVE_ROI_STATE_INACTIVE = 0 - EVE_ROI_STATE_ENTERING = 1 - EVE_ROI_STATE_LEAVING = 2 - EVE_ROI_STATE_SELECTED = 3 - -class CROIScore(ctypes.Structure): - _fields_ = [ - ("id", ctypes.c_uint), - ("intersectionScore", ctypes.c_double), - ("filteredScore", ctypes.c_double), - ("state", ctypes.c_int), - ] - -class CROIScoreData(ctypes.Structure): - _fields_ = [ - ("processingStatus", ctypes.c_int), - ("fusedRoiScoresCount", ctypes.c_uint), - ("fusedRoiScores", CROIScore * EVE_ROI_MAX_SCORE_COUNT), - ("faceRoiScoresCount", ctypes.c_uint), - ("faceRoiScores", CROIScore * EVE_ROI_MAX_SCORE_COUNT), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .EveProcessingStatus import * + +EVE_ROI_MAX_SCORE_COUNT = 20 + +class EveROIState(CtypesEnum): + EVE_ROI_STATE_INACTIVE = 0 + EVE_ROI_STATE_ENTERING = 1 + EVE_ROI_STATE_LEAVING = 2 + EVE_ROI_STATE_SELECTED = 3 + +class CROIScore(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_uint), + ("intersectionScore", ctypes.c_double), + ("filteredScore", ctypes.c_double), + ("state", ctypes.c_int), + ] + +class CROIScoreData(ctypes.Structure): + _fields_ = [ + ("processingStatus", ctypes.c_int), + ("fusedRoiScoresCount", ctypes.c_uint), + ("fusedRoiScores", CROIScore * EVE_ROI_MAX_SCORE_COUNT), + ("faceRoiScoresCount", ctypes.c_uint), + ("faceRoiScores", CROIScore * EVE_ROI_MAX_SCORE_COUNT), + ] + diff --git a/shared/eve_python/structs/CVisualSpeechStructs.py b/shared/eve_python/structs/CVisualSpeechStructs.py index 6696e6c823217ab47a1ba642cbabaa28e163b1f1..0473dc611989cbefc395a2e85230c779d9be0535 100644 --- a/shared/eve_python/structs/CVisualSpeechStructs.py +++ b/shared/eve_python/structs/CVisualSpeechStructs.py @@ -1,18 +1,18 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveProcessingStatus import * - - -class EveVisualSpeechState(CtypesEnum): - EVE_NOT_SET = 0 - EVE_NOT_SPEAKING = 1 - EVE_SPEAKING = 2 - -class CVisualSpeechData(ctypes.Structure): - _fields_ = [ - ("processingStatus", ctypes.c_int), - ("speechState", ctypes.c_int), - ("notSpeakingProbability", ctypes.c_double), - ("speakingProbability", ctypes.c_double), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .EveProcessingStatus import * + + +class EveVisualSpeechState(CtypesEnum): + EVE_NOT_SET = 0 + EVE_NOT_SPEAKING = 1 + EVE_SPEAKING = 2 + +class CVisualSpeechData(ctypes.Structure): + _fields_ = [ + ("processingStatus", ctypes.c_int), + ("speechState", ctypes.c_int), + ("notSpeakingProbability", ctypes.c_double), + ("speakingProbability", ctypes.c_double), + ] + diff --git a/shared/eve_python/structs/EveAlgorithm.py b/shared/eve_python/structs/EveAlgorithm.py index 48b251b546bd62fee87cb03f16e7ced724869aab..ca2fa7f8db978e9e90801b97cf34a9e96f3a9aa4 100644 --- a/shared/eve_python/structs/EveAlgorithm.py +++ b/shared/eve_python/structs/EveAlgorithm.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveAlgorithmStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveAlgorithmStructs import * + + diff --git a/shared/eve_python/structs/EveAlgorithmStructs.py b/shared/eve_python/structs/EveAlgorithmStructs.py index 79f7337f8c1699f7c0c18a069be4631224537853..dbf1255b3b4d08f48b1b9f2dbf7b58268c08ce5e 100644 --- a/shared/eve_python/structs/EveAlgorithmStructs.py +++ b/shared/eve_python/structs/EveAlgorithmStructs.py @@ -1,14 +1,14 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CAlgorithms import * -from .EveErrors import * - -EVE_ALGORITHMS_SIZE = 20 - -class EveSupportedAlgorithms(ctypes.Structure): - _fields_ = [ - ("errorCode", ctypes.c_int), - ("count", ctypes.c_uint), - ("algorithms", ctypes.c_int * EVE_ALGORITHMS_SIZE), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CAlgorithms import * +from .EveErrors import * + +EVE_ALGORITHMS_SIZE = 20 + +class EveSupportedAlgorithms(ctypes.Structure): + _fields_ = [ + ("errorCode", ctypes.c_int), + ("count", ctypes.c_uint), + ("algorithms", ctypes.c_int * EVE_ALGORITHMS_SIZE), + ] + diff --git a/shared/eve_python/structs/EveCallbackReturnData.py b/shared/eve_python/structs/EveCallbackReturnData.py index 7e05c11672da36c1a4ada3f4d7e4a2dff8b4c32d..673156d95b19ab6cf5ebe8aa7f04038c4b8ed919 100644 --- a/shared/eve_python/structs/EveCallbackReturnData.py +++ b/shared/eve_python/structs/EveCallbackReturnData.py @@ -1,13 +1,13 @@ -import ctypes -from ctypes_enum import CtypesEnum - - -class EveRequestedProcessingState(CtypesEnum): - EVE_REQUESTED_PROCESSING_STATE_CONTINUE = 0 - EVE_REQUESTED_PROCESSING_STATE_STOP = 1 - -class EveProcessingCallbackReturnData(ctypes.Structure): - _fields_ = [ - ("requestedState", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum + + +class EveRequestedProcessingState(CtypesEnum): + EVE_REQUESTED_PROCESSING_STATE_CONTINUE = 0 + EVE_REQUESTED_PROCESSING_STATE_STOP = 1 + +class EveProcessingCallbackReturnData(ctypes.Structure): + _fields_ = [ + ("requestedState", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveCamera.py b/shared/eve_python/structs/EveCamera.py index 49775c99e9b9388610997b20a10dd9c15681ba75..3893dd035ac359374600b1d0b5cdd34346b246c3 100644 --- a/shared/eve_python/structs/EveCamera.py +++ b/shared/eve_python/structs/EveCamera.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveCameraStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveCameraStructs import * + + diff --git a/shared/eve_python/structs/EveCameraStructs.py b/shared/eve_python/structs/EveCameraStructs.py index 31f14735f4cf0c64707920be0c26ec922ad41b9d..49ffb5e5ddfc3ddf7467303eb00a9dcb3cf00ba7 100644 --- a/shared/eve_python/structs/EveCameraStructs.py +++ b/shared/eve_python/structs/EveCameraStructs.py @@ -1,27 +1,47 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CCameraStructs import * -from .EveErrors import * - -EVE_CAMERA_FORMATS_SIZE = 20 - -class EveCameraFormats(ctypes.Structure): - _fields_ = [ - ("formats", CCameraFormat * EVE_CAMERA_FORMATS_SIZE), - ("formatsCount", ctypes.c_uint), - ("hadMoreFormats", ctypes.c_uint), - ("error", ctypes.c_int), - ] - -class EveCamera(ctypes.Structure): - _fields_ = [ - ("data", CCamera), - ("error", ctypes.c_int), - ] - -class EveNumberOfCameras(ctypes.Structure): - _fields_ = [ - ("count", ctypes.c_uint), - ("error", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CCameraStructs import * +from .EveErrors import * + +EVE_CAMERA_FORMATS_SIZE = 20 +EVE_MONITOR_SIZE = 4 + +class EveCameraFormats(ctypes.Structure): + _fields_ = [ + ("formats", CCameraFormat * EVE_CAMERA_FORMATS_SIZE), + ("formatsCount", ctypes.c_uint), + ("hadMoreFormats", ctypes.c_uint), + ("error", ctypes.c_int), + ] + +class EveCamera(ctypes.Structure): + _fields_ = [ + ("data", CCamera), + ("error", ctypes.c_int), + ] + +class EveNumberOfCameras(ctypes.Structure): + _fields_ = [ + ("count", ctypes.c_uint), + ("error", ctypes.c_int), + ] + +class EveMonitors(ctypes.Structure): + _fields_ = [ + ("monitorCount", ctypes.c_uint), + ("error", ctypes.c_int), + ("monitors", CMonitor * EVE_MONITOR_SIZE), + ] + +class EveCameraLocation(ctypes.Structure): + _fields_ = [ + ("location", CCameraLocation), + ("error", ctypes.c_int), + ] + +class EveCameraParameters(ctypes.Structure): + _fields_ = [ + ("parameters", CCameraParameters), + ("error", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveConfigurationParameters.py b/shared/eve_python/structs/EveConfigurationParameters.py index 28b28be60335a27a703be2a87e5fb3103c6ea37b..bbe529a289b569e4e7d54e0c60363cc61fd738d6 100644 --- a/shared/eve_python/structs/EveConfigurationParameters.py +++ b/shared/eve_python/structs/EveConfigurationParameters.py @@ -1,36 +1,45 @@ -import ctypes -from ctypes_enum import CtypesEnum - -EVE_PIPELINE_TYPE_SIZE = 2 - -class EveImageProvider(CtypesEnum): - EVE_CAMERA = 0 - EVE_CLIENT_PROVIDED = 1 - -class EveGpuPreference(CtypesEnum): - EVE_GPU_LOW_POWER = 0 - EVE_GPU_HIGH_PERFORMANCE = 1 - EVE_NO_GPU = 2 - -class EveStartupType(CtypesEnum): - EVE_SYNC = 0 - EVE_ASYNC = 1 - -class EveProcessingPipelineType(CtypesEnum): - EVE_FULL = 0 - EVE_HMI = 1 - EVE_PIPELINE_TYPE_SIZE = 2 - -class EveStartupParameters(ctypes.Structure): - _fields_ = [ - ("gpuPreference", ctypes.c_int), - ("imageProvider", ctypes.c_int), - ("startupType", ctypes.c_int), - ("pathOverride", ctypes.c_byte * 512), - ] - -class EveProcessingParameters(ctypes.Structure): - _fields_ = [ - ("type", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum + +EVE_PIPELINE_TYPE_SIZE = 2 +EVE_CAMERA_TYPE_SIZE = 3 + +class EveImageProvider(CtypesEnum): + EVE_CAMERA = 0 + EVE_CLIENT_PROVIDED = 1 + +class EveGpuPreference(CtypesEnum): + EVE_GPU_LOW_POWER = 0 + EVE_GPU_HIGH_PERFORMANCE = 1 + EVE_NO_GPU = 2 + +class EveStartupType(CtypesEnum): + EVE_SYNC = 0 + EVE_ASYNC = 1 + +class EveProcessingPipelineType(CtypesEnum): + EVE_FULL = 0 + EVE_HMI = 1 + EVE_PIPELINE_TYPE_SIZE = 2 + +class EveCameraType(CtypesEnum): + EVE_CAMERA_AUTO = 0 + EVE_CAMERA_RGB = 1 + EVE_CAMERA_IR = 2 + EVE_CAMERA_TYPE_SIZE = 3 + +class EveStartupParameters(ctypes.Structure): + _fields_ = [ + ("gpuPreference", ctypes.c_int), + ("imageProvider", ctypes.c_int), + ("startupType", ctypes.c_int), + ("pathOverride", ctypes.c_byte * 512), + ] + +class EveProcessingParameters(ctypes.Structure): + _fields_ = [ + ("type", ctypes.c_int), + ("cameraType", ctypes.c_int), + ("imageProvider", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveControlInterface.py b/shared/eve_python/structs/EveControlInterface.py index 7bf10108a1e3e3d1ff5f13559acdaa2271e16323..60822d8c3217c890e02f03cad62d127a86cfe825 100644 --- a/shared/eve_python/structs/EveControlInterface.py +++ b/shared/eve_python/structs/EveControlInterface.py @@ -1,9 +1,10 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CCameraStructs import * -from .EveCallbackReturnData import * -from .EveConfigurationParameters import * -from .EveErrors import * -from .EveImageStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .CCameraStructs import * +from .CLicenseResult import * +from .EveCallbackReturnData import * +from .EveConfigurationParameters import * +from .EveErrors import * +from .EveImageStructs import * + + diff --git a/shared/eve_python/structs/EveControlOption.py b/shared/eve_python/structs/EveControlOption.py index 33566e29d3f3700761107a9985aaed7887894a32..42ceca5b2991b298154331b09c8be85b377fd406 100644 --- a/shared/eve_python/structs/EveControlOption.py +++ b/shared/eve_python/structs/EveControlOption.py @@ -1,8 +1,8 @@ -import ctypes -from ctypes_enum import CtypesEnum - - -class EveOptionEnabled(CtypesEnum): - EVE_OPTION_DISABLED = 0 - EVE_OPTION_ENABLED = 1 - +import ctypes +from ctypes_enum import CtypesEnum + + +class EveOptionEnabled(CtypesEnum): + EVE_OPTION_DISABLED = 0 + EVE_OPTION_ENABLED = 1 + diff --git a/shared/eve_python/structs/EveErrors.py b/shared/eve_python/structs/EveErrors.py index 2f0ce4f3af7bd4e71ceccab3af7aa01a37783f60..0cecee834252675b23aa0584c505a1ad6010c3a6 100644 --- a/shared/eve_python/structs/EveErrors.py +++ b/shared/eve_python/structs/EveErrors.py @@ -1,22 +1,26 @@ -import ctypes -from ctypes_enum import CtypesEnum - - -class EveError(CtypesEnum): - EVE_ERROR_NO_ERROR = 0 - EVE_ERROR_NOT_CREATED = 1 - EVE_ERROR_NOT_STARTED = 2 - EVE_ERROR_PIPELINE_NOT_FOUND = 3 - EVE_ERROR_CAMERA_MANAGER_NOT_FOUND = 4 - EVE_ERROR_NO_CALLBACK = 5 - EVE_ERROR_NOT_ACCESSED_FROM_CALLBACK = 6 - EVE_INVALID_IMAGE_ENCODING = 7 - EVE_CALLBACK_WITHOUT_CONNECTING_TO_CAMERA = 8 - EVE_CAMERA_INTERACTION_WITHOUT_CAMERA = 9 - EVE_NO_MORE_DATA = 10 - EVE_INVALID_CAMERA_ID = 11 - EVE_FACE_ID_INVALID_THRESHOLD = 12 - EVE_ERROR_NO_CAMERA_INTERACTION_WITH_CAMERA = 13 - EVE_ERROR_NOT_IMPLEMENTED = 14 - EVE_ERROR_UNSUPPORTED_FORMAT = 15 - +import ctypes +from ctypes_enum import CtypesEnum + + +class EveError(CtypesEnum): + EVE_ERROR_NO_ERROR = 0 + EVE_ERROR_NOT_CREATED = 1 + EVE_ERROR_NOT_STARTED = 2 + EVE_ERROR_PIPELINE_NOT_FOUND = 3 + EVE_ERROR_CAMERA_MANAGER_NOT_FOUND = 4 + EVE_ERROR_NO_CALLBACK = 5 + EVE_ERROR_NOT_ACCESSED_FROM_CALLBACK = 6 + EVE_INVALID_IMAGE_ENCODING = 7 + EVE_CALLBACK_WITHOUT_CONNECTING_TO_CAMERA = 8 + EVE_CAMERA_INTERACTION_WITHOUT_CAMERA = 9 + EVE_NO_MORE_DATA = 10 + EVE_INVALID_CAMERA_ID = 11 + EVE_FACE_ID_INVALID_THRESHOLD = 12 + EVE_ERROR_NO_CAMERA_INTERACTION_WITH_CAMERA = 13 + EVE_ERROR_NOT_IMPLEMENTED = 14 + EVE_ERROR_UNSUPPORTED_FORMAT = 15 + EVE_ERROR_NO_MONITORS = 16 + EVE_OBJECT_DETECTION_INVALID_THRESHOLD = 17 + EVE_OBJECT_DETECTION_INVALID_MODEL_PATH = 18 + EVE_OBJECT_DETECTION_INVALID_CLASS_COUNT = 19 + diff --git a/shared/eve_python/structs/EveFaceId.py b/shared/eve_python/structs/EveFaceId.py index 41e0a0c46161be056c89e3ec50edb1ff968658d9..7257676a9e49e15de6308ae4d3633e20214d2cb7 100644 --- a/shared/eve_python/structs/EveFaceId.py +++ b/shared/eve_python/structs/EveFaceId.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveFaceIdStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveFaceIdStructs import * + + diff --git a/shared/eve_python/structs/EveFaceIdStructs.py b/shared/eve_python/structs/EveFaceIdStructs.py index d7cc0802aa9a7821966d236165fe2afc86c9e70a..c4eae62a41f479e497b1188cd164f21781e59951 100644 --- a/shared/eve_python/structs/EveFaceIdStructs.py +++ b/shared/eve_python/structs/EveFaceIdStructs.py @@ -1,31 +1,31 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CFaceIdStructs import * -from .EveControlOption import * -from .EveErrors import * - - -class EveFaceIdCalibrationPoseMode(CtypesEnum): - EVE_FACEID_CALIBRATION_FRONTAL_ONLY = 1 - -class EveFaceIdCommandStruct(ctypes.Structure): - _fields_ = [ - ("command", ctypes.c_int), - ("errorCode", ctypes.c_int), - ] - -class EveFaceIdOptions(ctypes.Structure): - _fields_ = [ - ("enabled", ctypes.c_int), - ("calibrationPoses", ctypes.c_int), - ("galleryPath", ctypes.c_byte * 256), - ("threshold", ctypes.c_float), - ("error", ctypes.c_int), - ] - -class EveFaceIdData(ctypes.Structure): - _fields_ = [ - ("data", CFaceIdentityData), - ("error", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CFaceIdStructs import * +from .EveControlOption import * +from .EveErrors import * + + +class EveFaceIdCalibrationPoseMode(CtypesEnum): + EVE_FACEID_CALIBRATION_FRONTAL_ONLY = 1 + +class EveFaceIdCommandStruct(ctypes.Structure): + _fields_ = [ + ("command", ctypes.c_int), + ("errorCode", ctypes.c_int), + ] + +class EveFaceIdOptions(ctypes.Structure): + _fields_ = [ + ("enabled", ctypes.c_int), + ("calibrationPoses", ctypes.c_int), + ("galleryPath", ctypes.c_byte * 256), + ("threshold", ctypes.c_float), + ("error", ctypes.c_int), + ] + +class EveFaceIdData(ctypes.Structure): + _fields_ = [ + ("data", CFaceIdentityData), + ("error", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveFaceTracker.py b/shared/eve_python/structs/EveFaceTracker.py index 9e44ace41a23a2bf1436064f216ea8bb2599f1e8..e10003b9ab1c55c164b28be2b1e05cd8c4615b77 100644 --- a/shared/eve_python/structs/EveFaceTracker.py +++ b/shared/eve_python/structs/EveFaceTracker.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveFaceTrackerStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveFaceTrackerStructs import * + + diff --git a/shared/eve_python/structs/EveFaceTrackerStructs.py b/shared/eve_python/structs/EveFaceTrackerStructs.py index 945706c8a086979972abbf655689796f893287ab..0a65730c8df21458bc24ff38394684079006d0b7 100644 --- a/shared/eve_python/structs/EveFaceTrackerStructs.py +++ b/shared/eve_python/structs/EveFaceTrackerStructs.py @@ -1,40 +1,40 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CFaceData import * -from .EveErrors import * - - -class EveFaceTrackerMinimumMode(CtypesEnum): - EVE_FACETRACKER_MINIMUM_MODE_OFF = 0 - EVE_FACETRACKER_MINIMUM_MODE_MINIMAL = 1 - EVE_FACETRACKER_MINIMUM_MODE_AVERAGE = 2 - EVE_FACETRACKER_MINIMUM_MODE_MAXIMAL = 3 - -class EveFaceTrackerOptions(ctypes.Structure): - _fields_ = [ - ("faceTrackerMode", ctypes.c_int), - ("enableEyeLandmarks", ctypes.c_uint), - ("enable3DFaceTracking", ctypes.c_uint), - ("enablePersonDetection", ctypes.c_uint), - ("fitSecondaryUsers", ctypes.c_uint), - ("error", ctypes.c_int), - ] - -class EveEyes(ctypes.Structure): - _fields_ = [ - ("data", CEyeLandmarks), - ("errorCode", ctypes.c_int), - ] - -class EvePupils(ctypes.Structure): - _fields_ = [ - ("data", CPupilLandmarks), - ("errorCode", ctypes.c_int), - ] - -class EveAllFacesData(ctypes.Structure): - _fields_ = [ - ("faceData", ctypes.POINTER(CAllFaces)), - ("errorCode", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CFaceData import * +from .EveErrors import * + + +class EveFaceTrackerMinimumMode(CtypesEnum): + EVE_FACETRACKER_MINIMUM_MODE_OFF = 0 + EVE_FACETRACKER_MINIMUM_MODE_MINIMAL = 1 + EVE_FACETRACKER_MINIMUM_MODE_AVERAGE = 2 + EVE_FACETRACKER_MINIMUM_MODE_MAXIMAL = 3 + +class EveFaceTrackerOptions(ctypes.Structure): + _fields_ = [ + ("faceTrackerMode", ctypes.c_int), + ("enableEyeLandmarks", ctypes.c_uint), + ("enable3DFaceTracking", ctypes.c_uint), + ("enablePersonDetection", ctypes.c_uint), + ("fitSecondaryUsers", ctypes.c_uint), + ("error", ctypes.c_int), + ] + +class EveEyes(ctypes.Structure): + _fields_ = [ + ("data", CEyeLandmarks), + ("errorCode", ctypes.c_int), + ] + +class EvePupils(ctypes.Structure): + _fields_ = [ + ("data", CPupilLandmarks), + ("errorCode", ctypes.c_int), + ] + +class EveAllFacesData(ctypes.Structure): + _fields_ = [ + ("faceData", ctypes.POINTER(CAllFaces)), + ("errorCode", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveFpga.py b/shared/eve_python/structs/EveFpga.py index 2a60a0e27970002f9569df0690b34267f7c5c1bc..52c2c0387a5b84266ccc68ae6d58cc5076797444 100644 --- a/shared/eve_python/structs/EveFpga.py +++ b/shared/eve_python/structs/EveFpga.py @@ -1,7 +1,7 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CFpgaData import * -from .EveFpgaStructs import * -from .EveImageStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .CFpgaData import * +from .EveFpgaStructs import * +from .EveImageStructs import * + + diff --git a/shared/eve_python/structs/EveFpgaStructs.py b/shared/eve_python/structs/EveFpgaStructs.py index cf9f898a3e4389dc592b85fba74389ec21aa8ae1..9f7813c780f417a45ae9cc3857f17de57d44554a 100644 --- a/shared/eve_python/structs/EveFpgaStructs.py +++ b/shared/eve_python/structs/EveFpgaStructs.py @@ -1,24 +1,24 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CFpgaData import * -from .EveErrors import * - - -class EveFpgaOptions(ctypes.Structure): - _fields_ = [ - ("parameters", CFpgaParameters), - ("error", ctypes.c_int), - ] - -class EveFpgaDebugOptions(ctypes.Structure): - _fields_ = [ - ("enableDrawingOnImage", ctypes.c_uint), - ("error", ctypes.c_int), - ] - -class EveFpgaData(ctypes.Structure): - _fields_ = [ - ("data", ctypes.POINTER(CFpgaData)), - ("error", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CFpgaData import * +from .EveErrors import * + + +class EveFpgaOptions(ctypes.Structure): + _fields_ = [ + ("parameters", CFpgaParameters), + ("error", ctypes.c_int), + ] + +class EveFpgaDebugOptions(ctypes.Structure): + _fields_ = [ + ("enableDrawingOnImage", ctypes.c_uint), + ("error", ctypes.c_int), + ] + +class EveFpgaData(ctypes.Structure): + _fields_ = [ + ("data", ctypes.POINTER(CFpgaData)), + ("error", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveHandGesture.py b/shared/eve_python/structs/EveHandGesture.py index f6a4adf6725096c11c7dbcef0c47303f0d45556e..d052d6295eba75d92222b90744ddd89aed32e312 100644 --- a/shared/eve_python/structs/EveHandGesture.py +++ b/shared/eve_python/structs/EveHandGesture.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveHandGestureStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveHandGestureStructs import * + + diff --git a/shared/eve_python/structs/EveHandGestureStructs.py b/shared/eve_python/structs/EveHandGestureStructs.py index 825fe46f65c4eabc91250787f2397af33cd87237..8ed3a899e8924ffd4b6e31bbbc7a5954018ac3a3 100644 --- a/shared/eve_python/structs/EveHandGestureStructs.py +++ b/shared/eve_python/structs/EveHandGestureStructs.py @@ -1,48 +1,35 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CHandGesture import * -from .EveControlOption import * -from .EveErrors import * - - -class EveHandGestureOptions(ctypes.Structure): - _fields_ = [ - ("enabled", ctypes.c_int), - ("redetectionDelay", ctypes.c_int), - ("async", ctypes.c_int), - ("run", ctypes.c_int), - ("errorCode", ctypes.c_int), - ] - -class EveHandGestureData(ctypes.Structure): - _fields_ = [ - ("hands", ctypes.POINTER(EveHandDetections)), - ("errorCode", ctypes.c_int), - ] - -class EveStaticGestureData(ctypes.Structure): - _fields_ = [ - ("gestures", EveStaticGestures), - ("errorCode", ctypes.c_int), - ] - -class EveDynamicGestureData(ctypes.Structure): - _fields_ = [ - ("gestures", EveDynamicGestures), - ("errorCode", ctypes.c_int), - ] - -class EveStaticGestureDefinitions(ctypes.Structure): - _fields_ = [ - ("count", ctypes.c_uint), - ("errorCode", ctypes.c_int), - ("definitions", EveStaticGestureDefinition * EVE_MAX_STATIC_GESTURES), - ] - -class EveDynamicGestureDefinitions(ctypes.Structure): - _fields_ = [ - ("count", ctypes.c_uint), - ("errorCode", ctypes.c_int), - ("definitions", EveDynamicGestureDefinition * EVE_DYNAMIC_GESTURE_SIZE), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CHandGesture import * +from .EveControlOption import * +from .EveErrors import * + + +class EveHandGestureOptions(ctypes.Structure): + _fields_ = [ + ("enabled", ctypes.c_int), + ("redetectionDelay", ctypes.c_int), + ("async", ctypes.c_int), + ("run", ctypes.c_int), + ("errorCode", ctypes.c_int), + ] + +class EveHandGestureData(ctypes.Structure): + _fields_ = [ + ("hands", ctypes.POINTER(EveHandDetections)), + ("errorCode", ctypes.c_int), + ] + +class EveStaticGestureData(ctypes.Structure): + _fields_ = [ + ("gestures", EveStaticGestures), + ("errorCode", ctypes.c_int), + ] + +class EveStaticGestureDefinitions(ctypes.Structure): + _fields_ = [ + ("count", ctypes.c_uint), + ("errorCode", ctypes.c_int), + ("definitions", EveStaticGestureDefinition * EVE_MAX_STATIC_GESTURES), + ] + diff --git a/shared/eve_python/structs/EveImage.py b/shared/eve_python/structs/EveImage.py index 3071fb0c5b29058ae26e4130bfa0d51220b84388..70300637e20b1a7020ea245d52455653eb87150b 100644 --- a/shared/eve_python/structs/EveImage.py +++ b/shared/eve_python/structs/EveImage.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveImageStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveImageStructs import * + + diff --git a/shared/eve_python/structs/EveImageManipulation.py b/shared/eve_python/structs/EveImageManipulation.py index 1bb76a79a9a8040c206b8e21a973b926f9757004..d4ac5222873809eff4721204dceb16c9b5dd1806 100644 --- a/shared/eve_python/structs/EveImageManipulation.py +++ b/shared/eve_python/structs/EveImageManipulation.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveImageManipulationStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveImageManipulationStructs import * + + diff --git a/shared/eve_python/structs/EveImageManipulationStructs.py b/shared/eve_python/structs/EveImageManipulationStructs.py index 1139a637db637598d3882037a756db705ae0e155..99db73f1c8ee96c6d9bc55b62ea0d0e6abbdc127 100644 --- a/shared/eve_python/structs/EveImageManipulationStructs.py +++ b/shared/eve_python/structs/EveImageManipulationStructs.py @@ -1,12 +1,12 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CImageManipulation import * -from .EveErrors import * - - -class EveImageManipulationOptions(ctypes.Structure): - _fields_ = [ - ("settings", CImageManipulationSettings), - ("errorCode", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CImageManipulation import * +from .EveErrors import * + + +class EveImageManipulationOptions(ctypes.Structure): + _fields_ = [ + ("settings", CImageManipulationSettings), + ("errorCode", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveImageStructs.py b/shared/eve_python/structs/EveImageStructs.py index d80ed90f4d66fcd8e25d74b6bcacc3c07c36a7b6..432899be97ade7894beab0467f3c4716c293a566 100644 --- a/shared/eve_python/structs/EveImageStructs.py +++ b/shared/eve_python/structs/EveImageStructs.py @@ -1,38 +1,53 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CBasicStructs import * -from .EveErrors import * - - -class EveInputImage(ctypes.Structure): - _fields_ = [ - ("data", ctypes.POINTER(ctypes.c_ubyte)), - ("width", ctypes.c_int), - ("height", ctypes.c_int), - ("encoding", ctypes.c_int), - ] - -class EveProcessedImage(ctypes.Structure): - _fields_ = [ - ("data", ctypes.POINTER(ctypes.c_ubyte)), - ("width", ctypes.c_int), - ("height", ctypes.c_int), - ("channels", ctypes.c_int), - ("timestamp", ctypes.c_longlong), - ("error", ctypes.c_int), - ("location", ctypes.c_int), - ] - -class EveProcessedFrameTime(ctypes.Structure): - _fields_ = [ - ("frameTime", ctypes.c_double), - ("error", ctypes.c_int), - ] - -class EveImageFormatRequest(ctypes.Structure): - _fields_ = [ - ("format", ctypes.c_int), - ("location", ctypes.c_int), - ("error", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CBasicStructs import * +from .EveControlOption import * +from .EveErrors import * + + +class EveInputImage(ctypes.Structure): + _fields_ = [ + ("data", ctypes.POINTER(ctypes.c_ubyte)), + ("width", ctypes.c_int), + ("height", ctypes.c_int), + ("encoding", ctypes.c_int), + ] + +class EveProcessedImage(ctypes.Structure): + _fields_ = [ + ("data", ctypes.POINTER(ctypes.c_ubyte)), + ("width", ctypes.c_int), + ("height", ctypes.c_int), + ("channels", ctypes.c_int), + ("timestamp", ctypes.c_longlong), + ("error", ctypes.c_int), + ("location", ctypes.c_int), + ("format", ctypes.c_int), + ] + +class EveProcessedFrameTime(ctypes.Structure): + _fields_ = [ + ("frameTime", ctypes.c_double), + ("error", ctypes.c_int), + ] + +class EveImageFormatRequest(ctypes.Structure): + _fields_ = [ + ("format", ctypes.c_int), + ("location", ctypes.c_int), + ("error", ctypes.c_int), + ] + +class EveRawImageRequest(ctypes.Structure): + _fields_ = [ + ("enabled", ctypes.c_int), + ("recordResults", ctypes.c_int), + ("error", ctypes.c_int), + ] + +class EveJsonData(ctypes.Structure): + _fields_ = [ + ("string", CStringData), + ("error", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveKarolinska.py b/shared/eve_python/structs/EveKarolinska.py index 8c03da5d42b45addcc836dd33a1c38c8faeb7da6..05e843ca40c936fff8dc11d68e7ad9de7fd77689 100644 --- a/shared/eve_python/structs/EveKarolinska.py +++ b/shared/eve_python/structs/EveKarolinska.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveKarolinskaStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveKarolinskaStructs import * + + diff --git a/shared/eve_python/structs/EveKarolinskaStructs.py b/shared/eve_python/structs/EveKarolinskaStructs.py index c36c40bab0b3d099536c793465d9ac87c6f579e6..d6d182be44d7b923d8411329fb93703e88b191b9 100644 --- a/shared/eve_python/structs/EveKarolinskaStructs.py +++ b/shared/eve_python/structs/EveKarolinskaStructs.py @@ -1,19 +1,19 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CKarolinska import * -from .EveControlOption import * -from .EveErrors import * - - -class EveKarolinskaOptions(ctypes.Structure): - _fields_ = [ - ("enabled", ctypes.c_int), - ("error", ctypes.c_int), - ] - -class EveKarolinskaData(ctypes.Structure): - _fields_ = [ - ("data", CKarolinskaData), - ("error", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CKarolinska import * +from .EveControlOption import * +from .EveErrors import * + + +class EveKarolinskaOptions(ctypes.Structure): + _fields_ = [ + ("enabled", ctypes.c_int), + ("error", ctypes.c_int), + ] + +class EveKarolinskaData(ctypes.Structure): + _fields_ = [ + ("data", CKarolinskaData), + ("error", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveObjectDetection.py b/shared/eve_python/structs/EveObjectDetection.py index 6c2a7c8fe5e98e1f960ccd3ead229675bf52efab..6b541e96aa7fa9b2b4d7f2e9dab8007608bcd01a 100644 --- a/shared/eve_python/structs/EveObjectDetection.py +++ b/shared/eve_python/structs/EveObjectDetection.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveObjectDetectionStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveObjectDetectionStructs import * + + diff --git a/shared/eve_python/structs/EveObjectDetectionStructs.py b/shared/eve_python/structs/EveObjectDetectionStructs.py index 900cf2cd68b96063f87e6cb4d601fe7c2e1c9945..7344d3807cd932564d3731b42c822d46cac8d337 100644 --- a/shared/eve_python/structs/EveObjectDetectionStructs.py +++ b/shared/eve_python/structs/EveObjectDetectionStructs.py @@ -1,25 +1,31 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CDetectionStructs import * -from .EveControlOption import * -from .EveErrors import * - - -class EveObjectDetectionOptions(ctypes.Structure): - _fields_ = [ - ("enabled", ctypes.c_int), - ("error", ctypes.c_int), - ] - -class EvePersonDetectionOptions(ctypes.Structure): - _fields_ = [ - ("enabled", ctypes.c_int), - ("error", ctypes.c_int), - ] - -class EveDetectionData(ctypes.Structure): - _fields_ = [ - ("data", ctypes.POINTER(CDetectionData)), - ("error", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CDetectionStructs import * +from .EveControlOption import * +from .EveErrors import * + +EVE_MAX_OBJECT_DETECTION_CLASSES = 128 + +class EveObjectDetectionOptions(ctypes.Structure): + _fields_ = [ + ("enabled", ctypes.c_int), + ("modelPath", ctypes.c_byte * 512), + ("nmsThreshold", ctypes.c_float), + ("iouThreshold", ctypes.c_float), + ("classNames", ctypes.c_byte * EVE_CLASS_ID_NAME_SIZE * EVE_MAX_OBJECT_DETECTION_CLASSES), + ("classCount", ctypes.c_int), + ("error", ctypes.c_int), + ] + +class EvePersonDetectionOptions(ctypes.Structure): + _fields_ = [ + ("enabled", ctypes.c_int), + ("error", ctypes.c_int), + ] + +class EveDetectionData(ctypes.Structure): + _fields_ = [ + ("data", ctypes.POINTER(CDetectionData)), + ("error", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveProcessingStatus.py b/shared/eve_python/structs/EveProcessingStatus.py index 7d9fdf8a0dadf4c38d227051893e049ac28b7e92..4fb13c39ea80196471237edee380133e971af5c6 100644 --- a/shared/eve_python/structs/EveProcessingStatus.py +++ b/shared/eve_python/structs/EveProcessingStatus.py @@ -1,10 +1,10 @@ -import ctypes -from ctypes_enum import CtypesEnum - - -class EveProcessingStatus(CtypesEnum): - EVE_PROCESSING_DISABLED = 0 - EVE_PROCESSING_ENABLED_FAILURE = 1 - EVE_PROCESSING_ENABLED_SUCCESS = 2 - EVE_SOURCED_FROM_FPGA = 3 - +import ctypes +from ctypes_enum import CtypesEnum + + +class EveProcessingStatus(CtypesEnum): + EVE_PROCESSING_DISABLED = 0 + EVE_PROCESSING_ENABLED_FAILURE = 1 + EVE_PROCESSING_ENABLED_SUCCESS = 2 + EVE_SOURCED_FROM_FPGA = 3 + diff --git a/shared/eve_python/structs/EveROI.py b/shared/eve_python/structs/EveROI.py index d577ee75757cb31b6c9f75095307f418d64f6ca3..cdf2dd2480059e7520128216d5aa7437949b2138 100644 --- a/shared/eve_python/structs/EveROI.py +++ b/shared/eve_python/structs/EveROI.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveROIStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveROIStructs import * + + diff --git a/shared/eve_python/structs/EveROIStructs.py b/shared/eve_python/structs/EveROIStructs.py index 5b162b80ff69a26dcae31de8c24411f1c8c3175f..556db22412697a4e03f70b72d7d4421cb1c0a97e 100644 --- a/shared/eve_python/structs/EveROIStructs.py +++ b/shared/eve_python/structs/EveROIStructs.py @@ -1,34 +1,34 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .CROIStructs import * -from .EveControlOption import * -from .EveErrors import * - -EVE_ROI_MAX_COUNT = 20 - -class EveROI(ctypes.Structure): - _fields_ = [ - ("id", ctypes.c_uint), - ("x", ctypes.c_int), - ("y", ctypes.c_int), - ("width", ctypes.c_int), - ("height", ctypes.c_int), - ("scoreThresholdForInactive", ctypes.c_double), - ] - -class EveROIOptions(ctypes.Structure): - _fields_ = [ - ("enabled", ctypes.c_int), - ("roiSelectionResponseTime", ctypes.c_double), - ("roiCount", ctypes.c_uint), - ("rois", EveROI * EVE_ROI_MAX_COUNT), - ("error", ctypes.c_int), - ("headVectorOnly", ctypes.c_uint), - ] - -class EveROIScoreData(ctypes.Structure): - _fields_ = [ - ("data", CROIScoreData), - ("error", ctypes.c_int), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .CROIStructs import * +from .EveControlOption import * +from .EveErrors import * + +EVE_ROI_MAX_COUNT = 20 + +class EveROI(ctypes.Structure): + _fields_ = [ + ("id", ctypes.c_uint), + ("x", ctypes.c_int), + ("y", ctypes.c_int), + ("width", ctypes.c_int), + ("height", ctypes.c_int), + ("scoreThresholdForInactive", ctypes.c_double), + ] + +class EveROIOptions(ctypes.Structure): + _fields_ = [ + ("enabled", ctypes.c_int), + ("roiSelectionResponseTime", ctypes.c_double), + ("roiCount", ctypes.c_uint), + ("rois", EveROI * EVE_ROI_MAX_COUNT), + ("error", ctypes.c_int), + ("headVectorOnly", ctypes.c_uint), + ] + +class EveROIScoreData(ctypes.Structure): + _fields_ = [ + ("data", CROIScoreData), + ("error", ctypes.c_int), + ] + diff --git a/shared/eve_python/structs/EveTiming.py b/shared/eve_python/structs/EveTiming.py index 7185b439663dec5a7eb3cf4bf782c96632e6beba..9dfa6d90d427e5cf7f4953fecc1bf883ec1e5190 100644 --- a/shared/eve_python/structs/EveTiming.py +++ b/shared/eve_python/structs/EveTiming.py @@ -1,5 +1,5 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveTimingStructs import * - - +import ctypes +from ctypes_enum import CtypesEnum +from .EveTimingStructs import * + + diff --git a/shared/eve_python/structs/EveTimingStructs.py b/shared/eve_python/structs/EveTimingStructs.py index b70c1a85193935851802a998c26bcc46df8fd063..e7c0965a52fbd79957be2d6dcf8aac1ea078c587 100644 --- a/shared/eve_python/structs/EveTimingStructs.py +++ b/shared/eve_python/structs/EveTimingStructs.py @@ -1,28 +1,28 @@ -import ctypes -from ctypes_enum import CtypesEnum -from .EveErrors import * - -TIME_SOURCE_SIZE = 16 - - -class EveTiming(ctypes.Structure): - _fields_ = [ - ("errorCode", ctypes.c_int), - ("eveCameraImageAcquisitionTimepoint", ctypes.c_longlong), - ("eveAlgorithmDuration", ctypes.c_longlong), - ("time1", ctypes.c_longlong), - ("time1Source", ctypes.c_byte * TIME_SOURCE_SIZE), - ("time2", ctypes.c_longlong), - ("time2Source", ctypes.c_byte * TIME_SOURCE_SIZE), - ("time3", ctypes.c_longlong), - ("time3Source", ctypes.c_byte * TIME_SOURCE_SIZE), - ("time4", ctypes.c_longlong), - ("time4Source", ctypes.c_byte * TIME_SOURCE_SIZE), - ("time5", ctypes.c_longlong), - ("time5Source", ctypes.c_byte * TIME_SOURCE_SIZE), - ("time6", ctypes.c_longlong), - ("time6Source", ctypes.c_byte * TIME_SOURCE_SIZE), - ("time7", ctypes.c_longlong), - ("time7Source", ctypes.c_byte * TIME_SOURCE_SIZE), - ] - +import ctypes +from ctypes_enum import CtypesEnum +from .EveErrors import * + +TIME_SOURCE_SIZE = 16 + + +class EveTiming(ctypes.Structure): + _fields_ = [ + ("errorCode", ctypes.c_int), + ("eveCameraImageAcquisitionTimepoint", ctypes.c_longlong), + ("eveAlgorithmDuration", ctypes.c_longlong), + ("time1", ctypes.c_longlong), + ("time1Source", ctypes.c_byte * TIME_SOURCE_SIZE), + ("time2", ctypes.c_longlong), + ("time2Source", ctypes.c_byte * TIME_SOURCE_SIZE), + ("time3", ctypes.c_longlong), + ("time3Source", ctypes.c_byte * TIME_SOURCE_SIZE), + ("time4", ctypes.c_longlong), + ("time4Source", ctypes.c_byte * TIME_SOURCE_SIZE), + ("time5", ctypes.c_longlong), + ("time5Source", ctypes.c_byte * TIME_SOURCE_SIZE), + ("time6", ctypes.c_longlong), + ("time6Source", ctypes.c_byte * TIME_SOURCE_SIZE), + ("time7", ctypes.c_longlong), + ("time7Source", ctypes.c_byte * TIME_SOURCE_SIZE), + ] + diff --git a/shared/eve_worker_pool.py b/shared/eve_worker_pool.py index bd1c42bcc8492e0240736660bf0cf0287a15208a..adfd1dfc891582fef934da09da8bf99ebf2d07ce 100644 --- a/shared/eve_worker_pool.py +++ b/shared/eve_worker_pool.py @@ -247,7 +247,7 @@ def _eve_worker_main( ) eve.enable_face_id(enabled=features.face_id) eve.enable_hand_gesture(enabled=features.hand_gesture) - eve.enable_object_detection(model_name=features.mod_model) + eve.enable_object_detection(config=features.mod_model_config) eve.enable_mirror(True) result = eve.inference(frame) del frame @@ -297,6 +297,12 @@ def _eve_worker_main( features = cmd.features + # Workers are reused across jobs, so reset the EVE pipeline + # before each video — otherwise tracking / ideal-user state + # leaks from the previous run and the second video shows no + # ideal user. + eve.reset_pipeline() + # Gallery restore (if requested) — must happen before # applying user feature flags because restore_gallery() # internally enables face/person detection for calibration. @@ -321,7 +327,7 @@ def _eve_worker_main( ) eve.enable_face_id(enabled=features.face_id) eve.enable_hand_gesture(enabled=features.hand_gesture) - eve.enable_object_detection(model_name=features.mod_model) + eve.enable_object_detection(config=features.mod_model_config) eve.enable_mirror(False) # Video processing loop diff --git a/shared/eve_wrapper.py b/shared/eve_wrapper.py index d72625e0e5a52bed76d0bd863867ac6cd2f2f338..20dbe0f683cc0b32f9cea3f8ca38fc6588a4b2ca 100644 --- a/shared/eve_wrapper.py +++ b/shared/eve_wrapper.py @@ -1,589 +1,674 @@ -import ctypes -import glob -import os -import platform -import sys -import time -from dataclasses import dataclass - -import cv2 -import numpy as np -from eve_python import eve_sdk as sdk -from eve_python.structs.CFaceIdStructs import ( - EveFaceIdCommand, - EveFaceIdIdentificationStatus, -) -from log_utils import setup_logger - -logger = setup_logger("EveWrapper") - -_is_windows = platform.system() == "Windows" - -DO_FAKE_MIRROR = True - -# Gate timing instrumentation behind the profiler env var -_TIMING_ENABLED = os.environ.get("ENABLE_PROFILER", "").strip() not in ("", "0", "false") - - -@dataclass -class CalibrationResult: - """Result of a face ID calibration attempt.""" - - success: bool - user_id: int - message: str - - -class EveWrapper: - def __init__(self, eve_bin_path="", eve_lib_path=""): - self._ensure_config_dir() - eve_bin_path, eve_lib_path = self._resolve_eve_paths(eve_bin_path, eve_lib_path) - - self._mirror = False - self._inference_frame_count = 0 - # Per-call timing: {name: [call_count, total_seconds]} - self._timings: dict[str, list[float]] = {} - self._timing_enabled = _TIMING_ENABLED - - image_provider = sdk.structs.EveImageProvider.EVE_CLIENT_PROVIDED - self.eve_sdk = self._load_and_create_eve(eve_bin_path, eve_lib_path, image_provider) - - if image_provider == sdk.structs.EveImageProvider.EVE_CAMERA: - self._set_camera() - - image_request = sdk.structs.EveImageFormatRequest( - location=sdk.structs.EveImageLocation.EVE_CPU, - format=sdk.structs.EveVideoFormat.EVE_BGRA, - ) - self.eve_sdk.EveConfigureProcessedImage(image_request) - - err = self.eve_sdk.StartEveWithParameters( - sdk.structs.EveProcessingParameters(type=sdk.structs.EveProcessingPipelineType.EVE_HMI) - ) - if err != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.info(f"StartEveWithParameters error code: {err}") - sys.exit(err) - - os.chdir(self._backup_cwd) - logger.info("EVE initialized") - - def _record_timing(self, name: str, elapsed: float) -> None: - entry = self._timings.get(name) - if entry is None: - self._timings[name] = [1, elapsed] - else: - entry[0] += 1 - entry[1] += elapsed - - def get_timing_stats(self, reset: bool = False) -> dict[str, tuple[int, float]]: - """Return accumulated per-call timings as {name: (count, total_seconds)}. - - Args: - reset: If True, clear the accumulators after reading. - """ - result = {k: (int(v[0]), v[1]) for k, v in self._timings.items()} - if reset: - self._timings.clear() - return result - - def inference(self, image: np.ndarray) -> np.ndarray: - t = self._timing_enabled - - if self._mirror and DO_FAKE_MIRROR: - if t: - _t0 = time.perf_counter() - image = cv2.flip(image, 1) - if t: - self._record_timing("flip", time.perf_counter() - _t0) - - if t: - _t0 = time.perf_counter() - if not self._send_frame(image): - return image - if t: - self._record_timing("EveSendImageForProcessing", time.perf_counter() - _t0) - - if t: - _t0 = time.perf_counter() - processed_image = self.eve_sdk.EveGetProcessedImage() - if t: - self._record_timing("EveGetProcessedImage", time.perf_counter() - _t0) - if processed_image.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.info(f"EveGetProcessedImage() error code: {processed_image.error}") - sys.exit(processed_image.error) - - if t: - _t0 = time.perf_counter() - img = np.ctypeslib.as_array( - processed_image.data, - shape=(processed_image.height, processed_image.width, processed_image.channels), - ).copy() - del processed_image - if t: - self._record_timing("as_array+copy", time.perf_counter() - _t0) - - if img.shape[2] == 2: - if t: - _t0 = time.perf_counter() - img = cv2.cvtColor(img, cv2.COLOR_YUV2BGR_YUYV) - if t: - self._record_timing("cvtColor", time.perf_counter() - _t0) - elif img.shape[2] == 4: - if t: - _t0 = time.perf_counter() - img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR) - if t: - self._record_timing("cvtColor", time.perf_counter() - _t0) - - # SDK requires all outputs to be consumed before the next frame - if t: - _t0 = time.perf_counter() - person_detection_data = self.eve_sdk.EveGetPersonDetectionData() - if t: - self._record_timing("EveGetPersonDetectionData", time.perf_counter() - _t0) - if person_detection_data.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.info(f"EveGetPersonDetectionData() error code: {person_detection_data.error}") - sys.exit(person_detection_data.error) - del person_detection_data - - if t: - _t0 = time.perf_counter() - all_faces = self.eve_sdk.EveGetAllFaceData() - if t: - self._record_timing("EveGetAllFaceData", time.perf_counter() - _t0) - if all_faces.errorCode == sdk.structs.EveError.EVE_ERROR_NO_ERROR: - face_count = all_faces.faceData.contents.detectedFacesCount - if face_count > 0: - self._inference_frame_count += 1 - # Log face ID data periodically to avoid flooding - if self._inference_frame_count % self._INFERENCE_LOG_INTERVAL == 1: - self._log_all_faces("inference", self._inference_frame_count, all_faces) - del all_faces - - if img.shape[2] in (1, 3): - return img - - print( - f"WRONG FORMAT: {img.shape} " - "(Consider converting it above here, or making EveImageFormatRequest work)" - ) - return None - - def shutdown(self) -> None: - """Cleanly shut down the Eve SDK instance.""" - try: - self.eve_sdk.ShutdownEve() - logger.info("EVE shut down") - except Exception as exc: - logger.warning(f"EVE shutdown error: {exc}") - - def enable_mirror(self, enabled: bool = True) -> None: - """Enable or disable image mirroring.""" - if DO_FAKE_MIRROR: - self._mirror = enabled - return - options = sdk.structs.EveImageManipulationOptions() - options.settings.mirrorImage = 1 if enabled else 0 - result = self.eve_sdk.EveConfigureImageManipulation(options) - if result.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.info(f"EveConfigureImageManipulation() error code: {result.errorCode}") - sys.exit(result.errorCode) - - def enable_object_detection(self, model_name: str | None = "GMOD-80"): - """Enable EVE's object detection (MOD) feature. - - Pass ``None`` / ``""`` / ``False`` to disable. Any truthy ``model_name`` - enables MOD; the name is recorded for future model-switching support - but is **not** acted on yet — the EVE SDK currently ships a single - hardcoded MOD model (GMOD-80, see ``EveEthosNpu/Models.h``). - - TODO: When the EVE C SDK gains a ``EveLoadObjectDetectionModel(path)`` - (or similar) entry point, hand-add the binding in - ``eve_python/eve_sdk.py`` and call it here before - ``EveConfigureObjectDetection`` to actually switch models. - """ - enabled = bool(model_name) - if enabled and model_name not in (None, "GMOD-80"): - logger.info( - f"enable_object_detection: requested {model_name!r} but SDK only " - f"ships GMOD-80; falling back to GMOD-80 weights." - ) - result = self.eve_sdk.EveConfigureObjectDetection( - sdk.structs.EveObjectDetectionOptions( - enabled=( - sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED - if enabled - else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED - ) - ) - ) - if result.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.info(f"eveConfigureObjectDetection() error code: {result.error}") - sys.exit(result.error) - - def enable_face_and_person_detection( - self, faceEnabled: bool = True, personEnabled: bool = True - ): - mode = sdk.structs.EveFaceTrackerMinimumMode.EVE_FACETRACKER_MINIMUM_MODE_OFF - - params = sdk.structs.EveFaceTrackerOptions( - faceTrackerMode=mode, - enable3DFaceTracking=( - sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED - if faceEnabled - else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED - ), - fitSecondaryUsers=( - sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED - if faceEnabled - else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED - ), - enablePersonDetection=( - sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED - if personEnabled - else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED - ), - enableEyeLandmarks=sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED, - ) - result = self.eve_sdk.EveConfigureFaceTracker(params) - if result.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.info(f"EveConfigureFaceTracker() error code: {result.error}") - sys.exit(result.error) - - def enable_face_id(self, enabled: bool = True, threshold: float = 0.7) -> None: - eve_enabled = ( - sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED - if enabled - else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED - ) - options = sdk.structs.EveFaceIdOptions(enabled=eve_enabled, threshold=threshold) - if enabled: - options.calibrationPoses = ( - sdk.structs.EveFaceIdCalibrationPoseMode.EVE_FACEID_CALIBRATION_FRONTAL_ONLY - ) - result = self.eve_sdk.EveConfigureFaceId(options) - if result.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.info(f"EveConfigureFaceId() error code: {result.error}") - sys.exit(result.error) - - def enable_hand_gesture(self, enabled=True, redetection_delay_ms=0): - eve_enabled = ( - sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED - if enabled - else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED - ) - result = self.eve_sdk.EveConfigureHandGesture( - sdk.structs.EveHandGestureOptions( - enabled=eve_enabled, redetectionDelay=redetection_delay_ms - ) - ) - if result.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.info(f"EveConfigureHandGesture() error code: {result.errorCode}") - sys.exit(result.errorCode) - - def _send_face_id_command( - self, command: EveFaceIdCommand - ) -> sdk.structs.EveFaceIdCommandStruct: - cmd = sdk.structs.EveFaceIdCommandStruct(command=command) - return self.eve_sdk.EveSendFaceIdCommand(cmd) - - def _log_all_faces(self, context: str, frame_idx: int, all_faces) -> None: - """Log face ID details for every detected face in one frame.""" - face_count = all_faces.faceData.contents.detectedFacesCount - if face_count == 0: - logger.debug(f"[{context}] frame {frame_idx}: no faces detected") - return - for j in range(face_count): - f = all_faces.faceData.contents.faces[j] - fid = f.faceId.faceIdentity - logger.debug( - f"[{context}] frame {frame_idx}: face {j}: " - f"id={fid.id}, confidence={fid.confidence:.4f}, " - f"similarity={fid.similarity:.4f}, " - f"identificationStatus={f.faceId.identificationStatus}, " - f"calibrationStatus={f.faceId.calibrationStatus}" - ) - - def _send_frame(self, frame: np.ndarray) -> None: - """Send a single frame through the SDK pipeline (send + consume output).""" - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - eve_image = self._create_eve_input_image(frame, frame.shape[1], frame.shape[0], "BGR") - send_err = self.eve_sdk.EveSendImageForProcessing(eve_image) - if send_err != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.warning(f"EveSendImageForProcessing failed: {send_err}") - return False - return True - - _FLUSH_FRAME_COUNT = 6 - _CALIBRATION_MAX_ATTEMPTS = 5 - _INFERENCE_LOG_INTERVAL = 30 # log face ID data every N frames during inference - - def calibrate_new_user(self, frames: list[np.ndarray]) -> CalibrationResult: - """Register a new face by sending frames through the SDK calibration pipeline. - - Retries up to ``_CALIBRATION_MAX_ATTEMPTS`` times because the SDK can - intermittently reject borderline-frontal faces. - - Args: - frames: BGR images to use for calibration. Each frame is sent exactly once - per attempt; returns at the first successful attempt. - - Returns: - CalibrationResult with success flag, SDK-assigned user ID, and message. - """ - last_result: CalibrationResult | None = None - for attempt in range(self._CALIBRATION_MAX_ATTEMPTS): - last_result = self._calibrate_new_user_once(frames) - if last_result.success: - return last_result - logger.info( - "calibrate_new_user: attempt %d/%d failed: %s", - attempt + 1, - self._CALIBRATION_MAX_ATTEMPTS, - last_result.message, - ) - return last_result # type: ignore[return-value] - - def _calibrate_new_user_once(self, frames: list[np.ndarray]) -> CalibrationResult: - """Single calibration attempt — flush, send ADD_NEW_USER + frames, check result.""" - self.enable_face_and_person_detection(faceEnabled=True) - self.enable_face_id(enabled=True) - - h, w = frames[0].shape[:2] - blank = np.zeros((h, w, 3), dtype=np.uint8) - for _ in range(self._FLUSH_FRAME_COUNT): - self._send_frame(blank) - - success_frame = -1 - for i, frame in enumerate(frames): - result = self._send_face_id_command(EveFaceIdCommand.EVE_FACE_ID_COMMAND_ADD_NEW_USER) - if result.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - return CalibrationResult( - False, 0, f"ADD_NEW_USER command failed: {result.errorCode}" - ) - - self._send_frame(frame) - - all_faces = self.eve_sdk.EveGetAllFaceData() - if all_faces.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - return CalibrationResult( - False, 0, f"EveGetAllFaceData failed: {all_faces.errorCode}" - ) - - face_count = all_faces.faceData.contents.detectedFacesCount - self._log_all_faces("calibrate frame", i, all_faces) - - matched_face = None - for fi in range(face_count): - face = all_faces.faceData.contents.faces[fi] - if ( - face.faceId.faceIdentity.id >= 0 - and face.faceId.identificationStatus - == EveFaceIdIdentificationStatus.EVE_FACE_ID_SUCCESS - ): - matched_face = face - break - if matched_face is not None: - success_frame = i - break - - if success_frame > -1: - user_id = matched_face.faceId.faceIdentity.id - confidence = matched_face.faceId.faceIdentity.confidence - similarity = matched_face.faceId.faceIdentity.similarity - - logger.debug( - f"calibrate_new_user: SUCCESS on frame {success_frame}, " - f"id={user_id}, confidence={confidence:.4f}, similarity={similarity:.4f}" - ) - if len(frames) == 1: - return CalibrationResult(True, user_id, f"Registered as user {user_id}") - else: - return CalibrationResult( - True, - user_id, - f"Calibration succeeded on frame {success_frame}. " - f"Registered as user {user_id}.", - ) - else: - return CalibrationResult( - False, - 0, - "Calibration did not succeed. " - "Ensure a clear, frontal face is visible in the upload.", - ) - - def remove_all_users(self) -> bool: - """Remove all users from the SDK face ID gallery. - - Returns: - True if removal succeeded. - """ - result = self._send_face_id_command(EveFaceIdCommand.EVE_FACE_ID_COMMAND_REMOVE_ALL_USERS) - if result.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.warning(f"REMOVE_ALL_USERS command error: {result.errorCode}") - return False - return True - - def restore_gallery(self, frames_per_user: list[list[np.ndarray]]) -> list[CalibrationResult]: - """Wipe the SDK gallery and re-register users from stored frames. - - Used before video processing to sync the SDK gallery with a session's - registered users. Sends a frame after removing to flush the command - through the SDK pipeline before re-registering. - - Args: - frames_per_user: List of frame lists, one per user to re-register. - - Returns: - List of CalibrationResult, one per user. - """ - if not frames_per_user: - return [] - - self.enable_face_and_person_detection(faceEnabled=True) - self.enable_face_id(enabled=True) - - self.remove_all_users() - # SDK commands are async — flush the remove by sending a frame - self._send_frame(frames_per_user[0][0]) - - results = [] - for idx, frames in enumerate(frames_per_user): - r = self.calibrate_new_user(frames) - logger.info( - f"restore_gallery: user {idx}: success={r.success}, " - f"sdk_id={r.user_id}, message='{r.message}'" - ) - results.append(r) - return results - - @staticmethod - def _ensure_config_dir(): - from pathlib import Path - - try: - new_dir_path = Path.home() / ".config" - new_dir_path.mkdir(exist_ok=True) - except Exception as e: - print(f"Error creating directory: {e}") - - @staticmethod - def _resolve_eve_paths(eve_bin_path="", eve_lib_path=""): - if eve_bin_path and eve_lib_path: - return eve_bin_path, eve_lib_path - - if _is_windows: - eve_bin_path = r"C:\TLT_SRC_DIR\EdgeVisionEngine\x64\Release\\" - eve_lib_path = eve_bin_path - else: - eve_dir_default_paths = glob.glob("/opt/EVE-*-Source", recursive=False) - eve_bin_path = eve_bin_path or ( - os.path.join(eve_dir_default_paths[0], "bin") if eve_dir_default_paths else "" - ) - eve_lib_path = eve_lib_path or ( - os.path.join(eve_dir_default_paths[0], "lib") if eve_dir_default_paths else "" - ) - return eve_bin_path, eve_lib_path - - def _load_and_create_eve(self, eve_bin_path, eve_lib_path, image_provider): - from pathlib import Path - - self._backup_cwd = os.getcwd() - os.chdir(eve_bin_path) - - if _is_windows: - eve_sdk_path = os.path.join(eve_bin_path, "EveSDK.dll") - root = Path(os.path.abspath(__file__)).parent - if not os.path.isfile(eve_sdk_path): - eve_sdk_path = os.path.join(root.parent.parent, eve_bin_path, "EveSDK.dll") - else: - eve_sdk_path = os.path.join(eve_bin_path, "libEveSDK.so") - if not os.path.isfile(eve_sdk_path): - eve_sdk_path = os.path.join(eve_lib_path, "libEveSDK.so") - - eve_sdk_instance = sdk.EveSDK(eve_sdk_path) - - ByteArray512 = ctypes.c_byte * 512 - encoded = os.path.dirname(eve_bin_path + os.sep).encode("utf-8") # EVE needs os.sep - pathOverride = ByteArray512(*encoded, *([0] * (512 - len(encoded)))) # zero-pad to 512 - - startup_options = sdk.structs.EveStartupParameters( - pathOverride=pathOverride, - gpuPreference=sdk.structs.EveGpuPreference.EVE_NO_GPU, - imageProvider=image_provider, - startupType=sdk.structs.EveStartupType.EVE_SYNC, - ) - err = eve_sdk_instance.CreateEve(startup_options) - if err != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - logger.info(f"CreateEve error code: {err}") - sys.exit(err) - - return eve_sdk_instance - - def _set_camera(self): - i = 0 - self._metaDataFpgaCameraId = -1 - self._fpgaCameraId = -1 - while True: - cameraInfo = self.eve_sdk.EveGetCamera(i) - if ( - cameraInfo.error == sdk.structs.EveError.EVE_INVALID_CAMERA_ID - or cameraInfo.error == sdk.structs.EveError.EVE_NO_MORE_DATA - ): - break - - pid = ctypes.cast(cameraInfo.data.pid, ctypes.c_char_p).value - vid = ctypes.cast(cameraInfo.data.vid, ctypes.c_char_p).value - if cameraInfo.data.isFpgaCamera == 1: - if self._metaDataFpgaCameraId == -1 and vid == b"META" and pid == b"DATA": - self._metaDataFpgaCameraId = i - elif self._fpgaCameraId == -1: - self._fpgaCameraId = i - print(i, self._fpgaCameraId, self._metaDataFpgaCameraId, cameraInfo.error, pid, vid) - if self._fpgaCameraId >= 0 and self._metaDataFpgaCameraId >= 0: - break - i += 1 - - if self._fpgaCameraId == -1 and self._metaDataFpgaCameraId == -1: - raise RuntimeError("No FPGA camera found") - print( - f" \n\t\t *** FPGA camera found: {self._fpgaCameraId}, metadata {self._metaDataFpgaCameraId}\n" - ) - - useMetadataCamera = False - if useMetadataCamera: - self._usedCameraId = self._metaDataFpgaCameraId - else: - self._usedCameraId = self._fpgaCameraId - - cameraFormat = sdk.structs.CCameraFormat() - cameraFormat.resolution.width = 640 - cameraFormat.resolution.height = 480 - cameraFormat.compareResolution = sdk.structs.EveCompare.EVE_AT_MOST - cameraFormat.compareFps = sdk.structs.EveCompare.EVE_AT_LEAST - formats = self.eve_sdk.EveGetFormats(self._usedCameraId, cameraFormat) - f = formats.formats[0] - - print( - f"camera selected: ID#{self._usedCameraId}: {f.resolution.width}x{f.resolution.height}, " - f"Format: {f.format} @ {f.fps}FPS" - ) - - errorCode = self.eve_sdk.EveSetCamera(self._usedCameraId, f) - if errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: - raise RuntimeError(f"Could't set camera {errorCode}") - - @staticmethod - def _create_eve_input_image(image_bin, width, height, encoding): - image = sdk.structs.EveInputImage() - image.data = image_bin.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)) - image.width = width - image.height = height - if encoding == "YUY2": - image.encoding = sdk.structs.EveVideoFormat.EVE_YUY2 - elif encoding == "NV12": - image.encoding = sdk.structs.EveVideoFormat.EVE_NV12 - elif encoding == "BGR": - image.encoding = sdk.structs.EveVideoFormat.EVE_BGR - return image +import ctypes +import glob +import os +import platform +import sys +import time +from dataclasses import dataclass + +import cv2 +import numpy as np +from eve_messages import ModelConfig +from eve_python import eve_sdk as sdk +from eve_python.structs.CFaceIdStructs import ( + EveFaceIdCommand, + EveFaceIdIdentificationStatus, +) +from log_utils import setup_logger + +logger = setup_logger("EveWrapper") + + +def _pack_cstring(dst, value: str) -> None: + """Write ``value`` as a null-terminated UTF-8 string into a ``c_byte`` array. + + ``c_byte`` is signed (-128..127), so bytes above 127 are folded into their + signed two's-complement form — the underlying memory is identical to an + unsigned ``char[]`` on the C side. The array is fully written so any stale + bytes from a recycled options struct are cleared. + + Args: + dst: A ctypes ``c_byte`` array (the destination buffer). + value: The string to store; must fit with room for the null terminator. + + Raises: + ValueError: If ``value`` does not leave room for a null terminator. + """ + raw = value.encode("utf-8") + capacity = len(dst) + if len(raw) >= capacity: + raise ValueError(f"{value!r} ({len(raw)} bytes) exceeds buffer of {capacity}") + for i in range(capacity): + b = raw[i] if i < len(raw) else 0 + dst[i] = b - 256 if b > 127 else b + + +def _pack_class_names(dst, names: tuple[str, ...]) -> None: + """Write ``names`` into a 2-D ``c_byte`` class-name table, one row each. + + Args: + dst: A ctypes 2-D ``c_byte`` array (rows of fixed-width names). + names: Class labels to store, in output-index order. + + Raises: + ValueError: If there are more names than table rows. + """ + if len(names) > len(dst): + raise ValueError(f"{len(names)} class names exceed table of {len(dst)} rows") + for i, name in enumerate(names): + _pack_cstring(dst[i], name) + + +_is_windows = platform.system() == "Windows" + +DO_FAKE_MIRROR = True + +# Gate timing instrumentation behind the profiler env var +_TIMING_ENABLED = os.environ.get("ENABLE_PROFILER", "").strip() not in ("", "0", "false") + + +@dataclass +class CalibrationResult: + """Result of a face ID calibration attempt.""" + + success: bool + user_id: int + message: str + + +class EveWrapper: + def __init__(self, eve_bin_path="", eve_lib_path=""): + self._ensure_config_dir() + eve_bin_path, eve_lib_path = self._resolve_eve_paths(eve_bin_path, eve_lib_path) + + self._mirror = False + self._inference_frame_count = 0 + self._last_sent_frame: np.ndarray | None = None + # Per-call timing: {name: [call_count, total_seconds]} + self._timings: dict[str, list[float]] = {} + self._timing_enabled = _TIMING_ENABLED + + image_provider = sdk.structs.EveImageProvider.EVE_CLIENT_PROVIDED + self.eve_sdk = self._load_and_create_eve(eve_bin_path, eve_lib_path, image_provider) + + if image_provider == sdk.structs.EveImageProvider.EVE_CAMERA: + self._set_camera() + + image_request = sdk.structs.EveImageFormatRequest( + location=sdk.structs.EveImageLocation.EVE_CPU, + format=sdk.structs.EveVideoFormat.EVE_BGRA, + ) + self.eve_sdk.EveConfigureProcessedImage(image_request) + + err = self.eve_sdk.StartEveWithParameters( + sdk.structs.EveProcessingParameters(type=sdk.structs.EveProcessingPipelineType.EVE_HMI) + ) + if err != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.info(f"StartEveWithParameters error code: {err}") + sys.exit(err) + + logger.info("EVE initialized") + + def _record_timing(self, name: str, elapsed: float) -> None: + entry = self._timings.get(name) + if entry is None: + self._timings[name] = [1, elapsed] + else: + entry[0] += 1 + entry[1] += elapsed + + def get_timing_stats(self, reset: bool = False) -> dict[str, tuple[int, float]]: + """Return accumulated per-call timings as {name: (count, total_seconds)}. + + Args: + reset: If True, clear the accumulators after reading. + """ + result = {k: (int(v[0]), v[1]) for k, v in self._timings.items()} + if reset: + self._timings.clear() + return result + + def inference(self, image: np.ndarray) -> np.ndarray: + t = self._timing_enabled + + if self._mirror and DO_FAKE_MIRROR: + if t: + _t0 = time.perf_counter() + image = cv2.flip(image, 1) + if t: + self._record_timing("flip", time.perf_counter() - _t0) + + if t: + _t0 = time.perf_counter() + if not self._send_frame(image): + return image + if t: + self._record_timing("EveSendImageForProcessing", time.perf_counter() - _t0) + + if t: + _t0 = time.perf_counter() + processed_image = self.eve_sdk.EveGetProcessedImage() + if t: + self._record_timing("EveGetProcessedImage", time.perf_counter() - _t0) + if processed_image.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.info(f"EveGetProcessedImage() error code: {processed_image.error}") + sys.exit(processed_image.error) + + if t: + _t0 = time.perf_counter() + img = np.ctypeslib.as_array( + processed_image.data, + shape=(processed_image.height, processed_image.width, processed_image.channels), + ).copy() + del processed_image + if t: + self._record_timing("as_array+copy", time.perf_counter() - _t0) + + if img.shape[2] == 2: + if t: + _t0 = time.perf_counter() + img = cv2.cvtColor(img, cv2.COLOR_YUV2BGR_YUYV) + if t: + self._record_timing("cvtColor", time.perf_counter() - _t0) + elif img.shape[2] == 4: + if t: + _t0 = time.perf_counter() + img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR) + if t: + self._record_timing("cvtColor", time.perf_counter() - _t0) + elif img.shape[2] == 3: + if t: + _t0 = time.perf_counter() + img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + if t: + self._record_timing("cvtColor", time.perf_counter() - _t0) + + # SDK requires all outputs to be consumed before the next frame + if t: + _t0 = time.perf_counter() + person_detection_data = self.eve_sdk.EveGetPersonDetectionData() + if t: + self._record_timing("EveGetPersonDetectionData", time.perf_counter() - _t0) + if person_detection_data.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.info(f"EveGetPersonDetectionData() error code: {person_detection_data.error}") + sys.exit(person_detection_data.error) + del person_detection_data + + if t: + _t0 = time.perf_counter() + all_faces = self.eve_sdk.EveGetAllFaceData() + if t: + self._record_timing("EveGetAllFaceData", time.perf_counter() - _t0) + if all_faces.errorCode == sdk.structs.EveError.EVE_ERROR_NO_ERROR: + face_count = all_faces.faceData.contents.detectedFacesCount + if face_count > 0: + self._inference_frame_count += 1 + # Log face ID data periodically to avoid flooding + if self._inference_frame_count % self._INFERENCE_LOG_INTERVAL == 1: + self._log_all_faces("inference", self._inference_frame_count, all_faces) + del all_faces + + if img.shape[2] in (1, 3): + return img + + print( + f"WRONG FORMAT: {img.shape} " + "(Consider converting it above here, or making EveImageFormatRequest work)" + ) + return None + + def reset_pipeline(self) -> None: + """Reset the EVE processing pipeline to a clean state. + + Pipeline state (face tracking, ideal-user selection) persists on the + long-lived SDK handle across inference calls. Workers are reused across + offline video jobs, so without a reset the second video inherits the + first run's pipeline state — most visibly, no ideal user is selected. + Call this before processing each new video. + """ + err = self.eve_sdk.EveResetPipeline() + if err != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.warning(f"EveResetPipeline error code: {err}") + + def shutdown(self) -> None: + """Cleanly shut down the Eve SDK instance.""" + try: + self.eve_sdk.ShutdownEve() + logger.info("EVE shut down") + except Exception as exc: + logger.warning(f"EVE shutdown error: {exc}") + + def enable_mirror(self, enabled: bool = True) -> None: + """Enable or disable image mirroring.""" + if DO_FAKE_MIRROR: + self._mirror = enabled + return + options = sdk.structs.EveImageManipulationOptions() + options.settings.mirrorImage = 1 if enabled else 0 + result = self.eve_sdk.EveConfigureImageManipulation(options) + if result.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.info(f"EveConfigureImageManipulation() error code: {result.errorCode}") + sys.exit(result.errorCode) + + def enable_object_detection(self, config: "ModelConfig | None" = None) -> None: + """Enable and configure EVE's object detection (MOD) feature. + + Pass ``None`` to leave MOD off (the worker simply does not configure + it, matching the SDK's disabled-by-default state). Otherwise: + + - ``config.model_path`` empty / ``None`` selects the SDK's bundled + default model (GMOD-80) with its default class names. + - a populated ``config.model_path`` loads that ``.tflite`` and, if + given, replaces the class table with ``config.class_names``. + + ``modelPath`` and ``classCount`` are rewritten on every call rather + than left to the values returned by + ``EveGetDefaultObjectDetectionOptions``: that getter echoes the + *previously configured* options, not pristine defaults. Without the + unconditional reset, switching from a custom model (e.g. AMOD-8) back + to the bundled default would leave the prior model's class names in + EVE's table. An empty ``modelPath`` with ``classCount == 0`` is the + SDK's documented "use the default model and its default class names". + + Args: + config: The resolved MOD model to load, or ``None`` to skip MOD + configuration entirely. + + Raises: + SystemExit: If ``EveConfigureObjectDetection`` returns an error. + """ + if config is None: + return + + options = self.eve_sdk.EveGetDefaultObjectDetectionOptions() + options.enabled = sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED + + _pack_cstring(options.modelPath, config.model_path or "") + if config.model_path and config.class_names: + _pack_class_names(options.classNames, config.class_names) + options.classCount = len(config.class_names) + else: + options.classCount = 0 + if config.nms_threshold is not None: + options.nmsThreshold = config.nms_threshold + if config.iou_threshold is not None: + options.iouThreshold = config.iou_threshold + + result = self.eve_sdk.EveConfigureObjectDetection(options) + if result.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.info(f"EveConfigureObjectDetection() error code: {result.error}") + sys.exit(result.error) + + def enable_face_and_person_detection( + self, faceEnabled: bool = True, personEnabled: bool = True + ): + mode = sdk.structs.EveFaceTrackerMinimumMode.EVE_FACETRACKER_MINIMUM_MODE_OFF + + params = sdk.structs.EveFaceTrackerOptions( + faceTrackerMode=mode, + enable3DFaceTracking=( + sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED + if faceEnabled + else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED + ), + fitSecondaryUsers=( + sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED + if faceEnabled + else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED + ), + enablePersonDetection=( + sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED + if personEnabled + else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED + ), + enableEyeLandmarks=sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED, + ) + result = self.eve_sdk.EveConfigureFaceTracker(params) + if result.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.info(f"EveConfigureFaceTracker() error code: {result.error}") + sys.exit(result.error) + + def enable_face_id(self, enabled: bool = True, threshold: float = 0.7) -> None: + eve_enabled = ( + sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED + if enabled + else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED + ) + options = sdk.structs.EveFaceIdOptions(enabled=eve_enabled, threshold=threshold) + if enabled: + options.calibrationPoses = ( + sdk.structs.EveFaceIdCalibrationPoseMode.EVE_FACEID_CALIBRATION_FRONTAL_ONLY + ) + result = self.eve_sdk.EveConfigureFaceId(options) + if result.error != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.info(f"EveConfigureFaceId() error code: {result.error}") + sys.exit(result.error) + + def enable_hand_gesture(self, enabled=True, redetection_delay_ms=0): + eve_enabled = ( + sdk.structs.EveOptionEnabled.EVE_OPTION_ENABLED + if enabled + else sdk.structs.EveOptionEnabled.EVE_OPTION_DISABLED + ) + result = self.eve_sdk.EveConfigureHandGesture( + sdk.structs.EveHandGestureOptions( + enabled=eve_enabled, redetectionDelay=redetection_delay_ms + ) + ) + if result.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.info(f"EveConfigureHandGesture() error code: {result.errorCode}") + sys.exit(result.errorCode) + + def _send_face_id_command( + self, command: EveFaceIdCommand + ) -> sdk.structs.EveFaceIdCommandStruct: + cmd = sdk.structs.EveFaceIdCommandStruct(command=command) + return self.eve_sdk.EveSendFaceIdCommand(cmd) + + def _log_all_faces(self, context: str, frame_idx: int, all_faces) -> None: + """Log face ID details for every detected face in one frame.""" + face_count = all_faces.faceData.contents.detectedFacesCount + if face_count == 0: + logger.debug(f"[{context}] frame {frame_idx}: no faces detected") + return + for j in range(face_count): + f = all_faces.faceData.contents.faces[j] + fid = f.faceId.faceIdentity + logger.debug( + f"[{context}] frame {frame_idx}: face {j}: " + f"id={fid.id}, confidence={fid.confidence:.4f}, " + f"similarity={fid.similarity:.4f}, " + f"identificationStatus={f.faceId.identificationStatus}, " + f"calibrationStatus={f.faceId.calibrationStatus}" + ) + + def _send_frame(self, frame: np.ndarray) -> None: + """Send a single frame through the SDK pipeline (send + consume output).""" + rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + # The SDK keeps the input pointer live past EveSendImageForProcessing and + # dereferences it during the matching EveGet*() calls. Retain the ndarray + # on the instance so it outlives every follow-up SDK call until the next + # frame is sent. Dropping the local ref here caused 1080p video runs to + # segfault inside libc memmove (use-after-free on the RGB buffer). + self._last_sent_frame = rgb + eve_image = self._create_eve_input_image(rgb, rgb.shape[1], rgb.shape[0], "RGB") + send_err = self.eve_sdk.EveSendImageForProcessing(eve_image) + if send_err != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.warning(f"EveSendImageForProcessing failed: {send_err}") + return False + return True + + _FLUSH_FRAME_COUNT = 6 + _CALIBRATION_MAX_ATTEMPTS = 5 + _INFERENCE_LOG_INTERVAL = 30 # log face ID data every N frames during inference + + def calibrate_new_user(self, frames: list[np.ndarray]) -> CalibrationResult: + """Register a new face by sending frames through the SDK calibration pipeline. + + Retries up to ``_CALIBRATION_MAX_ATTEMPTS`` times because the SDK can + intermittently reject borderline-frontal faces. + + Args: + frames: BGR images to use for calibration. Each frame is sent exactly once + per attempt; returns at the first successful attempt. + + Returns: + CalibrationResult with success flag, SDK-assigned user ID, and message. + """ + last_result: CalibrationResult | None = None + for attempt in range(self._CALIBRATION_MAX_ATTEMPTS): + last_result = self._calibrate_new_user_once(frames) + if last_result.success: + return last_result + logger.info( + "calibrate_new_user: attempt %d/%d failed: %s", + attempt + 1, + self._CALIBRATION_MAX_ATTEMPTS, + last_result.message, + ) + return last_result # type: ignore[return-value] + + def _calibrate_new_user_once(self, frames: list[np.ndarray]) -> CalibrationResult: + """Single calibration attempt — flush, send ADD_NEW_USER + frames, check result.""" + self.enable_face_and_person_detection(faceEnabled=True) + self.enable_face_id(enabled=True) + + h, w = frames[0].shape[:2] + blank = np.zeros((h, w, 3), dtype=np.uint8) + for _ in range(self._FLUSH_FRAME_COUNT): + self._send_frame(blank) + + success_frame = -1 + for i, frame in enumerate(frames): + result = self._send_face_id_command(EveFaceIdCommand.EVE_FACE_ID_COMMAND_ADD_NEW_USER) + if result.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + return CalibrationResult( + False, 0, f"ADD_NEW_USER command failed: {result.errorCode}" + ) + + self._send_frame(frame) + + all_faces = self.eve_sdk.EveGetAllFaceData() + if all_faces.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + return CalibrationResult( + False, 0, f"EveGetAllFaceData failed: {all_faces.errorCode}" + ) + + face_count = all_faces.faceData.contents.detectedFacesCount + self._log_all_faces("calibrate frame", i, all_faces) + + matched_face = None + for fi in range(face_count): + face = all_faces.faceData.contents.faces[fi] + if ( + face.faceId.faceIdentity.id >= 0 + and face.faceId.identificationStatus + == EveFaceIdIdentificationStatus.EVE_FACE_ID_SUCCESS + ): + matched_face = face + break + if matched_face is not None: + success_frame = i + break + + if success_frame > -1: + user_id = matched_face.faceId.faceIdentity.id + confidence = matched_face.faceId.faceIdentity.confidence + similarity = matched_face.faceId.faceIdentity.similarity + + logger.debug( + f"calibrate_new_user: SUCCESS on frame {success_frame}, " + f"id={user_id}, confidence={confidence:.4f}, similarity={similarity:.4f}" + ) + if len(frames) == 1: + return CalibrationResult(True, user_id, f"Registered as user {user_id}") + else: + return CalibrationResult( + True, + user_id, + f"Calibration succeeded on frame {success_frame}. " + f"Registered as user {user_id}.", + ) + else: + return CalibrationResult( + False, + 0, + "Calibration did not succeed. " + "Ensure a clear, frontal face is visible in the upload.", + ) + + def remove_all_users(self) -> bool: + """Remove all users from the SDK face ID gallery. + + Returns: + True if removal succeeded. + """ + result = self._send_face_id_command(EveFaceIdCommand.EVE_FACE_ID_COMMAND_REMOVE_ALL_USERS) + if result.errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.warning(f"REMOVE_ALL_USERS command error: {result.errorCode}") + return False + return True + + def restore_gallery(self, frames_per_user: list[list[np.ndarray]]) -> list[CalibrationResult]: + """Wipe the SDK gallery and re-register users from stored frames. + + Used before video processing to sync the SDK gallery with a session's + registered users. Sends a frame after removing to flush the command + through the SDK pipeline before re-registering. + + Args: + frames_per_user: List of frame lists, one per user to re-register. + + Returns: + List of CalibrationResult, one per user. + """ + if not frames_per_user: + return [] + + self.enable_face_and_person_detection(faceEnabled=True) + self.enable_face_id(enabled=True) + + self.remove_all_users() + # SDK commands are async — flush the remove by sending a frame + self._send_frame(frames_per_user[0][0]) + + results = [] + for idx, frames in enumerate(frames_per_user): + r = self.calibrate_new_user(frames) + logger.info( + f"restore_gallery: user {idx}: success={r.success}, " + f"sdk_id={r.user_id}, message='{r.message}'" + ) + results.append(r) + return results + + @staticmethod + def _ensure_config_dir(): + from pathlib import Path + + try: + new_dir_path = Path.home() / ".config" + new_dir_path.mkdir(exist_ok=True) + except Exception as e: + print(f"Error creating directory: {e}") + + @staticmethod + def _resolve_eve_paths(eve_bin_path="", eve_lib_path=""): + if eve_bin_path and eve_lib_path: + return eve_bin_path, eve_lib_path + + if _is_windows: + eve_bin_path = r"C:\TLT_SRC_DIR\EdgeVisionEngine\x64\Release\\" + eve_lib_path = eve_bin_path + else: + eve_dir_default_paths = glob.glob("/opt/EVE-*-Source", recursive=False) + eve_bin_path = eve_bin_path or ( + os.path.join(eve_dir_default_paths[0], "bin") if eve_dir_default_paths else "" + ) + eve_lib_path = eve_lib_path or ( + os.path.join(eve_dir_default_paths[0], "lib") if eve_dir_default_paths else "" + ) + return eve_bin_path, eve_lib_path + + def _load_and_create_eve(self, eve_bin_path, eve_lib_path, image_provider): + from pathlib import Path + + os.chdir(eve_bin_path) + + if _is_windows: + eve_sdk_path = os.path.join(eve_bin_path, "EveSDK.dll") + root = Path(os.path.abspath(__file__)).parent + if not os.path.isfile(eve_sdk_path): + eve_sdk_path = os.path.join(root.parent.parent, eve_bin_path, "EveSDK.dll") + else: + eve_sdk_path = os.path.join(eve_bin_path, "libEveSDK.so") + if not os.path.isfile(eve_sdk_path): + eve_sdk_path = os.path.join(eve_lib_path, "libEveSDK.so") + + eve_sdk_instance = sdk.EveSDK(eve_sdk_path) + + ByteArray512 = ctypes.c_byte * 512 + encoded = os.path.dirname(eve_bin_path + os.sep).encode("utf-8") # EVE needs os.sep + pathOverride = ByteArray512(*encoded, *([0] * (512 - len(encoded)))) # zero-pad to 512 + + startup_options = sdk.structs.EveStartupParameters( + pathOverride=pathOverride, + gpuPreference=sdk.structs.EveGpuPreference.EVE_NO_GPU, + imageProvider=image_provider, + startupType=sdk.structs.EveStartupType.EVE_SYNC, + ) + err = eve_sdk_instance.CreateEve(startup_options) + if err != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + logger.info(f"CreateEve error code: {err}") + sys.exit(err) + + return eve_sdk_instance + + def _set_camera(self): + i = 0 + self._metaDataFpgaCameraId = -1 + self._fpgaCameraId = -1 + while True: + cameraInfo = self.eve_sdk.EveGetCamera(i) + if ( + cameraInfo.error == sdk.structs.EveError.EVE_INVALID_CAMERA_ID + or cameraInfo.error == sdk.structs.EveError.EVE_NO_MORE_DATA + ): + break + + pid = ctypes.cast(cameraInfo.data.pid, ctypes.c_char_p).value + vid = ctypes.cast(cameraInfo.data.vid, ctypes.c_char_p).value + if cameraInfo.data.isFpgaCamera == 1: + if self._metaDataFpgaCameraId == -1 and vid == b"META" and pid == b"DATA": + self._metaDataFpgaCameraId = i + elif self._fpgaCameraId == -1: + self._fpgaCameraId = i + print(i, self._fpgaCameraId, self._metaDataFpgaCameraId, cameraInfo.error, pid, vid) + if self._fpgaCameraId >= 0 and self._metaDataFpgaCameraId >= 0: + break + i += 1 + + if self._fpgaCameraId == -1 and self._metaDataFpgaCameraId == -1: + raise RuntimeError("No FPGA camera found") + print( + f" \n\t\t *** FPGA camera found: {self._fpgaCameraId}, metadata {self._metaDataFpgaCameraId}\n" + ) + + useMetadataCamera = False + if useMetadataCamera: + self._usedCameraId = self._metaDataFpgaCameraId + else: + self._usedCameraId = self._fpgaCameraId + + cameraFormat = sdk.structs.CCameraFormat() + cameraFormat.resolution.width = 640 + cameraFormat.resolution.height = 480 + cameraFormat.compareResolution = sdk.structs.EveCompare.EVE_AT_MOST + cameraFormat.compareFps = sdk.structs.EveCompare.EVE_AT_LEAST + formats = self.eve_sdk.EveGetFormats(self._usedCameraId, cameraFormat) + f = formats.formats[0] + + print( + f"camera selected: ID#{self._usedCameraId}: {f.resolution.width}x{f.resolution.height}, " + f"Format: {f.format} @ {f.fps}FPS" + ) + + errorCode = self.eve_sdk.EveSetCamera(self._usedCameraId, f) + if errorCode != sdk.structs.EveError.EVE_ERROR_NO_ERROR: + raise RuntimeError(f"Could't set camera {errorCode}") + + @staticmethod + def _create_eve_input_image(image_bin, width, height, encoding): + image = sdk.structs.EveInputImage() + image.data = image_bin.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)) + image.width = width + image.height = height + if encoding == "YUY2": + image.encoding = sdk.structs.EveVideoFormat.EVE_YUY2 + elif encoding == "NV12": + image.encoding = sdk.structs.EveVideoFormat.EVE_NV12 + elif encoding == "BGR": + image.encoding = sdk.structs.EveVideoFormat.EVE_BGR + elif encoding == "RGB": + image.encoding = sdk.structs.EveVideoFormat.EVE_RGB + return image diff --git a/shared/face_id_tab.py b/shared/face_id_tab.py index 65a056bd4b0408c04d1c651ef5181efc1c70708d..8cbdfaf616a9d323d1e4cf560ddaa90c42d26f7b 100644 --- a/shared/face_id_tab.py +++ b/shared/face_id_tab.py @@ -1,963 +1,969 @@ -"""Face ID Registration Gradio tab for Eve SDK applications. - -Provides a self-contained Gradio tab for registering and unregistering faces -via the Eve SDK. Registration media is stored in per-session tmp directories -(``tmp//``) so that each browser session is isolated and files -are cleaned up when the session disconnects. - -Usage:: - - face_id_tab = FaceIdTab(eve, max_users=2) - - with gr.Blocks() as demo: - session_registry = gr.State(value={}) - with gr.Tabs(): - face_id_tab.build() - face_id_tab.wire(session_registry, concurrency_id="eve_sdk") -""" - -import math -import os -import shutil -import uuid -from dataclasses import dataclass -from pathlib import Path - -import cv2 -import gradio as gr -import numpy as np -from eve_messages import CalibrationResultMsg -from eve_worker_pool import EveWorkerPool, log_worker_activity -from frame_utils import ( - extract_frame_at_index, - extract_frames, - get_thumbnail, - get_thumbnail_base64, - load_media_frames, -) -from log_utils import setup_logger -from usage_analytics import UsageTracker -from video_processing import ( - VideoLimits, - build_video_constraints_accordion, - reencode_video, - validate_video, - wire_recording_limits, -) - -logger = setup_logger("FaceIdTab") - -_IMAGE_EXTENSIONS = frozenset({".png", ".jpg", ".jpeg", ".bmp", ".webp"}) -_VIDEO_EXTENSIONS = frozenset({".mp4", ".avi", ".mov", ".mkv", ".webm"}) -_MEDIA_EXTENSIONS = _IMAGE_EXTENSIONS | _VIDEO_EXTENSIONS - - -@dataclass -class FaceEntry: - """A registered face in the session gallery. - - Attributes: - path: Local path to the stored registration media. - sdk_id: EVE SDK face ID assigned during the most recent gallery restore. - ``None`` until the first video processing or live inference run. - from_webcam: True when the stored media is a mirrored webcam capture. - Mirrored storage keeps the thumbnail aligned with the selfie - preview the user saw; frames loaded back out must be flipped to - un-mirrored before being sent to the SDK for calibration so the - embedding matches the (un-mirrored) frames the SDK receives - during live/offline inference. - """ - - path: str - sdk_id: int | None = None - from_webcam: bool = False - - -class FaceIdTab: - """Gradio tab for registering and managing Face ID users via the Eve SDK. - - Args: - pool: Worker pool for acquiring Eve SDK workers. - max_users: Maximum number of simultaneously registered users. - examples_dir: Path to a directory of example images/videos to display. - accept_video: Whether to show the video upload input. Defaults to False - (image-only registration). - video_limits: Optional constraints for uploaded registration videos. - When provided, uploaded videos are validated against these limits - and a "Video Constraints" accordion is shown in the UI. - """ - - def __init__( - self, - pool: EveWorkerPool, - max_users: int = 2, - examples_dir: str = "", - accept_video: bool = False, - video_limits: VideoLimits | None = None, - tracker: UsageTracker | None = None, - ): - self._pool = pool - self._max_users = max_users - self._accept_video = accept_video - self._video_limits = video_limits - self._tracker = tracker - - # Scan for example images (and videos, if accepted) separately - self._image_examples: list[list[str]] = [] - self._video_examples: list[list[str]] = [] - if examples_dir: - examples_path = Path(examples_dir) - if examples_path.is_dir(): - for p in sorted(examples_path.iterdir()): - if not p.is_file(): - continue - suffix = p.suffix.lower() - if suffix in _IMAGE_EXTENSIONS: - self._image_examples.append([str(p)]) - elif accept_video and suffix in _VIDEO_EXTENSIONS: - self._video_examples.append([str(p)]) - - # Gradio components — populated by build() - self._face_image_input: gr.Image - self._face_video_input: gr.Video - self._register_btn: gr.Button - self._register_status: gr.Textbox - self._slot_imgs: list[gr.Image] = [] - self._remove_btns: list[gr.Button] = [] - self._image_example_dataset: gr.Dataset | None = None - self._video_example_dataset: gr.Dataset | None = None - self._image_accordion: gr.Accordion | None = None - self._video_accordion: gr.Accordion | None = None - self._summaries: list[dict] = [] # each: {column, hint, imgs, height} - self._example_path_state: gr.State - self._select_frame_btn: gr.Button - self._frame_preview: gr.Image - self._video_time: gr.Number - self._frame_index_state: gr.State - - # ------------------------------------------------------------------ - # UI construction - # ------------------------------------------------------------------ - - def build(self) -> None: - """Create the Face ID Registration tab UI. - - Must be called inside a ``gr.Blocks`` / ``gr.Tabs`` context. - """ - with gr.TabItem("Face ID Registration"): - gr.Markdown( - "### Register Faces for Identification\n\n" - "Register a user to use with Face ID in the Live Inference " - "tab or the Offline Inference tab.\n\n" - "> Note: When uploading a video, EVE will take the first valid frame to register the user." - ) - with gr.Accordion("Instructions", open=False): - gr.Markdown( - ( - "1. Choose between registering a face from an **Image** or a " "**Video**\n" - if self._accept_video - else "1. Select an example image or upload your own\n" - ) - + "2. For an image\n" - " 1. Select an example image, or upload your own\n" - " 2. Press the **Register Face** button\n" - "3. For a video\n" - " 1. Expand the video section\n" - " 2. Select an example video or upload your own\n" - " 3. Press the **Register Face** button\n" - "4. Go to another tab, enable **Face Identification**, and process " - "a video\n\n" - f"> **Platform note:** This demo supports up to {self._max_users} " - "registered users. The full Eve SDK supports larger galleries and " - "multi-pose calibration, but these features are limited here due to " - "HuggingFace Spaces constraints." - ) - - with gr.Row(): - # --- Left column: input --- - with gr.Column(scale=3): - if self._accept_video: - # Image section (expanded by default) - with gr.Accordion( - "Input from an Image", open=True - ) as self._image_accordion: - if self._image_examples: - with gr.Accordion("Examples", open=True): - self._image_example_dataset = gr.Dataset( - components=[gr.Image(visible=False)], - samples=self._image_examples, - show_label=False, - ) - self._face_image_input = gr.Image( - label="Upload Face Photo", - sources=["upload", "webcam"], - type="filepath", - ) - - # Video section (collapsed by default) - with gr.Accordion( - "Input from a Video", open=False - ) as self._video_accordion: - if self._video_examples: - with gr.Accordion("Examples", open=True): - self._video_example_dataset = gr.Dataset( - components=[gr.Video(visible=False)], - samples=self._video_examples, - show_label=False, - ) - if self._video_limits is not None: - build_video_constraints_accordion(self._video_limits) - with gr.Row(): - with gr.Column(): - self._face_video_input = gr.Video( - label="Upload Short Video", - sources=["upload", "webcam"], - elem_id="face-video-input", - ) - with gr.Column(): - self._frame_preview = gr.Image( - label="Selected Frame", - interactive=False, - visible=False, - ) - self._select_frame_btn = gr.Button( - "Select Current Frame", - variant="secondary", - size="sm", - visible=False, - ) - else: - if self._image_examples: - with gr.Accordion("Image Examples", open=True): - self._image_example_dataset = gr.Dataset( - components=[gr.Image(visible=False)], - samples=self._image_examples, - show_label=False, - ) - self._face_image_input = gr.Image( - label="Upload Face Photo", - sources=["upload", "webcam"], - type="filepath", - ) - # Hidden — needed for handler wiring but not shown - self._face_video_input = gr.Video(visible=False) - self._select_frame_btn = gr.Button(visible=False) - self._frame_preview = gr.Image(visible=False) - - self._register_btn = gr.Button( - "Register Face", variant="primary", interactive=False - ) - self._register_status = gr.Textbox(label="Status", interactive=False) - self._example_path_state = gr.State(value=None) - self._video_time = gr.Number(visible=False, value=0) - self._frame_index_state = gr.State(value=None) - - # --- Right column: registered users --- - with gr.Column(scale=1): - gr.Markdown("#### Registered Users") - for i in range(self._max_users): - img = gr.Image( - label=f"Slot {i + 1} — Empty", - interactive=False, - height=200, - ) - btn = gr.Button( - f"Remove Slot {i + 1}", - variant="stop", - interactive=False, - ) - self._slot_imgs.append(img) - self._remove_btns.append(btn) - - def build_summary(self, height: int = 80, scale: int = 1) -> None: - """Create a summary column with minimal HTML thumbnails of registered faces. - - Uses ``gr.HTML`` instead of ``gr.Image`` so there are no - fullscreen/download/share buttons — just a tiny thumbnail and label. - The column starts hidden and appears once a face is registered. - Can be called multiple times (e.g. once per tab) — each call creates - an independent summary widget that is kept in sync automatically. - Must be called **before** :meth:`wire`. - - Args: - height: Max pixel height of each thumbnail image. - scale: Column scale relative to siblings in the parent Row. - """ - imgs: list[gr.HTML] = [] - column = gr.Column(scale=scale, min_width=100, visible=False) - with column: - gr.Markdown("**Registered Faces**") - hint = gr.Markdown("_Go to the **Face ID Registration** tab to register faces._") - for _ in range(self._max_users): - imgs.append(gr.HTML(value="", visible=False)) - self._summaries.append({"column": column, "hint": hint, "imgs": imgs, "height": height}) - - # ------------------------------------------------------------------ - # Event wiring - # ------------------------------------------------------------------ - - def wire( - self, - session_registry: gr.State, - ) -> None: - """Connect event handlers to the tab's UI components. - - Must be called inside the same ``gr.Blocks`` context as :meth:`build`. - - Args: - session_registry: ``gr.State`` holding the per-session registry dict. - """ - # Validate uploaded registration videos against limits - if self._video_limits is not None: - wire_recording_limits( - self._face_video_input, - self._video_limits.max_duration_seconds, - ) - self._face_video_input.upload( - fn=self._validate_video_upload, - inputs=[self._face_video_input], - outputs=[self._face_video_input], - ) - - self._face_video_input.stop_recording( - fn=self._process_webcam_recording, - inputs=[self._face_video_input], - outputs=[self._face_video_input], - ) - - # Frame selector: show/hide capture button when video changes - self._face_video_input.change( - fn=self._on_video_change, - inputs=[self._face_video_input], - outputs=[self._select_frame_btn, self._frame_preview, self._frame_index_state], - ) - # Capture the currently displayed frame via the browser's video element - self._select_frame_btn.click( - fn=self._on_select_frame, - inputs=[self._face_video_input, self._video_time], - outputs=[self._frame_preview, self._frame_index_state], - js="(video_path, _) => {" - " const el = document.querySelector('#face-video-input video');" - " return [video_path, el ? el.currentTime : 0];" - "}", - ) - - # Mutually exclusive accordions: expanding one collapses the other - if self._image_accordion is not None and self._video_accordion is not None: - self._image_accordion.expand( - fn=lambda: gr.update(open=False), - outputs=[self._video_accordion], - ) - self._video_accordion.expand( - fn=lambda: gr.update(open=False), - outputs=[self._image_accordion], - ) - - # Load examples into the corresponding input on click - if self._image_example_dataset is not None: - self._image_example_dataset.click( - fn=self._load_image_example, - inputs=[self._image_example_dataset], - outputs=[ - self._face_image_input, - self._face_video_input, - self._example_path_state, - ], - ) - if self._video_example_dataset is not None: - self._video_example_dataset.click( - fn=self._load_video_example, - inputs=[self._video_example_dataset], - outputs=[ - self._face_image_input, - self._face_video_input, - self._example_path_state, - ], - ) - - # Enable/disable register button based on input availability - for component in (self._face_image_input, self._face_video_input): - component.change( - fn=self._on_input_change, - inputs=[ - self._face_image_input, - self._face_video_input, - session_registry, - ], - outputs=self._register_btn, - ) - - # Interleave slot images and remove buttons for outputs: - # [slot1_img, remove_btn1, slot2_img, remove_btn2, ...] - slot_outputs: list[gr.Component] = [] - for img, btn in zip(self._slot_imgs, self._remove_btns): - slot_outputs.extend([img, btn]) - - summary_outputs: list[gr.Component] = [] - for s in self._summaries: - summary_outputs.append(s["column"]) - summary_outputs.append(s["hint"]) - summary_outputs.extend(s["imgs"]) - - self._register_btn.click( - fn=self.register_face, - inputs=[ - self._face_image_input, - self._face_video_input, - self._example_path_state, - session_registry, - self._frame_index_state, - ], - outputs=[ - *slot_outputs, - *summary_outputs, - self._register_status, - self._face_image_input, - self._face_video_input, - session_registry, - self._example_path_state, - self._select_frame_btn, - self._frame_preview, - self._frame_index_state, - ], - ) - - # Each slot gets its own remove button - for slot_index in range(self._max_users): - self._remove_btns[slot_index].click( - fn=self._make_unregister_handler(slot_index), - inputs=[session_registry], - outputs=[ - *slot_outputs, - *summary_outputs, - self._register_status, - session_registry, - self._register_btn, - ], - ) - - # ------------------------------------------------------------------ - # Event handlers - # ------------------------------------------------------------------ - - # Reset on success: clear preview, hide button, clear frame index. - _FRAME_SELECTOR_RESET = ( - gr.update(visible=False), - gr.update(value=None, visible=False), - None, - ) - - def _error_return( - self, - registry: dict[int, FaceEntry], - msg: str, - frame_index: int | None, - ) -> tuple: - """Build a register_face return tuple for an error (state unchanged).""" - gr.Warning(msg) - return ( - *self._slot_updates(registry), - *self._summary_updates(registry), - msg, - gr.update(), - gr.update(), - registry, - None, - gr.update(), - gr.update(), - frame_index, - ) - - def register_face( - self, - image_path: str | None, - video_path: str | None, - example_fallback_path: str | None, - registry: dict[int, FaceEntry], - frame_index: int | None, - request: gr.Request, - ) -> tuple: - """Handle the Register Face button click. - - Returns: - (*slot_updates, *summary_updates, status, clear_image, clear_video, - registry, example_path_state, *frame_selector_reset) - """ - if len(registry) >= self._max_users: - return self._error_return( - registry, - f"Cannot register: maximum {self._max_users} users already registered.", - frame_index, - ) - - # Fallback: if the Image/Video components haven't updated yet - # (race between example-click and register-click), use the - # example path stored in gr.State. - if image_path is None and video_path is None and example_fallback_path is not None: - ext = os.path.splitext(example_fallback_path)[1].lower() - if ext in _IMAGE_EXTENSIONS: - image_path = example_fallback_path - elif ext in _VIDEO_EXTENSIONS: - video_path = example_fallback_path - - if image_path is None and video_path is None: - return self._error_return(registry, "Please upload an image or video.", frame_index) - - frame = None - is_webcam = video_path is not None and _is_recording(video_path) - try: - if video_path is not None and frame_index is not None: - frame = extract_frame_at_index(video_path, int(frame_index)) - frames = [frame] - else: - frames = extract_frames(image_path, video_path) - except Exception as exc: - logger.error("Failed to extract frames for registration: %s", exc) - return self._error_return(registry, f"Could not read media: {exc}", frame_index) - - if not frames: - return self._error_return(registry, "No frames read from media.", frame_index) - - worker = self._pool.acquire(request.session_hash) - log_worker_activity(logger, "acquired", "face-register", self._pool, worker.worker_id) - try: - worker.send_enable_face_id(enabled=True) - result: CalibrationResultMsg = worker.send_calibrate_new_user(frames) - - if not result.success: - return self._error_return( - registry, f"Registration failed: {result.message}", frame_index - ) - - session_tmp = _session_dir(request.session_hash) - uid_hex = uuid.uuid4().hex[:8] - if frame is not None: - # Save the selected frame as an image so thumbnail and - # restore_gallery use this exact frame, not the full video. - # Webcam jpgs are stored mirrored so the thumbnail matches - # the selfie-view preview the user saw. - stored_path = os.path.join(session_tmp, f"face_id_{uid_hex}.jpg") - display_frame = cv2.flip(frame, 1) if is_webcam else frame - cv2.imwrite(stored_path, display_frame) - _cleanup_recording(video_path) - else: - media_source = image_path if image_path is not None else video_path - ext = os.path.splitext(media_source)[1] - stored_path = os.path.join(session_tmp, f"face_id_{uid_hex}{ext}") - shutil.copy2(media_source, stored_path) - _cleanup_recording(video_path) - - next_key = max(registry.keys(), default=0) + 1 - registry = { - **registry, - next_key: FaceEntry(path=stored_path, from_webcam=is_webcam), - } - - all_frames = [_load_frames_for_sdk(entry) for entry in registry.values()] - # The new entry was just added last; reuse the raw frames we - # already have in memory instead of re-decoding + re-flipping - # the jpg we just wrote. - all_frames[-1] = frames - restore_results = worker.send_restore_gallery(all_frames) - for entry, r in zip(registry.values(), restore_results): - entry.sdk_id = r.user_id if r.success else None - except Exception as exc: - logger.error("Face registration failed: %s", exc) - return self._error_return(registry, f"Registration failed: {exc}", frame_index) - finally: - self._pool.release(worker) - log_worker_activity(logger, "released", "face-register", self._pool, worker.worker_id) - - if self._tracker: - media_type = "image" if image_path is not None else "video" - self._tracker.log( - request.session_hash, - "face_register", - media_type=media_type, - slot_count=len(registry), - ) - - return ( - *self._slot_updates(registry), - *self._summary_updates(registry), - ( - f"Successfully registered (Face ID: {registry[next_key].sdk_id})." - if registry[next_key].sdk_id is not None - else f"Successfully registered as User {next_key}." - ), - None, - None, - registry, - None, - *self._FRAME_SELECTOR_RESET, - ) - - def _make_unregister_handler(self, slot_index: int): - """Create a remove handler bound to a specific slot index.""" - - def handler(registry: dict[int, FaceEntry], request: gr.Request) -> tuple: - user_ids = sorted(registry.keys()) - if slot_index >= len(user_ids): - return ( - *self._slot_updates(registry), - *self._summary_updates(registry), - f"Slot {slot_index + 1} is empty.", - registry, - gr.update(), - ) - - uid = user_ids[slot_index] - - removed_entry = registry[uid] - if os.path.exists(removed_entry.path): - os.remove(removed_entry.path) - - remaining = {u: entry for u, entry in registry.items() if u != uid} - - # Re-register surviving users on a worker - worker = self._pool.acquire(request.session_hash) - log_worker_activity(logger, "acquired", "face-unregister", self._pool, worker.worker_id) - try: - worker.send_remove_all_users() - if remaining: - new_registry: dict[int, FaceEntry] = {} - for u, entry in remaining.items(): - frames = _load_frames_for_sdk(entry) - result = worker.send_calibrate_new_user(frames) - if result.success: - new_registry[u] = FaceEntry( - path=entry.path, - sdk_id=result.user_id, - from_webcam=entry.from_webcam, - ) - else: - logger.warning(f"Failed to re-register user {u}: {result.message}") - if os.path.exists(entry.path): - os.remove(entry.path) - registry = new_registry - else: - registry = remaining - finally: - self._pool.release(worker) - log_worker_activity( - logger, "released", "face-unregister", self._pool, worker.worker_id - ) - - if self._tracker: - self._tracker.log( - request.session_hash, - "face_remove", - slot_count=len(registry), - ) - - can_register = len(registry) < self._max_users - return ( - *self._slot_updates(registry), - *self._summary_updates(registry), - f"User {uid} removed.", - registry, - gr.update(interactive=can_register), - ) - - return handler - - # ------------------------------------------------------------------ - # Private helpers - # ------------------------------------------------------------------ - - def _validate_video_upload(self, video_path: str | None) -> str | None: - """Validate an uploaded registration video against limits. - - Returns: - The video path if valid, or None if rejected. - """ - if not video_path or self._video_limits is None: - return video_path - try: - validate_video(video_path, self._video_limits) - return video_path - except Exception as error: - gr.Warning(str(error), duration=None) - return None - - def _process_webcam_recording(self, video_path: str | None) -> str | None: - """Re-encode a webcam recording to an MP4 with proper time_base. - - Browser MediaRecorder produces WebM with duration=Infinity, which - breaks HTML5 scrubbing (``currentTime`` is stuck at 0). - Re-encoding to CFR H.264 MP4 with explicit ``stream.time_base`` - gives the file a known duration so the player can seek. - - File is stored un-mirrored in :data:`_RECORDINGS_DIR`. Gradio's - player CSS-flips webcam-sourced videos during playback, so - flipping the file would double-flip; frames read back out for - display are mirrored at extraction time by - :meth:`_extract_frame_for_display`. - """ - if not video_path: - return video_path - if self._video_limits is not None: - try: - validate_video(video_path, self._video_limits) - except Exception as error: - gr.Warning(str(error), duration=None) - return None - os.makedirs(_RECORDINGS_DIR, exist_ok=True) - output_path = os.path.join( - _RECORDINGS_DIR, f"{_RECORDING_BASENAME_PREFIX}{uuid.uuid4().hex[:8]}.mp4" - ) - try: - reencode_video(video_path, output_path) - except Exception as error: - logger.error("Failed to re-encode webcam recording: %s", error) - gr.Warning(f"Could not process recording: {error}") - return None - return output_path - - @staticmethod - def _extract_frame_for_display(video_path: str, frame_index: int): - """Extract a frame in the orientation the user sees in the preview. - - Webcam recording files are stored un-mirrored (see - :meth:`_process_webcam_recording`); Gradio's player CSS-flips - them during playback, so for the preview thumbnail to match what - the user saw we mirror the raw frame here. - - For SDK consumption, call :func:`extract_frame_at_index` directly - — the SDK processes un-mirrored frames at inference time, so - passing a mirrored frame here would produce a different - embedding than live/offline inference. - """ - frame = extract_frame_at_index(video_path, frame_index) - if _is_recording(video_path): - frame = cv2.flip(frame, 1) - return frame - - def _on_input_change( - self, - image_path: str | None, - video_path: str | None, - registry: dict[int, FaceEntry], - ) -> dict: - has_input = image_path is not None or video_path is not None - can_register = has_input and len(registry) < self._max_users - return gr.update(interactive=can_register) - - def _on_video_change(self, video_path: str | None) -> tuple: - """Auto-select frame 0 when a video is uploaded; reset when cleared.""" - if not video_path: - return self._FRAME_SELECTOR_RESET - try: - frame = self._extract_frame_for_display(video_path, 0) - preview = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - except RuntimeError: - return self._FRAME_SELECTOR_RESET - return gr.update(visible=True), gr.update(value=preview, visible=True), 0 - - def _on_select_frame(self, video_path: str | None, current_time: float) -> tuple: - """Capture the frame at the video's current playback position.""" - if not video_path: - return gr.update(), None - # Guard against NaN/Infinity that HTML5