@photivra/engine 2.15.0 → 2.17.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 +102 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +38 -0
- package/dist/camera/lens-projection.d.ts +5 -0
- package/dist/camera/lens-projection.js +10 -0
- package/dist/camera/lens-projection.js.map +1 -1
- package/dist/capture/native-environment-lean.js +1 -1
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-raw-development.d.ts +15 -0
- package/dist/capture/native-raw-development.js +12 -6
- package/dist/capture/native-raw-development.js.map +1 -1
- package/dist/capture/raw-development-kernel.d.ts +10 -3
- package/dist/capture/raw-development-kernel.js +25 -6
- package/dist/capture/raw-development-kernel.js.map +1 -1
- package/dist/core/internal-radiance-evaluator.d.ts +5 -2
- package/dist/core/internal-radiance-evaluator.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 +227 -0
- package/dist/equipment/generic-lens-catalogue.js +243 -0
- package/dist/equipment/generic-lens-catalogue.js.map +1 -0
- package/dist/facade/defocus-bokeh-zones.d.ts +33 -0
- package/dist/facade/defocus-bokeh-zones.js +53 -0
- package/dist/facade/defocus-bokeh-zones.js.map +1 -0
- package/dist/facade/defocus-image-space-provider.d.ts +118 -0
- package/dist/facade/defocus-image-space-provider.js +448 -0
- package/dist/facade/defocus-image-space-provider.js.map +1 -0
- package/dist/facade/diffraction-provider.d.ts +45 -0
- package/dist/facade/diffraction-provider.js +120 -14
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +4 -0
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +10 -0
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +30 -0
- package/dist/facade/display-view-lens-signal.js +427 -0
- package/dist/facade/display-view-lens-signal.js.map +1 -0
- package/dist/facade/display-view.d.ts +21 -0
- package/dist/facade/display-view.js +66 -8
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +7 -0
- package/dist/facade/expected-signal-setup.js +12 -2
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +8 -0
- package/dist/facade/expected-signal.js +150 -0
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +6 -0
- package/dist/facade/generic-lens-capture.js +8 -0
- package/dist/facade/generic-lens-capture.js.map +1 -0
- package/dist/facade/large-defocus-lattice.d.ts +39 -0
- package/dist/facade/large-defocus-lattice.js +64 -0
- package/dist/facade/large-defocus-lattice.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +5 -0
- package/dist/facade/scene-radiance.js +5 -2
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +148 -6
- package/dist/facade/simulate-capture.js +521 -88
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +29 -19
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +2 -9
- package/dist/gpu/display-view-quality-gpu.js +63 -13
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +2 -0
- package/dist/gpu/display-view-speed-gpu.js +47 -16
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/image-kernel-accelerator.js +3 -0
- package/dist/gpu/image-kernel-accelerator.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +4 -0
- package/dist/gpu/wgsl-display-view-quality-kernel.js +53 -2
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +15 -0
- package/dist/gpu/wgsl-display-view-speed-kernel.js +42 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/optics/defocus-bokeh-kernel.d.ts +207 -0
- package/dist/optics/defocus-bokeh-kernel.js +380 -0
- package/dist/optics/defocus-bokeh-kernel.js.map +1 -0
- package/dist/optics/defocus-disc-kernel.d.ts +44 -21
- package/dist/optics/defocus-disc-kernel.js +47 -25
- package/dist/optics/defocus-disc-kernel.js.map +1 -1
- package/dist/optics/generic-lens-psf.d.ts +68 -0
- package/dist/optics/generic-lens-psf.js +337 -0
- package/dist/optics/generic-lens-psf.js.map +1 -0
- package/dist/optics/illumination-vignetting.d.ts +6 -0
- package/dist/optics/illumination-vignetting.js +24 -7
- package/dist/optics/illumination-vignetting.js.map +1 -1
- package/dist/optics/sensor-environment-query.d.ts +37 -0
- package/dist/optics/sensor-environment-query.js +58 -19
- package/dist/optics/sensor-environment-query.js.map +1 -1
- package/dist/optics/site-kernel-runs.d.ts +77 -0
- package/dist/optics/site-kernel-runs.js +200 -0
- package/dist/optics/site-kernel-runs.js.map +1 -0
- package/dist/output/generic-lens-corrections.d.ts +24 -0
- package/dist/output/generic-lens-corrections.js +51 -0
- package/dist/output/generic-lens-corrections.js.map +1 -0
- package/dist/processing/development-detail.d.ts +132 -0
- package/dist/processing/development-detail.js +325 -0
- package/dist/processing/development-detail.js.map +1 -0
- package/docs/AGENT_GUIDE.md +1 -0
- package/docs/API_REFERENCE.md +54 -1
- package/docs/QUICK_START.md +444 -26
- package/docs/USAGE.md +71 -8
- package/docs/api/capture-native-raw-development.md +16 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +281 -0
- package/docs/api/exports.json +162 -2
- package/docs/api/facade-simulate-capture.md +151 -5
- package/docs/api/optics-defocus-bokeh-kernel.md +145 -0
- package/docs/api/optics-defocus-disc-kernel.md +25 -17
- package/docs/api/optics-sensor-environment-query.md +6 -0
- package/docs/api/output-generic-lens-corrections.md +39 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { resolveLensPsfConvolutionKernels } from "../optics/lens-psf-kernels.js";
|
|
2
|
+
import { type SiteKernelRuns } from "../optics/site-kernel-runs.js";
|
|
2
3
|
import type { DisplayViewExpectedSignalInput } from "./display-view.js";
|
|
3
4
|
type Psf = Parameters<typeof resolveLensPsfConvolutionKernels>[0]["psf"];
|
|
4
5
|
export interface ExpectedSignalSetupInput {
|
|
@@ -36,6 +37,12 @@ export interface ExpectedSignalSetupInput {
|
|
|
36
37
|
radius: number;
|
|
37
38
|
weights: Float64Array;
|
|
38
39
|
} | null | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* #564: a defocus kernel above the direct radius, as runs, used instead of `defocus`: the tables keep the in-focus kernels
|
|
42
|
+
* (diffraction, or a unit tap per node for the lattice formula) and the expected signal convolves the result with it. A
|
|
43
|
+
* `defocus` disc of radius above 64 sites takes this path too.
|
|
44
|
+
*/
|
|
45
|
+
largeDefocus?: SiteKernelRuns | undefined;
|
|
39
46
|
/** Material scenes: the materials (neutral reflectances, or radiance per wavelength), the material lookup and the white radiance. */
|
|
40
47
|
materials?: {
|
|
41
48
|
list: readonly ({
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
16
16
|
import { siteDiffractionKernel } from "../optics/site-diffraction-kernel.js";
|
|
17
17
|
import { convolveSiteKernels } from "../optics/defocus-disc-kernel.js";
|
|
18
|
+
import { DEFOCUS_DIRECT_KERNEL_MAXIMUM_RADIUS_SITES, siteKernelRuns } from "../optics/site-kernel-runs.js";
|
|
18
19
|
import { resolveProjectionDistanceMm } from "../motion/camera-rotation.js";
|
|
19
20
|
import { hypot3 } from "../core/portable-math.js";
|
|
21
|
+
/** A unit site kernel of radius 0 (shared: the tables only read it). */
|
|
22
|
+
const UNIT_TAP = Float64Array.of(1);
|
|
20
23
|
/** The query offsets of each site and the expected-signal input (see the module comment). */
|
|
21
24
|
export function buildExpectedSignalInput(input) {
|
|
22
25
|
const { boxSamples, pitch, wavelengths, imagingArea, subjectDistanceM, metresPerFrameUnit } = input;
|
|
@@ -35,13 +38,20 @@ export function buildExpectedSignalInput(input) {
|
|
|
35
38
|
return siteDiffractionKernel(diffraction.psf, wavelengthNm, pitch, radius, input.caller + ": diffraction kernels must sample the site pitch exactly.");
|
|
36
39
|
}) };
|
|
37
40
|
}
|
|
41
|
+
// #564: a disc above the direct radius (64 sites) is convolved by runs after the in-focus signal instead (below).
|
|
42
|
+
const largeDefocus = input.largeDefocus ?? (input.defocus && input.defocus.radius > DEFOCUS_DIRECT_KERNEL_MAXIMUM_RADIUS_SITES ?
|
|
43
|
+
siteKernelRuns(input.defocus.weights, input.defocus.radius) : undefined);
|
|
38
44
|
// Defocus (#492): the disc alone at every node (one shared array), or each node's diffraction kernel convolved with it.
|
|
39
|
-
const defocus = input.defocus;
|
|
45
|
+
const defocus = largeDefocus ? undefined : input.defocus;
|
|
40
46
|
if (defocus) {
|
|
41
47
|
const diffracted = kernels;
|
|
42
48
|
kernels = diffracted ? { radius: diffracted.radius + defocus.radius, perNode: diffracted.perNode.map((k) => convolveSiteKernels(k, diffracted.radius, defocus.weights, defocus.radius)) } :
|
|
43
49
|
{ radius: defocus.radius, perNode: wavelengths.map(() => defocus.weights) };
|
|
44
50
|
}
|
|
51
|
+
// #564: a large defocus kernel is applied after the in-focus signal (expected-signal.ts); without diffraction the in-focus
|
|
52
|
+
// kernels are a unit tap per node, so the tables read the scene on the lattice formula as the defocus path does.
|
|
53
|
+
if (largeDefocus && !kernels)
|
|
54
|
+
kernels = { radius: 0, perNode: wavelengths.map(() => UNIT_TAP) };
|
|
45
55
|
// Chart presets and (2.7) caller material scenes take the material path: each material tabulated once per node.
|
|
46
56
|
const materials = input.materials;
|
|
47
57
|
const scene = materials ? { kind: "materials", materialAt: materials.materialAt,
|
|
@@ -64,6 +74,6 @@ export function buildExpectedSignalInput(input) {
|
|
|
64
74
|
out[1] = ry / length * t / metresPerFrameUnit;
|
|
65
75
|
};
|
|
66
76
|
return { offsets, signalInput: { nativeWidth: input.nativeWidth, nativeHeight: input.nativeHeight, outputWidth: input.width, wavelengthsNm: wavelengths,
|
|
67
|
-
electronsPerRadiance: input.perNode, offsets, ...(kernels ? { kernels } : { queryPoint }), scene, channelAt: channelIndex } };
|
|
77
|
+
electronsPerRadiance: input.perNode, offsets, ...(kernels ? { kernels } : { queryPoint }), ...(largeDefocus ? { largeKernel: largeDefocus } : {}), scene, channelAt: channelIndex } };
|
|
68
78
|
}
|
|
69
79
|
//# sourceMappingURL=expected-signal-setup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expected-signal-setup.js","sourceRoot":"","sources":["../../src/facade/expected-signal-setup.ts"],"names":[],"mappings":"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","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 * Defocus (#492, `lens.defocus`): the geometric disc D on the site lattice (`createDefocusDiscSiteKernel`) enters as a site\n * kernel, so every query stays on the lattice. Without diffraction every spectral node takes D itself (a wavelength-independent\n * kernel). With diffraction node λ takes K_λ ∗ D, the full 2-D convolution of the diffraction kernel with the disc (radius\n * R_diff + R_defocus): a labelled approximation, since the defocused wave's PSF is not the in-focus Airy pattern convolved\n * with a geometric disc. The convolution is linear, so Σ_t (K_λ ∗ D)(t)·L(p − t) is the diffracted radiance blurred by D, the\n * order the capture's object-space convolutions apply them in. The kernels are built as the capture builds them\n * (`siteDiffractionKernel`, then `convolveSiteKernels`), so the two have the same bits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { resolveLensPsfConvolutionKernels } from \"../optics/lens-psf-kernels.js\";\nimport { siteDiffractionKernel } from \"../optics/site-diffraction-kernel.js\";\nimport { convolveSiteKernels } from \"../optics/defocus-disc-kernel.js\";\nimport { resolveProjectionDistanceMm } from \"../motion/camera-rotation.js\";\nimport { hypot3 } from \"../core/portable-math.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 /** Defocus (#492): the geometric disc on the site lattice, (2R + 1)² row-major weights; absent or null in focus. */\n defocus?: { radius: number; weights: Float64Array } | null | 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 return siteDiffractionKernel(diffraction.psf, wavelengthNm, pitch, radius, input.caller + \": diffraction kernels must sample the site pitch exactly.\");\n }) };\n }\n // Defocus (#492): the disc alone at every node (one shared array), or each node's diffraction kernel convolved with it.\n const defocus = input.defocus;\n if (defocus) {\n const diffracted = kernels;\n kernels = diffracted ? { radius: diffracted.radius + defocus.radius, perNode: diffracted.perNode.map((k) => convolveSiteKernels(k, diffracted.radius, defocus.weights, defocus.radius)) } :\n { radius: defocus.radius, perNode: wavelengths.map(() => defocus.weights) };\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 = hypot3(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\n"]}
|
|
1
|
+
{"version":3,"file":"expected-signal-setup.js","sourceRoot":"","sources":["../../src/facade/expected-signal-setup.ts"],"names":[],"mappings":"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","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 * Defocus (#492, `lens.defocus`): the geometric disc D on the site lattice (`createDefocusDiscSiteKernel`) enters as a site\n * kernel, so every query stays on the lattice. Without diffraction every spectral node takes D itself (a wavelength-independent\n * kernel). With diffraction node λ takes K_λ ∗ D, the full 2-D convolution of the diffraction kernel with the disc (radius\n * R_diff + R_defocus): a labelled approximation, since the defocused wave's PSF is not the in-focus Airy pattern convolved\n * with a geometric disc. The convolution is linear, so Σ_t (K_λ ∗ D)(t)·L(p − t) is the diffracted radiance blurred by D, the\n * order the capture's object-space convolutions apply them in. The kernels are built as the capture builds them\n * (`siteDiffractionKernel`, then `convolveSiteKernels`), so the two have the same bits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { resolveLensPsfConvolutionKernels } from \"../optics/lens-psf-kernels.js\";\nimport { siteDiffractionKernel } from \"../optics/site-diffraction-kernel.js\";\nimport { convolveSiteKernels } from \"../optics/defocus-disc-kernel.js\";\nimport { DEFOCUS_DIRECT_KERNEL_MAXIMUM_RADIUS_SITES, siteKernelRuns, type SiteKernelRuns } from \"../optics/site-kernel-runs.js\";\nimport { resolveProjectionDistanceMm } from \"../motion/camera-rotation.js\";\nimport { hypot3 } from \"../core/portable-math.js\";\nimport type { DisplayViewExpectedSignalInput, DisplayViewScene } from \"./display-view.js\";\n\n/** A unit site kernel of radius 0 (shared: the tables only read it). */\nconst UNIT_TAP = Float64Array.of(1);\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 /** Defocus (#492): the geometric disc on the site lattice, (2R + 1)² row-major weights; absent or null in focus. */\n defocus?: { radius: number; weights: Float64Array } | null | undefined;\n /**\n * #564: a defocus kernel above the direct radius, as runs, used instead of `defocus`: the tables keep the in-focus kernels\n * (diffraction, or a unit tap per node for the lattice formula) and the expected signal convolves the result with it. A\n * `defocus` disc of radius above 64 sites takes this path too.\n */\n largeDefocus?: SiteKernelRuns | 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 return siteDiffractionKernel(diffraction.psf, wavelengthNm, pitch, radius, input.caller + \": diffraction kernels must sample the site pitch exactly.\");\n }) };\n }\n // #564: a disc above the direct radius (64 sites) is convolved by runs after the in-focus signal instead (below).\n const largeDefocus = input.largeDefocus ?? (input.defocus && input.defocus.radius > DEFOCUS_DIRECT_KERNEL_MAXIMUM_RADIUS_SITES ?\n siteKernelRuns(input.defocus.weights, input.defocus.radius) : undefined);\n // Defocus (#492): the disc alone at every node (one shared array), or each node's diffraction kernel convolved with it.\n const defocus = largeDefocus ? undefined : input.defocus;\n if (defocus) {\n const diffracted = kernels;\n kernels = diffracted ? { radius: diffracted.radius + defocus.radius, perNode: diffracted.perNode.map((k) => convolveSiteKernels(k, diffracted.radius, defocus.weights, defocus.radius)) } :\n { radius: defocus.radius, perNode: wavelengths.map(() => defocus.weights) };\n }\n // #564: a large defocus kernel is applied after the in-focus signal (expected-signal.ts); without diffraction the in-focus\n // kernels are a unit tap per node, so the tables read the scene on the lattice formula as the defocus path does.\n if (largeDefocus && !kernels) kernels = { radius: 0, perNode: wavelengths.map(() => UNIT_TAP) };\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 = hypot3(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 }), ...(largeDefocus ? { largeKernel: largeDefocus } : {}), scene, channelAt: channelIndex } };\n}\n\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SiteKernelRuns } from "../optics/site-kernel-runs.js";
|
|
1
2
|
/** A query offset from a site centre in sites (+x right, +y down) and its weight (pixel-aperture node × filter spot). */
|
|
2
3
|
export interface DisplayViewQueryOffset {
|
|
3
4
|
x: number;
|
|
@@ -48,6 +49,13 @@ export interface DisplayViewExpectedSignalInput {
|
|
|
48
49
|
* lattice formula, as here.
|
|
49
50
|
*/
|
|
50
51
|
queryPoint?(x: number, y: number, offset: number, out: Float64Array): void;
|
|
52
|
+
/**
|
|
53
|
+
* #564: a defocus kernel D above the direct radius (64 sites), as runs. Each site's expected electrons are then
|
|
54
|
+
* Σ_t D(t)·E_c(x − t, y − t), with E_c the expected electrons of channel c (the site's own) through `kernels` (each node's
|
|
55
|
+
* diffraction kernel, or a unit tap for the lattice formula) at every native site position, convolved over the site
|
|
56
|
+
* lattice by runs in fixed tiles (see {@link createDisplayViewExpectedSignal}). Requires `kernels`.
|
|
57
|
+
*/
|
|
58
|
+
largeKernel?: SiteKernelRuns;
|
|
51
59
|
}
|
|
52
60
|
/** Expected photo electrons of real native sites. */
|
|
53
61
|
export interface DisplayViewExpectedSignal {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* only captures does not contain the display view or the portable functions it uses (issue #380).
|
|
6
6
|
*/
|
|
7
7
|
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
8
|
+
import { createSiteKernelRunsLattice } from "../optics/site-kernel-runs.js";
|
|
8
9
|
const OFFSET_TOLERANCE = 1e-9;
|
|
9
10
|
/** Largest cache window, in sites or lattice points; larger windows are evaluated without caching. */
|
|
10
11
|
const CACHE_LIMIT = 1 << 22;
|
|
@@ -199,6 +200,8 @@ function buildTables(input) {
|
|
|
199
200
|
* built wider). Only a speed hint: a window past it rebuilds its band, with the same values.
|
|
200
201
|
*/
|
|
201
202
|
export function createDisplayViewExpectedSignal(input, spanColumns = Infinity) {
|
|
203
|
+
if (input.largeKernel)
|
|
204
|
+
return createLargeKernelSignal(input);
|
|
202
205
|
const W = input.nativeWidth, H = input.nativeHeight, g = 2 / input.outputWidth, nodes = input.wavelengthsNm.length;
|
|
203
206
|
const R = input.kernels ? input.kernels.radius : 0;
|
|
204
207
|
if (input.kernels && input.kernels.perNode.length !== nodes)
|
|
@@ -864,4 +867,151 @@ export function createDisplayViewExpectedSignal(input, spanColumns = Infinity) {
|
|
|
864
867
|
}
|
|
865
868
|
};
|
|
866
869
|
}
|
|
870
|
+
/** #564: tile sides of the large-kernel expected signal: the in-focus signal E (3 channels) and the convolved sites. */
|
|
871
|
+
const LARGE_E_TILE = 64, LARGE_OUTPUT_TILE = 128;
|
|
872
|
+
/** Bytes kept per input for each of the two tile caches (least recently used first; a dropped tile is recomputed with the same bits). */
|
|
873
|
+
const LARGE_CACHE_BYTES = 32 * 1048576;
|
|
874
|
+
const LARGE_PREPARED = new WeakMap();
|
|
875
|
+
/**
|
|
876
|
+
* #564: the expected signal with a defocus kernel D above the direct radius. Defocus of a plane is space-invariant and
|
|
877
|
+
* wavelength-independent, so it commutes with the spectral and query-offset sums and with the diffraction kernels:
|
|
878
|
+
* μ_c(x) = Σ_o w_o Σ_λ A_c(λ) Σ_t (K_λ ∗ D)(t)·L(p_o(x) − t, λ) = Σ_s D(s)·E_c(x − s),
|
|
879
|
+
* with E_c(x) = Σ_o w_o Σ_λ A_c(λ) Σ_t K_λ(t)·L(p_o(x) − t, λ) the in-focus expected electrons of channel c at site position x
|
|
880
|
+
* (the same sum as `kernelSite`, on the lattice formula), so the disc is applied once per channel on the native site lattice
|
|
881
|
+
* instead of once per spectral node and lattice group. E is computed in fixed 64-site tiles (all three channels), and D ⊛ E_c
|
|
882
|
+
* by runs in fixed 128-site tiles, each site taking its own channel: every value depends only on the site, so regions and parts
|
|
883
|
+
* are bit-identical to the whole frame. The tiles are kept per input object, across its renders.
|
|
884
|
+
*
|
|
885
|
+
* Diagnostics: `lookups` counts the scene lookups this signal's calls caused (tiles computed for it; a tile an earlier render of
|
|
886
|
+
* the input computed costs none). `uniformMaterial` is −1 and nothing is tracked or marked, as for radiance scenes.
|
|
887
|
+
*/
|
|
888
|
+
function createLargeKernelSignal(input) {
|
|
889
|
+
if (!input.kernels || !input.largeKernel)
|
|
890
|
+
throw new InvalidConfigurationError("The large-kernel expected signal needs the in-focus kernels.");
|
|
891
|
+
let state = LARGE_PREPARED.get(input);
|
|
892
|
+
if (!state) {
|
|
893
|
+
state = prepareLargeKernel(input);
|
|
894
|
+
LARGE_PREPARED.set(input, state);
|
|
895
|
+
}
|
|
896
|
+
const prepared = state, start = prepared.lookups, channelAt = input.channelAt, scene = input.scene, nodes = input.wavelengthsNm.length;
|
|
897
|
+
const pilotNode = input.wavelengthsNm[Math.floor(nodes / 2)];
|
|
898
|
+
let pilots = 0;
|
|
899
|
+
return {
|
|
900
|
+
get lookups() { return prepared.lookups - start + pilots; },
|
|
901
|
+
get uniformMaterial() { return -1; },
|
|
902
|
+
get tracksMaterial() { return false; },
|
|
903
|
+
get cachesSites() { return false; },
|
|
904
|
+
get marksReads() { return false; },
|
|
905
|
+
markReads() { },
|
|
906
|
+
window() { },
|
|
907
|
+
site(x, y) { return prepared.lattice.valueAt(channelAt(x, y), x, y); },
|
|
908
|
+
pilot(u, v) {
|
|
909
|
+
pilots++;
|
|
910
|
+
return scene.kind === "materials" ? scene.materialAt(u, v) : scene.radianceAt(u, v, pilotNode);
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function prepareLargeKernel(input) {
|
|
915
|
+
const W = input.nativeWidth, H = input.nativeHeight, g = 2 / input.outputWidth, nodes = input.wavelengthsNm.length, R = input.kernels.radius;
|
|
916
|
+
const tables = displayViewSignalTables(input), { reflectance, spectralIndex, groups } = tables, scene = input.scene;
|
|
917
|
+
const state = { lookups: 0 };
|
|
918
|
+
const latticeU = (group, p) => (group.fx === 0 ? (p + 0.5 - W / 2) * g : (p + 0.5 + group.fx - W / 2) * g);
|
|
919
|
+
const latticeV = (group, q) => (group.fy === 0 ? -(q + 0.5 - H / 2) * g : -(q + 0.5 + group.fy - H / 2) * g);
|
|
920
|
+
/** E_c of the native site positions [x0, x0 + T) × [y0, y0 + T), all three channels: kernelSite's sum, each group's lattice read once. */
|
|
921
|
+
function inFocusTile(x0, y0, T) {
|
|
922
|
+
const out = [0, 1, 2].map(() => new Float64Array(T * T));
|
|
923
|
+
for (const group of groups) {
|
|
924
|
+
const cw = group.cw, ch = group.ch, gw = T + cw - 1, gh = T + ch - 1, p0 = x0 + group.minIx - R, q0 = y0 + group.minIy - R;
|
|
925
|
+
if (scene.kind === "materials") {
|
|
926
|
+
const material = new Int32Array(gw * gh);
|
|
927
|
+
for (let j = 0; j < gh; j++) {
|
|
928
|
+
const v = latticeV(group, q0 + j);
|
|
929
|
+
for (let i = 0; i < gw; i++)
|
|
930
|
+
material[j * gw + i] = scene.materialAt(latticeU(group, p0 + i), v);
|
|
931
|
+
}
|
|
932
|
+
state.lookups += gw * gh;
|
|
933
|
+
for (let c = 0; c < 3; c++) {
|
|
934
|
+
const nk = group.neutral[c], sk = group.spectral[c], e = out[c];
|
|
935
|
+
for (let y = 0; y < T; y++)
|
|
936
|
+
for (let x = 0; x < T; x++) {
|
|
937
|
+
let sum = 0;
|
|
938
|
+
for (let dy = 0; dy < ch; dy++)
|
|
939
|
+
for (let dx = 0; dx < cw; dx++) {
|
|
940
|
+
const m = material[(y + dy) * gw + x + dx], si = spectralIndex[m];
|
|
941
|
+
if (si === undefined)
|
|
942
|
+
throw new InvalidConfigurationError("Unknown chart material.");
|
|
943
|
+
sum += si < 0 ? reflectance[m] * nk[dy * cw + dx] : sk[si][dy * cw + dx];
|
|
944
|
+
}
|
|
945
|
+
e[y * T + x] = e[y * T + x] + sum;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
else {
|
|
950
|
+
const radiance = new Float64Array(gw * gh * nodes);
|
|
951
|
+
for (let j = 0; j < gh; j++) {
|
|
952
|
+
const v = latticeV(group, q0 + j);
|
|
953
|
+
for (let i = 0; i < gw; i++) {
|
|
954
|
+
const u = latticeU(group, p0 + i);
|
|
955
|
+
for (let n = 0; n < nodes; n++)
|
|
956
|
+
radiance[(j * gw + i) * nodes + n] = scene.radianceAt(u, v, input.wavelengthsNm[n]);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
state.lookups += gw * gh * nodes;
|
|
960
|
+
for (let c = 0; c < 3; c++) {
|
|
961
|
+
const e = out[c];
|
|
962
|
+
for (let y = 0; y < T; y++)
|
|
963
|
+
for (let x = 0; x < T; x++) {
|
|
964
|
+
let sum = 0;
|
|
965
|
+
for (let dy = 0; dy < ch; dy++)
|
|
966
|
+
for (let dx = 0; dx < cw; dx++) {
|
|
967
|
+
const r = ((y + dy) * gw + x + dx) * nodes;
|
|
968
|
+
for (let n = 0; n < nodes; n++)
|
|
969
|
+
sum += group.perNode[n][c][dy * cw + dx] * radiance[r + n];
|
|
970
|
+
}
|
|
971
|
+
e[y * T + x] = e[y * T + x] + sum;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return out;
|
|
977
|
+
}
|
|
978
|
+
// At least the in-focus tiles one output tile's source spans plus one more row of them, so the next output tile of a row
|
|
979
|
+
// reuses them (about 37 MiB at radius 512); otherwise the byte budget.
|
|
980
|
+
const span = Math.ceil((LARGE_OUTPUT_TILE + 2 * input.largeKernel.radius) / LARGE_E_TILE) + 1;
|
|
981
|
+
const inFocus = new Map(), inFocusCapacity = Math.max(span * (span + 1), Math.floor(LARGE_CACHE_BYTES / (3 * LARGE_E_TILE * LARGE_E_TILE * 8)));
|
|
982
|
+
function inFocusTileAt(tx, ty) {
|
|
983
|
+
const T = LARGE_E_TILE, key = (ty + 1048576) * 2097152 + tx + 1048576;
|
|
984
|
+
let tile = inFocus.get(key);
|
|
985
|
+
if (tile) {
|
|
986
|
+
inFocus.delete(key);
|
|
987
|
+
inFocus.set(key, tile);
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
tile = inFocusTile(tx * T, ty * T, T);
|
|
991
|
+
inFocus.set(key, tile);
|
|
992
|
+
if (inFocus.size > inFocusCapacity)
|
|
993
|
+
inFocus.delete(inFocus.keys().next().value);
|
|
994
|
+
}
|
|
995
|
+
return tile;
|
|
996
|
+
}
|
|
997
|
+
// Which sites of a tile row hold channel c: the CFA's 2 × 2 period, from channelAt (tile origins are even).
|
|
998
|
+
const phase = [0, 1].map((py) => [0, 1].map((px) => input.channelAt(px, py)));
|
|
999
|
+
const patterns = [0, 1, 2].map((c) => (row) => {
|
|
1000
|
+
const p = phase[row & 1], even = p[0] === c, odd = p[1] === c;
|
|
1001
|
+
return even && odd ? { start: 0, step: 1 } : even ? { start: 0, step: 2 } : odd ? { start: 1, step: 2 } : null;
|
|
1002
|
+
});
|
|
1003
|
+
state.lattice = createSiteKernelRunsLattice({ runs: input.largeKernel, tile: LARGE_OUTPUT_TILE, fields: 3, capacityBytes: LARGE_CACHE_BYTES,
|
|
1004
|
+
pattern: (c) => patterns[c], source: (x0, y0, width, height) => (c) => {
|
|
1005
|
+
// Copied from the in-focus tiles the rectangle overlaps, tile by tile, row segment by row segment (the values are copies).
|
|
1006
|
+
const samples = new Float64Array(width * height), T = LARGE_E_TILE;
|
|
1007
|
+
for (let ty = Math.floor(y0 / T); ty * T < y0 + height; ty++)
|
|
1008
|
+
for (let tx = Math.floor(x0 / T); tx * T < x0 + width; tx++) {
|
|
1009
|
+
const tile = inFocusTileAt(tx, ty)[c], ax = Math.max(x0, tx * T), bx = Math.min(x0 + width, (tx + 1) * T), ay = Math.max(y0, ty * T), by = Math.min(y0 + height, (ty + 1) * T);
|
|
1010
|
+
for (let y = ay; y < by; y++)
|
|
1011
|
+
samples.set(tile.subarray((y - ty * T) * T + ax - tx * T, (y - ty * T) * T + bx - tx * T), (y - y0) * width + ax - x0);
|
|
1012
|
+
}
|
|
1013
|
+
return samples;
|
|
1014
|
+
} });
|
|
1015
|
+
return state;
|
|
1016
|
+
}
|
|
867
1017
|
//# sourceMappingURL=expected-signal.js.map
|