@photivra/engine 2.20.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 (174) hide show
  1. package/CHANGELOG.md +178 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +7 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +7 -0
  6. package/dist/capture/dng-lens-correction-opcodes.d.ts +2 -2
  7. package/dist/capture/dng-lens-correction-opcodes.js +2 -2
  8. package/dist/capture/dng-lens-correction-opcodes.js.map +1 -1
  9. package/dist/capture/exposure-build-up-stage.d.ts +16 -0
  10. package/dist/capture/exposure-build-up-stage.js +4 -0
  11. package/dist/capture/exposure-build-up-stage.js.map +1 -0
  12. package/dist/capture/native-environment-lean.d.ts +2 -1
  13. package/dist/capture/native-environment-lean.js +41 -73
  14. package/dist/capture/native-environment-lean.js.map +1 -1
  15. package/dist/capture/native-environment-tabulated.js +4 -3
  16. package/dist/capture/native-environment-tabulated.js.map +1 -1
  17. package/dist/capture/native-raw.d.ts +3 -2
  18. package/dist/capture/native-raw.js +5 -4
  19. package/dist/capture/native-raw.js.map +1 -1
  20. package/dist/capture/sensor-raw-producer.d.ts +4 -1
  21. package/dist/capture/sensor-raw-producer.js +12 -3
  22. package/dist/capture/sensor-raw-producer.js.map +1 -1
  23. package/dist/core/version.d.ts +1 -1
  24. package/dist/core/version.js +1 -1
  25. package/dist/core/version.js.map +1 -1
  26. package/dist/equipment/generic-lens-catalogue.d.ts +3 -2
  27. package/dist/equipment/generic-lens-catalogue.js +18 -5
  28. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  29. package/dist/equipment/generic-tier-assets.d.ts +32 -0
  30. package/dist/equipment/generic-tier-assets.js +16 -11
  31. package/dist/equipment/generic-tier-assets.js.map +1 -1
  32. package/dist/equipment/sensor-profile-body.d.ts +78 -0
  33. package/dist/equipment/sensor-profile-body.js +77 -0
  34. package/dist/equipment/sensor-profile-body.js.map +1 -0
  35. package/dist/equipment/sensor-profiles.d.ts +8 -0
  36. package/dist/equipment/sensor-profiles.js +82 -0
  37. package/dist/equipment/sensor-profiles.js.map +1 -0
  38. package/dist/facade/catalogue-lens-signal.d.ts +2 -2
  39. package/dist/facade/catalogue-lens-signal.js +58 -71
  40. package/dist/facade/catalogue-lens-signal.js.map +1 -1
  41. package/dist/facade/chart-radiance-batch.js +2 -1
  42. package/dist/facade/chart-radiance-batch.js.map +1 -1
  43. package/dist/facade/defocus-image-space-provider.d.ts +7 -0
  44. package/dist/facade/defocus-image-space-provider.js +64 -76
  45. package/dist/facade/defocus-image-space-provider.js.map +1 -1
  46. package/dist/facade/display-view-gpu-session.js +2 -0
  47. package/dist/facade/display-view-gpu-session.js.map +1 -1
  48. package/dist/facade/display-view-gpu.d.ts +6 -1
  49. package/dist/facade/display-view-gpu.js.map +1 -1
  50. package/dist/facade/display-view-lens-signal.js +25 -23
  51. package/dist/facade/display-view-lens-signal.js.map +1 -1
  52. package/dist/facade/display-view.js +5 -23
  53. package/dist/facade/display-view.js.map +1 -1
  54. package/dist/facade/expected-signal-setup.d.ts +3 -1
  55. package/dist/facade/expected-signal-setup.js +1 -1
  56. package/dist/facade/expected-signal-setup.js.map +1 -1
  57. package/dist/facade/expected-signal.d.ts +47 -14
  58. package/dist/facade/expected-signal.js +117 -112
  59. package/dist/facade/expected-signal.js.map +1 -1
  60. package/dist/facade/exposure-build-up.d.ts +70 -0
  61. package/dist/facade/exposure-build-up.js +164 -0
  62. package/dist/facade/exposure-build-up.js.map +1 -0
  63. package/dist/facade/fast-methods.js +9 -1
  64. package/dist/facade/fast-methods.js.map +1 -1
  65. package/dist/facade/generic-lens-capture.d.ts +2 -2
  66. package/dist/facade/generic-lens-capture.js +2 -2
  67. package/dist/facade/generic-lens-capture.js.map +1 -1
  68. package/dist/facade/image-plane-enable.d.ts +7 -0
  69. package/dist/facade/image-plane-enable.js +17 -0
  70. package/dist/facade/image-plane-enable.js.map +1 -0
  71. package/dist/facade/image-plane-lattice-data.d.ts +6 -0
  72. package/dist/facade/image-plane-lattice-data.js +10 -0
  73. package/dist/facade/image-plane-lattice-data.js.map +1 -0
  74. package/dist/facade/image-plane-support.d.ts +33 -0
  75. package/dist/facade/image-plane-support.js +11 -0
  76. package/dist/facade/image-plane-support.js.map +1 -0
  77. package/dist/facade/image-plane.d.ts +36 -0
  78. package/dist/facade/image-plane.js +353 -0
  79. package/dist/facade/image-plane.js.map +1 -0
  80. package/dist/facade/mixed-illumination.d.ts +108 -0
  81. package/dist/facade/mixed-illumination.js +174 -0
  82. package/dist/facade/mixed-illumination.js.map +1 -0
  83. package/dist/facade/representative-light-sources.d.ts +72 -0
  84. package/dist/facade/representative-light-sources.js +124 -0
  85. package/dist/facade/representative-light-sources.js.map +1 -0
  86. package/dist/facade/scene-radiance.d.ts +129 -4
  87. package/dist/facade/scene-radiance.js +48 -13
  88. package/dist/facade/scene-radiance.js.map +1 -1
  89. package/dist/facade/simulate-capture.d.ts +39 -10
  90. package/dist/facade/simulate-capture.js +252 -92
  91. package/dist/facade/simulate-capture.js.map +1 -1
  92. package/dist/facade/simulate-display-view.js +14 -6
  93. package/dist/facade/simulate-display-view.js.map +1 -1
  94. package/dist/gpu/chart-composite-gpu.d.ts +13 -1
  95. package/dist/gpu/chart-composite-gpu.js +48 -9
  96. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  97. package/dist/gpu/chart-radiance-gpu.js +4 -30
  98. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  99. package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
  100. package/dist/gpu/display-view-composite-material-module.js +39 -13
  101. package/dist/gpu/display-view-composite-material-module.js.map +1 -1
  102. package/dist/gpu/display-view-image-material-module.d.ts +86 -0
  103. package/dist/gpu/display-view-image-material-module.js +339 -0
  104. package/dist/gpu/display-view-image-material-module.js.map +1 -0
  105. package/dist/gpu/display-view-image-quality.d.ts +45 -0
  106. package/dist/gpu/display-view-image-quality.js +380 -0
  107. package/dist/gpu/display-view-image-quality.js.map +1 -0
  108. package/dist/gpu/display-view-quality-gpu.d.ts +67 -1
  109. package/dist/gpu/display-view-quality-gpu.js +136 -205
  110. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  111. package/dist/gpu/display-view-speed-gpu.d.ts +51 -3
  112. package/dist/gpu/display-view-speed-gpu.js +131 -93
  113. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  114. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +29 -15
  115. package/dist/gpu/wgsl-display-view-quality-kernel.js +58 -36
  116. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  117. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +21 -6
  118. package/dist/gpu/wgsl-display-view-speed-kernel.js +46 -11
  119. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  120. package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
  121. package/dist/gpu/wgsl-exact-sum.js +42 -0
  122. package/dist/gpu/wgsl-exact-sum.js.map +1 -0
  123. package/dist/index.d.ts +11 -3
  124. package/dist/index.js +7 -1
  125. package/dist/index.js.map +1 -1
  126. package/dist/motion/subject-motion.d.ts +66 -0
  127. package/dist/motion/subject-motion.js +52 -0
  128. package/dist/motion/subject-motion.js.map +1 -0
  129. package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
  130. package/dist/optics/generic-lens-lateral-color.js +77 -0
  131. package/dist/optics/generic-lens-lateral-color.js.map +1 -0
  132. package/dist/optics/generic-lens-psf.js +54 -46
  133. package/dist/optics/generic-lens-psf.js.map +1 -1
  134. package/dist/optics/lens-psf-profile.d.ts +7 -0
  135. package/dist/optics/lens-psf-profile.js +2 -1
  136. package/dist/optics/lens-psf-profile.js.map +1 -1
  137. package/dist/output/developed-lens-corrections.d.ts +19 -5
  138. package/dist/output/developed-lens-corrections.js +28 -9
  139. package/dist/output/developed-lens-corrections.js.map +1 -1
  140. package/dist/output/generic-lens-corrections.d.ts +41 -5
  141. package/dist/output/generic-lens-corrections.js +52 -8
  142. package/dist/output/generic-lens-corrections.js.map +1 -1
  143. package/dist/output/print-plan.d.ts +41 -5
  144. package/dist/output/print-plan.js +59 -14
  145. package/dist/output/print-plan.js.map +1 -1
  146. package/dist/sensor/binomial-sample.d.ts +3 -0
  147. package/dist/sensor/binomial-sample.js +86 -0
  148. package/dist/sensor/binomial-sample.js.map +1 -0
  149. package/dist/sensor/sensor-characteristics.d.ts +10 -0
  150. package/dist/sensor/sensor-characteristics.js +20 -9
  151. package/dist/sensor/sensor-characteristics.js.map +1 -1
  152. package/docs/AGENT_GUIDE.md +1 -0
  153. package/docs/API_REFERENCE.md +98 -1
  154. package/docs/QUICK_START.md +125 -0
  155. package/docs/USAGE.md +92 -1
  156. package/docs/api/core-version.md +1 -1
  157. package/docs/api/equipment-generic-lens-catalogue.md +10 -5
  158. package/docs/api/equipment-sensor-profile-body.md +64 -0
  159. package/docs/api/equipment-sensor-profiles.md +86 -0
  160. package/docs/api/exports.json +207 -2
  161. package/docs/api/facade-exposure-build-up.md +105 -0
  162. package/docs/api/facade-fast-methods.md +9 -1
  163. package/docs/api/facade-image-plane-enable.md +16 -0
  164. package/docs/api/facade-mixed-illumination.md +67 -0
  165. package/docs/api/facade-representative-light-sources.md +131 -0
  166. package/docs/api/facade-scene-radiance.md +54 -3
  167. package/docs/api/facade-simulate-capture.md +26 -6
  168. package/docs/api/motion-subject-motion.md +23 -0
  169. package/docs/api/optics-generic-lens-lateral-color.md +79 -0
  170. package/docs/api/output-developed-lens-corrections.md +10 -1
  171. package/docs/api/output-generic-lens-corrections.md +40 -4
  172. package/docs/api/output-print-plan.md +35 -1
  173. package/llms.txt +2 -0
  174. package/package.json +1 -1
@@ -0,0 +1,131 @@
1
+ # facade/representative-light-sources.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
+ ## createRepresentativeLightSourceIlluminant
6
+
7
+ A capture illuminant for a source: `{ kind: "spectrum", spectrum, label }` with the spectrum of
8
+ {@link createRepresentativeLightSourceSpectrum} and the label "<name> (representative, not a named product)".
9
+
10
+ ```ts
11
+ export function createRepresentativeLightSourceIlluminant(input: { sourceId: RepresentativeLightSourceId }): Extract<CaptureIlluminant, { kind: "spectrum" }>;
12
+ ```
13
+
14
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
15
+
16
+ ## createRepresentativeLightSourceSpectrum
17
+
18
+ A source's relative spectral power distribution S(λ) = b + Σ aₖ·exp(−½((λ − μₖ)/σₖ)²) as a function spectrum on
19
+ 360–830 nm (relative units; values memoized per wavelength). Pass it to a capture as
20
+ `illuminant: { kind: "spectrum", spectrum }`; for color work use `sensor.spectralStepNanometers: 5` or finer
21
+ (docs/LIGHT_SOURCES.md, "Quadrature"). Throws `InvalidScientificInputError` outside 360–830 nm.
22
+
23
+ ```ts
24
+ export function createRepresentativeLightSourceSpectrum(input: { sourceId: RepresentativeLightSourceId }): ColorimetricSpectrum;
25
+ ```
26
+
27
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
28
+
29
+ ## getRepresentativeLightSourceSet
30
+
31
+ The built-in representative light sources: Photivra's own parametric spectra of a tri-phosphor fluorescent tube and
32
+ three white LEDs (low and high color rendering at 4000 K, warm at 2700 K). A static, deeply frozen engine contract.
33
+ Each is "representative, not a named product"; docs/LIGHT_SOURCES.md gives every formula and the reasons for it.
34
+
35
+ ```ts
36
+ export function getRepresentativeLightSourceSet(): RepresentativeLightSourceSet;
37
+ ```
38
+
39
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
40
+
41
+ ## REPRESENTATIVE_LIGHT_SOURCE_IDS
42
+
43
+ The source ids, in set order.
44
+
45
+ ```ts
46
+ REPRESENTATIVE_LIGHT_SOURCE_IDS = Object.freeze([
47
+ "tri-phosphor-fluorescent-4000k", "white-led-low-rendering-4000k", "white-led-high-rendering-4000k", "warm-white-led-2700k"
48
+ ] as const)
49
+ ```
50
+
51
+ ## REPRESENTATIVE_LIGHT_SOURCE_SET_ID
52
+
53
+ Identity of the built-in light-source set.
54
+
55
+ ```ts
56
+ REPRESENTATIVE_LIGHT_SOURCE_SET_ID = "photivra-representative-light-sources"
57
+ ```
58
+
59
+ ## REPRESENTATIVE_LIGHT_SOURCE_SET_VERSION
60
+
61
+ Version of the set's spectra; a change to any spectrum changes it.
62
+
63
+ ```ts
64
+ REPRESENTATIVE_LIGHT_SOURCE_SET_VERSION = "1.0.0"
65
+ ```
66
+
67
+ ## REPRESENTATIVE_LIGHT_SOURCE_WAVELENGTH_RANGE_NANOMETERS
68
+
69
+ Domain of every spectrum, nm (the CIE 1931 observer's domain; the capture facade uses 380–780 nm of it).
70
+
71
+ ```ts
72
+ REPRESENTATIVE_LIGHT_SOURCE_WAVELENGTH_RANGE_NANOMETERS = Object.freeze({ minimum: 360, maximum: 830 })
73
+ ```
74
+
75
+ ## RepresentativeLightSource
76
+
77
+ One built-in source: its id, name, design reason and spectrum definition.
78
+
79
+ ```ts
80
+ export interface RepresentativeLightSource {
81
+ sourceId: RepresentativeLightSourceId;
82
+ /** Photivra's descriptive name, for example "White LED, high color rendering, 4000 K". */
83
+ name: string;
84
+ family: "fluorescent" | "led";
85
+ /** The CCT the spectrum was designed for, K (its computed CCT is within 1 K of it, Duv within 0.0001; docs/LIGHT_SOURCES.md). */
86
+ nominalCorrelatedColorTemperatureKelvin: number;
87
+ /** Always "representative, not a named product". */
88
+ label: "representative, not a named product";
89
+ /** One sentence: what the source represents and why it is built this way. */
90
+ reason: string;
91
+ /** The constant b (a low continuum), relative units. */
92
+ base: number;
93
+ bands: readonly RepresentativeLightSourceBand[];
94
+ }
95
+ ```
96
+
97
+ ## RepresentativeLightSourceBand
98
+
99
+ One Gaussian band a·exp(−½((λ − μ)/σ)²) of a source spectrum.
100
+
101
+ ```ts
102
+ export interface RepresentativeLightSourceBand {
103
+ kind: "gaussian";
104
+ /** What the band represents, for example "blue pump" or "green phosphor line". */
105
+ role: string;
106
+ amplitude: number;
107
+ centerNanometers: number;
108
+ standardDeviationNanometers: number;
109
+ }
110
+ ```
111
+
112
+ ## RepresentativeLightSourceId
113
+
114
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
115
+
116
+ ```ts
117
+ export type RepresentativeLightSourceId = (typeof REPRESENTATIVE_LIGHT_SOURCE_IDS)[number];
118
+ ```
119
+
120
+ ## RepresentativeLightSourceSet
121
+
122
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
123
+
124
+ ```ts
125
+ export interface RepresentativeLightSourceSet {
126
+ setId: typeof REPRESENTATIVE_LIGHT_SOURCE_SET_ID;
127
+ setVersion: typeof REPRESENTATIVE_LIGHT_SOURCE_SET_VERSION;
128
+ wavelengthRangeNanometers: { readonly minimum: number; readonly maximum: number };
129
+ sources: readonly RepresentativeLightSource[];
130
+ }
131
+ ```
@@ -91,7 +91,8 @@ provider `providerProfileId`.
91
91
 
92
92
  ```ts
93
93
  export function createPlanarSceneRadianceProvider(input: {
94
- scene: ChartScene | ChartMaterialLayerScene | CallerReflectanceScene | CallerRadianceScene | CallerMaterialScene;
94
+ /** An `image-plane` scene (#581) is refused: it is taken by the facade's capture and display-view functions, which load its module. */
95
+ scene: ChartScene | ChartMaterialLayerScene | CallerReflectanceScene | CallerRadianceScene | CallerMaterialScene | ImagePlaneScene;
95
96
  illuminant: CaptureIlluminant;
96
97
  illuminanceLux: number;
97
98
  distanceM: number;
@@ -110,6 +111,47 @@ export function createPlanarSceneRadianceProvider(input: {
110
111
 
111
112
  This call is synchronous; any supplied provider must follow its explicit synchronous contract.
112
113
 
114
+ ## ImagePlaneImage
115
+
116
+ 2.21 (#581): an image the caller supplies, for an {@link ImagePlaneScene}. `data` holds `width × height` pixels row by row
117
+ from the top, `channels` (3: RGB, 4: RGBA) samples each, in the encoding's type: `srgb-8` Uint8Array or Uint8ClampedArray
118
+ (canvas `ImageData.data`), `srgb-16` Uint16Array, `linear-srgb` Float32Array. sRGB primaries and white (IEC 61966-2-1) in
119
+ every encoding; `srgb-8` and `srgb-16` are decoded with the sRGB transfer function, `linear-srgb` is already linear and is
120
+ clipped to [0, 1] (the clipped samples are counted). Alpha is linear coverage over the paper. The array is read, never
121
+ changed or kept beyond the call.
122
+
123
+ ```ts
124
+ export interface ImagePlaneImage {
125
+ width: number;
126
+ height: number;
127
+ channels: 3 | 4;
128
+ encoding: "srgb-8" | "srgb-16" | "linear-srgb";
129
+ data: Uint8Array | Uint8ClampedArray | Uint16Array | Float32Array;
130
+ }
131
+ ```
132
+
133
+ ## ImagePlaneScene
134
+
135
+ 2.21 (#581): a photo as a flat matte print on the scene plane, lit by the request's illuminant like a chart. Each pixel's
136
+ color becomes a reflectance spectrum through the engine's print reflectance lattice (docs/IMAGE_PLANE.md): an
137
+ approximation, a print of an image that was already processed, clipped and blurred by the camera that took it. The print's
138
+ physical size is `chartSize` (its height defaults to the image's aspect); without it the print is fitted inside the frame,
139
+ centered, on the 18 % surround.
140
+
141
+ ```ts
142
+ export interface ImagePlaneScene {
143
+ kind: "image-plane";
144
+ sceneId: string;
145
+ image: ImagePlaneImage;
146
+ /** The paper and the deepest ink: reflectances of sRGB white and black, 0 ≤ black < white ≤ 1. Defaults 0.90 and 0.01. */
147
+ paper?: { whiteReflectance?: number; blackReflectance?: number };
148
+ /** How the print is reconstructed between pixel centers. Default `bilinear` (the pixels' spectra, bilinearly weighted). */
149
+ interpolation?: "bilinear" | "nearest";
150
+ /** Opt-in raised pixel limit: an integer up to the full-resolution ceiling. Default 24 000 000 pixels. */
151
+ maximumPixels?: number;
152
+ }
153
+ ```
154
+
113
155
  ## PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W
114
156
 
115
157
  CIE maximum luminous efficacy for photopic vision, lm/W.
@@ -128,8 +170,11 @@ export interface PlanarSceneRadianceProvider {
128
170
  providerProfileId: string;
129
171
  evaluateRadiance(request: Readonly<SceneRadianceEvaluationRequest>): SceneRadianceEvaluationResult;
130
172
  evaluateApertureRadiance(request: Readonly<SceneRadianceEvaluationRequest>, ray: Readonly<SensorApertureRay>): SceneRadianceEvaluationResult;
131
- /** Spectral radiance at a plane point (frame units), the same value the callbacks return. */
132
- radianceAt(u: number, v: number, wavelengthNanometers: number): number;
173
+ /**
174
+ * Spectral radiance at a plane point (frame units), the same value the callbacks return. #551: a moving subject's provider
175
+ * reads it at time `t` (seconds from the first opening, default 0); a still scene ignores it.
176
+ */
177
+ radianceAt(u: number, v: number, wavelengthNanometers: number, t?: number): number;
133
178
  /**
134
179
  * 2.3, chart scenes only: the chart material at a plane point (frame units), or `materials.length` for the
135
180
  * surround of a fixed-size chart; `materialRadiance` gives that material's spectral radiance, the same value
@@ -137,6 +182,12 @@ export interface PlanarSceneRadianceProvider {
137
182
  */
138
183
  chartMaterialAt?(u: number, v: number): number;
139
184
  materialRadiance?(material: number, wavelengthNanometers: number): number;
185
+ /**
186
+ * 2.21 (#581), image-plane scenes only: the tabulated expected signal's scene at these spectral nodes and channel responses, its
187
+ * radiance per node and, for the direct path, Σ_n A_c(λ_n)·L(λ_n) integrated from each pixel's reconstructed spectrum
188
+ * (`ResolvedImagePlaneScene.pixelSignals`, the surround's reflectance times the neutral signal outside the print).
189
+ */
190
+ imageSignalScene?(wavelengths: readonly number[], perNode: readonly Float64Array[]): ImagePlaneSignalScene;
140
191
  }
141
192
  ```
142
193
 
@@ -144,7 +144,7 @@ a capture does. `motion.rotation` is a non-zero angular velocity; `sensor.tempor
144
144
 
145
145
  ```ts
146
146
  SIMULATE_CAPTURE_COST_OPTIONS = Object.freeze(["lens.pupilSampling", "lens.projection", "lens.distortion", "lens.vignetting", "lens.psf",
147
- "lens.diffraction", "lens.defocus", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "sensor.temporalSampleCount", "motion.rotation", "motion.shutter.rolling", "output.borderHandling.mirror", "output.region",
147
+ "lens.diffraction", "lens.defocus", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "sensor.temporalSampleCount", "motion.rotation", "motion.subject", "motion.shutter.rolling", "output.borderHandling.mirror", "output.region",
148
148
  "output.whiteBalance.auto", "output.demosaic.ahd", "output.highlightMode.reconstruct", "output.colorEncoding.wide-gamut", "output.gamutMapping.perceptual",
149
149
  "output.diffractionCorrection", "output.sharpening", "output.lensCorrections", "output.format.jpeg", "output.format.png", "output.format.dng"] as const)
150
150
  ```
@@ -175,7 +175,7 @@ the record form with `fast` and is reported in `diagnostics.fast.applied` when i
175
175
  whether or not it has a fast method yet; one without runs exact and reports `false`.
176
176
 
177
177
  ```ts
178
- SIMULATE_CAPTURE_FAST_OPTIONS = Object.freeze(["lens.diffraction", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd"] as const)
178
+ SIMULATE_CAPTURE_FAST_OPTIONS = Object.freeze(["lens.diffraction", "lens.catalogue", "sensor.antiAliasing", "sensor.pixelAperture.box", "output.demosaic.ahd", "motion.subject"] as const)
179
179
  ```
180
180
 
181
181
  ## SIMULATE_CAPTURE_METERING_MODES
@@ -473,8 +473,9 @@ export interface SimulateCaptureFastReport {
473
473
 
474
474
  `lens.catalogue` as a record (#568): the lens id as `kind` and the `fast` override (see {@link SimulateCaptureOptions.fast}).
475
475
  Fast: the tabulated fast method (#568), and with `lens.defocus` the lens's bokeh without its cat's eye (#567; fast wides and
476
- normals only), whose field-varying PSF needs a finer fast grid (measured 2.2–3.4× slower). Exact: the per-site PSF with the
477
- cat's eye. Blades and spherical aberration always apply (they cost nothing).
476
+ normals only), whose field-varying PSF needs a finer fast grid (measured 2.2–3.4× slower); with camera rotation under
477
+ `motion.shutter: "rolling"`, the per-tap PSF points instead of the lattice reading (#588). Exact: the per-site PSF with the
478
+ cat's eye, read on the lattice. Blades and spherical aberration always apply (they cost nothing).
478
479
 
479
480
  ```ts
480
481
  export interface SimulateCaptureLensCatalogueRecord { kind: string; fast?: boolean }
@@ -529,6 +530,11 @@ export interface SimulateCaptureMotion {
529
530
  shutter?: "global" | "rolling";
530
531
  /** Rolling shutter only: time from the first to the last line over the simulated frame, in s. Default: the body's declared readout skew. */
531
532
  rollingShutterSkewSeconds?: number;
533
+ /**
534
+ * #551: a moving subject: the whole scene, or one rectangle of it over a still background, translating on the scene plane
535
+ * (and optionally rotating about the optical axis) during the exposure. Off by default (a still scene).
536
+ */
537
+ subject?: SimulateCaptureSubjectMotion;
532
538
  }
533
539
  ```
534
540
 
@@ -672,9 +678,10 @@ export interface SimulateCaptureOptions {
672
678
  /**
673
679
  * Required: an engine chart (for example `{ preset: "uniform" }`, an 18 % grey card), a declared `material-layers` scene (#491:
674
680
  * a chart under shape-mask layers, as `resolveChartMaterialLayers` takes it; the capture is that of its `callerScene`, byte for
675
- * byte, and the display view can render it on the GPU), or a caller reflectance, radiance or material function.
681
+ * byte, and the display view can render it on the GPU), a caller reflectance, radiance or material function, or (2.21, #581) an
682
+ * `image-plane` scene: a photo as a flat print (docs/IMAGE_PLANE.md).
676
683
  */
677
- scene: ChartScene | ChartMaterialLayerScene | CallerReflectanceScene | CallerRadianceScene | CallerMaterialScene;
684
+ scene: ChartScene | ChartMaterialLayerScene | CallerReflectanceScene | CallerRadianceScene | CallerMaterialScene | ImagePlaneScene;
678
685
  /** Distance from the camera to the scene plane in metres. Default 5. */
679
686
  subjectDistanceM?: number;
680
687
  /**
@@ -689,6 +696,12 @@ export interface SimulateCaptureOptions {
689
696
  illuminant?: CaptureIlluminant;
690
697
  /** Illuminance on the scene plane, lux. Default about 21 481 lux (see `SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX` and the assumptions). */
691
698
  illuminanceLux?: number;
699
+ /**
700
+ * 2.22 (#550), opt-in mixed lighting: two or more sources blended across the chart (docs/MIXED_ILLUMINATION.md), instead of
701
+ * `illuminant` and `illuminanceLux`. The frame center's light sets the grey-card meter and the standard-exposure figures, and
702
+ * its strongest source the "scene-illuminant" white balance; both are reported in `assumptions`.
703
+ */
704
+ illumination?: MixedIllumination;
692
705
  output?: {
693
706
  /** Output pixels; the native sensor raster adds a one-site demosaic border on each side. Defaults 64 × 64. */
694
707
  width?: number;
@@ -788,6 +801,13 @@ export interface SimulateCaptureOptions {
788
801
  motion?: SimulateCaptureMotion;
789
802
  /** #555: plus the sensor characteristics ({@link SimulateCaptureSensorCharacteristics}), each off by default. */
790
803
  sensor?: SimulateCaptureSensorCharacteristics & {
804
+ /**
805
+ * #554: a sensor profile record (`SENSOR_PROFILES`, or `resolveSensorProfile(id)`), checked strictly. It replaces the body's ISO range, readout regimes
806
+ * (full well, read noise of each conversion gain, the dual-gain switch, high-gain storage, ADC bit depth), readout time and
807
+ * fixed-pattern noise, and sets the default pixel pitch and peak QE; explicit fields here still override it one at a time.
808
+ * Default: none (the body's own sensor). See docs/SENSOR_PROFILES.md.
809
+ */
810
+ profile?: SensorProfile;
791
811
  /**
792
812
  * Pixel pitch in micrometres, 0.5–1000. Default 6. The simulated sensor is output width × pitch wide. 2.5: above 100 µm the
793
813
  * capture is a downsized view of a whole frame (for example 36 mm / 48 pixels = 750 µm), recorded in the assumption
@@ -0,0 +1,23 @@
1
+ # motion/subject-motion.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
+ ## SimulateCaptureSubjectMotion
6
+
7
+ `motion.subject` of `simulateCapture`: a moving subject on the scene plane (#551).
8
+
9
+ ```ts
10
+ export interface SimulateCaptureSubjectMotion {
11
+ /** Velocity on the scene plane, m/s: +x right, +y up as seen from the camera. Missing components are 0. */
12
+ velocityMPerSec?: { x?: number; y?: number };
13
+ /** Rotation about the optical axis direction, rad/s, counter-clockwise as seen from the camera (+x toward +y). Default 0. */
14
+ angularVelocityRadPerSec?: number;
15
+ /**
16
+ * The moving part: a rectangle on the scene plane at rest (at the first opening reference), its centre and size in metres
17
+ * (+x right, +y up from the optical axis). Omitted: the whole scene moves.
18
+ */
19
+ region?: { centerXM: number; centerYM: number; widthM: number; heightM: number };
20
+ /** Exact or fast (2.20.0 convention): fast takes fewer temporal midpoints when the subject's streak is short (see the docs). */
21
+ fast?: boolean;
22
+ }
23
+ ```
@@ -0,0 +1,79 @@
1
+ # optics/generic-lens-lateral-color.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
+ ## calculateLateralColorFringe
6
+
7
+ The red–blue image displacement of a catalogue lens at a field position on a sensor, in micrometres and pixels, from the
8
+ lens's own lateral-colour model Δr(λ) = L·R·H·D(λ) at the lens corrections' channel wavelengths (610 and 465 nm), or at the
9
+ given `channelDispersion`. Exact for the model (no fast mode); an approximation of a generic lens, not a measured product.
10
+
11
+ ```ts
12
+ export function calculateLateralColorFringe(input: CalculateLateralColorFringeInput): CalculationResult<LateralColorFringe>;
13
+ ```
14
+
15
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
16
+
17
+ ## CalculateLateralColorFringeInput
18
+
19
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
20
+
21
+ ```ts
22
+ export interface CalculateLateralColorFringeInput {
23
+ /** A catalogue id (`GENERIC_LENS_IDS`). */
24
+ lensId: string;
25
+ /** The sensor's active capture area, mm (the lens is scaled to it as in `resolveGenericLens`). */
26
+ activeImagingArea: { widthMm: number; heightMm: number };
27
+ /** The sensor's pixel pitch, µm. */
28
+ pixelPitchMicrometers: number;
29
+ /**
30
+ * The field position as fractions of the frame's half-width (x, +right) and half-height (y, +up) from the optical axis, each
31
+ * in [−1, 1]. Default `{ x: 1, y: 1 }`: the top-right corner.
32
+ */
33
+ fieldPosition?: { x: number; y: number };
34
+ /**
35
+ * 2.21.0: each channel's effective dispersion (`calculateChannelEffectiveDispersion` for the camera's channels and the light;
36
+ * only `red` and `blue` are read). Default: D at the single wavelengths 610 and 465 nm.
37
+ */
38
+ channelDispersion?: ChannelDispersion;
39
+ }
40
+ ```
41
+
42
+ ## LateralColorDisplacement
43
+
44
+ A signed displacement of the blue image from the red one: radial (positive: blue farther from the axis) and its x and y parts.
45
+
46
+ ```ts
47
+ export interface LateralColorDisplacement {
48
+ radial: number;
49
+ x: number;
50
+ y: number;
51
+ }
52
+ ```
53
+
54
+ ## LateralColorFringe
55
+
56
+ The exact contract is declared below; use the domain guide for assumptions and staged integration.
57
+
58
+ ```ts
59
+ export interface LateralColorFringe {
60
+ lensId: string;
61
+ /** The image-plane point, mm from the optical axis (+x right, +y up). */
62
+ imagePointMm: { x: number; y: number };
63
+ /** Field height H: the point's distance from the axis as a fraction of the half-diagonal (1 at the corner). */
64
+ fieldHeight: number;
65
+ /** The channel wavelengths the displacement is taken at, nm; null with a given `channelDispersion`. */
66
+ channelWavelengthsNm: { red: number; blue: number } | null;
67
+ /** The red and blue dispersions D used (normalized: 0 at the d line, F minus C = 1). */
68
+ channelDispersion: { red: number; blue: number };
69
+ /** Blue-minus-red image displacement, µm. */
70
+ blueMinusRedMicrometers: LateralColorDisplacement;
71
+ /** The same in pixels of the given pitch. */
72
+ blueMinusRedPixels: LateralColorDisplacement;
73
+ /** The fringe width: |radial| displacement, µm and pixels. */
74
+ fringeWidthMicrometers: number;
75
+ fringeWidthPixels: number;
76
+ /** The lens's own F-to-C (486 to 656 nm) displacement at this point, L·R·H, µm. */
77
+ fToCMicrometers: number;
78
+ }
79
+ ```
@@ -2,10 +2,19 @@
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
+ ## ChannelDispersion
6
+
7
+ A normalized dispersion per channel (`genericLensDispersion` units: 0 at the d line, F minus C = 1).
8
+
9
+ ```ts
10
+ export type ChannelDispersion = Readonly<Record<LensCorrectionChannel, number>>;
11
+ ```
12
+
5
13
  ## GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM
6
14
 
7
15
  Effective wavelengths of the red, green and blue channels for the lateral-colour map, nm: a generic approximation of where
8
- a camera's channel responses peak, not a measured camera.
16
+ a camera's channel responses peak, not a measured camera. The default of `createGenericLensCorrectionProfile` without a
17
+ `channelDispersion`; `simulateCapture`'s `output.lensCorrections` uses the capture's own channels instead (#589).
9
18
 
10
19
  ```ts
11
20
  GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM: Readonly<Record<LensCorrectionChannel, number>> = Object.freeze({ red: 610, green: 540, blue: 465 })
@@ -2,13 +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). From profile version 1.1.0 (#584) the vignetting component
10
- (version 1.1.0) carries the distortion's area factor (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's
11
- sensor point, so it flattens a flat-field capture. The result passes `parseGenericLensCorrectionProfile`.
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`.
12
43
 
13
44
  ```ts
14
45
  export function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile;
@@ -28,5 +59,10 @@ export interface CreateGenericLensCorrectionProfileInput {
28
59
  activeImagingArea: { widthMm: number; heightMm: number };
29
60
  /** The exact camera state the profile applies to: its focal length must be the lens's on this sensor, its aperture in range. */
30
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;
31
67
  }
32
68
  ```
@@ -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
 
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@photivra/engine",
3
- "version": "2.20.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",