@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
@@ -1 +1 @@
1
- {"version":3,"file":"diffraction-provider.js","sourceRoot":"","sources":["../../src/facade/diffraction-provider.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Fast execution of `simulateCapture`'s `lens.diffraction` (2.1): an object-space convolution in the scene\n * provider, instead of one scene query per PSF tap in the environment PSF quadrature.\n *\n * The environment quadrature computes, at each site centre q and spectral node λ,\n * E(q, λ) = Σ_k K_λ(k)·T·L(s_k(q), λ),\n * where s_k(q) is the tap's source point (`sensorPsfSourcePoint`), L the scene radiance at the plane point\n * that source point sees, and T the radiance-to-irradiance factor. For the facade's planar scene seen\n * through the ideal rectilinear projection with a still camera and no lens field mapping, field throughput or\n * pupil rays:\n * - T is the same at every sensor point (paraxial π/(4·N_w²), no cos⁴ term), and\n * - with the kernel sampled on the site pitch, every source point is the centre of another site, so the plane\n * points form one regular lattice: u = (i + ½ − W/2)·2/w, v = −(j + ½ − H/2)·2/w (frame units, native\n * site (i, j), W×H native sites, w output width).\n * So E(q, λ) = T·B(q, λ) with B = K_λ ⊛ L sampled on that lattice: the same sum, in a different floating-point\n * order. B is computed once per spectral node with the engine's isoplanatic convolution\n * (`calculateIrradianceConvolution`, kernels from `resolveLensPsfConvolutionKernels`, exact site sampling) over a\n * grid that holds every source (the native raster plus the kernel radius on each side), in row bands; each band gathers only\n * its own values, the grid's interior (#419, `createIrradianceConvolutionInteriorRunner`: each value bit-identical to\n * `calculateIrradianceConvolution`'s there, without the 2R rows and columns of the band's grid that no query reads). The\n * provider then answers each site-centre query with B instead of L, and the environment request declares no\n * PSF. Incoherent imaging is a linear superposition of intensity PSFs, so convolving radiance on the object\n * plane and convolving irradiance on the image plane agree for a planar object and a space-invariant PSF\n * (MIT OpenCourseWare 2.71, lecture 22, free; also see: Goodman, Introduction to Fourier Optics, 4th ed., §7.1).\n *\n * With an anti-aliasing point-splitting filter (2.1, `sensor.antiAliasing`) the environment queries the PSF taps\n * around each pre-filter source point q − a (a = a split offset) instead of around q. Those taps lie on the site\n * lattice shifted by a, so the same argument holds on each shifted lattice: B is computed once per distinct\n * fractional shift (offsets that differ by whole sites share one lattice; the centred four-spot filter with a\n * one-site separation needs a single half-site-shifted lattice) over the native raster widened by the largest\n * offset, and the environment's spatial quadrature still forms the weighted split sum.\n *\n * Region renders (2.6, `window`): only the lattice rows and columns that the region's sites can query (the window\n * plus the largest offset on each side) are convolved, over a grid that adds the kernel radius on each side. Every\n * output value is the same gather as in the whole render: the grid always holds every tap of every value it keeps\n * (real scene radiance out to the kernel radius, never a truncated edge), the single kernel patch does not depend on\n * the grid's origin, and the gather (`convolveIrradianceRows`, and its interior form) sums each value in a fixed tap order (v outer, u inner). Narrowing\n * the grid therefore removes only values that no query reads, and the kept values are bit-identical.\n *\n * A query that does not land on one of the lattices (within 1e-6 site) is refused rather than approximated: the\n * facade uses this path only when the lattice argument above holds. A query outside the window is refused too.\n *\n * Defocus (#492, `lens.defocus`): the same argument holds for any space-invariant kernel on the site lattice, so the facade\n * passes `siteKernels` instead (the geometric defocus disc, or each node's diffraction kernel convolved with it). Their radius\n * may exceed the public 32-site convolution limit, so this path does not call `calculateIrradianceConvolution`: it checks the\n * samples and the kernel sums as that function does and gathers only the band's own values (`gatherInterior`), each summed\n * over the taps in the same fixed order (v outer, u inner, ascending; zero taps skipped, which adds exact zeros only). Like\n * the diffraction path (#419), it does not compute the 2R grid rows and columns around a band.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { isInternalSceneRadianceEvaluator, markInternalSceneRadianceEvaluator, registerInternalRadianceAccess } from \"../core/internal-radiance-evaluator.js\";\nimport type { SceneRadianceEvaluationRequest, SceneRadianceEvaluationResult } from \"../schema/scene-radiance.js\";\nimport { createIrradianceConvolutionInteriorRunner } from \"../optics/irradiance-convolution.js\";\nimport { siteDiffractionKernel } from \"../optics/site-diffraction-kernel.js\";\nimport type { ResolveLensSampledPsfInput } from \"../optics/lens-psf-profile.js\";\nimport type { PlanarSceneRadianceProvider } from \"./scene-radiance.js\";\n\n/** Rows of native sites per convolution band, and the bands kept. Banding changes memory only, never values. */\nconst BAND_ROWS = 32;\nconst CACHED_BANDS = 4;\nconst LATTICE_TOLERANCE_SITES = 1e-6;\n\nexport interface DiffractedPlanarProviderInput {\n base: PlanarSceneRadianceProvider;\n /** The optical state of the sampled PSF, as for `resolveLensSampledPsf`, without field point and wavelength. Unused with `siteKernels`. */\n psf?: Omit<ResolveLensSampledPsfInput, \"fieldPointMm\" | \"wavelengthNm\">;\n /**\n * #492: the kernel of each spectral node on the site lattice, (2·kernelRadius + 1)² taps in row-major order, applied\n * instead of the diffraction kernels of `psf` (the defocus disc, or each node's diffraction kernel convolved with it).\n * Any radius; each kernel's sum may not exceed 1 + 2^-20.\n */\n siteKernels?: (wavelengthNm: number) => Float64Array;\n /** Spectral nodes the environment queries (the profile's wavelength nodes). */\n wavelengthsNm: readonly number[];\n kernelRadius: number;\n nativeWidth: number;\n nativeHeight: number;\n /** Output (active) width in sites: frame units are 2/outputWidth per site. */\n outputWidth: number;\n sitePitchMicrometers: number;\n /** Planar geometry of the base provider, to map query directions to plane points exactly as it does. */\n distanceM: number;\n metresPerFrameUnit: number;\n /**\n * Offsets of the query points from the site centres, in sites (+x right, +y down): the anti-aliasing split\n * offsets with their sign reversed (the environment queries q − a). Default: one zero offset.\n */\n queryOffsetsSites?: readonly { x: number; y: number }[];\n /**\n * Region renders (2.6): the native sites that will be queried (x, y, width, height in the native raster). Only the\n * lattice they can reach is convolved; values are bit-identical to the whole render's. Default: the whole raster.\n */\n window?: { x: number; y: number; width: number; height: number };\n /** Called with the duration in ms of each band convolution (diagnostics only; never changes values). */\n onConvolution?: (milliseconds: number) => void;\n}\n\n/** Internal (not exported from the package): the base provider with diffraction applied as an object-space convolution. */\nexport function createDiffractedPlanarProvider(input: DiffractedPlanarProviderInput): PlanarSceneRadianceProvider {\n const { base, wavelengthsNm, nativeWidth: W, nativeHeight: H, kernelRadius: R } = input;\n const g = 2 / input.outputWidth, pitch = input.sitePitchMicrometers;\n const index = new Map(wavelengthsNm.map((nm, i) => [nm, i]));\n // One lattice per distinct fractional query offset; M extra sites per side hold the offset queries.\n const offsets = input.queryOffsetsSites ?? [{ x: 0, y: 0 }];\n const fraction = (value: number): number => { const f = value - Math.floor(value); return f > 1 - LATTICE_TOLERANCE_SITES ? 0 : f < LATTICE_TOLERANCE_SITES ? 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) <= LATTICE_TOLERANCE_SITES && Math.abs(s.y - f.y) <= LATTICE_TOLERANCE_SITES)) 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)) - LATTICE_TOLERANCE_SITES)));\n const LW = W + 2 * M, LH = H + 2 * M;\n // Lattice window (indices i + M, j + M): the sites the region queries plus the largest offset on each side, clipped\n // to the lattice. A site i queries i + floor(offset), and floor(offset) lies in [-M, 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(\"Diffraction window must overlap the native raster.\");\n const NW = lx1 - lx0;\n // One kernel per wavelength, on the site grid, from the engine's own tap-to-site mapping (field point 0, 0); kept across calls.\n const siteKernels = input.siteKernels;\n if (!siteKernels && !input.psf) throw new InvalidConfigurationError(\"The object-space convolution needs a PSF or site kernels.\");\n const kernels = wavelengthsNm.map((wavelengthNm) => siteKernels ? checkedSiteKernel(siteKernels(wavelengthNm), R) :\n siteDiffractionKernel(input.psf!, wavelengthNm, pitch, R, \"Diffraction kernels must sample the site pitch exactly.\"));\n // #492: each site kernel's non-zero taps as grid offsets, in the gather's order.\n const taps = siteKernels ? kernels.map((kernel) => kernelTaps(kernel, R, NW + 2 * R)) : [];\n const gridWidth = NW + 2 * R;\n const now = (): number => globalThis.performance?.now() ?? Date.now();\n const bands = new Map<number, Float64Array[][]>();\n const materialAt = base.chartMaterialAt?.bind(base), materialRadiance = base.materialRadiance?.bind(base);\n function band(b: number): Float64Array[][] {\n const cached = bands.get(b);\n if (cached) { bands.delete(b); bands.set(b, 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 // #453: the band's shifts × spectral nodes convolutions share one geometry, validated once (the first with every check of\n // calculateIrradianceConvolution; every later one still checks its samples and kernel). #419: only the band's own rows × NW\n // values are gathered (the grid's interior), each bit-identical to calculateIrradianceConvolution's value there.\n const convolve = siteKernels ? undefined : createIrradianceConvolutionInteriorRunner({ schemaVersion: \"0.1.0\", width: gridWidth, height: gridHeight, kernelRadius: R,\n patchWidth: gridWidth, patchHeight: gridHeight, boundary: \"zero-outside-grid\" });\n const values = shifts.map((shift) => {\n // The plane point of lattice grid sample (gx, gy) of this shift, the same expressions for both fills below.\n const vOf = (gy: number): number => shift.y === 0 ? -(y0 + gy - R - M + 0.5 - H / 2) * g : -(y0 + gy - R - M + 0.5 + shift.y - H / 2) * g;\n const uOf = (x: number): number => shift.x === 0 ? (x - R - M + 0.5 - W / 2) * g : (x - R - M + 0.5 + shift.x - W / 2) * g;\n // #453, material scenes (chart presets and caller material scenes): radianceAt(u, v, nm) is materialRadiance(chartMaterialAt(u, v), nm)\n // (scene-radiance.ts), so the material is looked up once per lattice point and each node's samples come from one\n // materialRadiance per material present, the same numbers as one radianceAt per point and node.\n let materialOf: Int32Array | undefined;\n if (materialAt && materialRadiance) {\n materialOf = new Int32Array(size);\n for (let gy = 0; gy < gridHeight; gy++) {\n const v = vOf(gy);\n for (let gx = 0, x = lx0; gx < gridWidth; gx++, x++) materialOf[gy * gridWidth + gx] = materialAt(uOf(x), v);\n }\n }\n return wavelengthsNm.map((nm, w) => {\n const samples = new Float64Array(size);\n if (materialOf) {\n const radiance = new Map<number, number>();\n for (let i = 0; i < size; i++) {\n const m = materialOf[i]!;\n let value = radiance.get(m);\n if (value === undefined) { value = materialRadiance!(m, nm); radiance.set(m, value); }\n samples[i] = value;\n }\n } else {\n for (let gy = 0; gy < gridHeight; gy++) {\n const v = vOf(gy);\n for (let gx = 0, x = lx0; gx < gridWidth; gx++, x++) samples[gy * gridWidth + gx] = base.radianceAt(uOf(x), v, nm);\n }\n }\n if (!convolve) return gatherInterior(samples, taps[w]!, gridWidth, NW, rows, R);\n return convolve(samples, kernels[w]!);\n });\n });\n bands.set(b, values);\n if (bands.size > CACHED_BANDS) bands.delete(bands.keys().next().value!);\n if (input.onConvolution) input.onConvolution(now() - started);\n return values;\n }\n function blurredAt(u: number, v: number, nm: number): number {\n const w = index.get(nm), fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;\n if (w !== undefined) 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) break;\n return band(Math.floor((lj - ly0) / BAND_ROWS))[s]![w]![((lj - ly0) % BAND_ROWS) * NW + li - lx0]!;\n }\n throw new InvalidConfigurationError(\"Diffraction query is off the site lattice; the object-space convolution does not apply.\");\n }\n const scale = input.metresPerFrameUnit, distance = input.distanceM, 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 const t = distance / -o.z;\n return { ...result, spectralRadianceWattsPerSquareMeterSteradianNanometer: blurredAt(-o.x * t / scale, -o.y * t / scale, request.wavelengthNanometers) };\n };\n // 2.6: the result is the base result with only its radiance replaced (same keys and records), so an engine-built base\n // evaluator keeps its mark (see core/internal-radiance-evaluator.ts).\n if (isInternalSceneRadianceEvaluator(baseEvaluate)) {\n markInternalSceneRadianceEvaluator(evaluateRadiance);\n // #394: its radiance is the convolved value at one lattice site (shift s, lattice column li, row lj) per wavelength, so\n // the lean executor may read it by that key: the same plane point, lattice choice and band value as evaluateRadiance.\n registerInternalRadianceAccess(evaluateRadiance, {\n tabulated: false,\n keyOfDirection(o): number | undefined {\n if (!(-o.z > 0)) return undefined;\n const t = distance / -o.z, u = -o.x * t / scale, v = -o.y * t / scale, fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;\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 radianceOfKey(key, nm): number {\n const w = index.get(nm);\n if (w === undefined) throw new InvalidConfigurationError(\"Diffraction query is off the site lattice; the object-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))[s]![w]![((lj - ly0) % BAND_ROWS) * NW + li - lx0]!;\n }\n });\n }\n return {\n sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt: base.radianceAt,\n evaluateRadiance,\n evaluateApertureRadiance(): SceneRadianceEvaluationResult {\n throw new InvalidConfigurationError(\"Diffraction by object-space convolution does not take pupil rays.\");\n }\n };\n}\n\n/** Largest kernel sum, and largest sample, the object-space convolution accepts (as `IRRADIANCE_CONVOLUTION_LIMITS`). */\nconst MAXIMUM_KERNEL_SUM = 1 + 2 ** -20, MAXIMUM_SAMPLE = 2 ** 100;\n\n/** #492: a site kernel of radius R, checked as `calculateIrradianceConvolution` checks its kernels. */\nfunction checkedSiteKernel(kernel: Float64Array, R: number): Float64Array {\n if (!(kernel instanceof Float64Array) || kernel.length !== (2 * R + 1) ** 2) throw new InvalidConfigurationError(\"Site kernels must be (2R + 1)² taps.\");\n let sum = 0;\n for (let k = 0; k < kernel.length; k++) {\n const w = kernel[k]!;\n if (!(w >= 0 && w <= 1)) throw new InvalidConfigurationError(\"Site kernel weights must be finite numbers in [0, 1].\");\n sum += w;\n }\n if (sum > MAXIMUM_KERNEL_SUM) throw new InvalidConfigurationError(\"A PSF kernel may not create energy (sum exceeds 1).\");\n return kernel;\n}\n\n/** #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. */\nfunction kernelTaps(kernel: Float64Array, R: number, gridWidth: number): { offsets: Int32Array; weights: Float64Array } {\n const offsets: number[] = [], weights: number[] = [];\n for (let v = -R, t = 0; v <= R; v++) for (let u = -R; u <= R; u++, t++) {\n const w = kernel[t]!;\n if (w !== 0) { offsets.push(-v * gridWidth - u); weights.push(w); }\n }\n return { offsets: Int32Array.from(offsets), weights: Float64Array.from(weights) };\n}\n\n/**\n * #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\n * these lies inside the grid (it holds R extra samples on each side), so no boundary rule applies. Samples are checked as\n * `calculateIrradianceConvolution` checks them.\n */\nfunction gatherInterior(samples: Float64Array, taps: { offsets: Int32Array; weights: Float64Array }, gridWidth: number, width: number, rows: number, R: number): Float64Array {\n for (let k = 0; k < samples.length; k++) {\n const v = samples[k]!;\n if (!(v >= 0 && v <= MAXIMUM_SAMPLE)) throw new InvalidConfigurationError(\"Irradiance samples must be finite, non-negative and within the declared magnitude.\");\n }\n const { offsets, weights } = taps, count = weights.length, out = new Float64Array(width * rows);\n for (let y = 0; y < rows; y++) {\n const rowBase = (y + R) * gridWidth + R;\n for (let x = 0; x < width; x++) {\n const base = rowBase + x;\n let sum = 0;\n for (let k = 0; k < count; k++) sum += samples[base + offsets[k]!]! * weights[k]!;\n out[y * width + x] = sum;\n }\n }\n return out;\n}\n"]}
1
+ {"version":3,"file":"diffraction-provider.js","sourceRoot":"","sources":["../../src/facade/diffraction-provider.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Fast execution of `simulateCapture`'s `lens.diffraction` (2.1): an object-space convolution in the scene\n * provider, instead of one scene query per PSF tap in the environment PSF quadrature.\n *\n * The environment quadrature computes, at each site centre q and spectral node λ,\n * E(q, λ) = Σ_k K_λ(k)·T·L(s_k(q), λ),\n * where s_k(q) is the tap's source point (`sensorPsfSourcePoint`), L the scene radiance at the plane point\n * that source point sees, and T the radiance-to-irradiance factor. For the facade's planar scene seen\n * through the ideal rectilinear projection with a still camera and no lens field mapping, field throughput or\n * pupil rays:\n * - T is the same at every sensor point (paraxial π/(4·N_w²), no cos⁴ term), and\n * - with the kernel sampled on the site pitch, every source point is the centre of another site, so the plane\n * points form one regular lattice: u = (i + ½ − W/2)·2/w, v = −(j + ½ − H/2)·2/w (frame units, native\n * site (i, j), W×H native sites, w output width).\n * So E(q, λ) = T·B(q, λ) with B = K_λ ⊛ L sampled on that lattice: the same sum, in a different floating-point\n * order. B is computed once per spectral node with the engine's isoplanatic convolution\n * (`calculateIrradianceConvolution`, kernels from `resolveLensPsfConvolutionKernels`, exact site sampling) over a\n * grid that holds every source (the native raster plus the kernel radius on each side), in row bands; each band gathers only\n * its own values, the grid's interior (#419, `createIrradianceConvolutionInteriorRunner`: each value bit-identical to\n * `calculateIrradianceConvolution`'s there, without the 2R rows and columns of the band's grid that no query reads). The\n * provider then answers each site-centre query with B instead of L, and the environment request declares no\n * PSF. Incoherent imaging is a linear superposition of intensity PSFs, so convolving radiance on the object\n * plane and convolving irradiance on the image plane agree for a planar object and a space-invariant PSF\n * (MIT OpenCourseWare 2.71, lecture 22, free; also see: Goodman, Introduction to Fourier Optics, 4th ed., §7.1).\n *\n * With an anti-aliasing point-splitting filter (2.1, `sensor.antiAliasing`) the environment queries the PSF taps\n * around each pre-filter source point q − a (a = a split offset) instead of around q. Those taps lie on the site\n * lattice shifted by a, so the same argument holds on each shifted lattice: B is computed once per distinct\n * fractional shift (offsets that differ by whole sites share one lattice; the centred four-spot filter with a\n * one-site separation needs a single half-site-shifted lattice) over the native raster widened by the largest\n * offset, and the environment's spatial quadrature still forms the weighted split sum.\n *\n * Region renders (2.6, `window`): only the lattice rows and columns that the region's sites can query (the window\n * plus the largest offset on each side) are convolved, over a grid that adds the kernel radius on each side. Every\n * output value is the same gather as in the whole render: the grid always holds every tap of every value it keeps\n * (real scene radiance out to the kernel radius, never a truncated edge), the single kernel patch does not depend on\n * the grid's origin, and the gather (`convolveIrradianceRows`, and its interior form) sums each value in a fixed tap order (v outer, u inner). Narrowing\n * the grid therefore removes only values that no query reads, and the kept values are bit-identical.\n *\n * A query that does not land on one of the lattices (within 1e-6 site) is refused rather than approximated: the\n * facade uses this path only when the lattice argument above holds. A query outside the window is refused too.\n *\n * Defocus (#492, `lens.defocus`): the same argument holds for any space-invariant kernel on the site lattice, so the facade\n * passes `siteKernels` instead (the geometric defocus disc, or each node's diffraction kernel convolved with it). Their radius\n * may exceed the public 32-site convolution limit, so this path does not call `calculateIrradianceConvolution`: it checks the\n * samples and the kernel sums as that function does and gathers only the band's own values (`gatherInterior`), each summed\n * over the taps in the same fixed order (v outer, u inner, ascending; zero taps skipped, which adds exact zeros only). Like\n * the diffraction path (#419), it does not compute the 2R grid rows and columns around a band.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { isInternalSceneRadianceEvaluator, markInternalSceneRadianceEvaluator, registerInternalRadianceAccess } from \"../core/internal-radiance-evaluator.js\";\nimport type { SceneRadianceEvaluationRequest, SceneRadianceEvaluationResult } from \"../schema/scene-radiance.js\";\nimport { createIrradianceConvolutionInteriorRunner } from \"../optics/irradiance-convolution.js\";\nimport { siteDiffractionKernel } from \"../optics/site-diffraction-kernel.js\";\nimport type { ResolveLensSampledPsfInput } from \"../optics/lens-psf-profile.js\";\nimport type { PlanarSceneRadianceProvider } from \"./scene-radiance.js\";\n\n/** Rows of native sites per convolution band, and the bands kept. Banding changes memory only, never values. */\nconst BAND_ROWS = 32;\nconst CACHED_BANDS = 4;\nconst LATTICE_TOLERANCE_SITES = 1e-6;\n\nexport interface DiffractedPlanarProviderInput {\n base: PlanarSceneRadianceProvider;\n /** The optical state of the sampled PSF, as for `resolveLensSampledPsf`, without field point and wavelength. Unused with `siteKernels`. */\n psf?: Omit<ResolveLensSampledPsfInput, \"fieldPointMm\" | \"wavelengthNm\">;\n /**\n * #492: the kernel of each spectral node on the site lattice, (2·kernelRadius + 1)² taps in row-major order, applied\n * instead of the diffraction kernels of `psf` (the defocus disc, or each node's diffraction kernel convolved with it).\n * Any radius; each kernel's sum may not exceed 1 + 2^-20.\n */\n siteKernels?: (wavelengthNm: number) => Float64Array;\n /** Spectral nodes the environment queries (the profile's wavelength nodes). */\n wavelengthsNm: readonly number[];\n kernelRadius: number;\n nativeWidth: number;\n nativeHeight: number;\n /** Output (active) width in sites: frame units are 2/outputWidth per site. */\n outputWidth: number;\n sitePitchMicrometers: number;\n /** Planar geometry of the base provider, to map query directions to plane points exactly as it does. */\n distanceM: number;\n metresPerFrameUnit: number;\n /**\n * Offsets of the query points from the site centres, in sites (+x right, +y down): the anti-aliasing split\n * offsets with their sign reversed (the environment queries q − a). Default: one zero offset.\n */\n queryOffsetsSites?: readonly { x: number; y: number }[];\n /**\n * Region renders (2.6): the native sites that will be queried (x, y, width, height in the native raster). Only the\n * lattice they can reach is convolved; values are bit-identical to the whole render's. Default: the whole raster.\n */\n window?: { x: number; y: number; width: number; height: number };\n /** Called with the duration in ms of each band convolution (diagnostics only; never changes values). */\n onConvolution?: (milliseconds: number) => void;\n}\n\n/** Internal (not exported from the package): the base provider with diffraction applied as an object-space convolution. */\nexport function createDiffractedPlanarProvider(input: DiffractedPlanarProviderInput): PlanarSceneRadianceProvider {\n const { base, wavelengthsNm, nativeWidth: W, nativeHeight: H, kernelRadius: R } = input;\n const g = 2 / input.outputWidth, pitch = input.sitePitchMicrometers;\n const index = new Map(wavelengthsNm.map((nm, i) => [nm, i]));\n // One lattice per distinct fractional query offset; M extra sites per side hold the offset queries.\n const offsets = input.queryOffsetsSites ?? [{ x: 0, y: 0 }];\n const fraction = (value: number): number => { const f = value - Math.floor(value); return f > 1 - LATTICE_TOLERANCE_SITES ? 0 : f < LATTICE_TOLERANCE_SITES ? 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) <= LATTICE_TOLERANCE_SITES && Math.abs(s.y - f.y) <= LATTICE_TOLERANCE_SITES)) 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)) - LATTICE_TOLERANCE_SITES)));\n const LW = W + 2 * M, LH = H + 2 * M;\n // Lattice window (indices i + M, j + M): the sites the region queries plus the largest offset on each side, clipped\n // to the lattice. A site i queries i + floor(offset), and floor(offset) lies in [-M, 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(\"Diffraction window must overlap the native raster.\");\n const NW = lx1 - lx0;\n // One kernel per wavelength, on the site grid, from the engine's own tap-to-site mapping (field point 0, 0); kept across calls.\n const siteKernels = input.siteKernels;\n if (!siteKernels && !input.psf) throw new InvalidConfigurationError(\"The object-space convolution needs a PSF or site kernels.\");\n const kernels = wavelengthsNm.map((wavelengthNm) => siteKernels ? checkedSiteKernel(siteKernels(wavelengthNm), R) :\n siteDiffractionKernel(input.psf!, wavelengthNm, pitch, R, \"Diffraction kernels must sample the site pitch exactly.\"));\n // #492: each site kernel's non-zero taps as grid offsets, in the gather's order.\n const taps = siteKernels ? kernels.map((kernel) => kernelTaps(kernel, R, NW + 2 * R)) : [];\n const gridWidth = NW + 2 * R;\n const now = (): number => globalThis.performance?.now() ?? Date.now();\n const bands = new Map<number, Float64Array[][]>();\n const materialAt = base.chartMaterialAt?.bind(base), materialRadiance = base.materialRadiance?.bind(base);\n function band(b: number): Float64Array[][] {\n const cached = bands.get(b);\n if (cached) { bands.delete(b); bands.set(b, 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 // #453: the band's shifts × spectral nodes convolutions share one geometry, validated once (the first with every check of\n // calculateIrradianceConvolution; every later one still checks its samples and kernel). #419: only the band's own rows × NW\n // values are gathered (the grid's interior), each bit-identical to calculateIrradianceConvolution's value there.\n const convolve = siteKernels ? undefined : createIrradianceConvolutionInteriorRunner({ schemaVersion: \"0.1.0\", width: gridWidth, height: gridHeight, kernelRadius: R,\n patchWidth: gridWidth, patchHeight: gridHeight, boundary: \"zero-outside-grid\" });\n const values = shifts.map((shift) => {\n // The plane point of lattice grid sample (gx, gy) of this shift, the same expressions for both fills below.\n const vOf = (gy: number): number => shift.y === 0 ? -(y0 + gy - R - M + 0.5 - H / 2) * g : -(y0 + gy - R - M + 0.5 + shift.y - H / 2) * g;\n const uOf = (x: number): number => shift.x === 0 ? (x - R - M + 0.5 - W / 2) * g : (x - R - M + 0.5 + shift.x - W / 2) * g;\n // #453, material scenes (chart presets and caller material scenes): radianceAt(u, v, nm) is materialRadiance(chartMaterialAt(u, v), nm)\n // (scene-radiance.ts), so the material is looked up once per lattice point and each node's samples come from one\n // materialRadiance per material present, the same numbers as one radianceAt per point and node.\n let materialOf: Int32Array | undefined;\n if (materialAt && materialRadiance) {\n materialOf = new Int32Array(size);\n for (let gy = 0; gy < gridHeight; gy++) {\n const v = vOf(gy);\n for (let gx = 0, x = lx0; gx < gridWidth; gx++, x++) materialOf[gy * gridWidth + gx] = materialAt(uOf(x), v);\n }\n }\n return wavelengthsNm.map((nm, w) => {\n const samples = new Float64Array(size);\n if (materialOf) {\n const radiance = new Map<number, number>();\n for (let i = 0; i < size; i++) {\n const m = materialOf[i]!;\n let value = radiance.get(m);\n if (value === undefined) { value = materialRadiance!(m, nm); radiance.set(m, value); }\n samples[i] = value;\n }\n } else {\n for (let gy = 0; gy < gridHeight; gy++) {\n const v = vOf(gy);\n for (let gx = 0, x = lx0; gx < gridWidth; gx++, x++) samples[gy * gridWidth + gx] = base.radianceAt(uOf(x), v, nm);\n }\n }\n if (!convolve) return gatherInterior(samples, taps[w]!, gridWidth, NW, rows, R);\n return convolve(samples, kernels[w]!);\n });\n });\n bands.set(b, values);\n if (bands.size > CACHED_BANDS) bands.delete(bands.keys().next().value!);\n if (input.onConvolution) input.onConvolution(now() - started);\n return values;\n }\n function blurredAt(u: number, v: number, nm: number): number {\n const w = index.get(nm), fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;\n if (w !== undefined) 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) break;\n return band(Math.floor((lj - ly0) / BAND_ROWS))[s]![w]![((lj - ly0) % BAND_ROWS) * NW + li - lx0]!;\n }\n throw new InvalidConfigurationError(\"Diffraction query is off the site lattice; the object-space convolution does not apply.\");\n }\n const scale = input.metresPerFrameUnit, distance = input.distanceM, 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 const t = distance / -o.z;\n return { ...result, spectralRadianceWattsPerSquareMeterSteradianNanometer: blurredAt(-o.x * t / scale, -o.y * t / scale, request.wavelengthNanometers) };\n };\n // 2.6: the result is the base result with only its radiance replaced (same keys and records), so an engine-built base\n // evaluator keeps its mark (see core/internal-radiance-evaluator.ts).\n if (isInternalSceneRadianceEvaluator(baseEvaluate)) {\n markInternalSceneRadianceEvaluator(evaluateRadiance);\n // #394: its radiance is the convolved value at one lattice site (shift s, lattice column li, row lj) per wavelength, so\n // the lean executor may read it by that key: the same plane point, lattice choice and band value as evaluateRadiance.\n registerInternalRadianceAccess(evaluateRadiance, {\n tabulated: false,\n keyOfDirection(o): number | undefined {\n if (!(-o.z > 0)) return undefined;\n const t = distance / -o.z, u = -o.x * t / scale, v = -o.y * t / scale, fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;\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 radianceOfKey(key, nm): number {\n const w = index.get(nm);\n if (w === undefined) throw new InvalidConfigurationError(\"Diffraction query is off the site lattice; the object-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))[s]![w]![((lj - ly0) % BAND_ROWS) * NW + li - lx0]!;\n }\n });\n }\n return {\n sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt: base.radianceAt,\n evaluateRadiance,\n evaluateApertureRadiance(): SceneRadianceEvaluationResult {\n throw new InvalidConfigurationError(\"Diffraction by object-space convolution does not take pupil rays.\");\n }\n };\n}\n\n/** Largest kernel sum, and largest sample, the object-space convolution accepts (as `IRRADIANCE_CONVOLUTION_LIMITS`). */\nconst MAXIMUM_KERNEL_SUM = 1 + 2 ** -20, MAXIMUM_SAMPLE = 2 ** 100;\n\n/** #492: a site kernel of radius R, checked as `calculateIrradianceConvolution` checks its kernels. */\nexport function checkedSiteKernel(kernel: Float64Array, R: number): Float64Array {\n if (!(kernel instanceof Float64Array) || kernel.length !== (2 * R + 1) ** 2) throw new InvalidConfigurationError(\"Site kernels must be (2R + 1)² taps.\");\n let sum = 0;\n for (let k = 0; k < kernel.length; k++) {\n const w = kernel[k]!;\n if (!(w >= 0 && w <= 1)) throw new InvalidConfigurationError(\"Site kernel weights must be finite numbers in [0, 1].\");\n sum += w;\n }\n if (sum > MAXIMUM_KERNEL_SUM) throw new InvalidConfigurationError(\"A PSF kernel may not create energy (sum exceeds 1).\");\n return kernel;\n}\n\n/** #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. */\nexport function kernelTaps(kernel: Float64Array, R: number, gridWidth: number): { offsets: Int32Array; weights: Float64Array } {\n const offsets: number[] = [], weights: number[] = [];\n for (let v = -R, t = 0; v <= R; v++) for (let u = -R; u <= R; u++, t++) {\n const w = kernel[t]!;\n if (w !== 0) { offsets.push(-v * gridWidth - u); weights.push(w); }\n }\n return { offsets: Int32Array.from(offsets), weights: Float64Array.from(weights) };\n}\n\n/**\n * #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\n * these lies inside the grid (it holds R extra samples on each side), so no boundary rule applies. Samples are checked as\n * `calculateIrradianceConvolution` checks them.\n */\nexport function gatherInterior(samples: Float64Array, taps: { offsets: Int32Array; weights: Float64Array }, gridWidth: number, width: number, rows: number, R: number): Float64Array {\n for (let k = 0; k < samples.length; k++) {\n const v = samples[k]!;\n if (!(v >= 0 && v <= MAXIMUM_SAMPLE)) throw new InvalidConfigurationError(\"Irradiance samples must be finite, non-negative and within the declared magnitude.\");\n }\n const { offsets, weights } = taps, count = weights.length, out = new Float64Array(width * rows);\n for (let y = 0; y < rows; y++) {\n const rowBase = (y + R) * gridWidth + R;\n for (let x = 0; x < width; x++) {\n const base = rowBase + x;\n let sum = 0;\n for (let k = 0; k < count; k++) sum += samples[base + offsets[k]!]! * weights[k]!;\n out[y * width + x] = sum;\n }\n }\n return out;\n}\n"]}
@@ -129,6 +129,16 @@ export interface DisplayViewRenderResult {
129
129
  * overlap weight (views where a footprint holds at most K² real pixels); "stratified-monte-carlo" otherwise.
130
130
  */
131
131
  estimation: "exact-area-mean" | "stratified-monte-carlo";
132
+ /**
133
+ * #533: per display pixel (region row-major), the expected number of the real capture's output pixels in its rectangle that
134
+ * have at least one clipped contributing site (`simulateCapture`'s `diagnostics.highlight.saturatedPixelCount`, counted on
135
+ * the capture's one noise sample): Σ_j w_j·P_j, w_j the area of real pixel j inside the rectangle and P_j the probability,
136
+ * under the view's site model, that a site j's demosaic reads is at or above the white level. Estimated as the expected value
137
+ * is (exact area mean or stratified Monte Carlo); it never changes a sample.
138
+ */
139
+ saturated: Float64Array;
140
+ /** #533: Σ of `saturated` in region row-major order: the region's expected saturated-pixel count (additive over regions). */
141
+ saturatedPixelCount: number;
132
142
  }
133
143
  /** One tap of a linear demosaic kernel: the site at (dx, dy) from the pixel, and its weight. */
134
144
  export interface DisplayViewKernelTap {
@@ -149,6 +159,17 @@ export declare function overlaps(start: number, end: number, limit: number): {
149
159
  first: number;
150
160
  w: Float64Array;
151
161
  };
162
+ /**
163
+ * Internal (#533, the GPU renderers' host): the saturated-pixel share `renderDisplayView` gives a display pixel on the exact path
164
+ * when every real pixel's saturation probability is exactly 0 or 1: bit j·nx + i of the 36-bit mask (`low` bits 0–31, `high` bits
165
+ * 32–35) is 1 for P = 1, nx the columns' overlap count. The arithmetic is the render's own (Σ_j a_j·P_j in its order, a_j = w_x·w_y / A,
166
+ * then times A), so the value has the reference's bits.
167
+ */
168
+ export declare function displayViewDecidedSaturation(columns: {
169
+ w: Float64Array;
170
+ }, rows: {
171
+ w: Float64Array;
172
+ }, area: number, low: number, high: number): number;
152
173
  /**
153
174
  * Internal (exported for tests). Footprint sums along one axis, for the closed-form B_c (docs/DISPLAY_VIEW.md, "Demosaic correlation"). Native
154
175
  * coordinate parity decides the CFA: red columns/rows are even, blue odd. With w(k) the overlap of real column k:
@@ -226,6 +226,22 @@ export function overlaps(start, end, limit) {
226
226
  w[k - first] = Math.min(end, k + 1) - Math.max(start, k);
227
227
  return { first, w };
228
228
  }
229
+ /**
230
+ * Internal (#533, the GPU renderers' host): the saturated-pixel share `renderDisplayView` gives a display pixel on the exact path
231
+ * when every real pixel's saturation probability is exactly 0 or 1: bit j·nx + i of the 36-bit mask (`low` bits 0–31, `high` bits
232
+ * 32–35) is 1 for P = 1, nx the columns' overlap count. The arithmetic is the render's own (Σ_j a_j·P_j in its order, a_j = w_x·w_y / A,
233
+ * then times A), so the value has the reference's bits.
234
+ */
235
+ export function displayViewDecidedSaturation(columns, rows, area, low, high) {
236
+ const nx = columns.w.length;
237
+ let sat = 0;
238
+ for (let j = 0; j < rows.w.length; j++)
239
+ for (let i = 0; i < nx; i++) {
240
+ const bit = j * nx + i, set = bit < 32 ? (low >>> bit) & 1 : (high >>> (bit - 32)) & 1;
241
+ sat += columns.w[i] * rows.w[j] / area * (set === 1 ? 1 : 0);
242
+ }
243
+ return (sat / 1) * area;
244
+ }
229
245
  export function axisSums(start, end, limit, margin) {
230
246
  const { first, w } = overlaps(start, end, limit);
231
247
  const at = (k) => (k >= first && k < first + w.length ? w[k - first] : 0);
@@ -482,9 +498,10 @@ function correctionStates(key) {
482
498
  /**
483
499
  * Recorded terms of a one-material sample: [0, 3) the channel site variances csv; [3, 21) per candidate d and channel c the
484
500
  * linear slopes a (3 + (3d + c)·3); [21, 27) per candidate the residual terms; [27, 30) the expected-value sums; [30, 33) the
485
- * AHD choice terms; 33 a 1 when the AHD correction applies and [34, 37) its values.
501
+ * AHD choice terms; 33 a 1 when the AHD correction applies and [34, 37) its values; 37 the real pixel's saturation probability
502
+ * (#533: the probability that at least one site its demosaic reads is clipped at the white level).
486
503
  */
487
- const MEMO_SIZE = 37;
504
+ const MEMO_SIZE = 38;
488
505
  /** Most real pixels a display pixel's footprint may have for the per-real-pixel memo (#392); larger footprints rarely repeat a real pixel. */
489
506
  const PIXEL_MEMO_LIMIT = 1 << 16;
490
507
  /**
@@ -793,7 +810,8 @@ export async function renderDisplayView(setup) {
793
810
  const maximum = setup.bitDepth === 8 ? 255 : 65535, step = 1 / maximum;
794
811
  const pixelCount = region.width * region.height;
795
812
  const samples = setup.bitDepth === 8 ? new Uint8Array(pixelCount * 3) : new Uint16Array(pixelCount * 3);
796
- const expected = new Float64Array(pixelCount * 3), noiseStd = new Float64Array(pixelCount * 3);
813
+ const expected = new Float64Array(pixelCount * 3), noiseStd = new Float64Array(pixelCount * 3), saturated = new Float64Array(pixelCount);
814
+ let saturatedTotal = 0;
797
815
  const columns = Array.from({ length: region.width }, (_, i) => axisSums((region.x + i) * sx, (region.x + i + 1) * sx, frameWidth, margin));
798
816
  const M = setup.matrix, m00 = M[0][0], m01 = M[0][1], m02 = M[0][2], m10 = M[1][0], m11 = M[1][1], m12 = M[1][2], m20 = M[2][0], m21 = M[2][1], m22 = M[2][2];
799
817
  const g0 = setup.gains[0], g1 = setup.gains[1], g2 = setup.gains[2], L = setup.clipLevel, reinhard = setup.toneCurve === "positive-reinhard-per-channel";
@@ -804,6 +822,8 @@ export async function renderDisplayView(setup) {
804
822
  const reach = Math.max(1, ...candidates.flat(3).map((t) => Math.max(Math.abs(t.dx), Math.abs(t.dy)))), side = 2 * reach + 1, slotsCount = side * side;
805
823
  const slotOf = (t) => (t.dy + reach) * side + t.dx + reach;
806
824
  const { dx: slotDx, dy: slotDy } = displayViewSlotOffsets(reach);
825
+ /** The raster index of native site (x, y) read reflect-101, as the sample loop reads it. */
826
+ const reflectedSite = (x, y) => (y < 0 ? -y : y >= nativeHeight ? 2 * nativeHeight - 2 - y : y) * nativeWidth + (x < 0 ? -x : x >= nativeWidth ? 2 * nativeWidth - 2 - x : x);
807
827
  // Per phase: the bilinear kernels (for the site variance of each channel), the candidates, and the slots they read.
808
828
  const phaseSlots = [], phaseWeights = [], phaseW2 = [], phaseList = [];
809
829
  const candidateSlots = [], candidateWeights = [], candidateDense = [], candidateList = [];
@@ -826,7 +846,7 @@ export async function renderDisplayView(setup) {
826
846
  }
827
847
  // The same tables flat (phase·2 + candidate, ·3 + channel), read by the sample loop below.
828
848
  const { count: candidateCount, denseAt, listAt, slotsAt, weightsAt } = flatDisplayViewCandidateTables(candidateSlots, candidateWeights, candidateDense, candidateList);
829
- const siteElectrons = new Float64Array(slotsCount), siteMean = new Float64Array(slotsCount), siteVar = new Float64Array(slotsCount), siteThird = new Float64Array(slotsCount), siteChannel = new Int32Array(slotsCount), clip = new Float64Array(3), B = new Float64Array(4), C = new Float64Array(9);
849
+ const siteElectrons = new Float64Array(slotsCount), siteClip = new Float64Array(slotsCount), siteMean = new Float64Array(slotsCount), siteVar = new Float64Array(slotsCount), siteThird = new Float64Array(slotsCount), siteChannel = new Int32Array(slotsCount), clip = new Float64Array(3), B = new Float64Array(4), C = new Float64Array(9);
830
850
  const cm = new Float64Array(3), cv = new Float64Array(3), csv = new Float64Array(3), bm = new Float64Array(3), bv = new Float64Array(3), bs = new Float64Array(3);
831
851
  const hm = new Float64Array(3), hv = new Float64Array(3), hs = new Float64Array(3), an = new Float64Array(9);
832
852
  const reach2 = CLIP_REACH_SIGMA * CLIP_REACH_SIGMA;
@@ -847,7 +867,7 @@ export async function renderDisplayView(setup) {
847
867
  const siteMask = siteRows - 1, ringCapacity = siteRows * siteW, ringUsable = exactView && ringCapacity <= SITE_RING_LIMIT;
848
868
  // Allocated at the first display pixel that uses the ring (a render whose windows never fill allocates nothing).
849
869
  let ringStamp = new Int32Array(0), ringStatStamp = ringStamp, ringMat = ringStamp, siteSlotQ = ringStamp, siteSlotStamp = ringStamp, markX = ringStamp, markY = ringStamp, markM = ringStamp;
850
- let ringV = new Float64Array(0), ringMean = ringV, ringVar = ringV, ringThird = ringV, markV = ringV;
870
+ let ringV = new Float64Array(0), ringMean = ringV, ringVar = ringV, ringThird = ringV, ringClip = ringV, markV = ringV;
851
871
  // (No closure assigns them, so they stay locals of the render loop.)
852
872
  // AHD low-signal correction: an 11 × 11 noisy neighbourhood (AHD reads up to 5 sites away) and the local AHD steps.
853
873
  const ahdCorrection = setup.ahdCorrection, windowReach = ahdCorrection ? Math.max(reach, AHD_REACH) : reach;
@@ -911,14 +931,17 @@ export async function renderDisplayView(setup) {
911
931
  const c = siteChannel[slot], e = siteElectrons[slot], kc = k[c], mean = kc * e, variance = kc * kc * e + c0[c], gap = 1 - mean;
912
932
  // Third cumulant of the normalized value: Poisson electrons have κ₃ = e (read and rounding noise add none).
913
933
  siteThird[slot] = kc * kc * kc * e;
934
+ // #533: the clip probability P(value ≥ 1) is 1 − P(value < 1), the clip's own slope (0 where the clip is out of reach).
914
935
  if (gap > 0 && gap * gap > reach2 * variance) {
915
936
  siteMean[slot] = mean;
916
937
  siteVar[slot] = variance;
938
+ siteClip[slot] = 0;
917
939
  }
918
940
  else {
919
941
  upperClip(mean, variance, 1, clip);
920
942
  siteMean[slot] = clip[0];
921
943
  siteVar[slot] = clip[1];
944
+ siteClip[slot] = 1 - clip[2];
922
945
  }
923
946
  }
924
947
  let siteCount = 0, sampleCount = 0, detail = 0, uniform = 0, lastYield = Date.now();
@@ -1003,6 +1026,7 @@ export async function renderDisplayView(setup) {
1003
1026
  ringMean = new Float64Array(ringCapacity);
1004
1027
  ringVar = new Float64Array(ringCapacity);
1005
1028
  ringThird = new Float64Array(ringCapacity);
1029
+ ringClip = new Float64Array(ringCapacity);
1006
1030
  siteSlotQ = new Int32Array(slotsCount);
1007
1031
  siteSlotStamp = new Int32Array(slotsCount);
1008
1032
  markX = new Int32Array(slotsCount);
@@ -1023,7 +1047,7 @@ export async function renderDisplayView(setup) {
1023
1047
  }
1024
1048
  pixelGeneration++;
1025
1049
  }
1026
- let s0 = 0, s1 = 0, s2 = 0, c00 = 0, c01 = 0, c02 = 0, c11 = 0, c12 = 0, c22 = 0, r0 = 0, r1 = 0, r2 = 0, pixelVariance = 0, corrected = false;
1050
+ let s0 = 0, s1 = 0, s2 = 0, c00 = 0, c01 = 0, c02 = 0, c11 = 0, c12 = 0, c22 = 0, r0 = 0, r1 = 0, r2 = 0, pixelVariance = 0, corrected = false, sat = 0;
1027
1051
  for (let n = 0; n < sampleTotal; n++) {
1028
1052
  // A sample's weights: 1 for the stratified estimator (its sums are divided by Q below); a_j for the expected value and
1029
1053
  // linear covariance and a_j² for the independent residual on the exact path. Multiplying by 1 leaves every value's bits.
@@ -1113,28 +1137,33 @@ export async function renderDisplayView(setup) {
1113
1137
  siteMean[slot] = ringMean[q];
1114
1138
  siteVar[slot] = ringVar[q];
1115
1139
  siteThird[slot] = ringThird[q];
1140
+ siteClip[slot] = ringClip[q];
1116
1141
  continue;
1117
1142
  }
1118
1143
  // As siteStatistics, written out here for the untracked (Speed) path.
1119
1144
  const kc = k[c], mean = kc * e, variance = kc * kc * e + c0[c], gap = 1 - mean, third = kc * kc * kc * e;
1120
- let sm, sv;
1145
+ let sm, sv, sp;
1121
1146
  if (gap > 0 && gap * gap > reach2 * variance) {
1122
1147
  sm = mean;
1123
1148
  sv = variance;
1149
+ sp = 0;
1124
1150
  }
1125
1151
  else {
1126
1152
  upperClip(mean, variance, 1, clip);
1127
1153
  sm = clip[0];
1128
1154
  sv = clip[1];
1155
+ sp = 1 - clip[2];
1129
1156
  }
1130
1157
  siteMean[slot] = sm;
1131
1158
  siteVar[slot] = sv;
1132
1159
  siteThird[slot] = third;
1160
+ siteClip[slot] = sp;
1133
1161
  if (q >= 0) {
1134
1162
  ringStatStamp[q] = rowStamp;
1135
1163
  ringMean[q] = sm;
1136
1164
  ringVar[q] = sv;
1137
1165
  ringThird[q] = third;
1166
+ ringClip[q] = sp;
1138
1167
  }
1139
1168
  }
1140
1169
  if (marks > 0)
@@ -1176,11 +1205,13 @@ export async function renderDisplayView(setup) {
1176
1205
  if (gap > 0 && gap * gap > reach2 * variance) {
1177
1206
  siteMean[slot] = mean;
1178
1207
  siteVar[slot] = variance;
1208
+ siteClip[slot] = 0;
1179
1209
  }
1180
1210
  else {
1181
1211
  upperClip(mean, variance, 1, clip);
1182
1212
  siteMean[slot] = clip[0];
1183
1213
  siteVar[slot] = clip[1];
1214
+ siteClip[slot] = 1 - clip[2];
1184
1215
  }
1185
1216
  }
1186
1217
  if (!tracking)
@@ -1225,6 +1256,7 @@ export async function renderDisplayView(setup) {
1225
1256
  s2 += wE * memo[mo + 36];
1226
1257
  corrected = true;
1227
1258
  }
1259
+ sat += wE * memo[mo + 37];
1228
1260
  continue;
1229
1261
  }
1230
1262
  // The terms are written where they are kept: the real pixel's memo slot, or (for the first sample of a one-material key)
@@ -1262,6 +1294,7 @@ export async function renderDisplayView(setup) {
1262
1294
  siteMean[slot] = ringMean[q];
1263
1295
  siteVar[slot] = ringVar[q];
1264
1296
  siteThird[slot] = ringThird[q];
1297
+ siteClip[slot] = ringClip[q];
1265
1298
  continue;
1266
1299
  }
1267
1300
  siteStatistics(slot);
@@ -1270,6 +1303,7 @@ export async function renderDisplayView(setup) {
1270
1303
  ringMean[q] = siteMean[slot];
1271
1304
  ringVar[q] = siteVar[slot];
1272
1305
  ringThird[q] = siteThird[slot];
1306
+ ringClip[q] = siteClip[slot];
1273
1307
  }
1274
1308
  }
1275
1309
  // Site variance of each channel through the bilinear kernels (for B_c), then the demosaic's own pixel statistics: the
@@ -1441,6 +1475,26 @@ export async function renderDisplayView(setup) {
1441
1475
  rt[ro + 28] = e1;
1442
1476
  rt[ro + 29] = e2;
1443
1477
  }
1478
+ // #533: the real pixel's saturation probability, 1 − Π_k (1 − p_k) over the distinct sites its demosaic reads (the
1479
+ // capture counts a pixel with at least one clipped contributing site; the sites' noises are independent). A site read
1480
+ // twice through the mirrored border counts once.
1481
+ let unclipped = 1;
1482
+ if (inside)
1483
+ for (let li = 0; li < list.length; li++)
1484
+ unclipped *= 1 - siteClip[list[li]];
1485
+ else
1486
+ for (let li = 0; li < list.length; li++) {
1487
+ const slot = list[li], at = reflectedSite(x + slotDx[slot], y + slotDy[slot]);
1488
+ let repeated = false;
1489
+ for (let lj = 0; lj < li && !repeated; lj++)
1490
+ repeated = reflectedSite(x + slotDx[list[lj]], y + slotDy[list[lj]]) === at;
1491
+ if (!repeated)
1492
+ unclipped *= 1 - siteClip[slot];
1493
+ }
1494
+ const saturation = 1 - unclipped;
1495
+ sat += wE * saturation;
1496
+ if (rt)
1497
+ rt[ro + 37] = saturation;
1444
1498
  if (count === 2) {
1445
1499
  // AHD's per-pixel choice between the candidates adds independent variance: with a noise-driven choice each pixel
1446
1500
  // deviates from the candidates' average by ±½(H − V), variance ¼·Var(H − V) (linear in the sites, at the last
@@ -1495,6 +1549,10 @@ export async function renderDisplayView(setup) {
1495
1549
  correctedPixels++;
1496
1550
  // Stratified: means over Q samples. Exact: the weights already sum to 1 (Σ a_j = 1) and the residual carries Σ a_j²·r_j.
1497
1551
  const Qd = exactView ? 1 : Q, mean0 = s0 / Qd, mean1 = s1 / Qd, mean2 = s2 / Qd, pixels = B[3];
1552
+ // #533: the expected saturated real pixels of the rectangle (A real pixels): A·Σ_j a_j·P_j, or A times the samples' mean.
1553
+ const saturatedHere = (sat / Qd) * area;
1554
+ saturated[ry * region.width + rx] = saturatedHere;
1555
+ saturatedTotal += saturatedHere;
1498
1556
  // The real pixels' output rounding (Δ²/12 in the encoded domain), mapped to linear light at the mean.
1499
1557
  const q0 = quantizationVariance(mean0, step), q1 = quantizationVariance(mean1, step), q2 = quantizationVariance(mean2, step);
1500
1558
  // Covariance in linear light: the linear part plus the independent per-pixel residuals and rounding.
@@ -1527,7 +1585,7 @@ export async function renderDisplayView(setup) {
1527
1585
  }
1528
1586
  }
1529
1587
  return { samples, expected, noiseStd, sampleCount, siteCount, detailPixelCount: detail, uniformPixelCount: uniform, ahdCorrectedPixelCount: correctedPixels,
1530
- estimation: exactView ? "exact-area-mean" : "stratified-monte-carlo" };
1588
+ estimation: exactView ? "exact-area-mean" : "stratified-monte-carlo", saturated, saturatedPixelCount: saturatedTotal };
1531
1589
  }
1532
1590
  /**
1533
1591
  * The centre c of a normal of variance v whose clip to [0, 1] has mean `target` (bisection; c = target when the noise does