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/STAGING-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)