gps-plus-slam-app-framework 1.2.0 → 1.4.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/{app-selectors-CcZ5UV-8.d.ts → app-selectors-CrOucL00.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 +87 -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 +7 -5
- package/dist/ar/index.d.ts +30 -19
- package/dist/ar/index.js +15 -4
- 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-Dc72Ksiu.js +382 -0
- package/dist/{ar-world-group-alignment-ColHB_5A.d.ts → ar-world-group-alignment-DDLkgNf_.d.ts} +1 -1
- package/dist/{camera-blit-capture-D5_mNhla.d.ts → camera-blit-capture-CqWe1G7s.d.ts} +66 -4
- package/dist/concurrency-DqAlKX4j.d.ts +50 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/{create-slam-app-store-qNXQ-tq5.d.ts → create-slam-app-store-Ce2FMKCp.d.ts} +5 -5
- package/dist/depth-grid-lookup-vMDrGTk_.d.ts +21 -0
- package/dist/{depth-sampler-Bhanfwla.d.ts → depth-sampler-DwfPYzS7.d.ts} +1 -1
- package/dist/{depth-unprojection-Y6i_mJuT.d.ts → depth-unprojection-CK3b9htH.d.ts} +1 -1
- package/dist/{enable-gps-ar-CapmADVN.d.ts → enable-gps-ar-CjZ1nEdu.d.ts} +4 -4
- package/dist/file-system-utils-Bs7lpeVP.d.ts +40 -0
- package/dist/{frame-conversions-BfsafGWR.d.ts → frame-conversions-D_Af8Dt3.d.ts} +1 -1
- package/dist/{fused-path-DJoVu98b.d.ts → fused-path-C0FHe67t.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/{gps-anchor-dYeCvvp5.d.ts → gps-anchor-C6I9NDHA.d.ts} +1 -1
- package/dist/{gps-ar-pose-sampler-DVb96iOy.d.ts → gps-ar-pose-sampler-DaHg2aPZ.d.ts} +1 -1
- package/dist/{gps-event-coordinator-BDIQde2x.d.ts → gps-event-coordinator-C7hwHyLA.d.ts} +3 -3
- package/dist/{gps-event-markers-Dc9FT-q1.d.ts → gps-event-markers-Dd0G2bD0.d.ts} +18 -0
- package/dist/{h3-proximity-I1msGPgE.d.ts → h3-proximity-B57C6rYL.d.ts} +39 -1
- package/dist/{image-capture-DZ4HFRgS.d.ts → image-capture-C2Feywet.d.ts} +30 -4
- package/dist/index-BvLo2poy.d.ts +399 -0
- package/dist/{index-KJq2amHF.d.ts → index-m_LneUSi.d.ts} +1 -1
- package/dist/index.d.ts +77 -67
- package/dist/index.js +18 -9
- package/dist/{leaflet-map-overlay-6CJC-E1l.d.ts → leaflet-map-overlay-bnb2U1NQ.d.ts} +1 -1
- package/dist/{map-data-CkloBxIl.d.ts → map-data-DdjPiSav.d.ts} +1 -1
- package/dist/{map-overlay-draw-krOKwDAq.d.ts → map-overlay-draw-CPVdAsFq.d.ts} +1 -1
- package/dist/{null-storage-backend-CxEjZHRW.d.ts → null-storage-backend-CuSdYFsM.d.ts} +2 -2
- package/dist/{occupancy-grid-BRNRaPyT.d.ts → occupancy-grid-Bvi2Gw1U.d.ts} +12 -2
- package/dist/{opfs-storage-DYm8WHq7.d.ts → opfs-storage-DMtlt57S.d.ts} +25 -6
- package/dist/{opfs-storage-backend-CjSAuqg_.d.ts → opfs-storage-backend-CeQAb0SD.d.ts} +3 -3
- package/dist/{persistence-middleware-DwyzgfWr.d.ts → persistence-middleware-5eearhSV.d.ts} +1 -1
- package/dist/planar-pnp-cjthe7Be.d.ts +67 -0
- package/dist/qr-debug-view-B8gTzjMm.d.ts +29 -0
- package/dist/qr-derived-pose-DinKE600.d.ts +122 -0
- package/dist/qr-detected-slice-Cpv-dg82.d.ts +166 -0
- package/dist/qr-detection-controller-B-tLVd-S.js +119 -0
- package/dist/qr-detection-controller-Cd2Ih4cC.d.ts +48 -0
- package/dist/qr-frontend-DufZp2xS.d.ts +58 -0
- package/dist/qr-pose-Bt2reSw7.d.ts +138 -0
- package/dist/qr-pose-aggregation-CFlmLm6w.d.ts +90 -0
- package/dist/qr-pose-aggregation-DXR9STHs.js +200 -0
- package/dist/qr-size-depth-context-onu36E9s.d.ts +13 -0
- package/dist/qr-size-measurer-D7739ZpB.d.ts +165 -0
- package/dist/qr-size-measurer-DLb33geH.js +596 -0
- package/dist/recording-options-D93kksJU.d.ts +369 -0
- package/dist/{recording-replayer-BL-x-7k0.d.ts → recording-replayer-CUEK82tM.d.ts} +2 -2
- package/dist/recording-slice-B-uCgGoh.d.ts +55 -0
- package/dist/{replay-engine-BRqszuQ9.d.ts → replay-engine-BSVd7Zzb.d.ts} +7 -4
- package/dist/{replay-scene-Bw58VL6p.d.ts → replay-scene-CPt56pps.d.ts} +3 -3
- package/dist/sensors/gps-error-handler.d.ts +1 -1
- package/dist/sensors/gps.d.ts +1 -1
- package/dist/sensors/index.d.ts +3 -3
- 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/app-selectors.js +3 -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/gps-ar-pose-sampler.d.ts +3 -3
- package/dist/state/gps-event-coordinator.d.ts +1 -1
- package/dist/state/index.d.ts +20 -17
- package/dist/state/index.js +3 -2
- package/dist/state/persistence-middleware.d.ts +1 -1
- 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 +128 -4
- package/dist/state/recording-replayer.d.ts +2 -2
- 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 +1 -1
- package/dist/state/tracking-slice.d.ts +1 -1
- package/dist/{state-BkIfpiyA.js → state-IEw0ZsBu.js} +1 -0
- package/dist/storage/file-system-utils.d.ts +2 -2
- package/dist/storage/file-system-utils.js +17 -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 +9 -8
- 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 +38 -60
- package/dist/storage/zip-reader.d.ts +1 -1
- package/dist/storage/zip-reader.js +1 -1
- package/dist/{storage-backend-C9HLFtgz.d.ts → storage-backend-CTJHNdMp.d.ts} +1 -1
- package/dist/{store-subscribers-rrkLkFTt.d.ts → store-subscribers-BZP81hjh.d.ts} +2 -2
- package/dist/{subscribe-to-selector-CP3BKSbF.d.ts → subscribe-to-selector-Bp3sLx8L.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 +8 -3
- package/dist/{tracking-slice-CdK8c-8P.d.ts → tracking-slice-DJZ9qeGg.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 +6 -6
- 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-QhcSSub5.js → webxr-session-CJF9Nijp.js} +234 -9
- package/dist/{webxr-session-DtAG9srK.d.ts → webxr-session-COsOjJwn.d.ts} +76 -8
- package/dist/zip-coverage-embed-tFohI2J4.d.ts +41 -0
- package/dist/{zip-export-D_5HAvFz.d.ts → zip-export-DDWXInTR.d.ts} +28 -24
- package/package.json +2 -2
- package/dist/concurrency-CO1FQvs3.d.ts +0 -24
- package/dist/file-system-C_eqiEA8.d.ts +0 -114
- package/dist/file-system-utils-DyAmcuvX.d.ts +0 -24
- 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-Pi2YeCrM.d.ts} +0 -0
- /package/dist/{alignment-lerper-Dm9G7JJO.d.ts → alignment-lerper-BUTsQ2yu.d.ts} +0 -0
- /package/dist/{ar-types-Dan1MVTi.d.ts → ar-types-BckI1E-O.d.ts} +0 -0
- /package/dist/{bresenham3d-CFfd7n6B.d.ts → bresenham3d-ByjcmEgM.d.ts} +0 -0
- /package/dist/{camera-follower-C2mwEn7c.d.ts → camera-follower-BTb2Yp12.d.ts} +0 -0
- /package/dist/{capability-checker-CQXJHxXg.d.ts → capability-checker-CXqoJYtb.d.ts} +0 -0
- /package/dist/{capture-failure-tracker-CUeU3L_M.d.ts → capture-failure-tracker-BL5SoG2a.d.ts} +0 -0
- /package/dist/{chromium-camera-access-workaround-Jqbn0apH.d.ts → chromium-camera-access-workaround-BNK189P0.d.ts} +0 -0
- /package/dist/{css3d-renderer-manager-ptu_t3B3.d.ts → css3d-renderer-manager-m-ab19vp.d.ts} +0 -0
- /package/dist/{failure-tracker-Cn8Fo9D4.d.ts → failure-tracker-Cnc_xWUw.d.ts} +0 -0
- /package/dist/{format-file-size-CLCZz3lC.d.ts → format-file-size-BFUWvir1.d.ts} +0 -0
- /package/dist/{frame-loop-CMewS8cY.d.ts → frame-loop-BEO9_q3k.d.ts} +0 -0
- /package/dist/{frustum-visibility-CmI96ESS.d.ts → frustum-visibility-D_RGmH98.d.ts} +0 -0
- /package/dist/{geo-types-SaPnIEUM.d.ts → geo-types-DqiBzPO9.d.ts} +0 -0
- /package/dist/{gps-VuezJWK3.d.ts → gps-C5inWeNV.d.ts} +0 -0
- /package/dist/{gps-compass-cubes-BM1uGiYV.d.ts → gps-compass-cubes-BExxrWJ-.d.ts} +0 -0
- /package/dist/{gps-error-handler-CJl_2MVm.d.ts → gps-error-handler-C2bS69oJ.d.ts} +0 -0
- /package/dist/{hit-test-reticle-BEzEybxZ.d.ts → hit-test-reticle-Dqak9hiR.d.ts} +0 -0
- /package/dist/{index-CF05QV2m.d.ts → index-eaI1Z1KR.d.ts} +0 -0
- /package/dist/{lerp-utils-BpV-0Dtk.d.ts → lerp-utils-kLxMJ2Xj.d.ts} +0 -0
- /package/dist/{list-formatter-RRcBe8Vg.d.ts → list-formatter-ct8txwvR.d.ts} +0 -0
- /package/dist/{logger-BEHXsnnq.d.ts → logger-CzQrCe6C.d.ts} +0 -0
- /package/dist/{map-overlay-bcogY5fK.d.ts → map-overlay-Dci4yvm3.d.ts} +0 -0
- /package/dist/{permission-checker-GpAiSCwd.d.ts → permission-checker-CtPtPQRq.d.ts} +0 -0
- /package/dist/{scene-node-names-Bu0S66H1.d.ts → scene-node-names-CdNeQjOE.d.ts} +0 -0
- /package/dist/{session-disposers-M-oashRH.js → session-disposers-D8wnO-uW.js} +0 -0
- /package/dist/{three-dispose-D950t71B.d.ts → three-dispose-BCZkSJp2.d.ts} +0 -0
- /package/dist/{vis-colors-D6omC64K.d.ts → vis-colors-BZsSLI4i.d.ts} +0 -0
- /package/dist/{webxr-nue-basis-CqKQmZzT.d.ts → webxr-nue-basis-CW1wsbm6.d.ts} +0 -0
- /package/dist/{xr-camera-texture-DBwCaKTd.d.ts → xr-camera-texture-BmrP7iNZ.d.ts} +0 -0
- /package/dist/{xr-error-handler-C0AQRmxs.d.ts → xr-error-handler-BEjRgZjt.d.ts} +0 -0
- /package/dist/{xr-frame-loop-BtYytZJg.d.ts → xr-frame-loop-K8ebdjLw.d.ts} +0 -0
- /package/dist/{zip-reader-DSEXt7K6.d.ts → zip-reader-Q2ue6zlC.d.ts} +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { i as Pose } from "./qr-pose-Bt2reSw7.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,200 @@
|
|
|
1
|
+
import { quat } from "gl-matrix";
|
|
2
|
+
//#region ../src/ar/qr-pose-aggregation.ts
|
|
3
|
+
/**
|
|
4
|
+
* QR pose aggregation — sliding-window pose stabilization
|
|
5
|
+
* (docs `2026-06-16-followup-qr-pose-stabilization-sliding-window.md`).
|
|
6
|
+
*
|
|
7
|
+
* Pure, device-free, store-free robust aggregation of a STATIC fiducial's pose
|
|
8
|
+
* across a short sliding window of detections. A QR is static in the world, so
|
|
9
|
+
* the last N world poses should be (nearly) identical — disagreement is noise,
|
|
10
|
+
* which robust aggregation removes. The aggregated pose is what drives the
|
|
11
|
+
* high-weight GPS vote and the debug overlay, instead of a raw single-frame pose
|
|
12
|
+
* whose ROTATION can swing (the on-device symptom this targets).
|
|
13
|
+
*
|
|
14
|
+
* Three layers, smallest first:
|
|
15
|
+
* - {@link averageRotation} — Option R1: angle-thresholded inlier set + hemisphere-
|
|
16
|
+
* aligned quaternion average. The real new work; translation already had
|
|
17
|
+
* {@link medianQrPosition} but rotation had no counterpart. Quaternion double
|
|
18
|
+
* cover (`q` ≡ `−q`) is handled explicitly — the single most common bug here.
|
|
19
|
+
* - {@link aggregateQrPose} — per-axis median position + R1 rotation, reporting
|
|
20
|
+
* the spreads + inlier count the stability gate needs.
|
|
21
|
+
* - {@link evaluateQrPoseStability} — the `unknown → measuring → stable` lifecycle
|
|
22
|
+
* over the last `window` poses, mirroring the size lifecycle. The pose is only
|
|
23
|
+
* exposed (for the vote / overlay) once `stable`.
|
|
24
|
+
*
|
|
25
|
+
* Operates on plain `Pose` lists (no slice import) so BOTH the framework
|
|
26
|
+
* controller and the demo controller can reuse it, and it is unit-testable
|
|
27
|
+
* without a store. The slice selectors `selectStableQrPose` /
|
|
28
|
+
* `selectQrPoseStability` wrap these over a marker's ring buffer.
|
|
29
|
+
*
|
|
30
|
+
* INVARIANT: the caller must feed RAW per-detection world poses here. Never feed
|
|
31
|
+
* a previously-aggregated pose back in — the window would average its own output
|
|
32
|
+
* and collapse toward whatever it first locked onto (a feedback loop that defeats
|
|
33
|
+
* outlier rejection). The `qrDetected` ring buffer stores raw poses for exactly
|
|
34
|
+
* this reason.
|
|
35
|
+
*
|
|
36
|
+
* @see ./qr-pose.ts — the `Pose` type and the (raw) pose solve this aggregates.
|
|
37
|
+
* @see ../state/qr-detected-slice.ts — `selectStableQrPose` / `selectQrPoseStability`.
|
|
38
|
+
*/
|
|
39
|
+
const RAD2DEG = 180 / Math.PI;
|
|
40
|
+
const DEG2RAD = Math.PI / 180;
|
|
41
|
+
/** Default inlier half-angle (deg) — start ~12°, confirm on device. */
|
|
42
|
+
const DEFAULT_ROTATION_INLIER_ANGLE_DEG = 12;
|
|
43
|
+
function toGl(q) {
|
|
44
|
+
const g = quat.fromValues(q[0], q[1], q[2], q[3]);
|
|
45
|
+
return quat.normalize(g, g);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Geodesic (shortest-arc) angle in radians between two unit quaternions.
|
|
49
|
+
* Double-cover safe: uses `2·dot² − 1 = cos θ`, which is invariant to the sign
|
|
50
|
+
* of either quaternion (`q` ≡ `−q`), and clamps for float error before `acos`.
|
|
51
|
+
*/
|
|
52
|
+
function geodesicAngleRad(a, b) {
|
|
53
|
+
const d = quat.dot(a, b);
|
|
54
|
+
const c = Math.min(1, Math.max(-1, 2 * d * d - 1));
|
|
55
|
+
return Math.acos(c);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Average the samples within `thresholdRad` of `ref`. Each inlier is flipped
|
|
59
|
+
* into `ref`'s hemisphere (sign of the dot) BEFORE the linear average so the
|
|
60
|
+
* double cover cannot cancel the sum; the normalized linear average equals the
|
|
61
|
+
* Markley eigenvector mean for a tight cluster and needs no eigensolver.
|
|
62
|
+
* Returns `null` when no sample is within threshold.
|
|
63
|
+
*/
|
|
64
|
+
function meanOfInliers(samples, ref, thresholdRad) {
|
|
65
|
+
const inliers = [];
|
|
66
|
+
for (const s of samples) if (geodesicAngleRad(s, ref) <= thresholdRad) inliers.push(s);
|
|
67
|
+
if (inliers.length === 0) return null;
|
|
68
|
+
const acc = quat.fromValues(0, 0, 0, 0);
|
|
69
|
+
for (const s of inliers) {
|
|
70
|
+
const sign = quat.dot(s, ref) < 0 ? -1 : 1;
|
|
71
|
+
acc[0] += sign * s[0];
|
|
72
|
+
acc[1] += sign * s[1];
|
|
73
|
+
acc[2] += sign * s[2];
|
|
74
|
+
acc[3] += sign * s[3];
|
|
75
|
+
}
|
|
76
|
+
const mean = quat.normalize(quat.create(), acc);
|
|
77
|
+
let maxAngleRad = 0;
|
|
78
|
+
for (const s of inliers) maxAngleRad = Math.max(maxAngleRad, geodesicAngleRad(s, mean));
|
|
79
|
+
return {
|
|
80
|
+
mean,
|
|
81
|
+
inlierCount: inliers.length,
|
|
82
|
+
maxAngleRad
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Pick the densest sample as the reference: the one with the most OTHER samples
|
|
87
|
+
* within the inlier threshold. For a single rigid pose, consensus-based outlier
|
|
88
|
+
* rejection degenerates to this mode-finding, and it is robust to the latest
|
|
89
|
+
* sample being an outlier.
|
|
90
|
+
*/
|
|
91
|
+
function bestReference(samples, thresholdRad) {
|
|
92
|
+
let best = samples[0];
|
|
93
|
+
let bestCount = -1;
|
|
94
|
+
for (const cand of samples) {
|
|
95
|
+
let count = 0;
|
|
96
|
+
for (const s of samples) if (geodesicAngleRad(s, cand) <= thresholdRad) count++;
|
|
97
|
+
if (count > bestCount) {
|
|
98
|
+
bestCount = count;
|
|
99
|
+
best = cand;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return best;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Robust mean of a set of rotations (Option R1). Picks a reference (mode-finding,
|
|
106
|
+
* or `options.reference`), rejects samples beyond the angular threshold, averages
|
|
107
|
+
* the inliers, then does ONE refit pass against the provisional mean. Returns
|
|
108
|
+
* `null` for an empty input.
|
|
109
|
+
*/
|
|
110
|
+
function averageRotation(quats, options = {}) {
|
|
111
|
+
if (quats.length === 0) return null;
|
|
112
|
+
const thresholdRad = (options.inlierAngleDeg ?? 12) * DEG2RAD;
|
|
113
|
+
const samples = quats.map(toGl);
|
|
114
|
+
const provisional = meanOfInliers(samples, options.reference ? toGl(options.reference) : bestReference(samples, thresholdRad), thresholdRad);
|
|
115
|
+
if (!provisional) return null;
|
|
116
|
+
const refined = meanOfInliers(samples, provisional.mean, thresholdRad) ?? provisional;
|
|
117
|
+
const m = refined.mean;
|
|
118
|
+
const s = m[3] < 0 ? -1 : 1;
|
|
119
|
+
return {
|
|
120
|
+
quat: [
|
|
121
|
+
s * m[0],
|
|
122
|
+
s * m[1],
|
|
123
|
+
s * m[2],
|
|
124
|
+
s * m[3]
|
|
125
|
+
],
|
|
126
|
+
inlierCount: refined.inlierCount,
|
|
127
|
+
maxAngleDeg: refined.maxAngleRad * RAD2DEG
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Per-axis median of a numeric list (lower-middle for even n, matching
|
|
131
|
+
* `medianQrPosition` in the slice so the two agree on the same data). */
|
|
132
|
+
function medianOf(values) {
|
|
133
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
134
|
+
return sorted[Math.floor((sorted.length - 1) / 2)];
|
|
135
|
+
}
|
|
136
|
+
function medianPosition(positions) {
|
|
137
|
+
return [
|
|
138
|
+
medianOf(positions.map((p) => p[0])),
|
|
139
|
+
medianOf(positions.map((p) => p[1])),
|
|
140
|
+
medianOf(positions.map((p) => p[2]))
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
/** Max absolute per-axis deviation from `center` across all positions. */
|
|
144
|
+
function translationSpread(positions, center) {
|
|
145
|
+
let max = 0;
|
|
146
|
+
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]));
|
|
147
|
+
return max;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Aggregate a window of raw poses into one filtered pose + the spreads the
|
|
151
|
+
* stability gate consumes. Returns `null` for an empty window.
|
|
152
|
+
*/
|
|
153
|
+
function aggregateQrPose(poses, options = {}) {
|
|
154
|
+
if (poses.length === 0) return null;
|
|
155
|
+
const positions = poses.map((p) => p.position);
|
|
156
|
+
const position = medianPosition(positions);
|
|
157
|
+
const rotation = averageRotation(poses.map((p) => p.rotation), options);
|
|
158
|
+
if (!rotation) return null;
|
|
159
|
+
return {
|
|
160
|
+
pose: {
|
|
161
|
+
position,
|
|
162
|
+
rotation: rotation.quat
|
|
163
|
+
},
|
|
164
|
+
translationSpreadM: translationSpread(positions, position),
|
|
165
|
+
rotationSpreadDeg: rotation.maxAngleDeg,
|
|
166
|
+
inlierCount: rotation.inlierCount
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const UNKNOWN_STABILITY = {
|
|
170
|
+
status: "unknown",
|
|
171
|
+
pose: null,
|
|
172
|
+
translationSpreadM: 0,
|
|
173
|
+
rotationSpreadDeg: 0,
|
|
174
|
+
inlierCount: 0,
|
|
175
|
+
sampleCount: 0
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Evaluate the pose-stability lifecycle over the last `window` of `poses`
|
|
179
|
+
* (oldest→newest). The returned `pose` is the filtered aggregate whenever there
|
|
180
|
+
* is data, but it is only the gate's responsibility (`status === 'stable'`) to
|
|
181
|
+
* decide when a consumer may TRUST it for the vote — `selectStableQrPose` reads
|
|
182
|
+
* the pose only in the `stable` state.
|
|
183
|
+
*/
|
|
184
|
+
function evaluateQrPoseStability(poses, options = {}) {
|
|
185
|
+
const { window = 8, minObservations = 5, maxTranslationSpreadM = .03, maxRotationSpreadDeg = 5 } = options;
|
|
186
|
+
if (poses.length === 0) return { ...UNKNOWN_STABILITY };
|
|
187
|
+
const windowPoses = poses.length > window ? poses.slice(poses.length - window) : poses;
|
|
188
|
+
const agg = aggregateQrPose(windowPoses, options);
|
|
189
|
+
if (!agg) return { ...UNKNOWN_STABILITY };
|
|
190
|
+
return {
|
|
191
|
+
status: windowPoses.length >= minObservations && agg.translationSpreadM <= maxTranslationSpreadM && agg.rotationSpreadDeg <= maxRotationSpreadDeg ? "stable" : "measuring",
|
|
192
|
+
pose: agg.pose,
|
|
193
|
+
translationSpreadM: agg.translationSpreadM,
|
|
194
|
+
rotationSpreadDeg: agg.rotationSpreadDeg,
|
|
195
|
+
inlierCount: agg.inlierCount,
|
|
196
|
+
sampleCount: windowPoses.length
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
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-BckI1E-O.js";
|
|
2
|
+
import { n as QrSizeDepthContext } from "./qr-size-measurer-D7739ZpB.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 };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { r as DepthPoint } from "./ar-types-BckI1E-O.js";
|
|
2
|
+
import { t as DepthUnprojector } from "./depth-unprojection-CK3b9htH.js";
|
|
3
|
+
import { r as Point2 } from "./qr-pose-Bt2reSw7.js";
|
|
4
|
+
import { Vector3 } from "gps-plus-slam-js";
|
|
5
|
+
|
|
6
|
+
//#region ../src/ar/qr-size-from-depth.d.ts
|
|
7
|
+
/** Where the size lifecycle currently sits for one marker (Note 3 / Note 4). */
|
|
8
|
+
type QrSizeStatus = /** No size authored and none measured yet — size-dependent features blocked. */'unknown' /** Measurements are accumulating but the estimate has not converged. */ | 'measuring' /** A reliably-estimated (or authored) size — size-dependent features unlock. */ | 'estimated';
|
|
9
|
+
/** Per-marker physical-size estimate (drives the Note 3 size lifecycle). */
|
|
10
|
+
interface QrSizeEstimate {
|
|
11
|
+
status: QrSizeStatus;
|
|
12
|
+
/** Running median side length, meters, or `null` while unknown. */
|
|
13
|
+
estimateM: number | null;
|
|
14
|
+
/** How many accepted samples back the estimate. */
|
|
15
|
+
sampleCount: number;
|
|
16
|
+
/**
|
|
17
|
+
* Robust confidence half-width of the estimate, meters (`1.4826·MAD/√N`), so
|
|
18
|
+
* it TIGHTENS as samples accumulate (WS-B) — not the raw max−min. 0 when <2
|
|
19
|
+
* samples. The HUD renders this as the shrinking `±mm`.
|
|
20
|
+
*/
|
|
21
|
+
spreadM: number;
|
|
22
|
+
}
|
|
23
|
+
/** One per-observation size read from a single detection's depth samples. */
|
|
24
|
+
interface QrSizeObservation {
|
|
25
|
+
/** Median of the 4 unprojected edge lengths, meters. */
|
|
26
|
+
sizeM: number;
|
|
27
|
+
/** Consistency score in [0,1]; 1 = a perfect planar square facing the camera. */
|
|
28
|
+
quality: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Estimate a QR's physical side length from one detection's depth samples.
|
|
32
|
+
*
|
|
33
|
+
* @param corners - the 4 corner depth samples, ordered TL, TR, BR, BL (matching
|
|
34
|
+
* `buildObjectPoints` / the detector corner-order normalization).
|
|
35
|
+
* @param interiorSamples - a few interior depth samples (may be empty); used
|
|
36
|
+
* only to strengthen the planarity check against corner edge depth-bleed.
|
|
37
|
+
* @param unprojector - built once per depth sample via `createDepthUnprojector`.
|
|
38
|
+
* @returns `{ sizeM, quality }`, or `null` when a corner cannot be unprojected
|
|
39
|
+
* or the quad is degenerate (collinear / zero-area).
|
|
40
|
+
*/
|
|
41
|
+
declare function estimateQrSizeFromDepth(corners: readonly [DepthPoint, DepthPoint, DepthPoint, DepthPoint], interiorSamples: readonly DepthPoint[], unprojector: DepthUnprojector): QrSizeObservation | null;
|
|
42
|
+
interface QrSizeAccumulatorOptions {
|
|
43
|
+
/** Minimum observation quality to ACCEPT a sample. Default 0.8. */
|
|
44
|
+
qualityThreshold?: number;
|
|
45
|
+
/** Accepted samples required before the estimate can be `estimated`. Default 8. */
|
|
46
|
+
minSamples?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Max confidence half-width (`spreadM`, m) allowed for the `estimated` status.
|
|
49
|
+
* Default 0.01. `spreadM` is a robust standard-error (`1.4826·MAD/√N`) that
|
|
50
|
+
* TIGHTENS as samples accumulate — not the raw max−min — so it converges
|
|
51
|
+
* instead of being pinned by one early stray.
|
|
52
|
+
*/
|
|
53
|
+
maxSpreadM?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Optional cap on retained accepted sizes. **Default: unbounded** (lifelong
|
|
56
|
+
* refinement — WS-B). The QR's physical size never changes, so keeping the
|
|
57
|
+
* full session history lets the robust median tighten the longer the QR is
|
|
58
|
+
* seen. Set a finite cap only if memory/perf demands a bounded window; the
|
|
59
|
+
* estimate then becomes a sliding-window median again.
|
|
60
|
+
*/
|
|
61
|
+
maxSamples?: number;
|
|
62
|
+
}
|
|
63
|
+
interface QrSizeAccumulator {
|
|
64
|
+
/**
|
|
65
|
+
* Offer one observation (or `null` for a failed read). Low-quality / null
|
|
66
|
+
* observations are ignored. Returns the updated {@link QrSizeEstimate}.
|
|
67
|
+
*/
|
|
68
|
+
add(observation: QrSizeObservation | null): QrSizeEstimate;
|
|
69
|
+
/** The current estimate without adding a sample. */
|
|
70
|
+
current(): QrSizeEstimate;
|
|
71
|
+
/** Drop all samples back to `unknown`. */
|
|
72
|
+
reset(): void;
|
|
73
|
+
}
|
|
74
|
+
declare function createQrSizeAccumulator(options?: QrSizeAccumulatorOptions): QrSizeAccumulator;
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region ../src/ar/qr-size-measurer.d.ts
|
|
77
|
+
/**
|
|
78
|
+
* Robustness knobs for the depth-at-corners stage (on top of the accumulator
|
|
79
|
+
* options). QR corners sit on a high-contrast print boundary where the coarse
|
|
80
|
+
* WebXR depth grid often has no near reading; these let a measurement still
|
|
81
|
+
* succeed instead of returning `null` every frame.
|
|
82
|
+
*/
|
|
83
|
+
interface QrSizeMeasurerOptions extends QrSizeAccumulatorOptions {
|
|
84
|
+
/**
|
|
85
|
+
* Points-per-side of the interior depth lattice sampled across the QR quad for
|
|
86
|
+
* the PRIMARY dense plane-fit estimate (WS-A). `latticeSize × latticeSize`
|
|
87
|
+
* points are sampled strictly inside the quad; reads with no depth are
|
|
88
|
+
* skipped. Default `7` (≤49 reads). Set to a small value (or rely on the
|
|
89
|
+
* corner fallback) to disable dense sampling. See {@link estimateQrSizeFromDepthDense}.
|
|
90
|
+
*/
|
|
91
|
+
latticeSize?: number;
|
|
92
|
+
/**
|
|
93
|
+
* When a corner pixel has no depth, retry at points inset toward the centroid
|
|
94
|
+
* by these fractions (in order) and borrow the first valid depth — keeping the
|
|
95
|
+
* TRUE corner screen position so the measured size is not shrunk. Default
|
|
96
|
+
* `[0.12, 0.25]`. Set to `[]` to disable inset fallback. Used by the corner-
|
|
97
|
+
* based FALLBACK estimate (when the interior lattice is too sparse for a fit).
|
|
98
|
+
*/
|
|
99
|
+
cornerInsetFractions?: number[];
|
|
100
|
+
/**
|
|
101
|
+
* Max corners whose depth may be reconstructed by a planar fit through the
|
|
102
|
+
* other three when still missing after the inset fallback. Default `1`
|
|
103
|
+
* (tolerate one un-sampleable corner); `0` disables reconstruction. Used by
|
|
104
|
+
* the corner-based FALLBACK estimate.
|
|
105
|
+
*/
|
|
106
|
+
maxReconstructedCorners?: number;
|
|
107
|
+
}
|
|
108
|
+
/** The per-frame depth access the measurer needs (a subset of the demo's `DepthContext`). */
|
|
109
|
+
interface QrSizeDepthContext {
|
|
110
|
+
/** Depth (m) at a normalized screen point, or `null` if unavailable there. */
|
|
111
|
+
depthAt: (screenX: number, screenY: number) => number | null;
|
|
112
|
+
/** Unprojector for the current depth sample (`createDepthUnprojector`). */
|
|
113
|
+
unprojector: DepthUnprojector;
|
|
114
|
+
}
|
|
115
|
+
/** Minimal frame dimensions needed to normalize pixel corners to screen coords. */
|
|
116
|
+
interface ImageSize {
|
|
117
|
+
width: number;
|
|
118
|
+
height: number;
|
|
119
|
+
}
|
|
120
|
+
/** One measurement: the accumulated estimate plus the raw depth samples used. */
|
|
121
|
+
interface QrSizeMeasurement {
|
|
122
|
+
/** The per-marker running estimate AFTER folding in this observation. */
|
|
123
|
+
estimate: QrSizeEstimate;
|
|
124
|
+
/**
|
|
125
|
+
* The 4 corner depth samples (detector order, normalized screen coords),
|
|
126
|
+
* best-effort. `null` when corner depth could not be sampled (e.g. a small QR
|
|
127
|
+
* whose corners fall between depth nodes) but the dense interior fit still
|
|
128
|
+
* produced an estimate — the dense path does not need corner depths.
|
|
129
|
+
*/
|
|
130
|
+
cornerSamples: [DepthPoint, DepthPoint, DepthPoint, DepthPoint] | null;
|
|
131
|
+
/**
|
|
132
|
+
* The interior depth reads used for the estimate: the dense lattice on the
|
|
133
|
+
* primary path, or the single centroid sample on the corner-based fallback
|
|
134
|
+
* (may be empty).
|
|
135
|
+
*/
|
|
136
|
+
interiorSamples: DepthPoint[];
|
|
137
|
+
}
|
|
138
|
+
interface QrSizeMeasurer {
|
|
139
|
+
/**
|
|
140
|
+
* Measure one detection's size from depth and fold it into the per-`text`
|
|
141
|
+
* accumulator. The PRIMARY path is the dense plane fit (WS-A): sample an
|
|
142
|
+
* interior lattice, fit the QR plane, recover the corners by ray-plane
|
|
143
|
+
* intersection. When the lattice is too sparse for a fit it FALLS BACK to the
|
|
144
|
+
* corner-based estimate (corner depths + inset/reconstruct robustness).
|
|
145
|
+
*
|
|
146
|
+
* Returns `null` only when neither path can run — `corners.length !== 4`, or
|
|
147
|
+
* the interior lattice yields too few reads AND the corner depths cannot be
|
|
148
|
+
* sampled (≥2 corners missing). A degenerate quad does not throw: the (null)
|
|
149
|
+
* observation is simply not accumulated and the prior estimate stands.
|
|
150
|
+
*/
|
|
151
|
+
measure(text: string, corners: readonly Point2[], image: ImageSize, ctx: QrSizeDepthContext): QrSizeMeasurement | null;
|
|
152
|
+
/** Current estimate for a marker without adding a sample. */
|
|
153
|
+
current(text: string): QrSizeEstimate;
|
|
154
|
+
/** Drop one marker's samples (`text` given) or all of them. */
|
|
155
|
+
reset(text?: string): void;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create a size measurer. Accumulator `options` (quality threshold, min samples,
|
|
159
|
+
* spread, cap) are forwarded to every per-marker {@link createQrSizeAccumulator};
|
|
160
|
+
* the depth-at-corners knobs ({@link QrSizeMeasurerOptions.cornerInsetFractions},
|
|
161
|
+
* {@link QrSizeMeasurerOptions.maxReconstructedCorners}) tune corner sampling.
|
|
162
|
+
*/
|
|
163
|
+
declare function createQrSizeMeasurer(options?: QrSizeMeasurerOptions): QrSizeMeasurer;
|
|
164
|
+
//#endregion
|
|
165
|
+
export { QrSizeMeasurerOptions as a, QrSizeAccumulatorOptions as c, QrSizeStatus as d, createQrSizeAccumulator as f, QrSizeMeasurer as i, QrSizeEstimate as l, QrSizeDepthContext as n, createQrSizeMeasurer as o, estimateQrSizeFromDepth as p, QrSizeMeasurement as r, QrSizeAccumulator as s, ImageSize as t, QrSizeObservation as u };
|