@photivra/engine 2.18.0 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/CHANGELOG.md +179 -1
  2. package/CITATION.cff +1 -1
  3. package/NODE_SUPPORT.md +7 -5
  4. package/README.md +6 -6
  5. package/SECURITY.md +1 -1
  6. package/THIRD_PARTY.md +14 -10
  7. package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
  8. package/dist/capture/dng-lens-correction-opcodes.js +119 -0
  9. package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
  10. package/dist/capture/focus-control.js +2 -75
  11. package/dist/capture/focus-control.js.map +1 -1
  12. package/dist/capture/native-environment-lean.d.ts +1 -0
  13. package/dist/capture/native-environment-lean.js +2 -6
  14. package/dist/capture/native-environment-lean.js.map +1 -1
  15. package/dist/capture/native-environment-optical-stage.d.ts +1 -0
  16. package/dist/capture/native-environment-optical-stage.js +2 -5
  17. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  18. package/dist/capture/native-environment-raw.js +1 -5
  19. package/dist/capture/native-environment-raw.js.map +1 -1
  20. package/dist/capture/native-raw-dng.d.ts +6 -0
  21. package/dist/capture/native-raw-dng.js +16 -2
  22. package/dist/capture/native-raw-dng.js.map +1 -1
  23. package/dist/capture/release-sequence.js +2 -47
  24. package/dist/capture/release-sequence.js.map +1 -1
  25. package/dist/capture/sensor-raw-producer.js +3 -5
  26. package/dist/capture/sensor-raw-producer.js.map +1 -1
  27. package/dist/color/white-balance.d.ts +2 -0
  28. package/dist/color/white-balance.js +3 -56
  29. package/dist/color/white-balance.js.map +1 -1
  30. package/dist/composition/image-formation-plan.d.ts +1 -0
  31. package/dist/composition/image-formation-plan.js +3 -49
  32. package/dist/composition/image-formation-plan.js.map +1 -1
  33. package/dist/core/evidence-provenance.js +1 -16
  34. package/dist/core/evidence-provenance.js.map +1 -1
  35. package/dist/core/record-validation.d.ts +54 -0
  36. package/dist/core/record-validation.js +176 -0
  37. package/dist/core/record-validation.js.map +1 -1
  38. package/dist/core/scientific-assurance.js +1 -18
  39. package/dist/core/scientific-assurance.js.map +1 -1
  40. package/dist/core/shared-helpers.d.ts +35 -0
  41. package/dist/core/shared-helpers.js +38 -0
  42. package/dist/core/shared-helpers.js.map +1 -0
  43. package/dist/core/spectral-composition.js +1 -36
  44. package/dist/core/spectral-composition.js.map +1 -1
  45. package/dist/core/spectral.js +1 -19
  46. package/dist/core/spectral.js.map +1 -1
  47. package/dist/core/version.d.ts +1 -1
  48. package/dist/core/version.js +1 -1
  49. package/dist/core/version.js.map +1 -1
  50. package/dist/equipment/exposure-capabilities.d.ts +1 -0
  51. package/dist/equipment/exposure-capabilities.js +2 -36
  52. package/dist/equipment/exposure-capabilities.js.map +1 -1
  53. package/dist/equipment/generic-lens-catalogue.d.ts +8 -2
  54. package/dist/equipment/generic-lens-catalogue.js +31 -2
  55. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  56. package/dist/equipment/generic-tier-assets.js +2 -3
  57. package/dist/equipment/generic-tier-assets.js.map +1 -1
  58. package/dist/equipment/generic-tier-presets.js +2 -3
  59. package/dist/equipment/generic-tier-presets.js.map +1 -1
  60. package/dist/equipment/iso-capabilities.js +2 -49
  61. package/dist/equipment/iso-capabilities.js.map +1 -1
  62. package/dist/equipment/metering-capabilities.js +1 -26
  63. package/dist/equipment/metering-capabilities.js.map +1 -1
  64. package/dist/equipment/release-capabilities.js +1 -53
  65. package/dist/equipment/release-capabilities.js.map +1 -1
  66. package/dist/exposure/auto-iso-slowest-shutter.js +1 -7
  67. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
  68. package/dist/exposure/exposure-duration-control.js +1 -16
  69. package/dist/exposure/exposure-duration-control.js.map +1 -1
  70. package/dist/exposure/exposure-mode-resolver.js +1 -17
  71. package/dist/exposure/exposure-mode-resolver.js.map +1 -1
  72. package/dist/exposure/filtered-exposure-time.d.ts +93 -0
  73. package/dist/exposure/filtered-exposure-time.js +129 -0
  74. package/dist/exposure/filtered-exposure-time.js.map +1 -0
  75. package/dist/exposure/flash-ttl.d.ts +1 -0
  76. package/dist/exposure/flash-ttl.js +1 -1
  77. package/dist/exposure/flash-ttl.js.map +1 -1
  78. package/dist/exposure/flash.js +1 -49
  79. package/dist/exposure/flash.js.map +1 -1
  80. package/dist/exposure/metering-scene-radiance.js +1 -25
  81. package/dist/exposure/metering-scene-radiance.js.map +1 -1
  82. package/dist/exposure/metering-target.js +1 -30
  83. package/dist/exposure/metering-target.js.map +1 -1
  84. package/dist/exposure/metering-temporal.js +1 -8
  85. package/dist/exposure/metering-temporal.js.map +1 -1
  86. package/dist/exposure/metering.js +1 -72
  87. package/dist/exposure/metering.js.map +1 -1
  88. package/dist/exposure/program-line.js +1 -45
  89. package/dist/exposure/program-line.js.map +1 -1
  90. package/dist/facade/develop-joined-parts.d.ts +29 -0
  91. package/dist/facade/develop-joined-parts.js +33 -0
  92. package/dist/facade/develop-joined-parts.js.map +1 -0
  93. package/dist/facade/display-view-gpu.d.ts +7 -4
  94. package/dist/facade/display-view-gpu.js +4 -4
  95. package/dist/facade/display-view-gpu.js.map +1 -1
  96. package/dist/facade/display-view-lens-signal.d.ts +1 -1
  97. package/dist/facade/display-view-lens-signal.js +5 -3
  98. package/dist/facade/display-view-lens-signal.js.map +1 -1
  99. package/dist/facade/display-view.d.ts +3 -1
  100. package/dist/facade/display-view.js +1 -1
  101. package/dist/facade/display-view.js.map +1 -1
  102. package/dist/facade/fast-methods.d.ts +50 -0
  103. package/dist/facade/fast-methods.js +50 -0
  104. package/dist/facade/fast-methods.js.map +1 -0
  105. package/dist/facade/fast-resolution.d.ts +12 -0
  106. package/dist/facade/fast-resolution.js +18 -0
  107. package/dist/facade/fast-resolution.js.map +1 -0
  108. package/dist/facade/generic-lens-capture.d.ts +6 -3
  109. package/dist/facade/generic-lens-capture.js +6 -3
  110. package/dist/facade/generic-lens-capture.js.map +1 -1
  111. package/dist/facade/simulate-capture.d.ts +88 -16
  112. package/dist/facade/simulate-capture.js +279 -153
  113. package/dist/facade/simulate-capture.js.map +1 -1
  114. package/dist/facade/simulate-display-view.d.ts +3 -2
  115. package/dist/facade/simulate-display-view.js +22 -12
  116. package/dist/facade/simulate-display-view.js.map +1 -1
  117. package/dist/gpu/chart-composite-gpu.js +1 -3
  118. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  119. package/dist/gpu/chart-radiance-gpu.js +1 -5
  120. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  121. package/dist/gpu/display-view-quality-gpu.d.ts +13 -1
  122. package/dist/gpu/display-view-quality-gpu.js +4 -8
  123. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  124. package/dist/gpu/display-view-speed-gpu.js +3 -25
  125. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  126. package/dist/gpu/sensor-stage-gpu.js +1 -5
  127. package/dist/gpu/sensor-stage-gpu.js.map +1 -1
  128. package/dist/gpu/webgpu-runtime.d.ts +2 -0
  129. package/dist/gpu/webgpu-runtime.js +3 -0
  130. package/dist/gpu/webgpu-runtime.js.map +1 -1
  131. package/dist/index.d.ts +11 -1
  132. package/dist/index.js +8 -0
  133. package/dist/index.js.map +1 -1
  134. package/dist/motion/camera-rotation.js +1 -6
  135. package/dist/motion/camera-rotation.js.map +1 -1
  136. package/dist/motion/capture-rotation-inverse-mapping.d.ts +12 -0
  137. package/dist/motion/capture-rotation-inverse-mapping.js +2 -2
  138. package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -1
  139. package/dist/motion/capture-rotation-temporal-quadrature.d.ts +1 -0
  140. package/dist/motion/capture-rotation-temporal-quadrature.js +1 -1
  141. package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -1
  142. package/dist/motion/capture-rotation-trajectory.js +2 -17
  143. package/dist/motion/capture-rotation-trajectory.js.map +1 -1
  144. package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts +1 -0
  145. package/dist/motion/capture-translation-parallax-temporal-quadrature.js +3 -14
  146. package/dist/motion/capture-translation-parallax-temporal-quadrature.js.map +1 -1
  147. package/dist/motion/extended-object-projection.js +5 -40
  148. package/dist/motion/extended-object-projection.js.map +1 -1
  149. package/dist/optics/aperture-diameter.d.ts +47 -0
  150. package/dist/optics/aperture-diameter.js +50 -0
  151. package/dist/optics/aperture-diameter.js.map +1 -0
  152. package/dist/optics/atmospheric-haze.js +1 -7
  153. package/dist/optics/atmospheric-haze.js.map +1 -1
  154. package/dist/optics/brown-conrady-distortion.js +1 -7
  155. package/dist/optics/brown-conrady-distortion.js.map +1 -1
  156. package/dist/optics/complex-pupil-psf.d.ts +1 -0
  157. package/dist/optics/complex-pupil-psf.js +2 -84
  158. package/dist/optics/complex-pupil-psf.js.map +1 -1
  159. package/dist/optics/detail-limit.d.ts +182 -0
  160. package/dist/optics/detail-limit.js +336 -0
  161. package/dist/optics/detail-limit.js.map +1 -0
  162. package/dist/optics/focus-state.js +1 -11
  163. package/dist/optics/focus-state.js.map +1 -1
  164. package/dist/optics/front-of-lens-filter.d.ts +5 -0
  165. package/dist/optics/front-of-lens-filter.js +3 -65
  166. package/dist/optics/front-of-lens-filter.js.map +1 -1
  167. package/dist/optics/generic-lens-psf.d.ts +18 -2
  168. package/dist/optics/generic-lens-psf.js +25 -2
  169. package/dist/optics/generic-lens-psf.js.map +1 -1
  170. package/dist/optics/generic-nd-filter-presets.d.ts +12 -0
  171. package/dist/optics/generic-nd-filter-presets.js +48 -0
  172. package/dist/optics/generic-nd-filter-presets.js.map +1 -0
  173. package/dist/optics/illumination-vignetting.js +1 -6
  174. package/dist/optics/illumination-vignetting.js.map +1 -1
  175. package/dist/optics/lens-psf-profile.d.ts +4 -1
  176. package/dist/optics/lens-psf-profile.js +12 -111
  177. package/dist/optics/lens-psf-profile.js.map +1 -1
  178. package/dist/optics/longitudinal-chromatic-aberration.js +1 -7
  179. package/dist/optics/longitudinal-chromatic-aberration.js.map +1 -1
  180. package/dist/optics/pupil-vignetting.js +1 -7
  181. package/dist/optics/pupil-vignetting.js.map +1 -1
  182. package/dist/optics/radial-distortion.js +1 -6
  183. package/dist/optics/radial-distortion.js.map +1 -1
  184. package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
  185. package/dist/optics/scene-to-sensor-irradiance.js +11 -96
  186. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  187. package/dist/optics/stray-light.d.ts +1 -0
  188. package/dist/optics/stray-light.js +1 -1
  189. package/dist/optics/stray-light.js.map +1 -1
  190. package/dist/optics/zernike-wavefront.js +1 -7
  191. package/dist/optics/zernike-wavefront.js.map +1 -1
  192. package/dist/output/capture-geometry.js +1 -8
  193. package/dist/output/capture-geometry.js.map +1 -1
  194. package/dist/output/crop-to-match-focal-length.d.ts +62 -0
  195. package/dist/output/crop-to-match-focal-length.js +63 -0
  196. package/dist/output/crop-to-match-focal-length.js.map +1 -0
  197. package/dist/output/developed-lens-corrections.d.ts +62 -0
  198. package/dist/output/developed-lens-corrections.js +135 -0
  199. package/dist/output/developed-lens-corrections.js.map +1 -0
  200. package/dist/output/generic-lens-corrections.d.ts +5 -7
  201. package/dist/output/generic-lens-corrections.js +11 -10
  202. package/dist/output/generic-lens-corrections.js.map +1 -1
  203. package/dist/output/geometric-transforms.js +1 -4
  204. package/dist/output/geometric-transforms.js.map +1 -1
  205. package/dist/output/lens-corrections.d.ts +12 -2
  206. package/dist/output/lens-corrections.js +22 -7
  207. package/dist/output/lens-corrections.js.map +1 -1
  208. package/dist/output/native-capture-sdr.d.ts +2 -2
  209. package/dist/output/native-capture-sdr.js +3 -2
  210. package/dist/output/native-capture-sdr.js.map +1 -1
  211. package/dist/output/print-contrast-reference.js +1 -1
  212. package/dist/output/print-contrast-reference.js.map +1 -1
  213. package/dist/output/print-size-blur-noise.d.ts +110 -0
  214. package/dist/output/print-size-blur-noise.js +138 -0
  215. package/dist/output/print-size-blur-noise.js.map +1 -0
  216. package/dist/processing/development-detail.d.ts +29 -0
  217. package/dist/processing/development-detail.js +128 -107
  218. package/dist/processing/development-detail.js.map +1 -1
  219. package/dist/schema/illumination-temporal.js +3 -72
  220. package/dist/schema/illumination-temporal.js.map +1 -1
  221. package/dist/schema/illumination.d.ts +1 -0
  222. package/dist/schema/illumination.js +2 -65
  223. package/dist/schema/illumination.js.map +1 -1
  224. package/dist/schema/scene-radiance-temporal.js +2 -14
  225. package/dist/schema/scene-radiance-temporal.js.map +1 -1
  226. package/dist/schema/scene-radiance.js +2 -60
  227. package/dist/schema/scene-radiance.js.map +1 -1
  228. package/dist/schema/validation.d.ts +2 -0
  229. package/dist/schema/validation.js +2 -17
  230. package/dist/schema/validation.js.map +1 -1
  231. package/dist/sensor/accumulated-charge.js +2 -59
  232. package/dist/sensor/accumulated-charge.js.map +1 -1
  233. package/dist/sensor/architecture.js +1 -16
  234. package/dist/sensor/architecture.js.map +1 -1
  235. package/dist/sensor/camera-saturation-capacity.d.ts +1 -0
  236. package/dist/sensor/camera-saturation-capacity.js +3 -55
  237. package/dist/sensor/camera-saturation-capacity.js.map +1 -1
  238. package/dist/sensor/capture-color-sampling-binding.js +1 -26
  239. package/dist/sensor/capture-color-sampling-binding.js.map +1 -1
  240. package/dist/sensor/capture-mode-timing.js +1 -39
  241. package/dist/sensor/capture-mode-timing.js.map +1 -1
  242. package/dist/sensor/capture-mode.js +1 -23
  243. package/dist/sensor/capture-mode.js.map +1 -1
  244. package/dist/sensor/color-sampling.js +1 -26
  245. package/dist/sensor/color-sampling.js.map +1 -1
  246. package/dist/sensor/constant-rate-temporal-integration.js +2 -46
  247. package/dist/sensor/constant-rate-temporal-integration.js.map +1 -1
  248. package/dist/sensor/dark-current.js +2 -45
  249. package/dist/sensor/dark-current.js.map +1 -1
  250. package/dist/sensor/eqe-electron-rate.js +2 -41
  251. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  252. package/dist/sensor/exposure-window.d.ts +7 -0
  253. package/dist/sensor/exposure-window.js +9 -24
  254. package/dist/sensor/exposure-window.js.map +1 -1
  255. package/dist/sensor/iso-gain.js +1 -7
  256. package/dist/sensor/iso-gain.js.map +1 -1
  257. package/dist/sensor/iso-signal-chain.js +1 -43
  258. package/dist/sensor/iso-signal-chain.js.map +1 -1
  259. package/dist/sensor/optical-stack.js +1 -25
  260. package/dist/sensor/optical-stack.js.map +1 -1
  261. package/dist/sensor/photon-energy.js +1 -26
  262. package/dist/sensor/photon-energy.js.map +1 -1
  263. package/dist/sensor/physical-charge-capacity.js +3 -77
  264. package/dist/sensor/physical-charge-capacity.js.map +1 -1
  265. package/dist/sensor/radiometry-readiness.js +2 -23
  266. package/dist/sensor/radiometry-readiness.js.map +1 -1
  267. package/dist/sensor/raw-readout.d.ts +3 -0
  268. package/dist/sensor/raw-readout.js +4 -66
  269. package/dist/sensor/raw-readout.js.map +1 -1
  270. package/dist/sensor/raw-reconstruction.js +2 -64
  271. package/dist/sensor/raw-reconstruction.js.map +1 -1
  272. package/dist/sensor/readout-exposure-linkage.js +1 -9
  273. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  274. package/dist/sensor/readout-timing.js +4 -79
  275. package/dist/sensor/readout-timing.js.map +1 -1
  276. package/dist/sensor/response-application-compatibility.d.ts +2 -0
  277. package/dist/sensor/response-application-compatibility.js +4 -35
  278. package/dist/sensor/response-application-compatibility.js.map +1 -1
  279. package/dist/sensor/response-operating-range.d.ts +1 -0
  280. package/dist/sensor/response-operating-range.js +3 -79
  281. package/dist/sensor/response-operating-range.js.map +1 -1
  282. package/dist/sensor/responsivity-photocurrent.js +4 -114
  283. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  284. package/dist/sensor/sampling-aperture.js +1 -25
  285. package/dist/sensor/sampling-aperture.js.map +1 -1
  286. package/dist/sensor/sensor-characteristics.d.ts +137 -0
  287. package/dist/sensor/sensor-characteristics.js +115 -0
  288. package/dist/sensor/sensor-characteristics.js.map +1 -0
  289. package/dist/sensor/spatial-sample-reduction.js +3 -24
  290. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  291. package/dist/sensor/spatial-sampling-quadrature.js +1 -3
  292. package/dist/sensor/spatial-sampling-quadrature.js.map +1 -1
  293. package/dist/sensor/spatio-spectral-reduction.js +1 -18
  294. package/dist/sensor/spatio-spectral-reduction.js.map +1 -1
  295. package/dist/sensor/spectral-coverage.d.ts +10 -1
  296. package/dist/sensor/spectral-coverage.js +1 -1
  297. package/dist/sensor/spectral-coverage.js.map +1 -1
  298. package/dist/sensor/spectral-quadrature.js +3 -41
  299. package/dist/sensor/spectral-quadrature.js.map +1 -1
  300. package/dist/sensor/spectral-response.js +1 -47
  301. package/dist/sensor/spectral-response.js.map +1 -1
  302. package/dist/sensor/temporal-photo-signal.d.ts +3 -0
  303. package/dist/sensor/temporal-photo-signal.js +5 -2
  304. package/dist/sensor/temporal-photo-signal.js.map +1 -1
  305. package/dist/stabilization/camera-shake.js +1 -6
  306. package/dist/stabilization/camera-shake.js.map +1 -1
  307. package/dist/stabilization/system.d.ts +9 -0
  308. package/dist/stabilization/system.js +7 -86
  309. package/dist/stabilization/system.js.map +1 -1
  310. package/docs/AGENT_GUIDE.md +5 -0
  311. package/docs/API_REFERENCE.md +116 -3
  312. package/docs/QUICK_START.md +178 -14
  313. package/docs/USAGE.md +111 -3
  314. package/docs/api/capture-native-raw-dng.md +6 -0
  315. package/docs/api/core-version.md +1 -1
  316. package/docs/api/equipment-generic-lens-catalogue.md +7 -2
  317. package/docs/api/exports.json +203 -3
  318. package/docs/api/exposure-filtered-exposure-time.md +105 -0
  319. package/docs/api/facade-develop-joined-parts.md +32 -0
  320. package/docs/api/facade-display-view-gpu.md +5 -2
  321. package/docs/api/facade-fast-methods.md +91 -0
  322. package/docs/api/facade-simulate-capture.md +72 -16
  323. package/docs/api/facade-simulate-display-view.md +3 -2
  324. package/docs/api/optics-aperture-diameter.md +51 -0
  325. package/docs/api/optics-detail-limit.md +203 -0
  326. package/docs/api/optics-generic-nd-filter-presets.md +34 -0
  327. package/docs/api/optics-lens-psf-profile.md +3 -1
  328. package/docs/api/output-crop-to-match-focal-length.md +67 -0
  329. package/docs/api/output-developed-lens-corrections.md +12 -0
  330. package/docs/api/output-generic-lens-corrections.md +3 -10
  331. package/docs/api/output-lens-corrections.md +11 -1
  332. package/docs/api/output-print-size-blur-noise.md +115 -0
  333. package/docs/api/sensor-sensor-characteristics.md +105 -0
  334. package/llms.txt +5 -0
  335. package/package.json +1 -1
@@ -0,0 +1,203 @@
1
+ # optics/detail-limit.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## calculateDetailLimit
6
+
7
+ Which limits detail for given settings: the lens (diffraction at this f-number, aberrations, defocus) or the sensor's pixel
8
+ pitch. Returns the optics' resolution (MTF50 and the diffraction cut-off, cycles/mm), the sampling's (Nyquist frequency
9
+ and, with a Bayer filter, the red and blue sites'), the system MTF50 (optics × pixel aperture), the f-number from which
10
+ diffraction limits on this sensor, and which one limits.
11
+
12
+ The sensor limits when the optics still pass 50 % contrast at the Nyquist frequency 1/(2p); otherwise the lens does,
13
+ through diffraction when its wavefront error is within the Maréchal criterion (RMS ≤ λ/14), else through aberrations or
14
+ defocus. The optics MTF is the exact scalar OTF of the stated wavefront at one wavelength; the decision rule and the
15
+ catalogue lenses' wavefronts are approximations.
16
+
17
+ Example: an ideal 50 mm lens at f/22 on a 36 × 24 mm, 6000-pixel-wide sensor (6 µm) is limited by diffraction (optics
18
+ MTF50 ≈ 33 cycles/mm against a Nyquist frequency of ≈ 83); at f/4 the pixel pitch limits.
19
+
20
+ ```ts
21
+ export function calculateDetailLimit(input: CalculateDetailLimitInput): CalculationResult<DetailLimit>;
22
+ ```
23
+
24
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
25
+
26
+ ## CalculateDetailLimitInput
27
+
28
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
29
+
30
+ ```ts
31
+ export interface CalculateDetailLimitInput {
32
+ /** Active imaging area and horizontal pixel count; pitch = widthMm / pixelWidth. */
33
+ sensor: {
34
+ widthMm: number;
35
+ heightMm: number;
36
+ pixelWidth: number;
37
+ /** Default `"bayer"`. `"none"`: every site records full detail (a monochrome sensor). */
38
+ colorFilterArray?: "bayer" | "none";
39
+ };
40
+ lens: DetailLimitIdealLens | DetailLimitCatalogueLens;
41
+ /** f-number set on the lens. */
42
+ fNumber: number;
43
+ /**
44
+ * Focus and subject distances from the lens in meters. Omitted: focused at infinity, subject in focus. `subjectDistanceM`
45
+ * omitted: the subject is at the focus distance.
46
+ */
47
+ focus?: { focusDistanceM: number; subjectDistanceM?: number };
48
+ /** Field height as a fraction of the half-diagonal, 0 (center, default) to 1 (corner). Only aberrations depend on it. */
49
+ fieldPosition?: number;
50
+ /** Wavelength in nanometres, 380–780. Default 550. */
51
+ wavelengthNm?: number;
52
+ }
53
+ ```
54
+
55
+ ## DETAIL_LIMIT_LIMITS
56
+
57
+ Limits of `calculateDetailLimit`: the wavelength range and the largest wavefront (sum of the magnitudes of its terms, in
58
+ waves) whose transfer function is integrated. Beyond it the blur is geometric and the quadrature would grow without
59
+ bound; use `calculateDefocusCircle` for such a blur.
60
+
61
+ ```ts
62
+ DETAIL_LIMIT_LIMITS = Object.freeze({ minimumWavelengthNm: 380, maximumWavelengthNm: 780, maximumWavefrontWaves: 64 })
63
+ ```
64
+
65
+ ## DetailLimit
66
+
67
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
68
+
69
+ ```ts
70
+ export interface DetailLimit {
71
+ /** The lens or the sensor. */
72
+ limitedBy: DetailLimitedBy;
73
+ /** Diffraction, aberrations or defocus (the lens), or the pixel pitch (the sensor). */
74
+ limitingFactor: DetailLimitingFactor;
75
+ /** Optics MTF50 / Nyquist frequency: at least 1 when the sensor limits. */
76
+ opticsToNyquistRatio: number;
77
+ fNumber: number;
78
+ wavelengthNm: number;
79
+ /** Physical focal length used (the catalogue lens's on this sensor), mm. */
80
+ focalLengthMm: number;
81
+ fieldPosition: number;
82
+ optics: DetailLimitOptics;
83
+ sampling: DetailLimitSampling;
84
+ system: DetailLimitSystem;
85
+ /**
86
+ * The f-number at which an aberration-free lens in focus has its MTF50 at the Nyquist frequency (2·p·0.40397/λ at
87
+ * infinity focus): from it on, diffraction limits detail on this sensor whatever the lens.
88
+ */
89
+ diffractionLimitedFNumber: number;
90
+ basis: Readonly<Record<"optics" | "sampling" | "system" | "limitedBy", DetailLimitBasis>>;
91
+ }
92
+ ```
93
+
94
+ ## DetailLimitBasis
95
+
96
+ Whether a quantity is exact under its stated conditions or an approximation.
97
+
98
+ ```ts
99
+ export interface DetailLimitBasis { kind: "exact" | "approximation"; statement: string }
100
+ ```
101
+
102
+ ## DetailLimitCatalogueLens
103
+
104
+ A generic catalogue lens (`GENERIC_LENS_IDS`), resolved for the sensor as `resolveGenericLens` does.
105
+
106
+ ```ts
107
+ export interface DetailLimitCatalogueLens {
108
+ kind: "catalogue";
109
+ lensId: string;
110
+ }
111
+ ```
112
+
113
+ ## DetailLimitedBy
114
+
115
+ Which component limits detail.
116
+
117
+ ```ts
118
+ export type DetailLimitedBy = "lens" | "sensor";
119
+ ```
120
+
121
+ ## DetailLimitIdealLens
122
+
123
+ A lens with no aberrations (diffraction only).
124
+
125
+ ```ts
126
+ export interface DetailLimitIdealLens {
127
+ kind: "ideal";
128
+ /** Physical focal length in millimeters (for the working f-number and the defocus of a subject off the focus distance). */
129
+ focalLengthMm: number;
130
+ }
131
+ ```
132
+
133
+ ## DetailLimitingFactor
134
+
135
+ What limits it: the lens's diffraction, aberrations or defocus, or the sensor's pixel pitch.
136
+
137
+ ```ts
138
+ export type DetailLimitingFactor = "diffraction" | "aberrations" | "defocus" | "pixel-pitch";
139
+ ```
140
+
141
+ ## DetailLimitOptics
142
+
143
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
144
+
145
+ ```ts
146
+ export interface DetailLimitOptics {
147
+ /** Working f-number N_w = N·v/f (thin-lens image distance v; N at infinity focus). */
148
+ workingFNumber: number;
149
+ /** Diffraction cut-off 1/(λ·N_w) in cycles/mm: no optics passes detail beyond it. */
150
+ cutoffCyclesPerMm: number;
151
+ /** Frequency where the optics' MTF falls to 0.5, the lower of the two directions, cycles/mm. */
152
+ mtf50CyclesPerMm: number;
153
+ /** MTF50 for detail along the radius from the image center (tangential), cycles/mm. */
154
+ tangentialMtf50CyclesPerMm: number;
155
+ /** MTF50 for detail across the radius (sagittal), cycles/mm. Equal to the tangential value at the center. */
156
+ sagittalMtf50CyclesPerMm: number;
157
+ /** MTF50 of an aberration-free lens in focus at the same working f-number (0.40397·cut-off), cycles/mm. */
158
+ diffractionMtf50CyclesPerMm: number;
159
+ /** Optics MTF at the sensor's Nyquist frequency (lower direction). */
160
+ mtfAtNyquist: number;
161
+ /** RMS wavefront error in waves at the wavelength: of the aberrations, of the defocus, and of both together. */
162
+ rmsWavefrontErrorWaves: { aberrations: number; defocus: number; total: number };
163
+ /** Whether the total RMS wavefront error is within the Maréchal criterion λ/14. */
164
+ withinMarechalCriterion: boolean;
165
+ /** Geometric blur-circle diameter of the defocus on the sensor, mm (0 in focus). */
166
+ defocusBlurDiameterMm: number;
167
+ }
168
+ ```
169
+
170
+ ## DetailLimitSampling
171
+
172
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
173
+
174
+ ```ts
175
+ export interface DetailLimitSampling {
176
+ colorFilterArray: "bayer" | "none";
177
+ /** Pixel pitch in micrometres: widthMm / pixelWidth. */
178
+ pixelPitchMicrometers: number;
179
+ /** 1/(2p), cycles/mm: the finest detail the pixel grid records without aliasing (Bayer green sites too, along rows and columns). */
180
+ nyquistCyclesPerMm: number;
181
+ /** Bayer only: 1/(4p), the Nyquist frequency of the red and blue sites. Null without a color filter array. */
182
+ redBlueNyquistCyclesPerMm: number | null;
183
+ /** Pixel aperture MTF at the Nyquist frequency: sinc(1/2) = 2/π. */
184
+ pixelApertureMtfAtNyquist: number;
185
+ /** Frequency where the pixel aperture MTF falls to 0.5 (0.60335/p), cycles/mm: beyond the Nyquist frequency. */
186
+ pixelApertureMtf50CyclesPerMm: number;
187
+ }
188
+ ```
189
+
190
+ ## DetailLimitSystem
191
+
192
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
193
+
194
+ ```ts
195
+ export interface DetailLimitSystem {
196
+ /** MTF50 of optics × pixel aperture (lower direction), cycles/mm. Detail above the Nyquist frequency aliases. */
197
+ mtf50CyclesPerMm: number;
198
+ /** The same in line pairs per picture height (cycles/mm × heightMm). */
199
+ mtf50LinePairsPerPictureHeight: number;
200
+ /** System MTF at the Nyquist frequency (lower direction). */
201
+ mtfAtNyquist: number;
202
+ }
203
+ ```
@@ -0,0 +1,34 @@
1
+ # optics/generic-nd-filter-presets.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## GENERIC_ND_FILTER_PROFILES
6
+
7
+ Frozen generic ND filter profiles keyed by stops: `GENERIC_ND_FILTER_PROFILES[10]` transmits
8
+ exactly 1/1024. Spectrally flat over 380–780 nm (air), idealized (`scientificStatus:
9
+ "approximation"`), unbranded. 1/125 s with the 10-stop preset gives 1024/125 ≈ 8.19 s in
10
+ `calculateFilteredExposureTime`.
11
+
12
+ ```ts
13
+ GENERIC_ND_FILTER_PROFILES: Readonly<Record<GenericNdFilterStops, FrontOfLensFilterProfile>> = freezeOwnedData({
14
+ 3: idealNdProfile(3),
15
+ 6: idealNdProfile(6),
16
+ 10: idealNdProfile(10)
17
+ })
18
+ ```
19
+
20
+ ## GENERIC_ND_FILTER_STOPS
21
+
22
+ Attenuations, in stops, of the generic ND presets.
23
+
24
+ ```ts
25
+ GENERIC_ND_FILTER_STOPS = Object.freeze([3, 6, 10] as const)
26
+ ```
27
+
28
+ ## GenericNdFilterStops
29
+
30
+ One of the generic ND preset attenuations: 3, 6 or 10 stops.
31
+
32
+ ```ts
33
+ export type GenericNdFilterStops = (typeof GENERIC_ND_FILTER_STOPS)[number];
34
+ ```
@@ -200,7 +200,9 @@ Kernel interpolation between regular-grid nodes.
200
200
  - `rotationally-symmetric-radial-field-centroid-aligned`: for a rotationally symmetric lens. Nodes
201
201
  are declared only along the +X field axis (fieldYmm = [0], fieldXmm >= 0). A query at field
202
202
  (x, y) is interpolated, with centroid alignment, at radius hypot(x, y). The kernel is then
203
- rotated by atan2(y, x) about its centre. The sample pitch must be square.
203
+ rotated by atan2(y, x) about its centre, with the kernel row index increasing with image +Y (the
204
+ convention the environment PSF quadrature applies), so an asymmetric kernel points the same way
205
+ as in the 2-D grid modes. The sample pitch must be square.
204
206
  Shifts and rotations use bilinear resampling. This smooths the kernel slightly (by at most about
205
207
  one sample). Samples outside the kernel are zero, and the result is renormalised to unit energy.
206
208
 
@@ -0,0 +1,67 @@
1
+ # output/crop-to-match-focal-length.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## calculateCropToMatchFocalLength
6
+
7
+ The centred crop that makes an image from a shorter lens match a longer lens's framing from the
8
+ same position: crop factor target ÷ focal length, and the pixels and megapixels left.
9
+
10
+ Exact for ideal rectilinear lenses focused at infinity, to whole pixels (the crop is
11
+ `calculateCenteredCrop`'s, which keeps the aspect ratio). At close focus the matching factor is
12
+ larger, F·(d − f)/(f·(d − F)) at object distance d (thin lens); that, distortion and focus
13
+ breathing are not modelled. The crop has the longer lens's framing and perspective but fewer
14
+ pixels: it records no more detail than the cropped part of the shorter lens's image.
15
+
16
+ Example: a 24 MP (6000 × 4000) image at 50 mm cropped to match 100 mm: factor 2, 3000 × 2000,
17
+ 6 MP.
18
+
19
+ ```ts
20
+ export function calculateCropToMatchFocalLength(
21
+ input: CalculateCropToMatchFocalLengthInput
22
+ ): CalculationResult<CropToMatchFocalLength>;
23
+ ```
24
+
25
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
26
+
27
+ ## CalculateCropToMatchFocalLengthInput
28
+
29
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
30
+
31
+ ```ts
32
+ export interface CalculateCropToMatchFocalLengthInput {
33
+ /** Focal length the image was taken with, in millimetres. */
34
+ focalLengthMm: number;
35
+ /** Longer focal length whose framing the crop should match, in millimetres. At least `focalLengthMm`. */
36
+ targetFocalLengthMm: number;
37
+ /** Image width in pixels (for example the sensor's output width). */
38
+ pixelWidth: number;
39
+ /** Image height in pixels. */
40
+ pixelHeight: number;
41
+ }
42
+ ```
43
+
44
+ ## CropToMatchFocalLength
45
+
46
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
47
+
48
+ ```ts
49
+ export interface CropToMatchFocalLength {
50
+ /** Focal length supplied by the caller, in millimetres. */
51
+ focalLengthMm: number;
52
+ /** Target focal length supplied by the caller, in millimetres. */
53
+ targetFocalLengthMm: number;
54
+ /** Linear crop factor targetFocalLengthMm / focalLengthMm (1 means no crop). */
55
+ cropFactor: number;
56
+ /** Width left after the centred crop, in pixels (as `calculateCenteredCrop`). */
57
+ pixelWidth: number;
58
+ /** Height left after the centred crop, in pixels. */
59
+ pixelHeight: number;
60
+ /** Pixels left, in millions. */
61
+ megapixels: number;
62
+ /** Megapixels of the uncropped image. */
63
+ sourceMegapixels: number;
64
+ /** Fraction of the original pixel area retained, about 1 / cropFactor². */
65
+ retainedAreaFraction: number;
66
+ }
67
+ ```
@@ -0,0 +1,12 @@
1
+ # output/developed-lens-corrections.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM
6
+
7
+ Effective wavelengths of the red, green and blue channels for the lateral-colour map, nm: a generic approximation of where
8
+ a camera's channel responses peak, not a measured camera.
9
+
10
+ ```ts
11
+ GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM: Readonly<Record<LensCorrectionChannel, number>> = Object.freeze({ red: 610, green: 540, blue: 465 })
12
+ ```
@@ -6,7 +6,9 @@ The package and root API versions are recorded once in [Navigation](../API_REFER
6
6
 
7
7
  Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own
8
8
  r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D(λ_channel), and the
9
- relative-illumination gain at the state's aperture (strength 1). The result passes `parseGenericLensCorrectionProfile`.
9
+ relative-illumination gain at the state's aperture (strength 1). From profile version 1.1.0 (#584) the vignetting component
10
+ (version 1.1.0) carries the distortion's area factor (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's
11
+ sensor point, so it flattens a flat-field capture. The result passes `parseGenericLensCorrectionProfile`.
10
12
 
11
13
  ```ts
12
14
  export function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile;
@@ -28,12 +30,3 @@ export interface CreateGenericLensCorrectionProfileInput {
28
30
  state: OpticalProfileState;
29
31
  }
30
32
  ```
31
-
32
- ## GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM
33
-
34
- Effective wavelengths of the red, green and blue channels for the lateral-colour map, nm: a generic approximation of where
35
- a camera's channel responses peak, not a measured camera.
36
-
37
- ```ts
38
- GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM: Readonly<Record<LensCorrectionChannel, number>> = Object.freeze({ red: 610, green: 540, blue: 465 })
39
- ```
@@ -31,7 +31,9 @@ This call is synchronous; any supplied provider must follow its explicit synchro
31
31
  ## calculatePeripheralIlluminationCorrection
32
32
 
33
33
  Applies a gain after capture and reports the variance before clipping. It does not rewrite
34
- the photon or SNR (signal-to-noise ratio) history of the capture.
34
+ the photon or SNR (signal-to-noise ratio) history of the capture. The gain is T^(−strength), T the relative illumination at
35
+ `imagePointMm`: E(ρ) of the component's profile, divided by det J(ρ_i) when the component has a `distortionAreaFactor` (#584;
36
+ then `imagePointMm` is the capture's sensor point, where the capture applied both).
35
37
 
36
38
  ```ts
37
39
  export function calculatePeripheralIlluminationCorrection(input: {
@@ -77,6 +79,14 @@ export interface IlluminationLensCorrection extends CorrectionComponentIdentity
77
79
  kind: "peripheral-illumination";
78
80
  profile: IlluminationVignettingProfile;
79
81
  strength: number;
82
+ /**
83
+ * Optional (#584): the capture's radial distortion r_d = r(1 + k1ρ² + k2ρ⁴ + k3ρ⁶), whose area factor 1/det J the capture also
84
+ * multiplied into the irradiance (det J = (1 + k1ρ_i² + k2ρ_i⁴ + k3ρ_i⁶)(1 + 3k1ρ_i² + 5k2ρ_i⁴ + 7k3ρ_i⁶) at the ideal point ρ_i that
85
+ * the lens images to the sensor point). With it, the relative illumination corrected is E(ρ_s)/det J(ρ_i), evaluated at the
86
+ * capture's sensor (source) point, so strength 1 flattens a flat field. It holds whether or not the distortion itself is corrected.
87
+ * Without it the gain is 1/E alone, as before.
88
+ */
89
+ distortionAreaFactor?: RadialDistortionProfile;
80
90
  }
81
91
  ```
82
92
 
@@ -0,0 +1,115 @@
1
+ # output/print-size-blur-noise.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## calculatePrintSizeLimitWithBlurAndNoise
6
+
7
+ Calculates the largest print, at a fixed viewing distance, at which the capture still looks sharp and clean, and names
8
+ the factor that limits it: pixels, blur or noise. An approximation throughout; the equations are exact under its model.
9
+
10
+ With θ the criterion angle, d the viewing distance, s = 2 d tan(θ/2) the length θ covers on the print, N_w the raster
11
+ width in pixels, b the blur diameter in pixels and SNR_px the per-pixel SNR (print width W, height in proportion):
12
+ - pixels: a print pixel (W / N_w) may cover at most s, so W ≤ N_w·s (as `calculatePrintPlan` with
13
+ `angular-pixel-pitch`, before its whole-pixel rounding);
14
+ - blur: the blur on the print (b·W / N_w) may cover at most s, so W ≤ N_w·s / b: the criterion of an acceptable circle
15
+ of confusion with the eye's resolution as the limit;
16
+ - noise: the eye averages the (W / N_w) wide pixels inside s, so the visible SNR is SNR_px·(N_w·s / W) while a pixel is
17
+ smaller than s (the mean of n independent values has 1/√n of their standard deviation); W ≤ N_w·s·SNR_px / SNR_min,
18
+ and noise never limits when SNR_px ≥ SNR_min.
19
+ One arcminute is normal (20/20) acuity under optimal conditions (Z. Alomar, K. D. Bojikian, "Visual Acuity Testing in
20
+ Adults", EyeWiki, American Academy of Ophthalmology, https://eyewiki.aao.org/Visual_Acuity_Testing_in_Adults, last edited
21
+ June 16, 2026); the SNR threshold has no default and is the caller's. Blur parts add in root-sum-square, which is exact
22
+ for Gaussian spots (variances of independent displacements add: C. M. Grinstead, J. L. Snell, "Introduction to
23
+ Probability", 2nd rev. ed., AMS 1997, sec. 6.2, free under the GNU FDL) and an approximation for discs and Airy patterns.
24
+
25
+ ```ts
26
+ export function calculatePrintSizeLimitWithBlurAndNoise(value: PrintSizeLimitWithBlurAndNoiseInput): CalculationResult<PrintSizeLimitWithBlurAndNoise>;
27
+ ```
28
+
29
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
30
+
31
+ ## PrintBlurInput
32
+
33
+ The capture's blur at the sensor plane, as blur-spot diameters in millimetres.
34
+ `components` combines the parts by root-sum-square (an approximation, see the docs); `measured-total` is one measured diameter.
35
+ A zero component is allowed and means that part is left out. Engine helpers give the parts: `calculateDefocusCircle` (defocus),
36
+ `calculateAiryDisk` (diffraction, micrometres), `calculateProjectedMotionBlur` (motion length).
37
+
38
+ ```ts
39
+ export type PrintBlurInput =
40
+ | { kind: "components"; lensMm?: number; defocusMm?: number; diffractionMm?: number; motionMm?: number }
41
+ | { kind: "measured-total"; diameterMm: number };
42
+ ```
43
+
44
+ ## PrintImageMm
45
+
46
+ A print size in millimetres at the raster's aspect ratio.
47
+
48
+ ```ts
49
+ export interface PrintImageMm { width: number; height: number }
50
+ ```
51
+
52
+ ## PrintNoiseInput
53
+
54
+ The noise of one pixel of the printed raster, for the tone the caller cares about (for example mid-gray).
55
+ `shot-and-read-noise` uses the engine's noise model (`calculateSignalToNoise`: Poisson shot noise plus RMS read noise);
56
+ `pixel-snr` is a per-pixel signal-to-noise ratio the caller already has. `capture-noise-figure` (2.20.0) takes a capture's own
57
+ mid-gray noise figure (#535: `result.sensor.midGrey` of a `simulateCapture` with `fast: false`), whose SNR S/σ_total counts photon
58
+ shot, read, dark-current, quantization and any fixed-pattern noise; it is one sensor site's, so it applies as the pixel SNR when one
59
+ printed pixel is one capture pixel. A clipped figure (the patch at white) is refused.
60
+
61
+ ```ts
62
+ export type PrintNoiseInput =
63
+ | { kind: "shot-and-read-noise"; signalElectrons: number; readNoiseElectrons: number }
64
+ | { kind: "pixel-snr"; snrLinear: number }
65
+ | { kind: "capture-noise-figure"; midGrey: NonNullable<SimulateCaptureSensorReport["midGrey"]> };
66
+ ```
67
+
68
+ ## PrintSharpCleanCriterion
69
+
70
+ What "looks sharp and clean" means: the smallest angle the viewer resolves, and the lowest signal-to-noise ratio the viewer
71
+ accepts after the eye averages over that angle. Both are the caller's choice; one arcminute is normal (20/20) acuity.
72
+
73
+ ```ts
74
+ export interface PrintSharpCleanCriterion {
75
+ kind: "visual-resolution";
76
+ arcminutes: number;
77
+ minimumVisibleSnr: number;
78
+ }
79
+ ```
80
+
81
+ ## PrintSizeLimitWithBlurAndNoise
82
+
83
+ Result. Each factor's own limit is the largest print it allows on its own; null means that factor never limits.
84
+ `limitingFactor` names the smallest; on a tie the order is pixels, blur, noise.
85
+
86
+ ```ts
87
+ export interface PrintSizeLimitWithBlurAndNoise {
88
+ engineApiVersion: string;
89
+ status: "finite" | "no-positive-size";
90
+ limitingFactor: "pixels" | "blur" | "noise";
91
+ maximumImageMm: PrintImageMm;
92
+ limits: { pixels: PrintImageMm; blur: PrintImageMm | null; noise: PrintImageMm | null };
93
+ viewingDistanceMm: number;
94
+ /** The criterion angle as a length on the print at the viewing distance: 2 d tan(θ/2). */
95
+ resolvedSpotMm: number;
96
+ blur: { diameterMm: number; diameterPixels: number; largestComponent: "lens" | "defocus" | "diffraction" | "motion" | "measured-total" | null };
97
+ noise: { pixelSnr: number; visibleSnrAtMaximum: number };
98
+ }
99
+ ```
100
+
101
+ ## PrintSizeLimitWithBlurAndNoiseInput
102
+
103
+ Input. The raster is the retained image that is printed (after any crop), with square pixels; `pixelPitchMm` is the
104
+ sensor length one of those pixels covers. The print keeps the raster's aspect ratio. The viewing distance is fixed.
105
+
106
+ ```ts
107
+ export interface PrintSizeLimitWithBlurAndNoiseInput {
108
+ raster: RasterDimensions;
109
+ pixelPitchMm: number;
110
+ viewingDistance: PrintLength;
111
+ criterion: PrintSharpCleanCriterion;
112
+ blur: PrintBlurInput;
113
+ noise: PrintNoiseInput;
114
+ }
115
+ ```
@@ -0,0 +1,105 @@
1
+ # sensor/sensor-characteristics.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## SimulateCaptureNoiseComponents
6
+
7
+ One RMS noise component of the mid-grey figure, e⁻ (input-referred).
8
+
9
+ ```ts
10
+ export interface SimulateCaptureNoiseComponents {
11
+ /** Photon shot noise √S (Poisson). */
12
+ photonShot: number;
13
+ /** Read noise of the capture's readout regime. */
14
+ read: number;
15
+ /** Dark-current shot noise √(D·t), D = 1 e⁻/s at 20 °C. */
16
+ darkCurrent: number;
17
+ /** ADC quantization, one code (and one more after a digital gain) as a uniform 1/12 code² variance, in e⁻. */
18
+ quantization: number;
19
+ /** With `sensor.fixedPatternNoise`: √((PRNU·S)² + (DSNU·D·t)² + row² + column²), hot pixels excluded; a spatial, not temporal, noise. */
20
+ fixedPattern?: number;
21
+ /** √ of the sum of the squares. */
22
+ total: number;
23
+ }
24
+ ```
25
+
26
+ ## SimulateCaptureSensorCharacteristics
27
+
28
+ #555: sensor characteristics a capture can set one at a time (`simulateCapture({ sensor })`). Each is off by default: an
29
+ omitted field keeps the camera body's value, and a request with none of them is byte-identical to one before 2.20.0.
30
+ Every value set is listed in the result's `assumptions`.
31
+
32
+ ```ts
33
+ export interface SimulateCaptureSensorCharacteristics {
34
+ /**
35
+ * Peak external quantum efficiency of the green channel (its value at 555 nm), 0.05–0.9. Default 0.5, the facade's
36
+ * idealized sensor. Every channel's EQE curve is multiplied by `peakQuantumEfficiency / 0.5`, so the curves keep their
37
+ * shape (and the colour matrix is unchanged) and the collected charge scales in proportion.
38
+ */
39
+ peakQuantumEfficiency?: number;
40
+ /**
41
+ * Full-well capacity: the charge, e⁻, that reaches the RAW white level at base ISO. Default: the body's base-ISO saturation
42
+ * at 6 µm, scaled by the pixel area (the `sensor.isoAreaNormalization` approximation). `link` `"pixel-area"` (default)
43
+ * scales `electrons`, the full well at `referencePixelPitchMicrometers` (default 6), by (pitch / reference)²; `"none"`
44
+ * uses `electrons` at any pitch. The result is 100 e⁻ to 1e10 e⁻.
45
+ */
46
+ fullWell?: { electrons: number; link?: "pixel-area" | "none"; referencePixelPitchMicrometers?: number };
47
+ /**
48
+ * Read noise, RMS e⁻ (0–100), of the low and the high conversion gain, and the dual-gain switch: the ISO setting from
49
+ * which the high conversion gain is used (any ISO from 100; above the body's highest ISO, the low gain is used throughout).
50
+ * An omitted field keeps the body's value. Needs readout regimes that declare their exposure index (the tiers do).
51
+ */
52
+ readNoise?: { lowGainElectrons?: number; highGainElectrons?: number; highGainFromIso?: number };
53
+ /** Readout time, s (1e-6 to 1): the rolling shutter's top-to-bottom skew. Default: the body's (tiers 8, 15 and 30 ms). */
54
+ readoutSeconds?: number;
55
+ /**
56
+ * ADC bit depth: 12, 14 or 16. Default: the body's (consumer 12, the others 14). The black level scales with the code range
57
+ * (×2^Δbits), and the conversion gain follows so the same charge reaches the same fraction of the range.
58
+ */
59
+ adcBitDepth?: 12 | 14 | 16;
60
+ /**
61
+ * Opt-in (advanced): the body's synthetic fixed-pattern noise profile (PRNU, DSNU, hot pixels, row and column offsets; EMVA
62
+ * 1288 Release 4.0 non-uniformities), the same pattern in every capture. Default false. Not in `simulateDisplayView`.
63
+ */
64
+ fixedPatternNoise?: boolean;
65
+ }
66
+ ```
67
+
68
+ ## SimulateCaptureSensorReport
69
+
70
+ #555, #535: the capture's sensor characteristics as used, and the figures calculated from them (reported, not settable).
71
+ Model figures from the engine's own noise model, not measurements.
72
+
73
+ ```ts
74
+ export interface SimulateCaptureSensorReport {
75
+ pixelPitchMicrometers: number;
76
+ /** The green channel's peak EQE (at 555 nm). */
77
+ peakQuantumEfficiency: number;
78
+ /** Full-well capacity, e⁻: the base-ISO saturation charge (`sensor.capacityElectrons`). */
79
+ fullWellElectrons: number;
80
+ /** Read noise, RMS e⁻, of each conversion gain, and the ISO from which the high gain is used (null: one gain throughout). */
81
+ readNoise: { lowGainElectrons: number; highGainElectrons: number; highGainFromIso: number | null };
82
+ /** The body's rolling readout skew, s (null when it declares no rolling readout). */
83
+ readoutSeconds: number | null;
84
+ /** ADC bit depth of the capture's readout regime. */
85
+ adcBitDepth: number;
86
+ fixedPatternNoise: boolean;
87
+ /**
88
+ * Engineering dynamic range at the capture's ISO, stops: log₂(S_sat / σ_floor), with S_sat the charge at the RAW white
89
+ * level (full well over the analog gain, limited by the charge capacity and the ADC range) and σ_floor the read noise and
90
+ * ADC quantization added in quadrature (the temporal dark noise of EMVA 1288 Release 4.0 without dark current). An ideal
91
+ * noiseless readout is limited by the ADC alone: log₂((white − black)·√12) for unit digital gain.
92
+ */
93
+ dynamicRangeStops: number;
94
+ /** The same per ISO setting of the body, in its order. */
95
+ dynamicRangeByIso: readonly { iso: number; stops: number }[];
96
+ /**
97
+ * #535: the expected signal-to-noise ratio of an 18 % Lambertian grey patch under the request's illuminant and illuminance
98
+ * at the capture's exposure, ISO and sensor, on the green channel next to the optical axis (no vignetting, PSF or motion).
99
+ * `snr` = S/σ_total and `snrDecibels` = 20·log₁₀(snr). `clipped`: S is at or above the white level, where the RAW clips and
100
+ * the figure no longer describes the image. Computed only when the request resolves to exact (`fast: false`, the default for
101
+ * DNG, JPEG and PNG exports; omitted for a fast request).
102
+ */
103
+ midGrey?: { reflectance: 0.18; channel: "green"; signalElectrons: number; noiseElectrons: SimulateCaptureNoiseComponents; snr: number; snrDecibels: number; clipped: boolean };
104
+ }
105
+ ```
package/llms.txt CHANGED
@@ -26,8 +26,13 @@
26
26
  - [calculateEquivalentFocalLength35Mm()](https://github.com/Photivra/photivra/blob/main/docs/api/camera-equivalent-focal-length.md#calculateequivalentfocallength35mm): crop factor and 35 mm-equivalent focal length
27
27
  - [calculateSensorEquivalence()](https://github.com/Photivra/photivra/blob/main/docs/api/camera-sensor-equivalence.md#calculatesensorequivalence): equivalent focal length and f-number, sensor area ratio and angles of view of a lens on one sensor, each marked exact or approximated
28
28
  - [calculateBlurShareOfFrame()](https://github.com/Photivra/photivra/blob/main/docs/api/camera-sensor-equivalence.md#calculateblurshareofframe): a blur circle as a share of the frame diagonal, or its size on a print
29
+ - [calculatePrintSizeLimitWithBlurAndNoise()](https://github.com/Photivra/photivra/blob/main/docs/api/output-print-size-blur-noise.md#calculateprintsizelimitwithblurandnoise): the largest print at a viewing distance that still looks sharp and clean, counting blur and noise as well as pixels (an approximation)
29
30
  - [calculateCenteredCrop()](https://github.com/Photivra/photivra/blob/main/docs/api/output-crop.md#calculatecenteredcrop): pixels left after a centred crop
31
+ - [calculateCropToMatchFocalLength()](https://github.com/Photivra/photivra/blob/main/docs/api/output-crop-to-match-focal-length.md#calculatecroptomatchfocallength): crop factor and pixels left when cropping a shorter lens's image to match a longer focal length
32
+ - [calculateApertureDiameter()](https://github.com/Photivra/photivra/blob/main/docs/api/optics-aperture-diameter.md#calculateaperturediameter): entrance-pupil (aperture) diameter in mm, focal length ÷ f-number
30
33
  - [calculateProjectedMotionBlur()](https://github.com/Photivra/photivra/blob/main/docs/api/motion-projected-motion.md#calculateprojectedmotionblur): how far a moving subject smears on the sensor during the shutter time
34
+ - [calculateFilteredExposureTime()](https://github.com/Photivra/photivra/blob/main/docs/api/exposure-filtered-exposure-time.md#calculatefilteredexposuretime): shutter time that keeps the same exposure with an ND (or any front-of-lens) filter, and the nearest shutter-dial value; `GENERIC_ND_FILTER_PROFILES` gives idealized 3-, 6- and 10-stop filters (exactly 2^-N)
35
+ - [calculateDetailLimit()](https://github.com/Photivra/photivra/blob/main/docs/api/optics-detail-limit.md#calculatedetaillimit): which limits detail for given settings, the lens (diffraction, aberrations, defocus) or the sensor's pixel pitch, with the optics MTF50, the Nyquist frequency and the system MTF50
31
36
  - [calculateAiryDisk()](https://github.com/Photivra/photivra/blob/main/docs/api/optics-diffraction.md#calculateairydisk): diffraction spot size from f-number and wavelength
32
37
  - [calculateSignalToNoise()](https://github.com/Photivra/photivra/blob/main/docs/api/sensor-signal-noise.md#calculatesignaltonoise): expected SNR from signal and read noise in electrons
33
38
  - [calculateChromaticityFromCorrelatedColorTemperature()](https://github.com/Photivra/photivra/blob/main/docs/api/color-illuminants.md#calculatechromaticityfromcorrelatedcolortemperature): white point for a colour temperature in kelvin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@photivra/engine",
3
- "version": "2.18.0",
3
+ "version": "2.20.0",
4
4
  "private": false,
5
5
  "description": "Scientific TypeScript engine for camera optics, exposure, focus, motion, sensor sampling, diffraction, and digital imaging calculations.",
6
6
  "type": "module",