@photivra/engine 2.16.0 → 2.18.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 +104 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +24 -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/camera/sensor-equivalence.d.ts +156 -0
- package/dist/camera/sensor-equivalence.js +208 -0
- package/dist/camera/sensor-equivalence.js.map +1 -0
- package/dist/capture/native-environment-tabulated.d.ts +5 -0
- package/dist/capture/native-environment-tabulated.js +1 -1
- package/dist/capture/native-environment-tabulated.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/equipment/ideal-lens.d.ts +25 -0
- package/dist/equipment/ideal-lens.js +21 -0
- package/dist/equipment/ideal-lens.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +94 -0
- package/dist/facade/catalogue-lens-signal.js +362 -0
- package/dist/facade/catalogue-lens-signal.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 +9 -0
- package/dist/facade/defocus-image-space-provider.js +101 -9
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +29 -0
- package/dist/facade/diffraction-provider.js +117 -11
- 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 +61 -0
- package/dist/facade/display-view-lens-signal.js +502 -0
- package/dist/facade/display-view-lens-signal.js.map +1 -0
- package/dist/facade/expected-signal-setup.d.ts +15 -0
- package/dist/facade/expected-signal-setup.js +19 -2
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +13 -0
- package/dist/facade/expected-signal.js +155 -1
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +7 -0
- package/dist/facade/generic-lens-capture.js +9 -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 +67 -7
- package/dist/facade/simulate-capture.js +400 -64
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +23 -17
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +0 -9
- package/dist/gpu/display-view-quality-gpu.js +6 -0
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.js +2 -0
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -0
- 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 +41 -19
- package/dist/optics/defocus-disc-kernel.js +44 -23
- 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 +6 -0
- package/dist/optics/sensor-environment-query.js +13 -6
- 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/docs/AGENT_GUIDE.md +4 -0
- package/docs/API_REFERENCE.md +77 -1
- package/docs/QUICK_START.md +305 -17
- package/docs/USAGE.md +98 -5
- package/docs/api/camera-sensor-equivalence.md +209 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +281 -0
- package/docs/api/equipment-ideal-lens.md +26 -0
- package/docs/api/exports.json +202 -2
- package/docs/api/facade-simulate-capture.md +48 -4
- package/docs/api/optics-defocus-bokeh-kernel.md +145 -0
- package/docs/api/optics-defocus-disc-kernel.md +22 -15
- package/docs/api/optics-sensor-environment-query.md +6 -0
- package/docs/api/output-generic-lens-corrections.md +39 -0
- package/llms.txt +3 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"display-view-gpu.js","sourceRoot":"","sources":["../../src/facade/display-view-gpu.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #481: how a prepared display view chooses between a WebGPU preview renderer and the CPU reference, and how a render labels\n * which ran. This module holds the option, the label and the renderer contract and imports nothing from the GPU code, so a\n * bundle that never asks for the GPU (`gpu: \"off\"`, the default) does not contain it; the device handling lives in\n * `display-view-gpu-session.ts`, loaded on demand by `prepareDisplayView` when `gpu` is `\"auto\"` or `\"require\"`.\n *\n * The vocabulary is the image-kernel accelerator's (`createImageKernelAccelerator`, docs/WEBGPU.md): `\"auto\"` is\n * `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is\n * `backend: \"cpu-reference\"`. The render's `diagnostics.fast.execution` names the backend that ran, the fallback reason and the\n * bound in the fields of `ImageKernelExecution`, plus the adapter and the run-time conformance check.\n *\n * No GPU display-view renderer exists yet (#478, #479): until one registers itself through {@link setDisplayViewGpuRendererFactory},\n * `\"auto\"` runs the CPU reference labelled `unsupported-on-backend` without touching a device, and `\"require\"` fails with that reason.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { ChartCompositeProbeRecord, ImageKernelAdapterDescription, ImageKernelBackend, ImageKernelExecution, ImageKernelFallbackReason } from \"../gpu/image-kernel-execution.js\";\nimport type { DisplayViewRenderResult, DisplayViewSetup } from \"./display-view.js\";\nimport type { SimulateCaptureFastReport } from \"./simulate-capture.js\";\n\n/**\n * 2.11 (#481): whether a prepared display view may render on the GPU. `\"off\"` (the default) is the CPU reference, today's\n * behaviour byte for byte. `\"auto\"` renders on WebGPU when a conforming device is available and the request is supported, and\n * otherwise runs the CPU reference and says why. `\"require\"` fails instead of falling back. In the accelerator's words: `\"auto\"`\n * is `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is `backend: \"cpu-reference\"`.\n */\nexport type DisplayViewGpuMode = \"off\" | \"auto\" | \"require\";\n\n/** 2.11 (#481): the second argument of `prepareDisplayView`. */\nexport interface PrepareDisplayViewOptions {\n /** See {@link DisplayViewGpuMode}. Default `\"off\"`. */\n gpu?: DisplayViewGpuMode;\n /**\n * Where the device comes from, with `gpu: \"auto\"` or `\"require\"`: a WebGPU entry point (a worker's `navigator.gpu`), or an\n * `ImageKernelAccelerator` whose device the view shares (the accelerator keeps ownership; `dispose()` of the prepared view\n * leaves it alone). Without it the engine acquires a device from `globalThis.navigator.gpu` and owns it until `dispose()`.\n * Refused with `gpu: \"off\"`.\n */\n gpuDevice?: unknown;\n}\n\n/** The parsed form of {@link PrepareDisplayViewOptions}. @internal */\nexport interface ParsedPrepareDisplayViewOptions { gpu: DisplayViewGpuMode; gpuDevice: unknown }\n\n/** Internal: validates the second argument of `prepareDisplayView`. */\nexport function parsePrepareDisplayViewOptions(value: unknown): ParsedPrepareDisplayViewOptions {\n if (value === undefined) return { gpu: \"off\", gpuDevice: undefined };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new InvalidConfigurationError(\"prepareDisplayView options must be an object { gpu, gpuDevice }.\");\n for (const key of Object.keys(value)) if (key !== \"gpu\" && key !== \"gpuDevice\") throw new InvalidConfigurationError(\"Unknown prepareDisplayView options field: \" + key + \".\");\n const { gpu, gpuDevice } = value as Record<string, unknown>;\n const mode: DisplayViewGpuMode = gpu === undefined ? \"off\" : gpu as DisplayViewGpuMode;\n if (mode !== \"off\" && mode !== \"auto\" && mode !== \"require\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpu must be \\\"off\\\", \\\"auto\\\" or \\\"require\\\".\");\n if (gpuDevice !== undefined && (typeof gpuDevice !== \"object\" || gpuDevice === null)) throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice must be a WebGPU entry point (navigator.gpu) or an ImageKernelAccelerator.\");\n if (gpuDevice !== undefined && mode === \"off\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice needs gpu \\\"auto\\\" or \\\"require\\\".\");\n return { gpu: mode, gpuDevice };\n}\n\n/**\n * 2.11 (#481): why a display view that asked for the GPU (`gpu: \"auto\"`) ran the CPU reference. The accelerator's acquisition\n * reasons, plus: `unsupported-on-backend` (no GPU display-view renderer exists in this version, or it does not support the\n * request), `conformance-failed` (the device failed the run-time self-check before the view's first GPU render), `device-lost`\n * (the device was lost; the rest of the view runs on the CPU) and `disposed` (the prepared view was disposed).\n */\nexport type DisplayViewGpuFallbackReason = ImageKernelFallbackReason | \"unsupported-on-backend\" | \"conformance-failed\" | \"device-lost\" | \"disposed\";\n\n/** 2.11 (#481): the run-time conformance self-check of a device, run once before the view's first GPU render. */\nexport interface DisplayViewGpuConformance {\n /** The ids of the cases run, in order (the built-in image-kernel cases, then the renderer's own). */\n cases: readonly string[];\n passed: boolean;\n /** The first case that failed, or null. */\n failedCase: string | null;\n /** What the failed case reported, or null. */\n detail: string | null;\n}\n\n/**\n * 2.11 (#481): where a display view's render ran, in the words of the image-kernel execution record (`ImageKernelExecution`):\n * `backend` names the path that ran (`cpu-reference`, the float64 engine reference, or `webgpu-f32`, a float32 approximation\n * within `errorBound`), `requestedBackend` what `gpu` asked for, and `fallbackReason` why a request for the GPU ran the CPU.\n * `adapter` describes the device's adapter whenever one was involved, also when the view then fell back; `conformance` is\n * the device's self-check once it has run. A CPU render keeps `assurance: \"engine-reference\"` and `fast: false`.\n */\nexport interface DisplayViewExecution extends Omit<ImageKernelExecution, \"fallbackReason\"> {\n /** The `gpu` option the view was prepared with. */\n gpu: DisplayViewGpuMode;\n fallbackReason: DisplayViewGpuFallbackReason | null;\n adapter: ImageKernelAdapterDescription | null;\n conformance: DisplayViewGpuConformance | null;\n /**\n * 2.11: the composite chart kernel's device probe when this render needed it (composite charts, the 2.3 elements and\n * `material-layers` scenes): the device class and the product path the kernel ran with, or, on a refusal (`fallbackReason`\n * `unsupported-on-backend`), why. Null when the render did not run the probe (the 2.0 presets, `gpu: \"off\"`).\n */\n compositeProbe: ChartCompositeProbeRecord | null;\n}\n\n/** 2.11 (#481): `diagnostics.fast` of a display view: the capture's report plus where the render ran. */\nexport interface DisplayViewFastReport extends SimulateCaptureFastReport {\n /** Where this render ran (see {@link DisplayViewExecution}). `assurance` above follows it. */\n execution: DisplayViewExecution;\n}\n\nconst CPU_LIMITATIONS = /* @__PURE__ */ Object.freeze([\"Float64 engine reference execution; results are the reference by definition\"]);\n\n/** Internal: the execution record of a display view rendered on the CPU reference. `requested` names what `gpu` asked for. */\nexport function cpuDisplayViewExecution(gpu: DisplayViewGpuMode, fallbackReason: DisplayViewGpuFallbackReason | null, adapter: ImageKernelAdapterDescription | null,\n conformance: DisplayViewGpuConformance | null, detail?: string, compositeProbe: ChartCompositeProbeRecord | null = null): DisplayViewExecution {\n const requestedBackend: ImageKernelBackend = gpu === \"off\" ? \"cpu-reference\" : \"webgpu-f32\";\n const limitations = detail === undefined ? CPU_LIMITATIONS : Object.freeze([...CPU_LIMITATIONS, \"WebGPU was requested (gpu: \\\"\" + gpu + \"\\\") and the CPU reference ran: \" + detail]);\n return Object.freeze({ gpu, backend: \"cpu-reference\", requestedBackend, fallbackReason, arithmetic: \"float64\", assurance: \"engine-reference\", fast: false,\n bitIdenticalToReference: true, errorBound: null, diagnosticsExact: true, diagnosticUncertaintySampleCount: 0, limitations, adapter, conformance, compositeProbe });\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// The renderer contract (#478 implements it; until then the only implementation is a test fake).\n\n/**\n * Internal (#481): the device a renderer or conformance case runs on. It is the engine's `WebGpuRuntime` object\n * (`src/gpu/webgpu-runtime.ts`: `device`, `guard`, `destroy` and the rest), seen here through its WebGPU-free members only so\n * that no WebGPU type reaches the published declarations (docs/WEBGPU.md); a renderer in the engine casts it to `WebGpuRuntime`.\n */\nexport interface DisplayViewGpuRuntimeHandle {\n readonly adapter: ImageKernelAdapterDescription | null;\n readonly maximumStorageBindingBytes: number;\n readonly maximumWorkgroupsPerDimension: number;\n isLost(): boolean;\n}\n\n/** What a prepared request asks of a GPU renderer (`supports`): the options that shape a render. */\nexport interface DisplayViewGpuRequest {\n ahd: boolean;\n diffraction: boolean;\n antiAliasing: boolean;\n /** Pixel-aperture samples per axis (1: centre sampling). */\n boxSamples: number;\n toneCurve: \"identity\" | \"positive-reinhard-per-channel\";\n /**\n * #492: true when `lens.defocus` needs the defocus disc (out of focus): the expected signal then carries the disc, or each\n * node's diffraction kernel convolved with it, as site kernels (#492 step 2b), which the engine's renderer gathers in its\n * Quality site pass (step 2c). Absent or false otherwise.\n */\n defocus?: boolean;\n /** The scene's form: an engine chart, a declared `material-layers` scene (#491), or a caller function. */\n scene: \"chart\" | \"material-layers\" | \"material-function\" | \"reflectance-function\" | \"radiance-function\";\n}\n/** One render of a region: the CPU renderer's own setup (tables, readout, demosaic, region, callbacks) and the render's signal. */\nexport interface DisplayViewGpuPart { setup: DisplayViewSetup; signal: AbortSignal | undefined }\n/** What a GPU render returns: the CPU renderer's result shape plus the a-priori bound of its values against the reference. */\nexport interface DisplayViewGpuRenderOutput extends DisplayViewRenderResult {\n errorBound: NonNullable<ImageKernelExecution[\"errorBound\"]>;\n /** Samples whose float32 classification fell inside its bound (0 when none). */\n diagnosticUncertaintySampleCount?: number;\n /** The composite chart kernel's device probe record, when the render used the composite material module. */\n compositeProbe?: ChartCompositeProbeRecord;\n}\n/** A conformance case: returns `true` when the device agrees with the reference within the case's bound, or a sentence saying how it did not. */\nexport interface DisplayViewGpuConformanceCase { id: string; run(runtime: DisplayViewGpuRuntimeHandle, signal: AbortSignal | undefined): Promise<true | string> }\n/**\n * Internal (#481): the contract a GPU display-view renderer fulfils (#478). One instance serves one device, created by the\n * registered factory for each prepared view that acquires or shares a device. `supports` says whether the renderer covers a\n * prepared request (`true`) or why not (a sentence; the view then runs the CPU reference labelled `unsupported-on-backend`).\n * `render` renders one region on the device and must throw `ImageKernelExecutionError` with reason `device-lost` or `aborted`\n * for those two events, as the accelerator's operations do; a render that throws `unsupported-on-backend` (a request the\n * renderer finds it does not cover only when rendering) runs the CPU reference labelled with that reason, or fails with it under\n * `\"require\"`. `conformanceCases` are run after the built-in image-kernel cases before the device's first render; a failure keeps\n * the whole view on the CPU. `dispose` releases what the renderer holds on the device (#491: uploaded scene records); the session\n * calls it when the prepared view is disposed.\n */\nexport interface DisplayViewGpuRenderer {\n readonly id: string;\n supports(request: DisplayViewGpuRequest): true | string;\n render(part: DisplayViewGpuPart): Promise<DisplayViewGpuRenderOutput>;\n readonly conformanceCases?: readonly DisplayViewGpuConformanceCase[];\n dispose?(): void;\n}\nexport type DisplayViewGpuRendererFactory = (runtime: DisplayViewGpuRuntimeHandle) => DisplayViewGpuRenderer;\n\nlet rendererFactory: DisplayViewGpuRendererFactory | null = null;\n/**\n * Internal (#481): registers the renderer that `prepareDisplayView` uses for `gpu: \"auto\" | \"require\"`, and returns the one\n * registered before (null: none). #478's renderer registers itself here; tests register a fake. With none registered no device\n * is requested and every GPU request falls back (or fails) with `unsupported-on-backend`.\n */\nexport function setDisplayViewGpuRendererFactory(factory: DisplayViewGpuRendererFactory | null): DisplayViewGpuRendererFactory | null {\n if (factory !== null && typeof factory !== \"function\") throw new InvalidConfigurationError(\"setDisplayViewGpuRendererFactory expects a function or null.\");\n const previous = rendererFactory;\n rendererFactory = factory;\n return previous;\n}\n/** Internal: the registered factory. */\nexport function displayViewGpuRendererFactory(): DisplayViewGpuRendererFactory | null { return rendererFactory; }\n"]}
|
|
1
|
+
{"version":3,"file":"display-view-gpu.js","sourceRoot":"","sources":["../../src/facade/display-view-gpu.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #481: how a prepared display view chooses between a WebGPU preview renderer and the CPU reference, and how a render labels\n * which ran. This module holds the option, the label and the renderer contract and imports nothing from the GPU code, so a\n * bundle that never asks for the GPU (`gpu: \"off\"`, the default) does not contain it; the device handling lives in\n * `display-view-gpu-session.ts`, loaded on demand by `prepareDisplayView` when `gpu` is `\"auto\"` or `\"require\"`.\n *\n * The vocabulary is the image-kernel accelerator's (`createImageKernelAccelerator`, docs/WEBGPU.md): `\"auto\"` is\n * `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is\n * `backend: \"cpu-reference\"`. The render's `diagnostics.fast.execution` names the backend that ran, the fallback reason and the\n * bound in the fields of `ImageKernelExecution`, plus the adapter and the run-time conformance check.\n *\n * No GPU display-view renderer exists yet (#478, #479): until one registers itself through {@link setDisplayViewGpuRendererFactory},\n * `\"auto\"` runs the CPU reference labelled `unsupported-on-backend` without touching a device, and `\"require\"` fails with that reason.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { ChartCompositeProbeRecord, ImageKernelAdapterDescription, ImageKernelBackend, ImageKernelExecution, ImageKernelFallbackReason } from \"../gpu/image-kernel-execution.js\";\nimport type { DisplayViewRenderResult, DisplayViewSetup } from \"./display-view.js\";\nimport type { SimulateCaptureFastReport } from \"./simulate-capture.js\";\n\n/**\n * 2.11 (#481): whether a prepared display view may render on the GPU. `\"off\"` (the default) is the CPU reference, today's\n * behaviour byte for byte. `\"auto\"` renders on WebGPU when a conforming device is available and the request is supported, and\n * otherwise runs the CPU reference and says why. `\"require\"` fails instead of falling back. In the accelerator's words: `\"auto\"`\n * is `backend: \"webgpu-f32\"` with `fallback: \"cpu-reference\"`, `\"require\"` is `fallback: \"fail\"`, `\"off\"` is `backend: \"cpu-reference\"`.\n */\nexport type DisplayViewGpuMode = \"off\" | \"auto\" | \"require\";\n\n/** 2.11 (#481): the second argument of `prepareDisplayView`. */\nexport interface PrepareDisplayViewOptions {\n /** See {@link DisplayViewGpuMode}. Default `\"off\"`. */\n gpu?: DisplayViewGpuMode;\n /**\n * Where the device comes from, with `gpu: \"auto\"` or `\"require\"`: a WebGPU entry point (a worker's `navigator.gpu`), or an\n * `ImageKernelAccelerator` whose device the view shares (the accelerator keeps ownership; `dispose()` of the prepared view\n * leaves it alone). Without it the engine acquires a device from `globalThis.navigator.gpu` and owns it until `dispose()`.\n * Refused with `gpu: \"off\"`.\n */\n gpuDevice?: unknown;\n}\n\n/** The parsed form of {@link PrepareDisplayViewOptions}. @internal */\nexport interface ParsedPrepareDisplayViewOptions { gpu: DisplayViewGpuMode; gpuDevice: unknown }\n\n/** Internal: validates the second argument of `prepareDisplayView`. */\nexport function parsePrepareDisplayViewOptions(value: unknown): ParsedPrepareDisplayViewOptions {\n if (value === undefined) return { gpu: \"off\", gpuDevice: undefined };\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new InvalidConfigurationError(\"prepareDisplayView options must be an object { gpu, gpuDevice }.\");\n for (const key of Object.keys(value)) if (key !== \"gpu\" && key !== \"gpuDevice\") throw new InvalidConfigurationError(\"Unknown prepareDisplayView options field: \" + key + \".\");\n const { gpu, gpuDevice } = value as Record<string, unknown>;\n const mode: DisplayViewGpuMode = gpu === undefined ? \"off\" : gpu as DisplayViewGpuMode;\n if (mode !== \"off\" && mode !== \"auto\" && mode !== \"require\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpu must be \\\"off\\\", \\\"auto\\\" or \\\"require\\\".\");\n if (gpuDevice !== undefined && (typeof gpuDevice !== \"object\" || gpuDevice === null)) throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice must be a WebGPU entry point (navigator.gpu) or an ImageKernelAccelerator.\");\n if (gpuDevice !== undefined && mode === \"off\") throw new InvalidConfigurationError(\"prepareDisplayView options.gpuDevice needs gpu \\\"auto\\\" or \\\"require\\\".\");\n return { gpu: mode, gpuDevice };\n}\n\n/**\n * 2.11 (#481): why a display view that asked for the GPU (`gpu: \"auto\"`) ran the CPU reference. The accelerator's acquisition\n * reasons, plus: `unsupported-on-backend` (no GPU display-view renderer exists in this version, or it does not support the\n * request), `conformance-failed` (the device failed the run-time self-check before the view's first GPU render), `device-lost`\n * (the device was lost; the rest of the view runs on the CPU) and `disposed` (the prepared view was disposed).\n */\nexport type DisplayViewGpuFallbackReason = ImageKernelFallbackReason | \"unsupported-on-backend\" | \"conformance-failed\" | \"device-lost\" | \"disposed\";\n\n/** 2.11 (#481): the run-time conformance self-check of a device, run once before the view's first GPU render. */\nexport interface DisplayViewGpuConformance {\n /** The ids of the cases run, in order (the built-in image-kernel cases, then the renderer's own). */\n cases: readonly string[];\n passed: boolean;\n /** The first case that failed, or null. */\n failedCase: string | null;\n /** What the failed case reported, or null. */\n detail: string | null;\n}\n\n/**\n * 2.11 (#481): where a display view's render ran, in the words of the image-kernel execution record (`ImageKernelExecution`):\n * `backend` names the path that ran (`cpu-reference`, the float64 engine reference, or `webgpu-f32`, a float32 approximation\n * within `errorBound`), `requestedBackend` what `gpu` asked for, and `fallbackReason` why a request for the GPU ran the CPU.\n * `adapter` describes the device's adapter whenever one was involved, also when the view then fell back; `conformance` is\n * the device's self-check once it has run. A CPU render keeps `assurance: \"engine-reference\"` and `fast: false`.\n */\nexport interface DisplayViewExecution extends Omit<ImageKernelExecution, \"fallbackReason\"> {\n /** The `gpu` option the view was prepared with. */\n gpu: DisplayViewGpuMode;\n fallbackReason: DisplayViewGpuFallbackReason | null;\n adapter: ImageKernelAdapterDescription | null;\n conformance: DisplayViewGpuConformance | null;\n /**\n * 2.11: the composite chart kernel's device probe when this render needed it (composite charts, the 2.3 elements and\n * `material-layers` scenes): the device class and the product path the kernel ran with, or, on a refusal (`fallbackReason`\n * `unsupported-on-backend`), why. Null when the render did not run the probe (the 2.0 presets, `gpu: \"off\"`).\n */\n compositeProbe: ChartCompositeProbeRecord | null;\n}\n\n/** 2.11 (#481): `diagnostics.fast` of a display view: the capture's report plus where the render ran. */\nexport interface DisplayViewFastReport extends SimulateCaptureFastReport {\n /** Where this render ran (see {@link DisplayViewExecution}). `assurance` above follows it. */\n execution: DisplayViewExecution;\n}\n\nconst CPU_LIMITATIONS = /* @__PURE__ */ Object.freeze([\"Float64 engine reference execution; results are the reference by definition\"]);\n\n/** Internal: the execution record of a display view rendered on the CPU reference. `requested` names what `gpu` asked for. */\nexport function cpuDisplayViewExecution(gpu: DisplayViewGpuMode, fallbackReason: DisplayViewGpuFallbackReason | null, adapter: ImageKernelAdapterDescription | null,\n conformance: DisplayViewGpuConformance | null, detail?: string, compositeProbe: ChartCompositeProbeRecord | null = null): DisplayViewExecution {\n const requestedBackend: ImageKernelBackend = gpu === \"off\" ? \"cpu-reference\" : \"webgpu-f32\";\n const limitations = detail === undefined ? CPU_LIMITATIONS : Object.freeze([...CPU_LIMITATIONS, \"WebGPU was requested (gpu: \\\"\" + gpu + \"\\\") and the CPU reference ran: \" + detail]);\n return Object.freeze({ gpu, backend: \"cpu-reference\", requestedBackend, fallbackReason, arithmetic: \"float64\", assurance: \"engine-reference\", fast: false,\n bitIdenticalToReference: true, errorBound: null, diagnosticsExact: true, diagnosticUncertaintySampleCount: 0, limitations, adapter, conformance, compositeProbe });\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// The renderer contract (#478 implements it; until then the only implementation is a test fake).\n\n/**\n * Internal (#481): the device a renderer or conformance case runs on. It is the engine's `WebGpuRuntime` object\n * (`src/gpu/webgpu-runtime.ts`: `device`, `guard`, `destroy` and the rest), seen here through its WebGPU-free members only so\n * that no WebGPU type reaches the published declarations (docs/WEBGPU.md); a renderer in the engine casts it to `WebGpuRuntime`.\n */\nexport interface DisplayViewGpuRuntimeHandle {\n readonly adapter: ImageKernelAdapterDescription | null;\n readonly maximumStorageBindingBytes: number;\n readonly maximumWorkgroupsPerDimension: number;\n isLost(): boolean;\n}\n\n/** What a prepared request asks of a GPU renderer (`supports`): the options that shape a render. */\nexport interface DisplayViewGpuRequest {\n ahd: boolean;\n diffraction: boolean;\n antiAliasing: boolean;\n /** Pixel-aperture samples per axis (1: centre sampling). */\n boxSamples: number;\n toneCurve: \"identity\" | \"positive-reinhard-per-channel\";\n /**\n * #492: true when `lens.defocus` needs the defocus disc (out of focus): the expected signal then carries the disc, or each\n * node's diffraction kernel convolved with it, as site kernels (#492 step 2b), which the engine's renderer gathers in its\n * Quality site pass (step 2c). Absent or false otherwise.\n */\n defocus?: boolean;\n /**\n * #564: true when the defocus kernel's radius is above 64 sites, so the expected signal convolves it by runs on the CPU\n * (`largeKernel`); no GPU renderer takes it. Absent or false otherwise.\n */\n largeDefocus?: boolean;\n /**\n * #563: true when the request has `lens.projection`, `lens.distortion`, `lens.vignetting` or camera rotation: the CPU reference\n * renders it (display-view-lens-signal.ts) and no GPU renderer is asked or device requested (`unsupported-on-backend`).\n */\n lens?: boolean;\n /** The scene's form: an engine chart, a declared `material-layers` scene (#491), or a caller function. */\n scene: \"chart\" | \"material-layers\" | \"material-function\" | \"reflectance-function\" | \"radiance-function\";\n}\n/** One render of a region: the CPU renderer's own setup (tables, readout, demosaic, region, callbacks) and the render's signal. */\nexport interface DisplayViewGpuPart { setup: DisplayViewSetup; signal: AbortSignal | undefined }\n/** What a GPU render returns: the CPU renderer's result shape plus the a-priori bound of its values against the reference. */\nexport interface DisplayViewGpuRenderOutput extends DisplayViewRenderResult {\n errorBound: NonNullable<ImageKernelExecution[\"errorBound\"]>;\n /** Samples whose float32 classification fell inside its bound (0 when none). */\n diagnosticUncertaintySampleCount?: number;\n /** The composite chart kernel's device probe record, when the render used the composite material module. */\n compositeProbe?: ChartCompositeProbeRecord;\n}\n/** A conformance case: returns `true` when the device agrees with the reference within the case's bound, or a sentence saying how it did not. */\nexport interface DisplayViewGpuConformanceCase { id: string; run(runtime: DisplayViewGpuRuntimeHandle, signal: AbortSignal | undefined): Promise<true | string> }\n/**\n * Internal (#481): the contract a GPU display-view renderer fulfils (#478). One instance serves one device, created by the\n * registered factory for each prepared view that acquires or shares a device. `supports` says whether the renderer covers a\n * prepared request (`true`) or why not (a sentence; the view then runs the CPU reference labelled `unsupported-on-backend`).\n * `render` renders one region on the device and must throw `ImageKernelExecutionError` with reason `device-lost` or `aborted`\n * for those two events, as the accelerator's operations do; a render that throws `unsupported-on-backend` (a request the\n * renderer finds it does not cover only when rendering) runs the CPU reference labelled with that reason, or fails with it under\n * `\"require\"`. `conformanceCases` are run after the built-in image-kernel cases before the device's first render; a failure keeps\n * the whole view on the CPU. `dispose` releases what the renderer holds on the device (#491: uploaded scene records); the session\n * calls it when the prepared view is disposed.\n */\nexport interface DisplayViewGpuRenderer {\n readonly id: string;\n supports(request: DisplayViewGpuRequest): true | string;\n render(part: DisplayViewGpuPart): Promise<DisplayViewGpuRenderOutput>;\n readonly conformanceCases?: readonly DisplayViewGpuConformanceCase[];\n dispose?(): void;\n}\nexport type DisplayViewGpuRendererFactory = (runtime: DisplayViewGpuRuntimeHandle) => DisplayViewGpuRenderer;\n\nlet rendererFactory: DisplayViewGpuRendererFactory | null = null;\n/**\n * Internal (#481): registers the renderer that `prepareDisplayView` uses for `gpu: \"auto\" | \"require\"`, and returns the one\n * registered before (null: none). #478's renderer registers itself here; tests register a fake. With none registered no device\n * is requested and every GPU request falls back (or fails) with `unsupported-on-backend`.\n */\nexport function setDisplayViewGpuRendererFactory(factory: DisplayViewGpuRendererFactory | null): DisplayViewGpuRendererFactory | null {\n if (factory !== null && typeof factory !== \"function\") throw new InvalidConfigurationError(\"setDisplayViewGpuRendererFactory expects a function or null.\");\n const previous = rendererFactory;\n rendererFactory = factory;\n return previous;\n}\n/** Internal: the registered factory. */\nexport function displayViewGpuRendererFactory(): DisplayViewGpuRendererFactory | null { return rendererFactory; }\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { PreparedDefocusImageSpaceGeometry } from "./defocus-image-space-provider.js";
|
|
2
|
+
import { type ResolveLensSampledPsfInput } from "../optics/lens-psf-profile.js";
|
|
3
|
+
import { type DisplayViewExpectedSignal, type DisplayViewExpectedSignalInput, type GroupTables } from "./expected-signal.js";
|
|
4
|
+
/** #569: field zones along the native raster's longer side. */
|
|
5
|
+
export declare const DISPLAY_VIEW_LENS_PSF_ZONES = 64;
|
|
6
|
+
/** #569: a field-dependent PSF for the display view (see the module comment). */
|
|
7
|
+
export interface DisplayViewLensPsf {
|
|
8
|
+
/** Kernel radius and zone side, in sites. */
|
|
9
|
+
readonly radius: number;
|
|
10
|
+
readonly zoneSites: number;
|
|
11
|
+
/** The kernels at the frame centre's zone, for the expected-signal input's lattice groups and reach. */
|
|
12
|
+
axisKernels(wavelengthsNm: readonly number[]): {
|
|
13
|
+
radius: number;
|
|
14
|
+
perNode: Float64Array[];
|
|
15
|
+
};
|
|
16
|
+
/** The combined kernels of the zone of native site (x, y), for an input whose kernels are the axis kernels. */
|
|
17
|
+
tables(input: DisplayViewExpectedSignalInput): (x: number, y: number) => readonly GroupTables[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* #569: the field-dependent PSF of a display view from the sampled PSF the capture resolves (everything but the field point and wavelength),
|
|
21
|
+
* of kernel radius R, on the W × H native raster of `pitch` µm.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createDisplayViewLensPsf(psf: Omit<ResolveLensSampledPsfInput, "fieldPointMm" | "wavelengthNm">, R: number, W: number, H: number, pitch: number): DisplayViewLensPsf;
|
|
24
|
+
/** What the lens signal needs from the resolved request. */
|
|
25
|
+
export interface DisplayViewLensSetup {
|
|
26
|
+
geometry: PreparedDefocusImageSpaceGeometry;
|
|
27
|
+
/** Temporal midpoints, seconds from exposure start: (i + 0.5)·shutter/T with camera rotation, [0] without. */
|
|
28
|
+
times: readonly number[];
|
|
29
|
+
/** The planar provider's plane distance and frame-unit scale. */
|
|
30
|
+
distanceM: number;
|
|
31
|
+
metresPerFrameUnit: number;
|
|
32
|
+
pitch: number;
|
|
33
|
+
imagingArea: {
|
|
34
|
+
widthMm: number;
|
|
35
|
+
heightMm: number;
|
|
36
|
+
};
|
|
37
|
+
boxSamples: number;
|
|
38
|
+
/** Anti-aliasing split spots (or one zero spot), in the order of the expected signal's offsets within each aperture node. */
|
|
39
|
+
spots: readonly {
|
|
40
|
+
offsetMicrometers: {
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
};
|
|
44
|
+
}[];
|
|
45
|
+
/**
|
|
46
|
+
* #569: a field-dependent lens PSF (`lens.catalogue`, {@link createDisplayViewLensPsf}): each site reads its field zone's kernels
|
|
47
|
+
* instead of the input's. The input's kernels (the axis zone's) set the lattice groups and the reach; absent, every site reads them.
|
|
48
|
+
*/
|
|
49
|
+
psf?: DisplayViewLensPsf | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* #569 (catalogue fisheyes): a ray that does not point toward the scene plane sees no scene (zero radiance), as the capture's provider
|
|
52
|
+
* with `zeroBeyondHorizon` gives it, instead of failing.
|
|
53
|
+
*/
|
|
54
|
+
zeroBeyondHorizon?: boolean | undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Checks that the mapping and the vignetting profile cover the farthest source point the view reads, (W/2 + reachSites, H/2 + reachSites)
|
|
58
|
+
* sites from the axis at each corner (reachSites: the kernel radius plus the largest query offset, less the half site to the outer site
|
|
59
|
+
* centre), and returns the factory of the view's expected signal for this request.
|
|
60
|
+
*/
|
|
61
|
+
export declare function prepareDisplayViewLensSignal(lens: DisplayViewLensSetup, nativeWidth: number, nativeHeight: number, reachSites: number, options: string): (input: DisplayViewExpectedSignalInput, spanColumns?: number) => DisplayViewExpectedSignal;
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* The display view's expected signal with a lens field mapping (`lens.projection`, `lens.distortion`), `lens.vignetting` or
|
|
4
|
+
* camera rotation (#563), in focus and with `lens.defocus` out of focus. Loaded on demand (a dynamic import in `runCapture`),
|
|
5
|
+
* so a display view without these options, and a bundle that never asks for them, does not load it.
|
|
6
|
+
*
|
|
7
|
+
* What it computes. The capture's environment chain forms, for site (i, j) of CFA channel c,
|
|
8
|
+
* μ_c(i, j) = Σ_o w_o · (1/T) Σ_t Σ_λ A_c(λ) Σ_k K_λ(k) · F(s) · L(D_t(s), λ), s = q_o(i, j) − k·p,
|
|
9
|
+
* with q_o the query point of offset o (pixel-aperture node minus anti-aliasing split), K_λ the site kernel (diffraction, the
|
|
10
|
+
* defocus disc or both; a single unit tap without them), D_t the scene ray of sensor point s at temporal midpoint t (inverse
|
|
11
|
+
* distortion, the declared projection, the camera rotation at t), F = T·A its field factor (vignetting throughput × distortion
|
|
12
|
+
* area factor) and A_c(λ) the electrons per unit spectral radiance on the axis (the display view's channel response, ideal
|
|
13
|
+
* optics). In focus with a kernel this is the environment PSF quadrature (the field factor applied once at the source point,
|
|
14
|
+
* before the PSF spreads the light); out of focus it is #548's image-space disc, the same sum in another order
|
|
15
|
+
* (`defocus-image-space-provider.ts`); without a kernel it is the environment's own per-query sum. Only the scene access changes:
|
|
16
|
+
* the readout, the estimators, the demosaic and the development run unchanged on these site values.
|
|
17
|
+
*
|
|
18
|
+
* Scene points. Without a kernel each query point is formed with the environment's own arithmetic (the site centre, aperture node
|
|
19
|
+
* and split offset of `native-environment-lean.ts`), then `lookOf` (the environment's mapping, rotation and field factor,
|
|
20
|
+
* #548) and the planar provider's plane-point arithmetic, so a point exactly on a chart boundary resolves as in the capture.
|
|
21
|
+
* With a kernel the sources are the site lattice of each fractional offset, with #548's lattice formula. Kernels, materials and
|
|
22
|
+
* per-material sums are the display view's own tables (`displayViewSignalTables`), shared with the rectilinear path.
|
|
23
|
+
*
|
|
24
|
+
* Caches (memory only, never values): site values in a ring of native rows across windows, and with a kernel each lattice
|
|
25
|
+
* point's material (or radiance at each node) and field factor per lattice group and temporal midpoint, in a two-dimensional
|
|
26
|
+
* ring tagged with the point, sized from the window and capped at LATTICE_CACHE_LIMIT entries (beyond it, points are
|
|
27
|
+
* evaluated as they are read).
|
|
28
|
+
*
|
|
29
|
+
* Field-dependent PSF (#569, `lens.catalogue`; {@link createDisplayViewLensPsf}). The capture's environment PSF quadrature resolves the
|
|
30
|
+
* catalogue lens's sampled PSF at each query point's own field position. The view cuts the native raster into square zones of
|
|
31
|
+
* Z = ⌈max(W, H) / DISPLAY_VIEW_LENS_PSF_ZONES⌉ sites (one site on frames of at most that many sites a side) and every site of a zone
|
|
32
|
+
* takes the PSF the capture's own resolver gives at the zone's centre, at each spectral node (an approximation: the PSF's change
|
|
33
|
+
* across half a zone). A profile kernel's rows run with image +Y (up) and its tap (i, j) reads (x − (i − c)·p, y + (j − c)·p) in
|
|
34
|
+
* native coordinates (`sensorPsfSourcePoint`), so the view's kernel (reading in(x − t)) is the profile kernel flipped vertically.
|
|
35
|
+
* Each zone's combined kernels are the expected signal's own tables (`buildTables`) with the zone's kernels, built when a site of
|
|
36
|
+
* the zone is first read and kept per input in a least-recently-used cache of at most 64 MB (at least four zones).
|
|
37
|
+
*
|
|
38
|
+
* Large defocus kernels (#564, `input.largeKernel`, radius above 64 sites). The image-space disc D is the same at every lattice
|
|
39
|
+
* group, spectral node and temporal midpoint, so it commutes with those sums as in the rectilinear signal (`expected-signal.ts`):
|
|
40
|
+
* μ_c(x) = Σ_s D(s)·E_c(x − s), with E_c(x) the sum above through the in-focus kernels (each node's diffraction kernel, or a unit
|
|
41
|
+
* tap) at site position x for channel c, whatever x's own channel. E is computed in fixed 64-site tiles (all three channels, each
|
|
42
|
+
* group's lattice points mapped once per tile and midpoint), and D ⊛ E_c by runs (`site-kernel-runs.ts`) in fixed 128-site tiles,
|
|
43
|
+
* each site taking its own channel: every value depends only on its site, so regions and parts equal the whole view.
|
|
44
|
+
*/
|
|
45
|
+
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
46
|
+
import { createSiteKernelRunsLattice } from "../optics/site-kernel-runs.js";
|
|
47
|
+
import { resolveLensSampledPsfKernel } from "../optics/lens-psf-profile.js";
|
|
48
|
+
import { buildTables, displayViewSignalTables } from "./expected-signal.js";
|
|
49
|
+
/** #569: field zones along the native raster's longer side. */
|
|
50
|
+
export const DISPLAY_VIEW_LENS_PSF_ZONES = 64;
|
|
51
|
+
/**
|
|
52
|
+
* #569: the field-dependent PSF of a display view from the sampled PSF the capture resolves (everything but the field point and wavelength),
|
|
53
|
+
* of kernel radius R, on the W × H native raster of `pitch` µm.
|
|
54
|
+
*/
|
|
55
|
+
export function createDisplayViewLensPsf(psf, R, W, H, pitch) {
|
|
56
|
+
const Z = Math.ceil(Math.max(W, H) / DISPLAY_VIEW_LENS_PSF_ZONES), zonesX = Math.ceil(W / Z), w = 2 * R + 1;
|
|
57
|
+
const zoneOf = (x, y) => Math.floor(y / Z) * zonesX + Math.floor(x / Z);
|
|
58
|
+
const kernelsOf = (zone, wavelengthsNm) => {
|
|
59
|
+
// The zone's centre in sites from the top-left corner (an edge zone may be narrower), in image millimetres (+Y up).
|
|
60
|
+
const zx = zone % zonesX, zy = (zone - zx) / zonesX, fieldPointMm = { x: ((zx * Z + Math.min(W, zx * Z + Z)) / 2 - W / 2) * pitch / 1000,
|
|
61
|
+
y: (H / 2 - (zy * Z + Math.min(H, zy * Z + Z)) / 2) * pitch / 1000 };
|
|
62
|
+
return wavelengthsNm.map((wavelengthNm) => {
|
|
63
|
+
// Every node of the engine-built profile has the same (2R + 1)² window, so every resolved kernel has it too.
|
|
64
|
+
const values = resolveLensSampledPsfKernel({ ...psf, fieldPointMm, wavelengthNm }).normalizedIntensity, out = new Float64Array(w * w);
|
|
65
|
+
for (let j = 0; j < w; j++)
|
|
66
|
+
for (let i = 0; i < w; i++)
|
|
67
|
+
out[(w - 1 - j) * w + i] = values[j * w + i];
|
|
68
|
+
return out;
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
const caches = new WeakMap();
|
|
72
|
+
return { radius: R, zoneSites: Z,
|
|
73
|
+
axisKernels: (wavelengthsNm) => ({ radius: R, perNode: kernelsOf(zoneOf(W >> 1, H >> 1), wavelengthsNm) }),
|
|
74
|
+
tables(input) {
|
|
75
|
+
let read = caches.get(input);
|
|
76
|
+
if (read)
|
|
77
|
+
return read;
|
|
78
|
+
const cache = new Map();
|
|
79
|
+
let kept = 0, last = -1, lastTables = [];
|
|
80
|
+
read = (x, y) => {
|
|
81
|
+
const zone = zoneOf(x, y);
|
|
82
|
+
if (zone === last)
|
|
83
|
+
return lastTables;
|
|
84
|
+
let tables = cache.get(zone);
|
|
85
|
+
if (tables)
|
|
86
|
+
cache.delete(zone);
|
|
87
|
+
else {
|
|
88
|
+
tables = buildTables({ ...input, kernels: { radius: R, perNode: kernelsOf(zone, input.wavelengthsNm) } }).groups;
|
|
89
|
+
// Zones kept: at least 4, and at most 64 MB of them (every zone's tables have the same size).
|
|
90
|
+
kept ||= Math.max(4, (1 << 23) / tables.reduce((n, g) => n + g.cw * g.ch * (g.neutral.length + g.spectral.flat().length + g.perNode.flat().length), 0));
|
|
91
|
+
if (cache.size >= kept)
|
|
92
|
+
cache.delete(cache.keys().next().value);
|
|
93
|
+
}
|
|
94
|
+
cache.set(zone, tables);
|
|
95
|
+
last = zone;
|
|
96
|
+
lastTables = tables;
|
|
97
|
+
return tables;
|
|
98
|
+
};
|
|
99
|
+
caches.set(input, read);
|
|
100
|
+
return read;
|
|
101
|
+
} };
|
|
102
|
+
}
|
|
103
|
+
/** Largest lattice cache, in entries over all lattice groups and temporal midpoints (times the spectral nodes for radiance scenes). */
|
|
104
|
+
const LATTICE_CACHE_LIMIT = 1 << 22;
|
|
105
|
+
const pow2 = (n) => { let p = 1; while (p < n)
|
|
106
|
+
p *= 2; return p; };
|
|
107
|
+
/**
|
|
108
|
+
* Checks that the mapping and the vignetting profile cover the farthest source point the view reads, (W/2 + reachSites, H/2 + reachSites)
|
|
109
|
+
* sites from the axis at each corner (reachSites: the kernel radius plus the largest query offset, less the half site to the outer site
|
|
110
|
+
* centre), and returns the factory of the view's expected signal for this request.
|
|
111
|
+
*/
|
|
112
|
+
export function prepareDisplayViewLensSignal(lens, nativeWidth, nativeHeight, reachSites, options) {
|
|
113
|
+
const xMm = (nativeWidth / 2 + reachSites) * lens.pitch / 1000, yMm = (nativeHeight / 2 + reachSites) * lens.pitch / 1000;
|
|
114
|
+
for (const t of lens.times)
|
|
115
|
+
for (const [x, y] of [[-xMm, -yMm], [xMm, -yMm], [-xMm, yMm], [xMm, yMm]]) {
|
|
116
|
+
try {
|
|
117
|
+
if (!(lens.geometry.lookOf(x, y, t).z > 0) && !lens.zeroBeyondHorizon)
|
|
118
|
+
throw new InvalidConfigurationError("its ray does not point toward the scene plane.");
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
throw new InvalidConfigurationError("simulateDisplayView with " + options + " reads the scene through the lens up to " + (reachSites + 0.5) + " sites beyond the native " +
|
|
122
|
+
"raster's outer site centres (the kernel radius and the query offsets), and there (" + Math.hypot(x, y).toFixed(3) + " mm from the axis) " + error.message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return (input, spanColumns = Infinity) => createLensSignal(lens, input, spanColumns);
|
|
126
|
+
}
|
|
127
|
+
function createLensSignal(lens, input, spanColumns) {
|
|
128
|
+
const W = input.nativeWidth, H = input.nativeHeight, nodes = input.wavelengthsNm.length, T = lens.times.length, times = lens.times;
|
|
129
|
+
const { geometry, distanceM: distance, metresPerFrameUnit: scale, pitch, imagingArea } = lens;
|
|
130
|
+
const mmPerSite = pitch / 1000, scene = input.scene, offsets = input.offsets, A = input.electronsPerRadiance, channelAt = input.channelAt;
|
|
131
|
+
const materials = scene.kind === "materials" ? scene : undefined, radianceAt = scene.kind === "radiance" ? scene.radianceAt : undefined;
|
|
132
|
+
const { reflectance, spectralIndex, neutralDirect, spectralDirect, groups } = displayViewSignalTables(input);
|
|
133
|
+
const kernels = input.kernels, R = kernels ? kernels.radius : 0;
|
|
134
|
+
/** #569: the combined kernels of the zone of site (x, y), or the input's own without a field-dependent PSF. */
|
|
135
|
+
const zoneTables = lens.psf ? lens.psf.tables(input) : () => groups;
|
|
136
|
+
let lookups = 0;
|
|
137
|
+
// The plane point (frame units) and field factor of native sensor point (x, y) mm at time t: the environment's mapping (lookOf) and
|
|
138
|
+
// the planar provider's own arithmetic for the outgoing direction −look.
|
|
139
|
+
// #569: with zeroBeyondHorizon, a ray at or beyond 90° reads the plane's origin with a zero factor (zero radiance; `beyond` is set).
|
|
140
|
+
let pu = 0, pv = 0, pf = 0, beyond = false;
|
|
141
|
+
const zeroBeyond = lens.zeroBeyondHorizon === true;
|
|
142
|
+
const map = (xMm, yMm, t) => {
|
|
143
|
+
const look = geometry.lookOf(xMm, yMm, t);
|
|
144
|
+
beyond = !(look.z > 0);
|
|
145
|
+
if (beyond) {
|
|
146
|
+
if (!zeroBeyond || !Number.isFinite(look.z))
|
|
147
|
+
throw new InvalidConfigurationError("simulateDisplayView: a query ray does not point toward the scene plane.");
|
|
148
|
+
pu = 0;
|
|
149
|
+
pv = 0;
|
|
150
|
+
pf = 0;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const s = distance / look.z;
|
|
154
|
+
pu = look.x * s / scale;
|
|
155
|
+
pv = look.y * s / scale;
|
|
156
|
+
pf = look.factor;
|
|
157
|
+
};
|
|
158
|
+
const materialOf = (m) => { if (spectralIndex[m] === undefined)
|
|
159
|
+
throw new InvalidConfigurationError("Unknown chart material."); return m; };
|
|
160
|
+
// Without a kernel: the environment's query point of offset o of site (x, y) (native-environment-lean.ts, as buildExpectedSignalInput's queryPoint).
|
|
161
|
+
const box = lens.boxSamples, siteWidthMm = pitch / 1000;
|
|
162
|
+
const phases = offsets.map((_, o) => {
|
|
163
|
+
const node = Math.floor(o / lens.spots.length), spot = lens.spots[o % lens.spots.length].offsetMicrometers;
|
|
164
|
+
return { xPhase: (node % box + 0.5) / box, yPhase: (Math.floor(node / box) + 0.5) / box, aaX: spot.x / 1000, aaY: spot.y / 1000 };
|
|
165
|
+
});
|
|
166
|
+
function directSite(x, y) {
|
|
167
|
+
const c = channelAt(x, y), centerX = -imagingArea.widthMm / 2 + (pitch / 2 + x * pitch + 0) / 1000, centerY = -imagingArea.heightMm / 2 + (pitch / 2 + y * pitch + 0) / 1000;
|
|
168
|
+
let total = 0;
|
|
169
|
+
for (let ti = 0; ti < T; ti++) {
|
|
170
|
+
let sum = 0;
|
|
171
|
+
for (let o = 0; o < offsets.length; o++) {
|
|
172
|
+
const node = phases[o];
|
|
173
|
+
map((centerX - siteWidthMm / 2 + node.xPhase * siteWidthMm) - node.aaX, (centerY - siteWidthMm / 2 + node.yPhase * siteWidthMm) - node.aaY, times[ti]);
|
|
174
|
+
let value;
|
|
175
|
+
if (materials) {
|
|
176
|
+
const m = materialOf(materials.materialAt(pu, pv)), si = spectralIndex[m];
|
|
177
|
+
value = si < 0 ? reflectance[m] * neutralDirect[c] : spectralDirect[c][si];
|
|
178
|
+
lookups++;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
value = 0;
|
|
182
|
+
for (let n = 0; n < nodes; n++)
|
|
183
|
+
value += A[c][n] * radianceAt(pu, pv, input.wavelengthsNm[n]);
|
|
184
|
+
lookups += nodes;
|
|
185
|
+
}
|
|
186
|
+
sum += offsets[o].weight * (pf * value);
|
|
187
|
+
}
|
|
188
|
+
total += sum;
|
|
189
|
+
}
|
|
190
|
+
return total / T;
|
|
191
|
+
}
|
|
192
|
+
// With a kernel: lattice point (p, q) of group g at time t, #548's lattice formula, cached in a ring tagged with (p, q).
|
|
193
|
+
const G = groups.length, slots = G * T, per = materials ? 1 : nodes;
|
|
194
|
+
let cacheRows = 0, cacheCols = 0, tagQ = new Int32Array(0), tagP = new Int32Array(0), cacheMaterial = new Int32Array(0), cacheFactor = new Float64Array(0), cacheRadiance = new Float64Array(0);
|
|
195
|
+
const radiance = new Float64Array(nodes);
|
|
196
|
+
let lm = 0, lf = 0;
|
|
197
|
+
function lattice(g, ti, p, q) {
|
|
198
|
+
let k = -1;
|
|
199
|
+
if (cacheRows > 0) {
|
|
200
|
+
k = ((g * T + ti) * cacheRows + (q & (cacheRows - 1))) * cacheCols + (p & (cacheCols - 1));
|
|
201
|
+
if (tagQ[k] === q && tagP[k] === p) {
|
|
202
|
+
lf = cacheFactor[k];
|
|
203
|
+
if (materials)
|
|
204
|
+
lm = cacheMaterial[k];
|
|
205
|
+
else
|
|
206
|
+
for (let n = 0; n < nodes; n++)
|
|
207
|
+
radiance[n] = cacheRadiance[k * nodes + n];
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const group = groups[g];
|
|
212
|
+
map((p + 0.5 + group.fx - W / 2) * mmPerSite, (q + 0.5 + group.fy - H / 2) * mmPerSite, times[ti]);
|
|
213
|
+
lf = pf;
|
|
214
|
+
if (materials) {
|
|
215
|
+
lm = materialOf(materials.materialAt(pu, pv));
|
|
216
|
+
lookups++;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
for (let n = 0; n < nodes; n++)
|
|
220
|
+
radiance[n] = radianceAt(pu, pv, input.wavelengthsNm[n]);
|
|
221
|
+
lookups += nodes;
|
|
222
|
+
}
|
|
223
|
+
if (k >= 0) {
|
|
224
|
+
tagQ[k] = q;
|
|
225
|
+
tagP[k] = p;
|
|
226
|
+
cacheFactor[k] = lf;
|
|
227
|
+
if (materials)
|
|
228
|
+
cacheMaterial[k] = lm;
|
|
229
|
+
else
|
|
230
|
+
for (let n = 0; n < nodes; n++)
|
|
231
|
+
cacheRadiance[k * nodes + n] = radiance[n];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function kernelSite(x, y) {
|
|
235
|
+
const c = channelAt(x, y), rowMask = cacheRows - 1, colMask = cacheCols - 1, zone = zoneTables(x, y);
|
|
236
|
+
let total = 0;
|
|
237
|
+
for (let ti = 0; ti < T; ti++) {
|
|
238
|
+
let sum = 0;
|
|
239
|
+
for (let g = 0; g < G; g++) {
|
|
240
|
+
const group = groups[g], p0 = x + group.minIx - R, q0 = y + group.minIy - R, cw = group.cw, ch = group.ch, weights = zone[g];
|
|
241
|
+
if (materials) {
|
|
242
|
+
const nk = weights.neutral[c], sk = weights.spectral[c];
|
|
243
|
+
for (let dy = 0, t = 0; dy < ch; dy++) {
|
|
244
|
+
// The cache row of lattice row q (read inline: the common case is a hit).
|
|
245
|
+
const q = q0 + dy, row = cacheRows > 0 ? ((g * T + ti) * cacheRows + (q & rowMask)) * cacheCols : -1;
|
|
246
|
+
for (let dx = 0; dx < cw; dx++, t++) {
|
|
247
|
+
const p = p0 + dx, k = row + (p & colMask);
|
|
248
|
+
if (row >= 0 && tagQ[k] === q && tagP[k] === p) {
|
|
249
|
+
lm = cacheMaterial[k];
|
|
250
|
+
lf = cacheFactor[k];
|
|
251
|
+
}
|
|
252
|
+
else
|
|
253
|
+
lattice(g, ti, p, q);
|
|
254
|
+
const si = spectralIndex[lm];
|
|
255
|
+
sum += lf * (si < 0 ? reflectance[lm] * nk[t] : sk[si][t]);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else
|
|
260
|
+
for (let dy = 0, t = 0; dy < ch; dy++)
|
|
261
|
+
for (let dx = 0; dx < cw; dx++, t++) {
|
|
262
|
+
lattice(g, ti, p0 + dx, q0 + dy);
|
|
263
|
+
let value = 0;
|
|
264
|
+
for (let n = 0; n < nodes; n++)
|
|
265
|
+
value += weights.perNode[n][c][t] * radiance[n];
|
|
266
|
+
sum += lf * value;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
total += sum;
|
|
270
|
+
}
|
|
271
|
+
return total / T;
|
|
272
|
+
}
|
|
273
|
+
const pilotNode = input.wavelengthsNm[Math.floor(nodes / 2)];
|
|
274
|
+
if (input.largeKernel)
|
|
275
|
+
return createLargeLensSignal(input, inFocusLensTile, () => lookups, pilot);
|
|
276
|
+
/** E_c (#564) of the native site positions [x0, x0 + T) × [y0, y0 + T) for the three channels: kernelSite's sum for each channel. */
|
|
277
|
+
function inFocusLensTile(x0, y0, TS) {
|
|
278
|
+
const out = [0, 1, 2].map(() => new Float64Array(TS * TS));
|
|
279
|
+
// A fixed tile reaches past the raster: positions more than the disc's radius beyond it feed no site, so they stay 0 and are never
|
|
280
|
+
// mapped (the lens profiles are checked only up to the view's reach).
|
|
281
|
+
const D = input.largeKernel.radius;
|
|
282
|
+
if (x0 + TS <= -D || x0 >= W + D || y0 + TS <= -D || y0 >= H + D)
|
|
283
|
+
return out;
|
|
284
|
+
if (x0 < -D || x0 + TS > W + D || y0 < -D || y0 + TS > H + D) {
|
|
285
|
+
const xa = Math.max(x0, -D), xb = Math.min(x0 + TS, W + D), ya = Math.max(y0, -D), yb = Math.min(y0 + TS, H + D);
|
|
286
|
+
const part = inFocusLensRect(xa, ya, xb - xa, yb - ya);
|
|
287
|
+
for (let c = 0; c < 3; c++)
|
|
288
|
+
for (let y = ya; y < yb; y++)
|
|
289
|
+
out[c].set(part[c].subarray((y - ya) * (xb - xa), (y - ya + 1) * (xb - xa)), (y - y0) * TS + xa - x0);
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
return inFocusLensRect(x0, y0, TS, TS);
|
|
293
|
+
}
|
|
294
|
+
/** E_c of the native site positions [x0, x0 + w) × [y0, y0 + h), row-major, for the three channels. */
|
|
295
|
+
function inFocusLensRect(x0, y0, TW, TH) {
|
|
296
|
+
const out = [0, 1, 2].map(() => new Float64Array(TW * TH)), sum = [0, 1, 2].map(() => new Float64Array(TW * TH));
|
|
297
|
+
for (let ti = 0; ti < T; ti++) {
|
|
298
|
+
for (const e of sum)
|
|
299
|
+
e.fill(0);
|
|
300
|
+
for (const group of groups) {
|
|
301
|
+
const cw = group.cw, ch = group.ch, gw = TW + cw - 1, gh = TH + ch - 1, p0 = x0 + group.minIx - R, q0 = y0 + group.minIy - R;
|
|
302
|
+
const factor = new Float64Array(gw * gh), material = materials ? new Int32Array(gw * gh) : undefined, values = materials ? undefined : new Float64Array(gw * gh * nodes);
|
|
303
|
+
for (let j = 0; j < gh; j++)
|
|
304
|
+
for (let i = 0; i < gw; i++) {
|
|
305
|
+
const k = j * gw + i;
|
|
306
|
+
map((p0 + i + 0.5 + group.fx - W / 2) * mmPerSite, (q0 + j + 0.5 + group.fy - H / 2) * mmPerSite, times[ti]);
|
|
307
|
+
factor[k] = pf;
|
|
308
|
+
if (material)
|
|
309
|
+
material[k] = materialOf(materials.materialAt(pu, pv));
|
|
310
|
+
else
|
|
311
|
+
for (let n = 0; n < nodes; n++)
|
|
312
|
+
values[k * nodes + n] = radianceAt(pu, pv, input.wavelengthsNm[n]);
|
|
313
|
+
}
|
|
314
|
+
lookups += material ? gw * gh : gw * gh * nodes;
|
|
315
|
+
for (let c = 0; c < 3; c++) {
|
|
316
|
+
const e = sum[c], nk = group.neutral[c], sk = group.spectral[c];
|
|
317
|
+
for (let y = 0; y < TH; y++)
|
|
318
|
+
for (let x = 0; x < TW; x++) {
|
|
319
|
+
let v = 0;
|
|
320
|
+
for (let dy = 0, t = 0; dy < ch; dy++)
|
|
321
|
+
for (let dx = 0; dx < cw; dx++, t++) {
|
|
322
|
+
const k = (y + dy) * gw + x + dx;
|
|
323
|
+
if (material) {
|
|
324
|
+
const m = material[k], si = spectralIndex[m];
|
|
325
|
+
v += factor[k] * (si < 0 ? reflectance[m] * nk[t] : sk[si][t]);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
let r = 0;
|
|
329
|
+
for (let n = 0; n < nodes; n++)
|
|
330
|
+
r += group.perNode[n][c][t] * values[k * nodes + n];
|
|
331
|
+
v += factor[k] * r;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
e[y * TW + x] = e[y * TW + x] + v;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for (let c = 0; c < 3; c++) {
|
|
339
|
+
const o = out[c], e = sum[c];
|
|
340
|
+
for (let k = 0; k < o.length; k++)
|
|
341
|
+
o[k] = o[k] + e[k];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const o of out)
|
|
345
|
+
for (let k = 0; k < o.length; k++)
|
|
346
|
+
o[k] = o[k] / T;
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
349
|
+
// Site values persist across windows in a ring of native rows (a site's value does not depend on the window).
|
|
350
|
+
let ringRows = 0, ring = new Float64Array(0), ringRowId = new Int32Array(0), ringOn = false;
|
|
351
|
+
const compute = kernels ? kernelSite : directSite;
|
|
352
|
+
return {
|
|
353
|
+
get lookups() { return lookups; },
|
|
354
|
+
uniformMaterial: -1,
|
|
355
|
+
tracksMaterial: false,
|
|
356
|
+
get cachesSites() { return ringOn; },
|
|
357
|
+
marksReads: false,
|
|
358
|
+
markReads() { },
|
|
359
|
+
window(x0, y0, x1, y1) {
|
|
360
|
+
const sw = Math.max(0, x1 - x0), sh = Math.max(0, y1 - y0);
|
|
361
|
+
if (sh > 0 && ringRows < 2 * sh) {
|
|
362
|
+
const rows = pow2(2 * sh);
|
|
363
|
+
if (rows * W <= LATTICE_CACHE_LIMIT) {
|
|
364
|
+
ringRows = rows;
|
|
365
|
+
ring = new Float64Array(rows * W).fill(NaN);
|
|
366
|
+
ringRowId = new Int32Array(rows).fill(-1);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
ringOn = sh > 0 && ringRows >= 2 * sh;
|
|
370
|
+
if (!kernels || sw === 0)
|
|
371
|
+
return;
|
|
372
|
+
// The lattice rectangle the window's sites read, per group; the cache keeps two such heights and the columns the render's windows span.
|
|
373
|
+
let cw = 0, ch = 0;
|
|
374
|
+
for (const group of groups) {
|
|
375
|
+
cw = Math.max(cw, group.cw);
|
|
376
|
+
ch = Math.max(ch, group.ch);
|
|
377
|
+
}
|
|
378
|
+
const height = sh + ch - 1, width = sw + cw - 1;
|
|
379
|
+
let rows = pow2(2 * height), cols = pow2(Math.min(W + 2 * (R + cw), Math.max(width, (Number.isFinite(spanColumns) ? spanColumns : W) + width)));
|
|
380
|
+
if (slots * per * rows * cols > LATTICE_CACHE_LIMIT)
|
|
381
|
+
rows = pow2(height);
|
|
382
|
+
if (slots * per * rows * cols > LATTICE_CACHE_LIMIT)
|
|
383
|
+
cols = pow2(width);
|
|
384
|
+
if (slots * per * rows * cols > LATTICE_CACHE_LIMIT) {
|
|
385
|
+
cacheRows = 0;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (rows > cacheRows || cols > cacheCols) {
|
|
389
|
+
rows = Math.max(rows, cacheRows);
|
|
390
|
+
cols = Math.max(cols, cacheCols);
|
|
391
|
+
if (slots * per * rows * cols > LATTICE_CACHE_LIMIT) {
|
|
392
|
+
rows = pow2(height);
|
|
393
|
+
cols = pow2(width);
|
|
394
|
+
}
|
|
395
|
+
cacheRows = rows;
|
|
396
|
+
cacheCols = cols;
|
|
397
|
+
const size = slots * rows * cols;
|
|
398
|
+
tagQ = new Int32Array(size).fill(-2147483648);
|
|
399
|
+
tagP = new Int32Array(size);
|
|
400
|
+
cacheFactor = new Float64Array(size);
|
|
401
|
+
if (materials)
|
|
402
|
+
cacheMaterial = new Int32Array(size);
|
|
403
|
+
else
|
|
404
|
+
cacheRadiance = new Float64Array(size * nodes);
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
site(x, y) {
|
|
408
|
+
if (!ringOn)
|
|
409
|
+
return compute(x, y);
|
|
410
|
+
const slot = y & (ringRows - 1), k = slot * W + x;
|
|
411
|
+
if (ringRowId[slot] === y) {
|
|
412
|
+
const v = ring[k];
|
|
413
|
+
if (v === v)
|
|
414
|
+
return v;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
ringRowId[slot] = y;
|
|
418
|
+
ring.fill(NaN, slot * W, (slot + 1) * W);
|
|
419
|
+
}
|
|
420
|
+
const v = compute(x, y);
|
|
421
|
+
ring[k] = v;
|
|
422
|
+
return v;
|
|
423
|
+
},
|
|
424
|
+
pilot
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
* The renderer's pilot point is a rectilinear plane point of the native raster: back to the sensor point, then through the lens at
|
|
428
|
+
* every temporal midpoint. A point whose scene value differs between the instants (motion) counts as detail (NaN equals nothing).
|
|
429
|
+
*/
|
|
430
|
+
function pilot(u, v) {
|
|
431
|
+
const g = 2 / input.outputWidth, xMm = u / g * mmPerSite, yMm = -v / g * mmPerSite;
|
|
432
|
+
let first = NaN;
|
|
433
|
+
try {
|
|
434
|
+
for (let ti = 0; ti < T; ti++) {
|
|
435
|
+
map(xMm, yMm, times[ti]);
|
|
436
|
+
const value = beyond ? -1 : materials ? materials.materialAt(pu, pv) : radianceAt(pu, pv, pilotNode);
|
|
437
|
+
lookups++;
|
|
438
|
+
if (ti === 0)
|
|
439
|
+
first = value;
|
|
440
|
+
else if (value !== first)
|
|
441
|
+
return NaN;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return NaN;
|
|
446
|
+
}
|
|
447
|
+
return first;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/** #564: tile sides of the large-kernel lens signal (the in-focus E and the convolved sites) and the bytes each tile cache keeps. */
|
|
451
|
+
const LARGE_E_TILE = 64, LARGE_OUTPUT_TILE = 128, LARGE_CACHE_BYTES = 32 * 1048576;
|
|
452
|
+
/** #564: the lens signal with a defocus kernel above 64 sites: D ⊛ E_c by runs over the in-focus tiles (see the module comment). */
|
|
453
|
+
function createLargeLensSignal(input, inFocusTile, lookups, pilot) {
|
|
454
|
+
const runs = input.largeKernel, channelAt = input.channelAt, TE = LARGE_E_TILE;
|
|
455
|
+
// 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 reuses them.
|
|
456
|
+
const span = Math.ceil((LARGE_OUTPUT_TILE + 2 * runs.radius) / TE) + 1;
|
|
457
|
+
const inFocus = new Map(), capacity = Math.max(span * (span + 1), Math.floor(LARGE_CACHE_BYTES / (3 * TE * TE * 8)));
|
|
458
|
+
const tileAt = (tx, ty) => {
|
|
459
|
+
const key = (ty + 1048576) * 2097152 + tx + 1048576;
|
|
460
|
+
let tile = inFocus.get(key);
|
|
461
|
+
if (tile) {
|
|
462
|
+
inFocus.delete(key);
|
|
463
|
+
inFocus.set(key, tile);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
tile = inFocusTile(tx * TE, ty * TE, TE);
|
|
467
|
+
inFocus.set(key, tile);
|
|
468
|
+
if (inFocus.size > capacity)
|
|
469
|
+
inFocus.delete(inFocus.keys().next().value);
|
|
470
|
+
}
|
|
471
|
+
return tile;
|
|
472
|
+
};
|
|
473
|
+
// Which sites of a tile row hold channel c: the CFA's 2 × 2 period (tile origins are even).
|
|
474
|
+
const phase = [0, 1].map((py) => [0, 1].map((px) => channelAt(px, py)));
|
|
475
|
+
const patterns = [0, 1, 2].map((c) => (row) => {
|
|
476
|
+
const p = phase[row & 1], even = p[0] === c, odd = p[1] === c;
|
|
477
|
+
return even && odd ? { start: 0, step: 1 } : even ? { start: 0, step: 2 } : odd ? { start: 1, step: 2 } : null;
|
|
478
|
+
});
|
|
479
|
+
const lattice = createSiteKernelRunsLattice({ runs, tile: LARGE_OUTPUT_TILE, fields: 3, capacityBytes: LARGE_CACHE_BYTES, pattern: (c) => patterns[c],
|
|
480
|
+
source: (x0, y0, width, height) => (c) => {
|
|
481
|
+
const samples = new Float64Array(width * height);
|
|
482
|
+
for (let ty = Math.floor(y0 / TE); ty * TE < y0 + height; ty++)
|
|
483
|
+
for (let tx = Math.floor(x0 / TE); tx * TE < x0 + width; tx++) {
|
|
484
|
+
const tile = tileAt(tx, ty)[c], ax = Math.max(x0, tx * TE), bx = Math.min(x0 + width, (tx + 1) * TE), ay = Math.max(y0, ty * TE), by = Math.min(y0 + height, (ty + 1) * TE);
|
|
485
|
+
for (let y = ay; y < by; y++)
|
|
486
|
+
samples.set(tile.subarray((y - ty * TE) * TE + ax - tx * TE, (y - ty * TE) * TE + bx - tx * TE), (y - y0) * width + ax - x0);
|
|
487
|
+
}
|
|
488
|
+
return samples;
|
|
489
|
+
} });
|
|
490
|
+
return {
|
|
491
|
+
get lookups() { return lookups(); },
|
|
492
|
+
uniformMaterial: -1,
|
|
493
|
+
tracksMaterial: false,
|
|
494
|
+
cachesSites: false,
|
|
495
|
+
marksReads: false,
|
|
496
|
+
markReads() { },
|
|
497
|
+
window() { },
|
|
498
|
+
site(x, y) { return lattice.valueAt(channelAt(x, y), x, y); },
|
|
499
|
+
pilot
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
//# sourceMappingURL=display-view-lens-signal.js.map
|