react-native-image-stitcher 0.14.2 → 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 +131 -0
- package/README.md +35 -0
- package/RNImageStitcher.podspec +8 -7
- 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/camera/Camera.d.ts +31 -16
- package/dist/camera/Camera.js +10 -2
- 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/camera/Camera.tsx +43 -22
- 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
package/dist/camera/Camera.js
CHANGED
|
@@ -265,6 +265,8 @@ function extractPanoramaOverrides(props) {
|
|
|
265
265
|
defaultFlowMaxTranslationCm: props.defaultFlowMaxTranslationCm,
|
|
266
266
|
defaultKeyframeMaxCount: props.defaultKeyframeMaxCount,
|
|
267
267
|
defaultKeyframeOverlapThreshold: props.defaultKeyframeOverlapThreshold,
|
|
268
|
+
defaultMaxKeyframeIntervalMs: props.defaultMaxKeyframeIntervalMs,
|
|
269
|
+
maxInscribedRectCrop: props.maxInscribedRectCrop,
|
|
268
270
|
};
|
|
269
271
|
}
|
|
270
272
|
// `toFileUri` (used to be an inline `toFileUri` here) lives in
|
|
@@ -871,7 +873,8 @@ function Camera(props) {
|
|
|
871
873
|
// reconstruction. Only meaningful in non-AR mode (in AR the
|
|
872
874
|
// native side uses pose-derived translation and ignores this).
|
|
873
875
|
const imuTotalTranslationM = isNonAR ? imuGate.getTotalAbsMetres() : 0;
|
|
874
|
-
const result = await incremental.finalize(panoOutputPath, 90,
|
|
876
|
+
const result = await incremental.finalize(panoOutputPath, 90, // default JPEG quality
|
|
877
|
+
deviceOrientation, imuTotalTranslationM);
|
|
875
878
|
if (typeof result.framesRequested === 'number'
|
|
876
879
|
&& typeof result.framesIncluded === 'number'
|
|
877
880
|
&& result.framesIncluded < result.framesRequested) {
|
|
@@ -904,7 +907,12 @@ function Camera(props) {
|
|
|
904
907
|
}
|
|
905
908
|
catch (err) {
|
|
906
909
|
const message = err instanceof Error ? err.message : String(err);
|
|
907
|
-
const code =
|
|
910
|
+
const code =
|
|
911
|
+
// Insufficient overlap surfaces two ways: cv::Stitcher's
|
|
912
|
+
// ERR_NEED_MORE_IMGS ("need more images") and the manual
|
|
913
|
+
// pipeline's "0 valid pairwise matches / frames may not overlap
|
|
914
|
+
// enough" — both are the same recoverable "pan more slowly" case.
|
|
915
|
+
/need more images|pairwise match|overlap enough/i.test(message) ? 'STITCH_NEED_MORE_IMGS'
|
|
908
916
|
: /homography/i.test(message) ? 'STITCH_HOMOGRAPHY_FAIL'
|
|
909
917
|
: /camera params/i.test(message) ? 'STITCH_CAMERA_PARAMS_FAIL'
|
|
910
918
|
: /out of memory|oom/i.test(message) ? 'STITCH_OOM'
|
|
@@ -18,10 +18,21 @@ import type { IncrementalFinalizeResult } from '../stitching/incremental';
|
|
|
18
18
|
export interface CaptureStitchStatsToastProps {
|
|
19
19
|
/** Toast message to show. Pass null to hide. */
|
|
20
20
|
message: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Optional bold title rendered above the message (e.g. an action ask
|
|
23
|
+
* like "Pan more slowly"). Omit for a plain single-line toast.
|
|
24
|
+
*/
|
|
25
|
+
title?: string | null;
|
|
21
26
|
/** Top inset for safe-area placement. Toast pinned `topInset + 12`. */
|
|
22
27
|
topInset?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Vertical placement. 'top' (default) pins it `topInset + 12` from the
|
|
30
|
+
* top; 'center' vertically centers it — more prominent, and dodges the
|
|
31
|
+
* notch / Dynamic Island entirely.
|
|
32
|
+
*/
|
|
33
|
+
placement?: 'top' | 'center';
|
|
23
34
|
}
|
|
24
|
-
export declare function CaptureStitchStatsToast({ message, topInset, }: CaptureStitchStatsToastProps): React.JSX.Element | null;
|
|
35
|
+
export declare function CaptureStitchStatsToast({ message, title, topInset, placement, }: CaptureStitchStatsToastProps): React.JSX.Element | null;
|
|
25
36
|
/**
|
|
26
37
|
* Imperative API for showing transient stitch-stats toasts.
|
|
27
38
|
*
|
|
@@ -38,7 +49,9 @@ export declare function CaptureStitchStatsToast({ message, topInset, }: CaptureS
|
|
|
38
49
|
*/
|
|
39
50
|
export interface UseStitchStatsToastReturn {
|
|
40
51
|
message: string | null;
|
|
41
|
-
|
|
52
|
+
/** Optional bold title shown above `message` (pass to the toast). */
|
|
53
|
+
title: string | null;
|
|
54
|
+
showFor: (msg: string, ms?: number, title?: string) => void;
|
|
42
55
|
showResult: (result: IncrementalFinalizeResult, ms?: number) => void;
|
|
43
56
|
}
|
|
44
57
|
export declare function useStitchStatsToast(): UseStitchStatsToastReturn;
|
|
@@ -53,14 +53,14 @@ exports.CaptureStitchStatsToast = CaptureStitchStatsToast;
|
|
|
53
53
|
exports.useStitchStatsToast = useStitchStatsToast;
|
|
54
54
|
const react_1 = __importStar(require("react"));
|
|
55
55
|
const react_native_1 = require("react-native");
|
|
56
|
-
function CaptureStitchStatsToast({ message, topInset = 0, }) {
|
|
56
|
+
function CaptureStitchStatsToast({ message, title = null, topInset = 0, placement = 'top', }) {
|
|
57
57
|
if (message === null)
|
|
58
58
|
return null;
|
|
59
|
-
return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style:
|
|
60
|
-
styles.
|
|
61
|
-
{ top: topInset + 12 },
|
|
62
|
-
] },
|
|
59
|
+
return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: placement === 'center'
|
|
60
|
+
? styles.wrapCenter
|
|
61
|
+
: [styles.wrap, { top: topInset + 12 }] },
|
|
63
62
|
react_1.default.createElement(react_native_1.View, { style: styles.capsule, accessibilityRole: "alert", accessibilityLiveRegion: "polite" },
|
|
63
|
+
title ? (react_1.default.createElement(react_native_1.Text, { style: styles.title, numberOfLines: 2 }, title)) : null,
|
|
64
64
|
react_1.default.createElement(react_native_1.Text, { style: styles.text, numberOfLines: 3 }, message))));
|
|
65
65
|
}
|
|
66
66
|
const styles = react_native_1.StyleSheet.create({
|
|
@@ -71,6 +71,16 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
71
71
|
alignItems: 'center',
|
|
72
72
|
zIndex: 110,
|
|
73
73
|
},
|
|
74
|
+
wrapCenter: {
|
|
75
|
+
position: 'absolute',
|
|
76
|
+
top: 0,
|
|
77
|
+
bottom: 0,
|
|
78
|
+
left: 24,
|
|
79
|
+
right: 24,
|
|
80
|
+
alignItems: 'center',
|
|
81
|
+
justifyContent: 'center',
|
|
82
|
+
zIndex: 110,
|
|
83
|
+
},
|
|
74
84
|
capsule: {
|
|
75
85
|
paddingHorizontal: 16,
|
|
76
86
|
paddingVertical: 10,
|
|
@@ -78,6 +88,13 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
78
88
|
backgroundColor: 'rgba(15, 23, 42, 0.92)',
|
|
79
89
|
maxWidth: '100%',
|
|
80
90
|
},
|
|
91
|
+
title: {
|
|
92
|
+
color: '#ffffff',
|
|
93
|
+
fontSize: 14,
|
|
94
|
+
fontWeight: '700',
|
|
95
|
+
textAlign: 'center',
|
|
96
|
+
marginBottom: 3,
|
|
97
|
+
},
|
|
81
98
|
text: {
|
|
82
99
|
color: '#ffffff',
|
|
83
100
|
fontSize: 13,
|
|
@@ -88,13 +105,16 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
88
105
|
const DEFAULT_DISMISS_MS = 4500;
|
|
89
106
|
function useStitchStatsToast() {
|
|
90
107
|
const [message, setMessage] = (0, react_1.useState)(null);
|
|
108
|
+
const [title, setTitle] = (0, react_1.useState)(null);
|
|
91
109
|
const timerRef = (0, react_1.useRef)(null);
|
|
92
|
-
const showFor = (0, react_1.useCallback)((msg, ms = DEFAULT_DISMISS_MS) => {
|
|
110
|
+
const showFor = (0, react_1.useCallback)((msg, ms = DEFAULT_DISMISS_MS, titleText) => {
|
|
93
111
|
if (timerRef.current)
|
|
94
112
|
clearTimeout(timerRef.current);
|
|
113
|
+
setTitle(titleText ?? null);
|
|
95
114
|
setMessage(msg);
|
|
96
115
|
timerRef.current = setTimeout(() => {
|
|
97
116
|
setMessage(null);
|
|
117
|
+
setTitle(null);
|
|
98
118
|
timerRef.current = null;
|
|
99
119
|
}, ms);
|
|
100
120
|
}, []);
|
|
@@ -128,6 +148,6 @@ function useStitchStatsToast() {
|
|
|
128
148
|
if (timerRef.current)
|
|
129
149
|
clearTimeout(timerRef.current);
|
|
130
150
|
}, []);
|
|
131
|
-
return { message, showFor, showResult };
|
|
151
|
+
return { message, title, showFor, showResult };
|
|
132
152
|
}
|
|
133
153
|
//# sourceMappingURL=CaptureStitchStatsToast.js.map
|
|
@@ -175,6 +175,16 @@ export interface FrameSelectionSettings {
|
|
|
175
175
|
* (`IncrementalStitcher.swift:962`).
|
|
176
176
|
*/
|
|
177
177
|
overlapThreshold: number;
|
|
178
|
+
/**
|
|
179
|
+
* Time-budget force-accept (BOTH strategies, AR + non-AR). When > 0,
|
|
180
|
+
* the gate accepts a keyframe whenever this many milliseconds have
|
|
181
|
+
* elapsed since the last accepted keyframe — even if the novelty /
|
|
182
|
+
* overlap threshold wasn't met — so a slow or static pan never goes
|
|
183
|
+
* longer than this without a keyframe. Counts toward `maxKeyframes`
|
|
184
|
+
* (the cap still finalises the capture). `0` disables it. Default
|
|
185
|
+
* `2000` (2 s). Maps to the native gate's `setMaxKeyframeIntervalMs`.
|
|
186
|
+
*/
|
|
187
|
+
maxKeyframeIntervalMs: number;
|
|
178
188
|
/**
|
|
179
189
|
* Sparse-optical-flow strategy tunables. Consulted only when
|
|
180
190
|
* `mode === 'flow-based'`; safe to omit otherwise. Defaults
|
|
@@ -252,227 +262,4 @@ export interface FlowGateSettings {
|
|
|
252
262
|
*/
|
|
253
263
|
export declare const DEFAULT_FLOW_GATE_SETTINGS: FlowGateSettings;
|
|
254
264
|
export declare const DEFAULT_PANORAMA_SETTINGS: PanoramaSettings;
|
|
255
|
-
/**
|
|
256
|
-
* Settings for slit-scan stitching engines (`slitscan-rotate`,
|
|
257
|
-
* `slitscan-both`, `firstwins-rectilinear`). Reached via
|
|
258
|
-
* `incremental.start({ engine: '<variant>', config: { ... } })`,
|
|
259
|
-
* NOT via <Camera> (which always uses batch-keyframe). Each
|
|
260
|
-
* sub-tree corresponds to a section of the native `RLISStitcherConfig`
|
|
261
|
-
* the slit-scan engine reads at start.
|
|
262
|
-
*
|
|
263
|
-
* Field-by-field native consumer references are documented in
|
|
264
|
-
* `OpenCVSlitScanStitcher.mm` / `OpenCVIncrementalStitcher.h`.
|
|
265
|
-
*/
|
|
266
|
-
export interface SlitscanSettings extends CaptureBaseSettings {
|
|
267
|
-
/**
|
|
268
|
-
* Which slit-scan variant the engine runs. All three share the
|
|
269
|
-
* same painting + registration + plane configuration; they differ
|
|
270
|
-
* in their internal motion model (rotation-only vs combined
|
|
271
|
-
* translation+rotation, and slit position).
|
|
272
|
-
*
|
|
273
|
-
* • `'slitscan-rotate'` — preferred name; rotation-only
|
|
274
|
-
* motion model.
|
|
275
|
-
* • `'slitscan-both'` — combined translation + rotation
|
|
276
|
-
* motion model.
|
|
277
|
-
* • `'firstwins-rectilinear'` — legacy alias of
|
|
278
|
-
* `'slitscan-rotate'` (V13.0a naming). Accepted natively
|
|
279
|
-
* but new code should prefer the canonical name.
|
|
280
|
-
*/
|
|
281
|
-
variant: 'slitscan-rotate' | 'slitscan-both' | 'firstwins-rectilinear';
|
|
282
|
-
/** Where the per-accept slit is taken from + how it's blended. */
|
|
283
|
-
painting: SlitscanPaintingSettings;
|
|
284
|
-
/** Frame-to-frame registration (NCC + RANSAC + triangulation). */
|
|
285
|
-
registration: SlitscanRegistrationSettings;
|
|
286
|
-
/** Plane projection (ARKit-detected, virtual, or disabled). */
|
|
287
|
-
plane: PlaneProjectionSettings;
|
|
288
|
-
/**
|
|
289
|
-
* Advanced motion-tuning knobs that the v0.3 modal never exposed.
|
|
290
|
-
* Both are read by the native side
|
|
291
|
-
* (`IncrementalStitcher.swift:1074, 1077`) and have sensible
|
|
292
|
-
* defaults; most consumers can leave this field undefined.
|
|
293
|
-
*/
|
|
294
|
-
advanced?: SlitscanAdvancedSettings;
|
|
295
|
-
}
|
|
296
|
-
export interface SlitscanAdvancedSettings {
|
|
297
|
-
/**
|
|
298
|
-
* Fraction of the pan-axis sensor extent used to compute the
|
|
299
|
-
* per-frame slit width. Range `[0.05, 0.90]`, default 0.70
|
|
300
|
-
* (engine internal). Higher = wider slits = fewer accepts per
|
|
301
|
-
* pan. Set this only if you know what the slit-scan motion
|
|
302
|
-
* model needs for your specific capture geometry.
|
|
303
|
-
* Native key: `kPanAxisFractionRect`.
|
|
304
|
-
*/
|
|
305
|
-
panAxisFractionRect?: number;
|
|
306
|
-
/**
|
|
307
|
-
* Minimum pan-axis delta (in canvas pixels) between consecutive
|
|
308
|
-
* accepted strips. Acts as a hard floor below which subsequent
|
|
309
|
-
* frames are rejected regardless of NCC scores. Range
|
|
310
|
-
* `[0, 500]`, default 0 (no floor). Native key:
|
|
311
|
-
* `kMinAcceptDeltaPx`.
|
|
312
|
-
*/
|
|
313
|
-
minAcceptDeltaPx?: number;
|
|
314
|
-
}
|
|
315
|
-
export interface SlitscanPaintingSettings {
|
|
316
|
-
/**
|
|
317
|
-
* How new strips are blended into already-painted canvas pixels.
|
|
318
|
-
*
|
|
319
|
-
* • `'FirstPaintedWins'` (default) — preserve the first frame's
|
|
320
|
-
* content at any pixel; later strips don't overwrite.
|
|
321
|
-
* • `'FeatherBlend'` — alpha-blend new strips into
|
|
322
|
-
* already-painted areas at slit boundaries. Smooths visible
|
|
323
|
-
* seams when many narrow slits stack.
|
|
324
|
-
*/
|
|
325
|
-
paintMode: 'FirstPaintedWins' | 'FeatherBlend';
|
|
326
|
-
/**
|
|
327
|
-
* Where on the camera frame the per-accept slit is sampled from.
|
|
328
|
-
* For a typical landscape vertical pan tilting DOWN, the leading
|
|
329
|
-
* edge (new content) is at the BOTTOM of the camera frame; for
|
|
330
|
-
* upward tilt, it's at the TOP. `'Center'` is the V13.x default.
|
|
331
|
-
*/
|
|
332
|
-
sliverPosition: 'Center' | 'Bottom' | 'Top';
|
|
333
|
-
/**
|
|
334
|
-
* When `true`, the very first frame's FULL frame is painted onto
|
|
335
|
-
* the canvas (not just the configured slit clip). Default
|
|
336
|
-
* `true` — gives the panorama a wider initial anchor that
|
|
337
|
-
* subsequent slits extend from. Set false if you want strict
|
|
338
|
-
* slit-only behaviour even on the first frame.
|
|
339
|
-
*/
|
|
340
|
-
firstFrameFullFrame: boolean;
|
|
341
|
-
}
|
|
342
|
-
export interface SlitscanRegistrationSettings {
|
|
343
|
-
/**
|
|
344
|
-
* 3D triangulation step. Cross-references features across
|
|
345
|
-
* multiple frames to estimate scene depth. Default `false` (off);
|
|
346
|
-
* adds latency, useful for parallax-heavy captures.
|
|
347
|
-
*/
|
|
348
|
-
enableTriangulation: boolean;
|
|
349
|
-
/**
|
|
350
|
-
* Triangulation accumulator — when `enableTriangulation` is on,
|
|
351
|
-
* keeps a running pose graph across the whole capture. Default
|
|
352
|
-
* `false` (off); needed for multi-shot fusion.
|
|
353
|
-
*/
|
|
354
|
-
enableTriAccumulator: boolean;
|
|
355
|
-
/**
|
|
356
|
-
* RANSAC homography fit per pair. Adds robustness to feature
|
|
357
|
-
* matching at the cost of a few ms per frame. Default `false`.
|
|
358
|
-
*/
|
|
359
|
-
enableRansacHomography: boolean;
|
|
360
|
-
/**
|
|
361
|
-
* 1D NCC strip alignment. Present iff enabled. Default
|
|
362
|
-
* undefined (disabled); engine uses pure feature matching.
|
|
363
|
-
*/
|
|
364
|
-
ncc1d?: Ncc1dSettings;
|
|
365
|
-
/**
|
|
366
|
-
* 2D NCC strip alignment. Present iff enabled. More expensive
|
|
367
|
-
* than 1D NCC; needed for shelf-scan captures with vertical
|
|
368
|
-
* misalignment. Default undefined (disabled).
|
|
369
|
-
*/
|
|
370
|
-
ncc2d?: Ncc2dSettings;
|
|
371
|
-
}
|
|
372
|
-
export interface Ncc1dSettings {
|
|
373
|
-
/**
|
|
374
|
-
* Search radius in working-resolution pixels (along the pan axis).
|
|
375
|
-
* Clamped to `[5, 60]`. Default 15 when the field is set.
|
|
376
|
-
*/
|
|
377
|
-
searchRadius: number;
|
|
378
|
-
}
|
|
379
|
-
export interface Ncc2dSettings {
|
|
380
|
-
/**
|
|
381
|
-
* 2D search margin in pixels (rectangular region around the
|
|
382
|
-
* predicted strip position). Clamped to `[4, 60]`. Default 12.
|
|
383
|
-
*/
|
|
384
|
-
searchMargin: number;
|
|
385
|
-
/**
|
|
386
|
-
* Minimum NCC score to accept a match. Below this the engine
|
|
387
|
-
* falls back to the predicted (pose-only) position. Clamped
|
|
388
|
-
* to `[0.30, 0.99]`. Default 0.99 (only accept very strong
|
|
389
|
-
* matches; the canvas falls back to pose-only quickly).
|
|
390
|
-
*/
|
|
391
|
-
confidenceThreshold: number;
|
|
392
|
-
/**
|
|
393
|
-
* EMA smoothing of the NCC-derived offset across consecutive
|
|
394
|
-
* strips. Present iff enabled. Default undefined. Useful
|
|
395
|
-
* for jittery captures.
|
|
396
|
-
*/
|
|
397
|
-
emaSmoothing?: {
|
|
398
|
-
alpha: number;
|
|
399
|
-
};
|
|
400
|
-
/**
|
|
401
|
-
* Pan-axis-lock — when enabled, the NCC offset is constrained
|
|
402
|
-
* to the dominant pan axis (cross-axis movement bounded by
|
|
403
|
-
* `crossAxisLockPx`). Useful when the operator's hand wobble
|
|
404
|
-
* introduces unwanted cross-axis motion. Present iff enabled.
|
|
405
|
-
*/
|
|
406
|
-
panAxisLock?: {
|
|
407
|
-
crossAxisLockPx: number;
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
export interface PlaneProjectionSettings {
|
|
411
|
-
/**
|
|
412
|
-
* Where the plane the slit-scan projects onto comes from.
|
|
413
|
-
*
|
|
414
|
-
* • `'Disabled'` — no plane projection; engine runs
|
|
415
|
-
* its baseline slit-scan path.
|
|
416
|
-
* • `'ARKitDetected'` — use the first vertical plane that
|
|
417
|
-
* ARKit/ARCore finds AND whose normal
|
|
418
|
-
* aligns with the camera (filtered by
|
|
419
|
-
* `alignmentThreshold`). Requires
|
|
420
|
-
* `captureSource === 'ar'`.
|
|
421
|
-
* • `'Virtual'` — synthesise a plane at a fixed depth
|
|
422
|
-
* (`virtualDepthMeters`) in front of the
|
|
423
|
-
* camera at first-frame pose. No
|
|
424
|
-
* ARKit dependency.
|
|
425
|
-
*/
|
|
426
|
-
source: 'Disabled' | 'ARKitDetected' | 'Virtual';
|
|
427
|
-
/**
|
|
428
|
-
* How frames are warped onto the plane. Only consulted when
|
|
429
|
-
* `source !== 'Disabled'`. Default `'Rectified'` for slit-scan.
|
|
430
|
-
*/
|
|
431
|
-
projectionStyle?: 'Trapezoidal' | 'Rectified';
|
|
432
|
-
/**
|
|
433
|
-
* Depth in metres for `source === 'Virtual'`. Range `[0.3, 5.0]`,
|
|
434
|
-
* default 1.5. Set close to the actual shelf distance for the
|
|
435
|
-
* cleanest projection.
|
|
436
|
-
*/
|
|
437
|
-
virtualDepthMeters?: number;
|
|
438
|
-
/**
|
|
439
|
-
* Minimum `|planeNormal · cameraForward|` for an ARKit-detected
|
|
440
|
-
* plane to be accepted (when `source === 'ARKitDetected'`).
|
|
441
|
-
* Range `[0, 1]`, default 0.6 (≈ 53° max off-axis). Higher =
|
|
442
|
-
* stricter, only accept very-on-axis planes.
|
|
443
|
-
*/
|
|
444
|
-
alignmentThreshold?: number;
|
|
445
|
-
}
|
|
446
|
-
export declare const DEFAULT_SLITSCAN_SETTINGS: SlitscanSettings;
|
|
447
|
-
/**
|
|
448
|
-
* Settings for the hybrid live-compositing engine
|
|
449
|
-
* (`incremental.start({ engine: 'hybrid', ... })`). Most consumers
|
|
450
|
-
* won't touch this — the hybrid engine is RetaiLens-specific and
|
|
451
|
-
* the public lib's batch-keyframe pipeline is a better fit for
|
|
452
|
-
* general-purpose captures. Exported here for completeness.
|
|
453
|
-
*
|
|
454
|
-
* Important: the hybrid engine has internal preset paths
|
|
455
|
-
* (`OpenCVIncrementalStitcher.mm:139-180`) that hard-set
|
|
456
|
-
* `enableTriangulation`, `enable2dNcc`, `enableRansacHomography`,
|
|
457
|
-
* `planeSource = Disabled`, etc. Code-reviewer flagged that
|
|
458
|
-
* exposing those fields would be misleading — the engine clobbers
|
|
459
|
-
* any overrides. So this type is intentionally minimal: only
|
|
460
|
-
* `projection` is reliably operator-tunable. Hosts that need to
|
|
461
|
-
* reach deeper-level hybrid knobs can pass a raw config dict to
|
|
462
|
-
* `incremental.start()` directly (Layer 2 escape hatch).
|
|
463
|
-
*/
|
|
464
|
-
export interface HybridSettings extends CaptureBaseSettings {
|
|
465
|
-
/**
|
|
466
|
-
* Internal projection during real-time compositing. Independent
|
|
467
|
-
* from the panorama-stitcher's warperType (which doesn't apply
|
|
468
|
-
* to the hybrid engine — its output is the live canvas directly).
|
|
469
|
-
*
|
|
470
|
-
* Note: only effective in the rotation-only preset path (hybrid
|
|
471
|
-
* preset 1). In the other hybrid presets the engine forces
|
|
472
|
-
* Planar internally regardless of this setting. Native source:
|
|
473
|
-
* `OpenCVIncrementalStitcher.mm:146,161,180`.
|
|
474
|
-
*/
|
|
475
|
-
projection: 'Cylindrical' | 'Planar';
|
|
476
|
-
}
|
|
477
|
-
export declare const DEFAULT_HYBRID_SETTINGS: HybridSettings;
|
|
478
265
|
//# sourceMappingURL=PanoramaSettings.d.ts.map
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* by-field mapping.
|
|
48
48
|
*/
|
|
49
49
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
-
exports.
|
|
50
|
+
exports.DEFAULT_PANORAMA_SETTINGS = exports.DEFAULT_FLOW_GATE_SETTINGS = void 0;
|
|
51
51
|
/**
|
|
52
52
|
* Canonical FlowGateSettings defaults, exported as a standalone
|
|
53
53
|
* constant so consumers (the bridge, the modal, prop translators)
|
|
@@ -81,40 +81,18 @@ exports.DEFAULT_PANORAMA_SETTINGS = {
|
|
|
81
81
|
warperType: 'plane',
|
|
82
82
|
blenderType: 'multiband',
|
|
83
83
|
seamFinderType: 'graphcut',
|
|
84
|
+
// v0.15 — inscribed-rect crop is OFF by default (bbox crop keeps all
|
|
85
|
+
// stitched content). Opt in with `maxInscribedRectCrop={true}` (or toggle
|
|
86
|
+
// it on in settings) for a clean-cornered rectangle — but it can shrink the
|
|
87
|
+
// output a lot on lopsided / ultra-wide masks, which is why it's opt-in.
|
|
84
88
|
enableMaxInscribedRectCrop: false,
|
|
85
89
|
},
|
|
86
90
|
frameSelection: {
|
|
87
91
|
mode: 'flow-based',
|
|
88
92
|
maxKeyframes: 6,
|
|
89
93
|
overlapThreshold: 0.20,
|
|
94
|
+
maxKeyframeIntervalMs: 2000,
|
|
90
95
|
flow: exports.DEFAULT_FLOW_GATE_SETTINGS,
|
|
91
96
|
},
|
|
92
97
|
};
|
|
93
|
-
exports.DEFAULT_SLITSCAN_SETTINGS = {
|
|
94
|
-
captureSource: 'ar',
|
|
95
|
-
debug: false,
|
|
96
|
-
variant: 'slitscan-rotate',
|
|
97
|
-
painting: {
|
|
98
|
-
paintMode: 'FirstPaintedWins',
|
|
99
|
-
sliverPosition: 'Bottom',
|
|
100
|
-
firstFrameFullFrame: true,
|
|
101
|
-
},
|
|
102
|
-
registration: {
|
|
103
|
-
enableTriangulation: false,
|
|
104
|
-
enableTriAccumulator: false,
|
|
105
|
-
enableRansacHomography: false,
|
|
106
|
-
// ncc1d / ncc2d omitted — both disabled by default.
|
|
107
|
-
},
|
|
108
|
-
plane: {
|
|
109
|
-
source: 'ARKitDetected',
|
|
110
|
-
projectionStyle: 'Rectified',
|
|
111
|
-
virtualDepthMeters: 1.5,
|
|
112
|
-
alignmentThreshold: 0.6,
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
exports.DEFAULT_HYBRID_SETTINGS = {
|
|
116
|
-
captureSource: 'ar',
|
|
117
|
-
debug: false,
|
|
118
|
-
projection: 'Planar',
|
|
119
|
-
};
|
|
120
98
|
//# sourceMappingURL=PanoramaSettings.js.map
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* SlitscanSettings or HybridSettings call the matching adapter
|
|
40
40
|
* before reaching `incremental.start()`.
|
|
41
41
|
*/
|
|
42
|
-
import { type PanoramaSettings
|
|
42
|
+
import { type PanoramaSettings } from './PanoramaSettings';
|
|
43
43
|
/**
|
|
44
44
|
* Flat config dictionary type — what the native bridges expect.
|
|
45
45
|
* Indexed by the native-side key name; values are platform-
|
|
@@ -58,27 +58,4 @@ export type NativeConfigDict = Record<string, boolean | number | string>;
|
|
|
58
58
|
* - Android `IncrementalStitcher.kt:280-430` (batch path)
|
|
59
59
|
*/
|
|
60
60
|
export declare function panoramaSettingsToNativeConfig(s: PanoramaSettings): NativeConfigDict;
|
|
61
|
-
/**
|
|
62
|
-
* Convert a v0.4 SlitscanSettings tree into the flat dict the
|
|
63
|
-
* slit-scan / firstwins native engines read. Handles the
|
|
64
|
-
* "presence-as-enable" boolean expansion: a non-undefined
|
|
65
|
-
* `registration.ncc1d` means `enable1dNcc: true` on the wire,
|
|
66
|
-
* with the sub-object's `searchRadius` carried alongside.
|
|
67
|
-
*
|
|
68
|
-
* Verified against:
|
|
69
|
-
* - iOS `IncrementalStitcher.swift:1006-1100` (applyConfigOverrides)
|
|
70
|
-
* - iOS `OpenCVSlitScanStitcher.mm` (all numbered references in
|
|
71
|
-
* the audit ground-truth matrix)
|
|
72
|
-
*/
|
|
73
|
-
export declare function slitscanSettingsToNativeConfig(s: SlitscanSettings): NativeConfigDict;
|
|
74
|
-
/**
|
|
75
|
-
* Convert a v0.4 HybridSettings tree into the flat dict the hybrid
|
|
76
|
-
* engine reads. Minimal surface — hybrid presets internally clobber
|
|
77
|
-
* almost everything; see HybridSettings JSDoc for context.
|
|
78
|
-
*
|
|
79
|
-
* Verified against:
|
|
80
|
-
* - iOS `OpenCVIncrementalStitcher.mm:139-180` (preset paths)
|
|
81
|
-
* - iOS `IncrementalStitcher.swift:1034-1040` (hybridProjection override)
|
|
82
|
-
*/
|
|
83
|
-
export declare function hybridSettingsToNativeConfig(s: HybridSettings): NativeConfigDict;
|
|
84
61
|
//# sourceMappingURL=PanoramaSettingsBridge.d.ts.map
|
|
@@ -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
|