react-native-image-stitcher 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +195 -0
- package/dist/camera/Camera.js +70 -58
- package/dist/camera/PanoramaSettings.d.ts +478 -0
- package/dist/camera/PanoramaSettings.js +120 -0
- package/dist/camera/PanoramaSettingsBridge.d.ts +84 -0
- package/dist/camera/PanoramaSettingsBridge.js +208 -0
- package/dist/camera/PanoramaSettingsModal.d.ts +50 -299
- package/dist/camera/PanoramaSettingsModal.js +189 -354
- package/dist/camera/buildPanoramaInitialSettings.d.ts +70 -0
- package/dist/camera/buildPanoramaInitialSettings.js +97 -0
- package/dist/camera/lowMemDevice.d.ts +24 -0
- package/dist/camera/lowMemDevice.js +69 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +23 -2
- package/package.json +6 -2
- package/src/camera/Camera.tsx +79 -71
- package/src/camera/PanoramaSettings.ts +605 -0
- package/src/camera/PanoramaSettingsBridge.ts +238 -0
- package/src/camera/PanoramaSettingsModal.tsx +296 -989
- package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +375 -0
- package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +119 -0
- package/src/camera/__tests__/lowMemDevice.test.ts +52 -0
- package/src/camera/buildPanoramaInitialSettings.ts +139 -0
- package/src/camera/lowMemDevice.ts +71 -0
- package/src/index.ts +42 -3
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* PanoramaSettings (v0.4) — engine-discriminated, hierarchical
|
|
4
|
+
* settings types.
|
|
5
|
+
*
|
|
6
|
+
* Background
|
|
7
|
+
* ──────────
|
|
8
|
+
*
|
|
9
|
+
* Pre-v0.4 the lib exported a single flat `PanoramaSettings`
|
|
10
|
+
* interface with 45+ fields covering three unrelated stitching
|
|
11
|
+
* engines (batch-keyframe, hybrid, slit-scan). The 2026-05-22
|
|
12
|
+
* audit (CHANGELOG entry for v0.3.0) traced every field's actual
|
|
13
|
+
* native consumer and proved:
|
|
14
|
+
*
|
|
15
|
+
* • batch-keyframe and the live engines (hybrid + slit-scan)
|
|
16
|
+
* share **zero** settings — they read disjoint subsets of
|
|
17
|
+
* the flat interface.
|
|
18
|
+
* • ~10 fields had no native consumer at all (dead surface).
|
|
19
|
+
* • The <Camera> public component hardcodes `engine:
|
|
20
|
+
* 'batch-keyframe'` and never reaches the slit-scan / hybrid
|
|
21
|
+
* branches.
|
|
22
|
+
*
|
|
23
|
+
* v0.4 splits the flat interface into three engine-specific types:
|
|
24
|
+
*
|
|
25
|
+
* • `PanoramaSettings` — what <Camera> consumes (batch-keyframe).
|
|
26
|
+
* • `SlitscanSettings` — for Layer 2 hosts using the slit-scan
|
|
27
|
+
* engine (incremental.start({ engine:
|
|
28
|
+
* 'slitscan-*', ... })).
|
|
29
|
+
* • `HybridSettings` — for the RetaiLens-specific hybrid live
|
|
30
|
+
* engine. Exported for completeness;
|
|
31
|
+
* most consumers won't touch it.
|
|
32
|
+
*
|
|
33
|
+
* Each type carries only the fields its target engine actually
|
|
34
|
+
* reads. Sub-objects (`stitcher`, `frameSelection`, `flow`,
|
|
35
|
+
* `painting`, `registration`, `plane`, `ncc2d`, `emaSmoothing`,
|
|
36
|
+
* `panAxisLock`) group related knobs so the modal can render
|
|
37
|
+
* collapsible sections that match the type tree.
|
|
38
|
+
*
|
|
39
|
+
* Migration
|
|
40
|
+
* ─────────
|
|
41
|
+
*
|
|
42
|
+
* No automated migration helper. v0.4 is a clean break; the
|
|
43
|
+
* v0.3 `PanoramaSettings` type is deleted. Consumers (notably
|
|
44
|
+
* `retailens-camera-sdk`) update their settings literals to match
|
|
45
|
+
* the new shape. See the v0.4.0 CHANGELOG entry for the field-
|
|
46
|
+
* by-field mapping.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
51
|
+
// CaptureBaseSettings — fields common to ALL engine-specific types.
|
|
52
|
+
// Extracted in response to a code-reviewer DRY flag (3-way duplication
|
|
53
|
+
// across PanoramaSettings / SlitscanSettings / HybridSettings).
|
|
54
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
export interface CaptureBaseSettings {
|
|
57
|
+
/**
|
|
58
|
+
* Which camera + tracking source feeds the engine:
|
|
59
|
+
*
|
|
60
|
+
* • `'ar'` — ARKit (iOS) / ARCore (Android) session. Rich
|
|
61
|
+
* pose with real translation. Required for
|
|
62
|
+
* plane-projected slit-scan; recommended for
|
|
63
|
+
* batch-keyframe whenever the device supports
|
|
64
|
+
* it (the auto-resolver's translation signal
|
|
65
|
+
* comes from AR pose).
|
|
66
|
+
* • `'non-ar'` — vision-camera fallback. Gyro-integrated yaw
|
|
67
|
+
* + pitch only; no translation from pose. The
|
|
68
|
+
* JS-side IMU translation gate fills in the
|
|
69
|
+
* translation signal. Required on devices
|
|
70
|
+
* without ARKit/ARCore support.
|
|
71
|
+
*/
|
|
72
|
+
captureSource: 'ar' | 'non-ar';
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Show the lib's built-in diagnostic overlay (memory pill,
|
|
76
|
+
* keyframe pill, orientation pill, stitch-stats toast, detailed
|
|
77
|
+
* metrics block). Default `false` so end-users don't see them.
|
|
78
|
+
* Hosts that compose their own debug surface can leave this off
|
|
79
|
+
* and mount the individual `Capture*Pill` components themselves.
|
|
80
|
+
*/
|
|
81
|
+
debug: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
86
|
+
// PanoramaSettings — what <Camera> uses (batch-keyframe engine).
|
|
87
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Top-level settings for the standard panorama capture flow
|
|
91
|
+
* exposed by <Camera>. Engine is fixed to batch-keyframe internally;
|
|
92
|
+
* the only mode choice exposed at this level is `captureSource`
|
|
93
|
+
* (AR-backed vs vision-camera fallback) and the `stitcher` /
|
|
94
|
+
* `frameSelection` sub-trees.
|
|
95
|
+
*/
|
|
96
|
+
export interface PanoramaSettings extends CaptureBaseSettings {
|
|
97
|
+
/** cv::Stitcher pipeline configuration (applied at finalize). */
|
|
98
|
+
stitcher: BatchStitcherSettings;
|
|
99
|
+
|
|
100
|
+
/** Per-frame keyframe-selection gate configuration. */
|
|
101
|
+
frameSelection: FrameSelectionSettings;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* cv::Stitcher tuning — these knobs reach the C++ stitcher at
|
|
107
|
+
* `finalize()` time, after all keyframes are collected. They have
|
|
108
|
+
* no effect on per-frame selection.
|
|
109
|
+
*/
|
|
110
|
+
export interface BatchStitcherSettings {
|
|
111
|
+
/**
|
|
112
|
+
* cv::Stitcher pipeline mode.
|
|
113
|
+
*
|
|
114
|
+
* • `'auto'` (default) — engine looks at the
|
|
115
|
+
* translation/rotation ratio between first + last accepted
|
|
116
|
+
* keyframe poses (and, in non-AR mode, the IMU translation
|
|
117
|
+
* accumulator) and picks `'panorama'` or `'scans'` at
|
|
118
|
+
* finalize.
|
|
119
|
+
* • `'panorama'` — rotation-only pipeline (ORB + BA-Ray +
|
|
120
|
+
* SphericalWarper). Best for "rotate phone in place" pans.
|
|
121
|
+
* Diverges on translation-heavy input.
|
|
122
|
+
* • `'scans'` — affine pipeline (Affine matcher + BA-Affine +
|
|
123
|
+
* PlaneWarper). Best for "walk past a shelf" captures.
|
|
124
|
+
* Slight quality drop on pure rotation, never diverges.
|
|
125
|
+
*
|
|
126
|
+
* Both platforms now honour this and the auto-resolver. Both
|
|
127
|
+
* also retry with the OPPOSITE mode if the configured mode
|
|
128
|
+
* produces degenerate camera params (warpRoi too large).
|
|
129
|
+
*/
|
|
130
|
+
stitchMode: 'auto' | 'panorama' | 'scans';
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Output projection. PANORAMA mode uses this directly; SCANS
|
|
134
|
+
* hard-wires PlaneWarper internally and ignores this field.
|
|
135
|
+
*/
|
|
136
|
+
warperType: 'plane' | 'cylindrical' | 'spherical';
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Pixel blender for the warped frames. `'multiband'` produces
|
|
140
|
+
* cleaner seams but holds all warped frames in memory; `'feather'`
|
|
141
|
+
* streams and uses less peak memory.
|
|
142
|
+
*/
|
|
143
|
+
blenderType: 'multiband' | 'feather';
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Seam-finder strategy. `'graphcut'` finds optimal seams before
|
|
147
|
+
* blending (pair with multiband); `'skip'` streams warp+feed (pair
|
|
148
|
+
* with feather for the lowest-memory configuration).
|
|
149
|
+
*/
|
|
150
|
+
seamFinderType: 'graphcut' | 'skip';
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Output crop strategy. `false` (default) crops to the bounding
|
|
154
|
+
* rectangle of non-black pixels. `true` runs the
|
|
155
|
+
* max-inscribed-rectangle + morph-close pipeline — cleaner output
|
|
156
|
+
* with no black corners, more CPU at finalize.
|
|
157
|
+
*/
|
|
158
|
+
enableMaxInscribedRectCrop: boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* KeyframeGate tuning — these knobs control which incoming frames
|
|
164
|
+
* become keyframes. The mode selects the strategy (passthrough,
|
|
165
|
+
* plane-overlap, or sparse optical flow); the `flow` sub-tree is
|
|
166
|
+
* only consulted when `mode === 'flow-based'`.
|
|
167
|
+
*/
|
|
168
|
+
export interface FrameSelectionSettings {
|
|
169
|
+
/**
|
|
170
|
+
* Frame selection strategy:
|
|
171
|
+
*
|
|
172
|
+
* • `'time-based'` — gate disabled. Every JS-driver / AR
|
|
173
|
+
* frame becomes a keyframe up to
|
|
174
|
+
* `maxKeyframes`. Useful for testing or
|
|
175
|
+
* when the host wants to do its own
|
|
176
|
+
* keyframe selection upstream.
|
|
177
|
+
* • `'pose-based'` — plane-overlap novelty (when a plane is
|
|
178
|
+
* latched) or angular-delta fallback (no
|
|
179
|
+
* plane). Cheap to evaluate but conservative
|
|
180
|
+
* about pure-rotation motion.
|
|
181
|
+
* • `'flow-based'` — sparse Shi-Tomasi corners + KLT tracking.
|
|
182
|
+
* More expensive (~3–5 ms per AR frame on
|
|
183
|
+
* a Galaxy A35) but accurate for translation.
|
|
184
|
+
* The default for v0.3+.
|
|
185
|
+
*/
|
|
186
|
+
mode: 'time-based' | 'pose-based' | 'flow-based';
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Hard cap on accepted keyframes per capture. Clamped to
|
|
190
|
+
* `[3, 10]` natively. Higher is rarely useful: cv::Stitcher
|
|
191
|
+
* convergence degrades past ~8-10 frames, and the per-keyframe
|
|
192
|
+
* disk + memory cost adds up fast at 4K+ resolutions.
|
|
193
|
+
*/
|
|
194
|
+
maxKeyframes: number;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Required NEW-content fraction (0..1) for a candidate frame to
|
|
198
|
+
* be accepted. Default 0.20 = 20% novel content per accept.
|
|
199
|
+
* Lower = more frames accepted, larger panoramas. Higher = fewer
|
|
200
|
+
* frames, faster captures but more conservative about coverage.
|
|
201
|
+
* Clamped to `[0.10, 0.80]` natively
|
|
202
|
+
* (`IncrementalStitcher.swift:962`).
|
|
203
|
+
*/
|
|
204
|
+
overlapThreshold: number;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Sparse-optical-flow strategy tunables. Consulted only when
|
|
208
|
+
* `mode === 'flow-based'`; safe to omit otherwise. Defaults
|
|
209
|
+
* track [DEFAULT_PANORAMA_SETTINGS.frameSelection.flow].
|
|
210
|
+
*/
|
|
211
|
+
flow?: FlowGateSettings;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Sparse-flow KLT tuning for the gate. All ranges are enforced
|
|
217
|
+
* (clamped silently) at the native boundary.
|
|
218
|
+
*/
|
|
219
|
+
export interface FlowGateSettings {
|
|
220
|
+
/**
|
|
221
|
+
* Percentile used to aggregate the per-feature absolute
|
|
222
|
+
* displacements into a single per-axis novelty estimate. Default
|
|
223
|
+
* 0.85 (V16 change from the pre-V16 median of 0.50). Higher
|
|
224
|
+
* percentile picks up leading-edge motion sooner; lower is more
|
|
225
|
+
* conservative. Clamped to `[0.50, 0.99]`.
|
|
226
|
+
*/
|
|
227
|
+
noveltyPercentile: number;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Caller-side throttle: evaluate the Flow strategy every Nth
|
|
231
|
+
* frame instead of every frame. Default 5 (≈ 6 Hz at 30 Hz
|
|
232
|
+
* ARCore). Pure CPU savings; doesn't change WHICH frames are
|
|
233
|
+
* accepted. Clamped to `[1, 10]`.
|
|
234
|
+
*/
|
|
235
|
+
evalEveryNFrames: number;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Translation budget in centimetres. When > 0, the gate
|
|
239
|
+
* force-accepts the next frame after the operator has translated
|
|
240
|
+
* more than this distance since the last accepted keyframe — even
|
|
241
|
+
* when novelty is below `overlapThreshold`. Bounds parallax
|
|
242
|
+
* between adjacent keyframes so the stitcher's matcher sees
|
|
243
|
+
* inputs it can handle. Default 50. `0` disables. Clamped
|
|
244
|
+
* to `[0, 100]`.
|
|
245
|
+
*/
|
|
246
|
+
maxTranslationCm: number;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Shi-Tomasi corner count. Default 150; clamped to `[50, 300]`.
|
|
250
|
+
* Higher = more robust median, slower detect (~15-25 ms at 150
|
|
251
|
+
* on Galaxy A35).
|
|
252
|
+
*/
|
|
253
|
+
maxCorners: number;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Shi-Tomasi quality level. Default 0.01; clamped to
|
|
257
|
+
* `[0.005, 0.05]`. Lower lets weaker corners in (more candidate
|
|
258
|
+
* points, more KLT noise); higher demands stronger corners.
|
|
259
|
+
*/
|
|
260
|
+
qualityLevel: number;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Shi-Tomasi minimum distance between detected corners, in
|
|
264
|
+
* working-resolution pixels (the gate downscales the input
|
|
265
|
+
* internally to a 720-px-longest-side working frame). Default
|
|
266
|
+
* 10; clamped to `[1, 50]`. Higher = more spatially-spread
|
|
267
|
+
* features = more representative median.
|
|
268
|
+
*/
|
|
269
|
+
minDistance: number;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Canonical FlowGateSettings defaults, exported as a standalone
|
|
275
|
+
* constant so consumers (the bridge, the modal, prop translators)
|
|
276
|
+
* can reach the values WITHOUT typing
|
|
277
|
+
* `DEFAULT_PANORAMA_SETTINGS.frameSelection.flow!.X` — the
|
|
278
|
+
* non-null-assertion form is brittle (will start crashing at
|
|
279
|
+
* runtime the moment someone "cleans up" the default tree and
|
|
280
|
+
* makes `flow` undefined in `DEFAULT_PANORAMA_SETTINGS`). Lifted
|
|
281
|
+
* out 2026-05-22 in the F10 Phase 2 review (NIT-4).
|
|
282
|
+
*
|
|
283
|
+
* Numerical values mirror the v0.3 defaults; they're verified
|
|
284
|
+
* against the native engine's compiled-in fallback values
|
|
285
|
+
* (`IncrementalStitcher.swift:1003-1029`, `IncrementalStitcher.kt:419-445`)
|
|
286
|
+
* — discrepancies are flagged in the v0.3.0 audit and resolved by
|
|
287
|
+
* the bridge always-emitting these on the wire (see
|
|
288
|
+
* `PanoramaSettingsBridge.ts:panoramaSettingsToNativeConfig`).
|
|
289
|
+
*/
|
|
290
|
+
export const DEFAULT_FLOW_GATE_SETTINGS: FlowGateSettings = {
|
|
291
|
+
noveltyPercentile: 0.85,
|
|
292
|
+
evalEveryNFrames: 5,
|
|
293
|
+
maxTranslationCm: 50,
|
|
294
|
+
maxCorners: 150,
|
|
295
|
+
qualityLevel: 0.01,
|
|
296
|
+
minDistance: 10,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
export const DEFAULT_PANORAMA_SETTINGS: PanoramaSettings = {
|
|
301
|
+
captureSource: 'ar',
|
|
302
|
+
debug: false,
|
|
303
|
+
stitcher: {
|
|
304
|
+
stitchMode: 'auto',
|
|
305
|
+
warperType: 'plane',
|
|
306
|
+
blenderType: 'multiband',
|
|
307
|
+
seamFinderType: 'graphcut',
|
|
308
|
+
enableMaxInscribedRectCrop: false,
|
|
309
|
+
},
|
|
310
|
+
frameSelection: {
|
|
311
|
+
mode: 'flow-based',
|
|
312
|
+
maxKeyframes: 6,
|
|
313
|
+
overlapThreshold: 0.20,
|
|
314
|
+
flow: DEFAULT_FLOW_GATE_SETTINGS,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
320
|
+
// SlitscanSettings — Layer 2 hosts using the slit-scan engine.
|
|
321
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Settings for slit-scan stitching engines (`slitscan-rotate`,
|
|
325
|
+
* `slitscan-both`, `firstwins-rectilinear`). Reached via
|
|
326
|
+
* `incremental.start({ engine: '<variant>', config: { ... } })`,
|
|
327
|
+
* NOT via <Camera> (which always uses batch-keyframe). Each
|
|
328
|
+
* sub-tree corresponds to a section of the native `RLISStitcherConfig`
|
|
329
|
+
* the slit-scan engine reads at start.
|
|
330
|
+
*
|
|
331
|
+
* Field-by-field native consumer references are documented in
|
|
332
|
+
* `OpenCVSlitScanStitcher.mm` / `OpenCVIncrementalStitcher.h`.
|
|
333
|
+
*/
|
|
334
|
+
export interface SlitscanSettings extends CaptureBaseSettings {
|
|
335
|
+
/**
|
|
336
|
+
* Which slit-scan variant the engine runs. All three share the
|
|
337
|
+
* same painting + registration + plane configuration; they differ
|
|
338
|
+
* in their internal motion model (rotation-only vs combined
|
|
339
|
+
* translation+rotation, and slit position).
|
|
340
|
+
*
|
|
341
|
+
* • `'slitscan-rotate'` — preferred name; rotation-only
|
|
342
|
+
* motion model.
|
|
343
|
+
* • `'slitscan-both'` — combined translation + rotation
|
|
344
|
+
* motion model.
|
|
345
|
+
* • `'firstwins-rectilinear'` — legacy alias of
|
|
346
|
+
* `'slitscan-rotate'` (V13.0a naming). Accepted natively
|
|
347
|
+
* but new code should prefer the canonical name.
|
|
348
|
+
*/
|
|
349
|
+
variant: 'slitscan-rotate' | 'slitscan-both' | 'firstwins-rectilinear';
|
|
350
|
+
|
|
351
|
+
/** Where the per-accept slit is taken from + how it's blended. */
|
|
352
|
+
painting: SlitscanPaintingSettings;
|
|
353
|
+
|
|
354
|
+
/** Frame-to-frame registration (NCC + RANSAC + triangulation). */
|
|
355
|
+
registration: SlitscanRegistrationSettings;
|
|
356
|
+
|
|
357
|
+
/** Plane projection (ARKit-detected, virtual, or disabled). */
|
|
358
|
+
plane: PlaneProjectionSettings;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Advanced motion-tuning knobs that the v0.3 modal never exposed.
|
|
362
|
+
* Both are read by the native side
|
|
363
|
+
* (`IncrementalStitcher.swift:1074, 1077`) and have sensible
|
|
364
|
+
* defaults; most consumers can leave this field undefined.
|
|
365
|
+
*/
|
|
366
|
+
advanced?: SlitscanAdvancedSettings;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
export interface SlitscanAdvancedSettings {
|
|
371
|
+
/**
|
|
372
|
+
* Fraction of the pan-axis sensor extent used to compute the
|
|
373
|
+
* per-frame slit width. Range `[0.05, 0.90]`, default 0.70
|
|
374
|
+
* (engine internal). Higher = wider slits = fewer accepts per
|
|
375
|
+
* pan. Set this only if you know what the slit-scan motion
|
|
376
|
+
* model needs for your specific capture geometry.
|
|
377
|
+
* Native key: `kPanAxisFractionRect`.
|
|
378
|
+
*/
|
|
379
|
+
panAxisFractionRect?: number;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Minimum pan-axis delta (in canvas pixels) between consecutive
|
|
383
|
+
* accepted strips. Acts as a hard floor below which subsequent
|
|
384
|
+
* frames are rejected regardless of NCC scores. Range
|
|
385
|
+
* `[0, 500]`, default 0 (no floor). Native key:
|
|
386
|
+
* `kMinAcceptDeltaPx`.
|
|
387
|
+
*/
|
|
388
|
+
minAcceptDeltaPx?: number;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
export interface SlitscanPaintingSettings {
|
|
393
|
+
/**
|
|
394
|
+
* How new strips are blended into already-painted canvas pixels.
|
|
395
|
+
*
|
|
396
|
+
* • `'FirstPaintedWins'` (default) — preserve the first frame's
|
|
397
|
+
* content at any pixel; later strips don't overwrite.
|
|
398
|
+
* • `'FeatherBlend'` — alpha-blend new strips into
|
|
399
|
+
* already-painted areas at slit boundaries. Smooths visible
|
|
400
|
+
* seams when many narrow slits stack.
|
|
401
|
+
*/
|
|
402
|
+
paintMode: 'FirstPaintedWins' | 'FeatherBlend';
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Where on the camera frame the per-accept slit is sampled from.
|
|
406
|
+
* For a typical landscape vertical pan tilting DOWN, the leading
|
|
407
|
+
* edge (new content) is at the BOTTOM of the camera frame; for
|
|
408
|
+
* upward tilt, it's at the TOP. `'Center'` is the V13.x default.
|
|
409
|
+
*/
|
|
410
|
+
sliverPosition: 'Center' | 'Bottom' | 'Top';
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* When `true`, the very first frame's FULL frame is painted onto
|
|
414
|
+
* the canvas (not just the configured slit clip). Default
|
|
415
|
+
* `true` — gives the panorama a wider initial anchor that
|
|
416
|
+
* subsequent slits extend from. Set false if you want strict
|
|
417
|
+
* slit-only behaviour even on the first frame.
|
|
418
|
+
*/
|
|
419
|
+
firstFrameFullFrame: boolean;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
export interface SlitscanRegistrationSettings {
|
|
424
|
+
/**
|
|
425
|
+
* 3D triangulation step. Cross-references features across
|
|
426
|
+
* multiple frames to estimate scene depth. Default `false` (off);
|
|
427
|
+
* adds latency, useful for parallax-heavy captures.
|
|
428
|
+
*/
|
|
429
|
+
enableTriangulation: boolean;
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Triangulation accumulator — when `enableTriangulation` is on,
|
|
433
|
+
* keeps a running pose graph across the whole capture. Default
|
|
434
|
+
* `false` (off); needed for multi-shot fusion.
|
|
435
|
+
*/
|
|
436
|
+
enableTriAccumulator: boolean;
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* RANSAC homography fit per pair. Adds robustness to feature
|
|
440
|
+
* matching at the cost of a few ms per frame. Default `false`.
|
|
441
|
+
*/
|
|
442
|
+
enableRansacHomography: boolean;
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* 1D NCC strip alignment. Present iff enabled. Default
|
|
446
|
+
* undefined (disabled); engine uses pure feature matching.
|
|
447
|
+
*/
|
|
448
|
+
ncc1d?: Ncc1dSettings;
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* 2D NCC strip alignment. Present iff enabled. More expensive
|
|
452
|
+
* than 1D NCC; needed for shelf-scan captures with vertical
|
|
453
|
+
* misalignment. Default undefined (disabled).
|
|
454
|
+
*/
|
|
455
|
+
ncc2d?: Ncc2dSettings;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
export interface Ncc1dSettings {
|
|
460
|
+
/**
|
|
461
|
+
* Search radius in working-resolution pixels (along the pan axis).
|
|
462
|
+
* Clamped to `[5, 60]`. Default 15 when the field is set.
|
|
463
|
+
*/
|
|
464
|
+
searchRadius: number;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
export interface Ncc2dSettings {
|
|
469
|
+
/**
|
|
470
|
+
* 2D search margin in pixels (rectangular region around the
|
|
471
|
+
* predicted strip position). Clamped to `[4, 60]`. Default 12.
|
|
472
|
+
*/
|
|
473
|
+
searchMargin: number;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Minimum NCC score to accept a match. Below this the engine
|
|
477
|
+
* falls back to the predicted (pose-only) position. Clamped
|
|
478
|
+
* to `[0.30, 0.99]`. Default 0.99 (only accept very strong
|
|
479
|
+
* matches; the canvas falls back to pose-only quickly).
|
|
480
|
+
*/
|
|
481
|
+
confidenceThreshold: number;
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* EMA smoothing of the NCC-derived offset across consecutive
|
|
485
|
+
* strips. Present iff enabled. Default undefined. Useful
|
|
486
|
+
* for jittery captures.
|
|
487
|
+
*/
|
|
488
|
+
emaSmoothing?: { alpha: number };
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Pan-axis-lock — when enabled, the NCC offset is constrained
|
|
492
|
+
* to the dominant pan axis (cross-axis movement bounded by
|
|
493
|
+
* `crossAxisLockPx`). Useful when the operator's hand wobble
|
|
494
|
+
* introduces unwanted cross-axis motion. Present iff enabled.
|
|
495
|
+
*/
|
|
496
|
+
panAxisLock?: { crossAxisLockPx: number };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
export interface PlaneProjectionSettings {
|
|
501
|
+
/**
|
|
502
|
+
* Where the plane the slit-scan projects onto comes from.
|
|
503
|
+
*
|
|
504
|
+
* • `'Disabled'` — no plane projection; engine runs
|
|
505
|
+
* its baseline slit-scan path.
|
|
506
|
+
* • `'ARKitDetected'` — use the first vertical plane that
|
|
507
|
+
* ARKit/ARCore finds AND whose normal
|
|
508
|
+
* aligns with the camera (filtered by
|
|
509
|
+
* `alignmentThreshold`). Requires
|
|
510
|
+
* `captureSource === 'ar'`.
|
|
511
|
+
* • `'Virtual'` — synthesise a plane at a fixed depth
|
|
512
|
+
* (`virtualDepthMeters`) in front of the
|
|
513
|
+
* camera at first-frame pose. No
|
|
514
|
+
* ARKit dependency.
|
|
515
|
+
*/
|
|
516
|
+
source: 'Disabled' | 'ARKitDetected' | 'Virtual';
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* How frames are warped onto the plane. Only consulted when
|
|
520
|
+
* `source !== 'Disabled'`. Default `'Rectified'` for slit-scan.
|
|
521
|
+
*/
|
|
522
|
+
projectionStyle?: 'Trapezoidal' | 'Rectified';
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Depth in metres for `source === 'Virtual'`. Range `[0.3, 5.0]`,
|
|
526
|
+
* default 1.5. Set close to the actual shelf distance for the
|
|
527
|
+
* cleanest projection.
|
|
528
|
+
*/
|
|
529
|
+
virtualDepthMeters?: number;
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Minimum `|planeNormal · cameraForward|` for an ARKit-detected
|
|
533
|
+
* plane to be accepted (when `source === 'ARKitDetected'`).
|
|
534
|
+
* Range `[0, 1]`, default 0.6 (≈ 53° max off-axis). Higher =
|
|
535
|
+
* stricter, only accept very-on-axis planes.
|
|
536
|
+
*/
|
|
537
|
+
alignmentThreshold?: number;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
export const DEFAULT_SLITSCAN_SETTINGS: SlitscanSettings = {
|
|
542
|
+
captureSource: 'ar',
|
|
543
|
+
debug: false,
|
|
544
|
+
variant: 'slitscan-rotate',
|
|
545
|
+
painting: {
|
|
546
|
+
paintMode: 'FirstPaintedWins',
|
|
547
|
+
sliverPosition: 'Bottom',
|
|
548
|
+
firstFrameFullFrame: true,
|
|
549
|
+
},
|
|
550
|
+
registration: {
|
|
551
|
+
enableTriangulation: false,
|
|
552
|
+
enableTriAccumulator: false,
|
|
553
|
+
enableRansacHomography: false,
|
|
554
|
+
// ncc1d / ncc2d omitted — both disabled by default.
|
|
555
|
+
},
|
|
556
|
+
plane: {
|
|
557
|
+
source: 'ARKitDetected',
|
|
558
|
+
projectionStyle: 'Rectified',
|
|
559
|
+
virtualDepthMeters: 1.5,
|
|
560
|
+
alignmentThreshold: 0.6,
|
|
561
|
+
},
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
566
|
+
// HybridSettings — RetaiLens-specific live engine.
|
|
567
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Settings for the hybrid live-compositing engine
|
|
571
|
+
* (`incremental.start({ engine: 'hybrid', ... })`). Most consumers
|
|
572
|
+
* won't touch this — the hybrid engine is RetaiLens-specific and
|
|
573
|
+
* the public lib's batch-keyframe pipeline is a better fit for
|
|
574
|
+
* general-purpose captures. Exported here for completeness.
|
|
575
|
+
*
|
|
576
|
+
* Important: the hybrid engine has internal preset paths
|
|
577
|
+
* (`OpenCVIncrementalStitcher.mm:139-180`) that hard-set
|
|
578
|
+
* `enableTriangulation`, `enable2dNcc`, `enableRansacHomography`,
|
|
579
|
+
* `planeSource = Disabled`, etc. Code-reviewer flagged that
|
|
580
|
+
* exposing those fields would be misleading — the engine clobbers
|
|
581
|
+
* any overrides. So this type is intentionally minimal: only
|
|
582
|
+
* `projection` is reliably operator-tunable. Hosts that need to
|
|
583
|
+
* reach deeper-level hybrid knobs can pass a raw config dict to
|
|
584
|
+
* `incremental.start()` directly (Layer 2 escape hatch).
|
|
585
|
+
*/
|
|
586
|
+
export interface HybridSettings extends CaptureBaseSettings {
|
|
587
|
+
/**
|
|
588
|
+
* Internal projection during real-time compositing. Independent
|
|
589
|
+
* from the panorama-stitcher's warperType (which doesn't apply
|
|
590
|
+
* to the hybrid engine — its output is the live canvas directly).
|
|
591
|
+
*
|
|
592
|
+
* Note: only effective in the rotation-only preset path (hybrid
|
|
593
|
+
* preset 1). In the other hybrid presets the engine forces
|
|
594
|
+
* Planar internally regardless of this setting. Native source:
|
|
595
|
+
* `OpenCVIncrementalStitcher.mm:146,161,180`.
|
|
596
|
+
*/
|
|
597
|
+
projection: 'Cylindrical' | 'Planar';
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
export const DEFAULT_HYBRID_SETTINGS: HybridSettings = {
|
|
602
|
+
captureSource: 'ar',
|
|
603
|
+
debug: false,
|
|
604
|
+
projection: 'Planar',
|
|
605
|
+
};
|