@photivra/engine 2.2.0 → 2.3.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 (47) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/dist/capture/native-environment-raw.d.ts +18 -0
  6. package/dist/capture/native-environment-raw.js +17 -1
  7. package/dist/capture/native-environment-raw.js.map +1 -1
  8. package/dist/capture/native-raw.d.ts +3 -0
  9. package/dist/capture/native-raw.js +8 -5
  10. package/dist/capture/native-raw.js.map +1 -1
  11. package/dist/core/version.d.ts +1 -1
  12. package/dist/core/version.js +1 -1
  13. package/dist/core/version.js.map +1 -1
  14. package/dist/equipment/generic-prime-lenses.d.ts +46 -0
  15. package/dist/equipment/generic-prime-lenses.js +59 -0
  16. package/dist/equipment/generic-prime-lenses.js.map +1 -0
  17. package/dist/facade/chart-scenes.d.ts +270 -1
  18. package/dist/facade/chart-scenes.js +443 -17
  19. package/dist/facade/chart-scenes.js.map +1 -1
  20. package/dist/facade/scene-radiance.d.ts +47 -0
  21. package/dist/facade/scene-radiance.js +65 -5
  22. package/dist/facade/scene-radiance.js.map +1 -1
  23. package/dist/facade/simulate-capture.d.ts +152 -5
  24. package/dist/facade/simulate-capture.js +302 -57
  25. package/dist/facade/simulate-capture.js.map +1 -1
  26. package/dist/facade/subject-framing.d.ts +55 -0
  27. package/dist/facade/subject-framing.js +66 -0
  28. package/dist/facade/subject-framing.js.map +1 -0
  29. package/dist/gpu/chart-radiance-gpu.d.ts +8 -0
  30. package/dist/gpu/chart-radiance-gpu.js +20 -1
  31. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  32. package/dist/index.d.ts +5 -3
  33. package/dist/index.js +5 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/optics/lens-psf-kernels.d.ts +3 -1
  36. package/dist/optics/lens-psf-kernels.js +7 -2
  37. package/dist/optics/lens-psf-kernels.js.map +1 -1
  38. package/docs/API_REFERENCE.md +48 -1
  39. package/docs/QUICK_START.md +269 -4
  40. package/docs/api/core-version.md +1 -1
  41. package/docs/api/equipment-generic-prime-lenses.md +85 -0
  42. package/docs/api/exports.json +167 -2
  43. package/docs/api/facade-chart-scenes.md +280 -4
  44. package/docs/api/facade-scene-radiance.md +42 -0
  45. package/docs/api/facade-simulate-capture.md +108 -3
  46. package/docs/api/facade-subject-framing.md +72 -0
  47. package/package.json +1 -1
@@ -2,6 +2,37 @@
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
+ ## BarGroupsChartScene
6
+
7
+ 2.3: groups of three dark bars on a light plate (the three-bar concept of the US Air Force 1951 target,
8
+ a US-government publication, in the engine's own arrangement): one group per frequency, vertical bars (horizontal resolution)
9
+ in the upper row and horizontal bars (vertical resolution) below, optionally labelled with engine-drawn
10
+ seven-segment numerals. Frequencies are line widths per picture height (LW/PH, as in ISO 12233), where
11
+ the picture height is the chart's height: one bar is PH/LW wide.
12
+
13
+ ```ts
14
+ export interface BarGroupsChartScene {
15
+ preset: "bar-groups";
16
+ /** One group per entry, left to right: integers 1..100000. Default [100, 200, 400, 800]. 1..32 entries. */
17
+ lineWidthsPerPictureHeight?: readonly number[];
18
+ /** Draw each group's LW/PH value below it. Default true. */
19
+ numerals?: boolean;
20
+ darkReflectance?: number;
21
+ lightReflectance?: number;
22
+ }
23
+ ```
24
+
25
+ ## calculateBarGroupTargets
26
+
27
+ The bar groups of a `bar-groups` chart, or of every `bar-groups` element of a composite, in the chart's
28
+ frame units (2.3). `aspect` is the chart's height / width, as for {@link resolveChartScene}.
29
+
30
+ ```ts
31
+ export function calculateBarGroupTargets(input: { scene: ChartScene; aspect: number }): ChartBarGroupTarget[];
32
+ ```
33
+
34
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
35
+
5
36
  ## calculatePointGridCenters
6
37
 
7
38
  Centres (frame units) of a point grid: one disk centred in each of columns × rows equal cells, row-major from the top-left.
@@ -12,6 +43,18 @@ export function calculatePointGridCenters(input: { columns: number; rows: number
12
43
 
13
44
  This call is synchronous; any supplied provider must follow its explicit synchronous contract.
14
45
 
46
+ ## calculateSlantedSquareEdges
47
+
48
+ Squares and edges of a `slanted-squares` chart, or of every `slanted-squares` element of a composite, in
49
+ the chart's frame units (2.3), for slanted-edge regions of interest (Burns, PICS 2000). Directions are unit
50
+ vectors along each edge (u right, v up).
51
+
52
+ ```ts
53
+ export function calculateSlantedSquareEdges(input: { scene: ChartScene; aspect: number }): ChartSlantedSquare[];
54
+ ```
55
+
56
+ This call is synchronous; any supplied provider must follow its explicit synchronous contract.
57
+
15
58
  ## calculateStepWedgeReflectances
16
59
 
17
60
  Step reflectances of a step wedge. `equal-density` spaces optical density D = -log10(R) evenly, which
@@ -24,6 +67,14 @@ export function calculateStepWedgeReflectances(input: { steps: number; minimumRe
24
67
 
25
68
  This call is synchronous; any supplied provider must follow its explicit synchronous contract.
26
69
 
70
+ ## CHART_CONTINUOUS_LEVELS
71
+
72
+ Neutral levels that represent a continuous pattern (grey ramp; 2.3 sine star, zone plate and dead-leaves greys).
73
+
74
+ ```ts
75
+ CHART_CONTINUOUS_LEVELS = 1024
76
+ ```
77
+
27
78
  ## CHART_SCENE_DEFAULTS
28
79
 
29
80
  Engine-chosen chart parameters, each reported when the caller omitted it.
@@ -37,7 +88,16 @@ CHART_SCENE_DEFAULTS = Object.freeze({
37
88
  "grey-ramp": Object.freeze({ minimumReflectance: 0.03, maximumReflectance: 0.9 }),
38
89
  checkerboard: Object.freeze({ squaresAcross: 8, darkReflectance: 0.05, lightReflectance: 0.8 }),
39
90
  "point-grid": Object.freeze({ columns: 5, rows: 3, radius: 0.01, pointReflectance: 0.9, backgroundReflectance: 0.02 }),
40
- patches: Object.freeze({ gapFraction: 0.1, backgroundReflectance: 0.18 })
91
+ patches: Object.freeze({ gapFraction: 0.1, backgroundReflectance: 0.18 }),
92
+ "dead-leaves": Object.freeze({ radiusMinimum: 0.01, radiusMaximum: 0.5, minimumReflectance: 0.2, maximumReflectance: 0.6, seed: 1 }),
93
+ "bar-groups": Object.freeze({ lineWidthsPerPictureHeight: Object.freeze([100, 200, 400, 800]), numerals: true, darkReflectance: 0.05, lightReflectance: 0.8 }),
94
+ "hyperbolic-wedge": Object.freeze({ orientation: "horizontal" as const, lineCount: 5, minimumLineWidthsPerPictureHeight: 100,
95
+ maximumLineWidthsPerPictureHeight: 1000, darkReflectance: 0.05, lightReflectance: 0.8 }),
96
+ "zone-plate": Object.freeze({ radiusFraction: 0.9, maximumCyclesPerPictureHeight: 200, darkReflectance: 0.1, lightReflectance: 0.7, backgroundReflectance: 0.18 }),
97
+ "slanted-squares": Object.freeze({ columns: 3, rows: 2, sizeFraction: 0.5, angleDegrees: 5, darkReflectance: 0.15, lightReflectance: 0.6 }),
98
+ "line-grid": Object.freeze({ spacing: 1 / 3, lineWidth: 0.01, lineReflectance: 0.42, backgroundReflectance: 0.6 }),
99
+ fiducial: Object.freeze({ radiusFraction: 0.8, ringFraction: 0.18, inverted: false, darkReflectance: 0.05, lightReflectance: 0.8, backgroundReflectance: 0.18 }),
100
+ composite: Object.freeze({ background: Object.freeze({ preset: "uniform" as const, reflectance: 0.18 }) })
41
101
  })
42
102
  ```
43
103
 
@@ -47,7 +107,8 @@ The exact contract is declared below; use the domain guide for assumptions and s
47
107
 
48
108
  ```ts
49
109
  CHART_SCENE_PRESETS: readonly ChartScenePresetId[] = Object.freeze(["uniform", "slanted-edge", "siemens-star",
50
- "step-wedge", "grey-ramp", "checkerboard", "point-grid", "patches"] as const)
110
+ "step-wedge", "grey-ramp", "checkerboard", "point-grid", "patches", "dead-leaves", "bar-groups", "hyperbolic-wedge", "zone-plate",
111
+ "slanted-squares", "line-grid", "fiducial", "composite"] as const)
51
112
  ```
52
113
 
53
114
  ## CHART_SPECTRAL_RANGE_NANOMETERS
@@ -58,6 +119,43 @@ Wavelength range every chart spectrum must cover (the facade's sensor quadrature
58
119
  CHART_SPECTRAL_RANGE_NANOMETERS = Object.freeze({ minimum: 380, maximum: 780 })
59
120
  ```
60
121
 
122
+ ## ChartBarGroupTarget
123
+
124
+ One bar group of a `bar-groups` chart or composite element, in the chart's frame units.
125
+
126
+ ```ts
127
+ export interface ChartBarGroupTarget {
128
+ /** Composite element index, or null for a plain `bar-groups` chart. */
129
+ elementIndex: number | null;
130
+ lineWidthsPerPictureHeight: number;
131
+ /** Line pairs (cycles) per picture height: LW/PH ÷ 2. */
132
+ cyclesPerPictureHeight: number;
133
+ /** Width of one bar, frame units. */
134
+ lineWidth: number;
135
+ /** The three vertical bars (horizontal resolution) and the three horizontal bars (vertical resolution). */
136
+ verticalBars: ChartRectangle;
137
+ horizontalBars: ChartRectangle;
138
+ }
139
+ ```
140
+
141
+ ## ChartElementScene
142
+
143
+ Any chart preset except `composite`: a composite's background or element.
144
+
145
+ ```ts
146
+ export type ChartElementScene = UniformChartScene | SlantedEdgeChartScene | SiemensStarChartScene | StepWedgeChartScene | GreyRampChartScene |
147
+ CheckerboardChartScene | PointGridChartScene | PatchChartScene | DeadLeavesChartScene | BarGroupsChartScene | HyperbolicWedgeChartScene |
148
+ ZonePlateChartScene | SlantedSquaresChartScene | LineGridChartScene | FiducialChartScene;
149
+ ```
150
+
151
+ ## ChartRectangle
152
+
153
+ An axis-aligned rectangle in chart frame units: u0 ≤ u ≤ u1, v0 ≤ v ≤ v1.
154
+
155
+ ```ts
156
+ export interface ChartRectangle { u0: number; v0: number; u1: number; v1: number }
157
+ ```
158
+
61
159
  ## ChartReflectance
62
160
 
63
161
  A neutral (spectrally flat) reflectance in [0, 1], or a caller reflectance spectrum (values in [0, 1]).
@@ -71,8 +169,7 @@ export type ChartReflectance = number | ColorimetricSpectrum;
71
169
  The exact contract is declared below; use the domain guide for assumptions and staged integration.
72
170
 
73
171
  ```ts
74
- export type ChartScene = UniformChartScene | SlantedEdgeChartScene | SiemensStarChartScene | StepWedgeChartScene |
75
- GreyRampChartScene | CheckerboardChartScene | PointGridChartScene | PatchChartScene;
172
+ export type ChartScene = ChartElementScene | CompositeChartScene;
76
173
  ```
77
174
 
78
175
  ## ChartScenePresetId
@@ -83,6 +180,25 @@ The exact contract is declared below; use the domain guide for assumptions and s
83
180
  export type ChartScenePresetId = ChartScene["preset"];
84
181
  ```
85
182
 
183
+ ## ChartSlantedSquare
184
+
185
+ One square of a `slanted-squares` chart and its four edges, in the chart's frame units.
186
+
187
+ ```ts
188
+ export interface ChartSlantedSquare {
189
+ elementIndex: number | null;
190
+ center: { u: number; v: number };
191
+ /** Side length, frame units. */
192
+ side: number;
193
+ /** Clockwise rotation, degrees. */
194
+ angleDegrees: number;
195
+ darkReflectance: number;
196
+ lightReflectance: number;
197
+ /** `left`/`right` edges are near-vertical (horizontal MTF), `top`/`bottom` near-horizontal (vertical MTF). */
198
+ edges: { side: "left" | "right" | "top" | "bottom"; midpoint: { u: number; v: number }; direction: { u: number; v: number }; length: number }[];
199
+ }
200
+ ```
201
+
86
202
  ## CheckerboardChartScene
87
203
 
88
204
  The exact contract is declared below; use the domain guide for assumptions and staged integration.
@@ -97,6 +213,82 @@ export interface CheckerboardChartScene {
97
213
  }
98
214
  ```
99
215
 
216
+ ## CompositeChartElement
217
+
218
+ One element of a composite chart: a preset drawn inside its placement rectangle (frame units of the
219
+ composite). The element sees its own frame: u = ±1 at the rectangle's left and right edges, aspect =
220
+ height / width, and LW/PH frequencies still refer to the composite's picture height.
221
+
222
+ ```ts
223
+ export interface CompositeChartElement {
224
+ scene: ChartElementScene;
225
+ centerU: number;
226
+ centerV: number;
227
+ width: number;
228
+ height: number;
229
+ /** `rectangle` (default) or `ellipse` inscribed in the rectangle; outside it, earlier elements or the background show. */
230
+ mask?: "rectangle" | "ellipse";
231
+ }
232
+ ```
233
+
234
+ ## CompositeChartScene
235
+
236
+ 2.3: an ordered list of elements over a background. Later elements cover earlier ones. The engine
237
+ provides only the generic parts; a branded or product-specific layout stays in the application.
238
+
239
+ ```ts
240
+ export interface CompositeChartScene {
241
+ preset: "composite";
242
+ /** Fills the whole composite frame. Default `{ preset: "uniform", reflectance: 0.18 }`. */
243
+ background?: ChartElementScene;
244
+ /** 1..256 elements, drawn in order. */
245
+ elements: readonly CompositeChartElement[];
246
+ }
247
+ ```
248
+
249
+ ## DeadLeavesChartScene
250
+
251
+ 2.3: seeded dead-leaves texture (Matheron's random closed set model, in the scale-invariant form used for
252
+ camera texture measurement by Cao, Guichard and Hornung): opaque disks with Poisson-distributed centres,
253
+ radius density ∝ r⁻³ on [radiusMinimum, radiusMaximum] and a grey drawn uniformly in [minimum, maximum]
254
+ reflectance. Disks are drawn in order with earlier disks on top until every point of the chart (plus 0.1
255
+ frame units around it) is covered, which samples the stationary dead-leaves image exactly (Kendall and
256
+ Thönnes' backward "perfect simulation"). Radii are in frame units (fraction of the frame half-width).
257
+
258
+ ```ts
259
+ export interface DeadLeavesChartScene {
260
+ preset: "dead-leaves";
261
+ /** Smallest disk radius, frame units, [0.002, radiusMaximum). Default 0.01. */
262
+ radiusMinimum?: number;
263
+ /** Largest disk radius, frame units, (radiusMinimum, 2]. Default 0.5. */
264
+ radiusMaximum?: number;
265
+ /** Grey range (neutral reflectances). Defaults 0.2 and 0.6. */
266
+ minimumReflectance?: number;
267
+ maximumReflectance?: number;
268
+ /** Random seed (uint32); the same seed gives the same disks on every platform. Default 1. */
269
+ seed?: number;
270
+ }
271
+ ```
272
+
273
+ ## FiducialChartScene
274
+
275
+ 2.3: registration fiducial: a 2 × 2 checker disk inside a dark ring; `inverted` swaps the checker phase (an orientation cue).
276
+
277
+ ```ts
278
+ export interface FiducialChartScene {
279
+ preset: "fiducial";
280
+ /** Disk radius as a fraction of the frame half-height, (0, 1]. Default 0.8. */
281
+ radiusFraction?: number;
282
+ /** Ring width as a fraction of the radius, (0, 1). Default 0.18. */
283
+ ringFraction?: number;
284
+ /** Default false: the upper-right and lower-left quadrants are dark. */
285
+ inverted?: boolean;
286
+ darkReflectance?: number;
287
+ lightReflectance?: number;
288
+ backgroundReflectance?: number;
289
+ }
290
+ ```
291
+
100
292
  ## GreyRampChartScene
101
293
 
102
294
  The exact contract is declared below; use the domain guide for assumptions and staged integration.
@@ -110,6 +302,43 @@ export interface GreyRampChartScene {
110
302
  }
111
303
  ```
112
304
 
305
+ ## HyperbolicWedgeChartScene
306
+
307
+ 2.3: hyperbolic wedge: `lineCount` dark lines separated by light gaps of the same width, whose width
308
+ shrinks along the wedge axis so that the frequency rises linearly from the minimum to the maximum LW/PH.
309
+ The line edges are hyperbolas. `horizontal`: the axis runs left to right and the lines are nearly
310
+ horizontal (vertical resolution); `vertical`: the axis runs bottom to top (horizontal resolution).
311
+
312
+ ```ts
313
+ export interface HyperbolicWedgeChartScene {
314
+ preset: "hyperbolic-wedge";
315
+ orientation?: "horizontal" | "vertical";
316
+ /** Dark lines, 1..50. Default 5. */
317
+ lineCount?: number;
318
+ /** LW/PH at the start and end of the axis. Defaults 100 and 1000. */
319
+ minimumLineWidthsPerPictureHeight?: number;
320
+ maximumLineWidthsPerPictureHeight?: number;
321
+ darkReflectance?: number;
322
+ lightReflectance?: number;
323
+ }
324
+ ```
325
+
326
+ ## LineGridChartScene
327
+
328
+ 2.3: straight lines at every multiple of `spacing` from the centre in u and v (a distortion and registration grid).
329
+
330
+ ```ts
331
+ export interface LineGridChartScene {
332
+ preset: "line-grid";
333
+ /** Distance between lines, frame units, (0, 2]. Default 1/3 (six cells across). */
334
+ spacing?: number;
335
+ /** Line width, frame units, (0, spacing). Default 0.01. */
336
+ lineWidth?: number;
337
+ lineReflectance?: number;
338
+ backgroundReflectance?: number;
339
+ }
340
+ ```
341
+
113
342
  ## PatchChartScene
114
343
 
115
344
  The exact contract is declared below; use the domain guide for assumptions and staged integration.
@@ -196,6 +425,13 @@ export interface SiemensStarChartScene {
196
425
  lightReflectance?: number;
197
426
  /** Reflectance outside the star. Default 0.18. */
198
427
  backgroundReflectance?: number;
428
+ /**
429
+ * 2.3: angular reflectance profile. `bar` (default): square-wave sectors. `sine`: a sinusoidal star,
430
+ * R(θ) = mid + amp·sin(2π·cycles·(θ/2π + ½)) between the dark and light reflectances, zero crossings at
431
+ * the bar star's sector edges, quantized to {@link CHART_CONTINUOUS_LEVELS} levels. Reported in the
432
+ * parameters only when given, so 2.2 scene digests are unchanged.
433
+ */
434
+ profile?: "bar" | "sine";
199
435
  }
200
436
  ```
201
437
 
@@ -215,6 +451,27 @@ export interface SlantedEdgeChartScene {
215
451
  }
216
452
  ```
217
453
 
454
+ ## SlantedSquaresChartScene
455
+
456
+ 2.3: a field of dark squares rotated clockwise by `angleDegrees` on a light background, one centred in
457
+ each of columns × rows equal cells: every square gives two near-vertical and two near-horizontal slanted
458
+ edges (Burns, PICS 2000). See {@link calculateSlantedSquareEdges}.
459
+
460
+ ```ts
461
+ export interface SlantedSquaresChartScene {
462
+ preset: "slanted-squares";
463
+ columns?: number;
464
+ rows?: number;
465
+ /** Square side as a fraction of the smaller cell side, (0, 1); the rotated square must stay in its cell. Default 0.5. */
466
+ sizeFraction?: number;
467
+ /** Clockwise rotation, degrees, in (-45, 45). Default 5. */
468
+ angleDegrees?: number;
469
+ /** Square (dark) and background (light) reflectances. Defaults 0.15 and 0.6 (4:1). */
470
+ darkReflectance?: number;
471
+ lightReflectance?: number;
472
+ }
473
+ ```
474
+
218
475
  ## StepWedgeChartScene
219
476
 
220
477
  The exact contract is declared below; use the domain guide for assumptions and staged integration.
@@ -238,3 +495,22 @@ The exact contract is declared below; use the domain guide for assumptions and s
238
495
  ```ts
239
496
  export interface UniformChartScene { preset: "uniform"; reflectance?: ChartReflectance }
240
497
  ```
498
+
499
+ ## ZonePlateChartScene
500
+
501
+ 2.3: circular zone plate R(r) = mid + amp·cos(π·k·r²): the local frequency k·r rises linearly from 0 at
502
+ the centre to `maximumCyclesPerPictureHeight` at the rim (the quadratic-phase chirp of a Fresnel zone
503
+ plate). Quantized to {@link CHART_CONTINUOUS_LEVELS} levels.
504
+
505
+ ```ts
506
+ export interface ZonePlateChartScene {
507
+ preset: "zone-plate";
508
+ /** Plate radius as a fraction of the frame half-height, (0, 1]. Default 0.9. */
509
+ radiusFraction?: number;
510
+ /** Frequency at the rim, cycles per picture height. Default 200. */
511
+ maximumCyclesPerPictureHeight?: number;
512
+ darkReflectance?: number;
513
+ lightReflectance?: number;
514
+ backgroundReflectance?: number;
515
+ }
516
+ ```
@@ -32,6 +32,29 @@ export type CaptureIlluminant =
32
32
  | { kind: "spectrum"; spectrum: ColorimetricSpectrum; label?: string };
33
33
  ```
34
34
 
35
+ ## CHART_SIZE_DEFAULT_SURROUND_REFLECTANCE
36
+
37
+ Default surround of a fixed-size chart: an 18 % grey plane.
38
+
39
+ ```ts
40
+ CHART_SIZE_DEFAULT_SURROUND_REFLECTANCE = 0.18
41
+ ```
42
+
43
+ ## ChartSizeOption
44
+
45
+ 2.3: a chart's physical size on the scene plane (`simulateCapture` `chartSize`, `createPlanarSceneRadianceProvider` `chartSize`).
46
+
47
+ ```ts
48
+ export interface ChartSizeOption {
49
+ /** Chart width in metres, (0, 10 000]. */
50
+ widthM: number;
51
+ /** Chart height in metres. Default: widthM × the frame aspect (height / width), so the chart has the frame's shape. */
52
+ heightM?: number;
53
+ /** Neutral reflectance of the plane around the chart, lit by the same illuminant. Default 0.18. */
54
+ surroundReflectance?: number;
55
+ }
56
+ ```
57
+
35
58
  ## createPlanarSceneRadianceProvider
36
59
 
37
60
  Public scene-preset provider. Places an engine chart (or a caller reflectance or radiance function) on
@@ -51,6 +74,11 @@ export function createPlanarSceneRadianceProvider(input: {
51
74
  aspect: number;
52
75
  sceneId: string;
53
76
  providerProfileId: string;
77
+ /**
78
+ * 2.3, opt-in: place the chart at this physical size, centred, instead of filling the frame; the chart preset
79
+ * is resolved with aspect heightM / widthM and the rest of the plane is the surround.
80
+ */
81
+ chartSize?: ChartSizeOption;
54
82
  }): PlanarSceneRadianceProvider & { chart: ResolvedChartScene | null };
55
83
  ```
56
84
 
@@ -76,6 +104,13 @@ export interface PlanarSceneRadianceProvider {
76
104
  evaluateApertureRadiance(request: Readonly<SceneRadianceEvaluationRequest>, ray: Readonly<SensorApertureRay>): SceneRadianceEvaluationResult;
77
105
  /** Spectral radiance at a plane point (frame units), the same value the callbacks return. */
78
106
  radianceAt(u: number, v: number, wavelengthNanometers: number): number;
107
+ /**
108
+ * 2.3, chart scenes only: the chart material at a plane point (frame units), or `materials.length` for the
109
+ * surround of a fixed-size chart; `materialRadiance` gives that material's spectral radiance, the same value
110
+ * `radianceAt` returns at its points.
111
+ */
112
+ chartMaterialAt?(u: number, v: number): number;
113
+ materialRadiance?(material: number, wavelengthNanometers: number): number;
79
114
  }
80
115
  ```
81
116
 
@@ -118,5 +153,12 @@ export interface ScenePlanePoint {
118
153
  u: number;
119
154
  v: number;
120
155
  wavelengthNanometers: number;
156
+ /**
157
+ * 2.3, only with a fixed-size chart (`chartSize`): the point in chart-local frame units (±1 at the chart's
158
+ * left/right edges, v up on the same scale, ±chart height/width at its top/bottom). The function is then
159
+ * called only for points on the chart; the surround is the placement's surround reflectance.
160
+ */
161
+ chartU?: number;
162
+ chartV?: number;
121
163
  }
122
164
  ```
@@ -2,6 +2,38 @@
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
+ ## IdealReferenceResult
6
+
7
+ Result of {@link simulateIdealReference}: an ideal reference image, not a capture (no RAW, plan or request).
8
+
9
+ ```ts
10
+ export interface IdealReferenceResult {
11
+ kind: "ideal-reference";
12
+ /** Human-readable label for a user interface. */
13
+ label: string;
14
+ facadeVersion: typeof SIMULATE_CAPTURE_FACADE_VERSION;
15
+ engineApiVersion: string;
16
+ image: { width: number; height: number; format: SimulateCaptureOutputFormat; samples: Uint8Array | Uint16Array; png?: Uint8Array; colorEncoding?: SimulateCaptureColorEncoding };
17
+ /** The resolved exposure of the capture this reference matches. */
18
+ exposure: { mode: SimulateCaptureExposureMode; settings: { fNumber: number; shutterSeconds: number; iso: number }; ev100: number };
19
+ scene: SimulateCaptureResult["scene"];
20
+ region?: { x: number; y: number; width: number; height: number; frameWidth: number; frameHeight: number };
21
+ diagnostics: { sampleCount: number; timingsMs: { reference: number; total: number } };
22
+ provenance: { scientificStatus: "approximation"; cameraPresetId: string; cameraPresetVersion: string; evidence: readonly EvidenceProvenance[];
23
+ composedContracts: readonly string[]; limitations: readonly string[] };
24
+ /** The capture request's assumptions, then `reference.kind`, `reference.excludes`, `reference.exposure` and `reference.sampling`. */
25
+ assumptions: readonly SimulateCaptureAssumption[];
26
+ }
27
+ ```
28
+
29
+ ## SIMULATE_CAPTURE_BOX_APERTURE_DEFAULT_SAMPLES
30
+
31
+ Default midpoint samples per axis of `sensor.pixelAperture: "box"`.
32
+
33
+ ```ts
34
+ SIMULATE_CAPTURE_BOX_APERTURE_DEFAULT_SAMPLES = 4
35
+ ```
36
+
5
37
  ## SIMULATE_CAPTURE_COST_OPTIONS
6
38
 
7
39
  Options reported in `diagnostics.options` (2.1), in this order, when they ran: the ones that change how much work
@@ -9,7 +41,7 @@ a capture does. `motion.rotation` is a non-zero angular velocity; `sensor.tempor
9
41
 
10
42
  ```ts
11
43
  SIMULATE_CAPTURE_COST_OPTIONS = Object.freeze(["lens.pupilSampling", "lens.projection", "lens.distortion", "lens.vignetting", "lens.psf",
12
- "lens.diffraction", "sensor.antiAliasing", "sensor.temporalSampleCount", "motion.rotation", "motion.shutter.rolling", "output.borderHandling.mirror",
44
+ "lens.diffraction", "sensor.antiAliasing", "sensor.pixelAperture.box", "sensor.temporalSampleCount", "motion.rotation", "motion.shutter.rolling", "output.borderHandling.mirror", "output.region",
13
45
  "output.whiteBalance.auto", "output.demosaic.ahd", "output.highlightMode.reconstruct", "output.colorEncoding.wide-gamut", "output.gamutMapping.perceptual",
14
46
  "output.format.jpeg", "output.format.png", "output.format.dng"] as const)
15
47
  ```
@@ -144,6 +176,14 @@ export interface SimulateCaptureOptions {
144
176
  lens?: {
145
177
  /** Must lie in the lens capability (the generic primes are 50 mm). Default: the lens's shortest focal length. */
146
178
  focalLengthMm?: number;
179
+ /**
180
+ * Opt-in (2.3): a lens of the generic prime family by its full-frame-equivalent focal length
181
+ * (`GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM`). Its physical focal length is the equivalent ÷ the diagonal crop
182
+ * factor of the simulated frame (output width × height × pixel pitch); it replaces the tier lens's focal length and
183
+ * f-number range (the tier lens's transmission is kept). An f-number outside the prime's range, or a subject or
184
+ * focus distance below its minimum focus distance, is rejected. Not combinable with `focalLengthMm`.
185
+ */
186
+ prime?: number;
147
187
  /** Aperture for manual and aperture-priority modes. Default 8. */
148
188
  fNumber?: number;
149
189
  /** Focus distance in metres. Default: the subject distance. */
@@ -165,7 +205,7 @@ export interface SimulateCaptureOptions {
165
205
  * Opt-in (2.1) diffraction blur of an ideal, aberration-free, in-focus lens with a circular pupil at the
166
206
  * capture's resolved f-number: the engine builds the Airy kernels (`createDiffractionLimitedPsfProfile`)
167
207
  * on the site pitch. `kernelRadiusSamples` (0–32) sets the kernel window; default: the third dark ring at
168
- * the longest spectral node. Not combinable with `psf` or `pupilSampling`. Default off.
208
+ * the longest spectral node, capped at 32 sites. Not combinable with `psf` or `pupilSampling`. Default off.
169
209
  */
170
210
  diffraction?: SimulateCaptureLensDiffraction;
171
211
  };
@@ -183,6 +223,14 @@ export interface SimulateCaptureOptions {
183
223
  scene: ChartScene | CallerReflectanceScene | CallerRadianceScene;
184
224
  /** Distance from the camera to the scene plane in metres. Default 5. */
185
225
  subjectDistanceM?: number;
226
+ /**
227
+ * Opt-in (2.3) fixed-size chart: place the scene at this physical size on the plane at the subject distance,
228
+ * centred on the optical axis, instead of sizing it to fill the frame. Outside it the plane is a neutral surround
229
+ * (default 18 %). Chart presets are resolved in the chart's own frame (aspect heightM / widthM); caller functions
230
+ * are called only on the chart and receive chart-local `chartU`, `chartV`. `result.scene.chart` reports the
231
+ * framed width, the fraction of the frame the chart covers and the thin-lens magnification.
232
+ */
233
+ chartSize?: ChartSizeOption;
186
234
  /** Default: Planckian 6500 K (a blackbody stand-in for daylight; CIE D65 is not bundled). */
187
235
  illuminant?: CaptureIlluminant;
188
236
  /** Illuminance on the scene plane, lux. Default about 21 481 lux (see `SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX` and the assumptions). */
@@ -225,6 +273,15 @@ export interface SimulateCaptureOptions {
225
273
  * memory grow with the pixel count (see "Large images" in the quick start); browsers may fail at large sizes.
226
274
  */
227
275
  maximumPixels?: number;
276
+ /**
277
+ * Opt-in (2.3) region rendering: render only this rectangle, in output-pixel coordinates of the whole frame
278
+ * `width` × `height` (x right, y down from its top-left). The pixels are byte-identical to the same pixels of
279
+ * the whole render: framing, metering, exposure, noise seeds (absolute native site indices), rolling-shutter row
280
+ * timing, diffraction and demosaic all use the whole frame. Use it to tile a large frame across workers or to
281
+ * render a 100 % crop. `result.image` is the region; `result.region` reports it. Not combinable with
282
+ * `whiteBalance: "auto"`, whose statistics would come from the region only.
283
+ */
284
+ region?: { x: number; y: number; width: number; height: number };
228
285
  };
229
286
  /** Camera motion and shutter type. Default: still camera, global electronic shutter. */
230
287
  motion?: SimulateCaptureMotion;
@@ -242,6 +299,13 @@ export interface SimulateCaptureOptions {
242
299
  * Multiplies the scene queries by the number of split points. Default off (the tiers declare no filter).
243
300
  */
244
301
  antiAliasing?: SimulateCaptureSensorAntiAliasing;
302
+ /**
303
+ * Opt-in (2.3) pixel aperture. `"point"` (default): each site samples the irradiance at its centre.
304
+ * `"box"`: each site integrates the irradiance over its whole square photosite (fill factor 1) with an
305
+ * N × N midpoint rule, N = `samplesPerAxis` (1–16, default 4), so detail finer than a pixel averages out as
306
+ * on a real sensor (pixel MTF |sinc(f·p)|) instead of aliasing. Multiplies the scene queries by N².
307
+ */
308
+ pixelAperture?: SimulateCapturePixelAperture;
245
309
  };
246
310
  /** Aborting cancels the running task; the promise rejects with a domain error and returns no partial image. */
247
311
  signal?: AbortSignal;
@@ -258,6 +322,14 @@ The exact contract is declared below; use the domain guide for assumptions and s
258
322
  export type SimulateCaptureOutputFormat = "srgb-8" | "srgb-16" | "jpeg" | "png" | "dng";
259
323
  ```
260
324
 
325
+ ## SimulateCapturePixelAperture
326
+
327
+ Pixel aperture (2.3): centre sampling, or the square photosite integrated with an N × N midpoint rule.
328
+
329
+ ```ts
330
+ export type SimulateCapturePixelAperture = "point" | "box" | { kind: "box"; samplesPerAxis?: number };
331
+ ```
332
+
261
333
  ## SimulateCaptureProgress
262
334
 
263
335
  The exact contract is declared below; use the domain guide for assumptions and staged integration.
@@ -293,9 +365,19 @@ export interface SimulateCaptureResult {
293
365
  /** Lossless uncompressed CFA DNG of the packed RAW when format is "dng" (`serializeNativeRawDng`); `samples` are then sRGB 8-bit. */
294
366
  dng?: Uint8Array;
295
367
  };
296
- /** Packed native RAW (codes, black levels, saturation flags) as produced by the native environment RAW task. */
368
+ /**
369
+ * Packed native RAW (codes, black levels, saturation flags) as produced by the native environment RAW task. With
370
+ * `output.region` (2.3): the region's native sites only, with `plan` describing that raster (its active area is the
371
+ * region); `request.environmentRaw` and `request.siteInput` stay those of the whole frame.
372
+ */
297
373
  raw: NativeRawOutput;
298
374
  plan: NativeRawPlan;
375
+ /**
376
+ * 2.3, present with `output.region`: the rendered rectangle in output pixels of the whole `frameWidth` × `frameHeight`
377
+ * frame, and the native sites rendered for it (`nativeRect`, in the whole frame's native raster: the region, the
378
+ * demosaic reach on each side clipped to the raster, and its origin rounded down to the 2 × 2 CFA repeat).
379
+ */
380
+ region?: { x: number; y: number; width: number; height: number; frameWidth: number; frameHeight: number; nativeRect: { x: number; y: number; width: number; height: number } };
299
381
  /** The complete underlying request. */
300
382
  request: {
301
383
  environmentRaw: NativeEnvironmentRawInput;
@@ -330,6 +412,14 @@ export interface SimulateCaptureResult {
330
412
  /** Physical size of the framed scene plane, metres. */
331
413
  framedWidthM: number;
332
414
  framedHeightM: number;
415
+ /**
416
+ * 2.3, present with `chartSize`: the chart's physical size and surround, the fraction of the frame width and
417
+ * height it covers (above 1 when it is larger than the frame and cropped), the thin-lens magnification
418
+ * v/d (image distance over subject distance, the scale the facade's framing uses) and the chart's size on the
419
+ * sensor in millimetres and output pixels.
420
+ */
421
+ chart?: { widthM: number; heightM: number; surroundReflectance: number; frameWidthFraction: number; frameHeightFraction: number;
422
+ magnification: number; imageWidthMm: number; imageHeightMm: number; imageWidthPixels: number; imageHeightPixels: number };
333
423
  /**
334
424
  * SHA-256 of the canonical scene description, recorded as the capture source digest. Caller spectra (a
335
425
  * `spectrum` or `daylight` illuminant, chart reflectance spectra) are part of the description: tabulated
@@ -402,3 +492,18 @@ export type SimulateCaptureWhiteBalance =
402
492
  | { kelvin: number; tint?: number }
403
493
  | { channelGains: { red: number; green: number; blue: number } };
404
494
  ```
495
+
496
+ ## simulateIdealReference
497
+
498
+ The ideal reference of a capture request (2.3): the same scene, framing, chart size, output size, region and pixel
499
+ grid as `simulateCapture(options)` would use, rendered with no camera effects (no lens blur, diffraction, defocus,
500
+ distortion, vignetting, anti-aliasing filter, mosaic or demosaic, noise, dark signal or motion), at the capture's
501
+ own exposure and developed with its white balance, colour matrices, tone curve and encoding (the
502
+ `reference.exposure` assumption). Deterministic and cheap: no RAW is simulated. Formats srgb-8, srgb-16 and png.
503
+ The result is labelled `kind: "ideal-reference"`; it is not a capture.
504
+
505
+ ```ts
506
+ export async function simulateIdealReference(options: SimulateCaptureOptions): Promise<IdealReferenceResult>;
507
+ ```
508
+
509
+ This call is asynchronous; byte hashing uses the caller runtime's Web Crypto.