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,90 @@
|
|
|
1
|
+
import { i as Pose } from "./qr-pose-D2rfu5DA.js";
|
|
2
|
+
import { Quaternion } from "gps-plus-slam-js";
|
|
3
|
+
|
|
4
|
+
//#region ../src/ar/qr-pose-aggregation.d.ts
|
|
5
|
+
/** Default inlier half-angle (deg) — start ~12°, confirm on device. */
|
|
6
|
+
declare const DEFAULT_ROTATION_INLIER_ANGLE_DEG = 12;
|
|
7
|
+
interface AverageRotationOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Reject samples whose geodesic angle to the reference exceeds this many
|
|
10
|
+
* degrees. Default {@link DEFAULT_ROTATION_INLIER_ANGLE_DEG}.
|
|
11
|
+
*/
|
|
12
|
+
inlierAngleDeg?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Provisional reference rotation for the first inlier pass. When omitted, a
|
|
15
|
+
* robust reference is chosen by MODE-FINDING (the sample with the most other
|
|
16
|
+
* samples within the inlier threshold — the densest cluster). This matters:
|
|
17
|
+
* the naive "use the latest sample" breaks exactly when the latest frame is
|
|
18
|
+
* the bad one, which is the whole reason for this filter.
|
|
19
|
+
*/
|
|
20
|
+
reference?: Quaternion;
|
|
21
|
+
}
|
|
22
|
+
interface AverageRotationResult {
|
|
23
|
+
/** Robust mean rotation, a unit quaternion `[x,y,z,w]` (canonicalized w ≥ 0). */
|
|
24
|
+
quat: Quaternion;
|
|
25
|
+
/** How many samples lie within the inlier threshold of the final mean. */
|
|
26
|
+
inlierCount: number;
|
|
27
|
+
/** Max geodesic angle (deg) among the inliers to the final mean — the spread. */
|
|
28
|
+
maxAngleDeg: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Robust mean of a set of rotations (Option R1). Picks a reference (mode-finding,
|
|
32
|
+
* or `options.reference`), rejects samples beyond the angular threshold, averages
|
|
33
|
+
* the inliers, then does ONE refit pass against the provisional mean. Returns
|
|
34
|
+
* `null` for an empty input.
|
|
35
|
+
*/
|
|
36
|
+
declare function averageRotation(quats: readonly Quaternion[], options?: AverageRotationOptions): AverageRotationResult | null;
|
|
37
|
+
interface AggregateQrPoseResult {
|
|
38
|
+
/** Filtered pose: per-axis median position + robust mean rotation. */
|
|
39
|
+
pose: Pose;
|
|
40
|
+
/** Max absolute per-axis deviation from the median position (meters). */
|
|
41
|
+
translationSpreadM: number;
|
|
42
|
+
/** Max geodesic angle among rotation inliers to the mean (degrees). */
|
|
43
|
+
rotationSpreadDeg: number;
|
|
44
|
+
/** Rotation inlier count (≤ pose count). */
|
|
45
|
+
inlierCount: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Aggregate a window of raw poses into one filtered pose + the spreads the
|
|
49
|
+
* stability gate consumes. Returns `null` for an empty window.
|
|
50
|
+
*/
|
|
51
|
+
declare function aggregateQrPose(poses: readonly Pose[], options?: AverageRotationOptions): AggregateQrPoseResult | null;
|
|
52
|
+
/**
|
|
53
|
+
* Where the pose-stability gate sits for one marker, mirroring the size
|
|
54
|
+
* lifecycle (`QrSizeStatus`):
|
|
55
|
+
* - `unknown` — no observations yet.
|
|
56
|
+
* - `measuring` — observations accumulating but not enough / not converged.
|
|
57
|
+
* - `stable` — ≥ `minObservations` AND both spreads below threshold; the pose
|
|
58
|
+
* may now drive the high-weight vote / the smooth overlay.
|
|
59
|
+
*/
|
|
60
|
+
type QrPoseStabilityStatus = 'unknown' | 'measuring' | 'stable';
|
|
61
|
+
interface QrPoseStabilityOptions extends AverageRotationOptions {
|
|
62
|
+
/** Sliding-window size: aggregate only the last `window` poses. Default 8. */
|
|
63
|
+
window?: number;
|
|
64
|
+
/** Minimum observations before the gate can be `stable`. Default 5. */
|
|
65
|
+
minObservations?: number;
|
|
66
|
+
/** Max translation spread (m) allowed for `stable`. Default 0.03. */
|
|
67
|
+
maxTranslationSpreadM?: number;
|
|
68
|
+
/** Max rotation spread (deg) allowed for `stable`. Default 5. */
|
|
69
|
+
maxRotationSpreadDeg?: number;
|
|
70
|
+
}
|
|
71
|
+
interface QrPoseStability {
|
|
72
|
+
status: QrPoseStabilityStatus;
|
|
73
|
+
/** The filtered pose (present once there is ≥1 observation), else `null`. */
|
|
74
|
+
pose: Pose | null;
|
|
75
|
+
translationSpreadM: number;
|
|
76
|
+
rotationSpreadDeg: number;
|
|
77
|
+
inlierCount: number;
|
|
78
|
+
/** Observations actually aggregated (≤ `window`). */
|
|
79
|
+
sampleCount: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Evaluate the pose-stability lifecycle over the last `window` of `poses`
|
|
83
|
+
* (oldest→newest). The returned `pose` is the filtered aggregate whenever there
|
|
84
|
+
* is data, but it is only the gate's responsibility (`status === 'stable'`) to
|
|
85
|
+
* decide when a consumer may TRUST it for the vote — `selectStableQrPose` reads
|
|
86
|
+
* the pose only in the `stable` state.
|
|
87
|
+
*/
|
|
88
|
+
declare function evaluateQrPoseStability(poses: readonly Pose[], options?: QrPoseStabilityOptions): QrPoseStability;
|
|
89
|
+
//#endregion
|
|
90
|
+
export { QrPoseStability as a, aggregateQrPose as c, DEFAULT_ROTATION_INLIER_ANGLE_DEG as i, averageRotation as l, AverageRotationOptions as n, QrPoseStabilityOptions as o, AverageRotationResult as r, QrPoseStabilityStatus as s, AggregateQrPoseResult as t, evaluateQrPoseStability as u };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { t as geodesicAngleRad } from "./geodesic-angle-BTldfFgs.js";
|
|
2
|
+
import { quat } from "gl-matrix";
|
|
3
|
+
//#region ../src/ar/qr-pose-aggregation.ts
|
|
4
|
+
/**
|
|
5
|
+
* QR pose aggregation — sliding-window pose stabilization
|
|
6
|
+
* (docs `2026-06-16-followup-qr-pose-stabilization-sliding-window.md`).
|
|
7
|
+
*
|
|
8
|
+
* Pure, device-free, store-free robust aggregation of a STATIC fiducial's pose
|
|
9
|
+
* across a short sliding window of detections. A QR is static in the world, so
|
|
10
|
+
* the last N world poses should be (nearly) identical — disagreement is noise,
|
|
11
|
+
* which robust aggregation removes. The aggregated pose is what drives the
|
|
12
|
+
* high-weight GPS vote and the debug overlay, instead of a raw single-frame pose
|
|
13
|
+
* whose ROTATION can swing (the on-device symptom this targets).
|
|
14
|
+
*
|
|
15
|
+
* Three layers, smallest first:
|
|
16
|
+
* - {@link averageRotation} — Option R1: angle-thresholded inlier set + hemisphere-
|
|
17
|
+
* aligned quaternion average. The real new work; translation already had
|
|
18
|
+
* {@link medianQrPosition} but rotation had no counterpart. Quaternion double
|
|
19
|
+
* cover (`q` ≡ `−q`) is handled explicitly — the single most common bug here.
|
|
20
|
+
* - {@link aggregateQrPose} — per-axis median position + R1 rotation, reporting
|
|
21
|
+
* the spreads + inlier count the stability gate needs.
|
|
22
|
+
* - {@link evaluateQrPoseStability} — the `unknown → measuring → stable` lifecycle
|
|
23
|
+
* over the last `window` poses, mirroring the size lifecycle. The pose is only
|
|
24
|
+
* exposed (for the vote / overlay) once `stable`.
|
|
25
|
+
*
|
|
26
|
+
* Operates on plain `Pose` lists (no slice import) so BOTH the framework
|
|
27
|
+
* controller and the demo controller can reuse it, and it is unit-testable
|
|
28
|
+
* without a store. The slice selectors `selectStableQrPose` /
|
|
29
|
+
* `selectQrPoseStability` wrap these over a marker's ring buffer.
|
|
30
|
+
*
|
|
31
|
+
* INVARIANT: the caller must feed RAW per-detection world poses here. Never feed
|
|
32
|
+
* a previously-aggregated pose back in — the window would average its own output
|
|
33
|
+
* and collapse toward whatever it first locked onto (a feedback loop that defeats
|
|
34
|
+
* outlier rejection). The `qrDetected` ring buffer stores raw poses for exactly
|
|
35
|
+
* this reason.
|
|
36
|
+
*
|
|
37
|
+
* @see ./qr-pose.ts — the `Pose` type and the (raw) pose solve this aggregates.
|
|
38
|
+
* @see ../state/qr-detected-slice.ts — `selectStableQrPose` / `selectQrPoseStability`.
|
|
39
|
+
*/
|
|
40
|
+
const RAD2DEG = 180 / Math.PI;
|
|
41
|
+
const DEG2RAD = Math.PI / 180;
|
|
42
|
+
/** Default inlier half-angle (deg) — start ~12°, confirm on device. */
|
|
43
|
+
const DEFAULT_ROTATION_INLIER_ANGLE_DEG = 12;
|
|
44
|
+
function toGl(q) {
|
|
45
|
+
const g = quat.fromValues(q[0], q[1], q[2], q[3]);
|
|
46
|
+
return quat.normalize(g, g);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Average the samples within `thresholdRad` of `ref`. Each inlier is flipped
|
|
50
|
+
* into `ref`'s hemisphere (sign of the dot) BEFORE the linear average so the
|
|
51
|
+
* double cover cannot cancel the sum; the normalized linear average equals the
|
|
52
|
+
* Markley eigenvector mean for a tight cluster and needs no eigensolver.
|
|
53
|
+
* Returns `null` when no sample is within threshold.
|
|
54
|
+
*/
|
|
55
|
+
function meanOfInliers(samples, ref, thresholdRad) {
|
|
56
|
+
const inliers = [];
|
|
57
|
+
for (const s of samples) if (geodesicAngleRad(s, ref) <= thresholdRad) inliers.push(s);
|
|
58
|
+
if (inliers.length === 0) return null;
|
|
59
|
+
const acc = quat.fromValues(0, 0, 0, 0);
|
|
60
|
+
for (const s of inliers) {
|
|
61
|
+
const sign = quat.dot(s, ref) < 0 ? -1 : 1;
|
|
62
|
+
acc[0] += sign * s[0];
|
|
63
|
+
acc[1] += sign * s[1];
|
|
64
|
+
acc[2] += sign * s[2];
|
|
65
|
+
acc[3] += sign * s[3];
|
|
66
|
+
}
|
|
67
|
+
const mean = quat.normalize(quat.create(), acc);
|
|
68
|
+
let maxAngleRad = 0;
|
|
69
|
+
for (const s of inliers) maxAngleRad = Math.max(maxAngleRad, geodesicAngleRad(s, mean));
|
|
70
|
+
return {
|
|
71
|
+
mean,
|
|
72
|
+
inlierCount: inliers.length,
|
|
73
|
+
maxAngleRad
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Pick the densest sample as the reference: the one with the most OTHER samples
|
|
78
|
+
* within the inlier threshold. For a single rigid pose, consensus-based outlier
|
|
79
|
+
* rejection degenerates to this mode-finding, and it is robust to the latest
|
|
80
|
+
* sample being an outlier.
|
|
81
|
+
*/
|
|
82
|
+
function bestReference(samples, thresholdRad) {
|
|
83
|
+
let best = samples[0];
|
|
84
|
+
let bestCount = -1;
|
|
85
|
+
for (const cand of samples) {
|
|
86
|
+
let count = 0;
|
|
87
|
+
for (const s of samples) if (geodesicAngleRad(s, cand) <= thresholdRad) count++;
|
|
88
|
+
if (count > bestCount) {
|
|
89
|
+
bestCount = count;
|
|
90
|
+
best = cand;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return best;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Robust mean of a set of rotations (Option R1). Picks a reference (mode-finding,
|
|
97
|
+
* or `options.reference`), rejects samples beyond the angular threshold, averages
|
|
98
|
+
* the inliers, then does ONE refit pass against the provisional mean. Returns
|
|
99
|
+
* `null` for an empty input.
|
|
100
|
+
*/
|
|
101
|
+
function averageRotation(quats, options = {}) {
|
|
102
|
+
if (quats.length === 0) return null;
|
|
103
|
+
const thresholdRad = (options.inlierAngleDeg ?? 12) * DEG2RAD;
|
|
104
|
+
const samples = quats.map(toGl);
|
|
105
|
+
const provisional = meanOfInliers(samples, options.reference ? toGl(options.reference) : bestReference(samples, thresholdRad), thresholdRad);
|
|
106
|
+
if (!provisional) return null;
|
|
107
|
+
const refined = meanOfInliers(samples, provisional.mean, thresholdRad) ?? provisional;
|
|
108
|
+
const m = refined.mean;
|
|
109
|
+
const s = m[3] < 0 ? -1 : 1;
|
|
110
|
+
return {
|
|
111
|
+
quat: [
|
|
112
|
+
s * m[0],
|
|
113
|
+
s * m[1],
|
|
114
|
+
s * m[2],
|
|
115
|
+
s * m[3]
|
|
116
|
+
],
|
|
117
|
+
inlierCount: refined.inlierCount,
|
|
118
|
+
maxAngleDeg: refined.maxAngleRad * RAD2DEG
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Per-axis median of a numeric list (lower-middle for even n, matching
|
|
122
|
+
* `medianQrPosition` in the slice so the two agree on the same data). */
|
|
123
|
+
function medianOf(values) {
|
|
124
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
125
|
+
return sorted[Math.floor((sorted.length - 1) / 2)];
|
|
126
|
+
}
|
|
127
|
+
function medianPosition(positions) {
|
|
128
|
+
return [
|
|
129
|
+
medianOf(positions.map((p) => p[0])),
|
|
130
|
+
medianOf(positions.map((p) => p[1])),
|
|
131
|
+
medianOf(positions.map((p) => p[2]))
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
/** Max absolute per-axis deviation from `center` across all positions. */
|
|
135
|
+
function translationSpread(positions, center) {
|
|
136
|
+
let max = 0;
|
|
137
|
+
for (const p of positions) max = Math.max(max, Math.abs(p[0] - center[0]), Math.abs(p[1] - center[1]), Math.abs(p[2] - center[2]));
|
|
138
|
+
return max;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Aggregate a window of raw poses into one filtered pose + the spreads the
|
|
142
|
+
* stability gate consumes. Returns `null` for an empty window.
|
|
143
|
+
*/
|
|
144
|
+
function aggregateQrPose(poses, options = {}) {
|
|
145
|
+
if (poses.length === 0) return null;
|
|
146
|
+
const positions = poses.map((p) => p.position);
|
|
147
|
+
const position = medianPosition(positions);
|
|
148
|
+
const rotation = averageRotation(poses.map((p) => p.rotation), options);
|
|
149
|
+
if (!rotation) return null;
|
|
150
|
+
return {
|
|
151
|
+
pose: {
|
|
152
|
+
position,
|
|
153
|
+
rotation: rotation.quat
|
|
154
|
+
},
|
|
155
|
+
translationSpreadM: translationSpread(positions, position),
|
|
156
|
+
rotationSpreadDeg: rotation.maxAngleDeg,
|
|
157
|
+
inlierCount: rotation.inlierCount
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const UNKNOWN_STABILITY = {
|
|
161
|
+
status: "unknown",
|
|
162
|
+
pose: null,
|
|
163
|
+
translationSpreadM: 0,
|
|
164
|
+
rotationSpreadDeg: 0,
|
|
165
|
+
inlierCount: 0,
|
|
166
|
+
sampleCount: 0
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Evaluate the pose-stability lifecycle over the last `window` of `poses`
|
|
170
|
+
* (oldest→newest). The returned `pose` is the filtered aggregate whenever there
|
|
171
|
+
* is data, but it is only the gate's responsibility (`status === 'stable'`) to
|
|
172
|
+
* decide when a consumer may TRUST it for the vote — `selectStableQrPose` reads
|
|
173
|
+
* the pose only in the `stable` state.
|
|
174
|
+
*/
|
|
175
|
+
function evaluateQrPoseStability(poses, options = {}) {
|
|
176
|
+
const { window = 8, minObservations = 5, maxTranslationSpreadM = .03, maxRotationSpreadDeg = 5 } = options;
|
|
177
|
+
if (poses.length === 0) return { ...UNKNOWN_STABILITY };
|
|
178
|
+
const windowPoses = poses.length > window ? poses.slice(poses.length - window) : poses;
|
|
179
|
+
const agg = aggregateQrPose(windowPoses, options);
|
|
180
|
+
if (!agg) return { ...UNKNOWN_STABILITY };
|
|
181
|
+
return {
|
|
182
|
+
status: windowPoses.length >= minObservations && agg.translationSpreadM <= maxTranslationSpreadM && agg.rotationSpreadDeg <= maxRotationSpreadDeg ? "stable" : "measuring",
|
|
183
|
+
pose: agg.pose,
|
|
184
|
+
translationSpreadM: agg.translationSpreadM,
|
|
185
|
+
rotationSpreadDeg: agg.rotationSpreadDeg,
|
|
186
|
+
inlierCount: agg.inlierCount,
|
|
187
|
+
sampleCount: windowPoses.length
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
export { evaluateQrPoseStability as i, aggregateQrPose as n, averageRotation as r, DEFAULT_ROTATION_INLIER_ANGLE_DEG as t };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { i as DepthSample } from "./ar-types-BFX2r1wk.js";
|
|
2
|
+
import { n as QrSizeDepthContext } from "./qr-size-measurer-DQdAsid1.js";
|
|
3
|
+
|
|
4
|
+
//#region ../src/ar/qr-size-depth-context.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Build a {@link QrSizeDepthContext} from one depth sample, or `null` when it has
|
|
7
|
+
* no projection matrix / a singular one (the unprojector cannot be built). The
|
|
8
|
+
* grid lookup is **bilinear** (depth varies smoothly across a small QR face,
|
|
9
|
+
* rather than snapping to one nearest node). Best-effort — never throws.
|
|
10
|
+
*/
|
|
11
|
+
declare function createQrSizeDepthContext(sample: DepthSample): QrSizeDepthContext | null;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { createQrSizeDepthContext as t };
|