@photivra/engine 2.14.1 → 2.16.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 (80) hide show
  1. package/CHANGELOG.md +69 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/THIRD_PARTY.md +14 -0
  6. package/dist/capture/native-environment-lean.js +1 -1
  7. package/dist/capture/native-environment-lean.js.map +1 -1
  8. package/dist/capture/native-raw-development.d.ts +15 -0
  9. package/dist/capture/native-raw-development.js +12 -6
  10. package/dist/capture/native-raw-development.js.map +1 -1
  11. package/dist/capture/raw-development-kernel.d.ts +10 -3
  12. package/dist/capture/raw-development-kernel.js +25 -6
  13. package/dist/capture/raw-development-kernel.js.map +1 -1
  14. package/dist/core/internal-radiance-evaluator.d.ts +5 -2
  15. package/dist/core/internal-radiance-evaluator.js.map +1 -1
  16. package/dist/core/version.d.ts +1 -1
  17. package/dist/core/version.js +1 -1
  18. package/dist/core/version.js.map +1 -1
  19. package/dist/facade/defocus-image-space-provider.d.ts +109 -0
  20. package/dist/facade/defocus-image-space-provider.js +359 -0
  21. package/dist/facade/defocus-image-space-provider.js.map +1 -0
  22. package/dist/facade/diffraction-provider.d.ts +16 -0
  23. package/dist/facade/diffraction-provider.js +3 -3
  24. package/dist/facade/diffraction-provider.js.map +1 -1
  25. package/dist/facade/display-view.d.ts +21 -0
  26. package/dist/facade/display-view.js +66 -8
  27. package/dist/facade/display-view.js.map +1 -1
  28. package/dist/facade/simulate-capture.d.ts +112 -3
  29. package/dist/facade/simulate-capture.js +235 -48
  30. package/dist/facade/simulate-capture.js.map +1 -1
  31. package/dist/facade/simulate-display-view.js +7 -3
  32. package/dist/facade/simulate-display-view.js.map +1 -1
  33. package/dist/gpu/display-view-quality-gpu.d.ts +2 -0
  34. package/dist/gpu/display-view-quality-gpu.js +57 -13
  35. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  36. package/dist/gpu/display-view-speed-gpu.d.ts +2 -0
  37. package/dist/gpu/display-view-speed-gpu.js +45 -16
  38. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  39. package/dist/gpu/image-kernel-accelerator.js +3 -0
  40. package/dist/gpu/image-kernel-accelerator.js.map +1 -1
  41. package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -0
  42. package/dist/gpu/wgsl-display-view-quality-kernel.js +53 -2
  43. package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
  44. package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +15 -0
  45. package/dist/gpu/wgsl-display-view-speed-kernel.js +42 -1
  46. package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
  47. package/dist/index.d.ts +3 -2
  48. package/dist/index.js +2 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/optics/defocus-disc-kernel.d.ts +91 -1
  51. package/dist/optics/defocus-disc-kernel.js +59 -5
  52. package/dist/optics/defocus-disc-kernel.js.map +1 -1
  53. package/dist/optics/diffraction-psf-profile.d.ts +9 -4
  54. package/dist/optics/diffraction-psf-profile.js +14 -6
  55. package/dist/optics/diffraction-psf-profile.js.map +1 -1
  56. package/dist/optics/lens-psf-kernels.d.ts +13 -2
  57. package/dist/optics/lens-psf-kernels.js +23 -6
  58. package/dist/optics/lens-psf-kernels.js.map +1 -1
  59. package/dist/optics/sensor-environment-query.d.ts +31 -0
  60. package/dist/optics/sensor-environment-query.js +45 -13
  61. package/dist/optics/sensor-environment-query.js.map +1 -1
  62. package/dist/optics/site-diffraction-kernel.d.ts +1 -1
  63. package/dist/optics/site-diffraction-kernel.js +37 -17
  64. package/dist/optics/site-diffraction-kernel.js.map +1 -1
  65. package/dist/processing/development-detail.d.ts +132 -0
  66. package/dist/processing/development-detail.js +325 -0
  67. package/dist/processing/development-detail.js.map +1 -0
  68. package/docs/AGENT_GUIDE.md +1 -0
  69. package/docs/API_REFERENCE.md +17 -1
  70. package/docs/QUICK_START.md +210 -16
  71. package/docs/USAGE.md +31 -3
  72. package/docs/api/capture-native-raw-development.md +16 -0
  73. package/docs/api/core-version.md +1 -1
  74. package/docs/api/exports.json +47 -2
  75. package/docs/api/facade-simulate-capture.md +129 -4
  76. package/docs/api/optics-defocus-disc-kernel.md +87 -0
  77. package/docs/api/optics-diffraction-psf-profile.md +6 -3
  78. package/docs/api/optics-lens-psf-kernels.md +3 -2
  79. package/llms.txt +1 -0
  80. package/package.json +1 -1
@@ -0,0 +1,109 @@
1
+ import { type CameraAngularVelocityRadPerSec } from "../motion/camera-rotation.js";
2
+ import { type IlluminationVignettingProfile } from "../optics/illumination-vignetting.js";
3
+ import { type PreparedEnvironmentLensFieldMapping } from "../optics/sensor-environment-query.js";
4
+ import type { PlanarSceneRadianceProvider } from "./scene-radiance.js";
5
+ /** The camera geometry the environment uses for each site query (the same options as the site inputs). */
6
+ export interface DefocusImageSpaceGeometry {
7
+ focalLengthMm: number;
8
+ focusDistanceM: number;
9
+ angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;
10
+ /** The site inputs' `lensFieldMapping`, prepared; omitted for the ideal rectilinear, distortion-free mapping. */
11
+ lensMapping?: PreparedEnvironmentLensFieldMapping;
12
+ /** The site inputs' radial-illumination-vignetting profile; omitted for unity field throughput. */
13
+ vignetting?: IlluminationVignettingProfile;
14
+ }
15
+ /** Internal: the scene ray and field factor of sensor points, and the sensor point of a scene ray, for one geometry. */
16
+ export interface PreparedDefocusImageSpaceGeometry {
17
+ /**
18
+ * The unit look direction (toward the scene, reference camera axes at exposure start) of native sensor point (x, y) mm
19
+ * (+X right, +Y down) at time t, and its field factor F = T·A (vignetting throughput × distortion area factor).
20
+ */
21
+ lookOf(xMm: number, yMm: number, timeSeconds: number): {
22
+ x: number;
23
+ y: number;
24
+ z: number;
25
+ factor: number;
26
+ };
27
+ /** The native sensor point (mm, +Y down) whose scene ray at time t is the look direction; undefined outside the mapping. */
28
+ sensorPointOf(look: {
29
+ x: number;
30
+ y: number;
31
+ z: number;
32
+ }, timeSeconds: number): {
33
+ x: number;
34
+ y: number;
35
+ } | undefined;
36
+ /** True with camera rotation: the ray of a sensor point depends on time. */
37
+ timeDependent: boolean;
38
+ }
39
+ /** Internal: prepares {@link PreparedDefocusImageSpaceGeometry} with the environment's own conventions and arithmetic. */
40
+ export declare function prepareDefocusImageSpaceGeometry(geometry: DefocusImageSpaceGeometry): PreparedDefocusImageSpaceGeometry;
41
+ export interface DefocusImageSpaceProviderInput {
42
+ base: PlanarSceneRadianceProvider;
43
+ geometry: PreparedDefocusImageSpaceGeometry;
44
+ /** Each spectral node's kernel on the site lattice, (2·kernelRadius + 1)² taps, row-major (the disc, or diffraction ⊛ disc). */
45
+ siteKernels: (wavelengthNm: number) => Float64Array;
46
+ wavelengthsNm: readonly number[];
47
+ kernelRadius: number;
48
+ nativeWidth: number;
49
+ nativeHeight: number;
50
+ sitePitchMicrometers: number;
51
+ /** Planar geometry of the base provider (plane distance and frame-unit scale), to find the plane point of a ray as it does. */
52
+ distanceM: number;
53
+ metresPerFrameUnit: number;
54
+ /** Offsets of the query points from the site centres, in sites (+x right, +y down). Default: one zero offset. */
55
+ queryOffsetsSites?: readonly {
56
+ x: number;
57
+ y: number;
58
+ }[];
59
+ /** Region renders: the native sites that will be queried. Values are bit-identical to the whole render's. Default: the whole raster. */
60
+ window?: {
61
+ x: number;
62
+ y: number;
63
+ width: number;
64
+ height: number;
65
+ };
66
+ /** Most distinct request times kept per band (the temporal sample count). Default 1. */
67
+ timesPerBand?: number;
68
+ /** Called with the duration in ms of each band convolution (diagnostics only). */
69
+ onConvolution?: (milliseconds: number) => void;
70
+ }
71
+ /** Internal (not exported from the package): the base provider with the defocus disc applied in image space (#548). */
72
+ export declare function createDefocusImageSpaceProvider(input: DefocusImageSpaceProviderInput): PlanarSceneRadianceProvider;
73
+ /** Field-variation bounds of the image-space disc for one request (#548), recorded in the `lens.defocus` assumption. */
74
+ export interface DefocusFieldVariation {
75
+ /** Sensor points the bounds were evaluated at: a grid of this many points per side over the native raster, edges included. */
76
+ gridPointsPerSide: number;
77
+ /**
78
+ * Camera rotation (pitch or yaw tilts the scene plane in the camera frame): the largest change of the disc diameter, in
79
+ * sites, between the axial depth of each scene point in the rotated camera frame and the subject distance, over the grid and
80
+ * the rendered temporal midpoints. 0 for roll only; null without rotation.
81
+ */
82
+ rotationMaximumDiameterChangeSites: number | null;
83
+ /**
84
+ * Distortion: the largest deviation from 1 of the local scale of the distortion map (singular values of d distorted / d ideal)
85
+ * over the grid: the relative disc-size difference to a model that blurs the ideal image and then distorts it. Null without distortion.
86
+ */
87
+ distortionMaximumLocalScaleDeviation: number | null;
88
+ /**
89
+ * A non-rectilinear projection: the largest change of the disc diameter, in sites, between the distance along each scene
90
+ * point's ray and its axial depth (the thin-lens model uses the axial depth). Null for the rectilinear projection.
91
+ */
92
+ projectionMaximumDiameterChangeSites: number | null;
93
+ }
94
+ /** Internal: {@link DefocusFieldVariation} on a 9 × 9 grid of sensor points (the extremes of these smooth maps lie on the frame's edges). */
95
+ export declare function calculateDefocusFieldVariation(input: {
96
+ geometry: PreparedDefocusImageSpaceGeometry;
97
+ lensMapping?: PreparedEnvironmentLensFieldMapping;
98
+ focalLengthMm: number;
99
+ fNumber: number;
100
+ focusDistanceM: number;
101
+ subjectDistanceM: number;
102
+ pitchMicrometers: number;
103
+ nativeWidth: number;
104
+ nativeHeight: number;
105
+ angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;
106
+ temporalMidpointsSeconds: readonly number[];
107
+ diameterSites: number;
108
+ discAt: (subjectDistanceM: number) => number;
109
+ }): DefocusFieldVariation;
@@ -0,0 +1,359 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * `simulateCapture`'s `lens.defocus` out of focus together with a lens field mapping (`lens.projection`,
4
+ * `lens.distortion`), `lens.vignetting` or camera rotation (#548): the geometric defocus disc applied in image space,
5
+ * on the site lattice of the sensor, instead of on the scene plane.
6
+ *
7
+ * Why the object-space path does not apply. `createDiffractedPlanarProvider` convolves the scene radiance on the plane
8
+ * lattice that the sites see through one linear map (a still, rectilinear, distortion-free camera with the same
9
+ * radiance-to-irradiance factor everywhere). A projection, a distortion or a rotation makes the map from sensor points
10
+ * to plane points non-linear (or time-dependent), and vignetting makes the factor vary over the field, so the disc on
11
+ * the plane is no longer the disc on the sensor.
12
+ *
13
+ * The image-space model. The environment PSF quadrature (`calculateSensorPsfIrradianceQuadrature`, the per-tap path
14
+ * `lens.diffraction` falls back to) defines a sensor-plane PSF K as
15
+ * E(q, λ, t) = Σ_k K(k)·F(s_k)·C·L(D_t(s_k), λ),
16
+ * with s_k = q + k·p the tap source points on the sensor (p the site pitch), D_t the scene ray of a sensor point at
17
+ * time t (inverse distortion, the declared projection, the camera rotation at t), F = T·A the field factor (vignetting
18
+ * throughput T, applied once before the PSF spreads the light, and the distortion area factor A = 1/det J) and C the
19
+ * paraxial radiance-to-irradiance factor π/(4·N_w²), the same everywhere. With K the defocus disc sampled on the site
20
+ * lattice (`createDefocusDiscSiteKernel`), every source point of a site-centre query is another lattice point, so
21
+ * E(q, λ, t) = C·G_t(q, λ), G_t = K ⊛ (F·L∘D_t) sampled on the lattice,
22
+ * the same sum in a different floating-point order. This provider computes G_t once per spectral node and time on the
23
+ * sensor lattice (in row bands, as the diffraction provider does) and answers the query of lattice point q at time t with
24
+ * G_t(q, λ) / F(q); the environment multiplies by F(q)·C again, so each site gets C·G_t(q, λ). For the still,
25
+ * rectilinear, distortion-free camera without vignetting it is the object-space convolution (F = 1 and D linear).
26
+ *
27
+ * The lattices. As in the diffraction provider, a pixel aperture or anti-aliasing spots add query offsets; each distinct
28
+ * fractional offset gets its own shifted lattice of sensor points, and every tap of a query on one lattice is on the same
29
+ * lattice. A query is located on its lattice by mapping its scene ray back to the sensor (camera rotation inverted at the
30
+ * request's time, the forward projection, the forward distortion), within 1e-4 site; a query that lands on no lattice point
31
+ * is refused rather than approximated.
32
+ *
33
+ * Time. With camera rotation each environment temporal midpoint is a still camera, so the disc is applied to that
34
+ * instant's image in that instant's frame: one G_t per distinct request time, cached per band. Without rotation the image
35
+ * does not depend on time and one G is kept. The facade refuses rotation with a rolling shutter (each row's instants differ).
36
+ *
37
+ * What is approximate is the disc itself (`lens.defocus` labels it; the facade records the field-variation bounds of
38
+ * each combination), not this evaluation of it.
39
+ */
40
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
41
+ import { hypot3 } from "../core/portable-math.js";
42
+ import { isInternalSceneRadianceEvaluator, markInternalSceneRadianceEvaluator, registerInternalRadianceAccess } from "../core/internal-radiance-evaluator.js";
43
+ import { lensProjectionDirectionValue, lensProjectionImagePointValue } from "../camera/lens-projection.js";
44
+ import { applyPhysicalCameraRotation, resolveProjectionDistanceMm } from "../motion/camera-rotation.js";
45
+ import { calculateIlluminationVignetting } from "../optics/illumination-vignetting.js";
46
+ import { distortedImagePoint, undistortedImagePoint } from "../optics/sensor-environment-query.js";
47
+ import { checkedSiteKernel, gatherInterior, kernelTaps } from "./diffraction-provider.js";
48
+ /** Rows of native sites per band; bands kept per time. Banding changes memory only, never values. */
49
+ const BAND_ROWS = 32;
50
+ const CACHED_BANDS = 2;
51
+ const LATTICE_TOLERANCE_SITES = 1e-4;
52
+ /** Internal: prepares {@link PreparedDefocusImageSpaceGeometry} with the environment's own conventions and arithmetic. */
53
+ export function prepareDefocusImageSpaceGeometry(geometry) {
54
+ const { focalLengthMm, focusDistanceM, angularVelocityRadPerSec: omega, lensMapping, vignetting } = geometry;
55
+ // The projection scale: the thin-lens image distance at the finite focus, for every projection model (the environment's).
56
+ const pd = resolveProjectionDistanceMm(focalLengthMm, focusDistanceM).projectionDistanceMm;
57
+ const model = lensMapping?.mapping.projectionModel ?? "rectilinear";
58
+ const timeDependent = omega.pitch !== 0 || omega.yaw !== 0 || omega.roll !== 0;
59
+ const displacement = (t) => timeDependent ? { pitch: omega.pitch * t, yaw: omega.yaw * t, roll: omega.roll * t } : { pitch: 0, yaw: 0, roll: 0 };
60
+ // The reference-to-camera rotation at the last time asked, as a matrix (its columns: the rotated basis vectors), so a lattice
61
+ // lookup costs no trigonometry; the rotation is linear, so this equals rotating the vector, up to rounding.
62
+ let matrixTime = NaN, m = [];
63
+ const toCamera = (v, t) => {
64
+ if (t !== matrixTime) {
65
+ const d = displacement(t), ex = applyPhysicalCameraRotation({ x: 1, y: 0, z: 0 }, d, "reference-to-camera"), ey = applyPhysicalCameraRotation({ x: 0, y: 1, z: 0 }, d, "reference-to-camera"), ez = applyPhysicalCameraRotation({ x: 0, y: 0, z: 1 }, d, "reference-to-camera");
66
+ m = [ex.x, ey.x, ez.x, ex.y, ey.y, ez.y, ex.z, ey.z, ez.z];
67
+ matrixTime = t;
68
+ }
69
+ return { x: m[0] * v.x + m[1] * v.y + m[2] * v.z, y: m[3] * v.x + m[4] * v.y + m[5] * v.z, z: m[6] * v.x + m[7] * v.y + m[8] * v.z };
70
+ };
71
+ return {
72
+ timeDependent,
73
+ lookOf(xMm, yMm, t) {
74
+ const image = { x: xMm, y: -yMm };
75
+ const { ideal, distortionAreaFactor } = lensMapping ? undistortedImagePoint(lensMapping, image) : { ideal: image, distortionAreaFactor: 1 };
76
+ let lx, ly, lz;
77
+ if (model === "rectilinear") {
78
+ // calculateInverseCameraRotationImageMapping: the captured ray (x/pd, y/pd, 1) in reference axes, projected at pd.
79
+ const reference = applyPhysicalCameraRotation({ x: ideal.x / pd, y: ideal.y / pd, z: 1 }, displacement(t), "camera-to-reference");
80
+ if (!(reference.z > 0))
81
+ throw new InvalidConfigurationError("lens.defocus: a lattice point's ray points to or behind the reference camera plane.");
82
+ lx = pd * (reference.x / reference.z);
83
+ ly = pd * (reference.y / reference.z);
84
+ lz = pd;
85
+ }
86
+ else {
87
+ const reference = applyPhysicalCameraRotation(lensProjectionDirectionValue(model, pd, ideal.x, ideal.y), displacement(t), "camera-to-reference");
88
+ lx = reference.x;
89
+ ly = reference.y;
90
+ lz = reference.z;
91
+ }
92
+ const length = hypot3(lx, ly, lz);
93
+ const throughput = vignetting ? calculateIlluminationVignetting({ imagePointMm: image, profile: vignetting }).value.linearThroughputFactor : 1;
94
+ return { x: lx / length, y: ly / length, z: lz / length, factor: throughput * distortionAreaFactor };
95
+ },
96
+ sensorPointOf(look, t) {
97
+ try {
98
+ const c = timeDependent ? toCamera(look, t) : look;
99
+ let ideal;
100
+ if (model === "rectilinear") {
101
+ if (!(c.z > 0))
102
+ return undefined;
103
+ ideal = { x: pd * (c.x / c.z), y: pd * (c.y / c.z) };
104
+ }
105
+ else
106
+ ideal = lensProjectionImagePointValue(model, pd, c.x, c.y, c.z).imagePointMm;
107
+ const image = lensMapping ? distortedImagePoint(lensMapping, ideal).pointMm : ideal;
108
+ return Number.isFinite(image.x) && Number.isFinite(image.y) ? { x: image.x, y: -image.y } : undefined;
109
+ }
110
+ catch {
111
+ return undefined;
112
+ }
113
+ }
114
+ };
115
+ }
116
+ /** Internal (not exported from the package): the base provider with the defocus disc applied in image space (#548). */
117
+ export function createDefocusImageSpaceProvider(input) {
118
+ const { base, geometry, wavelengthsNm, nativeWidth: W, nativeHeight: H, kernelRadius: R } = input;
119
+ const mmPerSite = input.sitePitchMicrometers / 1000;
120
+ const index = new Map(wavelengthsNm.map((nm, i) => [nm, i]));
121
+ const offsets = input.queryOffsetsSites ?? [{ x: 0, y: 0 }];
122
+ const fraction = (value) => { const f = value - Math.floor(value); return f > 1 - 1e-6 ? 0 : f < 1e-6 ? 0 : f; };
123
+ const shifts = [];
124
+ for (const o of offsets) {
125
+ const f = { x: fraction(o.x), y: fraction(o.y) };
126
+ if (!shifts.some((s) => Math.abs(s.x - f.x) <= 1e-6 && Math.abs(s.y - f.y) <= 1e-6))
127
+ shifts.push(f);
128
+ }
129
+ const M = Math.max(0, ...offsets.map((o) => Math.ceil(Math.max(Math.abs(o.x), Math.abs(o.y)) - 1e-6)));
130
+ const LW = W + 2 * M, LH = H + 2 * M;
131
+ const win = input.window ?? { x: 0, y: 0, width: W, height: H };
132
+ const lx0 = Math.max(0, win.x), lx1 = Math.min(LW, win.x + win.width + 2 * M);
133
+ const ly0 = Math.max(0, win.y), ly1 = Math.min(LH, win.y + win.height + 2 * M);
134
+ if (!(lx0 < lx1 && ly0 < ly1))
135
+ throw new InvalidConfigurationError("Defocus window must overlap the native raster.");
136
+ const NW = lx1 - lx0, gridWidth = NW + 2 * R;
137
+ const kernels = wavelengthsNm.map((nm) => checkedSiteKernel(input.siteKernels(nm), R));
138
+ const taps = kernels.map((kernel) => kernelTaps(kernel, R, gridWidth));
139
+ const sharedKernel = kernels.every((kernel) => kernel === kernels[0]);
140
+ const scale = input.metresPerFrameUnit, distance = input.distanceM;
141
+ const materialAt = base.chartMaterialAt?.bind(base), materialRadiance = base.materialRadiance?.bind(base);
142
+ const now = () => globalThis.performance?.now() ?? Date.now();
143
+ // Lattice point (li, lj) of shift s, as a native sensor point in mm (+Y down): site centre plus the shift.
144
+ const xMmOf = (li, s) => (li - M + 0.5 + s.x - W / 2) * mmPerSite;
145
+ const yMmOf = (lj, s) => (lj - M + 0.5 + s.y - H / 2) * mmPerSite;
146
+ // Bands per time (LRU over (time, band)): values[shift][node] holds G_t / F at the band's own lattice points.
147
+ const cache = new Map();
148
+ const capacity = CACHED_BANDS * Math.max(1, input.timesPerBand ?? 1);
149
+ // The last band read, so consecutive queries of one band skip the cache key (most queries).
150
+ let lastBand = -1, lastTime = NaN, lastValues;
151
+ function band(b, time) {
152
+ if (b === lastBand && time === lastTime)
153
+ return lastValues;
154
+ const values = bandOf(b, time);
155
+ lastBand = b;
156
+ lastTime = time;
157
+ lastValues = values;
158
+ return values;
159
+ }
160
+ function bandOf(b, time) {
161
+ const key = time + ":" + b, cached = cache.get(key);
162
+ if (cached) {
163
+ cache.delete(key);
164
+ cache.set(key, cached);
165
+ return cached;
166
+ }
167
+ const started = input.onConvolution ? now() : 0;
168
+ const y0 = ly0 + b * BAND_ROWS, rows = Math.min(BAND_ROWS, ly1 - y0), gridHeight = rows + 2 * R, size = gridWidth * gridHeight;
169
+ const values = shifts.map((shift) => {
170
+ // Each grid point's field factor and plane point (or material), shared by the spectral nodes.
171
+ const factor = new Float64Array(size), u = new Float64Array(size), v = new Float64Array(size);
172
+ const material = materialAt && materialRadiance ? new Int32Array(size) : undefined;
173
+ for (let gy = 0; gy < gridHeight; gy++) {
174
+ const yMm = yMmOf(y0 + gy - R, shift);
175
+ for (let gx = 0; gx < gridWidth; gx++) {
176
+ const k = gy * gridWidth + gx, look = geometry.lookOf(xMmOf(lx0 + gx - R, shift), yMm, time);
177
+ if (!(look.z > 0))
178
+ throw new InvalidConfigurationError("lens.defocus: a lattice point's ray does not point toward the scene plane.");
179
+ // The base provider's own plane-point arithmetic for the outgoing direction −look.
180
+ const t = distance / look.z;
181
+ factor[k] = look.factor;
182
+ u[k] = look.x * t / scale;
183
+ v[k] = look.y * t / scale;
184
+ if (material)
185
+ material[k] = materialAt(u[k], v[k]);
186
+ }
187
+ }
188
+ // The band's own lattice points' factors, to answer each query with G / F.
189
+ const own = new Float64Array(rows * NW);
190
+ for (let y = 0; y < rows; y++)
191
+ for (let x = 0; x < NW; x++)
192
+ own[y * NW + x] = factor[(y + R) * gridWidth + x + R];
193
+ // One kernel for every node (the disc without diffraction) and fewer materials than nodes: the convolution is linear, so
194
+ // G_λ = Σ_m L_m(λ)·(K ⊛ F·[material = m]); one convolution per material instead of one per node, the same sum regrouped.
195
+ if (material && sharedKernel) {
196
+ const present = [...new Set(material)];
197
+ if (present.length < wavelengthsNm.length) {
198
+ const perMaterial = present.map((m) => {
199
+ const samples = new Float64Array(size);
200
+ for (let k = 0; k < size; k++)
201
+ if (material[k] === m)
202
+ samples[k] = factor[k];
203
+ return gatherInterior(samples, taps[0], gridWidth, NW, rows, R);
204
+ });
205
+ return wavelengthsNm.map((nm) => {
206
+ const radiance = present.map((m) => materialRadiance(m, nm)), out = new Float64Array(rows * NW);
207
+ for (const value of radiance)
208
+ if (!(value >= 0 && value <= 2 ** 100))
209
+ throw new InvalidConfigurationError("Irradiance samples must be finite, non-negative and within the declared magnitude.");
210
+ for (let k = 0; k < out.length; k++) {
211
+ let sum = 0;
212
+ for (let i = 0; i < present.length; i++)
213
+ sum += radiance[i] * perMaterial[i][k];
214
+ out[k] = sum / own[k];
215
+ }
216
+ return out;
217
+ });
218
+ }
219
+ }
220
+ return wavelengthsNm.map((nm, w) => {
221
+ const samples = new Float64Array(size);
222
+ if (material) {
223
+ const radiance = new Map();
224
+ for (let k = 0; k < size; k++) {
225
+ const m = material[k];
226
+ let value = radiance.get(m);
227
+ if (value === undefined) {
228
+ value = materialRadiance(m, nm);
229
+ radiance.set(m, value);
230
+ }
231
+ samples[k] = factor[k] * value;
232
+ }
233
+ }
234
+ else
235
+ for (let k = 0; k < size; k++)
236
+ samples[k] = factor[k] * base.radianceAt(u[k], v[k], nm);
237
+ const gathered = gatherInterior(samples, taps[w], gridWidth, NW, rows, R);
238
+ for (let k = 0; k < gathered.length; k++)
239
+ gathered[k] = gathered[k] / own[k];
240
+ return gathered;
241
+ });
242
+ });
243
+ cache.set(key, values);
244
+ if (cache.size > capacity)
245
+ cache.delete(cache.keys().next().value);
246
+ if (input.onConvolution)
247
+ input.onConvolution(now() - started);
248
+ return values;
249
+ }
250
+ /** The lattice key ((shift·LH + lj)·LW + li) of a look direction at time t, or undefined off the lattices or outside the window. */
251
+ function keyOf(look, t) {
252
+ const point = geometry.sensorPointOf(look, t);
253
+ if (point === undefined)
254
+ return undefined;
255
+ const fi = point.x / mmPerSite + W / 2 - 0.5, fj = point.y / mmPerSite + H / 2 - 0.5;
256
+ for (let s = 0; s < shifts.length; s++) {
257
+ const shift = shifts[s], i = Math.round(fi - shift.x), j = Math.round(fj - shift.y);
258
+ if (Math.abs(fi - shift.x - i) > LATTICE_TOLERANCE_SITES || Math.abs(fj - shift.y - j) > LATTICE_TOLERANCE_SITES)
259
+ continue;
260
+ const li = i + M, lj = j + M;
261
+ if (li < lx0 || lj < ly0 || li >= lx1 || lj >= ly1)
262
+ return undefined;
263
+ return (s * LH + lj) * LW + li;
264
+ }
265
+ return undefined;
266
+ }
267
+ function valueOf(key, t, nm) {
268
+ const w = index.get(nm);
269
+ if (w === undefined)
270
+ throw new InvalidConfigurationError("Defocus query is off the sensor site lattice; the image-space convolution does not apply.");
271
+ const li = key % LW, rest = (key - li) / LW, lj = rest % LH, s = (rest - lj) / LH;
272
+ return band(Math.floor((lj - ly0) / BAND_ROWS), t)[s][w][((lj - ly0) % BAND_ROWS) * NW + li - lx0];
273
+ }
274
+ function blurredAt(look, time, nm) {
275
+ const t = geometry.timeDependent ? time : 0, key = keyOf(look, t);
276
+ if (key === undefined)
277
+ throw new InvalidConfigurationError("Defocus query is off the sensor site lattice; the image-space convolution does not apply.");
278
+ return valueOf(key, t, nm);
279
+ }
280
+ const baseEvaluate = base.evaluateRadiance;
281
+ const evaluateRadiance = (request) => {
282
+ const result = baseEvaluate.call(base, request), o = request.target.outgoingDirectionUnitVector;
283
+ return { ...result, spectralRadianceWattsPerSquareMeterSteradianNanometer: blurredAt({ x: -o.x, y: -o.y, z: -o.z }, request.timeSecondsFromExposureStart, request.wavelengthNanometers) };
284
+ };
285
+ // The result is the base result with only its radiance replaced, so an engine-built base evaluator keeps its mark. Its radiance is
286
+ // a pure function of the lattice key (and, with rotation, the request time) and the wavelength, so the lean executor may read it by
287
+ // key, as for the diffraction provider.
288
+ if (isInternalSceneRadianceEvaluator(baseEvaluate)) {
289
+ markInternalSceneRadianceEvaluator(evaluateRadiance);
290
+ // With rotation the key also carries the request time, as an index into the distinct times seen (one per temporal midpoint).
291
+ const times = [], timeIndex = new Map(), latticeKeys = shifts.length * LH * LW;
292
+ registerInternalRadianceAccess(evaluateRadiance, {
293
+ tabulated: false,
294
+ keyOfDirection(o, time) {
295
+ const t = geometry.timeDependent ? time : 0, key = keyOf({ x: -o.x, y: -o.y, z: -o.z }, t);
296
+ if (key === undefined)
297
+ return undefined;
298
+ let index = timeIndex.get(t);
299
+ if (index === undefined) {
300
+ index = times.length;
301
+ times.push(t);
302
+ timeIndex.set(t, index);
303
+ }
304
+ return index * latticeKeys + key;
305
+ },
306
+ radianceOfKey(key, nm) {
307
+ const index = Math.floor(key / latticeKeys);
308
+ return valueOf(key - index * latticeKeys, times[index], nm);
309
+ }
310
+ });
311
+ }
312
+ return {
313
+ sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt: base.radianceAt,
314
+ evaluateRadiance,
315
+ evaluateApertureRadiance() {
316
+ throw new InvalidConfigurationError("Defocus by image-space convolution does not take pupil rays.");
317
+ }
318
+ };
319
+ }
320
+ /** Internal: {@link DefocusFieldVariation} on a 9 × 9 grid of sensor points (the extremes of these smooth maps lie on the frame's edges). */
321
+ export function calculateDefocusFieldVariation(input) {
322
+ const { geometry, lensMapping, pitchMicrometers: pitch, nativeWidth: W, nativeHeight: H, subjectDistanceM: d, diameterSites, discAt } = input;
323
+ const G = 9, points = [];
324
+ for (let j = 0; j < G; j++)
325
+ for (let i = 0; i < G; i++)
326
+ points.push({ x: (i / (G - 1) - 0.5) * W * pitch / 1000, y: (j / (G - 1) - 0.5) * H * pitch / 1000 });
327
+ let rotation = null;
328
+ if (geometry.timeDependent) {
329
+ rotation = 0;
330
+ const omega = input.angularVelocityRadPerSec;
331
+ for (const t of input.temporalMidpointsSeconds) {
332
+ // The camera's optical axis at t in the reference axes; a scene point's axial depth in the rotated camera is P·axis.
333
+ const axis = applyPhysicalCameraRotation({ x: 0, y: 0, z: 1 }, { pitch: omega.pitch * t, yaw: omega.yaw * t, roll: omega.roll * t }, "camera-to-reference");
334
+ for (const p of points) {
335
+ const look = geometry.lookOf(p.x, p.y, t), reach = d / look.z;
336
+ const depth = reach * (look.x * axis.x + look.y * axis.y + look.z * axis.z);
337
+ rotation = Math.max(rotation, Math.abs(discAt(depth) - diameterSites));
338
+ }
339
+ }
340
+ }
341
+ let distortion = null;
342
+ if (lensMapping && lensMapping.mapping.distortion.kind !== "none") {
343
+ distortion = 0;
344
+ for (const p of points) {
345
+ const scales = distortedImagePoint(lensMapping, undistortedImagePoint(lensMapping, { x: p.x, y: -p.y }).ideal);
346
+ distortion = Math.max(distortion, scales.largestScale - 1, 1 - scales.smallestScale);
347
+ }
348
+ }
349
+ let projection = null;
350
+ if (lensMapping && lensMapping.mapping.projectionModel !== "rectilinear") {
351
+ projection = 0;
352
+ for (const p of points) {
353
+ const look = geometry.lookOf(p.x, p.y, 0);
354
+ projection = Math.max(projection, Math.abs(discAt(d / look.z) - diameterSites));
355
+ }
356
+ }
357
+ return { gridPointsPerSide: G, rotationMaximumDiameterChangeSites: rotation, distortionMaximumLocalScaleDeviation: distortion, projectionMaximumDiameterChangeSites: projection };
358
+ }
359
+ //# sourceMappingURL=defocus-image-space-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defocus-image-space-provider.js","sourceRoot":"","sources":["../../src/facade/defocus-image-space-provider.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * `simulateCapture`'s `lens.defocus` out of focus together with a lens field mapping (`lens.projection`,\n * `lens.distortion`), `lens.vignetting` or camera rotation (#548): the geometric defocus disc applied in image space,\n * on the site lattice of the sensor, instead of on the scene plane.\n *\n * Why the object-space path does not apply. `createDiffractedPlanarProvider` convolves the scene radiance on the plane\n * lattice that the sites see through one linear map (a still, rectilinear, distortion-free camera with the same\n * radiance-to-irradiance factor everywhere). A projection, a distortion or a rotation makes the map from sensor points\n * to plane points non-linear (or time-dependent), and vignetting makes the factor vary over the field, so the disc on\n * the plane is no longer the disc on the sensor.\n *\n * The image-space model. The environment PSF quadrature (`calculateSensorPsfIrradianceQuadrature`, the per-tap path\n * `lens.diffraction` falls back to) defines a sensor-plane PSF K as\n * E(q, λ, t) = Σ_k K(k)·F(s_k)·C·L(D_t(s_k), λ),\n * with s_k = q + k·p the tap source points on the sensor (p the site pitch), D_t the scene ray of a sensor point at\n * time t (inverse distortion, the declared projection, the camera rotation at t), F = T·A the field factor (vignetting\n * throughput T, applied once before the PSF spreads the light, and the distortion area factor A = 1/det J) and C the\n * paraxial radiance-to-irradiance factor π/(4·N_w²), the same everywhere. With K the defocus disc sampled on the site\n * lattice (`createDefocusDiscSiteKernel`), every source point of a site-centre query is another lattice point, so\n * E(q, λ, t) = C·G_t(q, λ), G_t = K ⊛ (F·L∘D_t) sampled on the lattice,\n * the same sum in a different floating-point order. This provider computes G_t once per spectral node and time on the\n * sensor lattice (in row bands, as the diffraction provider does) and answers the query of lattice point q at time t with\n * G_t(q, λ) / F(q); the environment multiplies by F(q)·C again, so each site gets C·G_t(q, λ). For the still,\n * rectilinear, distortion-free camera without vignetting it is the object-space convolution (F = 1 and D linear).\n *\n * The lattices. As in the diffraction provider, a pixel aperture or anti-aliasing spots add query offsets; each distinct\n * fractional offset gets its own shifted lattice of sensor points, and every tap of a query on one lattice is on the same\n * lattice. A query is located on its lattice by mapping its scene ray back to the sensor (camera rotation inverted at the\n * request's time, the forward projection, the forward distortion), within 1e-4 site; a query that lands on no lattice point\n * is refused rather than approximated.\n *\n * Time. With camera rotation each environment temporal midpoint is a still camera, so the disc is applied to that\n * instant's image in that instant's frame: one G_t per distinct request time, cached per band. Without rotation the image\n * does not depend on time and one G is kept. The facade refuses rotation with a rolling shutter (each row's instants differ).\n *\n * What is approximate is the disc itself (`lens.defocus` labels it; the facade records the field-variation bounds of\n * each combination), not this evaluation of it.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { hypot3 } from \"../core/portable-math.js\";\nimport { isInternalSceneRadianceEvaluator, markInternalSceneRadianceEvaluator, registerInternalRadianceAccess } from \"../core/internal-radiance-evaluator.js\";\nimport type { SceneRadianceEvaluationRequest, SceneRadianceEvaluationResult } from \"../schema/scene-radiance.js\";\nimport { lensProjectionDirectionValue, lensProjectionImagePointValue, type LensProjectionModel } from \"../camera/lens-projection.js\";\nimport { applyPhysicalCameraRotation, resolveProjectionDistanceMm, type CameraAngularVelocityRadPerSec } from \"../motion/camera-rotation.js\";\nimport { calculateIlluminationVignetting, type IlluminationVignettingProfile } from \"../optics/illumination-vignetting.js\";\nimport { distortedImagePoint, undistortedImagePoint, type PreparedEnvironmentLensFieldMapping } from \"../optics/sensor-environment-query.js\";\nimport { checkedSiteKernel, gatherInterior, kernelTaps } from \"./diffraction-provider.js\";\nimport type { PlanarSceneRadianceProvider } from \"./scene-radiance.js\";\n\n/** Rows of native sites per band; bands kept per time. Banding changes memory only, never values. */\nconst BAND_ROWS = 32;\nconst CACHED_BANDS = 2;\nconst LATTICE_TOLERANCE_SITES = 1e-4;\n\n/** The camera geometry the environment uses for each site query (the same options as the site inputs). */\nexport interface DefocusImageSpaceGeometry {\n focalLengthMm: number;\n focusDistanceM: number;\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;\n /** The site inputs' `lensFieldMapping`, prepared; omitted for the ideal rectilinear, distortion-free mapping. */\n lensMapping?: PreparedEnvironmentLensFieldMapping;\n /** The site inputs' radial-illumination-vignetting profile; omitted for unity field throughput. */\n vignetting?: IlluminationVignettingProfile;\n}\n\n/** Internal: the scene ray and field factor of sensor points, and the sensor point of a scene ray, for one geometry. */\nexport interface PreparedDefocusImageSpaceGeometry {\n /**\n * The unit look direction (toward the scene, reference camera axes at exposure start) of native sensor point (x, y) mm\n * (+X right, +Y down) at time t, and its field factor F = T·A (vignetting throughput × distortion area factor).\n */\n lookOf(xMm: number, yMm: number, timeSeconds: number): { x: number; y: number; z: number; factor: number };\n /** The native sensor point (mm, +Y down) whose scene ray at time t is the look direction; undefined outside the mapping. */\n sensorPointOf(look: { x: number; y: number; z: number }, timeSeconds: number): { x: number; y: number } | undefined;\n /** True with camera rotation: the ray of a sensor point depends on time. */\n timeDependent: boolean;\n}\n\n/** Internal: prepares {@link PreparedDefocusImageSpaceGeometry} with the environment's own conventions and arithmetic. */\nexport function prepareDefocusImageSpaceGeometry(geometry: DefocusImageSpaceGeometry): PreparedDefocusImageSpaceGeometry {\n const { focalLengthMm, focusDistanceM, angularVelocityRadPerSec: omega, lensMapping, vignetting } = geometry;\n // The projection scale: the thin-lens image distance at the finite focus, for every projection model (the environment's).\n const pd = resolveProjectionDistanceMm(focalLengthMm, focusDistanceM).projectionDistanceMm;\n const model: LensProjectionModel = lensMapping?.mapping.projectionModel ?? \"rectilinear\";\n const timeDependent = omega.pitch !== 0 || omega.yaw !== 0 || omega.roll !== 0;\n const displacement = (t: number): { pitch: number; yaw: number; roll: number } =>\n timeDependent ? { pitch: omega.pitch * t, yaw: omega.yaw * t, roll: omega.roll * t } : { pitch: 0, yaw: 0, roll: 0 };\n // The reference-to-camera rotation at the last time asked, as a matrix (its columns: the rotated basis vectors), so a lattice\n // lookup costs no trigonometry; the rotation is linear, so this equals rotating the vector, up to rounding.\n let matrixTime = NaN, m: number[] = [];\n const toCamera = (v: { x: number; y: number; z: number }, t: number): { x: number; y: number; z: number } => {\n if (t !== matrixTime) {\n const d = displacement(t), ex = applyPhysicalCameraRotation({ x: 1, y: 0, z: 0 }, d, \"reference-to-camera\"),\n ey = applyPhysicalCameraRotation({ x: 0, y: 1, z: 0 }, d, \"reference-to-camera\"), ez = applyPhysicalCameraRotation({ x: 0, y: 0, z: 1 }, d, \"reference-to-camera\");\n m = [ex.x, ey.x, ez.x, ex.y, ey.y, ez.y, ex.z, ey.z, ez.z];\n matrixTime = t;\n }\n return { x: m[0]! * v.x + m[1]! * v.y + m[2]! * v.z, y: m[3]! * v.x + m[4]! * v.y + m[5]! * v.z, z: m[6]! * v.x + m[7]! * v.y + m[8]! * v.z };\n };\n return {\n timeDependent,\n lookOf(xMm, yMm, t): { x: number; y: number; z: number; factor: number } {\n const image = { x: xMm, y: -yMm };\n const { ideal, distortionAreaFactor } = lensMapping ? undistortedImagePoint(lensMapping, image) : { ideal: image, distortionAreaFactor: 1 };\n let lx: number, ly: number, lz: number;\n if (model === \"rectilinear\") {\n // calculateInverseCameraRotationImageMapping: the captured ray (x/pd, y/pd, 1) in reference axes, projected at pd.\n const reference = applyPhysicalCameraRotation({ x: ideal.x / pd, y: ideal.y / pd, z: 1 }, displacement(t), \"camera-to-reference\");\n if (!(reference.z > 0)) throw new InvalidConfigurationError(\"lens.defocus: a lattice point's ray points to or behind the reference camera plane.\");\n lx = pd * (reference.x / reference.z); ly = pd * (reference.y / reference.z); lz = pd;\n } else {\n const reference = applyPhysicalCameraRotation(lensProjectionDirectionValue(model, pd, ideal.x, ideal.y), displacement(t), \"camera-to-reference\");\n lx = reference.x; ly = reference.y; lz = reference.z;\n }\n const length = hypot3(lx, ly, lz);\n const throughput = vignetting ? calculateIlluminationVignetting({ imagePointMm: image, profile: vignetting }).value.linearThroughputFactor : 1;\n return { x: lx / length, y: ly / length, z: lz / length, factor: throughput * distortionAreaFactor };\n },\n sensorPointOf(look, t): { x: number; y: number } | undefined {\n try {\n const c = timeDependent ? toCamera(look, t) : look;\n let ideal: { x: number; y: number };\n if (model === \"rectilinear\") {\n if (!(c.z > 0)) return undefined;\n ideal = { x: pd * (c.x / c.z), y: pd * (c.y / c.z) };\n } else ideal = lensProjectionImagePointValue(model, pd, c.x, c.y, c.z).imagePointMm;\n const image = lensMapping ? distortedImagePoint(lensMapping, ideal).pointMm : ideal;\n return Number.isFinite(image.x) && Number.isFinite(image.y) ? { x: image.x, y: -image.y } : undefined;\n } catch {\n return undefined;\n }\n }\n };\n}\n\nexport interface DefocusImageSpaceProviderInput {\n base: PlanarSceneRadianceProvider;\n geometry: PreparedDefocusImageSpaceGeometry;\n /** Each spectral node's kernel on the site lattice, (2·kernelRadius + 1)² taps, row-major (the disc, or diffraction ⊛ disc). */\n siteKernels: (wavelengthNm: number) => Float64Array;\n wavelengthsNm: readonly number[];\n kernelRadius: number;\n nativeWidth: number;\n nativeHeight: number;\n sitePitchMicrometers: number;\n /** Planar geometry of the base provider (plane distance and frame-unit scale), to find the plane point of a ray as it does. */\n distanceM: number;\n metresPerFrameUnit: number;\n /** Offsets of the query points from the site centres, in sites (+x right, +y down). Default: one zero offset. */\n queryOffsetsSites?: readonly { x: number; y: number }[];\n /** Region renders: the native sites that will be queried. Values are bit-identical to the whole render's. Default: the whole raster. */\n window?: { x: number; y: number; width: number; height: number };\n /** Most distinct request times kept per band (the temporal sample count). Default 1. */\n timesPerBand?: number;\n /** Called with the duration in ms of each band convolution (diagnostics only). */\n onConvolution?: (milliseconds: number) => void;\n}\n\n/** Internal (not exported from the package): the base provider with the defocus disc applied in image space (#548). */\nexport function createDefocusImageSpaceProvider(input: DefocusImageSpaceProviderInput): PlanarSceneRadianceProvider {\n const { base, geometry, wavelengthsNm, nativeWidth: W, nativeHeight: H, kernelRadius: R } = input;\n const mmPerSite = input.sitePitchMicrometers / 1000;\n const index = new Map(wavelengthsNm.map((nm, i) => [nm, i]));\n const offsets = input.queryOffsetsSites ?? [{ x: 0, y: 0 }];\n const fraction = (value: number): number => { const f = value - Math.floor(value); return f > 1 - 1e-6 ? 0 : f < 1e-6 ? 0 : f; };\n const shifts: { x: number; y: number }[] = [];\n for (const o of offsets) {\n const f = { x: fraction(o.x), y: fraction(o.y) };\n if (!shifts.some((s) => Math.abs(s.x - f.x) <= 1e-6 && Math.abs(s.y - f.y) <= 1e-6)) shifts.push(f);\n }\n const M = Math.max(0, ...offsets.map((o) => Math.ceil(Math.max(Math.abs(o.x), Math.abs(o.y)) - 1e-6)));\n const LW = W + 2 * M, LH = H + 2 * M;\n const win = input.window ?? { x: 0, y: 0, width: W, height: H };\n const lx0 = Math.max(0, win.x), lx1 = Math.min(LW, win.x + win.width + 2 * M);\n const ly0 = Math.max(0, win.y), ly1 = Math.min(LH, win.y + win.height + 2 * M);\n if (!(lx0 < lx1 && ly0 < ly1)) throw new InvalidConfigurationError(\"Defocus window must overlap the native raster.\");\n const NW = lx1 - lx0, gridWidth = NW + 2 * R;\n const kernels = wavelengthsNm.map((nm) => checkedSiteKernel(input.siteKernels(nm), R));\n const taps = kernels.map((kernel) => kernelTaps(kernel, R, gridWidth));\n const sharedKernel = kernels.every((kernel) => kernel === kernels[0]);\n const scale = input.metresPerFrameUnit, distance = input.distanceM;\n const materialAt = base.chartMaterialAt?.bind(base), materialRadiance = base.materialRadiance?.bind(base);\n const now = (): number => globalThis.performance?.now() ?? Date.now();\n // Lattice point (li, lj) of shift s, as a native sensor point in mm (+Y down): site centre plus the shift.\n const xMmOf = (li: number, s: { x: number }): number => (li - M + 0.5 + s.x - W / 2) * mmPerSite;\n const yMmOf = (lj: number, s: { y: number }): number => (lj - M + 0.5 + s.y - H / 2) * mmPerSite;\n // Bands per time (LRU over (time, band)): values[shift][node] holds G_t / F at the band's own lattice points.\n const cache = new Map<string, Float64Array[][]>();\n const capacity = CACHED_BANDS * Math.max(1, input.timesPerBand ?? 1);\n // The last band read, so consecutive queries of one band skip the cache key (most queries).\n let lastBand = -1, lastTime = NaN, lastValues: Float64Array[][] | undefined;\n function band(b: number, time: number): Float64Array[][] {\n if (b === lastBand && time === lastTime) return lastValues!;\n const values = bandOf(b, time);\n lastBand = b; lastTime = time; lastValues = values;\n return values;\n }\n function bandOf(b: number, time: number): Float64Array[][] {\n const key = time + \":\" + b, cached = cache.get(key);\n if (cached) { cache.delete(key); cache.set(key, cached); return cached; }\n const started = input.onConvolution ? now() : 0;\n const y0 = ly0 + b * BAND_ROWS, rows = Math.min(BAND_ROWS, ly1 - y0), gridHeight = rows + 2 * R, size = gridWidth * gridHeight;\n const values = shifts.map((shift) => {\n // Each grid point's field factor and plane point (or material), shared by the spectral nodes.\n const factor = new Float64Array(size), u = new Float64Array(size), v = new Float64Array(size);\n const material = materialAt && materialRadiance ? new Int32Array(size) : undefined;\n for (let gy = 0; gy < gridHeight; gy++) {\n const yMm = yMmOf(y0 + gy - R, shift);\n for (let gx = 0; gx < gridWidth; gx++) {\n const k = gy * gridWidth + gx, look = geometry.lookOf(xMmOf(lx0 + gx - R, shift), yMm, time);\n if (!(look.z > 0)) throw new InvalidConfigurationError(\"lens.defocus: a lattice point's ray does not point toward the scene plane.\");\n // The base provider's own plane-point arithmetic for the outgoing direction −look.\n const t = distance / look.z;\n factor[k] = look.factor; u[k] = look.x * t / scale; v[k] = look.y * t / scale;\n if (material) material[k] = materialAt!(u[k]!, v[k]!);\n }\n }\n // The band's own lattice points' factors, to answer each query with G / F.\n const own = new Float64Array(rows * NW);\n for (let y = 0; y < rows; y++) for (let x = 0; x < NW; x++) own[y * NW + x] = factor[(y + R) * gridWidth + x + R]!;\n // One kernel for every node (the disc without diffraction) and fewer materials than nodes: the convolution is linear, so\n // G_λ = Σ_m L_m(λ)·(K ⊛ F·[material = m]); one convolution per material instead of one per node, the same sum regrouped.\n if (material && sharedKernel) {\n const present = [...new Set(material)];\n if (present.length < wavelengthsNm.length) {\n const perMaterial = present.map((m) => {\n const samples = new Float64Array(size);\n for (let k = 0; k < size; k++) if (material[k] === m) samples[k] = factor[k]!;\n return gatherInterior(samples, taps[0]!, gridWidth, NW, rows, R);\n });\n return wavelengthsNm.map((nm) => {\n const radiance = present.map((m) => materialRadiance!(m, nm)), out = new Float64Array(rows * NW);\n for (const value of radiance) if (!(value >= 0 && value <= 2 ** 100)) throw new InvalidConfigurationError(\"Irradiance samples must be finite, non-negative and within the declared magnitude.\");\n for (let k = 0; k < out.length; k++) {\n let sum = 0;\n for (let i = 0; i < present.length; i++) sum += radiance[i]! * perMaterial[i]![k]!;\n out[k] = sum / own[k]!;\n }\n return out;\n });\n }\n }\n return wavelengthsNm.map((nm, w) => {\n const samples = new Float64Array(size);\n if (material) {\n const radiance = new Map<number, number>();\n for (let k = 0; k < size; k++) {\n const m = material[k]!;\n let value = radiance.get(m);\n if (value === undefined) { value = materialRadiance!(m, nm); radiance.set(m, value); }\n samples[k] = factor[k]! * value;\n }\n } else for (let k = 0; k < size; k++) samples[k] = factor[k]! * base.radianceAt(u[k]!, v[k]!, nm);\n const gathered = gatherInterior(samples, taps[w]!, gridWidth, NW, rows, R);\n for (let k = 0; k < gathered.length; k++) gathered[k] = gathered[k]! / own[k]!;\n return gathered;\n });\n });\n cache.set(key, values);\n if (cache.size > capacity) cache.delete(cache.keys().next().value!);\n if (input.onConvolution) input.onConvolution(now() - started);\n return values;\n }\n /** The lattice key ((shift·LH + lj)·LW + li) of a look direction at time t, or undefined off the lattices or outside the window. */\n function keyOf(look: { x: number; y: number; z: number }, t: number): number | undefined {\n const point = geometry.sensorPointOf(look, t);\n if (point === undefined) return undefined;\n const fi = point.x / mmPerSite + W / 2 - 0.5, fj = point.y / mmPerSite + H / 2 - 0.5;\n for (let s = 0; s < shifts.length; s++) {\n const shift = shifts[s]!, i = Math.round(fi - shift.x), j = Math.round(fj - shift.y);\n if (Math.abs(fi - shift.x - i) > LATTICE_TOLERANCE_SITES || Math.abs(fj - shift.y - j) > LATTICE_TOLERANCE_SITES) continue;\n const li = i + M, lj = j + M;\n if (li < lx0 || lj < ly0 || li >= lx1 || lj >= ly1) return undefined;\n return (s * LH + lj) * LW + li;\n }\n return undefined;\n }\n function valueOf(key: number, t: number, nm: number): number {\n const w = index.get(nm);\n if (w === undefined) throw new InvalidConfigurationError(\"Defocus query is off the sensor site lattice; the image-space convolution does not apply.\");\n const li = key % LW, rest = (key - li) / LW, lj = rest % LH, s = (rest - lj) / LH;\n return band(Math.floor((lj - ly0) / BAND_ROWS), t)[s]![w]![((lj - ly0) % BAND_ROWS) * NW + li - lx0]!;\n }\n function blurredAt(look: { x: number; y: number; z: number }, time: number, nm: number): number {\n const t = geometry.timeDependent ? time : 0, key = keyOf(look, t);\n if (key === undefined) throw new InvalidConfigurationError(\"Defocus query is off the sensor site lattice; the image-space convolution does not apply.\");\n return valueOf(key, t, nm);\n }\n const baseEvaluate = base.evaluateRadiance;\n const evaluateRadiance = (request: Readonly<SceneRadianceEvaluationRequest>): SceneRadianceEvaluationResult => {\n const result = baseEvaluate.call(base, request), o = (request.target as { outgoingDirectionUnitVector: { x: number; y: number; z: number } }).outgoingDirectionUnitVector;\n return { ...result, spectralRadianceWattsPerSquareMeterSteradianNanometer: blurredAt({ x: -o.x, y: -o.y, z: -o.z }, request.timeSecondsFromExposureStart, request.wavelengthNanometers) };\n };\n // The result is the base result with only its radiance replaced, so an engine-built base evaluator keeps its mark. Its radiance is\n // a pure function of the lattice key (and, with rotation, the request time) and the wavelength, so the lean executor may read it by\n // key, as for the diffraction provider.\n if (isInternalSceneRadianceEvaluator(baseEvaluate)) {\n markInternalSceneRadianceEvaluator(evaluateRadiance);\n // With rotation the key also carries the request time, as an index into the distinct times seen (one per temporal midpoint).\n const times: number[] = [], timeIndex = new Map<number, number>(), latticeKeys = shifts.length * LH * LW;\n registerInternalRadianceAccess(evaluateRadiance, {\n tabulated: false,\n keyOfDirection(o, time): number | undefined {\n const t = geometry.timeDependent ? time : 0, key = keyOf({ x: -o.x, y: -o.y, z: -o.z }, t);\n if (key === undefined) return undefined;\n let index = timeIndex.get(t);\n if (index === undefined) { index = times.length; times.push(t); timeIndex.set(t, index); }\n return index * latticeKeys + key;\n },\n radianceOfKey(key, nm): number {\n const index = Math.floor(key / latticeKeys);\n return valueOf(key - index * latticeKeys, times[index]!, nm);\n }\n });\n }\n return {\n sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt: base.radianceAt,\n evaluateRadiance,\n evaluateApertureRadiance(): SceneRadianceEvaluationResult {\n throw new InvalidConfigurationError(\"Defocus by image-space convolution does not take pupil rays.\");\n }\n };\n}\n\n/** Field-variation bounds of the image-space disc for one request (#548), recorded in the `lens.defocus` assumption. */\nexport interface DefocusFieldVariation {\n /** Sensor points the bounds were evaluated at: a grid of this many points per side over the native raster, edges included. */\n gridPointsPerSide: number;\n /**\n * Camera rotation (pitch or yaw tilts the scene plane in the camera frame): the largest change of the disc diameter, in\n * sites, between the axial depth of each scene point in the rotated camera frame and the subject distance, over the grid and\n * the rendered temporal midpoints. 0 for roll only; null without rotation.\n */\n rotationMaximumDiameterChangeSites: number | null;\n /**\n * Distortion: the largest deviation from 1 of the local scale of the distortion map (singular values of d distorted / d ideal)\n * over the grid: the relative disc-size difference to a model that blurs the ideal image and then distorts it. Null without distortion.\n */\n distortionMaximumLocalScaleDeviation: number | null;\n /**\n * A non-rectilinear projection: the largest change of the disc diameter, in sites, between the distance along each scene\n * point's ray and its axial depth (the thin-lens model uses the axial depth). Null for the rectilinear projection.\n */\n projectionMaximumDiameterChangeSites: number | null;\n}\n\n/** Internal: {@link DefocusFieldVariation} on a 9 × 9 grid of sensor points (the extremes of these smooth maps lie on the frame's edges). */\nexport function calculateDefocusFieldVariation(input: { geometry: PreparedDefocusImageSpaceGeometry; lensMapping?: PreparedEnvironmentLensFieldMapping;\n focalLengthMm: number; fNumber: number; focusDistanceM: number; subjectDistanceM: number; pitchMicrometers: number; nativeWidth: number; nativeHeight: number;\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec; temporalMidpointsSeconds: readonly number[]; diameterSites: number;\n discAt: (subjectDistanceM: number) => number }): DefocusFieldVariation {\n const { geometry, lensMapping, pitchMicrometers: pitch, nativeWidth: W, nativeHeight: H, subjectDistanceM: d, diameterSites, discAt } = input;\n const G = 9, points: { x: number; y: number }[] = [];\n for (let j = 0; j < G; j++) for (let i = 0; i < G; i++) points.push({ x: (i / (G - 1) - 0.5) * W * pitch / 1000, y: (j / (G - 1) - 0.5) * H * pitch / 1000 });\n let rotation: number | null = null;\n if (geometry.timeDependent) {\n rotation = 0;\n const omega = input.angularVelocityRadPerSec;\n for (const t of input.temporalMidpointsSeconds) {\n // The camera's optical axis at t in the reference axes; a scene point's axial depth in the rotated camera is P·axis.\n const axis = applyPhysicalCameraRotation({ x: 0, y: 0, z: 1 }, { pitch: omega.pitch * t, yaw: omega.yaw * t, roll: omega.roll * t }, \"camera-to-reference\");\n for (const p of points) {\n const look = geometry.lookOf(p.x, p.y, t), reach = d / look.z;\n const depth = reach * (look.x * axis.x + look.y * axis.y + look.z * axis.z);\n rotation = Math.max(rotation, Math.abs(discAt(depth) - diameterSites));\n }\n }\n }\n let distortion: number | null = null;\n if (lensMapping && lensMapping.mapping.distortion.kind !== \"none\") {\n distortion = 0;\n for (const p of points) {\n const scales = distortedImagePoint(lensMapping, undistortedImagePoint(lensMapping, { x: p.x, y: -p.y }).ideal);\n distortion = Math.max(distortion, scales.largestScale - 1, 1 - scales.smallestScale);\n }\n }\n let projection: number | null = null;\n if (lensMapping && lensMapping.mapping.projectionModel !== \"rectilinear\") {\n projection = 0;\n for (const p of points) {\n const look = geometry.lookOf(p.x, p.y, 0);\n projection = Math.max(projection, Math.abs(discAt(d / look.z) - diameterSites));\n }\n }\n return { gridPointsPerSide: G, rotationMaximumDiameterChangeSites: rotation, distortionMaximumLocalScaleDeviation: distortion, projectionMaximumDiameterChangeSites: projection };\n}\n"]}
@@ -44,3 +44,19 @@ export interface DiffractedPlanarProviderInput {
44
44
  }
45
45
  /** Internal (not exported from the package): the base provider with diffraction applied as an object-space convolution. */
46
46
  export declare function createDiffractedPlanarProvider(input: DiffractedPlanarProviderInput): PlanarSceneRadianceProvider;
47
+ /** #492: a site kernel of radius R, checked as `calculateIrradianceConvolution` checks its kernels. */
48
+ export declare function checkedSiteKernel(kernel: Float64Array, R: number): Float64Array;
49
+ /** #492: the non-zero taps of a kernel as offsets into a grid of `gridWidth` columns (−v·gridWidth − u) and weights, v outer, u inner, ascending. */
50
+ export declare function kernelTaps(kernel: Float64Array, R: number, gridWidth: number): {
51
+ offsets: Int32Array;
52
+ weights: Float64Array;
53
+ };
54
+ /**
55
+ * #492: out(x, y) = Σ_t K(t)·samples(x + R − t_u, y + R − t_v) for the band's own values only (x < width, y < rows): every tap of
56
+ * these lies inside the grid (it holds R extra samples on each side), so no boundary rule applies. Samples are checked as
57
+ * `calculateIrradianceConvolution` checks them.
58
+ */
59
+ export declare function gatherInterior(samples: Float64Array, taps: {
60
+ offsets: Int32Array;
61
+ weights: Float64Array;
62
+ }, gridWidth: number, width: number, rows: number, R: number): Float64Array;
@@ -218,7 +218,7 @@ export function createDiffractedPlanarProvider(input) {
218
218
  /** Largest kernel sum, and largest sample, the object-space convolution accepts (as `IRRADIANCE_CONVOLUTION_LIMITS`). */
219
219
  const MAXIMUM_KERNEL_SUM = 1 + 2 ** -20, MAXIMUM_SAMPLE = 2 ** 100;
220
220
  /** #492: a site kernel of radius R, checked as `calculateIrradianceConvolution` checks its kernels. */
221
- function checkedSiteKernel(kernel, R) {
221
+ export function checkedSiteKernel(kernel, R) {
222
222
  if (!(kernel instanceof Float64Array) || kernel.length !== (2 * R + 1) ** 2)
223
223
  throw new InvalidConfigurationError("Site kernels must be (2R + 1)² taps.");
224
224
  let sum = 0;
@@ -233,7 +233,7 @@ function checkedSiteKernel(kernel, R) {
233
233
  return kernel;
234
234
  }
235
235
  /** #492: the non-zero taps of a kernel as offsets into a grid of `gridWidth` columns (−v·gridWidth − u) and weights, v outer, u inner, ascending. */
236
- function kernelTaps(kernel, R, gridWidth) {
236
+ export function kernelTaps(kernel, R, gridWidth) {
237
237
  const offsets = [], weights = [];
238
238
  for (let v = -R, t = 0; v <= R; v++)
239
239
  for (let u = -R; u <= R; u++, t++) {
@@ -250,7 +250,7 @@ function kernelTaps(kernel, R, gridWidth) {
250
250
  * these lies inside the grid (it holds R extra samples on each side), so no boundary rule applies. Samples are checked as
251
251
  * `calculateIrradianceConvolution` checks them.
252
252
  */
253
- function gatherInterior(samples, taps, gridWidth, width, rows, R) {
253
+ export function gatherInterior(samples, taps, gridWidth, width, rows, R) {
254
254
  for (let k = 0; k < samples.length; k++) {
255
255
  const v = samples[k];
256
256
  if (!(v >= 0 && v <= MAXIMUM_SAMPLE))