@photivra/engine 2.0.0 → 2.2.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 +39 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/dist/capture/export-jpeg.js +7 -1
- package/dist/capture/export-jpeg.js.map +1 -1
- package/dist/capture/native-environment-lean.js +5 -2
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-raw.js +2 -2
- package/dist/capture/native-environment-raw.js.map +1 -1
- package/dist/capture/native-raw-development.js +1 -1
- package/dist/capture/native-raw-development.js.map +1 -1
- package/dist/capture/native-raw.d.ts +16 -0
- package/dist/capture/native-raw.js +16 -3
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/packed-sensor-raw.d.ts +7 -0
- package/dist/capture/packed-sensor-raw.js +6 -2
- package/dist/capture/packed-sensor-raw.js.map +1 -1
- package/dist/core/pixel-limits.d.ts +14 -0
- package/dist/core/pixel-limits.js +40 -0
- package/dist/core/pixel-limits.js.map +1 -0
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/facade/chart-radiance-batch.d.ts +7 -0
- package/dist/facade/chart-radiance-batch.js +7 -3
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/diffraction-provider.d.ts +30 -0
- package/dist/facade/diffraction-provider.js +127 -0
- package/dist/facade/diffraction-provider.js.map +1 -0
- package/dist/facade/host-yield.d.ts +1 -1
- package/dist/facade/host-yield.js +0 -4
- package/dist/facade/host-yield.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +88 -2
- package/dist/facade/simulate-capture.js +343 -41
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/motion/temporal-exposure-sum.d.ts +7 -0
- package/dist/motion/temporal-exposure-sum.js +6 -2
- package/dist/motion/temporal-exposure-sum.js.map +1 -1
- package/dist/optics/diffraction-psf-profile.d.ts +118 -0
- package/dist/optics/diffraction-psf-profile.js +252 -0
- package/dist/optics/diffraction-psf-profile.js.map +1 -0
- package/dist/optics/irradiance-convolution.d.ts +7 -0
- package/dist/optics/irradiance-convolution.js +6 -2
- package/dist/optics/irradiance-convolution.js.map +1 -1
- package/dist/output/native-capture-sdr.d.ts +7 -0
- package/dist/output/native-capture-sdr.js +6 -3
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/png-encoder.d.ts +10 -1
- package/dist/output/png-encoder.js +10 -4
- package/dist/output/png-encoder.js.map +1 -1
- package/dist/output/print-jpeg.d.ts +18 -0
- package/dist/output/print-jpeg.js +15 -4
- package/dist/output/print-jpeg.js.map +1 -1
- package/dist/sensor/sampling-aperture.d.ts +15 -0
- package/dist/sensor/sampling-aperture.js +31 -10
- package/dist/sensor/sampling-aperture.js.map +1 -1
- package/docs/API_REFERENCE.md +27 -1
- package/docs/QUICK_START.md +285 -13
- package/docs/USAGE.md +3 -1
- package/docs/api/capture-native-raw.md +11 -2
- package/docs/api/capture-packed-sensor-raw.md +7 -1
- package/docs/api/core-pixel-limits.md +19 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +62 -2
- package/docs/api/facade-chart-radiance-batch.md +8 -2
- package/docs/api/facade-simulate-capture.md +83 -3
- package/docs/api/motion-temporal-exposure-sum.md +7 -1
- package/docs/api/optics-diffraction-psf-profile.md +96 -0
- package/docs/api/optics-irradiance-convolution.md +7 -1
- package/docs/api/output-native-capture-sdr.md +7 -2
- package/docs/api/output-png-encoder.md +8 -1
- package/docs/api/output-print-jpeg.md +17 -0
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packed-sensor-raw.js","sourceRoot":"","sources":["../../src/capture/packed-sensor-raw.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Packed sensor stage (#277): expected electrons per native site → seeded Poisson photo and dark charge →\n * physical capacity → Gaussian read noise → pre-ADC limit → ADC (INL, round half up, optional digital\n * gain) → RAW code, on typed arrays for one rectangular region of the native raster.\n *\n * This is the float64 CPU reference for the `webgpu-f32` sensor stage. It runs the same equations, the\n * same seeded noise model (`photivra-native-raw-noise` 0.4.0) and the same floating-point operation\n * order as `calculateNativeRawSite` in the per-site producer, so for the same expected electrons and\n * readout regime its codes are bit-identical to the per-site engine (tested). The per-site stream\n * seeds are MurmurHash3_x86_32 of (capture seed, absolute native row-major index, stream ID), so the\n * region and any banding cannot change a code.\n *\n * Optional (2.0): a fixed-pattern noise profile (`SENSOR_FIXED_PATTERN_NOISE_MODEL`; PRNU and DSNU or\n * hot-pixel factors on the Poisson means, row and column offsets added with read noise) and up to 8\n * additional stored-charge components (Poisson or deterministic), in the producer's exact order.\n *\n * Supported domain (frame-wide, fails closed otherwise): one readout regime, one physical charge\n * capacity, one fixed-pattern profile. See docs/WEBGPU.md.\n */\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord } from \"../core/record-validation.js\";\nimport { adcTransfer, resolveSensorReadoutRegime, samplePoisson, type ResolvedSensorReadoutRegime,\n type SensorReadoutConversionProfile } from \"../sensor/raw-readout.js\";\nimport { createDeterministicUniformStream, deriveNativeSiteStreamSeedUint32, sampleDeterministicStandardNormal,\n NATIVE_SITE_CHARGE_STREAM_ID, NATIVE_SITE_READ_NOISE_STREAM_ID } from \"../sensor/stochastic-seed.js\";\nimport { evaluateFixedPatternColumnOffset, evaluateFixedPatternRowOffset, evaluateFixedPatternSiteFactors, parseSensorFixedPatternNoiseProfile,\n type SensorFixedPatternNoiseProfile } from \"../sensor/fixed-pattern-noise.js\";\nimport type { SensorAdditionalChargeSamplingModel } from \"../sensor/raw-readout.js\";\nimport { SENSOR_RAW_PRODUCER_NOISE_MODEL } from \"./sensor-raw-producer.js\";\n\nexport const PACKED_SENSOR_RAW_SCHEMA_VERSION = \"0.1.0\" as const;\nexport const PACKED_SENSOR_RAW_LIMITS = Object.freeze({\n maximumRegionSites: 24_000_000,\n maximumNativeSites: 0xffffffff,\n maximumDimension: 65_535,\n maximumAdcBitDepth: 16,\n maximumReadNoiseComponents: 16,\n maximumAdditionalChargeComponents: 8\n});\n\n/**\n * Optional fixed-pattern noise for a packed region. The colour-sampling site of native site (X, Y) is\n * (X + siteOrigin.x, Y + siteOrigin.y); the pattern is seeded from those coordinates, as in\n * `calculateSensorFixedPatternSite`.\n */\nexport interface PackedSensorFixedPatternNoise {\n profile: SensorFixedPatternNoiseProfile;\n /** Must equal `profile.colorSamplingProfileId`. */\n colorSamplingProfileId: string;\n siteOrigin: { x: number; y: number };\n}\n/** One additional stored-charge component, realized after photo and dark charge in declaration order. */\nexport interface PackedSensorAdditionalChargeComponent {\n componentId: string;\n /** `poisson` draws from the charge stream; `deterministic-expected-electron-equivalent` adds the expectation. */\n model: SensorAdditionalChargeSamplingModel;\n /** Expected electrons per region site, row-major. Finite, ≥ 0. */\n expectedElectrons: Float32Array | Float64Array;\n}\n\nexport interface PackedSensorRawInput {\n schemaVersion: typeof PACKED_SENSOR_RAW_SCHEMA_VERSION;\n /** Full native raster. Site seeds use the absolute index `(region.y + row) * nativeWidth + region.x + column`. */\n nativeWidth: number;\n nativeHeight: number;\n /** Region of the native raster to simulate, in native sites. */\n region: { x: number; y: number; width: number; height: number };\n /** The committed capture's noise seed (`capture.noise.seedUint32`). */\n captureSeedUint32: number;\n /** Expected photo-generated electrons per region site (EQE and local exposure applied), row-major. Finite, ≥ 0. */\n photoElectrons: Float32Array | Float64Array;\n /** Expected dark electrons per region site, row-major. Finite, ≥ 0. */\n darkElectrons: Float32Array | Float64Array;\n /** Physical charge storage capacity in electrons, the same at every site. */\n physicalChargeCapacityElectrons: number;\n readoutProfile: SensorReadoutConversionProfile;\n regimeId: string;\n /** Optional (2.0). */\n fixedPatternNoise?: PackedSensorFixedPatternNoise;\n /** Optional (2.0), at most 8. */\n additionalChargeComponents?: readonly PackedSensorAdditionalChargeComponent[];\n}\n\n/** Validated plan. Arrays are borrowed, not copied. */\nexport interface PackedSensorRawPlan {\n nativeWidth: number;\n nativeHeight: number;\n region: Readonly<{ x: number; y: number; width: number; height: number }>;\n siteCount: number;\n captureSeedUint32: number;\n physicalChargeCapacityElectrons: number;\n readout: ResolvedSensorReadoutRegime;\n noiseModel: typeof SENSOR_RAW_PRODUCER_NOISE_MODEL;\n seedSchedule: \"murmur3-capture-seed-native-index-stream-v2\";\n /** Largest expected photo and dark electron counts in the region (before fixed-pattern factors). */\n maximumPhotoElectrons: number;\n maximumDarkElectrons: number;\n /** Parsed fixed-pattern profile and colour-site origin, or null. */\n fixedPatternNoise: Readonly<{ profile: SensorFixedPatternNoiseProfile; siteOrigin: Readonly<{ x: number; y: number }> }> | null;\n /** Additional components (identity, model and largest expectation); their arrays stay on the input. */\n additionalChargeComponents: readonly Readonly<{ componentId: string; model: SensorAdditionalChargeSamplingModel; maximumExpectedElectrons: number }>[];\n}\n\n/** Per-region saturation and clamp counts, as in the per-site readout diagnostics. */\nexport interface PackedSensorRawDiagnostics {\n physicalSaturationSiteCount: number;\n preAdcSaturationSiteCount: number;\n digitalSaturationSiteCount: number;\n lowerClampSiteCount: number;\n}\n\nexport interface PackedSensorRawResult {\n plan: PackedSensorRawPlan;\n /** RAW codes, row-major over the region. */\n codes: Uint16Array;\n diagnostics: PackedSensorRawDiagnostics;\n}\n\nfunction integer(value: unknown, minimum: number, maximum: number): number {\n if (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < minimum || value > maximum) {\n throw new InvalidConfigurationError(\"Invalid packed sensor RAW geometry or seed.\");\n }\n return value;\n}\n\nfunction maximumOf(values: Float32Array | Float64Array, count: number): number {\n if (!(values instanceof Float32Array || values instanceof Float64Array) || values.length !== count) {\n throw new InvalidConfigurationError(\"Packed sensor RAW electron arrays must match the region size.\");\n }\n let maximum = 0;\n for (let i = 0; i < count; i++) {\n const v = values[i]!;\n // The negated comparison also rejects NaN.\n if (!(v >= 0 && v <= Number.MAX_VALUE)) throw new InvalidConfigurationError(\"Expected electrons must be finite and non-negative.\");\n if (v > maximum) maximum = v;\n }\n return maximum;\n}\n\n/** Validates the request (every array value once) and returns its plan. */\nexport function calculatePackedSensorRawPlan(input: PackedSensorRawInput): PackedSensorRawPlan {\n const r = requireAllowlistedRecord(input, [\"schemaVersion\", \"nativeWidth\", \"nativeHeight\", \"region\", \"captureSeedUint32\", \"photoElectrons\",\n \"darkElectrons\", \"physicalChargeCapacityElectrons\", \"readoutProfile\", \"regimeId\", \"fixedPatternNoise\", \"additionalChargeComponents\"],\n \"Invalid packed sensor RAW input.\");\n const L = PACKED_SENSOR_RAW_LIMITS;\n if (r.schemaVersion !== PACKED_SENSOR_RAW_SCHEMA_VERSION) throw new InvalidConfigurationError(\"Unsupported packed sensor RAW schema.\");\n const nativeWidth = integer(r.nativeWidth, 1, L.maximumDimension), nativeHeight = integer(r.nativeHeight, 1, L.maximumDimension);\n if (nativeWidth * nativeHeight > L.maximumNativeSites) throw new InvalidConfigurationError(\"Native raster exceeds the uint32 site index.\");\n const g = requireAllowlistedRecord(r.region, [\"x\", \"y\", \"width\", \"height\"], \"Invalid packed sensor RAW region.\");\n const x = integer(g.x, 0, nativeWidth - 1), y = integer(g.y, 0, nativeHeight - 1);\n const width = integer(g.width, 1, nativeWidth - x), height = integer(g.height, 1, nativeHeight - y);\n if (width * height > L.maximumRegionSites) throw new InvalidConfigurationError(\"Packed sensor RAW region exceeds the site limit.\");\n const captureSeedUint32 = integer(r.captureSeedUint32, 0, 0xffffffff);\n const capacity = r.physicalChargeCapacityElectrons;\n if (typeof capacity !== \"number\" || !(capacity > 0 && capacity <= Number.MAX_VALUE)) {\n throw new InvalidConfigurationError(\"Physical charge capacity must be finite and positive.\");\n }\n const readout = resolveSensorReadoutRegime({ profile: r.readoutProfile as SensorReadoutConversionProfile, regimeId: r.regimeId as string });\n if (readout.regime.adc.bitDepth > L.maximumAdcBitDepth || readout.regime.readNoiseComponents.length > L.maximumReadNoiseComponents) {\n throw new InvalidConfigurationError(\"Packed sensor RAW requires a uint16 ADC and at most 16 read-noise components.\");\n }\n const siteCount = width * height;\n const maximumPhotoElectrons = maximumOf(r.photoElectrons as Float64Array, siteCount);\n const maximumDarkElectrons = maximumOf(r.darkElectrons as Float64Array, siteCount);\n let fixedPatternNoise: PackedSensorRawPlan[\"fixedPatternNoise\"] = null;\n if (r.fixedPatternNoise !== undefined) {\n const f = requireAllowlistedRecord(r.fixedPatternNoise, [\"profile\", \"colorSamplingProfileId\", \"siteOrigin\"], \"Invalid packed fixed-pattern noise.\");\n const profile = parseSensorFixedPatternNoiseProfile(f.profile);\n if (f.colorSamplingProfileId !== profile.colorSamplingProfileId) {\n throw new InvalidConfigurationError(\"Fixed-pattern noise profile colour-sampling identity must match the packed region.\");\n }\n const o = requireAllowlistedRecord(f.siteOrigin, [\"x\", \"y\"], \"Invalid packed fixed-pattern site origin.\");\n const ox = integer(o.x, 0, 0xffffffff - (x + width - 1)), oy = integer(o.y, 0, 0xffffffff - (y + height - 1));\n fixedPatternNoise = Object.freeze({ profile, siteOrigin: Object.freeze({ x: ox, y: oy }) });\n }\n const components = r.additionalChargeComponents === undefined ? [] : r.additionalChargeComponents;\n if (!Array.isArray(components) || components.length > L.maximumAdditionalChargeComponents) {\n throw new InvalidConfigurationError(\"Packed sensor RAW takes at most 8 additional stored-charge components.\");\n }\n const ids = new Set<string>();\n const additionalChargeComponents = components.map((value: unknown) => {\n const c = requireAllowlistedRecord(value, [\"componentId\", \"model\", \"expectedElectrons\"], \"Invalid packed additional charge component.\");\n if (typeof c.componentId !== \"string\" || c.componentId.length === 0 || ids.has(c.componentId) ||\n (c.model !== \"poisson\" && c.model !== \"deterministic-expected-electron-equivalent\")) {\n throw new InvalidConfigurationError(\"Additional charge components need unique IDs and a Poisson or deterministic model.\");\n }\n ids.add(c.componentId);\n return Object.freeze({ componentId: c.componentId, model: c.model as SensorAdditionalChargeSamplingModel,\n maximumExpectedElectrons: maximumOf(c.expectedElectrons as Float64Array, siteCount) });\n });\n return Object.freeze({ nativeWidth, nativeHeight, region: Object.freeze({ x, y, width, height }), siteCount, captureSeedUint32,\n physicalChargeCapacityElectrons: capacity, readout, noiseModel: SENSOR_RAW_PRODUCER_NOISE_MODEL,\n seedSchedule: \"murmur3-capture-seed-native-index-stream-v2\" as const, maximumPhotoElectrons, maximumDarkElectrons,\n fixedPatternNoise, additionalChargeComponents: Object.freeze(additionalChargeComponents) });\n}\n\n/** Fixed-pattern values that one site needs. @internal */\nexport interface PackedSensorSitePattern { photoResponseGainFactor: number; darkCurrentFactor: number; readoutOffsetElectrons: number }\n\n/**\n * Evaluates the fixed-pattern values of region sites with the model's own functions. Row and column\n * offsets are computed once per row and column; `readoutOffsetElectrons` is row + column, as in\n * `calculateSensorFixedPatternSite`. @internal\n */\nexport function createPackedFixedPattern(plan: PackedSensorRawPlan): ((column: number, row: number) => PackedSensorSitePattern) | null {\n const f = plan.fixedPatternNoise;\n if (f === null) return null;\n const { region } = plan, x0 = region.x + f.siteOrigin.x, y0 = region.y + f.siteOrigin.y;\n const rows = new Float64Array(region.height), columns = new Float64Array(region.width);\n for (let row = 0; row < region.height; row++) rows[row] = evaluateFixedPatternRowOffset(f.profile, y0 + row);\n for (let column = 0; column < region.width; column++) columns[column] = evaluateFixedPatternColumnOffset(f.profile, x0 + column);\n return (column, row) => {\n const factors = evaluateFixedPatternSiteFactors(f.profile, x0 + column, y0 + row);\n return { photoResponseGainFactor: factors.photoResponseGainFactor, darkCurrentFactor: factors.darkCurrentFactor,\n readoutOffsetElectrons: rows[row]! + columns[column]! };\n };\n}\n\n/** Saturation and clamp flags of one site: bit 0 physical, 1 pre-ADC, 2 digital, 3 lower clamp. */\nexport const PACKED_SENSOR_RAW_FLAG = Object.freeze({ physical: 1, preAdc: 2, digital: 4, lower: 8 });\n\n/**\n * One site of the reference, in the operation order of `calculateNativeRawSite`. Returns the code and\n * its flags. `index` is the absolute native row-major index; `pattern` is the site's fixed-pattern\n * values (required when the plan has a profile) and `additional` its additional-component expectations,\n * in the plan's order.\n * @internal\n */\nexport function simulatePackedSensorSite(plan: PackedSensorRawPlan, photoE: number, darkE: number, index: number,\n pattern: PackedSensorSitePattern | null = null, additional: readonly number[] = []): { code: number; flags: number } {\n const seed = plan.captureSeedUint32, regime = plan.readout.regime, capacity = plan.physicalChargeCapacityElectrons;\n if ((plan.fixedPatternNoise === null) !== (pattern === null) || additional.length !== plan.additionalChargeComponents.length) {\n throw new InvalidConfigurationError(\"Packed sensor site extras must match the plan.\");\n }\n const random = createDeterministicUniformStream(deriveNativeSiteStreamSeedUint32(seed, index, NATIVE_SITE_CHARGE_STREAM_ID));\n // As in simulateSensorChargeRealization: the fixed-pattern factors multiply the Poisson means.\n const photoMean = pattern === null ? photoE : photoE * pattern.photoResponseGainFactor;\n const darkMean = pattern === null ? darkE : darkE * pattern.darkCurrentFactor;\n const photoRealized = samplePoisson(photoMean, random), darkRealized = samplePoisson(darkMean, random);\n let additionalRealized = 0;\n for (let k = 0; k < additional.length; k++) {\n const expected = additional[k]!;\n additionalRealized = additionalRealized + (plan.additionalChargeComponents[k]!.model === \"poisson\" ? samplePoisson(expected, random) : expected);\n }\n const realized = photoRealized + darkRealized + additionalRealized;\n const readRandom = createDeterministicUniformStream(deriveNativeSiteStreamSeedUint32(seed, index, NATIVE_SITE_READ_NOISE_STREAM_ID));\n const afterPhysical = Math.min(realized, capacity);\n let totalReadNoise = 0;\n for (const component of regime.readNoiseComponents) {\n totalReadNoise = totalReadNoise + (component.rmsElectrons.value === 0 ? 0 : sampleDeterministicStandardNormal(readRandom) * component.rmsElectrons.value);\n }\n const afterReadNoise = afterPhysical + totalReadNoise + (pattern === null ? 0 : pattern.readoutOffsetElectrons);\n const threshold = regime.preAdcSaturationElectronEquivalent.value;\n const afterPreAdc = Math.min(afterReadNoise, threshold);\n const quantized = adcTransfer(regime, plan.readout.adcMaximumCode, afterPreAdc).quantized;\n const saturation = regime.adc.digitalSaturationCode;\n const flags = (afterPhysical < realized ? PACKED_SENSOR_RAW_FLAG.physical : 0) | (afterReadNoise > threshold ? PACKED_SENSOR_RAW_FLAG.preAdc : 0) |\n (quantized >= saturation ? PACKED_SENSOR_RAW_FLAG.digital : 0) | (quantized < 0 ? PACKED_SENSOR_RAW_FLAG.lower : 0);\n return { code: Math.max(0, Math.min(quantized, saturation)), flags };\n}\n\n/**\n * Reference rows `[row0, row1)` of the region into `codes` (indexed from the region start), adding flags\n * into `counts` (physical, pre-ADC, digital, lower).\n * @internal\n */\nexport function simulatePackedSensorRows(plan: PackedSensorRawPlan, photo: Float32Array | Float64Array, dark: Float32Array | Float64Array,\n row0: number, row1: number, codes: Uint16Array, counts: number[], extras: PackedSensorExtras = NO_EXTRAS): void {\n const { region, nativeWidth } = plan;\n const additional = new Array<number>(extras.additional.length).fill(0);\n for (let row = row0; row < row1; row++) {\n for (let column = 0, i = row * region.width; column < region.width; column++, i++) {\n for (let k = 0; k < additional.length; k++) additional[k] = extras.additional[k]![i]!;\n const site = simulatePackedSensorSite(plan, photo[i]!, dark[i]!, (region.y + row) * nativeWidth + region.x + column,\n extras.pattern === null ? null : extras.pattern(column, row), additional);\n codes[i] = site.code;\n if (site.flags & 1) counts[0]!++;\n if (site.flags & 2) counts[1]!++;\n if (site.flags & 4) counts[2]!++;\n if (site.flags & 8) counts[3]!++;\n }\n }\n}\n\n/** Per-site extras of a region: the fixed-pattern evaluator and the additional-component arrays. @internal */\nexport interface PackedSensorExtras {\n pattern: ((column: number, row: number) => PackedSensorSitePattern) | null;\n additional: readonly (Float32Array | Float64Array)[];\n}\nconst NO_EXTRAS: PackedSensorExtras = Object.freeze({ pattern: null, additional: Object.freeze([]) });\n\n/** The extras of a validated input; `snapshot` copies the arrays. @internal */\nexport function packedSensorExtras(plan: PackedSensorRawPlan, input: PackedSensorRawInput, snapshot: boolean): PackedSensorExtras {\n return { pattern: createPackedFixedPattern(plan),\n additional: (input.additionalChargeComponents ?? []).map(c => (snapshot ? c.expectedElectrons.slice() : c.expectedElectrons)) };\n}\n\nexport function packedSensorDiagnostics(counts: readonly number[]): PackedSensorRawDiagnostics {\n return { physicalSaturationSiteCount: counts[0]!, preAdcSaturationSiteCount: counts[1]!, digitalSaturationSiteCount: counts[2]!,\n lowerClampSiteCount: counts[3]! };\n}\n\n/**\n * Synchronous float64 reference for a bounded region. Use the image-kernel accelerator for large\n * regions, host yields or the `webgpu-f32` backend.\n */\nexport function simulatePackedSensorRaw(input: PackedSensorRawInput): PackedSensorRawResult {\n const plan = calculatePackedSensorRawPlan(input);\n const codes = new Uint16Array(plan.siteCount), counts = [0, 0, 0, 0];\n simulatePackedSensorRows(plan, input.photoElectrons, input.darkElectrons, 0, plan.region.height, codes, counts,\n packedSensorExtras(plan, input, false));\n return { plan, codes, diagnostics: packedSensorDiagnostics(counts) };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"packed-sensor-raw.js","sourceRoot":"","sources":["../../src/capture/packed-sensor-raw.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Packed sensor stage (#277): expected electrons per native site → seeded Poisson photo and dark charge →\n * physical capacity → Gaussian read noise → pre-ADC limit → ADC (INL, round half up, optional digital\n * gain) → RAW code, on typed arrays for one rectangular region of the native raster.\n *\n * This is the float64 CPU reference for the `webgpu-f32` sensor stage. It runs the same equations, the\n * same seeded noise model (`photivra-native-raw-noise` 0.4.0) and the same floating-point operation\n * order as `calculateNativeRawSite` in the per-site producer, so for the same expected electrons and\n * readout regime its codes are bit-identical to the per-site engine (tested). The per-site stream\n * seeds are MurmurHash3_x86_32 of (capture seed, absolute native row-major index, stream ID), so the\n * region and any banding cannot change a code.\n *\n * Optional (2.0): a fixed-pattern noise profile (`SENSOR_FIXED_PATTERN_NOISE_MODEL`; PRNU and DSNU or\n * hot-pixel factors on the Poisson means, row and column offsets added with read noise) and up to 8\n * additional stored-charge components (Poisson or deterministic), in the producer's exact order.\n *\n * Supported domain (frame-wide, fails closed otherwise): one readout regime, one physical charge\n * capacity, one fixed-pattern profile. See docs/WEBGPU.md.\n */\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord } from \"../core/record-validation.js\";\nimport { FULL_RESOLUTION_CEILING_PIXELS, resolveRaisedLimit } from \"../core/pixel-limits.js\";\nimport { adcTransfer, resolveSensorReadoutRegime, samplePoisson, type ResolvedSensorReadoutRegime,\n type SensorReadoutConversionProfile } from \"../sensor/raw-readout.js\";\nimport { createDeterministicUniformStream, deriveNativeSiteStreamSeedUint32, sampleDeterministicStandardNormal,\n NATIVE_SITE_CHARGE_STREAM_ID, NATIVE_SITE_READ_NOISE_STREAM_ID } from \"../sensor/stochastic-seed.js\";\nimport { evaluateFixedPatternColumnOffset, evaluateFixedPatternRowOffset, evaluateFixedPatternSiteFactors, parseSensorFixedPatternNoiseProfile,\n type SensorFixedPatternNoiseProfile } from \"../sensor/fixed-pattern-noise.js\";\nimport type { SensorAdditionalChargeSamplingModel } from \"../sensor/raw-readout.js\";\nimport { SENSOR_RAW_PRODUCER_NOISE_MODEL } from \"./sensor-raw-producer.js\";\n\nexport const PACKED_SENSOR_RAW_SCHEMA_VERSION = \"0.1.0\" as const;\n/** `maximumRegionSites` is the default region limit; a request may raise it with its own `maximumRegionSites`, up to `ceilingRegionSites`. */\nexport const PACKED_SENSOR_RAW_LIMITS = Object.freeze({\n maximumRegionSites: 24_000_000,\n ceilingRegionSites: FULL_RESOLUTION_CEILING_PIXELS,\n maximumNativeSites: 0xffffffff,\n maximumDimension: 65_535,\n maximumAdcBitDepth: 16,\n maximumReadNoiseComponents: 16,\n maximumAdditionalChargeComponents: 8\n});\n\n/**\n * Optional fixed-pattern noise for a packed region. The colour-sampling site of native site (X, Y) is\n * (X + siteOrigin.x, Y + siteOrigin.y); the pattern is seeded from those coordinates, as in\n * `calculateSensorFixedPatternSite`.\n */\nexport interface PackedSensorFixedPatternNoise {\n profile: SensorFixedPatternNoiseProfile;\n /** Must equal `profile.colorSamplingProfileId`. */\n colorSamplingProfileId: string;\n siteOrigin: { x: number; y: number };\n}\n/** One additional stored-charge component, realized after photo and dark charge in declaration order. */\nexport interface PackedSensorAdditionalChargeComponent {\n componentId: string;\n /** `poisson` draws from the charge stream; `deterministic-expected-electron-equivalent` adds the expectation. */\n model: SensorAdditionalChargeSamplingModel;\n /** Expected electrons per region site, row-major. Finite, ≥ 0. */\n expectedElectrons: Float32Array | Float64Array;\n}\n\nexport interface PackedSensorRawInput {\n schemaVersion: typeof PACKED_SENSOR_RAW_SCHEMA_VERSION;\n /** Full native raster. Site seeds use the absolute index `(region.y + row) * nativeWidth + region.x + column`. */\n nativeWidth: number;\n nativeHeight: number;\n /** Region of the native raster to simulate, in native sites. */\n region: { x: number; y: number; width: number; height: number };\n /** The committed capture's noise seed (`capture.noise.seedUint32`). */\n captureSeedUint32: number;\n /** Expected photo-generated electrons per region site (EQE and local exposure applied), row-major. Finite, ≥ 0. */\n photoElectrons: Float32Array | Float64Array;\n /** Expected dark electrons per region site, row-major. Finite, ≥ 0. */\n darkElectrons: Float32Array | Float64Array;\n /** Physical charge storage capacity in electrons, the same at every site. */\n physicalChargeCapacityElectrons: number;\n readoutProfile: SensorReadoutConversionProfile;\n regimeId: string;\n /** Optional (2.0). */\n fixedPatternNoise?: PackedSensorFixedPatternNoise;\n /** Optional (2.0), at most 8. */\n additionalChargeComponents?: readonly PackedSensorAdditionalChargeComponent[];\n /**\n * Optional opt-in region limit for large sensors: an integer from 1 to `PACKED_SENSOR_RAW_LIMITS.ceilingRegionSites`.\n * Default `PACKED_SENSOR_RAW_LIMITS.maximumRegionSites` (24 000 000). It does not change the result.\n */\n maximumRegionSites?: number;\n}\n\n/** Validated plan. Arrays are borrowed, not copied. */\nexport interface PackedSensorRawPlan {\n nativeWidth: number;\n nativeHeight: number;\n region: Readonly<{ x: number; y: number; width: number; height: number }>;\n siteCount: number;\n captureSeedUint32: number;\n physicalChargeCapacityElectrons: number;\n readout: ResolvedSensorReadoutRegime;\n noiseModel: typeof SENSOR_RAW_PRODUCER_NOISE_MODEL;\n seedSchedule: \"murmur3-capture-seed-native-index-stream-v2\";\n /** Largest expected photo and dark electron counts in the region (before fixed-pattern factors). */\n maximumPhotoElectrons: number;\n maximumDarkElectrons: number;\n /** Parsed fixed-pattern profile and colour-site origin, or null. */\n fixedPatternNoise: Readonly<{ profile: SensorFixedPatternNoiseProfile; siteOrigin: Readonly<{ x: number; y: number }> }> | null;\n /** Additional components (identity, model and largest expectation); their arrays stay on the input. */\n additionalChargeComponents: readonly Readonly<{ componentId: string; model: SensorAdditionalChargeSamplingModel; maximumExpectedElectrons: number }>[];\n}\n\n/** Per-region saturation and clamp counts, as in the per-site readout diagnostics. */\nexport interface PackedSensorRawDiagnostics {\n physicalSaturationSiteCount: number;\n preAdcSaturationSiteCount: number;\n digitalSaturationSiteCount: number;\n lowerClampSiteCount: number;\n}\n\nexport interface PackedSensorRawResult {\n plan: PackedSensorRawPlan;\n /** RAW codes, row-major over the region. */\n codes: Uint16Array;\n diagnostics: PackedSensorRawDiagnostics;\n}\n\nfunction integer(value: unknown, minimum: number, maximum: number): number {\n if (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < minimum || value > maximum) {\n throw new InvalidConfigurationError(\"Invalid packed sensor RAW geometry or seed.\");\n }\n return value;\n}\n\nfunction maximumOf(values: Float32Array | Float64Array, count: number): number {\n if (!(values instanceof Float32Array || values instanceof Float64Array) || values.length !== count) {\n throw new InvalidConfigurationError(\"Packed sensor RAW electron arrays must match the region size.\");\n }\n let maximum = 0;\n for (let i = 0; i < count; i++) {\n const v = values[i]!;\n // The negated comparison also rejects NaN.\n if (!(v >= 0 && v <= Number.MAX_VALUE)) throw new InvalidConfigurationError(\"Expected electrons must be finite and non-negative.\");\n if (v > maximum) maximum = v;\n }\n return maximum;\n}\n\n/** Validates the request (every array value once) and returns its plan. */\nexport function calculatePackedSensorRawPlan(input: PackedSensorRawInput): PackedSensorRawPlan {\n const r = requireAllowlistedRecord(input, [\"schemaVersion\", \"nativeWidth\", \"nativeHeight\", \"region\", \"captureSeedUint32\", \"photoElectrons\",\n \"darkElectrons\", \"physicalChargeCapacityElectrons\", \"readoutProfile\", \"regimeId\", \"fixedPatternNoise\", \"additionalChargeComponents\", \"maximumRegionSites\"],\n \"Invalid packed sensor RAW input.\");\n const L = PACKED_SENSOR_RAW_LIMITS;\n if (r.schemaVersion !== PACKED_SENSOR_RAW_SCHEMA_VERSION) throw new InvalidConfigurationError(\"Unsupported packed sensor RAW schema.\");\n const nativeWidth = integer(r.nativeWidth, 1, L.maximumDimension), nativeHeight = integer(r.nativeHeight, 1, L.maximumDimension);\n if (nativeWidth * nativeHeight > L.maximumNativeSites) throw new InvalidConfigurationError(\"Native raster exceeds the uint32 site index.\");\n const g = requireAllowlistedRecord(r.region, [\"x\", \"y\", \"width\", \"height\"], \"Invalid packed sensor RAW region.\");\n const x = integer(g.x, 0, nativeWidth - 1), y = integer(g.y, 0, nativeHeight - 1);\n const width = integer(g.width, 1, nativeWidth - x), height = integer(g.height, 1, nativeHeight - y);\n const maximumRegionSites = resolveRaisedLimit(r.maximumRegionSites, L.maximumRegionSites, L.ceilingRegionSites,\n \"Packed sensor RAW maximumRegionSites must be an integer from 1 to \" + L.ceilingRegionSites + \".\");\n if (width * height > maximumRegionSites) throw new InvalidConfigurationError(\"Packed sensor RAW region exceeds the site limit.\");\n const captureSeedUint32 = integer(r.captureSeedUint32, 0, 0xffffffff);\n const capacity = r.physicalChargeCapacityElectrons;\n if (typeof capacity !== \"number\" || !(capacity > 0 && capacity <= Number.MAX_VALUE)) {\n throw new InvalidConfigurationError(\"Physical charge capacity must be finite and positive.\");\n }\n const readout = resolveSensorReadoutRegime({ profile: r.readoutProfile as SensorReadoutConversionProfile, regimeId: r.regimeId as string });\n if (readout.regime.adc.bitDepth > L.maximumAdcBitDepth || readout.regime.readNoiseComponents.length > L.maximumReadNoiseComponents) {\n throw new InvalidConfigurationError(\"Packed sensor RAW requires a uint16 ADC and at most 16 read-noise components.\");\n }\n const siteCount = width * height;\n const maximumPhotoElectrons = maximumOf(r.photoElectrons as Float64Array, siteCount);\n const maximumDarkElectrons = maximumOf(r.darkElectrons as Float64Array, siteCount);\n let fixedPatternNoise: PackedSensorRawPlan[\"fixedPatternNoise\"] = null;\n if (r.fixedPatternNoise !== undefined) {\n const f = requireAllowlistedRecord(r.fixedPatternNoise, [\"profile\", \"colorSamplingProfileId\", \"siteOrigin\"], \"Invalid packed fixed-pattern noise.\");\n const profile = parseSensorFixedPatternNoiseProfile(f.profile);\n if (f.colorSamplingProfileId !== profile.colorSamplingProfileId) {\n throw new InvalidConfigurationError(\"Fixed-pattern noise profile colour-sampling identity must match the packed region.\");\n }\n const o = requireAllowlistedRecord(f.siteOrigin, [\"x\", \"y\"], \"Invalid packed fixed-pattern site origin.\");\n const ox = integer(o.x, 0, 0xffffffff - (x + width - 1)), oy = integer(o.y, 0, 0xffffffff - (y + height - 1));\n fixedPatternNoise = Object.freeze({ profile, siteOrigin: Object.freeze({ x: ox, y: oy }) });\n }\n const components = r.additionalChargeComponents === undefined ? [] : r.additionalChargeComponents;\n if (!Array.isArray(components) || components.length > L.maximumAdditionalChargeComponents) {\n throw new InvalidConfigurationError(\"Packed sensor RAW takes at most 8 additional stored-charge components.\");\n }\n const ids = new Set<string>();\n const additionalChargeComponents = components.map((value: unknown) => {\n const c = requireAllowlistedRecord(value, [\"componentId\", \"model\", \"expectedElectrons\"], \"Invalid packed additional charge component.\");\n if (typeof c.componentId !== \"string\" || c.componentId.length === 0 || ids.has(c.componentId) ||\n (c.model !== \"poisson\" && c.model !== \"deterministic-expected-electron-equivalent\")) {\n throw new InvalidConfigurationError(\"Additional charge components need unique IDs and a Poisson or deterministic model.\");\n }\n ids.add(c.componentId);\n return Object.freeze({ componentId: c.componentId, model: c.model as SensorAdditionalChargeSamplingModel,\n maximumExpectedElectrons: maximumOf(c.expectedElectrons as Float64Array, siteCount) });\n });\n return Object.freeze({ nativeWidth, nativeHeight, region: Object.freeze({ x, y, width, height }), siteCount, captureSeedUint32,\n physicalChargeCapacityElectrons: capacity, readout, noiseModel: SENSOR_RAW_PRODUCER_NOISE_MODEL,\n seedSchedule: \"murmur3-capture-seed-native-index-stream-v2\" as const, maximumPhotoElectrons, maximumDarkElectrons,\n fixedPatternNoise, additionalChargeComponents: Object.freeze(additionalChargeComponents) });\n}\n\n/** Fixed-pattern values that one site needs. @internal */\nexport interface PackedSensorSitePattern { photoResponseGainFactor: number; darkCurrentFactor: number; readoutOffsetElectrons: number }\n\n/**\n * Evaluates the fixed-pattern values of region sites with the model's own functions. Row and column\n * offsets are computed once per row and column; `readoutOffsetElectrons` is row + column, as in\n * `calculateSensorFixedPatternSite`. @internal\n */\nexport function createPackedFixedPattern(plan: PackedSensorRawPlan): ((column: number, row: number) => PackedSensorSitePattern) | null {\n const f = plan.fixedPatternNoise;\n if (f === null) return null;\n const { region } = plan, x0 = region.x + f.siteOrigin.x, y0 = region.y + f.siteOrigin.y;\n const rows = new Float64Array(region.height), columns = new Float64Array(region.width);\n for (let row = 0; row < region.height; row++) rows[row] = evaluateFixedPatternRowOffset(f.profile, y0 + row);\n for (let column = 0; column < region.width; column++) columns[column] = evaluateFixedPatternColumnOffset(f.profile, x0 + column);\n return (column, row) => {\n const factors = evaluateFixedPatternSiteFactors(f.profile, x0 + column, y0 + row);\n return { photoResponseGainFactor: factors.photoResponseGainFactor, darkCurrentFactor: factors.darkCurrentFactor,\n readoutOffsetElectrons: rows[row]! + columns[column]! };\n };\n}\n\n/** Saturation and clamp flags of one site: bit 0 physical, 1 pre-ADC, 2 digital, 3 lower clamp. */\nexport const PACKED_SENSOR_RAW_FLAG = Object.freeze({ physical: 1, preAdc: 2, digital: 4, lower: 8 });\n\n/**\n * One site of the reference, in the operation order of `calculateNativeRawSite`. Returns the code and\n * its flags. `index` is the absolute native row-major index; `pattern` is the site's fixed-pattern\n * values (required when the plan has a profile) and `additional` its additional-component expectations,\n * in the plan's order.\n * @internal\n */\nexport function simulatePackedSensorSite(plan: PackedSensorRawPlan, photoE: number, darkE: number, index: number,\n pattern: PackedSensorSitePattern | null = null, additional: readonly number[] = []): { code: number; flags: number } {\n const seed = plan.captureSeedUint32, regime = plan.readout.regime, capacity = plan.physicalChargeCapacityElectrons;\n if ((plan.fixedPatternNoise === null) !== (pattern === null) || additional.length !== plan.additionalChargeComponents.length) {\n throw new InvalidConfigurationError(\"Packed sensor site extras must match the plan.\");\n }\n const random = createDeterministicUniformStream(deriveNativeSiteStreamSeedUint32(seed, index, NATIVE_SITE_CHARGE_STREAM_ID));\n // As in simulateSensorChargeRealization: the fixed-pattern factors multiply the Poisson means.\n const photoMean = pattern === null ? photoE : photoE * pattern.photoResponseGainFactor;\n const darkMean = pattern === null ? darkE : darkE * pattern.darkCurrentFactor;\n const photoRealized = samplePoisson(photoMean, random), darkRealized = samplePoisson(darkMean, random);\n let additionalRealized = 0;\n for (let k = 0; k < additional.length; k++) {\n const expected = additional[k]!;\n additionalRealized = additionalRealized + (plan.additionalChargeComponents[k]!.model === \"poisson\" ? samplePoisson(expected, random) : expected);\n }\n const realized = photoRealized + darkRealized + additionalRealized;\n const readRandom = createDeterministicUniformStream(deriveNativeSiteStreamSeedUint32(seed, index, NATIVE_SITE_READ_NOISE_STREAM_ID));\n const afterPhysical = Math.min(realized, capacity);\n let totalReadNoise = 0;\n for (const component of regime.readNoiseComponents) {\n totalReadNoise = totalReadNoise + (component.rmsElectrons.value === 0 ? 0 : sampleDeterministicStandardNormal(readRandom) * component.rmsElectrons.value);\n }\n const afterReadNoise = afterPhysical + totalReadNoise + (pattern === null ? 0 : pattern.readoutOffsetElectrons);\n const threshold = regime.preAdcSaturationElectronEquivalent.value;\n const afterPreAdc = Math.min(afterReadNoise, threshold);\n const quantized = adcTransfer(regime, plan.readout.adcMaximumCode, afterPreAdc).quantized;\n const saturation = regime.adc.digitalSaturationCode;\n const flags = (afterPhysical < realized ? PACKED_SENSOR_RAW_FLAG.physical : 0) | (afterReadNoise > threshold ? PACKED_SENSOR_RAW_FLAG.preAdc : 0) |\n (quantized >= saturation ? PACKED_SENSOR_RAW_FLAG.digital : 0) | (quantized < 0 ? PACKED_SENSOR_RAW_FLAG.lower : 0);\n return { code: Math.max(0, Math.min(quantized, saturation)), flags };\n}\n\n/**\n * Reference rows `[row0, row1)` of the region into `codes` (indexed from the region start), adding flags\n * into `counts` (physical, pre-ADC, digital, lower).\n * @internal\n */\nexport function simulatePackedSensorRows(plan: PackedSensorRawPlan, photo: Float32Array | Float64Array, dark: Float32Array | Float64Array,\n row0: number, row1: number, codes: Uint16Array, counts: number[], extras: PackedSensorExtras = NO_EXTRAS): void {\n const { region, nativeWidth } = plan;\n const additional = new Array<number>(extras.additional.length).fill(0);\n for (let row = row0; row < row1; row++) {\n for (let column = 0, i = row * region.width; column < region.width; column++, i++) {\n for (let k = 0; k < additional.length; k++) additional[k] = extras.additional[k]![i]!;\n const site = simulatePackedSensorSite(plan, photo[i]!, dark[i]!, (region.y + row) * nativeWidth + region.x + column,\n extras.pattern === null ? null : extras.pattern(column, row), additional);\n codes[i] = site.code;\n if (site.flags & 1) counts[0]!++;\n if (site.flags & 2) counts[1]!++;\n if (site.flags & 4) counts[2]!++;\n if (site.flags & 8) counts[3]!++;\n }\n }\n}\n\n/** Per-site extras of a region: the fixed-pattern evaluator and the additional-component arrays. @internal */\nexport interface PackedSensorExtras {\n pattern: ((column: number, row: number) => PackedSensorSitePattern) | null;\n additional: readonly (Float32Array | Float64Array)[];\n}\nconst NO_EXTRAS: PackedSensorExtras = Object.freeze({ pattern: null, additional: Object.freeze([]) });\n\n/** The extras of a validated input; `snapshot` copies the arrays. @internal */\nexport function packedSensorExtras(plan: PackedSensorRawPlan, input: PackedSensorRawInput, snapshot: boolean): PackedSensorExtras {\n return { pattern: createPackedFixedPattern(plan),\n additional: (input.additionalChargeComponents ?? []).map(c => (snapshot ? c.expectedElectrons.slice() : c.expectedElectrons)) };\n}\n\nexport function packedSensorDiagnostics(counts: readonly number[]): PackedSensorRawDiagnostics {\n return { physicalSaturationSiteCount: counts[0]!, preAdcSaturationSiteCount: counts[1]!, digitalSaturationSiteCount: counts[2]!,\n lowerClampSiteCount: counts[3]! };\n}\n\n/**\n * Synchronous float64 reference for a bounded region. Use the image-kernel accelerator for large\n * regions, host yields or the `webgpu-f32` backend.\n */\nexport function simulatePackedSensorRaw(input: PackedSensorRawInput): PackedSensorRawResult {\n const plan = calculatePackedSensorRawPlan(input);\n const codes = new Uint16Array(plan.siteCount), counts = [0, 0, 0, 0];\n simulatePackedSensorRows(plan, input.photoElectrons, input.darkElectrons, 0, plan.region.height, codes, counts,\n packedSensorExtras(plan, input, false));\n return { plan, codes, diagnostics: packedSensorDiagnostics(counts) };\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Default pixel limit of the full-resolution stages. */
|
|
2
|
+
export declare const FULL_RESOLUTION_DEFAULT_PIXELS = 24000000;
|
|
3
|
+
/** Hard ceiling of an opt-in raised pixel limit: 2^27 = 134 217 728 pixels (16 384 × 8 192). */
|
|
4
|
+
export declare const FULL_RESOLUTION_CEILING_PIXELS = 134217728;
|
|
5
|
+
/**
|
|
6
|
+
* Resolves an optional raised limit: `undefined` gives `defaultValue`; otherwise a safe integer from 1 to
|
|
7
|
+
* `ceiling`. Anything else (non-number, NaN, fraction, zero, negative, above the ceiling) throws `message`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveRaisedLimit(value: unknown, defaultValue: number, ceiling: number, message: string): number;
|
|
10
|
+
/**
|
|
11
|
+
* A budget ceiling sized for the default 24 000 000-pixel limit (a whole number of millions), scaled in
|
|
12
|
+
* proportion to a raised pixel limit and never below its default. Exact integer arithmetic.
|
|
13
|
+
*/
|
|
14
|
+
export declare function scaleBudgetCeiling(defaultCeiling: number, maximumPixels: number): number;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Opt-in raised size limits for the full-resolution stages.
|
|
4
|
+
*
|
|
5
|
+
* Every full-resolution stage admits at most 24 000 000 pixels (about a 24 MP sensor) by default. A caller
|
|
6
|
+
* that has decided it can afford more memory and time may raise a stage's limit with an optional input of
|
|
7
|
+
* the same name as the limit (`maximumPixels`, `maximumRegionSites`, `maximumQueries`), up to that stage's
|
|
8
|
+
* `ceiling*` value. Leaving the input out keeps the default, so default requests, plans and outputs are
|
|
9
|
+
* unchanged. Byte-budget checks (`maximumOutputBytes` and similar) still apply to every request.
|
|
10
|
+
*
|
|
11
|
+
* The ceiling is 2^27 = 134 217 728 pixels (16 384 × 8 192). It covers 100 MP rasters such as
|
|
12
|
+
* 12 252 × 8 168 or 11 552 × 8 664 plus their demosaic margin with about 34 % headroom. It also keeps the
|
|
13
|
+
* largest per-pixel typed array of the pipeline, interleaved float64 RGB (24 bytes per pixel), at 3 GiB,
|
|
14
|
+
* below the 4 GiB ArrayBuffer and 2^32-element typed-array bounds found in current JavaScript engines.
|
|
15
|
+
* Measured memory and time per megapixel are in docs/QUICK_START.md ("Large images").
|
|
16
|
+
*/
|
|
17
|
+
import { InvalidConfigurationError } from "./configuration-error.js";
|
|
18
|
+
/** Default pixel limit of the full-resolution stages. */
|
|
19
|
+
export const FULL_RESOLUTION_DEFAULT_PIXELS = 24_000_000;
|
|
20
|
+
/** Hard ceiling of an opt-in raised pixel limit: 2^27 = 134 217 728 pixels (16 384 × 8 192). */
|
|
21
|
+
export const FULL_RESOLUTION_CEILING_PIXELS = 134_217_728;
|
|
22
|
+
/**
|
|
23
|
+
* Resolves an optional raised limit: `undefined` gives `defaultValue`; otherwise a safe integer from 1 to
|
|
24
|
+
* `ceiling`. Anything else (non-number, NaN, fraction, zero, negative, above the ceiling) throws `message`.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveRaisedLimit(value, defaultValue, ceiling, message) {
|
|
27
|
+
if (value === undefined)
|
|
28
|
+
return defaultValue;
|
|
29
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > ceiling)
|
|
30
|
+
throw new InvalidConfigurationError(message);
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A budget ceiling sized for the default 24 000 000-pixel limit (a whole number of millions), scaled in
|
|
35
|
+
* proportion to a raised pixel limit and never below its default. Exact integer arithmetic.
|
|
36
|
+
*/
|
|
37
|
+
export function scaleBudgetCeiling(defaultCeiling, maximumPixels) {
|
|
38
|
+
return Math.max(defaultCeiling, Math.floor(defaultCeiling / 1_000_000 * maximumPixels / 24));
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=pixel-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pixel-limits.js","sourceRoot":"","sources":["../../src/core/pixel-limits.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAErE,yDAAyD;AACzD,MAAM,CAAC,MAAM,8BAA8B,GAAG,UAAU,CAAC;AACzD,gGAAgG;AAChG,MAAM,CAAC,MAAM,8BAA8B,GAAG,WAAW,CAAC;AAE1D;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAc,EAAE,YAAoB,EAAE,OAAe,EAAE,OAAe;IACvG,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,YAAY,CAAC;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,OAAO;QAAE,MAAM,IAAI,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC5I,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,cAAsB,EAAE,aAAqB;IAC9E,OAAO,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,SAAS,GAAG,aAAa,GAAG,EAAE,CAAC,CAAC,CAAC;AAC/F,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Opt-in raised size limits for the full-resolution stages.\n *\n * Every full-resolution stage admits at most 24 000 000 pixels (about a 24 MP sensor) by default. A caller\n * that has decided it can afford more memory and time may raise a stage's limit with an optional input of\n * the same name as the limit (`maximumPixels`, `maximumRegionSites`, `maximumQueries`), up to that stage's\n * `ceiling*` value. Leaving the input out keeps the default, so default requests, plans and outputs are\n * unchanged. Byte-budget checks (`maximumOutputBytes` and similar) still apply to every request.\n *\n * The ceiling is 2^27 = 134 217 728 pixels (16 384 × 8 192). It covers 100 MP rasters such as\n * 12 252 × 8 168 or 11 552 × 8 664 plus their demosaic margin with about 34 % headroom. It also keeps the\n * largest per-pixel typed array of the pipeline, interleaved float64 RGB (24 bytes per pixel), at 3 GiB,\n * below the 4 GiB ArrayBuffer and 2^32-element typed-array bounds found in current JavaScript engines.\n * Measured memory and time per megapixel are in docs/QUICK_START.md (\"Large images\").\n */\nimport { InvalidConfigurationError } from \"./configuration-error.js\";\n\n/** Default pixel limit of the full-resolution stages. */\nexport const FULL_RESOLUTION_DEFAULT_PIXELS = 24_000_000;\n/** Hard ceiling of an opt-in raised pixel limit: 2^27 = 134 217 728 pixels (16 384 × 8 192). */\nexport const FULL_RESOLUTION_CEILING_PIXELS = 134_217_728;\n\n/**\n * Resolves an optional raised limit: `undefined` gives `defaultValue`; otherwise a safe integer from 1 to\n * `ceiling`. Anything else (non-number, NaN, fraction, zero, negative, above the ceiling) throws `message`.\n */\nexport function resolveRaisedLimit(value: unknown, defaultValue: number, ceiling: number, message: string): number {\n if (value === undefined) return defaultValue;\n if (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < 1 || value > ceiling) throw new InvalidConfigurationError(message);\n return value;\n}\n\n/**\n * A budget ceiling sized for the default 24 000 000-pixel limit (a whole number of millions), scaled in\n * proportion to a raised pixel limit and never below its default. Exact integer arithmetic.\n */\nexport function scaleBudgetCeiling(defaultCeiling: number, maximumPixels: number): number {\n return Math.max(defaultCeiling, Math.floor(defaultCeiling / 1_000_000 * maximumPixels / 24));\n}\n"]}
|
package/dist/core/version.d.ts
CHANGED
package/dist/core/version.js
CHANGED
package/dist/core/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAgB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/core/version.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAgB,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Version of the engine release. Since 1.0.1 it equals the npm package version.\n *\n * New captures and plans record this version as their creator. Archived records keep the\n * version they were made with. Proof-of-concept (POC), schema and model contracts have their\n * own versions. Release checks confirm it matches package.json and the packed package.\n * @see docs/RELEASE_1_0_1.md\n */\nexport const ENGINE_API_VERSION = \"2.2.0\" as const;\n"]}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type ChartScene, type ResolvedChartScene } from "./chart-scenes.js";
|
|
2
2
|
import { type CaptureIlluminant, type ResolvedCaptureIlluminant } from "./scene-radiance.js";
|
|
3
3
|
export declare const CHART_RADIANCE_BATCH_SCHEMA_VERSION: "0.1.0";
|
|
4
|
+
/** `maximumQueries` is the default batch limit; a request may raise it with its own `maximumQueries`, up to `ceilingQueries`. */
|
|
4
5
|
export declare const CHART_RADIANCE_BATCH_LIMITS: Readonly<{
|
|
5
6
|
maximumQueries: 24000000;
|
|
7
|
+
ceilingQueries: 134217728;
|
|
6
8
|
maximumWavelengths: 256;
|
|
7
9
|
maximumChannels: 4;
|
|
8
10
|
}>;
|
|
@@ -22,6 +24,11 @@ export interface ChartRadianceBatchInput {
|
|
|
22
24
|
wavelengthIndices?: Uint8Array | Uint16Array;
|
|
23
25
|
/** Spectral integration: per channel (1–4), one finite weight per wavelength. */
|
|
24
26
|
channelWeights?: readonly (readonly number[])[];
|
|
27
|
+
/**
|
|
28
|
+
* Optional opt-in batch limit: an integer from 1 to `CHART_RADIANCE_BATCH_LIMITS.ceilingQueries`. Default
|
|
29
|
+
* `CHART_RADIANCE_BATCH_LIMITS.maximumQueries` (24 000 000). It does not change the values.
|
|
30
|
+
*/
|
|
31
|
+
maximumQueries?: number;
|
|
25
32
|
}
|
|
26
33
|
/** Validated plan with the resolved chart, illuminant and irradiance scale. Arrays are borrowed. */
|
|
27
34
|
export interface ChartRadianceBatchPlan {
|
|
@@ -17,13 +17,16 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
19
19
|
import { requireAllowlistedRecord } from "../core/record-validation.js";
|
|
20
|
+
import { FULL_RESOLUTION_CEILING_PIXELS, resolveRaisedLimit } from "../core/pixel-limits.js";
|
|
20
21
|
import { resolveChartScene } from "./chart-scenes.js";
|
|
21
22
|
import { buildPlanarSceneRadianceProvider, calculateIlluminantIrradianceScale, resolveCaptureIlluminant } from "./scene-radiance.js";
|
|
22
23
|
export const CHART_RADIANCE_BATCH_SCHEMA_VERSION = "0.1.0";
|
|
23
|
-
|
|
24
|
+
/** `maximumQueries` is the default batch limit; a request may raise it with its own `maximumQueries`, up to `ceilingQueries`. */
|
|
25
|
+
export const CHART_RADIANCE_BATCH_LIMITS = Object.freeze({ maximumQueries: 24_000_000, ceilingQueries: FULL_RESOLUTION_CEILING_PIXELS, maximumWavelengths: 256,
|
|
26
|
+
maximumChannels: 4 });
|
|
24
27
|
export function calculateChartRadianceBatchPlan(input) {
|
|
25
28
|
const r = requireAllowlistedRecord(input, ["schemaVersion", "scene", "illuminant", "illuminanceLux", "aspect", "points", "wavelengthsNanometers",
|
|
26
|
-
"wavelengthIndices", "channelWeights"], "Invalid chart radiance batch input.");
|
|
29
|
+
"wavelengthIndices", "channelWeights", "maximumQueries"], "Invalid chart radiance batch input.");
|
|
27
30
|
if (r.schemaVersion !== CHART_RADIANCE_BATCH_SCHEMA_VERSION)
|
|
28
31
|
throw new InvalidConfigurationError("Unsupported chart radiance batch schema.");
|
|
29
32
|
if (typeof r.aspect !== "number" || !(r.aspect > 0 && r.aspect <= Number.MAX_VALUE))
|
|
@@ -32,7 +35,8 @@ export function calculateChartRadianceBatchPlan(input) {
|
|
|
32
35
|
const illuminant = resolveCaptureIlluminant(r.illuminant);
|
|
33
36
|
const irradianceScale = calculateIlluminantIrradianceScale(illuminant.shape, r.illuminanceLux);
|
|
34
37
|
const L = CHART_RADIANCE_BATCH_LIMITS, points = r.points;
|
|
35
|
-
|
|
38
|
+
const maximumQueries = resolveRaisedLimit(r.maximumQueries, L.maximumQueries, L.ceilingQueries, "Chart radiance batch maximumQueries must be an integer from 1 to " + L.ceilingQueries + ".");
|
|
39
|
+
if (!(points instanceof Float32Array || points instanceof Float64Array) || points.length % 2 !== 0 || points.length / 2 > maximumQueries) {
|
|
36
40
|
throw new InvalidConfigurationError("points must be an interleaved Float32Array or Float64Array of (u, v) pairs.");
|
|
37
41
|
}
|
|
38
42
|
for (let i = 0; i < points.length; i++)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chart-radiance-batch.js","sourceRoot":"","sources":["../../src/facade/chart-radiance-batch.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Batched radiance queries for the engine's procedural chart scenes (#277): many frame points in one\n * call, each answered with spectral radiance at a declared wavelength, or with the spectral integral of\n * that radiance against declared channel weights. This is the float64 CPU reference for the\n * `webgpu-f32` chart-radiance stage (`ImageKernelAccelerator.evaluateChartRadiance`).\n *\n * Every value comes from the chart provider's own `radianceAt` (createPlanarSceneRadianceProvider): a\n * Lambertian reflectance ρ(λ) under the scaled illuminant, L = ρ·k·S(λ)/π. The spectral integral of a\n * query is Σ_j w_cj·L(u, v, λ_j), summed in wavelength order. Charts are uniform-illuminant Lambertian\n * patterns, so a query's integral depends only on its material: the GPU stage uses exactly that.\n *\n * The query shape mirrors a batched provider (one call, queries in, results in the same order). Points\n * are frame units (u right, v up; see chart-scenes.ts); `mapEnvironmentDirectionsToChartPoints` converts\n * environment-direction queries exactly as `evaluateRadiance` does.\n * @see docs/WEBGPU.md, \"Scene radiance on the GPU\".\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord } from \"../core/record-validation.js\";\nimport { resolveChartScene, type ChartScene, type ResolvedChartScene } from \"./chart-scenes.js\";\nimport { buildPlanarSceneRadianceProvider, calculateIlluminantIrradianceScale, resolveCaptureIlluminant, type CaptureIlluminant,\n type ResolvedCaptureIlluminant } from \"./scene-radiance.js\";\n\nexport const CHART_RADIANCE_BATCH_SCHEMA_VERSION = \"0.1.0\" as const;\nexport const CHART_RADIANCE_BATCH_LIMITS = Object.freeze({ maximumQueries: 24_000_000, maximumWavelengths: 256, maximumChannels: 4 });\n\nexport interface ChartRadianceBatchInput {\n schemaVersion: typeof CHART_RADIANCE_BATCH_SCHEMA_VERSION;\n /** An engine chart preset (caller scene functions are not supported here). */\n scene: ChartScene;\n illuminant: CaptureIlluminant;\n illuminanceLux: number;\n /** Frame height / width. */\n aspect: number;\n /** Frame points, interleaved [u0, v0, u1, v1, …]. Finite. */\n points: Float32Array | Float64Array;\n /** The wavelengths the queries use, in nanometres (1–256). */\n wavelengthsNanometers: readonly number[];\n /** Radiance queries: one wavelength index per point. Exactly one of this and `channelWeights`. */\n wavelengthIndices?: Uint8Array | Uint16Array;\n /** Spectral integration: per channel (1–4), one finite weight per wavelength. */\n channelWeights?: readonly (readonly number[])[];\n}\n\n/** Validated plan with the resolved chart, illuminant and irradiance scale. Arrays are borrowed. */\nexport interface ChartRadianceBatchPlan {\n chart: ResolvedChartScene;\n illuminant: ResolvedCaptureIlluminant;\n irradianceScale: number;\n queryCount: number;\n /** Output values per query: 1 for radiance queries, the channel count for spectral integration. */\n columns: number;\n mode: \"radiance\" | \"spectral-integral\";\n wavelengthsNanometers: readonly number[];\n channelWeights: readonly (readonly number[])[] | null;\n /** Spectral radiance of one material at a wavelength: the provider's own radianceAt for that material. */\n materialRadiance(material: number, wavelengthNanometers: number): number;\n /** The reference value of one query and output column (radianceAt, and its wavelength-ordered sum). */\n queryValue(points: Float32Array | Float64Array, wavelengthIndices: Uint8Array | Uint16Array | undefined, query: number, column: number): number;\n}\n\nexport function calculateChartRadianceBatchPlan(input: ChartRadianceBatchInput): ChartRadianceBatchPlan {\n const r = requireAllowlistedRecord(input, [\"schemaVersion\", \"scene\", \"illuminant\", \"illuminanceLux\", \"aspect\", \"points\", \"wavelengthsNanometers\",\n \"wavelengthIndices\", \"channelWeights\"], \"Invalid chart radiance batch input.\");\n if (r.schemaVersion !== CHART_RADIANCE_BATCH_SCHEMA_VERSION) throw new InvalidConfigurationError(\"Unsupported chart radiance batch schema.\");\n if (typeof r.aspect !== \"number\" || !(r.aspect > 0 && r.aspect <= Number.MAX_VALUE)) throw new InvalidConfigurationError(\"aspect must be positive.\");\n const chart = resolveChartScene({ scene: r.scene as ChartScene, aspect: r.aspect });\n const illuminant = resolveCaptureIlluminant(r.illuminant as CaptureIlluminant);\n const irradianceScale = calculateIlluminantIrradianceScale(illuminant.shape, r.illuminanceLux as number);\n const L = CHART_RADIANCE_BATCH_LIMITS, points = r.points;\n if (!(points instanceof Float32Array || points instanceof Float64Array) || points.length % 2 !== 0 || points.length / 2 > L.maximumQueries) {\n throw new InvalidConfigurationError(\"points must be an interleaved Float32Array or Float64Array of (u, v) pairs.\");\n }\n for (let i = 0; i < points.length; i++) if (!Number.isFinite(points[i]!)) throw new InvalidConfigurationError(\"Chart points must be finite.\");\n const queryCount = points.length / 2, wavelengths = r.wavelengthsNanometers;\n if (!Array.isArray(wavelengths) || wavelengths.length < 1 || wavelengths.length > L.maximumWavelengths ||\n wavelengths.some(nm => typeof nm !== \"number\" || !Number.isFinite(nm) || nm <= 0)) {\n throw new InvalidConfigurationError(\"wavelengthsNanometers must list 1 to 256 positive wavelengths.\");\n }\n const indices = r.wavelengthIndices, weights = r.channelWeights;\n if ((indices === undefined) === (weights === undefined)) throw new InvalidConfigurationError(\"Give exactly one of wavelengthIndices and channelWeights.\");\n if (indices !== undefined) {\n if (!(indices instanceof Uint8Array || indices instanceof Uint16Array) || indices.length !== queryCount) {\n throw new InvalidConfigurationError(\"wavelengthIndices must hold one index per point.\");\n }\n for (let i = 0; i < queryCount; i++) if (indices[i]! >= wavelengths.length) throw new InvalidConfigurationError(\"A wavelength index is out of range.\");\n } else if (!Array.isArray(weights) || weights.length < 1 || weights.length > L.maximumChannels ||\n weights.some(row => !Array.isArray(row) || row.length !== wavelengths.length || row.some(w => typeof w !== \"number\" || !Number.isFinite(w)))) {\n throw new InvalidConfigurationError(\"channelWeights must hold 1 to 4 rows of one finite weight per wavelength.\");\n }\n // The provider's own radiance function: chart reflectance × scaled illuminant / π, in its float64 order.\n const provider = buildPlanarSceneRadianceProvider({ scene: chart, sceneId: \"chart-radiance-batch\", providerProfileId: \"chart-radiance-batch\", illuminant,\n irradianceScale, geometry: { distanceM: 1, metresPerFrameUnit: 1 }, evidence: [], limitations: [] });\n const channelWeights = weights === undefined ? null : (weights as number[][]).map(row => Object.freeze([...row]));\n const nm = Object.freeze([...(wavelengths as number[])]);\n const materialRadiance = (material: number, wavelength: number): number => {\n const m = chart.materials[material]!;\n const reflectance = m.kind === \"neutral\" ? m.reflectance : m.spectrum.evaluate(wavelength);\n if (m.kind === \"spectrum\" && !(reflectance >= 0 && reflectance <= 1)) throw new InvalidConfigurationError(\"Chart reflectance spectrum left [0, 1].\");\n return reflectance * irradianceScale * illuminant.shape.evaluate(wavelength) / Math.PI;\n };\n return Object.freeze({ chart, illuminant, irradianceScale, queryCount, columns: channelWeights === null ? 1 : channelWeights.length,\n mode: channelWeights === null ? \"radiance\" as const : \"spectral-integral\" as const, wavelengthsNanometers: nm, channelWeights, materialRadiance,\n queryValue(p: Float32Array | Float64Array, w: Uint8Array | Uint16Array | undefined, query: number, column: number): number {\n const u = p[2 * query]!, v = p[2 * query + 1]!;\n if (channelWeights === null) return provider.radianceAt(u, v, nm[w![query]!]!);\n let sum = 0;\n const row = channelWeights[column]!;\n for (let j = 0; j < nm.length; j++) sum += row[j]! * provider.radianceAt(u, v, nm[j]!);\n return sum;\n } });\n}\n\n/** Float64 CPU reference: every query's value(s), row-major (query, column). */\nexport function calculateChartRadianceBatch(input: ChartRadianceBatchInput): { plan: ChartRadianceBatchPlan; values: Float64Array } {\n const plan = calculateChartRadianceBatchPlan(input), values = new Float64Array(plan.queryCount * plan.columns);\n for (let q = 0; q < plan.queryCount; q++) for (let c = 0; c < plan.columns; c++) values[q * plan.columns + c] = plan.queryValue(input.points, input.wavelengthIndices, q, c);\n return { plan, values };\n}\n\n/**\n * Frame points of environment-direction queries on the chart plane, exactly as the planar provider's\n * `evaluateRadiance` computes them: look direction l = −o, plane point l·distance/l.z, in frame units.\n * @param directions - Outgoing unit vectors (scene toward camera), interleaved [x, y, z, …].\n */\nexport function mapEnvironmentDirectionsToChartPoints(input: { directions: Float32Array | Float64Array; distanceM: number; metresPerFrameUnit: number }): Float64Array {\n const r = requireAllowlistedRecord(input, [\"directions\", \"distanceM\", \"metresPerFrameUnit\"], \"Invalid planar direction input.\");\n const d = r.directions, distance = r.distanceM, scale = r.metresPerFrameUnit;\n if (!(d instanceof Float32Array || d instanceof Float64Array) || d.length % 3 !== 0 || typeof distance !== \"number\" || !(distance > 0 && distance <= Number.MAX_VALUE) ||\n typeof scale !== \"number\" || !(scale > 0 && scale <= Number.MAX_VALUE)) {\n throw new InvalidConfigurationError(\"Planar directions need interleaved unit vectors, a positive distance and a positive frame scale.\");\n }\n const points = new Float64Array(d.length / 3 * 2);\n for (let i = 0; i < d.length / 3; i++) {\n const lz = -d[3 * i + 2]!;\n if (!(lz > 0)) throw new InvalidConfigurationError(\"Planar scene query does not point toward the scene plane.\");\n const t = distance / lz;\n const x = -d[3 * i]! * t / scale, y = -d[3 * i + 1]! * t / scale;\n // A grazing direction (lz near 0, e.g. 1e-320) or a non-finite component would give ±Infinity or NaN.\n if (!Number.isFinite(x) || !Number.isFinite(y)) throw new InvalidConfigurationError(\"Planar scene query maps to a non-finite chart point (grazing or non-finite direction).\");\n points[2 * i] = x;\n points[2 * i + 1] = y;\n }\n return points;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"chart-radiance-batch.js","sourceRoot":"","sources":["../../src/facade/chart-radiance-batch.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Batched radiance queries for the engine's procedural chart scenes (#277): many frame points in one\n * call, each answered with spectral radiance at a declared wavelength, or with the spectral integral of\n * that radiance against declared channel weights. This is the float64 CPU reference for the\n * `webgpu-f32` chart-radiance stage (`ImageKernelAccelerator.evaluateChartRadiance`).\n *\n * Every value comes from the chart provider's own `radianceAt` (createPlanarSceneRadianceProvider): a\n * Lambertian reflectance ρ(λ) under the scaled illuminant, L = ρ·k·S(λ)/π. The spectral integral of a\n * query is Σ_j w_cj·L(u, v, λ_j), summed in wavelength order. Charts are uniform-illuminant Lambertian\n * patterns, so a query's integral depends only on its material: the GPU stage uses exactly that.\n *\n * The query shape mirrors a batched provider (one call, queries in, results in the same order). Points\n * are frame units (u right, v up; see chart-scenes.ts); `mapEnvironmentDirectionsToChartPoints` converts\n * environment-direction queries exactly as `evaluateRadiance` does.\n * @see docs/WEBGPU.md, \"Scene radiance on the GPU\".\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord } from \"../core/record-validation.js\";\nimport { FULL_RESOLUTION_CEILING_PIXELS, resolveRaisedLimit } from \"../core/pixel-limits.js\";\nimport { resolveChartScene, type ChartScene, type ResolvedChartScene } from \"./chart-scenes.js\";\nimport { buildPlanarSceneRadianceProvider, calculateIlluminantIrradianceScale, resolveCaptureIlluminant, type CaptureIlluminant,\n type ResolvedCaptureIlluminant } from \"./scene-radiance.js\";\n\nexport const CHART_RADIANCE_BATCH_SCHEMA_VERSION = \"0.1.0\" as const;\n/** `maximumQueries` is the default batch limit; a request may raise it with its own `maximumQueries`, up to `ceilingQueries`. */\nexport const CHART_RADIANCE_BATCH_LIMITS = Object.freeze({ maximumQueries: 24_000_000, ceilingQueries: FULL_RESOLUTION_CEILING_PIXELS, maximumWavelengths: 256,\n maximumChannels: 4 });\n\nexport interface ChartRadianceBatchInput {\n schemaVersion: typeof CHART_RADIANCE_BATCH_SCHEMA_VERSION;\n /** An engine chart preset (caller scene functions are not supported here). */\n scene: ChartScene;\n illuminant: CaptureIlluminant;\n illuminanceLux: number;\n /** Frame height / width. */\n aspect: number;\n /** Frame points, interleaved [u0, v0, u1, v1, …]. Finite. */\n points: Float32Array | Float64Array;\n /** The wavelengths the queries use, in nanometres (1–256). */\n wavelengthsNanometers: readonly number[];\n /** Radiance queries: one wavelength index per point. Exactly one of this and `channelWeights`. */\n wavelengthIndices?: Uint8Array | Uint16Array;\n /** Spectral integration: per channel (1–4), one finite weight per wavelength. */\n channelWeights?: readonly (readonly number[])[];\n /**\n * Optional opt-in batch limit: an integer from 1 to `CHART_RADIANCE_BATCH_LIMITS.ceilingQueries`. Default\n * `CHART_RADIANCE_BATCH_LIMITS.maximumQueries` (24 000 000). It does not change the values.\n */\n maximumQueries?: number;\n}\n\n/** Validated plan with the resolved chart, illuminant and irradiance scale. Arrays are borrowed. */\nexport interface ChartRadianceBatchPlan {\n chart: ResolvedChartScene;\n illuminant: ResolvedCaptureIlluminant;\n irradianceScale: number;\n queryCount: number;\n /** Output values per query: 1 for radiance queries, the channel count for spectral integration. */\n columns: number;\n mode: \"radiance\" | \"spectral-integral\";\n wavelengthsNanometers: readonly number[];\n channelWeights: readonly (readonly number[])[] | null;\n /** Spectral radiance of one material at a wavelength: the provider's own radianceAt for that material. */\n materialRadiance(material: number, wavelengthNanometers: number): number;\n /** The reference value of one query and output column (radianceAt, and its wavelength-ordered sum). */\n queryValue(points: Float32Array | Float64Array, wavelengthIndices: Uint8Array | Uint16Array | undefined, query: number, column: number): number;\n}\n\nexport function calculateChartRadianceBatchPlan(input: ChartRadianceBatchInput): ChartRadianceBatchPlan {\n const r = requireAllowlistedRecord(input, [\"schemaVersion\", \"scene\", \"illuminant\", \"illuminanceLux\", \"aspect\", \"points\", \"wavelengthsNanometers\",\n \"wavelengthIndices\", \"channelWeights\", \"maximumQueries\"], \"Invalid chart radiance batch input.\");\n if (r.schemaVersion !== CHART_RADIANCE_BATCH_SCHEMA_VERSION) throw new InvalidConfigurationError(\"Unsupported chart radiance batch schema.\");\n if (typeof r.aspect !== \"number\" || !(r.aspect > 0 && r.aspect <= Number.MAX_VALUE)) throw new InvalidConfigurationError(\"aspect must be positive.\");\n const chart = resolveChartScene({ scene: r.scene as ChartScene, aspect: r.aspect });\n const illuminant = resolveCaptureIlluminant(r.illuminant as CaptureIlluminant);\n const irradianceScale = calculateIlluminantIrradianceScale(illuminant.shape, r.illuminanceLux as number);\n const L = CHART_RADIANCE_BATCH_LIMITS, points = r.points;\n const maximumQueries = resolveRaisedLimit(r.maximumQueries, L.maximumQueries, L.ceilingQueries,\n \"Chart radiance batch maximumQueries must be an integer from 1 to \" + L.ceilingQueries + \".\");\n if (!(points instanceof Float32Array || points instanceof Float64Array) || points.length % 2 !== 0 || points.length / 2 > maximumQueries) {\n throw new InvalidConfigurationError(\"points must be an interleaved Float32Array or Float64Array of (u, v) pairs.\");\n }\n for (let i = 0; i < points.length; i++) if (!Number.isFinite(points[i]!)) throw new InvalidConfigurationError(\"Chart points must be finite.\");\n const queryCount = points.length / 2, wavelengths = r.wavelengthsNanometers;\n if (!Array.isArray(wavelengths) || wavelengths.length < 1 || wavelengths.length > L.maximumWavelengths ||\n wavelengths.some(nm => typeof nm !== \"number\" || !Number.isFinite(nm) || nm <= 0)) {\n throw new InvalidConfigurationError(\"wavelengthsNanometers must list 1 to 256 positive wavelengths.\");\n }\n const indices = r.wavelengthIndices, weights = r.channelWeights;\n if ((indices === undefined) === (weights === undefined)) throw new InvalidConfigurationError(\"Give exactly one of wavelengthIndices and channelWeights.\");\n if (indices !== undefined) {\n if (!(indices instanceof Uint8Array || indices instanceof Uint16Array) || indices.length !== queryCount) {\n throw new InvalidConfigurationError(\"wavelengthIndices must hold one index per point.\");\n }\n for (let i = 0; i < queryCount; i++) if (indices[i]! >= wavelengths.length) throw new InvalidConfigurationError(\"A wavelength index is out of range.\");\n } else if (!Array.isArray(weights) || weights.length < 1 || weights.length > L.maximumChannels ||\n weights.some(row => !Array.isArray(row) || row.length !== wavelengths.length || row.some(w => typeof w !== \"number\" || !Number.isFinite(w)))) {\n throw new InvalidConfigurationError(\"channelWeights must hold 1 to 4 rows of one finite weight per wavelength.\");\n }\n // The provider's own radiance function: chart reflectance × scaled illuminant / π, in its float64 order.\n const provider = buildPlanarSceneRadianceProvider({ scene: chart, sceneId: \"chart-radiance-batch\", providerProfileId: \"chart-radiance-batch\", illuminant,\n irradianceScale, geometry: { distanceM: 1, metresPerFrameUnit: 1 }, evidence: [], limitations: [] });\n const channelWeights = weights === undefined ? null : (weights as number[][]).map(row => Object.freeze([...row]));\n const nm = Object.freeze([...(wavelengths as number[])]);\n const materialRadiance = (material: number, wavelength: number): number => {\n const m = chart.materials[material]!;\n const reflectance = m.kind === \"neutral\" ? m.reflectance : m.spectrum.evaluate(wavelength);\n if (m.kind === \"spectrum\" && !(reflectance >= 0 && reflectance <= 1)) throw new InvalidConfigurationError(\"Chart reflectance spectrum left [0, 1].\");\n return reflectance * irradianceScale * illuminant.shape.evaluate(wavelength) / Math.PI;\n };\n return Object.freeze({ chart, illuminant, irradianceScale, queryCount, columns: channelWeights === null ? 1 : channelWeights.length,\n mode: channelWeights === null ? \"radiance\" as const : \"spectral-integral\" as const, wavelengthsNanometers: nm, channelWeights, materialRadiance,\n queryValue(p: Float32Array | Float64Array, w: Uint8Array | Uint16Array | undefined, query: number, column: number): number {\n const u = p[2 * query]!, v = p[2 * query + 1]!;\n if (channelWeights === null) return provider.radianceAt(u, v, nm[w![query]!]!);\n let sum = 0;\n const row = channelWeights[column]!;\n for (let j = 0; j < nm.length; j++) sum += row[j]! * provider.radianceAt(u, v, nm[j]!);\n return sum;\n } });\n}\n\n/** Float64 CPU reference: every query's value(s), row-major (query, column). */\nexport function calculateChartRadianceBatch(input: ChartRadianceBatchInput): { plan: ChartRadianceBatchPlan; values: Float64Array } {\n const plan = calculateChartRadianceBatchPlan(input), values = new Float64Array(plan.queryCount * plan.columns);\n for (let q = 0; q < plan.queryCount; q++) for (let c = 0; c < plan.columns; c++) values[q * plan.columns + c] = plan.queryValue(input.points, input.wavelengthIndices, q, c);\n return { plan, values };\n}\n\n/**\n * Frame points of environment-direction queries on the chart plane, exactly as the planar provider's\n * `evaluateRadiance` computes them: look direction l = −o, plane point l·distance/l.z, in frame units.\n * @param directions - Outgoing unit vectors (scene toward camera), interleaved [x, y, z, …].\n */\nexport function mapEnvironmentDirectionsToChartPoints(input: { directions: Float32Array | Float64Array; distanceM: number; metresPerFrameUnit: number }): Float64Array {\n const r = requireAllowlistedRecord(input, [\"directions\", \"distanceM\", \"metresPerFrameUnit\"], \"Invalid planar direction input.\");\n const d = r.directions, distance = r.distanceM, scale = r.metresPerFrameUnit;\n if (!(d instanceof Float32Array || d instanceof Float64Array) || d.length % 3 !== 0 || typeof distance !== \"number\" || !(distance > 0 && distance <= Number.MAX_VALUE) ||\n typeof scale !== \"number\" || !(scale > 0 && scale <= Number.MAX_VALUE)) {\n throw new InvalidConfigurationError(\"Planar directions need interleaved unit vectors, a positive distance and a positive frame scale.\");\n }\n const points = new Float64Array(d.length / 3 * 2);\n for (let i = 0; i < d.length / 3; i++) {\n const lz = -d[3 * i + 2]!;\n if (!(lz > 0)) throw new InvalidConfigurationError(\"Planar scene query does not point toward the scene plane.\");\n const t = distance / lz;\n const x = -d[3 * i]! * t / scale, y = -d[3 * i + 1]! * t / scale;\n // A grazing direction (lz near 0, e.g. 1e-320) or a non-finite component would give ±Infinity or NaN.\n if (!Number.isFinite(x) || !Number.isFinite(y)) throw new InvalidConfigurationError(\"Planar scene query maps to a non-finite chart point (grazing or non-finite direction).\");\n points[2 * i] = x;\n points[2 * i + 1] = y;\n }\n return points;\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ResolveLensSampledPsfInput } from "../optics/lens-psf-profile.js";
|
|
2
|
+
import type { PlanarSceneRadianceProvider } from "./scene-radiance.js";
|
|
3
|
+
export interface DiffractedPlanarProviderInput {
|
|
4
|
+
base: PlanarSceneRadianceProvider;
|
|
5
|
+
/** The optical state of the sampled PSF, as for `resolveLensSampledPsf`, without field point and wavelength. */
|
|
6
|
+
psf: Omit<ResolveLensSampledPsfInput, "fieldPointMm" | "wavelengthNm">;
|
|
7
|
+
/** Spectral nodes the environment queries (the profile's wavelength nodes). */
|
|
8
|
+
wavelengthsNm: readonly number[];
|
|
9
|
+
kernelRadius: number;
|
|
10
|
+
nativeWidth: number;
|
|
11
|
+
nativeHeight: number;
|
|
12
|
+
/** Output (active) width in sites: frame units are 2/outputWidth per site. */
|
|
13
|
+
outputWidth: number;
|
|
14
|
+
sitePitchMicrometers: number;
|
|
15
|
+
/** Planar geometry of the base provider, to map query directions to plane points exactly as it does. */
|
|
16
|
+
distanceM: number;
|
|
17
|
+
metresPerFrameUnit: number;
|
|
18
|
+
/**
|
|
19
|
+
* Offsets of the query points from the site centres, in sites (+x right, +y down): the anti-aliasing split
|
|
20
|
+
* offsets with their sign reversed (the environment queries q − a). Default: one zero offset.
|
|
21
|
+
*/
|
|
22
|
+
queryOffsetsSites?: readonly {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
}[];
|
|
26
|
+
/** Called with the duration in ms of each band convolution (diagnostics only; never changes values). */
|
|
27
|
+
onConvolution?: (milliseconds: number) => void;
|
|
28
|
+
}
|
|
29
|
+
/** Internal (not exported from the package): the base provider with diffraction applied as an object-space convolution. */
|
|
30
|
+
export declare function createDiffractedPlanarProvider(input: DiffractedPlanarProviderInput): PlanarSceneRadianceProvider;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Fast execution of `simulateCapture`'s `lens.diffraction` (2.1): an object-space convolution in the scene
|
|
4
|
+
* provider, instead of one scene query per PSF tap in the environment PSF quadrature.
|
|
5
|
+
*
|
|
6
|
+
* The environment quadrature computes, at each site centre q and spectral node λ,
|
|
7
|
+
* E(q, λ) = Σ_k K_λ(k)·T·L(s_k(q), λ),
|
|
8
|
+
* where s_k(q) is the tap's source point (`sensorPsfSourcePoint`), L the scene radiance at the plane point
|
|
9
|
+
* that source point sees, and T the radiance-to-irradiance factor. For the facade's planar scene seen
|
|
10
|
+
* through the ideal rectilinear projection with a still camera and no lens field mapping, field throughput or
|
|
11
|
+
* pupil rays:
|
|
12
|
+
* - T is the same at every sensor point (paraxial π/(4·N_w²), no cos⁴ term), and
|
|
13
|
+
* - with the kernel sampled on the site pitch, every source point is the centre of another site, so the plane
|
|
14
|
+
* points form one regular lattice: u = (i + ½ − W/2)·2/w, v = −(j + ½ − H/2)·2/w (frame units, native
|
|
15
|
+
* site (i, j), W×H native sites, w output width).
|
|
16
|
+
* So E(q, λ) = T·B(q, λ) with B = K_λ ⊛ L sampled on that lattice: the same sum, in a different floating-point
|
|
17
|
+
* order. B is computed once per spectral node with the engine's isoplanatic convolution
|
|
18
|
+
* (`calculateIrradianceConvolution`, kernels from `resolveLensPsfConvolutionKernels`, exact site sampling) over a
|
|
19
|
+
* grid that holds every source (the native raster plus the kernel radius on each side), in row bands. The
|
|
20
|
+
* provider then answers each site-centre query with B instead of L, and the environment request declares no
|
|
21
|
+
* PSF. Incoherent imaging is a linear superposition of intensity PSFs, so convolving radiance on the object
|
|
22
|
+
* plane and convolving irradiance on the image plane agree for a planar object and a space-invariant PSF
|
|
23
|
+
* (MIT OpenCourseWare 2.71, lecture 22, free; also see: Goodman, Introduction to Fourier Optics, 4th ed., §6.1).
|
|
24
|
+
*
|
|
25
|
+
* With an anti-aliasing point-splitting filter (2.1, `sensor.antiAliasing`) the environment queries the PSF taps
|
|
26
|
+
* around each pre-filter source point q − a (a = a split offset) instead of around q. Those taps lie on the site
|
|
27
|
+
* lattice shifted by a, so the same argument holds on each shifted lattice: B is computed once per distinct
|
|
28
|
+
* fractional shift (offsets that differ by whole sites share one lattice; the centred four-spot filter with a
|
|
29
|
+
* one-site separation needs a single half-site-shifted lattice) over the native raster widened by the largest
|
|
30
|
+
* offset, and the environment's spatial quadrature still forms the weighted split sum.
|
|
31
|
+
*
|
|
32
|
+
* A query that does not land on one of the lattices (within 1e-6 site) is refused rather than approximated: the
|
|
33
|
+
* facade uses this path only when the lattice argument above holds.
|
|
34
|
+
*/
|
|
35
|
+
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
36
|
+
import { calculateIrradianceConvolution } from "../optics/irradiance-convolution.js";
|
|
37
|
+
import { resolveLensPsfConvolutionKernels } from "../optics/lens-psf-kernels.js";
|
|
38
|
+
/** Rows of native sites per convolution band, and the bands kept. Banding changes memory only, never values. */
|
|
39
|
+
const BAND_ROWS = 32;
|
|
40
|
+
const CACHED_BANDS = 4;
|
|
41
|
+
const LATTICE_TOLERANCE_SITES = 1e-6;
|
|
42
|
+
/** Internal (not exported from the package): the base provider with diffraction applied as an object-space convolution. */
|
|
43
|
+
export function createDiffractedPlanarProvider(input) {
|
|
44
|
+
const { base, wavelengthsNm, nativeWidth: W, nativeHeight: H, kernelRadius: R } = input;
|
|
45
|
+
const g = 2 / input.outputWidth, pitch = input.sitePitchMicrometers;
|
|
46
|
+
const index = new Map(wavelengthsNm.map((nm, i) => [nm, i]));
|
|
47
|
+
// One lattice per distinct fractional query offset; M extra sites per side hold the offset queries.
|
|
48
|
+
const offsets = input.queryOffsetsSites ?? [{ x: 0, y: 0 }];
|
|
49
|
+
const fraction = (value) => { const f = value - Math.floor(value); return f > 1 - LATTICE_TOLERANCE_SITES ? 0 : f < LATTICE_TOLERANCE_SITES ? 0 : f; };
|
|
50
|
+
const shifts = [];
|
|
51
|
+
for (const o of offsets) {
|
|
52
|
+
const f = { x: fraction(o.x), y: fraction(o.y) };
|
|
53
|
+
if (!shifts.some((s) => Math.abs(s.x - f.x) <= LATTICE_TOLERANCE_SITES && Math.abs(s.y - f.y) <= LATTICE_TOLERANCE_SITES))
|
|
54
|
+
shifts.push(f);
|
|
55
|
+
}
|
|
56
|
+
const M = Math.max(0, ...offsets.map((o) => Math.ceil(Math.max(Math.abs(o.x), Math.abs(o.y)) - LATTICE_TOLERANCE_SITES)));
|
|
57
|
+
const LW = W + 2 * M, LH = H + 2 * M;
|
|
58
|
+
// One kernel per wavelength, on the site grid, from the engine's own tap-to-site mapping (field point 0, 0).
|
|
59
|
+
const kernels = wavelengthsNm.map((wavelengthNm) => {
|
|
60
|
+
const k = resolveLensPsfConvolutionKernels({ schemaVersion: "0.1.0", psf: input.psf, spectralWeights: [{ wavelengthNm, weight: 1 }],
|
|
61
|
+
grid: { width: 1, height: 1, sitePitchMicrometers: { x: pitch, y: pitch }, opticalAxisSite: { x: 0.5, y: 0.5 } }, patchWidth: 1, patchHeight: 1,
|
|
62
|
+
kernelRadius: R }).value;
|
|
63
|
+
if (!k.exactSiteSampling || k.kernelRadius !== R)
|
|
64
|
+
throw new InvalidConfigurationError("Diffraction kernels must sample the site pitch exactly.");
|
|
65
|
+
return k.kernels;
|
|
66
|
+
});
|
|
67
|
+
const gridWidth = LW + 2 * R;
|
|
68
|
+
const now = () => globalThis.performance?.now() ?? Date.now();
|
|
69
|
+
const bands = new Map();
|
|
70
|
+
function band(b) {
|
|
71
|
+
const cached = bands.get(b);
|
|
72
|
+
if (cached) {
|
|
73
|
+
bands.delete(b);
|
|
74
|
+
bands.set(b, cached);
|
|
75
|
+
return cached;
|
|
76
|
+
}
|
|
77
|
+
const started = input.onConvolution ? now() : 0;
|
|
78
|
+
const y0 = b * BAND_ROWS, rows = Math.min(BAND_ROWS, LH - y0), gridHeight = rows + 2 * R;
|
|
79
|
+
const values = shifts.map((shift) => wavelengthsNm.map((nm, w) => {
|
|
80
|
+
const samples = new Float64Array(gridWidth * gridHeight);
|
|
81
|
+
for (let gy = 0; gy < gridHeight; gy++) {
|
|
82
|
+
const v = shift.y === 0 ? -(y0 + gy - R - M + 0.5 - H / 2) * g : -(y0 + gy - R - M + 0.5 + shift.y - H / 2) * g;
|
|
83
|
+
for (let gx = 0; gx < gridWidth; gx++)
|
|
84
|
+
samples[gy * gridWidth + gx] = base.radianceAt(shift.x === 0 ? (gx - R - M + 0.5 - W / 2) * g : (gx - R - M + 0.5 + shift.x - W / 2) * g, v, nm);
|
|
85
|
+
}
|
|
86
|
+
const blurred = calculateIrradianceConvolution({ schemaVersion: "0.1.0", width: gridWidth, height: gridHeight, samples, kernelRadius: R,
|
|
87
|
+
patchWidth: gridWidth, patchHeight: gridHeight, kernels: kernels[w], boundary: "zero-outside-grid" }).values;
|
|
88
|
+
const out = new Float64Array(LW * rows);
|
|
89
|
+
for (let y = 0; y < rows; y++)
|
|
90
|
+
out.set(blurred.subarray((y + R) * gridWidth + R, (y + R) * gridWidth + R + LW), y * LW);
|
|
91
|
+
return out;
|
|
92
|
+
}));
|
|
93
|
+
bands.set(b, values);
|
|
94
|
+
if (bands.size > CACHED_BANDS)
|
|
95
|
+
bands.delete(bands.keys().next().value);
|
|
96
|
+
if (input.onConvolution)
|
|
97
|
+
input.onConvolution(now() - started);
|
|
98
|
+
return values;
|
|
99
|
+
}
|
|
100
|
+
function blurredAt(u, v, nm) {
|
|
101
|
+
const w = index.get(nm), fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;
|
|
102
|
+
if (w !== undefined)
|
|
103
|
+
for (let s = 0; s < shifts.length; s++) {
|
|
104
|
+
const shift = shifts[s], i = Math.round(fi - shift.x), j = Math.round(fj - shift.y);
|
|
105
|
+
if (Math.abs(fi - shift.x - i) > LATTICE_TOLERANCE_SITES || Math.abs(fj - shift.y - j) > LATTICE_TOLERANCE_SITES)
|
|
106
|
+
continue;
|
|
107
|
+
if (i < -M || j < -M || i >= W + M || j >= H + M)
|
|
108
|
+
break;
|
|
109
|
+
const lj = j + M;
|
|
110
|
+
return band(Math.floor(lj / BAND_ROWS))[s][w][(lj % BAND_ROWS) * LW + i + M];
|
|
111
|
+
}
|
|
112
|
+
throw new InvalidConfigurationError("Diffraction query is off the site lattice; the object-space convolution does not apply.");
|
|
113
|
+
}
|
|
114
|
+
const scale = input.metresPerFrameUnit, distance = input.distanceM;
|
|
115
|
+
return {
|
|
116
|
+
sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt: base.radianceAt,
|
|
117
|
+
evaluateRadiance(request) {
|
|
118
|
+
const result = base.evaluateRadiance(request), o = request.target.outgoingDirectionUnitVector;
|
|
119
|
+
const t = distance / -o.z;
|
|
120
|
+
return { ...result, spectralRadianceWattsPerSquareMeterSteradianNanometer: blurredAt(-o.x * t / scale, -o.y * t / scale, request.wavelengthNanometers) };
|
|
121
|
+
},
|
|
122
|
+
evaluateApertureRadiance() {
|
|
123
|
+
throw new InvalidConfigurationError("Diffraction by object-space convolution does not take pupil rays.");
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=diffraction-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diffraction-provider.js","sourceRoot":"","sources":["../../src/facade/diffraction-provider.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Fast execution of `simulateCapture`'s `lens.diffraction` (2.1): an object-space convolution in the scene\n * provider, instead of one scene query per PSF tap in the environment PSF quadrature.\n *\n * The environment quadrature computes, at each site centre q and spectral node λ,\n * E(q, λ) = Σ_k K_λ(k)·T·L(s_k(q), λ),\n * where s_k(q) is the tap's source point (`sensorPsfSourcePoint`), L the scene radiance at the plane point\n * that source point sees, and T the radiance-to-irradiance factor. For the facade's planar scene seen\n * through the ideal rectilinear projection with a still camera and no lens field mapping, field throughput or\n * pupil rays:\n * - T is the same at every sensor point (paraxial π/(4·N_w²), no cos⁴ term), and\n * - with the kernel sampled on the site pitch, every source point is the centre of another site, so the plane\n * points form one regular lattice: u = (i + ½ − W/2)·2/w, v = −(j + ½ − H/2)·2/w (frame units, native\n * site (i, j), W×H native sites, w output width).\n * So E(q, λ) = T·B(q, λ) with B = K_λ ⊛ L sampled on that lattice: the same sum, in a different floating-point\n * order. B is computed once per spectral node with the engine's isoplanatic convolution\n * (`calculateIrradianceConvolution`, kernels from `resolveLensPsfConvolutionKernels`, exact site sampling) over a\n * grid that holds every source (the native raster plus the kernel radius on each side), in row bands. The\n * provider then answers each site-centre query with B instead of L, and the environment request declares no\n * PSF. Incoherent imaging is a linear superposition of intensity PSFs, so convolving radiance on the object\n * plane and convolving irradiance on the image plane agree for a planar object and a space-invariant PSF\n * (MIT OpenCourseWare 2.71, lecture 22, free; also see: Goodman, Introduction to Fourier Optics, 4th ed., §6.1).\n *\n * With an anti-aliasing point-splitting filter (2.1, `sensor.antiAliasing`) the environment queries the PSF taps\n * around each pre-filter source point q − a (a = a split offset) instead of around q. Those taps lie on the site\n * lattice shifted by a, so the same argument holds on each shifted lattice: B is computed once per distinct\n * fractional shift (offsets that differ by whole sites share one lattice; the centred four-spot filter with a\n * one-site separation needs a single half-site-shifted lattice) over the native raster widened by the largest\n * offset, and the environment's spatial quadrature still forms the weighted split sum.\n *\n * A query that does not land on one of the lattices (within 1e-6 site) is refused rather than approximated: the\n * facade uses this path only when the lattice argument above holds.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport type { SceneRadianceEvaluationRequest, SceneRadianceEvaluationResult } from \"../schema/scene-radiance.js\";\nimport { calculateIrradianceConvolution } from \"../optics/irradiance-convolution.js\";\nimport { resolveLensPsfConvolutionKernels } from \"../optics/lens-psf-kernels.js\";\nimport type { ResolveLensSampledPsfInput } from \"../optics/lens-psf-profile.js\";\nimport type { PlanarSceneRadianceProvider } from \"./scene-radiance.js\";\n\n/** Rows of native sites per convolution band, and the bands kept. Banding changes memory only, never values. */\nconst BAND_ROWS = 32;\nconst CACHED_BANDS = 4;\nconst LATTICE_TOLERANCE_SITES = 1e-6;\n\nexport interface DiffractedPlanarProviderInput {\n base: PlanarSceneRadianceProvider;\n /** The optical state of the sampled PSF, as for `resolveLensSampledPsf`, without field point and wavelength. */\n psf: Omit<ResolveLensSampledPsfInput, \"fieldPointMm\" | \"wavelengthNm\">;\n /** Spectral nodes the environment queries (the profile's wavelength nodes). */\n wavelengthsNm: readonly number[];\n kernelRadius: number;\n nativeWidth: number;\n nativeHeight: number;\n /** Output (active) width in sites: frame units are 2/outputWidth per site. */\n outputWidth: number;\n sitePitchMicrometers: number;\n /** Planar geometry of the base provider, to map query directions to plane points exactly as it does. */\n distanceM: number;\n metresPerFrameUnit: number;\n /**\n * Offsets of the query points from the site centres, in sites (+x right, +y down): the anti-aliasing split\n * offsets with their sign reversed (the environment queries q − a). Default: one zero offset.\n */\n queryOffsetsSites?: readonly { x: number; y: number }[];\n /** Called with the duration in ms of each band convolution (diagnostics only; never changes values). */\n onConvolution?: (milliseconds: number) => void;\n}\n\n/** Internal (not exported from the package): the base provider with diffraction applied as an object-space convolution. */\nexport function createDiffractedPlanarProvider(input: DiffractedPlanarProviderInput): PlanarSceneRadianceProvider {\n const { base, wavelengthsNm, nativeWidth: W, nativeHeight: H, kernelRadius: R } = input;\n const g = 2 / input.outputWidth, pitch = input.sitePitchMicrometers;\n const index = new Map(wavelengthsNm.map((nm, i) => [nm, i]));\n // One lattice per distinct fractional query offset; M extra sites per side hold the offset queries.\n const offsets = input.queryOffsetsSites ?? [{ x: 0, y: 0 }];\n const fraction = (value: number): number => { const f = value - Math.floor(value); return f > 1 - LATTICE_TOLERANCE_SITES ? 0 : f < LATTICE_TOLERANCE_SITES ? 0 : f; };\n const shifts: { x: number; y: number }[] = [];\n for (const o of offsets) {\n const f = { x: fraction(o.x), y: fraction(o.y) };\n if (!shifts.some((s) => Math.abs(s.x - f.x) <= LATTICE_TOLERANCE_SITES && Math.abs(s.y - f.y) <= LATTICE_TOLERANCE_SITES)) shifts.push(f);\n }\n const M = Math.max(0, ...offsets.map((o) => Math.ceil(Math.max(Math.abs(o.x), Math.abs(o.y)) - LATTICE_TOLERANCE_SITES)));\n const LW = W + 2 * M, LH = H + 2 * M;\n // One kernel per wavelength, on the site grid, from the engine's own tap-to-site mapping (field point 0, 0).\n const kernels = wavelengthsNm.map((wavelengthNm) => {\n const k = resolveLensPsfConvolutionKernels({ schemaVersion: \"0.1.0\", psf: input.psf, spectralWeights: [{ wavelengthNm, weight: 1 }],\n grid: { width: 1, height: 1, sitePitchMicrometers: { x: pitch, y: pitch }, opticalAxisSite: { x: 0.5, y: 0.5 } }, patchWidth: 1, patchHeight: 1,\n kernelRadius: R }).value;\n if (!k.exactSiteSampling || k.kernelRadius !== R) throw new InvalidConfigurationError(\"Diffraction kernels must sample the site pitch exactly.\");\n return k.kernels;\n });\n const gridWidth = LW + 2 * R;\n const now = (): number => globalThis.performance?.now() ?? Date.now();\n const bands = new Map<number, Float64Array[][]>();\n function band(b: number): Float64Array[][] {\n const cached = bands.get(b);\n if (cached) { bands.delete(b); bands.set(b, cached); return cached; }\n const started = input.onConvolution ? now() : 0;\n const y0 = b * BAND_ROWS, rows = Math.min(BAND_ROWS, LH - y0), gridHeight = rows + 2 * R;\n const values = shifts.map((shift) => wavelengthsNm.map((nm, w) => {\n const samples = new Float64Array(gridWidth * gridHeight);\n for (let gy = 0; gy < gridHeight; gy++) {\n const v = shift.y === 0 ? -(y0 + gy - R - M + 0.5 - H / 2) * g : -(y0 + gy - R - M + 0.5 + shift.y - H / 2) * g;\n for (let gx = 0; gx < gridWidth; gx++) samples[gy * gridWidth + gx] = base.radianceAt(shift.x === 0 ? (gx - R - M + 0.5 - W / 2) * g : (gx - R - M + 0.5 + shift.x - W / 2) * g, v, nm);\n }\n const blurred = calculateIrradianceConvolution({ schemaVersion: \"0.1.0\", width: gridWidth, height: gridHeight, samples, kernelRadius: R,\n patchWidth: gridWidth, patchHeight: gridHeight, kernels: kernels[w]!, boundary: \"zero-outside-grid\" }).values;\n const out = new Float64Array(LW * rows);\n for (let y = 0; y < rows; y++) out.set(blurred.subarray((y + R) * gridWidth + R, (y + R) * gridWidth + R + LW), y * LW);\n return out;\n }));\n bands.set(b, values);\n if (bands.size > CACHED_BANDS) bands.delete(bands.keys().next().value!);\n if (input.onConvolution) input.onConvolution(now() - started);\n return values;\n }\n function blurredAt(u: number, v: number, nm: number): number {\n const w = index.get(nm), fi = u / g - 0.5 + W / 2, fj = -v / g - 0.5 + H / 2;\n if (w !== undefined) for (let s = 0; s < shifts.length; s++) {\n const shift = shifts[s]!, i = Math.round(fi - shift.x), j = Math.round(fj - shift.y);\n if (Math.abs(fi - shift.x - i) > LATTICE_TOLERANCE_SITES || Math.abs(fj - shift.y - j) > LATTICE_TOLERANCE_SITES) continue;\n if (i < -M || j < -M || i >= W + M || j >= H + M) break;\n const lj = j + M;\n return band(Math.floor(lj / BAND_ROWS))[s]![w]![(lj % BAND_ROWS) * LW + i + M]!;\n }\n throw new InvalidConfigurationError(\"Diffraction query is off the site lattice; the object-space convolution does not apply.\");\n }\n const scale = input.metresPerFrameUnit, distance = input.distanceM;\n return {\n sceneId: base.sceneId, providerProfileId: base.providerProfileId, radianceAt: base.radianceAt,\n evaluateRadiance(request: Readonly<SceneRadianceEvaluationRequest>): SceneRadianceEvaluationResult {\n const result = base.evaluateRadiance(request), o = (request.target as { outgoingDirectionUnitVector: { x: number; y: number; z: number } }).outgoingDirectionUnitVector;\n const t = distance / -o.z;\n return { ...result, spectralRadianceWattsPerSquareMeterSteradianNanometer: blurredAt(-o.x * t / scale, -o.y * t / scale, request.wavelengthNanometers) };\n },\n evaluateApertureRadiance(): SceneRadianceEvaluationResult {\n throw new InvalidConfigurationError(\"Diffraction by object-space convolution does not take pupil rays.\");\n }\n };\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Mechanism chosen by {@link createHostYield}, for diagnostics and tests. */
|
|
2
|
-
export type HostYieldMechanism = "
|
|
2
|
+
export type HostYieldMechanism = "message-channel" | "set-immediate" | "set-timeout";
|
|
3
3
|
/** Returns a function that yields to the host event loop once per call, and the mechanism it uses. */
|
|
4
4
|
export declare function createHostYield(host?: object): {
|
|
5
5
|
mechanism: HostYieldMechanism;
|