spectacles-snapml
Spectacles SnapML — Reference Guide
SnapML lets you run custom machine learning models directly on the Spectacles hardware, with no cloud round-trip needed. Models run on the device's NPU (preferred) or GPU, making inference fast enough for real-time AR (typically 10–30 fps depending on model size).
Official docs: Spectacles Home · SnapML on Spectacles
Simulator note: On the Lens Studio desktop simulator, MLComponent falls back to CPU. Always profile on-device for accurate performance numbers.
Supported Model Formats
| Format | Notes |
|---|---|
TensorFlow Lite (.tflite) |
Primary format; recommended for NPU |
ONNX (.onnx) |
Supported via Lens Studio's ONNX importer |
Export your model as TFLite or ONNX and drag it into the Lens Studio Asset panel. Lens Studio will show it as an ML Model asset.
Core API: MLComponent
The MLComponent manages a model's lifecycle (load → input → run → output).
Setup in Lens Studio
- Add an ML Component to a Scene Object (Add Component → ML → ML Controller).
- Assign your ML Model asset to the component.
- Configure inputs (camera texture, custom texture, or float arrays) and outputs in the inspector.
Synchronous inference (per frame, blocking)
const mlComponent = this.sceneObject.getComponent('MLComponent')
const updateEvent = this.createEvent('UpdateEvent')
updateEvent.bind(() => {
mlComponent.runImmediate(false) // false = use camera texture input automatically
processOutputs()
})
function processOutputs(): void {
const outputData = mlComponent.getOutput('output_0').data as Float32Array
// Parse bounding boxes, class IDs, confidence scores...
}
Asynchronous inference (non-blocking, better framerate)
Running ML synchronously every frame can drop framerate. Use runScheduled to run inference async:
onAwake(): void {
const mlComponent = this.sceneObject.getComponent('MLComponent')
// Enable scheduled (async) mode
mlComponent.runScheduled(true)
// Outputs are updated automatically when inference completes
mlComponent.onRunningFinished.add(() => {
processOutputs(mlComponent)
})
}
Running inference every 2–3 frames also helps when you don't need 60 Hz detection:
let frameCounter = 0
updateEvent.bind(() => {
frameCounter++
if (frameCounter % 3 === 0) {
mlComponent.runImmediate(false)
processOutputs()
}
})
Object Detection Pattern
Object detection models output lists of bounding boxes + class IDs + confidence scores.
Parsing SSD-style output
const NUM_DETECTIONS = 20
const BOX_STRIDE = 4 // [ymin, xmin, ymax, xmax] per box
interface Detection {
ymin: number; xmin: number; ymax: number; xmax: number
score: number; classId: number
}
function parseDetections(
rawBoxes: Float32Array,
rawScores: Float32Array,
threshold: number
): Detection[] {
const detections: Detection[] = []
for (let i = 0; i < NUM_DETECTIONS; i++) {
const score = rawScores[i]
if (score < threshold) continue
const base = i * BOX_STRIDE
detections.push({
ymin: rawBoxes[base],
xmin: rawBoxes[base + 1],
ymax: rawBoxes[base + 2],
xmax: rawBoxes[base + 3],
score,
classId: i
})
}
return detections
}
Projecting bounding boxes to screen space
const camera = scene.findByName('Camera').getComponent('Camera') as Camera
function boxCenterToWorldPos(normX: number, normY: number, distance: number): vec3 {
const screenPos = new vec2(normX * screen.getWidth(), normY * screen.getHeight())
return camera.screenToWorld(screenPos, distance)
}
Smoothing Detection Jitter (Low-pass Filter)
const SMOOTH = 0.3 // lerp factor — lower = smoother, higher = more responsive
const smoothedBox = { x: 0, y: 0, w: 0, h: 0 }
function smoothDetection(rawBox: {x: number, y: number, w: number, h: number}): void {
smoothedBox.x = smoothedBox.x + (rawBox.x - smoothedBox.x) * SMOOTH
smoothedBox.y = smoothedBox.y + (rawBox.y - smoothedBox.y) * SMOOTH
smoothedBox.w = smoothedBox.w + (rawBox.w - smoothedBox.w) * SMOOTH
smoothedBox.h = smoothedBox.h + (rawBox.h - smoothedBox.h) * SMOOTH
}
Object Tracking (between inference frames)
After detecting an object, track it across frames using ObjectTracking3D:
const objectTracker = require('LensStudio:ObjectTracking3D')
const trackerSession = objectTracker.createSession({
inputTexture: cameraTexture,
boundingBox: initialDetectionBox, // from ML output
})
trackerSession.onUpdate.add((trackedObject) => {
myArObject.getTransform().setWorldTransform(trackedObject.pose)
})
trackerSession.onLost.add(() => {
myArObject.enabled = false
})
trackerSession.start()
The tracker is cheaper than full detection — run ML every N frames and fill gaps with tracking.
Integrating with Physics
ML detections can drive physics interactions:
const body = tableColliderObject.getComponent('Physics.BodyComponent')
const detectedBox = latestDetection.worldBoundingBox
tableColliderObject.getTransform().setWorldPosition(detectedBox.center)
tableColliderObject.getTransform().setWorldScale(detectedBox.size)
NPU Performance Tips
| Tip | Reason |
|---|---|
| Use INT8-quantised models | Smaller, faster; NPU is optimised for INT8 |
| Avoid FP32 layers in the model | FP32 ops may fall back from NPU to GPU |
| Match input texture resolution exactly | Avoid upsampling inside the model |
Use runScheduled(true) for async inference |
Keeps the AR framerate smooth |
| Run inference every 2–3 frames | Most detection tasks don't need 60 Hz |
| Profile in Lens Studio's Performance panel | Shows NPU vs. GPU time per frame |
Common Gotchas
- Input tensor shape must match exactly — check the model's expected input shape (e.g.,
[1, 320, 320, 3]) and set the ML Component input resolution accordingly. - Output tensor interpretation varies by architecture (SSD, YOLO, EfficientDet) — read the model paper or training code.
- On-device models cannot be updated without a lens update — use RSG + cloud inference (
spectacles-ai) if you need dynamic model updates. - Desktop simulator uses CPU — always test performance on-device (Spectacles) for realistic NPU numbers.
- Camera access permission must be enabled in Project Settings for ML to work on camera frames.
- ObjectTracking3D requires an initial detection as a seed — it won't track without one.
Reference Examples
- PinholeCameraModel.ts - Useful for spatializing screen coordinates.
More from rolandsmeenk/lensstudioagents
lens-studio-scripting
Reference guide for the Lens Studio TypeScript component system — covering the @component, @input, @hint, @allowUndefined, and @label decorators, the BaseScriptComponent lifecycle (onAwake vs OnStartEvent, UpdateEvent, DelayedCallbackEvent one-shot and repeating timers, TurnOnEvent/TurnOffEvent, onDestroy), accessing components with getComponent (plus null-check patterns to fix 'cannot read property of null' errors), cross-TypeScript imports with getTypeName(), NativeLogger vs print, prefab instantiation (sync and async), SceneObject hierarchy queries, and enabling/disabling objects. Use this skill whenever writing or debugging any Lens Studio TypeScript script, wiring up scene objects, or fixing 'this is undefined' or null-reference errors — platform-agnostic (works for Spectacles and phone lenses).
12spectacles-lens-essentials
Reference guide for foundational Lens Studio patterns on Spectacles — covering the GestureModule (pinch down/up/strength, targeting, grab, phone-in-hand with correct TypeScript API), SIK components (PinchButton, DragInteractable, GrabInteractable, ScrollView), hand-tracking gestures, physics bodies/colliders/callbacks (including audio-on-collision), LSTween animation (position/scale/rotation/color tweens), prefab instantiation at runtime, materials (clone-before-modify), spatial anchors, on-device persistent storage (putString/getFloat), spatial images, and the Path Pioneer raycasting pattern. Use this skill for any Spectacles lens that needs interaction, motion, animation, physics, audio, or persistent local storage — including Essentials, Throw Lab, Spatial Persistence, Spatial Image Gallery, Path Pioneer, Public Speaker, Voice Playback, Material Library, and DJ Specs samples.
9lens-studio-world-query
Reference guide for world understanding and scoring in Lens Studio — covering WorldQueryModule HitTestSession (HitTestSessionOptions.filter for jitter smoothing, semantic surface classification for floor/wall/ceiling/table detection, null result handling, per-frame performance), SIK InteractionManager targeting interactor ray pattern, Physics.createGlobalProbe().rayCast for scene-collider hits with collision layer filtering, aligning objects to surface normals using quat.lookAt, and the LeaderboardModule (create/retrieve with TTL and OrderingType, submitScore, getLeaderboardInfo with UsersType.Global/Friends). Use this skill when detecting real floors/walls/tables to place AR content, raycasting for hover or interaction against scene objects, or adding a global in-lens leaderboard — differentiates from spectacles-lens-essentials (physics/SIK) and from spectacles-cloud (Supabase persistence).
8spectacles-cloud
Reference guide for Snap Cloud (Supabase-powered backend) in Spectacles lenses — covering Fetch API setup (requires Internet Access capability in Project Settings), Postgres REST queries with the anon key, Row Level Security policies, Realtime WebSocket subscriptions with correct postgres_changes event format and reconnect-on-sleep patterns, cloud storage uploads of base64 images captured by Spectacles, serverless Edge Functions, and companion web dashboard architecture. Use this skill whenever a lens needs persistent cloud data, needs to share data with a web app in real time, uploads captured images to a bucket, or calls a cloud function — covering Snap Cloud and World Kindness Day samples. Use spectacles-networking for plain REST calls to non-Snap backends, and spectacles-connected-lenses for in-session multiplayer state.
7lens-studio-materials-shaders
Reference guide for materials and shaders in Lens Studio — covering runtime material property changes (clone-before-modify, mainPass.baseColor, mainPass.opacity, mainPass.baseTex), blend modes (Normal/Alpha/Add/Screen/Multiply), depth and cull settings (depthTest, depthWrite, twoSided, cullMode), render order, material variants, assigning textures and render targets, reading and writing RenderTarget textures for post-processing, the graph-based Material Editor node system, custom shader graph nodes, and common shader pitfalls. Use this skill for any lens that needs to change material colors or textures at runtime, implement custom visual effects with shaders, set up post-processing render pipelines, chain render targets, or debug material/blend-mode issues — covering MaterialEditor, Drawing, and HairSimulation examples.
6spectacles-networking
Reference guide for the Lens Studio Fetch API and WebView component in Spectacles lenses — covering InternetModule (Lens Studio 5.9+), Fetch API via internetModule.fetch(Request) with bytes/text/json response handling, performHttpRequest, Internet Access capability, GET/POST requests, custom headers, Bearer auth, polling, timeouts, CORS/HTTPS, WebSocket and RemoteMediaModule for media from URLs, and bidirectional WebView messaging. Use this skill for any lens that calls a REST API, polls a JSON endpoint, loads remote images, embeds a webpage, or talks to a custom backend — including the Fetch sample. Use spectacles-ai for LLM/RSG calls, or spectacles-cloud for Supabase/Snap Cloud integration.
6