@photivra/engine 2.21.0 → 2.23.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.
- package/CHANGELOG.md +150 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +11 -0
- package/dist/capture/c2pa-embed.d.ts +116 -0
- package/dist/capture/c2pa-embed.js +493 -0
- package/dist/capture/c2pa-embed.js.map +1 -0
- package/dist/capture/dng-import.d.ts +73 -8
- package/dist/capture/dng-import.js +315 -80
- package/dist/capture/dng-import.js.map +1 -1
- package/dist/capture/dng-opcodes.d.ts +96 -0
- package/dist/capture/dng-opcodes.js +391 -0
- package/dist/capture/dng-opcodes.js.map +1 -0
- package/dist/capture/dng-profile-maps.d.ts +99 -0
- package/dist/capture/dng-profile-maps.js +381 -0
- package/dist/capture/dng-profile-maps.js.map +1 -0
- package/dist/capture/dng-stage-image.d.ts +23 -0
- package/dist/capture/dng-stage-image.js +65 -0
- package/dist/capture/dng-stage-image.js.map +1 -0
- package/dist/capture/jpeg-xl-tiles.d.ts +22 -0
- package/dist/capture/jpeg-xl-tiles.js +151 -0
- package/dist/capture/jpeg-xl-tiles.js.map +1 -0
- package/dist/capture/lossy-jpeg.d.ts +17 -0
- package/dist/capture/lossy-jpeg.js +369 -0
- package/dist/capture/lossy-jpeg.js.map +1 -0
- package/dist/capture/native-raw-development.d.ts +2 -1
- package/dist/capture/native-raw-development.js.map +1 -1
- package/dist/color/neighbor-highlight-reconstruction.d.ts +76 -0
- package/dist/color/neighbor-highlight-reconstruction.js +257 -0
- package/dist/color/neighbor-highlight-reconstruction.js.map +1 -0
- package/dist/color/sensor-color-development.d.ts +5 -0
- package/dist/color/sensor-color-development.js +7 -1
- package/dist/color/sensor-color-development.js.map +1 -1
- package/dist/color/spectral-spikiness.d.ts +11 -0
- package/dist/color/spectral-spikiness.js +23 -0
- package/dist/color/spectral-spikiness.js.map +1 -0
- package/dist/core/execution-error.d.ts +21 -0
- package/dist/core/execution-error.js +12 -0
- package/dist/core/execution-error.js.map +1 -0
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/facade/catalogue-lens-signal.d.ts +26 -1
- package/dist/facade/catalogue-lens-signal.js +25 -7
- package/dist/facade/catalogue-lens-signal.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +1 -1
- package/dist/facade/diffraction-provider.js +4 -4
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +38 -6
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +13 -5
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-normal.d.ts +15 -0
- package/dist/facade/display-view-normal.js +44 -0
- package/dist/facade/display-view-normal.js.map +1 -0
- package/dist/facade/display-view-rendering.d.ts +102 -0
- package/dist/facade/display-view-rendering.js +633 -0
- package/dist/facade/display-view-rendering.js.map +1 -0
- package/dist/facade/display-view.d.ts +37 -65
- package/dist/facade/display-view.js +147 -476
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.js +2 -2
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/fast-methods.d.ts +1 -1
- package/dist/facade/fast-methods.js +37 -8
- package/dist/facade/fast-methods.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +1 -1
- package/dist/facade/generic-lens-capture.js +1 -1
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/image-plane.js +41 -10
- package/dist/facade/image-plane.js.map +1 -1
- package/dist/facade/on-demand-kits.d.ts +14 -0
- package/dist/facade/on-demand-kits.js +36 -0
- package/dist/facade/on-demand-kits.js.map +1 -0
- package/dist/facade/opt-in-kit.d.ts +9 -0
- package/dist/facade/opt-in-kit.js +11 -0
- package/dist/facade/opt-in-kit.js.map +1 -0
- package/dist/facade/quality-kit.d.ts +21 -0
- package/dist/facade/quality-kit.js +23 -0
- package/dist/facade/quality-kit.js.map +1 -0
- package/dist/facade/quality-paths.d.ts +22 -0
- package/dist/facade/quality-paths.js +44 -0
- package/dist/facade/quality-paths.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +18 -2
- package/dist/facade/scene-radiance.js +6 -4
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +32 -13
- package/dist/facade/simulate-capture.js +162 -69
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +10 -12
- package/dist/facade/simulate-display-view.js +57 -48
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/display-view-gpu-renderer.d.ts +43 -0
- package/dist/gpu/display-view-gpu-renderer.js +134 -0
- package/dist/gpu/display-view-gpu-renderer.js.map +1 -0
- package/dist/gpu/display-view-image-material-module.d.ts +3 -2
- package/dist/gpu/display-view-image-material-module.js +25 -3
- package/dist/gpu/display-view-image-material-module.js.map +1 -1
- package/dist/gpu/display-view-image-quality.d.ts +43 -1
- package/dist/gpu/display-view-image-quality.js +256 -11
- package/dist/gpu/display-view-image-quality.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +13 -64
- package/dist/gpu/display-view-quality-gpu.js +55 -240
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +16 -16
- package/dist/gpu/display-view-speed-gpu.js +62 -19
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/image-kernel-accelerator.js +3 -0
- package/dist/gpu/image-kernel-accelerator.js.map +1 -1
- package/dist/gpu/image-kernel-execution.d.ts +2 -13
- package/dist/gpu/image-kernel-execution.js +2 -10
- package/dist/gpu/image-kernel-execution.js.map +1 -1
- package/dist/gpu/wgsl-display-view-coupled-stub.d.ts +13 -0
- package/dist/gpu/wgsl-display-view-coupled-stub.js +29 -0
- package/dist/gpu/wgsl-display-view-coupled-stub.js.map +1 -0
- package/dist/gpu/wgsl-display-view-coupled.d.ts +91 -0
- package/dist/gpu/wgsl-display-view-coupled.js +365 -0
- package/dist/gpu/wgsl-display-view-coupled.js.map +1 -0
- package/dist/gpu/wgsl-display-view-neighbors.d.ts +46 -0
- package/dist/gpu/wgsl-display-view-neighbors.js +114 -0
- package/dist/gpu/wgsl-display-view-neighbors.js.map +1 -0
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +6 -9
- package/dist/gpu/wgsl-display-view-quality-kernel.js +113 -113
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +1 -41
- package/dist/gpu/wgsl-display-view-speed-kernel.js +73 -10
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/output/camera-channel-response.d.ts +50 -0
- package/dist/output/camera-channel-response.js +69 -0
- package/dist/output/camera-channel-response.js.map +1 -0
- package/dist/output/native-capture-sdr.js +5 -2
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/sdr-rendering.d.ts +18 -1
- package/dist/output/sdr-rendering.js +39 -2
- package/dist/output/sdr-rendering.js.map +1 -1
- package/dist/sensor/native-sensor-report.d.ts +10 -0
- package/dist/sensor/native-sensor-report.js +16 -0
- package/dist/sensor/native-sensor-report.js.map +1 -0
- package/dist/sensor/sensor-characteristics.d.ts +81 -13
- package/dist/sensor/sensor-characteristics.js +10 -6
- package/dist/sensor/sensor-characteristics.js.map +1 -1
- package/docs/API_REFERENCE.md +117 -4
- package/docs/QUICK_START.md +37 -8
- package/docs/USAGE.md +105 -22
- package/docs/api/capture-c2pa-embed.md +177 -0
- package/docs/api/capture-dng-import.md +86 -10
- package/docs/api/capture-dng-opcodes.md +93 -0
- package/docs/api/capture-dng-profile-maps.md +141 -0
- package/docs/api/capture-dng-stage-image.md +43 -0
- package/docs/api/capture-jpeg-xl-tiles.md +33 -0
- package/docs/api/capture-lossy-jpeg.md +41 -0
- package/docs/api/capture-native-raw-development.md +2 -1
- package/docs/api/core-execution-error.md +43 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +255 -5
- package/docs/api/facade-display-view-gpu.md +5 -2
- package/docs/api/facade-display-view.md +11 -15
- package/docs/api/facade-fast-methods.md +38 -9
- package/docs/api/facade-quality-paths.md +24 -0
- package/docs/api/facade-scene-radiance.md +1 -1
- package/docs/api/facade-simulate-capture.md +24 -7
- package/docs/api/facade-simulate-display-view.md +10 -12
- package/docs/api/gpu-image-kernel-execution.md +0 -36
- package/docs/api/output-camera-channel-response.md +55 -0
- package/docs/api/output-sdr-rendering.md +6 -1
- package/docs/api/sensor-sensor-characteristics.md +54 -2
- package/llms.txt +1 -0
- package/package.json +6 -5
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* #602 (2.22.0): highlight reconstruction from neighboring pixels, `highlightMode: "reconstruct-neighbors"` (opt-in; an estimate,
|
|
4
|
+
* not a recovery of the clipped scene signal). The engine's own method, acting on the developed, white-balanced camera values
|
|
5
|
+
* u = g·c of the whole image at use (no tables):
|
|
6
|
+
*
|
|
7
|
+
* 1. A pixel is clipped when one of its normalized camera channels c_k reaches `NEIGHBOR_HIGHLIGHT_THRESHOLD` of white; clipped
|
|
8
|
+
* pixels form regions (8-connected).
|
|
9
|
+
* 2. Each unclipped pixel has the color ratios ρ = u/(u₀ + u₁ + u₂) (its chromaticity in white-balanced camera space). Pixels
|
|
10
|
+
* within `NEIGHBOR_HIGHLIGHT_MARGIN` pixels of a clipped one give no ratios: their demosaic reads clipped sites, which lowers
|
|
11
|
+
* the clipped channel (and noise selects them by the threshold); the regions below are the clipped pixels widened by it.
|
|
12
|
+
* 3. The clipped region takes its ratios from its unclipped boundary. **Fast:** the mean of ρ over the unclipped pixels within
|
|
13
|
+
* `NEIGHBOR_HIGHLIGHT_RING` pixels (Chebyshev distance) of the region. **Full:** the discrete harmonic (Laplace) interpolation
|
|
14
|
+
* of ρ inward: ρ at each clipped pixel is the mean of its 4-neighbors', with the unclipped neighbors' ratios as Dirichlet
|
|
15
|
+
* boundary values (pixels outside the image are left out), solved by successive over-relaxation from the fast values. A region
|
|
16
|
+
* with no unclipped neighbor keeps the neutral ratios.
|
|
17
|
+
* 4. A clipped pixel keeps its unclipped channels; its scale s = Σ_unclipped u_k / Σ_unclipped ρ_k, and each clipped channel
|
|
18
|
+
* becomes max(u_k, s·ρ_k) (never below what was recorded). Where every channel is clipped, s = max_k u_k/ρ_k.
|
|
19
|
+
* The result is not clipped at the white level: the tone curve and gamut handling then render it.
|
|
20
|
+
*
|
|
21
|
+
* Mathematics only, general and free: the discrete Laplace equation and its Dirichlet problem, and successive over-relaxation
|
|
22
|
+
* (e.g. G. Strang, MIT OpenCourseWare 18.086, "Mathematical Methods for Engineers II", finite differences for Laplace's equation
|
|
23
|
+
* and iterative solvers, free at https://ocw.mit.edu/courses/18-086-mathematical-methods-for-engineers-ii-spring-2006/).
|
|
24
|
+
*/
|
|
25
|
+
/** Normalized channel level at which a channel counts as clipped. */
|
|
26
|
+
export const NEIGHBOR_HIGHLIGHT_THRESHOLD = 0.98;
|
|
27
|
+
/** Pixels this close (Chebyshev distance) to a clipped pixel give no ratios and are filled with the region. */
|
|
28
|
+
export const NEIGHBOR_HIGHLIGHT_MARGIN = 2;
|
|
29
|
+
/** The fast method's ring width in pixels around a (widened) clipped region. */
|
|
30
|
+
export const NEIGHBOR_HIGHLIGHT_RING = 2;
|
|
31
|
+
/** The full method's tolerance (the largest change of a sweep) and iteration cap; the relaxation factor is `neighborHighlightOmega`. */
|
|
32
|
+
export const NEIGHBOR_HIGHLIGHT_SOR = /* @__PURE__ */ Object.freeze({ tolerance: 1e-9, maximumIterations: 20000 });
|
|
33
|
+
/**
|
|
34
|
+
* #605 (2.23.0): the relaxation factor for a region whose members span a × b pixels: the optimal ω = 2/(1 + √(1 − ρ²)) of SOR
|
|
35
|
+
* for the discrete Laplace equation on the a × b rectangle, whose Jacobi iteration has spectral radius
|
|
36
|
+
* ρ = (cos(π/(a + 1)) + cos(π/(b + 1)))/2 (Young's theory of successive over-relaxation; e.g. G. Strang, MIT OpenCourseWare
|
|
37
|
+
* 18.086, and R. J. LeVeque, "Finite Difference Methods for Ordinary and Partial Differential Equations", SIAM 2007, §4.2).
|
|
38
|
+
* A region inside the rectangle has a Jacobi radius at most that of the rectangle, so this ω is at or above its optimum (where
|
|
39
|
+
* SOR converges at the rate ω − 1). The system (each member's neighbor count times its ratio minus its neighbors') is
|
|
40
|
+
* symmetric positive definite, so SOR converges for every ω in (0, 2) (Ostrowski–Reich). 2.22.0 used ω = 1.8 for every region:
|
|
41
|
+
* 5,728 sweeps on a 260 × 260 region against about 400 (docs/DISPLAY_VIEW.md).
|
|
42
|
+
*/
|
|
43
|
+
export function neighborHighlightOmega(a, b) {
|
|
44
|
+
const rho = (Math.cos(Math.PI / (a + 1)) + Math.cos(Math.PI / (b + 1))) / 2;
|
|
45
|
+
return 2 / (1 + Math.sqrt(Math.max(0, 1 - rho * rho)));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The region ratios of an image: per pixel the ratios ρ its rebuild uses (`ratios`, 3 per pixel; for an unclipped pixel its own),
|
|
49
|
+
* and which pixels are clipped. `clippedAt(i)` tells whether pixel i is clipped; `valueAt(i, out)` writes pixel i's u.
|
|
50
|
+
* Pixels are i = y·width + x.
|
|
51
|
+
*/
|
|
52
|
+
export function neighborHighlightRatios(width, height, clippedAt, valueAt, fast) {
|
|
53
|
+
const n = width * height, clipped = new Uint8Array(n), unknown = new Uint8Array(n), ratios = new Float64Array(3 * n), u = new Float64Array(3), D = NEIGHBOR_HIGHLIGHT_MARGIN;
|
|
54
|
+
for (let i = 0; i < n; i++)
|
|
55
|
+
if (clippedAt(i))
|
|
56
|
+
clipped[i] = 1;
|
|
57
|
+
// The clipped pixels widened by the margin (a separable dilation).
|
|
58
|
+
const rows = new Uint8Array(n);
|
|
59
|
+
for (let y = 0; y < height; y++)
|
|
60
|
+
for (let x = 0; x < width; x++)
|
|
61
|
+
if (clipped[y * width + x] === 1)
|
|
62
|
+
rows.fill(1, y * width + Math.max(0, x - D), y * width + Math.min(width, x + D + 1));
|
|
63
|
+
for (let x = 0; x < width; x++)
|
|
64
|
+
for (let y = 0; y < height; y++)
|
|
65
|
+
if (rows[y * width + x] === 1)
|
|
66
|
+
for (let yy = Math.max(0, y - D); yy <= Math.min(height - 1, y + D); yy++)
|
|
67
|
+
unknown[yy * width + x] = 1;
|
|
68
|
+
for (let i = 0; i < n; i++) {
|
|
69
|
+
if (unknown[i] === 1)
|
|
70
|
+
continue;
|
|
71
|
+
valueAt(i, u);
|
|
72
|
+
const sum = u[0] + u[1] + u[2];
|
|
73
|
+
if (sum > 0) {
|
|
74
|
+
ratios[3 * i] = u[0] / sum;
|
|
75
|
+
ratios[3 * i + 1] = u[1] / sum;
|
|
76
|
+
ratios[3 * i + 2] = u[2] / sum;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
ratios[3 * i] = 1 / 3;
|
|
80
|
+
ratios[3 * i + 1] = 1 / 3;
|
|
81
|
+
ratios[3 * i + 2] = 1 / 3;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const label = new Int32Array(n).fill(-1), stamp = new Int32Array(n).fill(-1), queue = new Int32Array(n);
|
|
85
|
+
let regionCount = 0, iterations = 0, converged = true;
|
|
86
|
+
for (let start = 0; start < n; start++) {
|
|
87
|
+
if (unknown[start] === 0 || label[start] >= 0)
|
|
88
|
+
continue;
|
|
89
|
+
// The region (8-connected), breadth first.
|
|
90
|
+
const region = regionCount++;
|
|
91
|
+
let head = 0, tail = 0;
|
|
92
|
+
queue[tail++] = start;
|
|
93
|
+
label[start] = region;
|
|
94
|
+
while (head < tail) {
|
|
95
|
+
const p = queue[head++], px = p % width, py = (p - px) / width;
|
|
96
|
+
for (let dy = -1; dy <= 1; dy++)
|
|
97
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
98
|
+
const x = px + dx, y = py + dy;
|
|
99
|
+
if (x < 0 || y < 0 || x >= width || y >= height)
|
|
100
|
+
continue;
|
|
101
|
+
const q = y * width + x;
|
|
102
|
+
if (unknown[q] === 1 && label[q] < 0) {
|
|
103
|
+
label[q] = region;
|
|
104
|
+
queue[tail++] = q;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Fast: the mean ratios over the ring (each unclipped pixel once).
|
|
109
|
+
let r0 = 0, r1 = 0, r2 = 0, count = 0;
|
|
110
|
+
const R = NEIGHBOR_HIGHLIGHT_RING;
|
|
111
|
+
for (let k = 0; k < tail; k++) {
|
|
112
|
+
const p = queue[k], px = p % width, py = (p - px) / width;
|
|
113
|
+
for (let y = Math.max(0, py - R); y <= Math.min(height - 1, py + R); y++)
|
|
114
|
+
for (let x = Math.max(0, px - R); x <= Math.min(width - 1, px + R); x++) {
|
|
115
|
+
const q = y * width + x;
|
|
116
|
+
if (unknown[q] === 1 || stamp[q] === region)
|
|
117
|
+
continue;
|
|
118
|
+
stamp[q] = region;
|
|
119
|
+
r0 += ratios[3 * q];
|
|
120
|
+
r1 += ratios[3 * q + 1];
|
|
121
|
+
r2 += ratios[3 * q + 2];
|
|
122
|
+
count++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const m0 = count > 0 ? r0 / count : 1 / 3, m1 = count > 0 ? r1 / count : 1 / 3, m2 = count > 0 ? r2 / count : 1 / 3;
|
|
126
|
+
for (let k = 0; k < tail; k++) {
|
|
127
|
+
const p = queue[k];
|
|
128
|
+
ratios[3 * p] = m0;
|
|
129
|
+
ratios[3 * p + 1] = m1;
|
|
130
|
+
ratios[3 * p + 2] = m2;
|
|
131
|
+
}
|
|
132
|
+
if (fast || count === 0)
|
|
133
|
+
continue;
|
|
134
|
+
// Full: successive over-relaxation of the discrete Laplace equation over the region, in the region's breadth-first order.
|
|
135
|
+
const { tolerance, maximumIterations } = NEIGHBOR_HIGHLIGHT_SOR;
|
|
136
|
+
let x0 = width, x1 = -1, y0 = height, y1 = -1;
|
|
137
|
+
for (let k = 0; k < tail; k++) {
|
|
138
|
+
const p = queue[k], px = p % width, py = (p - px) / width;
|
|
139
|
+
x0 = Math.min(x0, px);
|
|
140
|
+
x1 = Math.max(x1, px);
|
|
141
|
+
y0 = Math.min(y0, py);
|
|
142
|
+
y1 = Math.max(y1, py);
|
|
143
|
+
}
|
|
144
|
+
const omega = neighborHighlightOmega(x1 - x0 + 1, y1 - y0 + 1);
|
|
145
|
+
let sweep = 0, change = Infinity;
|
|
146
|
+
while (sweep < maximumIterations && change > tolerance) {
|
|
147
|
+
change = 0;
|
|
148
|
+
sweep++;
|
|
149
|
+
for (let k = 0; k < tail; k++) {
|
|
150
|
+
const p = queue[k], px = p % width, py = (p - px) / width;
|
|
151
|
+
let a0 = 0, a1 = 0, a2 = 0, m = 0;
|
|
152
|
+
for (let d = 0; d < 4; d++) {
|
|
153
|
+
const x = px + (d === 0 ? -1 : d === 1 ? 1 : 0), y = py + (d === 2 ? -1 : d === 3 ? 1 : 0);
|
|
154
|
+
if (x < 0 || y < 0 || x >= width || y >= height)
|
|
155
|
+
continue;
|
|
156
|
+
const q = y * width + x;
|
|
157
|
+
a0 += ratios[3 * q];
|
|
158
|
+
a1 += ratios[3 * q + 1];
|
|
159
|
+
a2 += ratios[3 * q + 2];
|
|
160
|
+
m++;
|
|
161
|
+
}
|
|
162
|
+
if (m === 0)
|
|
163
|
+
continue;
|
|
164
|
+
for (let c = 0; c < 3; c++) {
|
|
165
|
+
const old = ratios[3 * p + c], target = (c === 0 ? a0 : c === 1 ? a1 : a2) / m, next = old + omega * (target - old);
|
|
166
|
+
ratios[3 * p + c] = next;
|
|
167
|
+
const delta = Math.abs(next - old);
|
|
168
|
+
if (delta > change)
|
|
169
|
+
change = delta;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
iterations = Math.max(iterations, sweep);
|
|
174
|
+
if (change > tolerance)
|
|
175
|
+
converged = false;
|
|
176
|
+
}
|
|
177
|
+
return { clipped, ratios, regionCount, iterations, converged };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The rebuild of one clipped pixel (step 4) into b, with its slopes ∂b/∂c (row-major 3 × 3, a.e.) when `slopes` is given.
|
|
181
|
+
* `c` are the normalized camera channels, `g` the white-balance gains, `rho` the pixel's ratios.
|
|
182
|
+
*/
|
|
183
|
+
export function rebuildClippedPixel(c0, c1, c2, g, rho, b, slopes) {
|
|
184
|
+
const H = NEIGHBOR_HIGHLIGHT_THRESHOLD, u0 = c0 * g[0], u1 = c1 * g[1], u2 = c2 * g[2];
|
|
185
|
+
const k0 = c0 < H, k1 = c1 < H, k2 = c2 < H;
|
|
186
|
+
if (slopes)
|
|
187
|
+
slopes.fill(0);
|
|
188
|
+
if (k0 && k1 && k2) {
|
|
189
|
+
b[0] = u0;
|
|
190
|
+
b[1] = u1;
|
|
191
|
+
b[2] = u2;
|
|
192
|
+
if (slopes) {
|
|
193
|
+
slopes[0] = g[0];
|
|
194
|
+
slopes[4] = g[1];
|
|
195
|
+
slopes[8] = g[2];
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const r0 = rho[0], r1 = rho[1], r2 = rho[2];
|
|
200
|
+
if (!k0 && !k1 && !k2) {
|
|
201
|
+
const q0 = r0 > 0 ? u0 / r0 : 0, q1 = r1 > 0 ? u1 / r1 : 0, q2 = r2 > 0 ? u2 / r2 : 0, m = q0 > q1 ? (q0 > q2 ? 0 : 2) : (q1 > q2 ? 1 : 2), s = m === 0 ? q0 : m === 1 ? q1 : q2;
|
|
202
|
+
b[0] = s * r0;
|
|
203
|
+
b[1] = s * r1;
|
|
204
|
+
b[2] = s * r2;
|
|
205
|
+
if (slopes) {
|
|
206
|
+
const rm = m === 0 ? r0 : m === 1 ? r1 : r2;
|
|
207
|
+
if (rm > 0)
|
|
208
|
+
for (let k = 0; k < 3; k++)
|
|
209
|
+
slopes[k * 3 + m] = (k === 0 ? r0 : k === 1 ? r1 : r2) * g[m] / rm;
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const su = (k0 ? u0 : 0) + (k1 ? u1 : 0) + (k2 ? u2 : 0), sr = (k0 ? r0 : 0) + (k1 ? r1 : 0) + (k2 ? r2 : 0), s = sr > 0 ? su / sr : 0;
|
|
214
|
+
for (let k = 0; k < 3; k++) {
|
|
215
|
+
const kept = k === 0 ? k0 : k === 1 ? k1 : k2, uk = k === 0 ? u0 : k === 1 ? u1 : u2, rk = k === 0 ? r0 : k === 1 ? r1 : r2;
|
|
216
|
+
if (kept) {
|
|
217
|
+
b[k] = uk;
|
|
218
|
+
if (slopes)
|
|
219
|
+
slopes[k * 4] = g[k];
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const estimate = s * rk;
|
|
223
|
+
if (estimate > uk) {
|
|
224
|
+
b[k] = estimate;
|
|
225
|
+
if (slopes && sr > 0)
|
|
226
|
+
for (let j = 0; j < 3; j++)
|
|
227
|
+
if (j === 0 ? k0 : j === 1 ? k1 : k2)
|
|
228
|
+
slopes[k * 3 + j] = rk * g[j] / sr;
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
b[k] = uk;
|
|
232
|
+
if (slopes)
|
|
233
|
+
slopes[k * 4] = g[k];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Rebuilds the clipped pixels of a white-balanced image u (3 per pixel, in place; becomes b), with the normalized channels
|
|
239
|
+
* c = u/g deciding which channels are clipped.
|
|
240
|
+
*/
|
|
241
|
+
export function reconstructHighlightsFromNeighbors(u, width, height, gains, fast) {
|
|
242
|
+
const H = NEIGHBOR_HIGHLIGHT_THRESHOLD, g0 = gains[0], g1 = gains[1], g2 = gains[2];
|
|
243
|
+
const field = neighborHighlightRatios(width, height, (i) => u[3 * i] / g0 >= H || u[3 * i + 1] / g1 >= H || u[3 * i + 2] / g2 >= H, (i, out) => { out[0] = u[3 * i]; out[1] = u[3 * i + 1]; out[2] = u[3 * i + 2]; }, fast);
|
|
244
|
+
const b = new Float64Array(3);
|
|
245
|
+
let rebuilt = 0;
|
|
246
|
+
for (let i = 0; i < width * height; i++) {
|
|
247
|
+
if (field.clipped[i] === 0)
|
|
248
|
+
continue;
|
|
249
|
+
rebuildClippedPixel(u[3 * i] / g0, u[3 * i + 1] / g1, u[3 * i + 2] / g2, gains, field.ratios.subarray(3 * i, 3 * i + 3), b);
|
|
250
|
+
u[3 * i] = b[0];
|
|
251
|
+
u[3 * i + 1] = b[1];
|
|
252
|
+
u[3 * i + 2] = b[2];
|
|
253
|
+
rebuilt++;
|
|
254
|
+
}
|
|
255
|
+
return { method: fast ? "ring-average" : "harmonic-interpolation", reconstructedPixelCount: rebuilt, regionCount: field.regionCount, iterations: field.iterations, converged: field.converged };
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=neighbor-highlight-reconstruction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neighbor-highlight-reconstruction.js","sourceRoot":"","sources":["../../src/color/neighbor-highlight-reconstruction.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #602 (2.22.0): highlight reconstruction from neighboring pixels, `highlightMode: \"reconstruct-neighbors\"` (opt-in; an estimate,\n * not a recovery of the clipped scene signal). The engine's own method, acting on the developed, white-balanced camera values\n * u = g·c of the whole image at use (no tables):\n *\n * 1. A pixel is clipped when one of its normalized camera channels c_k reaches `NEIGHBOR_HIGHLIGHT_THRESHOLD` of white; clipped\n * pixels form regions (8-connected).\n * 2. Each unclipped pixel has the color ratios ρ = u/(u₀ + u₁ + u₂) (its chromaticity in white-balanced camera space). Pixels\n * within `NEIGHBOR_HIGHLIGHT_MARGIN` pixels of a clipped one give no ratios: their demosaic reads clipped sites, which lowers\n * the clipped channel (and noise selects them by the threshold); the regions below are the clipped pixels widened by it.\n * 3. The clipped region takes its ratios from its unclipped boundary. **Fast:** the mean of ρ over the unclipped pixels within\n * `NEIGHBOR_HIGHLIGHT_RING` pixels (Chebyshev distance) of the region. **Full:** the discrete harmonic (Laplace) interpolation\n * of ρ inward: ρ at each clipped pixel is the mean of its 4-neighbors', with the unclipped neighbors' ratios as Dirichlet\n * boundary values (pixels outside the image are left out), solved by successive over-relaxation from the fast values. A region\n * with no unclipped neighbor keeps the neutral ratios.\n * 4. A clipped pixel keeps its unclipped channels; its scale s = Σ_unclipped u_k / Σ_unclipped ρ_k, and each clipped channel\n * becomes max(u_k, s·ρ_k) (never below what was recorded). Where every channel is clipped, s = max_k u_k/ρ_k.\n * The result is not clipped at the white level: the tone curve and gamut handling then render it.\n *\n * Mathematics only, general and free: the discrete Laplace equation and its Dirichlet problem, and successive over-relaxation\n * (e.g. G. Strang, MIT OpenCourseWare 18.086, \"Mathematical Methods for Engineers II\", finite differences for Laplace's equation\n * and iterative solvers, free at https://ocw.mit.edu/courses/18-086-mathematical-methods-for-engineers-ii-spring-2006/).\n */\n\n/** Normalized channel level at which a channel counts as clipped. */\nexport const NEIGHBOR_HIGHLIGHT_THRESHOLD = 0.98;\n/** Pixels this close (Chebyshev distance) to a clipped pixel give no ratios and are filled with the region. */\nexport const NEIGHBOR_HIGHLIGHT_MARGIN = 2;\n/** The fast method's ring width in pixels around a (widened) clipped region. */\nexport const NEIGHBOR_HIGHLIGHT_RING = 2;\n/** The full method's tolerance (the largest change of a sweep) and iteration cap; the relaxation factor is `neighborHighlightOmega`. */\nexport const NEIGHBOR_HIGHLIGHT_SOR = /* @__PURE__ */ Object.freeze({ tolerance: 1e-9, maximumIterations: 20000 });\n\n/**\n * #605 (2.23.0): the relaxation factor for a region whose members span a × b pixels: the optimal ω = 2/(1 + √(1 − ρ²)) of SOR\n * for the discrete Laplace equation on the a × b rectangle, whose Jacobi iteration has spectral radius\n * ρ = (cos(π/(a + 1)) + cos(π/(b + 1)))/2 (Young's theory of successive over-relaxation; e.g. G. Strang, MIT OpenCourseWare\n * 18.086, and R. J. LeVeque, \"Finite Difference Methods for Ordinary and Partial Differential Equations\", SIAM 2007, §4.2).\n * A region inside the rectangle has a Jacobi radius at most that of the rectangle, so this ω is at or above its optimum (where\n * SOR converges at the rate ω − 1). The system (each member's neighbor count times its ratio minus its neighbors') is\n * symmetric positive definite, so SOR converges for every ω in (0, 2) (Ostrowski–Reich). 2.22.0 used ω = 1.8 for every region:\n * 5,728 sweeps on a 260 × 260 region against about 400 (docs/DISPLAY_VIEW.md).\n */\nexport function neighborHighlightOmega(a: number, b: number): number {\n const rho = (Math.cos(Math.PI / (a + 1)) + Math.cos(Math.PI / (b + 1))) / 2;\n return 2 / (1 + Math.sqrt(Math.max(0, 1 - rho * rho)));\n}\n\nexport interface NeighborHighlightReport {\n method: \"ring-average\" | \"harmonic-interpolation\";\n /** Clipped pixels rebuilt, and the clipped regions. */\n reconstructedPixelCount: number;\n regionCount: number;\n /** Full method: the largest number of relaxation sweeps a region needed, and whether every region met the tolerance. */\n iterations: number;\n converged: boolean;\n}\n\n/**\n * The region ratios of an image: per pixel the ratios ρ its rebuild uses (`ratios`, 3 per pixel; for an unclipped pixel its own),\n * and which pixels are clipped. `clippedAt(i)` tells whether pixel i is clipped; `valueAt(i, out)` writes pixel i's u.\n * Pixels are i = y·width + x.\n */\nexport function neighborHighlightRatios(width: number, height: number, clippedAt: (i: number) => boolean, valueAt: (i: number, out: Float64Array) => void, fast: boolean):\n { clipped: Uint8Array; ratios: Float64Array; regionCount: number; iterations: number; converged: boolean } {\n const n = width * height, clipped = new Uint8Array(n), unknown = new Uint8Array(n), ratios = new Float64Array(3 * n), u = new Float64Array(3), D = NEIGHBOR_HIGHLIGHT_MARGIN;\n for (let i = 0; i < n; i++) if (clippedAt(i)) clipped[i] = 1;\n // The clipped pixels widened by the margin (a separable dilation).\n const rows = new Uint8Array(n);\n for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) if (clipped[y * width + x] === 1) rows.fill(1, y * width + Math.max(0, x - D), y * width + Math.min(width, x + D + 1));\n for (let x = 0; x < width; x++) for (let y = 0; y < height; y++) if (rows[y * width + x] === 1) for (let yy = Math.max(0, y - D); yy <= Math.min(height - 1, y + D); yy++) unknown[yy * width + x] = 1;\n for (let i = 0; i < n; i++) {\n if (unknown[i] === 1) continue;\n valueAt(i, u);\n const sum = u[0]! + u[1]! + u[2]!;\n if (sum > 0) { ratios[3 * i] = u[0]! / sum; ratios[3 * i + 1] = u[1]! / sum; ratios[3 * i + 2] = u[2]! / sum; }\n else { ratios[3 * i] = 1 / 3; ratios[3 * i + 1] = 1 / 3; ratios[3 * i + 2] = 1 / 3; }\n }\n const label = new Int32Array(n).fill(-1), stamp = new Int32Array(n).fill(-1), queue = new Int32Array(n);\n let regionCount = 0, iterations = 0, converged = true;\n for (let start = 0; start < n; start++) {\n if (unknown[start] === 0 || label[start]! >= 0) continue;\n // The region (8-connected), breadth first.\n const region = regionCount++;\n let head = 0, tail = 0;\n queue[tail++] = start; label[start] = region;\n while (head < tail) {\n const p = queue[head++]!, px = p % width, py = (p - px) / width;\n for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {\n const x = px + dx, y = py + dy;\n if (x < 0 || y < 0 || x >= width || y >= height) continue;\n const q = y * width + x;\n if (unknown[q] === 1 && label[q]! < 0) { label[q] = region; queue[tail++] = q; }\n }\n }\n // Fast: the mean ratios over the ring (each unclipped pixel once).\n let r0 = 0, r1 = 0, r2 = 0, count = 0;\n const R = NEIGHBOR_HIGHLIGHT_RING;\n for (let k = 0; k < tail; k++) {\n const p = queue[k]!, px = p % width, py = (p - px) / width;\n for (let y = Math.max(0, py - R); y <= Math.min(height - 1, py + R); y++) for (let x = Math.max(0, px - R); x <= Math.min(width - 1, px + R); x++) {\n const q = y * width + x;\n if (unknown[q] === 1 || stamp[q] === region) continue;\n stamp[q] = region; r0 += ratios[3 * q]!; r1 += ratios[3 * q + 1]!; r2 += ratios[3 * q + 2]!; count++;\n }\n }\n const m0 = count > 0 ? r0 / count : 1 / 3, m1 = count > 0 ? r1 / count : 1 / 3, m2 = count > 0 ? r2 / count : 1 / 3;\n for (let k = 0; k < tail; k++) { const p = queue[k]!; ratios[3 * p] = m0; ratios[3 * p + 1] = m1; ratios[3 * p + 2] = m2; }\n if (fast || count === 0) continue;\n // Full: successive over-relaxation of the discrete Laplace equation over the region, in the region's breadth-first order.\n const { tolerance, maximumIterations } = NEIGHBOR_HIGHLIGHT_SOR;\n let x0 = width, x1 = -1, y0 = height, y1 = -1;\n for (let k = 0; k < tail; k++) { const p = queue[k]!, px = p % width, py = (p - px) / width; x0 = Math.min(x0, px); x1 = Math.max(x1, px); y0 = Math.min(y0, py); y1 = Math.max(y1, py); }\n const omega = neighborHighlightOmega(x1 - x0 + 1, y1 - y0 + 1);\n let sweep = 0, change = Infinity;\n while (sweep < maximumIterations && change > tolerance) {\n change = 0; sweep++;\n for (let k = 0; k < tail; k++) {\n const p = queue[k]!, px = p % width, py = (p - px) / width;\n let a0 = 0, a1 = 0, a2 = 0, m = 0;\n for (let d = 0; d < 4; d++) {\n const x = px + (d === 0 ? -1 : d === 1 ? 1 : 0), y = py + (d === 2 ? -1 : d === 3 ? 1 : 0);\n if (x < 0 || y < 0 || x >= width || y >= height) continue;\n const q = y * width + x;\n a0 += ratios[3 * q]!; a1 += ratios[3 * q + 1]!; a2 += ratios[3 * q + 2]!; m++;\n }\n if (m === 0) continue;\n for (let c = 0; c < 3; c++) {\n const old = ratios[3 * p + c]!, target = (c === 0 ? a0 : c === 1 ? a1 : a2) / m, next = old + omega * (target - old);\n ratios[3 * p + c] = next;\n const delta = Math.abs(next - old);\n if (delta > change) change = delta;\n }\n }\n }\n iterations = Math.max(iterations, sweep);\n if (change > tolerance) converged = false;\n }\n return { clipped, ratios, regionCount, iterations, converged };\n}\n\n/**\n * The rebuild of one clipped pixel (step 4) into b, with its slopes ∂b/∂c (row-major 3 × 3, a.e.) when `slopes` is given.\n * `c` are the normalized camera channels, `g` the white-balance gains, `rho` the pixel's ratios.\n */\nexport function rebuildClippedPixel(c0: number, c1: number, c2: number, g: ArrayLike<number>, rho: ArrayLike<number>, b: Float64Array | number[], slopes?: Float64Array): void {\n const H = NEIGHBOR_HIGHLIGHT_THRESHOLD, u0 = c0 * g[0]!, u1 = c1 * g[1]!, u2 = c2 * g[2]!;\n const k0 = c0 < H, k1 = c1 < H, k2 = c2 < H;\n if (slopes) slopes.fill(0);\n if (k0 && k1 && k2) {\n b[0] = u0; b[1] = u1; b[2] = u2;\n if (slopes) { slopes[0] = g[0]!; slopes[4] = g[1]!; slopes[8] = g[2]!; }\n return;\n }\n const r0 = rho[0]!, r1 = rho[1]!, r2 = rho[2]!;\n if (!k0 && !k1 && !k2) {\n const q0 = r0 > 0 ? u0 / r0 : 0, q1 = r1 > 0 ? u1 / r1 : 0, q2 = r2 > 0 ? u2 / r2 : 0, m = q0 > q1 ? (q0 > q2 ? 0 : 2) : (q1 > q2 ? 1 : 2), s = m === 0 ? q0 : m === 1 ? q1 : q2;\n b[0] = s * r0; b[1] = s * r1; b[2] = s * r2;\n if (slopes) { const rm = m === 0 ? r0 : m === 1 ? r1 : r2; if (rm > 0) for (let k = 0; k < 3; k++) slopes[k * 3 + m] = (k === 0 ? r0 : k === 1 ? r1 : r2) * g[m]! / rm; }\n return;\n }\n const su = (k0 ? u0 : 0) + (k1 ? u1 : 0) + (k2 ? u2 : 0), sr = (k0 ? r0 : 0) + (k1 ? r1 : 0) + (k2 ? r2 : 0), s = sr > 0 ? su / sr : 0;\n for (let k = 0; k < 3; k++) {\n const kept = k === 0 ? k0 : k === 1 ? k1 : k2, uk = k === 0 ? u0 : k === 1 ? u1 : u2, rk = k === 0 ? r0 : k === 1 ? r1 : r2;\n if (kept) { b[k] = uk; if (slopes) slopes[k * 4] = g[k]!; continue; }\n const estimate = s * rk;\n if (estimate > uk) {\n b[k] = estimate;\n if (slopes && sr > 0) for (let j = 0; j < 3; j++) if (j === 0 ? k0 : j === 1 ? k1 : k2) slopes[k * 3 + j] = rk * g[j]! / sr;\n } else { b[k] = uk; if (slopes) slopes[k * 4] = g[k]!; }\n }\n}\n\n/**\n * Rebuilds the clipped pixels of a white-balanced image u (3 per pixel, in place; becomes b), with the normalized channels\n * c = u/g deciding which channels are clipped.\n */\nexport function reconstructHighlightsFromNeighbors(u: Float64Array, width: number, height: number, gains: ArrayLike<number>, fast: boolean): NeighborHighlightReport {\n const H = NEIGHBOR_HIGHLIGHT_THRESHOLD, g0 = gains[0]!, g1 = gains[1]!, g2 = gains[2]!;\n const field = neighborHighlightRatios(width, height, (i) => u[3 * i]! / g0 >= H || u[3 * i + 1]! / g1 >= H || u[3 * i + 2]! / g2 >= H,\n (i, out) => { out[0] = u[3 * i]!; out[1] = u[3 * i + 1]!; out[2] = u[3 * i + 2]!; }, fast);\n const b = new Float64Array(3);\n let rebuilt = 0;\n for (let i = 0; i < width * height; i++) {\n if (field.clipped[i] === 0) continue;\n rebuildClippedPixel(u[3 * i]! / g0, u[3 * i + 1]! / g1, u[3 * i + 2]! / g2, gains, field.ratios.subarray(3 * i, 3 * i + 3), b);\n u[3 * i] = b[0]!; u[3 * i + 1] = b[1]!; u[3 * i + 2] = b[2]!;\n rebuilt++;\n }\n return { method: fast ? \"ring-average\" : \"harmonic-interpolation\", reconstructedPixelCount: rebuilt, regionCount: field.regionCount, iterations: field.iterations, converged: field.converged };\n}\n"]}
|
|
@@ -33,6 +33,11 @@ export interface SensorColorDevelopment {
|
|
|
33
33
|
highlightMode: RawHighlightMode;
|
|
34
34
|
/** Develops one normalized camera pixel into out[offset..offset+2] (linear sRGB/D65); updates counts. */
|
|
35
35
|
developInto(c0: number, c1: number, c2: number, out: Float64Array | number[], offset: number, counts: SensorColorDevelopmentCounts): void;
|
|
36
|
+
/**
|
|
37
|
+
* #602: the colour step alone, X·M·diag(w)·b, for white-balanced values b that a whole-image highlight step already formed
|
|
38
|
+
* (`highlightMode: "reconstruct-neighbors"`); the same products and order as the end of developInto.
|
|
39
|
+
*/
|
|
40
|
+
developWhiteBalancedInto(b0: number, b1: number, b2: number, out: Float64Array | number[], offset: number): void;
|
|
36
41
|
/** Convenience array form of developInto (counts discarded). */
|
|
37
42
|
develop(values: readonly number[]): number[];
|
|
38
43
|
}
|
|
@@ -117,8 +117,14 @@ export function prepareSensorColorDevelopment(cameraChannelsToXyz, gains, highli
|
|
|
117
117
|
out[offset + 1] = ((0 + x1[0] * X) + x1[1] * Y) + x1[2] * Z;
|
|
118
118
|
out[offset + 2] = ((0 + x2[0] * X) + x2[1] * Y) + x2[2] * Z;
|
|
119
119
|
}
|
|
120
|
+
function developWhiteBalancedInto(b0, b1, b2, out, offset) {
|
|
121
|
+
const X = ((0 + m0[0] * b0) + m0[1] * b1) + m0[2] * b2, Y = ((0 + m1[0] * b0) + m1[1] * b1) + m1[2] * b2, Z = ((0 + m2[0] * b0) + m2[1] * b1) + m2[2] * b2;
|
|
122
|
+
out[offset] = ((0 + x0[0] * X) + x0[1] * Y) + x0[2] * Z;
|
|
123
|
+
out[offset + 1] = ((0 + x1[0] * X) + x1[1] * Y) + x1[2] * Z;
|
|
124
|
+
out[offset + 2] = ((0 + x2[0] * X) + x2[1] * Y) + x2[2] * Z;
|
|
125
|
+
}
|
|
120
126
|
return {
|
|
121
|
-
referenceWhiteXyz: model.referenceWhiteXyz, cameraNeutralD65: Object.freeze(neutral), appliedGains: Object.freeze(applied),
|
|
127
|
+
developWhiteBalancedInto, referenceWhiteXyz: model.referenceWhiteXyz, cameraNeutralD65: Object.freeze(neutral), appliedGains: Object.freeze(applied),
|
|
122
128
|
asShotNeutral: Object.freeze(asShot), cameraToXyz: m, highlightMode, developInto,
|
|
123
129
|
develop(values) {
|
|
124
130
|
const out = [0, 0, 0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensor-color-development.js","sourceRoot":"","sources":["../../src/color/sensor-color-development.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * RAW colour development: turns normalized sensor RGB into linear sRGB, using a declared D65 camera\n * profile. Internal.\n *\n * The caller is responsible for the profile's evidence, for binding it to the CFA (colour filter\n * array) and colour basis, and for applying white balance (WB) only once. Only validated 3×3 matrices\n * are accepted, and their declared determinant and inverse bounds are kept.\n *\n * Development model (`sensor-color-development` 2.0.0). The input c is the normalized camera\n * channels: black level subtracted, divided by the effective white level and clipped at 1 per site,\n * all earlier in the pipeline.\n *\n * b = highlight(diag(g)·c), linear sRGB = X · M · diag(w) · b\n *\n * Where:\n * - M is the profile's camera→XYZ matrix and X is the XYZ→linear-sRGB matrix.\n * - w = M⁻¹·XYZ(D65), normalized to green. These are the camera-neutral coordinates of D65\n * (DNG 1.7 \"camera neutral\").\n * - The WB gains g map the scene white to equal channel values. diag(w) then maps equal channel values\n * to D65, for any invertible M. So a grey card lit by the gains' illuminant comes out neutral,\n * whether or not M is white-normalized.\n * - Without a resolved WB intent, g = 1/w, and development reduces to X·M·c (a D65 scene white).\n * - The camera neutral of the scene white, written to DNG `AsShotNeutral`, is g_green/g.\n * This module does not chromatically adapt the scene, infer calibration, change geometry or write\n * container metadata.\n *\n * Highlight handling (after each site is clipped at the sensor white level):\n * - `clip` (default): every WB-scaled channel is clipped at the smallest gain. That is the level at\n * which the least-amplified channel saturates, so fully clipped pixels stay neutral. This is the\n * documented default of dcraw (`-H 0`, \"clip all highlights to solid white\") and of most raw\n * converters.\n * - `reconstruct`: a desaturation blend defined by this engine. Let s be the largest normalized\n * channel. The weight t = clamp((s − 0.8)/0.2, 0, 1) blends the unclipped WB result u toward the\n * neutral n = max_k min(u_k, g_min): b = (1 − t)·u + t·n. Below 0.8 of white, colour is unchanged\n * and unclipped; at white it is neutral. This approximation follows the idea of highlight\n * desaturation (Guo, Cheng & Hu, \"Correcting over-exposure in photographs\", CVPR 2010). It does not\n * recover the clipped scene signal.\n * @see docs/PHOTOGRAPHIC_EXPORT.md and docs/NATIVE_RAW_AND_PRINT.md for equations and limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { resolveCaptureColorModel } from \"./capture-color.js\";\n\ntype Matrix = readonly (readonly number[])[];\nexport const SENSOR_COLOR_DEVELOPMENT_MODEL = /* @__PURE__ */ Object.freeze({ id: \"sensor-color-development\", version: \"2.0.0\" } as const);\n/** RAW development highlight policy; see the module notes. */\nexport type RawHighlightMode = \"clip\" | \"reconstruct\";\n/** Normalized channel level where `reconstruct` starts blending toward neutral. */\nexport const RAW_HIGHLIGHT_RECONSTRUCTION_START = 0.8;\n/** Profiles whose camera neutral is within this relative distance of [1,1,1] are white-normalized. */\nconst WHITE_NORMALIZED_TOLERANCE = 1e-12;\n\n/** Multiply in declared row order: ((0 + m0·v0) + m1·v1) + m2·v2. */\nfunction multiply(m: Matrix, values: readonly number[]): number[] {\n return m.map((row) => row.reduce((sum, v, i) => sum+v*values[i]!, 0));\n}\n/** Accepts validated 3×3 matrices only, and keeps their declared determinant and inverse bounds. */\nexport function invertSensorColorMatrix(m: Matrix): number[][] {\n const [a,b,c,d,e,f,g,h,i]=m.flat() as number[], det=a!*(e!*i!-f!*h!)-b!*(d!*i!-f!*g!)+c!*(d!*h!-e!*g!);\n if (!Number.isFinite(det) || Math.abs(det)<1e-9) throw new InvalidConfigurationError(\"Export color matrix is singular or outside supported conditioning.\");\n const result=[[e!*i!-f!*h!,c!*h!-b!*i!,b!*f!-c!*e!],[f!*g!-d!*i!,a!*i!-c!*g!,c!*d!-a!*f!],[d!*h!-e!*g!,b!*g!-a!*h!,a!*e!-b!*d!]].map((r) => r.map((v) => v/det));\n if (result.flat().some((v) => !Number.isFinite(v) || Math.abs(v)>100)) throw new InvalidConfigurationError(\"Export inverse color matrix exceeds supported range.\");\n return result;\n}\n\n/** Camera coordinates of the declared D65 white (not normalized). The caller decides container policy. */\nexport function sensorColorReferenceWhite(xyzToCamera: Matrix): number[] {\n const white = resolveCaptureColorModel().referenceWhiteXyz;\n return multiply(xyzToCamera, [white.x, 1, white.z]);\n}\n\n/** Per-pixel highlight/WB counters accumulated by {@link SensorColorDevelopment.developInto}. */\nexport interface SensorColorDevelopmentCounts { whiteBalanceClippedSampleCount: number; reconstructedPixelCount: number }\n\nexport interface SensorColorDevelopment {\n referenceWhiteXyz: { x: number; y: 1; z: number };\n /** Green-normalized camera-neutral coordinates of D65 (exactly [1,1,1] for white-normalized profiles). */\n cameraNeutralD65: readonly number[];\n /** Applied channel gains: the resolved WB gains, or 1/cameraNeutralD65 when WB is not required. */\n appliedGains: readonly number[];\n /** Green-normalized camera neutral of the assumed scene white: g_green/g (DNG AsShotNeutral). */\n asShotNeutral: readonly number[];\n /** X·M·diag(w) rows, applied as two products in the documented order. */\n cameraToXyz: readonly (readonly number[])[];\n highlightMode: RawHighlightMode;\n /** Develops one normalized camera pixel into out[offset..offset+2] (linear sRGB/D65); updates counts. */\n developInto(c0: number, c1: number, c2: number, out: Float64Array | number[], offset: number, counts: SensorColorDevelopmentCounts): void;\n /** Convenience array form of developInto (counts discarded). */\n develop(values: readonly number[]): number[];\n}\n\n/**\n * Internal development of validated, normalized sensor RGB with a declared D65 profile.\n * `gains` is the resolved WB intent, or null when WB is not required. Both the packed native path and\n * the bounded reference path use the returned operator, so there is one implementation.\n */\nexport function prepareSensorColorDevelopment(cameraChannelsToXyz: Matrix, gains: readonly number[] | null,\n highlightMode: RawHighlightMode = \"clip\"): SensorColorDevelopment {\n if (highlightMode !== \"clip\" && highlightMode !== \"reconstruct\") throw new InvalidConfigurationError(\"Unsupported RAW highlight mode.\");\n const model = resolveCaptureColorModel();\n const raw = sensorColorReferenceWhite(invertSensorColorMatrix(cameraChannelsToXyz));\n if (raw.some((x) => !(x > 0) || !Number.isFinite(x))) throw new InvalidConfigurationError(\"Reference white requires positive camera coordinates.\");\n let neutral = raw.map((x) => x/raw[1]!);\n if (neutral.every((x) => Math.abs(x-1) <= WHITE_NORMALIZED_TOLERANCE)) neutral = [1, 1, 1];\n if (gains && (gains.length !== 3 || gains.some((x) => !(x > 0) || !Number.isFinite(x)))) throw new InvalidConfigurationError(\"White-balance gains must be positive and finite.\");\n const applied = gains ? [...gains] : neutral.map((x) => 1/x);\n const asShot = applied.map((x) => applied[1]!/x), clipLevel = Math.min(...applied);\n const m = cameraChannelsToXyz.map((row) => row.map((v, i) => v*neutral[i]!)), x = model.xyzToCameraRgb;\n const m0 = m[0]!, m1 = m[1]!, m2 = m[2]!, x0 = x[0]!, x1 = x[1]!, x2 = x[2]!;\n const g0 = applied[0]!, g1 = applied[1]!, g2 = applied[2]!, reconstruct = highlightMode === \"reconstruct\";\n const start = RAW_HIGHLIGHT_RECONSTRUCTION_START;\n function developInto(c0: number, c1: number, c2: number, out: Float64Array | number[], offset: number, counts: SensorColorDevelopmentCounts): void {\n const u0 = c0*g0, u1 = c1*g1, u2 = c2*g2;\n let b0: number, b1: number, b2: number;\n const k0 = u0 > clipLevel ? clipLevel : u0, k1 = u1 > clipLevel ? clipLevel : u1, k2 = u2 > clipLevel ? clipLevel : u2;\n if (!reconstruct) {\n b0 = k0; b1 = k1; b2 = k2;\n counts.whiteBalanceClippedSampleCount += Number(u0 > clipLevel)+Number(u1 > clipLevel)+Number(u2 > clipLevel);\n } else {\n const s = c0 > c1 ? (c0 > c2 ? c0 : c2) : (c1 > c2 ? c1 : c2);\n if (s > start) {\n const t = s >= 1 ? 1 : (s-start)/(1-start), n = k0 > k1 ? (k0 > k2 ? k0 : k2) : (k1 > k2 ? k1 : k2);\n b0 = (1-t)*u0+t*n; b1 = (1-t)*u1+t*n; b2 = (1-t)*u2+t*n; counts.reconstructedPixelCount++;\n } else { b0 = u0; b1 = u1; b2 = u2; }\n }\n const X = ((0+m0[0]!*b0)+m0[1]!*b1)+m0[2]!*b2, Y = ((0+m1[0]!*b0)+m1[1]!*b1)+m1[2]!*b2, Z = ((0+m2[0]!*b0)+m2[1]!*b1)+m2[2]!*b2;\n out[offset] = ((0+x0[0]!*X)+x0[1]!*Y)+x0[2]!*Z;\n out[offset+1] = ((0+x1[0]!*X)+x1[1]!*Y)+x1[2]!*Z;\n out[offset+2] = ((0+x2[0]!*X)+x2[1]!*Y)+x2[2]!*Z;\n }\n return {\n referenceWhiteXyz: model.referenceWhiteXyz, cameraNeutralD65: Object.freeze(neutral), appliedGains: Object.freeze(applied),\n asShotNeutral: Object.freeze(asShot), cameraToXyz: m, highlightMode, developInto,\n develop(values: readonly number[]): number[] {\n const out = [0, 0, 0]; developInto(values[0]!, values[1]!, values[2]!, out, 0, { whiteBalanceClippedSampleCount: 0, reconstructedPixelCount: 0 }); return out;\n }\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sensor-color-development.js","sourceRoot":"","sources":["../../src/color/sensor-color-development.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * RAW colour development: turns normalized sensor RGB into linear sRGB, using a declared D65 camera\n * profile. Internal.\n *\n * The caller is responsible for the profile's evidence, for binding it to the CFA (colour filter\n * array) and colour basis, and for applying white balance (WB) only once. Only validated 3×3 matrices\n * are accepted, and their declared determinant and inverse bounds are kept.\n *\n * Development model (`sensor-color-development` 2.0.0). The input c is the normalized camera\n * channels: black level subtracted, divided by the effective white level and clipped at 1 per site,\n * all earlier in the pipeline.\n *\n * b = highlight(diag(g)·c), linear sRGB = X · M · diag(w) · b\n *\n * Where:\n * - M is the profile's camera→XYZ matrix and X is the XYZ→linear-sRGB matrix.\n * - w = M⁻¹·XYZ(D65), normalized to green. These are the camera-neutral coordinates of D65\n * (DNG 1.7 \"camera neutral\").\n * - The WB gains g map the scene white to equal channel values. diag(w) then maps equal channel values\n * to D65, for any invertible M. So a grey card lit by the gains' illuminant comes out neutral,\n * whether or not M is white-normalized.\n * - Without a resolved WB intent, g = 1/w, and development reduces to X·M·c (a D65 scene white).\n * - The camera neutral of the scene white, written to DNG `AsShotNeutral`, is g_green/g.\n * This module does not chromatically adapt the scene, infer calibration, change geometry or write\n * container metadata.\n *\n * Highlight handling (after each site is clipped at the sensor white level):\n * - `clip` (default): every WB-scaled channel is clipped at the smallest gain. That is the level at\n * which the least-amplified channel saturates, so fully clipped pixels stay neutral. This is the\n * documented default of dcraw (`-H 0`, \"clip all highlights to solid white\") and of most raw\n * converters.\n * - `reconstruct`: a desaturation blend defined by this engine. Let s be the largest normalized\n * channel. The weight t = clamp((s − 0.8)/0.2, 0, 1) blends the unclipped WB result u toward the\n * neutral n = max_k min(u_k, g_min): b = (1 − t)·u + t·n. Below 0.8 of white, colour is unchanged\n * and unclipped; at white it is neutral. This approximation follows the idea of highlight\n * desaturation (Guo, Cheng & Hu, \"Correcting over-exposure in photographs\", CVPR 2010). It does not\n * recover the clipped scene signal.\n * @see docs/PHOTOGRAPHIC_EXPORT.md and docs/NATIVE_RAW_AND_PRINT.md for equations and limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { resolveCaptureColorModel } from \"./capture-color.js\";\n\ntype Matrix = readonly (readonly number[])[];\nexport const SENSOR_COLOR_DEVELOPMENT_MODEL = /* @__PURE__ */ Object.freeze({ id: \"sensor-color-development\", version: \"2.0.0\" } as const);\n/** RAW development highlight policy; see the module notes. */\nexport type RawHighlightMode = \"clip\" | \"reconstruct\";\n/** Normalized channel level where `reconstruct` starts blending toward neutral. */\nexport const RAW_HIGHLIGHT_RECONSTRUCTION_START = 0.8;\n/** Profiles whose camera neutral is within this relative distance of [1,1,1] are white-normalized. */\nconst WHITE_NORMALIZED_TOLERANCE = 1e-12;\n\n/** Multiply in declared row order: ((0 + m0·v0) + m1·v1) + m2·v2. */\nfunction multiply(m: Matrix, values: readonly number[]): number[] {\n return m.map((row) => row.reduce((sum, v, i) => sum+v*values[i]!, 0));\n}\n/** Accepts validated 3×3 matrices only, and keeps their declared determinant and inverse bounds. */\nexport function invertSensorColorMatrix(m: Matrix): number[][] {\n const [a,b,c,d,e,f,g,h,i]=m.flat() as number[], det=a!*(e!*i!-f!*h!)-b!*(d!*i!-f!*g!)+c!*(d!*h!-e!*g!);\n if (!Number.isFinite(det) || Math.abs(det)<1e-9) throw new InvalidConfigurationError(\"Export color matrix is singular or outside supported conditioning.\");\n const result=[[e!*i!-f!*h!,c!*h!-b!*i!,b!*f!-c!*e!],[f!*g!-d!*i!,a!*i!-c!*g!,c!*d!-a!*f!],[d!*h!-e!*g!,b!*g!-a!*h!,a!*e!-b!*d!]].map((r) => r.map((v) => v/det));\n if (result.flat().some((v) => !Number.isFinite(v) || Math.abs(v)>100)) throw new InvalidConfigurationError(\"Export inverse color matrix exceeds supported range.\");\n return result;\n}\n\n/** Camera coordinates of the declared D65 white (not normalized). The caller decides container policy. */\nexport function sensorColorReferenceWhite(xyzToCamera: Matrix): number[] {\n const white = resolveCaptureColorModel().referenceWhiteXyz;\n return multiply(xyzToCamera, [white.x, 1, white.z]);\n}\n\n/** Per-pixel highlight/WB counters accumulated by {@link SensorColorDevelopment.developInto}. */\nexport interface SensorColorDevelopmentCounts { whiteBalanceClippedSampleCount: number; reconstructedPixelCount: number }\n\nexport interface SensorColorDevelopment {\n referenceWhiteXyz: { x: number; y: 1; z: number };\n /** Green-normalized camera-neutral coordinates of D65 (exactly [1,1,1] for white-normalized profiles). */\n cameraNeutralD65: readonly number[];\n /** Applied channel gains: the resolved WB gains, or 1/cameraNeutralD65 when WB is not required. */\n appliedGains: readonly number[];\n /** Green-normalized camera neutral of the assumed scene white: g_green/g (DNG AsShotNeutral). */\n asShotNeutral: readonly number[];\n /** X·M·diag(w) rows, applied as two products in the documented order. */\n cameraToXyz: readonly (readonly number[])[];\n highlightMode: RawHighlightMode;\n /** Develops one normalized camera pixel into out[offset..offset+2] (linear sRGB/D65); updates counts. */\n developInto(c0: number, c1: number, c2: number, out: Float64Array | number[], offset: number, counts: SensorColorDevelopmentCounts): void;\n /**\n * #602: the colour step alone, X·M·diag(w)·b, for white-balanced values b that a whole-image highlight step already formed\n * (`highlightMode: \"reconstruct-neighbors\"`); the same products and order as the end of developInto.\n */\n developWhiteBalancedInto(b0: number, b1: number, b2: number, out: Float64Array | number[], offset: number): void;\n /** Convenience array form of developInto (counts discarded). */\n develop(values: readonly number[]): number[];\n}\n\n/**\n * Internal development of validated, normalized sensor RGB with a declared D65 profile.\n * `gains` is the resolved WB intent, or null when WB is not required. Both the packed native path and\n * the bounded reference path use the returned operator, so there is one implementation.\n */\nexport function prepareSensorColorDevelopment(cameraChannelsToXyz: Matrix, gains: readonly number[] | null,\n highlightMode: RawHighlightMode = \"clip\"): SensorColorDevelopment {\n if (highlightMode !== \"clip\" && highlightMode !== \"reconstruct\") throw new InvalidConfigurationError(\"Unsupported RAW highlight mode.\");\n const model = resolveCaptureColorModel();\n const raw = sensorColorReferenceWhite(invertSensorColorMatrix(cameraChannelsToXyz));\n if (raw.some((x) => !(x > 0) || !Number.isFinite(x))) throw new InvalidConfigurationError(\"Reference white requires positive camera coordinates.\");\n let neutral = raw.map((x) => x/raw[1]!);\n if (neutral.every((x) => Math.abs(x-1) <= WHITE_NORMALIZED_TOLERANCE)) neutral = [1, 1, 1];\n if (gains && (gains.length !== 3 || gains.some((x) => !(x > 0) || !Number.isFinite(x)))) throw new InvalidConfigurationError(\"White-balance gains must be positive and finite.\");\n const applied = gains ? [...gains] : neutral.map((x) => 1/x);\n const asShot = applied.map((x) => applied[1]!/x), clipLevel = Math.min(...applied);\n const m = cameraChannelsToXyz.map((row) => row.map((v, i) => v*neutral[i]!)), x = model.xyzToCameraRgb;\n const m0 = m[0]!, m1 = m[1]!, m2 = m[2]!, x0 = x[0]!, x1 = x[1]!, x2 = x[2]!;\n const g0 = applied[0]!, g1 = applied[1]!, g2 = applied[2]!, reconstruct = highlightMode === \"reconstruct\";\n const start = RAW_HIGHLIGHT_RECONSTRUCTION_START;\n function developInto(c0: number, c1: number, c2: number, out: Float64Array | number[], offset: number, counts: SensorColorDevelopmentCounts): void {\n const u0 = c0*g0, u1 = c1*g1, u2 = c2*g2;\n let b0: number, b1: number, b2: number;\n const k0 = u0 > clipLevel ? clipLevel : u0, k1 = u1 > clipLevel ? clipLevel : u1, k2 = u2 > clipLevel ? clipLevel : u2;\n if (!reconstruct) {\n b0 = k0; b1 = k1; b2 = k2;\n counts.whiteBalanceClippedSampleCount += Number(u0 > clipLevel)+Number(u1 > clipLevel)+Number(u2 > clipLevel);\n } else {\n const s = c0 > c1 ? (c0 > c2 ? c0 : c2) : (c1 > c2 ? c1 : c2);\n if (s > start) {\n const t = s >= 1 ? 1 : (s-start)/(1-start), n = k0 > k1 ? (k0 > k2 ? k0 : k2) : (k1 > k2 ? k1 : k2);\n b0 = (1-t)*u0+t*n; b1 = (1-t)*u1+t*n; b2 = (1-t)*u2+t*n; counts.reconstructedPixelCount++;\n } else { b0 = u0; b1 = u1; b2 = u2; }\n }\n const X = ((0+m0[0]!*b0)+m0[1]!*b1)+m0[2]!*b2, Y = ((0+m1[0]!*b0)+m1[1]!*b1)+m1[2]!*b2, Z = ((0+m2[0]!*b0)+m2[1]!*b1)+m2[2]!*b2;\n out[offset] = ((0+x0[0]!*X)+x0[1]!*Y)+x0[2]!*Z;\n out[offset+1] = ((0+x1[0]!*X)+x1[1]!*Y)+x1[2]!*Z;\n out[offset+2] = ((0+x2[0]!*X)+x2[1]!*Y)+x2[2]!*Z;\n }\n function developWhiteBalancedInto(b0: number, b1: number, b2: number, out: Float64Array | number[], offset: number): void {\n const X = ((0+m0[0]!*b0)+m0[1]!*b1)+m0[2]!*b2, Y = ((0+m1[0]!*b0)+m1[1]!*b1)+m1[2]!*b2, Z = ((0+m2[0]!*b0)+m2[1]!*b1)+m2[2]!*b2;\n out[offset] = ((0+x0[0]!*X)+x0[1]!*Y)+x0[2]!*Z;\n out[offset+1] = ((0+x1[0]!*X)+x1[1]!*Y)+x1[2]!*Z;\n out[offset+2] = ((0+x2[0]!*X)+x2[1]!*Y)+x2[2]!*Z;\n }\n return {\n developWhiteBalancedInto, referenceWhiteXyz: model.referenceWhiteXyz, cameraNeutralD65: Object.freeze(neutral), appliedGains: Object.freeze(applied),\n asShotNeutral: Object.freeze(asShot), cameraToXyz: m, highlightMode, developInto,\n develop(values: readonly number[]): number[] {\n const out = [0, 0, 0]; developInto(values[0]!, values[1]!, values[2]!, out, 0, { whiteBalanceClippedSampleCount: 0, reconstructedPixelCount: 0 }); return out;\n }\n };\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #590 (2.22.0): how much a light's spectrum can defeat the facade's spectral midpoint quadrature. The composite midpoint rule
|
|
3
|
+
* over [a, b] with step h has the error (b − a)·h²·f″(ξ)/24 (DLMF §3.5(i)); normalized by the integral it gives the relative
|
|
4
|
+
* error indicator R(h) = h²·κ/24 with κ = Σ_λ |S(λ + 1) − 2S(λ) + S(λ − 1)| / Σ_λ S(λ) on a 1 nm grid over 380–780 nm (nm⁻²),
|
|
5
|
+
* from the illuminant's spectral power distribution alone (no sensor channel). A light is "spiky at h" when R(h) exceeds
|
|
6
|
+
* {@link SPECTRAL_SPIKINESS_THRESHOLD} (a design choice: the bound allows more than 1 % error).
|
|
7
|
+
*/
|
|
8
|
+
/** R(h) above this marks a spiky light (owner decision 2026-11-10: the fluorescent and LED representative sources count). */
|
|
9
|
+
export declare const SPECTRAL_SPIKINESS_THRESHOLD = 0.01;
|
|
10
|
+
/** κ, nm⁻², of a spectral power distribution evaluated at 380, 381, …, 780 nm (0 for a zero spectrum). */
|
|
11
|
+
export declare function spectralSpikiness(evaluate: (wavelengthNanometers: number) => number): number;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* #590 (2.22.0): how much a light's spectrum can defeat the facade's spectral midpoint quadrature. The composite midpoint rule
|
|
4
|
+
* over [a, b] with step h has the error (b − a)·h²·f″(ξ)/24 (DLMF §3.5(i)); normalized by the integral it gives the relative
|
|
5
|
+
* error indicator R(h) = h²·κ/24 with κ = Σ_λ |S(λ + 1) − 2S(λ) + S(λ − 1)| / Σ_λ S(λ) on a 1 nm grid over 380–780 nm (nm⁻²),
|
|
6
|
+
* from the illuminant's spectral power distribution alone (no sensor channel). A light is "spiky at h" when R(h) exceeds
|
|
7
|
+
* {@link SPECTRAL_SPIKINESS_THRESHOLD} (a design choice: the bound allows more than 1 % error).
|
|
8
|
+
*/
|
|
9
|
+
/** R(h) above this marks a spiky light (owner decision 2026-11-10: the fluorescent and LED representative sources count). */
|
|
10
|
+
export const SPECTRAL_SPIKINESS_THRESHOLD = 0.01;
|
|
11
|
+
/** κ, nm⁻², of a spectral power distribution evaluated at 380, 381, …, 780 nm (0 for a zero spectrum). */
|
|
12
|
+
export function spectralSpikiness(evaluate) {
|
|
13
|
+
let a = evaluate(380), b = evaluate(381), sum = a + b, second = 0;
|
|
14
|
+
for (let nm = 382; nm <= 780; nm++) {
|
|
15
|
+
const c = evaluate(nm);
|
|
16
|
+
second += Math.abs(c - 2 * b + a);
|
|
17
|
+
sum += c;
|
|
18
|
+
a = b;
|
|
19
|
+
b = c;
|
|
20
|
+
}
|
|
21
|
+
return sum > 0 ? second / sum : 0;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=spectral-spikiness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spectral-spikiness.js","sourceRoot":"","sources":["../../src/color/spectral-spikiness.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;GAMG;AAEH,6HAA6H;AAC7H,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC;AAEjD,0GAA0G;AAC1G,MAAM,UAAU,iBAAiB,CAAC,QAAkD;IAClF,IAAI,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;IAClE,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;QAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;QAAC,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAAC,GAAG,IAAI,CAAC,CAAC;QAAC,CAAC,GAAG,CAAC,CAAC;QAAC,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC;IAC1H,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #590 (2.22.0): how much a light's spectrum can defeat the facade's spectral midpoint quadrature. The composite midpoint rule\n * over [a, b] with step h has the error (b − a)·h²·f″(ξ)/24 (DLMF §3.5(i)); normalized by the integral it gives the relative\n * error indicator R(h) = h²·κ/24 with κ = Σ_λ |S(λ + 1) − 2S(λ) + S(λ − 1)| / Σ_λ S(λ) on a 1 nm grid over 380–780 nm (nm⁻²),\n * from the illuminant's spectral power distribution alone (no sensor channel). A light is \"spiky at h\" when R(h) exceeds\n * {@link SPECTRAL_SPIKINESS_THRESHOLD} (a design choice: the bound allows more than 1 % error).\n */\n\n/** R(h) above this marks a spiky light (owner decision 2026-11-10: the fluorescent and LED representative sources count). */\nexport const SPECTRAL_SPIKINESS_THRESHOLD = 0.01;\n\n/** κ, nm⁻², of a spectral power distribution evaluated at 380, 381, …, 780 nm (0 for a zero spectrum). */\nexport function spectralSpikiness(evaluate: (wavelengthNanometers: number) => number): number {\n let a = evaluate(380), b = evaluate(381), sum = a + b, second = 0;\n for (let nm = 382; nm <= 780; nm++) { const c = evaluate(nm); second += Math.abs(c - 2 * b + a); sum += c; a = b; b = c; }\n return sum > 0 ? second / sum : 0;\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The image-kernel backends' error class and its reasons (2.22.0: kept outside src/gpu/ so that the CPU's on-demand code loader,
|
|
3
|
+
* src/facade/on-demand-kits.ts, can report `quality-code-unavailable` without bringing GPU code into a bundle's first load;
|
|
4
|
+
* re-exported unchanged from src/gpu/image-kernel-execution.ts and the package root).
|
|
5
|
+
*/
|
|
6
|
+
/** Why a requested `webgpu-f32` backend ran the CPU reference instead. `webgpu-disabled`: `setWebGpuEnabled(false)` in this realm. */
|
|
7
|
+
export type ImageKernelFallbackReason = "webgpu-unavailable" | "webgpu-disabled" | "adapter-unavailable" | "device-request-failed" | "insufficient-limits" | "unsupported-platform-endianness";
|
|
8
|
+
/**
|
|
9
|
+
* Failure classes reported by {@link ImageKernelExecutionError}. `conformance-failed` (#481): a device failed the display view's
|
|
10
|
+
* run-time self-check with `gpu: "require"`. `quality-code-unavailable` (2.22.0, #594): on-demand full-quality code could not be
|
|
11
|
+
* loaded (for example a failed chunk download); `prepareQualityPaths()` rejects with it, a display-view render with `gpu: "require"`
|
|
12
|
+
* fails with it (with `gpu: "auto"` it runs the CPU reference), and a CPU request whose full-quality or opt-in code does not load
|
|
13
|
+
* fails with it (nothing is remembered: the next request tries again).
|
|
14
|
+
*/
|
|
15
|
+
export type ImageKernelFailureReason = ImageKernelFallbackReason | "device-lost" | "out-of-memory" | "gpu-validation" | "aborted" | "disposed" | "busy" | "unsupported-on-backend" | "unsupported-float32-domain" | "indeterminate-float32-classification" | "conformance-failed" | "quality-code-unavailable";
|
|
16
|
+
/** Domain error for getting and running a backend. Input validation still throws the engine's existing errors. */
|
|
17
|
+
export declare class ImageKernelExecutionError extends Error {
|
|
18
|
+
readonly code = "IMAGE_KERNEL_EXECUTION";
|
|
19
|
+
readonly reason: ImageKernelFailureReason;
|
|
20
|
+
constructor(reason: ImageKernelFailureReason, message: string);
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/** Domain error for getting and running a backend. Input validation still throws the engine's existing errors. */
|
|
3
|
+
export class ImageKernelExecutionError extends Error {
|
|
4
|
+
code = "IMAGE_KERNEL_EXECUTION";
|
|
5
|
+
reason;
|
|
6
|
+
constructor(reason, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ImageKernelExecutionError";
|
|
9
|
+
this.reason = reason;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=execution-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-error.js","sourceRoot":"","sources":["../../src/core/execution-error.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAuBtC,kHAAkH;AAClH,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IACzC,IAAI,GAAG,wBAAwB,CAAC;IAChC,MAAM,CAA2B;IAE1C,YAAY,MAAgC,EAAE,OAAe;QAC3D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * The image-kernel backends' error class and its reasons (2.22.0: kept outside src/gpu/ so that the CPU's on-demand code loader,\n * src/facade/on-demand-kits.ts, can report `quality-code-unavailable` without bringing GPU code into a bundle's first load;\n * re-exported unchanged from src/gpu/image-kernel-execution.ts and the package root).\n */\n\n/** Why a requested `webgpu-f32` backend ran the CPU reference instead. `webgpu-disabled`: `setWebGpuEnabled(false)` in this realm. */\nexport type ImageKernelFallbackReason = \"webgpu-unavailable\" | \"webgpu-disabled\" | \"adapter-unavailable\" | \"device-request-failed\" |\n \"insufficient-limits\" | \"unsupported-platform-endianness\";\n\n/**\n * Failure classes reported by {@link ImageKernelExecutionError}. `conformance-failed` (#481): a device failed the display view's\n * run-time self-check with `gpu: \"require\"`. `quality-code-unavailable` (2.22.0, #594): on-demand full-quality code could not be\n * loaded (for example a failed chunk download); `prepareQualityPaths()` rejects with it, a display-view render with `gpu: \"require\"`\n * fails with it (with `gpu: \"auto\"` it runs the CPU reference), and a CPU request whose full-quality or opt-in code does not load\n * fails with it (nothing is remembered: the next request tries again).\n */\nexport type ImageKernelFailureReason = ImageKernelFallbackReason | \"device-lost\" | \"out-of-memory\" | \"gpu-validation\" |\n \"aborted\" | \"disposed\" | \"busy\" | \"unsupported-on-backend\" | \"unsupported-float32-domain\" | \"indeterminate-float32-classification\" | \"conformance-failed\" |\n \"quality-code-unavailable\";\n\n/** Domain error for getting and running a backend. Input validation still throws the engine's existing errors. */\nexport class ImageKernelExecutionError extends Error {\n readonly code = \"IMAGE_KERNEL_EXECUTION\";\n readonly reason: ImageKernelFailureReason;\n\n constructor(reason: ImageKernelFailureReason, message: string) {\n super(message);\n this.name = \"ImageKernelExecutionError\";\n this.reason = reason;\n }\n}\n\n"]}
|
package/dist/core/version.d.ts
CHANGED
package/dist/core/version.js
CHANGED
package/dist/core/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAiB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,QAAiB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.23.0\" as const;\n"]}
|
|
@@ -3,6 +3,28 @@ import { type ResolveLensSampledPsfInput } from "../optics/lens-psf-profile.js";
|
|
|
3
3
|
import type { DisplayViewScene } from "./expected-signal.js";
|
|
4
4
|
/** Fewest grid cells between neighboring profile field nodes (10 since #589: with 4, 0.4 % of quick frames failed the run-time check). */
|
|
5
5
|
export declare const CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS = 10;
|
|
6
|
+
/**
|
|
7
|
+
* #603 (2.22.0): the fewest cells on a fast request (on screen: 2.20.0's grid, its run-time fallbacks to the exact method included);
|
|
8
|
+
* the full grid, {@link CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS}, on an exact one (the exports and the calculator).
|
|
9
|
+
*/
|
|
10
|
+
export declare const CATALOGUE_FAST_GRID_SPEED_SUBDIVISIONS = 4;
|
|
11
|
+
/**
|
|
12
|
+
* #603 (owner decision 2026-11-11): the lenses that keep the full grid on fast requests too, because the 4-cell grid's run-time
|
|
13
|
+
* fallbacks to the exact method made their fast frames slower on average. Measured November 11, 2026 (arm64, Node.js 24; every
|
|
14
|
+
* catalogue lens but the phone lenses, wide open and at max(f/8, widest), Siemens star and color patches, whole frames 192, 384 and
|
|
15
|
+
* 768 pixels wide, two runs each; docs/LENS_CATALOGUE.md, "Fast method"): only this lens fell back, 4 of its 8 frames at 192 pixels
|
|
16
|
+
* (f/2.8), 11.6 s each, against 0.17 s for the 10-cell grid.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CATALOGUE_FAST_GRID_FULL_ON_SCREEN: readonly {
|
|
19
|
+
lensId: string;
|
|
20
|
+
fallbacksAtFourCells: {
|
|
21
|
+
frames: number;
|
|
22
|
+
fellBack: number;
|
|
23
|
+
widthPixels: number;
|
|
24
|
+
};
|
|
25
|
+
}[];
|
|
26
|
+
/** #603: the fewest grid cells for a lens on a fast or an exact request. */
|
|
27
|
+
export declare function catalogueFastGridSubdivisions(lensId: string, fast: boolean): number;
|
|
6
28
|
/** Largest move of a node kernel's intensity centroid across one grid cell, in sites (sets finer grids where lateral color moves the PSF). */
|
|
7
29
|
export declare const CATALOGUE_FAST_GRID_CENTROID_STEP_SITES = 0.05;
|
|
8
30
|
/**
|
|
@@ -44,6 +66,8 @@ export interface CatalogueLensSignalInput {
|
|
|
44
66
|
/** The planar provider's plane distance and frame-unit scale. */
|
|
45
67
|
distanceM: number;
|
|
46
68
|
metresPerFrameUnit: number;
|
|
69
|
+
/** #603: the fewest grid cells between neighbouring field nodes (default {@link CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS}). */
|
|
70
|
+
gridSubdivisions?: number;
|
|
47
71
|
/** Internal test hook: resolve every site's kernels exactly (the exact method's sum, slowly) instead of blending the grid. */
|
|
48
72
|
exactKernels?: boolean;
|
|
49
73
|
}
|
|
@@ -66,7 +90,7 @@ export interface CatalogueLensSignal {
|
|
|
66
90
|
* ⌈Δc / GRID_CENTROID_STEP_SITES⌉) equal cells, Δc the largest centroid move between the interval's two nodes over the other
|
|
67
91
|
* axis's nodes and the wavelength nodes.
|
|
68
92
|
*/
|
|
69
|
-
export declare function catalogueFastGridAxes(psf: CatalogueLensSignalInput["psf"]): {
|
|
93
|
+
export declare function catalogueFastGridAxes(psf: CatalogueLensSignalInput["psf"], minimum?: number): {
|
|
70
94
|
x: number[];
|
|
71
95
|
y: number[];
|
|
72
96
|
};
|
|
@@ -87,6 +111,7 @@ export declare function catalogueFastAssumptions(run: {
|
|
|
87
111
|
rows: number;
|
|
88
112
|
resolved: number;
|
|
89
113
|
verificationRelativeDifference: number;
|
|
114
|
+
subdivisions: number;
|
|
90
115
|
}): {
|
|
91
116
|
id: string;
|
|
92
117
|
value: Record<string, unknown>;
|