react-native-image-stitcher 0.14.1 → 0.15.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/CHANGELOG.md +160 -0
- package/README.md +35 -0
- package/RNImageStitcher.podspec +8 -1
- package/android/build.gradle +0 -16
- package/android/src/main/cpp/CMakeLists.txt +2 -63
- package/android/src/main/cpp/image_stitcher_jni.cpp +14 -0
- package/android/src/main/cpp/keyframe_gate_jni.cpp +13 -0
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +285 -3
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +180 -1162
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +29 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +0 -4
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +13 -64
- package/cpp/keyframe_gate.cpp +82 -23
- package/cpp/keyframe_gate.hpp +31 -2
- package/cpp/stitcher.cpp +208 -28
- package/cpp/tests/CMakeLists.txt +18 -12
- package/cpp/tests/keyframe_timebudget_test.cpp +65 -0
- package/cpp/tests/warp_guard_test.cpp +48 -0
- package/cpp/warp_guard.hpp +41 -0
- package/dist/ar/useARSession.d.ts +9 -0
- package/dist/ar/useARSession.js +24 -2
- package/dist/camera/Camera.d.ts +31 -16
- package/dist/camera/Camera.js +27 -4
- package/dist/camera/CaptureStitchStatsToast.d.ts +15 -2
- package/dist/camera/CaptureStitchStatsToast.js +27 -7
- package/dist/camera/PanoramaSettings.d.ts +10 -223
- package/dist/camera/PanoramaSettings.js +6 -28
- package/dist/camera/PanoramaSettingsBridge.d.ts +1 -24
- package/dist/camera/PanoramaSettingsBridge.js +3 -102
- package/dist/camera/PanoramaSettingsModal.js +7 -1
- package/dist/camera/buildPanoramaInitialSettings.d.ts +11 -0
- package/dist/camera/buildPanoramaInitialSettings.js +4 -0
- package/dist/camera/cameraErrorMessages.d.ts +32 -0
- package/dist/camera/cameraErrorMessages.js +53 -0
- package/dist/camera/selectCaptureDevice.d.ts +5 -1
- package/dist/camera/selectCaptureDevice.js +22 -2
- package/dist/camera/useCapture.js +38 -0
- package/dist/index.d.ts +5 -8
- package/dist/index.js +11 -34
- package/dist/stitching/incremental.d.ts +1 -117
- package/dist/stitching/stitchVideo.d.ts +0 -35
- package/dist/types.d.ts +0 -87
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +96 -674
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +9 -12
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +14 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +7 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +6 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +2 -2
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +3 -3
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +28 -60
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +180 -921
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +10 -35
- package/ios/Sources/RNImageStitcher/Stitcher.swift +84 -35
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +13 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +132 -5
- package/package.json +3 -2
- package/src/ar/useARSession.ts +35 -5
- package/src/camera/Camera.tsx +63 -24
- package/src/camera/CaptureStitchStatsToast.tsx +58 -14
- package/src/camera/PanoramaSettings.ts +16 -289
- package/src/camera/PanoramaSettingsBridge.ts +3 -114
- package/src/camera/PanoramaSettingsModal.tsx +14 -1
- package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +3 -188
- package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +41 -0
- package/src/camera/__tests__/cameraErrorMessages.test.ts +76 -0
- package/src/camera/__tests__/selectCaptureDevice.test.ts +33 -0
- package/src/camera/buildPanoramaInitialSettings.ts +17 -0
- package/src/camera/cameraErrorMessages.ts +84 -0
- package/src/camera/selectCaptureDevice.ts +28 -3
- package/src/camera/useCapture.ts +44 -1
- package/src/index.ts +11 -40
- package/src/stitching/incremental.ts +3 -140
- package/src/stitching/stitchVideo.ts +0 -26
- package/src/types.ts +0 -95
- package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +0 -227
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +0 -1081
- package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +0 -103
- package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +0 -256
- package/cpp/stitcher_frame_jsi.cpp +0 -214
- package/cpp/stitcher_frame_jsi.hpp +0 -108
- package/cpp/stitcher_proxy_jsi.cpp +0 -109
- package/cpp/stitcher_proxy_jsi.hpp +0 -46
- package/cpp/stitcher_worklet_dispatch.cpp +0 -103
- package/cpp/stitcher_worklet_dispatch.hpp +0 -71
- package/cpp/stitcher_worklet_registry.cpp +0 -91
- package/cpp/stitcher_worklet_registry.hpp +0 -146
- package/cpp/tests/stitcher_worklet_registry_test.cpp +0 -195
- package/dist/stitching/IncrementalStitcherView.d.ts +0 -41
- package/dist/stitching/IncrementalStitcherView.js +0 -157
- package/dist/stitching/StitcherWorkletRegistry.d.ts +0 -117
- package/dist/stitching/StitcherWorkletRegistry.js +0 -78
- package/dist/stitching/ensureStitcherProxyInstalled.d.ts +0 -8
- package/dist/stitching/ensureStitcherProxyInstalled.js +0 -81
- package/dist/stitching/useFrameProcessor.d.ts +0 -119
- package/dist/stitching/useFrameProcessor.js +0 -196
- package/dist/stitching/useFrameStream.d.ts +0 -34
- package/dist/stitching/useFrameStream.js +0 -234
- package/dist/stitching/useThrottledFrameProcessor.d.ts +0 -33
- package/dist/stitching/useThrottledFrameProcessor.js +0 -132
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +0 -474
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +0 -1328
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +0 -103
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +0 -3285
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +0 -128
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +0 -313
- package/ios/Sources/RNImageStitcher/SaveFrameAsJpegPlugin.mm +0 -185
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.h +0 -60
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm +0 -214
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +0 -42
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +0 -160
- package/src/stitching/IncrementalStitcherView.tsx +0 -198
- package/src/stitching/StitcherWorkletRegistry.ts +0 -156
- package/src/stitching/__tests__/StitcherWorkletRegistry.test.ts +0 -176
- package/src/stitching/__tests__/ensureStitcherProxyInstalled.test.ts +0 -94
- package/src/stitching/__tests__/useThrottledFrameProcessor.test.ts +0 -178
- package/src/stitching/ensureStitcherProxyInstalled.ts +0 -141
- package/src/stitching/useFrameProcessor.ts +0 -226
- package/src/stitching/useFrameStream.ts +0 -271
- package/src/stitching/useThrottledFrameProcessor.ts +0 -145
|
@@ -43,8 +43,6 @@
|
|
|
43
43
|
*/
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.panoramaSettingsToNativeConfig = panoramaSettingsToNativeConfig;
|
|
46
|
-
exports.slitscanSettingsToNativeConfig = slitscanSettingsToNativeConfig;
|
|
47
|
-
exports.hybridSettingsToNativeConfig = hybridSettingsToNativeConfig;
|
|
48
46
|
const PanoramaSettings_1 = require("./PanoramaSettings");
|
|
49
47
|
/**
|
|
50
48
|
* Convert a v0.4 PanoramaSettings tree into the flat dict the
|
|
@@ -69,6 +67,9 @@ function panoramaSettingsToNativeConfig(s) {
|
|
|
69
67
|
frameSelectionMode: s.frameSelection.mode,
|
|
70
68
|
keyframeMaxCount: s.frameSelection.maxKeyframes,
|
|
71
69
|
keyframeOverlapThreshold: s.frameSelection.overlapThreshold,
|
|
70
|
+
// Time-budget force-accept (both strategies). Native reads
|
|
71
|
+
// configOverrides["maxKeyframeIntervalMs"] → setMaxKeyframeIntervalMs.
|
|
72
|
+
maxKeyframeIntervalMs: s.frameSelection.maxKeyframeIntervalMs,
|
|
72
73
|
};
|
|
73
74
|
// Flow strategy knobs — always serialised, regardless of
|
|
74
75
|
// `frameSelection.mode`. Two reasons:
|
|
@@ -105,104 +106,4 @@ function panoramaSettingsToNativeConfig(s) {
|
|
|
105
106
|
cfg.flowMinDistance = f.minDistance;
|
|
106
107
|
return cfg;
|
|
107
108
|
}
|
|
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
109
|
//# sourceMappingURL=PanoramaSettingsBridge.js.map
|
|
@@ -187,6 +187,12 @@ function PanoramaSettingsModal({ visible, settings, onChange, onClose, }) {
|
|
|
187
187
|
react_1.default.createElement(SegmentedControl, { options: ['20%', '30%', '40%', '50%', '60%'], value: `${Math.round(settings.frameSelection.overlapThreshold * 100)}%`, onChange: (v) => updateFrameSelection({
|
|
188
188
|
overlapThreshold: parseInt(v, 10) / 100,
|
|
189
189
|
}), caption: "Required NEW-content fraction. 20% (default): generous, ~5\u20136 keyframes for a 90\u00B0 pan. Native clamps to [10%, 80%]." }),
|
|
190
|
+
react_1.default.createElement(SectionHeader, { title: "Keyframe interval (time-budget force-accept)" }),
|
|
191
|
+
react_1.default.createElement(SegmentedControl, { options: ['off', '1s', '2s', '3s', '5s'], value: settings.frameSelection.maxKeyframeIntervalMs === 0
|
|
192
|
+
? 'off'
|
|
193
|
+
: `${settings.frameSelection.maxKeyframeIntervalMs / 1000}s`, onChange: (v) => updateFrameSelection({
|
|
194
|
+
maxKeyframeIntervalMs: v === 'off' ? 0 : parseInt(v, 10) * 1000,
|
|
195
|
+
}), caption: "Force-accept a keyframe at least this often even if novelty is low, so slow / static pans don't leave gaps. Counts toward the keyframe cap. off = disabled. 2s (default). Applies to AR + non-AR." }),
|
|
190
196
|
showFlowTunables && (react_1.default.createElement(react_native_1.View, { style: styles.nested },
|
|
191
197
|
react_1.default.createElement(react_native_1.Text, { style: styles.nestedLabel }, "Flow tuning"),
|
|
192
198
|
react_1.default.createElement(SectionHeader, { title: "Max corners (Shi-Tomasi)" }),
|
|
@@ -233,7 +239,7 @@ function PanoramaSettingsModal({ visible, settings, onChange, onClose, }) {
|
|
|
233
239
|
react_1.default.createElement(SectionHeader, { title: "Inscribed-rect crop" }),
|
|
234
240
|
react_1.default.createElement(SegmentedControl, { options: ['off', 'on'], value: settings.stitcher.enableMaxInscribedRectCrop ? 'on' : 'off', onChange: (v) => updateStitcher({
|
|
235
241
|
enableMaxInscribedRectCrop: v === 'on',
|
|
236
|
-
}), caption: "off (default): crop to cv::boundingRect of non-black pixels \u2014 preserves all stitched content; may leave black corners. on: run MaxInscribedRectFromMask + column-projection second-pass for a clean rectangle (can shrink output if mask is lopsided)." })),
|
|
242
|
+
}), caption: "off (default): crop to cv::boundingRect of non-black pixels \u2014 preserves all stitched content; may leave black corners. on: run MaxInscribedRectFromMask + column-projection second-pass for a clean rectangle (can shrink output a lot if mask is lopsided / ultra-wide)." })),
|
|
237
243
|
react_1.default.createElement(react_native_1.Pressable, { onPress: () => onChange(PanoramaSettings_1.DEFAULT_PANORAMA_SETTINGS), style: styles.resetBtn, accessibilityRole: "button", accessibilityLabel: "Reset to defaults" },
|
|
238
244
|
react_1.default.createElement(react_native_1.Text, { style: styles.resetText }, "Reset to defaults")))))));
|
|
239
245
|
}
|
|
@@ -50,6 +50,17 @@ export interface PanoramaPropOverrides {
|
|
|
50
50
|
defaultFlowMaxTranslationCm?: number;
|
|
51
51
|
defaultKeyframeMaxCount?: number;
|
|
52
52
|
defaultKeyframeOverlapThreshold?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Initial value for `frameSelection.maxKeyframeIntervalMs` — the
|
|
55
|
+
* time-budget force-accept (ms). `0` disables it. Default 2000.
|
|
56
|
+
*/
|
|
57
|
+
defaultMaxKeyframeIntervalMs?: number;
|
|
58
|
+
/**
|
|
59
|
+
* v0.15 — initial value for `stitcher.enableMaxInscribedRectCrop`.
|
|
60
|
+
* Maps from the standalone `maxInscribedRectCrop` <Camera> prop.
|
|
61
|
+
* Omitted ⇒ the stitcher default (false = bounding-rect crop).
|
|
62
|
+
*/
|
|
63
|
+
maxInscribedRectCrop?: boolean;
|
|
53
64
|
}
|
|
54
65
|
/**
|
|
55
66
|
* Whether this device is low-memory enough to benefit from the
|
|
@@ -76,12 +76,16 @@ function buildPanoramaInitialSettings(overrides, isLowMemDevice) {
|
|
|
76
76
|
warperType: overrides.defaultWarper ?? stitcherDefaults.warperType,
|
|
77
77
|
blenderType: overrides.defaultBlender ?? stitcherDefaults.blenderType,
|
|
78
78
|
seamFinderType: overrides.defaultSeamFinder ?? stitcherDefaults.seamFinderType,
|
|
79
|
+
enableMaxInscribedRectCrop: overrides.maxInscribedRectCrop
|
|
80
|
+
?? stitcherDefaults.enableMaxInscribedRectCrop,
|
|
79
81
|
},
|
|
80
82
|
frameSelection: {
|
|
81
83
|
...base.frameSelection,
|
|
82
84
|
maxKeyframes: overrides.defaultKeyframeMaxCount ?? base.frameSelection.maxKeyframes,
|
|
83
85
|
overlapThreshold: overrides.defaultKeyframeOverlapThreshold
|
|
84
86
|
?? base.frameSelection.overlapThreshold,
|
|
87
|
+
maxKeyframeIntervalMs: overrides.defaultMaxKeyframeIntervalMs
|
|
88
|
+
?? base.frameSelection.maxKeyframeIntervalMs,
|
|
85
89
|
flow: {
|
|
86
90
|
...flowDefaults,
|
|
87
91
|
noveltyPercentile: overrides.defaultFlowNoveltyPercentile
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Friendly, action-guiding copy for the *recoverable* stitch-failure
|
|
3
|
+
* `CameraErrorCode`s — the ones a user can fix by simply re-capturing.
|
|
4
|
+
* Hosts map these onto an Alert/toast instead of surfacing the raw
|
|
5
|
+
* cv::Stitcher diagnostic (e.g. "warpRoi too large (8171x12336) —
|
|
6
|
+
* estimator produced degenerate camera params").
|
|
7
|
+
*
|
|
8
|
+
* Returns `null` for every non-recoverable / non-stitch code (permission
|
|
9
|
+
* denied, device unavailable, generic finalize failure, unknown, ...):
|
|
10
|
+
* those have no single corrective action to suggest, so the host should
|
|
11
|
+
* fall back to its generic error display.
|
|
12
|
+
*
|
|
13
|
+
* Lives in the SDK (not per-host) so every consumer shows the same
|
|
14
|
+
* vetted guidance for the same failure — and so the mapping is
|
|
15
|
+
* unit-testable in isolation.
|
|
16
|
+
*/
|
|
17
|
+
import type { CameraErrorCode } from './Camera';
|
|
18
|
+
export interface UserFacingStitchError {
|
|
19
|
+
/** Short, friendly alert title. */
|
|
20
|
+
title: string;
|
|
21
|
+
/** One-paragraph, plain-language corrective guidance. */
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Maps a `CameraErrorCode` to friendly, action-guiding alert copy.
|
|
26
|
+
*
|
|
27
|
+
* @returns the title+message for a recoverable stitch failure, or `null`
|
|
28
|
+
* if `code` has no single user-recoverable action (the host should
|
|
29
|
+
* then show its generic error UI).
|
|
30
|
+
*/
|
|
31
|
+
export declare function userFacingStitchError(code: CameraErrorCode): UserFacingStitchError | null;
|
|
32
|
+
//# sourceMappingURL=cameraErrorMessages.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.userFacingStitchError = userFacingStitchError;
|
|
4
|
+
/**
|
|
5
|
+
* The four recoverable stitch outcomes, each with copy tuned to its
|
|
6
|
+
* actual root cause. `Partial<Record<...>>` keeps the keys
|
|
7
|
+
* compile-checked against the `CameraErrorCode` union — a renamed or
|
|
8
|
+
* dropped code breaks the build here rather than silently going
|
|
9
|
+
* unhandled.
|
|
10
|
+
*/
|
|
11
|
+
const RECOVERABLE_STITCH_GUIDANCE = {
|
|
12
|
+
// cv::Stitcher ERR_NEED_MORE_IMGS / the manual pipeline's "0 valid
|
|
13
|
+
// pairwise matches" — the frames simply don't overlap enough to chain.
|
|
14
|
+
STITCH_NEED_MORE_IMGS: {
|
|
15
|
+
title: 'Please pan more slowly',
|
|
16
|
+
message: "There wasn't enough overlap between the frames to stitch them "
|
|
17
|
+
+ 'together — each frame needs to overlap the one before it.',
|
|
18
|
+
},
|
|
19
|
+
// Bundle adjuster produced degenerate camera params (the warp canvas
|
|
20
|
+
// blew past the size guard) — almost always real camera *translation*
|
|
21
|
+
// breaking PANORAMA mode's pure-rotation assumption, amplified hugely
|
|
22
|
+
// on the ultra-wide lens.
|
|
23
|
+
STITCH_CAMERA_PARAMS_FAIL: {
|
|
24
|
+
title: 'Please pan more slowly',
|
|
25
|
+
message: 'The view moved too much between frames to line them up — usually '
|
|
26
|
+
+ 'because the phone moved through space rather than just turning. '
|
|
27
|
+
+ 'The ultra-wide (0.5x) lens is especially sensitive to this, so '
|
|
28
|
+
+ 'try 1x for wide scenes.',
|
|
29
|
+
},
|
|
30
|
+
// Pairwise homography estimation failed — frames couldn't be aligned.
|
|
31
|
+
STITCH_HOMOGRAPHY_FAIL: {
|
|
32
|
+
title: 'Please pan more slowly',
|
|
33
|
+
message: "The frames couldn't be aligned — keep the phone level and steady so "
|
|
34
|
+
+ 'each frame overlaps the one before it.',
|
|
35
|
+
},
|
|
36
|
+
// Ran out of memory finishing the stitch — usually an over-long sweep.
|
|
37
|
+
STITCH_OOM: {
|
|
38
|
+
title: 'Try a shorter sweep',
|
|
39
|
+
message: 'This panorama needs more memory than the device can spare to finish '
|
|
40
|
+
+ '— a shorter, narrower sweep (or 1x for wide scenes) will fit.',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Maps a `CameraErrorCode` to friendly, action-guiding alert copy.
|
|
45
|
+
*
|
|
46
|
+
* @returns the title+message for a recoverable stitch failure, or `null`
|
|
47
|
+
* if `code` has no single user-recoverable action (the host should
|
|
48
|
+
* then show its generic error UI).
|
|
49
|
+
*/
|
|
50
|
+
function userFacingStitchError(code) {
|
|
51
|
+
return RECOVERABLE_STITCH_GUIDANCE[code] ?? null;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=cameraErrorMessages.js.map
|
|
@@ -43,7 +43,11 @@ export interface DeviceLike {
|
|
|
43
43
|
export type CaptureDeviceMode =
|
|
44
44
|
/** One multi-cam device spans wide + ultra-wide; switch lenses via zoom. */
|
|
45
45
|
'multicam'
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Ultra-wide reached by remounting a dedicated ultra-wide device on 0.5x
|
|
48
|
+
* (the 1x primary may be a multi-cam *or* a standalone wide). Used when
|
|
49
|
+
* no multi-cam device can reach the ultra-wide by zoom.
|
|
50
|
+
*/
|
|
47
51
|
| 'standalone-uw'
|
|
48
52
|
/** No ultra-wide anywhere; wide-angle only (no 0.5× chip). */
|
|
49
53
|
| 'wide-only';
|
|
@@ -29,6 +29,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.selectCaptureDevice = selectCaptureDevice;
|
|
30
30
|
exports.zoomForLens = zoomForLens;
|
|
31
31
|
const hasLens = (d, lens) => d.physicalDevices.includes(lens);
|
|
32
|
+
/**
|
|
33
|
+
* Max `minZoom` a multi-cam device may report and still count as able to
|
|
34
|
+
* reach the ultra-wide *by zoom*. Real ultra-wides sit at ~0.5-0.65x, so a
|
|
35
|
+
* logical device whose zoom range genuinely extends to the ultra-wide reports
|
|
36
|
+
* `minZoom <= ~0.65`. A device that only *lists* the ultra-wide (a separate
|
|
37
|
+
* physical camera on Android/Camera2, not a zoom target) reports
|
|
38
|
+
* `minZoom = 1.0`. 0.7 cleanly separates the two.
|
|
39
|
+
*/
|
|
40
|
+
const UW_ZOOM_REACH_MAX = 0.7;
|
|
32
41
|
/**
|
|
33
42
|
* Choose the back-camera device(s) for capture.
|
|
34
43
|
*
|
|
@@ -59,7 +68,13 @@ function selectCaptureDevice(devices) {
|
|
|
59
68
|
// (more lenses → more reach), as a stable tiebreak.
|
|
60
69
|
const multicamCandidates = back.filter((d) => d.isMultiCam &&
|
|
61
70
|
hasLens(d, 'wide-angle-camera') &&
|
|
62
|
-
hasLens(d, 'ultra-wide-angle-camera')
|
|
71
|
+
hasLens(d, 'ultra-wide-angle-camera') &&
|
|
72
|
+
// Must reach the ultra-wide by zoom. On iOS the virtual device's zoom
|
|
73
|
+
// range spans it (minZoom ~0.5); on Android a logical device often
|
|
74
|
+
// *lists* the ultra-wide while its zoom range starts at 1.0 (separate
|
|
75
|
+
// physical camera, not a zoom target). If it can't zoom there, it
|
|
76
|
+
// does NOT qualify -- we fall through to the device-swap path below.
|
|
77
|
+
d.minZoom <= UW_ZOOM_REACH_MAX);
|
|
63
78
|
if (multicamCandidates.length > 0) {
|
|
64
79
|
const device = multicamCandidates.reduce((best, d) => {
|
|
65
80
|
// torch-bearing wins; then wider zoom span; then more lenses.
|
|
@@ -87,8 +102,13 @@ function selectCaptureDevice(devices) {
|
|
|
87
102
|
//
|
|
88
103
|
// Prefer a torch-bearing wide-angle device as the `1×`/primary mount.
|
|
89
104
|
const wideDevices = back.filter((d) => hasLens(d, 'wide-angle-camera'));
|
|
105
|
+
// A *true* standalone ultra-wide (its own id, NOT a multi-cam grouping).
|
|
106
|
+
// We deliberately do NOT fall back to a multi-cam device: mounting a
|
|
107
|
+
// logical multi-cam yields its WIDE member, not the ultra-wide, so a
|
|
108
|
+
// "swap" to it would silently show the wrong FOV. If the only ultra-wide
|
|
109
|
+
// lives inside a non-zoomable multi-cam device, it is undeliverable and we
|
|
110
|
+
// hide the chooser (wide-only) below.
|
|
90
111
|
const ultraWide = back.find((d) => !d.isMultiCam && hasLens(d, 'ultra-wide-angle-camera')) ??
|
|
91
|
-
back.find((d) => hasLens(d, 'ultra-wide-angle-camera')) ??
|
|
92
112
|
null;
|
|
93
113
|
if (wideDevices.length > 0 && ultraWide != null) {
|
|
94
114
|
// Prefer the simplest wide device (fewest extra lenses) with a torch
|
|
@@ -109,6 +109,44 @@ function useCapture(options = {}) {
|
|
|
109
109
|
device = legacyDevice ?? legacyFallback;
|
|
110
110
|
activeZoom = undefined;
|
|
111
111
|
}
|
|
112
|
+
// v0.15 diagnostic (dev-only) — for the "0.5× pill shows but tapping
|
|
113
|
+
// doesn't switch the camera" report on Android (Samsung). Logs the
|
|
114
|
+
// resolved capture mode + the mounted device's zoom range so logcat
|
|
115
|
+
// reveals whether `minZoom` actually reaches the ultra-wide. On
|
|
116
|
+
// Camera2 the logical multi-camera's zoom range usually starts at 1.0
|
|
117
|
+
// (the ultra-wide is a separate physical id, not a zoom target), so a
|
|
118
|
+
// zoom-based 0.5× switch is a silent no-op.
|
|
119
|
+
(0, react_1.useEffect)(() => {
|
|
120
|
+
if (!__DEV__)
|
|
121
|
+
return;
|
|
122
|
+
const summarise = (d) => d
|
|
123
|
+
? {
|
|
124
|
+
id: d.id,
|
|
125
|
+
physical: d.physicalDevices,
|
|
126
|
+
isMultiCam: d.isMultiCam,
|
|
127
|
+
minZoom: d.minZoom,
|
|
128
|
+
neutralZoom: d.neutralZoom,
|
|
129
|
+
maxZoom: d.maxZoom,
|
|
130
|
+
hasTorch: d.hasTorch,
|
|
131
|
+
}
|
|
132
|
+
: null;
|
|
133
|
+
const back = allDevices.filter((d) => d.position === 'back');
|
|
134
|
+
// eslint-disable-next-line no-console
|
|
135
|
+
console.log('[rnimagestitcher] lens-select ' +
|
|
136
|
+
JSON.stringify({
|
|
137
|
+
lens: lens ?? null,
|
|
138
|
+
mode: selection.mode,
|
|
139
|
+
has0_5x: selection.has0_5x,
|
|
140
|
+
activeZoom: activeZoom ?? null,
|
|
141
|
+
selected: summarise(selection.device),
|
|
142
|
+
ultraWide: summarise(selection.ultraWideDevice),
|
|
143
|
+
// Full back-camera enumeration — reveals whether a multicam
|
|
144
|
+
// device merely *lists* the ultra-wide while its zoom range
|
|
145
|
+
// can't reach it (minZoom ~1.0), and whether a STANDALONE
|
|
146
|
+
// ultra-wide device exists for the standalone-uw fallback.
|
|
147
|
+
allBack: back.map(summarise),
|
|
148
|
+
}));
|
|
149
|
+
}, [allDevices, selection, lens, activeZoom]);
|
|
112
150
|
// Enumerate ALL physical lens types available on the chosen
|
|
113
151
|
// position so the host can decide whether to render a switcher.
|
|
114
152
|
// Vision-camera's `useCameraDevices()` returns CameraDevice[]; each
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
*/
|
|
22
22
|
export { Camera, CameraError } from './camera/Camera';
|
|
23
23
|
export type { CameraProps, CameraCaptureResult, CameraErrorCode, CaptureSource, CaptureSourcesMode, CameraLens, StitchMode, Blender, SeamFinder, Warper, FramesDroppedInfo, } from './camera/Camera';
|
|
24
|
+
export { userFacingStitchError } from './camera/cameraErrorMessages';
|
|
25
|
+
export type { UserFacingStitchError } from './camera/cameraErrorMessages';
|
|
24
26
|
export { useARSession, ARTrackingState } from './ar/useARSession';
|
|
25
27
|
export type { UseARSessionReturn, FramePose, } from './ar/useARSession';
|
|
26
28
|
export { useIMUTranslationGate } from './sensors/useIMUTranslationGate';
|
|
@@ -50,9 +52,9 @@ export type { CaptureThumbnailItem } from './camera/CaptureThumbnailStrip';
|
|
|
50
52
|
export { PanoramaBandOverlay } from './camera/PanoramaBandOverlay';
|
|
51
53
|
export { PanoramaSettingsModal } from './camera/PanoramaSettingsModal';
|
|
52
54
|
export type { PanoramaSettingsModalProps } from './camera/PanoramaSettingsModal';
|
|
53
|
-
export { DEFAULT_PANORAMA_SETTINGS, DEFAULT_FLOW_GATE_SETTINGS,
|
|
54
|
-
export type { CaptureBaseSettings, PanoramaSettings, BatchStitcherSettings, FrameSelectionSettings, FlowGateSettings,
|
|
55
|
-
export { panoramaSettingsToNativeConfig,
|
|
55
|
+
export { DEFAULT_PANORAMA_SETTINGS, DEFAULT_FLOW_GATE_SETTINGS, } from './camera/PanoramaSettings';
|
|
56
|
+
export type { CaptureBaseSettings, PanoramaSettings, BatchStitcherSettings, FrameSelectionSettings, FlowGateSettings, } from './camera/PanoramaSettings';
|
|
57
|
+
export { panoramaSettingsToNativeConfig, } from './camera/PanoramaSettingsBridge';
|
|
56
58
|
export type { NativeConfigDict } from './camera/PanoramaSettingsBridge';
|
|
57
59
|
export { ViewportCropOverlay } from './camera/ViewportCropOverlay';
|
|
58
60
|
export { useCapture } from './camera/useCapture';
|
|
@@ -69,11 +71,6 @@ export type { IncrementalState, AcceptedKeyframe } from './stitching/incremental
|
|
|
69
71
|
export { useIncrementalStitcher } from './stitching/useIncrementalStitcher';
|
|
70
72
|
export { useKeyframeStream } from './stitching/useKeyframeStream';
|
|
71
73
|
export type { StitcherFrame, StitcherFrameProcessor, ARAnchor, } from './stitching/StitcherFrame';
|
|
72
|
-
export { useFrameProcessor } from './stitching/useFrameProcessor';
|
|
73
|
-
export { useThrottledFrameProcessor } from './stitching/useThrottledFrameProcessor';
|
|
74
|
-
export type { ThrottledFrameProcessorOptions } from './types';
|
|
75
|
-
export { useFrameStream } from './stitching/useFrameStream';
|
|
76
|
-
export type { FrameStreamOptions, SampledFrame } from './types';
|
|
77
74
|
export { useFrameProcessorDriver } from './stitching/useFrameProcessorDriver';
|
|
78
75
|
export type { UseFrameProcessorDriverOptions, FrameProcessorDriverHandle, } from './stitching/useFrameProcessorDriver';
|
|
79
76
|
export { useStitcherWorklet } from './stitching/useStitcherWorklet';
|
package/dist/index.js
CHANGED
|
@@ -22,13 +22,18 @@
|
|
|
22
22
|
* adds RetaiLens-specific features on top.
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.stitchVideo = exports.useStitcherWorklet = exports.useFrameProcessorDriver = exports.
|
|
25
|
+
exports.stitchVideo = exports.useStitcherWorklet = exports.useFrameProcessorDriver = exports.useKeyframeStream = exports.useIncrementalStitcher = exports.cleanupOldKeyframes = exports.getIncrementalNativeModule = exports.subscribeIncrementalState = exports.incrementalStitcherIsAvailable = exports.IncrementalOutcome = exports.OrientationDriftModal = exports.useOrientationDrift = exports.useDeviceOrientation = exports.useVideoCapture = exports.useCapture = exports.ViewportCropOverlay = exports.panoramaSettingsToNativeConfig = exports.DEFAULT_FLOW_GATE_SETTINGS = exports.DEFAULT_PANORAMA_SETTINGS = exports.PanoramaSettingsModal = exports.PanoramaBandOverlay = exports.CaptureThumbnailStrip = exports.useStitchStatsToast = exports.CaptureStitchStatsToast = exports.CaptureOrientationPill = exports.CaptureKeyframePill = exports.CaptureMemoryPill = exports.CaptureDebugOverlay = exports.CaptureStatusOverlay = exports.CapturePreview = exports.CaptureControlsBar = exports.CaptureHeader = exports.CameraView = exports.ARCameraView = exports.useIMUTranslationGate = exports.ARTrackingState = exports.useARSession = exports.userFacingStitchError = exports.CameraError = exports.Camera = void 0;
|
|
26
26
|
// ─────────────────────────────────────────────────────────────────────
|
|
27
27
|
// Layer 1 — the high-level <Camera> component
|
|
28
28
|
// ─────────────────────────────────────────────────────────────────────
|
|
29
29
|
var Camera_1 = require("./camera/Camera");
|
|
30
30
|
Object.defineProperty(exports, "Camera", { enumerable: true, get: function () { return Camera_1.Camera; } });
|
|
31
31
|
Object.defineProperty(exports, "CameraError", { enumerable: true, get: function () { return Camera_1.CameraError; } });
|
|
32
|
+
// Recoverable-stitch-failure → friendly Alert copy. Hosts call this in
|
|
33
|
+
// their onError handler to surface actionable guidance ("pan more slowly",
|
|
34
|
+
// "pivot in place") instead of the raw cv::Stitcher diagnostic.
|
|
35
|
+
var cameraErrorMessages_1 = require("./camera/cameraErrorMessages");
|
|
36
|
+
Object.defineProperty(exports, "userFacingStitchError", { enumerable: true, get: function () { return cameraErrorMessages_1.userFacingStitchError; } });
|
|
32
37
|
// ─────────────────────────────────────────────────────────────────────
|
|
33
38
|
// AR foundation (public since 0.1.0)
|
|
34
39
|
// ─────────────────────────────────────────────────────────────────────
|
|
@@ -106,16 +111,12 @@ Object.defineProperty(exports, "PanoramaSettingsModal", { enumerable: true, get:
|
|
|
106
111
|
var PanoramaSettings_1 = require("./camera/PanoramaSettings");
|
|
107
112
|
Object.defineProperty(exports, "DEFAULT_PANORAMA_SETTINGS", { enumerable: true, get: function () { return PanoramaSettings_1.DEFAULT_PANORAMA_SETTINGS; } });
|
|
108
113
|
Object.defineProperty(exports, "DEFAULT_FLOW_GATE_SETTINGS", { enumerable: true, get: function () { return PanoramaSettings_1.DEFAULT_FLOW_GATE_SETTINGS; } });
|
|
109
|
-
Object.defineProperty(exports, "DEFAULT_SLITSCAN_SETTINGS", { enumerable: true, get: function () { return PanoramaSettings_1.DEFAULT_SLITSCAN_SETTINGS; } });
|
|
110
|
-
Object.defineProperty(exports, "DEFAULT_HYBRID_SETTINGS", { enumerable: true, get: function () { return PanoramaSettings_1.DEFAULT_HYBRID_SETTINGS; } });
|
|
111
114
|
// Settings → native config adapters. Layer 2 hosts building their
|
|
112
115
|
// own capture flow on top of `incremental.start()` should always
|
|
113
116
|
// pass the result of the matching adapter as `config`; the bridge is
|
|
114
117
|
// the single source of truth for the JS↔native wire format.
|
|
115
118
|
var PanoramaSettingsBridge_1 = require("./camera/PanoramaSettingsBridge");
|
|
116
119
|
Object.defineProperty(exports, "panoramaSettingsToNativeConfig", { enumerable: true, get: function () { return PanoramaSettingsBridge_1.panoramaSettingsToNativeConfig; } });
|
|
117
|
-
Object.defineProperty(exports, "slitscanSettingsToNativeConfig", { enumerable: true, get: function () { return PanoramaSettingsBridge_1.slitscanSettingsToNativeConfig; } });
|
|
118
|
-
Object.defineProperty(exports, "hybridSettingsToNativeConfig", { enumerable: true, get: function () { return PanoramaSettingsBridge_1.hybridSettingsToNativeConfig; } });
|
|
119
120
|
var ViewportCropOverlay_1 = require("./camera/ViewportCropOverlay");
|
|
120
121
|
Object.defineProperty(exports, "ViewportCropOverlay", { enumerable: true, get: function () { return ViewportCropOverlay_1.ViewportCropOverlay; } });
|
|
121
122
|
// ── Capture hooks ─────────────────────────────────────────────────────
|
|
@@ -157,35 +158,11 @@ Object.defineProperty(exports, "useIncrementalStitcher", { enumerable: true, get
|
|
|
157
158
|
// keyframe, packet detection, server-side analysis, etc.).
|
|
158
159
|
var useKeyframeStream_1 = require("./stitching/useKeyframeStream");
|
|
159
160
|
Object.defineProperty(exports, "useKeyframeStream", { enumerable: true, get: function () { return useKeyframeStream_1.useKeyframeStream; } });
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
// API-stable but registration-only until Phase 4b lands the
|
|
166
|
-
// cross-runtime handoff (the AR runtime iterating the registry).
|
|
167
|
-
// See the hook's docstring + StitcherFrame.ts for the contract.
|
|
168
|
-
var useFrameProcessor_1 = require("./stitching/useFrameProcessor");
|
|
169
|
-
Object.defineProperty(exports, "useFrameProcessor", { enumerable: true, get: function () { return useFrameProcessor_1.useFrameProcessor; } });
|
|
170
|
-
// v0.9.0 Layer 2 — `useThrottledFrameProcessor`. Throttle gate over
|
|
171
|
-
// `useFrameProcessor` for sub-frame-rate worklet-native processing
|
|
172
|
-
// (native OCR via Vision.framework / ML Kit, TFLite ML detection,
|
|
173
|
-
// LiDAR depth). The worklet runtime has direct access to
|
|
174
|
-
// `frame.toArrayBuffer()` / `frame.arDepth`; bridge small payloads
|
|
175
|
-
// (bboxes, depth-derived metrics) to JS via `runOnJS`. For JS-thread
|
|
176
|
-
// JPEG consumers (file-path OCR libs, cloud upload, thumbnail UI),
|
|
177
|
-
// prefer `useFrameStream` (Layer 3, ships in the same release).
|
|
178
|
-
var useThrottledFrameProcessor_1 = require("./stitching/useThrottledFrameProcessor");
|
|
179
|
-
Object.defineProperty(exports, "useThrottledFrameProcessor", { enumerable: true, get: function () { return useThrottledFrameProcessor_1.useThrottledFrameProcessor; } });
|
|
180
|
-
// v0.9.0 Layer 3 — `useFrameStream`. JS-thread sampled-frame
|
|
181
|
-
// stream over Layer 1 (`save_frame_as_jpeg` vc plugin) + Layer 2
|
|
182
|
-
// (`useThrottledFrameProcessor`). Use for JS-thread consumers:
|
|
183
|
-
// file-path OCR libs (RN modules), cloud upload, thumbnail UI.
|
|
184
|
-
// For worklet-native processing (Vision/ML Kit as vc plugins,
|
|
185
|
-
// TFLite ML, LiDAR depth), prefer `useThrottledFrameProcessor`
|
|
186
|
-
// (Layer 2) — lower latency, no JPEG roundtrip.
|
|
187
|
-
var useFrameStream_1 = require("./stitching/useFrameStream");
|
|
188
|
-
Object.defineProperty(exports, "useFrameStream", { enumerable: true, get: function () { return useFrameStream_1.useFrameStream; } });
|
|
161
|
+
// NOTE: the host-worklet / frame-stream hooks `useFrameProcessor`,
|
|
162
|
+
// `useThrottledFrameProcessor` and `useFrameStream` (v0.8–v0.9) were
|
|
163
|
+
// archived in the batch-keyframe cleanup — they drove the third-party
|
|
164
|
+
// `__stitcherProxy` observer API, not batch-keyframe capture. Source is
|
|
165
|
+
// preserved under archive/src/stitching/ to build on later.
|
|
189
166
|
// vision-camera Frame Processor driver for non-AR captures. As
|
|
190
167
|
// of v0.6 the only non-AR driver exported (the legacy
|
|
191
168
|
// `useIncrementalJSDriver` was removed; was deprecated in v0.5).
|
|
@@ -403,7 +403,7 @@ export interface IncrementalStartOptions {
|
|
|
403
403
|
* 'slitscan' fall back to 'slitscan-both' with a deprecation warning
|
|
404
404
|
* in the native log.
|
|
405
405
|
*/
|
|
406
|
-
engine?: '
|
|
406
|
+
engine?: 'batch-keyframe';
|
|
407
407
|
/**
|
|
408
408
|
* V15 — per-stage correction config overrides. Mode-driven defaults
|
|
409
409
|
* are applied first (see RLISStitcherConfig +configForMode:); fields
|
|
@@ -418,122 +418,6 @@ export interface IncrementalStartOptions {
|
|
|
418
418
|
* fields optional (omit to accept the engine-mode default).
|
|
419
419
|
*/
|
|
420
420
|
export interface StitcherConfig {
|
|
421
|
-
/** Fraction of pan-axis the rectilinear slit retains per frame.
|
|
422
|
-
* Range 0.10 – 0.70, default 0.30 in V15 slit-scan modes. */
|
|
423
|
-
kPanAxisFractionRect: number;
|
|
424
|
-
/** Minimum pan-axis advance (px) before a frame is accepted.
|
|
425
|
-
* 0 = accept on every consumeFrame (Apple-dense slit-scan, V15
|
|
426
|
-
* default). 50 = V13.0g default. */
|
|
427
|
-
kMinAcceptDeltaPx: number;
|
|
428
|
-
/** V13.0e+ ORB triangulation + median-Z parallax correction. */
|
|
429
|
-
enableTriangulation: boolean;
|
|
430
|
-
/** V13.0g per-accept incremental Δt accumulator on top of triangulation. */
|
|
431
|
-
enableTriAccumulator: boolean;
|
|
432
|
-
/** V15 1D NCC perpendicular-axis wobble correction (slitscan-rotate
|
|
433
|
-
* default). Independent of the other correction stages. */
|
|
434
|
-
enable1dNcc: boolean;
|
|
435
|
-
/** 1D NCC search radius in pixels (5 – 60). */
|
|
436
|
-
nccSearchRadius1d: number;
|
|
437
|
-
/** V13.0g 2D NCC fine-alignment after triangulation. */
|
|
438
|
-
enable2dNcc: boolean;
|
|
439
|
-
/** V14.0a RANSAC homography per slit + cv::warpPerspective. When
|
|
440
|
-
* enabled and successful, supersedes the rectangular paste path. */
|
|
441
|
-
enableRansacHomography: boolean;
|
|
442
|
-
/** 'FirstPaintedWins' protects already-painted pixels (V13.0e+
|
|
443
|
-
* default). 'FeatherBlend' alpha-blends new content into already-
|
|
444
|
-
* painted overlap pixels (V13.0d-style; V15 slitscan-both default). */
|
|
445
|
-
paintMode: 'FirstPaintedWins' | 'FeatherBlend';
|
|
446
|
-
/** 'Cylindrical' (V12.x – V14.0a behaviour) or 'Planar' (V15 default;
|
|
447
|
-
* cv::detail::PlaneWarper). Planar is well-behaved for pans <60°. */
|
|
448
|
-
hybridProjection: 'Cylindrical' | 'Planar';
|
|
449
|
-
/** V15.0c — where on the camera frame the per-accept sliver is taken.
|
|
450
|
-
* 'Center' (V13.x default), 'Bottom' (leading edge for top-to-bottom
|
|
451
|
-
* pan), or 'Top' (leading edge for bottom-to-top pan). */
|
|
452
|
-
sliverPosition: 'Center' | 'Bottom' | 'Top';
|
|
453
|
-
/** V15.0c — when true, the FIRST accepted frame paints the entire
|
|
454
|
-
* camera frame at canvas (0, 0); subsequent frames still use the
|
|
455
|
-
* configured sliver clip. Default false; set true when sliverPosition
|
|
456
|
-
* is Bottom/Top so the canvas is anchored with full-frame content. */
|
|
457
|
-
firstFrameFullFrame: boolean;
|
|
458
|
-
/** **DEPRECATED in V15.0d** — use `planeSource` instead.
|
|
459
|
-
*
|
|
460
|
-
* V15.0b boolean toggle for the plane-projected stitch path.
|
|
461
|
-
* Kept for backward compat: when `planeSource` is left at its
|
|
462
|
-
* default (Disabled), `useDetectedPlane = true` upgrades it to
|
|
463
|
-
* ARKitDetected. New callers should set `planeSource` directly. */
|
|
464
|
-
useDetectedPlane: boolean;
|
|
465
|
-
/** V15.0d — source of the plane used by the V15.0b plane-projected
|
|
466
|
-
* stitch path.
|
|
467
|
-
*
|
|
468
|
-
* - 'Disabled' (default): no plane projection; slit-scan path runs.
|
|
469
|
-
* - 'ARKitDetected': use ARKit's first vertical plane that aligns
|
|
470
|
-
* with the camera's view direction (filter threshold:
|
|
471
|
-
* `arkitPlaneAlignmentThreshold`). Falls back to slit-scan
|
|
472
|
-
* silently when no aligned plane is found.
|
|
473
|
-
* - 'Virtual': synthesize a plane at first frame: origin =
|
|
474
|
-
* camera_pos + `virtualPlaneDepthMeters` × camera_forward;
|
|
475
|
-
* normal = -camera_forward. Always works; no ARKit dependency.
|
|
476
|
-
*
|
|
477
|
-
* Field testing showed ARKit plane detection often picks the WRONG
|
|
478
|
-
* surface (side wall, doorframe) — Virtual mode is the safer
|
|
479
|
-
* default for arbitrary scenes. ARKitDetected wins when ARKit
|
|
480
|
-
* finds the correct fixture face. */
|
|
481
|
-
planeSource: 'Disabled' | 'ARKitDetected' | 'Virtual';
|
|
482
|
-
/** V15.0d — depth (metres) at which the synthetic plane is placed
|
|
483
|
-
* in front of the camera when `planeSource = Virtual`. Set to
|
|
484
|
-
* the user's typical scan distance. Range 0.3 – 5.0 m. Default
|
|
485
|
-
* 1.5 m. */
|
|
486
|
-
virtualPlaneDepthMeters: number;
|
|
487
|
-
/** V15.0d — minimum dot product between an ARKit-detected plane's
|
|
488
|
-
* surface normal and the camera's facing direction for the plane
|
|
489
|
-
* to be accepted (when `planeSource = ARKitDetected`). 1.0 =
|
|
490
|
-
* plane perfectly facing camera; 0.0 = plane edge-on; negative
|
|
491
|
-
* = facing away. Range 0.0 – 1.0. Default 0.6 (≈53° max angle
|
|
492
|
-
* off-camera). */
|
|
493
|
-
arkitPlaneAlignmentThreshold: number;
|
|
494
|
-
/** V15.0g — how the plane-projection helper renders each frame onto
|
|
495
|
-
* the canvas. Affects ARKitDetected and Virtual modes; ignored
|
|
496
|
-
* when planeSource = Disabled.
|
|
497
|
-
*
|
|
498
|
-
* - 'Trapezoidal' (V15.0b legacy): geometrically-correct 3D
|
|
499
|
-
* raycast. Each camera pixel maps to its plane intersection.
|
|
500
|
-
* Result is a trapezoid that grows distorted with tilt
|
|
501
|
-
* (cooler-bottom-2.3×-wider-than-top problem).
|
|
502
|
-
* - 'Rectified' (V15.0g default): camera frame pasted as a clean
|
|
503
|
-
* rectangle around its plane-projected anchor. Eliminates the
|
|
504
|
-
* tilt-induced trapezoidal distortion at the cost of strict 3D-
|
|
505
|
-
* correctness — the camera's per-pixel perspective stays inside
|
|
506
|
-
* the rectangle but doesn't reconcile across tilts. */
|
|
507
|
-
planeProjectionStyle: 'Trapezoidal' | 'Rectified';
|
|
508
|
-
/** V15.0d — 2D NCC search half-window in pixels. Was hardcoded
|
|
509
|
-
* ±12 in V15.0c.4. Smaller = less wandering on repetitive
|
|
510
|
-
* textures (peg holes, slatted panels), but easier to miss the
|
|
511
|
-
* true overlap when pose noise is high. Range 4 – 30. Default
|
|
512
|
-
* 12. */
|
|
513
|
-
nccSearchMargin2d: number;
|
|
514
|
-
/** V15.0d — 2D NCC confidence threshold below which the correction
|
|
515
|
-
* is rejected. Was hardcoded 0.75 in V15.0c.4. Higher = stricter,
|
|
516
|
-
* fewer false matches on repetitive textures, but more frames
|
|
517
|
-
* where NCC silently doesn't fire. Range 0.30 – 0.99. Default
|
|
518
|
-
* 0.75. */
|
|
519
|
-
nccConfidenceThreshold2d: number;
|
|
520
|
-
/** V15.0d (1B) — exponential-moving-average smoothing on 2D NCC
|
|
521
|
-
* corrections. When enabled, the applied correction is
|
|
522
|
-
* `α × current + (1−α) × prev` instead of just `current`. Damps
|
|
523
|
-
* single-frame snaps to spurious peaks. Default false. */
|
|
524
|
-
enableNcc2dEmaSmoothing: boolean;
|
|
525
|
-
/** V15.0d — EMA weight on the CURRENT-frame NCC correction
|
|
526
|
-
* (1 − α weight on the previous correction). Range 0.05 – 0.95.
|
|
527
|
-
* Default 0.4 (60% prev / 40% current — heavy damping). */
|
|
528
|
-
ncc2dEmaAlpha: number;
|
|
529
|
-
/** V15.0d (1C) — pan-axis-aware 2D NCC. When enabled, the cross-
|
|
530
|
-
* axis (perpendicular to pan) NCC correction is clamped tighter
|
|
531
|
-
* than the pan-axis (since 1D NCC + pose already handle cross-
|
|
532
|
-
* axis wobble). Default false. */
|
|
533
|
-
enableNcc2dPanAxisLock: boolean;
|
|
534
|
-
/** V15.0d — cross-axis clamp (pixels) for the pan-axis-aware mode.
|
|
535
|
-
* Range 0 – 30. Default 5. */
|
|
536
|
-
ncc2dCrossAxisLockPx: number;
|
|
537
421
|
/** V16 — how the engine decides which ARFrames to ingest.
|
|
538
422
|
*
|
|
539
423
|
* - 'time-based' (default): every frame the AR delegate delivers
|