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,473 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// OpenCVIncrementalStitcher.h
|
|
4
|
+
//
|
|
5
|
+
// Per-frame incremental panorama stitcher. Replaces the batch-mode
|
|
6
|
+
// `cv::Stitcher` flow used by `OpenCVStitcher` with a streaming
|
|
7
|
+
// pipeline: each accepted frame is matched against the previous,
|
|
8
|
+
// warped via a RANSAC homography, and feather-blended onto a running
|
|
9
|
+
// canvas — no end-of-capture wait.
|
|
10
|
+
//
|
|
11
|
+
// Why incremental:
|
|
12
|
+
// See docs/site-content/design/2026-04-30-realtime-incremental-stitching.md
|
|
13
|
+
// for the full motivation. TL;DR: live preview, bounded memory,
|
|
14
|
+
// fail-fast on bad frames, no terminal BA stall.
|
|
15
|
+
//
|
|
16
|
+
// What this file owns:
|
|
17
|
+
// The C++/OpenCV side of the engine — feature extraction, matching,
|
|
18
|
+
// RANSAC, warp, feather blend. All `cv::*` types stay inside the
|
|
19
|
+
// `.mm` impl; this header exposes only Foundation types so it can
|
|
20
|
+
// be imported from pure Swift via the umbrella header.
|
|
21
|
+
//
|
|
22
|
+
// Threading:
|
|
23
|
+
// Methods on this class are NOT thread-safe internally. The Swift
|
|
24
|
+
// layer (`IncrementalStitcher`) owns a serial queue and
|
|
25
|
+
// funnels all calls through it. The lock is intentional: live
|
|
26
|
+
// capture wants ordered frame ingestion, not parallel mutation of
|
|
27
|
+
// the canvas.
|
|
28
|
+
//
|
|
29
|
+
|
|
30
|
+
#import <Foundation/Foundation.h>
|
|
31
|
+
#import <CoreVideo/CoreVideo.h>
|
|
32
|
+
|
|
33
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
34
|
+
|
|
35
|
+
/// NSError domain raised by incremental stitcher errors.
|
|
36
|
+
extern NSString *const RNImageStitcherIncrementalErrorDomain;
|
|
37
|
+
|
|
38
|
+
/// Per-frame outcome — drives the JS-side UX (silent accept, subtle
|
|
39
|
+
/// flag, explicit hint).
|
|
40
|
+
typedef NS_ENUM(NSInteger, RLISFrameOutcome) {
|
|
41
|
+
/// Frame accepted with high confidence. Silent UX update.
|
|
42
|
+
RLISFrameOutcomeAcceptedHigh = 0,
|
|
43
|
+
/// Frame accepted but match quality was middling. Show subtle
|
|
44
|
+
/// confidence flag (yellow ring) — not an error, just informational.
|
|
45
|
+
RLISFrameOutcomeAcceptedMedium = 1,
|
|
46
|
+
/// Frame skipped because pose hasn't moved enough since last accept.
|
|
47
|
+
/// Normal — operator hasn't panned past the overlap window yet.
|
|
48
|
+
RLISFrameOutcomeSkippedTooClose = 2,
|
|
49
|
+
/// Frame skipped because pose moved too far since last accept —
|
|
50
|
+
/// operator panned past the overlap window before another accept.
|
|
51
|
+
/// JS shows a "slow down" hint.
|
|
52
|
+
RLISFrameOutcomeRejectedTooFar = 3,
|
|
53
|
+
/// Feature matching produced too few correspondences. Scene is
|
|
54
|
+
/// likely uniform/textureless or the frame is motion-blurred.
|
|
55
|
+
/// JS shows a "scene too uniform" hint.
|
|
56
|
+
RLISFrameOutcomeRejectedSceneUniform = 4,
|
|
57
|
+
/// RANSAC homography failed or produced a degenerate transform.
|
|
58
|
+
/// JS shows an "alignment lost — slow down" hint.
|
|
59
|
+
RLISFrameOutcomeRejectedAlignmentLost = 5,
|
|
60
|
+
/// Tracking state from the AR session was poor at the time of
|
|
61
|
+
/// this frame — no point trying to incorporate it.
|
|
62
|
+
RLISFrameOutcomeSkippedTrackingPoor = 6,
|
|
63
|
+
/// V12.11 Step D — operator has panned BACKWARDS past the
|
|
64
|
+
/// running max along the pan axis by more than
|
|
65
|
+
/// `kReverseStopPx`. Engine has SKIPPED the paste; host should
|
|
66
|
+
/// auto-finalize the capture and surface the panorama as it
|
|
67
|
+
/// stood at the running-max position. Emitted by the
|
|
68
|
+
/// rectilinear engine only — cylindrical engines tolerate
|
|
69
|
+
/// reverse motion via their warp pipeline.
|
|
70
|
+
RLISFrameOutcomeRejectedReverseDirection = 7,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/// Telemetry returned alongside each addFrame call — host can log
|
|
74
|
+
/// these to refine threshold tuning during field testing.
|
|
75
|
+
@interface RLISFrameTelemetry : NSObject
|
|
76
|
+
@property (nonatomic, readonly) RLISFrameOutcome outcome;
|
|
77
|
+
/// Estimated FoV-overlap with the previously accepted frame, in
|
|
78
|
+
/// percent. Computed from pose-delta + intrinsics, NOT from
|
|
79
|
+
/// matched features (which would require running the matcher
|
|
80
|
+
/// every frame). Range [0, 100]. -1 if first frame.
|
|
81
|
+
@property (nonatomic, readonly) double overlapPercent;
|
|
82
|
+
/// Number of feature matches that survived ratio-test filtering.
|
|
83
|
+
/// Zero unless the frame went through feature matching (i.e.
|
|
84
|
+
/// passed the pose-delta gate).
|
|
85
|
+
@property (nonatomic, readonly) NSInteger matchCount;
|
|
86
|
+
/// Fraction of matches that survived RANSAC inlier filtering.
|
|
87
|
+
/// Range [0, 1]. Zero unless the frame went through RANSAC.
|
|
88
|
+
@property (nonatomic, readonly) double inlierRatio;
|
|
89
|
+
/// Composite confidence score [0, 1].
|
|
90
|
+
@property (nonatomic, readonly) double confidence;
|
|
91
|
+
/// Wall-clock milliseconds the addFrame call took (end-to-end).
|
|
92
|
+
@property (nonatomic, readonly) double processingMs;
|
|
93
|
+
/// V12.12 — physical device orientation as detected by the engine
|
|
94
|
+
/// from `R_panToCam` at first frame. TRUE for landscape capture
|
|
95
|
+
/// (vertical pan), FALSE for portrait capture (horizontal pan).
|
|
96
|
+
/// Stays at the FIRST-FRAME determination for the rest of the
|
|
97
|
+
/// capture (orientation can't physically change without restarting
|
|
98
|
+
/// pano). Defaults to FALSE (portrait) before first frame.
|
|
99
|
+
///
|
|
100
|
+
/// JS side reads this from `IncrementalState.isLandscape` to drive
|
|
101
|
+
/// orientation-aware UI (band overlay, dim bars). This is the
|
|
102
|
+
/// single source of truth for orientation across the SDK + host —
|
|
103
|
+
/// the V12.6 fix established that JS-side orientation hooks are
|
|
104
|
+
/// unreliable under iOS interface-orientation lock; pose detection
|
|
105
|
+
/// is.
|
|
106
|
+
@property (nonatomic, readonly) BOOL isLandscape;
|
|
107
|
+
|
|
108
|
+
/// V12.14.9 — running max paint position along the pan axis, in
|
|
109
|
+
/// canvas pixels. In landscape mode (`isLandscape == TRUE`) this
|
|
110
|
+
/// is the canvas Y at which the most-recently-pasted slit ends;
|
|
111
|
+
/// in portrait mode (`isLandscape == FALSE` = portrait+horizontal-pan
|
|
112
|
+
/// per the two-mode spec) this is the canvas X. Zero before
|
|
113
|
+
/// first frame is accepted. JS-side band overlay computes
|
|
114
|
+
/// `fillRatio = paintedExtent / panExtent` to size the thumb.
|
|
115
|
+
@property (nonatomic, readonly) NSInteger paintedExtent;
|
|
116
|
+
|
|
117
|
+
/// V12.14.9 — total pan-axis extent of the canvas (the engine's
|
|
118
|
+
/// `_canvasPanExtent` config value, default 5000). Constant for
|
|
119
|
+
/// the lifetime of a capture. Emitted on every telemetry frame
|
|
120
|
+
/// for symmetry with `paintedExtent`; JS uses the ratio.
|
|
121
|
+
@property (nonatomic, readonly) NSInteger panExtent;
|
|
122
|
+
@end
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
/// V15 — paint-mode toggle for the slit-scan engine.
|
|
126
|
+
/// `RLISPaintModeFirstPaintedWins` preserves the first frame's content
|
|
127
|
+
/// (V13.0e+ default). `RLISPaintModeFeatherBlend` alpha-blends new
|
|
128
|
+
/// content into already-painted pixels at slit boundaries (V13.0d-style
|
|
129
|
+
/// row alpha ramp), aiming to smooth visible seams when many slits
|
|
130
|
+
/// stack with small per-accept advance.
|
|
131
|
+
typedef NS_ENUM(NSInteger, RLISPaintMode) {
|
|
132
|
+
RLISPaintModeFirstPaintedWins = 0,
|
|
133
|
+
RLISPaintModeFeatherBlend = 1,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/// V15.0c — where on the camera frame the per-accept sliver is taken
|
|
137
|
+
/// from. For a typical landscape vertical pan tilting DOWN, the LEADING
|
|
138
|
+
/// EDGE (new content not seen by previous frames) is at the BOTTOM of
|
|
139
|
+
/// the camera sensor frame; for upward tilt, the leading edge is at
|
|
140
|
+
/// the TOP. `Center` is the V13.x default (sliver from the centred
|
|
141
|
+
/// 70% / 30% of pan-axis).
|
|
142
|
+
typedef NS_ENUM(NSInteger, RLISSliverPosition) {
|
|
143
|
+
RLISSliverPositionCenter = 0,
|
|
144
|
+
RLISSliverPositionBottom = 1,
|
|
145
|
+
RLISSliverPositionTop = 2,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/// V15 — projection toggle for the hybrid engine.
|
|
149
|
+
/// `RLISHybridProjectionCylindrical` is the V12.x baseline; `Planar`
|
|
150
|
+
/// uses cv::detail::PlaneWarper, well-behaved for pans under ~60°.
|
|
151
|
+
typedef NS_ENUM(NSInteger, RLISHybridProjection) {
|
|
152
|
+
RLISHybridProjectionCylindrical = 0,
|
|
153
|
+
RLISHybridProjectionPlanar = 1,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/// V15.0d — source of the plane used by the slit-scan engine's V15.0b
|
|
157
|
+
/// plane-projected stitch path. Replaces V15.0b's boolean
|
|
158
|
+
/// `useDetectedPlane` toggle (which is kept as a deprecated alias)
|
|
159
|
+
/// with three explicit options:
|
|
160
|
+
///
|
|
161
|
+
/// • `Disabled` — no plane projection; slit-scan path runs
|
|
162
|
+
/// (V13.x baseline + V15 refinements).
|
|
163
|
+
/// • `ARKitDetected` — use the first vertical plane that ARKit
|
|
164
|
+
/// finds AND whose surface normal aligns with
|
|
165
|
+
/// the camera's view direction (filter
|
|
166
|
+
/// threshold = `arkitPlaneAlignmentThreshold`).
|
|
167
|
+
/// If no aligned plane is detected, the engine
|
|
168
|
+
/// falls back to the slit-scan path silently.
|
|
169
|
+
/// • `Virtual` — synthesize a plane from the FIRST plane-
|
|
170
|
+
/// projected frame's camera pose:
|
|
171
|
+
/// origin = camera_pos + virtualPlaneDepthMeters
|
|
172
|
+
/// × camera_forward
|
|
173
|
+
/// normal = -camera_forward
|
|
174
|
+
/// No ARKit dependency; always available.
|
|
175
|
+
/// Loses the "real depth" advantage of an
|
|
176
|
+
/// ARKit-detected plane.
|
|
177
|
+
///
|
|
178
|
+
/// Why both ARKit and Virtual exist:
|
|
179
|
+
/// Field testing showed ARKit plane detection often picks the WRONG
|
|
180
|
+
/// surface (side wall, doorframe, table edge) instead of the fixture
|
|
181
|
+
/// face the user is scanning — producing nonsense projections (huge
|
|
182
|
+
/// corner ray distances, 90°-rotated quads). Virtual side-steps this
|
|
183
|
+
/// with a synthetic plane perpendicular to the camera at first frame.
|
|
184
|
+
/// Operators can A/B between the two and pick whichever wins for
|
|
185
|
+
/// their typical scene.
|
|
186
|
+
typedef NS_ENUM(NSInteger, RLISPlaneSource) {
|
|
187
|
+
RLISPlaneSourceDisabled = 0,
|
|
188
|
+
RLISPlaneSourceARKitDetected = 1,
|
|
189
|
+
RLISPlaneSourceVirtual = 2,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/// V15.0g — how the plane-projection helper renders each frame onto
|
|
193
|
+
/// the canvas. Affects ARKitDetected and Virtual modes; ignored when
|
|
194
|
+
/// planeSource = Disabled.
|
|
195
|
+
///
|
|
196
|
+
/// • `Trapezoidal` (V15.0b legacy):
|
|
197
|
+
/// Geometrically correct 3D mapping. Each camera pixel is
|
|
198
|
+
/// raycast onto the plane and pasted at the resulting plane-
|
|
199
|
+
/// local canvas position. When the camera tilts off-
|
|
200
|
+
/// perpendicular, the projected camera frame becomes a
|
|
201
|
+
/// TRAPEZOID — visually distorted (Ram observed cooler bottom
|
|
202
|
+
/// 2.3× wider than top at 30° tilt, 2026-05-08).
|
|
203
|
+
/// • `Rectified` (V15.0g default):
|
|
204
|
+
/// Camera frame is pasted as a CLEAN RECTANGLE around its
|
|
205
|
+
/// projected anchor on the canvas. Anchor is the canvas
|
|
206
|
+
/// position of the camera CENTER raycast. Rectangle size
|
|
207
|
+
/// depends on plane distance × pixels-per-meter. No
|
|
208
|
+
/// trapezoidal distortion regardless of tilt angle, at the
|
|
209
|
+
/// cost of true 3D-correctness (the camera's per-pixel
|
|
210
|
+
/// perspective is preserved within the rectangle, but
|
|
211
|
+
/// different tilts don't reconcile geometrically — they
|
|
212
|
+
/// overlap with FirstPaintedWins keeping the earliest paint).
|
|
213
|
+
///
|
|
214
|
+
/// Field-validate Rectified vs Trapezoidal; the right choice depends
|
|
215
|
+
/// on the operator's typical pan range and tolerance for distortion.
|
|
216
|
+
typedef NS_ENUM(NSInteger, RLISPlaneProjectionStyle) {
|
|
217
|
+
RLISPlaneProjectionStyleTrapezoidal = 0,
|
|
218
|
+
RLISPlaneProjectionStyleRectified = 1,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/// V15 stitcher config — single source of truth for which correction
|
|
222
|
+
/// stages run in the slit-scan and hybrid engines. Each engine mode
|
|
223
|
+
/// (`hybrid`, `slitscan-rotate`, `slitscan-both`) has a default config
|
|
224
|
+
/// returned by `+configForMode:`; JS-side callers (settings UI, capture
|
|
225
|
+
/// start options) override individual fields on top of the default.
|
|
226
|
+
///
|
|
227
|
+
/// V13.0e+/V13.0g/V14.0a correction stages are preserved in the source;
|
|
228
|
+
/// each is gated on the corresponding `enableX` flag. Field iteration
|
|
229
|
+
/// happens by toggling settings, not by recompiling.
|
|
230
|
+
@interface RLISStitcherConfig : NSObject
|
|
231
|
+
|
|
232
|
+
// ── Slit shaping (slit-scan engine only) ────────────────────────────
|
|
233
|
+
|
|
234
|
+
/// Fraction of the pan-axis the rectilinear slit retains per frame
|
|
235
|
+
/// (the rest is cropped equally from both edges). Range 0.10 – 0.70.
|
|
236
|
+
/// Default 0.30 for both slitscan modes; n/a for hybrid.
|
|
237
|
+
@property (nonatomic) double kPanAxisFractionRect;
|
|
238
|
+
|
|
239
|
+
/// Minimum pan-axis advance required before a frame is accepted.
|
|
240
|
+
/// 0 = accept on every consumeFrame (Apple-dense slit-scan); 50 =
|
|
241
|
+
/// V13.0g default. Default 0 for both slitscan modes; n/a for hybrid.
|
|
242
|
+
@property (nonatomic) NSInteger kMinAcceptDeltaPx;
|
|
243
|
+
|
|
244
|
+
// ── Per-stage correction toggles (slit-scan engine) ─────────────────
|
|
245
|
+
|
|
246
|
+
/// V13.0e+: ORB triangulation + median-Z parallax correction.
|
|
247
|
+
@property (nonatomic) BOOL enableTriangulation;
|
|
248
|
+
/// V13.0g: per-accept incremental Δt accumulator on top of triangulation.
|
|
249
|
+
@property (nonatomic) BOOL enableTriAccumulator;
|
|
250
|
+
|
|
251
|
+
/// V15 new: 1D NCC perpendicular-axis wobble correction (slitscan-rotate).
|
|
252
|
+
@property (nonatomic) BOOL enable1dNcc;
|
|
253
|
+
/// 1D NCC search radius in pixels (5 – 30).
|
|
254
|
+
@property (nonatomic) NSInteger nccSearchRadius1d;
|
|
255
|
+
|
|
256
|
+
/// V13.0g: 2D NCC fine-alignment after triangulation.
|
|
257
|
+
@property (nonatomic) BOOL enable2dNcc;
|
|
258
|
+
/// V15.0d: 2D NCC search half-window in pixels. Was a hardcoded
|
|
259
|
+
/// constexpr (V13.0g: 30, V15.0c.4: 12). Smaller = less wandering on
|
|
260
|
+
/// repetitive textures, but easier to miss the true overlap when pose
|
|
261
|
+
/// noise is high. Range 4 – 30. Default 12 for slitscan modes.
|
|
262
|
+
@property (nonatomic) NSInteger nccSearchMargin2d;
|
|
263
|
+
/// V15.0d: 2D NCC confidence threshold below which the correction
|
|
264
|
+
/// is rejected. Was hardcoded (V13.0g: 0.6, V15.0c.4: 0.75). Higher
|
|
265
|
+
/// = stricter — fewer false matches on repetitive textures, but more
|
|
266
|
+
/// frames where NCC silently doesn't fire. Range 0.4 – 0.95. Default
|
|
267
|
+
/// 0.75 for slitscan modes.
|
|
268
|
+
@property (nonatomic) double nccConfidenceThreshold2d;
|
|
269
|
+
|
|
270
|
+
/// V15.0d new (1B): EMA smoothing on 2D NCC corrections. When enabled,
|
|
271
|
+
/// the applied correction is `α × current + (1−α) × prev` instead of
|
|
272
|
+
/// just `current`. Dampens single-frame snaps to spurious peaks at
|
|
273
|
+
/// the cost of a 2-frame lag. Default OFF for slitscan modes.
|
|
274
|
+
@property (nonatomic) BOOL enableNcc2dEmaSmoothing;
|
|
275
|
+
/// V15.0d new: EMA weight on the CURRENT-frame NCC correction (the
|
|
276
|
+
/// remaining `1 − α` weight is on the previous correction). Range
|
|
277
|
+
/// 0.1 – 0.9. Default 0.4 (60% prev / 40% current — heavy damping).
|
|
278
|
+
@property (nonatomic) double ncc2dEmaAlpha;
|
|
279
|
+
|
|
280
|
+
/// V15.0d new (1C): pan-axis-aware 2D NCC. When enabled, the cross-
|
|
281
|
+
/// axis (perpendicular to the pan direction) NCC correction is
|
|
282
|
+
/// clamped to ±`ncc2dCrossAxisLockPx`, regardless of what the search
|
|
283
|
+
/// window size allows. Idea: 1D NCC already handles cross-axis
|
|
284
|
+
/// wobble; 2D NCC's cross-axis search is mostly noise. Default OFF.
|
|
285
|
+
@property (nonatomic) BOOL enableNcc2dPanAxisLock;
|
|
286
|
+
/// V15.0d new: cross-axis clamp for the pan-axis-aware mode. Range
|
|
287
|
+
/// 0 – 15 px. Default 5.
|
|
288
|
+
@property (nonatomic) NSInteger ncc2dCrossAxisLockPx;
|
|
289
|
+
|
|
290
|
+
/// V14.0a: RANSAC homography per slit + cv::warpPerspective.
|
|
291
|
+
@property (nonatomic) BOOL enableRansacHomography;
|
|
292
|
+
|
|
293
|
+
/// V15 new: paint mode for the slit-scan engine. Default
|
|
294
|
+
/// FirstPaintedWins for slitscan-rotate, FeatherBlend for slitscan-both.
|
|
295
|
+
@property (nonatomic) RLISPaintMode paintMode;
|
|
296
|
+
|
|
297
|
+
/// V15.0c new: where on the camera frame the per-accept sliver is
|
|
298
|
+
/// taken. Default Center (V13.x behaviour). Bottom = leading edge for
|
|
299
|
+
/// typical top-to-bottom landscape pan.
|
|
300
|
+
@property (nonatomic) RLISSliverPosition sliverPosition;
|
|
301
|
+
|
|
302
|
+
/// V15.0c new: when YES, the FIRST accepted frame paints the entire
|
|
303
|
+
/// camera frame at canvas (0, 0) instead of just the sliver. Subsequent
|
|
304
|
+
/// frames still use the configured sliver clip. Useful with sliverPosition=
|
|
305
|
+
/// Bottom: the first frame anchors the canvas with full-frame content,
|
|
306
|
+
/// then leading-edge slivers extend the canvas as the camera pans.
|
|
307
|
+
/// Default YES for slitscan-rotate / slitscan-both.
|
|
308
|
+
@property (nonatomic) BOOL firstFrameFullFrame;
|
|
309
|
+
|
|
310
|
+
/// **DEPRECATED in V15.0d** — use `planeSource` instead.
|
|
311
|
+
///
|
|
312
|
+
/// V15.0b boolean toggle for the plane-projected stitch path. Kept
|
|
313
|
+
/// as an alias for backward compat: when `planeSource` is left at
|
|
314
|
+
/// its default (Disabled), `useDetectedPlane = YES` upgrades it to
|
|
315
|
+
/// `ARKitDetected`. New callers should set `planeSource` directly.
|
|
316
|
+
///
|
|
317
|
+
/// V15.0b semantics: if YES, the slit-scan engine projects each
|
|
318
|
+
/// accepted frame onto a vertical plane. Composes with paint mode;
|
|
319
|
+
/// bypasses the slit-axis 2D refinements (triangulation, 2D NCC,
|
|
320
|
+
/// RANSAC homography) — those don't apply when the canvas is a
|
|
321
|
+
/// real 3D plane.
|
|
322
|
+
@property (nonatomic) BOOL useDetectedPlane;
|
|
323
|
+
|
|
324
|
+
/// V15.0d new: source of the plane used by the V15.0b path. See
|
|
325
|
+
/// `RLISPlaneSource` enum docs above for tradeoffs. Default
|
|
326
|
+
/// Disabled for all engine modes; settings UI / capture overrides
|
|
327
|
+
/// promote to ARKitDetected or Virtual.
|
|
328
|
+
@property (nonatomic) RLISPlaneSource planeSource;
|
|
329
|
+
|
|
330
|
+
/// V15.0d new: depth (metres) at which the synthetic plane is placed
|
|
331
|
+
/// in front of the camera when `planeSource = Virtual`. Set the
|
|
332
|
+
/// plane at the user's typical scan distance — too close = scene
|
|
333
|
+
/// content gets clipped behind the plane; too far = perspective
|
|
334
|
+
/// distortion grows. Range 0.3 – 5.0 m. Default 1.5 m.
|
|
335
|
+
@property (nonatomic) double virtualPlaneDepthMeters;
|
|
336
|
+
|
|
337
|
+
/// V15.0d new: minimum dot product between the candidate plane's
|
|
338
|
+
/// surface normal and the camera's negative-forward direction
|
|
339
|
+
/// (i.e. the direction the camera is facing). Used by
|
|
340
|
+
/// `RNSARSession.didAdd` to filter ARKit-detected planes for
|
|
341
|
+
/// `planeSource = ARKitDetected`. 1.0 = plane perfectly facing
|
|
342
|
+
/// camera; 0.0 = plane edge-on to camera; -1.0 = facing away.
|
|
343
|
+
/// Range 0.0 – 1.0. Default 0.6 (≈53° max angle off-camera).
|
|
344
|
+
@property (nonatomic) double arkitPlaneAlignmentThreshold;
|
|
345
|
+
|
|
346
|
+
/// V15.0g new: plane projection rendering style. See enum docs above
|
|
347
|
+
/// for tradeoffs (Trapezoidal = 3D-correct + distorted; Rectified =
|
|
348
|
+
/// clean-rectangle + slight 3D approximation). Ignored when
|
|
349
|
+
/// planeSource = Disabled. Default Rectified for slit-scan modes.
|
|
350
|
+
@property (nonatomic) RLISPlaneProjectionStyle planeProjectionStyle;
|
|
351
|
+
|
|
352
|
+
// ── Hybrid-specific ─────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
/// V15 new: projection for hybrid engine. Default Planar in V15
|
|
355
|
+
/// (was Cylindrical in V12.x – V14.0a).
|
|
356
|
+
@property (nonatomic) RLISHybridProjection hybridProjection;
|
|
357
|
+
|
|
358
|
+
/// Build a default config for the named engine mode.
|
|
359
|
+
/// Recognised modes: `@"hybrid"`, `@"slitscan-rotate"`,
|
|
360
|
+
/// `@"slitscan-both"`. Backward-compat: `@"firstwins-rectilinear"`
|
|
361
|
+
/// maps to `slitscan-rotate`; legacy `@"firstwins"` /
|
|
362
|
+
/// `@"firstwins-zoomed"` log a deprecation warning and fall back to
|
|
363
|
+
/// `slitscan-both`. Unrecognised modes default to `slitscan-both`.
|
|
364
|
+
+ (instancetype)configForMode:(NSString *)mode;
|
|
365
|
+
|
|
366
|
+
@end
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
/// Snapshot of the current panorama canvas. Returned by `snapshot`.
|
|
370
|
+
@interface RLISSnapshot : NSObject
|
|
371
|
+
/// Path to the JPEG written for this snapshot. Lives in
|
|
372
|
+
/// `NSTemporaryDirectory()` and is overwritten on each snapshot —
|
|
373
|
+
/// the host is expected to consume it before requesting the next.
|
|
374
|
+
@property (nonatomic, copy, readonly) NSString *panoramaPath;
|
|
375
|
+
@property (nonatomic, readonly) NSInteger width;
|
|
376
|
+
@property (nonatomic, readonly) NSInteger height;
|
|
377
|
+
@property (nonatomic, readonly) NSInteger acceptedCount;
|
|
378
|
+
@end
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@interface OpenCVIncrementalStitcher : NSObject
|
|
382
|
+
|
|
383
|
+
/// Initialise an engine ready to accept frames at the given compose
|
|
384
|
+
/// resolution. `composeWidth` and `composeHeight` are the dimensions
|
|
385
|
+
/// each ingested ARFrame is scaled to before feature extraction —
|
|
386
|
+
/// 720p (1280×720 landscape) is the design-doc default. Smaller =
|
|
387
|
+
/// faster + less memory at the cost of feature density.
|
|
388
|
+
///
|
|
389
|
+
/// `canvasWidth` and `canvasHeight` size the pre-allocated panorama
|
|
390
|
+
/// canvas (CV_8UC3). Pick generously to avoid clipping long pans.
|
|
391
|
+
/// Defaults if 0/0 passed: 4800×1600 (≈23 MB). The first accepted
|
|
392
|
+
/// frame is placed in the canvas centre so growth in either pan
|
|
393
|
+
/// direction is symmetric.
|
|
394
|
+
- (instancetype)initWithComposeWidth:(NSInteger)composeWidth
|
|
395
|
+
composeHeight:(NSInteger)composeHeight
|
|
396
|
+
canvasWidth:(NSInteger)canvasWidth
|
|
397
|
+
canvasHeight:(NSInteger)canvasHeight
|
|
398
|
+
featherPx:(NSInteger)featherPx
|
|
399
|
+
frameRotationDegrees:(NSInteger)frameRotationDegrees NS_DESIGNATED_INITIALIZER;
|
|
400
|
+
|
|
401
|
+
- (instancetype)init NS_UNAVAILABLE;
|
|
402
|
+
|
|
403
|
+
/// V15 — set the per-stage correction config. Should be called once
|
|
404
|
+
/// after init, before any `ingestPixelBuffer:` call. If never called,
|
|
405
|
+
/// the engine uses a default equivalent to
|
|
406
|
+
/// `+[RLISStitcherConfig configForMode:@"hybrid"]`.
|
|
407
|
+
- (void)setConfig:(RLISStitcherConfig *)config;
|
|
408
|
+
|
|
409
|
+
/// Try to incorporate `pixelBuffer` into the running panorama.
|
|
410
|
+
///
|
|
411
|
+
/// V6 (pose-driven): the engine builds the warp homography
|
|
412
|
+
/// `H = T · K · M · R_first⁻¹ · R_new · M · K⁻¹` directly from the
|
|
413
|
+
/// ARKit camera quaternion and intrinsics passed alongside the
|
|
414
|
+
/// frame. No feature extraction, no matching, no RANSAC — the
|
|
415
|
+
/// alignment is geometrically exact for the rotational pans that
|
|
416
|
+
/// dominate handheld panoramas. `M = diag(1, -1, -1)` converts
|
|
417
|
+
/// ARKit's (Y-up, -Z forward) camera frame to OpenCV's standard
|
|
418
|
+
/// (Y-down, +Z forward) frame.
|
|
419
|
+
///
|
|
420
|
+
/// Pose-delta gating still uses (yaw, pitch, fov*Degrees) to skip
|
|
421
|
+
/// frames outside the overlap window before any warp work runs.
|
|
422
|
+
///
|
|
423
|
+
/// `trackingPoor` should be YES when the AR session reports
|
|
424
|
+
/// non-tracking state at the time of this frame; the engine then
|
|
425
|
+
/// skips immediately with `RLISFrameOutcomeSkippedTrackingPoor`.
|
|
426
|
+
- (RLISFrameTelemetry *)ingestPixelBuffer:(CVPixelBufferRef)pixelBuffer
|
|
427
|
+
qx:(double)qx
|
|
428
|
+
qy:(double)qy
|
|
429
|
+
qz:(double)qz
|
|
430
|
+
qw:(double)qw
|
|
431
|
+
tx:(double)tx
|
|
432
|
+
ty:(double)ty
|
|
433
|
+
tz:(double)tz
|
|
434
|
+
fx:(double)fx
|
|
435
|
+
fy:(double)fy
|
|
436
|
+
cx:(double)cx
|
|
437
|
+
cy:(double)cy
|
|
438
|
+
imageWidth:(NSInteger)imageWidth
|
|
439
|
+
imageHeight:(NSInteger)imageHeight
|
|
440
|
+
yaw:(double)yaw
|
|
441
|
+
pitch:(double)pitch
|
|
442
|
+
fovHorizDegrees:(double)fovHorizDegrees
|
|
443
|
+
fovVertDegrees:(double)fovVertDegrees
|
|
444
|
+
trackingPoor:(BOOL)trackingPoor
|
|
445
|
+
NS_SWIFT_NAME(ingest(pixelBuffer:qx:qy:qz:qw:tx:ty:tz:fx:fy:cx:cy:imageWidth:imageHeight:yaw:pitch:fovHorizDegrees:fovVertDegrees:trackingPoor:));
|
|
446
|
+
|
|
447
|
+
/// Snapshot the current panorama as a JPEG (overwriting any previous
|
|
448
|
+
/// snapshot file). Cheap enough to call after each accepted frame
|
|
449
|
+
/// for live-preview UX. Returns nil with `error` populated if the
|
|
450
|
+
/// snapshot failed (disk full, permission, etc.).
|
|
451
|
+
- (nullable RLISSnapshot *)snapshotWithJpegQuality:(NSInteger)quality
|
|
452
|
+
error:(NSError **)error;
|
|
453
|
+
|
|
454
|
+
/// Final write at end of capture — same shape as `snapshot` but
|
|
455
|
+
/// written to `outputPath` (caller-controlled location). Includes
|
|
456
|
+
/// a tight crop to the actual panorama bounds (no trailing canvas
|
|
457
|
+
/// black). After this call, the canvas is reset; the engine is
|
|
458
|
+
/// ready for a fresh capture without re-init.
|
|
459
|
+
- (nullable RLISSnapshot *)finalizeAtPath:(NSString *)outputPath
|
|
460
|
+
jpegQuality:(NSInteger)quality
|
|
461
|
+
error:(NSError **)error;
|
|
462
|
+
|
|
463
|
+
/// Reset state so the engine can begin a new capture. Called
|
|
464
|
+
/// automatically by `finalizeAtPath:` and on construction.
|
|
465
|
+
- (void)reset;
|
|
466
|
+
|
|
467
|
+
/// Frames accepted into the panorama since `reset`. Read-only;
|
|
468
|
+
/// monotonically increasing within a capture.
|
|
469
|
+
@property (nonatomic, readonly) NSInteger acceptedCount;
|
|
470
|
+
|
|
471
|
+
@end
|
|
472
|
+
|
|
473
|
+
NS_ASSUME_NONNULL_END
|