@photivra/engine 2.19.0 → 2.21.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 (242) hide show
  1. package/CHANGELOG.md +289 -1
  2. package/CITATION.cff +1 -1
  3. package/NODE_SUPPORT.md +7 -5
  4. package/README.md +7 -6
  5. package/SECURITY.md +1 -1
  6. package/THIRD_PARTY.md +21 -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/exposure-build-up-stage.d.ts +16 -0
  11. package/dist/capture/exposure-build-up-stage.js +4 -0
  12. package/dist/capture/exposure-build-up-stage.js.map +1 -0
  13. package/dist/capture/native-environment-lean.d.ts +2 -1
  14. package/dist/capture/native-environment-lean.js +42 -78
  15. package/dist/capture/native-environment-lean.js.map +1 -1
  16. package/dist/capture/native-environment-tabulated.js +4 -3
  17. package/dist/capture/native-environment-tabulated.js.map +1 -1
  18. package/dist/capture/native-raw-dng.d.ts +6 -0
  19. package/dist/capture/native-raw-dng.js +16 -2
  20. package/dist/capture/native-raw-dng.js.map +1 -1
  21. package/dist/capture/native-raw.d.ts +3 -2
  22. package/dist/capture/native-raw.js +5 -4
  23. package/dist/capture/native-raw.js.map +1 -1
  24. package/dist/capture/sensor-raw-producer.d.ts +4 -1
  25. package/dist/capture/sensor-raw-producer.js +15 -8
  26. package/dist/capture/sensor-raw-producer.js.map +1 -1
  27. package/dist/core/shared-helpers.d.ts +2 -0
  28. package/dist/core/shared-helpers.js +4 -0
  29. package/dist/core/shared-helpers.js.map +1 -1
  30. package/dist/core/version.d.ts +1 -1
  31. package/dist/core/version.js +1 -1
  32. package/dist/core/version.js.map +1 -1
  33. package/dist/equipment/generic-lens-catalogue.d.ts +11 -4
  34. package/dist/equipment/generic-lens-catalogue.js +49 -7
  35. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  36. package/dist/equipment/generic-tier-assets.d.ts +32 -0
  37. package/dist/equipment/generic-tier-assets.js +18 -14
  38. package/dist/equipment/generic-tier-assets.js.map +1 -1
  39. package/dist/equipment/generic-tier-presets.js +2 -3
  40. package/dist/equipment/generic-tier-presets.js.map +1 -1
  41. package/dist/equipment/sensor-profile-body.d.ts +78 -0
  42. package/dist/equipment/sensor-profile-body.js +77 -0
  43. package/dist/equipment/sensor-profile-body.js.map +1 -0
  44. package/dist/equipment/sensor-profiles.d.ts +8 -0
  45. package/dist/equipment/sensor-profiles.js +82 -0
  46. package/dist/equipment/sensor-profiles.js.map +1 -0
  47. package/dist/facade/catalogue-lens-signal.d.ts +2 -2
  48. package/dist/facade/catalogue-lens-signal.js +58 -71
  49. package/dist/facade/catalogue-lens-signal.js.map +1 -1
  50. package/dist/facade/chart-radiance-batch.js +2 -1
  51. package/dist/facade/chart-radiance-batch.js.map +1 -1
  52. package/dist/facade/defocus-image-space-provider.d.ts +7 -0
  53. package/dist/facade/defocus-image-space-provider.js +64 -76
  54. package/dist/facade/defocus-image-space-provider.js.map +1 -1
  55. package/dist/facade/display-view-gpu-session.js +2 -0
  56. package/dist/facade/display-view-gpu-session.js.map +1 -1
  57. package/dist/facade/display-view-gpu.d.ts +13 -5
  58. package/dist/facade/display-view-gpu.js +4 -4
  59. package/dist/facade/display-view-gpu.js.map +1 -1
  60. package/dist/facade/display-view-lens-signal.d.ts +1 -1
  61. package/dist/facade/display-view-lens-signal.js +30 -26
  62. package/dist/facade/display-view-lens-signal.js.map +1 -1
  63. package/dist/facade/display-view.d.ts +3 -1
  64. package/dist/facade/display-view.js +6 -24
  65. package/dist/facade/display-view.js.map +1 -1
  66. package/dist/facade/expected-signal-setup.d.ts +3 -1
  67. package/dist/facade/expected-signal-setup.js +1 -1
  68. package/dist/facade/expected-signal-setup.js.map +1 -1
  69. package/dist/facade/expected-signal.d.ts +47 -14
  70. package/dist/facade/expected-signal.js +117 -112
  71. package/dist/facade/expected-signal.js.map +1 -1
  72. package/dist/facade/exposure-build-up.d.ts +70 -0
  73. package/dist/facade/exposure-build-up.js +164 -0
  74. package/dist/facade/exposure-build-up.js.map +1 -0
  75. package/dist/facade/fast-methods.d.ts +50 -0
  76. package/dist/facade/fast-methods.js +58 -0
  77. package/dist/facade/fast-methods.js.map +1 -0
  78. package/dist/facade/fast-resolution.d.ts +12 -0
  79. package/dist/facade/fast-resolution.js +18 -0
  80. package/dist/facade/fast-resolution.js.map +1 -0
  81. package/dist/facade/generic-lens-capture.d.ts +5 -4
  82. package/dist/facade/generic-lens-capture.js +5 -4
  83. package/dist/facade/generic-lens-capture.js.map +1 -1
  84. package/dist/facade/image-plane-enable.d.ts +7 -0
  85. package/dist/facade/image-plane-enable.js +17 -0
  86. package/dist/facade/image-plane-enable.js.map +1 -0
  87. package/dist/facade/image-plane-lattice-data.d.ts +6 -0
  88. package/dist/facade/image-plane-lattice-data.js +10 -0
  89. package/dist/facade/image-plane-lattice-data.js.map +1 -0
  90. package/dist/facade/image-plane-support.d.ts +33 -0
  91. package/dist/facade/image-plane-support.js +11 -0
  92. package/dist/facade/image-plane-support.js.map +1 -0
  93. package/dist/facade/image-plane.d.ts +36 -0
  94. package/dist/facade/image-plane.js +353 -0
  95. package/dist/facade/image-plane.js.map +1 -0
  96. package/dist/facade/mixed-illumination.d.ts +108 -0
  97. package/dist/facade/mixed-illumination.js +174 -0
  98. package/dist/facade/mixed-illumination.js.map +1 -0
  99. package/dist/facade/representative-light-sources.d.ts +72 -0
  100. package/dist/facade/representative-light-sources.js +124 -0
  101. package/dist/facade/representative-light-sources.js.map +1 -0
  102. package/dist/facade/scene-radiance.d.ts +129 -4
  103. package/dist/facade/scene-radiance.js +48 -13
  104. package/dist/facade/scene-radiance.js.map +1 -1
  105. package/dist/facade/simulate-capture.d.ts +88 -19
  106. package/dist/facade/simulate-capture.js +374 -145
  107. package/dist/facade/simulate-capture.js.map +1 -1
  108. package/dist/facade/simulate-display-view.d.ts +3 -2
  109. package/dist/facade/simulate-display-view.js +36 -18
  110. package/dist/facade/simulate-display-view.js.map +1 -1
  111. package/dist/gpu/chart-composite-gpu.d.ts +13 -1
  112. package/dist/gpu/chart-composite-gpu.js +48 -9
  113. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  114. package/dist/gpu/chart-radiance-gpu.js +4 -30
  115. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  116. package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
  117. package/dist/gpu/display-view-composite-material-module.js +39 -13
  118. package/dist/gpu/display-view-composite-material-module.js.map +1 -1
  119. package/dist/gpu/display-view-image-material-module.d.ts +86 -0
  120. package/dist/gpu/display-view-image-material-module.js +339 -0
  121. package/dist/gpu/display-view-image-material-module.js.map +1 -0
  122. package/dist/gpu/display-view-image-quality.d.ts +45 -0
  123. package/dist/gpu/display-view-image-quality.js +380 -0
  124. package/dist/gpu/display-view-image-quality.js.map +1 -0
  125. package/dist/gpu/display-view-quality-gpu.d.ts +67 -1
  126. package/dist/gpu/display-view-quality-gpu.js +136 -205
  127. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  128. package/dist/gpu/display-view-speed-gpu.d.ts +51 -3
  129. package/dist/gpu/display-view-speed-gpu.js +131 -93
  130. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  131. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +29 -15
  132. package/dist/gpu/wgsl-display-view-quality-kernel.js +58 -36
  133. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  134. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +21 -6
  135. package/dist/gpu/wgsl-display-view-speed-kernel.js +46 -11
  136. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  137. package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
  138. package/dist/gpu/wgsl-exact-sum.js +42 -0
  139. package/dist/gpu/wgsl-exact-sum.js.map +1 -0
  140. package/dist/index.d.ts +18 -3
  141. package/dist/index.js +12 -1
  142. package/dist/index.js.map +1 -1
  143. package/dist/motion/subject-motion.d.ts +66 -0
  144. package/dist/motion/subject-motion.js +52 -0
  145. package/dist/motion/subject-motion.js.map +1 -0
  146. package/dist/optics/aperture-diameter.d.ts +47 -0
  147. package/dist/optics/aperture-diameter.js +50 -0
  148. package/dist/optics/aperture-diameter.js.map +1 -0
  149. package/dist/optics/detail-limit.d.ts +182 -0
  150. package/dist/optics/detail-limit.js +336 -0
  151. package/dist/optics/detail-limit.js.map +1 -0
  152. package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
  153. package/dist/optics/generic-lens-lateral-color.js +77 -0
  154. package/dist/optics/generic-lens-lateral-color.js.map +1 -0
  155. package/dist/optics/generic-lens-psf.d.ts +18 -2
  156. package/dist/optics/generic-lens-psf.js +79 -48
  157. package/dist/optics/generic-lens-psf.js.map +1 -1
  158. package/dist/optics/lens-psf-profile.d.ts +7 -0
  159. package/dist/optics/lens-psf-profile.js +2 -1
  160. package/dist/optics/lens-psf-profile.js.map +1 -1
  161. package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
  162. package/dist/optics/scene-to-sensor-irradiance.js +7 -0
  163. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  164. package/dist/output/crop-to-match-focal-length.d.ts +62 -0
  165. package/dist/output/crop-to-match-focal-length.js +63 -0
  166. package/dist/output/crop-to-match-focal-length.js.map +1 -0
  167. package/dist/output/developed-lens-corrections.d.ts +19 -5
  168. package/dist/output/developed-lens-corrections.js +30 -11
  169. package/dist/output/developed-lens-corrections.js.map +1 -1
  170. package/dist/output/generic-lens-corrections.d.ts +41 -3
  171. package/dist/output/generic-lens-corrections.js +57 -9
  172. package/dist/output/generic-lens-corrections.js.map +1 -1
  173. package/dist/output/lens-corrections.d.ts +12 -2
  174. package/dist/output/lens-corrections.js +22 -7
  175. package/dist/output/lens-corrections.js.map +1 -1
  176. package/dist/output/print-contrast-reference.js +1 -1
  177. package/dist/output/print-contrast-reference.js.map +1 -1
  178. package/dist/output/print-plan.d.ts +41 -5
  179. package/dist/output/print-plan.js +59 -14
  180. package/dist/output/print-plan.js.map +1 -1
  181. package/dist/output/print-size-blur-noise.d.ts +110 -0
  182. package/dist/output/print-size-blur-noise.js +138 -0
  183. package/dist/output/print-size-blur-noise.js.map +1 -0
  184. package/dist/sensor/binomial-sample.d.ts +3 -0
  185. package/dist/sensor/binomial-sample.js +86 -0
  186. package/dist/sensor/binomial-sample.js.map +1 -0
  187. package/dist/sensor/eqe-electron-rate.js +1 -7
  188. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  189. package/dist/sensor/exposure-window.d.ts +2 -0
  190. package/dist/sensor/exposure-window.js +2 -2
  191. package/dist/sensor/exposure-window.js.map +1 -1
  192. package/dist/sensor/readout-timing.js +2 -23
  193. package/dist/sensor/readout-timing.js.map +1 -1
  194. package/dist/sensor/response-application-compatibility.js +1 -6
  195. package/dist/sensor/response-application-compatibility.js.map +1 -1
  196. package/dist/sensor/responsivity-photocurrent.js +1 -7
  197. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  198. package/dist/sensor/sensor-characteristics.d.ts +147 -0
  199. package/dist/sensor/sensor-characteristics.js +126 -0
  200. package/dist/sensor/sensor-characteristics.js.map +1 -0
  201. package/dist/sensor/spatial-sample-reduction.js +1 -5
  202. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  203. package/dist/sensor/spectral-quadrature.js +1 -6
  204. package/dist/sensor/spectral-quadrature.js.map +1 -1
  205. package/dist/sensor/temporal-photo-signal.d.ts +3 -0
  206. package/dist/sensor/temporal-photo-signal.js +5 -2
  207. package/dist/sensor/temporal-photo-signal.js.map +1 -1
  208. package/dist/stabilization/system.d.ts +9 -0
  209. package/dist/stabilization/system.js +5 -6
  210. package/dist/stabilization/system.js.map +1 -1
  211. package/docs/AGENT_GUIDE.md +5 -0
  212. package/docs/API_REFERENCE.md +173 -2
  213. package/docs/QUICK_START.md +213 -6
  214. package/docs/USAGE.md +157 -3
  215. package/docs/api/capture-native-raw-dng.md +6 -0
  216. package/docs/api/core-version.md +1 -1
  217. package/docs/api/equipment-generic-lens-catalogue.md +17 -7
  218. package/docs/api/equipment-sensor-profile-body.md +64 -0
  219. package/docs/api/equipment-sensor-profiles.md +86 -0
  220. package/docs/api/exports.json +357 -2
  221. package/docs/api/facade-display-view-gpu.md +5 -2
  222. package/docs/api/facade-exposure-build-up.md +105 -0
  223. package/docs/api/facade-fast-methods.md +99 -0
  224. package/docs/api/facade-image-plane-enable.md +16 -0
  225. package/docs/api/facade-mixed-illumination.md +67 -0
  226. package/docs/api/facade-representative-light-sources.md +131 -0
  227. package/docs/api/facade-scene-radiance.md +54 -3
  228. package/docs/api/facade-simulate-capture.md +60 -15
  229. package/docs/api/facade-simulate-display-view.md +3 -2
  230. package/docs/api/motion-subject-motion.md +23 -0
  231. package/docs/api/optics-aperture-diameter.md +51 -0
  232. package/docs/api/optics-detail-limit.md +203 -0
  233. package/docs/api/optics-generic-lens-lateral-color.md +79 -0
  234. package/docs/api/output-crop-to-match-focal-length.md +67 -0
  235. package/docs/api/output-developed-lens-corrections.md +10 -1
  236. package/docs/api/output-generic-lens-corrections.md +40 -2
  237. package/docs/api/output-lens-corrections.md +11 -1
  238. package/docs/api/output-print-plan.md +35 -1
  239. package/docs/api/output-print-size-blur-noise.md +115 -0
  240. package/docs/api/sensor-sensor-characteristics.md +105 -0
  241. package/llms.txt +6 -0
  242. package/package.json +1 -1
@@ -2,11 +2,44 @@
2
2
 
3
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
4
 
5
+ ## calculateChannelEffectiveDispersion
6
+
7
+ #589: each channel's effective dispersion for the catalogue lenses' lateral colour, D̄_c = Σ w·D(λ) / Σ w with
8
+ w = response_c(λ)·illuminant(λ) at the nodes and D the normalised Cauchy dispersion (`genericLensDispersion`: 0 at the
9
+ Fraunhofer d line, F minus C = 1). Under lateral colour L·r·D(λ) a neutral surface's image in channel c moves by L·r·D̄_c, so
10
+ blue minus red is L·r·(D̄_blue − D̄_red). Pass the result as `channelDispersion` to `createGenericLensCorrectionProfile`.
11
+ `simulateCapture`'s `output.lensCorrections` computes it from the capture's own channels and reports it in its assumption.
12
+ An approximation: a weighted mean over a neutral surface; a colored surface weights the band differently.
13
+
14
+ ```ts
15
+ export function calculateChannelEffectiveDispersion(input: CalculateChannelEffectiveDispersionInput): CalculationResult<ChannelDispersion>;
16
+ ```
17
+
18
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
19
+
20
+ ## CalculateChannelEffectiveDispersionInput
21
+
22
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
23
+
24
+ ```ts
25
+ export interface CalculateChannelEffectiveDispersionInput {
26
+ /** Spectral nodes, nm, strictly increasing, within 300–1100 nm. */
27
+ wavelengthsNm: readonly number[];
28
+ /** Each channel's response at the nodes (any common linear unit, ≥ 0; for example electrons per unit spectral radiance). */
29
+ channelResponse: { red: readonly number[]; green: readonly number[]; blue: readonly number[] };
30
+ /** The illuminant's relative spectral power at the nodes (≥ 0; the scale cancels). */
31
+ illuminant: readonly number[];
32
+ }
33
+ ```
34
+
5
35
  ## createGenericLensCorrectionProfile
6
36
 
7
37
  Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own
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`.
38
+ r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D̄_channel (`channelDispersion`,
39
+ by default D at the generic channel wavelengths), and the relative-illumination gain at the state's aperture (strength 1). From
40
+ profile version 1.1.0 (#584) the vignetting component (version 1.1.0) carries the distortion's area factor
41
+ (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's sensor point, so it flattens a flat-field capture. The
42
+ result passes `parseGenericLensCorrectionProfile`.
10
43
 
11
44
  ```ts
12
45
  export function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile;
@@ -26,5 +59,10 @@ export interface CreateGenericLensCorrectionProfileInput {
26
59
  activeImagingArea: { widthMm: number; heightMm: number };
27
60
  /** The exact camera state the profile applies to: its focal length must be the lens's on this sensor, its aperture in range. */
28
61
  state: OpticalProfileState;
62
+ /**
63
+ * #589: each channel's effective dispersion (`calculateChannelEffectiveDispersion` for the camera's channels and the light).
64
+ * Default: D at {@link GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM} (610, 540 and 465 nm), a generic approximation.
65
+ */
66
+ channelDispersion?: ChannelDispersion;
29
67
  }
30
68
  ```
@@ -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
 
@@ -42,14 +42,44 @@ export function parsePrintPlanInput(value: unknown): PrintPlanInput;
42
42
 
43
43
  This call is synchronous; any supplied provider must follow its explicit synchronous contract.
44
44
 
45
+ ## PrintContainLayout
46
+
47
+ The layout of a `contain` fit, in millimetres. The printed image itself is the plan's `printedImageMm`.
48
+
49
+ ```ts
50
+ export interface PrintContainLayout {
51
+ /** The paper as placed: after the quarter turn when `paperRotated`. */
52
+ paperMm: { width: number; height: number };
53
+ paperRotated: boolean;
54
+ /** Unprinted border on each side of the centered image; zero when the paper has the image's aspect ratio. */
55
+ bordersMm: { left: number; right: number; top: number; bottom: number };
56
+ }
57
+ ```
58
+
45
59
  ## PrintedImageSize
46
60
 
47
61
  The flat printed image area. It excludes paper margins, borders and canvas wraps.
62
+ With a `contain` fit it is instead the paper, or the printable area of it, that the image is fitted inside.
48
63
 
49
64
  ```ts
50
65
  export interface PrintedImageSize { width: number; height: number; unit: PrintLength["unit"] }
51
66
  ```
52
67
 
68
+ ## PrintFit
69
+
70
+ How the printed image relates to `printedImage`.
71
+ - `confirmed-native-aspect`: `printedImage` is the printed image itself and must already have the native aspect ratio,
72
+ within the stated relative error.
73
+ - `contain`: `printedImage` is the paper. The image keeps the native aspect ratio and is scaled to the largest size that
74
+ fits inside it, centered; the rest is unprinted border. `match-image` turns the paper a quarter turn when its
75
+ orientation (portrait or landscape) differs from the image's; `as-given` keeps it. Layout only: nothing is cropped or resampled.
76
+
77
+ ```ts
78
+ export type PrintFit =
79
+ | { kind: "confirmed-native-aspect"; maximumRelativeAspectError: number }
80
+ | { kind: "contain"; paperOrientation: "as-given" | "match-image" };
81
+ ```
82
+
53
83
  ## PrintLength
54
84
 
55
85
  A physical length. Not to be confused with image pixels or printer dots.
@@ -90,6 +120,8 @@ export interface PrintPlan {
90
120
  alternatives: { maximumImageMmAtCurrentDistance: { width: number; height: number }; minimumViewingDistanceMmAtCurrentSize: number | null } | null;
91
121
  capturedDetailAssessment: "unassessed";
92
122
  deliveryAssessment: "unassessed";
123
+ /** Present only with a `contain` fit; null when the native raster is unknown. */
124
+ contain?: PrintContainLayout | null;
93
125
  }
94
126
  ```
95
127
 
@@ -103,7 +135,7 @@ export interface PrintPlanInput {
103
135
  printedImage: PrintedImageSize;
104
136
  viewingDistance: PrintLength;
105
137
  sampling: PrintSamplingCriterion;
106
- fit: { kind: "confirmed-native-aspect"; maximumRelativeAspectError: number };
138
+ fit: PrintFit;
107
139
  provider?: PrintProviderConstraints;
108
140
  }
109
141
  ```
@@ -156,6 +188,8 @@ export interface PrintSizeLimit {
156
188
  maximumImageMm: { width: number; height: number } | null;
157
189
  reasons: string[];
158
190
  referencePlan: PrintPlan;
191
+ /** Present only with a `contain` fit: the paper that holds `maximumImageMm` with the same layout (borders scale with it). */
192
+ maximumPaperMm?: { width: number; height: number } | null;
159
193
  }
160
194
  ```
161
195
 
@@ -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
@@ -7,6 +7,8 @@
7
7
  - [Agent guide](https://github.com/Photivra/photivra/blob/main/docs/AGENT_GUIDE.md): which function to call, units, reading results, errors and limits
8
8
  - [Quick start](https://github.com/Photivra/photivra/blob/main/docs/QUICK_START.md): `simulateCapture()` inputs, defaults, chart presets and output formats
9
9
  - [Color patches](https://github.com/Photivra/photivra/blob/main/docs/COLOR_PATCHES.md): the built-in color chart patches (`color-patches` preset, `{ kind: "color-patch", patchId }`), their spectra and CIELAB
10
+ - [Image-plane scene](https://github.com/Photivra/photivra/blob/main/docs/IMAGE_PLANE.md): a photo as a flat print in front of the camera (`{ kind: "image-plane" }`), its input, placement, print reflectance lattice and accuracy
11
+ - [Light sources](https://github.com/Photivra/photivra/blob/main/docs/LIGHT_SOURCES.md): representative fluorescent and LED spectra designed by Photivra (`createRepresentativeLightSourceIlluminant`), their CCT and color rendering
10
12
  - [Usage guide](https://github.com/Photivra/photivra/blob/main/docs/USAGE.md): worked examples for every public area
11
13
  - [API reference](https://github.com/Photivra/photivra/blob/main/docs/API_REFERENCE.md): every root export, with a task-to-function index
12
14
 
@@ -26,9 +28,13 @@
26
28
  - [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
29
  - [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
30
  - [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
31
+ - [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
32
  - [calculateCenteredCrop()](https://github.com/Photivra/photivra/blob/main/docs/api/output-crop.md#calculatecenteredcrop): pixels left after a centred crop
33
+ - [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
34
+ - [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
35
  - [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
31
36
  - [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)
37
+ - [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
32
38
  - [calculateAiryDisk()](https://github.com/Photivra/photivra/blob/main/docs/api/optics-diffraction.md#calculateairydisk): diffraction spot size from f-number and wavelength
33
39
  - [calculateSignalToNoise()](https://github.com/Photivra/photivra/blob/main/docs/api/sensor-signal-noise.md#calculatesignaltonoise): expected SNR from signal and read noise in electrons
34
40
  - [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.19.0",
3
+ "version": "2.21.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",