@photivra/engine 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +86 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/dist/capture/native-environment-lean.d.ts +35 -5
  6. package/dist/capture/native-environment-lean.js +175 -16
  7. package/dist/capture/native-environment-lean.js.map +1 -1
  8. package/dist/capture/native-environment-raw.d.ts +9 -1
  9. package/dist/capture/native-environment-raw.js +14 -9
  10. package/dist/capture/native-environment-raw.js.map +1 -1
  11. package/dist/capture/native-environment-tabulated.d.ts +58 -0
  12. package/dist/capture/native-environment-tabulated.js +91 -0
  13. package/dist/capture/native-environment-tabulated.js.map +1 -0
  14. package/dist/core/internal-radiance-evaluator.d.ts +28 -0
  15. package/dist/core/internal-radiance-evaluator.js +30 -0
  16. package/dist/core/internal-radiance-evaluator.js.map +1 -1
  17. package/dist/core/version.d.ts +1 -1
  18. package/dist/core/version.js +1 -1
  19. package/dist/core/version.js.map +1 -1
  20. package/dist/facade/diffraction-provider.js +30 -2
  21. package/dist/facade/diffraction-provider.js.map +1 -1
  22. package/dist/facade/display-view.d.ts +14 -83
  23. package/dist/facade/display-view.js +331 -846
  24. package/dist/facade/display-view.js.map +1 -1
  25. package/dist/facade/expected-signal-setup.d.ts +65 -0
  26. package/dist/facade/expected-signal-setup.js +56 -0
  27. package/dist/facade/expected-signal-setup.js.map +1 -0
  28. package/dist/facade/expected-signal.d.ts +83 -0
  29. package/dist/facade/expected-signal.js +571 -0
  30. package/dist/facade/expected-signal.js.map +1 -0
  31. package/dist/facade/scene-radiance.d.ts +6 -0
  32. package/dist/facade/scene-radiance.js +31 -10
  33. package/dist/facade/scene-radiance.js.map +1 -1
  34. package/dist/facade/simulate-capture.js +65 -16
  35. package/dist/facade/simulate-capture.js.map +1 -1
  36. package/dist/facade/simulate-display-view.js +26 -49
  37. package/dist/facade/simulate-display-view.js.map +1 -1
  38. package/docs/API_REFERENCE.md +1 -1
  39. package/docs/QUICK_START.md +1 -1
  40. package/docs/api/core-version.md +1 -1
  41. package/docs/api/exports.json +2 -2
  42. package/package.json +1 -1
@@ -0,0 +1,65 @@
1
+ import { resolveLensPsfConvolutionKernels } from "../optics/lens-psf-kernels.js";
2
+ import type { DisplayViewExpectedSignalInput } from "./display-view.js";
3
+ type Psf = Parameters<typeof resolveLensPsfConvolutionKernels>[0]["psf"];
4
+ export interface ExpectedSignalSetupInput {
5
+ nativeWidth: number;
6
+ nativeHeight: number;
7
+ /** Frame output width (plane coordinates are 2 / width frame units per site). */
8
+ width: number;
9
+ wavelengths: readonly number[];
10
+ /** Per CFA channel (red, green, blue): expected electrons per unit spectral radiance at each node. */
11
+ perNode: readonly [Float64Array, Float64Array, Float64Array];
12
+ /** Pixel-aperture nodes in sites from the site centre (`apertureNodeOffsets(boxSamples)`) and the box sample count. */
13
+ apertureNodes: readonly {
14
+ x: number;
15
+ y: number;
16
+ }[];
17
+ boxSamples: number;
18
+ antiAliasing?: {
19
+ components: readonly {
20
+ offsetMicrometers: {
21
+ x: number;
22
+ y: number;
23
+ };
24
+ normalizedWeight: number;
25
+ }[];
26
+ } | undefined;
27
+ pitch: number;
28
+ /** Object-space diffraction: the kernel radius, the PSF and its profile wavelengths. */
29
+ diffraction?: {
30
+ radius: number;
31
+ psf: Psf;
32
+ profileWavelengths: readonly number[];
33
+ } | undefined;
34
+ /** Material scenes: the materials (neutral reflectances, or radiance per wavelength), the material lookup and the white radiance. */
35
+ materials?: {
36
+ list: readonly ({
37
+ neutral: number;
38
+ } | {
39
+ radiance: (nm: number) => number;
40
+ })[];
41
+ materialAt(u: number, v: number): number;
42
+ whiteRadiance(nm: number): number;
43
+ } | undefined;
44
+ radianceAt(u: number, v: number, nm: number): number;
45
+ imagingArea: {
46
+ widthMm: number;
47
+ heightMm: number;
48
+ };
49
+ focalLengthMm: number;
50
+ focusDistanceM: number;
51
+ subjectDistanceM: number;
52
+ metresPerFrameUnit: number;
53
+ /** Prefix of the error messages ("simulateDisplayView", "simulateCapture"). */
54
+ caller: string;
55
+ }
56
+ /** The query offsets of each site and the expected-signal input (see the module comment). */
57
+ export declare function buildExpectedSignalInput(input: ExpectedSignalSetupInput): {
58
+ offsets: {
59
+ x: number;
60
+ y: number;
61
+ weight: number;
62
+ }[];
63
+ signalInput: DisplayViewExpectedSignalInput;
64
+ };
65
+ export {};
@@ -0,0 +1,56 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Internal (#394, not exported from the package): the input of the tabulated expected signal (`createDisplayViewExpectedSignal`)
4
+ * for a resolved `simulateCapture` request, shared by the display view and the capture's tabulated RAW path so both build it
5
+ * the same way: query offsets of each site, the environment's query points, the diffraction kernels and the scene tables.
6
+ */
7
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
8
+ import { resolveLensPsfConvolutionKernels } from "../optics/lens-psf-kernels.js";
9
+ import { resolveProjectionDistanceMm } from "../motion/camera-rotation.js";
10
+ /** The query offsets of each site and the expected-signal input (see the module comment). */
11
+ export function buildExpectedSignalInput(input) {
12
+ const { boxSamples, pitch, wavelengths, imagingArea, subjectDistanceM, metresPerFrameUnit } = input;
13
+ // Query offsets of each site: pixel-aperture nodes minus anti-aliasing split offsets (sites, +x right, +y down), as the environment forms them.
14
+ const spots = input.antiAliasing ? input.antiAliasing.components : [{ offsetMicrometers: { x: 0, y: 0 }, normalizedWeight: 1 }];
15
+ const offsets = input.apertureNodes.flatMap((node) => spots.map((c) => ({ x: node.x - c.offsetMicrometers.x / pitch, y: node.y - c.offsetMicrometers.y / pitch,
16
+ weight: c.normalizedWeight / (boxSamples * boxSamples) })));
17
+ // Diffraction: the same per-node site kernels as the object-space convolution (createDiffractedPlanarProvider).
18
+ let kernels;
19
+ const diffraction = input.diffraction;
20
+ if (diffraction) {
21
+ const radius = diffraction.radius;
22
+ kernels = { radius, perNode: wavelengths.map((wavelengthNm) => {
23
+ if (!diffraction.profileWavelengths.includes(wavelengthNm))
24
+ throw new InvalidConfigurationError(input.caller + ": a spectral node has no diffraction kernel.");
25
+ const k = resolveLensPsfConvolutionKernels({ schemaVersion: "0.1.0", psf: diffraction.psf, spectralWeights: [{ wavelengthNm, weight: 1 }],
26
+ grid: { width: 1, height: 1, sitePitchMicrometers: { x: pitch, y: pitch }, opticalAxisSite: { x: 0.5, y: 0.5 } }, patchWidth: 1, patchHeight: 1, kernelRadius: radius }).value;
27
+ if (!k.exactSiteSampling || k.kernelRadius !== radius)
28
+ throw new InvalidConfigurationError(input.caller + ": diffraction kernels must sample the site pitch exactly.");
29
+ return Float64Array.from(k.kernels);
30
+ }) };
31
+ }
32
+ // Chart presets and (2.7) caller material scenes take the material path: each material tabulated once per node.
33
+ const materials = input.materials;
34
+ const scene = materials ? { kind: "materials", materialAt: materials.materialAt,
35
+ materials: materials.list.map((m) => ("neutral" in m ? { neutral: m.neutral } : { radiance: Float64Array.from(wavelengths, (nm) => m.radiance(nm)) })),
36
+ whiteRadiance: Float64Array.from(wavelengths, (nm) => materials.whiteRadiance(nm)) } :
37
+ { kind: "radiance", radianceAt: input.radianceAt };
38
+ const channelIndex = (x, y) => ((y & 1) === 0 ? ((x & 1) === 0 ? 0 : 1) : ((x & 1) === 0 ? 1 : 2));
39
+ // Without diffraction the environment's own query arithmetic (native-environment-lean.ts `geometry`, then the rectilinear
40
+ // still-camera ray and the planar provider's plane point), so boundary points resolve as in the capture.
41
+ const projectionDistance = resolveProjectionDistanceMm(input.focalLengthMm, input.focusDistanceM).projectionDistanceMm, siteWidthMm = pitch / 1000;
42
+ const nodePhases = input.apertureNodes.flatMap((_, index) => spots.map((c) => ({ xPhase: (index % boxSamples + 0.5) / boxSamples, yPhase: (Math.floor(index / boxSamples) + 0.5) / boxSamples,
43
+ aaX: c.offsetMicrometers.x / 1000, aaY: c.offsetMicrometers.y / 1000 })));
44
+ const queryPoint = (x, y, o, out) => {
45
+ const node = nodePhases[o];
46
+ const centerX = -imagingArea.widthMm / 2 + (pitch / 2 + x * pitch + 0) / 1000, centerY = -imagingArea.heightMm / 2 + (pitch / 2 + y * pitch + 0) / 1000;
47
+ const sourceX = (centerX - siteWidthMm / 2 + node.xPhase * siteWidthMm) - node.aaX, sourceY = (centerY - siteWidthMm / 2 + node.yPhase * siteWidthMm) - node.aaY;
48
+ const rx = projectionDistance * (sourceX / projectionDistance), ry = projectionDistance * (-sourceY / projectionDistance);
49
+ const length = Math.hypot(rx, ry, projectionDistance), t = subjectDistanceM / (projectionDistance / length);
50
+ out[0] = rx / length * t / metresPerFrameUnit;
51
+ out[1] = ry / length * t / metresPerFrameUnit;
52
+ };
53
+ return { offsets, signalInput: { nativeWidth: input.nativeWidth, nativeHeight: input.nativeHeight, outputWidth: input.width, wavelengthsNm: wavelengths,
54
+ electronsPerRadiance: input.perNode, offsets, ...(kernels ? { kernels } : { queryPoint }), scene, channelAt: channelIndex } };
55
+ }
56
+ //# sourceMappingURL=expected-signal-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expected-signal-setup.js","sourceRoot":"","sources":["../../src/facade/expected-signal-setup.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;GAIG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAgC3E,6FAA6F;AAC7F,MAAM,UAAU,wBAAwB,CAAC,KAA+B;IACtE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,GAAG,KAAK,CAAC;IACpG,gJAAgJ;IAChJ,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC;IAChI,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC,GAAG,KAAK;QAC5J,MAAM,EAAE,CAAC,CAAC,gBAAgB,GAAG,CAAC,UAAU,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9D,gHAAgH;IAChH,IAAI,OAAgE,CAAC;IACrE,MAAM,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACtC,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;QAClC,OAAO,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE;gBAC5D,IAAI,CAAC,WAAW,CAAC,kBAAkB,CAAC,QAAQ,CAAC,YAAY,CAAC;oBAAE,MAAM,IAAI,yBAAyB,CAAC,KAAK,CAAC,MAAM,GAAG,8CAA8C,CAAC,CAAC;gBAC/J,MAAM,CAAC,GAAG,gCAAgC,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,eAAe,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;oBACvI,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,oBAAoB,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,eAAe,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC;gBACjL,IAAI,CAAC,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC,YAAY,KAAK,MAAM;oBAAE,MAAM,IAAI,yBAAyB,CAAC,KAAK,CAAC,MAAM,GAAG,2DAA2D,CAAC,CAAC;gBACvK,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACtC,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IACD,gHAAgH;IAChH,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IAClC,MAAM,KAAK,GAAqB,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,CAAC,UAAU;QAC/F,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QACtJ,aAAa,EAAE,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QACtF,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC;IACrD,MAAM,YAAY,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3H,0HAA0H;IAC1H,yGAAyG;IACzG,MAAM,kBAAkB,GAAG,2BAA2B,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,oBAAoB,EAAE,WAAW,GAAG,KAAK,GAAG,IAAI,CAAC;IACnJ,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,GAAG,UAAU,GAAG,GAAG,CAAC,GAAG,UAAU,EAAE,MAAM,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,UAAU;QAC3L,GAAG,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,UAAU,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,GAAiB,EAAQ,EAAE;QAC9E,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,CAAC,WAAW,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,GAAG,CAAC,WAAW,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC;QACxJ,MAAM,OAAO,GAAG,CAAC,OAAO,GAAG,WAAW,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,CAAC,OAAO,GAAG,WAAW,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;QACjK,MAAM,EAAE,GAAG,kBAAkB,GAAG,CAAC,OAAO,GAAG,kBAAkB,CAAC,EAAE,EAAE,GAAG,kBAAkB,GAAG,CAAC,CAAC,OAAO,GAAG,kBAAkB,CAAC,CAAC;QAC1H,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,kBAAkB,CAAC,EAAE,CAAC,GAAG,gBAAgB,GAAG,CAAC,kBAAkB,GAAG,MAAM,CAAC,CAAC;QAC5G,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,kBAAkB,CAAC;QAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,kBAAkB,CAAC;IAC/F,CAAC,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW;YACrJ,oBAAoB,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,CAAC;AAClI,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (#394, not exported from the package): the input of the tabulated expected signal (`createDisplayViewExpectedSignal`)\n * for a resolved `simulateCapture` request, shared by the display view and the capture's tabulated RAW path so both build it\n * the same way: query offsets of each site, the environment's query points, the diffraction kernels and the scene tables.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { resolveLensPsfConvolutionKernels } from \"../optics/lens-psf-kernels.js\";\nimport { resolveProjectionDistanceMm } from \"../motion/camera-rotation.js\";\nimport type { DisplayViewExpectedSignalInput, DisplayViewScene } from \"./display-view.js\";\n\ntype Psf = Parameters<typeof resolveLensPsfConvolutionKernels>[0][\"psf\"];\n\nexport interface ExpectedSignalSetupInput {\n nativeWidth: number;\n nativeHeight: number;\n /** Frame output width (plane coordinates are 2 / width frame units per site). */\n width: number;\n wavelengths: readonly number[];\n /** Per CFA channel (red, green, blue): expected electrons per unit spectral radiance at each node. */\n perNode: readonly [Float64Array, Float64Array, Float64Array];\n /** Pixel-aperture nodes in sites from the site centre (`apertureNodeOffsets(boxSamples)`) and the box sample count. */\n apertureNodes: readonly { x: number; y: number }[];\n boxSamples: number;\n antiAliasing?: { components: readonly { offsetMicrometers: { x: number; y: number }; normalizedWeight: number }[] } | undefined;\n pitch: number;\n /** Object-space diffraction: the kernel radius, the PSF and its profile wavelengths. */\n diffraction?: { radius: number; psf: Psf; profileWavelengths: readonly number[] } | undefined;\n /** Material scenes: the materials (neutral reflectances, or radiance per wavelength), the material lookup and the white radiance. */\n materials?: { list: readonly ({ neutral: number } | { radiance: (nm: number) => number })[]; materialAt(u: number, v: number): number; whiteRadiance(nm: number): number } | undefined;\n radianceAt(u: number, v: number, nm: number): number;\n imagingArea: { widthMm: number; heightMm: number };\n focalLengthMm: number;\n focusDistanceM: number;\n subjectDistanceM: number;\n metresPerFrameUnit: number;\n /** Prefix of the error messages (\"simulateDisplayView\", \"simulateCapture\"). */\n caller: string;\n}\n\n/** The query offsets of each site and the expected-signal input (see the module comment). */\nexport function buildExpectedSignalInput(input: ExpectedSignalSetupInput): { offsets: { x: number; y: number; weight: number }[]; signalInput: DisplayViewExpectedSignalInput } {\n const { boxSamples, pitch, wavelengths, imagingArea, subjectDistanceM, metresPerFrameUnit } = input;\n // Query offsets of each site: pixel-aperture nodes minus anti-aliasing split offsets (sites, +x right, +y down), as the environment forms them.\n const spots = input.antiAliasing ? input.antiAliasing.components : [{ offsetMicrometers: { x: 0, y: 0 }, normalizedWeight: 1 }];\n const offsets = input.apertureNodes.flatMap((node) => spots.map((c) => ({ x: node.x - c.offsetMicrometers.x / pitch, y: node.y - c.offsetMicrometers.y / pitch,\n weight: c.normalizedWeight / (boxSamples * boxSamples) })));\n // Diffraction: the same per-node site kernels as the object-space convolution (createDiffractedPlanarProvider).\n let kernels: { radius: number; perNode: Float64Array[] } | undefined;\n const diffraction = input.diffraction;\n if (diffraction) {\n const radius = diffraction.radius;\n kernels = { radius, perNode: wavelengths.map((wavelengthNm) => {\n if (!diffraction.profileWavelengths.includes(wavelengthNm)) throw new InvalidConfigurationError(input.caller + \": a spectral node has no diffraction kernel.\");\n const k = resolveLensPsfConvolutionKernels({ schemaVersion: \"0.1.0\", psf: diffraction.psf, spectralWeights: [{ wavelengthNm, weight: 1 }],\n grid: { width: 1, height: 1, sitePitchMicrometers: { x: pitch, y: pitch }, opticalAxisSite: { x: 0.5, y: 0.5 } }, patchWidth: 1, patchHeight: 1, kernelRadius: radius }).value;\n if (!k.exactSiteSampling || k.kernelRadius !== radius) throw new InvalidConfigurationError(input.caller + \": diffraction kernels must sample the site pitch exactly.\");\n return Float64Array.from(k.kernels);\n }) };\n }\n // Chart presets and (2.7) caller material scenes take the material path: each material tabulated once per node.\n const materials = input.materials;\n const scene: DisplayViewScene = materials ? { kind: \"materials\", materialAt: materials.materialAt,\n materials: materials.list.map((m) => (\"neutral\" in m ? { neutral: m.neutral } : { radiance: Float64Array.from(wavelengths, (nm) => m.radiance(nm)) })),\n whiteRadiance: Float64Array.from(wavelengths, (nm) => materials.whiteRadiance(nm)) } :\n { kind: \"radiance\", radianceAt: input.radianceAt };\n const channelIndex = (x: number, y: number): number => ((y & 1) === 0 ? ((x & 1) === 0 ? 0 : 1) : ((x & 1) === 0 ? 1 : 2));\n // Without diffraction the environment's own query arithmetic (native-environment-lean.ts `geometry`, then the rectilinear\n // still-camera ray and the planar provider's plane point), so boundary points resolve as in the capture.\n const projectionDistance = resolveProjectionDistanceMm(input.focalLengthMm, input.focusDistanceM).projectionDistanceMm, siteWidthMm = pitch / 1000;\n const nodePhases = input.apertureNodes.flatMap((_, index) => spots.map((c) => ({ xPhase: (index % boxSamples + 0.5) / boxSamples, yPhase: (Math.floor(index / boxSamples) + 0.5) / boxSamples,\n aaX: c.offsetMicrometers.x / 1000, aaY: c.offsetMicrometers.y / 1000 })));\n const queryPoint = (x: number, y: number, o: number, out: Float64Array): void => {\n const node = nodePhases[o]!;\n const centerX = -imagingArea.widthMm / 2 + (pitch / 2 + x * pitch + 0) / 1000, centerY = -imagingArea.heightMm / 2 + (pitch / 2 + y * pitch + 0) / 1000;\n const sourceX = (centerX - siteWidthMm / 2 + node.xPhase * siteWidthMm) - node.aaX, sourceY = (centerY - siteWidthMm / 2 + node.yPhase * siteWidthMm) - node.aaY;\n const rx = projectionDistance * (sourceX / projectionDistance), ry = projectionDistance * (-sourceY / projectionDistance);\n const length = Math.hypot(rx, ry, projectionDistance), t = subjectDistanceM / (projectionDistance / length);\n out[0] = rx / length * t / metresPerFrameUnit; out[1] = ry / length * t / metresPerFrameUnit;\n };\n return { offsets, signalInput: { nativeWidth: input.nativeWidth, nativeHeight: input.nativeHeight, outputWidth: input.width, wavelengthsNm: wavelengths,\n electronsPerRadiance: input.perNode, offsets, ...(kernels ? { kernels } : { queryPoint }), scene, channelAt: channelIndex } };\n}\n"]}
@@ -0,0 +1,83 @@
1
+ /** A query offset from a site centre in sites (+x right, +y down) and its weight (pixel-aperture node × filter spot). */
2
+ export interface DisplayViewQueryOffset {
3
+ x: number;
4
+ y: number;
5
+ weight: number;
6
+ }
7
+ /** Scene access for the expected signal: chart materials (fast tables) or a radiance function. */
8
+ export type DisplayViewScene = {
9
+ kind: "materials";
10
+ materialAt(u: number, v: number): number;
11
+ /** Per material: `{ neutral: reflectance }` (radiance = reflectance × illuminant) or the spectral radiance at each node. */
12
+ materials: readonly ({
13
+ neutral: number;
14
+ } | {
15
+ radiance: Float64Array;
16
+ })[];
17
+ /** Illuminant radiance of a perfect white diffuser at each spectral node: irradianceScale·shape(λ)/π. */
18
+ whiteRadiance: Float64Array;
19
+ } | {
20
+ kind: "radiance";
21
+ radianceAt(u: number, v: number, nm: number): number;
22
+ };
23
+ export interface DisplayViewExpectedSignalInput {
24
+ nativeWidth: number;
25
+ nativeHeight: number;
26
+ /** Frame output width: plane coordinates are 2 / outputWidth frame units per site. */
27
+ outputWidth: number;
28
+ /** Spectral nodes and, per CFA channel (red, green, blue), the expected electrons per unit spectral radiance at each node. */
29
+ wavelengthsNm: readonly number[];
30
+ electronsPerRadiance: readonly [Float64Array, Float64Array, Float64Array];
31
+ offsets: readonly DisplayViewQueryOffset[];
32
+ /** Diffraction: per-node kernels on the site lattice, (2R + 1)² taps in row-major order, out(x) = Σ in(x − t)·K(t). */
33
+ kernels?: {
34
+ radius: number;
35
+ perNode: readonly Float64Array[];
36
+ };
37
+ scene: DisplayViewScene;
38
+ /** CFA channel index (0 red, 1 green, 2 blue) of native site (x, y). */
39
+ channelAt(x: number, y: number): number;
40
+ /**
41
+ * Without diffraction: the plane point (frame units) that the environment queries for offset `o` of native site (x, y),
42
+ * computed with the environment's own arithmetic, so points that fall exactly on a chart boundary resolve to the same
43
+ * material as in the capture. With diffraction the facade's object-space convolution uses the lattice formula, as here.
44
+ */
45
+ queryPoint?(x: number, y: number, offset: number, out: Float64Array): void;
46
+ }
47
+ /** Expected photo electrons of real native sites. */
48
+ export interface DisplayViewExpectedSignal {
49
+ /**
50
+ * Starts a new cache window: native sites [x0, x1) × [y0, y1) that the following `site` calls mostly read. Sites and
51
+ * scene lookups inside it are computed once; anything outside is computed without caching.
52
+ */
53
+ window(x0: number, y0: number, x1: number, y1: number): void;
54
+ /** Expected photo electrons of native site (x, y) for its own CFA channel (x, y inside the native raster). */
55
+ site(x: number, y: number): number;
56
+ /** A cheap classifier value of the scene at plane point (u, v) frame units: the material, or the radiance at one node. */
57
+ pilot(u: number, v: number): number;
58
+ /** Scene lookups performed so far (material or radiance queries), for diagnostics. */
59
+ readonly lookups: number;
60
+ /**
61
+ * The chart material that every kernel tap of the last `site` call read, when they all read one material (its value is
62
+ * then a function of that material and the site's CFA channel only); −1 when unknown or mixed.
63
+ */
64
+ readonly uniformMaterial: number;
65
+ /** Whether `uniformMaterial` is tracked in the current window (otherwise it is not meaningful). */
66
+ readonly tracksMaterial: boolean;
67
+ /**
68
+ * Whether the current window caches its sites: a second `site` call for a site inside the window then returns the stored
69
+ * value without any work or lookup (#392).
70
+ */
71
+ readonly cachesSites: boolean;
72
+ }
73
+ /**
74
+ * Expected photo electrons per site: μ_c(i, j) = Σ_o w_o Σ_λ A_c(λ) Σ_t K_λ(t)·L(p_o(i, j) − t, λ), where p_o is the
75
+ * query point of offset o and K the diffraction kernel (a single unit tap without diffraction). This is the sum the
76
+ * facade's environment chain forms for a still, rectilinear, distortion-free planar scene, where the
77
+ * radiance-to-irradiance factor is the same at every site (docs/DISPLAY_VIEW.md, "Expected signal").
78
+ *
79
+ * With diffraction, offsets that share a fractional shift read one lattice, and their kernels are combined in advance:
80
+ * C_c(d) = Σ_members w·Σ_λ A_c(λ)·K_λ(t)·L(λ) over the taps t that reach lattice offset d. A neutral material's radiance
81
+ * is its reflectance times the white radiance, so all neutral materials share one combined kernel per channel.
82
+ */
83
+ export declare function createDisplayViewExpectedSignal(input: DisplayViewExpectedSignalInput): DisplayViewExpectedSignal;