@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":"readout-timing.js","sourceRoot":"","sources":["../../src/sensor/readout-timing.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,mBAAmB,EAA0B,MAAM,+BAA+B,CAAC;AAC5F,OAAO,EACL,iBAAiB,EAElB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAI5F,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACzG,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AA2H3E,MAAM,aAAa,GAAG,IAAI,GAAG,CAAoB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;AACxE,MAAM,eAAe,GAAG,IAAI,GAAG,CAAmC;IAChE,eAAe;IACf,eAAe;IACf,eAAe;IACf,eAAe;CAChB,CAAC,CAAC;AACH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAA0B;IAC1D,YAAY;IACZ,0BAA0B;IAC1B,YAAY;CACb,CAAC,CAAC;AAEH,SAAS,aAAa,CACpB,KAAc,EACd,IAAY,EACZ,WAAqC;IAErC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,qBAAqB,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,WAAW,KAAK,IAAI;QAAE,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;IACxE,OAAO,KAAsB,CAAC;AAChC,CAAC;AAED,SAAS,yBAAyB,CAAC,IAAY,EAAE,KAAa;IAC5D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,2BAA2B,CACnC,IAAI,GAAG,uCAAuC,CAC/C,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,wBAAwB,CAAC,IAAY,EAAE,KAAc;IAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,2BAA2B,CACnC,IAAI,GAAG,yDAAyD,CACjE,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAY,EAAE,KAAc;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,2BAA2B,CACnC,IAAI,GAAG,6CAA6C,CACrD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,gBAAgB,CACvB,KAAc,EACd,IAAY,EACZ,QAAiB;IAEjB,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;IACzE,IAAI,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE,CAAC;QACxB,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,oBAAoB,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ;QACtB,CAAC,CAAC,qBAAqB,CAAC,IAAI,GAAG,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC;QACtD,CAAC,CAAC,wBAAwB,CAAC,IAAI,GAAG,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IAE5D,OAAO;QACL,KAAK,EAAE,OAAO;QACd,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,GAAG,WAAW,CAAC;KACjE,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CACzB,KAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;IACjE,IACE,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;QAChC,CAAC,eAAe,CAAC,GAAG,CAClB,MAAM,CAAC,KAAyC,CACjD,EACD,CAAC;QACD,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,oBAAoB,CAAC,CAAC;IACnE,CAAC;IAED,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAyC;QACvD,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,GAAG,WAAW,CAAC;KACjE,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAC9B,KAAc;IAEd,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,aAAa,EAAE,+BAA+B,EAAE,qBAAqB,EAAE,4BAA4B,CAAC,CAAC,CAAC;IACtJ,IACE,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ;QACtC,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,WAAgC,CAAC,EAC3D,CAAC;QACD,MAAM,IAAI,yBAAyB,CAAC,iCAAiC,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,6BAA6B,GAAG,gBAAgB,CACpD,MAAM,CAAC,6BAA6B,EACpC,uCAAuC,EACvC,KAAK,CACN,CAAC;IAEF,IAAI,MAAM,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QACpC,IAAI,MAAM,CAAC,mBAAmB,KAAK,SAAS,EAAE,CAAC;YAC7C,MAAM,IAAI,2BAA2B,CACnC,iEAAiE,CAClE,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,0BAA0B,KAAK,SAAS,EAAE,CAAC;YACpD,MAAM,IAAI,2BAA2B,CACnC,wEAAwE,CACzE,CAAC;QACJ,CAAC;QAED,OAAO;YACL,WAAW,EAAE,QAAQ;YACrB,6BAA6B;SAC9B,CAAC;IACJ,CAAC;IAED,OAAO;QACL,WAAW,EAAE,SAAS;QACtB,6BAA6B;QAC7B,mBAAmB,EAAE,kBAAkB,CACrC,MAAM,CAAC,mBAAmB,EAC1B,6BAA6B,CAC9B;QACD,0BAA0B,EAAE,gBAAgB,CAC1C,MAAM,CAAC,0BAA0B,EACjC,oCAAoC,EACpC,IAAI,CACL;KACF,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,MAAyB;IACrD,kBAAkB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC3C,sBAAsB,CAAC,yBAAyB,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACrE,sBAAsB,CAAC,0BAA0B,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,QAAQ,CAAC,MAAyB;IACzC,OAAO;QACL,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,KAAK,EAAE,MAAM,CAAC,UAAU;QACxB,MAAM,EAAE,MAAM,CAAC,WAAW;KAC3B,CAAC;AACJ,CAAC;AAED,SAAS,yBAAyB,CAChC,IAAgB,EAChB,MAAyB;IAEzB,yBAAyB,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACzD,yBAAyB,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACzD,sBAAsB,CAAC,yBAAyB,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9D,sBAAsB,CAAC,0BAA0B,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAEhE,IACE,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,UAAU;QACvC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,WAAW,EACzC,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,0DAA0D,CAC3D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,wBAAwB,CAC/B,KAAc;IAEd,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,kBAAkB,CAAC,GAAG,CAAC,KAAgC,CAAC,EACzD,CAAC;QACD,MAAM,IAAI,yBAAyB,CAAC,8BAA8B,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED,SAAS,gCAAgC,CACvC,KAAkB,EAClB,IAAgB,EAChB,KAAa;IAEb,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,2BAA2B,CACnC,qBAAqB,GAAG,KAAK,GAAG,+BAA+B,CAChE,CAAC;IACJ,CAAC;IAED,IACE,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAChB,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK;QAC7B,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAChB,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,EAC9B,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,qBAAqB;YACnB,KAAK;YACL,6DAA6D,CAChE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CACjB,SAA2C;IAE3C,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,eAAe;YAClB,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,KAAK,eAAe;YAClB,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACzB,KAAK,eAAe;YAClB,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,KAAK,eAAe;YAClB,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB,CAC7B,KAAkB,EAClB,IAAgB,EAChB,SAA2C;IAE3C,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,eAAe;YAClB,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1C,KAAK,eAAe;YAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;QAC9C,KAAK,eAAe;YAClB,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACzC,KAAK,eAAe;YAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;IAC/C,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,4BAA4B,CAC1C,KAAwC;IAExC,gBAAgB,CAAC,KAAK,EAAE,8BAA8B,EAAE,CAAC,cAAc,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,SAAS,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACpJ,oBAAoB,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACzC,wBAAwB,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAEjD,MAAM,OAAO,GAAG,uBAAuB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvD,MAAM,iBAAiB,GACrB,KAAK,CAAC,iBAAiB,IAAI,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC1D,yBAAyB,CAAC,iBAAiB,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAEjE,IACE,KAAK,CAAC,kBAAkB,KAAK,SAAS;QACtC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,EACxC,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,oDAAoD,CACrD,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GACR,OAAO,CAAC,WAAW,KAAK,SAAS;QAC/B,CAAC,CAAC;YACE,OAAO,EAAE,4BAAqC;YAC9C,eAAe,EAAE,OAAO,CAAC,mBAAmB;YAC5C,gBAAgB,EAAE,UAAU,CAAC,OAAO,CAAC,mBAAmB,CAAC,KAAK,CAAC;YAC/D,0BAA0B,EAAE,OAAO,CAAC,0BAA0B;SAC/D;QACH,CAAC,CAAC,IAAI,CAAC;IAEX,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACpE,gCAAgC,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,CAAC,CAAC;QAElE,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,OAAO;gBACL,WAAW,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE;gBACvC,sBAAsB,EAAE,IAAI;gBAC5B,yBAAyB,EAAE,CAAC;aAC7B,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAG,sBAAsB,CACvC,KAAK,EACL,iBAAiB,EACjB,IAAI,CAAC,eAAe,CAAC,KAAK,CAC3B,CAAC;QAEF,OAAO;YACL,WAAW,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE;YACvC,sBAAsB,EAAE,UAAU;YAClC,yBAAyB,EACvB,UAAU,GAAG,IAAI,CAAC,0BAA0B,CAAC,KAAK;SACrD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,mBAAmB,CACxB;QACE,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,YAAY,EAAE,EAAE,GAAG,KAAK,CAAC,YAAY,EAAE;QACvC,iBAAiB,EAAE,EAAE,GAAG,iBAAiB,EAAE;QAC3C,6BAA6B,EAC3B,OAAO,CAAC,6BAA6B;QACvC,iCAAiC,EAC/B,OAAO,CAAC,WAAW,KAAK,SAAS;YAC/B,CAAC,CAAC,OAAO,CAAC,0BAA0B,CAAC,KAAK;YAC1C,CAAC,CAAC,CAAC;QACP,IAAI;QACJ,OAAO;KACR,EACD,mCAAmC,EACnC,OAAO,EACP;QACE,6HAA6H;QAC7H,qHAAqH;QACrH,yKAAyK;QACzK,0JAA0J;QAC1J,kHAAkH;QAClH,+GAA+G;QAC/G,kHAAkH;QAClH,6GAA6G;QAC7G,mHAAmH;QACnH,0KAA0K;KAC3K,CACF,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Sensor readout timing: when each position on the sensor is read out during one capture.\n *\n * Rolling readout is modelled as a uniform linear scan along one axis of the declared active capture.\n * The caller supplies the scan's time span (skew) explicitly. It is never worked out from the total\n * data-readout time, the crop size, pixel density, output resolution, sensor-architecture metadata or\n * the shutter mechanism. Global readout has no skew in this model, even when a non-zero data-readout\n * time is declared.\n *\n * Not covered here: local exposure start and end times, mechanical curtain travel, EFCS (electronic\n * first-curtain shutter) behaviour, rolling-shutter image distortion, flash or flicker, and motion\n * integration.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError, requirePositiveInteger } from \"../core/validation.js\";\nimport type { RasterPoint, RasterRect } from \"../output/capture-geometry.js\";\nimport type { NativeImageRaster } from \"./sensor-geometry.js\";\nimport type { SensorReadoutArchitecture } from \"./architecture.js\";\nimport { requireInputObject, requireInputKeys, rejectUnknownFields } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\n/**\n * The readout mode chosen for one capture.\n *\n * It uses the same names as the architecture metadata, but does not claim the\n * hardware supports it. SensorArchitectureProfile.readoutCapabilities is\n * descriptive only and is not checked automatically.\n */\nexport type SensorReadoutMode = SensorReadoutArchitecture;\n\nexport type NativeSensorReadoutScanDirection =\n | \"top-to-bottom\"\n | \"bottom-to-top\"\n | \"left-to-right\"\n | \"right-to-left\";\n\nexport type CaptureShutterMechanism =\n | \"mechanical\"\n | \"electronic-first-curtain\"\n | \"electronic\";\n\n/**\n * Evidence-backed timing value with an explicit seconds unit.\n */\nexport interface SourcedSensorTimingSeconds\n extends EvidenceBackedFact<number> {\n unit: \"s\";\n}\n\nexport type SourcedSensorReadoutFact<T> = EvidenceBackedFact<T>;\n\nexport interface GlobalSensorReadoutTimingDeclaration {\n readoutMode: \"global\";\n /**\n * Sensor data-readout time for this capture.\n *\n * It is deliberately not treated as the spatial exposure or readout skew.\n */\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n scanDirectionNative?: never;\n spatialSamplingSkewSeconds?: never;\n}\n\nexport interface RollingSensorReadoutTimingDeclaration {\n readoutMode: \"rolling\";\n /**\n * Sensor data-readout time for this capture.\n *\n * It is kept separate from the spatial skew, because a published readout time\n * and the scan's time span are not necessarily the same quantity.\n */\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n /**\n * Native-sensor scan direction for the selected capture mode.\n */\n scanDirectionNative: SourcedSensorReadoutFact<NativeSensorReadoutScanDirection>;\n /**\n * Time between the first and last scan positions, in the uniform linear\n * timing approximation (the readout skew).\n */\n spatialSamplingSkewSeconds: SourcedSensorTimingSeconds;\n}\n\nexport type SensorReadoutTimingDeclaration =\n | GlobalSensorReadoutTimingDeclaration\n | RollingSensorReadoutTimingDeclaration;\n\nexport interface CalculateSensorReadoutTimingInput {\n nativeRaster: NativeImageRaster;\n /**\n * Active capture rectangle, in fixed native pixel coordinates.\n *\n * Defaults to the whole native image. Timing is never worked out by scaling\n * a full-frame value to this rectangle.\n */\n activeCaptureRect?: RasterRect;\n /**\n * The shutter mechanism chosen for the capture.\n *\n * It is reported separately and does not change the sensor readout timing\n * in this model.\n */\n shutterMechanism: CaptureShutterMechanism;\n readout: SensorReadoutTimingDeclaration;\n /**\n * Optional points, in continuous native pixel coordinates measured from pixel\n * edges, to report timing for. A pixel centre is at +0.5.\n */\n samplePointsNative?: readonly RasterPoint[];\n}\n\nexport interface SensorReadoutTimingSample {\n pointNative: RasterPoint;\n normalizedScanPosition: number | null;\n /**\n * Readout time of this point, in seconds, relative to the first scan position (0).\n *\n * It is not automatically an exposure-start time or a shutter-curtain time.\n */\n readoutPhaseOffsetSeconds: number;\n}\n\nexport interface SensorReadoutTiming {\n readoutMode: SensorReadoutMode;\n shutterMechanism: CaptureShutterMechanism;\n nativeRaster: NativeImageRaster;\n activeCaptureRect: RasterRect;\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n maximumSpatialSamplingSkewSeconds: number;\n scan:\n | {\n pattern: \"uniform-linear-single-axis\";\n directionNative: SourcedSensorReadoutFact<NativeSensorReadoutScanDirection>;\n unitVectorNative: { x: number; y: number };\n spatialSamplingSkewSeconds: SourcedSensorTimingSeconds;\n }\n | null;\n samples: readonly SensorReadoutTimingSample[];\n}\n\ntype UnknownRecord = Record<string, unknown>;\n\nconst READOUT_MODES = new Set<SensorReadoutMode>([\"rolling\", \"global\"]);\nconst SCAN_DIRECTIONS = new Set<NativeSensorReadoutScanDirection>([\n \"top-to-bottom\",\n \"bottom-to-top\",\n \"left-to-right\",\n \"right-to-left\"\n]);\nconst SHUTTER_MECHANISMS = new Set<CaptureShutterMechanism>([\n \"mechanical\",\n \"electronic-first-curtain\",\n \"electronic\"\n]);\n\nfunction requireRecord(\n value: unknown,\n path: string,\n allowedKeys: readonly string[] | null\n): UnknownRecord {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new InvalidConfigurationError(path + \" must be an object.\");\n }\n if (allowedKeys !== null) rejectUnknownFields(value, allowedKeys, path);\n return value as UnknownRecord;\n}\n\nfunction requireNonNegativeInteger(name: string, value: number): void {\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new InvalidScientificInputError(\n name + \" must be a non-negative safe integer.\"\n );\n }\n}\n\nfunction requireNonNegativeFinite(name: string, value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n throw new InvalidScientificInputError(\n name + \" must be a finite number greater than or equal to zero.\"\n );\n }\n return value;\n}\n\nfunction requirePositiveFinite(name: string, value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) {\n throw new InvalidScientificInputError(\n name + \" must be a finite number greater than zero.\"\n );\n }\n return value;\n}\n\nfunction parseSecondsFact(\n value: unknown,\n path: string,\n positive: boolean\n): SourcedSensorTimingSeconds {\n const record = requireRecord(value, path, [\"unit\", \"value\", \"evidence\"]);\n if (record.unit !== \"s\") {\n throw new InvalidConfigurationError(path + '.unit must be \"s\".');\n }\n\n const seconds = positive\n ? requirePositiveFinite(path + \".value\", record.value)\n : requireNonNegativeFinite(path + \".value\", record.value);\n\n return {\n value: seconds,\n unit: \"s\",\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseDirectionFact(\n value: unknown,\n path: string\n): SourcedSensorReadoutFact<NativeSensorReadoutScanDirection> {\n const record = requireRecord(value, path, [\"value\", \"evidence\"]);\n if (\n typeof record.value !== \"string\" ||\n !SCAN_DIRECTIONS.has(\n record.value as NativeSensorReadoutScanDirection\n )\n ) {\n throw new InvalidConfigurationError(path + \".value is invalid.\");\n }\n\n return {\n value: record.value as NativeSensorReadoutScanDirection,\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseReadoutDeclaration(\n value: unknown\n): SensorReadoutTimingDeclaration {\n const record = requireRecord(value, \"readout\", [\"readoutMode\", \"captureReadoutDurationSeconds\", \"scanDirectionNative\", \"spatialSamplingSkewSeconds\"]);\n if (\n typeof record.readoutMode !== \"string\" ||\n !READOUT_MODES.has(record.readoutMode as SensorReadoutMode)\n ) {\n throw new InvalidConfigurationError(\"readout.readoutMode is invalid.\");\n }\n\n const captureReadoutDurationSeconds = parseSecondsFact(\n record.captureReadoutDurationSeconds,\n \"readout.captureReadoutDurationSeconds\",\n false\n );\n\n if (record.readoutMode === \"global\") {\n if (record.scanDirectionNative !== undefined) {\n throw new InvalidScientificInputError(\n \"readout.scanDirectionNative must be omitted for global readout.\"\n );\n }\n if (record.spatialSamplingSkewSeconds !== undefined) {\n throw new InvalidScientificInputError(\n \"readout.spatialSamplingSkewSeconds must be omitted for global readout.\"\n );\n }\n\n return {\n readoutMode: \"global\",\n captureReadoutDurationSeconds\n };\n }\n\n return {\n readoutMode: \"rolling\",\n captureReadoutDurationSeconds,\n scanDirectionNative: parseDirectionFact(\n record.scanDirectionNative,\n \"readout.scanDirectionNative\"\n ),\n spatialSamplingSkewSeconds: parseSecondsFact(\n record.spatialSamplingSkewSeconds,\n \"readout.spatialSamplingSkewSeconds\",\n true\n )\n };\n}\n\nfunction validateNativeRaster(raster: NativeImageRaster): void {\n requireInputObject(raster, \"raster input\");\n requirePositiveInteger(\"nativeRaster.pixelWidth\", raster.pixelWidth);\n requirePositiveInteger(\"nativeRaster.pixelHeight\", raster.pixelHeight);\n}\n\nfunction fullRect(raster: NativeImageRaster): RasterRect {\n return {\n x: 0,\n y: 0,\n width: raster.pixelWidth,\n height: raster.pixelHeight\n };\n}\n\nfunction validateActiveCaptureRect(\n rect: RasterRect,\n raster: NativeImageRaster\n): void {\n requireNonNegativeInteger(\"activeCaptureRect.x\", rect.x);\n requireNonNegativeInteger(\"activeCaptureRect.y\", rect.y);\n requirePositiveInteger(\"activeCaptureRect.width\", rect.width);\n requirePositiveInteger(\"activeCaptureRect.height\", rect.height);\n\n if (\n rect.x + rect.width > raster.pixelWidth ||\n rect.y + rect.height > raster.pixelHeight\n ) {\n throw new InvalidScientificInputError(\n \"activeCaptureRect must fit entirely within nativeRaster.\"\n );\n }\n}\n\nfunction validateShutterMechanism(\n value: unknown\n): asserts value is CaptureShutterMechanism {\n if (\n typeof value !== \"string\" ||\n !SHUTTER_MECHANISMS.has(value as CaptureShutterMechanism)\n ) {\n throw new InvalidConfigurationError(\"shutterMechanism is invalid.\");\n }\n}\n\nfunction validatePointWithinActiveCapture(\n point: RasterPoint,\n rect: RasterRect,\n index: number\n): void {\n if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {\n throw new InvalidScientificInputError(\n \"samplePointsNative[\" + index + \"] coordinates must be finite.\"\n );\n }\n\n if (\n point.x < rect.x ||\n point.x > rect.x + rect.width ||\n point.y < rect.y ||\n point.y > rect.y + rect.height\n ) {\n throw new InvalidScientificInputError(\n \"samplePointsNative[\" +\n index +\n \"] must lie within activeCaptureRect edge-coordinate bounds.\"\n );\n }\n}\n\nfunction scanVector(\n direction: NativeSensorReadoutScanDirection\n): { x: number; y: number } {\n switch (direction) {\n case \"top-to-bottom\":\n return { x: 0, y: 1 };\n case \"bottom-to-top\":\n return { x: 0, y: -1 };\n case \"left-to-right\":\n return { x: 1, y: 0 };\n case \"right-to-left\":\n return { x: -1, y: 0 };\n }\n}\n\nfunction normalizedScanPosition(\n point: RasterPoint,\n rect: RasterRect,\n direction: NativeSensorReadoutScanDirection\n): number {\n switch (direction) {\n case \"top-to-bottom\":\n return (point.y - rect.y) / rect.height;\n case \"bottom-to-top\":\n return 1 - (point.y - rect.y) / rect.height;\n case \"left-to-right\":\n return (point.x - rect.x) / rect.width;\n case \"right-to-left\":\n return 1 - (point.x - rect.x) / rect.width;\n }\n}\n\n/**\n * Resolves the sensor readout scan schedule for one capture.\n *\n * Rolling readout is modelled as a uniform linear scan along one axis of the\n * declared active capture. The caller supplies the scan's time span (skew)\n * explicitly. It is never worked out from the total data-readout time, crop\n * size, pixel density, output resolution, sensor-architecture metadata or the\n * shutter mechanism.\n *\n * Global readout has no skew in this model, even when a non-zero data-readout\n * time is declared.\n *\n * Not covered: local exposure start and end times, mechanical curtain travel,\n * EFCS (electronic first-curtain shutter) behaviour, rolling-shutter image\n * distortion, flash or flicker, and motion integration.\n */\nexport function calculateSensorReadoutTiming(\n input: CalculateSensorReadoutTimingInput\n): CalculationResult<SensorReadoutTiming> {\n requireInputKeys(input, \"calculateSensorReadoutTiming\", [\"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"readout\", \"samplePointsNative\"]);\n validateNativeRaster(input.nativeRaster);\n validateShutterMechanism(input.shutterMechanism);\n\n const readout = parseReadoutDeclaration(input.readout);\n const activeCaptureRect =\n input.activeCaptureRect ?? fullRect(input.nativeRaster);\n validateActiveCaptureRect(activeCaptureRect, input.nativeRaster);\n\n if (\n input.samplePointsNative !== undefined &&\n !Array.isArray(input.samplePointsNative)\n ) {\n throw new InvalidConfigurationError(\n \"samplePointsNative must be an array when supplied.\"\n );\n }\n\n const scan =\n readout.readoutMode === \"rolling\"\n ? {\n pattern: \"uniform-linear-single-axis\" as const,\n directionNative: readout.scanDirectionNative,\n unitVectorNative: scanVector(readout.scanDirectionNative.value),\n spatialSamplingSkewSeconds: readout.spatialSamplingSkewSeconds\n }\n : null;\n\n const samples = (input.samplePointsNative ?? []).map((point, index) => {\n validatePointWithinActiveCapture(point, activeCaptureRect, index);\n\n if (scan === null) {\n return {\n pointNative: { x: point.x, y: point.y },\n normalizedScanPosition: null,\n readoutPhaseOffsetSeconds: 0\n };\n }\n\n const normalized = normalizedScanPosition(\n point,\n activeCaptureRect,\n scan.directionNative.value\n );\n\n return {\n pointNative: { x: point.x, y: point.y },\n normalizedScanPosition: normalized,\n readoutPhaseOffsetSeconds:\n normalized * scan.spatialSamplingSkewSeconds.value\n };\n });\n\n return approximationResult(\n {\n readoutMode: readout.readoutMode,\n shutterMechanism: input.shutterMechanism,\n nativeRaster: { ...input.nativeRaster },\n activeCaptureRect: { ...activeCaptureRect },\n captureReadoutDurationSeconds:\n readout.captureReadoutDurationSeconds,\n maximumSpatialSamplingSkewSeconds:\n readout.readoutMode === \"rolling\"\n ? readout.spatialSamplingSkewSeconds.value\n : 0,\n scan,\n samples\n },\n \"native-sensor-readout-scan-timing\",\n \"1.0.0\",\n [\n \"Native raster coordinates use top-left origin, +X right and +Y down and remain invariant under physical camera orientation.\",\n \"Rolling timing uses a uniform-linear single-axis approximation over continuous effective native-raster coordinates.\",\n \"NativeImageRaster is an effective image-sampling grid; the timing map does not assert one raster row or column equals one physical photodiode or hardware readout line.\",\n \"Capture data-readout duration and spatial sampling skew are independently declared facts; no equality or ordering relationship is inferred between them.\",\n \"Global readout has zero spatial sampling skew in this model even when capture data-readout duration is non-zero.\",\n \"Active-capture timing is caller-declared and is not inferred by scaling full-frame timing by crop dimensions.\",\n \"Physical orientation and digital output crop/resolution are downstream of this invariant native timing schedule.\",\n \"Shutter mechanism is reported independently and does not alter the sensor scan schedule in this foundation.\",\n \"The model does not represent segmented, center-out, interleaved, multi-tap or other non-uniform readout patterns.\",\n \"The model does not define exposure windows, shutter-curtain travel, rolling-shutter geometry distortion, flash/flicker interaction or camera/subject motion integration.\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"readout-timing.js","sourceRoot":"","sources":["../../src/sensor/readout-timing.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Sensor readout timing: when each position on the sensor is read out during one capture.\n *\n * Rolling readout is modelled as a uniform linear scan along one axis of the declared active capture.\n * The caller supplies the scan's time span (skew) explicitly. It is never worked out from the total\n * data-readout time, the crop size, pixel density, output resolution, sensor-architecture metadata or\n * the shutter mechanism. Global readout has no skew in this model, even when a non-zero data-readout\n * time is declared.\n *\n * Not covered here: local exposure start and end times, mechanical curtain travel, EFCS (electronic\n * first-curtain shutter) behaviour, rolling-shutter image distortion, flash or flicker, and motion\n * integration.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type { RasterPoint, RasterRect } from \"../output/capture-geometry.js\";\nimport type { NativeImageRaster } from \"./sensor-geometry.js\";\nimport type { SensorReadoutArchitecture } from \"./architecture.js\";\nimport { requireInputKeys, requireConfigRecord as requireRecord } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { fullRect } from \"../core/shared-helpers.js\";\nimport { normalizedScanPosition, validateNativeRaster, scanVector, requirePositiveFinite, validateActiveCaptureRect, validateShutterMechanism } from \"./exposure-window.js\";\n\n/**\n * The readout mode chosen for one capture.\n *\n * It uses the same names as the architecture metadata, but does not claim the\n * hardware supports it. SensorArchitectureProfile.readoutCapabilities is\n * descriptive only and is not checked automatically.\n */\nexport type SensorReadoutMode = SensorReadoutArchitecture;\n\nexport type NativeSensorReadoutScanDirection =\n | \"top-to-bottom\"\n | \"bottom-to-top\"\n | \"left-to-right\"\n | \"right-to-left\";\n\nexport type CaptureShutterMechanism =\n | \"mechanical\"\n | \"electronic-first-curtain\"\n | \"electronic\";\n\n/**\n * Evidence-backed timing value with an explicit seconds unit.\n */\nexport interface SourcedSensorTimingSeconds\n extends EvidenceBackedFact<number> {\n unit: \"s\";\n}\n\nexport type SourcedSensorReadoutFact<T> = EvidenceBackedFact<T>;\n\nexport interface GlobalSensorReadoutTimingDeclaration {\n readoutMode: \"global\";\n /**\n * Sensor data-readout time for this capture.\n *\n * It is deliberately not treated as the spatial exposure or readout skew.\n */\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n scanDirectionNative?: never;\n spatialSamplingSkewSeconds?: never;\n}\n\nexport interface RollingSensorReadoutTimingDeclaration {\n readoutMode: \"rolling\";\n /**\n * Sensor data-readout time for this capture.\n *\n * It is kept separate from the spatial skew, because a published readout time\n * and the scan's time span are not necessarily the same quantity.\n */\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n /**\n * Native-sensor scan direction for the selected capture mode.\n */\n scanDirectionNative: SourcedSensorReadoutFact<NativeSensorReadoutScanDirection>;\n /**\n * Time between the first and last scan positions, in the uniform linear\n * timing approximation (the readout skew).\n */\n spatialSamplingSkewSeconds: SourcedSensorTimingSeconds;\n}\n\nexport type SensorReadoutTimingDeclaration =\n | GlobalSensorReadoutTimingDeclaration\n | RollingSensorReadoutTimingDeclaration;\n\nexport interface CalculateSensorReadoutTimingInput {\n nativeRaster: NativeImageRaster;\n /**\n * Active capture rectangle, in fixed native pixel coordinates.\n *\n * Defaults to the whole native image. Timing is never worked out by scaling\n * a full-frame value to this rectangle.\n */\n activeCaptureRect?: RasterRect;\n /**\n * The shutter mechanism chosen for the capture.\n *\n * It is reported separately and does not change the sensor readout timing\n * in this model.\n */\n shutterMechanism: CaptureShutterMechanism;\n readout: SensorReadoutTimingDeclaration;\n /**\n * Optional points, in continuous native pixel coordinates measured from pixel\n * edges, to report timing for. A pixel centre is at +0.5.\n */\n samplePointsNative?: readonly RasterPoint[];\n}\n\nexport interface SensorReadoutTimingSample {\n pointNative: RasterPoint;\n normalizedScanPosition: number | null;\n /**\n * Readout time of this point, in seconds, relative to the first scan position (0).\n *\n * It is not automatically an exposure-start time or a shutter-curtain time.\n */\n readoutPhaseOffsetSeconds: number;\n}\n\nexport interface SensorReadoutTiming {\n readoutMode: SensorReadoutMode;\n shutterMechanism: CaptureShutterMechanism;\n nativeRaster: NativeImageRaster;\n activeCaptureRect: RasterRect;\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n maximumSpatialSamplingSkewSeconds: number;\n scan:\n | {\n pattern: \"uniform-linear-single-axis\";\n directionNative: SourcedSensorReadoutFact<NativeSensorReadoutScanDirection>;\n unitVectorNative: { x: number; y: number };\n spatialSamplingSkewSeconds: SourcedSensorTimingSeconds;\n }\n | null;\n samples: readonly SensorReadoutTimingSample[];\n}\n\nconst READOUT_MODES = new Set<SensorReadoutMode>([\"rolling\", \"global\"]);\nconst SCAN_DIRECTIONS = new Set<NativeSensorReadoutScanDirection>([\n \"top-to-bottom\",\n \"bottom-to-top\",\n \"left-to-right\",\n \"right-to-left\"\n]);\n\nfunction requireNonNegativeFinite(name: string, value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n throw new InvalidScientificInputError(\n name + \" must be a finite number greater than or equal to zero.\"\n );\n }\n return value;\n}\n\nfunction parseSecondsFact(\n value: unknown,\n path: string,\n positive: boolean\n): SourcedSensorTimingSeconds {\n const record = requireRecord(value, path, [\"unit\", \"value\", \"evidence\"]);\n if (record.unit !== \"s\") {\n throw new InvalidConfigurationError(path + '.unit must be \"s\".');\n }\n\n const seconds = positive\n ? requirePositiveFinite(path + \".value\", record.value)\n : requireNonNegativeFinite(path + \".value\", record.value);\n\n return {\n value: seconds,\n unit: \"s\",\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseDirectionFact(\n value: unknown,\n path: string\n): SourcedSensorReadoutFact<NativeSensorReadoutScanDirection> {\n const record = requireRecord(value, path, [\"value\", \"evidence\"]);\n if (\n typeof record.value !== \"string\" ||\n !SCAN_DIRECTIONS.has(\n record.value as NativeSensorReadoutScanDirection\n )\n ) {\n throw new InvalidConfigurationError(path + \".value is invalid.\");\n }\n\n return {\n value: record.value as NativeSensorReadoutScanDirection,\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseReadoutDeclaration(\n value: unknown\n): SensorReadoutTimingDeclaration {\n const record = requireRecord(value, \"readout\", [\"readoutMode\", \"captureReadoutDurationSeconds\", \"scanDirectionNative\", \"spatialSamplingSkewSeconds\"]);\n if (\n typeof record.readoutMode !== \"string\" ||\n !READOUT_MODES.has(record.readoutMode as SensorReadoutMode)\n ) {\n throw new InvalidConfigurationError(\"readout.readoutMode is invalid.\");\n }\n\n const captureReadoutDurationSeconds = parseSecondsFact(\n record.captureReadoutDurationSeconds,\n \"readout.captureReadoutDurationSeconds\",\n false\n );\n\n if (record.readoutMode === \"global\") {\n if (record.scanDirectionNative !== undefined) {\n throw new InvalidScientificInputError(\n \"readout.scanDirectionNative must be omitted for global readout.\"\n );\n }\n if (record.spatialSamplingSkewSeconds !== undefined) {\n throw new InvalidScientificInputError(\n \"readout.spatialSamplingSkewSeconds must be omitted for global readout.\"\n );\n }\n\n return {\n readoutMode: \"global\",\n captureReadoutDurationSeconds\n };\n }\n\n return {\n readoutMode: \"rolling\",\n captureReadoutDurationSeconds,\n scanDirectionNative: parseDirectionFact(\n record.scanDirectionNative,\n \"readout.scanDirectionNative\"\n ),\n spatialSamplingSkewSeconds: parseSecondsFact(\n record.spatialSamplingSkewSeconds,\n \"readout.spatialSamplingSkewSeconds\",\n true\n )\n };\n}\n\n\n\nfunction validatePointWithinActiveCapture(\n point: RasterPoint,\n rect: RasterRect,\n index: number\n): void {\n if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {\n throw new InvalidScientificInputError(\n \"samplePointsNative[\" + index + \"] coordinates must be finite.\"\n );\n }\n\n if (\n point.x < rect.x ||\n point.x > rect.x + rect.width ||\n point.y < rect.y ||\n point.y > rect.y + rect.height\n ) {\n throw new InvalidScientificInputError(\n \"samplePointsNative[\" +\n index +\n \"] must lie within activeCaptureRect edge-coordinate bounds.\"\n );\n }\n}\n\n/**\n * Resolves the sensor readout scan schedule for one capture.\n *\n * Rolling readout is modelled as a uniform linear scan along one axis of the\n * declared active capture. The caller supplies the scan's time span (skew)\n * explicitly. It is never worked out from the total data-readout time, crop\n * size, pixel density, output resolution, sensor-architecture metadata or the\n * shutter mechanism.\n *\n * Global readout has no skew in this model, even when a non-zero data-readout\n * time is declared.\n *\n * Not covered: local exposure start and end times, mechanical curtain travel,\n * EFCS (electronic first-curtain shutter) behaviour, rolling-shutter image\n * distortion, flash or flicker, and motion integration.\n */\nexport function calculateSensorReadoutTiming(\n input: CalculateSensorReadoutTimingInput\n): CalculationResult<SensorReadoutTiming> {\n requireInputKeys(input, \"calculateSensorReadoutTiming\", [\"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"readout\", \"samplePointsNative\"]);\n validateNativeRaster(input.nativeRaster);\n validateShutterMechanism(input.shutterMechanism);\n\n const readout = parseReadoutDeclaration(input.readout);\n const activeCaptureRect =\n input.activeCaptureRect ?? fullRect(input.nativeRaster);\n validateActiveCaptureRect(activeCaptureRect, input.nativeRaster);\n\n if (\n input.samplePointsNative !== undefined &&\n !Array.isArray(input.samplePointsNative)\n ) {\n throw new InvalidConfigurationError(\n \"samplePointsNative must be an array when supplied.\"\n );\n }\n\n const scan =\n readout.readoutMode === \"rolling\"\n ? {\n pattern: \"uniform-linear-single-axis\" as const,\n directionNative: readout.scanDirectionNative,\n unitVectorNative: scanVector(readout.scanDirectionNative.value),\n spatialSamplingSkewSeconds: readout.spatialSamplingSkewSeconds\n }\n : null;\n\n const samples = (input.samplePointsNative ?? []).map((point, index) => {\n validatePointWithinActiveCapture(point, activeCaptureRect, index);\n\n if (scan === null) {\n return {\n pointNative: { x: point.x, y: point.y },\n normalizedScanPosition: null,\n readoutPhaseOffsetSeconds: 0\n };\n }\n\n const normalized = normalizedScanPosition(\n point,\n activeCaptureRect,\n scan.directionNative.value\n );\n\n return {\n pointNative: { x: point.x, y: point.y },\n normalizedScanPosition: normalized,\n readoutPhaseOffsetSeconds:\n normalized * scan.spatialSamplingSkewSeconds.value\n };\n });\n\n return approximationResult(\n {\n readoutMode: readout.readoutMode,\n shutterMechanism: input.shutterMechanism,\n nativeRaster: { ...input.nativeRaster },\n activeCaptureRect: { ...activeCaptureRect },\n captureReadoutDurationSeconds:\n readout.captureReadoutDurationSeconds,\n maximumSpatialSamplingSkewSeconds:\n readout.readoutMode === \"rolling\"\n ? readout.spatialSamplingSkewSeconds.value\n : 0,\n scan,\n samples\n },\n \"native-sensor-readout-scan-timing\",\n \"1.0.0\",\n [\n \"Native raster coordinates use top-left origin, +X right and +Y down and remain invariant under physical camera orientation.\",\n \"Rolling timing uses a uniform-linear single-axis approximation over continuous effective native-raster coordinates.\",\n \"NativeImageRaster is an effective image-sampling grid; the timing map does not assert one raster row or column equals one physical photodiode or hardware readout line.\",\n \"Capture data-readout duration and spatial sampling skew are independently declared facts; no equality or ordering relationship is inferred between them.\",\n \"Global readout has zero spatial sampling skew in this model even when capture data-readout duration is non-zero.\",\n \"Active-capture timing is caller-declared and is not inferred by scaling full-frame timing by crop dimensions.\",\n \"Physical orientation and digital output crop/resolution are downstream of this invariant native timing schedule.\",\n \"Shutter mechanism is reported independently and does not alter the sensor scan schedule in this foundation.\",\n \"The model does not represent segmented, center-out, interleaved, multi-tap or other non-uniform readout patterns.\",\n \"The model does not define exposure windows, shutter-curtain travel, rolling-shutter geometry distortion, flash/flicker interaction or camera/subject motion integration.\"\n ]\n );\n}\n"]}
@@ -103,6 +103,7 @@ export interface SensorResponseApplicationCompatibilityAssessment {
103
103
  response: SensorSpatioSpectralIrradianceReduction["componentEvidence"]["spectral"];
104
104
  };
105
105
  }
106
+ export declare function parseReferenceConditionPolicy(value: unknown, path: string): SensorResponseReferenceConditionPolicy;
106
107
  /**
107
108
  * Validates untrusted input and returns it as a normalized, typed value. Unknown enum values,
108
109
  * missing required fields and inconsistent scientific data are rejected.
@@ -115,6 +116,7 @@ export interface SensorResponseApplicationCompatibilityAssessment {
115
116
  * @see docs/API_REFERENCE.md for the package export and its full type definitions.
116
117
  */
117
118
  export declare function parseSensorResponseApplicationProfile(value: unknown): SensorResponseApplicationProfile;
119
+ export declare function conditionsMatch(reference: SensorSpectralReferenceConditions, operating: SensorSpectralReferenceConditions): boolean;
118
120
  /**
119
121
  * Checks whether the source plane, area, profile, channel and reference conditions allow the
120
122
  * selected way of applying the response.
@@ -14,34 +14,8 @@ import { calculatedResult } from "../core/calculation-result.js";
14
14
  import { InvalidConfigurationError } from "../core/configuration-error.js";
15
15
  import { parseEvidenceList } from "../core/evidence-provenance.js";
16
16
  import { InvalidScientificInputError } from "../core/validation.js";
17
- import { requireInputObject, requireInputKeys, rejectUnknownFields, rejectUnknownVariantFields } from "../core/record-validation.js";
18
- const AREA_TOLERANCE = 1e-10;
19
- function requireRecord(value, path, allowedKeys) {
20
- if (typeof value !== "object" ||
21
- value === null ||
22
- Array.isArray(value)) {
23
- throw new InvalidConfigurationError(path + " must be an object.");
24
- }
25
- if (allowedKeys !== null)
26
- rejectUnknownFields(value, allowedKeys, path);
27
- return value;
28
- }
29
- function requireNonEmptyString(value, path) {
30
- if (typeof value !== "string" ||
31
- value.trim().length === 0) {
32
- throw new InvalidConfigurationError(path + " must be a non-empty string.");
33
- }
34
- return value;
35
- }
36
- function requirePositiveFinite(value, path) {
37
- if (typeof value !== "number" ||
38
- !Number.isFinite(value) ||
39
- value <= 0) {
40
- throw new InvalidConfigurationError(path +
41
- " must be finite and greater than zero.");
42
- }
43
- return value;
44
- }
17
+ import { requireInputObject, requireInputKeys, rejectUnknownVariantFields, requireConfigRecord as requireRecord, requireConfigString as requireNonEmptyString, requireConfigPositiveFinite as requirePositiveFinite } from "../core/record-validation.js";
18
+ import { approximatelyEqual } from "../core/shared-helpers.js";
45
19
  function parseIncidentAreaBasis(value, path) {
46
20
  const record = requireRecord(value, path, ["areaSquareMicrometers", "kind"]);
47
21
  const areaSquareMicrometers = requirePositiveFinite(record.areaSquareMicrometers, path + ".areaSquareMicrometers");
@@ -95,7 +69,7 @@ function parseSpatialResponseModel(value, path) {
95
69
  : { limitation })
96
70
  };
97
71
  }
98
- function parseReferenceConditionPolicy(value, path) {
72
+ export function parseReferenceConditionPolicy(value, path) {
99
73
  const record = requireRecord(value, path, null);
100
74
  rejectUnknownVariantFields(record, "kind", { "exact-match-required": ["kind"], "assume-compatible": ["kind", "limitation", "evidence"] }, path);
101
75
  if (record.kind ===
@@ -161,12 +135,7 @@ function requiredSourcePlane(scope) {
161
135
  ? "sensor-package-incident"
162
136
  : "site-incident";
163
137
  }
164
- function approximatelyEqual(actual, expected) {
165
- const scale = Math.max(1, Math.abs(actual), Math.abs(expected));
166
- return (Math.abs(actual - expected) <=
167
- AREA_TOLERANCE * scale);
168
- }
169
- function conditionsMatch(reference, operating) {
138
+ export function conditionsMatch(reference, operating) {
170
139
  if (reference.temperatureC !==
171
140
  undefined &&
172
141
  operating.temperatureC !==
@@ -1 +1 @@
1
- {"version":3,"file":"response-application-compatibility.js","sourceRoot":"","sources":["../../src/sensor/response-application-compatibility.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Response application compatibility: checks that a measured sensor response can be applied to this\n * light input.\n *\n * It checks the exact profile and channel, the plane where the input light is measured (source\n * plane), the area the response refers to, spatial uniformity and the reference conditions of the\n * measurement. Passing this check alone does not allow converting light to signal. The per-bin\n * operating-range check and the EQE or A/W conversion are separate steps.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n calculatedResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n SensorSpectralReferenceConditions,\n SensorSpectralResponseScientificStatus\n} from \"./spectral-response.js\";\nimport type {\n SensorSpatioSpectralIrradianceReduction\n} from \"./spatio-spectral-reduction.js\";\nimport { requireInputObject, requireInputKeys, rejectUnknownFields, rejectUnknownVariantFields } from \"../core/record-validation.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nconst AREA_TOLERANCE = 1e-10;\n\nexport type SensorResponseSourcePlane =\n | \"sensor-package-incident\"\n | \"site-incident\";\n\nexport type SensorResponseIncidentAreaBasis =\n | {\n kind: \"geometric-sensitive-aperture\";\n areaSquareMicrometers: number;\n }\n | {\n kind: \"full-site-cell\";\n areaSquareMicrometers: number;\n }\n | {\n kind: \"effective-collection-area\";\n areaSquareMicrometers: number;\n };\n\nexport type SensorResponseSpatialModel =\n | {\n kind:\n \"uniform-over-geometric-sensitive-aperture\";\n scientificStatus:\n SensorSpectralResponseScientificStatus;\n evidence: readonly EvidenceProvenance[];\n limitation?: string;\n }\n | {\n kind: \"not-established\";\n limitation: string;\n };\n\nexport type SensorResponseReferenceConditionPolicy =\n | {\n kind: \"exact-match-required\";\n }\n | {\n kind: \"assume-compatible\";\n limitation: string;\n evidence: readonly EvidenceProvenance[];\n };\n\nexport interface SensorResponseApplicationProfile {\n schemaVersion: \"0.1.0\";\n profileId: string;\n spectralResponseProfileId: string;\n colorSamplingProfileId: string;\n channelId: string;\n samplingApertureProfileId: string;\n opticalStackProfileId: string;\n evidence: readonly EvidenceProvenance[];\n incidentAreaBasis:\n SensorResponseIncidentAreaBasis;\n spatialResponseModel:\n SensorResponseSpatialModel;\n referenceConditionPolicy:\n SensorResponseReferenceConditionPolicy;\n}\n\nexport interface SourcedSensorResponseSourcePlane {\n value: SensorResponseSourcePlane;\n evidence: readonly EvidenceProvenance[];\n}\n\nexport interface AssessSensorResponseApplicationCompatibilityInput {\n reduction:\n SensorSpatioSpectralIrradianceReduction;\n applicationProfile:\n SensorResponseApplicationProfile;\n sourcePlane:\n SourcedSensorResponseSourcePlane;\n operatingConditions?:\n SensorSpectralReferenceConditions;\n}\n\nexport type SensorResponseApplicationCompatibilityBlocker =\n | \"spectral-response-profile-id-mismatch\"\n | \"color-sampling-profile-id-mismatch\"\n | \"channel-id-mismatch\"\n | \"sampling-aperture-profile-id-missing\"\n | \"sampling-aperture-profile-id-mismatch\"\n | \"optical-stack-profile-id-missing\"\n | \"optical-stack-profile-id-mismatch\"\n | \"source-plane-mismatch\"\n | \"wavelength-basis-unresolved\"\n | \"incident-area-basis-not-currently-integrated\"\n | \"incident-area-mismatch\"\n | \"spatial-response-uniformity-not-established\"\n | \"response-reference-conditions-not-declared\"\n | \"operating-conditions-not-declared\"\n | \"operating-conditions-mismatch\";\n\nexport type SensorResponseSignalPath =\n | \"photon-rate-to-electrons\"\n | \"radiant-power-to-current\";\n\nexport interface SensorResponseApplicationCompatibilityAssessment {\n applicationProfileId: string;\n spectralResponseProfileId: string;\n colorSamplingProfileId: string;\n channelId: string;\n responseScope:\n SensorSpatioSpectralIrradianceReduction[\"responseScope\"];\n requiredSourcePlane:\n SensorResponseSourcePlane;\n suppliedSourcePlane:\n SensorResponseSourcePlane;\n sourcePlaneMatched: boolean;\n responseIncidentAreaBasis:\n SensorResponseIncidentAreaBasis;\n currentlyIntegratedAreaBasis:\n \"geometric-sensitive-aperture\";\n geometricApertureAreaSquareMicrometers:\n number;\n nominalSiteCellAreaSquareMicrometers?:\n number;\n spatialResponseModel:\n SensorResponseSpatialModel;\n referenceConditionPolicy:\n SensorResponseReferenceConditionPolicy;\n responseReferenceConditions?:\n SensorSpectralReferenceConditions;\n operatingConditions?:\n SensorSpectralReferenceConditions;\n structuralCompatibilityEstablished:\n boolean;\n compatibilityStatus:\n | \"compatible\"\n | \"compatible-approximation\"\n | \"blocked\";\n blockers:\n readonly SensorResponseApplicationCompatibilityBlocker[];\n requiredSignalPath:\n SensorResponseSignalPath;\n quantifiedResponseUncertaintyAvailable:\n boolean;\n responseApplicationPerformed: false;\n signalConversionAuthorized: false;\n temporalIntegrationAuthorized: false;\n photonConversionPerformed: false;\n electronConversionPerformed: false;\n currentConversionPerformed: false;\n operatingRangeCompatibilityAssessed: false;\n spatiallyVaryingResponseSupported: false;\n componentEvidence: {\n applicationProfile:\n readonly EvidenceProvenance[];\n sourcePlane:\n readonly EvidenceProvenance[];\n spatialResponse:\n readonly EvidenceProvenance[];\n referenceConditionAssumption:\n readonly EvidenceProvenance[];\n response:\n SensorSpatioSpectralIrradianceReduction[\"componentEvidence\"][\"spectral\"];\n };\n}\n\nfunction requireRecord(\n value: unknown,\n path: string,\n allowedKeys: readonly string[] | null\n): UnknownRecord {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be an object.\"\n );\n }\n if (allowedKeys !== null) rejectUnknownFields(value, allowedKeys, path);\n return value as UnknownRecord;\n}\n\nfunction requireNonEmptyString(\n value: unknown,\n path: string\n): string {\n if (\n typeof value !== \"string\" ||\n value.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n path + \" must be a non-empty string.\"\n );\n }\n return value;\n}\n\nfunction requirePositiveFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value <= 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must be finite and greater than zero.\"\n );\n }\n return value;\n}\n\nfunction parseIncidentAreaBasis(\n value: unknown,\n path: string\n): SensorResponseIncidentAreaBasis {\n const record =\n requireRecord(value, path, [\"areaSquareMicrometers\", \"kind\"]);\n const areaSquareMicrometers =\n requirePositiveFinite(\n record.areaSquareMicrometers,\n path + \".areaSquareMicrometers\"\n );\n\n if (\n record.kind !==\n \"geometric-sensitive-aperture\" &&\n record.kind !== \"full-site-cell\" &&\n record.kind !==\n \"effective-collection-area\"\n ) {\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n }\n\n return {\n kind: record.kind,\n areaSquareMicrometers\n };\n}\n\nfunction parseSpatialResponseModel(\n value: unknown,\n path: string\n): SensorResponseSpatialModel {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", { \"not-established\": [\"kind\", \"limitation\"], \"uniform-over-geometric-sensitive-aperture\": [\"kind\", \"scientificStatus\", \"evidence\", \"limitation\"] }, path);\n\n if (\n record.kind === \"not-established\"\n ) {\n return {\n kind: \"not-established\",\n limitation: requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n )\n };\n }\n\n if (\n record.kind !==\n \"uniform-over-geometric-sensitive-aperture\"\n ) {\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n }\n\n if (\n record.scientificStatus !==\n \"calibrated\" &&\n record.scientificStatus !==\n \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n path +\n \".scientificStatus is invalid.\"\n );\n }\n\n const limitation =\n record.limitation === undefined\n ? undefined\n : requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n );\n\n if (\n record.scientificStatus ===\n \"approximation\" &&\n limitation === undefined\n ) {\n throw new InvalidConfigurationError(\n path +\n \".limitation is required for an approximation.\"\n );\n }\n\n return {\n kind:\n \"uniform-over-geometric-sensitive-aperture\",\n scientificStatus:\n record.scientificStatus,\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n ),\n ...(limitation === undefined\n ? {}\n : { limitation })\n };\n}\n\nfunction parseReferenceConditionPolicy(\n value: unknown,\n path: string\n): SensorResponseReferenceConditionPolicy {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", { \"exact-match-required\": [\"kind\"], \"assume-compatible\": [\"kind\", \"limitation\", \"evidence\"] }, path);\n\n if (\n record.kind ===\n \"exact-match-required\"\n ) {\n return {\n kind: \"exact-match-required\"\n };\n }\n\n if (\n record.kind === \"assume-compatible\"\n ) {\n return {\n kind: \"assume-compatible\",\n limitation: requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\n/**\n * Validates untrusted input and returns it as a normalized, typed value. Unknown enum values,\n * missing required fields and inconsistent scientific data are rejected.\n *\n * The profile states the source plane, incident-area basis, spatial uniformity and reference\n * conditions under which a response applies.\n * @param value - Untrusted data. TypeScript types alone are not validation.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function parseSensorResponseApplicationProfile(\n value: unknown\n): SensorResponseApplicationProfile {\n const record = requireRecord(\n value,\n \"sensorResponseApplication\", [\"schemaVersion\", \"profileId\", \"spectralResponseProfileId\", \"colorSamplingProfileId\", \"channelId\", \"samplingApertureProfileId\", \"opticalStackProfileId\", \"evidence\", \"incidentAreaBasis\", \"spatialResponseModel\", \"referenceConditionPolicy\"]\n );\n\n if (\n record.schemaVersion !== \"0.1.0\"\n ) {\n throw new InvalidConfigurationError(\n 'sensorResponseApplication.schemaVersion must be \"0.1.0\".'\n );\n }\n\n return {\n schemaVersion: \"0.1.0\",\n profileId: requireNonEmptyString(\n record.profileId,\n \"sensorResponseApplication.profileId\"\n ),\n spectralResponseProfileId:\n requireNonEmptyString(\n record.spectralResponseProfileId,\n \"sensorResponseApplication.spectralResponseProfileId\"\n ),\n colorSamplingProfileId:\n requireNonEmptyString(\n record.colorSamplingProfileId,\n \"sensorResponseApplication.colorSamplingProfileId\"\n ),\n channelId: requireNonEmptyString(\n record.channelId,\n \"sensorResponseApplication.channelId\"\n ),\n samplingApertureProfileId:\n requireNonEmptyString(\n record.samplingApertureProfileId,\n \"sensorResponseApplication.samplingApertureProfileId\"\n ),\n opticalStackProfileId:\n requireNonEmptyString(\n record.opticalStackProfileId,\n \"sensorResponseApplication.opticalStackProfileId\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n \"sensorResponseApplication.evidence\"\n ),\n incidentAreaBasis:\n parseIncidentAreaBasis(\n record.incidentAreaBasis,\n \"sensorResponseApplication.incidentAreaBasis\"\n ),\n spatialResponseModel:\n parseSpatialResponseModel(\n record.spatialResponseModel,\n \"sensorResponseApplication.spatialResponseModel\"\n ),\n referenceConditionPolicy:\n parseReferenceConditionPolicy(\n record.referenceConditionPolicy,\n \"sensorResponseApplication.referenceConditionPolicy\"\n )\n };\n}\n\nfunction parseSourcePlane(\n value:\n SourcedSensorResponseSourcePlane\n): SourcedSensorResponseSourcePlane {\n const record = requireRecord(\n value,\n \"sourcePlane\", [\"value\", \"evidence\"]\n );\n if (\n record.value !==\n \"sensor-package-incident\" &&\n record.value !== \"site-incident\"\n ) {\n throw new InvalidConfigurationError(\n \"sourcePlane.value is invalid.\"\n );\n }\n return {\n value: record.value,\n evidence: parseEvidenceList(\n record.evidence,\n \"sourcePlane.evidence\"\n )\n };\n}\n\nfunction requiredSourcePlane(\n scope:\n SensorSpatioSpectralIrradianceReduction[\"responseScope\"]\n): SensorResponseSourcePlane {\n return scope ===\n \"sensor-package-incident-effective-channel-response\"\n ? \"sensor-package-incident\"\n : \"site-incident\";\n}\n\nfunction approximatelyEqual(\n actual: number,\n expected: number\n): boolean {\n const scale = Math.max(\n 1,\n Math.abs(actual),\n Math.abs(expected)\n );\n return (\n Math.abs(actual - expected) <=\n AREA_TOLERANCE * scale\n );\n}\n\nfunction conditionsMatch(\n reference:\n SensorSpectralReferenceConditions,\n operating:\n SensorSpectralReferenceConditions\n): boolean {\n if (\n reference.temperatureC !==\n undefined &&\n operating.temperatureC !==\n reference.temperatureC\n ) {\n return false;\n }\n if (\n reference\n .incidenceAngleDegreesFromNormal !==\n undefined &&\n operating\n .incidenceAngleDegreesFromNormal !==\n reference\n .incidenceAngleDegreesFromNormal\n ) {\n return false;\n }\n if (\n reference.polarization !==\n undefined &&\n operating.polarization !==\n reference.polarization\n ) {\n return false;\n }\n return true;\n}\n\nfunction validateReductionBoundary(\n reduction:\n SensorSpatioSpectralIrradianceReduction\n): number {\n requireInputObject(reduction, \"reduction input\");\n if (\n reduction.outputMeaning !==\n \"pre-response-spatio-spectral-radiometric-reduction\" ||\n reduction\n .spectralResponseApplicationPerformed !==\n false ||\n reduction.quantumEfficiencyApplied !==\n false ||\n reduction.spectralResponsivityApplied !==\n false ||\n reduction.temporalIntegrationApplied !==\n false\n ) {\n throw new InvalidScientificInputError(\n \"reduction must remain a pre-response, pre-temporal spatio-spectral result.\"\n );\n }\n\n if (\n typeof reduction\n .geometricApertureAreaSquareMicrometers !==\n \"number\" ||\n !Number.isFinite(\n reduction\n .geometricApertureAreaSquareMicrometers\n ) ||\n reduction\n .geometricApertureAreaSquareMicrometers <=\n 0\n ) {\n throw new InvalidScientificInputError(\n \"reduction.geometricApertureAreaSquareMicrometers is required and must be positive.\"\n );\n }\n\n return reduction\n .geometricApertureAreaSquareMicrometers;\n}\n\n/**\n * Checks whether the source plane, area, profile, channel and reference conditions allow the\n * selected way of applying the response.\n *\n * Passing this alone does not allow converting light to signal. The per-bin operating-range check\n * and the EQE or A/W conversion are separate steps.\n * @param input - The input; see `AssessSensorResponseApplicationCompatibilityInput` for its fields, units and coordinates.\n * @returns The assessment. Read its structured blockers before acting on an authorization.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function assessSensorResponseApplicationCompatibility(\n input:\n AssessSensorResponseApplicationCompatibilityInput\n): CalculationResult<SensorResponseApplicationCompatibilityAssessment> {\n requireInputKeys(input, \"assessSensorResponseApplicationCompatibility\", [\"reduction\", \"applicationProfile\", \"sourcePlane\", \"operatingConditions\"]);\n const geometricArea =\n validateReductionBoundary(\n input.reduction\n );\n const profile =\n parseSensorResponseApplicationProfile(\n input.applicationProfile\n );\n const sourcePlane =\n parseSourcePlane(input.sourcePlane);\n const blockers:\n SensorResponseApplicationCompatibilityBlocker[] =\n [];\n\n if (\n profile.spectralResponseProfileId !==\n input.reduction.responseProfileId\n ) {\n blockers.push(\n \"spectral-response-profile-id-mismatch\"\n );\n }\n if (\n profile.colorSamplingProfileId !==\n input.reduction\n .colorSamplingProfileId\n ) {\n blockers.push(\n \"color-sampling-profile-id-mismatch\"\n );\n }\n if (\n profile.channelId !==\n input.reduction.channelId\n ) {\n blockers.push(\n \"channel-id-mismatch\"\n );\n }\n\n const samplingApertureProfileId =\n input.reduction\n .samplingApertureProfileId;\n if (\n samplingApertureProfileId ===\n undefined\n ) {\n blockers.push(\n \"sampling-aperture-profile-id-missing\"\n );\n } else if (\n samplingApertureProfileId !==\n profile.samplingApertureProfileId\n ) {\n blockers.push(\n \"sampling-aperture-profile-id-mismatch\"\n );\n }\n\n const opticalStackProfileId =\n input.reduction\n .opticalStackProfileId;\n if (\n opticalStackProfileId === undefined\n ) {\n blockers.push(\n \"optical-stack-profile-id-missing\"\n );\n } else if (\n opticalStackProfileId !==\n profile.opticalStackProfileId\n ) {\n blockers.push(\n \"optical-stack-profile-id-mismatch\"\n );\n }\n\n const requiredPlane =\n requiredSourcePlane(\n input.reduction.responseScope\n );\n const sourcePlaneMatched =\n sourcePlane.value === requiredPlane;\n if (!sourcePlaneMatched) {\n blockers.push(\n \"source-plane-mismatch\"\n );\n }\n\n if (\n !input.reduction\n .wavelengthBasisResolved\n ) {\n blockers.push(\n \"wavelength-basis-unresolved\"\n );\n }\n\n if (\n profile.incidentAreaBasis.kind !==\n \"geometric-sensitive-aperture\"\n ) {\n blockers.push(\n \"incident-area-basis-not-currently-integrated\"\n );\n } else if (\n !approximatelyEqual(\n profile.incidentAreaBasis\n .areaSquareMicrometers,\n geometricArea\n )\n ) {\n blockers.push(\n \"incident-area-mismatch\"\n );\n }\n\n if (\n profile.spatialResponseModel.kind ===\n \"not-established\"\n ) {\n blockers.push(\n \"spatial-response-uniformity-not-established\"\n );\n }\n\n if (\n profile.referenceConditionPolicy.kind ===\n \"exact-match-required\"\n ) {\n const referenceConditions =\n input.reduction\n .responseReferenceConditions;\n if (\n referenceConditions === undefined\n ) {\n blockers.push(\n \"response-reference-conditions-not-declared\"\n );\n } else if (\n input.operatingConditions ===\n undefined\n ) {\n blockers.push(\n \"operating-conditions-not-declared\"\n );\n } else if (\n !conditionsMatch(\n referenceConditions,\n input.operatingConditions\n )\n ) {\n blockers.push(\n \"operating-conditions-mismatch\"\n );\n }\n }\n\n const structurallyCompatible =\n blockers.length === 0;\n\n const approximation =\n input.reduction\n .responseScientificStatus ===\n \"approximation\" ||\n (\n profile.spatialResponseModel.kind ===\n \"uniform-over-geometric-sensitive-aperture\" &&\n profile.spatialResponseModel\n .scientificStatus ===\n \"approximation\"\n ) ||\n profile.referenceConditionPolicy.kind ===\n \"assume-compatible\";\n\n const compatibilityStatus =\n structurallyCompatible\n ? approximation\n ? \"compatible-approximation\"\n : \"compatible\"\n : \"blocked\";\n\n const spatialEvidence =\n profile.spatialResponseModel.kind ===\n \"uniform-over-geometric-sensitive-aperture\"\n ? profile.spatialResponseModel.evidence\n : [];\n const referenceConditionEvidence =\n profile.referenceConditionPolicy.kind ===\n \"assume-compatible\"\n ? profile.referenceConditionPolicy\n .evidence\n : [];\n\n return calculatedResult(\n {\n applicationProfileId:\n profile.profileId,\n spectralResponseProfileId:\n input.reduction\n .responseProfileId,\n colorSamplingProfileId:\n input.reduction\n .colorSamplingProfileId,\n channelId:\n input.reduction.channelId,\n responseScope:\n input.reduction.responseScope,\n requiredSourcePlane:\n requiredPlane,\n suppliedSourcePlane:\n sourcePlane.value,\n sourcePlaneMatched,\n responseIncidentAreaBasis:\n profile.incidentAreaBasis,\n currentlyIntegratedAreaBasis:\n \"geometric-sensitive-aperture\",\n geometricApertureAreaSquareMicrometers:\n geometricArea,\n ...(input.reduction\n .nominalSiteCellAreaSquareMicrometers ===\n undefined\n ? {}\n : {\n nominalSiteCellAreaSquareMicrometers:\n input.reduction\n .nominalSiteCellAreaSquareMicrometers\n }),\n spatialResponseModel:\n profile.spatialResponseModel,\n referenceConditionPolicy:\n profile.referenceConditionPolicy,\n ...(input.reduction\n .responseReferenceConditions ===\n undefined\n ? {}\n : {\n responseReferenceConditions:\n input.reduction\n .responseReferenceConditions\n }),\n ...(input.operatingConditions ===\n undefined\n ? {}\n : {\n operatingConditions:\n input.operatingConditions\n }),\n structuralCompatibilityEstablished:\n structurallyCompatible,\n compatibilityStatus,\n blockers,\n requiredSignalPath:\n input.reduction\n .sourceResponseKind ===\n \"effective-spectral-responsivity\"\n ? \"radiant-power-to-current\"\n : \"photon-rate-to-electrons\",\n quantifiedResponseUncertaintyAvailable:\n input.reduction\n .responseUncertainty.kind ===\n \"relative\",\n responseApplicationPerformed:\n false,\n signalConversionAuthorized:\n false,\n temporalIntegrationAuthorized:\n false,\n photonConversionPerformed:\n false,\n electronConversionPerformed:\n false,\n currentConversionPerformed:\n false,\n operatingRangeCompatibilityAssessed:\n false,\n spatiallyVaryingResponseSupported:\n false,\n componentEvidence: {\n applicationProfile:\n profile.evidence,\n sourcePlane:\n sourcePlane.evidence,\n spatialResponse:\n spatialEvidence,\n referenceConditionAssumption:\n referenceConditionEvidence,\n response:\n input.reduction\n .componentEvidence.spectral\n }\n },\n \"sensor-response-application-compatibility\",\n \"1.0.0\",\n [\n \"This result is a structural compatibility gate only; it never authorizes or performs sensor-response signal conversion.\",\n \"Response reference plane is derived from responseScope. Package-incident and site-incident inputs are not interchangeable.\",\n \"The current spatio-spectral reducer integrates the geometric sensitive aperture only. Full-site-cell and effective-collection-area response normalizations remain blocked until matching radiometric integrations exist.\",\n \"A scalar wavelength-only response may be applied after spatial reduction only when uniform/separable response over the integrated geometric aperture is explicitly established or declared as an approximation.\",\n \"Exact reference-condition matching is required when that policy is selected because temperature, incidence-angle and polarization dependence are not modeled.\",\n \"An unresolved wavelength basis is incompatible with physical response application.\",\n \"Typical total-pixel QE definitions can include fill factor and microlens effects; they must not be applied to geometric-sensitive-aperture flux unless the response normalization explicitly matches that area basis.\",\n \"Response linearity/dynamic-range validity is not assessed by this gate and remains required before a future conversion can claim calibrated applicability.\",\n \"EQE/filter×EQE responses require a future photon-rate→electron path; A/W responsivity requires a separate radiant-power→current path.\",\n \"Temporal integration, photons/electrons/current, noise, saturation, ADC/RAW conversion and reconstruction remain downstream.\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"response-application-compatibility.js","sourceRoot":"","sources":["../../src/sensor/response-application-compatibility.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Response application compatibility: checks that a measured sensor response can be applied to this\n * light input.\n *\n * It checks the exact profile and channel, the plane where the input light is measured (source\n * plane), the area the response refers to, spatial uniformity and the reference conditions of the\n * measurement. Passing this check alone does not allow converting light to signal. The per-bin\n * operating-range check and the EQE or A/W conversion are separate steps.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n calculatedResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n SensorSpectralReferenceConditions,\n SensorSpectralResponseScientificStatus\n} from \"./spectral-response.js\";\nimport type {\n SensorSpatioSpectralIrradianceReduction\n} from \"./spatio-spectral-reduction.js\";\nimport { requireInputObject, requireInputKeys, rejectUnknownVariantFields, requireConfigRecord as requireRecord, requireConfigString as requireNonEmptyString, requireConfigPositiveFinite as requirePositiveFinite } from \"../core/record-validation.js\";\nimport { approximatelyEqual } from \"../core/shared-helpers.js\";\n\n\nexport type SensorResponseSourcePlane =\n | \"sensor-package-incident\"\n | \"site-incident\";\n\nexport type SensorResponseIncidentAreaBasis =\n | {\n kind: \"geometric-sensitive-aperture\";\n areaSquareMicrometers: number;\n }\n | {\n kind: \"full-site-cell\";\n areaSquareMicrometers: number;\n }\n | {\n kind: \"effective-collection-area\";\n areaSquareMicrometers: number;\n };\n\nexport type SensorResponseSpatialModel =\n | {\n kind:\n \"uniform-over-geometric-sensitive-aperture\";\n scientificStatus:\n SensorSpectralResponseScientificStatus;\n evidence: readonly EvidenceProvenance[];\n limitation?: string;\n }\n | {\n kind: \"not-established\";\n limitation: string;\n };\n\nexport type SensorResponseReferenceConditionPolicy =\n | {\n kind: \"exact-match-required\";\n }\n | {\n kind: \"assume-compatible\";\n limitation: string;\n evidence: readonly EvidenceProvenance[];\n };\n\nexport interface SensorResponseApplicationProfile {\n schemaVersion: \"0.1.0\";\n profileId: string;\n spectralResponseProfileId: string;\n colorSamplingProfileId: string;\n channelId: string;\n samplingApertureProfileId: string;\n opticalStackProfileId: string;\n evidence: readonly EvidenceProvenance[];\n incidentAreaBasis:\n SensorResponseIncidentAreaBasis;\n spatialResponseModel:\n SensorResponseSpatialModel;\n referenceConditionPolicy:\n SensorResponseReferenceConditionPolicy;\n}\n\nexport interface SourcedSensorResponseSourcePlane {\n value: SensorResponseSourcePlane;\n evidence: readonly EvidenceProvenance[];\n}\n\nexport interface AssessSensorResponseApplicationCompatibilityInput {\n reduction:\n SensorSpatioSpectralIrradianceReduction;\n applicationProfile:\n SensorResponseApplicationProfile;\n sourcePlane:\n SourcedSensorResponseSourcePlane;\n operatingConditions?:\n SensorSpectralReferenceConditions;\n}\n\nexport type SensorResponseApplicationCompatibilityBlocker =\n | \"spectral-response-profile-id-mismatch\"\n | \"color-sampling-profile-id-mismatch\"\n | \"channel-id-mismatch\"\n | \"sampling-aperture-profile-id-missing\"\n | \"sampling-aperture-profile-id-mismatch\"\n | \"optical-stack-profile-id-missing\"\n | \"optical-stack-profile-id-mismatch\"\n | \"source-plane-mismatch\"\n | \"wavelength-basis-unresolved\"\n | \"incident-area-basis-not-currently-integrated\"\n | \"incident-area-mismatch\"\n | \"spatial-response-uniformity-not-established\"\n | \"response-reference-conditions-not-declared\"\n | \"operating-conditions-not-declared\"\n | \"operating-conditions-mismatch\";\n\nexport type SensorResponseSignalPath =\n | \"photon-rate-to-electrons\"\n | \"radiant-power-to-current\";\n\nexport interface SensorResponseApplicationCompatibilityAssessment {\n applicationProfileId: string;\n spectralResponseProfileId: string;\n colorSamplingProfileId: string;\n channelId: string;\n responseScope:\n SensorSpatioSpectralIrradianceReduction[\"responseScope\"];\n requiredSourcePlane:\n SensorResponseSourcePlane;\n suppliedSourcePlane:\n SensorResponseSourcePlane;\n sourcePlaneMatched: boolean;\n responseIncidentAreaBasis:\n SensorResponseIncidentAreaBasis;\n currentlyIntegratedAreaBasis:\n \"geometric-sensitive-aperture\";\n geometricApertureAreaSquareMicrometers:\n number;\n nominalSiteCellAreaSquareMicrometers?:\n number;\n spatialResponseModel:\n SensorResponseSpatialModel;\n referenceConditionPolicy:\n SensorResponseReferenceConditionPolicy;\n responseReferenceConditions?:\n SensorSpectralReferenceConditions;\n operatingConditions?:\n SensorSpectralReferenceConditions;\n structuralCompatibilityEstablished:\n boolean;\n compatibilityStatus:\n | \"compatible\"\n | \"compatible-approximation\"\n | \"blocked\";\n blockers:\n readonly SensorResponseApplicationCompatibilityBlocker[];\n requiredSignalPath:\n SensorResponseSignalPath;\n quantifiedResponseUncertaintyAvailable:\n boolean;\n responseApplicationPerformed: false;\n signalConversionAuthorized: false;\n temporalIntegrationAuthorized: false;\n photonConversionPerformed: false;\n electronConversionPerformed: false;\n currentConversionPerformed: false;\n operatingRangeCompatibilityAssessed: false;\n spatiallyVaryingResponseSupported: false;\n componentEvidence: {\n applicationProfile:\n readonly EvidenceProvenance[];\n sourcePlane:\n readonly EvidenceProvenance[];\n spatialResponse:\n readonly EvidenceProvenance[];\n referenceConditionAssumption:\n readonly EvidenceProvenance[];\n response:\n SensorSpatioSpectralIrradianceReduction[\"componentEvidence\"][\"spectral\"];\n };\n}\n\nfunction parseIncidentAreaBasis(\n value: unknown,\n path: string\n): SensorResponseIncidentAreaBasis {\n const record =\n requireRecord(value, path, [\"areaSquareMicrometers\", \"kind\"]);\n const areaSquareMicrometers =\n requirePositiveFinite(\n record.areaSquareMicrometers,\n path + \".areaSquareMicrometers\"\n );\n\n if (\n record.kind !==\n \"geometric-sensitive-aperture\" &&\n record.kind !== \"full-site-cell\" &&\n record.kind !==\n \"effective-collection-area\"\n ) {\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n }\n\n return {\n kind: record.kind,\n areaSquareMicrometers\n };\n}\n\nfunction parseSpatialResponseModel(\n value: unknown,\n path: string\n): SensorResponseSpatialModel {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", { \"not-established\": [\"kind\", \"limitation\"], \"uniform-over-geometric-sensitive-aperture\": [\"kind\", \"scientificStatus\", \"evidence\", \"limitation\"] }, path);\n\n if (\n record.kind === \"not-established\"\n ) {\n return {\n kind: \"not-established\",\n limitation: requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n )\n };\n }\n\n if (\n record.kind !==\n \"uniform-over-geometric-sensitive-aperture\"\n ) {\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n }\n\n if (\n record.scientificStatus !==\n \"calibrated\" &&\n record.scientificStatus !==\n \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n path +\n \".scientificStatus is invalid.\"\n );\n }\n\n const limitation =\n record.limitation === undefined\n ? undefined\n : requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n );\n\n if (\n record.scientificStatus ===\n \"approximation\" &&\n limitation === undefined\n ) {\n throw new InvalidConfigurationError(\n path +\n \".limitation is required for an approximation.\"\n );\n }\n\n return {\n kind:\n \"uniform-over-geometric-sensitive-aperture\",\n scientificStatus:\n record.scientificStatus,\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n ),\n ...(limitation === undefined\n ? {}\n : { limitation })\n };\n}\n\nexport function parseReferenceConditionPolicy(\n value: unknown,\n path: string\n): SensorResponseReferenceConditionPolicy {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", { \"exact-match-required\": [\"kind\"], \"assume-compatible\": [\"kind\", \"limitation\", \"evidence\"] }, path);\n\n if (\n record.kind ===\n \"exact-match-required\"\n ) {\n return {\n kind: \"exact-match-required\"\n };\n }\n\n if (\n record.kind === \"assume-compatible\"\n ) {\n return {\n kind: \"assume-compatible\",\n limitation: requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\n/**\n * Validates untrusted input and returns it as a normalized, typed value. Unknown enum values,\n * missing required fields and inconsistent scientific data are rejected.\n *\n * The profile states the source plane, incident-area basis, spatial uniformity and reference\n * conditions under which a response applies.\n * @param value - Untrusted data. TypeScript types alone are not validation.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function parseSensorResponseApplicationProfile(\n value: unknown\n): SensorResponseApplicationProfile {\n const record = requireRecord(\n value,\n \"sensorResponseApplication\", [\"schemaVersion\", \"profileId\", \"spectralResponseProfileId\", \"colorSamplingProfileId\", \"channelId\", \"samplingApertureProfileId\", \"opticalStackProfileId\", \"evidence\", \"incidentAreaBasis\", \"spatialResponseModel\", \"referenceConditionPolicy\"]\n );\n\n if (\n record.schemaVersion !== \"0.1.0\"\n ) {\n throw new InvalidConfigurationError(\n 'sensorResponseApplication.schemaVersion must be \"0.1.0\".'\n );\n }\n\n return {\n schemaVersion: \"0.1.0\",\n profileId: requireNonEmptyString(\n record.profileId,\n \"sensorResponseApplication.profileId\"\n ),\n spectralResponseProfileId:\n requireNonEmptyString(\n record.spectralResponseProfileId,\n \"sensorResponseApplication.spectralResponseProfileId\"\n ),\n colorSamplingProfileId:\n requireNonEmptyString(\n record.colorSamplingProfileId,\n \"sensorResponseApplication.colorSamplingProfileId\"\n ),\n channelId: requireNonEmptyString(\n record.channelId,\n \"sensorResponseApplication.channelId\"\n ),\n samplingApertureProfileId:\n requireNonEmptyString(\n record.samplingApertureProfileId,\n \"sensorResponseApplication.samplingApertureProfileId\"\n ),\n opticalStackProfileId:\n requireNonEmptyString(\n record.opticalStackProfileId,\n \"sensorResponseApplication.opticalStackProfileId\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n \"sensorResponseApplication.evidence\"\n ),\n incidentAreaBasis:\n parseIncidentAreaBasis(\n record.incidentAreaBasis,\n \"sensorResponseApplication.incidentAreaBasis\"\n ),\n spatialResponseModel:\n parseSpatialResponseModel(\n record.spatialResponseModel,\n \"sensorResponseApplication.spatialResponseModel\"\n ),\n referenceConditionPolicy:\n parseReferenceConditionPolicy(\n record.referenceConditionPolicy,\n \"sensorResponseApplication.referenceConditionPolicy\"\n )\n };\n}\n\nfunction parseSourcePlane(\n value:\n SourcedSensorResponseSourcePlane\n): SourcedSensorResponseSourcePlane {\n const record = requireRecord(\n value,\n \"sourcePlane\", [\"value\", \"evidence\"]\n );\n if (\n record.value !==\n \"sensor-package-incident\" &&\n record.value !== \"site-incident\"\n ) {\n throw new InvalidConfigurationError(\n \"sourcePlane.value is invalid.\"\n );\n }\n return {\n value: record.value,\n evidence: parseEvidenceList(\n record.evidence,\n \"sourcePlane.evidence\"\n )\n };\n}\n\nfunction requiredSourcePlane(\n scope:\n SensorSpatioSpectralIrradianceReduction[\"responseScope\"]\n): SensorResponseSourcePlane {\n return scope ===\n \"sensor-package-incident-effective-channel-response\"\n ? \"sensor-package-incident\"\n : \"site-incident\";\n}\n\n\nexport function conditionsMatch(\n reference:\n SensorSpectralReferenceConditions,\n operating:\n SensorSpectralReferenceConditions\n): boolean {\n if (\n reference.temperatureC !==\n undefined &&\n operating.temperatureC !==\n reference.temperatureC\n ) {\n return false;\n }\n if (\n reference\n .incidenceAngleDegreesFromNormal !==\n undefined &&\n operating\n .incidenceAngleDegreesFromNormal !==\n reference\n .incidenceAngleDegreesFromNormal\n ) {\n return false;\n }\n if (\n reference.polarization !==\n undefined &&\n operating.polarization !==\n reference.polarization\n ) {\n return false;\n }\n return true;\n}\n\nfunction validateReductionBoundary(\n reduction:\n SensorSpatioSpectralIrradianceReduction\n): number {\n requireInputObject(reduction, \"reduction input\");\n if (\n reduction.outputMeaning !==\n \"pre-response-spatio-spectral-radiometric-reduction\" ||\n reduction\n .spectralResponseApplicationPerformed !==\n false ||\n reduction.quantumEfficiencyApplied !==\n false ||\n reduction.spectralResponsivityApplied !==\n false ||\n reduction.temporalIntegrationApplied !==\n false\n ) {\n throw new InvalidScientificInputError(\n \"reduction must remain a pre-response, pre-temporal spatio-spectral result.\"\n );\n }\n\n if (\n typeof reduction\n .geometricApertureAreaSquareMicrometers !==\n \"number\" ||\n !Number.isFinite(\n reduction\n .geometricApertureAreaSquareMicrometers\n ) ||\n reduction\n .geometricApertureAreaSquareMicrometers <=\n 0\n ) {\n throw new InvalidScientificInputError(\n \"reduction.geometricApertureAreaSquareMicrometers is required and must be positive.\"\n );\n }\n\n return reduction\n .geometricApertureAreaSquareMicrometers;\n}\n\n/**\n * Checks whether the source plane, area, profile, channel and reference conditions allow the\n * selected way of applying the response.\n *\n * Passing this alone does not allow converting light to signal. The per-bin operating-range check\n * and the EQE or A/W conversion are separate steps.\n * @param input - The input; see `AssessSensorResponseApplicationCompatibilityInput` for its fields, units and coordinates.\n * @returns The assessment. Read its structured blockers before acting on an authorization.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function assessSensorResponseApplicationCompatibility(\n input:\n AssessSensorResponseApplicationCompatibilityInput\n): CalculationResult<SensorResponseApplicationCompatibilityAssessment> {\n requireInputKeys(input, \"assessSensorResponseApplicationCompatibility\", [\"reduction\", \"applicationProfile\", \"sourcePlane\", \"operatingConditions\"]);\n const geometricArea =\n validateReductionBoundary(\n input.reduction\n );\n const profile =\n parseSensorResponseApplicationProfile(\n input.applicationProfile\n );\n const sourcePlane =\n parseSourcePlane(input.sourcePlane);\n const blockers:\n SensorResponseApplicationCompatibilityBlocker[] =\n [];\n\n if (\n profile.spectralResponseProfileId !==\n input.reduction.responseProfileId\n ) {\n blockers.push(\n \"spectral-response-profile-id-mismatch\"\n );\n }\n if (\n profile.colorSamplingProfileId !==\n input.reduction\n .colorSamplingProfileId\n ) {\n blockers.push(\n \"color-sampling-profile-id-mismatch\"\n );\n }\n if (\n profile.channelId !==\n input.reduction.channelId\n ) {\n blockers.push(\n \"channel-id-mismatch\"\n );\n }\n\n const samplingApertureProfileId =\n input.reduction\n .samplingApertureProfileId;\n if (\n samplingApertureProfileId ===\n undefined\n ) {\n blockers.push(\n \"sampling-aperture-profile-id-missing\"\n );\n } else if (\n samplingApertureProfileId !==\n profile.samplingApertureProfileId\n ) {\n blockers.push(\n \"sampling-aperture-profile-id-mismatch\"\n );\n }\n\n const opticalStackProfileId =\n input.reduction\n .opticalStackProfileId;\n if (\n opticalStackProfileId === undefined\n ) {\n blockers.push(\n \"optical-stack-profile-id-missing\"\n );\n } else if (\n opticalStackProfileId !==\n profile.opticalStackProfileId\n ) {\n blockers.push(\n \"optical-stack-profile-id-mismatch\"\n );\n }\n\n const requiredPlane =\n requiredSourcePlane(\n input.reduction.responseScope\n );\n const sourcePlaneMatched =\n sourcePlane.value === requiredPlane;\n if (!sourcePlaneMatched) {\n blockers.push(\n \"source-plane-mismatch\"\n );\n }\n\n if (\n !input.reduction\n .wavelengthBasisResolved\n ) {\n blockers.push(\n \"wavelength-basis-unresolved\"\n );\n }\n\n if (\n profile.incidentAreaBasis.kind !==\n \"geometric-sensitive-aperture\"\n ) {\n blockers.push(\n \"incident-area-basis-not-currently-integrated\"\n );\n } else if (\n !approximatelyEqual(\n profile.incidentAreaBasis\n .areaSquareMicrometers,\n geometricArea\n )\n ) {\n blockers.push(\n \"incident-area-mismatch\"\n );\n }\n\n if (\n profile.spatialResponseModel.kind ===\n \"not-established\"\n ) {\n blockers.push(\n \"spatial-response-uniformity-not-established\"\n );\n }\n\n if (\n profile.referenceConditionPolicy.kind ===\n \"exact-match-required\"\n ) {\n const referenceConditions =\n input.reduction\n .responseReferenceConditions;\n if (\n referenceConditions === undefined\n ) {\n blockers.push(\n \"response-reference-conditions-not-declared\"\n );\n } else if (\n input.operatingConditions ===\n undefined\n ) {\n blockers.push(\n \"operating-conditions-not-declared\"\n );\n } else if (\n !conditionsMatch(\n referenceConditions,\n input.operatingConditions\n )\n ) {\n blockers.push(\n \"operating-conditions-mismatch\"\n );\n }\n }\n\n const structurallyCompatible =\n blockers.length === 0;\n\n const approximation =\n input.reduction\n .responseScientificStatus ===\n \"approximation\" ||\n (\n profile.spatialResponseModel.kind ===\n \"uniform-over-geometric-sensitive-aperture\" &&\n profile.spatialResponseModel\n .scientificStatus ===\n \"approximation\"\n ) ||\n profile.referenceConditionPolicy.kind ===\n \"assume-compatible\";\n\n const compatibilityStatus =\n structurallyCompatible\n ? approximation\n ? \"compatible-approximation\"\n : \"compatible\"\n : \"blocked\";\n\n const spatialEvidence =\n profile.spatialResponseModel.kind ===\n \"uniform-over-geometric-sensitive-aperture\"\n ? profile.spatialResponseModel.evidence\n : [];\n const referenceConditionEvidence =\n profile.referenceConditionPolicy.kind ===\n \"assume-compatible\"\n ? profile.referenceConditionPolicy\n .evidence\n : [];\n\n return calculatedResult(\n {\n applicationProfileId:\n profile.profileId,\n spectralResponseProfileId:\n input.reduction\n .responseProfileId,\n colorSamplingProfileId:\n input.reduction\n .colorSamplingProfileId,\n channelId:\n input.reduction.channelId,\n responseScope:\n input.reduction.responseScope,\n requiredSourcePlane:\n requiredPlane,\n suppliedSourcePlane:\n sourcePlane.value,\n sourcePlaneMatched,\n responseIncidentAreaBasis:\n profile.incidentAreaBasis,\n currentlyIntegratedAreaBasis:\n \"geometric-sensitive-aperture\",\n geometricApertureAreaSquareMicrometers:\n geometricArea,\n ...(input.reduction\n .nominalSiteCellAreaSquareMicrometers ===\n undefined\n ? {}\n : {\n nominalSiteCellAreaSquareMicrometers:\n input.reduction\n .nominalSiteCellAreaSquareMicrometers\n }),\n spatialResponseModel:\n profile.spatialResponseModel,\n referenceConditionPolicy:\n profile.referenceConditionPolicy,\n ...(input.reduction\n .responseReferenceConditions ===\n undefined\n ? {}\n : {\n responseReferenceConditions:\n input.reduction\n .responseReferenceConditions\n }),\n ...(input.operatingConditions ===\n undefined\n ? {}\n : {\n operatingConditions:\n input.operatingConditions\n }),\n structuralCompatibilityEstablished:\n structurallyCompatible,\n compatibilityStatus,\n blockers,\n requiredSignalPath:\n input.reduction\n .sourceResponseKind ===\n \"effective-spectral-responsivity\"\n ? \"radiant-power-to-current\"\n : \"photon-rate-to-electrons\",\n quantifiedResponseUncertaintyAvailable:\n input.reduction\n .responseUncertainty.kind ===\n \"relative\",\n responseApplicationPerformed:\n false,\n signalConversionAuthorized:\n false,\n temporalIntegrationAuthorized:\n false,\n photonConversionPerformed:\n false,\n electronConversionPerformed:\n false,\n currentConversionPerformed:\n false,\n operatingRangeCompatibilityAssessed:\n false,\n spatiallyVaryingResponseSupported:\n false,\n componentEvidence: {\n applicationProfile:\n profile.evidence,\n sourcePlane:\n sourcePlane.evidence,\n spatialResponse:\n spatialEvidence,\n referenceConditionAssumption:\n referenceConditionEvidence,\n response:\n input.reduction\n .componentEvidence.spectral\n }\n },\n \"sensor-response-application-compatibility\",\n \"1.0.0\",\n [\n \"This result is a structural compatibility gate only; it never authorizes or performs sensor-response signal conversion.\",\n \"Response reference plane is derived from responseScope. Package-incident and site-incident inputs are not interchangeable.\",\n \"The current spatio-spectral reducer integrates the geometric sensitive aperture only. Full-site-cell and effective-collection-area response normalizations remain blocked until matching radiometric integrations exist.\",\n \"A scalar wavelength-only response may be applied after spatial reduction only when uniform/separable response over the integrated geometric aperture is explicitly established or declared as an approximation.\",\n \"Exact reference-condition matching is required when that policy is selected because temperature, incidence-angle and polarization dependence are not modeled.\",\n \"An unresolved wavelength basis is incompatible with physical response application.\",\n \"Typical total-pixel QE definitions can include fill factor and microlens effects; they must not be applied to geometric-sensitive-aperture flux unless the response normalization explicitly matches that area basis.\",\n \"Response linearity/dynamic-range validity is not assessed by this gate and remains required before a future conversion can claim calibrated applicability.\",\n \"EQE/filter×EQE responses require a future photon-rate→electron path; A/W responsivity requires a separate radiant-power→current path.\",\n \"Temporal integration, photons/electrons/current, noise, saturation, ADC/RAW conversion and reconstruction remain downstream.\"\n ]\n );\n}\n"]}
@@ -138,6 +138,7 @@ export interface SensorResponseOperatingRangeAssessment {
138
138
  structuralCompatibility: SensorResponseApplicationCompatibilityAssessment["componentEvidence"];
139
139
  };
140
140
  }
141
+ export declare function parseUncertainty(value: unknown, path: string): SensorSpectralResponseUncertainty;
141
142
  /**
142
143
  * Validates untrusted input and returns it as a normalized, typed value. Unknown enum values,
143
144
  * missing required fields and inconsistent scientific data are rejected.
@@ -14,43 +14,9 @@ import { calculatedResult } from "../core/calculation-result.js";
14
14
  import { InvalidConfigurationError } from "../core/configuration-error.js";
15
15
  import { parseEvidenceList } from "../core/evidence-provenance.js";
16
16
  import { InvalidScientificInputError } from "../core/validation.js";
17
- import { requireInputObject, requireInputKeys, rejectUnknownFields, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS } from "../core/record-validation.js";
18
- function requireRecord(value, path, allowedKeys) {
19
- if (typeof value !== "object" ||
20
- value === null ||
21
- Array.isArray(value)) {
22
- throw new InvalidConfigurationError(path + " must be an object.");
23
- }
24
- if (allowedKeys !== null)
25
- rejectUnknownFields(value, allowedKeys, path);
26
- return value;
27
- }
28
- function requireNonEmptyString(value, path) {
29
- if (typeof value !== "string" ||
30
- value.trim().length === 0) {
31
- throw new InvalidConfigurationError(path + " must be a non-empty string.");
32
- }
33
- return value;
34
- }
35
- function requireFiniteNonNegative(value, path) {
36
- if (typeof value !== "number" ||
37
- !Number.isFinite(value) ||
38
- value < 0) {
39
- throw new InvalidConfigurationError(path +
40
- " must be finite and greater than or equal to zero.");
41
- }
42
- return value;
43
- }
44
- function requirePositiveFinite(value, path) {
45
- if (typeof value !== "number" ||
46
- !Number.isFinite(value) ||
47
- value <= 0) {
48
- throw new InvalidConfigurationError(path +
49
- " must be finite and greater than zero.");
50
- }
51
- return value;
52
- }
53
- function parseUncertainty(value, path) {
17
+ import { requireInputObject, requireInputKeys, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS, requireConfigRecord as requireRecord, requireConfigString as requireNonEmptyString, requireConfigPositiveFinite as requirePositiveFinite, requireConfigNonNegativeFinite as requireFiniteNonNegative } from "../core/record-validation.js";
18
+ import { parseReferenceConditionPolicy, conditionsMatch } from "./response-application-compatibility.js";
19
+ export function parseUncertainty(value, path) {
54
20
  const record = requireRecord(value, path, null);
55
21
  rejectUnknownVariantFields(record, "kind", UNCERTAINTY_VARIANT_FIELDS, path);
56
22
  if (record.kind === "relative") {
@@ -117,24 +83,6 @@ function parseReferenceConditions(value, path) {
117
83
  }
118
84
  return result;
119
85
  }
120
- function parseReferenceConditionPolicy(value, path) {
121
- const record = requireRecord(value, path, null);
122
- rejectUnknownVariantFields(record, "kind", { "exact-match-required": ["kind"], "assume-compatible": ["kind", "limitation", "evidence"] }, path);
123
- if (record.kind ===
124
- "exact-match-required") {
125
- return {
126
- kind: "exact-match-required"
127
- };
128
- }
129
- if (record.kind === "assume-compatible") {
130
- return {
131
- kind: "assume-compatible",
132
- limitation: requireNonEmptyString(record.limitation, path + ".limitation"),
133
- evidence: parseEvidenceList(record.evidence, path + ".evidence")
134
- };
135
- }
136
- throw new InvalidConfigurationError(path + ".kind is invalid.");
137
- }
138
86
  function parseInputRange(value, path) {
139
87
  const record = requireRecord(value, path, ["minimumInclusive", "maximumInclusive", "kind", "unit"]);
140
88
  const minimumInclusive = requireFiniteNonNegative(record.minimumInclusive, path + ".minimumInclusive");
@@ -361,30 +309,6 @@ export function parseSensorResponseOperatingRangeProfile(value) {
361
309
  referenceConditionPolicy
362
310
  };
363
311
  }
364
- function conditionsMatch(reference, operating) {
365
- if (reference.temperatureC !==
366
- undefined &&
367
- operating.temperatureC !==
368
- reference.temperatureC) {
369
- return false;
370
- }
371
- if (reference
372
- .incidenceAngleDegreesFromNormal !==
373
- undefined &&
374
- operating
375
- .incidenceAngleDegreesFromNormal !==
376
- reference
377
- .incidenceAngleDegreesFromNormal) {
378
- return false;
379
- }
380
- if (reference.polarization !==
381
- undefined &&
382
- operating.polarization !==
383
- reference.polarization) {
384
- return false;
385
- }
386
- return true;
387
- }
388
312
  function requireReductionMetric(reduction, range) {
389
313
  const value = range.kind ===
390
314
  "wavelength-integrated-geometric-aperture-radiant-power"