`LiveDiagnosticsSheet`
LiveDiagnosticsSheet is the trainer-facing diagnostics widget you
mount on a test page or behind a long-press gesture. It renders three
sections, live tracker state, the pose engine per-kernel timing, backend
parity, driven by a single ValueListenable<LiveDiagnosticsSnapshot>
the host page updates per frame.
The sheet is tracker-agnostic, it doesn’t reach into
MovementTracker or
ShowcaseTracker directly. The host
maintains a LiveDiagnosticsSnapshot and feeds it whatever signals
it has wired; rows without a value show a dash.
final ValueNotifier<LiveDiagnosticsSnapshot> diag = ValueNotifier( const LiveDiagnosticsSnapshot(),);
LiveDiagnosticsSheet(snapshot: diag);When to use it
For consumer-facing apps, gate it behind a debug flag or a long-press on a logo, it’s a developer / trainer tool, not a customer surface. A typical pattern is to mount it on an internal QA / test page behind a button so testers can verify hardware backends and FPS without attaching a debugger.
For Studio / showcase / CMS surfaces, mount it inline as a side panel while authoring, trainers benefit from seeing whether the camera or the inference pipeline is the bottleneck.
LiveDiagnosticsSnapshot shape
All fields are nullable. A field set to null renders as a dash.
class LiveDiagnosticsSnapshot { // Frame state final int? frameNumber; final int? imageWidth; final int? imageHeight; final double? fps; // successful inference rate final double? cameraFps; // camera-capture rate (BEFORE inference)
// the pose engine timing final double? inferenceMs; final bool? usedDetector;
// Per-stage Dart-side breakdown final double? colorConvertMs; final double? isolateSendMs; final double? isolateReceiveMs; final double? dartTrackingMs;
// Per-kernel C-side breakdown final double? cBackboneMs; final double? cLandmarkHeadMs; final double? cDetectorMs;
// Backend negotiation final String? backboneBackend; final String? decoderBackend; final String? landmarkBackend;
// Tracker state final int? repCount; final String? phaseId; final double? formScore; final double? lastRepFormScore;}copyWith is provided so you can patch individual fields without
rebuilding the whole snapshot.
Wiring the snapshot
final diag = ValueNotifier(const LiveDiagnosticsSnapshot());final fpsCounter = FpsCounter();final cameraFpsCounter = FpsCounter();
TrackedMovementView( // ... onCameraFrame: () { cameraFpsCounter.mark(); diag.value = diag.value.copyWith(cameraFps: cameraFpsCounter.value); }, onPipelineTiming: (report) { fpsCounter.mark(); diag.value = diag.value.copyWith( fps: fpsCounter.value, inferenceMs: report.totalMs, colorConvertMs: report.colorConvertMs, isolateSendMs: report.isolateSendMs, isolateReceiveMs: report.isolateReceiveMs, cBackboneMs: report.cBackboneMs, cLandmarkHeadMs: report.cLandmarkHeadMs, cDetectorMs: report.cDetectorMs, usedDetector: report.usedDetector, ); }, onTrackerTiming: (ms) { diag.value = diag.value.copyWith(dartTrackingMs: ms); }, onBackends: (backbone, decoder, landmark) { diag.value = diag.value.copyWith( backboneBackend: backbone, decoderBackend: decoder, landmarkBackend: landmark, ); }, onImageSize: (w, h) { diag.value = diag.value.copyWith(imageWidth: w, imageHeight: h); }, onTrackingResult: (result) { diag.value = diag.value.copyWith( repCount: result.repCount, phaseId: result.phaseId, formScore: result.formScore, lastRepFormScore: result.lastRepQuality?.formScore, ); },);
LiveDiagnosticsSheet(snapshot: diag);Reading the panel
| Row | What it tells you |
|---|---|
| Camera FPS vs Inference FPS | If they’re equal, you’re keeping up. If camera FPS > inference FPS, inference is the cap and frames are being dropped. If camera FPS < inference FPS, the camera itself is the cap (typical: package:camera 30 fps Android cap with a faster pipeline). |
| Inference | Per-frame total pose-engine C-pipeline time + a flag for heavy detector frames vs cheap tracking reuse. Detector frames dominate at startup or when tracking is lost. |
| Per-stage waterfall | Color convert → isolate send → C backbone → C landmark head → isolate receive → Dart tracking. Lets you spot which stage spiked when overall FPS dropped. |
| Backend row | The backends each segment of the pipeline negotiated, cpu, metal, nnapi, opencl, vulkan. A silent drop to CPU (driver-bind failure) is otherwise invisible behind a green FPS number. |
| Tracker state | Rep count, current phase, current form score, last rep’s form score. Sanity check against the trainer’s actual movement. |
FpsCounter
A rolling 30-sample FPS counter ships alongside LiveDiagnosticsSheet
for convenience:
final fps = FpsCounter();// per-framefps.mark();// for renderingfinal current = fps.value; // double, 0 until 2 frames recordedConstant-time per mark(), no allocation, just one integer write.
Read next
PoseCameraView, surfaces the callbacks that feed the snapshot.PipelineTimingReport, the per-stage breakdown structure.TrackedMovementView, forwards the same callbacks for the all-in-one widget path.