@photivra/engine 2.16.0 → 2.18.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 (107) hide show
  1. package/CHANGELOG.md +104 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +24 -0
  6. package/dist/camera/lens-projection.d.ts +5 -0
  7. package/dist/camera/lens-projection.js +10 -0
  8. package/dist/camera/lens-projection.js.map +1 -1
  9. package/dist/camera/sensor-equivalence.d.ts +156 -0
  10. package/dist/camera/sensor-equivalence.js +208 -0
  11. package/dist/camera/sensor-equivalence.js.map +1 -0
  12. package/dist/capture/native-environment-tabulated.d.ts +5 -0
  13. package/dist/capture/native-environment-tabulated.js +1 -1
  14. package/dist/capture/native-environment-tabulated.js.map +1 -1
  15. package/dist/core/version.d.ts +1 -1
  16. package/dist/core/version.js +1 -1
  17. package/dist/core/version.js.map +1 -1
  18. package/dist/equipment/generic-lens-catalogue.d.ts +227 -0
  19. package/dist/equipment/generic-lens-catalogue.js +243 -0
  20. package/dist/equipment/generic-lens-catalogue.js.map +1 -0
  21. package/dist/equipment/ideal-lens.d.ts +25 -0
  22. package/dist/equipment/ideal-lens.js +21 -0
  23. package/dist/equipment/ideal-lens.js.map +1 -0
  24. package/dist/facade/catalogue-lens-signal.d.ts +94 -0
  25. package/dist/facade/catalogue-lens-signal.js +362 -0
  26. package/dist/facade/catalogue-lens-signal.js.map +1 -0
  27. package/dist/facade/defocus-bokeh-zones.d.ts +33 -0
  28. package/dist/facade/defocus-bokeh-zones.js +53 -0
  29. package/dist/facade/defocus-bokeh-zones.js.map +1 -0
  30. package/dist/facade/defocus-image-space-provider.d.ts +9 -0
  31. package/dist/facade/defocus-image-space-provider.js +101 -9
  32. package/dist/facade/defocus-image-space-provider.js.map +1 -1
  33. package/dist/facade/diffraction-provider.d.ts +29 -0
  34. package/dist/facade/diffraction-provider.js +117 -11
  35. package/dist/facade/diffraction-provider.js.map +1 -1
  36. package/dist/facade/display-view-gpu-session.js +4 -0
  37. package/dist/facade/display-view-gpu-session.js.map +1 -1
  38. package/dist/facade/display-view-gpu.d.ts +10 -0
  39. package/dist/facade/display-view-gpu.js.map +1 -1
  40. package/dist/facade/display-view-lens-signal.d.ts +61 -0
  41. package/dist/facade/display-view-lens-signal.js +502 -0
  42. package/dist/facade/display-view-lens-signal.js.map +1 -0
  43. package/dist/facade/expected-signal-setup.d.ts +15 -0
  44. package/dist/facade/expected-signal-setup.js +19 -2
  45. package/dist/facade/expected-signal-setup.js.map +1 -1
  46. package/dist/facade/expected-signal.d.ts +13 -0
  47. package/dist/facade/expected-signal.js +155 -1
  48. package/dist/facade/expected-signal.js.map +1 -1
  49. package/dist/facade/generic-lens-capture.d.ts +7 -0
  50. package/dist/facade/generic-lens-capture.js +9 -0
  51. package/dist/facade/generic-lens-capture.js.map +1 -0
  52. package/dist/facade/large-defocus-lattice.d.ts +39 -0
  53. package/dist/facade/large-defocus-lattice.js +64 -0
  54. package/dist/facade/large-defocus-lattice.js.map +1 -0
  55. package/dist/facade/scene-radiance.d.ts +5 -0
  56. package/dist/facade/scene-radiance.js +5 -2
  57. package/dist/facade/scene-radiance.js.map +1 -1
  58. package/dist/facade/simulate-capture.d.ts +67 -7
  59. package/dist/facade/simulate-capture.js +400 -64
  60. package/dist/facade/simulate-capture.js.map +1 -1
  61. package/dist/facade/simulate-display-view.js +23 -17
  62. package/dist/facade/simulate-display-view.js.map +1 -1
  63. package/dist/gpu/display-view-quality-gpu.d.ts +0 -9
  64. package/dist/gpu/display-view-quality-gpu.js +6 -0
  65. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  66. package/dist/gpu/display-view-speed-gpu.js +2 -0
  67. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  68. package/dist/index.d.ts +6 -1
  69. package/dist/index.js +5 -0
  70. package/dist/index.js.map +1 -1
  71. package/dist/optics/defocus-bokeh-kernel.d.ts +207 -0
  72. package/dist/optics/defocus-bokeh-kernel.js +380 -0
  73. package/dist/optics/defocus-bokeh-kernel.js.map +1 -0
  74. package/dist/optics/defocus-disc-kernel.d.ts +41 -19
  75. package/dist/optics/defocus-disc-kernel.js +44 -23
  76. package/dist/optics/defocus-disc-kernel.js.map +1 -1
  77. package/dist/optics/generic-lens-psf.d.ts +68 -0
  78. package/dist/optics/generic-lens-psf.js +337 -0
  79. package/dist/optics/generic-lens-psf.js.map +1 -0
  80. package/dist/optics/illumination-vignetting.d.ts +6 -0
  81. package/dist/optics/illumination-vignetting.js +24 -7
  82. package/dist/optics/illumination-vignetting.js.map +1 -1
  83. package/dist/optics/sensor-environment-query.d.ts +6 -0
  84. package/dist/optics/sensor-environment-query.js +13 -6
  85. package/dist/optics/sensor-environment-query.js.map +1 -1
  86. package/dist/optics/site-kernel-runs.d.ts +77 -0
  87. package/dist/optics/site-kernel-runs.js +200 -0
  88. package/dist/optics/site-kernel-runs.js.map +1 -0
  89. package/dist/output/generic-lens-corrections.d.ts +24 -0
  90. package/dist/output/generic-lens-corrections.js +51 -0
  91. package/dist/output/generic-lens-corrections.js.map +1 -0
  92. package/docs/AGENT_GUIDE.md +4 -0
  93. package/docs/API_REFERENCE.md +77 -1
  94. package/docs/QUICK_START.md +305 -17
  95. package/docs/USAGE.md +98 -5
  96. package/docs/api/camera-sensor-equivalence.md +209 -0
  97. package/docs/api/core-version.md +1 -1
  98. package/docs/api/equipment-generic-lens-catalogue.md +281 -0
  99. package/docs/api/equipment-ideal-lens.md +26 -0
  100. package/docs/api/exports.json +202 -2
  101. package/docs/api/facade-simulate-capture.md +48 -4
  102. package/docs/api/optics-defocus-bokeh-kernel.md +145 -0
  103. package/docs/api/optics-defocus-disc-kernel.md +22 -15
  104. package/docs/api/optics-sensor-environment-query.md +6 -0
  105. package/docs/api/output-generic-lens-corrections.md +39 -0
  106. package/llms.txt +3 -0
  107. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Ideal lens (#576): the bounds of `simulateCapture`'s and the display view's `lens.ideal`, a plain aberration-free thin lens
3
+ * named by its physical focal length, on any sensor. Its blur is that of the options the request gives, the same models as with
4
+ * `lens.prime`: `lens.defocus` (the thin-lens blur circle, `calculateDefocusCircle`), `lens.diffraction` (the Airy pattern of a
5
+ * circular pupil) and `lens.vignetting` (a caller profile). Without them it renders sharp and evenly lit.
6
+ *
7
+ * - `fNumber.minimum` 0.5 is a physical limit: a lens in air that satisfies the Abbe sine condition (aplanatic) has
8
+ * N = 1/(2·n·sin θ′) with n = 1 and sin θ′ ≤ 1, so N ≥ 0.5 (the sine condition: Born & Wolf, *Principles of Optics*, 7th ed.;
9
+ * Kingslake & Johnson, *Lens Design Fundamentals*, 2nd ed.). The engine's thin-lens defocus and scalar Airy diffraction
10
+ * are paraxial models, so near f/0.5 they are approximations further from a real lens than at f/2.
11
+ * - `fNumber.maximum` 128 and both focal-length bounds are engine bounds, not physical limits: 1 mm is shorter than phone
12
+ * camera lenses, 4000 mm longer than photographic lenses, and f/128 beyond large-format lenses' smallest stops.
13
+ * - The ideal lens's f-number range is the whole of `fNumber`: manual and aperture priority take any `lens.fNumber` in it, and
14
+ * shutter priority and program may choose any aperture in it (in dim light, wider than real lenses open).
15
+ */
16
+ export declare const IDEAL_LENS_LIMITS: Readonly<{
17
+ focalLengthMm: Readonly<{
18
+ minimum: 1;
19
+ maximum: 4000;
20
+ }>;
21
+ fNumber: Readonly<{
22
+ minimum: 0.5;
23
+ maximum: 128;
24
+ }>;
25
+ }>;
@@ -0,0 +1,21 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Ideal lens (#576): the bounds of `simulateCapture`'s and the display view's `lens.ideal`, a plain aberration-free thin lens
4
+ * named by its physical focal length, on any sensor. Its blur is that of the options the request gives, the same models as with
5
+ * `lens.prime`: `lens.defocus` (the thin-lens blur circle, `calculateDefocusCircle`), `lens.diffraction` (the Airy pattern of a
6
+ * circular pupil) and `lens.vignetting` (a caller profile). Without them it renders sharp and evenly lit.
7
+ *
8
+ * - `fNumber.minimum` 0.5 is a physical limit: a lens in air that satisfies the Abbe sine condition (aplanatic) has
9
+ * N = 1/(2·n·sin θ′) with n = 1 and sin θ′ ≤ 1, so N ≥ 0.5 (the sine condition: Born & Wolf, *Principles of Optics*, 7th ed.;
10
+ * Kingslake & Johnson, *Lens Design Fundamentals*, 2nd ed.). The engine's thin-lens defocus and scalar Airy diffraction
11
+ * are paraxial models, so near f/0.5 they are approximations further from a real lens than at f/2.
12
+ * - `fNumber.maximum` 128 and both focal-length bounds are engine bounds, not physical limits: 1 mm is shorter than phone
13
+ * camera lenses, 4000 mm longer than photographic lenses, and f/128 beyond large-format lenses' smallest stops.
14
+ * - The ideal lens's f-number range is the whole of `fNumber`: manual and aperture priority take any `lens.fNumber` in it, and
15
+ * shutter priority and program may choose any aperture in it (in dim light, wider than real lenses open).
16
+ */
17
+ export const IDEAL_LENS_LIMITS = /* @__PURE__ */ Object.freeze({
18
+ focalLengthMm: Object.freeze({ minimum: 1, maximum: 4000 }),
19
+ fNumber: Object.freeze({ minimum: 0.5, maximum: 128 })
20
+ });
21
+ //# sourceMappingURL=ideal-lens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ideal-lens.js","sourceRoot":"","sources":["../../src/equipment/ideal-lens.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;IAC7D,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3D,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;CACvD,CAAC,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Ideal lens (#576): the bounds of `simulateCapture`'s and the display view's `lens.ideal`, a plain aberration-free thin lens\n * named by its physical focal length, on any sensor. Its blur is that of the options the request gives, the same models as with\n * `lens.prime`: `lens.defocus` (the thin-lens blur circle, `calculateDefocusCircle`), `lens.diffraction` (the Airy pattern of a\n * circular pupil) and `lens.vignetting` (a caller profile). Without them it renders sharp and evenly lit.\n *\n * - `fNumber.minimum` 0.5 is a physical limit: a lens in air that satisfies the Abbe sine condition (aplanatic) has\n * N = 1/(2·n·sin θ′) with n = 1 and sin θ′ ≤ 1, so N ≥ 0.5 (the sine condition: Born & Wolf, *Principles of Optics*, 7th ed.;\n * Kingslake & Johnson, *Lens Design Fundamentals*, 2nd ed.). The engine's thin-lens defocus and scalar Airy diffraction\n * are paraxial models, so near f/0.5 they are approximations further from a real lens than at f/2.\n * - `fNumber.maximum` 128 and both focal-length bounds are engine bounds, not physical limits: 1 mm is shorter than phone\n * camera lenses, 4000 mm longer than photographic lenses, and f/128 beyond large-format lenses' smallest stops.\n * - The ideal lens's f-number range is the whole of `fNumber`: manual and aperture priority take any `lens.fNumber` in it, and\n * shutter priority and program may choose any aperture in it (in dim light, wider than real lenses open).\n */\nexport const IDEAL_LENS_LIMITS = /* @__PURE__ */ Object.freeze({\n focalLengthMm: Object.freeze({ minimum: 1, maximum: 4000 }),\n fNumber: Object.freeze({ minimum: 0.5, maximum: 128 })\n});\n"]}
@@ -0,0 +1,94 @@
1
+ import { type IlluminationVignettingProfile } from "../optics/illumination-vignetting.js";
2
+ import { type ResolveLensSampledPsfInput } from "../optics/lens-psf-profile.js";
3
+ import type { DisplayViewScene } from "./expected-signal.js";
4
+ /** Fewest grid cells between neighboring profile field nodes. */
5
+ export declare const CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS = 4;
6
+ /** Largest move of a node kernel's intensity centroid across one grid cell, in sites (sets finer grids where lateral color moves the PSF). */
7
+ export declare const CATALOGUE_FAST_GRID_CENTROID_STEP_SITES = 0.05;
8
+ /**
9
+ * Largest relative difference between the fast and the exact expected electrons that the run-time verification (one site per
10
+ * CFA phase through the exact chain, `renderTabulatedNativeRaw`) accepts; above it the capture runs the exact method.
11
+ */
12
+ export declare const CATALOGUE_FAST_VERIFICATION_TOLERANCE = 0.005;
13
+ export interface CatalogueLensSignalInput {
14
+ nativeWidth: number;
15
+ nativeHeight: number;
16
+ /** Site pitch, µm. */
17
+ pitch: number;
18
+ /** The catalogue PSF's optical state (profile, focal length, focus, f-number, defocus), as the site inputs' `psf.configuration.psf`. */
19
+ psf: Omit<ResolveLensSampledPsfInput, "fieldPointMm" | "wavelengthNm">;
20
+ /** Spectral nodes and, per CFA channel (red, green, blue), the expected electrons per unit spectral radiance at each node. */
21
+ wavelengthsNm: readonly number[];
22
+ electronsPerRadiance: readonly [Float64Array, Float64Array, Float64Array];
23
+ /** The scene's chart materials (the tabulated path's material tables). */
24
+ scene: Extract<DisplayViewScene, {
25
+ kind: "materials";
26
+ }>;
27
+ /** CFA channel index (0 red, 1 green, 2 blue) of native site (x, y). */
28
+ channelAt(x: number, y: number): number;
29
+ /** The radial distortion profile (r_d = r·(1 + k1ρ² + k2ρ⁴ + k3ρ⁶), ρ = r / normalizationRadiusMm); absent without distortion. */
30
+ distortion?: {
31
+ normalizationRadiusMm: number;
32
+ coefficients: {
33
+ k1: number;
34
+ k2: number;
35
+ k3: number;
36
+ };
37
+ };
38
+ vignetting?: IlluminationVignettingProfile;
39
+ projection: "rectilinear" | "fisheye-equisolid-angle";
40
+ /** True for a catalogue fisheye: a sensor point outside the image circle looks straight back and sees no scene. */
41
+ outsideImageCircleSeesNothing: boolean;
42
+ focalLengthMm: number;
43
+ focusDistanceM: number;
44
+ /** The planar provider's plane distance and frame-unit scale. */
45
+ distanceM: number;
46
+ metresPerFrameUnit: number;
47
+ /** Internal test hook: resolve every site's kernels exactly (the exact method's sum, slowly) instead of blending the grid. */
48
+ exactKernels?: boolean;
49
+ }
50
+ export interface CatalogueLensSignal {
51
+ /** Starts a cache window of native sites [x0, x1) × [y0, y1) (see `DisplayViewExpectedSignal.window`). */
52
+ window(x0: number, y0: number, x1: number, y1: number): void;
53
+ /** Expected photo electrons of native site (x, y) for its own CFA channel. */
54
+ site(x: number, y: number): number;
55
+ /** Scene material lookups so far. */
56
+ readonly lookups: number;
57
+ /** Grid points per axis (columns, rows) and how many were resolved so far. */
58
+ readonly grid: {
59
+ columns: number;
60
+ rows: number;
61
+ resolved: number;
62
+ };
63
+ }
64
+ /**
65
+ * The grid axes: each interval between neighboring profile field nodes split into max(GRID_MINIMUM_SUBDIVISIONS,
66
+ * ⌈Δc / GRID_CENTROID_STEP_SITES⌉) equal cells, Δc the largest centroid move between the interval's two nodes over the other
67
+ * axis's nodes and the wavelength nodes.
68
+ */
69
+ export declare function catalogueFastGridAxes(psf: CatalogueLensSignalInput["psf"]): {
70
+ x: number[];
71
+ y: number[];
72
+ };
73
+ /**
74
+ * Internal test hook (never set by the engine): `exactKernels` makes the facade's signals resolve every site's kernels exactly;
75
+ * `onInput` receives each input the facade builds, so a test can compare the grid blend with the exact sum site by site.
76
+ */
77
+ export interface CatalogueLensSignalTestHook {
78
+ exactKernels?: boolean;
79
+ onInput?(input: CatalogueLensSignalInput): void;
80
+ }
81
+ export declare function setCatalogueLensSignalTestHook(hook: CatalogueLensSignalTestHook | undefined): void;
82
+ /** The fast method's expected signal (see the module comment). */
83
+ export declare function createCatalogueLensSignal(given: CatalogueLensSignalInput): CatalogueLensSignal;
84
+ /** The `raw.expectedSignal` and `fast.lens.catalogue` assumptions of a capture the fast method rendered (kept here, out of the capture bundle's entry). */
85
+ export declare function catalogueFastAssumptions(run: {
86
+ columns: number;
87
+ rows: number;
88
+ resolved: number;
89
+ verificationRelativeDifference: number;
90
+ }): {
91
+ id: string;
92
+ value: Record<string, unknown>;
93
+ explanation: string;
94
+ }[];
@@ -0,0 +1,362 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * #568: the fast method of `simulateCapture`'s `lens.catalogue` (requested with `fast: true` or `lens.catalogue.fast`): each
4
+ * native site's expected photo electrons from tables instead of the environment PSF quadrature's per-tap scene queries.
5
+ * Loaded on demand with the catalogue (`generic-lens-capture.ts`), so a capture without a catalogue lens never loads it.
6
+ *
7
+ * What the exact method computes. The environment PSF quadrature forms, for site q of CFA channel c,
8
+ * μ_c(q) = Σ_λ A_c(λ) Σ_k K_{q,λ}(k)·F(s_k)·L(D(s_k), λ), s_k = q + (c_x − k_x, k_y − c_y)·p,
9
+ * with K_{q,λ} the catalogue PSF resolved at the site's own field point (`resolveLensSampledPsfKernel`: the profile's 5 × 5 field
10
+ * nodes × 5 wavelength nodes interpolated with centroid alignment; row index k_y increases with image +Y, `sensorPsfSourcePoint`),
11
+ * F = T·A the field factor at the tap point (vignetting throughput × distortion area factor), D the scene ray (inverse radial
12
+ * distortion, then the rectilinear or equisolid projection at the thin-lens image distance) and A_c(λ) the electrons per unit
13
+ * spectral radiance on the axis (the tabulated path's channel response). Every tap of a site-centre query is a lattice point.
14
+ *
15
+ * The approximation (the only one). Resolving K_{q,λ} at every site and spectral node is what makes the exact method slow, so the
16
+ * fast method resolves it exactly on a field grid and blends it bilinearly between the four grid points around each site:
17
+ * K̃_{q,λ} = Σ_g b_g(q)·K_{g,λ}.
18
+ * The grid subdivides each interval between the profile's field nodes into at least GRID_MINIMUM_SUBDIVISIONS cells, and into
19
+ * more where the node kernels' intensity centroids move by more than GRID_CENTROID_STEP_SITES sites across the interval (lateral
20
+ * colour moves the PSF across the frame; a blend of two shifted kernels is not the shifted kernel). Since the blend is linear,
21
+ * the channel response and the material radiance fold into the grid kernels once per grid point:
22
+ * μ̃_c(q) = Σ_k F(s_k)·Σ_g b_g(q)·W_{g,c,m(s_k)}(k), W_{g,c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_{g,λ}(k),
23
+ * so a site costs (2R + 1)² taps instead of (2R + 1)² scene queries per spectral node. Its error against the exact method is
24
+ * bounded by ‖K̃ − K‖₁ times the brightest tap's signal; tests measure both (test/simulate-capture-lens-catalogue-fast.test.ts).
25
+ *
26
+ * The scene points. The mapping is the environment's (`undistortedImagePoint`, `lensProjectionDirectionValue`, the planar
27
+ * provider's plane point), with the radial inverse solved by Newton's method instead of bisection: both converge to the same
28
+ * root to rounding. Outside a circular fisheye's image circle (`outsideImageCircle: "backward-ray"`) and beyond the horizon the
29
+ * scene radiance is zero, as in the capture.
30
+ */
31
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
32
+ import { insideRadialImageCircle, lensProjectionDirectionValue } from "../camera/lens-projection.js";
33
+ import { resolveProjectionDistanceMm } from "../motion/camera-rotation.js";
34
+ import { prepareIlluminationVignettingThroughput } from "../optics/illumination-vignetting.js";
35
+ import { resolveLensSampledPsfKernel } from "../optics/lens-psf-profile.js";
36
+ /** Fewest grid cells between neighboring profile field nodes. */
37
+ export const CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS = 4;
38
+ /** Largest move of a node kernel's intensity centroid across one grid cell, in sites (sets finer grids where lateral color moves the PSF). */
39
+ export const CATALOGUE_FAST_GRID_CENTROID_STEP_SITES = 0.05;
40
+ /**
41
+ * Largest relative difference between the fast and the exact expected electrons that the run-time verification (one site per
42
+ * CFA phase through the exact chain, `renderTabulatedNativeRaw`) accepts; above it the capture runs the exact method.
43
+ */
44
+ export const CATALOGUE_FAST_VERIFICATION_TOLERANCE = 0.005;
45
+ /** Largest cache window, in lattice points; a larger window is evaluated without caching. */
46
+ const WINDOW_LIMIT = 1 << 22;
47
+ const NEWTON_ITERATIONS = 60;
48
+ /** A kernel's intensity centroid, in samples. */
49
+ function centroid(values, width) {
50
+ let sum = 0, x = 0, y = 0;
51
+ for (let i = 0; i < values.length; i++) {
52
+ const v = values[i];
53
+ sum += v;
54
+ x += v * (i % width);
55
+ y += v * Math.floor(i / width);
56
+ }
57
+ return { x: x / sum, y: y / sum };
58
+ }
59
+ /**
60
+ * The grid axes: each interval between neighboring profile field nodes split into max(GRID_MINIMUM_SUBDIVISIONS,
61
+ * ⌈Δc / GRID_CENTROID_STEP_SITES⌉) equal cells, Δc the largest centroid move between the interval's two nodes over the other
62
+ * axis's nodes and the wavelength nodes.
63
+ */
64
+ export function catalogueFastGridAxes(psf) {
65
+ const profile = psf.profile, axes = profile.axes, fx = axes.fieldXmm, fy = axes.fieldYmm, wl = axes.wavelengthNm;
66
+ const centroids = new Map();
67
+ for (const node of profile.nodes) {
68
+ const c = node.coordinate;
69
+ centroids.set(c.fieldXmm + ":" + c.fieldYmm + ":" + c.wavelengthNm, centroid(node.kernel.normalizedIntensity, node.kernel.widthSamples));
70
+ }
71
+ const at = (x, y, nm) => {
72
+ const c = centroids.get(x + ":" + y + ":" + nm);
73
+ if (!c)
74
+ throw new InvalidConfigurationError("lens.catalogue fast: the PSF profile lacks a field node.");
75
+ return c;
76
+ };
77
+ const axis = (own, other, along) => {
78
+ const out = [];
79
+ for (let i = 0; i + 1 < own.length; i++) {
80
+ let move = 0;
81
+ for (const o of other)
82
+ for (const nm of wl) {
83
+ const a = along ? at(own[i], o, nm) : at(o, own[i], nm), b = along ? at(own[i + 1], o, nm) : at(o, own[i + 1], nm);
84
+ move = Math.max(move, Math.hypot(a.x - b.x, a.y - b.y));
85
+ }
86
+ const cells = Math.max(CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, Math.ceil(move / CATALOGUE_FAST_GRID_CENTROID_STEP_SITES - 1e-9));
87
+ for (let k = 0; k < cells; k++)
88
+ out.push(own[i] + (own[i + 1] - own[i]) * k / cells);
89
+ }
90
+ out.push(own[own.length - 1]);
91
+ return out;
92
+ };
93
+ return { x: axis(fx, fy, true), y: axis(fy, fx, false) };
94
+ }
95
+ /** The cell [i, i + 1] of an ascending axis holding v (clamped to the ends) and v's weight toward i + 1. */
96
+ function cellOf(axis, v) {
97
+ let lo = 0, hi = axis.length - 2;
98
+ while (lo < hi) {
99
+ const mid = (lo + hi + 1) >> 1;
100
+ if (axis[mid] <= v)
101
+ lo = mid;
102
+ else
103
+ hi = mid - 1;
104
+ }
105
+ const f = (v - axis[lo]) / (axis[lo + 1] - axis[lo]);
106
+ return { i: lo, f: Math.min(1, Math.max(0, f)) };
107
+ }
108
+ let testHook;
109
+ export function setCatalogueLensSignalTestHook(hook) { testHook = hook; }
110
+ /** The fast method's expected signal (see the module comment). */
111
+ export function createCatalogueLensSignal(given) {
112
+ testHook?.onInput?.(given);
113
+ const input = testHook?.exactKernels ? { ...given, exactKernels: true } : given;
114
+ const { nativeWidth: W, nativeHeight: H, psf, wavelengthsNm, electronsPerRadiance: A, scene, channelAt } = input;
115
+ const mmPerSite = input.pitch / 1000, nodes = wavelengthsNm.length;
116
+ const first = psf.profile.nodes[0];
117
+ if (!first)
118
+ throw new InvalidConfigurationError("lens.catalogue fast: the PSF profile has no nodes.");
119
+ const kw = first.kernel.widthSamples, kh = first.kernel.heightSamples, cx = first.kernel.centerSampleX, cy = first.kernel.centerSampleY, taps = kw * kh;
120
+ for (const node of psf.profile.nodes)
121
+ if (node.kernel.widthSamples !== kw || node.kernel.heightSamples !== kh || node.kernel.centerSampleX !== cx || node.kernel.centerSampleY !== cy)
122
+ throw new InvalidConfigurationError("lens.catalogue fast: the PSF profile's kernels differ in size.");
123
+ // Lattice reach of the taps around a site: x from −cx … kw − 1 − cx (as c_x − k_x), y from −cy … kh − 1 − cy.
124
+ const reachLeft = kw - 1 - cx, reachRight = cx, reachUp = cy, reachDown = kh - 1 - cy;
125
+ // Materials: neutral ones by reflectance (class 0, the white radiance), spectral ones by class 1 + index.
126
+ const M = scene.materials.length, reflectance = new Float64Array(M), materialClass = new Int32Array(M);
127
+ const spectral = [];
128
+ scene.materials.forEach((m, index) => { if ("neutral" in m)
129
+ reflectance[index] = m.neutral;
130
+ else {
131
+ materialClass[index] = 1 + spectral.length;
132
+ spectral.push(m.radiance);
133
+ } });
134
+ const classes = 1 + spectral.length, classRadiance = [scene.whiteRadiance, ...spectral];
135
+ const tableSize = 3 * classes * taps;
136
+ // W_{c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_λ(k) for the kernels of one field point.
137
+ const foldKernels = (fieldXmm, fieldYmm) => {
138
+ const table = new Float64Array(tableSize);
139
+ for (let n = 0; n < nodes; n++) {
140
+ const kernel = resolveLensSampledPsfKernel({ ...psf, wavelengthNm: wavelengthsNm[n], fieldPointMm: { x: fieldXmm, y: fieldYmm } }).normalizedIntensity;
141
+ if (kernel.length !== taps)
142
+ throw new InvalidConfigurationError("lens.catalogue fast: a resolved kernel differs in size.");
143
+ for (let c = 0; c < 3; c++) {
144
+ const a = A[c][n];
145
+ if (a === 0)
146
+ continue;
147
+ for (let cl = 0; cl < classes; cl++) {
148
+ const weight = a * classRadiance[cl][n], base = (c * classes + cl) * taps;
149
+ if (weight === 0)
150
+ continue;
151
+ for (let k = 0; k < taps; k++)
152
+ table[base + k] = table[base + k] + weight * kernel[k];
153
+ }
154
+ }
155
+ }
156
+ return table;
157
+ };
158
+ // The field grid, resolved lazily (a region render resolves only the grid points around it).
159
+ const axes = catalogueFastGridAxes(psf), gx = axes.x, gy = axes.y, GX = gx.length;
160
+ const tables = new Array(GX * gy.length);
161
+ let resolved = 0;
162
+ const tableAt = (i, j) => {
163
+ const k = j * GX + i;
164
+ let t = tables[k];
165
+ if (!t) {
166
+ t = foldKernels(gx[i], gy[j]);
167
+ tables[k] = t;
168
+ resolved++;
169
+ }
170
+ return t;
171
+ };
172
+ // Each native column's and row's grid cell and blend weight (field +Y up: row y sits at −(y + ½ − H/2)·p).
173
+ const cellX = new Int32Array(W), weightX = new Float64Array(W), cellY = new Int32Array(H), weightY = new Float64Array(H);
174
+ for (let x = 0; x < W; x++) {
175
+ const c = cellOf(gx, (x + 0.5 - W / 2) * mmPerSite);
176
+ cellX[x] = c.i;
177
+ weightX[x] = c.f;
178
+ }
179
+ for (let y = 0; y < H; y++) {
180
+ const c = cellOf(gy, -(y + 0.5 - H / 2) * mmPerSite);
181
+ cellY[y] = c.i;
182
+ weightY[y] = c.f;
183
+ }
184
+ // The scene point and field factor of a native sensor point (mm, +Y down); factor 0 where the scene radiance is zero.
185
+ const pd = resolveProjectionDistanceMm(input.focalLengthMm, input.focusDistanceM).projectionDistanceMm;
186
+ const planeScale = input.distanceM / input.metresPerFrameUnit;
187
+ const throughputAt = input.vignetting ? prepareIlluminationVignettingThroughput(input.vignetting) : undefined;
188
+ const distortion = input.distortion, rectilinear = input.projection === "rectilinear";
189
+ let mu = 0, mv = 0, mf = 0;
190
+ const map = (xMm, yMm) => {
191
+ const imageX = xMm, imageY = -yMm;
192
+ let idealX = imageX, idealY = imageY, area = 1;
193
+ if (distortion) {
194
+ const { k1, k2, k3 } = distortion.coefficients, norm = distortion.normalizationRadiusMm, rd = Math.hypot(imageX, imageY) / norm;
195
+ let rho = rd;
196
+ if (rd > 0) {
197
+ // Newton's method on ρ·s(ρ) = ρ_d (monotonic over the profile's validated range), from ρ = ρ_d.
198
+ let converged = false;
199
+ for (let it = 0; it < NEWTON_ITERATIONS; it++) {
200
+ const r2 = rho * rho, value = rho * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - rd, slope = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;
201
+ if (!(slope > 0))
202
+ break;
203
+ const step = value / slope;
204
+ rho -= step;
205
+ if (Math.abs(step) <= 4 * Number.EPSILON * rho) {
206
+ converged = true;
207
+ break;
208
+ }
209
+ }
210
+ if (!converged || !(rho > 0))
211
+ throw new InvalidConfigurationError("lens.catalogue fast: the inverse distortion did not converge.");
212
+ }
213
+ const r2 = rho * rho, s = 1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2, derivative = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;
214
+ if (rd > 0) {
215
+ idealX = imageX / s;
216
+ idealY = imageY / s;
217
+ }
218
+ area = 1 / (s * derivative);
219
+ if (!(area > 0) || !Number.isFinite(area))
220
+ throw new InvalidConfigurationError("lens.catalogue fast: the distortion area factor must stay finite and positive.");
221
+ }
222
+ const factor = (throughputAt ? throughputAt({ x: imageX, y: imageY }) : 1) * area;
223
+ if (rectilinear) {
224
+ mu = idealX / pd * planeScale;
225
+ mv = idealY / pd * planeScale;
226
+ mf = factor;
227
+ return;
228
+ }
229
+ if (input.outsideImageCircleSeesNothing && !insideRadialImageCircle(input.projection, pd, Math.hypot(idealX, idealY))) {
230
+ mf = 0;
231
+ return;
232
+ }
233
+ const d = lensProjectionDirectionValue(input.projection, pd, idealX, idealY);
234
+ if (!(d.z > 0)) {
235
+ mf = 0;
236
+ return;
237
+ }
238
+ mu = d.x * (input.distanceM / d.z) / input.metresPerFrameUnit;
239
+ mv = d.y * (input.distanceM / d.z) / input.metresPerFrameUnit;
240
+ mf = factor;
241
+ };
242
+ let lookups = 0;
243
+ let lm = 0, lf = 0;
244
+ /** The material and field factor of lattice point (p, q) (sites from the top-left site, the lattice formula). */
245
+ const lattice = (p, q) => {
246
+ map((p + 0.5 - W / 2) * mmPerSite, (q + 0.5 - H / 2) * mmPerSite);
247
+ lf = mf;
248
+ if (mf === 0) {
249
+ lm = 0;
250
+ return;
251
+ }
252
+ const m = scene.materialAt(mu, mv);
253
+ if (!(m >= 0 && m < M) || !Number.isInteger(m))
254
+ throw new InvalidConfigurationError("Unknown chart material.");
255
+ lm = m;
256
+ lookups++;
257
+ };
258
+ // The window's lattice: points [wx0, wx0 + ww) × [wy0, wy0 + wh).
259
+ let wx0 = 0, wy0 = 0, ww = 0, wh = 0, winMaterial = new Int32Array(0), winFactor = new Float64Array(0);
260
+ const window = (x0, y0, x1, y1) => {
261
+ const w = x1 - x0 + reachLeft + reachRight, h = y1 - y0 + reachUp + reachDown;
262
+ if (!(w > 0 && h > 0) || w * h > WINDOW_LIMIT) {
263
+ ww = 0;
264
+ wh = 0;
265
+ return;
266
+ }
267
+ wx0 = x0 - reachLeft;
268
+ wy0 = y0 - reachUp;
269
+ ww = w;
270
+ wh = h;
271
+ if (winMaterial.length < w * h) {
272
+ winMaterial = new Int32Array(w * h);
273
+ winFactor = new Float64Array(w * h);
274
+ }
275
+ for (let q = 0; q < h; q++)
276
+ for (let p = 0; p < w; p++) {
277
+ lattice(wx0 + p, wy0 + q);
278
+ winMaterial[q * w + p] = lm;
279
+ winFactor[q * w + p] = lf;
280
+ }
281
+ };
282
+ const kernelOf = new Float64Array(tableSize);
283
+ let cellI = -1, cellJ = -1, c00 = kernelOf, c10 = kernelOf, c01 = kernelOf, c11 = kernelOf;
284
+ const site = (x, y) => {
285
+ const c = channelAt(x, y);
286
+ let t00, t10, t01, t11, b00, b10, b01, b11;
287
+ if (input.exactKernels) {
288
+ kernelOf.set(foldKernels((x + 0.5 - W / 2) * mmPerSite, -(y + 0.5 - H / 2) * mmPerSite));
289
+ t00 = t10 = t01 = t11 = kernelOf;
290
+ b00 = 1;
291
+ b10 = b01 = b11 = 0;
292
+ }
293
+ else {
294
+ const i = cellX[x], j = cellY[y], fx = weightX[x], fy = weightY[y];
295
+ // Neighboring sites share a cell: its four tables are kept from the last site.
296
+ if (i !== cellI || j !== cellJ) {
297
+ cellI = i;
298
+ cellJ = j;
299
+ c00 = tableAt(i, j);
300
+ c10 = tableAt(i + 1, j);
301
+ c01 = tableAt(i, j + 1);
302
+ c11 = tableAt(i + 1, j + 1);
303
+ }
304
+ t00 = c00;
305
+ t10 = c10;
306
+ t01 = c01;
307
+ t11 = c11;
308
+ b00 = (1 - fx) * (1 - fy);
309
+ b10 = fx * (1 - fy);
310
+ b01 = (1 - fx) * fy;
311
+ b11 = fx * fy;
312
+ }
313
+ const inside = ww > 0 && x - reachLeft >= wx0 && y - reachUp >= wy0 && x + reachRight < wx0 + ww && y + reachDown < wy0 + wh;
314
+ const channelBase = c * classes * taps;
315
+ let sum = 0;
316
+ for (let ky = 0, k = 0; ky < kh; ky++) {
317
+ const q = y + ky - cy;
318
+ for (let kx = 0; kx < kw; kx++, k++) {
319
+ const p = x + cx - kx;
320
+ let m, f;
321
+ if (inside) {
322
+ const at = (q - wy0) * ww + p - wx0;
323
+ m = winMaterial[at];
324
+ f = winFactor[at];
325
+ }
326
+ else {
327
+ lattice(p, q);
328
+ m = lm;
329
+ f = lf;
330
+ }
331
+ if (f === 0)
332
+ continue;
333
+ const cl = materialClass[m], at = channelBase + cl * taps + k;
334
+ const w = b00 * t00[at] + b10 * t10[at] + b01 * t01[at] + b11 * t11[at];
335
+ sum += f * (cl === 0 ? reflectance[m] * w : w);
336
+ }
337
+ }
338
+ return sum;
339
+ };
340
+ return {
341
+ window, site,
342
+ get lookups() { return lookups; },
343
+ get grid() { return { columns: GX, rows: gy.length, resolved }; }
344
+ };
345
+ }
346
+ /** The `raw.expectedSignal` and `fast.lens.catalogue` assumptions of a capture the fast method rendered (kept here, out of the capture bundle's entry). */
347
+ export function catalogueFastAssumptions(run) {
348
+ const tolerance = CATALOGUE_FAST_VERIFICATION_TOLERANCE, cells = CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, step = CATALOGUE_FAST_GRID_CENTROID_STEP_SITES;
349
+ return [{ id: "raw.expectedSignal", value: { method: "tabulated-expected-electrons-catalogue-psf-field-grid", verificationSites: 4, tolerance,
350
+ verificationRelativeDifference: run.verificationRelativeDifference },
351
+ explanation: "Each native site's expected photo electrons are tabulated: the sum over the PSF taps of the field factor (vignetting × distortion area factor) times the " +
352
+ "grid-blended kernel folded with the channel response and the material radiance (see fast.lens.catalogue). The sensor stage (dark charge, Poisson draws, read noise, ADC) " +
353
+ "is the native one with each site's own seeds. One site of each CFA phase ran the exact chain (the environment PSF quadrature): its readout was reproduced bit for bit and " +
354
+ "its electrons agreed with the fast ones within the tolerance (verificationRelativeDifference: the largest difference there)." },
355
+ { id: "fast.lens.catalogue", value: { method: "field-grid-bilinear-psf", gridColumns: run.columns, gridRows: run.rows, gridPointsResolved: run.resolved,
356
+ minimumSubdivisions: cells, centroidStepSites: step, verificationTolerance: tolerance },
357
+ explanation: "Fast method (#568), an approximation of the exact one: the catalogue PSF is resolved exactly on a field grid (each interval between the profile's field nodes " +
358
+ "split into at least " + cells + " cells, more where the node kernels' centroids move by over " + step + " site per cell), and each site takes the bilinear blend of its " +
359
+ "four grid points' kernels instead of its own. Its error is at most the kernels' L1 difference times the brightest tap's signal; docs/LENS_CATALOGUE.md (\"Fast method\") " +
360
+ "records it measured against the exact method. Distortion, vignetting and projection are the exact method's (the radial inverse by Newton's method, equal to rounding)." }];
361
+ }
362
+ //# sourceMappingURL=catalogue-lens-signal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogue-lens-signal.js","sourceRoot":"","sources":["../../src/facade/catalogue-lens-signal.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #568: the fast method of `simulateCapture`'s `lens.catalogue` (requested with `fast: true` or `lens.catalogue.fast`): each\n * native site's expected photo electrons from tables instead of the environment PSF quadrature's per-tap scene queries.\n * Loaded on demand with the catalogue (`generic-lens-capture.ts`), so a capture without a catalogue lens never loads it.\n *\n * What the exact method computes. The environment PSF quadrature forms, for site q of CFA channel c,\n * μ_c(q) = Σ_λ A_c(λ) Σ_k K_{q,λ}(k)·F(s_k)·L(D(s_k), λ), s_k = q + (c_x − k_x, k_y − c_y)·p,\n * with K_{q,λ} the catalogue PSF resolved at the site's own field point (`resolveLensSampledPsfKernel`: the profile's 5 × 5 field\n * nodes × 5 wavelength nodes interpolated with centroid alignment; row index k_y increases with image +Y, `sensorPsfSourcePoint`),\n * F = T·A the field factor at the tap point (vignetting throughput × distortion area factor), D the scene ray (inverse radial\n * distortion, then the rectilinear or equisolid projection at the thin-lens image distance) and A_c(λ) the electrons per unit\n * spectral radiance on the axis (the tabulated path's channel response). Every tap of a site-centre query is a lattice point.\n *\n * The approximation (the only one). Resolving K_{q,λ} at every site and spectral node is what makes the exact method slow, so the\n * fast method resolves it exactly on a field grid and blends it bilinearly between the four grid points around each site:\n * K̃_{q,λ} = Σ_g b_g(q)·K_{g,λ}.\n * The grid subdivides each interval between the profile's field nodes into at least GRID_MINIMUM_SUBDIVISIONS cells, and into\n * more where the node kernels' intensity centroids move by more than GRID_CENTROID_STEP_SITES sites across the interval (lateral\n * colour moves the PSF across the frame; a blend of two shifted kernels is not the shifted kernel). Since the blend is linear,\n * the channel response and the material radiance fold into the grid kernels once per grid point:\n * μ̃_c(q) = Σ_k F(s_k)·Σ_g b_g(q)·W_{g,c,m(s_k)}(k), W_{g,c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_{g,λ}(k),\n * so a site costs (2R + 1)² taps instead of (2R + 1)² scene queries per spectral node. Its error against the exact method is\n * bounded by ‖K̃ − K‖₁ times the brightest tap's signal; tests measure both (test/simulate-capture-lens-catalogue-fast.test.ts).\n *\n * The scene points. The mapping is the environment's (`undistortedImagePoint`, `lensProjectionDirectionValue`, the planar\n * provider's plane point), with the radial inverse solved by Newton's method instead of bisection: both converge to the same\n * root to rounding. Outside a circular fisheye's image circle (`outsideImageCircle: \"backward-ray\"`) and beyond the horizon the\n * scene radiance is zero, as in the capture.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { insideRadialImageCircle, lensProjectionDirectionValue } from \"../camera/lens-projection.js\";\nimport { resolveProjectionDistanceMm } from \"../motion/camera-rotation.js\";\nimport { prepareIlluminationVignettingThroughput, type IlluminationVignettingProfile } from \"../optics/illumination-vignetting.js\";\nimport { resolveLensSampledPsfKernel, type ResolveLensSampledPsfInput } from \"../optics/lens-psf-profile.js\";\nimport type { DisplayViewScene } from \"./expected-signal.js\";\n\n/** Fewest grid cells between neighboring profile field nodes. */\nexport const CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS = 4;\n/** Largest move of a node kernel's intensity centroid across one grid cell, in sites (sets finer grids where lateral color moves the PSF). */\nexport const CATALOGUE_FAST_GRID_CENTROID_STEP_SITES = 0.05;\n/**\n * Largest relative difference between the fast and the exact expected electrons that the run-time verification (one site per\n * CFA phase through the exact chain, `renderTabulatedNativeRaw`) accepts; above it the capture runs the exact method.\n */\nexport const CATALOGUE_FAST_VERIFICATION_TOLERANCE = 0.005;\n/** Largest cache window, in lattice points; a larger window is evaluated without caching. */\nconst WINDOW_LIMIT = 1 << 22;\nconst NEWTON_ITERATIONS = 60;\n\nexport interface CatalogueLensSignalInput {\n nativeWidth: number;\n nativeHeight: number;\n /** Site pitch, µm. */\n pitch: number;\n /** The catalogue PSF's optical state (profile, focal length, focus, f-number, defocus), as the site inputs' `psf.configuration.psf`. */\n psf: Omit<ResolveLensSampledPsfInput, \"fieldPointMm\" | \"wavelengthNm\">;\n /** Spectral nodes and, per CFA channel (red, green, blue), the expected electrons per unit spectral radiance at each node. */\n wavelengthsNm: readonly number[];\n electronsPerRadiance: readonly [Float64Array, Float64Array, Float64Array];\n /** The scene's chart materials (the tabulated path's material tables). */\n scene: Extract<DisplayViewScene, { kind: \"materials\" }>;\n /** CFA channel index (0 red, 1 green, 2 blue) of native site (x, y). */\n channelAt(x: number, y: number): number;\n /** The radial distortion profile (r_d = r·(1 + k1ρ² + k2ρ⁴ + k3ρ⁶), ρ = r / normalizationRadiusMm); absent without distortion. */\n distortion?: { normalizationRadiusMm: number; coefficients: { k1: number; k2: number; k3: number } };\n vignetting?: IlluminationVignettingProfile;\n projection: \"rectilinear\" | \"fisheye-equisolid-angle\";\n /** True for a catalogue fisheye: a sensor point outside the image circle looks straight back and sees no scene. */\n outsideImageCircleSeesNothing: boolean;\n focalLengthMm: number;\n focusDistanceM: number;\n /** The planar provider's plane distance and frame-unit scale. */\n distanceM: number;\n metresPerFrameUnit: number;\n /** Internal test hook: resolve every site's kernels exactly (the exact method's sum, slowly) instead of blending the grid. */\n exactKernels?: boolean;\n}\n\nexport interface CatalogueLensSignal {\n /** Starts a cache window of native sites [x0, x1) × [y0, y1) (see `DisplayViewExpectedSignal.window`). */\n window(x0: number, y0: number, x1: number, y1: number): void;\n /** Expected photo electrons of native site (x, y) for its own CFA channel. */\n site(x: number, y: number): number;\n /** Scene material lookups so far. */\n readonly lookups: number;\n /** Grid points per axis (columns, rows) and how many were resolved so far. */\n readonly grid: { columns: number; rows: number; resolved: number };\n}\n\n/** A kernel's intensity centroid, in samples. */\nfunction centroid(values: readonly number[], width: number): { x: number; y: number } {\n let sum = 0, x = 0, y = 0;\n for (let i = 0; i < values.length; i++) { const v = values[i]!; sum += v; x += v * (i % width); y += v * Math.floor(i / width); }\n return { x: x / sum, y: y / sum };\n}\n\n/**\n * The grid axes: each interval between neighboring profile field nodes split into max(GRID_MINIMUM_SUBDIVISIONS,\n * ⌈Δc / GRID_CENTROID_STEP_SITES⌉) equal cells, Δc the largest centroid move between the interval's two nodes over the other\n * axis's nodes and the wavelength nodes.\n */\nexport function catalogueFastGridAxes(psf: CatalogueLensSignalInput[\"psf\"]): { x: number[]; y: number[] } {\n const profile = psf.profile, axes = profile.axes, fx = axes.fieldXmm, fy = axes.fieldYmm, wl = axes.wavelengthNm;\n const centroids = new Map<string, { x: number; y: number }>();\n for (const node of profile.nodes) {\n const c = node.coordinate;\n centroids.set(c.fieldXmm + \":\" + c.fieldYmm + \":\" + c.wavelengthNm, centroid(node.kernel.normalizedIntensity, node.kernel.widthSamples));\n }\n const at = (x: number, y: number, nm: number): { x: number; y: number } => {\n const c = centroids.get(x + \":\" + y + \":\" + nm);\n if (!c) throw new InvalidConfigurationError(\"lens.catalogue fast: the PSF profile lacks a field node.\");\n return c;\n };\n const axis = (own: readonly number[], other: readonly number[], along: boolean): number[] => {\n const out: number[] = [];\n for (let i = 0; i + 1 < own.length; i++) {\n let move = 0;\n for (const o of other) for (const nm of wl) {\n const a = along ? at(own[i]!, o, nm) : at(o, own[i]!, nm), b = along ? at(own[i + 1]!, o, nm) : at(o, own[i + 1]!, nm);\n move = Math.max(move, Math.hypot(a.x - b.x, a.y - b.y));\n }\n const cells = Math.max(CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, Math.ceil(move / CATALOGUE_FAST_GRID_CENTROID_STEP_SITES - 1e-9));\n for (let k = 0; k < cells; k++) out.push(own[i]! + (own[i + 1]! - own[i]!) * k / cells);\n }\n out.push(own[own.length - 1]!);\n return out;\n };\n return { x: axis(fx, fy, true), y: axis(fy, fx, false) };\n}\n\n/** The cell [i, i + 1] of an ascending axis holding v (clamped to the ends) and v's weight toward i + 1. */\nfunction cellOf(axis: readonly number[], v: number): { i: number; f: number } {\n let lo = 0, hi = axis.length - 2;\n while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (axis[mid]! <= v) lo = mid; else hi = mid - 1; }\n const f = (v - axis[lo]!) / (axis[lo + 1]! - axis[lo]!);\n return { i: lo, f: Math.min(1, Math.max(0, f)) };\n}\n\n/**\n * Internal test hook (never set by the engine): `exactKernels` makes the facade's signals resolve every site's kernels exactly;\n * `onInput` receives each input the facade builds, so a test can compare the grid blend with the exact sum site by site.\n */\nexport interface CatalogueLensSignalTestHook { exactKernels?: boolean; onInput?(input: CatalogueLensSignalInput): void }\nlet testHook: CatalogueLensSignalTestHook | undefined;\nexport function setCatalogueLensSignalTestHook(hook: CatalogueLensSignalTestHook | undefined): void { testHook = hook; }\n\n/** The fast method's expected signal (see the module comment). */\nexport function createCatalogueLensSignal(given: CatalogueLensSignalInput): CatalogueLensSignal {\n testHook?.onInput?.(given);\n const input = testHook?.exactKernels ? { ...given, exactKernels: true } : given;\n const { nativeWidth: W, nativeHeight: H, psf, wavelengthsNm, electronsPerRadiance: A, scene, channelAt } = input;\n const mmPerSite = input.pitch / 1000, nodes = wavelengthsNm.length;\n const first = psf.profile.nodes[0];\n if (!first) throw new InvalidConfigurationError(\"lens.catalogue fast: the PSF profile has no nodes.\");\n const kw = first.kernel.widthSamples, kh = first.kernel.heightSamples, cx = first.kernel.centerSampleX, cy = first.kernel.centerSampleY, taps = kw * kh;\n for (const node of psf.profile.nodes) if (node.kernel.widthSamples !== kw || node.kernel.heightSamples !== kh || node.kernel.centerSampleX !== cx || node.kernel.centerSampleY !== cy)\n throw new InvalidConfigurationError(\"lens.catalogue fast: the PSF profile's kernels differ in size.\");\n // Lattice reach of the taps around a site: x from −cx … kw − 1 − cx (as c_x − k_x), y from −cy … kh − 1 − cy.\n const reachLeft = kw - 1 - cx, reachRight = cx, reachUp = cy, reachDown = kh - 1 - cy;\n // Materials: neutral ones by reflectance (class 0, the white radiance), spectral ones by class 1 + index.\n const M = scene.materials.length, reflectance = new Float64Array(M), materialClass = new Int32Array(M);\n const spectral: Float64Array[] = [];\n scene.materials.forEach((m, index) => { if (\"neutral\" in m) reflectance[index] = m.neutral; else { materialClass[index] = 1 + spectral.length; spectral.push(m.radiance); } });\n const classes = 1 + spectral.length, classRadiance = [scene.whiteRadiance, ...spectral];\n const tableSize = 3 * classes * taps;\n // W_{c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_λ(k) for the kernels of one field point.\n const foldKernels = (fieldXmm: number, fieldYmm: number): Float64Array => {\n const table = new Float64Array(tableSize);\n for (let n = 0; n < nodes; n++) {\n const kernel = resolveLensSampledPsfKernel({ ...psf, wavelengthNm: wavelengthsNm[n]!, fieldPointMm: { x: fieldXmm, y: fieldYmm } }).normalizedIntensity;\n if (kernel.length !== taps) throw new InvalidConfigurationError(\"lens.catalogue fast: a resolved kernel differs in size.\");\n for (let c = 0; c < 3; c++) {\n const a = A[c]![n]!;\n if (a === 0) continue;\n for (let cl = 0; cl < classes; cl++) {\n const weight = a * classRadiance[cl]![n]!, base = (c * classes + cl) * taps;\n if (weight === 0) continue;\n for (let k = 0; k < taps; k++) table[base + k] = table[base + k]! + weight * kernel[k]!;\n }\n }\n }\n return table;\n };\n // The field grid, resolved lazily (a region render resolves only the grid points around it).\n const axes = catalogueFastGridAxes(psf), gx = axes.x, gy = axes.y, GX = gx.length;\n const tables: (Float64Array | undefined)[] = new Array(GX * gy.length);\n let resolved = 0;\n const tableAt = (i: number, j: number): Float64Array => {\n const k = j * GX + i;\n let t = tables[k];\n if (!t) { t = foldKernels(gx[i]!, gy[j]!); tables[k] = t; resolved++; }\n return t;\n };\n // Each native column's and row's grid cell and blend weight (field +Y up: row y sits at −(y + ½ − H/2)·p).\n const cellX = new Int32Array(W), weightX = new Float64Array(W), cellY = new Int32Array(H), weightY = new Float64Array(H);\n for (let x = 0; x < W; x++) { const c = cellOf(gx, (x + 0.5 - W / 2) * mmPerSite); cellX[x] = c.i; weightX[x] = c.f; }\n for (let y = 0; y < H; y++) { const c = cellOf(gy, -(y + 0.5 - H / 2) * mmPerSite); cellY[y] = c.i; weightY[y] = c.f; }\n\n // The scene point and field factor of a native sensor point (mm, +Y down); factor 0 where the scene radiance is zero.\n const pd = resolveProjectionDistanceMm(input.focalLengthMm, input.focusDistanceM).projectionDistanceMm;\n const planeScale = input.distanceM / input.metresPerFrameUnit;\n const throughputAt = input.vignetting ? prepareIlluminationVignettingThroughput(input.vignetting) : undefined;\n const distortion = input.distortion, rectilinear = input.projection === \"rectilinear\";\n let mu = 0, mv = 0, mf = 0;\n const map = (xMm: number, yMm: number): void => {\n const imageX = xMm, imageY = -yMm;\n let idealX = imageX, idealY = imageY, area = 1;\n if (distortion) {\n const { k1, k2, k3 } = distortion.coefficients, norm = distortion.normalizationRadiusMm, rd = Math.hypot(imageX, imageY) / norm;\n let rho = rd;\n if (rd > 0) {\n // Newton's method on ρ·s(ρ) = ρ_d (monotonic over the profile's validated range), from ρ = ρ_d.\n let converged = false;\n for (let it = 0; it < NEWTON_ITERATIONS; it++) {\n const r2 = rho * rho, value = rho * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - rd, slope = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;\n if (!(slope > 0)) break;\n const step = value / slope;\n rho -= step;\n if (Math.abs(step) <= 4 * Number.EPSILON * rho) { converged = true; break; }\n }\n if (!converged || !(rho > 0)) throw new InvalidConfigurationError(\"lens.catalogue fast: the inverse distortion did not converge.\");\n }\n const r2 = rho * rho, s = 1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2, derivative = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;\n if (rd > 0) { idealX = imageX / s; idealY = imageY / s; }\n area = 1 / (s * derivative);\n if (!(area > 0) || !Number.isFinite(area)) throw new InvalidConfigurationError(\"lens.catalogue fast: the distortion area factor must stay finite and positive.\");\n }\n const factor = (throughputAt ? throughputAt({ x: imageX, y: imageY }) : 1) * area;\n if (rectilinear) { mu = idealX / pd * planeScale; mv = idealY / pd * planeScale; mf = factor; return; }\n if (input.outsideImageCircleSeesNothing && !insideRadialImageCircle(input.projection, pd, Math.hypot(idealX, idealY))) { mf = 0; return; }\n const d = lensProjectionDirectionValue(input.projection, pd, idealX, idealY);\n if (!(d.z > 0)) { mf = 0; return; }\n mu = d.x * (input.distanceM / d.z) / input.metresPerFrameUnit; mv = d.y * (input.distanceM / d.z) / input.metresPerFrameUnit; mf = factor;\n };\n let lookups = 0;\n let lm = 0, lf = 0;\n /** The material and field factor of lattice point (p, q) (sites from the top-left site, the lattice formula). */\n const lattice = (p: number, q: number): void => {\n map((p + 0.5 - W / 2) * mmPerSite, (q + 0.5 - H / 2) * mmPerSite);\n lf = mf;\n if (mf === 0) { lm = 0; return; }\n const m = scene.materialAt(mu, mv);\n if (!(m >= 0 && m < M) || !Number.isInteger(m)) throw new InvalidConfigurationError(\"Unknown chart material.\");\n lm = m; lookups++;\n };\n // The window's lattice: points [wx0, wx0 + ww) × [wy0, wy0 + wh).\n let wx0 = 0, wy0 = 0, ww = 0, wh = 0, winMaterial = new Int32Array(0), winFactor = new Float64Array(0);\n const window = (x0: number, y0: number, x1: number, y1: number): void => {\n const w = x1 - x0 + reachLeft + reachRight, h = y1 - y0 + reachUp + reachDown;\n if (!(w > 0 && h > 0) || w * h > WINDOW_LIMIT) { ww = 0; wh = 0; return; }\n wx0 = x0 - reachLeft; wy0 = y0 - reachUp; ww = w; wh = h;\n if (winMaterial.length < w * h) { winMaterial = new Int32Array(w * h); winFactor = new Float64Array(w * h); }\n for (let q = 0; q < h; q++) for (let p = 0; p < w; p++) { lattice(wx0 + p, wy0 + q); winMaterial[q * w + p] = lm; winFactor[q * w + p] = lf; }\n };\n const kernelOf = new Float64Array(tableSize);\n let cellI = -1, cellJ = -1, c00: Float64Array = kernelOf, c10: Float64Array = kernelOf, c01: Float64Array = kernelOf, c11: Float64Array = kernelOf;\n const site = (x: number, y: number): number => {\n const c = channelAt(x, y);\n let t00: Float64Array, t10: Float64Array, t01: Float64Array, t11: Float64Array, b00: number, b10: number, b01: number, b11: number;\n if (input.exactKernels) {\n kernelOf.set(foldKernels((x + 0.5 - W / 2) * mmPerSite, -(y + 0.5 - H / 2) * mmPerSite));\n t00 = t10 = t01 = t11 = kernelOf; b00 = 1; b10 = b01 = b11 = 0;\n } else {\n const i = cellX[x]!, j = cellY[y]!, fx = weightX[x]!, fy = weightY[y]!;\n // Neighboring sites share a cell: its four tables are kept from the last site.\n if (i !== cellI || j !== cellJ) { cellI = i; cellJ = j; c00 = tableAt(i, j); c10 = tableAt(i + 1, j); c01 = tableAt(i, j + 1); c11 = tableAt(i + 1, j + 1); }\n t00 = c00; t10 = c10; t01 = c01; t11 = c11;\n b00 = (1 - fx) * (1 - fy); b10 = fx * (1 - fy); b01 = (1 - fx) * fy; b11 = fx * fy;\n }\n const inside = ww > 0 && x - reachLeft >= wx0 && y - reachUp >= wy0 && x + reachRight < wx0 + ww && y + reachDown < wy0 + wh;\n const channelBase = c * classes * taps;\n let sum = 0;\n for (let ky = 0, k = 0; ky < kh; ky++) {\n const q = y + ky - cy;\n for (let kx = 0; kx < kw; kx++, k++) {\n const p = x + cx - kx;\n let m: number, f: number;\n if (inside) { const at = (q - wy0) * ww + p - wx0; m = winMaterial[at]!; f = winFactor[at]!; } else { lattice(p, q); m = lm; f = lf; }\n if (f === 0) continue;\n const cl = materialClass[m]!, at = channelBase + cl * taps + k;\n const w = b00 * t00[at]! + b10 * t10[at]! + b01 * t01[at]! + b11 * t11[at]!;\n sum += f * (cl === 0 ? reflectance[m]! * w : w);\n }\n }\n return sum;\n };\n return {\n window, site,\n get lookups(): number { return lookups; },\n get grid(): { columns: number; rows: number; resolved: number } { return { columns: GX, rows: gy.length, resolved }; }\n };\n}\n\n/** The `raw.expectedSignal` and `fast.lens.catalogue` assumptions of a capture the fast method rendered (kept here, out of the capture bundle's entry). */\nexport function catalogueFastAssumptions(run: { columns: number; rows: number; resolved: number; verificationRelativeDifference: number }):\n { id: string; value: Record<string, unknown>; explanation: string }[] {\n const tolerance = CATALOGUE_FAST_VERIFICATION_TOLERANCE, cells = CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, step = CATALOGUE_FAST_GRID_CENTROID_STEP_SITES;\n return [{ id: \"raw.expectedSignal\", value: { method: \"tabulated-expected-electrons-catalogue-psf-field-grid\", verificationSites: 4, tolerance,\n verificationRelativeDifference: run.verificationRelativeDifference },\n explanation: \"Each native site's expected photo electrons are tabulated: the sum over the PSF taps of the field factor (vignetting × distortion area factor) times the \" +\n \"grid-blended kernel folded with the channel response and the material radiance (see fast.lens.catalogue). The sensor stage (dark charge, Poisson draws, read noise, ADC) \" +\n \"is the native one with each site's own seeds. One site of each CFA phase ran the exact chain (the environment PSF quadrature): its readout was reproduced bit for bit and \" +\n \"its electrons agreed with the fast ones within the tolerance (verificationRelativeDifference: the largest difference there).\" },\n { id: \"fast.lens.catalogue\", value: { method: \"field-grid-bilinear-psf\", gridColumns: run.columns, gridRows: run.rows, gridPointsResolved: run.resolved,\n minimumSubdivisions: cells, centroidStepSites: step, verificationTolerance: tolerance },\n explanation: \"Fast method (#568), an approximation of the exact one: the catalogue PSF is resolved exactly on a field grid (each interval between the profile's field nodes \" +\n \"split into at least \" + cells + \" cells, more where the node kernels' centroids move by over \" + step + \" site per cell), and each site takes the bilinear blend of its \" +\n \"four grid points' kernels instead of its own. Its error is at most the kernels' L1 difference times the brightest tap's signal; docs/LENS_CATALOGUE.md (\\\"Fast method\\\") \" +\n \"records it measured against the exact method. Distortion, vignetting and projection are the exact method's (the radial inverse by Newton's method, equal to rounding).\" }];\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import type { SiteKernelFieldZones } from "./diffraction-provider.js";
2
+ import { type DefocusBokehSiteKernel, type ResolvedDefocusBokehOptions } from "../optics/defocus-bokeh-kernel.js";
3
+ export interface DefocusBokehZonePlan {
4
+ zones: SiteKernelFieldZones;
5
+ /** Field tangent of each zone centre (output-image axes, +Y up), row-major. */
6
+ fields: readonly {
7
+ x: number;
8
+ y: number;
9
+ }[];
10
+ /** Smallest open-area ratio over the zone centres, and the largest change between two zones sharing an edge. */
11
+ minimumOpenAreaRatio: number;
12
+ largestAdjacentOpenAreaRatioChange: number;
13
+ }
14
+ /**
15
+ * Plans the zones. `fieldOf(x, y)` is the field tangent of native point (x, y) in sites from the raster's top-left corner;
16
+ * `kernelAt(field)` builds a zone's kernel (its radius at most `radius`). Kernels are built on first use by the gather.
17
+ */
18
+ export declare function planDefocusBokehZones(input: {
19
+ options: ResolvedDefocusBokehOptions;
20
+ nativeWidth: number;
21
+ nativeHeight: number;
22
+ radius: number;
23
+ fNumber: number;
24
+ focalLengthMm: number;
25
+ fieldOf: (x: number, y: number) => {
26
+ x: number;
27
+ y: number;
28
+ };
29
+ kernelAt: (field: {
30
+ x: number;
31
+ y: number;
32
+ }) => DefocusBokehSiteKernel;
33
+ }): DefocusBokehZonePlan;