@photivra/engine 2.18.0 → 2.20.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 (335) hide show
  1. package/CHANGELOG.md +179 -1
  2. package/CITATION.cff +1 -1
  3. package/NODE_SUPPORT.md +7 -5
  4. package/README.md +6 -6
  5. package/SECURITY.md +1 -1
  6. package/THIRD_PARTY.md +14 -10
  7. package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
  8. package/dist/capture/dng-lens-correction-opcodes.js +119 -0
  9. package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
  10. package/dist/capture/focus-control.js +2 -75
  11. package/dist/capture/focus-control.js.map +1 -1
  12. package/dist/capture/native-environment-lean.d.ts +1 -0
  13. package/dist/capture/native-environment-lean.js +2 -6
  14. package/dist/capture/native-environment-lean.js.map +1 -1
  15. package/dist/capture/native-environment-optical-stage.d.ts +1 -0
  16. package/dist/capture/native-environment-optical-stage.js +2 -5
  17. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  18. package/dist/capture/native-environment-raw.js +1 -5
  19. package/dist/capture/native-environment-raw.js.map +1 -1
  20. package/dist/capture/native-raw-dng.d.ts +6 -0
  21. package/dist/capture/native-raw-dng.js +16 -2
  22. package/dist/capture/native-raw-dng.js.map +1 -1
  23. package/dist/capture/release-sequence.js +2 -47
  24. package/dist/capture/release-sequence.js.map +1 -1
  25. package/dist/capture/sensor-raw-producer.js +3 -5
  26. package/dist/capture/sensor-raw-producer.js.map +1 -1
  27. package/dist/color/white-balance.d.ts +2 -0
  28. package/dist/color/white-balance.js +3 -56
  29. package/dist/color/white-balance.js.map +1 -1
  30. package/dist/composition/image-formation-plan.d.ts +1 -0
  31. package/dist/composition/image-formation-plan.js +3 -49
  32. package/dist/composition/image-formation-plan.js.map +1 -1
  33. package/dist/core/evidence-provenance.js +1 -16
  34. package/dist/core/evidence-provenance.js.map +1 -1
  35. package/dist/core/record-validation.d.ts +54 -0
  36. package/dist/core/record-validation.js +176 -0
  37. package/dist/core/record-validation.js.map +1 -1
  38. package/dist/core/scientific-assurance.js +1 -18
  39. package/dist/core/scientific-assurance.js.map +1 -1
  40. package/dist/core/shared-helpers.d.ts +35 -0
  41. package/dist/core/shared-helpers.js +38 -0
  42. package/dist/core/shared-helpers.js.map +1 -0
  43. package/dist/core/spectral-composition.js +1 -36
  44. package/dist/core/spectral-composition.js.map +1 -1
  45. package/dist/core/spectral.js +1 -19
  46. package/dist/core/spectral.js.map +1 -1
  47. package/dist/core/version.d.ts +1 -1
  48. package/dist/core/version.js +1 -1
  49. package/dist/core/version.js.map +1 -1
  50. package/dist/equipment/exposure-capabilities.d.ts +1 -0
  51. package/dist/equipment/exposure-capabilities.js +2 -36
  52. package/dist/equipment/exposure-capabilities.js.map +1 -1
  53. package/dist/equipment/generic-lens-catalogue.d.ts +8 -2
  54. package/dist/equipment/generic-lens-catalogue.js +31 -2
  55. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  56. package/dist/equipment/generic-tier-assets.js +2 -3
  57. package/dist/equipment/generic-tier-assets.js.map +1 -1
  58. package/dist/equipment/generic-tier-presets.js +2 -3
  59. package/dist/equipment/generic-tier-presets.js.map +1 -1
  60. package/dist/equipment/iso-capabilities.js +2 -49
  61. package/dist/equipment/iso-capabilities.js.map +1 -1
  62. package/dist/equipment/metering-capabilities.js +1 -26
  63. package/dist/equipment/metering-capabilities.js.map +1 -1
  64. package/dist/equipment/release-capabilities.js +1 -53
  65. package/dist/equipment/release-capabilities.js.map +1 -1
  66. package/dist/exposure/auto-iso-slowest-shutter.js +1 -7
  67. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
  68. package/dist/exposure/exposure-duration-control.js +1 -16
  69. package/dist/exposure/exposure-duration-control.js.map +1 -1
  70. package/dist/exposure/exposure-mode-resolver.js +1 -17
  71. package/dist/exposure/exposure-mode-resolver.js.map +1 -1
  72. package/dist/exposure/filtered-exposure-time.d.ts +93 -0
  73. package/dist/exposure/filtered-exposure-time.js +129 -0
  74. package/dist/exposure/filtered-exposure-time.js.map +1 -0
  75. package/dist/exposure/flash-ttl.d.ts +1 -0
  76. package/dist/exposure/flash-ttl.js +1 -1
  77. package/dist/exposure/flash-ttl.js.map +1 -1
  78. package/dist/exposure/flash.js +1 -49
  79. package/dist/exposure/flash.js.map +1 -1
  80. package/dist/exposure/metering-scene-radiance.js +1 -25
  81. package/dist/exposure/metering-scene-radiance.js.map +1 -1
  82. package/dist/exposure/metering-target.js +1 -30
  83. package/dist/exposure/metering-target.js.map +1 -1
  84. package/dist/exposure/metering-temporal.js +1 -8
  85. package/dist/exposure/metering-temporal.js.map +1 -1
  86. package/dist/exposure/metering.js +1 -72
  87. package/dist/exposure/metering.js.map +1 -1
  88. package/dist/exposure/program-line.js +1 -45
  89. package/dist/exposure/program-line.js.map +1 -1
  90. package/dist/facade/develop-joined-parts.d.ts +29 -0
  91. package/dist/facade/develop-joined-parts.js +33 -0
  92. package/dist/facade/develop-joined-parts.js.map +1 -0
  93. package/dist/facade/display-view-gpu.d.ts +7 -4
  94. package/dist/facade/display-view-gpu.js +4 -4
  95. package/dist/facade/display-view-gpu.js.map +1 -1
  96. package/dist/facade/display-view-lens-signal.d.ts +1 -1
  97. package/dist/facade/display-view-lens-signal.js +5 -3
  98. package/dist/facade/display-view-lens-signal.js.map +1 -1
  99. package/dist/facade/display-view.d.ts +3 -1
  100. package/dist/facade/display-view.js +1 -1
  101. package/dist/facade/display-view.js.map +1 -1
  102. package/dist/facade/fast-methods.d.ts +50 -0
  103. package/dist/facade/fast-methods.js +50 -0
  104. package/dist/facade/fast-methods.js.map +1 -0
  105. package/dist/facade/fast-resolution.d.ts +12 -0
  106. package/dist/facade/fast-resolution.js +18 -0
  107. package/dist/facade/fast-resolution.js.map +1 -0
  108. package/dist/facade/generic-lens-capture.d.ts +6 -3
  109. package/dist/facade/generic-lens-capture.js +6 -3
  110. package/dist/facade/generic-lens-capture.js.map +1 -1
  111. package/dist/facade/simulate-capture.d.ts +88 -16
  112. package/dist/facade/simulate-capture.js +279 -153
  113. package/dist/facade/simulate-capture.js.map +1 -1
  114. package/dist/facade/simulate-display-view.d.ts +3 -2
  115. package/dist/facade/simulate-display-view.js +22 -12
  116. package/dist/facade/simulate-display-view.js.map +1 -1
  117. package/dist/gpu/chart-composite-gpu.js +1 -3
  118. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  119. package/dist/gpu/chart-radiance-gpu.js +1 -5
  120. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  121. package/dist/gpu/display-view-quality-gpu.d.ts +13 -1
  122. package/dist/gpu/display-view-quality-gpu.js +4 -8
  123. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  124. package/dist/gpu/display-view-speed-gpu.js +3 -25
  125. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  126. package/dist/gpu/sensor-stage-gpu.js +1 -5
  127. package/dist/gpu/sensor-stage-gpu.js.map +1 -1
  128. package/dist/gpu/webgpu-runtime.d.ts +2 -0
  129. package/dist/gpu/webgpu-runtime.js +3 -0
  130. package/dist/gpu/webgpu-runtime.js.map +1 -1
  131. package/dist/index.d.ts +11 -1
  132. package/dist/index.js +8 -0
  133. package/dist/index.js.map +1 -1
  134. package/dist/motion/camera-rotation.js +1 -6
  135. package/dist/motion/camera-rotation.js.map +1 -1
  136. package/dist/motion/capture-rotation-inverse-mapping.d.ts +12 -0
  137. package/dist/motion/capture-rotation-inverse-mapping.js +2 -2
  138. package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -1
  139. package/dist/motion/capture-rotation-temporal-quadrature.d.ts +1 -0
  140. package/dist/motion/capture-rotation-temporal-quadrature.js +1 -1
  141. package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -1
  142. package/dist/motion/capture-rotation-trajectory.js +2 -17
  143. package/dist/motion/capture-rotation-trajectory.js.map +1 -1
  144. package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts +1 -0
  145. package/dist/motion/capture-translation-parallax-temporal-quadrature.js +3 -14
  146. package/dist/motion/capture-translation-parallax-temporal-quadrature.js.map +1 -1
  147. package/dist/motion/extended-object-projection.js +5 -40
  148. package/dist/motion/extended-object-projection.js.map +1 -1
  149. package/dist/optics/aperture-diameter.d.ts +47 -0
  150. package/dist/optics/aperture-diameter.js +50 -0
  151. package/dist/optics/aperture-diameter.js.map +1 -0
  152. package/dist/optics/atmospheric-haze.js +1 -7
  153. package/dist/optics/atmospheric-haze.js.map +1 -1
  154. package/dist/optics/brown-conrady-distortion.js +1 -7
  155. package/dist/optics/brown-conrady-distortion.js.map +1 -1
  156. package/dist/optics/complex-pupil-psf.d.ts +1 -0
  157. package/dist/optics/complex-pupil-psf.js +2 -84
  158. package/dist/optics/complex-pupil-psf.js.map +1 -1
  159. package/dist/optics/detail-limit.d.ts +182 -0
  160. package/dist/optics/detail-limit.js +336 -0
  161. package/dist/optics/detail-limit.js.map +1 -0
  162. package/dist/optics/focus-state.js +1 -11
  163. package/dist/optics/focus-state.js.map +1 -1
  164. package/dist/optics/front-of-lens-filter.d.ts +5 -0
  165. package/dist/optics/front-of-lens-filter.js +3 -65
  166. package/dist/optics/front-of-lens-filter.js.map +1 -1
  167. package/dist/optics/generic-lens-psf.d.ts +18 -2
  168. package/dist/optics/generic-lens-psf.js +25 -2
  169. package/dist/optics/generic-lens-psf.js.map +1 -1
  170. package/dist/optics/generic-nd-filter-presets.d.ts +12 -0
  171. package/dist/optics/generic-nd-filter-presets.js +48 -0
  172. package/dist/optics/generic-nd-filter-presets.js.map +1 -0
  173. package/dist/optics/illumination-vignetting.js +1 -6
  174. package/dist/optics/illumination-vignetting.js.map +1 -1
  175. package/dist/optics/lens-psf-profile.d.ts +4 -1
  176. package/dist/optics/lens-psf-profile.js +12 -111
  177. package/dist/optics/lens-psf-profile.js.map +1 -1
  178. package/dist/optics/longitudinal-chromatic-aberration.js +1 -7
  179. package/dist/optics/longitudinal-chromatic-aberration.js.map +1 -1
  180. package/dist/optics/pupil-vignetting.js +1 -7
  181. package/dist/optics/pupil-vignetting.js.map +1 -1
  182. package/dist/optics/radial-distortion.js +1 -6
  183. package/dist/optics/radial-distortion.js.map +1 -1
  184. package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
  185. package/dist/optics/scene-to-sensor-irradiance.js +11 -96
  186. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  187. package/dist/optics/stray-light.d.ts +1 -0
  188. package/dist/optics/stray-light.js +1 -1
  189. package/dist/optics/stray-light.js.map +1 -1
  190. package/dist/optics/zernike-wavefront.js +1 -7
  191. package/dist/optics/zernike-wavefront.js.map +1 -1
  192. package/dist/output/capture-geometry.js +1 -8
  193. package/dist/output/capture-geometry.js.map +1 -1
  194. package/dist/output/crop-to-match-focal-length.d.ts +62 -0
  195. package/dist/output/crop-to-match-focal-length.js +63 -0
  196. package/dist/output/crop-to-match-focal-length.js.map +1 -0
  197. package/dist/output/developed-lens-corrections.d.ts +62 -0
  198. package/dist/output/developed-lens-corrections.js +135 -0
  199. package/dist/output/developed-lens-corrections.js.map +1 -0
  200. package/dist/output/generic-lens-corrections.d.ts +5 -7
  201. package/dist/output/generic-lens-corrections.js +11 -10
  202. package/dist/output/generic-lens-corrections.js.map +1 -1
  203. package/dist/output/geometric-transforms.js +1 -4
  204. package/dist/output/geometric-transforms.js.map +1 -1
  205. package/dist/output/lens-corrections.d.ts +12 -2
  206. package/dist/output/lens-corrections.js +22 -7
  207. package/dist/output/lens-corrections.js.map +1 -1
  208. package/dist/output/native-capture-sdr.d.ts +2 -2
  209. package/dist/output/native-capture-sdr.js +3 -2
  210. package/dist/output/native-capture-sdr.js.map +1 -1
  211. package/dist/output/print-contrast-reference.js +1 -1
  212. package/dist/output/print-contrast-reference.js.map +1 -1
  213. package/dist/output/print-size-blur-noise.d.ts +110 -0
  214. package/dist/output/print-size-blur-noise.js +138 -0
  215. package/dist/output/print-size-blur-noise.js.map +1 -0
  216. package/dist/processing/development-detail.d.ts +29 -0
  217. package/dist/processing/development-detail.js +128 -107
  218. package/dist/processing/development-detail.js.map +1 -1
  219. package/dist/schema/illumination-temporal.js +3 -72
  220. package/dist/schema/illumination-temporal.js.map +1 -1
  221. package/dist/schema/illumination.d.ts +1 -0
  222. package/dist/schema/illumination.js +2 -65
  223. package/dist/schema/illumination.js.map +1 -1
  224. package/dist/schema/scene-radiance-temporal.js +2 -14
  225. package/dist/schema/scene-radiance-temporal.js.map +1 -1
  226. package/dist/schema/scene-radiance.js +2 -60
  227. package/dist/schema/scene-radiance.js.map +1 -1
  228. package/dist/schema/validation.d.ts +2 -0
  229. package/dist/schema/validation.js +2 -17
  230. package/dist/schema/validation.js.map +1 -1
  231. package/dist/sensor/accumulated-charge.js +2 -59
  232. package/dist/sensor/accumulated-charge.js.map +1 -1
  233. package/dist/sensor/architecture.js +1 -16
  234. package/dist/sensor/architecture.js.map +1 -1
  235. package/dist/sensor/camera-saturation-capacity.d.ts +1 -0
  236. package/dist/sensor/camera-saturation-capacity.js +3 -55
  237. package/dist/sensor/camera-saturation-capacity.js.map +1 -1
  238. package/dist/sensor/capture-color-sampling-binding.js +1 -26
  239. package/dist/sensor/capture-color-sampling-binding.js.map +1 -1
  240. package/dist/sensor/capture-mode-timing.js +1 -39
  241. package/dist/sensor/capture-mode-timing.js.map +1 -1
  242. package/dist/sensor/capture-mode.js +1 -23
  243. package/dist/sensor/capture-mode.js.map +1 -1
  244. package/dist/sensor/color-sampling.js +1 -26
  245. package/dist/sensor/color-sampling.js.map +1 -1
  246. package/dist/sensor/constant-rate-temporal-integration.js +2 -46
  247. package/dist/sensor/constant-rate-temporal-integration.js.map +1 -1
  248. package/dist/sensor/dark-current.js +2 -45
  249. package/dist/sensor/dark-current.js.map +1 -1
  250. package/dist/sensor/eqe-electron-rate.js +2 -41
  251. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  252. package/dist/sensor/exposure-window.d.ts +7 -0
  253. package/dist/sensor/exposure-window.js +9 -24
  254. package/dist/sensor/exposure-window.js.map +1 -1
  255. package/dist/sensor/iso-gain.js +1 -7
  256. package/dist/sensor/iso-gain.js.map +1 -1
  257. package/dist/sensor/iso-signal-chain.js +1 -43
  258. package/dist/sensor/iso-signal-chain.js.map +1 -1
  259. package/dist/sensor/optical-stack.js +1 -25
  260. package/dist/sensor/optical-stack.js.map +1 -1
  261. package/dist/sensor/photon-energy.js +1 -26
  262. package/dist/sensor/photon-energy.js.map +1 -1
  263. package/dist/sensor/physical-charge-capacity.js +3 -77
  264. package/dist/sensor/physical-charge-capacity.js.map +1 -1
  265. package/dist/sensor/radiometry-readiness.js +2 -23
  266. package/dist/sensor/radiometry-readiness.js.map +1 -1
  267. package/dist/sensor/raw-readout.d.ts +3 -0
  268. package/dist/sensor/raw-readout.js +4 -66
  269. package/dist/sensor/raw-readout.js.map +1 -1
  270. package/dist/sensor/raw-reconstruction.js +2 -64
  271. package/dist/sensor/raw-reconstruction.js.map +1 -1
  272. package/dist/sensor/readout-exposure-linkage.js +1 -9
  273. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  274. package/dist/sensor/readout-timing.js +4 -79
  275. package/dist/sensor/readout-timing.js.map +1 -1
  276. package/dist/sensor/response-application-compatibility.d.ts +2 -0
  277. package/dist/sensor/response-application-compatibility.js +4 -35
  278. package/dist/sensor/response-application-compatibility.js.map +1 -1
  279. package/dist/sensor/response-operating-range.d.ts +1 -0
  280. package/dist/sensor/response-operating-range.js +3 -79
  281. package/dist/sensor/response-operating-range.js.map +1 -1
  282. package/dist/sensor/responsivity-photocurrent.js +4 -114
  283. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  284. package/dist/sensor/sampling-aperture.js +1 -25
  285. package/dist/sensor/sampling-aperture.js.map +1 -1
  286. package/dist/sensor/sensor-characteristics.d.ts +137 -0
  287. package/dist/sensor/sensor-characteristics.js +115 -0
  288. package/dist/sensor/sensor-characteristics.js.map +1 -0
  289. package/dist/sensor/spatial-sample-reduction.js +3 -24
  290. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  291. package/dist/sensor/spatial-sampling-quadrature.js +1 -3
  292. package/dist/sensor/spatial-sampling-quadrature.js.map +1 -1
  293. package/dist/sensor/spatio-spectral-reduction.js +1 -18
  294. package/dist/sensor/spatio-spectral-reduction.js.map +1 -1
  295. package/dist/sensor/spectral-coverage.d.ts +10 -1
  296. package/dist/sensor/spectral-coverage.js +1 -1
  297. package/dist/sensor/spectral-coverage.js.map +1 -1
  298. package/dist/sensor/spectral-quadrature.js +3 -41
  299. package/dist/sensor/spectral-quadrature.js.map +1 -1
  300. package/dist/sensor/spectral-response.js +1 -47
  301. package/dist/sensor/spectral-response.js.map +1 -1
  302. package/dist/sensor/temporal-photo-signal.d.ts +3 -0
  303. package/dist/sensor/temporal-photo-signal.js +5 -2
  304. package/dist/sensor/temporal-photo-signal.js.map +1 -1
  305. package/dist/stabilization/camera-shake.js +1 -6
  306. package/dist/stabilization/camera-shake.js.map +1 -1
  307. package/dist/stabilization/system.d.ts +9 -0
  308. package/dist/stabilization/system.js +7 -86
  309. package/dist/stabilization/system.js.map +1 -1
  310. package/docs/AGENT_GUIDE.md +5 -0
  311. package/docs/API_REFERENCE.md +116 -3
  312. package/docs/QUICK_START.md +178 -14
  313. package/docs/USAGE.md +111 -3
  314. package/docs/api/capture-native-raw-dng.md +6 -0
  315. package/docs/api/core-version.md +1 -1
  316. package/docs/api/equipment-generic-lens-catalogue.md +7 -2
  317. package/docs/api/exports.json +203 -3
  318. package/docs/api/exposure-filtered-exposure-time.md +105 -0
  319. package/docs/api/facade-develop-joined-parts.md +32 -0
  320. package/docs/api/facade-display-view-gpu.md +5 -2
  321. package/docs/api/facade-fast-methods.md +91 -0
  322. package/docs/api/facade-simulate-capture.md +72 -16
  323. package/docs/api/facade-simulate-display-view.md +3 -2
  324. package/docs/api/optics-aperture-diameter.md +51 -0
  325. package/docs/api/optics-detail-limit.md +203 -0
  326. package/docs/api/optics-generic-nd-filter-presets.md +34 -0
  327. package/docs/api/optics-lens-psf-profile.md +3 -1
  328. package/docs/api/output-crop-to-match-focal-length.md +67 -0
  329. package/docs/api/output-developed-lens-corrections.md +12 -0
  330. package/docs/api/output-generic-lens-corrections.md +3 -10
  331. package/docs/api/output-lens-corrections.md +11 -1
  332. package/docs/api/output-print-size-blur-noise.md +115 -0
  333. package/docs/api/sensor-sensor-characteristics.md +105 -0
  334. package/llms.txt +5 -0
  335. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"pupil-vignetting.js","sourceRoot":"","sources":["../../src/optics/pupil-vignetting.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAEjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAsE3E,SAAS,aAAa,CAAC,IAAY,EAAE,KAAc;IACjD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,2BAA2B,CAAC,GAAG,IAAI,kBAAkB,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,0BAA0B,CACxC,KAAsC;IAEtC,gBAAgB,CAAC,KAAK,EAAE,4BAA4B,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAC/H,IAAI,KAAK,CAAC,KAAK,KAAK,eAAe,IAAI,KAAK,CAAC,KAAK,KAAK,yBAAyB,EAAE,CAAC;QACjF,MAAM,IAAI,yBAAyB,CACjC,yDAAyD,CAC1D,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1E,MAAM,IAAI,yBAAyB,CAAC,iCAAiC,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAClB,aAAa,CAAC,gBAAgB,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,EACrD,aAAa,CAAC,gBAAgB,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CACtD,CAAC;IACF,qBAAqB,CAAC,qBAAqB,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACxE,MAAM,CAAC,GAAG,KAAK,CAAC,mBAAmB,CAAC;IACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACvC,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAAC;IAEvC,IAAI,oBAA4B,CAAC;IACjC,IAAI,KAAK,CAAC,KAAK,KAAK,eAAe,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,mBAAmB,KAAK,SAAS,EAAE,CAAC;YAC5C,MAAM,IAAI,2BAA2B,CACnC,sEAAsE,CACvE,CAAC;QACJ,CAAC;QACD,oBAAoB,GAAG,kBAAkB,CAAC;IAC5C,CAAC;SAAM,CAAC;QACN,qBAAqB,CAAC,qBAAqB,EAAE,KAAK,CAAC,mBAA6B,CAAC,CAAC;QAClF,MAAM,CAAC,GAAI,KAAK,CAAC,mBAA8B,GAAG,CAAC,CAAC;QACpD,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;QACjB,6FAA6F;QAC7F,oFAAoF;QACpF,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9B,oBAAoB,GAAG,OAAO,GAAG,MAAM,CAAC;IAC1C,CAAC;IACD,IAAI,CAAC,CAAC,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC1E,MAAM,IAAI,2BAA2B,CACnC,2EAA2E,CAC5E,CAAC;IACJ,CAAC;IACD,oBAAoB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC;IAEzD,OAAO,gBAAgB,CACrB;QACE,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,oBAAoB,EAAE,KAAK;QAC3B,oBAAoB;QACpB,gBAAgB,EAAE,oBAAoB,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC;QACnF,kBAAkB;KACnB,EACD,KAAK,CAAC,KAAK,KAAK,eAAe;QAC7B,CAAC,CAAC,kCAAkC;QACpC,CAAC,CAAC,4CAA4C,EAChD,OAAO,EACP;QACE,KAAK,CAAC,KAAK,KAAK,eAAe;YAC7B,CAAC,CAAC,2FAA2F;YAC7F,CAAC,CAAC,gHAAgH;QACpH,mGAAmG;QACnG,mFAAmF;KACpF,CACF,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,SAAS,sBAAsB,CAAC,EAAU,EAAE,EAAU,EAAE,CAAS;IAC/D,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IAC3B,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAClE,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CACjB,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAC5E,CAAC;IACF,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;AAC7C,CAAC;AAQD,SAAS,SAAS,CAChB,KAA+B,EAC/B,OAA0B,EAC1B,IAAY;IAEZ,OAAO,OAAO,CAAC,KAAK,CAClB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAChB,KAAK,KAAK,IAAI;QACd,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAC/E,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,SAAS,mBAAmB,CAC1B,OAA0B,EAC1B,OAAe;IAEf,MAAM,MAAM,GAA+B,EAAE,CAAC;IAC9C,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;QAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;YAC1C,MAAM,KAAK,GAAG;gBACZ,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBACxC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;aACzC,CAAC;YACF,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC,CAAC,CAAC;IACH,sEAAsE;IACtE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/C,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;YACtB,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;YACtB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAAE,SAAS;YACpE,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;YAC/D,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC3B,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC3B,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3B,MAAM,KAAK,GAAG;oBACZ,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,EAAE;oBACtC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,EAAE;iBACvC,CAAC;gBACF,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC;oBACvF,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACrB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/D,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/D,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACvF,8BAA8B;IAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QACxC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAE,CAAC;QAClE,OAAO,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7G,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED,SAAS,sBAAsB,CAC7B,OAA4C;IAE5C,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QACtB,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACpC,SAAS,IAAI,KAAK,CAAC;QACnB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;QAC1B,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;IAC5B,CAAC;IACD,OAAO;QACL,IAAI,EAAE,SAAS,GAAG,CAAC;QACnB,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE;KAC/D,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAC3C,KAAyC;IAEzC,gBAAgB,CAAC,KAAK,EAAE,+BAA+B,EAAE,CAAC,yBAAyB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,WAAW,EAAE,0BAA0B,CAAC,CAAC,CAAC;IAC3K,qBAAqB,CAAC,yBAAyB,EAAE,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAChF,MAAM,iBAAiB,GAAG,aAAa,CAAC,mBAAmB,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACtF,IAAI,iBAAiB,GAAG,CAAC,IAAI,iBAAiB,IAAI,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,2BAA2B,CAAC,wCAAwC,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,mBAAmB,GAAG,aAAa,CAAC,qBAAqB,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAC;IAC5F,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChG,MAAM,IAAI,2BAA2B,CAAC,6CAA6C,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,wBAAwB,IAAI,GAAG,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,EAAE,IAAI,OAAO,GAAG,IAAI,EAAE,CAAC;QACrE,MAAM,IAAI,2BAA2B,CACnC,mEAAmE,CACpE,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,iBAAiB,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;IAClD,MAAM,OAAO,GAAG,CAAC,mBAAmB,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,WAAW,GAAG,KAAK,CAAC,uBAAuB,GAAG,CAAC,CAAC;IACtD,MAAM,OAAO,GAAa,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IAC3D,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;QAC1C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtD,MAAM,IAAI,yBAAyB,CAAC,aAAa,KAAK,sBAAsB,CAAC,CAAC;QAChF,CAAC;QACD,qBAAqB,CAAC,aAAa,KAAK,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzE,MAAM,KAAK,GACT,aAAa,CAAC,aAAa,KAAK,sBAAsB,EAAE,QAAQ,CAAC,kBAAkB,CAAC;YACpF,QAAQ,CAAC;QACX,OAAO,CAAC,IAAI,CAAC;YACX,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAC5B,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YAC5B,CAAC,EAAE,QAAQ,CAAC,QAAQ;SACrB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,CAAC,EAAE,GAAG,WAAW,GAAG,WAAW,CAAC;IACtD,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnF,IAAI,QAAgB,CAAC;IACrB,IAAI,UAA8C,CAAC;IACnD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QAC1B,QAAQ,GAAG,sBAAsB,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACtF,UAAU,GAAG,kCAAkC,CAAC;IAClD,CAAC;SAAM,CAAC;QACN,QAAQ,GAAG,QAAQ,EAAE,IAAI,IAAI,CAAC,CAAC;QAC/B,UAAU,GAAG,kBAAkB,CAAC;IAClC,CAAC;IACD,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC;IACrE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAAC;IAEvC,OAAO,mBAAmB,CACxB;QACE,iBAAiB;QACjB,mBAAmB;QACnB,yBAAyB,EAAE,SAAS;QACpC,gBAAgB,EAAE,QAAQ;QAC1B,UAAU;QACV,aAAa;QACb,kBAAkB;QAClB,4BAA4B,EAAE,kBAAkB,GAAG,aAAa;QAChE,kBAAkB,EAAE,OAAO;QAC3B,mBAAmB,EAAE,QAAQ,EAAE,QAAQ,IAAI,IAAI;KAChD,EACD,gCAAgC,EAChC,OAAO,EACP;QACE,uIAAuI;QACvI,6HAA6H;QAC7H,qHAAqH;QACrH,iFAAiF;QACjF,mEAAmE;KACpE,CACF,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Physical vignetting models derived from pupil geometry. They are separate from the\n * calculateIlluminationVignetting() polynomial, whose coefficients the caller supplies.\n *\n * Apply at most one field-throughput model to a signal. Do not stack these results on top of a\n * supplied field-throughput profile.\n *\n * Natural vignetting (relative illumination of an aberration-free system with a uniform Lambertian\n * exit pupil):\n * - cosine-fourth: E(theta) / E(0) = cos^4(theta), theta the image-space chief-ray angle with\n * tan(theta) = h / z for image height h and exit-pupil distance z (Greivenkamp, OPTI-502 course\n * notes, University of Arizona, sec. 11, free. Also see: Slater 1980 Photographic Systems for\n * Engineers; Kingslake, Lens Design Fundamentals, 2nd ed., sec. 6.5; Ray, Applied Photographic\n * Optics, 3rd ed., sec. 12). It is the small-pupil limit of the next model.\n * - finite-lambertian-pupil: exact irradiance from a uniform Lambertian disk of radius a at distance z\n * onto a parallel plane at lateral offset h (Foote 1915, Bull. Bureau of Standards 12:583):\n * E = (pi L / 2) [1 - (z^2 + h^2 - a^2) / sqrt((z^2 + h^2 + a^2)^2 - 4 a^2 h^2)],\n * normalised by its on-axis value pi L a^2 / (z^2 + a^2). For a -> 0 it tends to cos^4(theta).\n * Pupil aberration and pupil distortion are not modeled.\n *\n * Mechanical (optical) vignetting and the cat's-eye pupil:\n * - At field angle theta, each clipping aperture (a lens barrel or element rim, referred to the\n * entrance-pupil plane) appears as a circle of its declared radius. The circle is shifted by\n * shiftPerTanFieldMm * tan(theta) along the field azimuth.\n * - The open pupil is where the entrance-pupil circle overlaps every shifted circle. Off axis, the\n * bundle stays circular and centred on the chief ray, and an aperture passes only the part of it\n * that it covers (Greivenkamp, OPTI-502 course notes, University of Arizona, sec. 10, free. Also\n * see: Kingslake, Lens Design Fundamentals, sec. 6.4; Smith, Modern Optical Engineering, 4th ed.,\n * sec. 6.3).\n * - With one aperture, the area is the exact area of the two-circle overlap.\n * - With any number of apertures, the exact convex boundary is also returned, sampled as a polygon\n * in pupil-plane mm (+X right, +Y up). Use it as a polygon pupil for\n * calculatePolygonPupilFraunhoferPsf() or as a mask for createWavefrontPupilGrid().\n * The combined relative illumination is cos^4(theta) x open-area ratio. This assumes an object at\n * infinity, a stop imaged without pupil aberration, and an object-space field angle equal to the\n * chief-ray angle.\n * @see docs/PHYSICS_FOUNDATION.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n calculatedResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport type NaturalVignettingModel =\n | \"cosine-fourth\"\n | \"finite-lambertian-pupil\";\n\nexport interface CalculateNaturalVignettingInput {\n model: NaturalVignettingModel;\n /** Image-plane point relative to the optical axis, mm. */\n imagePointMm: { x: number; y: number };\n /** Axial distance from the exit pupil to the image plane, mm. */\n exitPupilDistanceMm: number;\n /** Exit-pupil diameter, mm; required for finite-lambertian-pupil. */\n exitPupilDiameterMm?: number;\n}\n\nexport interface NaturalVignetting {\n model: NaturalVignettingModel;\n /** Image-space chief-ray angle, radians. */\n chiefRayAngleRadians: number;\n /** Relative illumination E / E(axis), in (0, 1]. */\n relativeIllumination: number;\n /** Positive exposure loss, stops. */\n attenuationStops: number;\n /** cos^4 of the chief-ray angle, reported for comparison. */\n cosineFourthFactor: number;\n}\n\nexport interface MechanicalVignettingAperture {\n /** Aperture radius referred to the entrance-pupil plane, mm. */\n radiusMm: number;\n /**\n * Signed shift of the aperture's projected centre per unit tan(field angle), in mm, along the\n * field azimuth. Positive means toward the field point.\n */\n shiftPerTanFieldMm: number;\n}\n\nexport interface CalculateMechanicalVignettingInput {\n /** Unclipped entrance-pupil diameter, mm. */\n entrancePupilDiameterMm: number;\n /** Object-space field angle from the optical axis, degrees, in [0, 90). */\n fieldAngleDegrees: number;\n /** Image-plane direction of the field point, degrees counter-clockwise from +X. */\n fieldAzimuthDegrees: number;\n /** One through eight clipping apertures. */\n apertures: readonly MechanicalVignettingAperture[];\n /** Boundary samples per circle for the polygon, from 16 to 4096 (default 256). */\n boundarySamplesPerCircle?: number;\n}\n\nexport interface MechanicalVignetting {\n fieldAngleDegrees: number;\n fieldAzimuthDegrees: number;\n entrancePupilAreaSquareMm: number;\n /** Open area in mm^2. Exact for a single aperture; the polygon area otherwise. */\n openAreaSquareMm: number;\n areaMethod: \"exact-circle-circle-intersection\" | \"boundary-polygon\";\n /** Open area / unclipped pupil area, in [0, 1]. */\n openAreaRatio: number;\n /** cos^4(field angle). */\n cosineFourthFactor: number;\n /** cos^4(theta) x openAreaRatio. */\n combinedRelativeIllumination: number;\n /** Counter-clockwise convex boundary of the open pupil (empty when fully vignetted), mm. */\n openPupilPolygonMm: readonly { x: number; y: number }[];\n /** Area centroid of the boundary polygon, mm (null when fully vignetted). */\n openPupilCentroidMm: { x: number; y: number } | null;\n}\n\nfunction requireFinite(name: string, value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n throw new InvalidScientificInputError(`${name} must be finite.`);\n }\n return value;\n}\n\n/**\n * Natural (cos^4 or finite Lambertian pupil) relative illumination at one image point.\n */\nexport function calculateNaturalVignetting(\n input: CalculateNaturalVignettingInput\n): CalculationResult<NaturalVignetting> {\n requireInputKeys(input, \"calculateNaturalVignetting\", [\"model\", \"imagePointMm\", \"exitPupilDistanceMm\", \"exitPupilDiameterMm\"]);\n if (input.model !== \"cosine-fourth\" && input.model !== \"finite-lambertian-pupil\") {\n throw new InvalidConfigurationError(\n \"model must be cosine-fourth or finite-lambertian-pupil.\"\n );\n }\n if (typeof input.imagePointMm !== \"object\" || input.imagePointMm === null) {\n throw new InvalidConfigurationError(\"imagePointMm must be an object.\");\n }\n const h = Math.hypot(\n requireFinite(\"imagePointMm.x\", input.imagePointMm.x),\n requireFinite(\"imagePointMm.y\", input.imagePointMm.y)\n );\n requirePositiveFinite(\"exitPupilDistanceMm\", input.exitPupilDistanceMm);\n const z = input.exitPupilDistanceMm;\n const theta = Math.atan2(h, z);\n const cos2 = (z * z) / (z * z + h * h);\n const cosineFourthFactor = cos2 * cos2;\n\n let relativeIllumination: number;\n if (input.model === \"cosine-fourth\") {\n if (input.exitPupilDiameterMm !== undefined) {\n throw new InvalidScientificInputError(\n \"exitPupilDiameterMm is not used by the cosine-fourth model; omit it.\"\n );\n }\n relativeIllumination = cosineFourthFactor;\n } else {\n requirePositiveFinite(\"exitPupilDiameterMm\", input.exitPupilDiameterMm as number);\n const a = (input.exitPupilDiameterMm as number) / 2;\n const z2 = z * z;\n const h2 = h * h;\n const a2 = a * a;\n // Bracket term written as 2 a^2 (...) / (sqrt + ...) to avoid cancellation for small pupils:\n // 1 - P / Q = (Q - P) / Q with Q - P = (Q^2 - P^2) / (Q + P) = 4 a^2 z^2 / (Q + P).\n const p = z2 + h2 - a2;\n const q = Math.sqrt((z2 + h2 + a2) ** 2 - 4 * a2 * h2);\n const offAxis = (4 * a2 * z2) / (q * (q + p)) / 2;\n const onAxis = a2 / (z2 + a2);\n relativeIllumination = offAxis / onAxis;\n }\n if (!(relativeIllumination > 0) || !Number.isFinite(relativeIllumination)) {\n throw new InvalidScientificInputError(\n \"Natural vignetting relative illumination must remain finite and positive.\"\n );\n }\n relativeIllumination = Math.min(1, relativeIllumination);\n\n return calculatedResult(\n {\n model: input.model,\n chiefRayAngleRadians: theta,\n relativeIllumination,\n attenuationStops: relativeIllumination === 1 ? 0 : -Math.log2(relativeIllumination),\n cosineFourthFactor\n },\n input.model === \"cosine-fourth\"\n ? \"cosine-fourth-natural-vignetting\"\n : \"finite-lambertian-pupil-natural-vignetting\",\n \"1.0.0\",\n [\n input.model === \"cosine-fourth\"\n ? \"Relative illumination cos^4 of the image-space chief-ray angle from the exit-pupil centre\"\n : \"Exact irradiance from a uniform Lambertian circular exit pupil onto a parallel image plane, normalised on axis\",\n \"Aberration-free pupil: pupil aberration, pupil distortion and mechanical clipping are not modeled\",\n \"Throughput only; apply at most one field-throughput model, in scene-linear signal\"\n ]\n );\n}\n\n/** Exact area of the intersection of two circles. */\nfunction circleIntersectionArea(r1: number, r2: number, d: number): number {\n if (d >= r1 + r2) return 0;\n if (d <= Math.abs(r1 - r2)) return Math.PI * Math.min(r1, r2) ** 2;\n const clamp = (v: number): number => Math.max(-1, Math.min(1, v));\n const a1 = Math.acos(clamp((d * d + r1 * r1 - r2 * r2) / (2 * d * r1)));\n const a2 = Math.acos(clamp((d * d + r2 * r2 - r1 * r1) / (2 * d * r2)));\n const k = Math.sqrt(\n Math.max(0, (-d + r1 + r2) * (d + r1 - r2) * (d - r1 + r2) * (d + r1 + r2))\n );\n return r1 * r1 * a1 + r2 * r2 * a2 - k / 2;\n}\n\ninterface Circle {\n x: number;\n y: number;\n r: number;\n}\n\nfunction insideAll(\n point: { x: number; y: number },\n circles: readonly Circle[],\n skip: number\n): boolean {\n return circles.every(\n (circle, index) =>\n index === skip ||\n Math.hypot(point.x - circle.x, point.y - circle.y) <= circle.r * (1 + 1e-12)\n );\n}\n\n/** Convex boundary of the intersection of circles, sampled with vertices on the true boundary. */\nfunction intersectionPolygon(\n circles: readonly Circle[],\n samples: number\n): { x: number; y: number }[] {\n const points: { x: number; y: number }[] = [];\n circles.forEach((circle, index) => {\n for (let k = 0; k < samples; k += 1) {\n const angle = (2 * Math.PI * k) / samples;\n const point = {\n x: circle.x + circle.r * Math.cos(angle),\n y: circle.y + circle.r * Math.sin(angle)\n };\n if (insideAll(point, circles, index)) points.push(point);\n }\n });\n // Pairwise boundary intersection points (the cusps of the cat's eye).\n for (let i = 0; i < circles.length; i += 1) {\n for (let j = i + 1; j < circles.length; j += 1) {\n const a = circles[i]!;\n const b = circles[j]!;\n const d = Math.hypot(b.x - a.x, b.y - a.y);\n if (d === 0 || d >= a.r + b.r || d <= Math.abs(a.r - b.r)) continue;\n const along = (d * d + a.r * a.r - b.r * b.r) / (2 * d);\n const half = Math.sqrt(Math.max(0, a.r * a.r - along * along));\n const ux = (b.x - a.x) / d;\n const uy = (b.y - a.y) / d;\n for (const sign of [1, -1]) {\n const point = {\n x: a.x + along * ux - sign * half * uy,\n y: a.y + along * uy + sign * half * ux\n };\n if (circles.every((c) => Math.hypot(point.x - c.x, point.y - c.y) <= c.r * (1 + 1e-9))) {\n points.push(point);\n }\n }\n }\n }\n if (points.length < 3) return [];\n const cx = points.reduce((s, p) => s + p.x, 0) / points.length;\n const cy = points.reduce((s, p) => s + p.y, 0) / points.length;\n points.sort((p, q) => Math.atan2(p.y - cy, p.x - cx) - Math.atan2(q.y - cy, q.x - cx));\n // Drop coincident neighbours.\n const unique = points.filter((p, index) => {\n const prev = points[(index + points.length - 1) % points.length]!;\n return index === 0 || Math.hypot(p.x - prev.x, p.y - prev.y) > 1e-12 * (1 + Math.abs(p.x) + Math.abs(p.y));\n });\n return unique.length >= 3 ? unique : [];\n}\n\nfunction polygonAreaAndCentroid(\n polygon: readonly { x: number; y: number }[]\n): { area: number; centroid: { x: number; y: number } } {\n let twiceArea = 0;\n let cx = 0;\n let cy = 0;\n for (let i = 0; i < polygon.length; i += 1) {\n const a = polygon[i]!;\n const b = polygon[(i + 1) % polygon.length]!;\n const cross = a.x * b.y - b.x * a.y;\n twiceArea += cross;\n cx += (a.x + b.x) * cross;\n cy += (a.y + b.y) * cross;\n }\n return {\n area: twiceArea / 2,\n centroid: { x: cx / (3 * twiceArea), y: cy / (3 * twiceArea) }\n };\n}\n\n/**\n * Mechanical vignetting at one field angle: the open (cat's-eye) pupil from intersecting the\n * entrance pupil with displaced clipping apertures, its area ratio, and cos^4 x area ratio.\n */\nexport function calculateMechanicalVignetting(\n input: CalculateMechanicalVignettingInput\n): CalculationResult<MechanicalVignetting> {\n requireInputKeys(input, \"calculateMechanicalVignetting\", [\"entrancePupilDiameterMm\", \"fieldAngleDegrees\", \"fieldAzimuthDegrees\", \"apertures\", \"boundarySamplesPerCircle\"]);\n requirePositiveFinite(\"entrancePupilDiameterMm\", input.entrancePupilDiameterMm);\n const fieldAngleDegrees = requireFinite(\"fieldAngleDegrees\", input.fieldAngleDegrees);\n if (fieldAngleDegrees < 0 || fieldAngleDegrees >= 90) {\n throw new InvalidScientificInputError(\"fieldAngleDegrees must lie in [0, 90).\");\n }\n const fieldAzimuthDegrees = requireFinite(\"fieldAzimuthDegrees\", input.fieldAzimuthDegrees);\n if (!Array.isArray(input.apertures) || input.apertures.length < 1 || input.apertures.length > 8) {\n throw new InvalidScientificInputError(\"apertures must contain 1 through 8 entries.\");\n }\n const samples = input.boundarySamplesPerCircle ?? 256;\n if (!Number.isSafeInteger(samples) || samples < 16 || samples > 4096) {\n throw new InvalidScientificInputError(\n \"boundarySamplesPerCircle must be an integer from 16 through 4096.\"\n );\n }\n const theta = (fieldAngleDegrees * Math.PI) / 180;\n const azimuth = (fieldAzimuthDegrees * Math.PI) / 180;\n const tanTheta = Math.tan(theta);\n const pupilRadius = input.entrancePupilDiameterMm / 2;\n const circles: Circle[] = [{ x: 0, y: 0, r: pupilRadius }];\n input.apertures.forEach((aperture, index) => {\n if (typeof aperture !== \"object\" || aperture === null) {\n throw new InvalidConfigurationError(`apertures[${index}] must be an object.`);\n }\n requirePositiveFinite(`apertures[${index}].radiusMm`, aperture.radiusMm);\n const shift =\n requireFinite(`apertures[${index}].shiftPerTanFieldMm`, aperture.shiftPerTanFieldMm) *\n tanTheta;\n circles.push({\n x: shift * Math.cos(azimuth),\n y: shift * Math.sin(azimuth),\n r: aperture.radiusMm\n });\n });\n\n const pupilArea = Math.PI * pupilRadius * pupilRadius;\n const polygon = intersectionPolygon(circles, samples);\n const geometry = polygon.length >= 3 ? polygonAreaAndCentroid(polygon) : undefined;\n let openArea: number;\n let areaMethod: MechanicalVignetting[\"areaMethod\"];\n if (circles.length === 2) {\n const other = circles[1]!;\n openArea = circleIntersectionArea(pupilRadius, other.r, Math.hypot(other.x, other.y));\n areaMethod = \"exact-circle-circle-intersection\";\n } else {\n openArea = geometry?.area ?? 0;\n areaMethod = \"boundary-polygon\";\n }\n const openAreaRatio = Math.min(1, Math.max(0, openArea / pupilArea));\n const cos2 = Math.cos(theta) ** 2;\n const cosineFourthFactor = cos2 * cos2;\n\n return approximationResult(\n {\n fieldAngleDegrees,\n fieldAzimuthDegrees,\n entrancePupilAreaSquareMm: pupilArea,\n openAreaSquareMm: openArea,\n areaMethod,\n openAreaRatio,\n cosineFourthFactor,\n combinedRelativeIllumination: cosineFourthFactor * openAreaRatio,\n openPupilPolygonMm: polygon,\n openPupilCentroidMm: geometry?.centroid ?? null\n },\n \"cats-eye-mechanical-vignetting\",\n \"1.0.0\",\n [\n \"Clipping apertures are circles referred to the entrance-pupil plane, displaced linearly with tan(field angle) along the field azimuth\",\n \"Open pupil = intersection of the entrance pupil with every displaced aperture; boundary vertices lie exactly on the circles\",\n \"Combined relative illumination cos^4(theta) x open-area ratio assumes an object at infinity and no pupil aberration\",\n \"Pupil coordinates are metric and aligned with the image plane (+X right, +Y up)\",\n \"Geometry is caller-declared; no named-lens barrel data is implied\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"pupil-vignetting.js","sourceRoot":"","sources":["../../src/optics/pupil-vignetting.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Physical vignetting models derived from pupil geometry. They are separate from the\n * calculateIlluminationVignetting() polynomial, whose coefficients the caller supplies.\n *\n * Apply at most one field-throughput model to a signal. Do not stack these results on top of a\n * supplied field-throughput profile.\n *\n * Natural vignetting (relative illumination of an aberration-free system with a uniform Lambertian\n * exit pupil):\n * - cosine-fourth: E(theta) / E(0) = cos^4(theta), theta the image-space chief-ray angle with\n * tan(theta) = h / z for image height h and exit-pupil distance z (Greivenkamp, OPTI-502 course\n * notes, University of Arizona, sec. 11, free. Also see: Slater 1980 Photographic Systems for\n * Engineers; Kingslake, Lens Design Fundamentals, 2nd ed., sec. 6.5; Ray, Applied Photographic\n * Optics, 3rd ed., sec. 12). It is the small-pupil limit of the next model.\n * - finite-lambertian-pupil: exact irradiance from a uniform Lambertian disk of radius a at distance z\n * onto a parallel plane at lateral offset h (Foote 1915, Bull. Bureau of Standards 12:583):\n * E = (pi L / 2) [1 - (z^2 + h^2 - a^2) / sqrt((z^2 + h^2 + a^2)^2 - 4 a^2 h^2)],\n * normalised by its on-axis value pi L a^2 / (z^2 + a^2). For a -> 0 it tends to cos^4(theta).\n * Pupil aberration and pupil distortion are not modeled.\n *\n * Mechanical (optical) vignetting and the cat's-eye pupil:\n * - At field angle theta, each clipping aperture (a lens barrel or element rim, referred to the\n * entrance-pupil plane) appears as a circle of its declared radius. The circle is shifted by\n * shiftPerTanFieldMm * tan(theta) along the field azimuth.\n * - The open pupil is where the entrance-pupil circle overlaps every shifted circle. Off axis, the\n * bundle stays circular and centred on the chief ray, and an aperture passes only the part of it\n * that it covers (Greivenkamp, OPTI-502 course notes, University of Arizona, sec. 10, free. Also\n * see: Kingslake, Lens Design Fundamentals, sec. 6.4; Smith, Modern Optical Engineering, 4th ed.,\n * sec. 6.3).\n * - With one aperture, the area is the exact area of the two-circle overlap.\n * - With any number of apertures, the exact convex boundary is also returned, sampled as a polygon\n * in pupil-plane mm (+X right, +Y up). Use it as a polygon pupil for\n * calculatePolygonPupilFraunhoferPsf() or as a mask for createWavefrontPupilGrid().\n * The combined relative illumination is cos^4(theta) x open-area ratio. This assumes an object at\n * infinity, a stop imaged without pupil aberration, and an object-space field angle equal to the\n * chief-ray angle.\n * @see docs/PHYSICS_FOUNDATION.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n calculatedResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport { requireInputKeys, requireNamedFinite as requireFinite } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport type NaturalVignettingModel =\n | \"cosine-fourth\"\n | \"finite-lambertian-pupil\";\n\nexport interface CalculateNaturalVignettingInput {\n model: NaturalVignettingModel;\n /** Image-plane point relative to the optical axis, mm. */\n imagePointMm: { x: number; y: number };\n /** Axial distance from the exit pupil to the image plane, mm. */\n exitPupilDistanceMm: number;\n /** Exit-pupil diameter, mm; required for finite-lambertian-pupil. */\n exitPupilDiameterMm?: number;\n}\n\nexport interface NaturalVignetting {\n model: NaturalVignettingModel;\n /** Image-space chief-ray angle, radians. */\n chiefRayAngleRadians: number;\n /** Relative illumination E / E(axis), in (0, 1]. */\n relativeIllumination: number;\n /** Positive exposure loss, stops. */\n attenuationStops: number;\n /** cos^4 of the chief-ray angle, reported for comparison. */\n cosineFourthFactor: number;\n}\n\nexport interface MechanicalVignettingAperture {\n /** Aperture radius referred to the entrance-pupil plane, mm. */\n radiusMm: number;\n /**\n * Signed shift of the aperture's projected centre per unit tan(field angle), in mm, along the\n * field azimuth. Positive means toward the field point.\n */\n shiftPerTanFieldMm: number;\n}\n\nexport interface CalculateMechanicalVignettingInput {\n /** Unclipped entrance-pupil diameter, mm. */\n entrancePupilDiameterMm: number;\n /** Object-space field angle from the optical axis, degrees, in [0, 90). */\n fieldAngleDegrees: number;\n /** Image-plane direction of the field point, degrees counter-clockwise from +X. */\n fieldAzimuthDegrees: number;\n /** One through eight clipping apertures. */\n apertures: readonly MechanicalVignettingAperture[];\n /** Boundary samples per circle for the polygon, from 16 to 4096 (default 256). */\n boundarySamplesPerCircle?: number;\n}\n\nexport interface MechanicalVignetting {\n fieldAngleDegrees: number;\n fieldAzimuthDegrees: number;\n entrancePupilAreaSquareMm: number;\n /** Open area in mm^2. Exact for a single aperture; the polygon area otherwise. */\n openAreaSquareMm: number;\n areaMethod: \"exact-circle-circle-intersection\" | \"boundary-polygon\";\n /** Open area / unclipped pupil area, in [0, 1]. */\n openAreaRatio: number;\n /** cos^4(field angle). */\n cosineFourthFactor: number;\n /** cos^4(theta) x openAreaRatio. */\n combinedRelativeIllumination: number;\n /** Counter-clockwise convex boundary of the open pupil (empty when fully vignetted), mm. */\n openPupilPolygonMm: readonly { x: number; y: number }[];\n /** Area centroid of the boundary polygon, mm (null when fully vignetted). */\n openPupilCentroidMm: { x: number; y: number } | null;\n}\n\n/**\n * Natural (cos^4 or finite Lambertian pupil) relative illumination at one image point.\n */\nexport function calculateNaturalVignetting(\n input: CalculateNaturalVignettingInput\n): CalculationResult<NaturalVignetting> {\n requireInputKeys(input, \"calculateNaturalVignetting\", [\"model\", \"imagePointMm\", \"exitPupilDistanceMm\", \"exitPupilDiameterMm\"]);\n if (input.model !== \"cosine-fourth\" && input.model !== \"finite-lambertian-pupil\") {\n throw new InvalidConfigurationError(\n \"model must be cosine-fourth or finite-lambertian-pupil.\"\n );\n }\n if (typeof input.imagePointMm !== \"object\" || input.imagePointMm === null) {\n throw new InvalidConfigurationError(\"imagePointMm must be an object.\");\n }\n const h = Math.hypot(\n requireFinite(\"imagePointMm.x\", input.imagePointMm.x),\n requireFinite(\"imagePointMm.y\", input.imagePointMm.y)\n );\n requirePositiveFinite(\"exitPupilDistanceMm\", input.exitPupilDistanceMm);\n const z = input.exitPupilDistanceMm;\n const theta = Math.atan2(h, z);\n const cos2 = (z * z) / (z * z + h * h);\n const cosineFourthFactor = cos2 * cos2;\n\n let relativeIllumination: number;\n if (input.model === \"cosine-fourth\") {\n if (input.exitPupilDiameterMm !== undefined) {\n throw new InvalidScientificInputError(\n \"exitPupilDiameterMm is not used by the cosine-fourth model; omit it.\"\n );\n }\n relativeIllumination = cosineFourthFactor;\n } else {\n requirePositiveFinite(\"exitPupilDiameterMm\", input.exitPupilDiameterMm as number);\n const a = (input.exitPupilDiameterMm as number) / 2;\n const z2 = z * z;\n const h2 = h * h;\n const a2 = a * a;\n // Bracket term written as 2 a^2 (...) / (sqrt + ...) to avoid cancellation for small pupils:\n // 1 - P / Q = (Q - P) / Q with Q - P = (Q^2 - P^2) / (Q + P) = 4 a^2 z^2 / (Q + P).\n const p = z2 + h2 - a2;\n const q = Math.sqrt((z2 + h2 + a2) ** 2 - 4 * a2 * h2);\n const offAxis = (4 * a2 * z2) / (q * (q + p)) / 2;\n const onAxis = a2 / (z2 + a2);\n relativeIllumination = offAxis / onAxis;\n }\n if (!(relativeIllumination > 0) || !Number.isFinite(relativeIllumination)) {\n throw new InvalidScientificInputError(\n \"Natural vignetting relative illumination must remain finite and positive.\"\n );\n }\n relativeIllumination = Math.min(1, relativeIllumination);\n\n return calculatedResult(\n {\n model: input.model,\n chiefRayAngleRadians: theta,\n relativeIllumination,\n attenuationStops: relativeIllumination === 1 ? 0 : -Math.log2(relativeIllumination),\n cosineFourthFactor\n },\n input.model === \"cosine-fourth\"\n ? \"cosine-fourth-natural-vignetting\"\n : \"finite-lambertian-pupil-natural-vignetting\",\n \"1.0.0\",\n [\n input.model === \"cosine-fourth\"\n ? \"Relative illumination cos^4 of the image-space chief-ray angle from the exit-pupil centre\"\n : \"Exact irradiance from a uniform Lambertian circular exit pupil onto a parallel image plane, normalised on axis\",\n \"Aberration-free pupil: pupil aberration, pupil distortion and mechanical clipping are not modeled\",\n \"Throughput only; apply at most one field-throughput model, in scene-linear signal\"\n ]\n );\n}\n\n/** Exact area of the intersection of two circles. */\nfunction circleIntersectionArea(r1: number, r2: number, d: number): number {\n if (d >= r1 + r2) return 0;\n if (d <= Math.abs(r1 - r2)) return Math.PI * Math.min(r1, r2) ** 2;\n const clamp = (v: number): number => Math.max(-1, Math.min(1, v));\n const a1 = Math.acos(clamp((d * d + r1 * r1 - r2 * r2) / (2 * d * r1)));\n const a2 = Math.acos(clamp((d * d + r2 * r2 - r1 * r1) / (2 * d * r2)));\n const k = Math.sqrt(\n Math.max(0, (-d + r1 + r2) * (d + r1 - r2) * (d - r1 + r2) * (d + r1 + r2))\n );\n return r1 * r1 * a1 + r2 * r2 * a2 - k / 2;\n}\n\ninterface Circle {\n x: number;\n y: number;\n r: number;\n}\n\nfunction insideAll(\n point: { x: number; y: number },\n circles: readonly Circle[],\n skip: number\n): boolean {\n return circles.every(\n (circle, index) =>\n index === skip ||\n Math.hypot(point.x - circle.x, point.y - circle.y) <= circle.r * (1 + 1e-12)\n );\n}\n\n/** Convex boundary of the intersection of circles, sampled with vertices on the true boundary. */\nfunction intersectionPolygon(\n circles: readonly Circle[],\n samples: number\n): { x: number; y: number }[] {\n const points: { x: number; y: number }[] = [];\n circles.forEach((circle, index) => {\n for (let k = 0; k < samples; k += 1) {\n const angle = (2 * Math.PI * k) / samples;\n const point = {\n x: circle.x + circle.r * Math.cos(angle),\n y: circle.y + circle.r * Math.sin(angle)\n };\n if (insideAll(point, circles, index)) points.push(point);\n }\n });\n // Pairwise boundary intersection points (the cusps of the cat's eye).\n for (let i = 0; i < circles.length; i += 1) {\n for (let j = i + 1; j < circles.length; j += 1) {\n const a = circles[i]!;\n const b = circles[j]!;\n const d = Math.hypot(b.x - a.x, b.y - a.y);\n if (d === 0 || d >= a.r + b.r || d <= Math.abs(a.r - b.r)) continue;\n const along = (d * d + a.r * a.r - b.r * b.r) / (2 * d);\n const half = Math.sqrt(Math.max(0, a.r * a.r - along * along));\n const ux = (b.x - a.x) / d;\n const uy = (b.y - a.y) / d;\n for (const sign of [1, -1]) {\n const point = {\n x: a.x + along * ux - sign * half * uy,\n y: a.y + along * uy + sign * half * ux\n };\n if (circles.every((c) => Math.hypot(point.x - c.x, point.y - c.y) <= c.r * (1 + 1e-9))) {\n points.push(point);\n }\n }\n }\n }\n if (points.length < 3) return [];\n const cx = points.reduce((s, p) => s + p.x, 0) / points.length;\n const cy = points.reduce((s, p) => s + p.y, 0) / points.length;\n points.sort((p, q) => Math.atan2(p.y - cy, p.x - cx) - Math.atan2(q.y - cy, q.x - cx));\n // Drop coincident neighbours.\n const unique = points.filter((p, index) => {\n const prev = points[(index + points.length - 1) % points.length]!;\n return index === 0 || Math.hypot(p.x - prev.x, p.y - prev.y) > 1e-12 * (1 + Math.abs(p.x) + Math.abs(p.y));\n });\n return unique.length >= 3 ? unique : [];\n}\n\nfunction polygonAreaAndCentroid(\n polygon: readonly { x: number; y: number }[]\n): { area: number; centroid: { x: number; y: number } } {\n let twiceArea = 0;\n let cx = 0;\n let cy = 0;\n for (let i = 0; i < polygon.length; i += 1) {\n const a = polygon[i]!;\n const b = polygon[(i + 1) % polygon.length]!;\n const cross = a.x * b.y - b.x * a.y;\n twiceArea += cross;\n cx += (a.x + b.x) * cross;\n cy += (a.y + b.y) * cross;\n }\n return {\n area: twiceArea / 2,\n centroid: { x: cx / (3 * twiceArea), y: cy / (3 * twiceArea) }\n };\n}\n\n/**\n * Mechanical vignetting at one field angle: the open (cat's-eye) pupil from intersecting the\n * entrance pupil with displaced clipping apertures, its area ratio, and cos^4 x area ratio.\n */\nexport function calculateMechanicalVignetting(\n input: CalculateMechanicalVignettingInput\n): CalculationResult<MechanicalVignetting> {\n requireInputKeys(input, \"calculateMechanicalVignetting\", [\"entrancePupilDiameterMm\", \"fieldAngleDegrees\", \"fieldAzimuthDegrees\", \"apertures\", \"boundarySamplesPerCircle\"]);\n requirePositiveFinite(\"entrancePupilDiameterMm\", input.entrancePupilDiameterMm);\n const fieldAngleDegrees = requireFinite(\"fieldAngleDegrees\", input.fieldAngleDegrees);\n if (fieldAngleDegrees < 0 || fieldAngleDegrees >= 90) {\n throw new InvalidScientificInputError(\"fieldAngleDegrees must lie in [0, 90).\");\n }\n const fieldAzimuthDegrees = requireFinite(\"fieldAzimuthDegrees\", input.fieldAzimuthDegrees);\n if (!Array.isArray(input.apertures) || input.apertures.length < 1 || input.apertures.length > 8) {\n throw new InvalidScientificInputError(\"apertures must contain 1 through 8 entries.\");\n }\n const samples = input.boundarySamplesPerCircle ?? 256;\n if (!Number.isSafeInteger(samples) || samples < 16 || samples > 4096) {\n throw new InvalidScientificInputError(\n \"boundarySamplesPerCircle must be an integer from 16 through 4096.\"\n );\n }\n const theta = (fieldAngleDegrees * Math.PI) / 180;\n const azimuth = (fieldAzimuthDegrees * Math.PI) / 180;\n const tanTheta = Math.tan(theta);\n const pupilRadius = input.entrancePupilDiameterMm / 2;\n const circles: Circle[] = [{ x: 0, y: 0, r: pupilRadius }];\n input.apertures.forEach((aperture, index) => {\n if (typeof aperture !== \"object\" || aperture === null) {\n throw new InvalidConfigurationError(`apertures[${index}] must be an object.`);\n }\n requirePositiveFinite(`apertures[${index}].radiusMm`, aperture.radiusMm);\n const shift =\n requireFinite(`apertures[${index}].shiftPerTanFieldMm`, aperture.shiftPerTanFieldMm) *\n tanTheta;\n circles.push({\n x: shift * Math.cos(azimuth),\n y: shift * Math.sin(azimuth),\n r: aperture.radiusMm\n });\n });\n\n const pupilArea = Math.PI * pupilRadius * pupilRadius;\n const polygon = intersectionPolygon(circles, samples);\n const geometry = polygon.length >= 3 ? polygonAreaAndCentroid(polygon) : undefined;\n let openArea: number;\n let areaMethod: MechanicalVignetting[\"areaMethod\"];\n if (circles.length === 2) {\n const other = circles[1]!;\n openArea = circleIntersectionArea(pupilRadius, other.r, Math.hypot(other.x, other.y));\n areaMethod = \"exact-circle-circle-intersection\";\n } else {\n openArea = geometry?.area ?? 0;\n areaMethod = \"boundary-polygon\";\n }\n const openAreaRatio = Math.min(1, Math.max(0, openArea / pupilArea));\n const cos2 = Math.cos(theta) ** 2;\n const cosineFourthFactor = cos2 * cos2;\n\n return approximationResult(\n {\n fieldAngleDegrees,\n fieldAzimuthDegrees,\n entrancePupilAreaSquareMm: pupilArea,\n openAreaSquareMm: openArea,\n areaMethod,\n openAreaRatio,\n cosineFourthFactor,\n combinedRelativeIllumination: cosineFourthFactor * openAreaRatio,\n openPupilPolygonMm: polygon,\n openPupilCentroidMm: geometry?.centroid ?? null\n },\n \"cats-eye-mechanical-vignetting\",\n \"1.0.0\",\n [\n \"Clipping apertures are circles referred to the entrance-pupil plane, displaced linearly with tan(field angle) along the field azimuth\",\n \"Open pupil = intersection of the entrance pupil with every displaced aperture; boundary vertices lie exactly on the circles\",\n \"Combined relative illumination cos^4(theta) x open-area ratio assumes an object at infinity and no pupil aberration\",\n \"Pupil coordinates are metric and aligned with the image plane (+X right, +Y up)\",\n \"Geometry is caller-declared; no named-lens barrel data is implied\"\n ]\n );\n}\n"]}
@@ -13,15 +13,10 @@
13
13
  */
14
14
  import { approximationResult } from "../core/calculation-result.js";
15
15
  import { InvalidScientificInputError, requirePositiveFinite } from "../core/validation.js";
16
- import { requireInputObject, requireInputKeys } from "../core/record-validation.js";
16
+ import { requireInputObject, requireInputKeys, assertNamedFinite as requireFinite } from "../core/record-validation.js";
17
17
  import { InvalidConfigurationError } from "../core/configuration-error.js";
18
18
  const INVERSE_BISECTION_ITERATIONS = 80;
19
19
  const INVERSE_MAPPED_RADIUS_FLOATING_TOLERANCE_ULPS = 16;
20
- function requireFinite(name, value) {
21
- if (!Number.isFinite(value)) {
22
- throw new InvalidScientificInputError(`${name} must be finite.`);
23
- }
24
- }
25
20
  function inverseMappedRadiusTolerance(distortedNormalizedRadius, maximumMappedNormalizedRadius) {
26
21
  return (Number.EPSILON *
27
22
  INVERSE_MAPPED_RADIUS_FLOATING_TOLERANCE_ULPS *
@@ -1 +1 @@
1
- {"version":3,"file":"radial-distortion.js","sourceRoot":"","sources":["../../src/optics/radial-distortion.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic radial lens distortion (barrel and pincushion).\n *\n * The mapping is rotationally symmetric about the optical axis:\n * p_distorted = p_ideal * (1 + k1 r² + k2 r⁴ + k3 r⁶)\n * where r is normalized by the physical reference radius the caller declares.\n *\n * A profile is accepted only if the radial mapping is strictly monotonic over its declared operating\n * radius. That makes the inverse mapping (destination-to-source sampling) well defined.\n * @see docs/PHYSICS_FOUNDATION.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport { requireInputObject, requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport interface RadialDistortionCoefficients {\n /** Dimensionless r² coefficient. */\n k1: number;\n /** Dimensionless r⁴ coefficient. */\n k2: number;\n /** Dimensionless r⁶ coefficient. */\n k3: number;\n}\n\nexport interface RadialDistortionProfile {\n /**\n * Physical image-plane radius used to normalize field position, in mm.\n * Coefficients are meaningful only with this declared normalization.\n */\n normalizationRadiusMm: number;\n /**\n * Largest undistorted normalized radius for which this profile is declared\n * valid. Within it the mapping must stay one-to-one and invertible.\n */\n maximumNormalizedRadius: number;\n coefficients: RadialDistortionCoefficients;\n}\n\nexport interface LensFieldPointMm {\n /** Image-plane X coordinate relative to the optical axis, in mm. */\n x: number;\n /** Image-plane Y coordinate relative to the optical axis, in mm. */\n y: number;\n}\n\nexport interface CalculateRadialDistortionMappingInput {\n /** Ideal/undistorted image-plane point. */\n imagePointMm: LensFieldPointMm;\n profile: RadialDistortionProfile;\n}\n\nexport interface CalculateInverseRadialDistortionMappingInput {\n /** Distorted image-plane point to inverse-map back to ideal coordinates. */\n distortedImagePointMm: LensFieldPointMm;\n profile: RadialDistortionProfile;\n}\n\nexport interface CalculateInverseRadialDistortionMappingsInput {\n /** Distorted image-plane destinations to inverse-map in one validated batch. */\n distortedImagePointsMm: readonly LensFieldPointMm[];\n profile: RadialDistortionProfile;\n}\n\nexport interface RadialDistortionMapping {\n direction: \"undistorted-to-distorted\";\n sourceImagePointMm: LensFieldPointMm;\n mappedImagePointMm: LensFieldPointMm;\n sourceNormalizedRadius: number;\n mappedNormalizedRadius: number;\n radialScale: number;\n deltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n profileMinimumRadialDerivative: number;\n}\n\nexport interface InverseRadialDistortionMapping {\n direction: \"distorted-to-undistorted\";\n distortedImagePointMm: LensFieldPointMm;\n sourceImagePointMm: LensFieldPointMm;\n distortedNormalizedRadius: number;\n sourceNormalizedRadius: number;\n radialScaleAtSource: number;\n deltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n profileMinimumRadialDerivative: number;\n}\n\nexport interface InverseRadialDistortionMappings {\n direction: \"distorted-to-undistorted-batch\";\n mappings: readonly InverseRadialDistortionMapping[];\n pointCount: number;\n profileMinimumRadialDerivative: number;\n}\n\n/** @internal Shared by the composite lens-field batch evaluators. Not exported from the package root. */\nexport interface ValidatedRadialProfile {\n normalizationRadiusMm: number;\n maximumNormalizedRadius: number;\n coefficients: RadialDistortionCoefficients;\n minimumRadialDerivative: number;\n maximumMappedNormalizedRadius: number;\n}\n\nconst INVERSE_BISECTION_ITERATIONS = 80;\nconst INVERSE_MAPPED_RADIUS_FLOATING_TOLERANCE_ULPS = 16;\n\nfunction requireFinite(name: string, value: number): void {\n if (!Number.isFinite(value)) {\n throw new InvalidScientificInputError(`${name} must be finite.`);\n }\n}\n\nfunction inverseMappedRadiusTolerance(\n distortedNormalizedRadius: number,\n maximumMappedNormalizedRadius: number\n): number {\n return (\n Number.EPSILON *\n INVERSE_MAPPED_RADIUS_FLOATING_TOLERANCE_ULPS *\n Math.max(\n 1,\n Math.abs(distortedNormalizedRadius),\n Math.abs(maximumMappedNormalizedRadius)\n )\n );\n}\n\nfunction radialScale(\n normalizedRadius: number,\n coefficients: RadialDistortionCoefficients\n): number {\n const r2 = normalizedRadius * normalizedRadius;\n const r4 = r2 * r2;\n const r6 = r4 * r2;\n return (\n 1 +\n coefficients.k1 * r2 +\n coefficients.k2 * r4 +\n coefficients.k3 * r6\n );\n}\n\nfunction mappedNormalizedRadius(\n normalizedRadius: number,\n coefficients: RadialDistortionCoefficients\n): number {\n return normalizedRadius * radialScale(normalizedRadius, coefficients);\n}\n\nfunction radialDerivativeFromSquaredRadius(\n squaredRadius: number,\n coefficients: RadialDistortionCoefficients\n): number {\n return (\n 1 +\n 3 * coefficients.k1 * squaredRadius +\n 5 * coefficients.k2 * squaredRadius * squaredRadius +\n 7 *\n coefficients.k3 *\n squaredRadius *\n squaredRadius *\n squaredRadius\n );\n}\n\nfunction derivativeCriticalSquaredRadii(\n coefficients: RadialDistortionCoefficients\n): number[] {\n // d/dr² of the radial derivative:\n // 3 k1 + 10 k2 s + 21 k3 s² = 0, where s = r².\n //\n // Scale the quadratic before solving, so finite caller coefficients cannot\n // overflow the discriminant. Use the numerically stable q-form roots. Then a\n // tiny quadratic term cannot erase the smaller physical root through\n // cancellation and let a non-monotonic profile pass by accident.\n const a = 21 * coefficients.k3;\n const b = 10 * coefficients.k2;\n const c = 3 * coefficients.k1;\n const coefficientScale = Math.max(\n Math.abs(a),\n Math.abs(b),\n Math.abs(c)\n );\n\n if (coefficientScale === 0) {\n return [];\n }\n\n const scaledA = a / coefficientScale;\n const scaledB = b / coefficientScale;\n const scaledC = c / coefficientScale;\n\n if (scaledA === 0) {\n return scaledB === 0 ? [] : [-scaledC / scaledB];\n }\n\n const squaredB = scaledB * scaledB;\n const fourAC = 4 * scaledA * scaledC;\n const discriminant = squaredB - fourAC;\n const discriminantTolerance =\n Number.EPSILON *\n 16 *\n Math.max(1, Math.abs(squaredB), Math.abs(fourAC));\n\n if (discriminant < -discriminantTolerance) {\n return [];\n }\n\n const root = Math.sqrt(Math.max(0, discriminant));\n if (root === 0) {\n return [-scaledB / (2 * scaledA)];\n }\n\n // root > 0 here, so q is never zero: for scaledB >= 0 the sum scaledB + root is positive and for\n // scaledB < 0 the difference scaledB - root is negative. (A former q === 0 branch was unreachable.)\n const q =\n -0.5 *\n (scaledB + (scaledB >= 0 ? root : -root));\n\n return [q / scaledA, scaledC / q];\n}\n\n/** @internal Validates and copies one profile for repeated use inside the package. */\nexport function validateRadialDistortionProfile(\n profile: RadialDistortionProfile\n): ValidatedRadialProfile {\n requireInputObject(profile, \"profile input\");\n requirePositiveFinite(\n \"profile.normalizationRadiusMm\",\n profile.normalizationRadiusMm\n );\n requirePositiveFinite(\n \"profile.maximumNormalizedRadius\",\n profile.maximumNormalizedRadius\n );\n requireFinite(\"profile.coefficients.k1\", profile.coefficients.k1);\n requireFinite(\"profile.coefficients.k2\", profile.coefficients.k2);\n requireFinite(\"profile.coefficients.k3\", profile.coefficients.k3);\n\n const maximumSquaredRadius =\n profile.maximumNormalizedRadius * profile.maximumNormalizedRadius;\n if (!Number.isFinite(maximumSquaredRadius)) {\n throw new InvalidScientificInputError(\n \"profile.maximumNormalizedRadius is too large to evaluate safely.\"\n );\n }\n\n const candidates = [\n 0,\n maximumSquaredRadius,\n ...derivativeCriticalSquaredRadii(profile.coefficients).filter(\n (value) => value > 0 && value < maximumSquaredRadius\n )\n ];\n const minimumRadialDerivative = Math.min(\n ...candidates.map((squaredRadius) =>\n radialDerivativeFromSquaredRadius(\n squaredRadius,\n profile.coefficients\n )\n )\n );\n\n if (\n !Number.isFinite(minimumRadialDerivative) ||\n minimumRadialDerivative <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Radial distortion profile must remain strictly monotonic over maximumNormalizedRadius so inverse mapping is single-valued.\"\n );\n }\n\n const maximumMappedRadius = mappedNormalizedRadius(\n profile.maximumNormalizedRadius,\n profile.coefficients\n );\n if (!Number.isFinite(maximumMappedRadius) || maximumMappedRadius <= 0) {\n throw new InvalidScientificInputError(\n \"Radial distortion profile produces an invalid mapped operating radius.\"\n );\n }\n\n return {\n normalizationRadiusMm: profile.normalizationRadiusMm,\n maximumNormalizedRadius: profile.maximumNormalizedRadius,\n coefficients: { ...profile.coefficients },\n minimumRadialDerivative,\n maximumMappedNormalizedRadius: maximumMappedRadius\n };\n}\n\nfunction requirePoint(name: string, point: LensFieldPointMm): void {\n if (typeof point !== \"object\" || point === null) {\n throw new InvalidConfigurationError(\n `${name} must be an object with finite numeric x and y coordinates.`\n );\n }\n if (\n typeof point.x !== \"number\" ||\n typeof point.y !== \"number\" ||\n !Number.isFinite(point.x) ||\n !Number.isFinite(point.y)\n ) {\n throw new InvalidScientificInputError(\n `${name} must contain finite numeric x and y coordinates.`\n );\n }\n}\n\nfunction mappingAssumptions(): readonly string[] {\n return [\n \"Generic rotationally symmetric radial polynomial field mapping\",\n \"Distortion is centered on the optical axis; decentering and tangential distortion are not modeled\",\n \"Normalized radius is image-plane radius divided by the caller-declared normalizationRadiusMm\",\n \"Radial scale is 1 + k1 r^2 + k2 r^4 + k3 r^6\",\n \"The declared operating radius is required to remain strictly monotonic so inverse mapping is single-valued\",\n \"Coefficients are generic caller inputs and do not represent a named lens unless separately calibrated with defensible provenance\"\n ];\n}\n\n/**\n * Applies a generic radial lens-distortion field mapping to one ideal\n * image-plane point.\n *\n * The mapping is rotationally symmetric about the optical axis:\n *\n * p_distorted = p_ideal * (1 + k1 r² + k2 r⁴ + k3 r⁶)\n *\n * where r is normalized by the caller-declared physical reference radius.\n *\n * A profile is accepted only if the radial mapping is strictly monotonic over\n * its declared operating radius. That makes the inverse mapping\n * (destination-to-source sampling) well defined.\n */\nexport function calculateRadialDistortionMapping(\n input: CalculateRadialDistortionMappingInput\n): CalculationResult<RadialDistortionMapping> {\n requireInputKeys(input, \"calculateRadialDistortionMapping\", [\"imagePointMm\", \"profile\"]);\n requirePoint(\"imagePointMm\", input.imagePointMm);\n const profile = validateRadialDistortionProfile(input.profile);\n\n const sourceRadiusMm = Math.hypot(\n input.imagePointMm.x,\n input.imagePointMm.y\n );\n const sourceNormalizedRadius =\n sourceRadiusMm / profile.normalizationRadiusMm;\n requireFinite(\"imagePointMm normalized radius\", sourceNormalizedRadius);\n\n if (sourceNormalizedRadius > profile.maximumNormalizedRadius) {\n throw new InvalidScientificInputError(\n \"imagePointMm lies outside the radial distortion profile maximumNormalizedRadius.\"\n );\n }\n\n const scale = radialScale(\n sourceNormalizedRadius,\n profile.coefficients\n );\n const mappedImagePointMm = {\n x: input.imagePointMm.x * scale,\n y: input.imagePointMm.y * scale\n };\n const mappedRadius = sourceNormalizedRadius * scale;\n const deltaX = mappedImagePointMm.x - input.imagePointMm.x;\n const deltaY = mappedImagePointMm.y - input.imagePointMm.y;\n\n return approximationResult(\n {\n direction: \"undistorted-to-distorted\",\n sourceImagePointMm: { ...input.imagePointMm },\n mappedImagePointMm,\n sourceNormalizedRadius,\n mappedNormalizedRadius: mappedRadius,\n radialScale: scale,\n deltaMm: {\n x: deltaX,\n y: deltaY,\n distance: Math.hypot(deltaX, deltaY)\n },\n profileMinimumRadialDerivative:\n profile.minimumRadialDerivative\n },\n \"generic-radial-lens-distortion\",\n \"1.0.0\",\n mappingAssumptions()\n );\n}\n\n/**\n * Inverse-maps one distorted image-plane point to its ideal (undistorted)\n * source point, for destination-to-source sampling in a renderer.\n *\n * The forward mapping must be monotonic, so the inverse is unique over the\n * declared operating radius. The exact centre, an identity profile and the\n * mapped boundary are solved directly. Other radii use a bounded,\n * deterministic bisection that stops when the floating-point bounds can no\n * longer narrow.\n */\nexport interface InverseRadialSourcePointValue {\n sourceImagePointMm: LensFieldPointMm;\n distortedNormalizedRadius: number;\n sourceNormalizedRadius: number;\n radialScaleAtSource: number;\n}\n\n/**\n * @internal Minimal inverse evaluation for composite batch models inside the\n * package that do not need the standalone radial delta or provenance wrapper.\n */\nexport function calculateInverseRadialSourcePointValue(\n distortedImagePointMm: LensFieldPointMm,\n profile: ValidatedRadialProfile,\n pointPath: string\n): InverseRadialSourcePointValue {\n requirePoint(pointPath, distortedImagePointMm);\n\n const distortedRadiusMm = Math.hypot(\n distortedImagePointMm.x,\n distortedImagePointMm.y\n );\n const distortedNormalizedRadius =\n distortedRadiusMm / profile.normalizationRadiusMm;\n requireFinite(\n `${pointPath} normalized radius`,\n distortedNormalizedRadius\n );\n\n const mappedRadiusTolerance = inverseMappedRadiusTolerance(\n distortedNormalizedRadius,\n profile.maximumMappedNormalizedRadius\n );\n if (\n distortedNormalizedRadius >\n profile.maximumMappedNormalizedRadius + mappedRadiusTolerance\n ) {\n throw new InvalidScientificInputError(\n `${pointPath} lies outside the mapped radial distortion profile operating radius.`\n );\n }\n const solverDistortedNormalizedRadius = Math.min(\n distortedNormalizedRadius,\n profile.maximumMappedNormalizedRadius\n );\n\n let sourceNormalizedRadius = 0;\n if (\n solverDistortedNormalizedRadius ===\n profile.maximumMappedNormalizedRadius\n ) {\n sourceNormalizedRadius = profile.maximumNormalizedRadius;\n } else if (solverDistortedNormalizedRadius > 0) {\n const { k1, k2, k3 } = profile.coefficients;\n if (k1 === 0 && k2 === 0 && k3 === 0) {\n sourceNormalizedRadius = solverDistortedNormalizedRadius;\n } else {\n let lower = 0;\n let upper = profile.maximumNormalizedRadius;\n\n for (\n let iteration = 0;\n iteration < INVERSE_BISECTION_ITERATIONS;\n iteration += 1\n ) {\n const midpoint = (lower + upper) / 2;\n if (midpoint === lower || midpoint === upper) {\n break;\n }\n\n const mapped = mappedNormalizedRadius(\n midpoint,\n profile.coefficients\n );\n if (mapped < solverDistortedNormalizedRadius) {\n lower = midpoint;\n } else {\n upper = midpoint;\n }\n }\n sourceNormalizedRadius = (lower + upper) / 2;\n }\n }\n\n const radialScaleAtSource = radialScale(\n sourceNormalizedRadius,\n profile.coefficients\n );\n const sourceImagePointMm =\n distortedNormalizedRadius === 0\n ? { x: 0, y: 0 }\n : solverDistortedNormalizedRadius === distortedNormalizedRadius\n ? {\n x: distortedImagePointMm.x / radialScaleAtSource,\n y: distortedImagePointMm.y / radialScaleAtSource\n }\n : {\n x:\n distortedImagePointMm.x *\n (sourceNormalizedRadius / distortedNormalizedRadius),\n y:\n distortedImagePointMm.y *\n (sourceNormalizedRadius / distortedNormalizedRadius)\n };\n\n return {\n sourceImagePointMm,\n distortedNormalizedRadius,\n sourceNormalizedRadius,\n radialScaleAtSource\n };\n}\n\nfunction calculateInverseRadialDistortionMappingValue(\n distortedImagePointMm: LensFieldPointMm,\n profile: ValidatedRadialProfile,\n pointPath: string\n): InverseRadialDistortionMapping {\n const source = calculateInverseRadialSourcePointValue(\n distortedImagePointMm,\n profile,\n pointPath\n );\n const deltaX =\n source.sourceImagePointMm.x - distortedImagePointMm.x;\n const deltaY =\n source.sourceImagePointMm.y - distortedImagePointMm.y;\n\n return {\n direction: \"distorted-to-undistorted\",\n distortedImagePointMm: { ...distortedImagePointMm },\n sourceImagePointMm: source.sourceImagePointMm,\n distortedNormalizedRadius: source.distortedNormalizedRadius,\n sourceNormalizedRadius: source.sourceNormalizedRadius,\n radialScaleAtSource: source.radialScaleAtSource,\n deltaMm: {\n x: deltaX,\n y: deltaY,\n distance: Math.hypot(deltaX, deltaY)\n },\n profileMinimumRadialDerivative: profile.minimumRadialDerivative\n };\n}\n\n/**\n * Inverts the declared monotonic radial field mapping inside its supported range, using the existing\n * bounded root solver. Optical coordinates are kept explicit.\n * @param input - CalculateInverseRadialDistortionMappingInput. See that type for coordinates, units and profile binding.\n * @returns CalculationResult<InverseRadialDistortionMapping>. The result states its shape and scientific status; a successful call does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the root export and exact type graph.\n */\nexport function calculateInverseRadialDistortionMapping(\n input: CalculateInverseRadialDistortionMappingInput\n): CalculationResult<InverseRadialDistortionMapping> {\n requireInputKeys(input, \"calculateInverseRadialDistortionMapping\", [\"distortedImagePointMm\", \"profile\"]);\n const profile = validateRadialDistortionProfile(input.profile);\n const mapping = calculateInverseRadialDistortionMappingValue(\n input.distortedImagePointMm,\n profile,\n \"distortedImagePointMm\"\n );\n\n return approximationResult(\n mapping,\n \"generic-inverse-radial-lens-distortion\",\n \"1.0.0\",\n [\n ...mappingAssumptions(),\n `Inverse radius uses exact center/identity/boundary solutions where applicable and otherwise up to ${INVERSE_BISECTION_ITERATIONS} deterministic bisection iterations, stopping when IEEE-754 bounds can no longer narrow`\n ]\n );\n}\n\n/**\n * Inverse-maps many distorted image-plane points at once. The radial profile\n * is validated and resolved only once for the whole batch.\n *\n * Each returned mapping is numerically identical to the single-point API.\n * Provenance and profile validation are reported once for the batch.\n */\nexport function calculateInverseRadialDistortionMappings(\n input: CalculateInverseRadialDistortionMappingsInput\n): CalculationResult<InverseRadialDistortionMappings> {\n requireInputKeys(input, \"calculateInverseRadialDistortionMappings\", [\"distortedImagePointsMm\", \"profile\"]);\n const profile = validateRadialDistortionProfile(input.profile);\n if (!Array.isArray(input.distortedImagePointsMm)) {\n throw new InvalidConfigurationError(\n \"distortedImagePointsMm must be an array of image-plane points.\"\n );\n }\n\n const mappings: InverseRadialDistortionMapping[] = [];\n for (\n let index = 0;\n index < input.distortedImagePointsMm.length;\n index += 1\n ) {\n const point = input.distortedImagePointsMm[index];\n if (point === undefined) {\n throw new InvalidScientificInputError(\n `distortedImagePointsMm[${index}] must be an image-plane point.`\n );\n }\n mappings.push(\n calculateInverseRadialDistortionMappingValue(\n point,\n profile,\n `distortedImagePointsMm[${index}]`\n )\n );\n }\n\n return approximationResult(\n {\n direction: \"distorted-to-undistorted-batch\",\n mappings,\n pointCount: mappings.length,\n profileMinimumRadialDerivative: profile.minimumRadialDerivative\n },\n \"generic-inverse-radial-lens-distortion-batch\",\n \"1.0.0\",\n [\n ...mappingAssumptions(),\n `Inverse radius uses exact center/identity/boundary solutions where applicable and otherwise up to ${INVERSE_BISECTION_ITERATIONS} deterministic bisection iterations per point, stopping when IEEE-754 bounds can no longer narrow`,\n \"The radial profile is validated and its invariant extrema are resolved once for the batch\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"radial-distortion.js","sourceRoot":"","sources":["../../src/optics/radial-distortion.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic radial lens distortion (barrel and pincushion).\n *\n * The mapping is rotationally symmetric about the optical axis:\n * p_distorted = p_ideal * (1 + k1 r² + k2 r⁴ + k3 r⁶)\n * where r is normalized by the physical reference radius the caller declares.\n *\n * A profile is accepted only if the radial mapping is strictly monotonic over its declared operating\n * radius. That makes the inverse mapping (destination-to-source sampling) well defined.\n * @see docs/PHYSICS_FOUNDATION.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport { requireInputObject, requireInputKeys, assertNamedFinite as requireFinite } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport interface RadialDistortionCoefficients {\n /** Dimensionless r² coefficient. */\n k1: number;\n /** Dimensionless r⁴ coefficient. */\n k2: number;\n /** Dimensionless r⁶ coefficient. */\n k3: number;\n}\n\nexport interface RadialDistortionProfile {\n /**\n * Physical image-plane radius used to normalize field position, in mm.\n * Coefficients are meaningful only with this declared normalization.\n */\n normalizationRadiusMm: number;\n /**\n * Largest undistorted normalized radius for which this profile is declared\n * valid. Within it the mapping must stay one-to-one and invertible.\n */\n maximumNormalizedRadius: number;\n coefficients: RadialDistortionCoefficients;\n}\n\nexport interface LensFieldPointMm {\n /** Image-plane X coordinate relative to the optical axis, in mm. */\n x: number;\n /** Image-plane Y coordinate relative to the optical axis, in mm. */\n y: number;\n}\n\nexport interface CalculateRadialDistortionMappingInput {\n /** Ideal/undistorted image-plane point. */\n imagePointMm: LensFieldPointMm;\n profile: RadialDistortionProfile;\n}\n\nexport interface CalculateInverseRadialDistortionMappingInput {\n /** Distorted image-plane point to inverse-map back to ideal coordinates. */\n distortedImagePointMm: LensFieldPointMm;\n profile: RadialDistortionProfile;\n}\n\nexport interface CalculateInverseRadialDistortionMappingsInput {\n /** Distorted image-plane destinations to inverse-map in one validated batch. */\n distortedImagePointsMm: readonly LensFieldPointMm[];\n profile: RadialDistortionProfile;\n}\n\nexport interface RadialDistortionMapping {\n direction: \"undistorted-to-distorted\";\n sourceImagePointMm: LensFieldPointMm;\n mappedImagePointMm: LensFieldPointMm;\n sourceNormalizedRadius: number;\n mappedNormalizedRadius: number;\n radialScale: number;\n deltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n profileMinimumRadialDerivative: number;\n}\n\nexport interface InverseRadialDistortionMapping {\n direction: \"distorted-to-undistorted\";\n distortedImagePointMm: LensFieldPointMm;\n sourceImagePointMm: LensFieldPointMm;\n distortedNormalizedRadius: number;\n sourceNormalizedRadius: number;\n radialScaleAtSource: number;\n deltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n profileMinimumRadialDerivative: number;\n}\n\nexport interface InverseRadialDistortionMappings {\n direction: \"distorted-to-undistorted-batch\";\n mappings: readonly InverseRadialDistortionMapping[];\n pointCount: number;\n profileMinimumRadialDerivative: number;\n}\n\n/** @internal Shared by the composite lens-field batch evaluators. Not exported from the package root. */\nexport interface ValidatedRadialProfile {\n normalizationRadiusMm: number;\n maximumNormalizedRadius: number;\n coefficients: RadialDistortionCoefficients;\n minimumRadialDerivative: number;\n maximumMappedNormalizedRadius: number;\n}\n\nconst INVERSE_BISECTION_ITERATIONS = 80;\nconst INVERSE_MAPPED_RADIUS_FLOATING_TOLERANCE_ULPS = 16;\n\nfunction inverseMappedRadiusTolerance(\n distortedNormalizedRadius: number,\n maximumMappedNormalizedRadius: number\n): number {\n return (\n Number.EPSILON *\n INVERSE_MAPPED_RADIUS_FLOATING_TOLERANCE_ULPS *\n Math.max(\n 1,\n Math.abs(distortedNormalizedRadius),\n Math.abs(maximumMappedNormalizedRadius)\n )\n );\n}\n\nfunction radialScale(\n normalizedRadius: number,\n coefficients: RadialDistortionCoefficients\n): number {\n const r2 = normalizedRadius * normalizedRadius;\n const r4 = r2 * r2;\n const r6 = r4 * r2;\n return (\n 1 +\n coefficients.k1 * r2 +\n coefficients.k2 * r4 +\n coefficients.k3 * r6\n );\n}\n\nfunction mappedNormalizedRadius(\n normalizedRadius: number,\n coefficients: RadialDistortionCoefficients\n): number {\n return normalizedRadius * radialScale(normalizedRadius, coefficients);\n}\n\nfunction radialDerivativeFromSquaredRadius(\n squaredRadius: number,\n coefficients: RadialDistortionCoefficients\n): number {\n return (\n 1 +\n 3 * coefficients.k1 * squaredRadius +\n 5 * coefficients.k2 * squaredRadius * squaredRadius +\n 7 *\n coefficients.k3 *\n squaredRadius *\n squaredRadius *\n squaredRadius\n );\n}\n\nfunction derivativeCriticalSquaredRadii(\n coefficients: RadialDistortionCoefficients\n): number[] {\n // d/dr² of the radial derivative:\n // 3 k1 + 10 k2 s + 21 k3 s² = 0, where s = r².\n //\n // Scale the quadratic before solving, so finite caller coefficients cannot\n // overflow the discriminant. Use the numerically stable q-form roots. Then a\n // tiny quadratic term cannot erase the smaller physical root through\n // cancellation and let a non-monotonic profile pass by accident.\n const a = 21 * coefficients.k3;\n const b = 10 * coefficients.k2;\n const c = 3 * coefficients.k1;\n const coefficientScale = Math.max(\n Math.abs(a),\n Math.abs(b),\n Math.abs(c)\n );\n\n if (coefficientScale === 0) {\n return [];\n }\n\n const scaledA = a / coefficientScale;\n const scaledB = b / coefficientScale;\n const scaledC = c / coefficientScale;\n\n if (scaledA === 0) {\n return scaledB === 0 ? [] : [-scaledC / scaledB];\n }\n\n const squaredB = scaledB * scaledB;\n const fourAC = 4 * scaledA * scaledC;\n const discriminant = squaredB - fourAC;\n const discriminantTolerance =\n Number.EPSILON *\n 16 *\n Math.max(1, Math.abs(squaredB), Math.abs(fourAC));\n\n if (discriminant < -discriminantTolerance) {\n return [];\n }\n\n const root = Math.sqrt(Math.max(0, discriminant));\n if (root === 0) {\n return [-scaledB / (2 * scaledA)];\n }\n\n // root > 0 here, so q is never zero: for scaledB >= 0 the sum scaledB + root is positive and for\n // scaledB < 0 the difference scaledB - root is negative. (A former q === 0 branch was unreachable.)\n const q =\n -0.5 *\n (scaledB + (scaledB >= 0 ? root : -root));\n\n return [q / scaledA, scaledC / q];\n}\n\n/** @internal Validates and copies one profile for repeated use inside the package. */\nexport function validateRadialDistortionProfile(\n profile: RadialDistortionProfile\n): ValidatedRadialProfile {\n requireInputObject(profile, \"profile input\");\n requirePositiveFinite(\n \"profile.normalizationRadiusMm\",\n profile.normalizationRadiusMm\n );\n requirePositiveFinite(\n \"profile.maximumNormalizedRadius\",\n profile.maximumNormalizedRadius\n );\n requireFinite(\"profile.coefficients.k1\", profile.coefficients.k1);\n requireFinite(\"profile.coefficients.k2\", profile.coefficients.k2);\n requireFinite(\"profile.coefficients.k3\", profile.coefficients.k3);\n\n const maximumSquaredRadius =\n profile.maximumNormalizedRadius * profile.maximumNormalizedRadius;\n if (!Number.isFinite(maximumSquaredRadius)) {\n throw new InvalidScientificInputError(\n \"profile.maximumNormalizedRadius is too large to evaluate safely.\"\n );\n }\n\n const candidates = [\n 0,\n maximumSquaredRadius,\n ...derivativeCriticalSquaredRadii(profile.coefficients).filter(\n (value) => value > 0 && value < maximumSquaredRadius\n )\n ];\n const minimumRadialDerivative = Math.min(\n ...candidates.map((squaredRadius) =>\n radialDerivativeFromSquaredRadius(\n squaredRadius,\n profile.coefficients\n )\n )\n );\n\n if (\n !Number.isFinite(minimumRadialDerivative) ||\n minimumRadialDerivative <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Radial distortion profile must remain strictly monotonic over maximumNormalizedRadius so inverse mapping is single-valued.\"\n );\n }\n\n const maximumMappedRadius = mappedNormalizedRadius(\n profile.maximumNormalizedRadius,\n profile.coefficients\n );\n if (!Number.isFinite(maximumMappedRadius) || maximumMappedRadius <= 0) {\n throw new InvalidScientificInputError(\n \"Radial distortion profile produces an invalid mapped operating radius.\"\n );\n }\n\n return {\n normalizationRadiusMm: profile.normalizationRadiusMm,\n maximumNormalizedRadius: profile.maximumNormalizedRadius,\n coefficients: { ...profile.coefficients },\n minimumRadialDerivative,\n maximumMappedNormalizedRadius: maximumMappedRadius\n };\n}\n\nfunction requirePoint(name: string, point: LensFieldPointMm): void {\n if (typeof point !== \"object\" || point === null) {\n throw new InvalidConfigurationError(\n `${name} must be an object with finite numeric x and y coordinates.`\n );\n }\n if (\n typeof point.x !== \"number\" ||\n typeof point.y !== \"number\" ||\n !Number.isFinite(point.x) ||\n !Number.isFinite(point.y)\n ) {\n throw new InvalidScientificInputError(\n `${name} must contain finite numeric x and y coordinates.`\n );\n }\n}\n\nfunction mappingAssumptions(): readonly string[] {\n return [\n \"Generic rotationally symmetric radial polynomial field mapping\",\n \"Distortion is centered on the optical axis; decentering and tangential distortion are not modeled\",\n \"Normalized radius is image-plane radius divided by the caller-declared normalizationRadiusMm\",\n \"Radial scale is 1 + k1 r^2 + k2 r^4 + k3 r^6\",\n \"The declared operating radius is required to remain strictly monotonic so inverse mapping is single-valued\",\n \"Coefficients are generic caller inputs and do not represent a named lens unless separately calibrated with defensible provenance\"\n ];\n}\n\n/**\n * Applies a generic radial lens-distortion field mapping to one ideal\n * image-plane point.\n *\n * The mapping is rotationally symmetric about the optical axis:\n *\n * p_distorted = p_ideal * (1 + k1 r² + k2 r⁴ + k3 r⁶)\n *\n * where r is normalized by the caller-declared physical reference radius.\n *\n * A profile is accepted only if the radial mapping is strictly monotonic over\n * its declared operating radius. That makes the inverse mapping\n * (destination-to-source sampling) well defined.\n */\nexport function calculateRadialDistortionMapping(\n input: CalculateRadialDistortionMappingInput\n): CalculationResult<RadialDistortionMapping> {\n requireInputKeys(input, \"calculateRadialDistortionMapping\", [\"imagePointMm\", \"profile\"]);\n requirePoint(\"imagePointMm\", input.imagePointMm);\n const profile = validateRadialDistortionProfile(input.profile);\n\n const sourceRadiusMm = Math.hypot(\n input.imagePointMm.x,\n input.imagePointMm.y\n );\n const sourceNormalizedRadius =\n sourceRadiusMm / profile.normalizationRadiusMm;\n requireFinite(\"imagePointMm normalized radius\", sourceNormalizedRadius);\n\n if (sourceNormalizedRadius > profile.maximumNormalizedRadius) {\n throw new InvalidScientificInputError(\n \"imagePointMm lies outside the radial distortion profile maximumNormalizedRadius.\"\n );\n }\n\n const scale = radialScale(\n sourceNormalizedRadius,\n profile.coefficients\n );\n const mappedImagePointMm = {\n x: input.imagePointMm.x * scale,\n y: input.imagePointMm.y * scale\n };\n const mappedRadius = sourceNormalizedRadius * scale;\n const deltaX = mappedImagePointMm.x - input.imagePointMm.x;\n const deltaY = mappedImagePointMm.y - input.imagePointMm.y;\n\n return approximationResult(\n {\n direction: \"undistorted-to-distorted\",\n sourceImagePointMm: { ...input.imagePointMm },\n mappedImagePointMm,\n sourceNormalizedRadius,\n mappedNormalizedRadius: mappedRadius,\n radialScale: scale,\n deltaMm: {\n x: deltaX,\n y: deltaY,\n distance: Math.hypot(deltaX, deltaY)\n },\n profileMinimumRadialDerivative:\n profile.minimumRadialDerivative\n },\n \"generic-radial-lens-distortion\",\n \"1.0.0\",\n mappingAssumptions()\n );\n}\n\n/**\n * Inverse-maps one distorted image-plane point to its ideal (undistorted)\n * source point, for destination-to-source sampling in a renderer.\n *\n * The forward mapping must be monotonic, so the inverse is unique over the\n * declared operating radius. The exact centre, an identity profile and the\n * mapped boundary are solved directly. Other radii use a bounded,\n * deterministic bisection that stops when the floating-point bounds can no\n * longer narrow.\n */\nexport interface InverseRadialSourcePointValue {\n sourceImagePointMm: LensFieldPointMm;\n distortedNormalizedRadius: number;\n sourceNormalizedRadius: number;\n radialScaleAtSource: number;\n}\n\n/**\n * @internal Minimal inverse evaluation for composite batch models inside the\n * package that do not need the standalone radial delta or provenance wrapper.\n */\nexport function calculateInverseRadialSourcePointValue(\n distortedImagePointMm: LensFieldPointMm,\n profile: ValidatedRadialProfile,\n pointPath: string\n): InverseRadialSourcePointValue {\n requirePoint(pointPath, distortedImagePointMm);\n\n const distortedRadiusMm = Math.hypot(\n distortedImagePointMm.x,\n distortedImagePointMm.y\n );\n const distortedNormalizedRadius =\n distortedRadiusMm / profile.normalizationRadiusMm;\n requireFinite(\n `${pointPath} normalized radius`,\n distortedNormalizedRadius\n );\n\n const mappedRadiusTolerance = inverseMappedRadiusTolerance(\n distortedNormalizedRadius,\n profile.maximumMappedNormalizedRadius\n );\n if (\n distortedNormalizedRadius >\n profile.maximumMappedNormalizedRadius + mappedRadiusTolerance\n ) {\n throw new InvalidScientificInputError(\n `${pointPath} lies outside the mapped radial distortion profile operating radius.`\n );\n }\n const solverDistortedNormalizedRadius = Math.min(\n distortedNormalizedRadius,\n profile.maximumMappedNormalizedRadius\n );\n\n let sourceNormalizedRadius = 0;\n if (\n solverDistortedNormalizedRadius ===\n profile.maximumMappedNormalizedRadius\n ) {\n sourceNormalizedRadius = profile.maximumNormalizedRadius;\n } else if (solverDistortedNormalizedRadius > 0) {\n const { k1, k2, k3 } = profile.coefficients;\n if (k1 === 0 && k2 === 0 && k3 === 0) {\n sourceNormalizedRadius = solverDistortedNormalizedRadius;\n } else {\n let lower = 0;\n let upper = profile.maximumNormalizedRadius;\n\n for (\n let iteration = 0;\n iteration < INVERSE_BISECTION_ITERATIONS;\n iteration += 1\n ) {\n const midpoint = (lower + upper) / 2;\n if (midpoint === lower || midpoint === upper) {\n break;\n }\n\n const mapped = mappedNormalizedRadius(\n midpoint,\n profile.coefficients\n );\n if (mapped < solverDistortedNormalizedRadius) {\n lower = midpoint;\n } else {\n upper = midpoint;\n }\n }\n sourceNormalizedRadius = (lower + upper) / 2;\n }\n }\n\n const radialScaleAtSource = radialScale(\n sourceNormalizedRadius,\n profile.coefficients\n );\n const sourceImagePointMm =\n distortedNormalizedRadius === 0\n ? { x: 0, y: 0 }\n : solverDistortedNormalizedRadius === distortedNormalizedRadius\n ? {\n x: distortedImagePointMm.x / radialScaleAtSource,\n y: distortedImagePointMm.y / radialScaleAtSource\n }\n : {\n x:\n distortedImagePointMm.x *\n (sourceNormalizedRadius / distortedNormalizedRadius),\n y:\n distortedImagePointMm.y *\n (sourceNormalizedRadius / distortedNormalizedRadius)\n };\n\n return {\n sourceImagePointMm,\n distortedNormalizedRadius,\n sourceNormalizedRadius,\n radialScaleAtSource\n };\n}\n\nfunction calculateInverseRadialDistortionMappingValue(\n distortedImagePointMm: LensFieldPointMm,\n profile: ValidatedRadialProfile,\n pointPath: string\n): InverseRadialDistortionMapping {\n const source = calculateInverseRadialSourcePointValue(\n distortedImagePointMm,\n profile,\n pointPath\n );\n const deltaX =\n source.sourceImagePointMm.x - distortedImagePointMm.x;\n const deltaY =\n source.sourceImagePointMm.y - distortedImagePointMm.y;\n\n return {\n direction: \"distorted-to-undistorted\",\n distortedImagePointMm: { ...distortedImagePointMm },\n sourceImagePointMm: source.sourceImagePointMm,\n distortedNormalizedRadius: source.distortedNormalizedRadius,\n sourceNormalizedRadius: source.sourceNormalizedRadius,\n radialScaleAtSource: source.radialScaleAtSource,\n deltaMm: {\n x: deltaX,\n y: deltaY,\n distance: Math.hypot(deltaX, deltaY)\n },\n profileMinimumRadialDerivative: profile.minimumRadialDerivative\n };\n}\n\n/**\n * Inverts the declared monotonic radial field mapping inside its supported range, using the existing\n * bounded root solver. Optical coordinates are kept explicit.\n * @param input - CalculateInverseRadialDistortionMappingInput. See that type for coordinates, units and profile binding.\n * @returns CalculationResult<InverseRadialDistortionMapping>. The result states its shape and scientific status; a successful call does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the root export and exact type graph.\n */\nexport function calculateInverseRadialDistortionMapping(\n input: CalculateInverseRadialDistortionMappingInput\n): CalculationResult<InverseRadialDistortionMapping> {\n requireInputKeys(input, \"calculateInverseRadialDistortionMapping\", [\"distortedImagePointMm\", \"profile\"]);\n const profile = validateRadialDistortionProfile(input.profile);\n const mapping = calculateInverseRadialDistortionMappingValue(\n input.distortedImagePointMm,\n profile,\n \"distortedImagePointMm\"\n );\n\n return approximationResult(\n mapping,\n \"generic-inverse-radial-lens-distortion\",\n \"1.0.0\",\n [\n ...mappingAssumptions(),\n `Inverse radius uses exact center/identity/boundary solutions where applicable and otherwise up to ${INVERSE_BISECTION_ITERATIONS} deterministic bisection iterations, stopping when IEEE-754 bounds can no longer narrow`\n ]\n );\n}\n\n/**\n * Inverse-maps many distorted image-plane points at once. The radial profile\n * is validated and resolved only once for the whole batch.\n *\n * Each returned mapping is numerically identical to the single-point API.\n * Provenance and profile validation are reported once for the batch.\n */\nexport function calculateInverseRadialDistortionMappings(\n input: CalculateInverseRadialDistortionMappingsInput\n): CalculationResult<InverseRadialDistortionMappings> {\n requireInputKeys(input, \"calculateInverseRadialDistortionMappings\", [\"distortedImagePointsMm\", \"profile\"]);\n const profile = validateRadialDistortionProfile(input.profile);\n if (!Array.isArray(input.distortedImagePointsMm)) {\n throw new InvalidConfigurationError(\n \"distortedImagePointsMm must be an array of image-plane points.\"\n );\n }\n\n const mappings: InverseRadialDistortionMapping[] = [];\n for (\n let index = 0;\n index < input.distortedImagePointsMm.length;\n index += 1\n ) {\n const point = input.distortedImagePointsMm[index];\n if (point === undefined) {\n throw new InvalidScientificInputError(\n `distortedImagePointsMm[${index}] must be an image-plane point.`\n );\n }\n mappings.push(\n calculateInverseRadialDistortionMappingValue(\n point,\n profile,\n `distortedImagePointsMm[${index}]`\n )\n );\n }\n\n return approximationResult(\n {\n direction: \"distorted-to-undistorted-batch\",\n mappings,\n pointCount: mappings.length,\n profileMinimumRadialDerivative: profile.minimumRadialDerivative\n },\n \"generic-inverse-radial-lens-distortion-batch\",\n \"1.0.0\",\n [\n ...mappingAssumptions(),\n `Inverse radius uses exact center/identity/boundary solutions where applicable and otherwise up to ${INVERSE_BISECTION_ITERATIONS} deterministic bisection iterations per point, stopping when IEEE-754 bounds can no longer narrow`,\n \"The radial profile is validated and its invariant extrema are resolved once for the batch\"\n ]\n );\n}\n"]}
@@ -25,6 +25,19 @@ import { type SpectralWavelengthBasis } from "../core/spectral.js";
25
25
  import { type FrontOfLensFilterProfile } from "./front-of-lens-filter.js";
26
26
  import type { IlluminationVignetting } from "./illumination-vignetting.js";
27
27
  import { type SceneRadianceEvaluationRequest, type SceneRadianceEvaluationResult } from "../schema/scene-radiance.js";
28
+ /**
29
+ * The scope fields every engine-built scene-to-sensor profile declares, in this order (shared by the capture facade and the
30
+ * generic tier presets): the bridge leaves distortion area mapping and PSF redistribution downstream and models none of the rest.
31
+ */
32
+ export declare const SCENE_TO_SENSOR_BRIDGE_SCOPE: Readonly<{
33
+ distortionAreaMappingOwnership: "not-applied-by-bridge";
34
+ psfRedistributionOwnership: "downstream-normalized-energy-redistribution";
35
+ sensorOpticalStackIncluded: false;
36
+ strayLightIncluded: false;
37
+ polarizationModeled: false;
38
+ wavelengthChangingBehaviorModeled: false;
39
+ volumetricScatteringModeled: false;
40
+ }>;
28
41
  export declare const SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION: "0.1.0";
29
42
  export type OpticalBridgeScientificStatus = "calibrated" | "approximation";
30
43
  export type OpticalBridgeUncertainty = {
@@ -23,74 +23,21 @@
23
23
  import { approximationResult } from "../core/calculation-result.js";
24
24
  import { InvalidConfigurationError } from "../core/configuration-error.js";
25
25
  import { parseEvidenceList } from "../core/evidence-provenance.js";
26
- import { parseSpectralWavelengthBasis } from "../core/spectral.js";
26
+ import {} from "../core/spectral.js";
27
27
  import { InvalidScientificInputError } from "../core/validation.js";
28
- import { composeFrontOfLensFilterTransmission } from "./front-of-lens-filter.js";
28
+ import { composeFrontOfLensFilterTransmission, parseRange, parseResolvedBasis } from "./front-of-lens-filter.js";
29
29
  import { calculateThinLensImageDistance } from "./thin-lens.js";
30
30
  import { parseSceneRadianceEvaluationRequest, parseSceneRadianceEvaluationResult } from "../schema/scene-radiance.js";
31
- import { requireInputKeys, rejectUnknownFields, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS } from "../core/record-validation.js";
31
+ import { requireInputKeys, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, parseConfigScientificStatus as parseScientificStatus, requireInputPositiveFinite as requirePositiveFiniteScientific, parseConfigLimitations as parseLimitations, requireConfigFiniteAboveZero as requirePositiveFinite } from "../core/record-validation.js";
32
+ import { requireFraction } from "../color/white-balance.js";
33
+ /**
34
+ * The scope fields every engine-built scene-to-sensor profile declares, in this order (shared by the capture facade and the
35
+ * generic tier presets): the bridge leaves distortion area mapping and PSF redistribution downstream and models none of the rest.
36
+ */
37
+ export const SCENE_TO_SENSOR_BRIDGE_SCOPE = /* @__PURE__ */ Object.freeze({ distortionAreaMappingOwnership: "not-applied-by-bridge",
38
+ psfRedistributionOwnership: "downstream-normalized-energy-redistribution", sensorOpticalStackIncluded: false, strayLightIncluded: false,
39
+ polarizationModeled: false, wavelengthChangingBehaviorModeled: false, volumetricScatteringModeled: false });
32
40
  export const SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION = "0.1.0";
33
- function requireRecord(value, path, allowedKeys) {
34
- if (typeof value !== "object" ||
35
- value === null ||
36
- Array.isArray(value)) {
37
- throw new InvalidConfigurationError(path + " must be an object.");
38
- }
39
- if (allowedKeys !== null)
40
- rejectUnknownFields(value, allowedKeys, path);
41
- return value;
42
- }
43
- function requireNonEmptyString(value, path) {
44
- if (typeof value !== "string" ||
45
- value.trim().length === 0) {
46
- throw new InvalidConfigurationError(path + " must be a non-empty string.");
47
- }
48
- return value.trim();
49
- }
50
- function requireFinite(value, path) {
51
- if (typeof value !== "number" ||
52
- !Number.isFinite(value)) {
53
- throw new InvalidConfigurationError(path + " must be finite.");
54
- }
55
- return value;
56
- }
57
- function requirePositiveFinite(value, path) {
58
- const parsed = requireFinite(value, path);
59
- if (parsed <= 0) {
60
- throw new InvalidConfigurationError(path +
61
- " must be greater than zero.");
62
- }
63
- return parsed;
64
- }
65
- function requireFraction(value, path) {
66
- const parsed = requireFinite(value, path);
67
- if (parsed < 0 ||
68
- parsed > 1) {
69
- throw new InvalidConfigurationError(path +
70
- " must be from zero through one.");
71
- }
72
- return parsed;
73
- }
74
- function parseRange(value, path) {
75
- const record = requireRecord(value, path, ["minimum", "maximum"]);
76
- const minimum = requirePositiveFinite(record.minimum, path + ".minimum");
77
- const maximum = requirePositiveFinite(record.maximum, path + ".maximum");
78
- if (minimum > maximum) {
79
- throw new InvalidConfigurationError(path +
80
- ".minimum must be less than or equal to maximum.");
81
- }
82
- return {
83
- minimum,
84
- maximum
85
- };
86
- }
87
- function parseScientificStatus(value, path) {
88
- if (value !== "calibrated" &&
89
- value !== "approximation") {
90
- throw new InvalidConfigurationError(path + " is invalid.");
91
- }
92
- return value;
93
- }
94
41
  function parseUncertainty(value, path) {
95
42
  const record = requireRecord(value, path, null);
96
43
  rejectUnknownVariantFields(record, "kind", UNCERTAINTY_VARIANT_FIELDS, path);
@@ -110,21 +57,6 @@ function parseUncertainty(value, path) {
110
57
  }
111
58
  throw new InvalidConfigurationError(path + ".kind is invalid.");
112
59
  }
113
- function parseLimitations(value, path) {
114
- if (!Array.isArray(value)) {
115
- throw new InvalidConfigurationError(path + " must be an array.");
116
- }
117
- const parsed = value.map((entry, index) => requireNonEmptyString(entry, path +
118
- "[" +
119
- index +
120
- "]"));
121
- if (new Set(parsed).size !==
122
- parsed.length) {
123
- throw new InvalidConfigurationError(path +
124
- " must not contain duplicates.");
125
- }
126
- return parsed;
127
- }
128
60
  function parseReusableNumericEvidence(value, path) {
129
61
  const evidence = parseEvidenceList(value, path);
130
62
  if (evidence.some((entry) => entry.reuseStatus ===
@@ -238,14 +170,6 @@ function parseTransmission(value, path) {
238
170
  }
239
171
  throw new InvalidConfigurationError(path + ".kind is invalid.");
240
172
  }
241
- function parseResolvedBasis(value, path) {
242
- const parsed = parseSpectralWavelengthBasis(value, path);
243
- if (parsed === "unspecified") {
244
- throw new InvalidConfigurationError(path +
245
- ' must resolve to "air" or "vacuum".');
246
- }
247
- return parsed;
248
- }
249
173
  function parseFocusApplicability(value, path) {
250
174
  const record = requireRecord(value, path, null);
251
175
  rejectUnknownVariantFields(record, "kind", { any: ["kind"], "finite-distance-range": ["kind", "objectDistanceM"] }, path);
@@ -381,15 +305,6 @@ function workingFNumber(focus, focalLengthMm, nominalFNumber) {
381
305
  }
382
306
  throw new InvalidConfigurationError("focus.kind is invalid.");
383
307
  }
384
- function requirePositiveFiniteScientific(value, path) {
385
- if (typeof value !== "number" ||
386
- !Number.isFinite(value) ||
387
- value <= 0) {
388
- throw new InvalidScientificInputError(path +
389
- " must be finite and greater than zero.");
390
- }
391
- return value;
392
- }
393
308
  function finiteFocusDistance(focus) {
394
309
  if (focus.kind ===
395
310
  "ideal-symmetric-thin-lens") {