@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
@@ -1 +1 @@
1
- {"version":3,"file":"program-line.js","sourceRoot":"","sources":["../../src/exposure/program-line.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic exposure program lines, for teaching and for products. A program line is the curve of\n * aperture/shutter pairs a camera's Program mode follows.\n *\n * A program line is control policy, not physics. Each node declares a wanted aperture/shutter pair at\n * an explicit number of stops of optical exposure, relative to the resolver's reference exposure.\n * The resolver later checks that every node is physically consistent with that reference and with\n * what the equipment can do.\n * @see docs/USAGE.md for the equations, units, coordinate conventions, known blockers and support\n * limits.\n */\n\nimport { rejectUnknownFields } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport const EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION =\n \"0.1.0\" as const;\n\nexport interface ExposureProgramLineNode {\n nodeId: string;\n opticalExposureStopsFromReference: number;\n aperture: number;\n shutterSeconds: number;\n}\n\nexport interface ExposureProgramLineProfile {\n schemaVersion:\n typeof EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n scientificStatus: \"approximation\";\n policyKind:\n \"generic-program-line\";\n interpolation:\n \"log2-aperture-shutter\";\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n nodes:\n readonly ExposureProgramLineNode[];\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 requireFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be finite.\"\n );\n }\n return value;\n}\n\nfunction requirePositiveFinite(\n value: unknown,\n path: string\n): number {\n const parsed =\n requireFinite(value, path);\n if (parsed <= 0) {\n throw new InvalidConfigurationError(\n path +\n \" must be greater than zero.\"\n );\n }\n return parsed;\n}\n\nfunction parseLimitations(\n value: unknown,\n path: string\n): readonly string[] {\n if (!Array.isArray(value)) {\n throw new InvalidConfigurationError(\n path + \" must be an array.\"\n );\n }\n const parsed = value.map(\n (entry, index) =>\n requireNonEmptyString(\n entry,\n path + \"[\" + index + \"]\"\n )\n );\n if (\n new Set(parsed).size !==\n parsed.length\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must not contain duplicates.\"\n );\n }\n return parsed;\n}\n\n/**\n * Parses a generic educational/product exposure program line.\n *\n * A program line is control policy, not physics. Each node declares a wanted\n * aperture/shutter pair at an explicit number of stops of optical exposure,\n * relative to the resolver's reference exposure. The resolver later checks\n * that every node is physically consistent with that reference and with what\n * the equipment can do.\n */\nexport function parseExposureProgramLineProfile(\n value: unknown\n): ExposureProgramLineProfile {\n const record = requireRecord(\n value,\n \"exposureProgramLineProfile\", [\"schemaVersion\", \"scientificStatus\", \"policyKind\", \"interpolation\", \"nodes\", \"profileId\", \"profileVersion\", \"evidence\", \"limitations\"]\n );\n\n if (\n record.schemaVersion !==\n EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n 'exposureProgramLineProfile.schemaVersion must be \"' +\n EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION +\n '\".'\n );\n }\n if (\n record.scientificStatus !==\n \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n 'exposureProgramLineProfile.scientificStatus must be \"approximation\".'\n );\n }\n if (\n record.policyKind !==\n \"generic-program-line\"\n ) {\n throw new InvalidConfigurationError(\n 'exposureProgramLineProfile.policyKind must be \"generic-program-line\".'\n );\n }\n if (\n record.interpolation !==\n \"log2-aperture-shutter\"\n ) {\n throw new InvalidConfigurationError(\n 'exposureProgramLineProfile.interpolation must be \"log2-aperture-shutter\".'\n );\n }\n if (\n !Array.isArray(record.nodes) ||\n record.nodes.length < 2\n ) {\n throw new InvalidConfigurationError(\n \"exposureProgramLineProfile.nodes must contain at least two nodes.\"\n );\n }\n\n let previousStops =\n Number.NEGATIVE_INFINITY;\n const ids: string[] = [];\n\n const nodes = record.nodes.map(\n (entry, index) => {\n const path =\n \"exposureProgramLineProfile.nodes[\" +\n index +\n \"]\";\n const node =\n requireRecord(entry, path, [\"nodeId\", \"opticalExposureStopsFromReference\", \"aperture\", \"shutterSeconds\"]);\n const nodeId =\n requireNonEmptyString(\n node.nodeId,\n path + \".nodeId\"\n );\n const stops =\n requireFinite(\n node\n .opticalExposureStopsFromReference,\n path +\n \".opticalExposureStopsFromReference\"\n );\n if (stops <= previousStops) {\n throw new InvalidConfigurationError(\n \"exposureProgramLineProfile.nodes opticalExposureStopsFromReference must be strictly increasing.\"\n );\n }\n previousStops = stops;\n ids.push(nodeId);\n\n return {\n nodeId,\n opticalExposureStopsFromReference:\n stops,\n aperture:\n requirePositiveFinite(\n node.aperture,\n path + \".aperture\"\n ),\n shutterSeconds:\n requirePositiveFinite(\n node.shutterSeconds,\n path +\n \".shutterSeconds\"\n )\n };\n }\n );\n\n if (\n new Set(ids).size !== ids.length\n ) {\n throw new InvalidConfigurationError(\n \"exposureProgramLineProfile.nodes[].nodeId must not contain duplicates.\"\n );\n }\n\n return {\n schemaVersion:\n EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION,\n profileId:\n requireNonEmptyString(\n record.profileId,\n \"exposureProgramLineProfile.profileId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n \"exposureProgramLineProfile.profileVersion\"\n ),\n scientificStatus:\n \"approximation\",\n policyKind:\n \"generic-program-line\",\n interpolation:\n \"log2-aperture-shutter\",\n evidence: parseEvidenceList(\n record.evidence,\n \"exposureProgramLineProfile.evidence\"\n ),\n limitations:\n parseLimitations(\n record.limitations,\n \"exposureProgramLineProfile.limitations\"\n ),\n nodes\n };\n}\n"]}
1
+ {"version":3,"file":"program-line.js","sourceRoot":"","sources":["../../src/exposure/program-line.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic exposure program lines, for teaching and for products. A program line is the curve of\n * aperture/shutter pairs a camera's Program mode follows.\n *\n * A program line is control policy, not physics. Each node declares a wanted aperture/shutter pair at\n * an explicit number of stops of optical exposure, relative to the resolver's reference exposure.\n * The resolver later checks that every node is physically consistent with that reference and with\n * what the equipment can do.\n * @see docs/USAGE.md for the equations, units, coordinate conventions, known blockers and support\n * limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, requireConfigFinite as requireFinite, parseConfigLimitations as parseLimitations, requireConfigFiniteAboveZero as requirePositiveFinite } from \"../core/record-validation.js\";\n\nexport const EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION =\n \"0.1.0\" as const;\n\nexport interface ExposureProgramLineNode {\n nodeId: string;\n opticalExposureStopsFromReference: number;\n aperture: number;\n shutterSeconds: number;\n}\n\nexport interface ExposureProgramLineProfile {\n schemaVersion:\n typeof EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n scientificStatus: \"approximation\";\n policyKind:\n \"generic-program-line\";\n interpolation:\n \"log2-aperture-shutter\";\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n nodes:\n readonly ExposureProgramLineNode[];\n}\n\n/**\n * Parses a generic educational/product exposure program line.\n *\n * A program line is control policy, not physics. Each node declares a wanted\n * aperture/shutter pair at an explicit number of stops of optical exposure,\n * relative to the resolver's reference exposure. The resolver later checks\n * that every node is physically consistent with that reference and with what\n * the equipment can do.\n */\nexport function parseExposureProgramLineProfile(\n value: unknown\n): ExposureProgramLineProfile {\n const record = requireRecord(\n value,\n \"exposureProgramLineProfile\", [\"schemaVersion\", \"scientificStatus\", \"policyKind\", \"interpolation\", \"nodes\", \"profileId\", \"profileVersion\", \"evidence\", \"limitations\"]\n );\n\n if (\n record.schemaVersion !==\n EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n 'exposureProgramLineProfile.schemaVersion must be \"' +\n EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION +\n '\".'\n );\n }\n if (\n record.scientificStatus !==\n \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n 'exposureProgramLineProfile.scientificStatus must be \"approximation\".'\n );\n }\n if (\n record.policyKind !==\n \"generic-program-line\"\n ) {\n throw new InvalidConfigurationError(\n 'exposureProgramLineProfile.policyKind must be \"generic-program-line\".'\n );\n }\n if (\n record.interpolation !==\n \"log2-aperture-shutter\"\n ) {\n throw new InvalidConfigurationError(\n 'exposureProgramLineProfile.interpolation must be \"log2-aperture-shutter\".'\n );\n }\n if (\n !Array.isArray(record.nodes) ||\n record.nodes.length < 2\n ) {\n throw new InvalidConfigurationError(\n \"exposureProgramLineProfile.nodes must contain at least two nodes.\"\n );\n }\n\n let previousStops =\n Number.NEGATIVE_INFINITY;\n const ids: string[] = [];\n\n const nodes = record.nodes.map(\n (entry, index) => {\n const path =\n \"exposureProgramLineProfile.nodes[\" +\n index +\n \"]\";\n const node =\n requireRecord(entry, path, [\"nodeId\", \"opticalExposureStopsFromReference\", \"aperture\", \"shutterSeconds\"]);\n const nodeId =\n requireNonEmptyString(\n node.nodeId,\n path + \".nodeId\"\n );\n const stops =\n requireFinite(\n node\n .opticalExposureStopsFromReference,\n path +\n \".opticalExposureStopsFromReference\"\n );\n if (stops <= previousStops) {\n throw new InvalidConfigurationError(\n \"exposureProgramLineProfile.nodes opticalExposureStopsFromReference must be strictly increasing.\"\n );\n }\n previousStops = stops;\n ids.push(nodeId);\n\n return {\n nodeId,\n opticalExposureStopsFromReference:\n stops,\n aperture:\n requirePositiveFinite(\n node.aperture,\n path + \".aperture\"\n ),\n shutterSeconds:\n requirePositiveFinite(\n node.shutterSeconds,\n path +\n \".shutterSeconds\"\n )\n };\n }\n );\n\n if (\n new Set(ids).size !== ids.length\n ) {\n throw new InvalidConfigurationError(\n \"exposureProgramLineProfile.nodes[].nodeId must not contain duplicates.\"\n );\n }\n\n return {\n schemaVersion:\n EXPOSURE_PROGRAM_LINE_SCHEMA_VERSION,\n profileId:\n requireNonEmptyString(\n record.profileId,\n \"exposureProgramLineProfile.profileId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n \"exposureProgramLineProfile.profileVersion\"\n ),\n scientificStatus:\n \"approximation\",\n policyKind:\n \"generic-program-line\",\n interpolation:\n \"log2-aperture-shutter\",\n evidence: parseEvidenceList(\n record.evidence,\n \"exposureProgramLineProfile.evidence\"\n ),\n limitations:\n parseLimitations(\n record.limitations,\n \"exposureProgramLineProfile.limitations\"\n ),\n nodes\n };\n}\n"]}
@@ -0,0 +1,94 @@
1
+ import { type IlluminationVignettingProfile } from "../optics/illumination-vignetting.js";
2
+ import { type ResolveLensSampledPsfInput } from "../optics/lens-psf-profile.js";
3
+ import type { DisplayViewScene } from "./expected-signal.js";
4
+ /** Fewest grid cells between neighboring profile field nodes. */
5
+ export declare const CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS = 4;
6
+ /** Largest move of a node kernel's intensity centroid across one grid cell, in sites (sets finer grids where lateral color moves the PSF). */
7
+ export declare const CATALOGUE_FAST_GRID_CENTROID_STEP_SITES = 0.05;
8
+ /**
9
+ * Largest relative difference between the fast and the exact expected electrons that the run-time verification (one site per
10
+ * CFA phase through the exact chain, `renderTabulatedNativeRaw`) accepts; above it the capture runs the exact method.
11
+ */
12
+ export declare const CATALOGUE_FAST_VERIFICATION_TOLERANCE = 0.005;
13
+ export interface CatalogueLensSignalInput {
14
+ nativeWidth: number;
15
+ nativeHeight: number;
16
+ /** Site pitch, µm. */
17
+ pitch: number;
18
+ /** The catalogue PSF's optical state (profile, focal length, focus, f-number, defocus), as the site inputs' `psf.configuration.psf`. */
19
+ psf: Omit<ResolveLensSampledPsfInput, "fieldPointMm" | "wavelengthNm">;
20
+ /** Spectral nodes and, per CFA channel (red, green, blue), the expected electrons per unit spectral radiance at each node. */
21
+ wavelengthsNm: readonly number[];
22
+ electronsPerRadiance: readonly [Float64Array, Float64Array, Float64Array];
23
+ /** The scene's chart materials (the tabulated path's material tables). */
24
+ scene: Extract<DisplayViewScene, {
25
+ kind: "materials";
26
+ }>;
27
+ /** CFA channel index (0 red, 1 green, 2 blue) of native site (x, y). */
28
+ channelAt(x: number, y: number): number;
29
+ /** The radial distortion profile (r_d = r·(1 + k1ρ² + k2ρ⁴ + k3ρ⁶), ρ = r / normalizationRadiusMm); absent without distortion. */
30
+ distortion?: {
31
+ normalizationRadiusMm: number;
32
+ coefficients: {
33
+ k1: number;
34
+ k2: number;
35
+ k3: number;
36
+ };
37
+ };
38
+ vignetting?: IlluminationVignettingProfile;
39
+ projection: "rectilinear" | "fisheye-equisolid-angle";
40
+ /** True for a catalogue fisheye: a sensor point outside the image circle looks straight back and sees no scene. */
41
+ outsideImageCircleSeesNothing: boolean;
42
+ focalLengthMm: number;
43
+ focusDistanceM: number;
44
+ /** The planar provider's plane distance and frame-unit scale. */
45
+ distanceM: number;
46
+ metresPerFrameUnit: number;
47
+ /** Internal test hook: resolve every site's kernels exactly (the exact method's sum, slowly) instead of blending the grid. */
48
+ exactKernels?: boolean;
49
+ }
50
+ export interface CatalogueLensSignal {
51
+ /** Starts a cache window of native sites [x0, x1) × [y0, y1) (see `DisplayViewExpectedSignal.window`). */
52
+ window(x0: number, y0: number, x1: number, y1: number): void;
53
+ /** Expected photo electrons of native site (x, y) for its own CFA channel. */
54
+ site(x: number, y: number): number;
55
+ /** Scene material lookups so far. */
56
+ readonly lookups: number;
57
+ /** Grid points per axis (columns, rows) and how many were resolved so far. */
58
+ readonly grid: {
59
+ columns: number;
60
+ rows: number;
61
+ resolved: number;
62
+ };
63
+ }
64
+ /**
65
+ * The grid axes: each interval between neighboring profile field nodes split into max(GRID_MINIMUM_SUBDIVISIONS,
66
+ * ⌈Δc / GRID_CENTROID_STEP_SITES⌉) equal cells, Δc the largest centroid move between the interval's two nodes over the other
67
+ * axis's nodes and the wavelength nodes.
68
+ */
69
+ export declare function catalogueFastGridAxes(psf: CatalogueLensSignalInput["psf"]): {
70
+ x: number[];
71
+ y: number[];
72
+ };
73
+ /**
74
+ * Internal test hook (never set by the engine): `exactKernels` makes the facade's signals resolve every site's kernels exactly;
75
+ * `onInput` receives each input the facade builds, so a test can compare the grid blend with the exact sum site by site.
76
+ */
77
+ export interface CatalogueLensSignalTestHook {
78
+ exactKernels?: boolean;
79
+ onInput?(input: CatalogueLensSignalInput): void;
80
+ }
81
+ export declare function setCatalogueLensSignalTestHook(hook: CatalogueLensSignalTestHook | undefined): void;
82
+ /** The fast method's expected signal (see the module comment). */
83
+ export declare function createCatalogueLensSignal(given: CatalogueLensSignalInput): CatalogueLensSignal;
84
+ /** The `raw.expectedSignal` and `fast.lens.catalogue` assumptions of a capture the fast method rendered (kept here, out of the capture bundle's entry). */
85
+ export declare function catalogueFastAssumptions(run: {
86
+ columns: number;
87
+ rows: number;
88
+ resolved: number;
89
+ verificationRelativeDifference: number;
90
+ }): {
91
+ id: string;
92
+ value: Record<string, unknown>;
93
+ explanation: string;
94
+ }[];
@@ -0,0 +1,362 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * #568: the fast method of `simulateCapture`'s `lens.catalogue` (requested with `fast: true` or `lens.catalogue.fast`): each
4
+ * native site's expected photo electrons from tables instead of the environment PSF quadrature's per-tap scene queries.
5
+ * Loaded on demand with the catalogue (`generic-lens-capture.ts`), so a capture without a catalogue lens never loads it.
6
+ *
7
+ * What the exact method computes. The environment PSF quadrature forms, for site q of CFA channel c,
8
+ * μ_c(q) = Σ_λ A_c(λ) Σ_k K_{q,λ}(k)·F(s_k)·L(D(s_k), λ), s_k = q + (c_x − k_x, k_y − c_y)·p,
9
+ * with K_{q,λ} the catalogue PSF resolved at the site's own field point (`resolveLensSampledPsfKernel`: the profile's 5 × 5 field
10
+ * nodes × 5 wavelength nodes interpolated with centroid alignment; row index k_y increases with image +Y, `sensorPsfSourcePoint`),
11
+ * F = T·A the field factor at the tap point (vignetting throughput × distortion area factor), D the scene ray (inverse radial
12
+ * distortion, then the rectilinear or equisolid projection at the thin-lens image distance) and A_c(λ) the electrons per unit
13
+ * spectral radiance on the axis (the tabulated path's channel response). Every tap of a site-centre query is a lattice point.
14
+ *
15
+ * The approximation (the only one). Resolving K_{q,λ} at every site and spectral node is what makes the exact method slow, so the
16
+ * fast method resolves it exactly on a field grid and blends it bilinearly between the four grid points around each site:
17
+ * K̃_{q,λ} = Σ_g b_g(q)·K_{g,λ}.
18
+ * The grid subdivides each interval between the profile's field nodes into at least GRID_MINIMUM_SUBDIVISIONS cells, and into
19
+ * more where the node kernels' intensity centroids move by more than GRID_CENTROID_STEP_SITES sites across the interval (lateral
20
+ * colour moves the PSF across the frame; a blend of two shifted kernels is not the shifted kernel). Since the blend is linear,
21
+ * the channel response and the material radiance fold into the grid kernels once per grid point:
22
+ * μ̃_c(q) = Σ_k F(s_k)·Σ_g b_g(q)·W_{g,c,m(s_k)}(k), W_{g,c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_{g,λ}(k),
23
+ * so a site costs (2R + 1)² taps instead of (2R + 1)² scene queries per spectral node. Its error against the exact method is
24
+ * bounded by ‖K̃ − K‖₁ times the brightest tap's signal; tests measure both (test/simulate-capture-lens-catalogue-fast.test.ts).
25
+ *
26
+ * The scene points. The mapping is the environment's (`undistortedImagePoint`, `lensProjectionDirectionValue`, the planar
27
+ * provider's plane point), with the radial inverse solved by Newton's method instead of bisection: both converge to the same
28
+ * root to rounding. Outside a circular fisheye's image circle (`outsideImageCircle: "backward-ray"`) and beyond the horizon the
29
+ * scene radiance is zero, as in the capture.
30
+ */
31
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
32
+ import { insideRadialImageCircle, lensProjectionDirectionValue } from "../camera/lens-projection.js";
33
+ import { resolveProjectionDistanceMm } from "../motion/camera-rotation.js";
34
+ import { prepareIlluminationVignettingThroughput } from "../optics/illumination-vignetting.js";
35
+ import { resolveLensSampledPsfKernel } from "../optics/lens-psf-profile.js";
36
+ /** Fewest grid cells between neighboring profile field nodes. */
37
+ export const CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS = 4;
38
+ /** Largest move of a node kernel's intensity centroid across one grid cell, in sites (sets finer grids where lateral color moves the PSF). */
39
+ export const CATALOGUE_FAST_GRID_CENTROID_STEP_SITES = 0.05;
40
+ /**
41
+ * Largest relative difference between the fast and the exact expected electrons that the run-time verification (one site per
42
+ * CFA phase through the exact chain, `renderTabulatedNativeRaw`) accepts; above it the capture runs the exact method.
43
+ */
44
+ export const CATALOGUE_FAST_VERIFICATION_TOLERANCE = 0.005;
45
+ /** Largest cache window, in lattice points; a larger window is evaluated without caching. */
46
+ const WINDOW_LIMIT = 1 << 22;
47
+ const NEWTON_ITERATIONS = 60;
48
+ /** A kernel's intensity centroid, in samples. */
49
+ function centroid(values, width) {
50
+ let sum = 0, x = 0, y = 0;
51
+ for (let i = 0; i < values.length; i++) {
52
+ const v = values[i];
53
+ sum += v;
54
+ x += v * (i % width);
55
+ y += v * Math.floor(i / width);
56
+ }
57
+ return { x: x / sum, y: y / sum };
58
+ }
59
+ /**
60
+ * The grid axes: each interval between neighboring profile field nodes split into max(GRID_MINIMUM_SUBDIVISIONS,
61
+ * ⌈Δc / GRID_CENTROID_STEP_SITES⌉) equal cells, Δc the largest centroid move between the interval's two nodes over the other
62
+ * axis's nodes and the wavelength nodes.
63
+ */
64
+ export function catalogueFastGridAxes(psf) {
65
+ const profile = psf.profile, axes = profile.axes, fx = axes.fieldXmm, fy = axes.fieldYmm, wl = axes.wavelengthNm;
66
+ const centroids = new Map();
67
+ for (const node of profile.nodes) {
68
+ const c = node.coordinate;
69
+ centroids.set(c.fieldXmm + ":" + c.fieldYmm + ":" + c.wavelengthNm, centroid(node.kernel.normalizedIntensity, node.kernel.widthSamples));
70
+ }
71
+ const at = (x, y, nm) => {
72
+ const c = centroids.get(x + ":" + y + ":" + nm);
73
+ if (!c)
74
+ throw new InvalidConfigurationError("lens.catalogue fast: the PSF profile lacks a field node.");
75
+ return c;
76
+ };
77
+ const axis = (own, other, along) => {
78
+ const out = [];
79
+ for (let i = 0; i + 1 < own.length; i++) {
80
+ let move = 0;
81
+ for (const o of other)
82
+ for (const nm of wl) {
83
+ const a = along ? at(own[i], o, nm) : at(o, own[i], nm), b = along ? at(own[i + 1], o, nm) : at(o, own[i + 1], nm);
84
+ move = Math.max(move, Math.hypot(a.x - b.x, a.y - b.y));
85
+ }
86
+ const cells = Math.max(CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, Math.ceil(move / CATALOGUE_FAST_GRID_CENTROID_STEP_SITES - 1e-9));
87
+ for (let k = 0; k < cells; k++)
88
+ out.push(own[i] + (own[i + 1] - own[i]) * k / cells);
89
+ }
90
+ out.push(own[own.length - 1]);
91
+ return out;
92
+ };
93
+ return { x: axis(fx, fy, true), y: axis(fy, fx, false) };
94
+ }
95
+ /** The cell [i, i + 1] of an ascending axis holding v (clamped to the ends) and v's weight toward i + 1. */
96
+ function cellOf(axis, v) {
97
+ let lo = 0, hi = axis.length - 2;
98
+ while (lo < hi) {
99
+ const mid = (lo + hi + 1) >> 1;
100
+ if (axis[mid] <= v)
101
+ lo = mid;
102
+ else
103
+ hi = mid - 1;
104
+ }
105
+ const f = (v - axis[lo]) / (axis[lo + 1] - axis[lo]);
106
+ return { i: lo, f: Math.min(1, Math.max(0, f)) };
107
+ }
108
+ let testHook;
109
+ export function setCatalogueLensSignalTestHook(hook) { testHook = hook; }
110
+ /** The fast method's expected signal (see the module comment). */
111
+ export function createCatalogueLensSignal(given) {
112
+ testHook?.onInput?.(given);
113
+ const input = testHook?.exactKernels ? { ...given, exactKernels: true } : given;
114
+ const { nativeWidth: W, nativeHeight: H, psf, wavelengthsNm, electronsPerRadiance: A, scene, channelAt } = input;
115
+ const mmPerSite = input.pitch / 1000, nodes = wavelengthsNm.length;
116
+ const first = psf.profile.nodes[0];
117
+ if (!first)
118
+ throw new InvalidConfigurationError("lens.catalogue fast: the PSF profile has no nodes.");
119
+ const kw = first.kernel.widthSamples, kh = first.kernel.heightSamples, cx = first.kernel.centerSampleX, cy = first.kernel.centerSampleY, taps = kw * kh;
120
+ for (const node of psf.profile.nodes)
121
+ if (node.kernel.widthSamples !== kw || node.kernel.heightSamples !== kh || node.kernel.centerSampleX !== cx || node.kernel.centerSampleY !== cy)
122
+ throw new InvalidConfigurationError("lens.catalogue fast: the PSF profile's kernels differ in size.");
123
+ // Lattice reach of the taps around a site: x from −cx … kw − 1 − cx (as c_x − k_x), y from −cy … kh − 1 − cy.
124
+ const reachLeft = kw - 1 - cx, reachRight = cx, reachUp = cy, reachDown = kh - 1 - cy;
125
+ // Materials: neutral ones by reflectance (class 0, the white radiance), spectral ones by class 1 + index.
126
+ const M = scene.materials.length, reflectance = new Float64Array(M), materialClass = new Int32Array(M);
127
+ const spectral = [];
128
+ scene.materials.forEach((m, index) => { if ("neutral" in m)
129
+ reflectance[index] = m.neutral;
130
+ else {
131
+ materialClass[index] = 1 + spectral.length;
132
+ spectral.push(m.radiance);
133
+ } });
134
+ const classes = 1 + spectral.length, classRadiance = [scene.whiteRadiance, ...spectral];
135
+ const tableSize = 3 * classes * taps;
136
+ // W_{c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_λ(k) for the kernels of one field point.
137
+ const foldKernels = (fieldXmm, fieldYmm) => {
138
+ const table = new Float64Array(tableSize);
139
+ for (let n = 0; n < nodes; n++) {
140
+ const kernel = resolveLensSampledPsfKernel({ ...psf, wavelengthNm: wavelengthsNm[n], fieldPointMm: { x: fieldXmm, y: fieldYmm } }).normalizedIntensity;
141
+ if (kernel.length !== taps)
142
+ throw new InvalidConfigurationError("lens.catalogue fast: a resolved kernel differs in size.");
143
+ for (let c = 0; c < 3; c++) {
144
+ const a = A[c][n];
145
+ if (a === 0)
146
+ continue;
147
+ for (let cl = 0; cl < classes; cl++) {
148
+ const weight = a * classRadiance[cl][n], base = (c * classes + cl) * taps;
149
+ if (weight === 0)
150
+ continue;
151
+ for (let k = 0; k < taps; k++)
152
+ table[base + k] = table[base + k] + weight * kernel[k];
153
+ }
154
+ }
155
+ }
156
+ return table;
157
+ };
158
+ // The field grid, resolved lazily (a region render resolves only the grid points around it).
159
+ const axes = catalogueFastGridAxes(psf), gx = axes.x, gy = axes.y, GX = gx.length;
160
+ const tables = new Array(GX * gy.length);
161
+ let resolved = 0;
162
+ const tableAt = (i, j) => {
163
+ const k = j * GX + i;
164
+ let t = tables[k];
165
+ if (!t) {
166
+ t = foldKernels(gx[i], gy[j]);
167
+ tables[k] = t;
168
+ resolved++;
169
+ }
170
+ return t;
171
+ };
172
+ // Each native column's and row's grid cell and blend weight (field +Y up: row y sits at −(y + ½ − H/2)·p).
173
+ const cellX = new Int32Array(W), weightX = new Float64Array(W), cellY = new Int32Array(H), weightY = new Float64Array(H);
174
+ for (let x = 0; x < W; x++) {
175
+ const c = cellOf(gx, (x + 0.5 - W / 2) * mmPerSite);
176
+ cellX[x] = c.i;
177
+ weightX[x] = c.f;
178
+ }
179
+ for (let y = 0; y < H; y++) {
180
+ const c = cellOf(gy, -(y + 0.5 - H / 2) * mmPerSite);
181
+ cellY[y] = c.i;
182
+ weightY[y] = c.f;
183
+ }
184
+ // The scene point and field factor of a native sensor point (mm, +Y down); factor 0 where the scene radiance is zero.
185
+ const pd = resolveProjectionDistanceMm(input.focalLengthMm, input.focusDistanceM).projectionDistanceMm;
186
+ const planeScale = input.distanceM / input.metresPerFrameUnit;
187
+ const throughputAt = input.vignetting ? prepareIlluminationVignettingThroughput(input.vignetting) : undefined;
188
+ const distortion = input.distortion, rectilinear = input.projection === "rectilinear";
189
+ let mu = 0, mv = 0, mf = 0;
190
+ const map = (xMm, yMm) => {
191
+ const imageX = xMm, imageY = -yMm;
192
+ let idealX = imageX, idealY = imageY, area = 1;
193
+ if (distortion) {
194
+ const { k1, k2, k3 } = distortion.coefficients, norm = distortion.normalizationRadiusMm, rd = Math.hypot(imageX, imageY) / norm;
195
+ let rho = rd;
196
+ if (rd > 0) {
197
+ // Newton's method on ρ·s(ρ) = ρ_d (monotonic over the profile's validated range), from ρ = ρ_d.
198
+ let converged = false;
199
+ for (let it = 0; it < NEWTON_ITERATIONS; it++) {
200
+ const r2 = rho * rho, value = rho * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - rd, slope = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;
201
+ if (!(slope > 0))
202
+ break;
203
+ const step = value / slope;
204
+ rho -= step;
205
+ if (Math.abs(step) <= 4 * Number.EPSILON * rho) {
206
+ converged = true;
207
+ break;
208
+ }
209
+ }
210
+ if (!converged || !(rho > 0))
211
+ throw new InvalidConfigurationError("lens.catalogue fast: the inverse distortion did not converge.");
212
+ }
213
+ const r2 = rho * rho, s = 1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2, derivative = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;
214
+ if (rd > 0) {
215
+ idealX = imageX / s;
216
+ idealY = imageY / s;
217
+ }
218
+ area = 1 / (s * derivative);
219
+ if (!(area > 0) || !Number.isFinite(area))
220
+ throw new InvalidConfigurationError("lens.catalogue fast: the distortion area factor must stay finite and positive.");
221
+ }
222
+ const factor = (throughputAt ? throughputAt({ x: imageX, y: imageY }) : 1) * area;
223
+ if (rectilinear) {
224
+ mu = idealX / pd * planeScale;
225
+ mv = idealY / pd * planeScale;
226
+ mf = factor;
227
+ return;
228
+ }
229
+ if (input.outsideImageCircleSeesNothing && !insideRadialImageCircle(input.projection, pd, Math.hypot(idealX, idealY))) {
230
+ mf = 0;
231
+ return;
232
+ }
233
+ const d = lensProjectionDirectionValue(input.projection, pd, idealX, idealY);
234
+ if (!(d.z > 0)) {
235
+ mf = 0;
236
+ return;
237
+ }
238
+ mu = d.x * (input.distanceM / d.z) / input.metresPerFrameUnit;
239
+ mv = d.y * (input.distanceM / d.z) / input.metresPerFrameUnit;
240
+ mf = factor;
241
+ };
242
+ let lookups = 0;
243
+ let lm = 0, lf = 0;
244
+ /** The material and field factor of lattice point (p, q) (sites from the top-left site, the lattice formula). */
245
+ const lattice = (p, q) => {
246
+ map((p + 0.5 - W / 2) * mmPerSite, (q + 0.5 - H / 2) * mmPerSite);
247
+ lf = mf;
248
+ if (mf === 0) {
249
+ lm = 0;
250
+ return;
251
+ }
252
+ const m = scene.materialAt(mu, mv);
253
+ if (!(m >= 0 && m < M) || !Number.isInteger(m))
254
+ throw new InvalidConfigurationError("Unknown chart material.");
255
+ lm = m;
256
+ lookups++;
257
+ };
258
+ // The window's lattice: points [wx0, wx0 + ww) × [wy0, wy0 + wh).
259
+ let wx0 = 0, wy0 = 0, ww = 0, wh = 0, winMaterial = new Int32Array(0), winFactor = new Float64Array(0);
260
+ const window = (x0, y0, x1, y1) => {
261
+ const w = x1 - x0 + reachLeft + reachRight, h = y1 - y0 + reachUp + reachDown;
262
+ if (!(w > 0 && h > 0) || w * h > WINDOW_LIMIT) {
263
+ ww = 0;
264
+ wh = 0;
265
+ return;
266
+ }
267
+ wx0 = x0 - reachLeft;
268
+ wy0 = y0 - reachUp;
269
+ ww = w;
270
+ wh = h;
271
+ if (winMaterial.length < w * h) {
272
+ winMaterial = new Int32Array(w * h);
273
+ winFactor = new Float64Array(w * h);
274
+ }
275
+ for (let q = 0; q < h; q++)
276
+ for (let p = 0; p < w; p++) {
277
+ lattice(wx0 + p, wy0 + q);
278
+ winMaterial[q * w + p] = lm;
279
+ winFactor[q * w + p] = lf;
280
+ }
281
+ };
282
+ const kernelOf = new Float64Array(tableSize);
283
+ let cellI = -1, cellJ = -1, c00 = kernelOf, c10 = kernelOf, c01 = kernelOf, c11 = kernelOf;
284
+ const site = (x, y) => {
285
+ const c = channelAt(x, y);
286
+ let t00, t10, t01, t11, b00, b10, b01, b11;
287
+ if (input.exactKernels) {
288
+ kernelOf.set(foldKernels((x + 0.5 - W / 2) * mmPerSite, -(y + 0.5 - H / 2) * mmPerSite));
289
+ t00 = t10 = t01 = t11 = kernelOf;
290
+ b00 = 1;
291
+ b10 = b01 = b11 = 0;
292
+ }
293
+ else {
294
+ const i = cellX[x], j = cellY[y], fx = weightX[x], fy = weightY[y];
295
+ // Neighboring sites share a cell: its four tables are kept from the last site.
296
+ if (i !== cellI || j !== cellJ) {
297
+ cellI = i;
298
+ cellJ = j;
299
+ c00 = tableAt(i, j);
300
+ c10 = tableAt(i + 1, j);
301
+ c01 = tableAt(i, j + 1);
302
+ c11 = tableAt(i + 1, j + 1);
303
+ }
304
+ t00 = c00;
305
+ t10 = c10;
306
+ t01 = c01;
307
+ t11 = c11;
308
+ b00 = (1 - fx) * (1 - fy);
309
+ b10 = fx * (1 - fy);
310
+ b01 = (1 - fx) * fy;
311
+ b11 = fx * fy;
312
+ }
313
+ const inside = ww > 0 && x - reachLeft >= wx0 && y - reachUp >= wy0 && x + reachRight < wx0 + ww && y + reachDown < wy0 + wh;
314
+ const channelBase = c * classes * taps;
315
+ let sum = 0;
316
+ for (let ky = 0, k = 0; ky < kh; ky++) {
317
+ const q = y + ky - cy;
318
+ for (let kx = 0; kx < kw; kx++, k++) {
319
+ const p = x + cx - kx;
320
+ let m, f;
321
+ if (inside) {
322
+ const at = (q - wy0) * ww + p - wx0;
323
+ m = winMaterial[at];
324
+ f = winFactor[at];
325
+ }
326
+ else {
327
+ lattice(p, q);
328
+ m = lm;
329
+ f = lf;
330
+ }
331
+ if (f === 0)
332
+ continue;
333
+ const cl = materialClass[m], at = channelBase + cl * taps + k;
334
+ const w = b00 * t00[at] + b10 * t10[at] + b01 * t01[at] + b11 * t11[at];
335
+ sum += f * (cl === 0 ? reflectance[m] * w : w);
336
+ }
337
+ }
338
+ return sum;
339
+ };
340
+ return {
341
+ window, site,
342
+ get lookups() { return lookups; },
343
+ get grid() { return { columns: GX, rows: gy.length, resolved }; }
344
+ };
345
+ }
346
+ /** The `raw.expectedSignal` and `fast.lens.catalogue` assumptions of a capture the fast method rendered (kept here, out of the capture bundle's entry). */
347
+ export function catalogueFastAssumptions(run) {
348
+ const tolerance = CATALOGUE_FAST_VERIFICATION_TOLERANCE, cells = CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, step = CATALOGUE_FAST_GRID_CENTROID_STEP_SITES;
349
+ return [{ id: "raw.expectedSignal", value: { method: "tabulated-expected-electrons-catalogue-psf-field-grid", verificationSites: 4, tolerance,
350
+ verificationRelativeDifference: run.verificationRelativeDifference },
351
+ explanation: "Each native site's expected photo electrons are tabulated: the sum over the PSF taps of the field factor (vignetting × distortion area factor) times the " +
352
+ "grid-blended kernel folded with the channel response and the material radiance (see fast.lens.catalogue). The sensor stage (dark charge, Poisson draws, read noise, ADC) " +
353
+ "is the native one with each site's own seeds. One site of each CFA phase ran the exact chain (the environment PSF quadrature): its readout was reproduced bit for bit and " +
354
+ "its electrons agreed with the fast ones within the tolerance (verificationRelativeDifference: the largest difference there)." },
355
+ { id: "fast.lens.catalogue", value: { method: "field-grid-bilinear-psf", gridColumns: run.columns, gridRows: run.rows, gridPointsResolved: run.resolved,
356
+ minimumSubdivisions: cells, centroidStepSites: step, verificationTolerance: tolerance },
357
+ explanation: "Fast method (#568), an approximation of the exact one: the catalogue PSF is resolved exactly on a field grid (each interval between the profile's field nodes " +
358
+ "split into at least " + cells + " cells, more where the node kernels' centroids move by over " + step + " site per cell), and each site takes the bilinear blend of its " +
359
+ "four grid points' kernels instead of its own. Its error is at most the kernels' L1 difference times the brightest tap's signal; docs/LENS_CATALOGUE.md (\"Fast method\") " +
360
+ "records it measured against the exact method. Distortion, vignetting and projection are the exact method's (the radial inverse by Newton's method, equal to rounding)." }];
361
+ }
362
+ //# sourceMappingURL=catalogue-lens-signal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogue-lens-signal.js","sourceRoot":"","sources":["../../src/facade/catalogue-lens-signal.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #568: the fast method of `simulateCapture`'s `lens.catalogue` (requested with `fast: true` or `lens.catalogue.fast`): each\n * native site's expected photo electrons from tables instead of the environment PSF quadrature's per-tap scene queries.\n * Loaded on demand with the catalogue (`generic-lens-capture.ts`), so a capture without a catalogue lens never loads it.\n *\n * What the exact method computes. The environment PSF quadrature forms, for site q of CFA channel c,\n * μ_c(q) = Σ_λ A_c(λ) Σ_k K_{q,λ}(k)·F(s_k)·L(D(s_k), λ), s_k = q + (c_x − k_x, k_y − c_y)·p,\n * with K_{q,λ} the catalogue PSF resolved at the site's own field point (`resolveLensSampledPsfKernel`: the profile's 5 × 5 field\n * nodes × 5 wavelength nodes interpolated with centroid alignment; row index k_y increases with image +Y, `sensorPsfSourcePoint`),\n * F = T·A the field factor at the tap point (vignetting throughput × distortion area factor), D the scene ray (inverse radial\n * distortion, then the rectilinear or equisolid projection at the thin-lens image distance) and A_c(λ) the electrons per unit\n * spectral radiance on the axis (the tabulated path's channel response). Every tap of a site-centre query is a lattice point.\n *\n * The approximation (the only one). Resolving K_{q,λ} at every site and spectral node is what makes the exact method slow, so the\n * fast method resolves it exactly on a field grid and blends it bilinearly between the four grid points around each site:\n * K̃_{q,λ} = Σ_g b_g(q)·K_{g,λ}.\n * The grid subdivides each interval between the profile's field nodes into at least GRID_MINIMUM_SUBDIVISIONS cells, and into\n * more where the node kernels' intensity centroids move by more than GRID_CENTROID_STEP_SITES sites across the interval (lateral\n * colour moves the PSF across the frame; a blend of two shifted kernels is not the shifted kernel). Since the blend is linear,\n * the channel response and the material radiance fold into the grid kernels once per grid point:\n * μ̃_c(q) = Σ_k F(s_k)·Σ_g b_g(q)·W_{g,c,m(s_k)}(k), W_{g,c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_{g,λ}(k),\n * so a site costs (2R + 1)² taps instead of (2R + 1)² scene queries per spectral node. Its error against the exact method is\n * bounded by ‖K̃ − K‖₁ times the brightest tap's signal; tests measure both (test/simulate-capture-lens-catalogue-fast.test.ts).\n *\n * The scene points. The mapping is the environment's (`undistortedImagePoint`, `lensProjectionDirectionValue`, the planar\n * provider's plane point), with the radial inverse solved by Newton's method instead of bisection: both converge to the same\n * root to rounding. Outside a circular fisheye's image circle (`outsideImageCircle: \"backward-ray\"`) and beyond the horizon the\n * scene radiance is zero, as in the capture.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { insideRadialImageCircle, lensProjectionDirectionValue } from \"../camera/lens-projection.js\";\nimport { resolveProjectionDistanceMm } from \"../motion/camera-rotation.js\";\nimport { prepareIlluminationVignettingThroughput, type IlluminationVignettingProfile } from \"../optics/illumination-vignetting.js\";\nimport { resolveLensSampledPsfKernel, type ResolveLensSampledPsfInput } from \"../optics/lens-psf-profile.js\";\nimport type { DisplayViewScene } from \"./expected-signal.js\";\n\n/** Fewest grid cells between neighboring profile field nodes. */\nexport const CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS = 4;\n/** Largest move of a node kernel's intensity centroid across one grid cell, in sites (sets finer grids where lateral color moves the PSF). */\nexport const CATALOGUE_FAST_GRID_CENTROID_STEP_SITES = 0.05;\n/**\n * Largest relative difference between the fast and the exact expected electrons that the run-time verification (one site per\n * CFA phase through the exact chain, `renderTabulatedNativeRaw`) accepts; above it the capture runs the exact method.\n */\nexport const CATALOGUE_FAST_VERIFICATION_TOLERANCE = 0.005;\n/** Largest cache window, in lattice points; a larger window is evaluated without caching. */\nconst WINDOW_LIMIT = 1 << 22;\nconst NEWTON_ITERATIONS = 60;\n\nexport interface CatalogueLensSignalInput {\n nativeWidth: number;\n nativeHeight: number;\n /** Site pitch, µm. */\n pitch: number;\n /** The catalogue PSF's optical state (profile, focal length, focus, f-number, defocus), as the site inputs' `psf.configuration.psf`. */\n psf: Omit<ResolveLensSampledPsfInput, \"fieldPointMm\" | \"wavelengthNm\">;\n /** Spectral nodes and, per CFA channel (red, green, blue), the expected electrons per unit spectral radiance at each node. */\n wavelengthsNm: readonly number[];\n electronsPerRadiance: readonly [Float64Array, Float64Array, Float64Array];\n /** The scene's chart materials (the tabulated path's material tables). */\n scene: Extract<DisplayViewScene, { kind: \"materials\" }>;\n /** CFA channel index (0 red, 1 green, 2 blue) of native site (x, y). */\n channelAt(x: number, y: number): number;\n /** The radial distortion profile (r_d = r·(1 + k1ρ² + k2ρ⁴ + k3ρ⁶), ρ = r / normalizationRadiusMm); absent without distortion. */\n distortion?: { normalizationRadiusMm: number; coefficients: { k1: number; k2: number; k3: number } };\n vignetting?: IlluminationVignettingProfile;\n projection: \"rectilinear\" | \"fisheye-equisolid-angle\";\n /** True for a catalogue fisheye: a sensor point outside the image circle looks straight back and sees no scene. */\n outsideImageCircleSeesNothing: boolean;\n focalLengthMm: number;\n focusDistanceM: number;\n /** The planar provider's plane distance and frame-unit scale. */\n distanceM: number;\n metresPerFrameUnit: number;\n /** Internal test hook: resolve every site's kernels exactly (the exact method's sum, slowly) instead of blending the grid. */\n exactKernels?: boolean;\n}\n\nexport interface CatalogueLensSignal {\n /** Starts a cache window of native sites [x0, x1) × [y0, y1) (see `DisplayViewExpectedSignal.window`). */\n window(x0: number, y0: number, x1: number, y1: number): void;\n /** Expected photo electrons of native site (x, y) for its own CFA channel. */\n site(x: number, y: number): number;\n /** Scene material lookups so far. */\n readonly lookups: number;\n /** Grid points per axis (columns, rows) and how many were resolved so far. */\n readonly grid: { columns: number; rows: number; resolved: number };\n}\n\n/** A kernel's intensity centroid, in samples. */\nfunction centroid(values: readonly number[], width: number): { x: number; y: number } {\n let sum = 0, x = 0, y = 0;\n for (let i = 0; i < values.length; i++) { const v = values[i]!; sum += v; x += v * (i % width); y += v * Math.floor(i / width); }\n return { x: x / sum, y: y / sum };\n}\n\n/**\n * The grid axes: each interval between neighboring profile field nodes split into max(GRID_MINIMUM_SUBDIVISIONS,\n * ⌈Δc / GRID_CENTROID_STEP_SITES⌉) equal cells, Δc the largest centroid move between the interval's two nodes over the other\n * axis's nodes and the wavelength nodes.\n */\nexport function catalogueFastGridAxes(psf: CatalogueLensSignalInput[\"psf\"]): { x: number[]; y: number[] } {\n const profile = psf.profile, axes = profile.axes, fx = axes.fieldXmm, fy = axes.fieldYmm, wl = axes.wavelengthNm;\n const centroids = new Map<string, { x: number; y: number }>();\n for (const node of profile.nodes) {\n const c = node.coordinate;\n centroids.set(c.fieldXmm + \":\" + c.fieldYmm + \":\" + c.wavelengthNm, centroid(node.kernel.normalizedIntensity, node.kernel.widthSamples));\n }\n const at = (x: number, y: number, nm: number): { x: number; y: number } => {\n const c = centroids.get(x + \":\" + y + \":\" + nm);\n if (!c) throw new InvalidConfigurationError(\"lens.catalogue fast: the PSF profile lacks a field node.\");\n return c;\n };\n const axis = (own: readonly number[], other: readonly number[], along: boolean): number[] => {\n const out: number[] = [];\n for (let i = 0; i + 1 < own.length; i++) {\n let move = 0;\n for (const o of other) for (const nm of wl) {\n const a = along ? at(own[i]!, o, nm) : at(o, own[i]!, nm), b = along ? at(own[i + 1]!, o, nm) : at(o, own[i + 1]!, nm);\n move = Math.max(move, Math.hypot(a.x - b.x, a.y - b.y));\n }\n const cells = Math.max(CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, Math.ceil(move / CATALOGUE_FAST_GRID_CENTROID_STEP_SITES - 1e-9));\n for (let k = 0; k < cells; k++) out.push(own[i]! + (own[i + 1]! - own[i]!) * k / cells);\n }\n out.push(own[own.length - 1]!);\n return out;\n };\n return { x: axis(fx, fy, true), y: axis(fy, fx, false) };\n}\n\n/** The cell [i, i + 1] of an ascending axis holding v (clamped to the ends) and v's weight toward i + 1. */\nfunction cellOf(axis: readonly number[], v: number): { i: number; f: number } {\n let lo = 0, hi = axis.length - 2;\n while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (axis[mid]! <= v) lo = mid; else hi = mid - 1; }\n const f = (v - axis[lo]!) / (axis[lo + 1]! - axis[lo]!);\n return { i: lo, f: Math.min(1, Math.max(0, f)) };\n}\n\n/**\n * Internal test hook (never set by the engine): `exactKernels` makes the facade's signals resolve every site's kernels exactly;\n * `onInput` receives each input the facade builds, so a test can compare the grid blend with the exact sum site by site.\n */\nexport interface CatalogueLensSignalTestHook { exactKernels?: boolean; onInput?(input: CatalogueLensSignalInput): void }\nlet testHook: CatalogueLensSignalTestHook | undefined;\nexport function setCatalogueLensSignalTestHook(hook: CatalogueLensSignalTestHook | undefined): void { testHook = hook; }\n\n/** The fast method's expected signal (see the module comment). */\nexport function createCatalogueLensSignal(given: CatalogueLensSignalInput): CatalogueLensSignal {\n testHook?.onInput?.(given);\n const input = testHook?.exactKernels ? { ...given, exactKernels: true } : given;\n const { nativeWidth: W, nativeHeight: H, psf, wavelengthsNm, electronsPerRadiance: A, scene, channelAt } = input;\n const mmPerSite = input.pitch / 1000, nodes = wavelengthsNm.length;\n const first = psf.profile.nodes[0];\n if (!first) throw new InvalidConfigurationError(\"lens.catalogue fast: the PSF profile has no nodes.\");\n const kw = first.kernel.widthSamples, kh = first.kernel.heightSamples, cx = first.kernel.centerSampleX, cy = first.kernel.centerSampleY, taps = kw * kh;\n for (const node of psf.profile.nodes) if (node.kernel.widthSamples !== kw || node.kernel.heightSamples !== kh || node.kernel.centerSampleX !== cx || node.kernel.centerSampleY !== cy)\n throw new InvalidConfigurationError(\"lens.catalogue fast: the PSF profile's kernels differ in size.\");\n // Lattice reach of the taps around a site: x from −cx … kw − 1 − cx (as c_x − k_x), y from −cy … kh − 1 − cy.\n const reachLeft = kw - 1 - cx, reachRight = cx, reachUp = cy, reachDown = kh - 1 - cy;\n // Materials: neutral ones by reflectance (class 0, the white radiance), spectral ones by class 1 + index.\n const M = scene.materials.length, reflectance = new Float64Array(M), materialClass = new Int32Array(M);\n const spectral: Float64Array[] = [];\n scene.materials.forEach((m, index) => { if (\"neutral\" in m) reflectance[index] = m.neutral; else { materialClass[index] = 1 + spectral.length; spectral.push(m.radiance); } });\n const classes = 1 + spectral.length, classRadiance = [scene.whiteRadiance, ...spectral];\n const tableSize = 3 * classes * taps;\n // W_{c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_λ(k) for the kernels of one field point.\n const foldKernels = (fieldXmm: number, fieldYmm: number): Float64Array => {\n const table = new Float64Array(tableSize);\n for (let n = 0; n < nodes; n++) {\n const kernel = resolveLensSampledPsfKernel({ ...psf, wavelengthNm: wavelengthsNm[n]!, fieldPointMm: { x: fieldXmm, y: fieldYmm } }).normalizedIntensity;\n if (kernel.length !== taps) throw new InvalidConfigurationError(\"lens.catalogue fast: a resolved kernel differs in size.\");\n for (let c = 0; c < 3; c++) {\n const a = A[c]![n]!;\n if (a === 0) continue;\n for (let cl = 0; cl < classes; cl++) {\n const weight = a * classRadiance[cl]![n]!, base = (c * classes + cl) * taps;\n if (weight === 0) continue;\n for (let k = 0; k < taps; k++) table[base + k] = table[base + k]! + weight * kernel[k]!;\n }\n }\n }\n return table;\n };\n // The field grid, resolved lazily (a region render resolves only the grid points around it).\n const axes = catalogueFastGridAxes(psf), gx = axes.x, gy = axes.y, GX = gx.length;\n const tables: (Float64Array | undefined)[] = new Array(GX * gy.length);\n let resolved = 0;\n const tableAt = (i: number, j: number): Float64Array => {\n const k = j * GX + i;\n let t = tables[k];\n if (!t) { t = foldKernels(gx[i]!, gy[j]!); tables[k] = t; resolved++; }\n return t;\n };\n // Each native column's and row's grid cell and blend weight (field +Y up: row y sits at −(y + ½ − H/2)·p).\n const cellX = new Int32Array(W), weightX = new Float64Array(W), cellY = new Int32Array(H), weightY = new Float64Array(H);\n for (let x = 0; x < W; x++) { const c = cellOf(gx, (x + 0.5 - W / 2) * mmPerSite); cellX[x] = c.i; weightX[x] = c.f; }\n for (let y = 0; y < H; y++) { const c = cellOf(gy, -(y + 0.5 - H / 2) * mmPerSite); cellY[y] = c.i; weightY[y] = c.f; }\n\n // The scene point and field factor of a native sensor point (mm, +Y down); factor 0 where the scene radiance is zero.\n const pd = resolveProjectionDistanceMm(input.focalLengthMm, input.focusDistanceM).projectionDistanceMm;\n const planeScale = input.distanceM / input.metresPerFrameUnit;\n const throughputAt = input.vignetting ? prepareIlluminationVignettingThroughput(input.vignetting) : undefined;\n const distortion = input.distortion, rectilinear = input.projection === \"rectilinear\";\n let mu = 0, mv = 0, mf = 0;\n const map = (xMm: number, yMm: number): void => {\n const imageX = xMm, imageY = -yMm;\n let idealX = imageX, idealY = imageY, area = 1;\n if (distortion) {\n const { k1, k2, k3 } = distortion.coefficients, norm = distortion.normalizationRadiusMm, rd = Math.hypot(imageX, imageY) / norm;\n let rho = rd;\n if (rd > 0) {\n // Newton's method on ρ·s(ρ) = ρ_d (monotonic over the profile's validated range), from ρ = ρ_d.\n let converged = false;\n for (let it = 0; it < NEWTON_ITERATIONS; it++) {\n const r2 = rho * rho, value = rho * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - rd, slope = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;\n if (!(slope > 0)) break;\n const step = value / slope;\n rho -= step;\n if (Math.abs(step) <= 4 * Number.EPSILON * rho) { converged = true; break; }\n }\n if (!converged || !(rho > 0)) throw new InvalidConfigurationError(\"lens.catalogue fast: the inverse distortion did not converge.\");\n }\n const r2 = rho * rho, s = 1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2, derivative = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;\n if (rd > 0) { idealX = imageX / s; idealY = imageY / s; }\n area = 1 / (s * derivative);\n if (!(area > 0) || !Number.isFinite(area)) throw new InvalidConfigurationError(\"lens.catalogue fast: the distortion area factor must stay finite and positive.\");\n }\n const factor = (throughputAt ? throughputAt({ x: imageX, y: imageY }) : 1) * area;\n if (rectilinear) { mu = idealX / pd * planeScale; mv = idealY / pd * planeScale; mf = factor; return; }\n if (input.outsideImageCircleSeesNothing && !insideRadialImageCircle(input.projection, pd, Math.hypot(idealX, idealY))) { mf = 0; return; }\n const d = lensProjectionDirectionValue(input.projection, pd, idealX, idealY);\n if (!(d.z > 0)) { mf = 0; return; }\n mu = d.x * (input.distanceM / d.z) / input.metresPerFrameUnit; mv = d.y * (input.distanceM / d.z) / input.metresPerFrameUnit; mf = factor;\n };\n let lookups = 0;\n let lm = 0, lf = 0;\n /** The material and field factor of lattice point (p, q) (sites from the top-left site, the lattice formula). */\n const lattice = (p: number, q: number): void => {\n map((p + 0.5 - W / 2) * mmPerSite, (q + 0.5 - H / 2) * mmPerSite);\n lf = mf;\n if (mf === 0) { lm = 0; return; }\n const m = scene.materialAt(mu, mv);\n if (!(m >= 0 && m < M) || !Number.isInteger(m)) throw new InvalidConfigurationError(\"Unknown chart material.\");\n lm = m; lookups++;\n };\n // The window's lattice: points [wx0, wx0 + ww) × [wy0, wy0 + wh).\n let wx0 = 0, wy0 = 0, ww = 0, wh = 0, winMaterial = new Int32Array(0), winFactor = new Float64Array(0);\n const window = (x0: number, y0: number, x1: number, y1: number): void => {\n const w = x1 - x0 + reachLeft + reachRight, h = y1 - y0 + reachUp + reachDown;\n if (!(w > 0 && h > 0) || w * h > WINDOW_LIMIT) { ww = 0; wh = 0; return; }\n wx0 = x0 - reachLeft; wy0 = y0 - reachUp; ww = w; wh = h;\n if (winMaterial.length < w * h) { winMaterial = new Int32Array(w * h); winFactor = new Float64Array(w * h); }\n for (let q = 0; q < h; q++) for (let p = 0; p < w; p++) { lattice(wx0 + p, wy0 + q); winMaterial[q * w + p] = lm; winFactor[q * w + p] = lf; }\n };\n const kernelOf = new Float64Array(tableSize);\n let cellI = -1, cellJ = -1, c00: Float64Array = kernelOf, c10: Float64Array = kernelOf, c01: Float64Array = kernelOf, c11: Float64Array = kernelOf;\n const site = (x: number, y: number): number => {\n const c = channelAt(x, y);\n let t00: Float64Array, t10: Float64Array, t01: Float64Array, t11: Float64Array, b00: number, b10: number, b01: number, b11: number;\n if (input.exactKernels) {\n kernelOf.set(foldKernels((x + 0.5 - W / 2) * mmPerSite, -(y + 0.5 - H / 2) * mmPerSite));\n t00 = t10 = t01 = t11 = kernelOf; b00 = 1; b10 = b01 = b11 = 0;\n } else {\n const i = cellX[x]!, j = cellY[y]!, fx = weightX[x]!, fy = weightY[y]!;\n // Neighboring sites share a cell: its four tables are kept from the last site.\n if (i !== cellI || j !== cellJ) { cellI = i; cellJ = j; c00 = tableAt(i, j); c10 = tableAt(i + 1, j); c01 = tableAt(i, j + 1); c11 = tableAt(i + 1, j + 1); }\n t00 = c00; t10 = c10; t01 = c01; t11 = c11;\n b00 = (1 - fx) * (1 - fy); b10 = fx * (1 - fy); b01 = (1 - fx) * fy; b11 = fx * fy;\n }\n const inside = ww > 0 && x - reachLeft >= wx0 && y - reachUp >= wy0 && x + reachRight < wx0 + ww && y + reachDown < wy0 + wh;\n const channelBase = c * classes * taps;\n let sum = 0;\n for (let ky = 0, k = 0; ky < kh; ky++) {\n const q = y + ky - cy;\n for (let kx = 0; kx < kw; kx++, k++) {\n const p = x + cx - kx;\n let m: number, f: number;\n if (inside) { const at = (q - wy0) * ww + p - wx0; m = winMaterial[at]!; f = winFactor[at]!; } else { lattice(p, q); m = lm; f = lf; }\n if (f === 0) continue;\n const cl = materialClass[m]!, at = channelBase + cl * taps + k;\n const w = b00 * t00[at]! + b10 * t10[at]! + b01 * t01[at]! + b11 * t11[at]!;\n sum += f * (cl === 0 ? reflectance[m]! * w : w);\n }\n }\n return sum;\n };\n return {\n window, site,\n get lookups(): number { return lookups; },\n get grid(): { columns: number; rows: number; resolved: number } { return { columns: GX, rows: gy.length, resolved }; }\n };\n}\n\n/** The `raw.expectedSignal` and `fast.lens.catalogue` assumptions of a capture the fast method rendered (kept here, out of the capture bundle's entry). */\nexport function catalogueFastAssumptions(run: { columns: number; rows: number; resolved: number; verificationRelativeDifference: number }):\n { id: string; value: Record<string, unknown>; explanation: string }[] {\n const tolerance = CATALOGUE_FAST_VERIFICATION_TOLERANCE, cells = CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, step = CATALOGUE_FAST_GRID_CENTROID_STEP_SITES;\n return [{ id: \"raw.expectedSignal\", value: { method: \"tabulated-expected-electrons-catalogue-psf-field-grid\", verificationSites: 4, tolerance,\n verificationRelativeDifference: run.verificationRelativeDifference },\n explanation: \"Each native site's expected photo electrons are tabulated: the sum over the PSF taps of the field factor (vignetting × distortion area factor) times the \" +\n \"grid-blended kernel folded with the channel response and the material radiance (see fast.lens.catalogue). The sensor stage (dark charge, Poisson draws, read noise, ADC) \" +\n \"is the native one with each site's own seeds. One site of each CFA phase ran the exact chain (the environment PSF quadrature): its readout was reproduced bit for bit and \" +\n \"its electrons agreed with the fast ones within the tolerance (verificationRelativeDifference: the largest difference there).\" },\n { id: \"fast.lens.catalogue\", value: { method: \"field-grid-bilinear-psf\", gridColumns: run.columns, gridRows: run.rows, gridPointsResolved: run.resolved,\n minimumSubdivisions: cells, centroidStepSites: step, verificationTolerance: tolerance },\n explanation: \"Fast method (#568), an approximation of the exact one: the catalogue PSF is resolved exactly on a field grid (each interval between the profile's field nodes \" +\n \"split into at least \" + cells + \" cells, more where the node kernels' centroids move by over \" + step + \" site per cell), and each site takes the bilinear blend of its \" +\n \"four grid points' kernels instead of its own. Its error is at most the kernels' L1 difference times the brightest tap's signal; docs/LENS_CATALOGUE.md (\\\"Fast method\\\") \" +\n \"records it measured against the exact method. Distortion, vignetting and projection are the exact method's (the radial inverse by Newton's method, equal to rounding).\" }];\n}\n"]}
@@ -42,7 +42,7 @@
42
42
  import { InvalidConfigurationError } from "../core/configuration-error.js";
43
43
  import { hypot3 } from "../core/portable-math.js";
44
44
  import { isInternalSceneRadianceEvaluator, markInternalSceneRadianceEvaluator, registerInternalRadianceAccess } from "../core/internal-radiance-evaluator.js";
45
- import { lensProjectionDirectionValue, lensProjectionImagePointValue } from "../camera/lens-projection.js";
45
+ import { insideRadialImageCircle, lensProjectionDirectionValue, lensProjectionImagePointValue } from "../camera/lens-projection.js";
46
46
  import { applyPhysicalCameraRotation, resolveProjectionDistanceMm } from "../motion/camera-rotation.js";
47
47
  import { prepareIlluminationVignettingThroughput } from "../optics/illumination-vignetting.js";
48
48
  import { distortedImagePoint, undistortedImagePoint } from "../optics/sensor-environment-query.js";
@@ -58,6 +58,8 @@ export function prepareDefocusImageSpaceGeometry(geometry) {
58
58
  // The projection scale: the thin-lens image distance at the finite focus, for every projection model (the environment's).
59
59
  const pd = resolveProjectionDistanceMm(focalLengthMm, focusDistanceM).projectionDistanceMm;
60
60
  const model = lensMapping?.mapping.projectionModel ?? "rectilinear";
61
+ // #569: with "backward-ray" (catalogue fisheyes), a point outside the image circle looks straight back, as the environment's mapped query does.
62
+ const backward = lensMapping?.mapping.outsideImageCircle === "backward-ray";
61
63
  const timeDependent = omega.pitch !== 0 || omega.yaw !== 0 || omega.roll !== 0;
62
64
  // calculateIlluminationVignetting's throughput with the profile validated once (#563: the same numbers, without a result envelope per point).
63
65
  const throughputAt = vignetting ? prepareIlluminationVignettingThroughput(vignetting) : undefined;
@@ -89,7 +91,8 @@ export function prepareDefocusImageSpaceGeometry(geometry) {
89
91
  lz = pd;
90
92
  }
91
93
  else {
92
- const reference = applyPhysicalCameraRotation(lensProjectionDirectionValue(model, pd, ideal.x, ideal.y), displacement(t), "camera-to-reference");
94
+ const reference = applyPhysicalCameraRotation(backward && !insideRadialImageCircle(model, pd, Math.hypot(ideal.x, ideal.y)) ? { x: 0, y: 0, z: -1 } :
95
+ lensProjectionDirectionValue(model, pd, ideal.x, ideal.y), displacement(t), "camera-to-reference");
93
96
  lx = reference.x;
94
97
  ly = reference.y;
95
98
  lz = reference.z;