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 CHANGED
@@ -16,6 +16,201 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [0.4.0] — 2026-05-23
20
+
21
+ ### v0.4 settings revamp (F10)
22
+
23
+ > [!WARNING]
24
+ > **Breaking type change.** The flat 45-field `PanoramaSettings`
25
+ > interface from v0.3 has been replaced with three engine-discriminated
26
+ > hierarchical types (`PanoramaSettings`, `SlitscanSettings`,
27
+ > `HybridSettings`). Consumers passing custom settings literals to
28
+ > `<Camera>` or to a Layer 2 modal must migrate to the new shape; the
29
+ > v0.3 type is deleted, not aliased. The C++ engine wire format is
30
+ > unchanged — only the JS-side type surface moved.
31
+ >
32
+ > **Migration guide:** [`docs/migrations/v0.3-to-v0.4-panorama-settings.md`](docs/migrations/v0.3-to-v0.4-panorama-settings.md)
33
+ > walks through every recipe (default-only hosts, custom-literal
34
+ > hosts, slit-scan / hybrid hosts, storage migration for persisted
35
+ > settings).
36
+
37
+ #### Why
38
+
39
+ The 2026-05-22 audit (entry below in v0.3.0) traced every
40
+ `PanoramaSettings` field's native consumer and proved the flat type
41
+ mixed three engines' (batch-keyframe, slit-scan, hybrid) settings into
42
+ one bag of disjoint subsets. Hosts had no way to know at the type
43
+ level which settings their chosen engine would even read; the modal
44
+ exposed knobs that were silently ignored on the active engine. The
45
+ revamp splits the type along engine boundaries so the types match what
46
+ each engine actually consumes.
47
+
48
+ #### What changed
49
+
50
+ - **New file:** `src/camera/PanoramaSettings.ts` — `CaptureBaseSettings`
51
+ + three top-level types (`PanoramaSettings`, `SlitscanSettings`,
52
+ `HybridSettings`), each with co-located `DEFAULT_*_SETTINGS`. Sub-trees
53
+ group related knobs: `stitcher` / `frameSelection.flow` (panorama);
54
+ `painting` / `registration.ncc1d` / `registration.ncc2d.emaSmoothing` /
55
+ `registration.ncc2d.panAxisLock` / `plane` / `advanced` (slitscan).
56
+ - **New file:** `src/camera/PanoramaSettingsBridge.ts` — three pure
57
+ adapter functions (`panoramaSettingsToNativeConfig`,
58
+ `slitscanSettingsToNativeConfig`, `hybridSettingsToNativeConfig`)
59
+ that translate the typed JS tree → the flat
60
+ `Record<string, primitive>` the native bridges consume. Handles
61
+ presence-as-enable (`ncc1d` defined ⇒ `enable1dNcc: true` on the
62
+ wire) and source-conditional plane optionals.
63
+ - **New file:** `src/camera/buildPanoramaInitialSettings.ts` — pure
64
+ helper that translates `<Camera>`'s `default*` props into the
65
+ initial `PanoramaSettings` snapshot. Takes the device's low-mem
66
+ classification as an argument so the function stays pure and
67
+ testable.
68
+ - **Rewritten:** `src/camera/PanoramaSettingsModal.tsx` — now consumes
69
+ the new `PanoramaSettings` shape. UI sections mirror the type tree
70
+ (Capture source, Debug, Stitcher accordion, Frame Selection
71
+ accordion with nested Flow tunables). ~600 LOC smaller than v0.3
72
+ because dead slit-scan / hybrid / video-recording fields are gone.
73
+ - **Rewired:** `src/camera/Camera.tsx` — settings state uses the new
74
+ type; `incremental.start({ config })` now passes
75
+ `panoramaSettingsToNativeConfig(settings)` instead of an inline flat
76
+ dict. IMU translation gate reads
77
+ `settings.frameSelection.flow?.maxTranslationCm`. Debug overlay
78
+ reads `settings.frameSelection.mode` + `settings.stitcher.stitchMode`.
79
+ - **Updated:** `src/index.ts` — exports the new types + adapters; drops
80
+ the deleted v0.3 type.
81
+ - **Test infra:** added `jest` + `ts-jest` + `@types/jest` devDeps; new
82
+ `jest.config.js`, `tsconfig.test.json`, `tsconfig.build.json` (the
83
+ latter excludes `__tests__/` from the shipped `dist/`). 19 tests
84
+ across two suites cover the bridge round-trips, presence-as-enable
85
+ cases, plane-source variants, and prop→settings-tree translation.
86
+
87
+ #### Migration table — v0.3 flat → v0.4 hierarchical
88
+
89
+ For `<Camera>`-consuming hosts (the only public path that took
90
+ `PanoramaSettings` in v0.3):
91
+
92
+ | v0.3 field | v0.4 path |
93
+ |----------------------------------|-------------------------------------------------|
94
+ | `captureSource` | `captureSource` (unchanged) |
95
+ | `debug` | `debug` (unchanged) |
96
+ | `stitchMode` | `stitcher.stitchMode` |
97
+ | `warperType` | `stitcher.warperType` |
98
+ | `blenderType` | `stitcher.blenderType` |
99
+ | `seamFinderType` | `stitcher.seamFinderType` |
100
+ | `enableMaxInscribedRectCrop` | `stitcher.enableMaxInscribedRectCrop` |
101
+ | `frameSelectionMode` | `frameSelection.mode` |
102
+ | `keyframeMaxCount` | `frameSelection.maxKeyframes` |
103
+ | `keyframeOverlapThreshold` | `frameSelection.overlapThreshold` |
104
+ | `flowNoveltyPercentile` | `frameSelection.flow.noveltyPercentile` |
105
+ | `flowEvalEveryNFrames` | `frameSelection.flow.evalEveryNFrames` |
106
+ | `flowMaxTranslationCm` | `frameSelection.flow.maxTranslationCm` |
107
+ | `flowMaxCorners` | `frameSelection.flow.maxCorners` |
108
+ | `flowQualityLevel` | `frameSelection.flow.qualityLevel` |
109
+ | `flowMinDistance` | `frameSelection.flow.minDistance` |
110
+
111
+ #### Deleted from the public type surface
112
+
113
+ These fields were consumed only by slit-scan or hybrid engines (or
114
+ not consumed at all per the audit) and were dead surface on
115
+ `<Camera>`'s batch-keyframe path:
116
+
117
+ - `incrementalEngine` — `<Camera>` always uses `batch-keyframe`; the
118
+ knob never reached this component. Hosts that want slit-scan or
119
+ hybrid build their own capture flow on `incremental.start()` and
120
+ pass `SlitscanSettings` / `HybridSettings` instead.
121
+ - `useARPreview` — superseded by `captureSource` ('ar' / 'non-ar').
122
+ - `useDetectedPlane` — superseded by `SlitscanSettings.plane.source`.
123
+ - `planeSource`, `virtualPlaneDepthMeters`, `arkitPlaneAlignmentThreshold`,
124
+ `planeProjectionStyle` — slit-scan only; on `SlitscanSettings.plane.*`.
125
+ - `slitWidthFraction`, `sliverPosition`, `firstFrameFullFrame`,
126
+ `paintMode` — slit-scan only; on `SlitscanSettings.painting.*`.
127
+ - `acceptGate`, `enableTriangulation`, `enableTriAccumulator`,
128
+ `enable2dNcc`, `enableRansacHomography`, `nccSearchRadius1d`,
129
+ `nccSearchMargin2d`, `nccConfidenceThreshold2d`,
130
+ `enableNcc2dEmaSmoothing`, `ncc2dEmaAlpha`,
131
+ `enableNcc2dPanAxisLock`, `ncc2dCrossAxisLockPx` — slit-scan only;
132
+ on `SlitscanSettings.registration.*`.
133
+ - `hybridProjection` — hybrid only; on `HybridSettings.projection`.
134
+ - `maxRecordingMs`, `framesPerSecond`, `minFrames`, `maxFrames`,
135
+ `quality` — historical video-recording fallback fields with no
136
+ consumer on `<Camera>`'s batch-keyframe path.
137
+
138
+ #### Latent v0.3 bug fixed in passing
139
+
140
+ The v0.3 `<Camera>` accepted a `defaultCaptureSource` prop but the
141
+ internal `buildInitialSettings` function never copied it into
142
+ `settings.captureSource` — only into `arPreference` state. The
143
+ discrepancy meant the wire dict sent to native always reported
144
+ `captureSource: 'ar'` even when the operator's effective source was
145
+ `'non-ar'`, which silently disabled Android's `disableAngularFallback`
146
+ opt-out (audit fix F1). v0.4's `extractPanoramaOverrides` +
147
+ `buildPanoramaInitialSettings` route the prop through correctly.
148
+ Hosts using `defaultCaptureSource="non-ar"` will see native receive
149
+ the matching value for the first time.
150
+
151
+ #### Known limitation — modal Capture-source field vs. AR toggle
152
+
153
+ The on-screen AR toggle button at the bottom of `<Camera>` updates
154
+ `arPreference` state (and through it `effectiveCaptureSource`),
155
+ which decides which preview component mounts. The Capture-source
156
+ segmented control inside the settings modal updates
157
+ `settings.captureSource`, which only affects what's reported to the
158
+ native engine via `panoramaSettingsToNativeConfig` (gates Android's
159
+ angular-fallback opt-out per audit fix F1). These two values can
160
+ drift if the operator toggles the AR button without re-opening
161
+ settings, OR flips the modal field without touching the AR button.
162
+ The on-screen toggle is the canonical UI affordance for the live
163
+ preview path; the modal field is best thought of as a tester escape
164
+ hatch for the wire-format consequence. A future cleanup is to make
165
+ both update the same source of truth — out of scope for v0.4.
166
+
167
+ #### Migration example
168
+
169
+ ```ts
170
+ // Before (v0.3)
171
+ const settings: PanoramaSettings = {
172
+ captureSource: 'ar',
173
+ stitchMode: 'auto',
174
+ blenderType: 'multiband',
175
+ flowMaxTranslationCm: 50,
176
+ flowNoveltyPercentile: 0.85,
177
+ keyframeMaxCount: 6,
178
+ frameSelectionMode: 'flow-based',
179
+ // … 40+ more fields
180
+ };
181
+
182
+ // After (v0.4)
183
+ const settings: PanoramaSettings = {
184
+ captureSource: 'ar',
185
+ debug: false,
186
+ stitcher: {
187
+ stitchMode: 'auto',
188
+ warperType: 'plane',
189
+ blenderType: 'multiband',
190
+ seamFinderType: 'graphcut',
191
+ enableMaxInscribedRectCrop: false,
192
+ },
193
+ frameSelection: {
194
+ mode: 'flow-based',
195
+ maxKeyframes: 6,
196
+ overlapThreshold: 0.20,
197
+ flow: {
198
+ noveltyPercentile: 0.85,
199
+ evalEveryNFrames: 5,
200
+ maxTranslationCm: 50,
201
+ maxCorners: 150,
202
+ qualityLevel: 0.01,
203
+ minDistance: 10,
204
+ },
205
+ },
206
+ };
207
+
208
+ // Or just use the default:
209
+ import { DEFAULT_PANORAMA_SETTINGS } from 'react-native-image-stitcher';
210
+ const settings = { ...DEFAULT_PANORAMA_SETTINGS, captureSource: 'non-ar' };
211
+ ```
212
+
213
+
19
214
  ## [0.3.0] — 2026-05-23
20
215
 
21
216
  > [!IMPORTANT]
@@ -90,7 +90,10 @@ const CaptureKeyframePill_1 = require("./CaptureKeyframePill");
90
90
  const CaptureOrientationPill_1 = require("./CaptureOrientationPill");
91
91
  const CaptureStitchStatsToast_1 = require("./CaptureStitchStatsToast");
92
92
  const PanoramaBandOverlay_1 = require("./PanoramaBandOverlay");
93
+ const PanoramaSettingsBridge_1 = require("./PanoramaSettingsBridge");
93
94
  const PanoramaSettingsModal_1 = require("./PanoramaSettingsModal");
95
+ const buildPanoramaInitialSettings_1 = require("./buildPanoramaInitialSettings");
96
+ const lowMemDevice_1 = require("./lowMemDevice");
94
97
  const useCapture_1 = require("./useCapture");
95
98
  const useDeviceOrientation_1 = require("./useDeviceOrientation");
96
99
  const incremental_1 = require("../stitching/incremental");
@@ -228,32 +231,31 @@ function deriveEffectiveCaptureSource(arPreference, lens, isARSupportedOnDevice)
228
231
  return arPreference ? 'ar' : 'non-ar';
229
232
  }
230
233
  /**
231
- * Apply per-prop defaults to build the initial settings snapshot.
232
- * The settings live in component state from there; the prop values
233
- * never re-flow.
234
+ * Pluck the props that influence the initial PanoramaSettings tree.
235
+ * Kept inline (vs. a wide structural type) so future Camera prop
236
+ * additions don't accidentally widen the settings-translation
237
+ * surface — the pure builder in `./buildPanoramaInitialSettings.ts`
238
+ * has the canonical interface; this just forwards the relevant
239
+ * fields.
234
240
  *
235
- * Note: the `default*ResolMP` props don't have a home on PanoramaSettings
236
- * yet they're accepted on the prop interface for forward compatibility
237
- * but ignored here. Wiring is a follow-up once PanoramaSettings is
238
- * extended.
241
+ * The `default*ResolMP` props on `CameraProps` are documented as
242
+ * forward-looking no-ops; the new PanoramaSettings tree has no home
243
+ * for them yet (the v0.3 audit found cv::Stitcher's resol knobs
244
+ * aren't reached by either platform's bridge). They're accepted on
245
+ * the prop interface for API stability and ignored here.
239
246
  */
240
- function buildInitialSettings(props) {
247
+ function extractPanoramaOverrides(props) {
241
248
  return {
242
- ...PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS,
243
- stitchMode: props.defaultStitchMode ?? PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.stitchMode,
244
- blenderType: props.defaultBlender ?? PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.blenderType,
245
- seamFinderType: props.defaultSeamFinder ?? PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.seamFinderType,
246
- warperType: props.defaultWarper ?? PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.warperType,
247
- flowNoveltyPercentile: props.defaultFlowNoveltyPercentile ??
248
- PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.flowNoveltyPercentile,
249
- flowEvalEveryNFrames: props.defaultFlowEvalEveryNFrames ??
250
- PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.flowEvalEveryNFrames,
251
- flowMaxTranslationCm: props.defaultFlowMaxTranslationCm ??
252
- PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.flowMaxTranslationCm,
253
- keyframeMaxCount: props.defaultKeyframeMaxCount ??
254
- PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.keyframeMaxCount,
255
- keyframeOverlapThreshold: props.defaultKeyframeOverlapThreshold ??
256
- PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.keyframeOverlapThreshold,
249
+ defaultCaptureSource: props.defaultCaptureSource,
250
+ defaultStitchMode: props.defaultStitchMode,
251
+ defaultBlender: props.defaultBlender,
252
+ defaultSeamFinder: props.defaultSeamFinder,
253
+ defaultWarper: props.defaultWarper,
254
+ defaultFlowNoveltyPercentile: props.defaultFlowNoveltyPercentile,
255
+ defaultFlowEvalEveryNFrames: props.defaultFlowEvalEveryNFrames,
256
+ defaultFlowMaxTranslationCm: props.defaultFlowMaxTranslationCm,
257
+ defaultKeyframeMaxCount: props.defaultKeyframeMaxCount,
258
+ defaultKeyframeOverlapThreshold: props.defaultKeyframeOverlapThreshold,
257
259
  };
258
260
  }
259
261
  // `toFileUri` (used to be an inline `toFileUri` here) lives in
@@ -273,7 +275,7 @@ function Camera(props) {
273
275
  // ── State ───────────────────────────────────────────────────────
274
276
  const [arPreference, setArPreference] = (0, react_1.useState)(defaultCaptureSource === 'ar');
275
277
  const [lens, setLens] = (0, react_1.useState)(defaultLens);
276
- const [settings, setSettings] = (0, react_1.useState)(() => buildInitialSettings(props));
278
+ const [settings, setSettings] = (0, react_1.useState)(() => (0, buildPanoramaInitialSettings_1.buildPanoramaInitialSettings)(extractPanoramaOverrides(props), (0, lowMemDevice_1.isLowMemDevice)()));
277
279
  const [settingsModalVisible, setSettingsModalVisible] = (0, react_1.useState)(false);
278
280
  const [statusPhase, setStatusPhase] = (0, react_1.useState)('idle');
279
281
  const [recordingStartedAt, setRecordingStartedAt] = (0, react_1.useState)(null);
@@ -399,11 +401,16 @@ function Camera(props) {
399
401
  // |residual|` — which undercounted any time a non-IMU accept
400
402
  // (flow novelty, force-last) reset the integrator before the
401
403
  // budget threshold was reached.
404
+ // The translation budget lives at `frameSelection.flow.maxTranslationCm`
405
+ // in the new hierarchical settings shape. When `flow` is undefined
406
+ // (the consumer opted out of the flow strategy entirely), the gate
407
+ // stays disabled — same observable behaviour as v0.3's `0` default.
408
+ const flowMaxTranslationCm = settings.frameSelection.flow?.maxTranslationCm ?? 0;
402
409
  const imuGate = (0, useIMUTranslationGate_1.useIMUTranslationGate)({
403
410
  enabled: isNonAR
404
411
  && statusPhase === 'recording'
405
- && settings.flowMaxTranslationCm > 0,
406
- budgetMeters: Math.max(0.001, settings.flowMaxTranslationCm / 100.0),
412
+ && flowMaxTranslationCm > 0,
413
+ budgetMeters: Math.max(0.001, flowMaxTranslationCm / 100.0),
407
414
  onBudgetExceeded: () => {
408
415
  const mod = (0, incremental_1.getIncrementalNativeModule)();
409
416
  mod?.markNextFrameAsLastKeyframe?.().catch(() => undefined);
@@ -578,6 +585,25 @@ function Camera(props) {
578
585
  const orientationRotation = deviceOrientation === 'portrait' ? 90
579
586
  : deviceOrientation === 'portrait-upside-down' ? 270
580
587
  : 0;
588
+ // v0.4 — the inline-flat config dict that v0.3 maintained here
589
+ // moved into `panoramaSettingsToNativeConfig` (see
590
+ // PanoramaSettingsBridge.ts). That adapter is the single source
591
+ // of truth for the JS→native wire format; both this call site
592
+ // AND the modal's reset-to-defaults preview agree on the same
593
+ // mapping. Audit fixes F1 / F4 / F6 from v0.3 are now properties
594
+ // of the bridge (verified by the unit tests in
595
+ // src/camera/__tests__/PanoramaSettingsBridge.test.ts).
596
+ //
597
+ // 2026-05-23 — override `captureSource` with the runtime-derived
598
+ // `effectiveCaptureSource` (from `arPreference + lens +
599
+ // AR-device-support`). Pre-this change the camera-screen AR
600
+ // toggle wrote ONLY to local `arPreference` state while the
601
+ // bridge read `settings.captureSource` — so native could think
602
+ // the capture was AR while the operator had toggled it off (or
603
+ // vice-versa). Single source of truth now: whatever camera the
604
+ // operator can see is what native is told it is. The settings
605
+ // modal's `captureSource` control has been removed for the same
606
+ // reason — see PanoramaSettingsModal.tsx for the rationale.
581
607
  await incremental.start({
582
608
  snapshotJpegQuality: 75,
583
609
  snapshotEveryNAccepts: 1,
@@ -589,37 +615,10 @@ function Camera(props) {
589
615
  canvasWidth: 5000,
590
616
  canvasHeight: 5000,
591
617
  engine: 'batch-keyframe',
592
- config: {
593
- // ── cv::Stitcher (batch finalize) ─────────────────────────
594
- stitchMode: settings.stitchMode,
595
- warperType: settings.warperType,
596
- blenderType: settings.blenderType,
597
- seamFinderType: settings.seamFinderType,
598
- enableMaxInscribedRectCrop: settings.enableMaxInscribedRectCrop,
599
- // ── KeyframeGate (per-frame selection) ────────────────────
600
- // F6 audit fix: pass settings.frameSelectionMode through
601
- // instead of hardcoding 'flow-based' (which silently made the
602
- // time-based / pose-based modal options no-ops).
603
- frameSelectionMode: settings.frameSelectionMode,
604
- keyframeMaxCount: settings.keyframeMaxCount,
605
- keyframeOverlapThreshold: settings.keyframeOverlapThreshold,
606
- // ── Flow-strategy tunables ────────────────────────────────
607
- // F4 audit fix: previously omitted, which made the modal
608
- // sliders for these three a complete no-op (only iOS native
609
- // even read them, and only when JS sent them).
610
- flowNoveltyPercentile: settings.flowNoveltyPercentile,
611
- flowEvalEveryNFrames: settings.flowEvalEveryNFrames,
612
- flowMaxTranslationCm: settings.flowMaxTranslationCm,
613
- flowMaxCorners: settings.flowMaxCorners,
614
- flowQualityLevel: settings.flowQualityLevel,
615
- flowMinDistance: settings.flowMinDistance,
616
- // ── Engine-routing flags consumed by native ───────────────
617
- // F1 audit fix: Android keyframe gate's disableAngularFallback
618
- // opt-out reads this to decide whether to skip the angular
619
- // fallback (gyro pose is too noisy for the FoV-overlap calc
620
- // in non-AR mode, causing degenerate cv::Stitcher params).
621
- captureSource: settings.captureSource,
622
- },
618
+ config: (0, PanoramaSettingsBridge_1.panoramaSettingsToNativeConfig)({
619
+ ...settings,
620
+ captureSource: effectiveCaptureSource,
621
+ }),
623
622
  });
624
623
  imuGate.resetAnchor();
625
624
  // Start pumping vision-camera snapshots into the engine for
@@ -642,6 +641,7 @@ function Camera(props) {
642
641
  isNonAR,
643
642
  deviceOrientation,
644
643
  settings,
644
+ effectiveCaptureSource,
645
645
  imuGate,
646
646
  jsDriver,
647
647
  onError,
@@ -723,6 +723,18 @@ function Camera(props) {
723
723
  onError,
724
724
  recordingStartedAt,
725
725
  jsDriver,
726
+ // F10 Phase 2 review N1 — these four were missing pre-fix. The
727
+ // callback reads `settings.debug` (to gate the stitchToast),
728
+ // `isNonAR` (to decide whether to read IMU totalAbs translation),
729
+ // `imuGate` (the read itself), and `stitchToast` (the toast hook
730
+ // object). If any of those identities change between the user
731
+ // pressing-and-holding the shutter and the release, the stale-
732
+ // closure read could disagree with the actual current state.
733
+ // Pre-existing v0.3 bug; v0.4 was the natural time to address it.
734
+ settings,
735
+ isNonAR,
736
+ imuGate,
737
+ stitchToast,
726
738
  ]);
727
739
  // ── Lens / AR-toggle handlers ───────────────────────────────────
728
740
  const handleLensChange = (0, react_1.useCallback)((next) => {
@@ -749,7 +761,7 @@ function Camera(props) {
749
761
  react_1.default.createElement(CaptureOrientationPill_1.CaptureOrientationPill, { orientation: deviceOrientation, topInset: insets.top }),
750
762
  react_1.default.createElement(CaptureKeyframePill_1.CaptureKeyframePill, { state: incrementalState, topInset: insets.top }),
751
763
  react_1.default.createElement(CaptureMemoryPill_1.CaptureMemoryPill, { topInset: insets.top }),
752
- react_1.default.createElement(CaptureDebugOverlay_1.CaptureDebugOverlay, { incrementalState: incrementalState, imuTranslationMetres: isNonAR ? imuGate.getTranslationMetres() : null, captureSource: effectiveCaptureSource, frameSelectionMode: settings.frameSelectionMode, stitchMode: settings.stitchMode }))),
764
+ react_1.default.createElement(CaptureDebugOverlay_1.CaptureDebugOverlay, { incrementalState: incrementalState, imuTranslationMetres: isNonAR ? imuGate.getTranslationMetres() : null, captureSource: effectiveCaptureSource, frameSelectionMode: settings.frameSelection.mode, stitchMode: settings.stitcher.stitchMode }))),
753
765
  react_1.default.createElement(CaptureStitchStatsToast_1.CaptureStitchStatsToast, { message: stitchToast.message, topInset: insets.top }),
754
766
  showSettingsButton && (react_1.default.createElement(SettingsButton, { topInset: insets.top, onPress: () => setSettingsModalVisible(true) })),
755
767
  react_1.default.createElement(react_native_1.View, { pointerEvents: "box-none", style: [styles.bottomArea, { paddingBottom: insets.bottom + 12 }] },