react-native-image-stitcher 0.1.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 +96 -0
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +189 -0
- package/RNImageStitcher.podspec +76 -0
- package/android/build.gradle +224 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/cpp/CMakeLists.txt +124 -0
- package/android/src/main/cpp/image_stitcher_jni.cpp +145 -0
- package/android/src/main/cpp/keyframe_gate_jni.cpp +204 -0
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +426 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +960 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2371 -0
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +256 -0
- package/android/src/main/java/io/imagestitcher/rn/QualityChecker.kt +167 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +39 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +558 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +35 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +784 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/BackgroundRenderer.kt +176 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/ShaderUtil.kt +67 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +201 -0
- package/cpp/ar_frame_pose.h +63 -0
- package/cpp/keyframe_gate.cpp +927 -0
- package/cpp/keyframe_gate.hpp +240 -0
- package/cpp/stitcher.cpp +2207 -0
- package/cpp/stitcher.hpp +275 -0
- package/dist/ar/useARSession.d.ts +102 -0
- package/dist/ar/useARSession.js +133 -0
- package/dist/camera/ARCameraView.d.ts +93 -0
- package/dist/camera/ARCameraView.js +170 -0
- package/dist/camera/Camera.d.ts +134 -0
- package/dist/camera/Camera.js +688 -0
- package/dist/camera/CameraShutter.d.ts +80 -0
- package/dist/camera/CameraShutter.js +237 -0
- package/dist/camera/CameraView.d.ts +65 -0
- package/dist/camera/CameraView.js +117 -0
- package/dist/camera/CaptureControlsBar.d.ts +87 -0
- package/dist/camera/CaptureControlsBar.js +82 -0
- package/dist/camera/CaptureHeader.d.ts +62 -0
- package/dist/camera/CaptureHeader.js +81 -0
- package/dist/camera/CapturePreview.d.ts +70 -0
- package/dist/camera/CapturePreview.js +188 -0
- package/dist/camera/CaptureStatusOverlay.d.ts +75 -0
- package/dist/camera/CaptureStatusOverlay.js +326 -0
- package/dist/camera/CaptureThumbnailStrip.d.ts +87 -0
- package/dist/camera/CaptureThumbnailStrip.js +177 -0
- package/dist/camera/IncrementalPanGuide.d.ts +83 -0
- package/dist/camera/IncrementalPanGuide.js +267 -0
- package/dist/camera/PanoramaBandOverlay.d.ts +107 -0
- package/dist/camera/PanoramaBandOverlay.js +399 -0
- package/dist/camera/PanoramaConfirmModal.d.ts +57 -0
- package/dist/camera/PanoramaConfirmModal.js +128 -0
- package/dist/camera/PanoramaGuidance.d.ts +79 -0
- package/dist/camera/PanoramaGuidance.js +246 -0
- package/dist/camera/PanoramaSettingsModal.d.ts +311 -0
- package/dist/camera/PanoramaSettingsModal.js +611 -0
- package/dist/camera/ViewportCropOverlay.d.ts +46 -0
- package/dist/camera/ViewportCropOverlay.js +67 -0
- package/dist/camera/useCapture.d.ts +111 -0
- package/dist/camera/useCapture.js +160 -0
- package/dist/camera/useDeviceOrientation.d.ts +48 -0
- package/dist/camera/useDeviceOrientation.js +131 -0
- package/dist/camera/useVideoCapture.d.ts +79 -0
- package/dist/camera/useVideoCapture.js +151 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +39 -0
- package/dist/quality/normaliseOrientation.d.ts +36 -0
- package/dist/quality/normaliseOrientation.js +62 -0
- package/dist/quality/runQualityCheck.d.ts +41 -0
- package/dist/quality/runQualityCheck.js +98 -0
- package/dist/sensors/useIMUTranslationGate.d.ts +70 -0
- package/dist/sensors/useIMUTranslationGate.js +235 -0
- package/dist/stitching/IncrementalStitcherView.d.ts +41 -0
- package/dist/stitching/IncrementalStitcherView.js +157 -0
- package/dist/stitching/incremental.d.ts +930 -0
- package/dist/stitching/incremental.js +133 -0
- package/dist/stitching/stitchFrames.d.ts +55 -0
- package/dist/stitching/stitchFrames.js +56 -0
- package/dist/stitching/stitchVideo.d.ts +119 -0
- package/dist/stitching/stitchVideo.js +57 -0
- package/dist/stitching/useIncrementalJSDriver.d.ts +74 -0
- package/dist/stitching/useIncrementalJSDriver.js +199 -0
- package/dist/stitching/useIncrementalStitcher.d.ts +58 -0
- package/dist/stitching/useIncrementalStitcher.js +172 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +15 -0
- package/ios/Package.swift +72 -0
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +33 -0
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +40 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.m +55 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +149 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +2727 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +85 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +625 -0
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +328 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +141 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +278 -0
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +473 -0
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +1326 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +97 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +296 -0
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +103 -0
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +3285 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +238 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +1880 -0
- package/ios/Sources/RNImageStitcher/QualityChecker.swift +252 -0
- package/ios/Sources/RNImageStitcher/QualityCheckerBridge.m +26 -0
- package/ios/Sources/RNImageStitcher/QualityCheckerBridge.swift +72 -0
- package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +114 -0
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +1111 -0
- package/ios/Sources/RNImageStitcher/Stitcher.swift +243 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +28 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +246 -0
- package/package.json +73 -0
- package/react-native.config.js +34 -0
- package/scripts/opencv-version.txt +1 -0
- package/scripts/postinstall-fetch-binaries.js +286 -0
- package/src/ar/useARSession.ts +210 -0
- package/src/camera/.gitkeep +0 -0
- package/src/camera/ARCameraView.tsx +256 -0
- package/src/camera/Camera.tsx +1053 -0
- package/src/camera/CameraShutter.tsx +292 -0
- package/src/camera/CameraView.tsx +157 -0
- package/src/camera/CaptureControlsBar.tsx +204 -0
- package/src/camera/CaptureHeader.tsx +184 -0
- package/src/camera/CapturePreview.tsx +318 -0
- package/src/camera/CaptureStatusOverlay.tsx +391 -0
- package/src/camera/CaptureThumbnailStrip.tsx +277 -0
- package/src/camera/IncrementalPanGuide.tsx +328 -0
- package/src/camera/PanoramaBandOverlay.tsx +498 -0
- package/src/camera/PanoramaConfirmModal.tsx +206 -0
- package/src/camera/PanoramaGuidance.tsx +327 -0
- package/src/camera/PanoramaSettingsModal.tsx +1357 -0
- package/src/camera/ViewportCropOverlay.tsx +81 -0
- package/src/camera/useCapture.ts +279 -0
- package/src/camera/useDeviceOrientation.ts +140 -0
- package/src/camera/useVideoCapture.ts +236 -0
- package/src/index.ts +53 -0
- package/src/quality/.gitkeep +0 -0
- package/src/quality/normaliseOrientation.ts +79 -0
- package/src/quality/runQualityCheck.ts +131 -0
- package/src/sensors/useIMUTranslationGate.ts +347 -0
- package/src/stitching/.gitkeep +0 -0
- package/src/stitching/IncrementalStitcherView.tsx +198 -0
- package/src/stitching/incremental.ts +1021 -0
- package/src/stitching/stitchFrames.ts +88 -0
- package/src/stitching/stitchVideo.ts +153 -0
- package/src/stitching/useIncrementalJSDriver.ts +273 -0
- package/src/stitching/useIncrementalStitcher.ts +252 -0
- package/src/types.ts +78 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* PanoramaGuidance — gyroscope-driven pan-speed indicator for the
|
|
5
|
+
* tap-and-hold panorama flow.
|
|
6
|
+
*
|
|
7
|
+
* ┌──────────────────────────────────────────────────────────┐
|
|
8
|
+
* │ (camera preview) │
|
|
9
|
+
* │ │
|
|
10
|
+
* │ ↓ │ ← portrait + landscape pan
|
|
11
|
+
* │ green / yellow / red │
|
|
12
|
+
* │ │
|
|
13
|
+
* │ "Pan slowly" / "Slow down" / "Too fast" │
|
|
14
|
+
* └──────────────────────────────────────────────────────────┘
|
|
15
|
+
*
|
|
16
|
+
* Why this exists
|
|
17
|
+
* The SCANS-mode stitcher needs ~30–50 % overlap between
|
|
18
|
+
* consecutive frames. At 30 fps, frames are ~33 ms apart, so
|
|
19
|
+
* pan rates above roughly 30°/s (≈ 0.5 rad/s) produce frames
|
|
20
|
+
* the stitcher can't align — and the user finds out only after
|
|
21
|
+
* the post-release "Stitching failed" alert. Real-time feedback
|
|
22
|
+
* prevents that failure mode.
|
|
23
|
+
*
|
|
24
|
+
* What it does
|
|
25
|
+
* - Subscribes to the device gyroscope (react-native-sensors)
|
|
26
|
+
* ONLY while `active` is true; tears down on inactive so the
|
|
27
|
+
* sensor isn't running the rest of the time the screen is up.
|
|
28
|
+
* - Detects portrait vs landscape from window dimensions; the
|
|
29
|
+
* dominant pan axis changes accordingly:
|
|
30
|
+
* portrait → user pans horizontally → we track gyro Y.
|
|
31
|
+
* landscape → user pans vertically → we track gyro X.
|
|
32
|
+
* - Maps the dominant axis's |rad/s| onto a colour scale and a
|
|
33
|
+
* human-readable hint. Defaults are tuned for SCANS but
|
|
34
|
+
* overrideable.
|
|
35
|
+
*
|
|
36
|
+
* Performance
|
|
37
|
+
* The gyroscope fires ~30 Hz. We update an Animated.Value (which
|
|
38
|
+
* updates the colour interpolation on the native driver) and only
|
|
39
|
+
* call setState when the qualitative bucket (good/warn/bad)
|
|
40
|
+
* changes — keeps re-render volume low.
|
|
41
|
+
*/
|
|
42
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
43
|
+
if (k2 === undefined) k2 = k;
|
|
44
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
45
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
46
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
47
|
+
}
|
|
48
|
+
Object.defineProperty(o, k2, desc);
|
|
49
|
+
}) : (function(o, m, k, k2) {
|
|
50
|
+
if (k2 === undefined) k2 = k;
|
|
51
|
+
o[k2] = m[k];
|
|
52
|
+
}));
|
|
53
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
54
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
55
|
+
}) : function(o, v) {
|
|
56
|
+
o["default"] = v;
|
|
57
|
+
});
|
|
58
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
59
|
+
var ownKeys = function(o) {
|
|
60
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
61
|
+
var ar = [];
|
|
62
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
63
|
+
return ar;
|
|
64
|
+
};
|
|
65
|
+
return ownKeys(o);
|
|
66
|
+
};
|
|
67
|
+
return function (mod) {
|
|
68
|
+
if (mod && mod.__esModule) return mod;
|
|
69
|
+
var result = {};
|
|
70
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
71
|
+
__setModuleDefault(result, mod);
|
|
72
|
+
return result;
|
|
73
|
+
};
|
|
74
|
+
})();
|
|
75
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
76
|
+
exports.PanoramaGuidance = PanoramaGuidance;
|
|
77
|
+
const react_1 = __importStar(require("react"));
|
|
78
|
+
const react_native_1 = require("react-native");
|
|
79
|
+
const react_native_sensors_1 = require("react-native-sensors");
|
|
80
|
+
const useDeviceOrientation_1 = require("./useDeviceOrientation");
|
|
81
|
+
const DEFAULT_GOOD = 0.5;
|
|
82
|
+
const DEFAULT_WARN = 1.0;
|
|
83
|
+
const COLOR_GOOD = '#34C759';
|
|
84
|
+
const COLOR_WARN = '#FFCC00';
|
|
85
|
+
const COLOR_BAD = '#FF3B30';
|
|
86
|
+
const DEFAULT_MESSAGES = {
|
|
87
|
+
good: 'Good pace — keep going',
|
|
88
|
+
warn: 'Slow down a bit',
|
|
89
|
+
bad: 'Too fast — slow down',
|
|
90
|
+
};
|
|
91
|
+
function bucketFor(rate, good, warn) {
|
|
92
|
+
const abs = Math.abs(rate);
|
|
93
|
+
if (abs <= good)
|
|
94
|
+
return 'good';
|
|
95
|
+
if (abs <= warn)
|
|
96
|
+
return 'warn';
|
|
97
|
+
return 'bad';
|
|
98
|
+
}
|
|
99
|
+
function colorFor(bucket) {
|
|
100
|
+
switch (bucket) {
|
|
101
|
+
case 'good':
|
|
102
|
+
return COLOR_GOOD;
|
|
103
|
+
case 'warn':
|
|
104
|
+
return COLOR_WARN;
|
|
105
|
+
case 'bad':
|
|
106
|
+
return COLOR_BAD;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function PanoramaGuidance({ active, axis, goodMaxRadPerSec = DEFAULT_GOOD, warnMaxRadPerSec = DEFAULT_WARN, messages, style, }) {
|
|
110
|
+
// Use the accelerometer-based hook (NOT useWindowDimensions) so
|
|
111
|
+
// we detect physical orientation even though the app is
|
|
112
|
+
// portrait-locked at the OS level.
|
|
113
|
+
const deviceOrientation = (0, useDeviceOrientation_1.useDeviceOrientation)();
|
|
114
|
+
const isPortrait = deviceOrientation === 'portrait'
|
|
115
|
+
|| deviceOrientation === 'portrait-upside-down';
|
|
116
|
+
// Auto-detect: in portrait the user pans horizontally
|
|
117
|
+
// (left↔right across the rack) → gyro Y axis dominates.
|
|
118
|
+
// In landscape the user pans vertically (up↕down a tall fixture)
|
|
119
|
+
// → gyro X axis dominates.
|
|
120
|
+
const resolvedAxis = axis ?? (isPortrait ? 'horizontal' : 'vertical');
|
|
121
|
+
// Qualitative bucket — drives both the message and (via colour)
|
|
122
|
+
// the arrow tint. Stored in state so a *change* in bucket
|
|
123
|
+
// re-renders, but per-sample updates do NOT.
|
|
124
|
+
const [bucket, setBucket] = (0, react_1.useState)('good');
|
|
125
|
+
// Last known rotation rate for the dominant axis, kept in a ref
|
|
126
|
+
// to avoid re-rendering every sample. Read by the bucket logic
|
|
127
|
+
// and (indirectly, via colour interpolation) the animated tint.
|
|
128
|
+
const lastBucketRef = (0, react_1.useRef)('good');
|
|
129
|
+
(0, react_1.useEffect)(() => {
|
|
130
|
+
if (!active) {
|
|
131
|
+
lastBucketRef.current = 'good';
|
|
132
|
+
setBucket('good');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Sample at 33 ms (~30 Hz) — matches the typical recording
|
|
136
|
+
// frame rate so each gyro sample maps to one frame's pan.
|
|
137
|
+
(0, react_native_sensors_1.setUpdateIntervalForType)(react_native_sensors_1.SensorTypes.gyroscope, 33);
|
|
138
|
+
let subscription = react_native_sensors_1.gyroscope.subscribe({
|
|
139
|
+
next: ({ x, y }) => {
|
|
140
|
+
const rate = resolvedAxis === 'horizontal' ? y : x;
|
|
141
|
+
const next = bucketFor(rate, goodMaxRadPerSec, warnMaxRadPerSec);
|
|
142
|
+
if (next !== lastBucketRef.current) {
|
|
143
|
+
lastBucketRef.current = next;
|
|
144
|
+
setBucket(next);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
error: (err) => {
|
|
148
|
+
// eslint-disable-next-line no-console
|
|
149
|
+
console.warn('[PanoramaGuidance] gyroscope error', err);
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
return () => {
|
|
153
|
+
subscription?.unsubscribe();
|
|
154
|
+
subscription = null;
|
|
155
|
+
};
|
|
156
|
+
}, [active, resolvedAxis, goodMaxRadPerSec, warnMaxRadPerSec]);
|
|
157
|
+
const resolvedMessages = (0, react_1.useMemo)(() => ({
|
|
158
|
+
good: messages?.good ?? DEFAULT_MESSAGES.good,
|
|
159
|
+
warn: messages?.warn ?? DEFAULT_MESSAGES.warn,
|
|
160
|
+
bad: messages?.bad ?? DEFAULT_MESSAGES.bad,
|
|
161
|
+
}), [messages]);
|
|
162
|
+
if (!active)
|
|
163
|
+
return null;
|
|
164
|
+
const tint = colorFor(bucket);
|
|
165
|
+
const message = resolvedMessages[bucket];
|
|
166
|
+
// Arrow glyph for the dominant axis. The arrow renders a
|
|
167
|
+
// pannable direction hint — landscape gets a vertical arrow
|
|
168
|
+
// (the user's panning that way), portrait gets a horizontal
|
|
169
|
+
// arrow.
|
|
170
|
+
const arrow = resolvedAxis === 'horizontal' ? '↔' : '↕';
|
|
171
|
+
// Place the pill at user-perceived bottom across all four
|
|
172
|
+
// orientations. Same pattern as <CaptureStatusOverlay> — the
|
|
173
|
+
// app layout is portrait-locked so we re-position via absolute
|
|
174
|
+
// coords + apply a rotation transform.
|
|
175
|
+
const pillOrientationStyle = pillStyleForOrientation(deviceOrientation);
|
|
176
|
+
return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: [styles.root, pillOrientationStyle, style], accessibilityRole: "alert", accessibilityLiveRegion: "polite" },
|
|
177
|
+
react_1.default.createElement(react_native_1.View, { style: [styles.pill, { borderColor: tint }] },
|
|
178
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.arrow, { color: tint }] }, arrow),
|
|
179
|
+
react_1.default.createElement(react_native_1.Text, { style: [styles.message, { color: tint }], numberOfLines: 1 }, message))));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Mirror of bannerStyleForOrientation in CaptureStatusOverlay,
|
|
183
|
+
* but anchored at user-perceived BOTTOM instead of TOP.
|
|
184
|
+
*/
|
|
185
|
+
function pillStyleForOrientation(orientation) {
|
|
186
|
+
switch (orientation) {
|
|
187
|
+
case 'landscape-left':
|
|
188
|
+
return {
|
|
189
|
+
top: 0,
|
|
190
|
+
bottom: 0,
|
|
191
|
+
left: 8,
|
|
192
|
+
alignItems: 'flex-start',
|
|
193
|
+
justifyContent: 'center',
|
|
194
|
+
transform: [{ rotate: '90deg' }],
|
|
195
|
+
};
|
|
196
|
+
case 'landscape-right':
|
|
197
|
+
return {
|
|
198
|
+
top: 0,
|
|
199
|
+
bottom: 0,
|
|
200
|
+
right: 8,
|
|
201
|
+
alignItems: 'flex-end',
|
|
202
|
+
justifyContent: 'center',
|
|
203
|
+
transform: [{ rotate: '-90deg' }],
|
|
204
|
+
};
|
|
205
|
+
case 'portrait-upside-down':
|
|
206
|
+
return {
|
|
207
|
+
top: 24,
|
|
208
|
+
left: 0,
|
|
209
|
+
right: 0,
|
|
210
|
+
alignItems: 'center',
|
|
211
|
+
transform: [{ rotate: '180deg' }],
|
|
212
|
+
};
|
|
213
|
+
case 'portrait':
|
|
214
|
+
default:
|
|
215
|
+
return {
|
|
216
|
+
bottom: 24,
|
|
217
|
+
left: 0,
|
|
218
|
+
right: 0,
|
|
219
|
+
alignItems: 'center',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const styles = react_native_1.StyleSheet.create({
|
|
224
|
+
root: {
|
|
225
|
+
position: 'absolute',
|
|
226
|
+
},
|
|
227
|
+
pill: {
|
|
228
|
+
flexDirection: 'row',
|
|
229
|
+
alignItems: 'center',
|
|
230
|
+
paddingHorizontal: 14,
|
|
231
|
+
paddingVertical: 8,
|
|
232
|
+
borderRadius: 24,
|
|
233
|
+
borderWidth: 2,
|
|
234
|
+
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
235
|
+
},
|
|
236
|
+
arrow: {
|
|
237
|
+
fontSize: 22,
|
|
238
|
+
fontWeight: '700',
|
|
239
|
+
marginRight: 8,
|
|
240
|
+
},
|
|
241
|
+
message: {
|
|
242
|
+
fontSize: 14,
|
|
243
|
+
fontWeight: '600',
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
//# sourceMappingURL=PanoramaGuidance.js.map
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PanoramaSettingsModal — runtime A/B testing surface for the
|
|
3
|
+
* stitcher pipeline. Operators in the field can toggle warper,
|
|
4
|
+
* blender, and tuning constants between captures to see what
|
|
5
|
+
* looks best on real shelf scenes.
|
|
6
|
+
*
|
|
7
|
+
* The modal is presentational: the host owns the settings state
|
|
8
|
+
* (typically `useState<PanoramaSettings>`) and renders the modal
|
|
9
|
+
* with `visible` toggled by a gear-icon press in the capture
|
|
10
|
+
* header. Settings flow OUT via `onChange` for each tweak.
|
|
11
|
+
*
|
|
12
|
+
* Why expose this as an SDK component instead of leaving it to
|
|
13
|
+
* each host? The set of tunable knobs IS the SDK's contract —
|
|
14
|
+
* if a new setting is added (e.g. registration MP) the SDK ships
|
|
15
|
+
* the UI for it in lockstep with the param itself, instead of
|
|
16
|
+
* forcing every host app to update its settings screen.
|
|
17
|
+
*/
|
|
18
|
+
import React from 'react';
|
|
19
|
+
export interface PanoramaSettings {
|
|
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
|
+
* iOS NOTE: as of 2026-05-14 the iOS stitcher uses a hand-rolled
|
|
296
|
+
* PANORAMA-style pipeline (OpenCVStitcher.mm:600+) regardless of
|
|
297
|
+
* this setting. Setting is passed through to iOS but ignored.
|
|
298
|
+
* Android honours it via image_stitcher_jni.cpp. Bridging iOS is
|
|
299
|
+
* a follow-up.
|
|
300
|
+
*/
|
|
301
|
+
stitchMode: 'auto' | 'panorama' | 'scans';
|
|
302
|
+
}
|
|
303
|
+
export declare const DEFAULT_PANORAMA_SETTINGS: PanoramaSettings;
|
|
304
|
+
export interface PanoramaSettingsModalProps {
|
|
305
|
+
visible: boolean;
|
|
306
|
+
settings: PanoramaSettings;
|
|
307
|
+
onChange: (next: PanoramaSettings) => void;
|
|
308
|
+
onClose: () => void;
|
|
309
|
+
}
|
|
310
|
+
export declare function PanoramaSettingsModal({ visible, settings, onChange, onClose, }: PanoramaSettingsModalProps): React.JSX.Element;
|
|
311
|
+
//# sourceMappingURL=PanoramaSettingsModal.d.ts.map
|