@photivra/engine 2.17.0 → 2.19.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 (288) hide show
  1. package/CHANGELOG.md +116 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/dist/camera/sensor-equivalence.d.ts +156 -0
  6. package/dist/camera/sensor-equivalence.js +208 -0
  7. package/dist/camera/sensor-equivalence.js.map +1 -0
  8. package/dist/capture/focus-control.js +2 -75
  9. package/dist/capture/focus-control.js.map +1 -1
  10. package/dist/capture/native-environment-lean.d.ts +1 -0
  11. package/dist/capture/native-environment-lean.js +1 -1
  12. package/dist/capture/native-environment-lean.js.map +1 -1
  13. package/dist/capture/native-environment-optical-stage.d.ts +1 -0
  14. package/dist/capture/native-environment-optical-stage.js +2 -5
  15. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  16. package/dist/capture/native-environment-raw.js +1 -5
  17. package/dist/capture/native-environment-raw.js.map +1 -1
  18. package/dist/capture/native-environment-tabulated.d.ts +5 -0
  19. package/dist/capture/native-environment-tabulated.js +1 -1
  20. package/dist/capture/native-environment-tabulated.js.map +1 -1
  21. package/dist/capture/release-sequence.js +2 -47
  22. package/dist/capture/release-sequence.js.map +1 -1
  23. package/dist/color/white-balance.d.ts +2 -0
  24. package/dist/color/white-balance.js +3 -56
  25. package/dist/color/white-balance.js.map +1 -1
  26. package/dist/composition/image-formation-plan.d.ts +1 -0
  27. package/dist/composition/image-formation-plan.js +3 -49
  28. package/dist/composition/image-formation-plan.js.map +1 -1
  29. package/dist/core/evidence-provenance.js +1 -16
  30. package/dist/core/evidence-provenance.js.map +1 -1
  31. package/dist/core/record-validation.d.ts +54 -0
  32. package/dist/core/record-validation.js +176 -0
  33. package/dist/core/record-validation.js.map +1 -1
  34. package/dist/core/scientific-assurance.js +1 -18
  35. package/dist/core/scientific-assurance.js.map +1 -1
  36. package/dist/core/shared-helpers.d.ts +33 -0
  37. package/dist/core/shared-helpers.js +34 -0
  38. package/dist/core/shared-helpers.js.map +1 -0
  39. package/dist/core/spectral-composition.js +1 -36
  40. package/dist/core/spectral-composition.js.map +1 -1
  41. package/dist/core/spectral.js +1 -19
  42. package/dist/core/spectral.js.map +1 -1
  43. package/dist/core/version.d.ts +1 -1
  44. package/dist/core/version.js +1 -1
  45. package/dist/core/version.js.map +1 -1
  46. package/dist/equipment/exposure-capabilities.d.ts +1 -0
  47. package/dist/equipment/exposure-capabilities.js +2 -36
  48. package/dist/equipment/exposure-capabilities.js.map +1 -1
  49. package/dist/equipment/ideal-lens.d.ts +25 -0
  50. package/dist/equipment/ideal-lens.js +21 -0
  51. package/dist/equipment/ideal-lens.js.map +1 -0
  52. package/dist/equipment/iso-capabilities.js +2 -49
  53. package/dist/equipment/iso-capabilities.js.map +1 -1
  54. package/dist/equipment/metering-capabilities.js +1 -26
  55. package/dist/equipment/metering-capabilities.js.map +1 -1
  56. package/dist/equipment/release-capabilities.js +1 -53
  57. package/dist/equipment/release-capabilities.js.map +1 -1
  58. package/dist/exposure/auto-iso-slowest-shutter.js +1 -7
  59. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
  60. package/dist/exposure/exposure-duration-control.js +1 -16
  61. package/dist/exposure/exposure-duration-control.js.map +1 -1
  62. package/dist/exposure/exposure-mode-resolver.js +1 -17
  63. package/dist/exposure/exposure-mode-resolver.js.map +1 -1
  64. package/dist/exposure/filtered-exposure-time.d.ts +93 -0
  65. package/dist/exposure/filtered-exposure-time.js +129 -0
  66. package/dist/exposure/filtered-exposure-time.js.map +1 -0
  67. package/dist/exposure/flash-ttl.d.ts +1 -0
  68. package/dist/exposure/flash-ttl.js +1 -1
  69. package/dist/exposure/flash-ttl.js.map +1 -1
  70. package/dist/exposure/flash.js +1 -49
  71. package/dist/exposure/flash.js.map +1 -1
  72. package/dist/exposure/metering-scene-radiance.js +1 -25
  73. package/dist/exposure/metering-scene-radiance.js.map +1 -1
  74. package/dist/exposure/metering-target.js +1 -30
  75. package/dist/exposure/metering-target.js.map +1 -1
  76. package/dist/exposure/metering-temporal.js +1 -8
  77. package/dist/exposure/metering-temporal.js.map +1 -1
  78. package/dist/exposure/metering.js +1 -72
  79. package/dist/exposure/metering.js.map +1 -1
  80. package/dist/exposure/program-line.js +1 -45
  81. package/dist/exposure/program-line.js.map +1 -1
  82. package/dist/facade/catalogue-lens-signal.d.ts +94 -0
  83. package/dist/facade/catalogue-lens-signal.js +362 -0
  84. package/dist/facade/catalogue-lens-signal.js.map +1 -0
  85. package/dist/facade/defocus-image-space-provider.js +5 -2
  86. package/dist/facade/defocus-image-space-provider.js.map +1 -1
  87. package/dist/facade/develop-joined-parts.d.ts +29 -0
  88. package/dist/facade/develop-joined-parts.js +33 -0
  89. package/dist/facade/develop-joined-parts.js.map +1 -0
  90. package/dist/facade/display-view-lens-signal.d.ts +32 -1
  91. package/dist/facade/display-view-lens-signal.js +85 -10
  92. package/dist/facade/display-view-lens-signal.js.map +1 -1
  93. package/dist/facade/expected-signal-setup.d.ts +8 -0
  94. package/dist/facade/expected-signal-setup.js +7 -0
  95. package/dist/facade/expected-signal-setup.js.map +1 -1
  96. package/dist/facade/expected-signal.d.ts +5 -0
  97. package/dist/facade/expected-signal.js +5 -1
  98. package/dist/facade/expected-signal.js.map +1 -1
  99. package/dist/facade/generic-lens-capture.d.ts +5 -2
  100. package/dist/facade/generic-lens-capture.js +5 -2
  101. package/dist/facade/generic-lens-capture.js.map +1 -1
  102. package/dist/facade/simulate-capture.d.ts +67 -8
  103. package/dist/facade/simulate-capture.js +262 -126
  104. package/dist/facade/simulate-capture.js.map +1 -1
  105. package/dist/facade/simulate-display-view.js +1 -1
  106. package/dist/facade/simulate-display-view.js.map +1 -1
  107. package/dist/gpu/chart-composite-gpu.js +1 -3
  108. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  109. package/dist/gpu/chart-radiance-gpu.js +1 -5
  110. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  111. package/dist/gpu/display-view-quality-gpu.d.ts +13 -1
  112. package/dist/gpu/display-view-quality-gpu.js +4 -8
  113. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  114. package/dist/gpu/display-view-speed-gpu.js +3 -25
  115. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  116. package/dist/gpu/sensor-stage-gpu.js +1 -5
  117. package/dist/gpu/sensor-stage-gpu.js.map +1 -1
  118. package/dist/gpu/webgpu-runtime.d.ts +2 -0
  119. package/dist/gpu/webgpu-runtime.js +3 -0
  120. package/dist/gpu/webgpu-runtime.js.map +1 -1
  121. package/dist/index.d.ts +6 -1
  122. package/dist/index.js +5 -0
  123. package/dist/index.js.map +1 -1
  124. package/dist/motion/camera-rotation.js +1 -6
  125. package/dist/motion/camera-rotation.js.map +1 -1
  126. package/dist/motion/capture-rotation-inverse-mapping.d.ts +12 -0
  127. package/dist/motion/capture-rotation-inverse-mapping.js +2 -2
  128. package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -1
  129. package/dist/motion/capture-rotation-temporal-quadrature.d.ts +1 -0
  130. package/dist/motion/capture-rotation-temporal-quadrature.js +1 -1
  131. package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -1
  132. package/dist/motion/capture-rotation-trajectory.js +2 -17
  133. package/dist/motion/capture-rotation-trajectory.js.map +1 -1
  134. package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts +1 -0
  135. package/dist/motion/capture-translation-parallax-temporal-quadrature.js +3 -14
  136. package/dist/motion/capture-translation-parallax-temporal-quadrature.js.map +1 -1
  137. package/dist/motion/extended-object-projection.js +5 -40
  138. package/dist/motion/extended-object-projection.js.map +1 -1
  139. package/dist/optics/atmospheric-haze.js +1 -7
  140. package/dist/optics/atmospheric-haze.js.map +1 -1
  141. package/dist/optics/brown-conrady-distortion.js +1 -7
  142. package/dist/optics/brown-conrady-distortion.js.map +1 -1
  143. package/dist/optics/complex-pupil-psf.d.ts +1 -0
  144. package/dist/optics/complex-pupil-psf.js +2 -84
  145. package/dist/optics/complex-pupil-psf.js.map +1 -1
  146. package/dist/optics/focus-state.js +1 -11
  147. package/dist/optics/focus-state.js.map +1 -1
  148. package/dist/optics/front-of-lens-filter.d.ts +5 -0
  149. package/dist/optics/front-of-lens-filter.js +3 -65
  150. package/dist/optics/front-of-lens-filter.js.map +1 -1
  151. package/dist/optics/generic-nd-filter-presets.d.ts +12 -0
  152. package/dist/optics/generic-nd-filter-presets.js +48 -0
  153. package/dist/optics/generic-nd-filter-presets.js.map +1 -0
  154. package/dist/optics/illumination-vignetting.js +1 -6
  155. package/dist/optics/illumination-vignetting.js.map +1 -1
  156. package/dist/optics/lens-psf-profile.d.ts +4 -1
  157. package/dist/optics/lens-psf-profile.js +12 -111
  158. package/dist/optics/lens-psf-profile.js.map +1 -1
  159. package/dist/optics/longitudinal-chromatic-aberration.js +1 -7
  160. package/dist/optics/longitudinal-chromatic-aberration.js.map +1 -1
  161. package/dist/optics/pupil-vignetting.js +1 -7
  162. package/dist/optics/pupil-vignetting.js.map +1 -1
  163. package/dist/optics/radial-distortion.js +1 -6
  164. package/dist/optics/radial-distortion.js.map +1 -1
  165. package/dist/optics/scene-to-sensor-irradiance.js +4 -96
  166. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  167. package/dist/optics/stray-light.d.ts +1 -0
  168. package/dist/optics/stray-light.js +1 -1
  169. package/dist/optics/stray-light.js.map +1 -1
  170. package/dist/optics/zernike-wavefront.js +1 -7
  171. package/dist/optics/zernike-wavefront.js.map +1 -1
  172. package/dist/output/capture-geometry.js +1 -8
  173. package/dist/output/capture-geometry.js.map +1 -1
  174. package/dist/output/developed-lens-corrections.d.ts +62 -0
  175. package/dist/output/developed-lens-corrections.js +135 -0
  176. package/dist/output/developed-lens-corrections.js.map +1 -0
  177. package/dist/output/generic-lens-corrections.d.ts +2 -6
  178. package/dist/output/generic-lens-corrections.js +3 -6
  179. package/dist/output/generic-lens-corrections.js.map +1 -1
  180. package/dist/output/geometric-transforms.js +1 -4
  181. package/dist/output/geometric-transforms.js.map +1 -1
  182. package/dist/output/native-capture-sdr.d.ts +2 -2
  183. package/dist/output/native-capture-sdr.js +3 -2
  184. package/dist/output/native-capture-sdr.js.map +1 -1
  185. package/dist/processing/development-detail.d.ts +29 -0
  186. package/dist/processing/development-detail.js +128 -107
  187. package/dist/processing/development-detail.js.map +1 -1
  188. package/dist/schema/illumination-temporal.js +3 -72
  189. package/dist/schema/illumination-temporal.js.map +1 -1
  190. package/dist/schema/illumination.d.ts +1 -0
  191. package/dist/schema/illumination.js +2 -65
  192. package/dist/schema/illumination.js.map +1 -1
  193. package/dist/schema/scene-radiance-temporal.js +2 -14
  194. package/dist/schema/scene-radiance-temporal.js.map +1 -1
  195. package/dist/schema/scene-radiance.js +2 -60
  196. package/dist/schema/scene-radiance.js.map +1 -1
  197. package/dist/schema/validation.d.ts +2 -0
  198. package/dist/schema/validation.js +2 -17
  199. package/dist/schema/validation.js.map +1 -1
  200. package/dist/sensor/accumulated-charge.js +2 -59
  201. package/dist/sensor/accumulated-charge.js.map +1 -1
  202. package/dist/sensor/architecture.js +1 -16
  203. package/dist/sensor/architecture.js.map +1 -1
  204. package/dist/sensor/camera-saturation-capacity.d.ts +1 -0
  205. package/dist/sensor/camera-saturation-capacity.js +3 -55
  206. package/dist/sensor/camera-saturation-capacity.js.map +1 -1
  207. package/dist/sensor/capture-color-sampling-binding.js +1 -26
  208. package/dist/sensor/capture-color-sampling-binding.js.map +1 -1
  209. package/dist/sensor/capture-mode-timing.js +1 -39
  210. package/dist/sensor/capture-mode-timing.js.map +1 -1
  211. package/dist/sensor/capture-mode.js +1 -23
  212. package/dist/sensor/capture-mode.js.map +1 -1
  213. package/dist/sensor/color-sampling.js +1 -26
  214. package/dist/sensor/color-sampling.js.map +1 -1
  215. package/dist/sensor/constant-rate-temporal-integration.js +2 -46
  216. package/dist/sensor/constant-rate-temporal-integration.js.map +1 -1
  217. package/dist/sensor/dark-current.js +2 -45
  218. package/dist/sensor/dark-current.js.map +1 -1
  219. package/dist/sensor/eqe-electron-rate.js +2 -35
  220. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  221. package/dist/sensor/exposure-window.d.ts +5 -0
  222. package/dist/sensor/exposure-window.js +7 -22
  223. package/dist/sensor/exposure-window.js.map +1 -1
  224. package/dist/sensor/iso-gain.js +1 -7
  225. package/dist/sensor/iso-gain.js.map +1 -1
  226. package/dist/sensor/iso-signal-chain.js +1 -43
  227. package/dist/sensor/iso-signal-chain.js.map +1 -1
  228. package/dist/sensor/optical-stack.js +1 -25
  229. package/dist/sensor/optical-stack.js.map +1 -1
  230. package/dist/sensor/photon-energy.js +1 -26
  231. package/dist/sensor/photon-energy.js.map +1 -1
  232. package/dist/sensor/physical-charge-capacity.js +3 -77
  233. package/dist/sensor/physical-charge-capacity.js.map +1 -1
  234. package/dist/sensor/radiometry-readiness.js +2 -23
  235. package/dist/sensor/radiometry-readiness.js.map +1 -1
  236. package/dist/sensor/raw-readout.d.ts +3 -0
  237. package/dist/sensor/raw-readout.js +4 -66
  238. package/dist/sensor/raw-readout.js.map +1 -1
  239. package/dist/sensor/raw-reconstruction.js +2 -64
  240. package/dist/sensor/raw-reconstruction.js.map +1 -1
  241. package/dist/sensor/readout-exposure-linkage.js +1 -9
  242. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  243. package/dist/sensor/readout-timing.js +3 -57
  244. package/dist/sensor/readout-timing.js.map +1 -1
  245. package/dist/sensor/response-application-compatibility.d.ts +2 -0
  246. package/dist/sensor/response-application-compatibility.js +3 -29
  247. package/dist/sensor/response-application-compatibility.js.map +1 -1
  248. package/dist/sensor/response-operating-range.d.ts +1 -0
  249. package/dist/sensor/response-operating-range.js +3 -79
  250. package/dist/sensor/response-operating-range.js.map +1 -1
  251. package/dist/sensor/responsivity-photocurrent.js +4 -108
  252. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  253. package/dist/sensor/sampling-aperture.js +1 -25
  254. package/dist/sensor/sampling-aperture.js.map +1 -1
  255. package/dist/sensor/spatial-sample-reduction.js +2 -19
  256. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  257. package/dist/sensor/spatial-sampling-quadrature.js +1 -3
  258. package/dist/sensor/spatial-sampling-quadrature.js.map +1 -1
  259. package/dist/sensor/spatio-spectral-reduction.js +1 -18
  260. package/dist/sensor/spatio-spectral-reduction.js.map +1 -1
  261. package/dist/sensor/spectral-coverage.d.ts +10 -1
  262. package/dist/sensor/spectral-coverage.js +1 -1
  263. package/dist/sensor/spectral-coverage.js.map +1 -1
  264. package/dist/sensor/spectral-quadrature.js +2 -35
  265. package/dist/sensor/spectral-quadrature.js.map +1 -1
  266. package/dist/sensor/spectral-response.js +1 -47
  267. package/dist/sensor/spectral-response.js.map +1 -1
  268. package/dist/stabilization/camera-shake.js +1 -6
  269. package/dist/stabilization/camera-shake.js.map +1 -1
  270. package/dist/stabilization/system.js +2 -80
  271. package/dist/stabilization/system.js.map +1 -1
  272. package/docs/AGENT_GUIDE.md +4 -0
  273. package/docs/API_REFERENCE.md +69 -2
  274. package/docs/QUICK_START.md +136 -6
  275. package/docs/USAGE.md +89 -3
  276. package/docs/api/camera-sensor-equivalence.md +209 -0
  277. package/docs/api/core-version.md +1 -1
  278. package/docs/api/equipment-ideal-lens.md +26 -0
  279. package/docs/api/exports.json +118 -3
  280. package/docs/api/exposure-filtered-exposure-time.md +105 -0
  281. package/docs/api/facade-develop-joined-parts.md +32 -0
  282. package/docs/api/facade-simulate-capture.md +62 -8
  283. package/docs/api/optics-generic-nd-filter-presets.md +34 -0
  284. package/docs/api/optics-lens-psf-profile.md +3 -1
  285. package/docs/api/output-developed-lens-corrections.md +12 -0
  286. package/docs/api/output-generic-lens-corrections.md +0 -9
  287. package/llms.txt +3 -0
  288. package/package.json +1 -1
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Small helpers shared by several modules (2.19.0: these were private copies in each; one shared copy keeps the bundles
4
+ * smaller). Behaviour is unchanged.
5
+ */
6
+ /** Freezes a value and every object it holds, depth first; already frozen objects are left as they are. */
7
+ export function deepFreeze(value) {
8
+ if (typeof value !== "object" || value === null || Object.isFrozen(value))
9
+ return value;
10
+ Object.freeze(value);
11
+ for (const child of Object.values(value))
12
+ deepFreeze(child);
13
+ return value;
14
+ }
15
+ /** A deep copy of JSON data. */
16
+ export function cloneJson(value) {
17
+ return JSON.parse(JSON.stringify(value));
18
+ }
19
+ /** Adds `value` to a Kahan-compensated sum (Kahan 1965). */
20
+ export function addCompensated(state, value) {
21
+ const adjusted = value - state.correction;
22
+ const next = state.sum + adjusted;
23
+ state.correction = (next - state.sum) - adjusted;
24
+ state.sum = next;
25
+ }
26
+ /** Whether two points (or sites) have the same x and y. */
27
+ export function sameSite(a, b) {
28
+ return a.x === b.x && a.y === b.y;
29
+ }
30
+ /** The whole raster as a rectangle at the origin. */
31
+ export function fullRect(raster) {
32
+ return { x: 0, y: 0, width: raster.pixelWidth, height: raster.pixelHeight };
33
+ }
34
+ //# sourceMappingURL=shared-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-helpers.js","sourceRoot":"","sources":["../../src/core/shared-helpers.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;GAGG;AAEH,2GAA2G;AAC3G,MAAM,UAAU,UAAU,CAAI,KAAQ;IACpC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,KAAgC,CAAC;QAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IACvF,OAAO,KAAK,CAAC;AACf,CAAC;AACD,gCAAgC;AAChC,MAAM,UAAU,SAAS,CAAI,KAAQ;IACnC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAM,CAAC;AAChD,CAAC;AAGD,4DAA4D;AAC5D,MAAM,UAAU,cAAc,CAAC,KAAqB,EAAE,KAAa;IACjE,MAAM,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC;IAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,GAAG,QAAQ,CAAC;IAClC,KAAK,CAAC,UAAU,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;IACjD,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC;AACnB,CAAC;AACD,2DAA2D;AAC3D,MAAM,UAAU,QAAQ,CAAC,CAA2B,EAAE,CAA2B;IAC/E,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AACD,qDAAqD;AACrD,MAAM,UAAU,QAAQ,CAAC,MAAmD;IAC1E,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;AAC9E,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Small helpers shared by several modules (2.19.0: these were private copies in each; one shared copy keeps the bundles\n * smaller). Behaviour is unchanged.\n */\n\n/** Freezes a value and every object it holds, depth first; already frozen objects are left as they are. */\nexport function deepFreeze<T>(value: T): T {\n if (typeof value !== \"object\" || value === null || Object.isFrozen(value)) return value;\n Object.freeze(value);\n for (const child of Object.values(value as Record<string, unknown>)) deepFreeze(child);\n return value;\n}\n/** A deep copy of JSON data. */\nexport function cloneJson<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n/** A running sum with Kahan compensation. */\nexport interface CompensatedSum { sum: number; correction: number }\n/** Adds `value` to a Kahan-compensated sum (Kahan 1965). */\nexport function addCompensated(state: CompensatedSum, value: number): void {\n const adjusted = value - state.correction;\n const next = state.sum + adjusted;\n state.correction = (next - state.sum) - adjusted;\n state.sum = next;\n}\n/** Whether two points (or sites) have the same x and y. */\nexport function sameSite(a: { x: number; y: number }, b: { x: number; y: number }): boolean {\n return a.x === b.x && a.y === b.y;\n}\n/** The whole raster as a rectangle at the origin. */\nexport function fullRect(raster: { pixelWidth: number; pixelHeight: number }): { x: number; y: number; width: number; height: number } {\n return { x: 0, y: 0, width: raster.pixelWidth, height: raster.pixelHeight };\n}\n"]}
@@ -11,42 +11,7 @@
11
11
  */
12
12
  import { InvalidConfigurationError } from "./configuration-error.js";
13
13
  import { parseSpectralWavelengthBasis, parseSpectralWavelengthRangeNanometers } from "./spectral.js";
14
- import { requireInputKeys, rejectUnknownFields } from "./record-validation.js";
15
- function requireRecord(value, path, allowedKeys) {
16
- if (typeof value !== "object" ||
17
- value === null ||
18
- Array.isArray(value)) {
19
- throw new InvalidConfigurationError(path + " must be an object.");
20
- }
21
- if (allowedKeys !== null)
22
- rejectUnknownFields(value, allowedKeys, path);
23
- return value;
24
- }
25
- function requireNonEmptyString(value, path) {
26
- if (typeof value !== "string" ||
27
- value.trim().length === 0) {
28
- throw new InvalidConfigurationError(path + " must be a non-empty string.");
29
- }
30
- return value.trim();
31
- }
32
- function requirePositiveFinite(value, path) {
33
- if (typeof value !== "number" ||
34
- !Number.isFinite(value) ||
35
- value <= 0) {
36
- throw new InvalidConfigurationError(path +
37
- " must be finite and greater than zero.");
38
- }
39
- return value;
40
- }
41
- function requireNonNegativeFinite(value, path) {
42
- if (typeof value !== "number" ||
43
- !Number.isFinite(value) ||
44
- value < 0) {
45
- throw new InvalidConfigurationError(path +
46
- " must be finite and greater than or equal to zero.");
47
- }
48
- return value;
49
- }
14
+ import { requireInputKeys, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, requireConfigPositiveFinite as requirePositiveFinite, requireConfigNonNegativeFinite as requireNonNegativeFinite } from "./record-validation.js";
50
15
  function parseResolvedBasis(value, path) {
51
16
  const basis = parseSpectralWavelengthBasis(value, path);
52
17
  if (basis === "unspecified") {
@@ -1 +1 @@
1
- {"version":3,"file":"spectral-composition.js","sourceRoot":"","sources":["../../src/core/spectral-composition.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Combines the wavelength coverage of several spectral inputs (light source, filters, sensor\n * response) before they are multiplied and integrated.\n *\n * Continuous spectra (values per nanometre) and discrete lines (narrow emission lines treated as\n * delta functions) are handled separately, because they integrate differently. This module\n * describes wavelength ranges and interpolation breakpoints only, not spectral values.\n * @see docs/SCENE_RADIANCE_AND_ILLUMINATION.md for equations, coordinate/unit conventions, blockers\n * and support limits.\n */\n\nimport { InvalidConfigurationError } from \"./configuration-error.js\";\nimport {\n parseSpectralWavelengthBasis,\n parseSpectralWavelengthRangeNanometers,\n type SpectralWavelengthBasis,\n type SpectralWavelengthRangeNanometers\n} from \"./spectral.js\";\nimport { requireInputKeys, rejectUnknownFields } from \"./record-validation.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport type ResolvedSpectralWavelengthBasis =\n Exclude<SpectralWavelengthBasis, \"unspecified\">;\n\nexport type SpectralCoverageParticipantRole =\n | \"illumination-source\"\n | \"scene-radiance\"\n | \"material-response\"\n | \"optical-transmission\"\n | \"sensor-response\"\n | \"other\";\n\nexport interface SpectralCoverageParticipant {\n participantId: string;\n role: SpectralCoverageParticipantRole;\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n wavelengthRangeNanometers:\n SpectralWavelengthRangeNanometers;\n /**\n * Internal piecewise-continuous breakpoints. Range endpoints are carried\n * separately and must not be duplicated here.\n */\n breakpointsNanometers:\n readonly number[];\n}\n\nexport interface ComposeSpectralCoverageInput {\n participants:\n readonly SpectralCoverageParticipant[];\n}\n\nexport interface SpectralCoverageComposition {\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n commonWavelengthRangeNanometers:\n SpectralWavelengthRangeNanometers;\n /**\n * Common-range endpoints plus every participant breakpoint that falls\n * strictly inside the common overlap.\n */\n segmentBoundariesNanometers:\n readonly number[];\n participantIds: readonly string[];\n participantRoles:\n readonly SpectralCoverageParticipantRole[];\n continuousCoverageIntersectionEstablished:\n true;\n breakpointUnionEstablished: true;\n responseValuesApplied: false;\n spectralDensityIntegrated: false;\n discreteLinesIncluded: false;\n airVacuumConversionPerformed: false;\n}\n\nexport interface NormalizedDiscreteSpectralLine {\n lineId: string;\n wavelengthNanometers: number;\n normalizedIntegratedWeight: number;\n}\n\nexport interface NormalizedDiscreteSpectralLineDistribution {\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n lineModel: \"delta-like-integrated\";\n normalization:\n \"sum-normalized-integrated-weight-to-one\";\n lines:\n readonly NormalizedDiscreteSpectralLine[];\n}\n\nexport type DiscreteSpectralLineQuantityUnit =\n | \"relative\"\n | \"W/sr\"\n | \"W/m^2\"\n | \"W/m^2/sr\";\n\nexport interface DiscreteSpectralLineMeasureEntry {\n lineId: string;\n wavelengthNanometers: number;\n integratedQuantity: number;\n}\n\nexport interface DiscreteSpectralLineMeasure {\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n quantityUnit:\n DiscreteSpectralLineQuantityUnit;\n lineModel: \"delta-like-integrated\";\n lines:\n readonly DiscreteSpectralLineMeasureEntry[];\n continuousSpectralDensityAssumed: false;\n wavelengthMeasureMultiplicationRequired:\n false;\n}\n\nexport interface IntegratedDiscreteSpectralLineMeasure {\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n quantityUnit:\n DiscreteSpectralLineQuantityUnit;\n integratedQuantity: number;\n lineCount: number;\n continuousQuadratureApplied: false;\n wavelengthMeasureMultiplicationApplied:\n false;\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.trim();\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 requireNonNegativeFinite(\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 or equal to zero.\"\n );\n }\n return value;\n}\n\nfunction parseResolvedBasis(\n value: unknown,\n path: string\n): ResolvedSpectralWavelengthBasis {\n const basis =\n parseSpectralWavelengthBasis(\n value,\n path\n );\n if (basis === \"unspecified\") {\n throw new InvalidConfigurationError(\n path +\n \" must be air or vacuum for spectral composition.\"\n );\n }\n return basis;\n}\n\nfunction parseRole(\n value: unknown,\n path: string\n): SpectralCoverageParticipantRole {\n if (\n value !== \"illumination-source\" &&\n value !== \"scene-radiance\" &&\n value !== \"material-response\" &&\n value !== \"optical-transmission\" &&\n value !== \"sensor-response\" &&\n value !== \"other\"\n ) {\n throw new InvalidConfigurationError(\n path + \" is invalid.\"\n );\n }\n return value;\n}\n\nfunction parseBreakpoints(\n value: unknown,\n range:\n SpectralWavelengthRangeNanometers,\n path: string\n): readonly number[] {\n if (!Array.isArray(value)) {\n throw new InvalidConfigurationError(\n path + \" must be an array.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n return value.map((entry, index) => {\n const breakpoint =\n requirePositiveFinite(\n entry,\n path + \"[\" + index + \"]\"\n );\n if (\n breakpoint <= range.minimum ||\n breakpoint >= range.maximum\n ) {\n throw new InvalidConfigurationError(\n path +\n \" values must lie strictly inside the participant wavelength range.\"\n );\n }\n if (breakpoint <= previous) {\n throw new InvalidConfigurationError(\n path +\n \" must be strictly increasing with no duplicates.\"\n );\n }\n previous = breakpoint;\n return breakpoint;\n });\n}\n\n/**\n * Parses the wavelength coverage of one continuous spectral input.\n *\n * It describes only the wavelength range (support) and interpolation breakpoints. It\n * carries no spectral values, transmission, response, radiance or uncertainty.\n */\nexport function parseSpectralCoverageParticipant(\n value: unknown,\n path = \"spectralCoverageParticipant\"\n): SpectralCoverageParticipant {\n const record = requireRecord(\n value,\n path, [\"wavelengthRangeNanometers\", \"participantId\", \"role\", \"wavelengthBasis\", \"breakpointsNanometers\"]\n );\n const range =\n parseSpectralWavelengthRangeNanometers(\n record.wavelengthRangeNanometers,\n path +\n \".wavelengthRangeNanometers\"\n );\n return {\n participantId:\n requireNonEmptyString(\n record.participantId,\n path + \".participantId\"\n ),\n role: parseRole(\n record.role,\n path + \".role\"\n ),\n wavelengthBasis:\n parseResolvedBasis(\n record.wavelengthBasis,\n path + \".wavelengthBasis\"\n ),\n wavelengthRangeNanometers:\n range,\n breakpointsNanometers:\n parseBreakpoints(\n record.breakpointsNanometers,\n range,\n path +\n \".breakpointsNanometers\"\n )\n };\n}\n\n/**\n * Finds the wavelength range that all continuous inputs share, and collects every\n * interpolation breakpoint inside it.\n *\n * Air and vacuum wavelengths are never converted implicitly. Discrete lines are not\n * accepted here: a delta-like line is integrated differently from a continuous\n * per-nanometre density.\n */\nexport function composeSpectralCoverage(\n input: ComposeSpectralCoverageInput\n): SpectralCoverageComposition {\n requireInputKeys(input, \"composeSpectralCoverage\", [\"participants\"]);\n if (\n !Array.isArray(input.participants) ||\n input.participants.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"participants must be a non-empty array.\"\n );\n }\n\n const participants =\n input.participants.map(\n (entry, index) =>\n parseSpectralCoverageParticipant(\n entry,\n \"participants[\" +\n index +\n \"]\"\n )\n );\n const participantIds =\n participants.map(\n (entry) => entry.participantId\n );\n if (\n new Set(participantIds).size !==\n participantIds.length\n ) {\n throw new InvalidConfigurationError(\n \"participants[].participantId must not contain duplicates.\"\n );\n }\n\n const wavelengthBasis =\n participants[0]!\n .wavelengthBasis;\n if (\n participants.some(\n (entry) =>\n entry.wavelengthBasis !==\n wavelengthBasis\n )\n ) {\n throw new InvalidConfigurationError(\n \"All spectral-coverage participants must use the same resolved wavelengthBasis; air/vacuum conversion is not implicit.\"\n );\n }\n\n const minimum = Math.max(\n ...participants.map(\n (entry) =>\n entry\n .wavelengthRangeNanometers\n .minimum\n )\n );\n const maximum = Math.min(\n ...participants.map(\n (entry) =>\n entry\n .wavelengthRangeNanometers\n .maximum\n )\n );\n if (!(minimum < maximum)) {\n throw new InvalidConfigurationError(\n \"Spectral-coverage participants do not share a non-empty wavelength interval.\"\n );\n }\n\n const boundaries =\n new Set<number>([\n minimum,\n maximum\n ]);\n for (const participant of participants) {\n for (\n const breakpoint of\n participant.breakpointsNanometers\n ) {\n if (\n breakpoint > minimum &&\n breakpoint < maximum\n ) {\n boundaries.add(breakpoint);\n }\n }\n }\n\n return {\n wavelengthBasis,\n commonWavelengthRangeNanometers: {\n minimum,\n maximum\n },\n segmentBoundariesNanometers:\n [...boundaries].sort(\n (a, b) => a - b\n ),\n participantIds,\n participantRoles:\n participants.map(\n (entry) => entry.role\n ),\n continuousCoverageIntersectionEstablished:\n true,\n breakpointUnionEstablished: true,\n responseValuesApplied: false,\n spectralDensityIntegrated: false,\n discreteLinesIncluded: false,\n airVacuumConversionPerformed: false\n };\n}\n\nconst LINE_WEIGHT_TOLERANCE = 1e-9;\n\n/**\n * Parses a normalized set of discrete spectral lines (each treated as a delta function).\n *\n * Each weight is the fraction of the total carried by that line. It is not a\n * per-nanometre density sample. The weights must sum to one.\n */\nexport function parseNormalizedDiscreteSpectralLineDistribution(\n value: unknown,\n path =\n \"discreteSpectralLineDistribution\"\n): NormalizedDiscreteSpectralLineDistribution {\n const record = requireRecord(\n value,\n path, [\"lineModel\", \"normalization\", \"lines\", \"wavelengthBasis\"]\n );\n if (\n record.lineModel !==\n \"delta-like-integrated\"\n ) {\n throw new InvalidConfigurationError(\n path +\n '.lineModel must be \"delta-like-integrated\".'\n );\n }\n if (\n record.normalization !==\n \"sum-normalized-integrated-weight-to-one\"\n ) {\n throw new InvalidConfigurationError(\n path +\n '.normalization must be \"sum-normalized-integrated-weight-to-one\".'\n );\n }\n if (\n !Array.isArray(record.lines) ||\n record.lines.length === 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \".lines must be a non-empty array.\"\n );\n }\n\n const wavelengthBasis =\n parseResolvedBasis(\n record.wavelengthBasis,\n path + \".wavelengthBasis\"\n );\n let previousWavelength =\n Number.NEGATIVE_INFINITY;\n const lineIds: string[] = [];\n let weightSum = 0;\n\n const lines = record.lines.map(\n (entry, index) => {\n const linePath =\n path + \".lines[\" +\n index +\n \"]\";\n const line = requireRecord(\n entry,\n linePath, [\"lineId\", \"wavelengthNanometers\", \"normalizedIntegratedWeight\"]\n );\n const lineId =\n requireNonEmptyString(\n line.lineId,\n linePath + \".lineId\"\n );\n const wavelengthNanometers =\n requirePositiveFinite(\n line.wavelengthNanometers,\n linePath +\n \".wavelengthNanometers\"\n );\n if (\n wavelengthNanometers <=\n previousWavelength\n ) {\n throw new InvalidConfigurationError(\n path +\n \".lines wavelengths must be strictly increasing with no duplicates.\"\n );\n }\n previousWavelength =\n wavelengthNanometers;\n const normalizedIntegratedWeight =\n requirePositiveFinite(\n line.normalizedIntegratedWeight,\n linePath +\n \".normalizedIntegratedWeight\"\n );\n lineIds.push(lineId);\n weightSum +=\n normalizedIntegratedWeight;\n return {\n lineId,\n wavelengthNanometers,\n normalizedIntegratedWeight\n };\n }\n );\n\n if (\n new Set(lineIds).size !==\n lineIds.length\n ) {\n throw new InvalidConfigurationError(\n path +\n \".lines[].lineId must not contain duplicates.\"\n );\n }\n if (\n !Number.isFinite(weightSum) ||\n Math.abs(weightSum - 1) >\n LINE_WEIGHT_TOLERANCE\n ) {\n throw new InvalidConfigurationError(\n path +\n \".lines normalizedIntegratedWeight values must sum to 1.\"\n );\n }\n\n return {\n wavelengthBasis,\n lineModel:\n \"delta-like-integrated\",\n normalization:\n \"sum-normalized-integrated-weight-to-one\",\n lines\n };\n}\n\nfunction parseQuantityUnit(\n value: unknown,\n path: string\n): DiscreteSpectralLineQuantityUnit {\n if (\n value !== \"relative\" &&\n value !== \"W/sr\" &&\n value !== \"W/m^2\" &&\n value !== \"W/m^2/sr\"\n ) {\n throw new InvalidConfigurationError(\n path + \" is invalid.\"\n );\n }\n return value;\n}\n\nexport interface DistributeIntegratedQuantityAcrossDiscreteLinesInput {\n distribution:\n NormalizedDiscreteSpectralLineDistribution;\n totalIntegratedQuantity: number;\n quantityUnit:\n DiscreteSpectralLineQuantityUnit;\n}\n\n/**\n * Distributes one wavelength-integrated quantity across normalized discrete\n * line fractions without inventing a continuous line width.\n */\nexport function distributeIntegratedQuantityAcrossDiscreteSpectralLines(\n input:\n DistributeIntegratedQuantityAcrossDiscreteLinesInput\n): DiscreteSpectralLineMeasure {\n requireInputKeys(input, \"distributeIntegratedQuantityAcrossDiscreteSpectralLines\", [\"distribution\", \"totalIntegratedQuantity\", \"quantityUnit\"]);\n const distribution =\n parseNormalizedDiscreteSpectralLineDistribution(\n input.distribution,\n \"distribution\"\n );\n const totalIntegratedQuantity =\n requireNonNegativeFinite(\n input.totalIntegratedQuantity,\n \"totalIntegratedQuantity\"\n );\n const quantityUnit =\n parseQuantityUnit(\n input.quantityUnit,\n \"quantityUnit\"\n );\n\n return {\n wavelengthBasis:\n distribution.wavelengthBasis,\n quantityUnit,\n lineModel:\n \"delta-like-integrated\",\n lines:\n distribution.lines.map(\n (line) => ({\n lineId: line.lineId,\n wavelengthNanometers:\n line.wavelengthNanometers,\n integratedQuantity:\n totalIntegratedQuantity *\n line\n .normalizedIntegratedWeight\n })\n ),\n continuousSpectralDensityAssumed:\n false,\n wavelengthMeasureMultiplicationRequired:\n false\n };\n}\n\n/**\n * Integrates over discrete lines by summing their contributions, which are already\n * integrated over wavelength. No dλ (wavelength step) factor is applied.\n */\nexport function integrateDiscreteSpectralLineMeasure(\n measure:\n DiscreteSpectralLineMeasure\n): IntegratedDiscreteSpectralLineMeasure {\n const record = requireRecord(\n measure,\n \"measure\", [\"wavelengthBasis\", \"quantityUnit\", \"lineModel\", \"continuousSpectralDensityAssumed\", \"wavelengthMeasureMultiplicationRequired\", \"lines\"]\n );\n const wavelengthBasis =\n parseResolvedBasis(\n record.wavelengthBasis,\n \"measure.wavelengthBasis\"\n );\n const quantityUnit =\n parseQuantityUnit(\n record.quantityUnit,\n \"measure.quantityUnit\"\n );\n\n if (\n record.lineModel !==\n \"delta-like-integrated\"\n ) {\n throw new InvalidConfigurationError(\n 'measure.lineModel must be \"delta-like-integrated\".'\n );\n }\n if (\n record.continuousSpectralDensityAssumed !==\n false ||\n record.wavelengthMeasureMultiplicationRequired !==\n false\n ) {\n throw new InvalidConfigurationError(\n \"Discrete spectral line measures must not claim continuous-density or d-lambda integration semantics.\"\n );\n }\n if (\n !Array.isArray(record.lines) ||\n record.lines.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"measure.lines must be a non-empty array.\"\n );\n }\n\n let previousWavelength =\n Number.NEGATIVE_INFINITY;\n const lineIds: string[] = [];\n let integratedQuantity = 0;\n\n for (\n let index = 0;\n index < record.lines.length;\n index += 1\n ) {\n const linePath =\n \"measure.lines[\" + index + \"]\";\n const line = requireRecord(\n record.lines[index],\n linePath, [\"lineId\", \"wavelengthNanometers\", \"integratedQuantity\"]\n );\n const lineId =\n requireNonEmptyString(\n line.lineId,\n linePath + \".lineId\"\n );\n const wavelengthNanometers =\n requirePositiveFinite(\n line.wavelengthNanometers,\n linePath +\n \".wavelengthNanometers\"\n );\n if (\n wavelengthNanometers <=\n previousWavelength\n ) {\n throw new InvalidConfigurationError(\n \"measure.lines wavelengths must be strictly increasing with no duplicates.\"\n );\n }\n previousWavelength =\n wavelengthNanometers;\n lineIds.push(lineId);\n\n integratedQuantity +=\n requireNonNegativeFinite(\n line.integratedQuantity,\n linePath +\n \".integratedQuantity\"\n );\n if (\n !Number.isFinite(\n integratedQuantity\n )\n ) {\n throw new InvalidConfigurationError(\n \"Discrete spectral line integrated quantity must remain finite.\"\n );\n }\n }\n\n if (\n new Set(lineIds).size !==\n lineIds.length\n ) {\n throw new InvalidConfigurationError(\n \"measure.lines[].lineId must not contain duplicates.\"\n );\n }\n\n return {\n wavelengthBasis,\n quantityUnit,\n integratedQuantity,\n lineCount: record.lines.length,\n continuousQuadratureApplied: false,\n wavelengthMeasureMultiplicationApplied:\n false\n };\n}\n"]}
1
+ {"version":3,"file":"spectral-composition.js","sourceRoot":"","sources":["../../src/core/spectral-composition.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Combines the wavelength coverage of several spectral inputs (light source, filters, sensor\n * response) before they are multiplied and integrated.\n *\n * Continuous spectra (values per nanometre) and discrete lines (narrow emission lines treated as\n * delta functions) are handled separately, because they integrate differently. This module\n * describes wavelength ranges and interpolation breakpoints only, not spectral values.\n * @see docs/SCENE_RADIANCE_AND_ILLUMINATION.md for equations, coordinate/unit conventions, blockers\n * and support limits.\n */\n\nimport { InvalidConfigurationError } from \"./configuration-error.js\";\nimport {\n parseSpectralWavelengthBasis,\n parseSpectralWavelengthRangeNanometers,\n type SpectralWavelengthBasis,\n type SpectralWavelengthRangeNanometers\n} from \"./spectral.js\";\nimport { requireInputKeys, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, requireConfigPositiveFinite as requirePositiveFinite, requireConfigNonNegativeFinite as requireNonNegativeFinite } from \"./record-validation.js\";\n\n\nexport type ResolvedSpectralWavelengthBasis =\n Exclude<SpectralWavelengthBasis, \"unspecified\">;\n\nexport type SpectralCoverageParticipantRole =\n | \"illumination-source\"\n | \"scene-radiance\"\n | \"material-response\"\n | \"optical-transmission\"\n | \"sensor-response\"\n | \"other\";\n\nexport interface SpectralCoverageParticipant {\n participantId: string;\n role: SpectralCoverageParticipantRole;\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n wavelengthRangeNanometers:\n SpectralWavelengthRangeNanometers;\n /**\n * Internal piecewise-continuous breakpoints. Range endpoints are carried\n * separately and must not be duplicated here.\n */\n breakpointsNanometers:\n readonly number[];\n}\n\nexport interface ComposeSpectralCoverageInput {\n participants:\n readonly SpectralCoverageParticipant[];\n}\n\nexport interface SpectralCoverageComposition {\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n commonWavelengthRangeNanometers:\n SpectralWavelengthRangeNanometers;\n /**\n * Common-range endpoints plus every participant breakpoint that falls\n * strictly inside the common overlap.\n */\n segmentBoundariesNanometers:\n readonly number[];\n participantIds: readonly string[];\n participantRoles:\n readonly SpectralCoverageParticipantRole[];\n continuousCoverageIntersectionEstablished:\n true;\n breakpointUnionEstablished: true;\n responseValuesApplied: false;\n spectralDensityIntegrated: false;\n discreteLinesIncluded: false;\n airVacuumConversionPerformed: false;\n}\n\nexport interface NormalizedDiscreteSpectralLine {\n lineId: string;\n wavelengthNanometers: number;\n normalizedIntegratedWeight: number;\n}\n\nexport interface NormalizedDiscreteSpectralLineDistribution {\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n lineModel: \"delta-like-integrated\";\n normalization:\n \"sum-normalized-integrated-weight-to-one\";\n lines:\n readonly NormalizedDiscreteSpectralLine[];\n}\n\nexport type DiscreteSpectralLineQuantityUnit =\n | \"relative\"\n | \"W/sr\"\n | \"W/m^2\"\n | \"W/m^2/sr\";\n\nexport interface DiscreteSpectralLineMeasureEntry {\n lineId: string;\n wavelengthNanometers: number;\n integratedQuantity: number;\n}\n\nexport interface DiscreteSpectralLineMeasure {\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n quantityUnit:\n DiscreteSpectralLineQuantityUnit;\n lineModel: \"delta-like-integrated\";\n lines:\n readonly DiscreteSpectralLineMeasureEntry[];\n continuousSpectralDensityAssumed: false;\n wavelengthMeasureMultiplicationRequired:\n false;\n}\n\nexport interface IntegratedDiscreteSpectralLineMeasure {\n wavelengthBasis:\n ResolvedSpectralWavelengthBasis;\n quantityUnit:\n DiscreteSpectralLineQuantityUnit;\n integratedQuantity: number;\n lineCount: number;\n continuousQuadratureApplied: false;\n wavelengthMeasureMultiplicationApplied:\n false;\n}\n\nfunction parseResolvedBasis(\n value: unknown,\n path: string\n): ResolvedSpectralWavelengthBasis {\n const basis =\n parseSpectralWavelengthBasis(\n value,\n path\n );\n if (basis === \"unspecified\") {\n throw new InvalidConfigurationError(\n path +\n \" must be air or vacuum for spectral composition.\"\n );\n }\n return basis;\n}\n\nfunction parseRole(\n value: unknown,\n path: string\n): SpectralCoverageParticipantRole {\n if (\n value !== \"illumination-source\" &&\n value !== \"scene-radiance\" &&\n value !== \"material-response\" &&\n value !== \"optical-transmission\" &&\n value !== \"sensor-response\" &&\n value !== \"other\"\n ) {\n throw new InvalidConfigurationError(\n path + \" is invalid.\"\n );\n }\n return value;\n}\n\nfunction parseBreakpoints(\n value: unknown,\n range:\n SpectralWavelengthRangeNanometers,\n path: string\n): readonly number[] {\n if (!Array.isArray(value)) {\n throw new InvalidConfigurationError(\n path + \" must be an array.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n return value.map((entry, index) => {\n const breakpoint =\n requirePositiveFinite(\n entry,\n path + \"[\" + index + \"]\"\n );\n if (\n breakpoint <= range.minimum ||\n breakpoint >= range.maximum\n ) {\n throw new InvalidConfigurationError(\n path +\n \" values must lie strictly inside the participant wavelength range.\"\n );\n }\n if (breakpoint <= previous) {\n throw new InvalidConfigurationError(\n path +\n \" must be strictly increasing with no duplicates.\"\n );\n }\n previous = breakpoint;\n return breakpoint;\n });\n}\n\n/**\n * Parses the wavelength coverage of one continuous spectral input.\n *\n * It describes only the wavelength range (support) and interpolation breakpoints. It\n * carries no spectral values, transmission, response, radiance or uncertainty.\n */\nexport function parseSpectralCoverageParticipant(\n value: unknown,\n path = \"spectralCoverageParticipant\"\n): SpectralCoverageParticipant {\n const record = requireRecord(\n value,\n path, [\"wavelengthRangeNanometers\", \"participantId\", \"role\", \"wavelengthBasis\", \"breakpointsNanometers\"]\n );\n const range =\n parseSpectralWavelengthRangeNanometers(\n record.wavelengthRangeNanometers,\n path +\n \".wavelengthRangeNanometers\"\n );\n return {\n participantId:\n requireNonEmptyString(\n record.participantId,\n path + \".participantId\"\n ),\n role: parseRole(\n record.role,\n path + \".role\"\n ),\n wavelengthBasis:\n parseResolvedBasis(\n record.wavelengthBasis,\n path + \".wavelengthBasis\"\n ),\n wavelengthRangeNanometers:\n range,\n breakpointsNanometers:\n parseBreakpoints(\n record.breakpointsNanometers,\n range,\n path +\n \".breakpointsNanometers\"\n )\n };\n}\n\n/**\n * Finds the wavelength range that all continuous inputs share, and collects every\n * interpolation breakpoint inside it.\n *\n * Air and vacuum wavelengths are never converted implicitly. Discrete lines are not\n * accepted here: a delta-like line is integrated differently from a continuous\n * per-nanometre density.\n */\nexport function composeSpectralCoverage(\n input: ComposeSpectralCoverageInput\n): SpectralCoverageComposition {\n requireInputKeys(input, \"composeSpectralCoverage\", [\"participants\"]);\n if (\n !Array.isArray(input.participants) ||\n input.participants.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"participants must be a non-empty array.\"\n );\n }\n\n const participants =\n input.participants.map(\n (entry, index) =>\n parseSpectralCoverageParticipant(\n entry,\n \"participants[\" +\n index +\n \"]\"\n )\n );\n const participantIds =\n participants.map(\n (entry) => entry.participantId\n );\n if (\n new Set(participantIds).size !==\n participantIds.length\n ) {\n throw new InvalidConfigurationError(\n \"participants[].participantId must not contain duplicates.\"\n );\n }\n\n const wavelengthBasis =\n participants[0]!\n .wavelengthBasis;\n if (\n participants.some(\n (entry) =>\n entry.wavelengthBasis !==\n wavelengthBasis\n )\n ) {\n throw new InvalidConfigurationError(\n \"All spectral-coverage participants must use the same resolved wavelengthBasis; air/vacuum conversion is not implicit.\"\n );\n }\n\n const minimum = Math.max(\n ...participants.map(\n (entry) =>\n entry\n .wavelengthRangeNanometers\n .minimum\n )\n );\n const maximum = Math.min(\n ...participants.map(\n (entry) =>\n entry\n .wavelengthRangeNanometers\n .maximum\n )\n );\n if (!(minimum < maximum)) {\n throw new InvalidConfigurationError(\n \"Spectral-coverage participants do not share a non-empty wavelength interval.\"\n );\n }\n\n const boundaries =\n new Set<number>([\n minimum,\n maximum\n ]);\n for (const participant of participants) {\n for (\n const breakpoint of\n participant.breakpointsNanometers\n ) {\n if (\n breakpoint > minimum &&\n breakpoint < maximum\n ) {\n boundaries.add(breakpoint);\n }\n }\n }\n\n return {\n wavelengthBasis,\n commonWavelengthRangeNanometers: {\n minimum,\n maximum\n },\n segmentBoundariesNanometers:\n [...boundaries].sort(\n (a, b) => a - b\n ),\n participantIds,\n participantRoles:\n participants.map(\n (entry) => entry.role\n ),\n continuousCoverageIntersectionEstablished:\n true,\n breakpointUnionEstablished: true,\n responseValuesApplied: false,\n spectralDensityIntegrated: false,\n discreteLinesIncluded: false,\n airVacuumConversionPerformed: false\n };\n}\n\nconst LINE_WEIGHT_TOLERANCE = 1e-9;\n\n/**\n * Parses a normalized set of discrete spectral lines (each treated as a delta function).\n *\n * Each weight is the fraction of the total carried by that line. It is not a\n * per-nanometre density sample. The weights must sum to one.\n */\nexport function parseNormalizedDiscreteSpectralLineDistribution(\n value: unknown,\n path =\n \"discreteSpectralLineDistribution\"\n): NormalizedDiscreteSpectralLineDistribution {\n const record = requireRecord(\n value,\n path, [\"lineModel\", \"normalization\", \"lines\", \"wavelengthBasis\"]\n );\n if (\n record.lineModel !==\n \"delta-like-integrated\"\n ) {\n throw new InvalidConfigurationError(\n path +\n '.lineModel must be \"delta-like-integrated\".'\n );\n }\n if (\n record.normalization !==\n \"sum-normalized-integrated-weight-to-one\"\n ) {\n throw new InvalidConfigurationError(\n path +\n '.normalization must be \"sum-normalized-integrated-weight-to-one\".'\n );\n }\n if (\n !Array.isArray(record.lines) ||\n record.lines.length === 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \".lines must be a non-empty array.\"\n );\n }\n\n const wavelengthBasis =\n parseResolvedBasis(\n record.wavelengthBasis,\n path + \".wavelengthBasis\"\n );\n let previousWavelength =\n Number.NEGATIVE_INFINITY;\n const lineIds: string[] = [];\n let weightSum = 0;\n\n const lines = record.lines.map(\n (entry, index) => {\n const linePath =\n path + \".lines[\" +\n index +\n \"]\";\n const line = requireRecord(\n entry,\n linePath, [\"lineId\", \"wavelengthNanometers\", \"normalizedIntegratedWeight\"]\n );\n const lineId =\n requireNonEmptyString(\n line.lineId,\n linePath + \".lineId\"\n );\n const wavelengthNanometers =\n requirePositiveFinite(\n line.wavelengthNanometers,\n linePath +\n \".wavelengthNanometers\"\n );\n if (\n wavelengthNanometers <=\n previousWavelength\n ) {\n throw new InvalidConfigurationError(\n path +\n \".lines wavelengths must be strictly increasing with no duplicates.\"\n );\n }\n previousWavelength =\n wavelengthNanometers;\n const normalizedIntegratedWeight =\n requirePositiveFinite(\n line.normalizedIntegratedWeight,\n linePath +\n \".normalizedIntegratedWeight\"\n );\n lineIds.push(lineId);\n weightSum +=\n normalizedIntegratedWeight;\n return {\n lineId,\n wavelengthNanometers,\n normalizedIntegratedWeight\n };\n }\n );\n\n if (\n new Set(lineIds).size !==\n lineIds.length\n ) {\n throw new InvalidConfigurationError(\n path +\n \".lines[].lineId must not contain duplicates.\"\n );\n }\n if (\n !Number.isFinite(weightSum) ||\n Math.abs(weightSum - 1) >\n LINE_WEIGHT_TOLERANCE\n ) {\n throw new InvalidConfigurationError(\n path +\n \".lines normalizedIntegratedWeight values must sum to 1.\"\n );\n }\n\n return {\n wavelengthBasis,\n lineModel:\n \"delta-like-integrated\",\n normalization:\n \"sum-normalized-integrated-weight-to-one\",\n lines\n };\n}\n\nfunction parseQuantityUnit(\n value: unknown,\n path: string\n): DiscreteSpectralLineQuantityUnit {\n if (\n value !== \"relative\" &&\n value !== \"W/sr\" &&\n value !== \"W/m^2\" &&\n value !== \"W/m^2/sr\"\n ) {\n throw new InvalidConfigurationError(\n path + \" is invalid.\"\n );\n }\n return value;\n}\n\nexport interface DistributeIntegratedQuantityAcrossDiscreteLinesInput {\n distribution:\n NormalizedDiscreteSpectralLineDistribution;\n totalIntegratedQuantity: number;\n quantityUnit:\n DiscreteSpectralLineQuantityUnit;\n}\n\n/**\n * Distributes one wavelength-integrated quantity across normalized discrete\n * line fractions without inventing a continuous line width.\n */\nexport function distributeIntegratedQuantityAcrossDiscreteSpectralLines(\n input:\n DistributeIntegratedQuantityAcrossDiscreteLinesInput\n): DiscreteSpectralLineMeasure {\n requireInputKeys(input, \"distributeIntegratedQuantityAcrossDiscreteSpectralLines\", [\"distribution\", \"totalIntegratedQuantity\", \"quantityUnit\"]);\n const distribution =\n parseNormalizedDiscreteSpectralLineDistribution(\n input.distribution,\n \"distribution\"\n );\n const totalIntegratedQuantity =\n requireNonNegativeFinite(\n input.totalIntegratedQuantity,\n \"totalIntegratedQuantity\"\n );\n const quantityUnit =\n parseQuantityUnit(\n input.quantityUnit,\n \"quantityUnit\"\n );\n\n return {\n wavelengthBasis:\n distribution.wavelengthBasis,\n quantityUnit,\n lineModel:\n \"delta-like-integrated\",\n lines:\n distribution.lines.map(\n (line) => ({\n lineId: line.lineId,\n wavelengthNanometers:\n line.wavelengthNanometers,\n integratedQuantity:\n totalIntegratedQuantity *\n line\n .normalizedIntegratedWeight\n })\n ),\n continuousSpectralDensityAssumed:\n false,\n wavelengthMeasureMultiplicationRequired:\n false\n };\n}\n\n/**\n * Integrates over discrete lines by summing their contributions, which are already\n * integrated over wavelength. No dλ (wavelength step) factor is applied.\n */\nexport function integrateDiscreteSpectralLineMeasure(\n measure:\n DiscreteSpectralLineMeasure\n): IntegratedDiscreteSpectralLineMeasure {\n const record = requireRecord(\n measure,\n \"measure\", [\"wavelengthBasis\", \"quantityUnit\", \"lineModel\", \"continuousSpectralDensityAssumed\", \"wavelengthMeasureMultiplicationRequired\", \"lines\"]\n );\n const wavelengthBasis =\n parseResolvedBasis(\n record.wavelengthBasis,\n \"measure.wavelengthBasis\"\n );\n const quantityUnit =\n parseQuantityUnit(\n record.quantityUnit,\n \"measure.quantityUnit\"\n );\n\n if (\n record.lineModel !==\n \"delta-like-integrated\"\n ) {\n throw new InvalidConfigurationError(\n 'measure.lineModel must be \"delta-like-integrated\".'\n );\n }\n if (\n record.continuousSpectralDensityAssumed !==\n false ||\n record.wavelengthMeasureMultiplicationRequired !==\n false\n ) {\n throw new InvalidConfigurationError(\n \"Discrete spectral line measures must not claim continuous-density or d-lambda integration semantics.\"\n );\n }\n if (\n !Array.isArray(record.lines) ||\n record.lines.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"measure.lines must be a non-empty array.\"\n );\n }\n\n let previousWavelength =\n Number.NEGATIVE_INFINITY;\n const lineIds: string[] = [];\n let integratedQuantity = 0;\n\n for (\n let index = 0;\n index < record.lines.length;\n index += 1\n ) {\n const linePath =\n \"measure.lines[\" + index + \"]\";\n const line = requireRecord(\n record.lines[index],\n linePath, [\"lineId\", \"wavelengthNanometers\", \"integratedQuantity\"]\n );\n const lineId =\n requireNonEmptyString(\n line.lineId,\n linePath + \".lineId\"\n );\n const wavelengthNanometers =\n requirePositiveFinite(\n line.wavelengthNanometers,\n linePath +\n \".wavelengthNanometers\"\n );\n if (\n wavelengthNanometers <=\n previousWavelength\n ) {\n throw new InvalidConfigurationError(\n \"measure.lines wavelengths must be strictly increasing with no duplicates.\"\n );\n }\n previousWavelength =\n wavelengthNanometers;\n lineIds.push(lineId);\n\n integratedQuantity +=\n requireNonNegativeFinite(\n line.integratedQuantity,\n linePath +\n \".integratedQuantity\"\n );\n if (\n !Number.isFinite(\n integratedQuantity\n )\n ) {\n throw new InvalidConfigurationError(\n \"Discrete spectral line integrated quantity must remain finite.\"\n );\n }\n }\n\n if (\n new Set(lineIds).size !==\n lineIds.length\n ) {\n throw new InvalidConfigurationError(\n \"measure.lines[].lineId must not contain duplicates.\"\n );\n }\n\n return {\n wavelengthBasis,\n quantityUnit,\n integratedQuantity,\n lineCount: record.lines.length,\n continuousQuadratureApplied: false,\n wavelengthMeasureMultiplicationApplied:\n false\n };\n}\n"]}
@@ -4,26 +4,8 @@
4
4
  * wavelength interval, all in nanometres.
5
5
  * @see docs/API_STYLE.md for equations, coordinate/unit conventions, blockers and support limits.
6
6
  */
7
- import { rejectUnknownFields } from "./record-validation.js";
8
7
  import { InvalidConfigurationError } from "./configuration-error.js";
9
- function requireRecord(value, path, allowedKeys) {
10
- if (typeof value !== "object" ||
11
- value === null ||
12
- Array.isArray(value)) {
13
- throw new InvalidConfigurationError(path + " must be an object.");
14
- }
15
- if (allowedKeys !== null)
16
- rejectUnknownFields(value, allowedKeys, path);
17
- return value;
18
- }
19
- function requirePositiveFinite(value, path) {
20
- if (typeof value !== "number" ||
21
- !Number.isFinite(value) ||
22
- value <= 0) {
23
- throw new InvalidConfigurationError(path + " must be finite and greater than zero.");
24
- }
25
- return value;
26
- }
8
+ import { requireConfigRecord as requireRecord, requireConfigPositiveFinite as requirePositiveFinite } from "./record-validation.js";
27
9
  /**
28
10
  * Parses a wavelength basis. It never converts between air and vacuum.
29
11
  */
@@ -1 +1 @@
1
- {"version":3,"file":"spectral.js","sourceRoot":"","sources":["../../src/core/spectral.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AA+BrE,SAAS,aAAa,CACpB,KAAc,EACd,IAAY,EACZ,WAAqC;IAErC,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EACpB,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,qBAAqB,CAC7B,CAAC;IACJ,CAAC;IACD,IAAI,WAAW,KAAK,IAAI;QAAE,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;IACxE,OAAO,KAAsB,CAAC;AAChC,CAAC;AAED,SAAS,qBAAqB,CAC5B,KAAc,EACd,IAAY;IAEZ,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,KAAK,IAAI,CAAC,EACV,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,wCAAwC,CAChD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,4BAA4B,CAC1C,KAAc,EACd,IAAI,GAAG,iBAAiB;IAExB,IACE,KAAK,KAAK,KAAK;QACf,KAAK,KAAK,QAAQ;QAClB,KAAK,KAAK,aAAa,EACvB,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,cAAc,CACtB,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,6BAA6B,CAC3C,KAAc,EACd,IAAI,GAAG,gBAAgB;IAEvB,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACpE,OAAO;QACL,oBAAoB,EAClB,qBAAqB,CACnB,MAAM,CAAC,oBAAoB,EAC3B,IAAI,GAAG,uBAAuB,CAC/B;KACJ,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sCAAsC,CACpD,KAAc,EACd,IAAI,GAAG,2BAA2B;IAElC,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,qBAAqB,CACnC,MAAM,CAAC,OAAO,EACd,IAAI,GAAG,UAAU,CAClB,CAAC;IACF,MAAM,OAAO,GAAG,qBAAqB,CACnC,MAAM,CAAC,OAAO,EACd,IAAI,GAAG,UAAU,CAClB,CAAC;IAEF,IAAI,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,qCAAqC,CAC7C,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Wavelength types and parsers: a wavelength basis (air or vacuum), a single wavelength and a\n * wavelength interval, all in nanometres.\n * @see docs/API_STYLE.md for equations, coordinate/unit conventions, blockers and support limits.\n */\n\nimport { rejectUnknownFields } from \"./record-validation.js\";\nimport { InvalidConfigurationError } from \"./configuration-error.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\n/**\n * Whether wavelengths are measured in air or in vacuum.\n *\n * \"unspecified\" is allowed only where a downstream contract explicitly accepts\n * unresolved or approximate wavelengths. Calibrated physical calculations must\n * resolve the basis first.\n */\nexport type SpectralWavelengthBasis =\n | \"air\"\n | \"vacuum\"\n | \"unspecified\";\n\n/**\n * One wavelength coordinate expressed in nanometres.\n */\nexport interface SpectralWavelengthSample {\n wavelengthNanometers: number;\n}\n\n/**\n * Closed wavelength interval expressed in nanometres.\n */\nexport interface SpectralWavelengthRangeNanometers {\n minimum: number;\n maximum: number;\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 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 + \" must be finite and greater than zero.\"\n );\n }\n return value;\n}\n\n/**\n * Parses a wavelength basis. It never converts between air and vacuum.\n */\nexport function parseSpectralWavelengthBasis(\n value: unknown,\n path = \"wavelengthBasis\"\n): SpectralWavelengthBasis {\n if (\n value !== \"air\" &&\n value !== \"vacuum\" &&\n value !== \"unspecified\"\n ) {\n throw new InvalidConfigurationError(\n path + \" is invalid.\"\n );\n }\n return value;\n}\n\n/**\n * Parses one positive wavelength coordinate in nanometres.\n */\nexport function parseSpectralWavelengthSample(\n value: unknown,\n path = \"spectralSample\"\n): SpectralWavelengthSample {\n const record = requireRecord(value, path, [\"wavelengthNanometers\"]);\n return {\n wavelengthNanometers:\n requirePositiveFinite(\n record.wavelengthNanometers,\n path + \".wavelengthNanometers\"\n )\n };\n}\n\n/**\n * Parses a positive non-empty wavelength interval in nanometres.\n */\nexport function parseSpectralWavelengthRangeNanometers(\n value: unknown,\n path = \"wavelengthRangeNanometers\"\n): SpectralWavelengthRangeNanometers {\n const record = requireRecord(value, path, [\"minimum\", \"maximum\"]);\n const minimum = requirePositiveFinite(\n record.minimum,\n path + \".minimum\"\n );\n const maximum = requirePositiveFinite(\n record.maximum,\n path + \".maximum\"\n );\n\n if (!(minimum < maximum)) {\n throw new InvalidConfigurationError(\n path + \".minimum must be less than maximum.\"\n );\n }\n\n return { minimum, maximum };\n}\n"]}
1
+ {"version":3,"file":"spectral.js","sourceRoot":"","sources":["../../src/core/spectral.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;GAIG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,mBAAmB,IAAI,aAAa,EAAE,2BAA2B,IAAI,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AA8BpI;;GAEG;AACH,MAAM,UAAU,4BAA4B,CAC1C,KAAc,EACd,IAAI,GAAG,iBAAiB;IAExB,IACE,KAAK,KAAK,KAAK;QACf,KAAK,KAAK,QAAQ;QAClB,KAAK,KAAK,aAAa,EACvB,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,cAAc,CACtB,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,6BAA6B,CAC3C,KAAc,EACd,IAAI,GAAG,gBAAgB;IAEvB,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACpE,OAAO;QACL,oBAAoB,EAClB,qBAAqB,CACnB,MAAM,CAAC,oBAAoB,EAC3B,IAAI,GAAG,uBAAuB,CAC/B;KACJ,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sCAAsC,CACpD,KAAc,EACd,IAAI,GAAG,2BAA2B;IAElC,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,qBAAqB,CACnC,MAAM,CAAC,OAAO,EACd,IAAI,GAAG,UAAU,CAClB,CAAC;IACF,MAAM,OAAO,GAAG,qBAAqB,CACnC,MAAM,CAAC,OAAO,EACd,IAAI,GAAG,UAAU,CAClB,CAAC;IAEF,IAAI,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,qCAAqC,CAC7C,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Wavelength types and parsers: a wavelength basis (air or vacuum), a single wavelength and a\n * wavelength interval, all in nanometres.\n * @see docs/API_STYLE.md for equations, coordinate/unit conventions, blockers and support limits.\n */\n\nimport { InvalidConfigurationError } from \"./configuration-error.js\";\nimport { requireConfigRecord as requireRecord, requireConfigPositiveFinite as requirePositiveFinite } from \"./record-validation.js\";\n\n\n/**\n * Whether wavelengths are measured in air or in vacuum.\n *\n * \"unspecified\" is allowed only where a downstream contract explicitly accepts\n * unresolved or approximate wavelengths. Calibrated physical calculations must\n * resolve the basis first.\n */\nexport type SpectralWavelengthBasis =\n | \"air\"\n | \"vacuum\"\n | \"unspecified\";\n\n/**\n * One wavelength coordinate expressed in nanometres.\n */\nexport interface SpectralWavelengthSample {\n wavelengthNanometers: number;\n}\n\n/**\n * Closed wavelength interval expressed in nanometres.\n */\nexport interface SpectralWavelengthRangeNanometers {\n minimum: number;\n maximum: number;\n}\n\n/**\n * Parses a wavelength basis. It never converts between air and vacuum.\n */\nexport function parseSpectralWavelengthBasis(\n value: unknown,\n path = \"wavelengthBasis\"\n): SpectralWavelengthBasis {\n if (\n value !== \"air\" &&\n value !== \"vacuum\" &&\n value !== \"unspecified\"\n ) {\n throw new InvalidConfigurationError(\n path + \" is invalid.\"\n );\n }\n return value;\n}\n\n/**\n * Parses one positive wavelength coordinate in nanometres.\n */\nexport function parseSpectralWavelengthSample(\n value: unknown,\n path = \"spectralSample\"\n): SpectralWavelengthSample {\n const record = requireRecord(value, path, [\"wavelengthNanometers\"]);\n return {\n wavelengthNanometers:\n requirePositiveFinite(\n record.wavelengthNanometers,\n path + \".wavelengthNanometers\"\n )\n };\n}\n\n/**\n * Parses a positive non-empty wavelength interval in nanometres.\n */\nexport function parseSpectralWavelengthRangeNanometers(\n value: unknown,\n path = \"wavelengthRangeNanometers\"\n): SpectralWavelengthRangeNanometers {\n const record = requireRecord(value, path, [\"minimum\", \"maximum\"]);\n const minimum = requirePositiveFinite(\n record.minimum,\n path + \".minimum\"\n );\n const maximum = requirePositiveFinite(\n record.maximum,\n path + \".maximum\"\n );\n\n if (!(minimum < maximum)) {\n throw new InvalidConfigurationError(\n path + \".minimum must be less than maximum.\"\n );\n }\n\n return { minimum, maximum };\n}\n"]}
@@ -6,4 +6,4 @@
6
6
  * own versions. Release checks confirm it matches package.json and the packed package.
7
7
  * @see docs/RELEASE_1_0_1.md
8
8
  */
9
- export declare const ENGINE_API_VERSION: "2.17.0";
9
+ export declare const ENGINE_API_VERSION: "2.19.0";
@@ -7,5 +7,5 @@
7
7
  * own versions. Release checks confirm it matches package.json and the packed package.
8
8
  * @see docs/RELEASE_1_0_1.md
9
9
  */
10
- export const ENGINE_API_VERSION = "2.17.0";
10
+ export const ENGINE_API_VERSION = "2.19.0";
11
11
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAiB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.17.0\" as const;\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAiB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.19.0\" as const;\n"]}
@@ -99,6 +99,7 @@ export interface ResolveGenericEquipmentExposureCapabilitiesInput {
99
99
  lensProfile: GenericLensExposureCapabilityProfile;
100
100
  selectedFocalLengthMm: number;
101
101
  }
102
+ export declare function parseRange(value: unknown, path: string): NumericCapabilityRange;
102
103
  /**
103
104
  * Parses a generic Photivra body exposure-capability profile.
104
105
  *
@@ -15,35 +15,9 @@
15
15
  import { InvalidConfigurationError } from "../core/configuration-error.js";
16
16
  import { parseEvidenceList } from "../core/evidence-provenance.js";
17
17
  import { InvalidScientificInputError } from "../core/validation.js";
18
- import { requireInputKeys, rejectUnknownFields, rejectUnknownVariantFields } from "../core/record-validation.js";
18
+ import { requireInputKeys, rejectUnknownVariantFields, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, requireConfigPositiveFinite as requirePositiveFinite, parseConfigAvailability as parseAvailability } from "../core/record-validation.js";
19
19
  export const GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION = "0.1.0";
20
- function requireRecord(value, path, allowedKeys) {
21
- if (typeof value !== "object" ||
22
- value === null ||
23
- Array.isArray(value)) {
24
- throw new InvalidConfigurationError(path + " must be an object.");
25
- }
26
- if (allowedKeys !== null)
27
- rejectUnknownFields(value, allowedKeys, path);
28
- return value;
29
- }
30
- function requireNonEmptyString(value, path) {
31
- if (typeof value !== "string" ||
32
- value.trim().length === 0) {
33
- throw new InvalidConfigurationError(path + " must be a non-empty string.");
34
- }
35
- return value.trim();
36
- }
37
- function requirePositiveFinite(value, path) {
38
- if (typeof value !== "number" ||
39
- !Number.isFinite(value) ||
40
- value <= 0) {
41
- throw new InvalidConfigurationError(path +
42
- " must be finite and greater than zero.");
43
- }
44
- return value;
45
- }
46
- function parseRange(value, path) {
20
+ export function parseRange(value, path) {
47
21
  const record = requireRecord(value, path, ["minimum", "maximum"]);
48
22
  const minimum = requirePositiveFinite(record.minimum, path + ".minimum");
49
23
  const maximum = requirePositiveFinite(record.maximum, path + ".maximum");
@@ -70,14 +44,6 @@ function parseSourcedPositiveNumber(value, path) {
70
44
  evidence: parseEvidenceList(record.evidence, path + ".evidence")
71
45
  };
72
46
  }
73
- function parseAvailability(value, path) {
74
- if (value !== "supported" &&
75
- value !== "unsupported" &&
76
- value !== "unknown") {
77
- throw new InvalidConfigurationError(path + " is invalid.");
78
- }
79
- return value;
80
- }
81
47
  function parseSourcedAvailability(value, path) {
82
48
  const record = requireRecord(value, path, ["value", "evidence"]);
83
49
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"exposure-capabilities.js","sourceRoot":"","sources":["../../src/equipment/exposure-capabilities.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Exposure capabilities of generic Photivra camera bodies and lenses: which ISO, shutter and aperture\n * settings they offer.\n *\n * This is capability metadata only. ISO values say nothing about how noise or gain is implemented.\n * Shutter-duration capability does not imply a particular shutter mechanism or sensor-readout\n * schedule.\n *\n * For lenses, \"widest\" means the smallest available f-number. This avoids the ambiguous phrases\n * \"minimum aperture\" and \"maximum aperture\".\n * @see docs/GENERIC_TIER_PRESETS.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, rejectUnknownFields, rejectUnknownVariantFields } from \"../core/record-validation.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport const GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION =\n \"0.1.0\" as const;\n\nexport type GenericCapabilityAvailability =\n | \"supported\"\n | \"unsupported\"\n | \"unknown\";\n\nexport interface NumericCapabilityRange {\n minimum: number;\n maximum: number;\n}\n\nexport type NumericSettingGrid =\n | {\n kind: \"continuous-within-range\";\n }\n | {\n kind: \"discrete-values\";\n values:\n EvidenceBackedFact<\n readonly number[]\n >;\n };\n\nexport interface GenericBodyExposureCapabilityProfile {\n schemaVersion:\n typeof GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n scientificStatus: \"approximation\";\n evidence: readonly EvidenceProvenance[];\n shutter: {\n durationSecondsRange:\n EvidenceBackedFact<\n NumericCapabilityRange\n >;\n settingGrid: NumericSettingGrid;\n };\n iso: {\n range:\n EvidenceBackedFact<\n NumericCapabilityRange\n >;\n settingGrid: NumericSettingGrid;\n autoIso:\n EvidenceBackedFact<\n GenericCapabilityAvailability\n >;\n };\n}\n\nexport interface FocalLengthFNumberSample {\n focalLengthMm: number;\n fNumber: number;\n}\n\nexport type WidestAvailableFNumberCapability =\n | {\n kind: \"constant\";\n fNumber:\n EvidenceBackedFact<number>;\n }\n | {\n kind:\n \"piecewise-linear-by-focal-length\";\n samples:\n EvidenceBackedFact<\n readonly FocalLengthFNumberSample[]\n >;\n };\n\nexport interface GenericLensExposureCapabilityProfile {\n schemaVersion:\n typeof GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n scientificStatus: \"approximation\";\n evidence: readonly EvidenceProvenance[];\n focalLengthMmRange:\n EvidenceBackedFact<\n NumericCapabilityRange\n >;\n aperture: {\n widestAvailableFNumber:\n WidestAvailableFNumberCapability;\n narrowestAvailableFNumber:\n EvidenceBackedFact<number>;\n settingGrid: NumericSettingGrid;\n };\n}\n\nexport type ResolvedNumericSettingGrid =\n | {\n kind: \"continuous-within-range\";\n }\n | {\n kind: \"discrete-values\";\n values: readonly number[];\n };\n\nexport interface ResolvedGenericEquipmentExposureCapabilities {\n schemaVersion:\n typeof GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION;\n scientificStatus: \"approximation\";\n bodyProfile: {\n profileId: string;\n profileVersion: string;\n };\n lensProfile: {\n profileId: string;\n profileVersion: string;\n };\n selectedFocalLengthMm: number;\n aperture: {\n widestAvailableFNumber: number;\n narrowestAvailableFNumber: number;\n settingGrid:\n ResolvedNumericSettingGrid;\n };\n shutter: {\n minimumSeconds: number;\n maximumSeconds: number;\n settingGrid:\n ResolvedNumericSettingGrid;\n };\n iso: {\n minimum: number;\n maximum: number;\n settingGrid:\n ResolvedNumericSettingGrid;\n autoIsoAvailability:\n GenericCapabilityAvailability;\n /**\n * Optional bounds that apply only to Auto ISO. Manual ISO still uses the\n * minimum, the maximum and the complete standard setting grid.\n */\n autoIsoMinimum?: number;\n autoIsoMaximum?: number;\n };\n sourceProfilesMutated: false;\n exactNamedEquipmentEmulationClaimed:\n false;\n}\n\nexport interface ResolveGenericEquipmentExposureCapabilitiesInput {\n bodyProfile:\n GenericBodyExposureCapabilityProfile;\n lensProfile:\n GenericLensExposureCapabilityProfile;\n selectedFocalLengthMm: number;\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.trim();\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 parseRange(\n value: unknown,\n path: string\n): NumericCapabilityRange {\n const record = requireRecord(\n value,\n path, [\"minimum\", \"maximum\"]\n );\n const minimum =\n requirePositiveFinite(\n record.minimum,\n path + \".minimum\"\n );\n const maximum =\n requirePositiveFinite(\n record.maximum,\n path + \".maximum\"\n );\n if (minimum > maximum) {\n throw new InvalidConfigurationError(\n path +\n \".minimum must be less than or equal to maximum.\"\n );\n }\n return {\n minimum,\n maximum\n };\n}\n\nfunction parseSourcedRange(\n value: unknown,\n path: string\n): EvidenceBackedFact<\n NumericCapabilityRange\n> {\n const record = requireRecord(\n value,\n path, [\"value\", \"evidence\"]\n );\n return {\n value: parseRange(\n record.value,\n path + \".value\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\nfunction parseSourcedPositiveNumber(\n value: unknown,\n path: string\n): EvidenceBackedFact<number> {\n const record = requireRecord(\n value,\n path, [\"value\", \"evidence\"]\n );\n return {\n value: requirePositiveFinite(\n record.value,\n path + \".value\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\nfunction parseAvailability(\n value: unknown,\n path: string\n): GenericCapabilityAvailability {\n if (\n value !== \"supported\" &&\n value !== \"unsupported\" &&\n value !== \"unknown\"\n ) {\n throw new InvalidConfigurationError(\n path + \" is invalid.\"\n );\n }\n return value;\n}\n\nfunction parseSourcedAvailability(\n value: unknown,\n path: string\n): EvidenceBackedFact<\n GenericCapabilityAvailability\n> {\n const record = requireRecord(\n value,\n path, [\"value\", \"evidence\"]\n );\n return {\n value: parseAvailability(\n record.value,\n path + \".value\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\nfunction parseDiscreteValues(\n value: unknown,\n range: NumericCapabilityRange,\n path: string\n): readonly number[] {\n if (\n !Array.isArray(value) ||\n value.length === 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must be a non-empty array.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n return value.map(\n (entry, index) => {\n const parsed =\n requirePositiveFinite(\n entry,\n path +\n \"[\" +\n index +\n \"]\"\n );\n if (parsed <= previous) {\n throw new InvalidConfigurationError(\n path +\n \" must be strictly increasing with no duplicates.\"\n );\n }\n if (\n parsed < range.minimum ||\n parsed > range.maximum\n ) {\n throw new InvalidConfigurationError(\n path +\n \" values must lie inside the declared capability range.\"\n );\n }\n previous = parsed;\n return parsed;\n }\n );\n}\n\nfunction parseSettingGrid(\n value: unknown,\n range: NumericCapabilityRange,\n path: string\n): NumericSettingGrid {\n const record = requireRecord(\n value,\n path, null\n );\n rejectUnknownVariantFields(record, \"kind\", { \"continuous-within-range\": [\"kind\"], \"discrete-values\": [\"kind\", \"values\"] }, path);\n\n if (\n record.kind ===\n \"continuous-within-range\"\n ) {\n return {\n kind:\n \"continuous-within-range\"\n };\n }\n\n if (\n record.kind ===\n \"discrete-values\"\n ) {\n const valuesFact =\n requireRecord(\n record.values,\n path + \".values\", [\"value\", \"evidence\"]\n );\n return {\n kind: \"discrete-values\",\n values: {\n value: parseDiscreteValues(\n valuesFact.value,\n range,\n path +\n \".values.value\"\n ),\n evidence: parseEvidenceList(\n valuesFact.evidence,\n path +\n \".values.evidence\"\n )\n }\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\nfunction requireApproximationProfileHeader(\n record: UnknownRecord,\n path: string\n): {\n profileId: string;\n profileVersion: string;\n evidence:\n readonly EvidenceProvenance[];\n} {\n if (\n record.schemaVersion !==\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n path +\n '.schemaVersion must be \"' +\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION +\n '\".'\n );\n }\n if (\n record.scientificStatus !==\n \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n path +\n '.scientificStatus must be \"approximation\" in schema 0.1.0.'\n );\n }\n\n return {\n profileId:\n requireNonEmptyString(\n record.profileId,\n path + \".profileId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n path + \".profileVersion\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\n/**\n * Parses a generic Photivra body exposure-capability profile.\n *\n * This is capability metadata only. ISO values say nothing about how noise or\n * gain is implemented. Shutter-duration capability does not imply a\n * particular shutter mechanism or sensor-readout schedule.\n */\nexport function parseGenericBodyExposureCapabilityProfile(\n value: unknown\n): GenericBodyExposureCapabilityProfile {\n const record = requireRecord(\n value,\n \"genericBodyExposureCapabilityProfile\", [\"schemaVersion\", \"scientificStatus\", \"profileId\", \"profileVersion\", \"evidence\", \"shutter\", \"iso\"]\n );\n const header =\n requireApproximationProfileHeader(\n record,\n \"genericBodyExposureCapabilityProfile\"\n );\n\n const shutter = requireRecord(\n record.shutter,\n \"genericBodyExposureCapabilityProfile.shutter\", [\"durationSecondsRange\", \"settingGrid\"]\n );\n const shutterRange =\n parseSourcedRange(\n shutter.durationSecondsRange,\n \"genericBodyExposureCapabilityProfile.shutter.durationSecondsRange\"\n );\n\n const iso = requireRecord(\n record.iso,\n \"genericBodyExposureCapabilityProfile.iso\", [\"range\", \"settingGrid\", \"autoIso\"]\n );\n const isoRange =\n parseSourcedRange(\n iso.range,\n \"genericBodyExposureCapabilityProfile.iso.range\"\n );\n\n return {\n schemaVersion:\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION,\n ...header,\n scientificStatus:\n \"approximation\",\n shutter: {\n durationSecondsRange:\n shutterRange,\n settingGrid:\n parseSettingGrid(\n shutter.settingGrid,\n shutterRange.value,\n \"genericBodyExposureCapabilityProfile.shutter.settingGrid\"\n )\n },\n iso: {\n range: isoRange,\n settingGrid:\n parseSettingGrid(\n iso.settingGrid,\n isoRange.value,\n \"genericBodyExposureCapabilityProfile.iso.settingGrid\"\n ),\n autoIso:\n parseSourcedAvailability(\n iso.autoIso,\n \"genericBodyExposureCapabilityProfile.iso.autoIso\"\n )\n }\n };\n}\n\nfunction parseFocalSamples(\n value: unknown,\n focalRange:\n NumericCapabilityRange,\n path: string\n): readonly FocalLengthFNumberSample[] {\n if (\n !Array.isArray(value) ||\n value.length < 2\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must contain at least two samples.\"\n );\n }\n\n let previousFocal =\n Number.NEGATIVE_INFINITY;\n const samples = value.map(\n (entry, index) => {\n const samplePath =\n path +\n \"[\" +\n index +\n \"]\";\n const record = requireRecord(\n entry,\n samplePath, [\"focalLengthMm\", \"fNumber\"]\n );\n const focalLengthMm =\n requirePositiveFinite(\n record.focalLengthMm,\n samplePath +\n \".focalLengthMm\"\n );\n if (\n focalLengthMm <=\n previousFocal\n ) {\n throw new InvalidConfigurationError(\n path +\n \" focal lengths must be strictly increasing.\"\n );\n }\n previousFocal =\n focalLengthMm;\n return {\n focalLengthMm,\n fNumber:\n requirePositiveFinite(\n record.fNumber,\n samplePath +\n \".fNumber\"\n )\n };\n }\n );\n\n if (\n samples[0]!.focalLengthMm !==\n focalRange.minimum ||\n samples[\n samples.length - 1\n ]!.focalLengthMm !==\n focalRange.maximum\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must begin at the declared minimum focal length and end at the declared maximum focal length.\"\n );\n }\n\n return samples;\n}\n\nfunction parseWidestFNumberCapability(\n value: unknown,\n focalRange:\n NumericCapabilityRange,\n narrowestFNumber: number,\n path: string\n): WidestAvailableFNumberCapability {\n const record = requireRecord(\n value,\n path, null\n );\n rejectUnknownVariantFields(record, \"kind\", { constant: [\"kind\", \"fNumber\"], \"piecewise-linear-by-focal-length\": [\"kind\", \"samples\"] }, path);\n\n if (record.kind === \"constant\") {\n const fNumber =\n parseSourcedPositiveNumber(\n record.fNumber,\n path + \".fNumber\"\n );\n if (\n fNumber.value >\n narrowestFNumber\n ) {\n throw new InvalidConfigurationError(\n path +\n \".fNumber must not exceed narrowestAvailableFNumber.\"\n );\n }\n return {\n kind: \"constant\",\n fNumber\n };\n }\n\n if (\n record.kind ===\n \"piecewise-linear-by-focal-length\"\n ) {\n const samplesFact =\n requireRecord(\n record.samples,\n path + \".samples\", [\"value\", \"evidence\"]\n );\n const samples =\n parseFocalSamples(\n samplesFact.value,\n focalRange,\n path +\n \".samples.value\"\n );\n if (\n samples.some(\n (sample) =>\n sample.fNumber >\n narrowestFNumber\n )\n ) {\n throw new InvalidConfigurationError(\n path +\n \" sample f-numbers must not exceed narrowestAvailableFNumber.\"\n );\n }\n return {\n kind:\n \"piecewise-linear-by-focal-length\",\n samples: {\n value: samples,\n evidence: parseEvidenceList(\n samplesFact.evidence,\n path +\n \".samples.evidence\"\n )\n }\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\n/**\n * Parses a generic Photivra lens exposure-capability profile.\n *\n * \"Widest\" means the smallest available f-number. This avoids the ambiguous\n * phrases \"minimum aperture\" and \"maximum aperture\".\n */\nexport function parseGenericLensExposureCapabilityProfile(\n value: unknown\n): GenericLensExposureCapabilityProfile {\n const record = requireRecord(\n value,\n \"genericLensExposureCapabilityProfile\", [\"schemaVersion\", \"scientificStatus\", \"profileId\", \"profileVersion\", \"evidence\", \"focalLengthMmRange\", \"aperture\"]\n );\n const header =\n requireApproximationProfileHeader(\n record,\n \"genericLensExposureCapabilityProfile\"\n );\n\n const focalRange =\n parseSourcedRange(\n record.focalLengthMmRange,\n \"genericLensExposureCapabilityProfile.focalLengthMmRange\"\n );\n const aperture = requireRecord(\n record.aperture,\n \"genericLensExposureCapabilityProfile.aperture\", [\"narrowestAvailableFNumber\", \"widestAvailableFNumber\", \"settingGrid\"]\n );\n const narrowest =\n parseSourcedPositiveNumber(\n aperture.narrowestAvailableFNumber,\n \"genericLensExposureCapabilityProfile.aperture.narrowestAvailableFNumber\"\n );\n const widest =\n parseWidestFNumberCapability(\n aperture.widestAvailableFNumber,\n focalRange.value,\n narrowest.value,\n \"genericLensExposureCapabilityProfile.aperture.widestAvailableFNumber\"\n );\n\n const broadApertureRange = {\n minimum:\n widest.kind === \"constant\"\n ? widest.fNumber.value\n : Math.min(\n ...widest.samples.value.map(\n (sample) =>\n sample.fNumber\n )\n ),\n maximum: narrowest.value\n };\n\n return {\n schemaVersion:\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION,\n ...header,\n scientificStatus:\n \"approximation\",\n focalLengthMmRange:\n focalRange,\n aperture: {\n widestAvailableFNumber:\n widest,\n narrowestAvailableFNumber:\n narrowest,\n settingGrid:\n parseSettingGrid(\n aperture.settingGrid,\n broadApertureRange,\n \"genericLensExposureCapabilityProfile.aperture.settingGrid\"\n )\n }\n };\n}\n\nfunction interpolateWidestFNumber(\n capability:\n WidestAvailableFNumberCapability,\n selectedFocalLengthMm: number\n): number {\n if (capability.kind === \"constant\") {\n return capability.fNumber.value;\n }\n\n const samples =\n capability.samples.value;\n for (\n let index = 1;\n index < samples.length;\n index += 1\n ) {\n const right = samples[index]!;\n if (\n selectedFocalLengthMm <=\n right.focalLengthMm\n ) {\n const left =\n samples[index - 1]!;\n const span =\n right.focalLengthMm -\n left.focalLengthMm;\n const phase =\n (selectedFocalLengthMm -\n left.focalLengthMm) /\n span;\n return (\n left.fNumber +\n phase *\n (right.fNumber -\n left.fNumber)\n );\n }\n }\n\n return samples[\n samples.length - 1\n ]!.fNumber;\n}\n\nfunction resolveGrid(\n grid: NumericSettingGrid,\n minimum: number,\n maximum: number,\n path: string\n): ResolvedNumericSettingGrid {\n if (\n grid.kind ===\n \"continuous-within-range\"\n ) {\n return {\n kind:\n \"continuous-within-range\"\n };\n }\n\n const values =\n grid.values.value.filter(\n (value) =>\n value >= minimum &&\n value <= maximum\n );\n if (values.length === 0) {\n throw new InvalidScientificInputError(\n path +\n \" has no discrete values inside the resolved capability range.\"\n );\n }\n\n return {\n kind: \"discrete-values\",\n values\n };\n}\n\n/**\n * Resolves the exposure-relevant capability subset for one generic body+lens\n * combination at one selected focal length.\n *\n * The source profiles are not changed. This resolver does not choose exposure\n * settings. It only provides the range of available settings (the capability\n * envelope) that the exposure-mode resolvers use.\n */\nexport function resolveGenericEquipmentExposureCapabilities(\n input:\n ResolveGenericEquipmentExposureCapabilitiesInput\n): ResolvedGenericEquipmentExposureCapabilities {\n requireInputKeys(input, \"resolveGenericEquipmentExposureCapabilities\", [\"bodyProfile\", \"lensProfile\", \"selectedFocalLengthMm\"]);\n const body =\n parseGenericBodyExposureCapabilityProfile(\n input.bodyProfile\n );\n const lens =\n parseGenericLensExposureCapabilityProfile(\n input.lensProfile\n );\n\n if (\n typeof input.selectedFocalLengthMm !==\n \"number\" ||\n !Number.isFinite(\n input.selectedFocalLengthMm\n ) ||\n input.selectedFocalLengthMm <= 0\n ) {\n throw new InvalidScientificInputError(\n \"selectedFocalLengthMm must be finite and greater than zero.\"\n );\n }\n\n const focalRange =\n lens.focalLengthMmRange.value;\n if (\n input.selectedFocalLengthMm <\n focalRange.minimum ||\n input.selectedFocalLengthMm >\n focalRange.maximum\n ) {\n throw new InvalidScientificInputError(\n \"selectedFocalLengthMm lies outside the lens capability range.\"\n );\n }\n\n const widest =\n interpolateWidestFNumber(\n lens.aperture\n .widestAvailableFNumber,\n input.selectedFocalLengthMm\n );\n const narrowest =\n lens.aperture\n .narrowestAvailableFNumber\n .value;\n\n if (\n !Number.isFinite(widest) ||\n widest <= 0 ||\n widest > narrowest\n ) {\n throw new InvalidScientificInputError(\n \"Resolved aperture range is invalid.\"\n );\n }\n\n const shutterRange =\n body.shutter\n .durationSecondsRange.value;\n const isoRange =\n body.iso.range.value;\n\n return {\n schemaVersion:\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION,\n scientificStatus:\n \"approximation\",\n bodyProfile: {\n profileId: body.profileId,\n profileVersion:\n body.profileVersion\n },\n lensProfile: {\n profileId: lens.profileId,\n profileVersion:\n lens.profileVersion\n },\n selectedFocalLengthMm:\n input.selectedFocalLengthMm,\n aperture: {\n widestAvailableFNumber:\n widest,\n narrowestAvailableFNumber:\n narrowest,\n settingGrid:\n resolveGrid(\n lens.aperture\n .settingGrid,\n widest,\n narrowest,\n \"lens aperture setting grid\"\n )\n },\n shutter: {\n minimumSeconds:\n shutterRange.minimum,\n maximumSeconds:\n shutterRange.maximum,\n settingGrid:\n resolveGrid(\n body.shutter.settingGrid,\n shutterRange.minimum,\n shutterRange.maximum,\n \"body shutter setting grid\"\n )\n },\n iso: {\n minimum: isoRange.minimum,\n maximum: isoRange.maximum,\n settingGrid:\n resolveGrid(\n body.iso.settingGrid,\n isoRange.minimum,\n isoRange.maximum,\n \"body ISO setting grid\"\n ),\n autoIsoAvailability:\n body.iso.autoIso.value\n },\n sourceProfilesMutated: false,\n exactNamedEquipmentEmulationClaimed:\n false\n };\n}\n"]}
1
+ {"version":3,"file":"exposure-capabilities.js","sourceRoot":"","sources":["../../src/equipment/exposure-capabilities.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Exposure capabilities of generic Photivra camera bodies and lenses: which ISO, shutter and aperture\n * settings they offer.\n *\n * This is capability metadata only. ISO values say nothing about how noise or gain is implemented.\n * Shutter-duration capability does not imply a particular shutter mechanism or sensor-readout\n * schedule.\n *\n * For lenses, \"widest\" means the smallest available f-number. This avoids the ambiguous phrases\n * \"minimum aperture\" and \"maximum aperture\".\n * @see docs/GENERIC_TIER_PRESETS.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, rejectUnknownVariantFields, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, requireConfigPositiveFinite as requirePositiveFinite, parseConfigAvailability as parseAvailability } from \"../core/record-validation.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport const GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION =\n \"0.1.0\" as const;\n\nexport type GenericCapabilityAvailability =\n | \"supported\"\n | \"unsupported\"\n | \"unknown\";\n\nexport interface NumericCapabilityRange {\n minimum: number;\n maximum: number;\n}\n\nexport type NumericSettingGrid =\n | {\n kind: \"continuous-within-range\";\n }\n | {\n kind: \"discrete-values\";\n values:\n EvidenceBackedFact<\n readonly number[]\n >;\n };\n\nexport interface GenericBodyExposureCapabilityProfile {\n schemaVersion:\n typeof GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n scientificStatus: \"approximation\";\n evidence: readonly EvidenceProvenance[];\n shutter: {\n durationSecondsRange:\n EvidenceBackedFact<\n NumericCapabilityRange\n >;\n settingGrid: NumericSettingGrid;\n };\n iso: {\n range:\n EvidenceBackedFact<\n NumericCapabilityRange\n >;\n settingGrid: NumericSettingGrid;\n autoIso:\n EvidenceBackedFact<\n GenericCapabilityAvailability\n >;\n };\n}\n\nexport interface FocalLengthFNumberSample {\n focalLengthMm: number;\n fNumber: number;\n}\n\nexport type WidestAvailableFNumberCapability =\n | {\n kind: \"constant\";\n fNumber:\n EvidenceBackedFact<number>;\n }\n | {\n kind:\n \"piecewise-linear-by-focal-length\";\n samples:\n EvidenceBackedFact<\n readonly FocalLengthFNumberSample[]\n >;\n };\n\nexport interface GenericLensExposureCapabilityProfile {\n schemaVersion:\n typeof GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n scientificStatus: \"approximation\";\n evidence: readonly EvidenceProvenance[];\n focalLengthMmRange:\n EvidenceBackedFact<\n NumericCapabilityRange\n >;\n aperture: {\n widestAvailableFNumber:\n WidestAvailableFNumberCapability;\n narrowestAvailableFNumber:\n EvidenceBackedFact<number>;\n settingGrid: NumericSettingGrid;\n };\n}\n\nexport type ResolvedNumericSettingGrid =\n | {\n kind: \"continuous-within-range\";\n }\n | {\n kind: \"discrete-values\";\n values: readonly number[];\n };\n\nexport interface ResolvedGenericEquipmentExposureCapabilities {\n schemaVersion:\n typeof GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION;\n scientificStatus: \"approximation\";\n bodyProfile: {\n profileId: string;\n profileVersion: string;\n };\n lensProfile: {\n profileId: string;\n profileVersion: string;\n };\n selectedFocalLengthMm: number;\n aperture: {\n widestAvailableFNumber: number;\n narrowestAvailableFNumber: number;\n settingGrid:\n ResolvedNumericSettingGrid;\n };\n shutter: {\n minimumSeconds: number;\n maximumSeconds: number;\n settingGrid:\n ResolvedNumericSettingGrid;\n };\n iso: {\n minimum: number;\n maximum: number;\n settingGrid:\n ResolvedNumericSettingGrid;\n autoIsoAvailability:\n GenericCapabilityAvailability;\n /**\n * Optional bounds that apply only to Auto ISO. Manual ISO still uses the\n * minimum, the maximum and the complete standard setting grid.\n */\n autoIsoMinimum?: number;\n autoIsoMaximum?: number;\n };\n sourceProfilesMutated: false;\n exactNamedEquipmentEmulationClaimed:\n false;\n}\n\nexport interface ResolveGenericEquipmentExposureCapabilitiesInput {\n bodyProfile:\n GenericBodyExposureCapabilityProfile;\n lensProfile:\n GenericLensExposureCapabilityProfile;\n selectedFocalLengthMm: number;\n}\n\nexport function parseRange(\n value: unknown,\n path: string\n): NumericCapabilityRange {\n const record = requireRecord(\n value,\n path, [\"minimum\", \"maximum\"]\n );\n const minimum =\n requirePositiveFinite(\n record.minimum,\n path + \".minimum\"\n );\n const maximum =\n requirePositiveFinite(\n record.maximum,\n path + \".maximum\"\n );\n if (minimum > maximum) {\n throw new InvalidConfigurationError(\n path +\n \".minimum must be less than or equal to maximum.\"\n );\n }\n return {\n minimum,\n maximum\n };\n}\n\nfunction parseSourcedRange(\n value: unknown,\n path: string\n): EvidenceBackedFact<\n NumericCapabilityRange\n> {\n const record = requireRecord(\n value,\n path, [\"value\", \"evidence\"]\n );\n return {\n value: parseRange(\n record.value,\n path + \".value\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\nfunction parseSourcedPositiveNumber(\n value: unknown,\n path: string\n): EvidenceBackedFact<number> {\n const record = requireRecord(\n value,\n path, [\"value\", \"evidence\"]\n );\n return {\n value: requirePositiveFinite(\n record.value,\n path + \".value\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\nfunction parseSourcedAvailability(\n value: unknown,\n path: string\n): EvidenceBackedFact<\n GenericCapabilityAvailability\n> {\n const record = requireRecord(\n value,\n path, [\"value\", \"evidence\"]\n );\n return {\n value: parseAvailability(\n record.value,\n path + \".value\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\nfunction parseDiscreteValues(\n value: unknown,\n range: NumericCapabilityRange,\n path: string\n): readonly number[] {\n if (\n !Array.isArray(value) ||\n value.length === 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must be a non-empty array.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n return value.map(\n (entry, index) => {\n const parsed =\n requirePositiveFinite(\n entry,\n path +\n \"[\" +\n index +\n \"]\"\n );\n if (parsed <= previous) {\n throw new InvalidConfigurationError(\n path +\n \" must be strictly increasing with no duplicates.\"\n );\n }\n if (\n parsed < range.minimum ||\n parsed > range.maximum\n ) {\n throw new InvalidConfigurationError(\n path +\n \" values must lie inside the declared capability range.\"\n );\n }\n previous = parsed;\n return parsed;\n }\n );\n}\n\nfunction parseSettingGrid(\n value: unknown,\n range: NumericCapabilityRange,\n path: string\n): NumericSettingGrid {\n const record = requireRecord(\n value,\n path, null\n );\n rejectUnknownVariantFields(record, \"kind\", { \"continuous-within-range\": [\"kind\"], \"discrete-values\": [\"kind\", \"values\"] }, path);\n\n if (\n record.kind ===\n \"continuous-within-range\"\n ) {\n return {\n kind:\n \"continuous-within-range\"\n };\n }\n\n if (\n record.kind ===\n \"discrete-values\"\n ) {\n const valuesFact =\n requireRecord(\n record.values,\n path + \".values\", [\"value\", \"evidence\"]\n );\n return {\n kind: \"discrete-values\",\n values: {\n value: parseDiscreteValues(\n valuesFact.value,\n range,\n path +\n \".values.value\"\n ),\n evidence: parseEvidenceList(\n valuesFact.evidence,\n path +\n \".values.evidence\"\n )\n }\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\nfunction requireApproximationProfileHeader(\n record: UnknownRecord,\n path: string\n): {\n profileId: string;\n profileVersion: string;\n evidence:\n readonly EvidenceProvenance[];\n} {\n if (\n record.schemaVersion !==\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n path +\n '.schemaVersion must be \"' +\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION +\n '\".'\n );\n }\n if (\n record.scientificStatus !==\n \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n path +\n '.scientificStatus must be \"approximation\" in schema 0.1.0.'\n );\n }\n\n return {\n profileId:\n requireNonEmptyString(\n record.profileId,\n path + \".profileId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n path + \".profileVersion\"\n ),\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\n/**\n * Parses a generic Photivra body exposure-capability profile.\n *\n * This is capability metadata only. ISO values say nothing about how noise or\n * gain is implemented. Shutter-duration capability does not imply a\n * particular shutter mechanism or sensor-readout schedule.\n */\nexport function parseGenericBodyExposureCapabilityProfile(\n value: unknown\n): GenericBodyExposureCapabilityProfile {\n const record = requireRecord(\n value,\n \"genericBodyExposureCapabilityProfile\", [\"schemaVersion\", \"scientificStatus\", \"profileId\", \"profileVersion\", \"evidence\", \"shutter\", \"iso\"]\n );\n const header =\n requireApproximationProfileHeader(\n record,\n \"genericBodyExposureCapabilityProfile\"\n );\n\n const shutter = requireRecord(\n record.shutter,\n \"genericBodyExposureCapabilityProfile.shutter\", [\"durationSecondsRange\", \"settingGrid\"]\n );\n const shutterRange =\n parseSourcedRange(\n shutter.durationSecondsRange,\n \"genericBodyExposureCapabilityProfile.shutter.durationSecondsRange\"\n );\n\n const iso = requireRecord(\n record.iso,\n \"genericBodyExposureCapabilityProfile.iso\", [\"range\", \"settingGrid\", \"autoIso\"]\n );\n const isoRange =\n parseSourcedRange(\n iso.range,\n \"genericBodyExposureCapabilityProfile.iso.range\"\n );\n\n return {\n schemaVersion:\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION,\n ...header,\n scientificStatus:\n \"approximation\",\n shutter: {\n durationSecondsRange:\n shutterRange,\n settingGrid:\n parseSettingGrid(\n shutter.settingGrid,\n shutterRange.value,\n \"genericBodyExposureCapabilityProfile.shutter.settingGrid\"\n )\n },\n iso: {\n range: isoRange,\n settingGrid:\n parseSettingGrid(\n iso.settingGrid,\n isoRange.value,\n \"genericBodyExposureCapabilityProfile.iso.settingGrid\"\n ),\n autoIso:\n parseSourcedAvailability(\n iso.autoIso,\n \"genericBodyExposureCapabilityProfile.iso.autoIso\"\n )\n }\n };\n}\n\nfunction parseFocalSamples(\n value: unknown,\n focalRange:\n NumericCapabilityRange,\n path: string\n): readonly FocalLengthFNumberSample[] {\n if (\n !Array.isArray(value) ||\n value.length < 2\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must contain at least two samples.\"\n );\n }\n\n let previousFocal =\n Number.NEGATIVE_INFINITY;\n const samples = value.map(\n (entry, index) => {\n const samplePath =\n path +\n \"[\" +\n index +\n \"]\";\n const record = requireRecord(\n entry,\n samplePath, [\"focalLengthMm\", \"fNumber\"]\n );\n const focalLengthMm =\n requirePositiveFinite(\n record.focalLengthMm,\n samplePath +\n \".focalLengthMm\"\n );\n if (\n focalLengthMm <=\n previousFocal\n ) {\n throw new InvalidConfigurationError(\n path +\n \" focal lengths must be strictly increasing.\"\n );\n }\n previousFocal =\n focalLengthMm;\n return {\n focalLengthMm,\n fNumber:\n requirePositiveFinite(\n record.fNumber,\n samplePath +\n \".fNumber\"\n )\n };\n }\n );\n\n if (\n samples[0]!.focalLengthMm !==\n focalRange.minimum ||\n samples[\n samples.length - 1\n ]!.focalLengthMm !==\n focalRange.maximum\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must begin at the declared minimum focal length and end at the declared maximum focal length.\"\n );\n }\n\n return samples;\n}\n\nfunction parseWidestFNumberCapability(\n value: unknown,\n focalRange:\n NumericCapabilityRange,\n narrowestFNumber: number,\n path: string\n): WidestAvailableFNumberCapability {\n const record = requireRecord(\n value,\n path, null\n );\n rejectUnknownVariantFields(record, \"kind\", { constant: [\"kind\", \"fNumber\"], \"piecewise-linear-by-focal-length\": [\"kind\", \"samples\"] }, path);\n\n if (record.kind === \"constant\") {\n const fNumber =\n parseSourcedPositiveNumber(\n record.fNumber,\n path + \".fNumber\"\n );\n if (\n fNumber.value >\n narrowestFNumber\n ) {\n throw new InvalidConfigurationError(\n path +\n \".fNumber must not exceed narrowestAvailableFNumber.\"\n );\n }\n return {\n kind: \"constant\",\n fNumber\n };\n }\n\n if (\n record.kind ===\n \"piecewise-linear-by-focal-length\"\n ) {\n const samplesFact =\n requireRecord(\n record.samples,\n path + \".samples\", [\"value\", \"evidence\"]\n );\n const samples =\n parseFocalSamples(\n samplesFact.value,\n focalRange,\n path +\n \".samples.value\"\n );\n if (\n samples.some(\n (sample) =>\n sample.fNumber >\n narrowestFNumber\n )\n ) {\n throw new InvalidConfigurationError(\n path +\n \" sample f-numbers must not exceed narrowestAvailableFNumber.\"\n );\n }\n return {\n kind:\n \"piecewise-linear-by-focal-length\",\n samples: {\n value: samples,\n evidence: parseEvidenceList(\n samplesFact.evidence,\n path +\n \".samples.evidence\"\n )\n }\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\n/**\n * Parses a generic Photivra lens exposure-capability profile.\n *\n * \"Widest\" means the smallest available f-number. This avoids the ambiguous\n * phrases \"minimum aperture\" and \"maximum aperture\".\n */\nexport function parseGenericLensExposureCapabilityProfile(\n value: unknown\n): GenericLensExposureCapabilityProfile {\n const record = requireRecord(\n value,\n \"genericLensExposureCapabilityProfile\", [\"schemaVersion\", \"scientificStatus\", \"profileId\", \"profileVersion\", \"evidence\", \"focalLengthMmRange\", \"aperture\"]\n );\n const header =\n requireApproximationProfileHeader(\n record,\n \"genericLensExposureCapabilityProfile\"\n );\n\n const focalRange =\n parseSourcedRange(\n record.focalLengthMmRange,\n \"genericLensExposureCapabilityProfile.focalLengthMmRange\"\n );\n const aperture = requireRecord(\n record.aperture,\n \"genericLensExposureCapabilityProfile.aperture\", [\"narrowestAvailableFNumber\", \"widestAvailableFNumber\", \"settingGrid\"]\n );\n const narrowest =\n parseSourcedPositiveNumber(\n aperture.narrowestAvailableFNumber,\n \"genericLensExposureCapabilityProfile.aperture.narrowestAvailableFNumber\"\n );\n const widest =\n parseWidestFNumberCapability(\n aperture.widestAvailableFNumber,\n focalRange.value,\n narrowest.value,\n \"genericLensExposureCapabilityProfile.aperture.widestAvailableFNumber\"\n );\n\n const broadApertureRange = {\n minimum:\n widest.kind === \"constant\"\n ? widest.fNumber.value\n : Math.min(\n ...widest.samples.value.map(\n (sample) =>\n sample.fNumber\n )\n ),\n maximum: narrowest.value\n };\n\n return {\n schemaVersion:\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION,\n ...header,\n scientificStatus:\n \"approximation\",\n focalLengthMmRange:\n focalRange,\n aperture: {\n widestAvailableFNumber:\n widest,\n narrowestAvailableFNumber:\n narrowest,\n settingGrid:\n parseSettingGrid(\n aperture.settingGrid,\n broadApertureRange,\n \"genericLensExposureCapabilityProfile.aperture.settingGrid\"\n )\n }\n };\n}\n\nfunction interpolateWidestFNumber(\n capability:\n WidestAvailableFNumberCapability,\n selectedFocalLengthMm: number\n): number {\n if (capability.kind === \"constant\") {\n return capability.fNumber.value;\n }\n\n const samples =\n capability.samples.value;\n for (\n let index = 1;\n index < samples.length;\n index += 1\n ) {\n const right = samples[index]!;\n if (\n selectedFocalLengthMm <=\n right.focalLengthMm\n ) {\n const left =\n samples[index - 1]!;\n const span =\n right.focalLengthMm -\n left.focalLengthMm;\n const phase =\n (selectedFocalLengthMm -\n left.focalLengthMm) /\n span;\n return (\n left.fNumber +\n phase *\n (right.fNumber -\n left.fNumber)\n );\n }\n }\n\n return samples[\n samples.length - 1\n ]!.fNumber;\n}\n\nfunction resolveGrid(\n grid: NumericSettingGrid,\n minimum: number,\n maximum: number,\n path: string\n): ResolvedNumericSettingGrid {\n if (\n grid.kind ===\n \"continuous-within-range\"\n ) {\n return {\n kind:\n \"continuous-within-range\"\n };\n }\n\n const values =\n grid.values.value.filter(\n (value) =>\n value >= minimum &&\n value <= maximum\n );\n if (values.length === 0) {\n throw new InvalidScientificInputError(\n path +\n \" has no discrete values inside the resolved capability range.\"\n );\n }\n\n return {\n kind: \"discrete-values\",\n values\n };\n}\n\n/**\n * Resolves the exposure-relevant capability subset for one generic body+lens\n * combination at one selected focal length.\n *\n * The source profiles are not changed. This resolver does not choose exposure\n * settings. It only provides the range of available settings (the capability\n * envelope) that the exposure-mode resolvers use.\n */\nexport function resolveGenericEquipmentExposureCapabilities(\n input:\n ResolveGenericEquipmentExposureCapabilitiesInput\n): ResolvedGenericEquipmentExposureCapabilities {\n requireInputKeys(input, \"resolveGenericEquipmentExposureCapabilities\", [\"bodyProfile\", \"lensProfile\", \"selectedFocalLengthMm\"]);\n const body =\n parseGenericBodyExposureCapabilityProfile(\n input.bodyProfile\n );\n const lens =\n parseGenericLensExposureCapabilityProfile(\n input.lensProfile\n );\n\n if (\n typeof input.selectedFocalLengthMm !==\n \"number\" ||\n !Number.isFinite(\n input.selectedFocalLengthMm\n ) ||\n input.selectedFocalLengthMm <= 0\n ) {\n throw new InvalidScientificInputError(\n \"selectedFocalLengthMm must be finite and greater than zero.\"\n );\n }\n\n const focalRange =\n lens.focalLengthMmRange.value;\n if (\n input.selectedFocalLengthMm <\n focalRange.minimum ||\n input.selectedFocalLengthMm >\n focalRange.maximum\n ) {\n throw new InvalidScientificInputError(\n \"selectedFocalLengthMm lies outside the lens capability range.\"\n );\n }\n\n const widest =\n interpolateWidestFNumber(\n lens.aperture\n .widestAvailableFNumber,\n input.selectedFocalLengthMm\n );\n const narrowest =\n lens.aperture\n .narrowestAvailableFNumber\n .value;\n\n if (\n !Number.isFinite(widest) ||\n widest <= 0 ||\n widest > narrowest\n ) {\n throw new InvalidScientificInputError(\n \"Resolved aperture range is invalid.\"\n );\n }\n\n const shutterRange =\n body.shutter\n .durationSecondsRange.value;\n const isoRange =\n body.iso.range.value;\n\n return {\n schemaVersion:\n GENERIC_EQUIPMENT_EXPOSURE_CAPABILITY_SCHEMA_VERSION,\n scientificStatus:\n \"approximation\",\n bodyProfile: {\n profileId: body.profileId,\n profileVersion:\n body.profileVersion\n },\n lensProfile: {\n profileId: lens.profileId,\n profileVersion:\n lens.profileVersion\n },\n selectedFocalLengthMm:\n input.selectedFocalLengthMm,\n aperture: {\n widestAvailableFNumber:\n widest,\n narrowestAvailableFNumber:\n narrowest,\n settingGrid:\n resolveGrid(\n lens.aperture\n .settingGrid,\n widest,\n narrowest,\n \"lens aperture setting grid\"\n )\n },\n shutter: {\n minimumSeconds:\n shutterRange.minimum,\n maximumSeconds:\n shutterRange.maximum,\n settingGrid:\n resolveGrid(\n body.shutter.settingGrid,\n shutterRange.minimum,\n shutterRange.maximum,\n \"body shutter setting grid\"\n )\n },\n iso: {\n minimum: isoRange.minimum,\n maximum: isoRange.maximum,\n settingGrid:\n resolveGrid(\n body.iso.settingGrid,\n isoRange.minimum,\n isoRange.maximum,\n \"body ISO setting grid\"\n ),\n autoIsoAvailability:\n body.iso.autoIso.value\n },\n sourceProfilesMutated: false,\n exactNamedEquipmentEmulationClaimed:\n false\n };\n}\n"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Ideal lens (#576): the bounds of `simulateCapture`'s and the display view's `lens.ideal`, a plain aberration-free thin lens
3
+ * named by its physical focal length, on any sensor. Its blur is that of the options the request gives, the same models as with
4
+ * `lens.prime`: `lens.defocus` (the thin-lens blur circle, `calculateDefocusCircle`), `lens.diffraction` (the Airy pattern of a
5
+ * circular pupil) and `lens.vignetting` (a caller profile). Without them it renders sharp and evenly lit.
6
+ *
7
+ * - `fNumber.minimum` 0.5 is a physical limit: a lens in air that satisfies the Abbe sine condition (aplanatic) has
8
+ * N = 1/(2·n·sin θ′) with n = 1 and sin θ′ ≤ 1, so N ≥ 0.5 (the sine condition: Born & Wolf, *Principles of Optics*, 7th ed.;
9
+ * Kingslake & Johnson, *Lens Design Fundamentals*, 2nd ed.). The engine's thin-lens defocus and scalar Airy diffraction
10
+ * are paraxial models, so near f/0.5 they are approximations further from a real lens than at f/2.
11
+ * - `fNumber.maximum` 128 and both focal-length bounds are engine bounds, not physical limits: 1 mm is shorter than phone
12
+ * camera lenses, 4000 mm longer than photographic lenses, and f/128 beyond large-format lenses' smallest stops.
13
+ * - The ideal lens's f-number range is the whole of `fNumber`: manual and aperture priority take any `lens.fNumber` in it, and
14
+ * shutter priority and program may choose any aperture in it (in dim light, wider than real lenses open).
15
+ */
16
+ export declare const IDEAL_LENS_LIMITS: Readonly<{
17
+ focalLengthMm: Readonly<{
18
+ minimum: 1;
19
+ maximum: 4000;
20
+ }>;
21
+ fNumber: Readonly<{
22
+ minimum: 0.5;
23
+ maximum: 128;
24
+ }>;
25
+ }>;
@@ -0,0 +1,21 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Ideal lens (#576): the bounds of `simulateCapture`'s and the display view's `lens.ideal`, a plain aberration-free thin lens
4
+ * named by its physical focal length, on any sensor. Its blur is that of the options the request gives, the same models as with
5
+ * `lens.prime`: `lens.defocus` (the thin-lens blur circle, `calculateDefocusCircle`), `lens.diffraction` (the Airy pattern of a
6
+ * circular pupil) and `lens.vignetting` (a caller profile). Without them it renders sharp and evenly lit.
7
+ *
8
+ * - `fNumber.minimum` 0.5 is a physical limit: a lens in air that satisfies the Abbe sine condition (aplanatic) has
9
+ * N = 1/(2·n·sin θ′) with n = 1 and sin θ′ ≤ 1, so N ≥ 0.5 (the sine condition: Born & Wolf, *Principles of Optics*, 7th ed.;
10
+ * Kingslake & Johnson, *Lens Design Fundamentals*, 2nd ed.). The engine's thin-lens defocus and scalar Airy diffraction
11
+ * are paraxial models, so near f/0.5 they are approximations further from a real lens than at f/2.
12
+ * - `fNumber.maximum` 128 and both focal-length bounds are engine bounds, not physical limits: 1 mm is shorter than phone
13
+ * camera lenses, 4000 mm longer than photographic lenses, and f/128 beyond large-format lenses' smallest stops.
14
+ * - The ideal lens's f-number range is the whole of `fNumber`: manual and aperture priority take any `lens.fNumber` in it, and
15
+ * shutter priority and program may choose any aperture in it (in dim light, wider than real lenses open).
16
+ */
17
+ export const IDEAL_LENS_LIMITS = /* @__PURE__ */ Object.freeze({
18
+ focalLengthMm: Object.freeze({ minimum: 1, maximum: 4000 }),
19
+ fNumber: Object.freeze({ minimum: 0.5, maximum: 128 })
20
+ });
21
+ //# sourceMappingURL=ideal-lens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ideal-lens.js","sourceRoot":"","sources":["../../src/equipment/ideal-lens.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;IAC7D,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3D,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;CACvD,CAAC,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Ideal lens (#576): the bounds of `simulateCapture`'s and the display view's `lens.ideal`, a plain aberration-free thin lens\n * named by its physical focal length, on any sensor. Its blur is that of the options the request gives, the same models as with\n * `lens.prime`: `lens.defocus` (the thin-lens blur circle, `calculateDefocusCircle`), `lens.diffraction` (the Airy pattern of a\n * circular pupil) and `lens.vignetting` (a caller profile). Without them it renders sharp and evenly lit.\n *\n * - `fNumber.minimum` 0.5 is a physical limit: a lens in air that satisfies the Abbe sine condition (aplanatic) has\n * N = 1/(2·n·sin θ′) with n = 1 and sin θ′ ≤ 1, so N ≥ 0.5 (the sine condition: Born & Wolf, *Principles of Optics*, 7th ed.;\n * Kingslake & Johnson, *Lens Design Fundamentals*, 2nd ed.). The engine's thin-lens defocus and scalar Airy diffraction\n * are paraxial models, so near f/0.5 they are approximations further from a real lens than at f/2.\n * - `fNumber.maximum` 128 and both focal-length bounds are engine bounds, not physical limits: 1 mm is shorter than phone\n * camera lenses, 4000 mm longer than photographic lenses, and f/128 beyond large-format lenses' smallest stops.\n * - The ideal lens's f-number range is the whole of `fNumber`: manual and aperture priority take any `lens.fNumber` in it, and\n * shutter priority and program may choose any aperture in it (in dim light, wider than real lenses open).\n */\nexport const IDEAL_LENS_LIMITS = /* @__PURE__ */ Object.freeze({\n focalLengthMm: Object.freeze({ minimum: 1, maximum: 4000 }),\n fNumber: Object.freeze({ minimum: 0.5, maximum: 128 })\n});\n"]}