feat: local AI analytics (ONNX object detection)#23
Merged
Conversation
- New OpenIPC.Viewer.Analytics project on ONNX Runtime (no Python sidecar) - Core contracts: IObjectDetector, ModelSpec, Detection, FrameBuffer, ExecutionProvider - Execution-provider selection with mandatory CPU fallback (never crash on GPU) - Pure YOLOX post-processor (grid/stride decode + per-class NMS) + letterbox map - Unit tests for decode, NMS, class filter, threshold, and inverse letterbox
- IAnalyticsEngine + ObjectDetectionEngine: shared detector, one inference worker - FrameSampler thins decoder FPS to analyticsFps; bounded drop-oldest channel - Full-frame downscale-copy off the decoder thread (buffer is session-owned) - IModelProvider/ModelProvider: download-on-first-enable, AppData cache, SHA-256 - ModelCatalog pins Apache-2.0 YOLOX-tiny; AnalyticsSettings + diagnostics - FrameSampler + settings unit tests
- Migration 010: Ai* columns on Cameras (enabled, classes CSV, threshold, fps, auto-record) - Camera/New/UpdateCameraRequest carry AnalyticsSettings; repo maps flat columns - CameraDirectoryService.SetAnalyticsAsync + thread analytics through add/update - Global AiAcceleration user setting (auto/force-cpu) via IUserSettingsAccessor - Register IModelProvider/IObjectDetector/IAnalyticsEngine (boot-safe, opt-in) - Suppress Android XA0141 (ORT native 16KB page-size advisory, upstream)
- AutoRecordWindow: pure start/extend/stop state machine with cooldown - AutoRecordCoordinator: detections -> RecordingService start, 1Hz quiet-stop tick - Only stops recordings it started; manual recordings are never auto-stopped - Graceful stop via RecordingService (avoids corrupt MP4); Failed event for logging - AutoRecordWindow unit tests (extend, quiet-stop, cooldown)
- New EventKind.Detection; MotionTick carries optional Kind + Summary - EventIngestionService threads kind/summary into opened + finalized events - AnalyticsMotionEventSource bridges engine results -> motion ticks (debounce, quiet-close, live observable reused) - Detection summary "person ×2, car ×1"; summary unit tests
- DetectionOverlay control: scales normalized boxes to tile, per-class colors + labels - CameraTileViewModel attaches/detaches frames per session; exposes Detections + counter - Analytics pauses with Smart Pause (suspended/non-playing tile -> isActive false) - AnalyticsBootstrap: lazy engine init + auto-record start on first enabled tile (best-effort) - GridPage: overlay above video + bottom-center object counter
- Camera editor: enable, class toggles (curated COCO subset), threshold, fps, auto-record + post-event - Hydrates/persists AnalyticsSettings through New/UpdateCameraRequest - Settings: "Force CPU for AI analytics" toggle wired to AiAcceleration - EN/RU localization for the new strings
- AnalyticsPage: engine status + diagnostics, analytics-enabled cameras, recent detection events - 1Hz diagnostics poll bound to the view's visual lifetime - Nav entry (desktop sidebar) + VM registration + DataTemplate; EN/RU strings
- OpenIPC.Viewer.Analytics.Tests: engine attach/detach wiring with a fake detector - Skippable real-model ONNX integration test gated on OPENIPC_DETECTION_MODEL - Mark Phase 15 done in docs/ROADMAP.md
- IconRadar is a stroke-designed Lucide path; PathIcon filled its open arcs into a blob - Use Path.lucide (Fill=Transparent + stroke) per the icon convention
- GridPage rebuilds a live tile when its analytics settings change, so enabling detection in the editor takes effect without an app restart (tile held a stale Camera snapshot, so it never attached to the engine) - BottomNavBar: add the AI entry (6 columns) so Android can reach the control center
- Confirmed the upstream 0.1.1rc0 asset downloads (~20MB); pin its SHA-256 so the cached/downloaded model is integrity-checked
- New AnalyticsEngineStatus (NotStarted/Preparing/Loading/Ready/Failed) on IAnalyticsEngine - Engine sets it through init: Preparing (model cache/download) -> Loading -> Ready, Failed on error - AI page Overview shows a localized Status row (e.g. "Downloading model…", "Ready", "Failed (see logs)") - EN/RU strings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds on-device object detection for cameras: per-camera detection
with class filtering, bounding-box + counter overlay on the live grid,
auto-record on detection with a smart auto-stop, detection events, and an AI
control center. Everything runs locally via ONNX Runtime in-process — no
cloud, no Python sidecar. Inference never leaves the machine.
Related
Detection core (15.1/15.2) — new
OpenIPC.Viewer.Analyticsproject onMicrosoft.ML.OnnxRuntime. Pure, testable YOLOX post-processor (grid/stridedecode + per-class NMS) + letterbox mapping. Execution-provider selection with
a mandatory CPU fallback — the app never crashes because a GPU/NPU EP is
missing.
behind a bounded drop-oldest queue. Frames are thinned to
analyticsFps(default 3) and downscaled off the decode thread, so memory/CPU stay bounded
regardless of camera count.
subset), confidence threshold, FPS, auto-record + post-event seconds. Migration
010_ai_analytics.sql. Global "Force CPU" toggle in Settings.DetectionOverlaydraws normalized boxes + per-classcolors/labels and an object counter on the live tile. Analytics pauses with
Smart Pause (Phase 12) — hidden/suspended tiles stop inferring.
postEventSecondsafter the last hit (each hit extends the window); gracefulstop via
RecordingService(no corrupted MP4); cooldown against flapping.Manual recordings are never auto-stopped.
EventKind.Detectionflowing through theexisting Phase 7 ingestion path (reuses debounce / quiet-close / live feed).
frames processed/dropped, queue depth, latency), analytics-enabled cameras,
and recent detections. Includes an engine status line
(Not started → Downloading model… → Loading… → Ready / Failed).
Architecture
IObjectDetector,IAnalyticsEngine,IModelProvider,ModelSpec,Detection,AnalyticsSettings,AutoRecordCoordinator, and thepure helpers) live in Core; the ONNX-backed implementation lives in
Analytics and is wired via
SharedComposition. TheApp → Core onlyruleis preserved (App talks to interfaces, never to ONNX Runtime).
first enabled live tile, so registering it on every head is boot-safe.
Model & privacy
to stay MIT/store-compatible. The post-processor is pluggable via
ModelSpec.%LOCALAPPDATA%/OpenIPC.Viewer/models/, and SHA-256 verified. After thatit runs fully offline. An
OPENIPC_DETECTION_MODELenv var overrides with alocal file. The repo ships no binaries.
Platforms / execution providers
XA014116 KB-page advisory suppressed on the Android head).packages; Windows/Linux run on CPU for now.
Tests
letterbox, frame sampler rate gate, auto-record window (extend/quiet-stop/
cooldown), detection summary.
OpenIPC.Viewer.Analytics.Tests: engine attach/detach wiring with a fakedetector; a
SkippableFactreal-model ONNX integration test gated onOPENIPC_DETECTION_MODEL.How to try
AI page: "Downloading model…" → "Ready"); requires internet once.
Known limitations / follow-ups
camera's tile attached; a headless/background detection mode is out of scope.
"pick model file" button) is deferred to the packaging phase.
Type
Checklist
TreatWarningsAsErrors=true).dotnet test); new Core logic has unit tests.AppreferencesCoreonly (Infrastructure / Video / Devices wired via DI in a head).Platforms tested
Screenshots / notes