@photivra/engine 2.18.0 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/CHANGELOG.md +179 -1
  2. package/CITATION.cff +1 -1
  3. package/NODE_SUPPORT.md +7 -5
  4. package/README.md +6 -6
  5. package/SECURITY.md +1 -1
  6. package/THIRD_PARTY.md +14 -10
  7. package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
  8. package/dist/capture/dng-lens-correction-opcodes.js +119 -0
  9. package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
  10. package/dist/capture/focus-control.js +2 -75
  11. package/dist/capture/focus-control.js.map +1 -1
  12. package/dist/capture/native-environment-lean.d.ts +1 -0
  13. package/dist/capture/native-environment-lean.js +2 -6
  14. package/dist/capture/native-environment-lean.js.map +1 -1
  15. package/dist/capture/native-environment-optical-stage.d.ts +1 -0
  16. package/dist/capture/native-environment-optical-stage.js +2 -5
  17. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  18. package/dist/capture/native-environment-raw.js +1 -5
  19. package/dist/capture/native-environment-raw.js.map +1 -1
  20. package/dist/capture/native-raw-dng.d.ts +6 -0
  21. package/dist/capture/native-raw-dng.js +16 -2
  22. package/dist/capture/native-raw-dng.js.map +1 -1
  23. package/dist/capture/release-sequence.js +2 -47
  24. package/dist/capture/release-sequence.js.map +1 -1
  25. package/dist/capture/sensor-raw-producer.js +3 -5
  26. package/dist/capture/sensor-raw-producer.js.map +1 -1
  27. package/dist/color/white-balance.d.ts +2 -0
  28. package/dist/color/white-balance.js +3 -56
  29. package/dist/color/white-balance.js.map +1 -1
  30. package/dist/composition/image-formation-plan.d.ts +1 -0
  31. package/dist/composition/image-formation-plan.js +3 -49
  32. package/dist/composition/image-formation-plan.js.map +1 -1
  33. package/dist/core/evidence-provenance.js +1 -16
  34. package/dist/core/evidence-provenance.js.map +1 -1
  35. package/dist/core/record-validation.d.ts +54 -0
  36. package/dist/core/record-validation.js +176 -0
  37. package/dist/core/record-validation.js.map +1 -1
  38. package/dist/core/scientific-assurance.js +1 -18
  39. package/dist/core/scientific-assurance.js.map +1 -1
  40. package/dist/core/shared-helpers.d.ts +35 -0
  41. package/dist/core/shared-helpers.js +38 -0
  42. package/dist/core/shared-helpers.js.map +1 -0
  43. package/dist/core/spectral-composition.js +1 -36
  44. package/dist/core/spectral-composition.js.map +1 -1
  45. package/dist/core/spectral.js +1 -19
  46. package/dist/core/spectral.js.map +1 -1
  47. package/dist/core/version.d.ts +1 -1
  48. package/dist/core/version.js +1 -1
  49. package/dist/core/version.js.map +1 -1
  50. package/dist/equipment/exposure-capabilities.d.ts +1 -0
  51. package/dist/equipment/exposure-capabilities.js +2 -36
  52. package/dist/equipment/exposure-capabilities.js.map +1 -1
  53. package/dist/equipment/generic-lens-catalogue.d.ts +8 -2
  54. package/dist/equipment/generic-lens-catalogue.js +31 -2
  55. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  56. package/dist/equipment/generic-tier-assets.js +2 -3
  57. package/dist/equipment/generic-tier-assets.js.map +1 -1
  58. package/dist/equipment/generic-tier-presets.js +2 -3
  59. package/dist/equipment/generic-tier-presets.js.map +1 -1
  60. package/dist/equipment/iso-capabilities.js +2 -49
  61. package/dist/equipment/iso-capabilities.js.map +1 -1
  62. package/dist/equipment/metering-capabilities.js +1 -26
  63. package/dist/equipment/metering-capabilities.js.map +1 -1
  64. package/dist/equipment/release-capabilities.js +1 -53
  65. package/dist/equipment/release-capabilities.js.map +1 -1
  66. package/dist/exposure/auto-iso-slowest-shutter.js +1 -7
  67. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
  68. package/dist/exposure/exposure-duration-control.js +1 -16
  69. package/dist/exposure/exposure-duration-control.js.map +1 -1
  70. package/dist/exposure/exposure-mode-resolver.js +1 -17
  71. package/dist/exposure/exposure-mode-resolver.js.map +1 -1
  72. package/dist/exposure/filtered-exposure-time.d.ts +93 -0
  73. package/dist/exposure/filtered-exposure-time.js +129 -0
  74. package/dist/exposure/filtered-exposure-time.js.map +1 -0
  75. package/dist/exposure/flash-ttl.d.ts +1 -0
  76. package/dist/exposure/flash-ttl.js +1 -1
  77. package/dist/exposure/flash-ttl.js.map +1 -1
  78. package/dist/exposure/flash.js +1 -49
  79. package/dist/exposure/flash.js.map +1 -1
  80. package/dist/exposure/metering-scene-radiance.js +1 -25
  81. package/dist/exposure/metering-scene-radiance.js.map +1 -1
  82. package/dist/exposure/metering-target.js +1 -30
  83. package/dist/exposure/metering-target.js.map +1 -1
  84. package/dist/exposure/metering-temporal.js +1 -8
  85. package/dist/exposure/metering-temporal.js.map +1 -1
  86. package/dist/exposure/metering.js +1 -72
  87. package/dist/exposure/metering.js.map +1 -1
  88. package/dist/exposure/program-line.js +1 -45
  89. package/dist/exposure/program-line.js.map +1 -1
  90. package/dist/facade/develop-joined-parts.d.ts +29 -0
  91. package/dist/facade/develop-joined-parts.js +33 -0
  92. package/dist/facade/develop-joined-parts.js.map +1 -0
  93. package/dist/facade/display-view-gpu.d.ts +7 -4
  94. package/dist/facade/display-view-gpu.js +4 -4
  95. package/dist/facade/display-view-gpu.js.map +1 -1
  96. package/dist/facade/display-view-lens-signal.d.ts +1 -1
  97. package/dist/facade/display-view-lens-signal.js +5 -3
  98. package/dist/facade/display-view-lens-signal.js.map +1 -1
  99. package/dist/facade/display-view.d.ts +3 -1
  100. package/dist/facade/display-view.js +1 -1
  101. package/dist/facade/display-view.js.map +1 -1
  102. package/dist/facade/fast-methods.d.ts +50 -0
  103. package/dist/facade/fast-methods.js +50 -0
  104. package/dist/facade/fast-methods.js.map +1 -0
  105. package/dist/facade/fast-resolution.d.ts +12 -0
  106. package/dist/facade/fast-resolution.js +18 -0
  107. package/dist/facade/fast-resolution.js.map +1 -0
  108. package/dist/facade/generic-lens-capture.d.ts +6 -3
  109. package/dist/facade/generic-lens-capture.js +6 -3
  110. package/dist/facade/generic-lens-capture.js.map +1 -1
  111. package/dist/facade/simulate-capture.d.ts +88 -16
  112. package/dist/facade/simulate-capture.js +279 -153
  113. package/dist/facade/simulate-capture.js.map +1 -1
  114. package/dist/facade/simulate-display-view.d.ts +3 -2
  115. package/dist/facade/simulate-display-view.js +22 -12
  116. package/dist/facade/simulate-display-view.js.map +1 -1
  117. package/dist/gpu/chart-composite-gpu.js +1 -3
  118. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  119. package/dist/gpu/chart-radiance-gpu.js +1 -5
  120. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  121. package/dist/gpu/display-view-quality-gpu.d.ts +13 -1
  122. package/dist/gpu/display-view-quality-gpu.js +4 -8
  123. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  124. package/dist/gpu/display-view-speed-gpu.js +3 -25
  125. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  126. package/dist/gpu/sensor-stage-gpu.js +1 -5
  127. package/dist/gpu/sensor-stage-gpu.js.map +1 -1
  128. package/dist/gpu/webgpu-runtime.d.ts +2 -0
  129. package/dist/gpu/webgpu-runtime.js +3 -0
  130. package/dist/gpu/webgpu-runtime.js.map +1 -1
  131. package/dist/index.d.ts +11 -1
  132. package/dist/index.js +8 -0
  133. package/dist/index.js.map +1 -1
  134. package/dist/motion/camera-rotation.js +1 -6
  135. package/dist/motion/camera-rotation.js.map +1 -1
  136. package/dist/motion/capture-rotation-inverse-mapping.d.ts +12 -0
  137. package/dist/motion/capture-rotation-inverse-mapping.js +2 -2
  138. package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -1
  139. package/dist/motion/capture-rotation-temporal-quadrature.d.ts +1 -0
  140. package/dist/motion/capture-rotation-temporal-quadrature.js +1 -1
  141. package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -1
  142. package/dist/motion/capture-rotation-trajectory.js +2 -17
  143. package/dist/motion/capture-rotation-trajectory.js.map +1 -1
  144. package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts +1 -0
  145. package/dist/motion/capture-translation-parallax-temporal-quadrature.js +3 -14
  146. package/dist/motion/capture-translation-parallax-temporal-quadrature.js.map +1 -1
  147. package/dist/motion/extended-object-projection.js +5 -40
  148. package/dist/motion/extended-object-projection.js.map +1 -1
  149. package/dist/optics/aperture-diameter.d.ts +47 -0
  150. package/dist/optics/aperture-diameter.js +50 -0
  151. package/dist/optics/aperture-diameter.js.map +1 -0
  152. package/dist/optics/atmospheric-haze.js +1 -7
  153. package/dist/optics/atmospheric-haze.js.map +1 -1
  154. package/dist/optics/brown-conrady-distortion.js +1 -7
  155. package/dist/optics/brown-conrady-distortion.js.map +1 -1
  156. package/dist/optics/complex-pupil-psf.d.ts +1 -0
  157. package/dist/optics/complex-pupil-psf.js +2 -84
  158. package/dist/optics/complex-pupil-psf.js.map +1 -1
  159. package/dist/optics/detail-limit.d.ts +182 -0
  160. package/dist/optics/detail-limit.js +336 -0
  161. package/dist/optics/detail-limit.js.map +1 -0
  162. package/dist/optics/focus-state.js +1 -11
  163. package/dist/optics/focus-state.js.map +1 -1
  164. package/dist/optics/front-of-lens-filter.d.ts +5 -0
  165. package/dist/optics/front-of-lens-filter.js +3 -65
  166. package/dist/optics/front-of-lens-filter.js.map +1 -1
  167. package/dist/optics/generic-lens-psf.d.ts +18 -2
  168. package/dist/optics/generic-lens-psf.js +25 -2
  169. package/dist/optics/generic-lens-psf.js.map +1 -1
  170. package/dist/optics/generic-nd-filter-presets.d.ts +12 -0
  171. package/dist/optics/generic-nd-filter-presets.js +48 -0
  172. package/dist/optics/generic-nd-filter-presets.js.map +1 -0
  173. package/dist/optics/illumination-vignetting.js +1 -6
  174. package/dist/optics/illumination-vignetting.js.map +1 -1
  175. package/dist/optics/lens-psf-profile.d.ts +4 -1
  176. package/dist/optics/lens-psf-profile.js +12 -111
  177. package/dist/optics/lens-psf-profile.js.map +1 -1
  178. package/dist/optics/longitudinal-chromatic-aberration.js +1 -7
  179. package/dist/optics/longitudinal-chromatic-aberration.js.map +1 -1
  180. package/dist/optics/pupil-vignetting.js +1 -7
  181. package/dist/optics/pupil-vignetting.js.map +1 -1
  182. package/dist/optics/radial-distortion.js +1 -6
  183. package/dist/optics/radial-distortion.js.map +1 -1
  184. package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
  185. package/dist/optics/scene-to-sensor-irradiance.js +11 -96
  186. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  187. package/dist/optics/stray-light.d.ts +1 -0
  188. package/dist/optics/stray-light.js +1 -1
  189. package/dist/optics/stray-light.js.map +1 -1
  190. package/dist/optics/zernike-wavefront.js +1 -7
  191. package/dist/optics/zernike-wavefront.js.map +1 -1
  192. package/dist/output/capture-geometry.js +1 -8
  193. package/dist/output/capture-geometry.js.map +1 -1
  194. package/dist/output/crop-to-match-focal-length.d.ts +62 -0
  195. package/dist/output/crop-to-match-focal-length.js +63 -0
  196. package/dist/output/crop-to-match-focal-length.js.map +1 -0
  197. package/dist/output/developed-lens-corrections.d.ts +62 -0
  198. package/dist/output/developed-lens-corrections.js +135 -0
  199. package/dist/output/developed-lens-corrections.js.map +1 -0
  200. package/dist/output/generic-lens-corrections.d.ts +5 -7
  201. package/dist/output/generic-lens-corrections.js +11 -10
  202. package/dist/output/generic-lens-corrections.js.map +1 -1
  203. package/dist/output/geometric-transforms.js +1 -4
  204. package/dist/output/geometric-transforms.js.map +1 -1
  205. package/dist/output/lens-corrections.d.ts +12 -2
  206. package/dist/output/lens-corrections.js +22 -7
  207. package/dist/output/lens-corrections.js.map +1 -1
  208. package/dist/output/native-capture-sdr.d.ts +2 -2
  209. package/dist/output/native-capture-sdr.js +3 -2
  210. package/dist/output/native-capture-sdr.js.map +1 -1
  211. package/dist/output/print-contrast-reference.js +1 -1
  212. package/dist/output/print-contrast-reference.js.map +1 -1
  213. package/dist/output/print-size-blur-noise.d.ts +110 -0
  214. package/dist/output/print-size-blur-noise.js +138 -0
  215. package/dist/output/print-size-blur-noise.js.map +1 -0
  216. package/dist/processing/development-detail.d.ts +29 -0
  217. package/dist/processing/development-detail.js +128 -107
  218. package/dist/processing/development-detail.js.map +1 -1
  219. package/dist/schema/illumination-temporal.js +3 -72
  220. package/dist/schema/illumination-temporal.js.map +1 -1
  221. package/dist/schema/illumination.d.ts +1 -0
  222. package/dist/schema/illumination.js +2 -65
  223. package/dist/schema/illumination.js.map +1 -1
  224. package/dist/schema/scene-radiance-temporal.js +2 -14
  225. package/dist/schema/scene-radiance-temporal.js.map +1 -1
  226. package/dist/schema/scene-radiance.js +2 -60
  227. package/dist/schema/scene-radiance.js.map +1 -1
  228. package/dist/schema/validation.d.ts +2 -0
  229. package/dist/schema/validation.js +2 -17
  230. package/dist/schema/validation.js.map +1 -1
  231. package/dist/sensor/accumulated-charge.js +2 -59
  232. package/dist/sensor/accumulated-charge.js.map +1 -1
  233. package/dist/sensor/architecture.js +1 -16
  234. package/dist/sensor/architecture.js.map +1 -1
  235. package/dist/sensor/camera-saturation-capacity.d.ts +1 -0
  236. package/dist/sensor/camera-saturation-capacity.js +3 -55
  237. package/dist/sensor/camera-saturation-capacity.js.map +1 -1
  238. package/dist/sensor/capture-color-sampling-binding.js +1 -26
  239. package/dist/sensor/capture-color-sampling-binding.js.map +1 -1
  240. package/dist/sensor/capture-mode-timing.js +1 -39
  241. package/dist/sensor/capture-mode-timing.js.map +1 -1
  242. package/dist/sensor/capture-mode.js +1 -23
  243. package/dist/sensor/capture-mode.js.map +1 -1
  244. package/dist/sensor/color-sampling.js +1 -26
  245. package/dist/sensor/color-sampling.js.map +1 -1
  246. package/dist/sensor/constant-rate-temporal-integration.js +2 -46
  247. package/dist/sensor/constant-rate-temporal-integration.js.map +1 -1
  248. package/dist/sensor/dark-current.js +2 -45
  249. package/dist/sensor/dark-current.js.map +1 -1
  250. package/dist/sensor/eqe-electron-rate.js +2 -41
  251. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  252. package/dist/sensor/exposure-window.d.ts +7 -0
  253. package/dist/sensor/exposure-window.js +9 -24
  254. package/dist/sensor/exposure-window.js.map +1 -1
  255. package/dist/sensor/iso-gain.js +1 -7
  256. package/dist/sensor/iso-gain.js.map +1 -1
  257. package/dist/sensor/iso-signal-chain.js +1 -43
  258. package/dist/sensor/iso-signal-chain.js.map +1 -1
  259. package/dist/sensor/optical-stack.js +1 -25
  260. package/dist/sensor/optical-stack.js.map +1 -1
  261. package/dist/sensor/photon-energy.js +1 -26
  262. package/dist/sensor/photon-energy.js.map +1 -1
  263. package/dist/sensor/physical-charge-capacity.js +3 -77
  264. package/dist/sensor/physical-charge-capacity.js.map +1 -1
  265. package/dist/sensor/radiometry-readiness.js +2 -23
  266. package/dist/sensor/radiometry-readiness.js.map +1 -1
  267. package/dist/sensor/raw-readout.d.ts +3 -0
  268. package/dist/sensor/raw-readout.js +4 -66
  269. package/dist/sensor/raw-readout.js.map +1 -1
  270. package/dist/sensor/raw-reconstruction.js +2 -64
  271. package/dist/sensor/raw-reconstruction.js.map +1 -1
  272. package/dist/sensor/readout-exposure-linkage.js +1 -9
  273. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  274. package/dist/sensor/readout-timing.js +4 -79
  275. package/dist/sensor/readout-timing.js.map +1 -1
  276. package/dist/sensor/response-application-compatibility.d.ts +2 -0
  277. package/dist/sensor/response-application-compatibility.js +4 -35
  278. package/dist/sensor/response-application-compatibility.js.map +1 -1
  279. package/dist/sensor/response-operating-range.d.ts +1 -0
  280. package/dist/sensor/response-operating-range.js +3 -79
  281. package/dist/sensor/response-operating-range.js.map +1 -1
  282. package/dist/sensor/responsivity-photocurrent.js +4 -114
  283. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  284. package/dist/sensor/sampling-aperture.js +1 -25
  285. package/dist/sensor/sampling-aperture.js.map +1 -1
  286. package/dist/sensor/sensor-characteristics.d.ts +137 -0
  287. package/dist/sensor/sensor-characteristics.js +115 -0
  288. package/dist/sensor/sensor-characteristics.js.map +1 -0
  289. package/dist/sensor/spatial-sample-reduction.js +3 -24
  290. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  291. package/dist/sensor/spatial-sampling-quadrature.js +1 -3
  292. package/dist/sensor/spatial-sampling-quadrature.js.map +1 -1
  293. package/dist/sensor/spatio-spectral-reduction.js +1 -18
  294. package/dist/sensor/spatio-spectral-reduction.js.map +1 -1
  295. package/dist/sensor/spectral-coverage.d.ts +10 -1
  296. package/dist/sensor/spectral-coverage.js +1 -1
  297. package/dist/sensor/spectral-coverage.js.map +1 -1
  298. package/dist/sensor/spectral-quadrature.js +3 -41
  299. package/dist/sensor/spectral-quadrature.js.map +1 -1
  300. package/dist/sensor/spectral-response.js +1 -47
  301. package/dist/sensor/spectral-response.js.map +1 -1
  302. package/dist/sensor/temporal-photo-signal.d.ts +3 -0
  303. package/dist/sensor/temporal-photo-signal.js +5 -2
  304. package/dist/sensor/temporal-photo-signal.js.map +1 -1
  305. package/dist/stabilization/camera-shake.js +1 -6
  306. package/dist/stabilization/camera-shake.js.map +1 -1
  307. package/dist/stabilization/system.d.ts +9 -0
  308. package/dist/stabilization/system.js +7 -86
  309. package/dist/stabilization/system.js.map +1 -1
  310. package/docs/AGENT_GUIDE.md +5 -0
  311. package/docs/API_REFERENCE.md +116 -3
  312. package/docs/QUICK_START.md +178 -14
  313. package/docs/USAGE.md +111 -3
  314. package/docs/api/capture-native-raw-dng.md +6 -0
  315. package/docs/api/core-version.md +1 -1
  316. package/docs/api/equipment-generic-lens-catalogue.md +7 -2
  317. package/docs/api/exports.json +203 -3
  318. package/docs/api/exposure-filtered-exposure-time.md +105 -0
  319. package/docs/api/facade-develop-joined-parts.md +32 -0
  320. package/docs/api/facade-display-view-gpu.md +5 -2
  321. package/docs/api/facade-fast-methods.md +91 -0
  322. package/docs/api/facade-simulate-capture.md +72 -16
  323. package/docs/api/facade-simulate-display-view.md +3 -2
  324. package/docs/api/optics-aperture-diameter.md +51 -0
  325. package/docs/api/optics-detail-limit.md +203 -0
  326. package/docs/api/optics-generic-nd-filter-presets.md +34 -0
  327. package/docs/api/optics-lens-psf-profile.md +3 -1
  328. package/docs/api/output-crop-to-match-focal-length.md +67 -0
  329. package/docs/api/output-developed-lens-corrections.md +12 -0
  330. package/docs/api/output-generic-lens-corrections.md +3 -10
  331. package/docs/api/output-lens-corrections.md +11 -1
  332. package/docs/api/output-print-size-blur-noise.md +115 -0
  333. package/docs/api/sensor-sensor-characteristics.md +105 -0
  334. package/llms.txt +5 -0
  335. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  # Public API reference
2
2
 
3
- Generated from the exact root exports for **@photivra/engine 2.18.0 / root contract 2.18.0** by `node scripts/generate-api-reference.mjs`. No module deep import is supported.
3
+ Generated from the exact root exports for **@photivra/engine 2.20.0 / root contract 2.20.0** by `node scripts/generate-api-reference.mjs`. No module deep import is supported.
4
4
 
5
5
  Use [the developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md) for executable paths and scientific boundaries. Types are contracts, not runtime validation: parse untrusted data, preserve units/reference frames, read assessment blockers, and retain evidence/limitations. Optional properties do not imply a universal default. Model/schema IDs remain independent of the distribution.
6
6
 
@@ -15,10 +15,13 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
15
15
  | Exposure value and equivalent settings | [`calculateExposureValue100`](api/exposure-exposure.md#calculateexposurevalue100), [`calculateEquivalentIso`](api/exposure-exposure.md#calculateequivalentiso), [`calculateRelativeRenderedExposure`](api/exposure-exposure.md#calculaterelativerenderedexposure) |
16
16
  | Depth of field, hyperfocal distance and defocus blur | [`calculateDepthOfField`](api/optics-depth-of-field.md#calculatedepthoffield), [`calculateDefocusCircle`](api/optics-depth-of-field.md#calculatedefocuscircle) |
17
17
  | Field of view | [`calculateFieldOfView`](api/camera-field-of-view.md#calculatefieldofview) |
18
- | Crop factor, 35 mm-equivalent focal length and crops | [`calculateEquivalentFocalLength35Mm`](api/camera-equivalent-focal-length.md#calculateequivalentfocallength35mm), [`calculateCenteredCrop`](api/output-crop.md#calculatecenteredcrop) |
18
+ | Crop factor, 35 mm-equivalent focal length and crops | [`calculateEquivalentFocalLength35Mm`](api/camera-equivalent-focal-length.md#calculateequivalentfocallength35mm), [`calculateCenteredCrop`](api/output-crop.md#calculatecenteredcrop), [`calculateCropToMatchFocalLength`](api/output-crop-to-match-focal-length.md#calculatecroptomatchfocallength) |
19
+ | Aperture (entrance-pupil) diameter | [`calculateApertureDiameter`](api/optics-aperture-diameter.md#calculateaperturediameter) |
19
20
  | Equivalent f-number, sensor area and blur as a share of the frame | [`calculateSensorEquivalence`](api/camera-sensor-equivalence.md#calculatesensorequivalence), [`calculateBlurShareOfFrame`](api/camera-sensor-equivalence.md#calculateblurshareofframe) |
20
21
  | Motion blur and shutter speed | [`calculateProjectedMotionBlur`](api/motion-projected-motion.md#calculateprojectedmotionblur), [`calculateShutterAngleExposureSeconds`](api/motion-video-sequence.md#calculateshutterangleexposureseconds) |
22
+ | Shutter time with an ND filter | [`calculateFilteredExposureTime`](api/exposure-filtered-exposure-time.md#calculatefilteredexposuretime) |
21
23
  | Diffraction | [`calculateAiryDisk`](api/optics-diffraction.md#calculateairydisk) |
24
+ | Which limits detail: the lens or the sensor | [`calculateDetailLimit`](api/optics-detail-limit.md#calculatedetaillimit) |
22
25
  | Sensor signal, noise and SNR | [`calculatePhotoelectrons`](api/sensor-signal-noise.md#calculatephotoelectrons), [`calculateSignalToNoise`](api/sensor-signal-noise.md#calculatesignaltonoise) |
23
26
  | Colour temperature | [`calculateChromaticityFromCorrelatedColorTemperature`](api/color-illuminants.md#calculatechromaticityfromcorrelatedcolortemperature), [`calculateCorrelatedColorTemperature`](api/color-illuminants.md#calculatecorrelatedcolortemperature) |
24
27
  | White balance from Kelvin and tint | [`createColorTemperatureWhiteBalanceIntent`](api/color-white-balance.md#createcolortemperaturewhitebalanceintent), [`resolveColorTemperatureWhiteBalance`](api/color-white-balance.md#resolvecolortemperaturewhitebalance) |
@@ -1056,6 +1059,18 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
1056
1059
  | [`RelativeOpticalExposure`](api/exposure-exposure.md#relativeopticalexposure) | Type |
1057
1060
  | [`RelativeRenderedExposure`](api/exposure-exposure.md#relativerenderedexposure) | Type |
1058
1061
 
1062
+ ## exposure/filtered-exposure-time.ts
1063
+
1064
+ [Detailed contracts](api/exposure-filtered-exposure-time.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/exposure/filtered-exposure-time.ts)
1065
+
1066
+ | Export | Kind |
1067
+ | --- | --- |
1068
+ | [`calculateFilteredExposureTime`](api/exposure-filtered-exposure-time.md#calculatefilteredexposuretime) | Runtime |
1069
+ | [`CalculateFilteredExposureTimeInput`](api/exposure-filtered-exposure-time.md#calculatefilteredexposuretimeinput) | Type |
1070
+ | [`FILTERED_EXPOSURE_DEFAULT_WAVELENGTH_NANOMETERS`](api/exposure-filtered-exposure-time.md#filtered_exposure_default_wavelength_nanometers) | Runtime |
1071
+ | [`FilteredExposureDialSetting`](api/exposure-filtered-exposure-time.md#filteredexposuredialsetting) | Type |
1072
+ | [`FilteredExposureTime`](api/exposure-filtered-exposure-time.md#filteredexposuretime) | Type |
1073
+
1059
1074
  ## exposure/flash-ttl.ts
1060
1075
 
1061
1076
  [Detailed contracts](api/exposure-flash-ttl.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/exposure/flash-ttl.ts)
@@ -1267,6 +1282,14 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
1267
1282
  | [`EvaluateColorPatchReflectanceInput`](api/facade-color-patches.md#evaluatecolorpatchreflectanceinput) | Type |
1268
1283
  | [`getColorPatchSet`](api/facade-color-patches.md#getcolorpatchset) | Runtime |
1269
1284
 
1285
+ ## facade/develop-joined-parts.ts
1286
+
1287
+ [Detailed contracts](api/facade-develop-joined-parts.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/facade/develop-joined-parts.ts)
1288
+
1289
+ | Export | Kind |
1290
+ | --- | --- |
1291
+ | [`simulateCaptureFromParts`](api/facade-develop-joined-parts.md#simulatecapturefromparts) | Runtime |
1292
+
1270
1293
  ## facade/display-view-gpu.ts
1271
1294
 
1272
1295
  [Detailed contracts](api/facade-display-view-gpu.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/facade/display-view-gpu.ts)
@@ -1291,6 +1314,15 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
1291
1314
  | [`DisplayViewCorrectionStateSet`](api/facade-display-view.md#displayviewcorrectionstateset) | Type |
1292
1315
  | [`serializeDisplayViewCorrectionStates`](api/facade-display-view.md#serializedisplayviewcorrectionstates) | Runtime |
1293
1316
 
1317
+ ## facade/fast-methods.ts
1318
+
1319
+ [Detailed contracts](api/facade-fast-methods.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/facade/fast-methods.ts)
1320
+
1321
+ | Export | Kind |
1322
+ | --- | --- |
1323
+ | [`SIMULATE_CAPTURE_FAST_METHODS`](api/facade-fast-methods.md#simulate_capture_fast_methods) | Runtime |
1324
+ | [`SimulateCaptureFastMethod`](api/facade-fast-methods.md#simulatecapturefastmethod) | Type |
1325
+
1294
1326
  ## facade/scene-radiance.ts
1295
1327
 
1296
1328
  [Detailed contracts](api/facade-scene-radiance.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/facade/scene-radiance.ts)
@@ -1347,6 +1379,7 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
1347
1379
  | [`SimulateCaptureFastOption`](api/facade-simulate-capture.md#simulatecapturefastoption) | Type |
1348
1380
  | [`SimulateCaptureFastReport`](api/facade-simulate-capture.md#simulatecapturefastreport) | Type |
1349
1381
  | [`SimulateCaptureLensCatalogueRecord`](api/facade-simulate-capture.md#simulatecapturelenscataloguerecord) | Type |
1382
+ | [`SimulateCaptureLensCorrections`](api/facade-simulate-capture.md#simulatecapturelenscorrections) | Type |
1350
1383
  | [`SimulateCaptureLensDistortion`](api/facade-simulate-capture.md#simulatecapturelensdistortion) | Type |
1351
1384
  | [`SimulateCaptureMetering`](api/facade-simulate-capture.md#simulatecapturemetering) | Type |
1352
1385
  | [`SimulateCaptureMotion`](api/facade-simulate-capture.md#simulatecapturemotion) | Type |
@@ -1561,6 +1594,16 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
1561
1594
  | [`VIDEO_FRAME_SEQUENCE_VERSION`](api/motion-video-sequence.md#video_frame_sequence_version) | Runtime |
1562
1595
  | [`VideoFrameSequence`](api/motion-video-sequence.md#videoframesequence) | Type |
1563
1596
 
1597
+ ## optics/aperture-diameter.ts
1598
+
1599
+ [Detailed contracts](api/optics-aperture-diameter.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/optics/aperture-diameter.ts)
1600
+
1601
+ | Export | Kind |
1602
+ | --- | --- |
1603
+ | [`ApertureDiameter`](api/optics-aperture-diameter.md#aperturediameter) | Type |
1604
+ | [`calculateApertureDiameter`](api/optics-aperture-diameter.md#calculateaperturediameter) | Runtime |
1605
+ | [`CalculateApertureDiameterInput`](api/optics-aperture-diameter.md#calculateaperturediameterinput) | Type |
1606
+
1564
1607
  ## optics/aperture.ts
1565
1608
 
1566
1609
  [Detailed contracts](api/optics-aperture.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/optics/aperture.ts)
@@ -1665,6 +1708,25 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
1665
1708
  | [`DefocusCircle`](api/optics-depth-of-field.md#defocuscircle) | Type |
1666
1709
  | [`DepthOfField`](api/optics-depth-of-field.md#depthoffield) | Type |
1667
1710
 
1711
+ ## optics/detail-limit.ts
1712
+
1713
+ [Detailed contracts](api/optics-detail-limit.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/optics/detail-limit.ts)
1714
+
1715
+ | Export | Kind |
1716
+ | --- | --- |
1717
+ | [`calculateDetailLimit`](api/optics-detail-limit.md#calculatedetaillimit) | Runtime |
1718
+ | [`CalculateDetailLimitInput`](api/optics-detail-limit.md#calculatedetaillimitinput) | Type |
1719
+ | [`DETAIL_LIMIT_LIMITS`](api/optics-detail-limit.md#detail_limit_limits) | Runtime |
1720
+ | [`DetailLimit`](api/optics-detail-limit.md#detaillimit) | Type |
1721
+ | [`DetailLimitBasis`](api/optics-detail-limit.md#detaillimitbasis) | Type |
1722
+ | [`DetailLimitCatalogueLens`](api/optics-detail-limit.md#detaillimitcataloguelens) | Type |
1723
+ | [`DetailLimitedBy`](api/optics-detail-limit.md#detaillimitedby) | Type |
1724
+ | [`DetailLimitIdealLens`](api/optics-detail-limit.md#detaillimitideallens) | Type |
1725
+ | [`DetailLimitingFactor`](api/optics-detail-limit.md#detaillimitingfactor) | Type |
1726
+ | [`DetailLimitOptics`](api/optics-detail-limit.md#detaillimitoptics) | Type |
1727
+ | [`DetailLimitSampling`](api/optics-detail-limit.md#detaillimitsampling) | Type |
1728
+ | [`DetailLimitSystem`](api/optics-detail-limit.md#detaillimitsystem) | Type |
1729
+
1668
1730
  ## optics/diffraction-psf-profile.ts
1669
1731
 
1670
1732
  [Detailed contracts](api/optics-diffraction-psf-profile.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/optics/diffraction-psf-profile.ts)
@@ -1739,6 +1801,16 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
1739
1801
  | [`resolveFrontOfLensFilterTransmission`](api/optics-front-of-lens-filter.md#resolvefrontoflensfiltertransmission) | Runtime |
1740
1802
  | [`ResolveFrontOfLensFilterTransmissionInput`](api/optics-front-of-lens-filter.md#resolvefrontoflensfiltertransmissioninput) | Type |
1741
1803
 
1804
+ ## optics/generic-nd-filter-presets.ts
1805
+
1806
+ [Detailed contracts](api/optics-generic-nd-filter-presets.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/optics/generic-nd-filter-presets.ts)
1807
+
1808
+ | Export | Kind |
1809
+ | --- | --- |
1810
+ | [`GENERIC_ND_FILTER_PROFILES`](api/optics-generic-nd-filter-presets.md#generic_nd_filter_profiles) | Runtime |
1811
+ | [`GENERIC_ND_FILTER_STOPS`](api/optics-generic-nd-filter-presets.md#generic_nd_filter_stops) | Runtime |
1812
+ | [`GenericNdFilterStops`](api/optics-generic-nd-filter-presets.md#genericndfilterstops) | Type |
1813
+
1742
1814
  ## optics/illumination-vignetting.ts
1743
1815
 
1744
1816
  [Detailed contracts](api/optics-illumination-vignetting.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/optics/illumination-vignetting.ts)
@@ -2085,6 +2157,16 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
2085
2157
  | [`CaptureSdrResult`](api/output-capture-sdr.md#capturesdrresult) | Type |
2086
2158
  | [`parseCaptureSdrInput`](api/output-capture-sdr.md#parsecapturesdrinput) | Runtime |
2087
2159
 
2160
+ ## output/crop-to-match-focal-length.ts
2161
+
2162
+ [Detailed contracts](api/output-crop-to-match-focal-length.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/crop-to-match-focal-length.ts)
2163
+
2164
+ | Export | Kind |
2165
+ | --- | --- |
2166
+ | [`calculateCropToMatchFocalLength`](api/output-crop-to-match-focal-length.md#calculatecroptomatchfocallength) | Runtime |
2167
+ | [`CalculateCropToMatchFocalLengthInput`](api/output-crop-to-match-focal-length.md#calculatecroptomatchfocallengthinput) | Type |
2168
+ | [`CropToMatchFocalLength`](api/output-crop-to-match-focal-length.md#croptomatchfocallength) | Type |
2169
+
2088
2170
  ## output/crop.ts
2089
2171
 
2090
2172
  [Detailed contracts](api/output-crop.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/crop.ts)
@@ -2095,6 +2177,14 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
2095
2177
  | [`CalculateCenteredCropInput`](api/output-crop.md#calculatecenteredcropinput) | Type |
2096
2178
  | [`CenteredCrop`](api/output-crop.md#centeredcrop) | Type |
2097
2179
 
2180
+ ## output/developed-lens-corrections.ts
2181
+
2182
+ [Detailed contracts](api/output-developed-lens-corrections.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/developed-lens-corrections.ts)
2183
+
2184
+ | Export | Kind |
2185
+ | --- | --- |
2186
+ | [`GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM`](api/output-developed-lens-corrections.md#generic_lens_correction_channel_wavelengths_nm) | Runtime |
2187
+
2098
2188
  ## output/generic-lens-corrections.ts
2099
2189
 
2100
2190
  [Detailed contracts](api/output-generic-lens-corrections.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/generic-lens-corrections.ts)
@@ -2103,7 +2193,6 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
2103
2193
  | --- | --- |
2104
2194
  | [`createGenericLensCorrectionProfile`](api/output-generic-lens-corrections.md#creategenericlenscorrectionprofile) | Runtime |
2105
2195
  | [`CreateGenericLensCorrectionProfileInput`](api/output-generic-lens-corrections.md#creategenericlenscorrectionprofileinput) | Type |
2106
- | [`GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM`](api/output-generic-lens-corrections.md#generic_lens_correction_channel_wavelengths_nm) | Runtime |
2107
2196
 
2108
2197
  ## output/geometric-transforms.ts
2109
2198
 
@@ -2277,6 +2366,20 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
2277
2366
  | [`PrintRegionRaster`](api/output-print-region-statistics.md#printregionraster) | Type |
2278
2367
  | [`PrintRegionStatisticsAssessment`](api/output-print-region-statistics.md#printregionstatisticsassessment) | Type |
2279
2368
 
2369
+ ## output/print-size-blur-noise.ts
2370
+
2371
+ [Detailed contracts](api/output-print-size-blur-noise.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/print-size-blur-noise.ts)
2372
+
2373
+ | Export | Kind |
2374
+ | --- | --- |
2375
+ | [`calculatePrintSizeLimitWithBlurAndNoise`](api/output-print-size-blur-noise.md#calculateprintsizelimitwithblurandnoise) | Runtime |
2376
+ | [`PrintBlurInput`](api/output-print-size-blur-noise.md#printblurinput) | Type |
2377
+ | [`PrintImageMm`](api/output-print-size-blur-noise.md#printimagemm) | Type |
2378
+ | [`PrintNoiseInput`](api/output-print-size-blur-noise.md#printnoiseinput) | Type |
2379
+ | [`PrintSharpCleanCriterion`](api/output-print-size-blur-noise.md#printsharpcleancriterion) | Type |
2380
+ | [`PrintSizeLimitWithBlurAndNoise`](api/output-print-size-blur-noise.md#printsizelimitwithblurandnoise) | Type |
2381
+ | [`PrintSizeLimitWithBlurAndNoiseInput`](api/output-print-size-blur-noise.md#printsizelimitwithblurandnoiseinput) | Type |
2382
+
2280
2383
  ## output/sdr-rendering.ts
2281
2384
 
2282
2385
  [Detailed contracts](api/output-sdr-rendering.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/sdr-rendering.ts)
@@ -3085,6 +3188,16 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
3085
3188
  | [`CalculateSceneSensorEqeTemporalExposureInput`](api/sensor-scene-eqe-temporal-exposure.md#calculatescenesensoreqetemporalexposureinput) | Type |
3086
3189
  | [`SceneSensorEqeTemporalExposure`](api/sensor-scene-eqe-temporal-exposure.md#scenesensoreqetemporalexposure) | Type |
3087
3190
 
3191
+ ## sensor/sensor-characteristics.ts
3192
+
3193
+ [Detailed contracts](api/sensor-sensor-characteristics.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/sensor/sensor-characteristics.ts)
3194
+
3195
+ | Export | Kind |
3196
+ | --- | --- |
3197
+ | [`SimulateCaptureNoiseComponents`](api/sensor-sensor-characteristics.md#simulatecapturenoisecomponents) | Type |
3198
+ | [`SimulateCaptureSensorCharacteristics`](api/sensor-sensor-characteristics.md#simulatecapturesensorcharacteristics) | Type |
3199
+ | [`SimulateCaptureSensorReport`](api/sensor-sensor-characteristics.md#simulatecapturesensorreport) | Type |
3200
+
3088
3201
  ## sensor/sensor-geometry.ts
3089
3202
 
3090
3203
  [Detailed contracts](api/sensor-sensor-geometry.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/sensor/sensor-geometry.ts)
@@ -48,6 +48,7 @@ the example above is [simulate-capture.mjs](examples/simulate-capture.mjs).
48
48
  | `raw` | The packed native RAW: codes, black levels, saturation flags and per-phase white levels |
49
49
  | `plan` | The native RAW plan the engine executed |
50
50
  | `request` | The complete underlying request: `environmentRaw` (the native environment RAW input), `siteInput(index)` (rebuilds the physical input of any sensor site), `development` (the RAW development input) and `whiteBalanceState` |
51
+ | `sensor` | 2.20 (#555, #535): the sensor characteristics used (pitch, peak QE, full well, read noise of each conversion gain and the dual-gain switch ISO, readout time, ADC bit depth, fixed-pattern noise), the calculated dynamic range (`dynamicRangeStops`, and per ISO setting) and, on exact requests, the mid-gray noise figure (`midGrey`); see [Sensor characteristics](#sensor-characteristics-555) |
51
52
  | `exposure` | Mode, resolved settings, EV100, `isoControl` (`"manual"` or `"automatic"`) and, with `iso: "auto"`, `autoIso` (how the ISO was chosen and what limited it), metered scene luminance, the deviation from the standard exposure in stops (`standardExposureDeviationStops`), the illuminance at which the settings give the standard exposure (`standardExposureIlluminanceLux`), from 2.14 the deviation from the standard exposure of an 18 % grey card under the scene's illuminance (`greyCardDeviationStops`, incident metering) and the exposure-mode resolver's full output |
52
53
  | `scene` | Chart parameters, illuminant, distance, framed size and the scene's SHA-256 digest (it covers caller illuminant and reflectance spectra; see `sourceSha256`) |
53
54
  | `diagnostics` | Radiance query count, highlight and rendering counters, per-stage timings (`timingsMs`) and, from 2.1, the opt-ins that ran (`options`); see [Cost of each option](#cost-of-each-option) |
@@ -104,6 +105,7 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
104
105
  | `output.exposureCompensationStops` | 0 | #556: a development exposure push, −2 to +5 stops, on the RAW before white balance (see [Development exposure push](#development-exposure-push-556)). |
105
106
  | `output.diffractionCorrection` | off | #552: `{ wavelengthNm?, regularization?, maximumGain?, kernelRadiusPixels? }`, a bounded deconvolution of the diffraction blur; needs `lens.diffraction`. It never restores detail beyond the diffraction cut-off (see [Sharpening and diffraction correction](#sharpening-and-diffraction-correction-552)). |
106
107
  | `output.sharpening` | off | #552: `{ radiusPixels?, amount?, threshold? }`, an unsharp mask after the tone curve; sRGB output only (see [Sharpening and diffraction correction](#sharpening-and-diffraction-correction-552)). |
108
+ | `output.lensCorrections` | off | #570, with `lens.catalogue`: `{ distortion?, lateralColor?, vignetting? }`, the lens's own corrections on the developed JPEG, PNG and samples; the DNG is never corrected. Works with `output.region` (see [Generic lens catalogue](#generic-lens-catalogue-549)). |
107
109
  | `output.whiteBalance` | `"scene-illuminant"` | Also `"auto"`, `"none"`, `{ kelvin, tint? }` and `{ channelGains }`. |
108
110
  | `output.jpegQuantizationStep` | 2 | JPEG only: constant step 1–255, not a manufacturer quality scale. |
109
111
  | `sensor.pixelPitchMicrometers` | 6 | 0.5–1000 (2.5; 0.5–100 before). The sensor is `width × pitch` wide (see [Geometry](#geometry)); the ISO readout scales with the pixel area (see [ISO and pixel size](#iso-and-pixel-size)). Above 100 µm the capture is a downsized view of a whole frame (see [Whole-frame views](#whole-frame-views-25)). |
@@ -116,7 +118,7 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
116
118
  | `motion.angularVelocityRadPerSec` | still | `{ pitch?, yaw?, roll? }` in rad/s (see [Motion](#motion)). |
117
119
  | `motion.shutter` | `"global"` | Or `"rolling"`. |
118
120
  | `motion.rollingShutterSkewSeconds` | the body's readout skew | Rolling only. |
119
- | `signal`, `onProgress` | none | An `AbortSignal` cancels the running task; `onProgress` reports `raw`, `white-balance`, `development` and `jpeg` tiles. The last event of each stage has `completedTileCount === totalTileCount`. The capture yields to the host about every 8 ms with a `MessageChannel` message (else `setImmediate`, else `setTimeout`), so other queued tasks run in between: an abort sent from another task, such as a Web Worker `message` event, takes effect at the next yield. |
121
+ | `signal`, `onProgress` | none | An `AbortSignal` cancels the running task; `onProgress` reports `raw`, `white-balance`, `development`, `diffraction-correction` and `sharpening` (#571: output rows, with those options) and `jpeg` tiles. The last event of each stage has `completedTileCount === totalTileCount`. The capture yields to the host about every 8 ms with a `MessageChannel` message (else `setImmediate`, else `setTimeout`), so other queued tasks run in between: an abort sent from another task, such as a Web Worker `message` event, takes effect at the next yield. |
120
122
 
121
123
  Unknown fields are rejected, so a typo never silently falls back to a default.
122
124
 
@@ -205,11 +207,13 @@ and every number is a labelled approximation.
205
207
  for the macro); an f-number or distance outside the lens's range is refused.
206
208
  - **Its character is rendered:** a field- and wavelength-dependent PSF (diffraction with the lens's Seidel aberrations, axial and
207
209
  lateral colour) through the environment PSF quadrature, its radial distortion, its vignetting and, for the fisheyes, the
208
- equisolid projection (beyond 90° there is no scene, so the 8 mm's circle is black outside). Flare, focus breathing and the bokeh
209
- shape are reported in the assumption `lens.catalogue.character`, not rendered.
210
+ equisolid projection (beyond 90° there is no scene, so the 8 mm's circle is black outside). Flare and focus breathing are
211
+ reported in the assumption `lens.catalogue.character`, not rendered.
210
212
  - **Professional lenses are better** where the owner's table says so: sharper centres and corners, less coma, lateral colour,
211
213
  distortion and vignetting, better close focus and focus consistency. The measurements are in the catalogue page.
212
- - **Combinations:** `lens.defocus` convolves the disc into the lens's PSF (a combined kernel up to 16 sites in radius);
214
+ - **Combinations:** `lens.defocus` convolves the lens's own bokeh kernel (#567: its blades, its W040 and, on the fast wides
215
+ and normals, a cat's eye, which is lens.catalogue's exact method: on when the request resolves to exact (`fast: false`, the
216
+ default for DNG, JPEG and PNG), off on fast requests, since it would slow the fast method 2–3.5×; [Bokeh](https://github.com/Photivra/photivra/blob/main/docs/LENS_CATALOGUE.md#bokeh-567)) into the lens's PSF (a combined kernel up to 16 sites in radius);
213
217
  `lens.focusScatter: { seed, shotIndex? }` moves the focus by a seeded autofocus error (`simulateGenericLensFocusError`), off
214
218
  by default. `prime`, `focalLengthMm`, `projection`, `distortion`, `vignetting`, `psf`, `diffraction` and `pupilSampling` are
215
219
  set by the lens and refused with it. The display view renders `lens.catalogue` since #569, with the PSF taken per field zone
@@ -217,6 +221,13 @@ and every number is a labelled approximation.
217
221
  - **Cost:** one scene query per kernel tap through the lens field mapping, like `lens.distortion`: a 48 × 48 region of a full frame
218
222
  at 6 µm takes about 1 s with *PV 50 1.2 Pro* and 6 s with *PV 50 1.8* (Apple M5 Pro), against 0.01 s with `lens.prime`. Render
219
223
  regions, or use a coarser `sensor.spectralStepNanometers`.
224
+ - **Lens corrections (#570):** `output: { lensCorrections: { distortion: true, lateralColor: true, vignetting: true } }` applies the
225
+ lens's generic correction profile (`createGenericLensCorrectionProfile`) to the developed linear colour, each correction
226
+ switched on separately, as cameras do for their JPEGs. The RAW and the DNG are never corrected. The image keeps its size and is
227
+ magnified just enough to have no missing pixels (`magnification` in the `output.lensCorrections` assumption, 1 for the barrel
228
+ wide angles); vignetting is corrected to a flat field. Regions and parts are byte-identical to the whole frame. The costs are
229
+ real: stretched corners lose resolution and brightened corners are noisier
230
+ ([DIGITAL_OPTICS_FOUNDATION.md](https://github.com/Photivra/photivra/blob/main/docs/DIGITAL_OPTICS_FOUNDATION.md#lens-corrections-in-the-development-570)).
220
231
  - **Fast method (#568):** `fast: true` (or `lens: { catalogue: { kind: "pv-50-1.8", fast: true } }`) blends the PSF over a field
221
232
  grid and tabulates each site's signal, a labeled approximation within 2.3e-4 of the frame's peak signal of the exact method
222
233
  (measured): a 768 × 512 whole full frame renders within 2× of the same `lens.prime` capture. It needs a still camera, site-center
@@ -252,9 +263,20 @@ the crop factor, so a 50 mm lens is 50 mm on full frame and on APS-C, as when a
252
263
  - **An optional f-number range:** `lens.ideal: { focalLengthMm, widestFNumber, narrowestFNumber }` (either or both, within
253
264
  `IDEAL_LENS_LIMITS`, the widest no larger than the narrowest; each defaults to its bound) narrows that range like a real
254
265
  lens's: `lens.fNumber` must lie in it, and shutter priority and program choose apertures only in it. The assumption then
255
- records the range (`{ lensId, focalLengthMm, widestFNumber, narrowestFNumber }`); without it nothing changes. Program follows
256
- the engine's program line (f/4–f/16) and refuses a range that does not hold it, as for `lens.prime` and `lens.catalogue`; a
257
- range without f/8 takes the reference exposure at its aperture nearest f/8 (the same N²/t).
266
+ records the range (`{ lensId, focalLengthMm, widestFNumber, narrowestFNumber }`); without it nothing changes. A range without
267
+ f/8 takes the reference exposure at its aperture nearest f/8 (the same N²/t). Where that shutter would be shorter than the
268
+ body's shortest (a range narrower than about f/1, such as f/0.5–f/0.9), the anchor takes the shortest shutter instead, a lower
269
+ N²/t that the meter and the exposure modes measure from, so the exposure is the same (#585; refused in every mode before 2.19.0).
270
+ - **Program with a range (#583):** program follows the engine's program line (f/16 1/2000 s … f/8 1/125 s … f/4 1.024 s). A
271
+ range that holds f/4–f/16 keeps it unchanged. Otherwise the line's f/4 and f/16 move to the range (each clamped to it): the
272
+ same shape, with the aperture held at the range's end and the shutter taking up the stops (the same N²/t, so the same
273
+ exposure), still from 1/2000 s to 1.024 s. Beyond the line's ends the resolver holds them, as before (with `iso: "auto"` the
274
+ ISO rises in dim light; with a fixed ISO the capture is under- or overexposed and `exposure.standardExposureDeviationStops`
275
+ says by how much). For example f/8–f/32 gives f/16 1/2000 s … f/16 1/125 s … f/8 1/125 s … f/8 1.024 s. The
276
+ `exposure.programLine` assumption then records the limited line (profile id `"facade-generic-program-line-lens-range"`, with its `nodes`). The same
277
+ applies to the `lens.prime` and `lens.catalogue` ranges that do not hold f/4–f/16 (the 800 mm and 1200 mm-equivalent primes;
278
+ *PV 800 5.6 Pro*, *PV 1200 8 Pro*, *PV 200 5.6*, *PV 300 5.6*, *PV 400 5.6*, *PV 500 5.6*, *PV 600 6.3* and *PV 800 9*),
279
+ which program refused before 2.19.0. An approximation: a generic line, not a manufacturer's program.
258
280
  - **The same blur models as `lens.prime`:** `lens.defocus` (the thin-lens blur circle of `calculateDefocusCircle`), `lens.diffraction`
259
281
  (the Airy pattern) and `lens.vignetting` (a caller profile). Without them the ideal lens renders sharp and evenly lit. It focuses
260
282
  at any distance beyond the focal length (no minimum focus distance; the subject must be at least twice the focal length away, as
@@ -441,6 +463,73 @@ large-pixel sensor may therefore saturate earlier, and a real small-pixel sensor
441
463
  given a higher base ISO. When the pitch is not 6 µm, the assumption `sensor.isoAreaNormalization`
442
464
  records the area ratio and this approximation.
443
465
 
466
+ ### Sensor characteristics (#555)
467
+
468
+ Each characteristic of the body's sensor can be set on its own under `sensor`. All are off by
469
+ default: a request that sets none is byte-identical to 2.19.0, and every value set is listed in the
470
+ assumption `sensor.characteristics`. The figures under the result's `sensor` are calculated from
471
+ them; they are results, not settings.
472
+
473
+ ```ts
474
+ const r = await simulateCapture({ scene: { preset: "uniform" }, sensor: {
475
+ pixelPitchMicrometers: 4,
476
+ peakQuantumEfficiency: 0.8, // green EQE at 555 nm, 0.05–0.9 (default 0.5)
477
+ fullWell: { electrons: 50_000, link: "none" }, // or linked to pixel area (the default)
478
+ readNoise: { lowGainElectrons: 3, highGainElectrons: 1.2, highGainFromIso: 640 },
479
+ readoutSeconds: 0.012, // rolling-shutter skew
480
+ adcBitDepth: 16, // 12, 14 or 16
481
+ fixedPatternNoise: true // the body's synthetic pattern (advanced)
482
+ }, fast: false }); // fast: false computes the mid-gray noise figure
483
+ r.sensor.dynamicRangeStops; r.sensor.midGrey?.snrDecibels;
484
+ ```
485
+
486
+ | Option | Range | Default | Effect |
487
+ | --- | --- | --- | --- |
488
+ | `peakQuantumEfficiency` | 0.05–0.9 | 0.5 | Multiplies every channel's EQE curve by value / 0.5: the curves keep their shape and the colour matrix, and the collected charge scales in proportion. 0.9 keeps the blue channel's peak below 1. |
489
+ | `fullWell` | 100–1e10 e⁻ at the pitch | the body's base-ISO saturation at 6 µm, × pitch²/36 µm² | The charge at the RAW white level at base ISO (the saturation capacity of EMVA 1288 Release 4.0). `link: "pixel-area"` (default) scales `electrons`, the full well at `referencePixelPitchMicrometers` (default 6), by (pitch / reference)²; `link: "none"` uses it at any pitch. |
490
+ | `readNoise` | 0–100 e⁻; switch ISO 100–1e7 | the body's (tiers: 2/1.1, 3/1.4 and 6/2.5 e⁻, switch at ISO 800) | Read noise of the low and the high conversion gain, and `highGainFromIso`, the ISO from which the high gain is used. A switch above the body's top ISO keeps the low gain throughout. |
491
+ | `readoutSeconds` | 1e-6–1 s | the body's (8, 15, 30 ms) | The rolling shutter's top-to-bottom skew (`motion.shutter: "rolling"`; `motion.rollingShutterSkewSeconds` still overrides it). |
492
+ | `adcBitDepth` | 12, 14, 16 | the body's (consumer 12, others 14) | The black level scales by 2^Δbits (1 024 at 14 bits → 4 096 at 16), the saturation code is 2^bits − 1 and the conversion gain follows, so the same charge reaches the same fraction of the code range. A 16-bit RAW writes a 16-bit DNG with white level 65 535. |
493
+ | `fixedPatternNoise` | `true`/`false` | `false` | The body's synthetic fixed-pattern profile (PRNU, DSNU, hot pixels, row and column offsets), the same pattern in every capture. Not available in `simulateDisplayView`, which refuses it. |
494
+
495
+ How the full well and the ISO rating interact (an approximation, recorded in the assumption): the
496
+ facade keeps each ISO setting's readout regime, so the full well is reached at the white level of
497
+ base ISO and every regime's conversion gain and pre-ADC saturation scale with it (`chargeScale`).
498
+ The output per unit focal-plane exposure H then changes by 2^`sensitivityOffsetStops` =
499
+ QE scale × (pixel area / 36 µm²) × (body full well / full well): the body's rating S = 10/H (CIPA
500
+ DC-004-2020 §3.2) still holds only when that is 1, as it is for a linked full well and the default
501
+ QE. A higher QE makes the same exposure brighter; a larger unlinked full well makes it darker and
502
+ gives more highlight headroom. The high conversion gain's smaller charge storage is not modelled
503
+ when the switch ISO is lowered. The assumption notes a full well outside 200–10 000 e⁻ per µm² of
504
+ pixel area as unusual for the pitch; that is a design range around the tiers' 556–1 667 e⁻/µm² and
505
+ published values such as about 4 000 e⁻/µm² at 2 µm and 1 400 e⁻/µm² at 5.2 µm (Farrell, Xiao &
506
+ Kavusi, *Proc. SPIE* 6069, 2006, Table 1), not a published bound.
507
+
508
+ **Dynamic range** (`sensor.dynamicRangeStops` at the capture's ISO, `sensor.dynamicRangeByIso` for
509
+ every ISO setting of the body): engineering dynamic range log₂(S_sat / σ_floor). S_sat is the charge
510
+ at the RAW white level (the full well over the analog gain, limited by the charge capacity and the
511
+ ADC range) and σ_floor = √(σ_read² + σ_q²), the temporal dark noise of EMVA 1288 Release 4.0 without
512
+ dark current, with σ_q the ADC quantization (1/12 code², plus 1/12 code² of rounding after a digital
513
+ gain) in electrons. With no read noise the range is limited by the ADC alone: log₂((white − black)·√12).
514
+ The Professional tier gives 14.7 stops at ISO 100, 12.7 at ISO 800 (high gain) and 9.7 at ISO 6400;
515
+ the Consumer tier 11.7, 10.0 and 7.0.
516
+
517
+ **Noise figure** (#535, `sensor.midGrey`): computed only when the request resolves to exact (`fast:
518
+ false`, the default for DNG, JPEG and PNG exports; about 0.3 ms); a fast request (the sRGB default)
519
+ omits it. The expected signal-to-noise ratio of an 18 % Lambertian
520
+ gray patch under the request's illuminant and illuminance, at the capture's exposure, ISO and
521
+ sensor, from the engine's own noise model (a model figure, not a measurement). It is the green
522
+ channel next to the optical axis (no vignetting, PSF or motion). `signalElectrons` S is the patch's
523
+ expected charge; `noiseElectrons` splits the noise into photon shot √S, read, dark-current shot
524
+ √(D·t) with D = 1 e⁻/s, ADC quantization and, with `fixedPatternNoise`, the fixed pattern
525
+ √((PRNU·S)² + (DSNU·D·t)² + row² + column²) (hot pixels excluded; a spatial noise, the same in every
526
+ capture), added in quadrature as `total`. `snr` = S / total and `snrDecibels` = 20·log₁₀(snr).
527
+ `clipped` is true when the patch reaches the white level, where the figure no longer describes
528
+ the image. With the defaults (Professional, f/8, 1/125 s, ISO 100, 21 481 lux) S ≈ 7 842 e⁻ and the
529
+ SNR is 88.5 (38.9 dB); at ISO 6400 with 1/64 of the light, S ≈ 123 e⁻ and the SNR is 11.0 (20.8 dB):
530
+ the photon shot noise grows as √S, so less light means more noise whatever the ISO. The display view
531
+ reports the same `sensor` record as its capture.
532
+
444
533
  ## Exposure
445
534
 
446
535
  The facade meters the scene like a reflected-light meter:
@@ -533,7 +622,7 @@ Exposure compensation applies to the meter target first, as with a numeric ISO.
533
622
  | `manual` | Your aperture and shutter; the ISO makes up the exposure (`resolveManualExposureMode`, automatic ISO). |
534
623
  | `aperture-priority` | Your aperture. The shutter follows the meter at the lowest ISO, but no slower than `exposure.autoIso.slowestPreferredShutterSeconds` (required; seconds within the body's shutter range, or `"auto"`, see below); the ISO makes up the rest. At the Auto ISO maximum, `afterMaximumIso` decides: `"allow-slower-shutter"` (the default) lengthens the shutter to reach the target, `"hold-preferred-shutter"` keeps it and leaves the exposure under target (`resolveAperturePriorityAutoIsoExposureMode`). |
535
624
  | `shutter-priority` | Your shutter. The aperture is chosen at the lowest ISO, no wider than the target needs, so it stops at the widest aperture; the ISO makes up the rest (`resolveShutterPriorityExposureMode`, policy `minimum-iso-aperture-first`). |
536
- | `program` | The generic program line at the lowest ISO; beyond the line's end (f/4, 1.024 s) the ISO rises (`resolveProgramAutoExposureMode`, automatic ISO). With `exposure.autoIso.slowestPreferredShutterSeconds` (optional, a number or `"auto"`, #504) the line's shutter is held at it before the ISO rises, and `afterMaximumIso` applies as in aperture priority, never past the line's own shutter. |
625
+ | `program` | The generic program line (limited to the lens's f-number range, #583) at the lowest ISO; beyond the line's end (f/4, 1.024 s) the ISO rises (`resolveProgramAutoExposureMode`, automatic ISO). With `exposure.autoIso.slowestPreferredShutterSeconds` (optional, a number or `"auto"`, #504) the line's shutter is held at it before the ISO rises, and `afterMaximumIso` applies as in aperture priority, never past the line's own shutter. |
537
626
 
538
627
  The result says what happened. `exposure.settings.iso` is the ISO used and `exposure.isoControl` is `"automatic"`.
539
628
  `exposure.autoIso` reports `idealIsoBeforeConstraints`, the Auto ISO range (`autoIsoMinimum`, `autoIsoMaximum`),
@@ -971,10 +1060,14 @@ console.log(result.diagnostics.detail); // { diffractionCorrection: {...}, sharp
971
1060
  noise; it adds no detail. Not with a wide-gamut `output.colorEncoding` or `output.gamutMapping: "perceptual"`.
972
1061
  - **Reported:** the `output.diffractionCorrection` and `output.sharpening` assumptions (settings, cut-off, measured filter
973
1062
  response, defaults used), `diagnostics.detail` (ringing, clipping and halo counts) and `diagnostics.options`.
974
- - **Not combinable** with `output.region`; not offered by `simulateDisplayView` or `simulateIdealReference`. The DNG holds the
975
- RAW codes and is unchanged; only `image.samples` (and the JPEG or PNG made from them) change.
976
- - **Cost:** both stages hold the whole developed image, as AHD does. At 256 × 256 on an Apple M5 Pro (Node.js 24.19.0) the
1063
+ - **Not combinable** with `output.region`; not offered by `simulateDisplayView` or `simulateIdealReference`. A capture rendered
1064
+ in region parts takes them through `simulateCaptureFromParts` (#571, [Developing a capture rendered in parts](#developing-a-capture-rendered-in-parts-571)).
1065
+ The DNG holds the RAW codes and is unchanged; only `image.samples` (and the JPEG or PNG made from them) change.
1066
+ - **Cost:** both stages need the whole developed image, as AHD does. At 256 × 256 on an Apple M5 Pro (Node.js 24.19.0) the
977
1067
  development took 8 ms by default, 15 ms with sharpening and 18–20 ms with diffraction correction (R = 3–5), one run each.
1068
+ Since #571 they run row by row in place, with the same sums in the same order (the same bytes as 2.18.0): one developed
1069
+ image in memory instead of up to six, and `onProgress` reports each output row as the `diffraction-correction` and
1070
+ `sharpening` stages. At 24 MP see [Developing a capture rendered in parts](#developing-a-capture-rendered-in-parts-571).
978
1071
 
979
1072
  The trade-offs of these and the lens corrections (corner resolution after distortion correction, corner noise after
980
1073
  vignetting correction, residual longitudinal CA, halo overshoot, nothing beyond the cut-off) are measured in
@@ -1070,6 +1163,18 @@ The optics options feed only what the environment site contract accepts:
1070
1163
  scene; a test checks it to 1e-12 per site. With camera rotation, a projection or distortion, or
1071
1164
  vignetting, the per-tap quadrature runs instead (much slower). `request.diffraction` holds the profile,
1072
1165
  the equivalent `sampled-local` PSF and which execution ran.
1166
+ - Ties on chart boundaries (2.20.0, #565): the per-tap quadrature reads each tap's scene point at its site-lattice point, as
1167
+ the object-space convolution, the out-of-focus capture and the display view do, so a tap exactly on a chart boundary (a
1168
+ Siemens star's spokes through lattice points) resolves to the same material on every path. With camera rotation under
1169
+ `motion.shutter: "rolling"` every row has its own instants, so the lattice is read once per instant; there reading the
1170
+ lattice is `lens.diffraction`'s exact method and keeping each tap's own point its fast method; the lattice costs about
1171
+ 1.2× the time (dead leaves 48 × 32 at f/8: 4.0–4.1 s against 4.8–5.0 s; a Siemens star 32 × 24 at f/16: 6.8 s against
1172
+ 8.1–8.2 s; November 9, 2026, Apple M5 Pro, Node 24.19.0). The format default decides (2.20.0): fast for sRGB samples, exact for
1173
+ DNG, JPEG and PNG; `lens.diffraction: { kind, fast }` or the top-level `fast` wins, and the fast method reports
1174
+ `diagnostics.fast.applied["lens.diffraction"]: true` and the assumption `fast.lens.diffraction`. The two readings differ
1175
+ only at such ties. Region renders joined into one frame (`serializeSimulatedCaptureDngFromParts`,
1176
+ `simulateCaptureFromParts`) match the whole-frame render only if every part resolves `fast` the same way: give the parts
1177
+ and the whole request the same `fast` (a part rendered fast is refused by an exact join).
1073
1178
  - The convolution runs in 32-row bands and computes only each band's own values, not the 2R rows and
1074
1179
  columns of padding around it (#419; the values are bit-identical to the full-grid convolution's). On a
1075
1180
  function scene with the Quality options at 1 µm and f/16 (R 32), 96 × 72, `diagnostics.timingsMs.diffraction`
@@ -1400,9 +1505,9 @@ lens: { focalLengthMm: 50, fNumber: 4, focusDistanceM: 1.4, defocus: { model: "g
1400
1505
  combined with `lens.diffraction` (every zone would need its own combined kernel per node). Above a kernel radius of 64
1401
1506
  sites it is refused: #564's run convolution takes one kernel per request, and extending it to one kernel per zone is left
1402
1507
  for later. `simulateDisplayView` and `prepareDisplayView` refuse it; they render `blades` and `sphericalAberration`.
1403
- - **With `lens.catalogue` (#549)** the three options are refused by name. The catalogue lens folds a plain disc into its
1404
- own PSF, so they would otherwise be dropped. Feeding a catalogue lens's spherical aberration and character into the
1405
- bokeh kernel is a follow-up ([#567](https://github.com/Photivra/photivra/issues/567)), not a 2.17.0 feature.
1508
+ - **With `lens.catalogue` (#549)** the three options are refused by name: since 2.20.0 (#567) the catalogue lens sets
1509
+ all three itself (its blades, its own W040 and, on the fast wides and normals, a cat's eye) and folds that kernel into
1510
+ its PSF ([Lens catalogue, Bokeh](https://github.com/Photivra/photivra/blob/main/docs/LENS_CATALOGUE.md#bokeh-567)).
1406
1511
  - **Sampling.** Without spherical aberration each site's weight is the exact area of the projected open pupil (a convex
1407
1512
  polygon; circles and blade arcs are 1024-gons scaled to their true area, within 5e-6 of the radius of the true curve)
1408
1513
  inside the site's cell, divided by its area (`"exact-polygon-cell-area"`). With it the pupil is sampled on a square grid
@@ -1865,6 +1970,65 @@ const dng = await serializeSimulatedCaptureDngFromParts(dngFrame, parts);
1865
1970
  console.log(dng.bytes.length);
1866
1971
  ```
1867
1972
 
1973
+ ### Developing a capture rendered in parts (#571)
1974
+
1975
+ `simulateCaptureFromParts(options, parts)` develops the whole frame of `options` from region renders of the same
1976
+ request. It joins the parts' RAW exactly as `serializeSimulatedCaptureDngFromParts` does (the same parts, the same checks),
1977
+ then develops the joined RAW as `simulateCapture(options)` would, including the steps that filter across the whole image and
1978
+ that a region refuses: `output.diffractionCorrection` and `output.sharpening`. The joined RAW is the whole render's RAW, so the
1979
+ result's `image.samples`, JPEG or PNG bytes, `raw` and `plan` are those of the whole-frame capture, bit for bit. Pass the
1980
+ whole request (no `output.region`); render the parts with the same request plus `output.region`, without the detail stages,
1981
+ `output.dng` and auto white balance (none changes the RAW codes; auto white balance is measured here over the joined frame).
1982
+ `output.lensCorrections` (#570) is honored as in the whole-frame capture, bit for bit; a part may keep or drop it (a corrected
1983
+ region only widens its `region.nativeRect`). The corrections run on the whole developed frame and hold a second developed
1984
+ image while they resample it.
1985
+
1986
+ `onProgress` reports `development`, then `diffraction-correction` and `sharpening` once per output row of the whole image
1987
+ (each stage ends with `completedTileCount === totalTileCount`), then `jpeg`; no `raw` stage, since no RAW is rendered. A host
1988
+ can show these as "Applying diffraction correction to the whole image" and "Applying sharpening to the whole image". The
1989
+ lens corrections run between the two and report no stage of their own.
1990
+ `signal` cancels the step. In the result, `diagnostics.providerEvaluationCount` is 0, `diagnostics.timingsMs` are this call's
1991
+ (`raw` is the join), `diagnostics.fast` and the `raw.*` assumptions of how the RAW was rendered are in the parts' results
1992
+ instead, and a `parts` assumption records the join.
1993
+
1994
+ ```ts
1995
+ import { simulateCaptureFromParts, simulateCapture, type SimulateCaptureDngPart, type SimulateCaptureOptions } from "@photivra/engine";
1996
+
1997
+ const whole: SimulateCaptureOptions = { scene: { preset: "slanted-edge" }, lens: { fNumber: 16, diffraction: "circular-aperture" },
1998
+ output: { width: 96, height: 64, format: "jpeg", diffractionCorrection: {}, sharpening: {} } };
1999
+ // Two strips rendered without the whole-image stages (in an application, in separate Web Workers).
2000
+ const parts: SimulateCaptureDngPart[] = await Promise.all([0, 32].map(async (y) => {
2001
+ const part = await simulateCapture({ ...whole, output: { width: 96, height: 64, region: { x: 0, y, width: 96, height: 32 } } });
2002
+ return { raw: part.raw, region: part.region! };
2003
+ }));
2004
+ const developed = await simulateCaptureFromParts({ ...whole, onProgress: ({ stage, completedTileCount, totalTileCount }) => {
2005
+ if (stage === "sharpening") console.log("Applying sharpening to the whole image", completedTileCount / totalTileCount);
2006
+ } }, parts);
2007
+ console.log(developed.image.jpeg!.length); // the bytes of simulateCapture(whole).image.jpeg
2008
+ ```
2009
+
2010
+ Tests compare the result with the whole-frame capture byte for byte: JPEG with both stages, 16-bit sRGB with a thresholded
2011
+ mask, AHD with auto white balance, a development push and the four-spot filter, Display P3 PNG with diffraction correction,
2012
+ DNG with both stages, no stage at all, and a catalogue lens with all three lens corrections and sharpening (parts rendered
2013
+ with and without the corrections); and the row-by-row stages against whole-image passes on odd sizes.
2014
+
2015
+ **Measured at 24 MP** (6000 × 4000 at 6 µm, slanted edge at f/8 with `lens.diffraction`, 8 parts; Apple M5 Pro, Node.js 24.19.0,
2016
+ one thread, a shared machine, two runs; `node scripts/benchmark-joined-development.mjs`):
2017
+
2018
+ | Step | Time | Peak memory (process) |
2019
+ | --- | --- | --- |
2020
+ | Join and develop, no detail stage | 1.9–2.2 s (join 0.13–0.15 s) | 532–551 MiB |
2021
+ | … with sharpening (σ = 1) | 3.3–3.7 s (sharpening 1.9–2.1 s) | 1,080 MiB |
2022
+ | … with diffraction correction (R = 3, the default) | 3.7–3.9 s (correction 2.0 s) | 1,079–1,080 MiB |
2023
+ | … with both | 5.1–5.4 s (development 1.2 s, correction 2.0–2.1 s, sharpening 1.7–1.8 s) | 1,079 MiB |
2024
+ | Whole `simulateCapture` with both, for comparison | 10.0–10.4 s (RAW 4.8–4.9 s) | 924 MiB |
2025
+
2026
+ The peak includes the parts' RAW read back (7 bytes per native site) and the joined RAW. The detail stages' share is the one
2027
+ developed image in 64-bit floats (24 bytes per pixel, 549 MiB at 24 MP), held as before by AHD and the wide-gamut paths. In
2028
+ 2.18.0 the stages held up to six such images: the same whole capture with both stages peaked at 3,670 MB (resident set
2029
+ sampled every 50 ms). The extra pass therefore costs about 5 s on one worker after the parts, against about 10 s to render
2030
+ the whole frame again on one worker.
2031
+
1868
2032
  ### A Print JPEG written in strips
1869
2033
 
1870
2034
  At 1:1 (output size equal to the crop), a Print JPEG's rows can be coded on separate workers.