@photivra/engine 2.19.0 → 2.21.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 +289 -1
- package/CITATION.cff +1 -1
- package/NODE_SUPPORT.md +7 -5
- package/README.md +7 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +21 -10
- package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
- package/dist/capture/dng-lens-correction-opcodes.js +119 -0
- package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
- package/dist/capture/exposure-build-up-stage.d.ts +16 -0
- package/dist/capture/exposure-build-up-stage.js +4 -0
- package/dist/capture/exposure-build-up-stage.js.map +1 -0
- package/dist/capture/native-environment-lean.d.ts +2 -1
- package/dist/capture/native-environment-lean.js +42 -78
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-tabulated.js +4 -3
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/capture/native-raw-dng.d.ts +6 -0
- package/dist/capture/native-raw-dng.js +16 -2
- package/dist/capture/native-raw-dng.js.map +1 -1
- package/dist/capture/native-raw.d.ts +3 -2
- package/dist/capture/native-raw.js +5 -4
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.d.ts +4 -1
- package/dist/capture/sensor-raw-producer.js +15 -8
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/core/shared-helpers.d.ts +2 -0
- package/dist/core/shared-helpers.js +4 -0
- package/dist/core/shared-helpers.js.map +1 -1
- 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/equipment/generic-lens-catalogue.d.ts +11 -4
- package/dist/equipment/generic-lens-catalogue.js +49 -7
- package/dist/equipment/generic-lens-catalogue.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +32 -0
- package/dist/equipment/generic-tier-assets.js +18 -14
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.js +2 -3
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/equipment/sensor-profile-body.d.ts +78 -0
- package/dist/equipment/sensor-profile-body.js +77 -0
- package/dist/equipment/sensor-profile-body.js.map +1 -0
- package/dist/equipment/sensor-profiles.d.ts +8 -0
- package/dist/equipment/sensor-profiles.js +82 -0
- package/dist/equipment/sensor-profiles.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +2 -2
- package/dist/facade/catalogue-lens-signal.js +58 -71
- package/dist/facade/catalogue-lens-signal.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +2 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/defocus-image-space-provider.d.ts +7 -0
- package/dist/facade/defocus-image-space-provider.js +64 -76
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +2 -0
- 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 +4 -4
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +1 -1
- package/dist/facade/display-view-lens-signal.js +30 -26
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/display-view.d.ts +3 -1
- package/dist/facade/display-view.js +6 -24
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +3 -1
- package/dist/facade/expected-signal-setup.js +1 -1
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +47 -14
- package/dist/facade/expected-signal.js +117 -112
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/exposure-build-up.d.ts +70 -0
- package/dist/facade/exposure-build-up.js +164 -0
- package/dist/facade/exposure-build-up.js.map +1 -0
- package/dist/facade/fast-methods.d.ts +50 -0
- package/dist/facade/fast-methods.js +58 -0
- package/dist/facade/fast-methods.js.map +1 -0
- package/dist/facade/fast-resolution.d.ts +12 -0
- package/dist/facade/fast-resolution.js +18 -0
- package/dist/facade/fast-resolution.js.map +1 -0
- package/dist/facade/generic-lens-capture.d.ts +5 -4
- package/dist/facade/generic-lens-capture.js +5 -4
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/image-plane-enable.d.ts +7 -0
- package/dist/facade/image-plane-enable.js +17 -0
- package/dist/facade/image-plane-enable.js.map +1 -0
- package/dist/facade/image-plane-lattice-data.d.ts +6 -0
- package/dist/facade/image-plane-lattice-data.js +10 -0
- package/dist/facade/image-plane-lattice-data.js.map +1 -0
- package/dist/facade/image-plane-support.d.ts +33 -0
- package/dist/facade/image-plane-support.js +11 -0
- package/dist/facade/image-plane-support.js.map +1 -0
- package/dist/facade/image-plane.d.ts +36 -0
- package/dist/facade/image-plane.js +353 -0
- package/dist/facade/image-plane.js.map +1 -0
- package/dist/facade/mixed-illumination.d.ts +108 -0
- package/dist/facade/mixed-illumination.js +174 -0
- package/dist/facade/mixed-illumination.js.map +1 -0
- package/dist/facade/representative-light-sources.d.ts +72 -0
- package/dist/facade/representative-light-sources.js +124 -0
- package/dist/facade/representative-light-sources.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +129 -4
- package/dist/facade/scene-radiance.js +48 -13
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +88 -19
- package/dist/facade/simulate-capture.js +374 -145
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +3 -2
- package/dist/facade/simulate-display-view.js +36 -18
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.d.ts +13 -1
- package/dist/gpu/chart-composite-gpu.js +48 -9
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/chart-radiance-gpu.js +4 -30
- package/dist/gpu/chart-radiance-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
- package/dist/gpu/display-view-composite-material-module.js +39 -13
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/display-view-image-material-module.d.ts +86 -0
- package/dist/gpu/display-view-image-material-module.js +339 -0
- package/dist/gpu/display-view-image-material-module.js.map +1 -0
- package/dist/gpu/display-view-image-quality.d.ts +45 -0
- package/dist/gpu/display-view-image-quality.js +380 -0
- package/dist/gpu/display-view-image-quality.js.map +1 -0
- package/dist/gpu/display-view-quality-gpu.d.ts +67 -1
- package/dist/gpu/display-view-quality-gpu.js +136 -205
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +51 -3
- package/dist/gpu/display-view-speed-gpu.js +131 -93
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +29 -15
- package/dist/gpu/wgsl-display-view-quality-kernel.js +58 -36
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +21 -6
- package/dist/gpu/wgsl-display-view-speed-kernel.js +46 -11
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
- package/dist/gpu/wgsl-exact-sum.js +42 -0
- package/dist/gpu/wgsl-exact-sum.js.map +1 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/motion/subject-motion.d.ts +66 -0
- package/dist/motion/subject-motion.js +52 -0
- package/dist/motion/subject-motion.js.map +1 -0
- package/dist/optics/aperture-diameter.d.ts +47 -0
- package/dist/optics/aperture-diameter.js +50 -0
- package/dist/optics/aperture-diameter.js.map +1 -0
- package/dist/optics/detail-limit.d.ts +182 -0
- package/dist/optics/detail-limit.js +336 -0
- package/dist/optics/detail-limit.js.map +1 -0
- package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
- package/dist/optics/generic-lens-lateral-color.js +77 -0
- package/dist/optics/generic-lens-lateral-color.js.map +1 -0
- package/dist/optics/generic-lens-psf.d.ts +18 -2
- package/dist/optics/generic-lens-psf.js +79 -48
- package/dist/optics/generic-lens-psf.js.map +1 -1
- package/dist/optics/lens-psf-profile.d.ts +7 -0
- package/dist/optics/lens-psf-profile.js +2 -1
- package/dist/optics/lens-psf-profile.js.map +1 -1
- package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
- package/dist/optics/scene-to-sensor-irradiance.js +7 -0
- package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
- package/dist/output/crop-to-match-focal-length.d.ts +62 -0
- package/dist/output/crop-to-match-focal-length.js +63 -0
- package/dist/output/crop-to-match-focal-length.js.map +1 -0
- package/dist/output/developed-lens-corrections.d.ts +19 -5
- package/dist/output/developed-lens-corrections.js +30 -11
- package/dist/output/developed-lens-corrections.js.map +1 -1
- package/dist/output/generic-lens-corrections.d.ts +41 -3
- package/dist/output/generic-lens-corrections.js +57 -9
- package/dist/output/generic-lens-corrections.js.map +1 -1
- package/dist/output/lens-corrections.d.ts +12 -2
- package/dist/output/lens-corrections.js +22 -7
- package/dist/output/lens-corrections.js.map +1 -1
- package/dist/output/print-contrast-reference.js +1 -1
- package/dist/output/print-contrast-reference.js.map +1 -1
- package/dist/output/print-plan.d.ts +41 -5
- package/dist/output/print-plan.js +59 -14
- package/dist/output/print-plan.js.map +1 -1
- package/dist/output/print-size-blur-noise.d.ts +110 -0
- package/dist/output/print-size-blur-noise.js +138 -0
- package/dist/output/print-size-blur-noise.js.map +1 -0
- package/dist/sensor/binomial-sample.d.ts +3 -0
- package/dist/sensor/binomial-sample.js +86 -0
- package/dist/sensor/binomial-sample.js.map +1 -0
- package/dist/sensor/eqe-electron-rate.js +1 -7
- package/dist/sensor/eqe-electron-rate.js.map +1 -1
- package/dist/sensor/exposure-window.d.ts +2 -0
- package/dist/sensor/exposure-window.js +2 -2
- package/dist/sensor/exposure-window.js.map +1 -1
- package/dist/sensor/readout-timing.js +2 -23
- package/dist/sensor/readout-timing.js.map +1 -1
- package/dist/sensor/response-application-compatibility.js +1 -6
- package/dist/sensor/response-application-compatibility.js.map +1 -1
- package/dist/sensor/responsivity-photocurrent.js +1 -7
- package/dist/sensor/responsivity-photocurrent.js.map +1 -1
- package/dist/sensor/sensor-characteristics.d.ts +147 -0
- package/dist/sensor/sensor-characteristics.js +126 -0
- package/dist/sensor/sensor-characteristics.js.map +1 -0
- package/dist/sensor/spatial-sample-reduction.js +1 -5
- package/dist/sensor/spatial-sample-reduction.js.map +1 -1
- package/dist/sensor/spectral-quadrature.js +1 -6
- package/dist/sensor/spectral-quadrature.js.map +1 -1
- package/dist/sensor/temporal-photo-signal.d.ts +3 -0
- package/dist/sensor/temporal-photo-signal.js +5 -2
- package/dist/sensor/temporal-photo-signal.js.map +1 -1
- package/dist/stabilization/system.d.ts +9 -0
- package/dist/stabilization/system.js +5 -6
- package/dist/stabilization/system.js.map +1 -1
- package/docs/AGENT_GUIDE.md +5 -0
- package/docs/API_REFERENCE.md +173 -2
- package/docs/QUICK_START.md +213 -6
- package/docs/USAGE.md +157 -3
- package/docs/api/capture-native-raw-dng.md +6 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +17 -7
- package/docs/api/equipment-sensor-profile-body.md +64 -0
- package/docs/api/equipment-sensor-profiles.md +86 -0
- package/docs/api/exports.json +357 -2
- package/docs/api/facade-display-view-gpu.md +5 -2
- package/docs/api/facade-exposure-build-up.md +105 -0
- package/docs/api/facade-fast-methods.md +99 -0
- package/docs/api/facade-image-plane-enable.md +16 -0
- package/docs/api/facade-mixed-illumination.md +67 -0
- package/docs/api/facade-representative-light-sources.md +131 -0
- package/docs/api/facade-scene-radiance.md +54 -3
- package/docs/api/facade-simulate-capture.md +60 -15
- package/docs/api/facade-simulate-display-view.md +3 -2
- package/docs/api/motion-subject-motion.md +23 -0
- package/docs/api/optics-aperture-diameter.md +51 -0
- package/docs/api/optics-detail-limit.md +203 -0
- package/docs/api/optics-generic-lens-lateral-color.md +79 -0
- package/docs/api/output-crop-to-match-focal-length.md +67 -0
- package/docs/api/output-developed-lens-corrections.md +10 -1
- package/docs/api/output-generic-lens-corrections.md +40 -2
- package/docs/api/output-lens-corrections.md +11 -1
- package/docs/api/output-print-plan.md +35 -1
- package/docs/api/output-print-size-blur-noise.md +115 -0
- package/docs/api/sensor-sensor-characteristics.md +105 -0
- package/llms.txt +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Internal (#534): a seeded binomial draw, for splitting a realized charge count between an earlier and a later part of one
|
|
4
|
+
* exposure. Not exported from the package.
|
|
5
|
+
*
|
|
6
|
+
* - Mean n·min(p, 1 − p) below 10: inversion, the sequential search of the cumulative distribution from 0 with the recurrence
|
|
7
|
+
* f(x) = f(x − 1)·((n + 1)·s / x − s), s = p/(1 − p) (L. Devroye, Non-Uniform Random Variate Generation, Springer 1986, free
|
|
8
|
+
* from the author's site, chapter X, section 4 "The binomial distribution").
|
|
9
|
+
* - Otherwise: BTRS, transformed rejection with squeeze (W. Hörmann, "The generation of binomial random variates", Preprint Series
|
|
10
|
+
* of the Department of Applied Statistics and Data Processing, WU Vienna, no. 1, April 1992, free,
|
|
11
|
+
* doi:10.57938/79ec156b-9c3d-4b5d-a939-07a1846443fd), the binomial companion of the PTRS Poisson sampler `samplePoisson` uses.
|
|
12
|
+
* Its acceptance test compares log(f(k)/f(m)) at the mode m with Stirling's series for log x! (tail terms 1/12, −1/360, 1/1260
|
|
13
|
+
* in 1/(x + 1); exact sums below 10). The hat constants (b, a, c, v_r, α) are BTRS's as implemented in widely used libraries;
|
|
14
|
+
* they could not be checked against the preprint's text here, so the chi-square tests of the probability mass function in
|
|
15
|
+
* test/binomial-sample.test.ts are the evidence that the draws follow the binomial distribution.
|
|
16
|
+
* - p > 1/2 draws n − Binomial(n, 1 − p).
|
|
17
|
+
*
|
|
18
|
+
* Both methods are exact (they draw from the binomial distribution itself, not an approximation of it) up to floating-point
|
|
19
|
+
* rounding of the probabilities. The same n, p and stream reproduce the same draw. The tests check the probability mass
|
|
20
|
+
* function by a chi-square test and the moments.
|
|
21
|
+
*/
|
|
22
|
+
import { InvalidScientificInputError } from "../core/validation.js";
|
|
23
|
+
const HALF_LOG_TWO_PI = 0.5 * Math.log(2 * Math.PI);
|
|
24
|
+
/** log x! − ((x + ½)·log(x + 1) − (x + 1) + ½·log 2π) for x = 0 … 9, from the exact sums. */
|
|
25
|
+
const SMALL_TAILS = (() => {
|
|
26
|
+
const table = new Float64Array(10);
|
|
27
|
+
let logFactorial = 0;
|
|
28
|
+
for (let x = 0; x < 10; x++) {
|
|
29
|
+
if (x > 1)
|
|
30
|
+
logFactorial += Math.log(x);
|
|
31
|
+
table[x] = logFactorial - ((x + 0.5) * Math.log(x + 1) - (x + 1) + HALF_LOG_TWO_PI);
|
|
32
|
+
}
|
|
33
|
+
return table;
|
|
34
|
+
})();
|
|
35
|
+
function stirlingTail(x) {
|
|
36
|
+
if (x < 10)
|
|
37
|
+
return SMALL_TAILS[x];
|
|
38
|
+
const z = x + 1, z2 = z * z;
|
|
39
|
+
return (1 / 12 - (1 / 360 - 1 / 1260 / z2) / z2) / z;
|
|
40
|
+
}
|
|
41
|
+
/** Draws Binomial(n, p): n a non-negative safe integer, 0 ≤ p ≤ 1. */
|
|
42
|
+
export function sampleBinomial(n, p, random) {
|
|
43
|
+
if (!Number.isSafeInteger(n) || n < 0 || !(p >= 0 && p <= 1))
|
|
44
|
+
throw new InvalidScientificInputError("Binomial draws need a non-negative integer count and a probability in [0, 1].");
|
|
45
|
+
if (n === 0 || p === 0)
|
|
46
|
+
return 0;
|
|
47
|
+
if (p === 1)
|
|
48
|
+
return n;
|
|
49
|
+
if (p > 0.5)
|
|
50
|
+
return n - sampleBinomial(n, 1 - p, random);
|
|
51
|
+
const q = 1 - p;
|
|
52
|
+
if (n * p < 10) {
|
|
53
|
+
const s = p / q, a = (n + 1) * s, start = Math.exp(n * Math.log(q));
|
|
54
|
+
for (;;) {
|
|
55
|
+
let u = random.next(), f = start, x = 0;
|
|
56
|
+
// Rounding can leave a remainder above the last probability: draw again (the remainder is of the order of 1e-16).
|
|
57
|
+
while (u > f && x < n) {
|
|
58
|
+
u -= f;
|
|
59
|
+
x++;
|
|
60
|
+
f *= a / x - s;
|
|
61
|
+
}
|
|
62
|
+
if (u <= f)
|
|
63
|
+
return x;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const spq = Math.sqrt(n * p * q), b = 1.15 + 2.53 * spq, a = -0.0873 + 0.0248 * b + 0.01 * p, c = n * p + 0.5, vr = 0.92 - 4.2 / b;
|
|
67
|
+
const r = p / q, alpha = (2.83 + 5.1 / b) * spq, m = Math.floor((n + 1) * p);
|
|
68
|
+
const modeTerm = (m + 0.5) * Math.log((m + 1) / (r * (n - m + 1))) + stirlingTail(m) + stirlingTail(n - m);
|
|
69
|
+
for (;;) {
|
|
70
|
+
const u = random.next() - 0.5, us = 0.5 - Math.abs(u);
|
|
71
|
+
let v = random.next();
|
|
72
|
+
if (us <= 0)
|
|
73
|
+
continue;
|
|
74
|
+
const k = Math.floor((2 * a / us + b) * u + c);
|
|
75
|
+
if (k < 0 || k > n)
|
|
76
|
+
continue;
|
|
77
|
+
if (us >= 0.07 && v <= vr)
|
|
78
|
+
return k;
|
|
79
|
+
v = Math.log(v * alpha / (a / (us * us) + b));
|
|
80
|
+
// log(f(k)/f(m)) by Stirling's series, in a form without the cancellation of large log-factorials.
|
|
81
|
+
const bound = modeTerm + (n + 1) * Math.log((n - m + 1) / (n - k + 1)) + (k + 0.5) * Math.log(r * (n - k + 1) / (k + 1)) - stirlingTail(k) - stirlingTail(n - k);
|
|
82
|
+
if (v <= bound)
|
|
83
|
+
return k;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=binomial-sample.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binomial-sample.js","sourceRoot":"","sources":["../../src/sensor/binomial-sample.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (#534): a seeded binomial draw, for splitting a realized charge count between an earlier and a later part of one\n * exposure. Not exported from the package.\n *\n * - Mean n·min(p, 1 − p) below 10: inversion, the sequential search of the cumulative distribution from 0 with the recurrence\n * f(x) = f(x − 1)·((n + 1)·s / x − s), s = p/(1 − p) (L. Devroye, Non-Uniform Random Variate Generation, Springer 1986, free\n * from the author's site, chapter X, section 4 \"The binomial distribution\").\n * - Otherwise: BTRS, transformed rejection with squeeze (W. Hörmann, \"The generation of binomial random variates\", Preprint Series\n * of the Department of Applied Statistics and Data Processing, WU Vienna, no. 1, April 1992, free,\n * doi:10.57938/79ec156b-9c3d-4b5d-a939-07a1846443fd), the binomial companion of the PTRS Poisson sampler `samplePoisson` uses.\n * Its acceptance test compares log(f(k)/f(m)) at the mode m with Stirling's series for log x! (tail terms 1/12, −1/360, 1/1260\n * in 1/(x + 1); exact sums below 10). The hat constants (b, a, c, v_r, α) are BTRS's as implemented in widely used libraries;\n * they could not be checked against the preprint's text here, so the chi-square tests of the probability mass function in\n * test/binomial-sample.test.ts are the evidence that the draws follow the binomial distribution.\n * - p > 1/2 draws n − Binomial(n, 1 − p).\n *\n * Both methods are exact (they draw from the binomial distribution itself, not an approximation of it) up to floating-point\n * rounding of the probabilities. The same n, p and stream reproduce the same draw. The tests check the probability mass\n * function by a chi-square test and the moments.\n */\n\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type { DeterministicUniformStream } from \"./stochastic-seed.js\";\n\nconst HALF_LOG_TWO_PI = 0.5 * Math.log(2 * Math.PI);\n/** log x! − ((x + ½)·log(x + 1) − (x + 1) + ½·log 2π) for x = 0 … 9, from the exact sums. */\nconst SMALL_TAILS = ((): Float64Array => {\n const table = new Float64Array(10);\n let logFactorial = 0;\n for (let x = 0; x < 10; x++) {\n if (x > 1) logFactorial += Math.log(x);\n table[x] = logFactorial - ((x + 0.5) * Math.log(x + 1) - (x + 1) + HALF_LOG_TWO_PI);\n }\n return table;\n})();\nfunction stirlingTail(x: number): number {\n if (x < 10) return SMALL_TAILS[x]!;\n const z = x + 1, z2 = z * z;\n return (1 / 12 - (1 / 360 - 1 / 1260 / z2) / z2) / z;\n}\n\n/** Draws Binomial(n, p): n a non-negative safe integer, 0 ≤ p ≤ 1. */\nexport function sampleBinomial(n: number, p: number, random: DeterministicUniformStream): number {\n if (!Number.isSafeInteger(n) || n < 0 || !(p >= 0 && p <= 1)) throw new InvalidScientificInputError(\"Binomial draws need a non-negative integer count and a probability in [0, 1].\");\n if (n === 0 || p === 0) return 0;\n if (p === 1) return n;\n if (p > 0.5) return n - sampleBinomial(n, 1 - p, random);\n const q = 1 - p;\n if (n * p < 10) {\n const s = p / q, a = (n + 1) * s, start = Math.exp(n * Math.log(q));\n for (;;) {\n let u = random.next(), f = start, x = 0;\n // Rounding can leave a remainder above the last probability: draw again (the remainder is of the order of 1e-16).\n while (u > f && x < n) { u -= f; x++; f *= a / x - s; }\n if (u <= f) return x;\n }\n }\n const spq = Math.sqrt(n * p * q), b = 1.15 + 2.53 * spq, a = -0.0873 + 0.0248 * b + 0.01 * p, c = n * p + 0.5, vr = 0.92 - 4.2 / b;\n const r = p / q, alpha = (2.83 + 5.1 / b) * spq, m = Math.floor((n + 1) * p);\n const modeTerm = (m + 0.5) * Math.log((m + 1) / (r * (n - m + 1))) + stirlingTail(m) + stirlingTail(n - m);\n for (;;) {\n const u = random.next() - 0.5, us = 0.5 - Math.abs(u);\n let v = random.next();\n if (us <= 0) continue;\n const k = Math.floor((2 * a / us + b) * u + c);\n if (k < 0 || k > n) continue;\n if (us >= 0.07 && v <= vr) return k;\n v = Math.log(v * alpha / (a / (us * us) + b));\n // log(f(k)/f(m)) by Stirling's series, in a form without the cancellation of large log-factorials.\n const bound = modeTerm + (n + 1) * Math.log((n - m + 1) / (n - k + 1)) + (k + 0.5) * Math.log(r * (n - k + 1) / (k + 1)) - stirlingTail(k) - stirlingTail(n - k);\n if (v <= bound) return k;\n }\n}\n"]}
|
|
@@ -17,13 +17,7 @@ import { parseSensorColorSamplingProfile } from "./color-sampling.js";
|
|
|
17
17
|
import { createSensorSpectralResponseChannelBinding, requireCurrentSensorSpectralResponseChannelBinding, parseSensorSpectralResponseProfile, resolveParsedSensorSpectralResponseAtWavelength } from "./spectral-response.js";
|
|
18
18
|
import { requireInputObject, requireInputKeys, requireInputNonNegativeFinite as requireNonNegativeFinite, requireInputPositiveFinite as requirePositiveFinite, requireInputNonNegativeSafeInteger as requireSafeNonNegativeInteger } from "../core/record-validation.js";
|
|
19
19
|
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
20
|
-
import { addCompensated } from "../core/shared-helpers.js";
|
|
21
|
-
const REDUCTION_TOLERANCE = 1e-10;
|
|
22
|
-
function approximatelyEqual(actual, expected) {
|
|
23
|
-
const scale = Math.max(1, Math.abs(actual), Math.abs(expected));
|
|
24
|
-
return (Math.abs(actual - expected) <=
|
|
25
|
-
REDUCTION_TOLERANCE * scale);
|
|
26
|
-
}
|
|
20
|
+
import { addCompensated, approximatelyEqual } from "../core/shared-helpers.js";
|
|
27
21
|
/**
|
|
28
22
|
* Identity string for parsed plain data that ignores key order, so records with
|
|
29
23
|
* the same content compare equal whatever order their properties were added in.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eqe-electron-rate.js","sourceRoot":"","sources":["../../src/sensor/eqe-electron-rate.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;GASG;AAEH,OAAO,EACL,mBAAmB,EAEpB,MAAM,+BAA+B,CAAC;AAIvC,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EACL,mCAAmC,EAIpC,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EACL,+BAA+B,EAEhC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,0CAA0C,EAC1C,kDAAkD,EAClD,kCAAkC,EAClC,+CAA+C,EAGhD,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,6BAA6B,IAAI,wBAAwB,EAAE,0BAA0B,IAAI,qBAAqB,EAAE,kCAAkC,IAAI,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AACzQ,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAwHlC,SAAS,kBAAkB,CACzB,MAAc,EACd,QAAgB;IAEhB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAChB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CACnB,CAAC;IACF,OAAO,CACL,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC;QAC3B,mBAAmB,GAAG,KAAK,CAC5B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CACpB,KAAc;IAEd,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAC5C,OAAO,sBAAsB,CAAC,KAAK,EAAE;QACnC,yBAAyB,EAAE,MAAM;QACjC,sBAAsB,EACpB,8DAA8D;QAChE,uBAAuB,EACrB,0DAA0D;KAC7D,CAAC,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CACxB,MAAe,EACf,QAAiB,EACjB,OAAe;IAEf,IACE,aAAa,CAAC,MAAM,CAAC;QACrB,aAAa,CAAC,QAAQ,CAAC,EACvB,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,OAAO,CACR,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B,CACjC,SACyC,EACzC,aACkD,EAClD,cACwC;IAExC,kBAAkB,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IACjD,IACE,SAAS,CAAC,aAAa;QACrB,oDAAoD;QACtD,SAAS;aACN,oCAAoC;YACrC,KAAK;QACP,SAAS,CAAC,wBAAwB;YAChC,KAAK;QACP,SAAS,CAAC,0BAA0B;YAClC,KAAK,EACP,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,iFAAiF,CAClF,CAAC;IACJ,CAAC;IAED,IACE,CAAC,aAAa;SACX,kCAAkC;QACrC,aAAa,CAAC,mBAAmB;YAC/B,SAAS;QACX,aAAa,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QACnC,CAAC,aAAa,CAAC,kBAAkB;QACjC,aAAa,CAAC,kBAAkB;YAC9B,0BAA0B,EAC5B,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,oFAAoF,CACrF,CAAC;IACJ,CAAC;IAED,IACE,aAAa,CAAC,yBAAyB;QACrC,SAAS,CAAC,iBAAiB;QAC7B,aAAa,CAAC,sBAAsB;YAClC,SAAS,CAAC,sBAAsB;QAClC,aAAa,CAAC,SAAS;YACrB,SAAS,CAAC,SAAS;QACrB,aAAa,CAAC,aAAa;YACzB,SAAS,CAAC,aAAa,EACzB,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IAED,IACE,CAAC,cAAc;SACZ,mCAAmC;QACtC,CAAC,cAAc;aACZ,oCAAoC;QACvC,CAAC,cAAc;aACZ,gCAAgC;QACnC,cAAc,CAAC,MAAM,KAAK,SAAS;QACnC,cAAc,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QACpC,cAAc,CAAC,kBAAkB;YAC/B,0BAA0B;QAC5B,cAAc;aACX,4BAA4B;YAC7B,KAAK;QACP,cAAc;aACX,yBAAyB,KAAK,KAAK;QACtC,cAAc;aACX,2BAA2B,KAAK,KAAK;QACxC,cAAc;aACX,6BAA6B,KAAK,KAAK,EAC1C,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,iFAAiF,CAClF,CAAC;IACJ,CAAC;IAED,IACE,CAAC,cAAc;SACZ,sCAAsC;QACzC,cAAc;aACX,kBAAkB,CAAC,IAAI;YACxB,kBAAkB,EACpB,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,sHAAsH,CACvH,CAAC;IACJ,CAAC;IAED,IACE,cAAc;SACX,qBAAqB,CAAC,IAAI;QAC3B,8CAA8C,EAChD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,iFAAiF,CAClF,CAAC;IACJ,CAAC;IAED,IACE,cAAc,CAAC,yBAAyB;QACtC,SAAS,CAAC,iBAAiB;QAC7B,cAAc,CAAC,sBAAsB;YACnC,SAAS,CAAC,sBAAsB;QAClC,cAAc,CAAC,SAAS;YACtB,SAAS,CAAC,SAAS;QACrB,cAAc,CAAC,4BAA4B;YACzC,aAAa,CAAC,oBAAoB,EACpC,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,8BAA8B,CACrC,SACyC;IAEzC,IACE,CAAC,KAAK,CAAC,OAAO,CACZ,SAAS,CAAC,aAAa,CACxB;QACD,CAAC,MAAM,CAAC,aAAa,CACnB,SAAS,CAAC,iBAAiB,CAC5B;QACD,SAAS,CAAC,iBAAiB,IAAI,CAAC;QAChC,SAAS,CAAC,aAAa,CAAC,MAAM;YAC5B,SAAS,CAAC,iBAAiB,EAC7B,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,yEAAyE,CAC1E,CAAC;IACJ,CAAC;IAED,IAAI,kBAAkB,GACpB,MAAM,CAAC,iBAAiB,CAAC;IAC3B,IAAI,mBAAmB,GAAG,CAAC,CAAC;IAC5B,MAAM,eAAe,GACF;QACf,GAAG,EAAE,CAAC;QACN,UAAU,EAAE,CAAC;KACd,CAAC;IAEJ,SAAS,CAAC,aAAa,CAAC,OAAO,CAC7B,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;QACpB,MAAM,WAAW,GACf,6BAA6B,CAC3B,KAAK,CAAC,mBAAmB,EACzB,0BAA0B;YACxB,UAAU;YACV,uBAAuB,CAC1B,CAAC;QACJ,IAAI,WAAW,KAAK,UAAU,EAAE,CAAC;YAC/B,MAAM,IAAI,2BAA2B,CACnC,mGAAmG,CACpG,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GACd,qBAAqB,CACnB,KAAK,CAAC,oBAAoB,EAC1B,0BAA0B;YACxB,UAAU;YACV,wBAAwB,CAC3B,CAAC;QACJ,IACE,UAAU;YACV,kBAAkB,EAClB,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,6EAA6E,CAC9E,CAAC;QACJ,CAAC;QACD,kBAAkB;YAChB,UAAU,CAAC;QAEb,MAAM,OAAO,GACX,qBAAqB,CACnB,KAAK,CAAC,2BAA2B,EACjC,0BAA0B;YACxB,UAAU;YACV,+BAA+B,CAClC,CAAC;QACJ,MAAM,gBAAgB,GACpB,qBAAqB,CACnB,KAAK,CAAC,0BAA0B,EAChC,0BAA0B;YACxB,UAAU;YACV,8BAA8B,CACjC,CAAC;QACJ,MAAM,WAAW,GACf,wBAAwB,CACtB,KAAK;aACF,sDAAsD,EACzD,0BAA0B;YACxB,UAAU;YACV,0DAA0D,CAC7D,CAAC;QACJ,MAAM,YAAY,GAChB,wBAAwB,CACtB,KAAK;aACF,kEAAkE,EACrE,0BAA0B;YACxB,UAAU;YACV,sEAAsE,CACzE,CAAC;QACJ,MAAM,oBAAoB,GACxB,WAAW,GAAG,OAAO,CAAC;QACxB,IACE,CAAC,MAAM,CAAC,QAAQ,CACd,oBAAoB,CACrB;YACD,CAAC,kBAAkB,CACjB,YAAY,EACZ,oBAAoB,CACrB,EACD,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,kGAAkG,CACnG,CAAC;QACJ,CAAC;QAED,mBAAmB;YACjB,gBAAgB,CAAC;QACnB,IACE,CAAC,MAAM,CAAC,QAAQ,CACd,mBAAmB,CACpB,EACD,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,6DAA6D,CAC9D,CAAC;QACJ,CAAC;QACD,cAAc,CACZ,eAAe,EACf,YAAY,CACb,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,IACE,CAAC,kBAAkB,CACjB,mBAAmB,EACnB,CAAC,CACF,EACD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,wDAAwD,CACzD,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GACd,wBAAwB,CACtB,SAAS;SACN,sDAAsD,EACzD,kEAAkE,CACnE,CAAC;IACJ,IACE,CAAC,kBAAkB,CACjB,eAAe,CAAC,GAAG,EACnB,UAAU,CACX,EACD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,sGAAsG,CACvG,CAAC;IACJ,CAAC;IAED,OAAO,SAAS,CAAC,aAAa,CAAC;AACjC,CAAC;AAED,SAAS,iCAAiC,CACxC,SACyC,EACzC,KACoD,EACpD,cACwC;IAExC,MAAM,SAAS,GACb,cAAc;SACX,2BAA2B,CAAC;IAEjC,IACE,SAAS,KAAK,SAAS;QACvB,SAAS,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EACjC,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,kGAAkG,CACnG,CAAC;IACJ,CAAC;IAED,IACE,cAAc;SACX,kCAAkC;SAClC,OAAO;QACR,SAAS,CAAC,yBAAyB;aAChC,OAAO;QACZ,cAAc;aACX,kCAAkC;aAClC,OAAO;YACR,SAAS,CAAC,yBAAyB;iBAChC,OAAO,EACZ,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,mEAAmE,CACpE,CAAC;IACJ,CAAC;IAED,SAAS,CAAC,OAAO,CACf,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACf,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAE,CAAC;QAC3B,IACE,KAAK,CAAC,mBAAmB;YACvB,IAAI,CAAC,mBAAmB;YAC1B,KAAK,CAAC,oBAAoB;gBACxB,IAAI,CAAC,oBAAoB;YAC3B,KAAK,CAAC,2BAA2B;gBAC/B,IAAI,CAAC,2BAA2B,EAClC,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,2EAA2E,CAC5E,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GACZ,cAAc,CAAC,UAAU,CAAC,IAAI;YAC5B,wDAAwD;YACxD,CAAC,CAAC,IAAI;iBACD,kEAAkE;YACvE,CAAC,CAAC,IAAI;iBACD,iEAAiE,CAAC;QAE3E,IACE,KAAK,CAAC,IAAI;YACR,cAAc,CAAC,UAAU,CAAC,IAAI;YAChC,KAAK,CAAC,IAAI;gBACR,cAAc,CAAC,UAAU,CAAC,IAAI;YAChC,CAAC,kBAAkB,CACjB,KAAK,CAAC,KAAK,EACX,QAAQ,CACT,EACD,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,qGAAqG,CACtG,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CACtB,SACyC,EACzC,KACoD,EACpD,OAEW;IAKX,IACE,SAAS,CAAC,eAAe;QACvB,QAAQ,EACV,CAAC;QACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,MAAM,IAAI,2BAA2B,CACnC,yEAAyE,CAC1E,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;IAED,IACE,SAAS,CAAC,eAAe,KAAK,KAAK,EACnC,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,kFAAkF,CACnF,CAAC;IACJ,CAAC;IAED,IACE,OAAO,KAAK,SAAS;QACrB,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QAC/B,OAAO,CAAC,OAAO,CAAC,MAAM;YACpB,KAAK,CAAC,MAAM,EACd,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,gGAAgG,CACjG,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GACX,IAAI,GAAG,EAGJ,CAAC;IAEN,OAAO,CAAC,OAAO,CAAC,OAAO,CACrB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;QACpB,IACE,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,KAAK,IAAI,EACd,CAAC;YACD,MAAM,IAAI,yBAAyB,CACjC,iCAAiC;gBAC/B,UAAU;gBACV,sBAAsB,CACzB,CAAC;QACJ,CAAC;QAED,MAAM,mBAAmB,GACvB,6BAA6B,CAC3B,KAAK,CAAC,mBAAmB,EACzB,iCAAiC;YAC/B,UAAU;YACV,uBAAuB,CAC1B,CAAC;QACJ,IACE,mBAAmB;YACjB,KAAK,CAAC,MAAM;YACd,OAAO,CAAC,GAAG,CACT,mBAAmB,CACpB,EACD,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,iFAAiF,CAClF,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,GAAG,CACT,mBAAmB,EACnB,KAAK,CAAC,eAAe,CACtB,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,uBAAuB,CAC9B,MAC+B,EAC/B,QAC+B;IAE/B,OAAO,CACL,aAAa,CAAC,MAAM,CAAC;QACrB,aAAa,CAAC,QAAQ,CAAC,CACxB,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAC9B,QAGY,EACZ,SACyC;IAEzC,IACE,QAAQ,CAAC,SAAS;QAChB,SAAS,CAAC,iBAAiB;QAC7B,QAAQ,CAAC,sBAAsB;YAC7B,SAAS,CAAC,sBAAsB;QAClC,QAAQ,CAAC,SAAS;YAChB,SAAS,CAAC,SAAS;QACrB,QAAQ,CAAC,kBAAkB;YACzB,SAAS,CAAC,kBAAkB;QAC9B,QAAQ,CAAC,aAAa;YACpB,SAAS,CAAC,aAAa;QACzB,QAAQ,CAAC,gBAAgB;YACvB,SAAS,CAAC,wBAAwB,EACpC,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,sGAAsG,CACvG,CAAC;IACJ,CAAC;IAED,iBAAiB,CACf,QAAQ,CAAC,WAAW,EACpB,SAAS,CAAC,mBAAmB,EAC7B,kEAAkE,CACnE,CAAC;IAEF,MAAM,gBAAgB,GACpB,SAAS,CAAC,iBAAiB,CAAC,QAAQ,CAAC;IACvC,IACE,CAAC,uBAAuB,CACtB,QAAQ,CAAC,iBAAiB,CAAC,OAAO,EAClC,gBAAgB,CAAC,OAAO,CACzB;QACD,CAAC,uBAAuB,CACtB,QAAQ,CAAC,iBAAiB,CAAC,OAAO,EAClC,gBAAgB,CAAC,OAAO,CACzB;QACD,aAAa,CACX,QAAQ,CAAC,iBAAiB,CAAC,MAAM,CAClC;YACC,aAAa,CACX,gBAAgB,CAAC,MAAM,CACxB,EACH,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,mGAAmG,CACpG,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,gCAAgC,CACvC,eAC+B,EAC/B,SACyC;IAEzC,MAAM,QAAQ,GACZ,eAAe,CAAC,QAAQ,CAAC,IAAI,CAC3B,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,SAAS;QACf,SAAS,CAAC,SAAS,CACtB,CAAC;IACJ,IAAI,SAAS,CAAC,sBAAsB,KAAK,SAAS,EAAE,CAAC;QACnD,kDAAkD,CAChD,SAAS,CAAC,sBAAsB,CACjC,CAAC;IACJ,CAAC;IACD,IACE,QAAQ,KAAK,SAAS;QACtB,SAAS,CAAC,sBAAsB;YAC9B,SAAS;QACX,aAAa,CACX,0CAA0C,CACxC,QAAQ,CACT,CACF;YACC,aAAa,CACX,SAAS,CAAC,sBAAsB,CACjC,EACH,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,mHAAmH,CACpH,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CACzB,QACqC;IAQrC,IACE,QAAQ,CAAC,IAAI;QACb,uCAAuC,EACvC,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,wEAAwE,CACzE,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,8BAA8B,CAC5C,KACqC;IAErC,gBAAgB,CAAC,KAAK,EAAE,gCAAgC,EAAE,CAAC,WAAW,EAAE,eAAe,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,wBAAwB,CAAC,CAAC,CAAC;IACzL,0BAA0B,CACxB,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,aAAa,EACnB,KAAK,CAAC,cAAc,CACrB,CAAC;IAEF,IACE,KAAK,CAAC,SAAS;SACZ,kBAAkB;QACnB,iCAAiC,EACnC,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,qGAAqG,CACtG,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAChB,+BAA+B,CAC7B,KAAK,CAAC,oBAAoB,CAC3B,CAAC;IACJ,MAAM,eAAe,GACnB,kCAAkC,CAChC,KAAK,CAAC,uBAAuB,CAC9B,CAAC;IAEJ,IACE,YAAY,CAAC,SAAS;QACpB,KAAK,CAAC,SAAS;aACZ,sBAAsB;QAC3B,eAAe,CAAC,SAAS;YACvB,KAAK,CAAC,SAAS;iBACZ,iBAAiB;QACtB,eAAe;aACZ,sBAAsB;YACvB,YAAY,CAAC,SAAS,EACxB,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,wFAAwF,CACzF,CAAC;IACJ,CAAC;IAED,gCAAgC,CAC9B,eAAe,EACf,KAAK,CAAC,SAAS,CAChB,CAAC;IAEF,MAAM,KAAK,GACT,8BAA8B,CAC5B,KAAK,CAAC,SAAS,CAChB,CAAC;IACJ,iCAAiC,CAC/B,KAAK,CAAC,SAAS,EACf,KAAK,EACL,KAAK,CAAC,cAAc,CACrB,CAAC;IAEF,MAAM,UAAU,GACd,eAAe,CACb,KAAK,CAAC,SAAS,EACf,KAAK,EACL,KAAK,CAAC,sBAAsB,CAC7B,CAAC;IAEJ,MAAM,aAAa,GACA;QACf,GAAG,EAAE,CAAC;QACN,UAAU,EAAE,CAAC;KACd,CAAC;IACJ,MAAM,eAAe,GACF;QACf,GAAG,EAAE,CAAC;QACN,UAAU,EAAE,CAAC;KACd,CAAC;IACJ,MAAM,aAAa,GAEf,EAAE,CAAC;IAEP,IAAI,wBAAwB,GAC1B,KAAK,CAAC;IACR,IAAI,gCAAgC,GAClC,KAAK,CAAC;IAER,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GACZ,+CAA+C,CAAC;YAC9C,oBAAoB,EAClB,YAAY;YACd,uBAAuB,EACrB,eAAe;YACjB,SAAS,EACP,KAAK,CAAC,SAAS,CAAC,SAAS;YAC3B,oBAAoB,EAClB,IAAI,CAAC,oBAAoB;YAC3B,eAAe,EACb,KAAK,CAAC,SAAS;iBACZ,eAAe;SACrB,CAAC,CAAC;QAEL,IAAI,CAAC,wBAAwB,EAAE,CAAC;YAC9B,uBAAuB,CACrB,QAAQ,CAAC,KAAK,EACd,KAAK,CAAC,SAAS,CAChB,CAAC;YACF,wBAAwB;gBACtB,IAAI,CAAC;QACT,CAAC;QAED,MAAM,GAAG,GACP,kBAAkB,CAChB,QAAQ,CAAC,KAAK,CAAC,QAAQ,CACxB,CAAC;QAEJ,MAAM,QAAQ,GACZ,UAAU,CAAC,GAAG,CACZ,IAAI,CAAC,mBAAmB,CACzB,CAAC;QACJ,MAAM,YAAY,GAChB,mCAAmC,CAAC;YAClC,oBAAoB,EAClB,IAAI,CAAC,oBAAoB;YAC3B,eAAe,EACb,KAAK,CAAC,SAAS;iBACZ,eAAe;YACpB,GAAG,CAAC,KAAK,CAAC,SAAS;iBAChB,eAAe,KAAK,KAAK;gBAC1B,CAAC,CAAC;oBACE,uBAAuB,EACrB,QAAS;oBACX,GAAG,CAAC,KAAK,CAAC,sBAAuB;yBAC9B,mBAAmB;wBACtB,SAAS;wBACP,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC;4BACE,sBAAsB,EACpB,KAAK;iCACF,sBAAuB;iCACvB,mBAAmB;yBACzB,CAAC;oBACN,kBAAkB,EAChB,KAAK,CAAC,sBAAuB;yBAC1B,eAAe;iBACrB;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC,KAAK,CAAC;QAEX,MAAM,6BAA6B,GACjC,IAAI;aACD,sDAAsD;YACzD,IAAI,CAAC,2BAA2B,CAAC;QACnC,MAAM,2BAA2B,GAC/B,6BAA6B;YAC7B,YAAY,CAAC,kBAAkB,CAAC;QAClC,MAAM,sCAAsC,GAC1C,2BAA2B;YAC3B,GAAG,CAAC,yBAAyB,CAAC;QAEhC,IACE,CAAC,MAAM,CAAC,QAAQ,CACd,6BAA6B,CAC9B;YACD,CAAC,MAAM,CAAC,QAAQ,CACd,2BAA2B,CAC5B;YACD,CAAC,MAAM,CAAC,QAAQ,CACd,sCAAsC,CACvC,EACD,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,0DAA0D,CAC3D,CAAC;QACJ,CAAC;QAED,cAAc,CACZ,aAAa,EACb,2BAA2B,CAC5B,CAAC;QACF,cAAc,CACZ,eAAe,EACf,sCAAsC,CACvC,CAAC;QAEF,IACE,GAAG,CAAC,WAAW;YACf,iDAAiD,EACjD,CAAC;YACD,gCAAgC;gBAC9B,IAAI,CAAC;QACT,CAAC;QAED,aAAa,CAAC,IAAI,CAAC;YACjB,mBAAmB,EACjB,IAAI,CAAC,mBAAmB;YAC1B,oBAAoB,EAClB,IAAI,CAAC,oBAAoB;YAC3B,2BAA2B,EACzB,IAAI,CAAC,2BAA2B;YAClC,sDAAsD,EACpD,IAAI;iBACD,sDAAsD;YAC3D,6BAA6B;YAC7B,0BAA0B,EACxB,YAAY;iBACT,0BAA0B;YAC/B,kBAAkB,EAChB,YAAY;iBACT,kBAAkB;YACvB,oBAAoB,EAClB,YAAY;iBACT,oBAAoB;YACzB,GAAG,CAAC,YAAY;iBACb,wBAAwB;gBAC3B,SAAS;gBACP,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,wBAAwB,EACtB,YAAY;yBACT,wBAAwB;iBAC9B,CAAC;YACN,GAAG,CAAC,YAAY;iBACb,yBAAyB;gBAC5B,SAAS;gBACP,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,yBAAyB,EACvB,YAAY;yBACT,yBAAyB;iBAC/B,CAAC;YACN,GAAG,CAAC,YAAY;iBACb,0BAA0B;gBAC7B,SAAS;gBACP,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,0BAA0B,EACxB,YAAY;yBACT,0BAA0B;iBAChC,CAAC;YACN,2BAA2B;YAC3B,kCAAkC,EAChC,GAAG,CAAC,yBAAyB;YAC/B,mBAAmB,EACjB,GAAG,CAAC,WAAW;YACjB,GAAG,CAAC,GAAG,CAAC,0BAA0B;gBAClC,SAAS;gBACP,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,0BAA0B,EACxB,GAAG,CAAC,0BAA0B;iBACjC,CAAC;YACN,GAAG,CAAC,GAAG;iBACJ,iCAAiC;gBACpC,SAAS;gBACP,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,iCAAiC,EAC/B,GAAG;yBACA,iCAAiC;iBACvC,CAAC;YACN,yBAAyB,EACvB,QAAQ,CAAC,KAAK;iBACX,iBAAiB;YACtB,sCAAsC;SACvC,CAAC,CAAC;IACL,CAAC;IAED,IACE,CAAC,MAAM,CAAC,QAAQ,CACd,aAAa,CAAC,GAAG,CAClB;QACD,CAAC,MAAM,CAAC,QAAQ,CACd,eAAe,CAAC,GAAG,CACpB,EACD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,qDAAqD,CACtD,CAAC;IACJ,CAAC;IAED,OAAO,mBAAmB,CACxB;QACE,sBAAsB,EACpB,KAAK,CAAC,SAAS;aACZ,sBAAsB;QAC3B,iBAAiB,EACf,KAAK,CAAC,SAAS;aACZ,iBAAiB;QACtB,4BAA4B,EAC1B,KAAK,CAAC,aAAa;aAChB,oBAAoB;QACzB,uBAAuB,EACrB,KAAK,CAAC,cAAc;aACjB,uBAAuB;QAC5B,IAAI,EAAE;YACJ,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI;SACxB;QACD,SAAS,EACP,KAAK,CAAC,SAAS,CAAC,SAAS;QAC3B,kBAAkB,EAChB,KAAK,CAAC,SAAS;aACZ,kBAAkB;QACvB,aAAa,EACX,KAAK,CAAC,SAAS,CAAC,aAAa;QAC/B,sBAAsB,EACpB,KAAK,CAAC,aAAa;aAChB,mBAAmB;QACxB,eAAe,EACb,KAAK,CAAC,SAAS;aACZ,eAAe;QACpB,yBAAyB,EAAE;YACzB,GAAG,KAAK,CAAC,SAAS;iBACf,yBAAyB;SAC7B;QACD,iBAAiB,EACf,KAAK,CAAC,MAAM;QACd,aAAa;QACb,2BAA2B,EACzB,aAAa,CAAC,GAAG;QACnB,sCAAsC,EACpC,eAAe,CAAC,GAAG;QACrB,eAAe,EACb,mBAAmB;QACrB,4BAA4B,EAC1B,IAAI;QACN,wBAAwB,EACtB,IAAI;QACN,2BAA2B,EACzB,KAAK;QACP,gCAAgC;QAChC,oBAAoB,EAAE,IAAI;QAC1B,sBAAsB,EAAE,IAAI;QAC5B,qBAAqB,EAAE,KAAK;QAC5B,uBAAuB,EAAE,KAAK;QAC9B,iBAAiB,EAAE,KAAK;QACxB,0BAA0B,EACxB,KAAK;QACP,uBAAuB,EAAE,KAAK;QAC9B,kBAAkB,EAAE,KAAK;QACzB,gBAAgB,EAAE,KAAK;QACvB,gBAAgB,EAAE,KAAK;QACvB,sBAAsB,EACpB,KAAK;QACP,oBAAoB,EAAE,KAAK;QAC3B,6BAA6B,EAC3B,KAAK;QACP,iCAAiC,EAC/B,KAAK;QACP,mCAAmC,EACjC,KAAK;KACR,EACD,0BAA0B,EAC1B,OAAO,EACP;QACE,wIAAwI;QACxI,wJAAwJ;QACxJ,4LAA4L;QAC5L,gKAAgK;QAChK,oIAAoI;QACpI,2KAA2K;QAC3K,qMAAqM;QACrM,kKAAkK;QAClK,yIAAyI;QACzI,2LAA2L;QAC3L,iJAAiJ;KAClJ,CACF,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * EQE electron rate: converts the light power reaching a sensor site into a photon rate and an\n * expected electron rate, using the sensor's EQE (external quantum efficiency, the fraction of\n * incident photons that produce a collected electron).\n *\n * The response is applied at each wavelength node. One broadband-average QE is never applied to the\n * total power. Outputs are rates per second; integrating over the exposure time happens later.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport type {\n EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { stringifyCanonicalJson } from \"../core/canonical-json.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport {\n calculatePhotonEnergyFromWavelength,\n type AirRefractiveIndexConditionPolicy,\n type AirRefractiveIndexReferenceConditions,\n type SourcedAirPhaseRefractiveIndex\n} from \"./photon-energy.js\";\nimport type {\n SensorResponseApplicationCompatibilityAssessment\n} from \"./response-application-compatibility.js\";\nimport type {\n SensorResponseOperatingRangeAssessment\n} from \"./response-operating-range.js\";\nimport {\n parseSensorColorSamplingProfile,\n type SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport {\n createSensorSpectralResponseChannelBinding,\n requireCurrentSensorSpectralResponseChannelBinding,\n parseSensorSpectralResponseProfile,\n resolveParsedSensorSpectralResponseAtWavelength,\n type ResolvedSensorSpectralResponseValue,\n type SensorSpectralResponseProfile\n} from \"./spectral-response.js\";\nimport type {\n SensorSpatioSpectralIrradianceReduction,\n SensorSpatioSpectralWavelengthReduction\n} from \"./spatio-spectral-reduction.js\";\nimport { requireInputObject, requireInputKeys, requireInputNonNegativeFinite as requireNonNegativeFinite, requireInputPositiveFinite as requirePositiveFinite, requireInputNonNegativeSafeInteger as requireSafeNonNegativeInteger } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { addCompensated } from \"../core/shared-helpers.js\";\n\nconst REDUCTION_TOLERANCE = 1e-10;\n\nexport interface SensorEqeAirRefractiveIndexSample {\n spectralSampleIndex: number;\n refractiveIndex:\n SourcedAirPhaseRefractiveIndex;\n}\n\nexport interface SensorEqeAirPhotonEnergyContext {\n samples:\n readonly SensorEqeAirRefractiveIndexSample[];\n operatingConditions?:\n AirRefractiveIndexReferenceConditions;\n conditionPolicy:\n AirRefractiveIndexConditionPolicy;\n}\n\nexport interface CalculateSensorEqeElectronRateInput {\n reduction:\n SensorSpatioSpectralIrradianceReduction;\n compatibility:\n SensorResponseApplicationCompatibilityAssessment;\n operatingRange:\n SensorResponseOperatingRangeAssessment;\n colorSamplingProfile:\n SensorColorSamplingProfile;\n spectralResponseProfile:\n SensorSpectralResponseProfile;\n airPhotonEnergyContext?:\n SensorEqeAirPhotonEnergyContext;\n}\n\nexport interface SensorEqeWavelengthRateContribution {\n spectralSampleIndex: number;\n wavelengthNanometers: number;\n wavelengthMeasureNanometers: number;\n geometricApertureIncidentSpectralFluxWattsPerNanometer:\n number;\n radiantPowerContributionWatts: number;\n vacuumWavelengthNanometers: number;\n photonEnergyJoules: number;\n wavelengthConversion:\n | \"vacuum-identity\"\n | \"air-to-vacuum-via-phase-refractive-index\";\n phaseRefractiveIndexUsed?: number;\n airConditionCompatibility?:\n | \"exact-match\"\n | \"assumed-compatible\";\n airRefractiveIndexEvidence?:\n readonly EvidenceProvenance[];\n incidentPhotonRatePerSecond: number;\n effectiveExternalQuantumEfficiency: number;\n responseComposition:\n | \"direct-effective-response\"\n | \"channel-filter-transmittance-times-detector-eqe\";\n channelFilterTransmittance?: number;\n detectorExternalQuantumEfficiency?: number;\n responseInterpolationUsed: boolean;\n expectedGeneratedElectronRatePerSecond:\n number;\n}\n\nexport interface SensorEqeElectronRate {\n colorSamplingProfileId: string;\n responseProfileId: string;\n responseApplicationProfileId: string;\n operatingRangeProfileId: string;\n /** The exact source colour site. Results produced by the engine fill this in. */\n site?: {\n x: number;\n y: number;\n };\n channelId: string;\n sourceResponseKind:\n | \"effective-external-quantum-efficiency\"\n | \"separable-channel-filter-and-detector-eqe\";\n responseScope:\n SensorSpatioSpectralIrradianceReduction[\"responseScope\"];\n responseReferencePlane:\n SensorResponseApplicationCompatibilityAssessment[\"requiredSourcePlane\"];\n wavelengthBasis:\n SensorSpatioSpectralIrradianceReduction[\"wavelengthBasis\"];\n wavelengthRangeNanometers:\n SensorSpatioSpectralIrradianceReduction[\"wavelengthRangeNanometers\"];\n spectralNodeCount: number;\n perWavelength:\n readonly SensorEqeWavelengthRateContribution[];\n incidentPhotonRatePerSecond: number;\n expectedGeneratedElectronRatePerSecond:\n number;\n summationMethod:\n \"kahan-compensated\";\n responseApplicationPerformed: true;\n quantumEfficiencyApplied: true;\n spectralResponsivityApplied: false;\n channelFilterTransmissionApplied:\n boolean;\n photonRateCalculated: true;\n electronRateCalculated: true;\n photonCountCalculated: false;\n electronCountCalculated: false;\n currentCalculated: false;\n temporalIntegrationApplied: false;\n exposureDurationApplied: false;\n saturationAssessed: false;\n shotNoiseApplied: false;\n readNoiseApplied: false;\n adcQuantizationApplied: false;\n rawCodeValueProduced: false;\n responseUncertaintyPropagated: false;\n photonEnergyUncertaintyPropagated: false;\n quadratureConvergenceErrorEstimated:\n false;\n}\n\ninterface CompensatedSum {\n sum: number;\n correction: number;\n}\n\nfunction approximatelyEqual(\n actual: number,\n expected: number\n): boolean {\n const scale = Math.max(\n 1,\n Math.abs(actual),\n Math.abs(expected)\n );\n return (\n Math.abs(actual - expected) <=\n REDUCTION_TOLERANCE * scale\n );\n}\n\n/**\n * Identity string for parsed plain data that ignores key order, so records with\n * the same content compare equal whatever order their properties were added in.\n */\nfunction canonicalJson(\n value: unknown\n): string {\n if (value === undefined) return \"undefined\";\n return stringifyCanonicalJson(value, {\n undefinedObjectProperties: \"omit\",\n nonFiniteNumberMessage:\n \"EQE electron-rate identity data must contain finite numbers.\",\n unsupportedValueMessage:\n \"EQE electron-rate identity data must be plain JSON data.\"\n });\n}\n\nfunction requireEquivalent(\n actual: unknown,\n expected: unknown,\n message: string\n): void {\n if (\n canonicalJson(actual) !==\n canonicalJson(expected)\n ) {\n throw new InvalidScientificInputError(\n message\n );\n }\n}\n\nfunction validateAuthorizationChain(\n reduction:\n SensorSpatioSpectralIrradianceReduction,\n compatibility:\n SensorResponseApplicationCompatibilityAssessment,\n operatingRange:\n SensorResponseOperatingRangeAssessment\n): void {\n requireInputObject(reduction, \"reduction input\");\n if (\n reduction.outputMeaning !==\n \"pre-response-spatio-spectral-radiometric-reduction\" ||\n reduction\n .spectralResponseApplicationPerformed !==\n false ||\n reduction.quantumEfficiencyApplied !==\n false ||\n reduction.temporalIntegrationApplied !==\n false\n ) {\n throw new InvalidScientificInputError(\n \"reduction must remain pre-response and pre-temporal before EQE rate conversion.\"\n );\n }\n\n if (\n !compatibility\n .structuralCompatibilityEstablished ||\n compatibility.compatibilityStatus ===\n \"blocked\" ||\n compatibility.blockers.length !== 0 ||\n !compatibility.sourcePlaneMatched ||\n compatibility.requiredSignalPath !==\n \"photon-rate-to-electrons\"\n ) {\n throw new InvalidScientificInputError(\n \"compatibility must establish a non-blocked photon-rate-to-electrons response path.\"\n );\n }\n\n if (\n compatibility.spectralResponseProfileId !==\n reduction.responseProfileId ||\n compatibility.colorSamplingProfileId !==\n reduction.colorSamplingProfileId ||\n compatibility.channelId !==\n reduction.channelId ||\n compatibility.responseScope !==\n reduction.responseScope\n ) {\n throw new InvalidScientificInputError(\n \"compatibility identities must exactly match the reduction.\"\n );\n }\n\n if (\n !operatingRange\n .operatingRangeCompatibilityAssessed ||\n !operatingRange\n .instantaneousResponseRangeCompatible ||\n !operatingRange\n .responseRateConversionAuthorized ||\n operatingRange.status === \"blocked\" ||\n operatingRange.blockers.length !== 0 ||\n operatingRange.requiredSignalPath !==\n \"photon-rate-to-electrons\" ||\n operatingRange\n .responseApplicationPerformed !==\n false ||\n operatingRange\n .photonConversionPerformed !== false ||\n operatingRange\n .electronConversionPerformed !== false ||\n operatingRange\n .temporalIntegrationAuthorized !== false\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange must authorize a clean photon-rate-to-electrons conversion path.\"\n );\n }\n\n if (\n !operatingRange\n .spectralNodeRangeCompatibilityAssessed ||\n operatingRange\n .spectralInputModel.kind !==\n \"per-spectral-bin\"\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange must establish per-spectral-bin input-range applicability before wavelength-dependent EQE conversion.\"\n );\n }\n\n if (\n operatingRange\n .spatialLinearityModel.kind !==\n \"linear-superposition-over-geometric-aperture\"\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange must establish linear superposition over the geometric aperture.\"\n );\n }\n\n if (\n operatingRange.spectralResponseProfileId !==\n reduction.responseProfileId ||\n operatingRange.colorSamplingProfileId !==\n reduction.colorSamplingProfileId ||\n operatingRange.channelId !==\n reduction.channelId ||\n operatingRange.responseApplicationProfileId !==\n compatibility.applicationProfileId\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange identities must exactly match the reduction and compatibility assessment.\"\n );\n }\n}\n\nfunction validateReductionSpectralNodes(\n reduction:\n SensorSpatioSpectralIrradianceReduction\n): readonly SensorSpatioSpectralWavelengthReduction[] {\n if (\n !Array.isArray(\n reduction.perWavelength\n ) ||\n !Number.isSafeInteger(\n reduction.spectralNodeCount\n ) ||\n reduction.spectralNodeCount <= 0 ||\n reduction.perWavelength.length !==\n reduction.spectralNodeCount\n ) {\n throw new InvalidScientificInputError(\n \"reduction.perWavelength must contain exactly spectralNodeCount entries.\"\n );\n }\n\n let previousWavelength =\n Number.NEGATIVE_INFINITY;\n let normalizedWeightSum = 0;\n const integratedPower:\n CompensatedSum = {\n sum: 0,\n correction: 0\n };\n\n reduction.perWavelength.forEach(\n (entry, arrayIndex) => {\n const sampleIndex =\n requireSafeNonNegativeInteger(\n entry.spectralSampleIndex,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].spectralSampleIndex\"\n );\n if (sampleIndex !== arrayIndex) {\n throw new InvalidScientificInputError(\n \"reduction spectralSampleIndex values must form the exact contiguous range 0..spectralNodeCount-1.\"\n );\n }\n\n const wavelength =\n requirePositiveFinite(\n entry.wavelengthNanometers,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].wavelengthNanometers\"\n );\n if (\n wavelength <=\n previousWavelength\n ) {\n throw new InvalidScientificInputError(\n \"reduction per-wavelength samples must be strictly increasing in wavelength.\"\n );\n }\n previousWavelength =\n wavelength;\n\n const measure =\n requirePositiveFinite(\n entry.wavelengthMeasureNanometers,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].wavelengthMeasureNanometers\"\n );\n const normalizedWeight =\n requirePositiveFinite(\n entry.normalizedWavelengthWeight,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].normalizedWavelengthWeight\"\n );\n const fluxDensity =\n requireNonNegativeFinite(\n entry\n .geometricApertureIncidentSpectralFluxWattsPerNanometer,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].geometricApertureIncidentSpectralFluxWattsPerNanometer\"\n );\n const contribution =\n requireNonNegativeFinite(\n entry\n .wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts\"\n );\n const expectedContribution =\n fluxDensity * measure;\n if (\n !Number.isFinite(\n expectedContribution\n ) ||\n !approximatelyEqual(\n contribution,\n expectedContribution\n )\n ) {\n throw new InvalidScientificInputError(\n \"reduction per-wavelength radiant-power contribution must equal spectral flux density × d-lambda.\"\n );\n }\n\n normalizedWeightSum +=\n normalizedWeight;\n if (\n !Number.isFinite(\n normalizedWeightSum\n )\n ) {\n throw new InvalidScientificInputError(\n \"reduction normalized wavelength weights must remain finite.\"\n );\n }\n addCompensated(\n integratedPower,\n contribution\n );\n }\n );\n\n if (\n !approximatelyEqual(\n normalizedWeightSum,\n 1\n )\n ) {\n throw new InvalidScientificInputError(\n \"reduction normalized wavelength weights must sum to 1.\"\n );\n }\n\n const totalPower =\n requireNonNegativeFinite(\n reduction\n .wavelengthIntegratedGeometricApertureIncidentFluxWatts,\n \"reduction.wavelengthIntegratedGeometricApertureIncidentFluxWatts\"\n );\n if (\n !approximatelyEqual(\n integratedPower.sum,\n totalPower\n )\n ) {\n throw new InvalidScientificInputError(\n \"reduction total geometric-aperture radiant power must match the sum of per-wavelength contributions.\"\n );\n }\n\n return reduction.perWavelength;\n}\n\nfunction validateOperatingRangeNodeBinding(\n reduction:\n SensorSpatioSpectralIrradianceReduction,\n nodes:\n readonly SensorSpatioSpectralWavelengthReduction[],\n operatingRange:\n SensorResponseOperatingRangeAssessment\n): void {\n const evaluated =\n operatingRange\n .evaluatedSpectralNodeInputs;\n\n if (\n evaluated === undefined ||\n evaluated.length !== nodes.length\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange must carry one evaluated spectral-node input for every reduction wavelength node.\"\n );\n }\n\n if (\n operatingRange\n .evaluatedWavelengthRangeNanometers\n .minimum !==\n reduction.wavelengthRangeNanometers\n .minimum ||\n operatingRange\n .evaluatedWavelengthRangeNanometers\n .maximum !==\n reduction.wavelengthRangeNanometers\n .maximum\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange wavelength range must exactly match the reduction.\"\n );\n }\n\n evaluated.forEach(\n (entry, index) => {\n const node = nodes[index]!;\n if (\n entry.spectralSampleIndex !==\n node.spectralSampleIndex ||\n entry.wavelengthNanometers !==\n node.wavelengthNanometers ||\n entry.wavelengthMeasureNanometers !==\n node.wavelengthMeasureNanometers\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange spectral-node identities must exactly match the reduction.\"\n );\n }\n\n const expected =\n operatingRange.inputRange.kind ===\n \"wavelength-integrated-geometric-aperture-radiant-power\"\n ? node\n .wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts\n : node\n .wavelengthIntegratedSpatialAverageContributionWattsPerSquareMeter;\n\n if (\n entry.kind !==\n operatingRange.inputRange.kind ||\n entry.unit !==\n operatingRange.inputRange.unit ||\n !approximatelyEqual(\n entry.value,\n expected\n )\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange spectral-node values must match the reduction values in the calibrated input domain.\"\n );\n }\n }\n );\n}\n\nfunction parseAirContext(\n reduction:\n SensorSpatioSpectralIrradianceReduction,\n nodes:\n readonly SensorSpatioSpectralWavelengthReduction[],\n context:\n SensorEqeAirPhotonEnergyContext |\n undefined\n): ReadonlyMap<\n number,\n SourcedAirPhaseRefractiveIndex\n> {\n if (\n reduction.wavelengthBasis ===\n \"vacuum\"\n ) {\n if (context !== undefined) {\n throw new InvalidScientificInputError(\n \"airPhotonEnergyContext must be omitted for vacuum-basis EQE conversion.\"\n );\n }\n return new Map();\n }\n\n if (\n reduction.wavelengthBasis !== \"air\"\n ) {\n throw new InvalidScientificInputError(\n \"EQE electron-rate conversion requires a resolved air or vacuum wavelength basis.\"\n );\n }\n\n if (\n context === undefined ||\n !Array.isArray(context.samples) ||\n context.samples.length !==\n nodes.length\n ) {\n throw new InvalidScientificInputError(\n \"Air-basis EQE conversion requires exactly one refractive-index sample for every spectral node.\"\n );\n }\n\n const byIndex =\n new Map<\n number,\n SourcedAirPhaseRefractiveIndex\n >();\n\n context.samples.forEach(\n (entry, arrayIndex) => {\n if (\n typeof entry !== \"object\" ||\n entry === null\n ) {\n throw new InvalidConfigurationError(\n \"airPhotonEnergyContext.samples[\" +\n arrayIndex +\n \"] must be an object.\"\n );\n }\n\n const spectralSampleIndex =\n requireSafeNonNegativeInteger(\n entry.spectralSampleIndex,\n \"airPhotonEnergyContext.samples[\" +\n arrayIndex +\n \"].spectralSampleIndex\"\n );\n if (\n spectralSampleIndex >=\n nodes.length ||\n byIndex.has(\n spectralSampleIndex\n )\n ) {\n throw new InvalidScientificInputError(\n \"airPhotonEnergyContext must contain unique in-range spectral sample identities.\"\n );\n }\n byIndex.set(\n spectralSampleIndex,\n entry.refractiveIndex\n );\n }\n );\n\n return byIndex;\n}\n\nfunction responseEvidenceMatches(\n actual:\n readonly EvidenceProvenance[],\n expected:\n readonly EvidenceProvenance[]\n): boolean {\n return (\n canonicalJson(actual) ===\n canonicalJson(expected)\n );\n}\n\nfunction validateResponseBinding(\n resolved:\n ReturnType<\n typeof resolveParsedSensorSpectralResponseAtWavelength\n >[\"value\"],\n reduction:\n SensorSpatioSpectralIrradianceReduction\n): void {\n if (\n resolved.profileId !==\n reduction.responseProfileId ||\n resolved.colorSamplingProfileId !==\n reduction.colorSamplingProfileId ||\n resolved.channelId !==\n reduction.channelId ||\n resolved.sourceResponseKind !==\n reduction.sourceResponseKind ||\n resolved.responseScope !==\n reduction.responseScope ||\n resolved.scientificStatus !==\n reduction.responseScientificStatus\n ) {\n throw new InvalidScientificInputError(\n \"Resolved spectral response metadata must exactly match the response used to construct the reduction.\"\n );\n }\n\n requireEquivalent(\n resolved.uncertainty,\n reduction.responseUncertainty,\n \"Resolved spectral response uncertainty must match the reduction.\"\n );\n\n const expectedEvidence =\n reduction.componentEvidence.spectral;\n if (\n !responseEvidenceMatches(\n resolved.componentEvidence.profile,\n expectedEvidence.profile\n ) ||\n !responseEvidenceMatches(\n resolved.componentEvidence.channel,\n expectedEvidence.channel\n ) ||\n canonicalJson(\n resolved.componentEvidence.curves\n ) !==\n canonicalJson(\n expectedEvidence.curves\n )\n ) {\n throw new InvalidScientificInputError(\n \"Resolved spectral response evidence must match the calibration evidence carried by the reduction.\"\n );\n }\n}\n\nfunction validateExactResponseDataBinding(\n responseProfile:\n SensorSpectralResponseProfile,\n reduction:\n SensorSpatioSpectralIrradianceReduction\n): void {\n const response =\n responseProfile.channels.find(\n (entry) =>\n entry.channelId ===\n reduction.channelId\n );\n if (reduction.responseChannelBinding !== undefined) {\n requireCurrentSensorSpectralResponseChannelBinding(\n reduction.responseChannelBinding\n );\n }\n if (\n response === undefined ||\n reduction.responseChannelBinding ===\n undefined ||\n canonicalJson(\n createSensorSpectralResponseChannelBinding(\n response\n )\n ) !==\n canonicalJson(\n reduction.responseChannelBinding\n )\n ) {\n throw new InvalidScientificInputError(\n \"Supplied spectral response channel data must exactly match the response-channel binding carried by the reduction.\"\n );\n }\n}\n\nfunction requireEqeResponse(\n response:\n ResolvedSensorSpectralResponseValue\n): Extract<\n ResolvedSensorSpectralResponseValue,\n {\n kind:\n \"effective-external-quantum-efficiency\";\n }\n> {\n if (\n response.kind !==\n \"effective-external-quantum-efficiency\"\n ) {\n throw new InvalidScientificInputError(\n \"EQE electron-rate conversion cannot consume A/W spectral responsivity.\"\n );\n }\n return response;\n}\n\n/**\n * Converts the spectral radiant power collected by the site's geometric aperture\n * (before the sensor response) into an incident photon rate and an expected\n * generated electron rate.\n *\n * The response is applied at each wavelength node. One broadband-average QE is\n * never applied to the total power.\n *\n * Integration over exposure time happens later, so all outputs are rates per\n * second, not photon or electron counts.\n */\nexport function calculateSensorEqeElectronRate(\n input:\n CalculateSensorEqeElectronRateInput\n): CalculationResult<SensorEqeElectronRate> {\n requireInputKeys(input, \"calculateSensorEqeElectronRate\", [\"reduction\", \"compatibility\", \"operatingRange\", \"colorSamplingProfile\", \"spectralResponseProfile\", \"airPhotonEnergyContext\"]);\n validateAuthorizationChain(\n input.reduction,\n input.compatibility,\n input.operatingRange\n );\n\n if (\n input.reduction\n .sourceResponseKind ===\n \"effective-spectral-responsivity\"\n ) {\n throw new InvalidScientificInputError(\n \"EQE electron-rate conversion requires an EQE or filter×detector-EQE response, not A/W responsivity.\"\n );\n }\n\n const colorProfile =\n parseSensorColorSamplingProfile(\n input.colorSamplingProfile\n );\n const responseProfile =\n parseSensorSpectralResponseProfile(\n input.spectralResponseProfile\n );\n\n if (\n colorProfile.profileId !==\n input.reduction\n .colorSamplingProfileId ||\n responseProfile.profileId !==\n input.reduction\n .responseProfileId ||\n responseProfile\n .colorSamplingProfileId !==\n colorProfile.profileId\n ) {\n throw new InvalidScientificInputError(\n \"Supplied color/spectral response profiles must exactly match the reduction identities.\"\n );\n }\n\n validateExactResponseDataBinding(\n responseProfile,\n input.reduction\n );\n\n const nodes =\n validateReductionSpectralNodes(\n input.reduction\n );\n validateOperatingRangeNodeBinding(\n input.reduction,\n nodes,\n input.operatingRange\n );\n\n const airIndices =\n parseAirContext(\n input.reduction,\n nodes,\n input.airPhotonEnergyContext\n );\n\n const photonRateSum:\n CompensatedSum = {\n sum: 0,\n correction: 0\n };\n const electronRateSum:\n CompensatedSum = {\n sum: 0,\n correction: 0\n };\n const perWavelength:\n SensorEqeWavelengthRateContribution[] =\n [];\n\n let responseBindingValidated =\n false;\n let channelFilterTransmissionApplied =\n false;\n\n for (const node of nodes) {\n const resolved =\n resolveParsedSensorSpectralResponseAtWavelength({\n colorSamplingProfile:\n colorProfile,\n spectralResponseProfile:\n responseProfile,\n channelId:\n input.reduction.channelId,\n wavelengthNanometers:\n node.wavelengthNanometers,\n wavelengthBasis:\n input.reduction\n .wavelengthBasis\n });\n\n if (!responseBindingValidated) {\n validateResponseBinding(\n resolved.value,\n input.reduction\n );\n responseBindingValidated =\n true;\n }\n\n const eqe =\n requireEqeResponse(\n resolved.value.response\n );\n\n const airIndex =\n airIndices.get(\n node.spectralSampleIndex\n );\n const photonEnergy =\n calculatePhotonEnergyFromWavelength({\n wavelengthNanometers:\n node.wavelengthNanometers,\n wavelengthBasis:\n input.reduction\n .wavelengthBasis,\n ...(input.reduction\n .wavelengthBasis === \"air\"\n ? {\n airPhaseRefractiveIndex:\n airIndex!,\n ...(input.airPhotonEnergyContext!\n .operatingConditions ===\n undefined\n ? {}\n : {\n airOperatingConditions:\n input\n .airPhotonEnergyContext!\n .operatingConditions\n }),\n airConditionPolicy:\n input.airPhotonEnergyContext!\n .conditionPolicy\n }\n : {})\n }).value;\n\n const radiantPowerContributionWatts =\n node\n .geometricApertureIncidentSpectralFluxWattsPerNanometer *\n node.wavelengthMeasureNanometers;\n const incidentPhotonRatePerSecond =\n radiantPowerContributionWatts /\n photonEnergy.photonEnergyJoules;\n const expectedGeneratedElectronRatePerSecond =\n incidentPhotonRatePerSecond *\n eqe.externalQuantumEfficiency;\n\n if (\n !Number.isFinite(\n radiantPowerContributionWatts\n ) ||\n !Number.isFinite(\n incidentPhotonRatePerSecond\n ) ||\n !Number.isFinite(\n expectedGeneratedElectronRatePerSecond\n )\n ) {\n throw new InvalidScientificInputError(\n \"EQE wavelength-node rate calculation must remain finite.\"\n );\n }\n\n addCompensated(\n photonRateSum,\n incidentPhotonRatePerSecond\n );\n addCompensated(\n electronRateSum,\n expectedGeneratedElectronRatePerSecond\n );\n\n if (\n eqe.composition ===\n \"channel-filter-transmittance-times-detector-eqe\"\n ) {\n channelFilterTransmissionApplied =\n true;\n }\n\n perWavelength.push({\n spectralSampleIndex:\n node.spectralSampleIndex,\n wavelengthNanometers:\n node.wavelengthNanometers,\n wavelengthMeasureNanometers:\n node.wavelengthMeasureNanometers,\n geometricApertureIncidentSpectralFluxWattsPerNanometer:\n node\n .geometricApertureIncidentSpectralFluxWattsPerNanometer,\n radiantPowerContributionWatts,\n vacuumWavelengthNanometers:\n photonEnergy\n .vacuumWavelengthNanometers,\n photonEnergyJoules:\n photonEnergy\n .photonEnergyJoules,\n wavelengthConversion:\n photonEnergy\n .wavelengthConversion,\n ...(photonEnergy\n .phaseRefractiveIndexUsed ===\n undefined\n ? {}\n : {\n phaseRefractiveIndexUsed:\n photonEnergy\n .phaseRefractiveIndexUsed\n }),\n ...(photonEnergy\n .airConditionCompatibility ===\n undefined\n ? {}\n : {\n airConditionCompatibility:\n photonEnergy\n .airConditionCompatibility\n }),\n ...(photonEnergy\n .airRefractiveIndexEvidence ===\n undefined\n ? {}\n : {\n airRefractiveIndexEvidence:\n photonEnergy\n .airRefractiveIndexEvidence\n }),\n incidentPhotonRatePerSecond,\n effectiveExternalQuantumEfficiency:\n eqe.externalQuantumEfficiency,\n responseComposition:\n eqe.composition,\n ...(eqe.channelFilterTransmittance ===\n undefined\n ? {}\n : {\n channelFilterTransmittance:\n eqe.channelFilterTransmittance\n }),\n ...(eqe\n .detectorExternalQuantumEfficiency ===\n undefined\n ? {}\n : {\n detectorExternalQuantumEfficiency:\n eqe\n .detectorExternalQuantumEfficiency\n }),\n responseInterpolationUsed:\n resolved.value\n .interpolationUsed,\n expectedGeneratedElectronRatePerSecond\n });\n }\n\n if (\n !Number.isFinite(\n photonRateSum.sum\n ) ||\n !Number.isFinite(\n electronRateSum.sum\n )\n ) {\n throw new InvalidScientificInputError(\n \"Total EQE photon/electron rates must remain finite.\"\n );\n }\n\n return approximationResult(\n {\n colorSamplingProfileId:\n input.reduction\n .colorSamplingProfileId,\n responseProfileId:\n input.reduction\n .responseProfileId,\n responseApplicationProfileId:\n input.compatibility\n .applicationProfileId,\n operatingRangeProfileId:\n input.operatingRange\n .operatingRangeProfileId,\n site: {\n ...input.reduction.site\n },\n channelId:\n input.reduction.channelId,\n sourceResponseKind:\n input.reduction\n .sourceResponseKind,\n responseScope:\n input.reduction.responseScope,\n responseReferencePlane:\n input.compatibility\n .requiredSourcePlane,\n wavelengthBasis:\n input.reduction\n .wavelengthBasis,\n wavelengthRangeNanometers: {\n ...input.reduction\n .wavelengthRangeNanometers\n },\n spectralNodeCount:\n nodes.length,\n perWavelength,\n incidentPhotonRatePerSecond:\n photonRateSum.sum,\n expectedGeneratedElectronRatePerSecond:\n electronRateSum.sum,\n summationMethod:\n \"kahan-compensated\",\n responseApplicationPerformed:\n true,\n quantumEfficiencyApplied:\n true,\n spectralResponsivityApplied:\n false,\n channelFilterTransmissionApplied,\n photonRateCalculated: true,\n electronRateCalculated: true,\n photonCountCalculated: false,\n electronCountCalculated: false,\n currentCalculated: false,\n temporalIntegrationApplied:\n false,\n exposureDurationApplied: false,\n saturationAssessed: false,\n shotNoiseApplied: false,\n readNoiseApplied: false,\n adcQuantizationApplied:\n false,\n rawCodeValueProduced: false,\n responseUncertaintyPropagated:\n false,\n photonEnergyUncertaintyPropagated:\n false,\n quadratureConvergenceErrorEstimated:\n false\n },\n \"sensor-eqe-electron-rate\",\n \"1.0.0\",\n [\n \"Response is evaluated independently at every wavelength quadrature node; broadband radiant power is never multiplied by an average QE.\",\n \"Each node converts radiant power to photon rate using photon energy hν, then applies effective external QE to obtain expected generated-electron rate.\",\n \"Direct effective EQE is preserved as direct response; explicitly separable channel-filter transmittance × detector EQE is multiplied only through the existing spectral-response resolver.\",\n \"The response calibration metadata/evidence must match the spectral evidence carried by the pre-response reduction; matching profile IDs alone is insufficient.\",\n \"The structural compatibility and operating-range assessments must both be non-blocked and authorize the photon-rate→electron path.\",\n \"Operating-range authorization must be per spectral bin, with spectral-node identities and calibrated-domain values bound back to the exact reduction used for conversion.\",\n \"Operating-range authorization must establish linear superposition over the geometric aperture so spatial reduction may precede response application without hiding sub-aperture nonlinear behavior.\",\n \"Air-basis spectral nodes require one exact-wavelength sourced phase refractive index per spectral sample plus the photon-energy atmosphere compatibility policy.\",\n \"Kahan compensated summation is used across wavelength nodes to reduce loss of small contributions when rates span large dynamic ranges.\",\n \"Outputs are instantaneous expected rates only. Exposure duration, photon/electron counts, full-well saturation, shot/read noise, ADC/RAW conversion and reconstruction remain downstream.\",\n \"Response/refractive-index uncertainties and quadrature convergence error are not propagated into a combined output uncertainty by this version.\"\n ]\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"eqe-electron-rate.js","sourceRoot":"","sources":["../../src/sensor/eqe-electron-rate.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * EQE electron rate: converts the light power reaching a sensor site into a photon rate and an\n * expected electron rate, using the sensor's EQE (external quantum efficiency, the fraction of\n * incident photons that produce a collected electron).\n *\n * The response is applied at each wavelength node. One broadband-average QE is never applied to the\n * total power. Outputs are rates per second; integrating over the exposure time happens later.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport type {\n EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { stringifyCanonicalJson } from \"../core/canonical-json.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport {\n calculatePhotonEnergyFromWavelength,\n type AirRefractiveIndexConditionPolicy,\n type AirRefractiveIndexReferenceConditions,\n type SourcedAirPhaseRefractiveIndex\n} from \"./photon-energy.js\";\nimport type {\n SensorResponseApplicationCompatibilityAssessment\n} from \"./response-application-compatibility.js\";\nimport type {\n SensorResponseOperatingRangeAssessment\n} from \"./response-operating-range.js\";\nimport {\n parseSensorColorSamplingProfile,\n type SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport {\n createSensorSpectralResponseChannelBinding,\n requireCurrentSensorSpectralResponseChannelBinding,\n parseSensorSpectralResponseProfile,\n resolveParsedSensorSpectralResponseAtWavelength,\n type ResolvedSensorSpectralResponseValue,\n type SensorSpectralResponseProfile\n} from \"./spectral-response.js\";\nimport type {\n SensorSpatioSpectralIrradianceReduction,\n SensorSpatioSpectralWavelengthReduction\n} from \"./spatio-spectral-reduction.js\";\nimport { requireInputObject, requireInputKeys, requireInputNonNegativeFinite as requireNonNegativeFinite, requireInputPositiveFinite as requirePositiveFinite, requireInputNonNegativeSafeInteger as requireSafeNonNegativeInteger } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { addCompensated, approximatelyEqual } from \"../core/shared-helpers.js\";\n\n\nexport interface SensorEqeAirRefractiveIndexSample {\n spectralSampleIndex: number;\n refractiveIndex:\n SourcedAirPhaseRefractiveIndex;\n}\n\nexport interface SensorEqeAirPhotonEnergyContext {\n samples:\n readonly SensorEqeAirRefractiveIndexSample[];\n operatingConditions?:\n AirRefractiveIndexReferenceConditions;\n conditionPolicy:\n AirRefractiveIndexConditionPolicy;\n}\n\nexport interface CalculateSensorEqeElectronRateInput {\n reduction:\n SensorSpatioSpectralIrradianceReduction;\n compatibility:\n SensorResponseApplicationCompatibilityAssessment;\n operatingRange:\n SensorResponseOperatingRangeAssessment;\n colorSamplingProfile:\n SensorColorSamplingProfile;\n spectralResponseProfile:\n SensorSpectralResponseProfile;\n airPhotonEnergyContext?:\n SensorEqeAirPhotonEnergyContext;\n}\n\nexport interface SensorEqeWavelengthRateContribution {\n spectralSampleIndex: number;\n wavelengthNanometers: number;\n wavelengthMeasureNanometers: number;\n geometricApertureIncidentSpectralFluxWattsPerNanometer:\n number;\n radiantPowerContributionWatts: number;\n vacuumWavelengthNanometers: number;\n photonEnergyJoules: number;\n wavelengthConversion:\n | \"vacuum-identity\"\n | \"air-to-vacuum-via-phase-refractive-index\";\n phaseRefractiveIndexUsed?: number;\n airConditionCompatibility?:\n | \"exact-match\"\n | \"assumed-compatible\";\n airRefractiveIndexEvidence?:\n readonly EvidenceProvenance[];\n incidentPhotonRatePerSecond: number;\n effectiveExternalQuantumEfficiency: number;\n responseComposition:\n | \"direct-effective-response\"\n | \"channel-filter-transmittance-times-detector-eqe\";\n channelFilterTransmittance?: number;\n detectorExternalQuantumEfficiency?: number;\n responseInterpolationUsed: boolean;\n expectedGeneratedElectronRatePerSecond:\n number;\n}\n\nexport interface SensorEqeElectronRate {\n colorSamplingProfileId: string;\n responseProfileId: string;\n responseApplicationProfileId: string;\n operatingRangeProfileId: string;\n /** The exact source colour site. Results produced by the engine fill this in. */\n site?: {\n x: number;\n y: number;\n };\n channelId: string;\n sourceResponseKind:\n | \"effective-external-quantum-efficiency\"\n | \"separable-channel-filter-and-detector-eqe\";\n responseScope:\n SensorSpatioSpectralIrradianceReduction[\"responseScope\"];\n responseReferencePlane:\n SensorResponseApplicationCompatibilityAssessment[\"requiredSourcePlane\"];\n wavelengthBasis:\n SensorSpatioSpectralIrradianceReduction[\"wavelengthBasis\"];\n wavelengthRangeNanometers:\n SensorSpatioSpectralIrradianceReduction[\"wavelengthRangeNanometers\"];\n spectralNodeCount: number;\n perWavelength:\n readonly SensorEqeWavelengthRateContribution[];\n incidentPhotonRatePerSecond: number;\n expectedGeneratedElectronRatePerSecond:\n number;\n summationMethod:\n \"kahan-compensated\";\n responseApplicationPerformed: true;\n quantumEfficiencyApplied: true;\n spectralResponsivityApplied: false;\n channelFilterTransmissionApplied:\n boolean;\n photonRateCalculated: true;\n electronRateCalculated: true;\n photonCountCalculated: false;\n electronCountCalculated: false;\n currentCalculated: false;\n temporalIntegrationApplied: false;\n exposureDurationApplied: false;\n saturationAssessed: false;\n shotNoiseApplied: false;\n readNoiseApplied: false;\n adcQuantizationApplied: false;\n rawCodeValueProduced: false;\n responseUncertaintyPropagated: false;\n photonEnergyUncertaintyPropagated: false;\n quadratureConvergenceErrorEstimated:\n false;\n}\n\ninterface CompensatedSum {\n sum: number;\n correction: number;\n}\n\n\n/**\n * Identity string for parsed plain data that ignores key order, so records with\n * the same content compare equal whatever order their properties were added in.\n */\nfunction canonicalJson(\n value: unknown\n): string {\n if (value === undefined) return \"undefined\";\n return stringifyCanonicalJson(value, {\n undefinedObjectProperties: \"omit\",\n nonFiniteNumberMessage:\n \"EQE electron-rate identity data must contain finite numbers.\",\n unsupportedValueMessage:\n \"EQE electron-rate identity data must be plain JSON data.\"\n });\n}\n\nfunction requireEquivalent(\n actual: unknown,\n expected: unknown,\n message: string\n): void {\n if (\n canonicalJson(actual) !==\n canonicalJson(expected)\n ) {\n throw new InvalidScientificInputError(\n message\n );\n }\n}\n\nfunction validateAuthorizationChain(\n reduction:\n SensorSpatioSpectralIrradianceReduction,\n compatibility:\n SensorResponseApplicationCompatibilityAssessment,\n operatingRange:\n SensorResponseOperatingRangeAssessment\n): void {\n requireInputObject(reduction, \"reduction input\");\n if (\n reduction.outputMeaning !==\n \"pre-response-spatio-spectral-radiometric-reduction\" ||\n reduction\n .spectralResponseApplicationPerformed !==\n false ||\n reduction.quantumEfficiencyApplied !==\n false ||\n reduction.temporalIntegrationApplied !==\n false\n ) {\n throw new InvalidScientificInputError(\n \"reduction must remain pre-response and pre-temporal before EQE rate conversion.\"\n );\n }\n\n if (\n !compatibility\n .structuralCompatibilityEstablished ||\n compatibility.compatibilityStatus ===\n \"blocked\" ||\n compatibility.blockers.length !== 0 ||\n !compatibility.sourcePlaneMatched ||\n compatibility.requiredSignalPath !==\n \"photon-rate-to-electrons\"\n ) {\n throw new InvalidScientificInputError(\n \"compatibility must establish a non-blocked photon-rate-to-electrons response path.\"\n );\n }\n\n if (\n compatibility.spectralResponseProfileId !==\n reduction.responseProfileId ||\n compatibility.colorSamplingProfileId !==\n reduction.colorSamplingProfileId ||\n compatibility.channelId !==\n reduction.channelId ||\n compatibility.responseScope !==\n reduction.responseScope\n ) {\n throw new InvalidScientificInputError(\n \"compatibility identities must exactly match the reduction.\"\n );\n }\n\n if (\n !operatingRange\n .operatingRangeCompatibilityAssessed ||\n !operatingRange\n .instantaneousResponseRangeCompatible ||\n !operatingRange\n .responseRateConversionAuthorized ||\n operatingRange.status === \"blocked\" ||\n operatingRange.blockers.length !== 0 ||\n operatingRange.requiredSignalPath !==\n \"photon-rate-to-electrons\" ||\n operatingRange\n .responseApplicationPerformed !==\n false ||\n operatingRange\n .photonConversionPerformed !== false ||\n operatingRange\n .electronConversionPerformed !== false ||\n operatingRange\n .temporalIntegrationAuthorized !== false\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange must authorize a clean photon-rate-to-electrons conversion path.\"\n );\n }\n\n if (\n !operatingRange\n .spectralNodeRangeCompatibilityAssessed ||\n operatingRange\n .spectralInputModel.kind !==\n \"per-spectral-bin\"\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange must establish per-spectral-bin input-range applicability before wavelength-dependent EQE conversion.\"\n );\n }\n\n if (\n operatingRange\n .spatialLinearityModel.kind !==\n \"linear-superposition-over-geometric-aperture\"\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange must establish linear superposition over the geometric aperture.\"\n );\n }\n\n if (\n operatingRange.spectralResponseProfileId !==\n reduction.responseProfileId ||\n operatingRange.colorSamplingProfileId !==\n reduction.colorSamplingProfileId ||\n operatingRange.channelId !==\n reduction.channelId ||\n operatingRange.responseApplicationProfileId !==\n compatibility.applicationProfileId\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange identities must exactly match the reduction and compatibility assessment.\"\n );\n }\n}\n\nfunction validateReductionSpectralNodes(\n reduction:\n SensorSpatioSpectralIrradianceReduction\n): readonly SensorSpatioSpectralWavelengthReduction[] {\n if (\n !Array.isArray(\n reduction.perWavelength\n ) ||\n !Number.isSafeInteger(\n reduction.spectralNodeCount\n ) ||\n reduction.spectralNodeCount <= 0 ||\n reduction.perWavelength.length !==\n reduction.spectralNodeCount\n ) {\n throw new InvalidScientificInputError(\n \"reduction.perWavelength must contain exactly spectralNodeCount entries.\"\n );\n }\n\n let previousWavelength =\n Number.NEGATIVE_INFINITY;\n let normalizedWeightSum = 0;\n const integratedPower:\n CompensatedSum = {\n sum: 0,\n correction: 0\n };\n\n reduction.perWavelength.forEach(\n (entry, arrayIndex) => {\n const sampleIndex =\n requireSafeNonNegativeInteger(\n entry.spectralSampleIndex,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].spectralSampleIndex\"\n );\n if (sampleIndex !== arrayIndex) {\n throw new InvalidScientificInputError(\n \"reduction spectralSampleIndex values must form the exact contiguous range 0..spectralNodeCount-1.\"\n );\n }\n\n const wavelength =\n requirePositiveFinite(\n entry.wavelengthNanometers,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].wavelengthNanometers\"\n );\n if (\n wavelength <=\n previousWavelength\n ) {\n throw new InvalidScientificInputError(\n \"reduction per-wavelength samples must be strictly increasing in wavelength.\"\n );\n }\n previousWavelength =\n wavelength;\n\n const measure =\n requirePositiveFinite(\n entry.wavelengthMeasureNanometers,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].wavelengthMeasureNanometers\"\n );\n const normalizedWeight =\n requirePositiveFinite(\n entry.normalizedWavelengthWeight,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].normalizedWavelengthWeight\"\n );\n const fluxDensity =\n requireNonNegativeFinite(\n entry\n .geometricApertureIncidentSpectralFluxWattsPerNanometer,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].geometricApertureIncidentSpectralFluxWattsPerNanometer\"\n );\n const contribution =\n requireNonNegativeFinite(\n entry\n .wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts,\n \"reduction.perWavelength[\" +\n arrayIndex +\n \"].wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts\"\n );\n const expectedContribution =\n fluxDensity * measure;\n if (\n !Number.isFinite(\n expectedContribution\n ) ||\n !approximatelyEqual(\n contribution,\n expectedContribution\n )\n ) {\n throw new InvalidScientificInputError(\n \"reduction per-wavelength radiant-power contribution must equal spectral flux density × d-lambda.\"\n );\n }\n\n normalizedWeightSum +=\n normalizedWeight;\n if (\n !Number.isFinite(\n normalizedWeightSum\n )\n ) {\n throw new InvalidScientificInputError(\n \"reduction normalized wavelength weights must remain finite.\"\n );\n }\n addCompensated(\n integratedPower,\n contribution\n );\n }\n );\n\n if (\n !approximatelyEqual(\n normalizedWeightSum,\n 1\n )\n ) {\n throw new InvalidScientificInputError(\n \"reduction normalized wavelength weights must sum to 1.\"\n );\n }\n\n const totalPower =\n requireNonNegativeFinite(\n reduction\n .wavelengthIntegratedGeometricApertureIncidentFluxWatts,\n \"reduction.wavelengthIntegratedGeometricApertureIncidentFluxWatts\"\n );\n if (\n !approximatelyEqual(\n integratedPower.sum,\n totalPower\n )\n ) {\n throw new InvalidScientificInputError(\n \"reduction total geometric-aperture radiant power must match the sum of per-wavelength contributions.\"\n );\n }\n\n return reduction.perWavelength;\n}\n\nfunction validateOperatingRangeNodeBinding(\n reduction:\n SensorSpatioSpectralIrradianceReduction,\n nodes:\n readonly SensorSpatioSpectralWavelengthReduction[],\n operatingRange:\n SensorResponseOperatingRangeAssessment\n): void {\n const evaluated =\n operatingRange\n .evaluatedSpectralNodeInputs;\n\n if (\n evaluated === undefined ||\n evaluated.length !== nodes.length\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange must carry one evaluated spectral-node input for every reduction wavelength node.\"\n );\n }\n\n if (\n operatingRange\n .evaluatedWavelengthRangeNanometers\n .minimum !==\n reduction.wavelengthRangeNanometers\n .minimum ||\n operatingRange\n .evaluatedWavelengthRangeNanometers\n .maximum !==\n reduction.wavelengthRangeNanometers\n .maximum\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange wavelength range must exactly match the reduction.\"\n );\n }\n\n evaluated.forEach(\n (entry, index) => {\n const node = nodes[index]!;\n if (\n entry.spectralSampleIndex !==\n node.spectralSampleIndex ||\n entry.wavelengthNanometers !==\n node.wavelengthNanometers ||\n entry.wavelengthMeasureNanometers !==\n node.wavelengthMeasureNanometers\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange spectral-node identities must exactly match the reduction.\"\n );\n }\n\n const expected =\n operatingRange.inputRange.kind ===\n \"wavelength-integrated-geometric-aperture-radiant-power\"\n ? node\n .wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts\n : node\n .wavelengthIntegratedSpatialAverageContributionWattsPerSquareMeter;\n\n if (\n entry.kind !==\n operatingRange.inputRange.kind ||\n entry.unit !==\n operatingRange.inputRange.unit ||\n !approximatelyEqual(\n entry.value,\n expected\n )\n ) {\n throw new InvalidScientificInputError(\n \"operatingRange spectral-node values must match the reduction values in the calibrated input domain.\"\n );\n }\n }\n );\n}\n\nfunction parseAirContext(\n reduction:\n SensorSpatioSpectralIrradianceReduction,\n nodes:\n readonly SensorSpatioSpectralWavelengthReduction[],\n context:\n SensorEqeAirPhotonEnergyContext |\n undefined\n): ReadonlyMap<\n number,\n SourcedAirPhaseRefractiveIndex\n> {\n if (\n reduction.wavelengthBasis ===\n \"vacuum\"\n ) {\n if (context !== undefined) {\n throw new InvalidScientificInputError(\n \"airPhotonEnergyContext must be omitted for vacuum-basis EQE conversion.\"\n );\n }\n return new Map();\n }\n\n if (\n reduction.wavelengthBasis !== \"air\"\n ) {\n throw new InvalidScientificInputError(\n \"EQE electron-rate conversion requires a resolved air or vacuum wavelength basis.\"\n );\n }\n\n if (\n context === undefined ||\n !Array.isArray(context.samples) ||\n context.samples.length !==\n nodes.length\n ) {\n throw new InvalidScientificInputError(\n \"Air-basis EQE conversion requires exactly one refractive-index sample for every spectral node.\"\n );\n }\n\n const byIndex =\n new Map<\n number,\n SourcedAirPhaseRefractiveIndex\n >();\n\n context.samples.forEach(\n (entry, arrayIndex) => {\n if (\n typeof entry !== \"object\" ||\n entry === null\n ) {\n throw new InvalidConfigurationError(\n \"airPhotonEnergyContext.samples[\" +\n arrayIndex +\n \"] must be an object.\"\n );\n }\n\n const spectralSampleIndex =\n requireSafeNonNegativeInteger(\n entry.spectralSampleIndex,\n \"airPhotonEnergyContext.samples[\" +\n arrayIndex +\n \"].spectralSampleIndex\"\n );\n if (\n spectralSampleIndex >=\n nodes.length ||\n byIndex.has(\n spectralSampleIndex\n )\n ) {\n throw new InvalidScientificInputError(\n \"airPhotonEnergyContext must contain unique in-range spectral sample identities.\"\n );\n }\n byIndex.set(\n spectralSampleIndex,\n entry.refractiveIndex\n );\n }\n );\n\n return byIndex;\n}\n\nfunction responseEvidenceMatches(\n actual:\n readonly EvidenceProvenance[],\n expected:\n readonly EvidenceProvenance[]\n): boolean {\n return (\n canonicalJson(actual) ===\n canonicalJson(expected)\n );\n}\n\nfunction validateResponseBinding(\n resolved:\n ReturnType<\n typeof resolveParsedSensorSpectralResponseAtWavelength\n >[\"value\"],\n reduction:\n SensorSpatioSpectralIrradianceReduction\n): void {\n if (\n resolved.profileId !==\n reduction.responseProfileId ||\n resolved.colorSamplingProfileId !==\n reduction.colorSamplingProfileId ||\n resolved.channelId !==\n reduction.channelId ||\n resolved.sourceResponseKind !==\n reduction.sourceResponseKind ||\n resolved.responseScope !==\n reduction.responseScope ||\n resolved.scientificStatus !==\n reduction.responseScientificStatus\n ) {\n throw new InvalidScientificInputError(\n \"Resolved spectral response metadata must exactly match the response used to construct the reduction.\"\n );\n }\n\n requireEquivalent(\n resolved.uncertainty,\n reduction.responseUncertainty,\n \"Resolved spectral response uncertainty must match the reduction.\"\n );\n\n const expectedEvidence =\n reduction.componentEvidence.spectral;\n if (\n !responseEvidenceMatches(\n resolved.componentEvidence.profile,\n expectedEvidence.profile\n ) ||\n !responseEvidenceMatches(\n resolved.componentEvidence.channel,\n expectedEvidence.channel\n ) ||\n canonicalJson(\n resolved.componentEvidence.curves\n ) !==\n canonicalJson(\n expectedEvidence.curves\n )\n ) {\n throw new InvalidScientificInputError(\n \"Resolved spectral response evidence must match the calibration evidence carried by the reduction.\"\n );\n }\n}\n\nfunction validateExactResponseDataBinding(\n responseProfile:\n SensorSpectralResponseProfile,\n reduction:\n SensorSpatioSpectralIrradianceReduction\n): void {\n const response =\n responseProfile.channels.find(\n (entry) =>\n entry.channelId ===\n reduction.channelId\n );\n if (reduction.responseChannelBinding !== undefined) {\n requireCurrentSensorSpectralResponseChannelBinding(\n reduction.responseChannelBinding\n );\n }\n if (\n response === undefined ||\n reduction.responseChannelBinding ===\n undefined ||\n canonicalJson(\n createSensorSpectralResponseChannelBinding(\n response\n )\n ) !==\n canonicalJson(\n reduction.responseChannelBinding\n )\n ) {\n throw new InvalidScientificInputError(\n \"Supplied spectral response channel data must exactly match the response-channel binding carried by the reduction.\"\n );\n }\n}\n\nfunction requireEqeResponse(\n response:\n ResolvedSensorSpectralResponseValue\n): Extract<\n ResolvedSensorSpectralResponseValue,\n {\n kind:\n \"effective-external-quantum-efficiency\";\n }\n> {\n if (\n response.kind !==\n \"effective-external-quantum-efficiency\"\n ) {\n throw new InvalidScientificInputError(\n \"EQE electron-rate conversion cannot consume A/W spectral responsivity.\"\n );\n }\n return response;\n}\n\n/**\n * Converts the spectral radiant power collected by the site's geometric aperture\n * (before the sensor response) into an incident photon rate and an expected\n * generated electron rate.\n *\n * The response is applied at each wavelength node. One broadband-average QE is\n * never applied to the total power.\n *\n * Integration over exposure time happens later, so all outputs are rates per\n * second, not photon or electron counts.\n */\nexport function calculateSensorEqeElectronRate(\n input:\n CalculateSensorEqeElectronRateInput\n): CalculationResult<SensorEqeElectronRate> {\n requireInputKeys(input, \"calculateSensorEqeElectronRate\", [\"reduction\", \"compatibility\", \"operatingRange\", \"colorSamplingProfile\", \"spectralResponseProfile\", \"airPhotonEnergyContext\"]);\n validateAuthorizationChain(\n input.reduction,\n input.compatibility,\n input.operatingRange\n );\n\n if (\n input.reduction\n .sourceResponseKind ===\n \"effective-spectral-responsivity\"\n ) {\n throw new InvalidScientificInputError(\n \"EQE electron-rate conversion requires an EQE or filter×detector-EQE response, not A/W responsivity.\"\n );\n }\n\n const colorProfile =\n parseSensorColorSamplingProfile(\n input.colorSamplingProfile\n );\n const responseProfile =\n parseSensorSpectralResponseProfile(\n input.spectralResponseProfile\n );\n\n if (\n colorProfile.profileId !==\n input.reduction\n .colorSamplingProfileId ||\n responseProfile.profileId !==\n input.reduction\n .responseProfileId ||\n responseProfile\n .colorSamplingProfileId !==\n colorProfile.profileId\n ) {\n throw new InvalidScientificInputError(\n \"Supplied color/spectral response profiles must exactly match the reduction identities.\"\n );\n }\n\n validateExactResponseDataBinding(\n responseProfile,\n input.reduction\n );\n\n const nodes =\n validateReductionSpectralNodes(\n input.reduction\n );\n validateOperatingRangeNodeBinding(\n input.reduction,\n nodes,\n input.operatingRange\n );\n\n const airIndices =\n parseAirContext(\n input.reduction,\n nodes,\n input.airPhotonEnergyContext\n );\n\n const photonRateSum:\n CompensatedSum = {\n sum: 0,\n correction: 0\n };\n const electronRateSum:\n CompensatedSum = {\n sum: 0,\n correction: 0\n };\n const perWavelength:\n SensorEqeWavelengthRateContribution[] =\n [];\n\n let responseBindingValidated =\n false;\n let channelFilterTransmissionApplied =\n false;\n\n for (const node of nodes) {\n const resolved =\n resolveParsedSensorSpectralResponseAtWavelength({\n colorSamplingProfile:\n colorProfile,\n spectralResponseProfile:\n responseProfile,\n channelId:\n input.reduction.channelId,\n wavelengthNanometers:\n node.wavelengthNanometers,\n wavelengthBasis:\n input.reduction\n .wavelengthBasis\n });\n\n if (!responseBindingValidated) {\n validateResponseBinding(\n resolved.value,\n input.reduction\n );\n responseBindingValidated =\n true;\n }\n\n const eqe =\n requireEqeResponse(\n resolved.value.response\n );\n\n const airIndex =\n airIndices.get(\n node.spectralSampleIndex\n );\n const photonEnergy =\n calculatePhotonEnergyFromWavelength({\n wavelengthNanometers:\n node.wavelengthNanometers,\n wavelengthBasis:\n input.reduction\n .wavelengthBasis,\n ...(input.reduction\n .wavelengthBasis === \"air\"\n ? {\n airPhaseRefractiveIndex:\n airIndex!,\n ...(input.airPhotonEnergyContext!\n .operatingConditions ===\n undefined\n ? {}\n : {\n airOperatingConditions:\n input\n .airPhotonEnergyContext!\n .operatingConditions\n }),\n airConditionPolicy:\n input.airPhotonEnergyContext!\n .conditionPolicy\n }\n : {})\n }).value;\n\n const radiantPowerContributionWatts =\n node\n .geometricApertureIncidentSpectralFluxWattsPerNanometer *\n node.wavelengthMeasureNanometers;\n const incidentPhotonRatePerSecond =\n radiantPowerContributionWatts /\n photonEnergy.photonEnergyJoules;\n const expectedGeneratedElectronRatePerSecond =\n incidentPhotonRatePerSecond *\n eqe.externalQuantumEfficiency;\n\n if (\n !Number.isFinite(\n radiantPowerContributionWatts\n ) ||\n !Number.isFinite(\n incidentPhotonRatePerSecond\n ) ||\n !Number.isFinite(\n expectedGeneratedElectronRatePerSecond\n )\n ) {\n throw new InvalidScientificInputError(\n \"EQE wavelength-node rate calculation must remain finite.\"\n );\n }\n\n addCompensated(\n photonRateSum,\n incidentPhotonRatePerSecond\n );\n addCompensated(\n electronRateSum,\n expectedGeneratedElectronRatePerSecond\n );\n\n if (\n eqe.composition ===\n \"channel-filter-transmittance-times-detector-eqe\"\n ) {\n channelFilterTransmissionApplied =\n true;\n }\n\n perWavelength.push({\n spectralSampleIndex:\n node.spectralSampleIndex,\n wavelengthNanometers:\n node.wavelengthNanometers,\n wavelengthMeasureNanometers:\n node.wavelengthMeasureNanometers,\n geometricApertureIncidentSpectralFluxWattsPerNanometer:\n node\n .geometricApertureIncidentSpectralFluxWattsPerNanometer,\n radiantPowerContributionWatts,\n vacuumWavelengthNanometers:\n photonEnergy\n .vacuumWavelengthNanometers,\n photonEnergyJoules:\n photonEnergy\n .photonEnergyJoules,\n wavelengthConversion:\n photonEnergy\n .wavelengthConversion,\n ...(photonEnergy\n .phaseRefractiveIndexUsed ===\n undefined\n ? {}\n : {\n phaseRefractiveIndexUsed:\n photonEnergy\n .phaseRefractiveIndexUsed\n }),\n ...(photonEnergy\n .airConditionCompatibility ===\n undefined\n ? {}\n : {\n airConditionCompatibility:\n photonEnergy\n .airConditionCompatibility\n }),\n ...(photonEnergy\n .airRefractiveIndexEvidence ===\n undefined\n ? {}\n : {\n airRefractiveIndexEvidence:\n photonEnergy\n .airRefractiveIndexEvidence\n }),\n incidentPhotonRatePerSecond,\n effectiveExternalQuantumEfficiency:\n eqe.externalQuantumEfficiency,\n responseComposition:\n eqe.composition,\n ...(eqe.channelFilterTransmittance ===\n undefined\n ? {}\n : {\n channelFilterTransmittance:\n eqe.channelFilterTransmittance\n }),\n ...(eqe\n .detectorExternalQuantumEfficiency ===\n undefined\n ? {}\n : {\n detectorExternalQuantumEfficiency:\n eqe\n .detectorExternalQuantumEfficiency\n }),\n responseInterpolationUsed:\n resolved.value\n .interpolationUsed,\n expectedGeneratedElectronRatePerSecond\n });\n }\n\n if (\n !Number.isFinite(\n photonRateSum.sum\n ) ||\n !Number.isFinite(\n electronRateSum.sum\n )\n ) {\n throw new InvalidScientificInputError(\n \"Total EQE photon/electron rates must remain finite.\"\n );\n }\n\n return approximationResult(\n {\n colorSamplingProfileId:\n input.reduction\n .colorSamplingProfileId,\n responseProfileId:\n input.reduction\n .responseProfileId,\n responseApplicationProfileId:\n input.compatibility\n .applicationProfileId,\n operatingRangeProfileId:\n input.operatingRange\n .operatingRangeProfileId,\n site: {\n ...input.reduction.site\n },\n channelId:\n input.reduction.channelId,\n sourceResponseKind:\n input.reduction\n .sourceResponseKind,\n responseScope:\n input.reduction.responseScope,\n responseReferencePlane:\n input.compatibility\n .requiredSourcePlane,\n wavelengthBasis:\n input.reduction\n .wavelengthBasis,\n wavelengthRangeNanometers: {\n ...input.reduction\n .wavelengthRangeNanometers\n },\n spectralNodeCount:\n nodes.length,\n perWavelength,\n incidentPhotonRatePerSecond:\n photonRateSum.sum,\n expectedGeneratedElectronRatePerSecond:\n electronRateSum.sum,\n summationMethod:\n \"kahan-compensated\",\n responseApplicationPerformed:\n true,\n quantumEfficiencyApplied:\n true,\n spectralResponsivityApplied:\n false,\n channelFilterTransmissionApplied,\n photonRateCalculated: true,\n electronRateCalculated: true,\n photonCountCalculated: false,\n electronCountCalculated: false,\n currentCalculated: false,\n temporalIntegrationApplied:\n false,\n exposureDurationApplied: false,\n saturationAssessed: false,\n shotNoiseApplied: false,\n readNoiseApplied: false,\n adcQuantizationApplied:\n false,\n rawCodeValueProduced: false,\n responseUncertaintyPropagated:\n false,\n photonEnergyUncertaintyPropagated:\n false,\n quadratureConvergenceErrorEstimated:\n false\n },\n \"sensor-eqe-electron-rate\",\n \"1.0.0\",\n [\n \"Response is evaluated independently at every wavelength quadrature node; broadband radiant power is never multiplied by an average QE.\",\n \"Each node converts radiant power to photon rate using photon energy hν, then applies effective external QE to obtain expected generated-electron rate.\",\n \"Direct effective EQE is preserved as direct response; explicitly separable channel-filter transmittance × detector EQE is multiplied only through the existing spectral-response resolver.\",\n \"The response calibration metadata/evidence must match the spectral evidence carried by the pre-response reduction; matching profile IDs alone is insufficient.\",\n \"The structural compatibility and operating-range assessments must both be non-blocked and authorize the photon-rate→electron path.\",\n \"Operating-range authorization must be per spectral bin, with spectral-node identities and calibrated-domain values bound back to the exact reduction used for conversion.\",\n \"Operating-range authorization must establish linear superposition over the geometric aperture so spatial reduction may precede response application without hiding sub-aperture nonlinear behavior.\",\n \"Air-basis spectral nodes require one exact-wavelength sourced phase refractive index per spectral sample plus the photon-energy atmosphere compatibility policy.\",\n \"Kahan compensated summation is used across wavelength nodes to reduce loss of small contributions when rates span large dynamic ranges.\",\n \"Outputs are instantaneous expected rates only. Exposure duration, photon/electron counts, full-well saturation, shot/read noise, ADC/RAW conversion and reconstruction remain downstream.\",\n \"Response/refractive-index uncertainties and quadrature convergence error are not propagated into a combined output uncertainty by this version.\"\n ]\n );\n}\n"]}
|
|
@@ -106,6 +106,8 @@ export declare function parseSecondsFact(value: unknown, path: string): SourcedC
|
|
|
106
106
|
/** Parses one exposure boundary schedule (opening or closing). Not a root export. */
|
|
107
107
|
export declare function parseBoundarySchedule(value: unknown, path: string): ExposureBoundarySchedule;
|
|
108
108
|
export declare function validateNativeRaster(raster: NativeImageRaster): void;
|
|
109
|
+
export declare function validateActiveCaptureRect(rect: RasterRect, raster: NativeImageRaster): void;
|
|
110
|
+
export declare function validateShutterMechanism(value: unknown): asserts value is CaptureShutterMechanism;
|
|
109
111
|
export declare function scanVector(direction: NativeSensorReadoutScanDirection): RasterVector;
|
|
110
112
|
export declare function normalizedScanPosition(point: RasterPoint, rect: RasterRect, direction: NativeSensorReadoutScanDirection): number;
|
|
111
113
|
/**
|
|
@@ -112,7 +112,7 @@ export function validateNativeRaster(raster) {
|
|
|
112
112
|
requirePositiveInteger("nativeRaster.pixelWidth", raster.pixelWidth);
|
|
113
113
|
requirePositiveInteger("nativeRaster.pixelHeight", raster.pixelHeight);
|
|
114
114
|
}
|
|
115
|
-
function validateActiveCaptureRect(rect, raster) {
|
|
115
|
+
export function validateActiveCaptureRect(rect, raster) {
|
|
116
116
|
requireNonNegativeInteger("activeCaptureRect.x", rect.x);
|
|
117
117
|
requireNonNegativeInteger("activeCaptureRect.y", rect.y);
|
|
118
118
|
requirePositiveInteger("activeCaptureRect.width", rect.width);
|
|
@@ -122,7 +122,7 @@ function validateActiveCaptureRect(rect, raster) {
|
|
|
122
122
|
throw new InvalidScientificInputError("activeCaptureRect must fit entirely within nativeRaster.");
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
function validateShutterMechanism(value) {
|
|
125
|
+
export function validateShutterMechanism(value) {
|
|
126
126
|
if (typeof value !== "string" ||
|
|
127
127
|
!SHUTTER_MECHANISMS.has(value)) {
|
|
128
128
|
throw new InvalidConfigurationError("shutterMechanism is invalid.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exposure-window.js","sourceRoot":"","sources":["../../src/sensor/exposure-window.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Exposure windows: when each point on the sensor starts and stops collecting light.\n *\n * The shutter has an opening boundary and a closing boundary, each with its own schedule. With a\n * rolling or curtain shutter the boundaries sweep across the sensor, so each point has its own\n * (local) window. Times are seconds from the first opening boundary. That is deliberately not the\n * same as the camera-rotation API's global \"exposure start\"; a later layer must link the two\n * explicitly.\n *\n * The mechanism records whether each boundary is mechanical or electronic, but supplies no sweep\n * duration or direction. Every timing value and direction for a non-simultaneous boundary is declared\n * by the caller, with evidence. Only simultaneous boundaries and a uniform linear sweep along one\n * native sensor axis are supported. Not modelled: curtain acceleration, segmented or non-linear\n * electronic schedules, flash or flicker, rolling-shutter image distortion, shutter shock, and\n * EFCS (electronic first-curtain shutter) effects on the pupil or bokeh.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact\n} from \"../core/evidence-provenance.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveInteger\n} from \"../core/validation.js\";\nimport type {\n RasterPoint,\n RasterRect,\n RasterVector\n} from \"../output/capture-geometry.js\";\nimport type { NativeImageRaster } from \"./sensor-geometry.js\";\nimport type {\n CaptureShutterMechanism,\n NativeSensorReadoutScanDirection,\n SourcedSensorTimingSeconds\n} from \"./readout-timing.js\";\nimport { requireInputObject, requireInputKeys, requireConfigRecord as requireRecord } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { fullRect } from \"../core/shared-helpers.js\";\n\nexport type CaptureExposureBoundaryActuator = \"mechanical\" | \"electronic\";\n\nexport type SourcedCaptureTimingSeconds = SourcedSensorTimingSeconds;\n\nexport type SourcedCaptureBoundaryDirection =\n EvidenceBackedFact<NativeSensorReadoutScanDirection>;\n\nexport interface SimultaneousExposureBoundarySchedule {\n kind: \"simultaneous\";\n directionNative?: never;\n traversalDurationSeconds?: never;\n}\n\nexport interface UniformLinearExposureBoundarySchedule {\n kind: \"uniform-linear-native-scan\";\n directionNative: SourcedCaptureBoundaryDirection;\n traversalDurationSeconds: SourcedCaptureTimingSeconds;\n}\n\nexport type ExposureBoundarySchedule =\n | SimultaneousExposureBoundarySchedule\n | UniformLinearExposureBoundarySchedule;\n\nexport interface CalculateCaptureExposureWindowsInput {\n nativeRaster: NativeImageRaster;\n /**\n * Active capture rectangle, in fixed native pixel coordinates.\n *\n * Defaults to the whole native image. Sweep timing is never worked out by\n * scaling a full-frame timing value to this rectangle.\n */\n activeCaptureRect?: RasterRect;\n shutterMechanism: CaptureShutterMechanism;\n /**\n * Nominal time between the opening and closing reference points (phase zero).\n *\n * A point's local exposure can differ from this when the opening and closing\n * boundaries sweep differently across the sensor.\n */\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n opening: ExposureBoundarySchedule;\n closing: ExposureBoundarySchedule;\n /**\n * Optional points, in continuous native pixel coordinates measured from pixel\n * edges, to report windows for. A pixel centre is at +0.5.\n */\n samplePointsNative?: readonly RasterPoint[];\n}\n\nexport interface ResolvedCaptureExposureBoundary {\n actuator: CaptureExposureBoundaryActuator;\n schedule:\n | {\n kind: \"simultaneous\";\n }\n | {\n kind: \"uniform-linear-native-scan\";\n directionNative: SourcedCaptureBoundaryDirection;\n unitVectorNative: RasterVector;\n traversalDurationSeconds: SourcedCaptureTimingSeconds;\n };\n}\n\nexport interface CaptureExposureWindowSample {\n pointNative: RasterPoint;\n openingNormalizedScanPosition: number | null;\n closingNormalizedScanPosition: number | null;\n /**\n * Seconds from the first opening boundary.\n */\n startOffsetSecondsFromOpeningReference: number;\n /**\n * Seconds from the first opening boundary.\n */\n endOffsetSecondsFromOpeningReference: number;\n localExposureDurationSeconds: number;\n}\n\nexport interface CaptureExposureWindows {\n shutterMechanism: CaptureShutterMechanism;\n timeReference: \"first-opening-boundary-phase\";\n nativeRaster: NativeImageRaster;\n activeCaptureRect: RasterRect;\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n opening: ResolvedCaptureExposureBoundary;\n closing: ResolvedCaptureExposureBoundary;\n localExposureDurationRangeSeconds: {\n minimum: number;\n maximum: number;\n };\n samples: readonly CaptureExposureWindowSample[];\n}\n\nconst SHUTTER_MECHANISMS = new Set<CaptureShutterMechanism>([\n \"mechanical\",\n \"electronic-first-curtain\",\n \"electronic\"\n]);\n\nconst SCAN_DIRECTIONS = new Set<NativeSensorReadoutScanDirection>([\n \"top-to-bottom\",\n \"bottom-to-top\",\n \"left-to-right\",\n \"right-to-left\"\n]);\n\nconst BOUNDARY_KINDS = new Set<ExposureBoundarySchedule[\"kind\"]>([\n \"simultaneous\",\n \"uniform-linear-native-scan\"\n]);\n\nexport function requireNonNegativeInteger(name: string, value: number): void {\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new InvalidScientificInputError(\n name + \" must be a non-negative safe integer.\"\n );\n }\n}\n\nexport function requirePositiveFinite(name: string, value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) {\n throw new InvalidScientificInputError(\n name + \" must be a finite number greater than zero.\"\n );\n }\n return value;\n}\n\n/** Parses a sourced duration in seconds (`{ unit: \"s\", value, evidence }`). Not a root export. */\nexport function parseSecondsFact(\n value: unknown,\n path: string\n): SourcedCaptureTimingSeconds {\n const record = requireRecord(value, path, [\"unit\", \"value\", \"evidence\"]);\n if (record.unit !== \"s\") {\n throw new InvalidConfigurationError(path + '.unit must be \"s\".');\n }\n\n return {\n value: requirePositiveFinite(path + \".value\", record.value),\n unit: \"s\",\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseDirectionFact(\n value: unknown,\n path: string\n): SourcedCaptureBoundaryDirection {\n const record = requireRecord(value, path, [\"value\", \"evidence\"]);\n if (\n typeof record.value !== \"string\" ||\n !SCAN_DIRECTIONS.has(\n record.value as NativeSensorReadoutScanDirection\n )\n ) {\n throw new InvalidConfigurationError(path + \".value is invalid.\");\n }\n\n return {\n value: record.value as NativeSensorReadoutScanDirection,\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\n/** Parses one exposure boundary schedule (opening or closing). Not a root export. */\nexport function parseBoundarySchedule(\n value: unknown,\n path: string\n): ExposureBoundarySchedule {\n // unitVectorNative is the derived field of a resolved schedule; it may be passed back if it matches.\n const record = requireRecord(value, path, [\"kind\", \"directionNative\", \"traversalDurationSeconds\", \"unitVectorNative\"]);\n if (\n typeof record.kind !== \"string\" ||\n !BOUNDARY_KINDS.has(record.kind as ExposureBoundarySchedule[\"kind\"])\n ) {\n throw new InvalidConfigurationError(path + \".kind is invalid.\");\n }\n\n if (record.kind === \"simultaneous\") {\n if (record.directionNative !== undefined) {\n throw new InvalidScientificInputError(\n path + \".directionNative must be omitted for a simultaneous boundary.\"\n );\n }\n if (record.traversalDurationSeconds !== undefined) {\n throw new InvalidScientificInputError(\n path +\n \".traversalDurationSeconds must be omitted for a simultaneous boundary.\"\n );\n }\n if (record.unitVectorNative !== undefined) {\n throw new InvalidScientificInputError(\n path + \".unitVectorNative must be omitted for a simultaneous boundary.\"\n );\n }\n\n return { kind: \"simultaneous\" };\n }\n\n const directionNative = parseDirectionFact(\n record.directionNative,\n path + \".directionNative\"\n );\n if (record.unitVectorNative !== undefined) {\n const supplied = requireRecord(record.unitVectorNative, path + \".unitVectorNative\", [\"x\", \"y\"]);\n const derived = scanVector(directionNative.value);\n if (supplied.x !== derived.x || supplied.y !== derived.y) {\n throw new InvalidConfigurationError(path + \".unitVectorNative must match directionNative.\");\n }\n }\n return {\n kind: \"uniform-linear-native-scan\",\n directionNative,\n traversalDurationSeconds: parseSecondsFact(\n record.traversalDurationSeconds,\n path + \".traversalDurationSeconds\"\n )\n };\n}\n\nexport function validateNativeRaster(raster: NativeImageRaster): void {\n requireInputObject(raster, \"raster input\");\n requirePositiveInteger(\"nativeRaster.pixelWidth\", raster.pixelWidth);\n requirePositiveInteger(\"nativeRaster.pixelHeight\", raster.pixelHeight);\n}\n\nfunction validateActiveCaptureRect(\n rect: RasterRect,\n raster: NativeImageRaster\n): void {\n requireNonNegativeInteger(\"activeCaptureRect.x\", rect.x);\n requireNonNegativeInteger(\"activeCaptureRect.y\", rect.y);\n requirePositiveInteger(\"activeCaptureRect.width\", rect.width);\n requirePositiveInteger(\"activeCaptureRect.height\", rect.height);\n\n if (\n rect.x + rect.width > raster.pixelWidth ||\n rect.y + rect.height > raster.pixelHeight\n ) {\n throw new InvalidScientificInputError(\n \"activeCaptureRect must fit entirely within nativeRaster.\"\n );\n }\n}\n\nfunction validateShutterMechanism(\n value: unknown\n): asserts value is CaptureShutterMechanism {\n if (\n typeof value !== \"string\" ||\n !SHUTTER_MECHANISMS.has(value as CaptureShutterMechanism)\n ) {\n throw new InvalidConfigurationError(\"shutterMechanism is invalid.\");\n }\n}\n\nfunction validatePointWithinActiveCapture(\n point: RasterPoint,\n rect: RasterRect,\n path: string\n): void {\n if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {\n throw new InvalidScientificInputError(\n path + \" coordinates must be finite.\"\n );\n }\n\n if (\n point.x < rect.x ||\n point.x > rect.x + rect.width ||\n point.y < rect.y ||\n point.y > rect.y + rect.height\n ) {\n throw new InvalidScientificInputError(\n path + \" must lie within activeCaptureRect edge-coordinate bounds.\"\n );\n }\n}\n\nexport function scanVector(\n direction: NativeSensorReadoutScanDirection\n): RasterVector {\n switch (direction) {\n case \"top-to-bottom\":\n return { x: 0, y: 1 };\n case \"bottom-to-top\":\n return { x: 0, y: -1 };\n case \"left-to-right\":\n return { x: 1, y: 0 };\n case \"right-to-left\":\n return { x: -1, y: 0 };\n }\n}\n\nexport function normalizedScanPosition(\n point: RasterPoint,\n rect: RasterRect,\n direction: NativeSensorReadoutScanDirection\n): number {\n switch (direction) {\n case \"top-to-bottom\":\n return (point.y - rect.y) / rect.height;\n case \"bottom-to-top\":\n return 1 - (point.y - rect.y) / rect.height;\n case \"left-to-right\":\n return (point.x - rect.x) / rect.width;\n case \"right-to-left\":\n return 1 - (point.x - rect.x) / rect.width;\n }\n}\n\nfunction boundaryPhase(\n point: RasterPoint,\n rect: RasterRect,\n schedule: ExposureBoundarySchedule\n): {\n normalizedScanPosition: number | null;\n phaseOffsetSeconds: number;\n} {\n if (schedule.kind === \"simultaneous\") {\n return {\n normalizedScanPosition: null,\n phaseOffsetSeconds: 0\n };\n }\n\n const normalized = normalizedScanPosition(\n point,\n rect,\n schedule.directionNative.value\n );\n\n return {\n normalizedScanPosition: normalized,\n phaseOffsetSeconds:\n normalized * schedule.traversalDurationSeconds.value\n };\n}\n\nfunction resolveBoundaryActuators(\n mechanism: CaptureShutterMechanism\n): {\n opening: CaptureExposureBoundaryActuator;\n closing: CaptureExposureBoundaryActuator;\n} {\n switch (mechanism) {\n case \"mechanical\":\n return {\n opening: \"mechanical\",\n closing: \"mechanical\"\n };\n case \"electronic-first-curtain\":\n return {\n opening: \"electronic\",\n closing: \"mechanical\"\n };\n case \"electronic\":\n return {\n opening: \"electronic\",\n closing: \"electronic\"\n };\n }\n}\n\nfunction resolveBoundary(\n actuator: CaptureExposureBoundaryActuator,\n schedule: ExposureBoundarySchedule\n): ResolvedCaptureExposureBoundary {\n if (schedule.kind === \"simultaneous\") {\n return {\n actuator,\n schedule: {\n kind: \"simultaneous\"\n }\n };\n }\n\n return {\n actuator,\n schedule: {\n kind: \"uniform-linear-native-scan\",\n directionNative: schedule.directionNative,\n unitVectorNative: scanVector(schedule.directionNative.value),\n traversalDurationSeconds: schedule.traversalDurationSeconds\n }\n };\n}\n\nfunction createWindowSample(\n point: RasterPoint,\n rect: RasterRect,\n opening: ExposureBoundarySchedule,\n closing: ExposureBoundarySchedule,\n nominalExposureDurationSeconds: number\n): CaptureExposureWindowSample {\n const openingPhase = boundaryPhase(point, rect, opening);\n const closingPhase = boundaryPhase(point, rect, closing);\n const startOffsetSecondsFromOpeningReference =\n openingPhase.phaseOffsetSeconds;\n const endOffsetSecondsFromOpeningReference =\n nominalExposureDurationSeconds + closingPhase.phaseOffsetSeconds;\n const localExposureDurationSeconds =\n endOffsetSecondsFromOpeningReference -\n startOffsetSecondsFromOpeningReference;\n\n return {\n pointNative: { ...point },\n openingNormalizedScanPosition:\n openingPhase.normalizedScanPosition,\n closingNormalizedScanPosition:\n closingPhase.normalizedScanPosition,\n startOffsetSecondsFromOpeningReference,\n endOffsetSecondsFromOpeningReference,\n localExposureDurationSeconds\n };\n}\n\nfunction activeRectCorners(rect: RasterRect): readonly RasterPoint[] {\n return [\n { x: rect.x, y: rect.y },\n { x: rect.x + rect.width, y: rect.y },\n { x: rect.x, y: rect.y + rect.height },\n {\n x: rect.x + rect.width,\n y: rect.y + rect.height\n }\n ];\n}\n\n/**\n * Works out each point's local exposure window from separate opening and\n * closing boundary schedules.\n *\n * Times are seconds from the first opening boundary. This is deliberately not\n * the camera-rotation API's global \"exposure start\"; a later layer must link the\n * two explicitly.\n *\n * The mechanism records whether each boundary is mechanical or electronic, but\n * supplies no sweep duration or direction. Every timing value and direction for\n * a non-simultaneous boundary is declared by the caller, with evidence.\n *\n * Only simultaneous boundaries and a uniform linear sweep along one native\n * sensor axis are supported. Not modelled: curtain acceleration, segmented or\n * non-linear electronic schedules, flash or flicker, rolling-shutter image\n * distortion, shutter shock, and EFCS (electronic first-curtain shutter)\n * effects on the pupil or bokeh.\n */\nexport function calculateCaptureExposureWindows(\n input: CalculateCaptureExposureWindowsInput\n): CalculationResult<CaptureExposureWindows> {\n requireInputKeys(input, \"calculateCaptureExposureWindows\", [\"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"samplePointsNative\"]);\n validateNativeRaster(input.nativeRaster);\n validateShutterMechanism(input.shutterMechanism);\n\n const nominalExposureDurationSeconds = parseSecondsFact(\n input.nominalExposureDurationSeconds,\n \"nominalExposureDurationSeconds\"\n );\n const opening = parseBoundarySchedule(input.opening, \"opening\");\n const closing = parseBoundarySchedule(input.closing, \"closing\");\n\n const activeCaptureRect =\n input.activeCaptureRect ?? fullRect(input.nativeRaster);\n validateActiveCaptureRect(activeCaptureRect, input.nativeRaster);\n\n if (\n input.samplePointsNative !== undefined &&\n !Array.isArray(input.samplePointsNative)\n ) {\n throw new InvalidConfigurationError(\n \"samplePointsNative must be an array when supplied.\"\n );\n }\n\n const cornerSamples = activeRectCorners(activeCaptureRect).map(\n (point) =>\n createWindowSample(\n point,\n activeCaptureRect,\n opening,\n closing,\n nominalExposureDurationSeconds.value\n )\n );\n\n const localDurations = cornerSamples.map(\n (sample) => sample.localExposureDurationSeconds\n );\n const minimumLocalExposureDurationSeconds = Math.min(...localDurations);\n const maximumLocalExposureDurationSeconds = Math.max(...localDurations);\n\n if (\n !Number.isFinite(minimumLocalExposureDurationSeconds) ||\n minimumLocalExposureDurationSeconds <= 0\n ) {\n throw new InvalidScientificInputError(\n \"The declared opening/closing schedules produce a zero or negative local exposure duration somewhere within activeCaptureRect.\"\n );\n }\n\n const samples = (input.samplePointsNative ?? []).map((point, index) => {\n validatePointWithinActiveCapture(\n point,\n activeCaptureRect,\n \"samplePointsNative[\" + index + \"]\"\n );\n\n return createWindowSample(\n point,\n activeCaptureRect,\n opening,\n closing,\n nominalExposureDurationSeconds.value\n );\n });\n\n const actuators = resolveBoundaryActuators(input.shutterMechanism);\n\n return approximationResult(\n {\n shutterMechanism: input.shutterMechanism,\n timeReference: \"first-opening-boundary-phase\",\n nativeRaster: { ...input.nativeRaster },\n activeCaptureRect: { ...activeCaptureRect },\n nominalExposureDurationSeconds,\n opening: resolveBoundary(actuators.opening, opening),\n closing: resolveBoundary(actuators.closing, closing),\n localExposureDurationRangeSeconds: {\n minimum: minimumLocalExposureDurationSeconds,\n maximum: maximumLocalExposureDurationSeconds\n },\n samples\n },\n \"capture-exposure-window-schedule\",\n \"1.0.0\",\n [\n \"Time is reported in seconds from the first opening-boundary phase, not from a universal local exposure-start event.\",\n \"Nominal exposure duration, opening traversal, and closing traversal are independent declared quantities.\",\n \"Mechanical, electronic-first-curtain, and electronic mechanisms determine boundary actuator categories only; they do not supply timing or direction.\",\n \"Uniform-linear scan boundaries are an explicit approximation over continuous effective native-raster coordinates and do not assert one raster row/column equals one physical curtain line, photodiode row, or hardware readout line.\",\n \"Opening and closing directions and traversal durations are independent and may produce spatially varying local exposure duration.\",\n \"The complete active rectangle is validated using the extrema of the affine boundary schedules, so unsampled invalid corners cannot pass.\",\n \"Active-capture timing is caller-declared and is not inferred by scaling full-frame timing from crop dimensions.\",\n \"Physical camera orientation and digital output crop/resolution remain downstream of the invariant native timing schedule.\",\n \"Sensor readout timing remains a separate contract; this model does not automatically equate a sensor readout phase with an exposure boundary.\",\n \"This model does not include curtain acceleration, segmented/nonlinear electronic timing, rolling-shutter image distortion, motion integration, flash/flicker interaction, shutter shock, or EFCS-specific pupil/bokeh behavior.\"\n ]\n );\n}\n\n/**\n * Internal, for the faster (\"lean\") native environment executor (a 2.0 performance change). It checks a\n * schedule once with every check of calculateCaptureExposureWindows, then returns a per-point sampler that\n * uses the same point validation and the same createWindowSample arithmetic. So each sampled window is\n * identical to what calculateCaptureExposureWindows returns for that point. Not exported from the package.\n */\nexport function prepareCaptureExposureWindowSampler(\n input: Omit<CalculateCaptureExposureWindowsInput, \"samplePointsNative\">\n): (point: RasterPoint) => CaptureExposureWindowSample {\n const admitted = calculateCaptureExposureWindows({ ...input, samplePointsNative: [] }).value;\n const rect = { ...admitted.activeCaptureRect };\n const opening = parseBoundarySchedule(input.opening, \"opening\");\n const closing = parseBoundarySchedule(input.closing, \"closing\");\n const nominal = admitted.nominalExposureDurationSeconds.value;\n return (point) => {\n validatePointWithinActiveCapture(point, rect, \"samplePointsNative[0]\");\n return createWindowSample(point, rect, opening, closing, nominal);\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"exposure-window.js","sourceRoot":"","sources":["../../src/sensor/exposure-window.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Exposure windows: when each point on the sensor starts and stops collecting light.\n *\n * The shutter has an opening boundary and a closing boundary, each with its own schedule. With a\n * rolling or curtain shutter the boundaries sweep across the sensor, so each point has its own\n * (local) window. Times are seconds from the first opening boundary. That is deliberately not the\n * same as the camera-rotation API's global \"exposure start\"; a later layer must link the two\n * explicitly.\n *\n * The mechanism records whether each boundary is mechanical or electronic, but supplies no sweep\n * duration or direction. Every timing value and direction for a non-simultaneous boundary is declared\n * by the caller, with evidence. Only simultaneous boundaries and a uniform linear sweep along one\n * native sensor axis are supported. Not modelled: curtain acceleration, segmented or non-linear\n * electronic schedules, flash or flicker, rolling-shutter image distortion, shutter shock, and\n * EFCS (electronic first-curtain shutter) effects on the pupil or bokeh.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact\n} from \"../core/evidence-provenance.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveInteger\n} from \"../core/validation.js\";\nimport type {\n RasterPoint,\n RasterRect,\n RasterVector\n} from \"../output/capture-geometry.js\";\nimport type { NativeImageRaster } from \"./sensor-geometry.js\";\nimport type {\n CaptureShutterMechanism,\n NativeSensorReadoutScanDirection,\n SourcedSensorTimingSeconds\n} from \"./readout-timing.js\";\nimport { requireInputObject, requireInputKeys, requireConfigRecord as requireRecord } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { fullRect } from \"../core/shared-helpers.js\";\n\nexport type CaptureExposureBoundaryActuator = \"mechanical\" | \"electronic\";\n\nexport type SourcedCaptureTimingSeconds = SourcedSensorTimingSeconds;\n\nexport type SourcedCaptureBoundaryDirection =\n EvidenceBackedFact<NativeSensorReadoutScanDirection>;\n\nexport interface SimultaneousExposureBoundarySchedule {\n kind: \"simultaneous\";\n directionNative?: never;\n traversalDurationSeconds?: never;\n}\n\nexport interface UniformLinearExposureBoundarySchedule {\n kind: \"uniform-linear-native-scan\";\n directionNative: SourcedCaptureBoundaryDirection;\n traversalDurationSeconds: SourcedCaptureTimingSeconds;\n}\n\nexport type ExposureBoundarySchedule =\n | SimultaneousExposureBoundarySchedule\n | UniformLinearExposureBoundarySchedule;\n\nexport interface CalculateCaptureExposureWindowsInput {\n nativeRaster: NativeImageRaster;\n /**\n * Active capture rectangle, in fixed native pixel coordinates.\n *\n * Defaults to the whole native image. Sweep timing is never worked out by\n * scaling a full-frame timing value to this rectangle.\n */\n activeCaptureRect?: RasterRect;\n shutterMechanism: CaptureShutterMechanism;\n /**\n * Nominal time between the opening and closing reference points (phase zero).\n *\n * A point's local exposure can differ from this when the opening and closing\n * boundaries sweep differently across the sensor.\n */\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n opening: ExposureBoundarySchedule;\n closing: ExposureBoundarySchedule;\n /**\n * Optional points, in continuous native pixel coordinates measured from pixel\n * edges, to report windows for. A pixel centre is at +0.5.\n */\n samplePointsNative?: readonly RasterPoint[];\n}\n\nexport interface ResolvedCaptureExposureBoundary {\n actuator: CaptureExposureBoundaryActuator;\n schedule:\n | {\n kind: \"simultaneous\";\n }\n | {\n kind: \"uniform-linear-native-scan\";\n directionNative: SourcedCaptureBoundaryDirection;\n unitVectorNative: RasterVector;\n traversalDurationSeconds: SourcedCaptureTimingSeconds;\n };\n}\n\nexport interface CaptureExposureWindowSample {\n pointNative: RasterPoint;\n openingNormalizedScanPosition: number | null;\n closingNormalizedScanPosition: number | null;\n /**\n * Seconds from the first opening boundary.\n */\n startOffsetSecondsFromOpeningReference: number;\n /**\n * Seconds from the first opening boundary.\n */\n endOffsetSecondsFromOpeningReference: number;\n localExposureDurationSeconds: number;\n}\n\nexport interface CaptureExposureWindows {\n shutterMechanism: CaptureShutterMechanism;\n timeReference: \"first-opening-boundary-phase\";\n nativeRaster: NativeImageRaster;\n activeCaptureRect: RasterRect;\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n opening: ResolvedCaptureExposureBoundary;\n closing: ResolvedCaptureExposureBoundary;\n localExposureDurationRangeSeconds: {\n minimum: number;\n maximum: number;\n };\n samples: readonly CaptureExposureWindowSample[];\n}\n\nconst SHUTTER_MECHANISMS = new Set<CaptureShutterMechanism>([\n \"mechanical\",\n \"electronic-first-curtain\",\n \"electronic\"\n]);\n\nconst SCAN_DIRECTIONS = new Set<NativeSensorReadoutScanDirection>([\n \"top-to-bottom\",\n \"bottom-to-top\",\n \"left-to-right\",\n \"right-to-left\"\n]);\n\nconst BOUNDARY_KINDS = new Set<ExposureBoundarySchedule[\"kind\"]>([\n \"simultaneous\",\n \"uniform-linear-native-scan\"\n]);\n\nexport function requireNonNegativeInteger(name: string, value: number): void {\n if (!Number.isSafeInteger(value) || value < 0) {\n throw new InvalidScientificInputError(\n name + \" must be a non-negative safe integer.\"\n );\n }\n}\n\nexport function requirePositiveFinite(name: string, value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) {\n throw new InvalidScientificInputError(\n name + \" must be a finite number greater than zero.\"\n );\n }\n return value;\n}\n\n/** Parses a sourced duration in seconds (`{ unit: \"s\", value, evidence }`). Not a root export. */\nexport function parseSecondsFact(\n value: unknown,\n path: string\n): SourcedCaptureTimingSeconds {\n const record = requireRecord(value, path, [\"unit\", \"value\", \"evidence\"]);\n if (record.unit !== \"s\") {\n throw new InvalidConfigurationError(path + '.unit must be \"s\".');\n }\n\n return {\n value: requirePositiveFinite(path + \".value\", record.value),\n unit: \"s\",\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseDirectionFact(\n value: unknown,\n path: string\n): SourcedCaptureBoundaryDirection {\n const record = requireRecord(value, path, [\"value\", \"evidence\"]);\n if (\n typeof record.value !== \"string\" ||\n !SCAN_DIRECTIONS.has(\n record.value as NativeSensorReadoutScanDirection\n )\n ) {\n throw new InvalidConfigurationError(path + \".value is invalid.\");\n }\n\n return {\n value: record.value as NativeSensorReadoutScanDirection,\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\n/** Parses one exposure boundary schedule (opening or closing). Not a root export. */\nexport function parseBoundarySchedule(\n value: unknown,\n path: string\n): ExposureBoundarySchedule {\n // unitVectorNative is the derived field of a resolved schedule; it may be passed back if it matches.\n const record = requireRecord(value, path, [\"kind\", \"directionNative\", \"traversalDurationSeconds\", \"unitVectorNative\"]);\n if (\n typeof record.kind !== \"string\" ||\n !BOUNDARY_KINDS.has(record.kind as ExposureBoundarySchedule[\"kind\"])\n ) {\n throw new InvalidConfigurationError(path + \".kind is invalid.\");\n }\n\n if (record.kind === \"simultaneous\") {\n if (record.directionNative !== undefined) {\n throw new InvalidScientificInputError(\n path + \".directionNative must be omitted for a simultaneous boundary.\"\n );\n }\n if (record.traversalDurationSeconds !== undefined) {\n throw new InvalidScientificInputError(\n path +\n \".traversalDurationSeconds must be omitted for a simultaneous boundary.\"\n );\n }\n if (record.unitVectorNative !== undefined) {\n throw new InvalidScientificInputError(\n path + \".unitVectorNative must be omitted for a simultaneous boundary.\"\n );\n }\n\n return { kind: \"simultaneous\" };\n }\n\n const directionNative = parseDirectionFact(\n record.directionNative,\n path + \".directionNative\"\n );\n if (record.unitVectorNative !== undefined) {\n const supplied = requireRecord(record.unitVectorNative, path + \".unitVectorNative\", [\"x\", \"y\"]);\n const derived = scanVector(directionNative.value);\n if (supplied.x !== derived.x || supplied.y !== derived.y) {\n throw new InvalidConfigurationError(path + \".unitVectorNative must match directionNative.\");\n }\n }\n return {\n kind: \"uniform-linear-native-scan\",\n directionNative,\n traversalDurationSeconds: parseSecondsFact(\n record.traversalDurationSeconds,\n path + \".traversalDurationSeconds\"\n )\n };\n}\n\nexport function validateNativeRaster(raster: NativeImageRaster): void {\n requireInputObject(raster, \"raster input\");\n requirePositiveInteger(\"nativeRaster.pixelWidth\", raster.pixelWidth);\n requirePositiveInteger(\"nativeRaster.pixelHeight\", raster.pixelHeight);\n}\n\nexport function validateActiveCaptureRect(\n rect: RasterRect,\n raster: NativeImageRaster\n): void {\n requireNonNegativeInteger(\"activeCaptureRect.x\", rect.x);\n requireNonNegativeInteger(\"activeCaptureRect.y\", rect.y);\n requirePositiveInteger(\"activeCaptureRect.width\", rect.width);\n requirePositiveInteger(\"activeCaptureRect.height\", rect.height);\n\n if (\n rect.x + rect.width > raster.pixelWidth ||\n rect.y + rect.height > raster.pixelHeight\n ) {\n throw new InvalidScientificInputError(\n \"activeCaptureRect must fit entirely within nativeRaster.\"\n );\n }\n}\n\nexport function validateShutterMechanism(\n value: unknown\n): asserts value is CaptureShutterMechanism {\n if (\n typeof value !== \"string\" ||\n !SHUTTER_MECHANISMS.has(value as CaptureShutterMechanism)\n ) {\n throw new InvalidConfigurationError(\"shutterMechanism is invalid.\");\n }\n}\n\nfunction validatePointWithinActiveCapture(\n point: RasterPoint,\n rect: RasterRect,\n path: string\n): void {\n if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {\n throw new InvalidScientificInputError(\n path + \" coordinates must be finite.\"\n );\n }\n\n if (\n point.x < rect.x ||\n point.x > rect.x + rect.width ||\n point.y < rect.y ||\n point.y > rect.y + rect.height\n ) {\n throw new InvalidScientificInputError(\n path + \" must lie within activeCaptureRect edge-coordinate bounds.\"\n );\n }\n}\n\nexport function scanVector(\n direction: NativeSensorReadoutScanDirection\n): RasterVector {\n switch (direction) {\n case \"top-to-bottom\":\n return { x: 0, y: 1 };\n case \"bottom-to-top\":\n return { x: 0, y: -1 };\n case \"left-to-right\":\n return { x: 1, y: 0 };\n case \"right-to-left\":\n return { x: -1, y: 0 };\n }\n}\n\nexport function normalizedScanPosition(\n point: RasterPoint,\n rect: RasterRect,\n direction: NativeSensorReadoutScanDirection\n): number {\n switch (direction) {\n case \"top-to-bottom\":\n return (point.y - rect.y) / rect.height;\n case \"bottom-to-top\":\n return 1 - (point.y - rect.y) / rect.height;\n case \"left-to-right\":\n return (point.x - rect.x) / rect.width;\n case \"right-to-left\":\n return 1 - (point.x - rect.x) / rect.width;\n }\n}\n\nfunction boundaryPhase(\n point: RasterPoint,\n rect: RasterRect,\n schedule: ExposureBoundarySchedule\n): {\n normalizedScanPosition: number | null;\n phaseOffsetSeconds: number;\n} {\n if (schedule.kind === \"simultaneous\") {\n return {\n normalizedScanPosition: null,\n phaseOffsetSeconds: 0\n };\n }\n\n const normalized = normalizedScanPosition(\n point,\n rect,\n schedule.directionNative.value\n );\n\n return {\n normalizedScanPosition: normalized,\n phaseOffsetSeconds:\n normalized * schedule.traversalDurationSeconds.value\n };\n}\n\nfunction resolveBoundaryActuators(\n mechanism: CaptureShutterMechanism\n): {\n opening: CaptureExposureBoundaryActuator;\n closing: CaptureExposureBoundaryActuator;\n} {\n switch (mechanism) {\n case \"mechanical\":\n return {\n opening: \"mechanical\",\n closing: \"mechanical\"\n };\n case \"electronic-first-curtain\":\n return {\n opening: \"electronic\",\n closing: \"mechanical\"\n };\n case \"electronic\":\n return {\n opening: \"electronic\",\n closing: \"electronic\"\n };\n }\n}\n\nfunction resolveBoundary(\n actuator: CaptureExposureBoundaryActuator,\n schedule: ExposureBoundarySchedule\n): ResolvedCaptureExposureBoundary {\n if (schedule.kind === \"simultaneous\") {\n return {\n actuator,\n schedule: {\n kind: \"simultaneous\"\n }\n };\n }\n\n return {\n actuator,\n schedule: {\n kind: \"uniform-linear-native-scan\",\n directionNative: schedule.directionNative,\n unitVectorNative: scanVector(schedule.directionNative.value),\n traversalDurationSeconds: schedule.traversalDurationSeconds\n }\n };\n}\n\nfunction createWindowSample(\n point: RasterPoint,\n rect: RasterRect,\n opening: ExposureBoundarySchedule,\n closing: ExposureBoundarySchedule,\n nominalExposureDurationSeconds: number\n): CaptureExposureWindowSample {\n const openingPhase = boundaryPhase(point, rect, opening);\n const closingPhase = boundaryPhase(point, rect, closing);\n const startOffsetSecondsFromOpeningReference =\n openingPhase.phaseOffsetSeconds;\n const endOffsetSecondsFromOpeningReference =\n nominalExposureDurationSeconds + closingPhase.phaseOffsetSeconds;\n const localExposureDurationSeconds =\n endOffsetSecondsFromOpeningReference -\n startOffsetSecondsFromOpeningReference;\n\n return {\n pointNative: { ...point },\n openingNormalizedScanPosition:\n openingPhase.normalizedScanPosition,\n closingNormalizedScanPosition:\n closingPhase.normalizedScanPosition,\n startOffsetSecondsFromOpeningReference,\n endOffsetSecondsFromOpeningReference,\n localExposureDurationSeconds\n };\n}\n\nfunction activeRectCorners(rect: RasterRect): readonly RasterPoint[] {\n return [\n { x: rect.x, y: rect.y },\n { x: rect.x + rect.width, y: rect.y },\n { x: rect.x, y: rect.y + rect.height },\n {\n x: rect.x + rect.width,\n y: rect.y + rect.height\n }\n ];\n}\n\n/**\n * Works out each point's local exposure window from separate opening and\n * closing boundary schedules.\n *\n * Times are seconds from the first opening boundary. This is deliberately not\n * the camera-rotation API's global \"exposure start\"; a later layer must link the\n * two explicitly.\n *\n * The mechanism records whether each boundary is mechanical or electronic, but\n * supplies no sweep duration or direction. Every timing value and direction for\n * a non-simultaneous boundary is declared by the caller, with evidence.\n *\n * Only simultaneous boundaries and a uniform linear sweep along one native\n * sensor axis are supported. Not modelled: curtain acceleration, segmented or\n * non-linear electronic schedules, flash or flicker, rolling-shutter image\n * distortion, shutter shock, and EFCS (electronic first-curtain shutter)\n * effects on the pupil or bokeh.\n */\nexport function calculateCaptureExposureWindows(\n input: CalculateCaptureExposureWindowsInput\n): CalculationResult<CaptureExposureWindows> {\n requireInputKeys(input, \"calculateCaptureExposureWindows\", [\"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"samplePointsNative\"]);\n validateNativeRaster(input.nativeRaster);\n validateShutterMechanism(input.shutterMechanism);\n\n const nominalExposureDurationSeconds = parseSecondsFact(\n input.nominalExposureDurationSeconds,\n \"nominalExposureDurationSeconds\"\n );\n const opening = parseBoundarySchedule(input.opening, \"opening\");\n const closing = parseBoundarySchedule(input.closing, \"closing\");\n\n const activeCaptureRect =\n input.activeCaptureRect ?? fullRect(input.nativeRaster);\n validateActiveCaptureRect(activeCaptureRect, input.nativeRaster);\n\n if (\n input.samplePointsNative !== undefined &&\n !Array.isArray(input.samplePointsNative)\n ) {\n throw new InvalidConfigurationError(\n \"samplePointsNative must be an array when supplied.\"\n );\n }\n\n const cornerSamples = activeRectCorners(activeCaptureRect).map(\n (point) =>\n createWindowSample(\n point,\n activeCaptureRect,\n opening,\n closing,\n nominalExposureDurationSeconds.value\n )\n );\n\n const localDurations = cornerSamples.map(\n (sample) => sample.localExposureDurationSeconds\n );\n const minimumLocalExposureDurationSeconds = Math.min(...localDurations);\n const maximumLocalExposureDurationSeconds = Math.max(...localDurations);\n\n if (\n !Number.isFinite(minimumLocalExposureDurationSeconds) ||\n minimumLocalExposureDurationSeconds <= 0\n ) {\n throw new InvalidScientificInputError(\n \"The declared opening/closing schedules produce a zero or negative local exposure duration somewhere within activeCaptureRect.\"\n );\n }\n\n const samples = (input.samplePointsNative ?? []).map((point, index) => {\n validatePointWithinActiveCapture(\n point,\n activeCaptureRect,\n \"samplePointsNative[\" + index + \"]\"\n );\n\n return createWindowSample(\n point,\n activeCaptureRect,\n opening,\n closing,\n nominalExposureDurationSeconds.value\n );\n });\n\n const actuators = resolveBoundaryActuators(input.shutterMechanism);\n\n return approximationResult(\n {\n shutterMechanism: input.shutterMechanism,\n timeReference: \"first-opening-boundary-phase\",\n nativeRaster: { ...input.nativeRaster },\n activeCaptureRect: { ...activeCaptureRect },\n nominalExposureDurationSeconds,\n opening: resolveBoundary(actuators.opening, opening),\n closing: resolveBoundary(actuators.closing, closing),\n localExposureDurationRangeSeconds: {\n minimum: minimumLocalExposureDurationSeconds,\n maximum: maximumLocalExposureDurationSeconds\n },\n samples\n },\n \"capture-exposure-window-schedule\",\n \"1.0.0\",\n [\n \"Time is reported in seconds from the first opening-boundary phase, not from a universal local exposure-start event.\",\n \"Nominal exposure duration, opening traversal, and closing traversal are independent declared quantities.\",\n \"Mechanical, electronic-first-curtain, and electronic mechanisms determine boundary actuator categories only; they do not supply timing or direction.\",\n \"Uniform-linear scan boundaries are an explicit approximation over continuous effective native-raster coordinates and do not assert one raster row/column equals one physical curtain line, photodiode row, or hardware readout line.\",\n \"Opening and closing directions and traversal durations are independent and may produce spatially varying local exposure duration.\",\n \"The complete active rectangle is validated using the extrema of the affine boundary schedules, so unsampled invalid corners cannot pass.\",\n \"Active-capture timing is caller-declared and is not inferred by scaling full-frame timing from crop dimensions.\",\n \"Physical camera orientation and digital output crop/resolution remain downstream of the invariant native timing schedule.\",\n \"Sensor readout timing remains a separate contract; this model does not automatically equate a sensor readout phase with an exposure boundary.\",\n \"This model does not include curtain acceleration, segmented/nonlinear electronic timing, rolling-shutter image distortion, motion integration, flash/flicker interaction, shutter shock, or EFCS-specific pupil/bokeh behavior.\"\n ]\n );\n}\n\n/**\n * Internal, for the faster (\"lean\") native environment executor (a 2.0 performance change). It checks a\n * schedule once with every check of calculateCaptureExposureWindows, then returns a per-point sampler that\n * uses the same point validation and the same createWindowSample arithmetic. So each sampled window is\n * identical to what calculateCaptureExposureWindows returns for that point. Not exported from the package.\n */\nexport function prepareCaptureExposureWindowSampler(\n input: Omit<CalculateCaptureExposureWindowsInput, \"samplePointsNative\">\n): (point: RasterPoint) => CaptureExposureWindowSample {\n const admitted = calculateCaptureExposureWindows({ ...input, samplePointsNative: [] }).value;\n const rect = { ...admitted.activeCaptureRect };\n const opening = parseBoundarySchedule(input.opening, \"opening\");\n const closing = parseBoundarySchedule(input.closing, \"closing\");\n const nominal = admitted.nominalExposureDurationSeconds.value;\n return (point) => {\n validatePointWithinActiveCapture(point, rect, \"samplePointsNative[0]\");\n return createWindowSample(point, rect, opening, closing, nominal);\n };\n}\n"]}
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { approximationResult } from "../core/calculation-result.js";
|
|
18
18
|
import { parseEvidenceList } from "../core/evidence-provenance.js";
|
|
19
|
-
import { InvalidScientificInputError
|
|
19
|
+
import { InvalidScientificInputError } from "../core/validation.js";
|
|
20
20
|
import { requireInputKeys, requireConfigRecord as requireRecord } from "../core/record-validation.js";
|
|
21
21
|
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
22
22
|
import { fullRect } from "../core/shared-helpers.js";
|
|
23
|
-
import { normalizedScanPosition, validateNativeRaster, scanVector, requirePositiveFinite,
|
|
23
|
+
import { normalizedScanPosition, validateNativeRaster, scanVector, requirePositiveFinite, validateActiveCaptureRect, validateShutterMechanism } from "./exposure-window.js";
|
|
24
24
|
const READOUT_MODES = new Set(["rolling", "global"]);
|
|
25
25
|
const SCAN_DIRECTIONS = new Set([
|
|
26
26
|
"top-to-bottom",
|
|
@@ -28,11 +28,6 @@ const SCAN_DIRECTIONS = new Set([
|
|
|
28
28
|
"left-to-right",
|
|
29
29
|
"right-to-left"
|
|
30
30
|
]);
|
|
31
|
-
const SHUTTER_MECHANISMS = new Set([
|
|
32
|
-
"mechanical",
|
|
33
|
-
"electronic-first-curtain",
|
|
34
|
-
"electronic"
|
|
35
|
-
]);
|
|
36
31
|
function requireNonNegativeFinite(name, value) {
|
|
37
32
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
38
33
|
throw new InvalidScientificInputError(name + " must be a finite number greater than or equal to zero.");
|
|
@@ -90,22 +85,6 @@ function parseReadoutDeclaration(value) {
|
|
|
90
85
|
spatialSamplingSkewSeconds: parseSecondsFact(record.spatialSamplingSkewSeconds, "readout.spatialSamplingSkewSeconds", true)
|
|
91
86
|
};
|
|
92
87
|
}
|
|
93
|
-
function validateActiveCaptureRect(rect, raster) {
|
|
94
|
-
requireNonNegativeInteger("activeCaptureRect.x", rect.x);
|
|
95
|
-
requireNonNegativeInteger("activeCaptureRect.y", rect.y);
|
|
96
|
-
requirePositiveInteger("activeCaptureRect.width", rect.width);
|
|
97
|
-
requirePositiveInteger("activeCaptureRect.height", rect.height);
|
|
98
|
-
if (rect.x + rect.width > raster.pixelWidth ||
|
|
99
|
-
rect.y + rect.height > raster.pixelHeight) {
|
|
100
|
-
throw new InvalidScientificInputError("activeCaptureRect must fit entirely within nativeRaster.");
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
function validateShutterMechanism(value) {
|
|
104
|
-
if (typeof value !== "string" ||
|
|
105
|
-
!SHUTTER_MECHANISMS.has(value)) {
|
|
106
|
-
throw new InvalidConfigurationError("shutterMechanism is invalid.");
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
88
|
function validatePointWithinActiveCapture(point, rect, index) {
|
|
110
89
|
if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {
|
|
111
90
|
throw new InvalidScientificInputError("samplePointsNative[" + index + "] coordinates must be finite.");
|