react-native-image-stitcher 0.3.0 → 0.4.1
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/CHANGELOG.md +220 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +137 -124
- package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +212 -119
- package/dist/camera/Camera.js +70 -58
- package/dist/camera/PanoramaSettings.d.ts +478 -0
- package/dist/camera/PanoramaSettings.js +120 -0
- package/dist/camera/PanoramaSettingsBridge.d.ts +84 -0
- package/dist/camera/PanoramaSettingsBridge.js +208 -0
- package/dist/camera/PanoramaSettingsModal.d.ts +50 -299
- package/dist/camera/PanoramaSettingsModal.js +189 -354
- package/dist/camera/buildPanoramaInitialSettings.d.ts +70 -0
- package/dist/camera/buildPanoramaInitialSettings.js +97 -0
- package/dist/camera/lowMemDevice.d.ts +24 -0
- package/dist/camera/lowMemDevice.js +69 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +23 -2
- package/package.json +6 -2
- package/src/camera/Camera.tsx +79 -71
- package/src/camera/PanoramaSettings.ts +605 -0
- package/src/camera/PanoramaSettingsBridge.ts +238 -0
- package/src/camera/PanoramaSettingsModal.tsx +296 -989
- package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +375 -0
- package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +119 -0
- package/src/camera/__tests__/lowMemDevice.test.ts +52 -0
- package/src/camera/buildPanoramaInitialSettings.ts +139 -0
- package/src/camera/lowMemDevice.ts +71 -0
- package/src/index.ts +42 -3
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* PanoramaSettingsBridge — JS-side adapters that convert the v0.4
|
|
5
|
+
* typed `PanoramaSettings` / `SlitscanSettings` / `HybridSettings`
|
|
6
|
+
* shape into the flat `configOverrides` dictionary the native
|
|
7
|
+
* bridges read.
|
|
8
|
+
*
|
|
9
|
+
* Why this file exists
|
|
10
|
+
* ────────────────────
|
|
11
|
+
*
|
|
12
|
+
* The v0.4 types use hierarchical sub-trees (`stitcher`,
|
|
13
|
+
* `frameSelection.flow`, `painting`, `registration.ncc1d`,
|
|
14
|
+
* `registration.ncc2d.emaSmoothing`, `plane`, …) to give consumers
|
|
15
|
+
* a clean, ergonomic settings surface that mirrors the native
|
|
16
|
+
* engine's domain. But the native bridges (iOS Swift's
|
|
17
|
+
* `applyConfigOverrides`, Android Kotlin's `IncrementalStitcher.start`)
|
|
18
|
+
* read a FLAT dictionary of native-named keys (e.g. `nccSearchRadius1d`,
|
|
19
|
+
* `enable1dNcc`, `ncc2dEmaAlpha`, `flowMaxTranslationCm`).
|
|
20
|
+
*
|
|
21
|
+
* Two semantic gaps to bridge:
|
|
22
|
+
*
|
|
23
|
+
* 1. **Naming.** JS `registration.ncc1d.searchRadius` →
|
|
24
|
+
* native `nccSearchRadius1d`. JS `painting.paintMode` →
|
|
25
|
+
* native `paintMode` (same). Etc.
|
|
26
|
+
*
|
|
27
|
+
* 2. **Presence-as-enable.** The native side reads explicit
|
|
28
|
+
* `enable1dNcc`, `enable2dNcc`, `enableNcc2dEmaSmoothing`,
|
|
29
|
+
* `enableNcc2dPanAxisLock` booleans. JS models these as
|
|
30
|
+
* optional sub-objects (sub-object present ⇒ enabled). This
|
|
31
|
+
* adapter flattens the booleans for the wire.
|
|
32
|
+
*
|
|
33
|
+
* 3. **Skipped engine defaults.** Hybrid engine presets internally
|
|
34
|
+
* clobber most fields (see HybridSettings JSDoc), so we don't
|
|
35
|
+
* send overrides that would be ignored — just the small useful
|
|
36
|
+
* surface.
|
|
37
|
+
*
|
|
38
|
+
* The Camera component calls `panoramaSettingsToNativeConfig` once
|
|
39
|
+
* per capture start to produce the value passed as
|
|
40
|
+
* `incremental.start({ config: … })`. Layer 2 callers building
|
|
41
|
+
* SlitscanSettings or HybridSettings call the matching adapter
|
|
42
|
+
* before reaching `incremental.start()`.
|
|
43
|
+
*/
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.panoramaSettingsToNativeConfig = panoramaSettingsToNativeConfig;
|
|
46
|
+
exports.slitscanSettingsToNativeConfig = slitscanSettingsToNativeConfig;
|
|
47
|
+
exports.hybridSettingsToNativeConfig = hybridSettingsToNativeConfig;
|
|
48
|
+
const PanoramaSettings_1 = require("./PanoramaSettings");
|
|
49
|
+
/**
|
|
50
|
+
* Convert a v0.4 PanoramaSettings tree into the flat dict the
|
|
51
|
+
* batch-keyframe native side reads. Maps every consumed field
|
|
52
|
+
* exactly once and skips fields the engine doesn't reach.
|
|
53
|
+
*
|
|
54
|
+
* Verified against:
|
|
55
|
+
* - iOS `IncrementalStitcher.swift:810-960` (batch path)
|
|
56
|
+
* - Android `IncrementalStitcher.kt:280-430` (batch path)
|
|
57
|
+
*/
|
|
58
|
+
function panoramaSettingsToNativeConfig(s) {
|
|
59
|
+
const cfg = {
|
|
60
|
+
// ── Cross-cutting ────────────────────────────────────────────
|
|
61
|
+
captureSource: s.captureSource,
|
|
62
|
+
// ── BatchStitcherSettings → cv::Stitcher knobs ───────────────
|
|
63
|
+
stitchMode: s.stitcher.stitchMode,
|
|
64
|
+
warperType: s.stitcher.warperType,
|
|
65
|
+
blenderType: s.stitcher.blenderType,
|
|
66
|
+
seamFinderType: s.stitcher.seamFinderType,
|
|
67
|
+
enableMaxInscribedRectCrop: s.stitcher.enableMaxInscribedRectCrop,
|
|
68
|
+
// ── FrameSelectionSettings → KeyframeGate knobs ──────────────
|
|
69
|
+
frameSelectionMode: s.frameSelection.mode,
|
|
70
|
+
keyframeMaxCount: s.frameSelection.maxKeyframes,
|
|
71
|
+
keyframeOverlapThreshold: s.frameSelection.overlapThreshold,
|
|
72
|
+
};
|
|
73
|
+
// Flow strategy knobs — always serialised, regardless of
|
|
74
|
+
// `frameSelection.mode`. Two reasons:
|
|
75
|
+
//
|
|
76
|
+
// 1. Mode-flip-mid-session: hosts can change `mode` without
|
|
77
|
+
// restarting capture; consistent flow serialisation means
|
|
78
|
+
// `'time-based' → 'flow-based'` mid-session doesn't slip
|
|
79
|
+
// back to stale native-side defaults. Native ignores these
|
|
80
|
+
// keys when the active mode doesn't use them.
|
|
81
|
+
//
|
|
82
|
+
// 2. **Native compiled-in defaults disagree with the JS
|
|
83
|
+
// defaults.** Specifically: native sets `flowMaxTranslationCm
|
|
84
|
+
// = 0` and `flowEvalEveryNFrames = 1` when the keys are
|
|
85
|
+
// missing (iOS `IncrementalStitcher.swift:1003-1029`,
|
|
86
|
+
// Android `IncrementalStitcher.kt:419-445`), whereas the JS
|
|
87
|
+
// `DEFAULT_PANORAMA_SETTINGS.frameSelection.flow` values are
|
|
88
|
+
// `50` and `5`. Hosts who write sparse settings literals
|
|
89
|
+
// (omitted `flow` sub-tree, legal per the optional `?`)
|
|
90
|
+
// would silently get IMU translation gate disabled and
|
|
91
|
+
// ~5× CPU on flow evaluation — a v0.3-style behaviour
|
|
92
|
+
// regression on the wire that the type system can't catch.
|
|
93
|
+
// Filling from `DEFAULT_FLOW_GATE_SETTINGS` here closes the
|
|
94
|
+
// gap; the JS defaults become the canonical defaults across
|
|
95
|
+
// both layers.
|
|
96
|
+
//
|
|
97
|
+
// See the F10 Phase 2 review (B1 + N3 + N6) for the full
|
|
98
|
+
// discussion of why this matters.
|
|
99
|
+
const f = s.frameSelection.flow ?? PanoramaSettings_1.DEFAULT_FLOW_GATE_SETTINGS;
|
|
100
|
+
cfg.flowNoveltyPercentile = f.noveltyPercentile;
|
|
101
|
+
cfg.flowEvalEveryNFrames = f.evalEveryNFrames;
|
|
102
|
+
cfg.flowMaxTranslationCm = f.maxTranslationCm;
|
|
103
|
+
cfg.flowMaxCorners = f.maxCorners;
|
|
104
|
+
cfg.flowQualityLevel = f.qualityLevel;
|
|
105
|
+
cfg.flowMinDistance = f.minDistance;
|
|
106
|
+
return cfg;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Convert a v0.4 SlitscanSettings tree into the flat dict the
|
|
110
|
+
* slit-scan / firstwins native engines read. Handles the
|
|
111
|
+
* "presence-as-enable" boolean expansion: a non-undefined
|
|
112
|
+
* `registration.ncc1d` means `enable1dNcc: true` on the wire,
|
|
113
|
+
* with the sub-object's `searchRadius` carried alongside.
|
|
114
|
+
*
|
|
115
|
+
* Verified against:
|
|
116
|
+
* - iOS `IncrementalStitcher.swift:1006-1100` (applyConfigOverrides)
|
|
117
|
+
* - iOS `OpenCVSlitScanStitcher.mm` (all numbered references in
|
|
118
|
+
* the audit ground-truth matrix)
|
|
119
|
+
*/
|
|
120
|
+
function slitscanSettingsToNativeConfig(s) {
|
|
121
|
+
const cfg = {
|
|
122
|
+
captureSource: s.captureSource,
|
|
123
|
+
// The native side reads `engine: 'slitscan-…'` at start time
|
|
124
|
+
// from a separate top-level field, NOT from configOverrides.
|
|
125
|
+
// We still serialise the variant here for hosts that want to
|
|
126
|
+
// round-trip a single settings object through both surfaces.
|
|
127
|
+
engineVariant: s.variant,
|
|
128
|
+
// ── Painting ─────────────────────────────────────────────────
|
|
129
|
+
paintMode: s.painting.paintMode,
|
|
130
|
+
sliverPosition: s.painting.sliverPosition,
|
|
131
|
+
firstFrameFullFrame: s.painting.firstFrameFullFrame,
|
|
132
|
+
// ── Registration (explicit booleans) ─────────────────────────
|
|
133
|
+
enableTriangulation: s.registration.enableTriangulation,
|
|
134
|
+
enableTriAccumulator: s.registration.enableTriAccumulator,
|
|
135
|
+
enableRansacHomography: s.registration.enableRansacHomography,
|
|
136
|
+
// ── Plane projection ─────────────────────────────────────────
|
|
137
|
+
planeSource: s.plane.source,
|
|
138
|
+
};
|
|
139
|
+
// ── 1D NCC: presence-as-enable ─────────────────────────────────
|
|
140
|
+
if (s.registration.ncc1d) {
|
|
141
|
+
cfg.enable1dNcc = true;
|
|
142
|
+
cfg.nccSearchRadius1d = s.registration.ncc1d.searchRadius;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
cfg.enable1dNcc = false;
|
|
146
|
+
}
|
|
147
|
+
// ── 2D NCC: presence-as-enable + nested optionals ──────────────
|
|
148
|
+
if (s.registration.ncc2d) {
|
|
149
|
+
const n2 = s.registration.ncc2d;
|
|
150
|
+
cfg.enable2dNcc = true;
|
|
151
|
+
cfg.nccSearchMargin2d = n2.searchMargin;
|
|
152
|
+
cfg.nccConfidenceThreshold2d = n2.confidenceThreshold;
|
|
153
|
+
if (n2.emaSmoothing) {
|
|
154
|
+
cfg.enableNcc2dEmaSmoothing = true;
|
|
155
|
+
cfg.ncc2dEmaAlpha = n2.emaSmoothing.alpha;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
cfg.enableNcc2dEmaSmoothing = false;
|
|
159
|
+
}
|
|
160
|
+
if (n2.panAxisLock) {
|
|
161
|
+
cfg.enableNcc2dPanAxisLock = true;
|
|
162
|
+
cfg.ncc2dCrossAxisLockPx = n2.panAxisLock.crossAxisLockPx;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
cfg.enableNcc2dPanAxisLock = false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
cfg.enable2dNcc = false;
|
|
170
|
+
}
|
|
171
|
+
// ── Plane optionals ────────────────────────────────────────────
|
|
172
|
+
// Only emit when `source` actually consumes the field. Native
|
|
173
|
+
// tolerates unsolicited keys but the modal also walks the dict
|
|
174
|
+
// to decide which sliders to render — extra keys would mislead.
|
|
175
|
+
if (s.plane.source !== 'Disabled' && s.plane.projectionStyle !== undefined) {
|
|
176
|
+
cfg.planeProjectionStyle = s.plane.projectionStyle;
|
|
177
|
+
}
|
|
178
|
+
if (s.plane.source === 'Virtual' && s.plane.virtualDepthMeters !== undefined) {
|
|
179
|
+
cfg.virtualPlaneDepthMeters = s.plane.virtualDepthMeters;
|
|
180
|
+
}
|
|
181
|
+
if (s.plane.source === 'ARKitDetected' && s.plane.alignmentThreshold !== undefined) {
|
|
182
|
+
cfg.arkitPlaneAlignmentThreshold = s.plane.alignmentThreshold;
|
|
183
|
+
}
|
|
184
|
+
// ── Advanced motion knobs (only emit if explicitly set) ────────
|
|
185
|
+
if (s.advanced?.panAxisFractionRect !== undefined) {
|
|
186
|
+
cfg.kPanAxisFractionRect = s.advanced.panAxisFractionRect;
|
|
187
|
+
}
|
|
188
|
+
if (s.advanced?.minAcceptDeltaPx !== undefined) {
|
|
189
|
+
cfg.kMinAcceptDeltaPx = s.advanced.minAcceptDeltaPx;
|
|
190
|
+
}
|
|
191
|
+
return cfg;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Convert a v0.4 HybridSettings tree into the flat dict the hybrid
|
|
195
|
+
* engine reads. Minimal surface — hybrid presets internally clobber
|
|
196
|
+
* almost everything; see HybridSettings JSDoc for context.
|
|
197
|
+
*
|
|
198
|
+
* Verified against:
|
|
199
|
+
* - iOS `OpenCVIncrementalStitcher.mm:139-180` (preset paths)
|
|
200
|
+
* - iOS `IncrementalStitcher.swift:1034-1040` (hybridProjection override)
|
|
201
|
+
*/
|
|
202
|
+
function hybridSettingsToNativeConfig(s) {
|
|
203
|
+
return {
|
|
204
|
+
captureSource: s.captureSource,
|
|
205
|
+
hybridProjection: s.projection,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=PanoramaSettingsBridge.js.map
|
|
@@ -1,307 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PanoramaSettingsModal — runtime
|
|
3
|
-
*
|
|
4
|
-
* blender, and tuning constants between captures to see what
|
|
5
|
-
* looks best on real shelf scenes.
|
|
2
|
+
* PanoramaSettingsModal — runtime tuning surface for <Camera>'s
|
|
3
|
+
* batch-keyframe panorama capture.
|
|
6
4
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* with `visible` toggled by a gear-icon press in the capture
|
|
10
|
-
* header. Settings flow OUT via `onChange` for each tweak.
|
|
5
|
+
* v0.4 rewrite (Phase 2 of F10):
|
|
6
|
+
* ──────────────────────────────
|
|
11
7
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
8
|
+
* The v0.3 modal exposed a flat 45-field surface that mixed
|
|
9
|
+
* batch-keyframe knobs with slit-scan, hybrid, and video-recording
|
|
10
|
+
* fallback fields the engine never reads in <Camera>'s
|
|
11
|
+
* `engine: 'batch-keyframe'` path. The 2026-05-22 audit (v0.3.0
|
|
12
|
+
* CHANGELOG) traced every field's native consumer and proved most of
|
|
13
|
+
* the cross-engine fields were dead surface in this modal.
|
|
14
|
+
*
|
|
15
|
+
* v0.4 narrows the modal to exactly the surface <Camera> consumes:
|
|
16
|
+
* the `PanoramaSettings` type defined in `./PanoramaSettings.ts`. Each
|
|
17
|
+
* section in the modal mirrors a sub-tree of that type — operators see
|
|
18
|
+
* the same shape in the UI as the code, and host apps that want to
|
|
19
|
+
* tune slit-scan or hybrid engines build their own analogous
|
|
20
|
+
* SlitscanSettingsModal / HybridSettingsModal on top of those types.
|
|
21
|
+
*
|
|
22
|
+
* UI structure (matches the type tree):
|
|
23
|
+
*
|
|
24
|
+
* - Debug (top-level, `debug`)
|
|
25
|
+
* - Frame selection (`frameSelection`, closed by default)
|
|
26
|
+
* - Mode
|
|
27
|
+
* - Max keyframes
|
|
28
|
+
* - Overlap threshold
|
|
29
|
+
* - Flow tunables (`frameSelection.flow`, only when
|
|
30
|
+
* mode === 'flow-based')
|
|
31
|
+
* - Max corners
|
|
32
|
+
* - Quality level
|
|
33
|
+
* - Min distance
|
|
34
|
+
* - Max translation cm
|
|
35
|
+
* - Novelty percentile
|
|
36
|
+
* - Eval every N frames
|
|
37
|
+
* - Stitcher (`stitcher`, closed by default)
|
|
38
|
+
* - Stitch mode
|
|
39
|
+
* - Warper type
|
|
40
|
+
* - Blender
|
|
41
|
+
* - Seam finder
|
|
42
|
+
* - Inscribed-rect crop
|
|
43
|
+
* - Reset to defaults (button)
|
|
44
|
+
*
|
|
45
|
+
* Note: `captureSource` (AR vs non-AR) is NOT surfaced here. The
|
|
46
|
+
* camera-screen AR toggle owns that state — Camera.tsx overrides the
|
|
47
|
+
* native bridge's `captureSource` with the derived
|
|
48
|
+
* `effectiveCaptureSource` so settings and runtime stay in sync.
|
|
49
|
+
*
|
|
50
|
+
* The reusable `Accordion` + `SectionHeader` + `SegmentedControl` +
|
|
51
|
+
* `Tag` helpers from the v0.3 modal are preserved verbatim — only the
|
|
52
|
+
* data-binding layer changed.
|
|
17
53
|
*/
|
|
18
54
|
import React from 'react';
|
|
19
|
-
|
|
20
|
-
warperType: 'plane' | 'cylindrical' | 'spherical';
|
|
21
|
-
blenderType: 'multiband' | 'feather';
|
|
22
|
-
/**
|
|
23
|
-
* Seam finder strategy. "graphcut" finds optimal seams before
|
|
24
|
-
* blending (cleaner output, pairs with multiband, more memory).
|
|
25
|
-
* "skip" streams warp+feed (lower peak memory, fine with feather).
|
|
26
|
-
*/
|
|
27
|
-
seamFinderType: 'graphcut' | 'skip';
|
|
28
|
-
/**
|
|
29
|
-
* V16 Phase 1b.fix5c (Ram's call 2026-05-10) — toggle the
|
|
30
|
-
* max-inscribed-rectangle crop on the batch-keyframe output
|
|
31
|
-
* panorama. When false (default), the output is cropped to the
|
|
32
|
-
* bounding rectangle of non-black pixels only (cv::boundingRect)
|
|
33
|
-
* — preserves all stitched content at the cost of some black
|
|
34
|
-
* corners where cv::Stitcher's projection didn't fill. When
|
|
35
|
-
* true, the post-stitch pipeline additionally runs
|
|
36
|
-
* `MaxInscribedRectFromMask` to find the largest axis-aligned
|
|
37
|
-
* rectangle entirely inside content, followed by the
|
|
38
|
-
* column-projection second-pass. Inscribed-rect can be
|
|
39
|
-
* over-aggressive on lopsided masks (field log showed a
|
|
40
|
-
* 1146×1102 bbox shrinking to a 602×1102 strip), so default OFF
|
|
41
|
-
* lets the operator see the full stitched scene; flip ON to
|
|
42
|
-
* A/B against the cleaner-but-smaller output.
|
|
43
|
-
*/
|
|
44
|
-
enableMaxInscribedRectCrop: boolean;
|
|
45
|
-
/**
|
|
46
|
-
* Phase 4.4 EXPERIMENTAL: when true, the host swaps the
|
|
47
|
-
* vision-camera-backed CameraView for an ARKit-backed ARCameraView
|
|
48
|
-
* during panorama capture. Default false (keeps the existing
|
|
49
|
-
* stitcher flow untouched). Phase 5 will add AR-backed photo /
|
|
50
|
-
* video capture and pose-driven stitching; until then this is
|
|
51
|
-
* preview-only — useful for verifying the AR session renders
|
|
52
|
-
* cleanly on the operator's device before we cut over.
|
|
53
|
-
*/
|
|
54
|
-
useARPreview: boolean;
|
|
55
|
-
/**
|
|
56
|
-
* V15 — Incremental engine choice for live realtime stitching.
|
|
57
|
-
* 'hybrid' — Whole-frame projection + feature matching;
|
|
58
|
-
* planar by default (was cylindrical).
|
|
59
|
-
* 'slitscan-rotate' — V13.0a baseline + 1D NCC for rotation
|
|
60
|
-
* wobble correction.
|
|
61
|
-
* 'slitscan-both' — DEFAULT. V13.0a + no accept gate +
|
|
62
|
-
* feather blend. Iterate via per-stage
|
|
63
|
-
* toggles below.
|
|
64
|
-
*
|
|
65
|
-
* All three are A/B-comparable on the same scene by toggling here
|
|
66
|
-
* without restarting the app.
|
|
67
|
-
*/
|
|
68
|
-
incrementalEngine: 'batch-keyframe' | 'hybrid' | 'slitscan-rotate' | 'slitscan-both';
|
|
69
|
-
/**
|
|
70
|
-
* V15 — Slit-scan slit width (fraction of pan-axis retained per
|
|
71
|
-
* frame). Range 0.10 – 0.70. Smaller = less within-slit multi-
|
|
72
|
-
* depth disagreement but tighter overlap budget at fast pans.
|
|
73
|
-
* Default 0.30. Only applied to slitscan-* engines.
|
|
74
|
-
*/
|
|
75
|
-
slitWidthFraction: number;
|
|
76
|
-
/**
|
|
77
|
-
* V15 — Per-stage correction toggles for slitscan-both. Settings
|
|
78
|
-
* UI exposes these so iteration happens via toggles, not rebuilds.
|
|
79
|
-
*/
|
|
80
|
-
acceptGate: 0 | 50;
|
|
81
|
-
enableTriangulation: boolean;
|
|
82
|
-
enableTriAccumulator: boolean;
|
|
83
|
-
enable2dNcc: boolean;
|
|
84
|
-
enableRansacHomography: boolean;
|
|
85
|
-
paintMode: 'FirstPaintedWins' | 'FeatherBlend';
|
|
86
|
-
hybridProjection: 'Cylindrical' | 'Planar';
|
|
87
|
-
/** 1D NCC search radius (slitscan-rotate only). */
|
|
88
|
-
nccSearchRadius1d: number;
|
|
89
|
-
/** **DEPRECATED in V15.0d** — see `planeSource`. Kept on the type
|
|
90
|
-
* for backward compat with stored settings. When `planeSource`
|
|
91
|
-
* is 'Disabled' (default) and this is true, the engine treats it
|
|
92
|
-
* as 'ARKitDetected'. */
|
|
93
|
-
useDetectedPlane: boolean;
|
|
94
|
-
/** V15.0d — source of the plane used by the V15.0b plane-projected
|
|
95
|
-
* stitch path. Slit-scan modes only.
|
|
96
|
-
*
|
|
97
|
-
* - 'Disabled': no plane projection (plain slit-scan).
|
|
98
|
-
* - 'ARKitDetected': use ARKit's first vertical plane that aligns
|
|
99
|
-
* with the camera's view direction. Falls back to slit-scan
|
|
100
|
-
* silently when no aligned plane is found.
|
|
101
|
-
* - 'Virtual': synthesize a plane perpendicular to the camera at
|
|
102
|
-
* `virtualPlaneDepthMeters` distance. Always works; loses
|
|
103
|
-
* "real depth" advantage but immune to ARKit picking the wrong
|
|
104
|
-
* surface (which is the common failure mode for ARKitDetected). */
|
|
105
|
-
planeSource: 'Disabled' | 'ARKitDetected' | 'Virtual';
|
|
106
|
-
/** V15.0d — depth (m) of the synthetic plane in front of the camera
|
|
107
|
-
* when `planeSource = 'Virtual'`. 0.3 – 5.0 m. Default 1.5 m. */
|
|
108
|
-
virtualPlaneDepthMeters: number;
|
|
109
|
-
/** V15.0d — alignment threshold (cosine) for ARKit-detected planes.
|
|
110
|
-
* Higher = stricter (fewer planes accepted). 0.0 – 1.0.
|
|
111
|
-
* Default 0.6 (≈53° max angle off-camera). */
|
|
112
|
-
arkitPlaneAlignmentThreshold: number;
|
|
113
|
-
/** V15.0g — plane-projection rendering style. Trapezoidal is the
|
|
114
|
-
* V15.0b legacy 3D-correct mapping; Rectified is V15.0g's clean-
|
|
115
|
-
* rectangle paste that eliminates tilt-induced trapezoidal
|
|
116
|
-
* distortion. Default Rectified. Ignored when planeSource =
|
|
117
|
-
* Disabled. */
|
|
118
|
-
planeProjectionStyle: 'Trapezoidal' | 'Rectified';
|
|
119
|
-
/** V15.0d — 2D NCC search half-window in pixels. 4 – 30.
|
|
120
|
-
* Default 12. */
|
|
121
|
-
nccSearchMargin2d: number;
|
|
122
|
-
/** V15.0d — 2D NCC confidence threshold below which corrections
|
|
123
|
-
* are rejected. 0.30 – 0.99. Default 0.75. */
|
|
124
|
-
nccConfidenceThreshold2d: number;
|
|
125
|
-
/** V15.0d (1B) — EMA smoothing on 2D NCC corrections to damp
|
|
126
|
-
* single-frame snaps. Default false. */
|
|
127
|
-
enableNcc2dEmaSmoothing: boolean;
|
|
128
|
-
/** V15.0d — EMA weight on the CURRENT-frame correction. 0.05 – 0.95.
|
|
129
|
-
* Default 0.4 (60% prev / 40% current). */
|
|
130
|
-
ncc2dEmaAlpha: number;
|
|
131
|
-
/** V15.0d (1C) — pan-axis-aware 2D NCC: clamp the cross-axis
|
|
132
|
-
* correction tighter than the pan-axis. Default false. */
|
|
133
|
-
enableNcc2dPanAxisLock: boolean;
|
|
134
|
-
/** V15.0d — cross-axis clamp (px) when pan-axis lock is on.
|
|
135
|
-
* 0 – 30. Default 5. */
|
|
136
|
-
ncc2dCrossAxisLockPx: number;
|
|
137
|
-
/** V16 — frame-selection mode for the live engine.
|
|
138
|
-
*
|
|
139
|
-
* - 'time-based' (default): every ARFrame is forwarded to the
|
|
140
|
-
* engine; the engine's own gate (kMinAcceptDeltaPx etc.) decides.
|
|
141
|
-
* Backward-compatible with all prior versions.
|
|
142
|
-
* - 'pose-based': frames are pre-filtered by a KeyframeGate that
|
|
143
|
-
* projects each onto the latched ARKit plane and accepts only
|
|
144
|
-
* when overlap with the previous keyframe is < 1 −
|
|
145
|
-
* overlapThreshold. Bounded to keyframeMaxCount frames per
|
|
146
|
-
* capture (matches iOS Camera / Samsung Pano architecture).
|
|
147
|
-
* Requires planeSource != 'Disabled' to engage.
|
|
148
|
-
* - 'flow-based' (V16 A2, DEFAULT): same KeyframeGate cap +
|
|
149
|
-
* threshold but the novelty metric is sparse-Lucas-Kanade
|
|
150
|
-
* optical flow on full-frame content instead of plane-projected
|
|
151
|
-
* polygon overlap. Plane-independent (scale-invariant — works
|
|
152
|
-
* regardless of latched plane size); the metric is "median
|
|
153
|
-
* pan-axis feature displacement / pan-axis frame dim", which is
|
|
154
|
-
* a direct measure of % new content on the leading edge. Falls
|
|
155
|
-
* back to angular delta when feature tracking fails (texture-
|
|
156
|
-
* poor scene / motion exceeds KLT pyramid window). */
|
|
157
|
-
frameSelectionMode: 'time-based' | 'pose-based' | 'flow-based';
|
|
158
|
-
/** V16 — required NEW-content fraction for a keyframe to be
|
|
159
|
-
* accepted (pose-based AND flow-based modes share this knob;
|
|
160
|
-
* both interpret 0.40 as "40 % new content"). Tuneable from
|
|
161
|
-
* 0.20 to 0.60 in the modal. */
|
|
162
|
-
keyframeOverlapThreshold: number;
|
|
163
|
-
/** V16 — hard cap on keyframes per capture (pose-based + flow-
|
|
164
|
-
* based modes). Default 6. Once reached, all further frames are
|
|
165
|
-
* rejected and the host should auto-finalize. */
|
|
166
|
-
keyframeMaxCount: number;
|
|
167
|
-
/** V16 A2 — flow-based mode: max Shi-Tomasi corners to detect per
|
|
168
|
-
* accepted keyframe. More = more robust median pan-axis
|
|
169
|
-
* displacement but slower detect (~15-25 ms at 150 on iPhone 13
|
|
170
|
-
* Pro). Range 50 – 300, default 150. */
|
|
171
|
-
flowMaxCorners: number;
|
|
172
|
-
/** V16 A2 — flow-based mode: Shi-Tomasi quality level (0, 1].
|
|
173
|
-
* Lower = more (weaker) corners detected; higher = fewer
|
|
174
|
-
* (stronger) corners. Default 0.01. Range 0.005 – 0.05 in the
|
|
175
|
-
* modal. */
|
|
176
|
-
flowQualityLevel: number;
|
|
177
|
-
/** V16 A2 — flow-based mode: minimum pixel distance between
|
|
178
|
-
* detected corners at WORKING resolution (the gate internally
|
|
179
|
-
* downscales the frame to 720 px longest side for KLT). Higher
|
|
180
|
-
* = more spatially-spread features. Default 10. */
|
|
181
|
-
flowMinDistance: number;
|
|
182
|
-
/** V16 — flow-based mode: translation budget in CENTIMETRES.
|
|
183
|
-
* When > 0, the gate force-accepts a frame if the camera has
|
|
184
|
-
* translated more than this distance (3D Euclidean) since the
|
|
185
|
-
* last accepted keyframe — even when novelty < threshold.
|
|
186
|
-
* Bounds the parallax between adjacent keyframes so the
|
|
187
|
-
* downstream affine stitcher matcher can fit a homography.
|
|
188
|
-
* Range 0 – 100 cm in the modal, default 0 = disabled.
|
|
189
|
-
* Recommended starting value once enabled: 8 cm. */
|
|
190
|
-
flowMaxTranslationCm: number;
|
|
191
|
-
/** V16 — flow-based mode: percentile used to aggregate tracked-
|
|
192
|
-
* feature absolute displacements into the novelty estimate.
|
|
193
|
-
* Pre-V16 used median (0.50); 0.85 picks up leading-edge
|
|
194
|
-
* motion sooner — matches user perception of "new content
|
|
195
|
-
* visible" better. Range 0.50 – 0.99, default 0.85. */
|
|
196
|
-
flowNoveltyPercentile: number;
|
|
197
|
-
/** V16 — flow-based mode: eval-throttle. Gate evaluation runs
|
|
198
|
-
* every Nth consumeFrame from the AR delegate instead of every
|
|
199
|
-
* frame. Pure CPU/battery savings — doesn't change WHICH
|
|
200
|
-
* frames are accepted, just samples less frequently. Range
|
|
201
|
-
* 1 – 10, default 1 (every frame). */
|
|
202
|
-
flowEvalEveryNFrames: number;
|
|
203
|
-
/** V15.0c — sliver position within the camera frame. 'Center' is
|
|
204
|
-
* V13.x default. 'Bottom' takes leading-edge content for top-to-
|
|
205
|
-
* bottom pan; 'Top' for bottom-to-top pan. */
|
|
206
|
-
sliverPosition: 'Center' | 'Bottom' | 'Top';
|
|
207
|
-
/** V15.0c — paint full first frame, then add slivers as user pans.
|
|
208
|
-
* Useful with 'Bottom' or 'Top' sliverPosition. */
|
|
209
|
-
firstFrameFullFrame: boolean;
|
|
210
|
-
/** Hard cap on hold duration (ms). 0 disables auto-stop. */
|
|
211
|
-
maxRecordingMs: number;
|
|
212
|
-
/** Frames per second of recording to sample for stitching. */
|
|
213
|
-
framesPerSecond: number;
|
|
214
|
-
/** Floor / ceiling on extracted frame count. */
|
|
215
|
-
minFrames: number;
|
|
216
|
-
maxFrames: number;
|
|
217
|
-
/** JPEG quality (0-100) for output panorama. */
|
|
218
|
-
quality: number;
|
|
219
|
-
/**
|
|
220
|
-
* 2026-05-14 (revised) — capture-source picker for the panorama
|
|
221
|
-
* camera screen. Two options after the 2026-05-14 user-reported
|
|
222
|
-
* Galaxy A35 crash + simplification request:
|
|
223
|
-
*
|
|
224
|
-
* 'ar' (DEFAULT) — Use the AR stack (ARKit on iOS, ARCore on
|
|
225
|
-
* Android). Plane detection, pose-aware
|
|
226
|
-
* capture, pose-driven gate. Falls back to
|
|
227
|
-
* non-AR silently if the device doesn't
|
|
228
|
-
* support AR.
|
|
229
|
-
* 'non-ar' — Use vision-camera. Disables all AR-based
|
|
230
|
-
* services (planeSource=Disabled, no plane
|
|
231
|
-
* polling, no AR session, frameSelectionMode
|
|
232
|
-
* flipped to flow-based). Lens-switcher chip
|
|
233
|
-
* on the capture screen lets the operator
|
|
234
|
-
* toggle 0.5× / 1× without re-opening Settings.
|
|
235
|
-
* The chip is hidden if the device has only
|
|
236
|
-
* one physical back lens.
|
|
237
|
-
*
|
|
238
|
-
* Cascade: switching from 'ar' → 'non-ar' triggers a useEffect
|
|
239
|
-
* in `AuditCaptureScreen` that patches dependent settings
|
|
240
|
-
* (planeSource, frameSelectionMode, useARPreview) to a coherent
|
|
241
|
-
* non-AR state. Operators don't have to know which other
|
|
242
|
-
* settings to flip.
|
|
243
|
-
*
|
|
244
|
-
* Earlier draft (replaced 2026-05-14) had 4 values:
|
|
245
|
-
* 'auto' | 'ar' | 'wide' | 'ultrawide'. The pre-mount
|
|
246
|
-
* physical-lens selection ('wide' / 'ultrawide') crashed the
|
|
247
|
-
* Galaxy A35 vision-camera CameraCaptureSession with a Parcel
|
|
248
|
-
* exception (physical_camera_id=null in AidlCamera3-Device
|
|
249
|
-
* configureStreams) — Camera2 can't be reliably steered to a
|
|
250
|
-
* specific physical lens via vision-camera's `physicalDevices`
|
|
251
|
-
* filter on this hardware. The post-mount on-screen chip path
|
|
252
|
-
* works because vision-camera selects the safe multi-lens
|
|
253
|
-
* virtual device first, and the lens swap happens against an
|
|
254
|
-
* already-open camera.
|
|
255
|
-
*/
|
|
256
|
-
captureSource: 'ar' | 'non-ar';
|
|
257
|
-
/**
|
|
258
|
-
* 2026-05-16 (Issue 5) — diagnostic toast on every successful
|
|
259
|
-
* finalize. When `true`, the host renders a transient toast
|
|
260
|
-
* summarising the C+D progressive-confidence retry telemetry:
|
|
261
|
-
*
|
|
262
|
-
* "Stitch: 6/6 frames retained at thresh 1.00 (1 attempt)"
|
|
263
|
-
*
|
|
264
|
-
* Defaults to `false` so end-users don't see it. Toggle from the
|
|
265
|
-
* Settings modal under "Debug". Independent from any log-level
|
|
266
|
-
* controls — purely a UI affordance for field testing.
|
|
267
|
-
*/
|
|
268
|
-
debug: boolean;
|
|
269
|
-
/**
|
|
270
|
-
* 2026-05-14 — `cv::Stitcher` pipeline mode for the batch stitch.
|
|
271
|
-
*
|
|
272
|
-
* 'auto' (DEFAULT)
|
|
273
|
-
* The capture engine looks at the accumulated translation vs
|
|
274
|
-
* rotation magnitudes between first and last accepted keyframe
|
|
275
|
-
* poses (AR-mode) or the windowed IMU integration (non-AR
|
|
276
|
-
* mode) and picks PANORAMA or SCANS at finalize time.
|
|
277
|
-
*
|
|
278
|
-
* 'panorama'
|
|
279
|
-
* `cv::Stitcher::PANORAMA` — rotation-only pipeline. Best for
|
|
280
|
-
* "rotate phone in place to capture a wide field of view"
|
|
281
|
-
* captures. ORB feature matching + global BundleAdjusterRay +
|
|
282
|
-
* SphericalWarper. Sharp seams, expensive memory. WARNING:
|
|
283
|
-
* on translation-heavy input the rotation-only homography fit
|
|
284
|
-
* diverges and the canvas can blow up to multi-GB on Android
|
|
285
|
-
* (2026-05-14 lmkd kill observed). Pick this only for genuine
|
|
286
|
-
* rotation panoramas.
|
|
287
|
-
*
|
|
288
|
-
* 'scans'
|
|
289
|
-
* `cv::Stitcher::SCANS` — translational pipeline. Best for
|
|
290
|
-
* "walk past a shelf and pan sideways" captures. Affine
|
|
291
|
-
* matcher + AffineBasedEstimator + BundleAdjusterAffine +
|
|
292
|
-
* PlaneWarper. Canvas size bounded by sum of frame areas.
|
|
293
|
-
* Slight quality drop on pure rotations but works for them too.
|
|
294
|
-
*
|
|
295
|
-
* Both platforms honour this as of 2026-05-22 (audit F2). Android
|
|
296
|
-
* routes through `image_stitcher_jni.cpp` → `cpp/stitcher.cpp`;
|
|
297
|
-
* iOS routes through `OpenCVStitcher.stitchFramePaths(stitchMode:)`
|
|
298
|
-
* → `cpp/stitcher.cpp`. Both 'auto' resolutions use the same
|
|
299
|
-
* translation/rotation ratio heuristic
|
|
300
|
-
* (`resolveStitchModeAuto` on each side).
|
|
301
|
-
*/
|
|
302
|
-
stitchMode: 'auto' | 'panorama' | 'scans';
|
|
303
|
-
}
|
|
304
|
-
export declare const DEFAULT_PANORAMA_SETTINGS: PanoramaSettings;
|
|
55
|
+
import { type PanoramaSettings } from './PanoramaSettings';
|
|
305
56
|
export interface PanoramaSettingsModalProps {
|
|
306
57
|
visible: boolean;
|
|
307
58
|
settings: PanoramaSettings;
|