gps-plus-slam-app-framework 1.3.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/absolute-orientation-CAEh840t.d.ts +54 -0
- package/dist/{app-selectors-B8VQoMDY.d.ts → app-selectors-ikTUQZpe.d.ts} +1 -1
- package/dist/ar/bresenham3d.d.ts +1 -1
- package/dist/ar/camera-blit-capture.d.ts +2 -2
- package/dist/ar/camera-blit-capture.js +74 -9
- package/dist/ar/capability-checker.d.ts +1 -1
- package/dist/ar/capture-failure-tracker.d.ts +1 -1
- package/dist/ar/chromium-camera-access-workaround.d.ts +1 -1
- package/dist/ar/depth-grid-lookup.d.ts +2 -0
- package/dist/ar/depth-grid-lookup.js +43 -0
- package/dist/ar/depth-sampler.d.ts +2 -2
- package/dist/ar/depth-sampler.js +2 -4
- package/dist/ar/depth-unprojection.d.ts +1 -1
- package/dist/ar/enable-gps-ar.d.ts +1 -1
- package/dist/ar/enable-gps-ar.js +26 -1
- package/dist/ar/frame-loop.d.ts +1 -1
- package/dist/ar/image-capture.d.ts +2 -2
- package/dist/ar/image-capture.js +1 -154
- package/dist/ar/image-quality.d.ts +2 -0
- package/dist/ar/image-quality.js +168 -0
- package/dist/ar/index.d.ts +31 -19
- package/dist/ar/index.js +17 -5
- package/dist/ar/occupancy-grid.d.ts +1 -1
- package/dist/ar/occupancy-grid.js +29 -2
- package/dist/ar/planar-pnp.d.ts +2 -0
- package/dist/ar/planar-pnp.js +425 -0
- package/dist/ar/qr-debug-view.d.ts +2 -0
- package/dist/ar/qr-debug-view.js +89 -0
- package/dist/ar/qr-derived-pose.d.ts +2 -0
- package/dist/ar/qr-derived-pose.js +114 -0
- package/dist/ar/qr-detection-controller.d.ts +2 -0
- package/dist/ar/qr-detection-controller.js +2 -0
- package/dist/ar/qr-frontend.d.ts +2 -0
- package/dist/ar/qr-frontend.js +60 -0
- package/dist/ar/qr-pose.d.ts +2 -0
- package/dist/ar/qr-pose.js +267 -0
- package/dist/ar/qr-size-depth-context.d.ts +3 -0
- package/dist/ar/qr-size-depth-context.js +42 -0
- package/dist/ar/qr-size-measurer.d.ts +2 -0
- package/dist/ar/qr-size-measurer.js +2 -0
- package/dist/ar/replay-scene.d.ts +1 -1
- package/dist/ar/replay-scene.js +1 -1
- package/dist/ar/scene-node-names.d.ts +1 -1
- package/dist/ar/webxr-nue-basis.d.ts +1 -1
- package/dist/ar/webxr-session.d.ts +4 -4
- package/dist/ar/webxr-session.js +2 -2
- package/dist/ar/xr-camera-texture.d.ts +1 -1
- package/dist/ar/xr-error-handler.d.ts +1 -1
- package/dist/ar/xr-frame-loop.d.ts +1 -1
- package/dist/ar-MFpFlV7j.js +382 -0
- package/dist/{ar-world-group-alignment-C9lwwIyI.d.ts → ar-world-group-alignment-69fgnB39.d.ts} +1 -1
- package/dist/{camera-blit-capture-CnxUDbKx.d.ts → camera-blit-capture-NKT9H5-e.d.ts} +57 -4
- package/dist/capture-motion-gate-0Yl2NpQG.d.ts +24 -0
- package/dist/capture-motion-gate-4EscNZCw.js +94 -0
- package/dist/concurrency-By0oOTZt.d.ts +50 -0
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/create-slam-app-store-DkCHOpvb.js +149 -0
- package/dist/{create-slam-app-store-BljusOua.d.ts → create-slam-app-store-n_VF63D5.d.ts} +50 -40
- package/dist/depth-grid-lookup-DQ-IYhO3.d.ts +21 -0
- package/dist/{depth-sampler-Bhanfwla.d.ts → depth-sampler-asc8exHt.d.ts} +1 -1
- package/dist/{depth-unprojection-Y6i_mJuT.d.ts → depth-unprojection-DjsbJW9L.d.ts} +1 -1
- package/dist/{enable-gps-ar-BfUJOgpW.d.ts → enable-gps-ar-DkNhFv4Y.d.ts} +4 -4
- package/dist/{frame-conversions-BfsafGWR.d.ts → frame-conversions-DObhj8Wf.d.ts} +1 -1
- package/dist/{fused-path-DJoVu98b.d.ts → fused-path-v6eOmLl3.d.ts} +1 -1
- package/dist/geo/h3-proximity.d.ts +2 -2
- package/dist/geo/h3-proximity.js +61 -2
- package/dist/geo/index.d.ts +2 -2
- package/dist/geo/index.js +2 -2
- package/dist/geodesic-angle-BTldfFgs.js +39 -0
- package/dist/{gps-anchor-dYeCvvp5.d.ts → gps-anchor-BeFO2RYa.d.ts} +1 -1
- package/dist/{gps-ar-pose-sampler-DVb96iOy.d.ts → gps-ar-pose-sampler-DHMZmYAc.d.ts} +1 -1
- package/dist/{gps-event-coordinator-CWIDOk4U.d.ts → gps-event-coordinator-CjWW636x.d.ts} +21 -11
- package/dist/{gps-event-markers-Dc9FT-q1.d.ts → gps-event-markers-Cz9RMA2k.d.ts} +18 -0
- package/dist/{h3-proximity-I1msGPgE.d.ts → h3-proximity-C-j0pw1j.d.ts} +39 -1
- package/dist/image-capture-B2-b6tKK.js +374 -0
- package/dist/{image-capture-qAZ5Fh5X.d.ts → image-capture-CNZDrQXG.d.ts} +103 -3
- package/dist/image-quality-smCBoFj1.d.ts +158 -0
- package/dist/index-BAPokxMb.d.ts +22 -0
- package/dist/{index-BYYUdf9v.d.ts → index-BODDUTvD.d.ts} +1 -1
- package/dist/index-BneSj4_5.d.ts +399 -0
- package/dist/index-_Rhgoqkh.d.ts +2 -0
- package/dist/index.d.ts +80 -67
- package/dist/index.js +24 -12
- package/dist/{leaflet-map-overlay-6CJC-E1l.d.ts → leaflet-map-overlay-CmIyiE8n.d.ts} +1 -1
- package/dist/{map-data-CkloBxIl.d.ts → map-data-B4oEehUh.d.ts} +1 -1
- package/dist/{map-overlay-draw-krOKwDAq.d.ts → map-overlay-draw-ymz1s7fG.d.ts} +1 -1
- package/dist/{null-storage-backend-uKQE8tl7.d.ts → null-storage-backend-B9rtyDfG.d.ts} +2 -2
- package/dist/{occupancy-grid-BRNRaPyT.d.ts → occupancy-grid-DnOc-6PU.d.ts} +12 -2
- package/dist/{opfs-storage-D5zAn_lx.d.ts → opfs-storage-3VZh-qH6.d.ts} +23 -4
- package/dist/{opfs-storage-backend-6aYMUZlJ.d.ts → opfs-storage-backend-JJiUJo-3.d.ts} +3 -3
- package/dist/{persistence-middleware-B9ei4Nhx.d.ts → persistence-middleware-DAPTTwB2.d.ts} +1 -1
- package/dist/planar-pnp-my9bqULS.d.ts +67 -0
- package/dist/qr-debug-view-Brl73HGO.d.ts +29 -0
- package/dist/qr-derived-pose-CmxTP2m4.d.ts +122 -0
- package/dist/qr-detected-slice-VQssZmQJ.d.ts +166 -0
- package/dist/qr-detection-controller-B-tLVd-S.js +119 -0
- package/dist/qr-detection-controller-CDurHv8E.d.ts +48 -0
- package/dist/qr-frontend--fFX25mV.d.ts +58 -0
- package/dist/qr-pose-D2rfu5DA.d.ts +138 -0
- package/dist/qr-pose-aggregation-B7tWJq7j.d.ts +90 -0
- package/dist/qr-pose-aggregation-DBiaLW0O.js +191 -0
- package/dist/qr-size-depth-context-CeAG53fy.d.ts +13 -0
- package/dist/qr-size-measurer-DLb33geH.js +596 -0
- package/dist/qr-size-measurer-DQdAsid1.d.ts +165 -0
- package/dist/recording-options-BPCpMHLK.d.ts +514 -0
- package/dist/{recording-replayer-r0DZ9ALp.d.ts → recording-replayer-DQKpAGyQ.d.ts} +2 -2
- package/dist/recording-slice-DaxHZvNu.d.ts +55 -0
- package/dist/{replay-engine-XpDa0dy1.d.ts → replay-engine-CmKh_4wf.d.ts} +7 -4
- package/dist/{replay-scene-Bw58VL6p.d.ts → replay-scene-DBlDrV_x.d.ts} +3 -3
- package/dist/sensors/absolute-orientation.d.ts +2 -0
- package/dist/sensors/absolute-orientation.js +145 -0
- package/dist/sensors/gps-error-handler.d.ts +1 -1
- package/dist/sensors/gps.d.ts +1 -1
- package/dist/sensors/index.d.ts +5 -4
- package/dist/sensors/index.js +2 -1
- package/dist/sensors/permission-checker.d.ts +1 -1
- package/dist/sensors/permission-checker.js +1 -1
- package/dist/state/app-selectors.d.ts +1 -1
- package/dist/state/combined-root-state.d.ts +1 -1
- package/dist/state/create-slam-app-store.d.ts +1 -1
- package/dist/state/create-slam-app-store.js +1 -74
- package/dist/state/gps-ar-pose-sampler.d.ts +3 -3
- package/dist/state/gps-event-coordinator.d.ts +2 -2
- package/dist/state/gps-event-coordinator.js +27 -17
- package/dist/state/index.d.ts +20 -17
- package/dist/state/index.js +5 -4
- package/dist/state/persistence-middleware.d.ts +1 -1
- package/dist/state/persistence-middleware.js +30 -19
- package/dist/state/qr-detected-slice.d.ts +4 -0
- package/dist/state/qr-detected-slice.js +311 -0
- package/dist/state/recording-options.d.ts +2 -2
- package/dist/state/recording-options.js +273 -7
- package/dist/state/recording-replayer.d.ts +2 -2
- package/dist/state/recording-replayer.js +1 -1
- package/dist/state/recording-slice.d.ts +1 -1
- package/dist/state/recording-slice.js +5 -1
- package/dist/state/replay-engine.d.ts +1 -1
- package/dist/state/replay-engine.js +7 -3
- package/dist/state/store-subscribers.d.ts +2 -2
- package/dist/state/subscribe-to-selector.d.ts +1 -1
- package/dist/state/tracking-quality.d.ts +2 -2
- package/dist/state/tracking-quality.js +40 -128
- package/dist/state/tracking-slice.d.ts +1 -1
- package/dist/{state-BkIfpiyA.js → state-E51QT_-j.js} +2 -1
- package/dist/storage/file-system-utils.d.ts +1 -1
- package/dist/storage/index.d.ts +9 -9
- package/dist/storage/index.js +4 -4
- package/dist/storage/null-storage-backend.d.ts +1 -1
- package/dist/storage/opfs-storage-backend.d.ts +1 -1
- package/dist/storage/opfs-storage-backend.js +3 -3
- package/dist/storage/opfs-storage.d.ts +1 -1
- package/dist/storage/opfs-storage.js +5 -4
- package/dist/storage/storage-backend.d.ts +1 -1
- package/dist/storage/zip-coverage-embed.d.ts +2 -0
- package/dist/storage/zip-coverage-embed.js +88 -0
- package/dist/storage/zip-export.d.ts +2 -2
- package/dist/storage/zip-export.js +37 -59
- package/dist/storage/zip-reader.d.ts +1 -1
- package/dist/{storage-backend-Do6YDpNo.d.ts → storage-backend-BdVLD0dU.d.ts} +1 -1
- package/dist/{store-subscribers-0YWyOLvV.d.ts → store-subscribers-CA6AJH_8.d.ts} +2 -2
- package/dist/{subscribe-to-selector-CZ6k1NFP.d.ts → subscribe-to-selector-B6EUmQ3I.d.ts} +1 -1
- package/dist/test-utils/browser-mocks.d.ts +7 -1
- package/dist/test-utils/browser-mocks.js +6 -0
- package/dist/test-utils/zip-round-trip-helpers.d.ts +9 -1
- package/dist/test-utils/zip-round-trip-helpers.js +5 -1
- package/dist/{tracking-slice-CdK8c-8P.d.ts → tracking-slice-BuAZxXZ5.d.ts} +1 -1
- package/dist/types/ar-types.d.ts +1 -1
- package/dist/types/geo-types.d.ts +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/concurrency.d.ts +2 -2
- package/dist/utils/concurrency.js +66 -2
- package/dist/utils/failure-tracker.d.ts +1 -1
- package/dist/utils/format-file-size.d.ts +1 -1
- package/dist/utils/fused-path.d.ts +1 -1
- package/dist/utils/index.d.ts +8 -7
- package/dist/utils/index.js +2 -1
- package/dist/utils/list-formatter.d.ts +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/visualization/accuracy-circles.d.ts +1 -1
- package/dist/visualization/alignment-lerper.d.ts +1 -1
- package/dist/visualization/ar-world-group-alignment.d.ts +1 -1
- package/dist/visualization/ar-world-group-alignment.js +1 -1
- package/dist/visualization/camera-follower.d.ts +1 -1
- package/dist/visualization/css3d-renderer-manager.d.ts +1 -1
- package/dist/visualization/frame-conversions.d.ts +1 -1
- package/dist/visualization/frustum-visibility.d.ts +1 -1
- package/dist/visualization/gps-anchor.d.ts +1 -1
- package/dist/visualization/gps-compass-cubes.d.ts +1 -1
- package/dist/visualization/gps-event-markers.d.ts +1 -1
- package/dist/visualization/gps-event-markers.js +40 -5
- package/dist/visualization/hit-test-reticle.d.ts +1 -1
- package/dist/visualization/hit-test-reticle.js +1 -0
- package/dist/visualization/index.d.ts +18 -18
- package/dist/visualization/leaflet-map-overlay.d.ts +1 -1
- package/dist/visualization/leaflet-map-overlay.js +15 -3
- package/dist/visualization/lerp-utils.d.ts +1 -1
- package/dist/visualization/map-data.d.ts +1 -1
- package/dist/visualization/map-overlay-draw.d.ts +1 -1
- package/dist/visualization/map-overlay.d.ts +1 -1
- package/dist/visualization/three-dispose.d.ts +1 -1
- package/dist/visualization/vis-colors.d.ts +1 -1
- package/dist/{webxr-session-TIaD883f.js → webxr-session-DG-eNfny.js} +244 -11
- package/dist/{webxr-session-GjZ2BIpS.d.ts → webxr-session-Do5L6G3B.d.ts} +86 -8
- package/dist/zip-coverage-embed-DCAREVmw.d.ts +41 -0
- package/dist/{zip-export-G14hkSPG.d.ts → zip-export-ol0S_Ofd.d.ts} +27 -23
- package/package.json +2 -2
- package/dist/concurrency-CO1FQvs3.d.ts +0 -24
- package/dist/file-system-CwXTNHmB.d.ts +0 -114
- package/dist/index-CF05QV2m.d.ts +0 -2
- package/dist/recording-options-DJBXuMxV.d.ts +0 -182
- package/dist/recording-slice-89O07ebs.d.ts +0 -36
- package/dist/storage/file-system.d.ts +0 -2
- package/dist/storage/file-system.js +0 -289
- /package/dist/{accuracy-circles-BpwWAs8G.d.ts → accuracy-circles-BtqOP_v1.d.ts} +0 -0
- /package/dist/{alignment-lerper-Dm9G7JJO.d.ts → alignment-lerper-Bil6vbVw.d.ts} +0 -0
- /package/dist/{ar-types-Dan1MVTi.d.ts → ar-types-BFX2r1wk.d.ts} +0 -0
- /package/dist/{bresenham3d-CFfd7n6B.d.ts → bresenham3d-CU1Gv2W4.d.ts} +0 -0
- /package/dist/{camera-follower-C2mwEn7c.d.ts → camera-follower-CqJJ7zRj.d.ts} +0 -0
- /package/dist/{capability-checker-CQXJHxXg.d.ts → capability-checker-DUQLg6gq.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-CUeU3L_M.d.ts → capture-failure-tracker-gsm8q7yi.d.ts} +0 -0
- /package/dist/{chromium-camera-access-workaround-Jqbn0apH.d.ts → chromium-camera-access-workaround-D4K2uwwQ.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-ptu_t3B3.d.ts → css3d-renderer-manager-B3kGNtcn.d.ts} +0 -0
- /package/dist/{failure-tracker-Cn8Fo9D4.d.ts → failure-tracker-BO7M7jF_.d.ts} +0 -0
- /package/dist/{file-system-utils-CXIoL-DM.d.ts → file-system-utils-DOajnMm-.d.ts} +0 -0
- /package/dist/{format-file-size-CLCZz3lC.d.ts → format-file-size-Dc98ulzB.d.ts} +0 -0
- /package/dist/{frame-loop-CMewS8cY.d.ts → frame-loop-CpVjQ1dH.d.ts} +0 -0
- /package/dist/{frustum-visibility-CmI96ESS.d.ts → frustum-visibility-CeoCZchO.d.ts} +0 -0
- /package/dist/{geo-types-SaPnIEUM.d.ts → geo-types-3Ip1NzVR.d.ts} +0 -0
- /package/dist/{gps-VuezJWK3.d.ts → gps-BBO3hKfT.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-BM1uGiYV.d.ts → gps-compass-cubes-DSN87L8p.d.ts} +0 -0
- /package/dist/{gps-error-handler-CJl_2MVm.d.ts → gps-error-handler-BbPsfw-_.d.ts} +0 -0
- /package/dist/{hit-test-reticle-BEzEybxZ.d.ts → hit-test-reticle-DV-2TpB9.d.ts} +0 -0
- /package/dist/{lerp-utils-BpV-0Dtk.d.ts → lerp-utils-BLmHhkVe.d.ts} +0 -0
- /package/dist/{list-formatter-RRcBe8Vg.d.ts → list-formatter-KNMS0ay2.d.ts} +0 -0
- /package/dist/{logger-BEHXsnnq.d.ts → logger-CLnPrHiD.d.ts} +0 -0
- /package/dist/{map-overlay-bcogY5fK.d.ts → map-overlay-Dh2B97MP.d.ts} +0 -0
- /package/dist/{permission-checker-GpAiSCwd.d.ts → permission-checker-CDuI8BwC.d.ts} +0 -0
- /package/dist/{scene-node-names-Bu0S66H1.d.ts → scene-node-names-BiShOQDB.d.ts} +0 -0
- /package/dist/{session-disposers-M-oashRH.js → session-disposers-zv9OGXPB.js} +0 -0
- /package/dist/{three-dispose-D950t71B.d.ts → three-dispose-Bt1k7WKh.d.ts} +0 -0
- /package/dist/{vis-colors-D6omC64K.d.ts → vis-colors-Obw2pLqT.d.ts} +0 -0
- /package/dist/{webxr-nue-basis-CqKQmZzT.d.ts → webxr-nue-basis-3nfjDJJp.d.ts} +0 -0
- /package/dist/{xr-camera-texture-DBwCaKTd.d.ts → xr-camera-texture-Cva1q3Qj.d.ts} +0 -0
- /package/dist/{xr-error-handler-C0AQRmxs.d.ts → xr-error-handler-CtvHpjfm.d.ts} +0 -0
- /package/dist/{xr-frame-loop-BtYytZJg.d.ts → xr-frame-loop-DIYtRcUa.d.ts} +0 -0
- /package/dist/{zip-reader-DSEXt7K6.d.ts → zip-reader-D9iPQwdQ.d.ts} +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
//#region ../src/ar/image-quality.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Image-quality metrics + the drop/retry verdict policy for the image-content
|
|
4
|
+
* capture gate (blur + blackness), the increment layered ON TOP of the motion
|
|
5
|
+
* gate (`capture-motion-gate.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Everything here is **pure and deterministic** — plain typed-array math plus a
|
|
8
|
+
* tiny rolling-history class. There is **no DOM and no Web Worker** in this
|
|
9
|
+
* module: it is fully unit-testable with synthetic buffers, and the recorder's
|
|
10
|
+
* `image-quality.worker.ts` is a thin shell that decodes a frame to pixels and
|
|
11
|
+
* calls into these functions. Keeping the verdict/history policy here (rather
|
|
12
|
+
* than inside the worker, as the original plan §8 sketched) honours the project
|
|
13
|
+
* rule that no untested logic ships: the worker is the device layer and is not
|
|
14
|
+
* unit-built, so the median/threshold decision must live where tests can pin it.
|
|
15
|
+
*
|
|
16
|
+
* Metrics:
|
|
17
|
+
* - {@link sharpnessScore} — variance of the Laplacian over a grayscale buffer
|
|
18
|
+
* (the standard Pech-Pacheco focus measure). Scene-dependent in absolute
|
|
19
|
+
* terms, so the gate compares it RELATIVE to a rolling median (§5).
|
|
20
|
+
* - {@link meanLuminance} — mean Rec. 601 luma of an RGBA buffer. Near-zero ⇒
|
|
21
|
+
* black/empty frame; an absolute cutoff is safe because "black is black"
|
|
22
|
+
* regardless of scene texture (§5).
|
|
23
|
+
*
|
|
24
|
+
* @see GpsPlusSlamJs_Docs/docs/2026-06-24-image-quality-gate-plan.md
|
|
25
|
+
* @see ./capture-motion-gate.ts — the motion gate this builds on (same shape:
|
|
26
|
+
* a shared config type + a small stateful window + a pure decision).
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* User-/consumer-facing configuration for the image-quality gate. Shared by both
|
|
30
|
+
* config shapes that carry it — `ImageCaptureConfig` (what `ImageCaptureManager`
|
|
31
|
+
* consumes) and `ImageCaptureOptions` (the persisted recorder options) — so the
|
|
32
|
+
* one definition cannot drift between them, exactly as `MotionFilterConfig` is
|
|
33
|
+
* shared (see `capture-motion-gate.ts` and the motion-gate follow-up §3).
|
|
34
|
+
*/
|
|
35
|
+
interface QualityFilterConfig {
|
|
36
|
+
/** Master switch for the image-quality gate. Default `false` (see below). */
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Blur cutoff as a fraction `k` of the recent sharpness median: a frame is
|
|
40
|
+
* judged blurry when its sharpness `< k · median(recent)`. ~0.5 is a sensible
|
|
41
|
+
* start. Relative (not absolute) because variance-of-Laplacian is
|
|
42
|
+
* scene-dependent — a correctly-focused blank wall scores low (§5).
|
|
43
|
+
*/
|
|
44
|
+
blurRelativeThreshold: number;
|
|
45
|
+
/**
|
|
46
|
+
* Absolute black cutoff on a 0–255 luma scale: a frame whose mean luminance is
|
|
47
|
+
* below this is judged black/empty and dropped. Absolute is safe because black
|
|
48
|
+
* is black regardless of scene (§5). Supersedes the byte-size
|
|
49
|
+
* `MIN_VALID_IMAGE_BYTES` heuristic for correctness (kept as a cheap
|
|
50
|
+
* pre-filter).
|
|
51
|
+
*/
|
|
52
|
+
minMeanLuminance: number;
|
|
53
|
+
/**
|
|
54
|
+
* Never-good safety fallback: once a due capture has been retried for this many
|
|
55
|
+
* ms without an acceptable frame, the next frame is saved regardless of the
|
|
56
|
+
* image verdict, so a recording interval is never silently lost (mirrors the
|
|
57
|
+
* motion gate's `maxWaitMs`). A sensible value is ~2× the capture interval.
|
|
58
|
+
*/
|
|
59
|
+
maxWaitMs: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Default image-quality configuration. **Disabled by default** (plan §10): the
|
|
63
|
+
* relative blur threshold is unvalidated, and a mis-tuned gate silently dropping
|
|
64
|
+
* good frames for every consumer app is worse than the motion gate's low-risk
|
|
65
|
+
* default-on. Flip to `true` once the thresholds are field-tuned. The numeric
|
|
66
|
+
* values are PLACEHOLDERS pending on-device tuning (record measured values in
|
|
67
|
+
* implementation-progress.md). `maxWaitMs` of 4000 ms is 2× the default 2000 ms
|
|
68
|
+
* image interval.
|
|
69
|
+
*/
|
|
70
|
+
declare const DEFAULT_QUALITY_FILTER: QualityFilterConfig;
|
|
71
|
+
/** Default number of recent (non-black) sharpness scores the gate keeps. */
|
|
72
|
+
declare const DEFAULT_SHARPNESS_HISTORY_SIZE = 15;
|
|
73
|
+
/**
|
|
74
|
+
* Default minimum samples before the relative blur check engages. Below this the
|
|
75
|
+
* gate is in "cold start" and accepts every non-black frame — the same
|
|
76
|
+
* "no baseline yet ⇒ don't block" principle the motion gate uses for an empty
|
|
77
|
+
* window.
|
|
78
|
+
*/
|
|
79
|
+
declare const DEFAULT_SHARPNESS_MIN_SAMPLES = 3;
|
|
80
|
+
/**
|
|
81
|
+
* Compute the **variance of the Laplacian** of a single-channel grayscale image
|
|
82
|
+
* — the standard focus measure (higher ⇒ sharper / more in-focus).
|
|
83
|
+
*
|
|
84
|
+
* The discrete Laplacian per interior pixel is
|
|
85
|
+
* `up + down + left + right − 4·centre` (the 4-neighbour kernel); the score is
|
|
86
|
+
* the variance of that response over all interior pixels. A flat/constant image
|
|
87
|
+
* yields 0; a focused, textured image yields a large value.
|
|
88
|
+
*
|
|
89
|
+
* Defensive: returns `0` (rather than throwing or `NaN`) for any input that has
|
|
90
|
+
* no interior pixels — non-integer or `< 3` dimensions, or a buffer shorter than
|
|
91
|
+
* `width · height`. A tiny negative variance from floating-point cancellation is
|
|
92
|
+
* clamped to 0.
|
|
93
|
+
*
|
|
94
|
+
* @param gray Row-major single-channel intensities, length ≥ `width · height`.
|
|
95
|
+
* @param width Image width in pixels.
|
|
96
|
+
* @param height Image height in pixels.
|
|
97
|
+
*/
|
|
98
|
+
declare function sharpnessScore(gray: Uint8Array | Uint8ClampedArray | readonly number[], width: number, height: number): number;
|
|
99
|
+
/**
|
|
100
|
+
* Convert an RGBA buffer to a single-channel grayscale (Rec. 601 luma) buffer,
|
|
101
|
+
* the input {@link sharpnessScore} expects. Pure; ignores the alpha channel. A
|
|
102
|
+
* partial trailing pixel (length not a multiple of 4) is ignored.
|
|
103
|
+
*/
|
|
104
|
+
declare function rgbaToGrayscale(rgba: Uint8Array | Uint8ClampedArray): Uint8ClampedArray;
|
|
105
|
+
/**
|
|
106
|
+
* Mean Rec. 601 luma (`0.299R + 0.587G + 0.114B`) over an RGBA buffer, on a
|
|
107
|
+
* 0–255 scale. Near-zero ⇒ black/empty frame. Alpha is ignored. Returns `0` for
|
|
108
|
+
* an empty buffer.
|
|
109
|
+
*/
|
|
110
|
+
declare function meanLuminance(rgba: Uint8Array | Uint8ClampedArray): number;
|
|
111
|
+
/** Reason a frame was rejected by the quality gate, or `null` when accepted. */
|
|
112
|
+
type QualityRejectReason = 'black' | 'blurry';
|
|
113
|
+
/** Outcome of evaluating one frame against the quality gate. */
|
|
114
|
+
interface QualityVerdict {
|
|
115
|
+
/** `true` to save the frame, `false` to drop + retry. */
|
|
116
|
+
readonly accept: boolean;
|
|
117
|
+
/** Why it was dropped, or `null` when accepted. */
|
|
118
|
+
readonly reason: QualityRejectReason | null;
|
|
119
|
+
/** The frame's variance-of-Laplacian sharpness (for logging/tuning). */
|
|
120
|
+
readonly sharpness: number;
|
|
121
|
+
/** The frame's mean luminance (for logging/tuning). */
|
|
122
|
+
readonly meanLuminance: number;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The self-calibrating blur+blackness verdict, with the rolling sharpness
|
|
126
|
+
* history that makes the blur check scene-relative (§5). Stateful but pure (no
|
|
127
|
+
* DOM/worker): one instance per recording session, fed each analysed frame's
|
|
128
|
+
* metrics.
|
|
129
|
+
*
|
|
130
|
+
* Policy per frame ({@link evaluate}):
|
|
131
|
+
* 1. **Blackness — absolute.** `meanLuminance < config.minMeanLuminance` ⇒
|
|
132
|
+
* reject `'black'`. The score is NOT recorded (a black frame's ~0 sharpness
|
|
133
|
+
* must not drag the median down and disarm the blur check).
|
|
134
|
+
* 2. **Blur — relative.** Once at least `minSamples` non-black frames have been
|
|
135
|
+
* seen, reject `'blurry'` when `sharpness < blurRelativeThreshold · median`.
|
|
136
|
+
* Before that (cold start) every non-black frame is accepted.
|
|
137
|
+
* 3. The non-black frame's sharpness is recorded into the rolling window
|
|
138
|
+
* (capped at `historySize`) so the median tracks the scene's texture level —
|
|
139
|
+
* including a genuinely softening scene, which gradually lowers the bar (the
|
|
140
|
+
* §10 "retry storm" regime where the fallback then guarantees progress).
|
|
141
|
+
*/
|
|
142
|
+
declare class ImageQualityGate {
|
|
143
|
+
private readonly history;
|
|
144
|
+
private readonly historySize;
|
|
145
|
+
private readonly minSamples;
|
|
146
|
+
constructor(historySize?: number, minSamples?: number);
|
|
147
|
+
/**
|
|
148
|
+
* Judge one frame from its precomputed metrics + the current config, updating
|
|
149
|
+
* the rolling history. See the class doc for the policy.
|
|
150
|
+
*/
|
|
151
|
+
evaluate(sharpness: number, meanLum: number, config: QualityFilterConfig): QualityVerdict;
|
|
152
|
+
/** Number of (non-black) sharpness samples currently retained. */
|
|
153
|
+
historyLength(): number;
|
|
154
|
+
/** Clear the rolling history (e.g. on a new recording session). */
|
|
155
|
+
reset(): void;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
export { QualityFilterConfig as a, meanLuminance as c, ImageQualityGate as i, rgbaToGrayscale as l, DEFAULT_SHARPNESS_HISTORY_SIZE as n, QualityRejectReason as o, DEFAULT_SHARPNESS_MIN_SAMPLES as r, QualityVerdict as s, DEFAULT_QUALITY_FILTER as t, sharpnessScore as u };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ReadonlyQuat } from "gl-matrix";
|
|
2
|
+
|
|
3
|
+
//#region ../src/utils/geodesic-angle.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Geodesic (shortest-arc) rotation angle in radians between two **unit**
|
|
6
|
+
* quaternions.
|
|
7
|
+
*
|
|
8
|
+
* Uses `cos θ = 2·dot² − 1`, which is invariant to the sign of either input
|
|
9
|
+
* (`q` ≡ `−q`, the quaternion double cover) because it depends only on `dot²`.
|
|
10
|
+
* The argument is clamped to `[-1, 1]` before `acos` so float error on a
|
|
11
|
+
* near-identical pair (where `2·dot² − 1` can drift slightly above 1) returns
|
|
12
|
+
* `0` rather than `NaN` — this is the guard `matrixDelta` previously applied
|
|
13
|
+
* explicitly around the raw `quat.getAngle`.
|
|
14
|
+
*
|
|
15
|
+
* Precondition: both inputs are (approximately) unit length. Callers that hold
|
|
16
|
+
* un-normalized quaternions must normalize first (gl-matrix `quat.normalize`).
|
|
17
|
+
*
|
|
18
|
+
* @returns angle in radians, in `[0, π]`. Never `NaN` for finite unit inputs.
|
|
19
|
+
*/
|
|
20
|
+
declare function geodesicAngleRad(a: ReadonlyQuat, b: ReadonlyQuat): number;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { geodesicAngleRad as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { N as CombinedRootState, m as TrackingQualityReport } from "./create-slam-app-store-n_VF63D5.js";
|
|
2
2
|
import { Add2dImagePayload, GpsPoint as GpsPoint$1, LatLong as LatLong$1, RawDeviceOrientation, RawGpsPoint as RawGpsPoint$1, RecordGpsEventPayload as RecordGpsEventPayload$1, add2dImage, calcRelativeCoordsInMeters, recordGpsEvent as recordGpsEvent$1, setZeroPos as setZeroPos$1 } from "gps-plus-slam-js";
|
|
3
3
|
|
|
4
4
|
//#region ../src/state/onboarding-guidance.d.ts
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { t as GridCell } from "./bresenham3d-CU1Gv2W4.js";
|
|
2
|
+
import { a as QrPoseSolution, i as Pose, r as Point2, t as CameraIntrinsics } from "./qr-pose-D2rfu5DA.js";
|
|
3
|
+
import { a as QrFrontEnd, o as RgbaImage } from "./qr-frontend--fFX25mV.js";
|
|
4
|
+
import { Quaternion, RecordGpsEventPayload, Vector3 } from "gps-plus-slam-js";
|
|
5
|
+
|
|
6
|
+
//#region ../src/ar/qr-gps-vote.d.ts
|
|
7
|
+
/** Mean meters per degree of latitude (WGS-84 average). */
|
|
8
|
+
declare const METERS_PER_DEG_LAT = 111320;
|
|
9
|
+
/** Absolute geo pose of the printed QR, from the level file. */
|
|
10
|
+
interface QrGeoPose {
|
|
11
|
+
lat: number;
|
|
12
|
+
lon: number;
|
|
13
|
+
/** Altitude of the QR center, meters. */
|
|
14
|
+
alt: number;
|
|
15
|
+
/**
|
|
16
|
+
* Compass bearing (degrees clockwise from true North) that the QR's local +X
|
|
17
|
+
* axis points toward. The QR is assumed vertical (wall-mounted): local +Y =
|
|
18
|
+
* world up, local +X = horizontal along the wall at this bearing.
|
|
19
|
+
*/
|
|
20
|
+
headingDeg: number;
|
|
21
|
+
}
|
|
22
|
+
interface QrGpsVoteInput {
|
|
23
|
+
/** Solved QR pose in raw-WebXR/odom space (`solveQrPose().qrPoseWorld`). */
|
|
24
|
+
qrPoseWorld: Pose;
|
|
25
|
+
/** Printed physical side length, meters. */
|
|
26
|
+
sizeM: number;
|
|
27
|
+
/** Absolute geo pose from the level file. */
|
|
28
|
+
qrGeo: QrGeoPose;
|
|
29
|
+
/**
|
|
30
|
+
* Synthetic GPS accuracy in meters. Small → very high weight (the core
|
|
31
|
+
* library computes `weight = 1/accuracy^gpsAccuracyExponent`). Pick & validate
|
|
32
|
+
* against the fusion rather than hardcoding blindly (plan §6).
|
|
33
|
+
*/
|
|
34
|
+
syntheticAccuracyM: number;
|
|
35
|
+
/**
|
|
36
|
+
* Device/odom rotation to stamp on each payload. Defaults to the QR's world
|
|
37
|
+
* rotation; the fusion uses this only for the device-rotation derivation, not
|
|
38
|
+
* for the positional correspondence.
|
|
39
|
+
*/
|
|
40
|
+
odomRotation?: Quaternion;
|
|
41
|
+
/** 4 corner observations (default) vs. a single center observation. */
|
|
42
|
+
multiCorrespondence?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* WIDE-BASELINE north-stiffness knob (Note 2 of the follow-up plan). When > 0,
|
|
45
|
+
* synthesize the correspondences on a regular polygon of THIS radius (meters)
|
|
46
|
+
* in the QR plane instead of using the physical corners. Because the full
|
|
47
|
+
* 6-DoF pose + geo + heading are known, these virtual points are consistent in
|
|
48
|
+
* BOTH odom and geo space by construction; a wider baseline gives the single
|
|
49
|
+
* pose far more lever-arm, so corner-pixel noise maps to a much smaller heading
|
|
50
|
+
* error. The physical-corner mode is the `baselineM = 0`/undefined special case.
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ This adds NO new information — every point derives from the one solved
|
|
53
|
+
* pose — so it is a pure leverage/reweighting device. Increasing `count`
|
|
54
|
+
* makes a BAD detection harder for the outlier-rejection step to reject (all
|
|
55
|
+
* pairs are wrong together); it is only safe because the pre-injection gates (reprojection,
|
|
56
|
+
* occupancy plausibility, N-consecutive-lock) ensure only good detections vote.
|
|
57
|
+
* Treat it as a bounded tuning knob, not a free dial (plan §1 "dominates but
|
|
58
|
+
* does not entirely erase").
|
|
59
|
+
*/
|
|
60
|
+
baselineM?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Number of synthetic correspondences in wide-baseline mode (≥ 3 for a
|
|
63
|
+
* non-collinear, well-posed rotation constraint). Default 4. Ignored unless
|
|
64
|
+
* `baselineM > 0`. This is the dominance (vote-count) half of the knob.
|
|
65
|
+
*/
|
|
66
|
+
count?: number;
|
|
67
|
+
/** Epoch ms stamped on every synthetic point. Defaults to `Date.now()`. */
|
|
68
|
+
timestamp?: number;
|
|
69
|
+
/** Id prefix for the synthetic points. Default `'qr'`. */
|
|
70
|
+
idPrefix?: string;
|
|
71
|
+
}
|
|
72
|
+
/** East/North/Up offset (meters) of a QR-plane point from the QR center. */
|
|
73
|
+
interface Enu {
|
|
74
|
+
east: number;
|
|
75
|
+
north: number;
|
|
76
|
+
up: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Map a QR-local plane offset (meters; +x right, +y up on the printed face) to
|
|
80
|
+
* an East/North/Up offset, given the QR's compass heading. Vertical-QR
|
|
81
|
+
* convention: +y is world up; +x is horizontal at `headingDeg` (clockwise from
|
|
82
|
+
* North), so east = x·sin(h), north = x·cos(h).
|
|
83
|
+
*/
|
|
84
|
+
declare function localPlaneToEnu(localX: number, localY: number, headingDeg: number): Enu;
|
|
85
|
+
/** Apply an ENU meter offset to a geo pose (equirectangular; exact enough for sub-meter QR corners). */
|
|
86
|
+
declare function offsetGeo(center: QrGeoPose, enu: Enu): {
|
|
87
|
+
latitude: number;
|
|
88
|
+
longitude: number;
|
|
89
|
+
altitude: number;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Build the synthetic `recordGpsEvent` payload(s) for one QR detection. Returns
|
|
93
|
+
* 4 corner correspondences by default, or 1 center correspondence when
|
|
94
|
+
* `multiCorrespondence` is false. Each payload pairs the corner's odom position
|
|
95
|
+
* (from the solved pose) with its absolute geo position (from the level file),
|
|
96
|
+
* stamped with the tiny synthetic accuracy.
|
|
97
|
+
*
|
|
98
|
+
* @throws RangeError on a non-positive `sizeM` or `syntheticAccuracyM`.
|
|
99
|
+
*/
|
|
100
|
+
declare function buildQrGpsVotes(input: QrGpsVoteInput): RecordGpsEventPayload[];
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region ../src/ar/qr-level.d.ts
|
|
103
|
+
/**
|
|
104
|
+
* A validated QR level file.
|
|
105
|
+
*
|
|
106
|
+
* Both `physicalSizeM` and `geo` are OPTIONAL (Note 3 of the follow-up plan:
|
|
107
|
+
* flat optionals + a capability model, not a discriminated union). Their
|
|
108
|
+
* PRESENCE gates which capabilities activate, and the use-cases are combinable:
|
|
109
|
+
* - `geo` present → the high-weight GPS vote (`buildQrGpsVotes`) runs.
|
|
110
|
+
* - `physicalSizeM` present → size is authored; otherwise it must be MEASURED
|
|
111
|
+
* first (Note 4 depth path) before size-dependent features (PnP solve, vote)
|
|
112
|
+
* unlock. See the size lifecycle in `state/qr-detected-slice.ts`.
|
|
113
|
+
* - neither → a debug/observe or trigger-only level (still keyed by payload).
|
|
114
|
+
*/
|
|
115
|
+
interface QrLevel {
|
|
116
|
+
/** Schema version for forward-compat. */
|
|
117
|
+
version: number;
|
|
118
|
+
qr: {
|
|
119
|
+
/** Printed physical side length, meters. Optional — may be measured instead. */physicalSizeM?: number; /** Absolute geo pose of the QR center + heading. Optional — geo-less levels skip the vote. */
|
|
120
|
+
geo?: QrGeoPose;
|
|
121
|
+
};
|
|
122
|
+
/** AR content to instantiate (format deferred — plan §12). Opaque here. */
|
|
123
|
+
content?: unknown;
|
|
124
|
+
}
|
|
125
|
+
/** Thrown when a fetched level file fails validation. */
|
|
126
|
+
declare class QrLevelValidationError extends Error {
|
|
127
|
+
constructor(message: string);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Validate an already-parsed value as a {@link QrLevel}. Throws
|
|
131
|
+
* {@link QrLevelValidationError} with a descriptive message on any violation.
|
|
132
|
+
*/
|
|
133
|
+
declare function parseQrLevel(data: unknown): QrLevel;
|
|
134
|
+
/** Minimal `fetch` slice used by {@link fetchQrLevel}. */
|
|
135
|
+
type FetchLike = (url: string, init?: {
|
|
136
|
+
signal?: AbortSignal;
|
|
137
|
+
}) => Promise<{
|
|
138
|
+
ok: boolean;
|
|
139
|
+
status: number;
|
|
140
|
+
json(): Promise<unknown>;
|
|
141
|
+
}>;
|
|
142
|
+
interface FetchQrLevelOptions {
|
|
143
|
+
/** Injected fetch (defaults to global `fetch`). */
|
|
144
|
+
fetchImpl?: FetchLike;
|
|
145
|
+
/** Optional abort signal for cancellation. */
|
|
146
|
+
signal?: AbortSignal;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Fetch and validate a level file from `url`. Rejects with
|
|
150
|
+
* {@link QrLevelValidationError} on a non-OK response, non-JSON body, or a
|
|
151
|
+
* schema violation.
|
|
152
|
+
*/
|
|
153
|
+
declare function fetchQrLevel(url: string, options?: FetchQrLevelOptions): Promise<QrLevel>;
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region ../src/ar/qr-tracking-controller.d.ts
|
|
156
|
+
type QrTrackingStatus = 'idle' | 'scanning' | 'loading-level' | 'tracking' | 'error';
|
|
157
|
+
/** Inputs to the injected pose solve (so the controller doesn't import OpenCV). */
|
|
158
|
+
interface QrSolvePoseInput {
|
|
159
|
+
imagePoints: readonly Point2[];
|
|
160
|
+
sizeM: number;
|
|
161
|
+
intrinsics: CameraIntrinsics;
|
|
162
|
+
cameraPose: Pose;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Emitted on every lock, INDEPENDENTLY of the GPS vote (Note 3). The app maps
|
|
166
|
+
* this onto `recordQrDetection` of the `qrDetected` slice. Kept structural (no
|
|
167
|
+
* import of the state slice) so the `ar` layer never depends on `state` — that
|
|
168
|
+
* would close a cycle (`state/qr-detected-slice` already imports `ar/qr-pose`).
|
|
169
|
+
*/
|
|
170
|
+
interface QrDetectionEvent {
|
|
171
|
+
/** Decoded payload (text/URL) — the marker key. */
|
|
172
|
+
text: string;
|
|
173
|
+
qrPoseWorld: Pose;
|
|
174
|
+
qrPoseInCamera: Pose;
|
|
175
|
+
reprojectionErrorPx: number;
|
|
176
|
+
/** Epoch ms (or the injected clock) when the lock fired. */
|
|
177
|
+
timestamp: number;
|
|
178
|
+
}
|
|
179
|
+
interface QrTrackingControllerConfig {
|
|
180
|
+
/** Detect + decode (BarcodeDetector / OpenCV front-end). */
|
|
181
|
+
frontEnd: QrFrontEnd;
|
|
182
|
+
/** Solve the QR world pose from corners (production: wraps `solveQrPose`). */
|
|
183
|
+
solvePose: (input: QrSolvePoseInput) => QrPoseSolution | null;
|
|
184
|
+
/** Fetch + validate a level file from the decoded URL (cached by the controller). */
|
|
185
|
+
fetchLevel: (url: string) => Promise<QrLevel>;
|
|
186
|
+
/** Dispatch the synthetic GPS votes (production: `recordGpsEvent` per payload). */
|
|
187
|
+
dispatchVotes: (votes: RecordGpsEventPayload[]) => void;
|
|
188
|
+
/**
|
|
189
|
+
* Emitted on every lock, independent of the vote (Note 3). Apps wire this to
|
|
190
|
+
* `dispatch(recordQrDetection(event))`. The vote is conditional on `geo`;
|
|
191
|
+
* this emission is not.
|
|
192
|
+
*/
|
|
193
|
+
onDetection?: (event: QrDetectionEvent) => void;
|
|
194
|
+
/**
|
|
195
|
+
* Resolve the physical size (m) when the level omits `physicalSizeM` — e.g. a
|
|
196
|
+
* depth-measured running median (Note 4). Returns `null` while size is still
|
|
197
|
+
* unknown, which BLOCKS the pose solve (and therefore the vote + detection
|
|
198
|
+
* emission) until a size is authored or measured-and-locked (the Note 3 size
|
|
199
|
+
* lifecycle gate). Ignored when the level already carries `physicalSizeM`.
|
|
200
|
+
*/
|
|
201
|
+
resolveSizeM?: (text: string, level: QrLevel) => number | null;
|
|
202
|
+
/**
|
|
203
|
+
* Resolve the STABLE (sliding-window filtered) world pose for the vote — e.g.
|
|
204
|
+
* `selectStableQrPose(store.getState(), text)`. Returns `null` until the pose
|
|
205
|
+
* has converged, which GATES the high-weight vote (the detection emission is
|
|
206
|
+
* unconditional; only the vote waits for stability). When omitted (back-compat)
|
|
207
|
+
* the raw single-frame solve pose drives the vote.
|
|
208
|
+
*
|
|
209
|
+
* Ordering: the `onDetection` emission above feeds this frame's RAW pose into
|
|
210
|
+
* the slice synchronously, so the window this reads already includes it. See
|
|
211
|
+
* the sliding-window stabilization design doc.
|
|
212
|
+
*/
|
|
213
|
+
resolveStablePose?: (text: string) => Pose | null;
|
|
214
|
+
/** Current camera pose in raw-WebXR/odom space, or `null` if unavailable. */
|
|
215
|
+
getCameraPose: () => Pose | null;
|
|
216
|
+
/** Intrinsics for the exact frame buffer, or `null` if unavailable. */
|
|
217
|
+
getIntrinsics: (image: RgbaImage) => CameraIntrinsics | null;
|
|
218
|
+
/** Synthetic GPS accuracy (m) → vote weight. */
|
|
219
|
+
syntheticAccuracyM: number;
|
|
220
|
+
/**
|
|
221
|
+
* Optional wide-baseline north-stiffness knob (Note 2), forwarded to
|
|
222
|
+
* `buildQrGpsVotes`. `voteBaselineM > 0` synthesizes the correspondences on a
|
|
223
|
+
* polygon of that radius (stiffer rotation fit); `voteCount` (≥3) sets how
|
|
224
|
+
* many. Leave unset for the default physical-corner mode. Treat as a bounded
|
|
225
|
+
* tuning knob — a larger count makes a bad detection harder to outlier-reject.
|
|
226
|
+
*/
|
|
227
|
+
voteBaselineM?: number;
|
|
228
|
+
voteCount?: number;
|
|
229
|
+
/** Optional plausibility gate (e.g. occupancy self-check); `false` rejects. */
|
|
230
|
+
isPlausible?: (solution: QrPoseSolution, cameraPose: Pose) => boolean;
|
|
231
|
+
/** Status-change notifications for the UI. */
|
|
232
|
+
onStatus?: (status: QrTrackingStatus) => void;
|
|
233
|
+
/** Called each time a locked detection dispatches votes. */
|
|
234
|
+
onLocked?: (solution: QrPoseSolution, level: QrLevel) => void;
|
|
235
|
+
/** Surfaced failures (level fetch, detect throw). */
|
|
236
|
+
onError?: (err: unknown) => void;
|
|
237
|
+
/** Scheduler tuning (see `detection-scheduler.ts`). */
|
|
238
|
+
minIntervalMs?: number;
|
|
239
|
+
requiredLockCount?: number;
|
|
240
|
+
now?: () => number;
|
|
241
|
+
}
|
|
242
|
+
interface QrTrackingController {
|
|
243
|
+
/** Offer the latest camera frame; throttled/coalesced internally. */
|
|
244
|
+
offerFrame(image: RgbaImage): void;
|
|
245
|
+
/** Current status. */
|
|
246
|
+
readonly status: QrTrackingStatus;
|
|
247
|
+
/** Stop tracking and reset to `idle` (clears the level cache). */
|
|
248
|
+
reset(): void;
|
|
249
|
+
}
|
|
250
|
+
declare function createQrTrackingController(config: QrTrackingControllerConfig): QrTrackingController;
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region ../src/ar/camera-frame-source.d.ts
|
|
253
|
+
/** Tuning for the camera frame source. */
|
|
254
|
+
interface CameraFrameSourceConfig {
|
|
255
|
+
/**
|
|
256
|
+
* Minimum interval between captures in milliseconds. Default 125 ms (≈ 8 Hz),
|
|
257
|
+
* the plan §9 5–10 Hz detection target. The capture (and therefore the blit)
|
|
258
|
+
* fires at most once per `intervalMs`.
|
|
259
|
+
*/
|
|
260
|
+
intervalMs: number;
|
|
261
|
+
}
|
|
262
|
+
/** Injected I/O for the camera frame source. */
|
|
263
|
+
interface CameraFrameSourceCallbacks {
|
|
264
|
+
/**
|
|
265
|
+
* Capture the current XR frame as top-left-origin RGBA, or `null` when no
|
|
266
|
+
* frame is available (no camera texture yet, GL failure). This is the GPU
|
|
267
|
+
* blit + readback; the source only invokes it at the throttled cadence so
|
|
268
|
+
* the cost is bounded. A `null` return does NOT consume the interval slot —
|
|
269
|
+
* the next frame retries immediately (a missing texture is transient).
|
|
270
|
+
*/
|
|
271
|
+
capture: () => RgbaImage | null;
|
|
272
|
+
/** Receive a throttled, successfully-captured frame. */
|
|
273
|
+
onCapture: (image: RgbaImage) => void;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Throttled RGBA capturer. Construct with the injected `capture`/`onCapture`
|
|
277
|
+
* pair, `start()`, then call `onFrame(timestamp)` once per XR frame.
|
|
278
|
+
*/
|
|
279
|
+
declare class CameraFrameSource {
|
|
280
|
+
private readonly callbacks;
|
|
281
|
+
private readonly config;
|
|
282
|
+
private running;
|
|
283
|
+
private captureCount;
|
|
284
|
+
private lastCaptureTime;
|
|
285
|
+
constructor(callbacks: CameraFrameSourceCallbacks, config?: Partial<CameraFrameSourceConfig>);
|
|
286
|
+
/** Begin throttling. Resets the cadence so the first tick captures. */
|
|
287
|
+
start(): void;
|
|
288
|
+
/** Stop capturing. `onFrame` becomes a no-op until `start()` is called again. */
|
|
289
|
+
stop(): void;
|
|
290
|
+
isRunning(): boolean;
|
|
291
|
+
/** Number of frames successfully captured + delivered since `start()`. */
|
|
292
|
+
getFrameCount(): number;
|
|
293
|
+
getConfig(): CameraFrameSourceConfig;
|
|
294
|
+
/**
|
|
295
|
+
* Apply partial config (e.g. the app's detection cadence). Invalid values
|
|
296
|
+
* are ignored defensively — `intervalMs` requires a finite positive number.
|
|
297
|
+
*/
|
|
298
|
+
updateConfig(config: Partial<CameraFrameSourceConfig>): void;
|
|
299
|
+
/**
|
|
300
|
+
* Per-XR-frame tick. Captures + delivers a frame at most once per
|
|
301
|
+
* `intervalMs`; otherwise a cheap no-op so it is safe to call every frame.
|
|
302
|
+
*
|
|
303
|
+
* @param timestamp - monotonic frame time in ms (the XR `time` argument).
|
|
304
|
+
*/
|
|
305
|
+
onFrame(timestamp: number): void;
|
|
306
|
+
/**
|
|
307
|
+
* Run the injected capture, guarded so a blit failure (e.g. GL context loss)
|
|
308
|
+
* can never throw out of the XR frame loop — it degrades to "no frame this
|
|
309
|
+
* tick", exactly like a missing texture.
|
|
310
|
+
*/
|
|
311
|
+
private captureSafely;
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region ../src/ar/detection-scheduler.d.ts
|
|
315
|
+
interface DetectionSchedulerConfig<TResult, TImage = RgbaImage> {
|
|
316
|
+
/** The full detect→solve step; resolves to a result or `null` (no hit / rejected). */
|
|
317
|
+
detect: (image: TImage) => Promise<TResult | null>;
|
|
318
|
+
/** Minimum ms between detection STARTS (throttle). 100 ms ≈ 10 Hz. */
|
|
319
|
+
minIntervalMs: number;
|
|
320
|
+
/** Consecutive successes required before a lock is reported. Default 3. */
|
|
321
|
+
requiredLockCount?: number;
|
|
322
|
+
/** Injectable clock (ms). Defaults to `performance.now()`/`Date.now()`. */
|
|
323
|
+
now?: () => number;
|
|
324
|
+
/** Called on each success once locked (consecutiveLocks ≥ requiredLockCount). */
|
|
325
|
+
onLocked?: (result: TResult) => void;
|
|
326
|
+
/** Called when a detection completes with no usable result. */
|
|
327
|
+
onMiss?: () => void;
|
|
328
|
+
/** Called when `detect` rejects (the counter is reset). */
|
|
329
|
+
onError?: (err: unknown) => void;
|
|
330
|
+
}
|
|
331
|
+
interface DetectionScheduler<TImage = RgbaImage> {
|
|
332
|
+
/** Offer the latest camera frame; may or may not start a detection. */
|
|
333
|
+
offerFrame(image: TImage): void;
|
|
334
|
+
/** True while a detection is awaiting `detect`. */
|
|
335
|
+
readonly inFlight: boolean;
|
|
336
|
+
/** Current consecutive-success count (capped at requiredLockCount). */
|
|
337
|
+
readonly consecutiveLocks: number;
|
|
338
|
+
/** True once `consecutiveLocks` has reached `requiredLockCount`. */
|
|
339
|
+
readonly locked: boolean;
|
|
340
|
+
}
|
|
341
|
+
declare function createDetectionScheduler<TResult, TImage = RgbaImage>(config: DetectionSchedulerConfig<TResult, TImage>): DetectionScheduler<TImage>;
|
|
342
|
+
/** {@link DetectionSchedulerConfig} specialized to the QR pose solution. */
|
|
343
|
+
type QrDetectionSchedulerConfig = DetectionSchedulerConfig<QrPoseSolution>;
|
|
344
|
+
/** {@link DetectionScheduler} specialized to the QR (RGBA) frame. */
|
|
345
|
+
type QrDetectionScheduler = DetectionScheduler<RgbaImage>;
|
|
346
|
+
/** The QR path: a detection scheduler whose result is a {@link QrPoseSolution}. */
|
|
347
|
+
declare function createQrDetectionScheduler(config: QrDetectionSchedulerConfig): QrDetectionScheduler;
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region ../src/ar/qr-occupancy-check.d.ts
|
|
350
|
+
/**
|
|
351
|
+
* The slice of `OccupancyGrid` this check needs. Declared structurally so the
|
|
352
|
+
* real grid satisfies it and tests can pass a tiny synthetic stand-in.
|
|
353
|
+
*/
|
|
354
|
+
interface OccupancySurface {
|
|
355
|
+
readonly cellSizeM: number;
|
|
356
|
+
getOccupiedCells(minObservations?: number): readonly GridCell[];
|
|
357
|
+
getCellCenter(cell: GridCell): Vector3;
|
|
358
|
+
}
|
|
359
|
+
type QrPlausibilityVerdict = 'plausible' | 'floating' | 'behind-surface' | 'no-grid';
|
|
360
|
+
interface QrPlausibility {
|
|
361
|
+
verdict: QrPlausibilityVerdict;
|
|
362
|
+
/** `false` only for an implausible pose (`floating` / `behind-surface`). */
|
|
363
|
+
ok: boolean;
|
|
364
|
+
/** Distance from the QR center to the nearest occupied voxel; `Infinity` when the grid is empty. */
|
|
365
|
+
nearestSurfaceDistanceM: number;
|
|
366
|
+
}
|
|
367
|
+
interface QrPlausibilityOptions {
|
|
368
|
+
/**
|
|
369
|
+
* How far (in `cellSizeM` units) the QR may sit from the nearest occupied
|
|
370
|
+
* voxel before it counts as "floating". Default 1.5.
|
|
371
|
+
*/
|
|
372
|
+
surfaceToleranceCells?: number;
|
|
373
|
+
/** Minimum observation count for a voxel to count as occupied. Default 1. */
|
|
374
|
+
minObservations?: number;
|
|
375
|
+
/**
|
|
376
|
+
* Perpendicular distance (in `cellSizeM` units) within which an occupied
|
|
377
|
+
* voxel counts as lying ON the camera→QR ray. Default 1.
|
|
378
|
+
*/
|
|
379
|
+
rayCorridorCells?: number;
|
|
380
|
+
/**
|
|
381
|
+
* Margin (in `cellSizeM` units) subtracted from the camera→QR distance so the
|
|
382
|
+
* QR's own wall voxel is not mistaken for an occluder. Default 2.
|
|
383
|
+
*/
|
|
384
|
+
occlusionMarginCells?: number;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Classify a solved QR center against the occupancy grid.
|
|
388
|
+
*
|
|
389
|
+
* - `no-grid` (ok): the grid has no occupied voxels — nothing to check against.
|
|
390
|
+
* - `floating` (not ok): nearest occupied voxel is farther than the tolerance.
|
|
391
|
+
* - `behind-surface` (not ok): an occupied voxel lies on the camera→QR ray
|
|
392
|
+
* clearly in front of the QR (the QR is inside / beyond geometry).
|
|
393
|
+
* - `plausible` (ok): on a surface with empty space toward the camera.
|
|
394
|
+
*
|
|
395
|
+
* All positions are raw-WebXR/odom space (the same frame as `qrPoseWorld`).
|
|
396
|
+
*/
|
|
397
|
+
declare function checkQrPlausibility(qrCenterWorld: Vector3, cameraPosWorld: Vector3, grid: OccupancySurface, options?: QrPlausibilityOptions): QrPlausibility;
|
|
398
|
+
//#endregion
|
|
399
|
+
export { QrGpsVoteInput as A, QrLevel as C, Enu as D, parseQrLevel as E, localPlaneToEnu as M, offsetGeo as N, METERS_PER_DEG_LAT as O, FetchQrLevelOptions as S, fetchQrLevel as T, QrTrackingController as _, checkQrPlausibility as a, createQrTrackingController as b, QrDetectionScheduler as c, createQrDetectionScheduler as d, CameraFrameSource as f, QrSolvePoseInput as g, QrDetectionEvent as h, QrPlausibilityVerdict as i, buildQrGpsVotes as j, QrGeoPose as k, QrDetectionSchedulerConfig as l, CameraFrameSourceConfig as m, QrPlausibility as n, DetectionScheduler as o, CameraFrameSourceCallbacks as p, QrPlausibilityOptions as r, DetectionSchedulerConfig as s, OccupancySurface as t, createDetectionScheduler as u, QrTrackingControllerConfig as v, QrLevelValidationError as w, FetchLike as x, QrTrackingStatus as y };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { ArImageCapture as ArImageCapture$1, GpsPoint as GpsPoint$1, LatLong as LatLong$1, LatLongAlt, Matrix4 as Matrix4$1, Quaternion as Quaternion$1, RootState as LibraryRootState, RootState as RootState$1, Vector3 as Vector3$1, arNorthBearingDeg, bearingDeltaDeg, calcGpsCoords as calcGpsCoords$1, calcRelativeCoordsInMeters, createGpsSlamStore, isIdentityMatrix4, magneticHeadingFromEnuQuat, odometryTrackingRestarted, validateLicenseKey as validateLicenseKey$1, webxrToNUE } from "gps-plus-slam-js";
|
|
2
|
+
export { odometryTrackingRestarted as _, LibraryRootState as a, RootState$1 as c, bearingDeltaDeg as d, calcGpsCoords$1 as f, magneticHeadingFromEnuQuat as g, isIdentityMatrix4 as h, LatLongAlt as i, Vector3$1 as l, createGpsSlamStore as m, GpsPoint$1 as n, Matrix4$1 as o, calcRelativeCoordsInMeters as p, LatLong$1 as r, Quaternion$1 as s, ArImageCapture$1 as t, arNorthBearingDeg as u, validateLicenseKey$1 as v, webxrToNUE as y };
|