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.
Files changed (151) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +21 -0
  4. package/README.md +189 -0
  5. package/RNImageStitcher.podspec +76 -0
  6. package/android/build.gradle +224 -0
  7. package/android/src/main/AndroidManifest.xml +3 -0
  8. package/android/src/main/cpp/CMakeLists.txt +124 -0
  9. package/android/src/main/cpp/image_stitcher_jni.cpp +145 -0
  10. package/android/src/main/cpp/keyframe_gate_jni.cpp +204 -0
  11. package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +426 -0
  12. package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +960 -0
  13. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2371 -0
  14. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +256 -0
  15. package/android/src/main/java/io/imagestitcher/rn/QualityChecker.kt +167 -0
  16. package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +39 -0
  17. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +558 -0
  18. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +35 -0
  19. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +784 -0
  20. package/android/src/main/java/io/imagestitcher/rn/ar/BackgroundRenderer.kt +176 -0
  21. package/android/src/main/java/io/imagestitcher/rn/ar/ShaderUtil.kt +67 -0
  22. package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +201 -0
  23. package/cpp/ar_frame_pose.h +63 -0
  24. package/cpp/keyframe_gate.cpp +927 -0
  25. package/cpp/keyframe_gate.hpp +240 -0
  26. package/cpp/stitcher.cpp +2207 -0
  27. package/cpp/stitcher.hpp +275 -0
  28. package/dist/ar/useARSession.d.ts +102 -0
  29. package/dist/ar/useARSession.js +133 -0
  30. package/dist/camera/ARCameraView.d.ts +93 -0
  31. package/dist/camera/ARCameraView.js +170 -0
  32. package/dist/camera/Camera.d.ts +134 -0
  33. package/dist/camera/Camera.js +688 -0
  34. package/dist/camera/CameraShutter.d.ts +80 -0
  35. package/dist/camera/CameraShutter.js +237 -0
  36. package/dist/camera/CameraView.d.ts +65 -0
  37. package/dist/camera/CameraView.js +117 -0
  38. package/dist/camera/CaptureControlsBar.d.ts +87 -0
  39. package/dist/camera/CaptureControlsBar.js +82 -0
  40. package/dist/camera/CaptureHeader.d.ts +62 -0
  41. package/dist/camera/CaptureHeader.js +81 -0
  42. package/dist/camera/CapturePreview.d.ts +70 -0
  43. package/dist/camera/CapturePreview.js +188 -0
  44. package/dist/camera/CaptureStatusOverlay.d.ts +75 -0
  45. package/dist/camera/CaptureStatusOverlay.js +326 -0
  46. package/dist/camera/CaptureThumbnailStrip.d.ts +87 -0
  47. package/dist/camera/CaptureThumbnailStrip.js +177 -0
  48. package/dist/camera/IncrementalPanGuide.d.ts +83 -0
  49. package/dist/camera/IncrementalPanGuide.js +267 -0
  50. package/dist/camera/PanoramaBandOverlay.d.ts +107 -0
  51. package/dist/camera/PanoramaBandOverlay.js +399 -0
  52. package/dist/camera/PanoramaConfirmModal.d.ts +57 -0
  53. package/dist/camera/PanoramaConfirmModal.js +128 -0
  54. package/dist/camera/PanoramaGuidance.d.ts +79 -0
  55. package/dist/camera/PanoramaGuidance.js +246 -0
  56. package/dist/camera/PanoramaSettingsModal.d.ts +311 -0
  57. package/dist/camera/PanoramaSettingsModal.js +611 -0
  58. package/dist/camera/ViewportCropOverlay.d.ts +46 -0
  59. package/dist/camera/ViewportCropOverlay.js +67 -0
  60. package/dist/camera/useCapture.d.ts +111 -0
  61. package/dist/camera/useCapture.js +160 -0
  62. package/dist/camera/useDeviceOrientation.d.ts +48 -0
  63. package/dist/camera/useDeviceOrientation.js +131 -0
  64. package/dist/camera/useVideoCapture.d.ts +79 -0
  65. package/dist/camera/useVideoCapture.js +151 -0
  66. package/dist/index.d.ts +26 -0
  67. package/dist/index.js +39 -0
  68. package/dist/quality/normaliseOrientation.d.ts +36 -0
  69. package/dist/quality/normaliseOrientation.js +62 -0
  70. package/dist/quality/runQualityCheck.d.ts +41 -0
  71. package/dist/quality/runQualityCheck.js +98 -0
  72. package/dist/sensors/useIMUTranslationGate.d.ts +70 -0
  73. package/dist/sensors/useIMUTranslationGate.js +235 -0
  74. package/dist/stitching/IncrementalStitcherView.d.ts +41 -0
  75. package/dist/stitching/IncrementalStitcherView.js +157 -0
  76. package/dist/stitching/incremental.d.ts +930 -0
  77. package/dist/stitching/incremental.js +133 -0
  78. package/dist/stitching/stitchFrames.d.ts +55 -0
  79. package/dist/stitching/stitchFrames.js +56 -0
  80. package/dist/stitching/stitchVideo.d.ts +119 -0
  81. package/dist/stitching/stitchVideo.js +57 -0
  82. package/dist/stitching/useIncrementalJSDriver.d.ts +74 -0
  83. package/dist/stitching/useIncrementalJSDriver.js +199 -0
  84. package/dist/stitching/useIncrementalStitcher.d.ts +58 -0
  85. package/dist/stitching/useIncrementalStitcher.js +172 -0
  86. package/dist/types.d.ts +58 -0
  87. package/dist/types.js +15 -0
  88. package/ios/Package.swift +72 -0
  89. package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +33 -0
  90. package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +40 -0
  91. package/ios/Sources/RNImageStitcher/ARSessionBridge.m +55 -0
  92. package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +149 -0
  93. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +2727 -0
  94. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +85 -0
  95. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +625 -0
  96. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +328 -0
  97. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +141 -0
  98. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +278 -0
  99. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +473 -0
  100. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +1326 -0
  101. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +97 -0
  102. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +296 -0
  103. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +103 -0
  104. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +3285 -0
  105. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +238 -0
  106. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +1880 -0
  107. package/ios/Sources/RNImageStitcher/QualityChecker.swift +252 -0
  108. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.m +26 -0
  109. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.swift +72 -0
  110. package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +114 -0
  111. package/ios/Sources/RNImageStitcher/RNSARSession.swift +1111 -0
  112. package/ios/Sources/RNImageStitcher/Stitcher.swift +243 -0
  113. package/ios/Sources/RNImageStitcher/StitcherBridge.m +28 -0
  114. package/ios/Sources/RNImageStitcher/StitcherBridge.swift +246 -0
  115. package/package.json +73 -0
  116. package/react-native.config.js +34 -0
  117. package/scripts/opencv-version.txt +1 -0
  118. package/scripts/postinstall-fetch-binaries.js +286 -0
  119. package/src/ar/useARSession.ts +210 -0
  120. package/src/camera/.gitkeep +0 -0
  121. package/src/camera/ARCameraView.tsx +256 -0
  122. package/src/camera/Camera.tsx +1053 -0
  123. package/src/camera/CameraShutter.tsx +292 -0
  124. package/src/camera/CameraView.tsx +157 -0
  125. package/src/camera/CaptureControlsBar.tsx +204 -0
  126. package/src/camera/CaptureHeader.tsx +184 -0
  127. package/src/camera/CapturePreview.tsx +318 -0
  128. package/src/camera/CaptureStatusOverlay.tsx +391 -0
  129. package/src/camera/CaptureThumbnailStrip.tsx +277 -0
  130. package/src/camera/IncrementalPanGuide.tsx +328 -0
  131. package/src/camera/PanoramaBandOverlay.tsx +498 -0
  132. package/src/camera/PanoramaConfirmModal.tsx +206 -0
  133. package/src/camera/PanoramaGuidance.tsx +327 -0
  134. package/src/camera/PanoramaSettingsModal.tsx +1357 -0
  135. package/src/camera/ViewportCropOverlay.tsx +81 -0
  136. package/src/camera/useCapture.ts +279 -0
  137. package/src/camera/useDeviceOrientation.ts +140 -0
  138. package/src/camera/useVideoCapture.ts +236 -0
  139. package/src/index.ts +53 -0
  140. package/src/quality/.gitkeep +0 -0
  141. package/src/quality/normaliseOrientation.ts +79 -0
  142. package/src/quality/runQualityCheck.ts +131 -0
  143. package/src/sensors/useIMUTranslationGate.ts +347 -0
  144. package/src/stitching/.gitkeep +0 -0
  145. package/src/stitching/IncrementalStitcherView.tsx +198 -0
  146. package/src/stitching/incremental.ts +1021 -0
  147. package/src/stitching/stitchFrames.ts +88 -0
  148. package/src/stitching/stitchVideo.ts +153 -0
  149. package/src/stitching/useIncrementalJSDriver.ts +273 -0
  150. package/src/stitching/useIncrementalStitcher.ts +252 -0
  151. package/src/types.ts +78 -0
@@ -0,0 +1,275 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // stitcher.hpp — shared cv::Stitcher orchestration used by both
4
+ // iOS (via Obj-C++ bridge in OpenCVStitcherBridge.mm) and Android
5
+ // (via JNI in image_stitcher_jni.cpp).
6
+ //
7
+ // Why this exists
8
+ // ───────────────
9
+ //
10
+ // Before 2026-05-15, iOS had a hand-rolled cv::detail::* pipeline
11
+ // (~3,000 lines in OpenCVStitcher.mm) while Android used the
12
+ // high-level cv::Stitcher::create() API (~600 lines in
13
+ // image_stitcher_jni.cpp). Two implementations of the same algorithm
14
+ // drifted independently — fixes landed on one platform and didn't on
15
+ // the other. This file collapses that into a single source of truth.
16
+ //
17
+ // V1 scope (this commit): port the Android high-level pipeline
18
+ // verbatim into shared C++ + add the C+D progressive-confidence retry
19
+ // loop + dimension/memory instrumentation. Both platforms call this.
20
+ //
21
+ // V2 scope (follow-up): port iOS's manual cv::detail::* pipeline
22
+ // features (explicit leaveBiggestComponent retry around
23
+ // just the prune step rather than the whole stitch — 5-10× cheaper;
24
+ // wave correction; exposure compensator) into this file as a
25
+ // SECOND code path selectable via StitchConfig::useManualPipeline.
26
+ // Until then, iOS gets the Android-level capability set.
27
+ //
28
+ // API design
29
+ // ──────────
30
+ //
31
+ // One function: stitchFramePaths(framePaths, outputPath, config).
32
+ //
33
+ // All inputs marshalled as primitive C++ types. Output is a
34
+ // StitchResult struct that carries success/error info + the C+D
35
+ // drop-count telemetry. Loggin happens via an optional callback so
36
+ // the iOS bridge can plumb to os_log and the Android bridge to
37
+ // __android_log_print — same source, different sink.
38
+ //
39
+ // Threading: not thread-safe. Caller must serialise. Each
40
+ // invocation is independent (no shared mutable state).
41
+
42
+ #pragma once
43
+
44
+ #include <cstdint>
45
+ #include <functional>
46
+ #include <string>
47
+ #include <vector>
48
+
49
+
50
+ namespace retailens {
51
+
52
+ // Stable error codes. Mirror the JS-side `StitchErrorCode` enum so
53
+ // the bridge layers can map these to NSError.code / Java throwable
54
+ // without translation tables.
55
+ //
56
+ // Bit-for-bit aligned to cv::Stitcher::Status values where possible
57
+ // (NeedMoreImages, HomographyEstimationFailed, CameraParamsAdjustFailed)
58
+ // + a few new codes for failure modes that cv::Stitcher itself
59
+ // doesn't surface (image read/write failure, all-frames-dropped).
60
+ //
61
+ // Manual-pipeline-specific failure modes (added 2026-05-15 as part of
62
+ // the V2 shared-port code-review pass; previously these all collapsed
63
+ // into UnknownCvException, which made post-mortem triage from JS-side
64
+ // telemetry impossible):
65
+ // PreStitchMemoryAbort — manual pipeline detected RSS above
66
+ // the per-device pre-stitch threshold
67
+ // and bailed before allocating compose
68
+ // buffers. Operator should retry on
69
+ // fresh app launch or lower compose MP.
70
+ // ComposeResizeFailed — cv::resize threw inside the compose-
71
+ // stage downscale loop (Step 7c). Most
72
+ // commonly a recycled-mmap allocator
73
+ // issue when stitching consecutively;
74
+ // a fresh process usually recovers.
75
+ // WarpFailed — warper->warp threw inside the warp
76
+ // loop (Step 8b). Camera params from
77
+ // BA may be degenerate; check
78
+ // framesIncluded vs framesRequested.
79
+ // EmptyPanorama — blender->blend completed but produced
80
+ // a 0×0 panorama. Should never happen
81
+ // in practice; if it does, the failure
82
+ // is upstream in the warp/feed loop.
83
+ enum class StitchErrorCode : int32_t {
84
+ Ok = 0,
85
+ NeedMoreImages = 1, // cv::Stitcher::ERR_NEED_MORE_IMGS
86
+ HomographyEstimationFailed = 2, // cv::Stitcher::ERR_HOMOGRAPHY_EST_FAIL
87
+ CameraParamsAdjustFailed = 3, // cv::Stitcher::ERR_CAMERA_PARAMS_ADJUST_FAIL
88
+ ImageReadFailed = 100,
89
+ ImageWriteFailed = 101,
90
+ AllFramesDroppedByConfidence = 102,
91
+ PreStitchMemoryAbort = 103,
92
+ ComposeResizeFailed = 104,
93
+ WarpFailed = 105,
94
+ EmptyPanorama = 106,
95
+ InvalidArgument = 200,
96
+ UnknownCvException = 300,
97
+ };
98
+
99
+
100
+ // Stitcher mode selector — maps to cv::Stitcher::Mode.
101
+ //
102
+ // Panorama: rotation-only (spherical/cylindrical/plane warper +
103
+ // BundleAdjusterRay + BestOf2NearestMatcher). Best for
104
+ // rotate-in-place captures.
105
+ // Scans: affine (plane warper + BundleAdjusterAffine +
106
+ // AffineBestOf2NearestMatcher). Best for shelf-pan
107
+ // translation captures.
108
+ //
109
+ // Caller (typically the JS engineMode resolver) picks per capture.
110
+ // "auto" resolution happens UPSTREAM in JS via accumulated
111
+ // translation vs rotation totals from the KeyframeGate — by the
112
+ // time we get here, it's a concrete mode.
113
+ enum class StitchMode : int32_t {
114
+ Panorama = 0,
115
+ Scans = 1,
116
+ };
117
+
118
+
119
+ // Configuration bundle for a single stitch invocation. All fields
120
+ // have safe defaults so callers only override what they care about.
121
+ //
122
+ // Resolution budgets (`*ResolMP`) are in megapixels per frame:
123
+ // < 0.0 → entry-point picks its own appropriate default
124
+ // ≥ 0.0 → cap at this MP target via cv::Stitcher::set*Resol()
125
+ //
126
+ // compositingResolMP intentionally defaults to a NEGATIVE SENTINEL so
127
+ // the two entry points can pick different appropriate defaults:
128
+ //
129
+ // * High-level stitchFramePaths() (cv::Stitcher::create wrapper):
130
+ // falls back to 1.0 MP. cv::Stitcher's library default for
131
+ // compositing is ORIG_RESOL (-1.0) which composes at full sensor
132
+ // resolution and trivially OOMs on Android — 1.0 MP caps that
133
+ // while preserving most of the sharpness.
134
+ //
135
+ // * Manual stitchFramePathsManual() (cv::detail::* pipeline):
136
+ // falls back to 0.6 MP. The hand-rolled pipeline blends at
137
+ // compose-MP DIRECTLY (rather than re-warping from features-
138
+ // resolution work frames as cv::Stitcher does internally), which
139
+ // means memory peak scales more aggressively with compose-MP.
140
+ // 1.0 MP pushed iOS into jetsam territory; 0.6 MP is the "safe
141
+ // sharp" sweet spot documented in OpenCVStitcher.mm comments.
142
+ struct StitchConfig {
143
+ std::string warperType = "plane"; // "plane"|"cylindrical"|"spherical"
144
+ std::string blenderType = "multiband"; // "multiband"|"feather"
145
+ std::string seamFinderType = "graphcut"; // "graphcut"|"skip"|"voronoi"
146
+ StitchMode stitchMode = StitchMode::Panorama;
147
+ std::string captureOrientation = "portrait"; // "portrait"|"portrait-upside-down"|"landscape-left"|"landscape-right"
148
+ bool useInscribedRectCrop = false; // bbox-only crop is the default
149
+ double registrationResolMP = -1.0; // < 0 = cv default (0.6 MP)
150
+ double seamEstimationResolMP = -1.0; // < 0 = cv default (0.1 MP)
151
+ double compositingResolMP = -1.0; // < 0 = entry-specific default (high-level: 1.0 MP, manual: 0.6 MP)
152
+ int jpegQuality = 85;
153
+
154
+ // Total device RAM in megabytes. Used by the manual pipeline's
155
+ // pre-stitch memory abort heuristic to decide whether to short-
156
+ // circuit a stitch that would likely OOM. When < 0 (default),
157
+ // falls back to a conservative assumption (4 GB = kAssumedTotalRAMGB
158
+ // in stitcher.cpp). Callers should plumb:
159
+ // iOS: NSProcessInfo.processInfo.physicalMemory / (1024*1024)
160
+ // Android: ActivityManager.getMemoryInfo().totalMem / (1024*1024)
161
+ // or sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE) / (1024*1024)
162
+ double availableRamMB = -1.0;
163
+
164
+ // Manual-pipeline opt-in (V2 of the shared port).
165
+ //
166
+ // Set true to route stitchFramePaths() through the hand-rolled
167
+ // cv::detail::* pipeline implemented in stitchFramePathsManual()
168
+ // instead of the high-level cv::Stitcher::create() wrapper. The
169
+ // manual pipeline gives finer control that the high-level API
170
+ // hides behind defaults that don't fit our shelf-pan capture
171
+ // shape:
172
+ //
173
+ // * Seam finder runs at a SEPARATE seam-MP budget (~0.1 MP
174
+ // default) and the seam mask is upscaled back to compose-MP
175
+ // before feeding the blender. GraphCut is roughly O(N²) in
176
+ // pixels — running it at compose-MP (1.0 MP) costs ~100× more
177
+ // than at seam-MP and was timing out finalize() in JS.
178
+ //
179
+ // * MultiBandBlender's Laplacian pyramid is built at compose-MP
180
+ // directly (rather than re-warping from features-resolution
181
+ // work frames). Cylindrical-era sharpness restored on iOS.
182
+ //
183
+ // * leaveBiggestComponent runs at PRUNE granularity (i.e., the
184
+ // retry happens BEFORE the expensive BA / warp / blend), not
185
+ // around the whole pipeline. Retry cost is 5-10× cheaper than
186
+ // the high-level cv::Stitcher's C+D loop that re-runs every
187
+ // stage at each threshold.
188
+ //
189
+ // * Explicit BundleAdjusterRay + wave correction + median focal
190
+ // length scale determination — all features cv::Stitcher does
191
+ // internally but with parameters we can't override (iter cap,
192
+ // wave-correct kind, confidence threshold).
193
+ //
194
+ // Android currently leaves this false (the high-level pipeline
195
+ // works fine on Android's pre-V16 keyframe budgets). iOS will
196
+ // flip it to true once the manual port is verified — separate
197
+ // commit from this V2 introduction.
198
+ bool useManualPipeline = false;
199
+ };
200
+
201
+
202
+ // Result returned to the caller. On success: outputPath written +
203
+ // dimensions + C+D telemetry. On failure: errorCode + errorMessage
204
+ // (errorCode is the primary signal; message is human-readable).
205
+ struct StitchResult {
206
+ bool success = false;
207
+ StitchErrorCode errorCode = StitchErrorCode::UnknownCvException;
208
+ std::string errorMessage;
209
+
210
+ int32_t width = 0;
211
+ int32_t height = 0;
212
+
213
+ // C+D telemetry — filled in even on success. See
214
+ // 2026-05-15 commit 57ecccd for context.
215
+ int32_t framesRequested = 0;
216
+ int32_t framesIncluded = 0;
217
+ double finalConfidenceThresh = -1.0; // The threshold value that succeeded; -1 if not relevant.
218
+
219
+ int64_t durationMs = 0;
220
+ };
221
+
222
+
223
+ // Logging callback type — bridge layers plug their platform logger
224
+ // (os_log on iOS, __android_log_print on Android). Use nullptr to
225
+ // silence.
226
+ //
227
+ // level: 0=info, 1=warn, 2=error
228
+ // tag: short tag like "[stitch]" or "[dimstat]"
229
+ // msg: the formatted message (caller must format before passing)
230
+ using LogFn = std::function<void(int level, const char* tag, const char* msg)>;
231
+
232
+
233
+ // Primary entry point. Loads input JPEGs, configures cv::Stitcher
234
+ // per the config, runs the C+D progressive-confidence retry loop,
235
+ // crops, bake-rotates, writes the output JPEG.
236
+ //
237
+ // When `config.useManualPipeline` is true the call is routed to
238
+ // `stitchFramePathsManual()` instead — see below for the manual
239
+ // pipeline's structural differences.
240
+ //
241
+ // Thread-safe per-call (no shared state); caller must serialise
242
+ // concurrent calls.
243
+ //
244
+ // On failure (StitchResult::success == false) the output file is
245
+ // not written. errorCode + errorMessage tell why.
246
+ StitchResult stitchFramePaths(
247
+ const std::vector<std::string>& framePaths,
248
+ const std::string& outputPath,
249
+ const StitchConfig& config,
250
+ LogFn logFn = nullptr);
251
+
252
+
253
+ // Manual cv::detail::* pipeline entry point. Same input/output
254
+ // contract as stitchFramePaths(), but uses the hand-rolled stitching
255
+ // pipeline ported from OpenCVStitcher.mm (ORB → BestOf2NearestMatcher
256
+ // → HomographyBasedEstimator → BundleAdjusterRay → wave correct →
257
+ // median-focal warper scale → two-stage resolution (registration_MP
258
+ // / compose_MP) → GraphCutSeamFinder at seam_MP → MultiBandBlender).
259
+ //
260
+ // Use via config.useManualPipeline = true to get this entry point
261
+ // indirectly from stitchFramePaths(). Also callable directly if a
262
+ // future caller wants to bypass the high-level wrapper entirely.
263
+ //
264
+ // Thread-safe per-call (no shared state); caller must serialise
265
+ // concurrent calls.
266
+ //
267
+ // On failure (StitchResult::success == false) the output file is
268
+ // not written. errorCode + errorMessage tell why.
269
+ StitchResult stitchFramePathsManual(
270
+ const std::vector<std::string>& framePaths,
271
+ const std::string& outputPath,
272
+ const StitchConfig& config,
273
+ LogFn logFn = nullptr);
274
+
275
+ } // namespace retailens
@@ -0,0 +1,102 @@
1
+ /**
2
+ * useARSession — React hook for the SDK's ARKit (iOS) / ARCore
3
+ * (Android) session foundation.
4
+ *
5
+ * Phase 4 of the AR measurement plan
6
+ * (docs/site-content/design/2026-04-29-ar-measurement-and-detection.md).
7
+ *
8
+ * What this gives the host:
9
+ * - `isAvailable`: whether the device can run AR at all
10
+ * - `trackingState`: current AR tracking quality (mirrors Apple's
11
+ * enum values exactly — same numeric ids on both platforms)
12
+ * - `start()` / `stop()`: lifecycle controls
13
+ * - `getFramePoses()`: snapshot the per-frame pose log captured
14
+ * during the most recent session, used by Phase 5 stitching
15
+ * and Phase 6 measurement
16
+ *
17
+ * What this does NOT give:
18
+ * The hook is camera-agnostic. It just runs the AR tracking
19
+ * session. Frame display + capture happen via the SDK's
20
+ * AR-backed `<CameraView>` (Phase 4.4 — coming) or the existing
21
+ * vision-camera-backed view if AR is unavailable.
22
+ */
23
+ /**
24
+ * AR tracking state. Numeric values mirror iOS' enum and the
25
+ * Android constants in `RNSARSession.companion`. Cross-
26
+ * platform identical; no branching needed in JS.
27
+ */
28
+ export declare enum ARTrackingState {
29
+ /** AR not running, not supported, or session was stopped. */
30
+ NotAvailable = 0,
31
+ /** Session running but tracking quality not yet usable. */
32
+ Initialising = 1,
33
+ /** Session running with usable tracking — poses are good. */
34
+ Tracking = 2,
35
+ /** Tracking quality dropped mid-session. Poses degraded. */
36
+ Limited = 3
37
+ }
38
+ /**
39
+ * One captured frame's pose. Coordinates are in the AR session's
40
+ * world frame (right-handed, Y-up on iOS / Y-up on Android), with
41
+ * translation in metres. Rotation is a unit quaternion; w is the
42
+ * real component.
43
+ */
44
+ export interface FramePose {
45
+ tx: number;
46
+ ty: number;
47
+ tz: number;
48
+ qx: number;
49
+ qy: number;
50
+ qz: number;
51
+ qw: number;
52
+ /** Camera intrinsics (focal length + principal point) in pixels. */
53
+ fx: number;
54
+ fy: number;
55
+ cx: number;
56
+ cy: number;
57
+ imageWidth: number;
58
+ imageHeight: number;
59
+ /** Frame timestamp in ms relative to AR session start. */
60
+ timestampMs: number;
61
+ trackingState: ARTrackingState;
62
+ }
63
+ export interface UseARSessionReturn {
64
+ /**
65
+ * Whether the device can run AR. Set after the first `start()`
66
+ * call (or by the explicit `checkAvailability()`). False on
67
+ * older iPhones, simulators, and unsupported Android devices.
68
+ */
69
+ isAvailable: boolean;
70
+ /**
71
+ * Whether the session is currently running. True between
72
+ * `start()` and `stop()`.
73
+ */
74
+ isRunning: boolean;
75
+ /** Current tracking quality. Polled every 500ms while running. */
76
+ trackingState: ARTrackingState;
77
+ /**
78
+ * Start the AR session. On Android, may trigger a Play Services
79
+ * for AR install dialog the first time it runs. Throws if the
80
+ * device doesn't support AR.
81
+ */
82
+ start: () => Promise<void>;
83
+ /**
84
+ * Stop the AR session and clear the pose log. Idempotent;
85
+ * calling on a stopped session is a no-op.
86
+ */
87
+ stop: () => Promise<void>;
88
+ /**
89
+ * Snapshot the per-frame pose log captured since the last
90
+ * `start()`. Used by the stitcher and measurement APIs after
91
+ * recording stops.
92
+ */
93
+ getFramePoses: () => Promise<FramePose[]>;
94
+ /**
95
+ * Drop everything in the pose log. Call before each new
96
+ * panorama capture so the log doesn't carry stale poses from
97
+ * an earlier session.
98
+ */
99
+ clearPoseLog: () => Promise<void>;
100
+ }
101
+ export declare function useARSession(): UseARSessionReturn;
102
+ //# sourceMappingURL=useARSession.d.ts.map
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * useARSession — React hook for the SDK's ARKit (iOS) / ARCore
5
+ * (Android) session foundation.
6
+ *
7
+ * Phase 4 of the AR measurement plan
8
+ * (docs/site-content/design/2026-04-29-ar-measurement-and-detection.md).
9
+ *
10
+ * What this gives the host:
11
+ * - `isAvailable`: whether the device can run AR at all
12
+ * - `trackingState`: current AR tracking quality (mirrors Apple's
13
+ * enum values exactly — same numeric ids on both platforms)
14
+ * - `start()` / `stop()`: lifecycle controls
15
+ * - `getFramePoses()`: snapshot the per-frame pose log captured
16
+ * during the most recent session, used by Phase 5 stitching
17
+ * and Phase 6 measurement
18
+ *
19
+ * What this does NOT give:
20
+ * The hook is camera-agnostic. It just runs the AR tracking
21
+ * session. Frame display + capture happen via the SDK's
22
+ * AR-backed `<CameraView>` (Phase 4.4 — coming) or the existing
23
+ * vision-camera-backed view if AR is unavailable.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ARTrackingState = void 0;
27
+ exports.useARSession = useARSession;
28
+ const react_1 = require("react");
29
+ const react_native_1 = require("react-native");
30
+ /**
31
+ * AR tracking state. Numeric values mirror iOS' enum and the
32
+ * Android constants in `RNSARSession.companion`. Cross-
33
+ * platform identical; no branching needed in JS.
34
+ */
35
+ var ARTrackingState;
36
+ (function (ARTrackingState) {
37
+ /** AR not running, not supported, or session was stopped. */
38
+ ARTrackingState[ARTrackingState["NotAvailable"] = 0] = "NotAvailable";
39
+ /** Session running but tracking quality not yet usable. */
40
+ ARTrackingState[ARTrackingState["Initialising"] = 1] = "Initialising";
41
+ /** Session running with usable tracking — poses are good. */
42
+ ARTrackingState[ARTrackingState["Tracking"] = 2] = "Tracking";
43
+ /** Tracking quality dropped mid-session. Poses degraded. */
44
+ ARTrackingState[ARTrackingState["Limited"] = 3] = "Limited";
45
+ })(ARTrackingState || (exports.ARTrackingState = ARTrackingState = {}));
46
+ function getNativeModule() {
47
+ const m = react_native_1.NativeModules['RNSARSession'];
48
+ if (!m || typeof m !== 'object')
49
+ return null;
50
+ return m;
51
+ }
52
+ /**
53
+ * Polling interval for tracking-state updates. 500ms is enough to
54
+ * feel responsive in the UI without flooding the bridge.
55
+ */
56
+ const STATE_POLL_INTERVAL_MS = 500;
57
+ function useARSession() {
58
+ const [isAvailable, setIsAvailable] = (0, react_1.useState)(false);
59
+ const [isRunning, setIsRunning] = (0, react_1.useState)(false);
60
+ const [trackingState, setTrackingState] = (0, react_1.useState)(ARTrackingState.NotAvailable);
61
+ const pollRef = (0, react_1.useRef)(null);
62
+ const native = getNativeModule();
63
+ // Probe availability once on mount. Running on a device without
64
+ // AR support shouldn't crash anything — `isAvailable` stays
65
+ // false and the rest of the SDK falls back to vision-camera.
66
+ (0, react_1.useEffect)(() => {
67
+ if (!native)
68
+ return;
69
+ native.isSupported().then(setIsAvailable).catch((err) => {
70
+ // eslint-disable-next-line no-console
71
+ console.warn('[useARSession] isSupported failed', err);
72
+ });
73
+ }, [native]);
74
+ const stopPolling = (0, react_1.useCallback)(() => {
75
+ if (pollRef.current !== null) {
76
+ clearInterval(pollRef.current);
77
+ pollRef.current = null;
78
+ }
79
+ }, []);
80
+ const startPolling = (0, react_1.useCallback)(() => {
81
+ stopPolling();
82
+ if (!native)
83
+ return;
84
+ pollRef.current = setInterval(async () => {
85
+ try {
86
+ const state = await native.getState();
87
+ setIsRunning(state.isRunning);
88
+ setTrackingState(state.trackingState);
89
+ }
90
+ catch {
91
+ // Bridge errors during tear-down are expected; ignore.
92
+ }
93
+ }, STATE_POLL_INTERVAL_MS);
94
+ }, [native, stopPolling]);
95
+ // Always tear down the poll on unmount.
96
+ (0, react_1.useEffect)(() => stopPolling, [stopPolling]);
97
+ const start = (0, react_1.useCallback)(async () => {
98
+ if (!native) {
99
+ throw new Error('useARSession: RNSARSession native module unavailable');
100
+ }
101
+ await native.start();
102
+ setIsRunning(true);
103
+ startPolling();
104
+ }, [native, startPolling]);
105
+ const stop = (0, react_1.useCallback)(async () => {
106
+ if (!native)
107
+ return;
108
+ stopPolling();
109
+ await native.stop();
110
+ setIsRunning(false);
111
+ setTrackingState(ARTrackingState.NotAvailable);
112
+ }, [native, stopPolling]);
113
+ const getFramePoses = (0, react_1.useCallback)(async () => {
114
+ if (!native)
115
+ return [];
116
+ return native.snapshotPoseLog();
117
+ }, [native]);
118
+ const clearPoseLog = (0, react_1.useCallback)(async () => {
119
+ if (!native)
120
+ return;
121
+ await native.clearPoseLog();
122
+ }, [native]);
123
+ return {
124
+ isAvailable,
125
+ isRunning,
126
+ trackingState,
127
+ start,
128
+ stop,
129
+ getFramePoses,
130
+ clearPoseLog,
131
+ };
132
+ }
133
+ //# sourceMappingURL=useARSession.js.map
@@ -0,0 +1,93 @@
1
+ /**
2
+ * ARCameraView — AR-backed alternative to ``<CameraView>`` for
3
+ * audits that need pose-aware capture (panorama mode, packet
4
+ * detection). Renders the ARKit camera feed via the native
5
+ * `RNSARCameraView` UIView; the underlying ARSession is the
6
+ * SDK singleton (`RNSARSession.shared`), shared between the
7
+ * preview and the pose log that feeds Phase 5 stitching + Phase 6
8
+ * measurement.
9
+ *
10
+ * Why a separate component (vs. a polymorphic CameraView)?
11
+ * 1. **Different imperative API.** The vision-camera-backed
12
+ * CameraView exposes `takePhoto / startRecording` via its ref
13
+ * (Phase 5 will add equivalents to this component, but they
14
+ * route through ARFrame.capturedImage + AVAssetWriter rather
15
+ * than vision-camera's APIs).
16
+ * 2. **Camera-access conflict.** ARKit and AVCaptureSession
17
+ * can't share the camera. Forcing the host to pick one
18
+ * component over the other (instead of toggling a prop on a
19
+ * shared component) makes the conflict impossible to misuse —
20
+ * you can't accidentally mount both at the same time.
21
+ * 3. **Lifecycle clarity.** The native side starts the AR
22
+ * session in `didMoveToWindow`. Mount = start, unmount =
23
+ * stop. No flag-twiddling.
24
+ *
25
+ * This component is preview-only in Phase 4.4. Photo + video
26
+ * capture come in Phase 5 (Step 5 of the AR design plan). Until
27
+ * then, the host's panorama capture flow continues to use
28
+ * vision-camera; ARCameraView is opt-in via a settings flag for
29
+ * developer verification.
30
+ */
31
+ import React from 'react';
32
+ import { type ViewStyle } from 'react-native';
33
+ export interface ARCameraViewProps {
34
+ /** Layout style, typically `StyleSheet.absoluteFill` or `flex: 1`. */
35
+ style?: ViewStyle;
36
+ /**
37
+ * Optional themed guidance banner shown over the preview at the
38
+ * top, mirrors the `<CameraView>` prop so host apps can swap
39
+ * components without rewriting their guidance text plumbing.
40
+ */
41
+ guidance?: string;
42
+ }
43
+ /**
44
+ * Imperative handle exposed via the ref — shape mirrors the subset
45
+ * of vision-camera's `Camera` ref methods that the host's
46
+ * `useCapture` / `useVideoCapture` hooks call. Hosts can pass the
47
+ * SAME ref to those hooks as they do for the vision-camera path,
48
+ * with no branching required.
49
+ *
50
+ * Note we do NOT exhaustively mirror vision-camera's API surface —
51
+ * only the methods the panorama capture flow uses today. As the
52
+ * SDK grows AR-aware features, methods are added here.
53
+ */
54
+ export interface ARCameraViewHandle {
55
+ /**
56
+ * Capture the latest ARFrame as a JPEG. Resolves with a
57
+ * vision-camera-compatible PhotoFile (`{ path, width, height,
58
+ * isMirrored, isRawPhoto }`). Native generates a temp path —
59
+ * caller does NOT need to construct one.
60
+ */
61
+ takePhoto: (options?: {
62
+ quality?: number;
63
+ }) => Promise<{
64
+ path: string;
65
+ width: number;
66
+ height: number;
67
+ isMirrored: boolean;
68
+ isRawPhoto: boolean;
69
+ }>;
70
+ /**
71
+ * Begin recording AR frames into an mp4. Mirrors vision-camera's
72
+ * callback-based API: takes `onRecordingFinished` /
73
+ * `onRecordingError` handlers; the actual VideoFile is delivered
74
+ * via `onRecordingFinished` AFTER the host calls `stopRecording`.
75
+ *
76
+ * Synchronous return (void) — useVideoCapture wraps it in a
77
+ * Promise on top of the callbacks.
78
+ */
79
+ startRecording: (options: {
80
+ onRecordingFinished?: (video: {
81
+ path: string;
82
+ duration: number;
83
+ size: number;
84
+ width: number;
85
+ height: number;
86
+ }) => void;
87
+ onRecordingError?: (err: Error) => void;
88
+ }) => void;
89
+ /** Finalise the in-progress recording. */
90
+ stopRecording: () => Promise<void>;
91
+ }
92
+ export declare const ARCameraView: React.ForwardRefExoticComponent<ARCameraViewProps & React.RefAttributes<ARCameraViewHandle>>;
93
+ //# sourceMappingURL=ARCameraView.d.ts.map