@photivra/engine 2.17.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/dist/camera/sensor-equivalence.d.ts +156 -0
- package/dist/camera/sensor-equivalence.js +208 -0
- package/dist/camera/sensor-equivalence.js.map +1 -0
- package/dist/capture/native-environment-tabulated.d.ts +5 -0
- package/dist/capture/native-environment-tabulated.js +1 -1
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/ideal-lens.d.ts +25 -0
- package/dist/equipment/ideal-lens.js +21 -0
- package/dist/equipment/ideal-lens.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +94 -0
- package/dist/facade/catalogue-lens-signal.js +362 -0
- package/dist/facade/catalogue-lens-signal.js.map +1 -0
- package/dist/facade/defocus-image-space-provider.js +5 -2
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +32 -1
- package/dist/facade/display-view-lens-signal.js +85 -10
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +8 -0
- package/dist/facade/expected-signal-setup.js +7 -0
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +5 -0
- package/dist/facade/expected-signal.js +5 -1
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +1 -0
- package/dist/facade/generic-lens-capture.js +1 -0
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +31 -4
- package/dist/facade/simulate-capture.js +117 -38
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +1 -1
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/docs/AGENT_GUIDE.md +3 -0
- package/docs/API_REFERENCE.md +29 -1
- package/docs/QUICK_START.md +49 -1
- package/docs/USAGE.md +43 -2
- package/docs/api/camera-sensor-equivalence.md +209 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-ideal-lens.md +26 -0
- package/docs/api/exports.json +67 -2
- package/docs/api/facade-simulate-capture.md +27 -4
- package/llms.txt +2 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-environment-tabulated.js","sourceRoot":"","sources":["../../src/capture/native-environment-tabulated.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (#394 step 2, not exported from the package): the native RAW of an engine-built planar scene (a chart preset or a\n * caller material scene, still camera, rectilinear, no distortion, vignetting, pupil rays or caller PSF) from tabulated\n * expected electrons instead of the per-site environment chain.\n *\n * Each site's expected photo electrons come from the display view's tabulated expected signal (`createDisplayViewExpectedSignal`):\n * the same sum over pixel-aperture nodes, anti-aliasing spots, diffraction taps and spectral nodes as the environment chain,\n * formed in another order, so the value differs from the chain's only in the last bits. The sensor stage is the native one:\n * dark charge, both Poisson draws, read noise and the ADC with the site's own seeds (`createVerifiedNativeSiteReadout`). A\n * site whose expected electrons differ in the last bit can therefore, rarely, draw a different integer; the statistics are\n * unchanged.\n *\n * Verification at run time: one site of each CFA phase runs the full chain (planning, the evaluator's callbacks, composition,\n * parsing and `calculateNativeRawSite`). Its readout template must reproduce that site bit for bit, and its tabulated\n * electrons must agree with the chain's within `TABULATED_RELATIVE_TOLERANCE`; otherwise the caller runs the environment task.\n * #453: the chain's photo signal comes from `executeEngineOwnedEnvironmentPhotoSignal`, the same operations and values as\n * `executeEnvironmentSensorPhotoSignal` for these engine-built records, without re-validating records that cannot have changed.\n */\n\nimport type { EnvironmentRadianceEvaluator } from \"../sensor/environment-photo-signal.js\";\nimport { executeEngineOwnedEnvironmentPhotoSignal, executeEnvironmentSensorPhotoSignal } from \"../sensor/environment-photo-signal.js\";\nimport { isInternalSceneRadianceEvaluator } from \"../core/internal-radiance-evaluator.js\";\nimport { calculateCaptureExposureWindows, prepareCaptureExposureWindowSampler } from \"../sensor/exposure-window.js\";\nimport { createEnvironmentRawFrameCanonicalMemo, planEnvironmentRawSite, type SimulateEnvironmentSensorRawFrameInput } from \"./environment-raw-producer.js\";\nimport { admitNativeEnvironmentInput, type NativeEnvironmentRawInput } from \"./native-environment-raw.js\";\nimport { composeNativeEnvironmentSensorSite } from \"./native-environment-optical-stage.js\";\nimport { calculateNativeRawSite, parseNativeRawProducerSite } from \"./sensor-raw-producer.js\";\nimport { calculateSiteEffectiveSaturationCode, createPhaseWhiteLevelAccumulator } from \"./raw-white-level.js\";\nimport { createVerifiedNativeSiteReadout, type NativeSiteReadout } from \"./native-environment-lean.js\";\nimport type { NativeRawOutput } from \"./native-raw.js\";\n\n/** Largest relative difference between a verification site's tabulated and chain electrons: rounding only (a few ulps of each term). */\nexport const TABULATED_RELATIVE_TOLERANCE = 1e-9;\n/** Rows per expected-signal cache window. */\nconst BAND_ROWS = 32;\n/** Host-yield budget in milliseconds (the native task's cadence); the task also yields after its last row. */\nconst YIELD_MS = 8;\n\n/** Internal counters (tests assert how many verification sites ran the chain per call; four, one per CFA phase). */\nexport const tabulatedVerificationStatistics = { chainExecutions: 0 };\n\n/**\n * Internal test hook (never set by the engine): `fullPhoto` also runs each verification site through\n * `executeEnvironmentSensorPhotoSignal` (with the uncounted evaluator) and hands both photo signals to the test;\n * `tabulatedElectrons` replaces the tabulated electrons the gate compares, to show that a wrong fast path is rejected.\n */\nexport interface TabulatedVerificationTestHook {\n fullPhoto?(engineOwned: unknown, full: unknown): void;\n tabulatedElectrons?(value: number, x: number, y: number): number;\n}\nlet testHook: TabulatedVerificationTestHook | undefined;\nexport function setTabulatedVerificationTestHook(hook: TabulatedVerificationTestHook | undefined): void { testHook = hook; }\n\nlet tabulatedDisabled = false;\n/** Disables the tabulated path process-wide (internal; tests of the environment path itself turn it off). */\nexport function setTabulatedNativeRawEnabled(enabled: boolean): void { tabulatedDisabled = !enabled; }\n\nexport interface TabulatedNativeRawInput {\n input: NativeEnvironmentRawInput;\n /** Native rectangle to render (region rendering); absent: the whole raster. */\n region?: { x: number; y: number; width: number; height: number } | undefined;\n /** The engine-built site record of a native index (the facade's `siteInput`). */\n siteInput(index: number): SimulateEnvironmentSensorRawFrameInput[\"sites\"][number];\n /** The evaluator the environment task would use (for the verification sites). */\n evaluateRadiance: EnvironmentRadianceEvaluator;\n /** Expected photo electrons of native sites (`createDisplayViewExpectedSignal`). */\n electrons: { window(x0: number, y0: number, x1: number, y1: number): void; site(x: number, y: number): number; readonly lookups: number };\n /** Called once per native tile-equivalent (progress), as the environment task reads its tiles. */\n onTile?: () => void;\n tileWidth: number;\n yieldControl(): Promise<void>;\n checkAbort(): void;\n}\n\nexport interface TabulatedNativeRawResult {\n raw: NativeRawOutput;\n /** Evaluator callbacks of the verification sites plus the tabulated signal's scene lookups. */\n providerEvaluationCount: number;\n /** Largest relative difference between tabulated and chain electrons over the verification sites. */\n verificationRelativeDifference: number;\n}\n\n/**\n * #449: the local exposure duration shared by every site of `region`, or undefined when the sampler must run per site. With\n * simultaneous opening and closing boundaries both boundary phases are 0 at every point (exposure-window.ts, boundaryPhase), so\n * each site's duration is the nominal one the sampler gives at the region's first site. The sampler's only other per-point step\n * is its check that the point lies in the active capture rectangle; the region's first and last site centres bound every other\n * centre, so when both pass every site passes. When either throws, the per-site path runs (and throws where it did).\n */\nexport function simultaneousExposureDuration(schedule: { opening: unknown; closing: unknown }, sampler: (point: { x: number; y: number }) => { localExposureDurationSeconds: number },\n region: { x: number; y: number; width: number; height: number }): number | undefined {\n if ((schedule.opening as { kind?: unknown }).kind !== \"simultaneous\" || (schedule.closing as { kind?: unknown }).kind !== \"simultaneous\") return undefined;\n try {\n const first = sampler({ x: region.x + 0.5, y: region.y + 0.5 }).localExposureDurationSeconds;\n const last = sampler({ x: region.x + region.width - 0.5, y: region.y + region.height - 0.5 }).localExposureDurationSeconds;\n return Object.is(first, last) ? first : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Renders the native RAW from tabulated expected electrons, or returns undefined when verification fails (see the module comment). */\nexport async function renderTabulatedNativeRaw(t: TabulatedNativeRawInput): Promise<TabulatedNativeRawResult | undefined> {\n if (tabulatedDisabled) return undefined;\n const { plan, sceneBinding } = admitNativeEnvironmentInput(t.input);\n const native = plan.exposure.geometry.nativeRaster, W = native.pixelWidth, layout = plan.frame.colorSamplingProfile.layout;\n if (layout.kind !== \"periodic-mosaic\" || layout.repeatWidthSites !== 2 || layout.repeatHeightSites !== 2 || !plan.exposureWindow) return undefined;\n const region = t.region ?? { x: 0, y: 0, width: W, height: native.pixelHeight };\n if (region.width < 2 || region.height < 2) return undefined;\n const seed = plan.exposure.noise.seedUint32, schedule = plan.exposureWindow, memo = createEnvironmentRawFrameCanonicalMemo(plan.frame, schedule);\n let evaluations = 0;\n const evaluate: EnvironmentRadianceEvaluator = (request) => { evaluations++; return t.evaluateRadiance(request); };\n // The counting wrapper returns the evaluator's own results, so they are engine-built exactly when the evaluator is.\n const engineBuiltResults = isInternalSceneRadianceEvaluator(t.evaluateRadiance);\n // Verification: the region's top-left 2 × 2 block, one site per CFA phase, through the full chain.\n const readouts: NativeSiteReadout[] = [];\n let worst = 0;\n for (let k = 0; k < 4; k++) {\n const x = region.x + (k & 1), y = region.y + (k >> 1), index = y * W + x, site = t.siteInput(index);\n const full = planEnvironmentRawSite(site, index, plan.frame, sceneBinding, schedule, \"engine-owned\", memo, \"photo-only\");\n // #453: the chain for these engine-built site records (the same operations and values as executeEnvironmentSensorPhotoSignal,\n // without re-validating records that have not changed; see executeEngineOwnedEnvironmentPhotoSignal).\n const photoResult = executeEngineOwnedEnvironmentPhotoSignal(full, evaluate, engineBuiltResults), photo = photoResult.value.photoSignal;\n tabulatedVerificationStatistics.chainExecutions++;\n if (testHook?.fullPhoto) testHook.fullPhoto(photoResult, executeEnvironmentSensorPhotoSignal(full, t.evaluateRadiance, undefined, \"photo-only\").value.photo);\n const timing = calculateCaptureExposureWindows({ ...schedule, nativeRaster: native, samplePointsNative: [{ x: x + 0.5, y: y + 0.5 }] }).value.samples[0];\n const parsed = parseNativeRawProducerSite(composeNativeEnvironmentSensorSite(site, photo), index, plan.frame, timing);\n const result = calculateNativeRawSite(parsed, seed, index);\n const whiteCode = calculateSiteEffectiveSaturationCode(result.readout.value, parsed.readoutProfile, parsed.regimeId);\n const readout = createVerifiedNativeSiteReadout(parsed, result, whiteCode, photo, x, y, index, seed);\n if (!readout) return undefined;\n t.electrons.window(x, y, x + 1, y + 1);\n const chain = photo.expectedGeneratedElectronCount, tabulated = testHook?.tabulatedElectrons ? testHook.tabulatedElectrons(t.electrons.site(x, y), x, y) : t.electrons.site(x, y);\n const difference = chain === tabulated ? 0 : Math.abs(tabulated - chain) / Math.max(Math.abs(chain), Number.MIN_VALUE);\n if (!(difference <= TABULATED_RELATIVE_TOLERANCE)) return undefined;\n worst = Math.max(worst, difference);\n readouts[(y & 1) * 2 + (x & 1)] = readout;\n }\n const length = region.width * region.height;\n const raw: NativeRawOutput = { plan, codes: new Uint16Array(length), blackLevels: new Uint16Array(length), digitalSaturationCodes: new Uint16Array(length),\n saturationFlags: new Uint8Array(length), effectiveSaturationCodes: new Uint16Array(4) };\n const white = createPhaseWhiteLevelAccumulator(W, 2, 2), sampler = prepareCaptureExposureWindowSampler({ ...schedule, nativeRaster: native });\n const tilesPerRow = Math.ceil(region.width / t.tileWidth), point = { x: 0, y: 0 };\n const constantDuration = simultaneousExposureDuration(schedule, sampler, region);\n let last = Date.now();\n for (let y0 = region.y; y0 < region.y + region.height; y0 += BAND_ROWS) {\n const y1 = Math.min(region.y + region.height, y0 + BAND_ROWS);\n t.electrons.window(region.x, y0, region.x + region.width, y1);\n for (let y = y0; y < y1; y++) {\n for (let x = region.x; x < region.x + region.width; x++) {\n const index = y * W + x, at = (y - region.y) * region.width + x - region.x;\n let duration = constantDuration;\n if (duration === undefined) { point.x = x + 0.5; point.y = y + 0.5; duration = sampler(point).localExposureDurationSeconds; }\n const o = readouts[(y & 1) * 2 + (x & 1)]!.compute(t.electrons.site(x, y), duration, x, y, index, seed);\n // A check of the full path failed for this site: let the environment task decide (it throws its own error).\n if (!o) return undefined;\n raw.codes[at] = o.rawCode; raw.blackLevels[at] = o.blackLevelCode; raw.digitalSaturationCodes[at] = o.digitalSaturationCode; raw.saturationFlags[at] = o.flags;\n white.record(index, o.whiteCode);\n }\n for (let k = 0; k < tilesPerRow; k++) t.onTile?.();\n if (y === region.y + region.height - 1 || Date.now() - last >= YIELD_MS) { await t.yieldControl(); t.checkAbort(); last = Date.now(); }\n }\n }\n raw.effectiveSaturationCodes.set(t.region ? white.partial() : white.finish());\n return { raw, providerEvaluationCount: evaluations + t.electrons.lookups, verificationRelativeDifference: worst };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"native-environment-tabulated.js","sourceRoot":"","sources":["../../src/capture/native-environment-tabulated.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (#394 step 2, not exported from the package): the native RAW of an engine-built planar scene (a chart preset or a\n * caller material scene, still camera, rectilinear, no distortion, vignetting, pupil rays or caller PSF) from tabulated\n * expected electrons instead of the per-site environment chain.\n *\n * Each site's expected photo electrons come from the display view's tabulated expected signal (`createDisplayViewExpectedSignal`):\n * the same sum over pixel-aperture nodes, anti-aliasing spots, diffraction taps and spectral nodes as the environment chain,\n * formed in another order, so the value differs from the chain's only in the last bits. The sensor stage is the native one:\n * dark charge, both Poisson draws, read noise and the ADC with the site's own seeds (`createVerifiedNativeSiteReadout`). A\n * site whose expected electrons differ in the last bit can therefore, rarely, draw a different integer; the statistics are\n * unchanged.\n *\n * Verification at run time: one site of each CFA phase runs the full chain (planning, the evaluator's callbacks, composition,\n * parsing and `calculateNativeRawSite`). Its readout template must reproduce that site bit for bit, and its tabulated\n * electrons must agree with the chain's within `TABULATED_RELATIVE_TOLERANCE`; otherwise the caller runs the environment task.\n * #453: the chain's photo signal comes from `executeEngineOwnedEnvironmentPhotoSignal`, the same operations and values as\n * `executeEnvironmentSensorPhotoSignal` for these engine-built records, without re-validating records that cannot have changed.\n */\n\nimport type { EnvironmentRadianceEvaluator } from \"../sensor/environment-photo-signal.js\";\nimport { executeEngineOwnedEnvironmentPhotoSignal, executeEnvironmentSensorPhotoSignal } from \"../sensor/environment-photo-signal.js\";\nimport { isInternalSceneRadianceEvaluator } from \"../core/internal-radiance-evaluator.js\";\nimport { calculateCaptureExposureWindows, prepareCaptureExposureWindowSampler } from \"../sensor/exposure-window.js\";\nimport { createEnvironmentRawFrameCanonicalMemo, planEnvironmentRawSite, type SimulateEnvironmentSensorRawFrameInput } from \"./environment-raw-producer.js\";\nimport { admitNativeEnvironmentInput, type NativeEnvironmentRawInput } from \"./native-environment-raw.js\";\nimport { composeNativeEnvironmentSensorSite } from \"./native-environment-optical-stage.js\";\nimport { calculateNativeRawSite, parseNativeRawProducerSite } from \"./sensor-raw-producer.js\";\nimport { calculateSiteEffectiveSaturationCode, createPhaseWhiteLevelAccumulator } from \"./raw-white-level.js\";\nimport { createVerifiedNativeSiteReadout, type NativeSiteReadout } from \"./native-environment-lean.js\";\nimport type { NativeRawOutput } from \"./native-raw.js\";\n\n/** Largest relative difference between a verification site's tabulated and chain electrons: rounding only (a few ulps of each term). */\nexport const TABULATED_RELATIVE_TOLERANCE = 1e-9;\n/** Rows per expected-signal cache window. */\nconst BAND_ROWS = 32;\n/** Host-yield budget in milliseconds (the native task's cadence); the task also yields after its last row. */\nconst YIELD_MS = 8;\n\n/** Internal counters (tests assert how many verification sites ran the chain per call; four, one per CFA phase). */\nexport const tabulatedVerificationStatistics = { chainExecutions: 0 };\n\n/**\n * Internal test hook (never set by the engine): `fullPhoto` also runs each verification site through\n * `executeEnvironmentSensorPhotoSignal` (with the uncounted evaluator) and hands both photo signals to the test;\n * `tabulatedElectrons` replaces the tabulated electrons the gate compares, to show that a wrong fast path is rejected.\n */\nexport interface TabulatedVerificationTestHook {\n fullPhoto?(engineOwned: unknown, full: unknown): void;\n tabulatedElectrons?(value: number, x: number, y: number): number;\n}\nlet testHook: TabulatedVerificationTestHook | undefined;\nexport function setTabulatedVerificationTestHook(hook: TabulatedVerificationTestHook | undefined): void { testHook = hook; }\n\nlet tabulatedDisabled = false;\n/** Disables the tabulated path process-wide (internal; tests of the environment path itself turn it off). */\nexport function setTabulatedNativeRawEnabled(enabled: boolean): void { tabulatedDisabled = !enabled; }\n\nexport interface TabulatedNativeRawInput {\n input: NativeEnvironmentRawInput;\n /** Native rectangle to render (region rendering); absent: the whole raster. */\n region?: { x: number; y: number; width: number; height: number } | undefined;\n /** The engine-built site record of a native index (the facade's `siteInput`). */\n siteInput(index: number): SimulateEnvironmentSensorRawFrameInput[\"sites\"][number];\n /** The evaluator the environment task would use (for the verification sites). */\n evaluateRadiance: EnvironmentRadianceEvaluator;\n /** Expected photo electrons of native sites (`createDisplayViewExpectedSignal`). */\n electrons: { window(x0: number, y0: number, x1: number, y1: number): void; site(x: number, y: number): number; readonly lookups: number };\n /** Called once per native tile-equivalent (progress), as the environment task reads its tiles. */\n onTile?: () => void;\n tileWidth: number;\n /**\n * #568: the largest relative difference the verification accepts (default `TABULATED_RELATIVE_TOLERANCE`): a fast method that\n * approximates the chain (`lens.catalogue`'s field-grid PSF) passes its own measured bound.\n */\n relativeTolerance?: number;\n yieldControl(): Promise<void>;\n checkAbort(): void;\n}\n\nexport interface TabulatedNativeRawResult {\n raw: NativeRawOutput;\n /** Evaluator callbacks of the verification sites plus the tabulated signal's scene lookups. */\n providerEvaluationCount: number;\n /** Largest relative difference between tabulated and chain electrons over the verification sites. */\n verificationRelativeDifference: number;\n}\n\n/**\n * #449: the local exposure duration shared by every site of `region`, or undefined when the sampler must run per site. With\n * simultaneous opening and closing boundaries both boundary phases are 0 at every point (exposure-window.ts, boundaryPhase), so\n * each site's duration is the nominal one the sampler gives at the region's first site. The sampler's only other per-point step\n * is its check that the point lies in the active capture rectangle; the region's first and last site centres bound every other\n * centre, so when both pass every site passes. When either throws, the per-site path runs (and throws where it did).\n */\nexport function simultaneousExposureDuration(schedule: { opening: unknown; closing: unknown }, sampler: (point: { x: number; y: number }) => { localExposureDurationSeconds: number },\n region: { x: number; y: number; width: number; height: number }): number | undefined {\n if ((schedule.opening as { kind?: unknown }).kind !== \"simultaneous\" || (schedule.closing as { kind?: unknown }).kind !== \"simultaneous\") return undefined;\n try {\n const first = sampler({ x: region.x + 0.5, y: region.y + 0.5 }).localExposureDurationSeconds;\n const last = sampler({ x: region.x + region.width - 0.5, y: region.y + region.height - 0.5 }).localExposureDurationSeconds;\n return Object.is(first, last) ? first : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Renders the native RAW from tabulated expected electrons, or returns undefined when verification fails (see the module comment). */\nexport async function renderTabulatedNativeRaw(t: TabulatedNativeRawInput): Promise<TabulatedNativeRawResult | undefined> {\n if (tabulatedDisabled) return undefined;\n const { plan, sceneBinding } = admitNativeEnvironmentInput(t.input);\n const native = plan.exposure.geometry.nativeRaster, W = native.pixelWidth, layout = plan.frame.colorSamplingProfile.layout;\n if (layout.kind !== \"periodic-mosaic\" || layout.repeatWidthSites !== 2 || layout.repeatHeightSites !== 2 || !plan.exposureWindow) return undefined;\n const region = t.region ?? { x: 0, y: 0, width: W, height: native.pixelHeight };\n if (region.width < 2 || region.height < 2) return undefined;\n const seed = plan.exposure.noise.seedUint32, schedule = plan.exposureWindow, memo = createEnvironmentRawFrameCanonicalMemo(plan.frame, schedule);\n let evaluations = 0;\n const evaluate: EnvironmentRadianceEvaluator = (request) => { evaluations++; return t.evaluateRadiance(request); };\n // The counting wrapper returns the evaluator's own results, so they are engine-built exactly when the evaluator is.\n const engineBuiltResults = isInternalSceneRadianceEvaluator(t.evaluateRadiance);\n // Verification: the region's top-left 2 × 2 block, one site per CFA phase, through the full chain.\n const readouts: NativeSiteReadout[] = [];\n let worst = 0;\n for (let k = 0; k < 4; k++) {\n const x = region.x + (k & 1), y = region.y + (k >> 1), index = y * W + x, site = t.siteInput(index);\n const full = planEnvironmentRawSite(site, index, plan.frame, sceneBinding, schedule, \"engine-owned\", memo, \"photo-only\");\n // #453: the chain for these engine-built site records (the same operations and values as executeEnvironmentSensorPhotoSignal,\n // without re-validating records that have not changed; see executeEngineOwnedEnvironmentPhotoSignal).\n const photoResult = executeEngineOwnedEnvironmentPhotoSignal(full, evaluate, engineBuiltResults), photo = photoResult.value.photoSignal;\n tabulatedVerificationStatistics.chainExecutions++;\n if (testHook?.fullPhoto) testHook.fullPhoto(photoResult, executeEnvironmentSensorPhotoSignal(full, t.evaluateRadiance, undefined, \"photo-only\").value.photo);\n const timing = calculateCaptureExposureWindows({ ...schedule, nativeRaster: native, samplePointsNative: [{ x: x + 0.5, y: y + 0.5 }] }).value.samples[0];\n const parsed = parseNativeRawProducerSite(composeNativeEnvironmentSensorSite(site, photo), index, plan.frame, timing);\n const result = calculateNativeRawSite(parsed, seed, index);\n const whiteCode = calculateSiteEffectiveSaturationCode(result.readout.value, parsed.readoutProfile, parsed.regimeId);\n const readout = createVerifiedNativeSiteReadout(parsed, result, whiteCode, photo, x, y, index, seed);\n if (!readout) return undefined;\n t.electrons.window(x, y, x + 1, y + 1);\n const chain = photo.expectedGeneratedElectronCount, tabulated = testHook?.tabulatedElectrons ? testHook.tabulatedElectrons(t.electrons.site(x, y), x, y) : t.electrons.site(x, y);\n const difference = chain === tabulated ? 0 : Math.abs(tabulated - chain) / Math.max(Math.abs(chain), Number.MIN_VALUE);\n if (!(difference <= (t.relativeTolerance ?? TABULATED_RELATIVE_TOLERANCE))) return undefined;\n worst = Math.max(worst, difference);\n readouts[(y & 1) * 2 + (x & 1)] = readout;\n }\n const length = region.width * region.height;\n const raw: NativeRawOutput = { plan, codes: new Uint16Array(length), blackLevels: new Uint16Array(length), digitalSaturationCodes: new Uint16Array(length),\n saturationFlags: new Uint8Array(length), effectiveSaturationCodes: new Uint16Array(4) };\n const white = createPhaseWhiteLevelAccumulator(W, 2, 2), sampler = prepareCaptureExposureWindowSampler({ ...schedule, nativeRaster: native });\n const tilesPerRow = Math.ceil(region.width / t.tileWidth), point = { x: 0, y: 0 };\n const constantDuration = simultaneousExposureDuration(schedule, sampler, region);\n let last = Date.now();\n for (let y0 = region.y; y0 < region.y + region.height; y0 += BAND_ROWS) {\n const y1 = Math.min(region.y + region.height, y0 + BAND_ROWS);\n t.electrons.window(region.x, y0, region.x + region.width, y1);\n for (let y = y0; y < y1; y++) {\n for (let x = region.x; x < region.x + region.width; x++) {\n const index = y * W + x, at = (y - region.y) * region.width + x - region.x;\n let duration = constantDuration;\n if (duration === undefined) { point.x = x + 0.5; point.y = y + 0.5; duration = sampler(point).localExposureDurationSeconds; }\n const o = readouts[(y & 1) * 2 + (x & 1)]!.compute(t.electrons.site(x, y), duration, x, y, index, seed);\n // A check of the full path failed for this site: let the environment task decide (it throws its own error).\n if (!o) return undefined;\n raw.codes[at] = o.rawCode; raw.blackLevels[at] = o.blackLevelCode; raw.digitalSaturationCodes[at] = o.digitalSaturationCode; raw.saturationFlags[at] = o.flags;\n white.record(index, o.whiteCode);\n }\n for (let k = 0; k < tilesPerRow; k++) t.onTile?.();\n if (y === region.y + region.height - 1 || Date.now() - last >= YIELD_MS) { await t.yieldControl(); t.checkAbort(); last = Date.now(); }\n }\n }\n raw.effectiveSaturationCodes.set(t.region ? white.partial() : white.finish());\n return { raw, providerEvaluationCount: evaluations + t.electrons.lookups, verificationRelativeDifference: worst };\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,QAAiB,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,QAAiB,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.18.0\" as const;\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ideal lens (#576): the bounds of `simulateCapture`'s and the display view's `lens.ideal`, a plain aberration-free thin lens
|
|
3
|
+
* named by its physical focal length, on any sensor. Its blur is that of the options the request gives, the same models as with
|
|
4
|
+
* `lens.prime`: `lens.defocus` (the thin-lens blur circle, `calculateDefocusCircle`), `lens.diffraction` (the Airy pattern of a
|
|
5
|
+
* circular pupil) and `lens.vignetting` (a caller profile). Without them it renders sharp and evenly lit.
|
|
6
|
+
*
|
|
7
|
+
* - `fNumber.minimum` 0.5 is a physical limit: a lens in air that satisfies the Abbe sine condition (aplanatic) has
|
|
8
|
+
* N = 1/(2·n·sin θ′) with n = 1 and sin θ′ ≤ 1, so N ≥ 0.5 (the sine condition: Born & Wolf, *Principles of Optics*, 7th ed.;
|
|
9
|
+
* Kingslake & Johnson, *Lens Design Fundamentals*, 2nd ed.). The engine's thin-lens defocus and scalar Airy diffraction
|
|
10
|
+
* are paraxial models, so near f/0.5 they are approximations further from a real lens than at f/2.
|
|
11
|
+
* - `fNumber.maximum` 128 and both focal-length bounds are engine bounds, not physical limits: 1 mm is shorter than phone
|
|
12
|
+
* camera lenses, 4000 mm longer than photographic lenses, and f/128 beyond large-format lenses' smallest stops.
|
|
13
|
+
* - The ideal lens's f-number range is the whole of `fNumber`: manual and aperture priority take any `lens.fNumber` in it, and
|
|
14
|
+
* shutter priority and program may choose any aperture in it (in dim light, wider than real lenses open).
|
|
15
|
+
*/
|
|
16
|
+
export declare const IDEAL_LENS_LIMITS: Readonly<{
|
|
17
|
+
focalLengthMm: Readonly<{
|
|
18
|
+
minimum: 1;
|
|
19
|
+
maximum: 4000;
|
|
20
|
+
}>;
|
|
21
|
+
fNumber: Readonly<{
|
|
22
|
+
minimum: 0.5;
|
|
23
|
+
maximum: 128;
|
|
24
|
+
}>;
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Ideal lens (#576): the bounds of `simulateCapture`'s and the display view's `lens.ideal`, a plain aberration-free thin lens
|
|
4
|
+
* named by its physical focal length, on any sensor. Its blur is that of the options the request gives, the same models as with
|
|
5
|
+
* `lens.prime`: `lens.defocus` (the thin-lens blur circle, `calculateDefocusCircle`), `lens.diffraction` (the Airy pattern of a
|
|
6
|
+
* circular pupil) and `lens.vignetting` (a caller profile). Without them it renders sharp and evenly lit.
|
|
7
|
+
*
|
|
8
|
+
* - `fNumber.minimum` 0.5 is a physical limit: a lens in air that satisfies the Abbe sine condition (aplanatic) has
|
|
9
|
+
* N = 1/(2·n·sin θ′) with n = 1 and sin θ′ ≤ 1, so N ≥ 0.5 (the sine condition: Born & Wolf, *Principles of Optics*, 7th ed.;
|
|
10
|
+
* Kingslake & Johnson, *Lens Design Fundamentals*, 2nd ed.). The engine's thin-lens defocus and scalar Airy diffraction
|
|
11
|
+
* are paraxial models, so near f/0.5 they are approximations further from a real lens than at f/2.
|
|
12
|
+
* - `fNumber.maximum` 128 and both focal-length bounds are engine bounds, not physical limits: 1 mm is shorter than phone
|
|
13
|
+
* camera lenses, 4000 mm longer than photographic lenses, and f/128 beyond large-format lenses' smallest stops.
|
|
14
|
+
* - The ideal lens's f-number range is the whole of `fNumber`: manual and aperture priority take any `lens.fNumber` in it, and
|
|
15
|
+
* shutter priority and program may choose any aperture in it (in dim light, wider than real lenses open).
|
|
16
|
+
*/
|
|
17
|
+
export const IDEAL_LENS_LIMITS = /* @__PURE__ */ Object.freeze({
|
|
18
|
+
focalLengthMm: Object.freeze({ minimum: 1, maximum: 4000 }),
|
|
19
|
+
fNumber: Object.freeze({ minimum: 0.5, maximum: 128 })
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=ideal-lens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ideal-lens.js","sourceRoot":"","sources":["../../src/equipment/ideal-lens.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;IAC7D,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3D,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;CACvD,CAAC,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Ideal lens (#576): the bounds of `simulateCapture`'s and the display view's `lens.ideal`, a plain aberration-free thin lens\n * named by its physical focal length, on any sensor. Its blur is that of the options the request gives, the same models as with\n * `lens.prime`: `lens.defocus` (the thin-lens blur circle, `calculateDefocusCircle`), `lens.diffraction` (the Airy pattern of a\n * circular pupil) and `lens.vignetting` (a caller profile). Without them it renders sharp and evenly lit.\n *\n * - `fNumber.minimum` 0.5 is a physical limit: a lens in air that satisfies the Abbe sine condition (aplanatic) has\n * N = 1/(2·n·sin θ′) with n = 1 and sin θ′ ≤ 1, so N ≥ 0.5 (the sine condition: Born & Wolf, *Principles of Optics*, 7th ed.;\n * Kingslake & Johnson, *Lens Design Fundamentals*, 2nd ed.). The engine's thin-lens defocus and scalar Airy diffraction\n * are paraxial models, so near f/0.5 they are approximations further from a real lens than at f/2.\n * - `fNumber.maximum` 128 and both focal-length bounds are engine bounds, not physical limits: 1 mm is shorter than phone\n * camera lenses, 4000 mm longer than photographic lenses, and f/128 beyond large-format lenses' smallest stops.\n * - The ideal lens's f-number range is the whole of `fNumber`: manual and aperture priority take any `lens.fNumber` in it, and\n * shutter priority and program may choose any aperture in it (in dim light, wider than real lenses open).\n */\nexport const IDEAL_LENS_LIMITS = /* @__PURE__ */ Object.freeze({\n focalLengthMm: Object.freeze({ minimum: 1, maximum: 4000 }),\n fNumber: Object.freeze({ minimum: 0.5, maximum: 128 })\n});\n"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type IlluminationVignettingProfile } from "../optics/illumination-vignetting.js";
|
|
2
|
+
import { type ResolveLensSampledPsfInput } from "../optics/lens-psf-profile.js";
|
|
3
|
+
import type { DisplayViewScene } from "./expected-signal.js";
|
|
4
|
+
/** Fewest grid cells between neighboring profile field nodes. */
|
|
5
|
+
export declare const CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS = 4;
|
|
6
|
+
/** Largest move of a node kernel's intensity centroid across one grid cell, in sites (sets finer grids where lateral color moves the PSF). */
|
|
7
|
+
export declare const CATALOGUE_FAST_GRID_CENTROID_STEP_SITES = 0.05;
|
|
8
|
+
/**
|
|
9
|
+
* Largest relative difference between the fast and the exact expected electrons that the run-time verification (one site per
|
|
10
|
+
* CFA phase through the exact chain, `renderTabulatedNativeRaw`) accepts; above it the capture runs the exact method.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CATALOGUE_FAST_VERIFICATION_TOLERANCE = 0.005;
|
|
13
|
+
export interface CatalogueLensSignalInput {
|
|
14
|
+
nativeWidth: number;
|
|
15
|
+
nativeHeight: number;
|
|
16
|
+
/** Site pitch, µm. */
|
|
17
|
+
pitch: number;
|
|
18
|
+
/** The catalogue PSF's optical state (profile, focal length, focus, f-number, defocus), as the site inputs' `psf.configuration.psf`. */
|
|
19
|
+
psf: Omit<ResolveLensSampledPsfInput, "fieldPointMm" | "wavelengthNm">;
|
|
20
|
+
/** Spectral nodes and, per CFA channel (red, green, blue), the expected electrons per unit spectral radiance at each node. */
|
|
21
|
+
wavelengthsNm: readonly number[];
|
|
22
|
+
electronsPerRadiance: readonly [Float64Array, Float64Array, Float64Array];
|
|
23
|
+
/** The scene's chart materials (the tabulated path's material tables). */
|
|
24
|
+
scene: Extract<DisplayViewScene, {
|
|
25
|
+
kind: "materials";
|
|
26
|
+
}>;
|
|
27
|
+
/** CFA channel index (0 red, 1 green, 2 blue) of native site (x, y). */
|
|
28
|
+
channelAt(x: number, y: number): number;
|
|
29
|
+
/** The radial distortion profile (r_d = r·(1 + k1ρ² + k2ρ⁴ + k3ρ⁶), ρ = r / normalizationRadiusMm); absent without distortion. */
|
|
30
|
+
distortion?: {
|
|
31
|
+
normalizationRadiusMm: number;
|
|
32
|
+
coefficients: {
|
|
33
|
+
k1: number;
|
|
34
|
+
k2: number;
|
|
35
|
+
k3: number;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
vignetting?: IlluminationVignettingProfile;
|
|
39
|
+
projection: "rectilinear" | "fisheye-equisolid-angle";
|
|
40
|
+
/** True for a catalogue fisheye: a sensor point outside the image circle looks straight back and sees no scene. */
|
|
41
|
+
outsideImageCircleSeesNothing: boolean;
|
|
42
|
+
focalLengthMm: number;
|
|
43
|
+
focusDistanceM: number;
|
|
44
|
+
/** The planar provider's plane distance and frame-unit scale. */
|
|
45
|
+
distanceM: number;
|
|
46
|
+
metresPerFrameUnit: number;
|
|
47
|
+
/** Internal test hook: resolve every site's kernels exactly (the exact method's sum, slowly) instead of blending the grid. */
|
|
48
|
+
exactKernels?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface CatalogueLensSignal {
|
|
51
|
+
/** Starts a cache window of native sites [x0, x1) × [y0, y1) (see `DisplayViewExpectedSignal.window`). */
|
|
52
|
+
window(x0: number, y0: number, x1: number, y1: number): void;
|
|
53
|
+
/** Expected photo electrons of native site (x, y) for its own CFA channel. */
|
|
54
|
+
site(x: number, y: number): number;
|
|
55
|
+
/** Scene material lookups so far. */
|
|
56
|
+
readonly lookups: number;
|
|
57
|
+
/** Grid points per axis (columns, rows) and how many were resolved so far. */
|
|
58
|
+
readonly grid: {
|
|
59
|
+
columns: number;
|
|
60
|
+
rows: number;
|
|
61
|
+
resolved: number;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The grid axes: each interval between neighboring profile field nodes split into max(GRID_MINIMUM_SUBDIVISIONS,
|
|
66
|
+
* ⌈Δc / GRID_CENTROID_STEP_SITES⌉) equal cells, Δc the largest centroid move between the interval's two nodes over the other
|
|
67
|
+
* axis's nodes and the wavelength nodes.
|
|
68
|
+
*/
|
|
69
|
+
export declare function catalogueFastGridAxes(psf: CatalogueLensSignalInput["psf"]): {
|
|
70
|
+
x: number[];
|
|
71
|
+
y: number[];
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Internal test hook (never set by the engine): `exactKernels` makes the facade's signals resolve every site's kernels exactly;
|
|
75
|
+
* `onInput` receives each input the facade builds, so a test can compare the grid blend with the exact sum site by site.
|
|
76
|
+
*/
|
|
77
|
+
export interface CatalogueLensSignalTestHook {
|
|
78
|
+
exactKernels?: boolean;
|
|
79
|
+
onInput?(input: CatalogueLensSignalInput): void;
|
|
80
|
+
}
|
|
81
|
+
export declare function setCatalogueLensSignalTestHook(hook: CatalogueLensSignalTestHook | undefined): void;
|
|
82
|
+
/** The fast method's expected signal (see the module comment). */
|
|
83
|
+
export declare function createCatalogueLensSignal(given: CatalogueLensSignalInput): CatalogueLensSignal;
|
|
84
|
+
/** The `raw.expectedSignal` and `fast.lens.catalogue` assumptions of a capture the fast method rendered (kept here, out of the capture bundle's entry). */
|
|
85
|
+
export declare function catalogueFastAssumptions(run: {
|
|
86
|
+
columns: number;
|
|
87
|
+
rows: number;
|
|
88
|
+
resolved: number;
|
|
89
|
+
verificationRelativeDifference: number;
|
|
90
|
+
}): {
|
|
91
|
+
id: string;
|
|
92
|
+
value: Record<string, unknown>;
|
|
93
|
+
explanation: string;
|
|
94
|
+
}[];
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* #568: the fast method of `simulateCapture`'s `lens.catalogue` (requested with `fast: true` or `lens.catalogue.fast`): each
|
|
4
|
+
* native site's expected photo electrons from tables instead of the environment PSF quadrature's per-tap scene queries.
|
|
5
|
+
* Loaded on demand with the catalogue (`generic-lens-capture.ts`), so a capture without a catalogue lens never loads it.
|
|
6
|
+
*
|
|
7
|
+
* What the exact method computes. The environment PSF quadrature forms, for site q of CFA channel c,
|
|
8
|
+
* μ_c(q) = Σ_λ A_c(λ) Σ_k K_{q,λ}(k)·F(s_k)·L(D(s_k), λ), s_k = q + (c_x − k_x, k_y − c_y)·p,
|
|
9
|
+
* with K_{q,λ} the catalogue PSF resolved at the site's own field point (`resolveLensSampledPsfKernel`: the profile's 5 × 5 field
|
|
10
|
+
* nodes × 5 wavelength nodes interpolated with centroid alignment; row index k_y increases with image +Y, `sensorPsfSourcePoint`),
|
|
11
|
+
* F = T·A the field factor at the tap point (vignetting throughput × distortion area factor), D the scene ray (inverse radial
|
|
12
|
+
* distortion, then the rectilinear or equisolid projection at the thin-lens image distance) and A_c(λ) the electrons per unit
|
|
13
|
+
* spectral radiance on the axis (the tabulated path's channel response). Every tap of a site-centre query is a lattice point.
|
|
14
|
+
*
|
|
15
|
+
* The approximation (the only one). Resolving K_{q,λ} at every site and spectral node is what makes the exact method slow, so the
|
|
16
|
+
* fast method resolves it exactly on a field grid and blends it bilinearly between the four grid points around each site:
|
|
17
|
+
* K̃_{q,λ} = Σ_g b_g(q)·K_{g,λ}.
|
|
18
|
+
* The grid subdivides each interval between the profile's field nodes into at least GRID_MINIMUM_SUBDIVISIONS cells, and into
|
|
19
|
+
* more where the node kernels' intensity centroids move by more than GRID_CENTROID_STEP_SITES sites across the interval (lateral
|
|
20
|
+
* colour moves the PSF across the frame; a blend of two shifted kernels is not the shifted kernel). Since the blend is linear,
|
|
21
|
+
* the channel response and the material radiance fold into the grid kernels once per grid point:
|
|
22
|
+
* μ̃_c(q) = Σ_k F(s_k)·Σ_g b_g(q)·W_{g,c,m(s_k)}(k), W_{g,c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_{g,λ}(k),
|
|
23
|
+
* so a site costs (2R + 1)² taps instead of (2R + 1)² scene queries per spectral node. Its error against the exact method is
|
|
24
|
+
* bounded by ‖K̃ − K‖₁ times the brightest tap's signal; tests measure both (test/simulate-capture-lens-catalogue-fast.test.ts).
|
|
25
|
+
*
|
|
26
|
+
* The scene points. The mapping is the environment's (`undistortedImagePoint`, `lensProjectionDirectionValue`, the planar
|
|
27
|
+
* provider's plane point), with the radial inverse solved by Newton's method instead of bisection: both converge to the same
|
|
28
|
+
* root to rounding. Outside a circular fisheye's image circle (`outsideImageCircle: "backward-ray"`) and beyond the horizon the
|
|
29
|
+
* scene radiance is zero, as in the capture.
|
|
30
|
+
*/
|
|
31
|
+
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
32
|
+
import { insideRadialImageCircle, lensProjectionDirectionValue } from "../camera/lens-projection.js";
|
|
33
|
+
import { resolveProjectionDistanceMm } from "../motion/camera-rotation.js";
|
|
34
|
+
import { prepareIlluminationVignettingThroughput } from "../optics/illumination-vignetting.js";
|
|
35
|
+
import { resolveLensSampledPsfKernel } from "../optics/lens-psf-profile.js";
|
|
36
|
+
/** Fewest grid cells between neighboring profile field nodes. */
|
|
37
|
+
export const CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS = 4;
|
|
38
|
+
/** Largest move of a node kernel's intensity centroid across one grid cell, in sites (sets finer grids where lateral color moves the PSF). */
|
|
39
|
+
export const CATALOGUE_FAST_GRID_CENTROID_STEP_SITES = 0.05;
|
|
40
|
+
/**
|
|
41
|
+
* Largest relative difference between the fast and the exact expected electrons that the run-time verification (one site per
|
|
42
|
+
* CFA phase through the exact chain, `renderTabulatedNativeRaw`) accepts; above it the capture runs the exact method.
|
|
43
|
+
*/
|
|
44
|
+
export const CATALOGUE_FAST_VERIFICATION_TOLERANCE = 0.005;
|
|
45
|
+
/** Largest cache window, in lattice points; a larger window is evaluated without caching. */
|
|
46
|
+
const WINDOW_LIMIT = 1 << 22;
|
|
47
|
+
const NEWTON_ITERATIONS = 60;
|
|
48
|
+
/** A kernel's intensity centroid, in samples. */
|
|
49
|
+
function centroid(values, width) {
|
|
50
|
+
let sum = 0, x = 0, y = 0;
|
|
51
|
+
for (let i = 0; i < values.length; i++) {
|
|
52
|
+
const v = values[i];
|
|
53
|
+
sum += v;
|
|
54
|
+
x += v * (i % width);
|
|
55
|
+
y += v * Math.floor(i / width);
|
|
56
|
+
}
|
|
57
|
+
return { x: x / sum, y: y / sum };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The grid axes: each interval between neighboring profile field nodes split into max(GRID_MINIMUM_SUBDIVISIONS,
|
|
61
|
+
* ⌈Δc / GRID_CENTROID_STEP_SITES⌉) equal cells, Δc the largest centroid move between the interval's two nodes over the other
|
|
62
|
+
* axis's nodes and the wavelength nodes.
|
|
63
|
+
*/
|
|
64
|
+
export function catalogueFastGridAxes(psf) {
|
|
65
|
+
const profile = psf.profile, axes = profile.axes, fx = axes.fieldXmm, fy = axes.fieldYmm, wl = axes.wavelengthNm;
|
|
66
|
+
const centroids = new Map();
|
|
67
|
+
for (const node of profile.nodes) {
|
|
68
|
+
const c = node.coordinate;
|
|
69
|
+
centroids.set(c.fieldXmm + ":" + c.fieldYmm + ":" + c.wavelengthNm, centroid(node.kernel.normalizedIntensity, node.kernel.widthSamples));
|
|
70
|
+
}
|
|
71
|
+
const at = (x, y, nm) => {
|
|
72
|
+
const c = centroids.get(x + ":" + y + ":" + nm);
|
|
73
|
+
if (!c)
|
|
74
|
+
throw new InvalidConfigurationError("lens.catalogue fast: the PSF profile lacks a field node.");
|
|
75
|
+
return c;
|
|
76
|
+
};
|
|
77
|
+
const axis = (own, other, along) => {
|
|
78
|
+
const out = [];
|
|
79
|
+
for (let i = 0; i + 1 < own.length; i++) {
|
|
80
|
+
let move = 0;
|
|
81
|
+
for (const o of other)
|
|
82
|
+
for (const nm of wl) {
|
|
83
|
+
const a = along ? at(own[i], o, nm) : at(o, own[i], nm), b = along ? at(own[i + 1], o, nm) : at(o, own[i + 1], nm);
|
|
84
|
+
move = Math.max(move, Math.hypot(a.x - b.x, a.y - b.y));
|
|
85
|
+
}
|
|
86
|
+
const cells = Math.max(CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, Math.ceil(move / CATALOGUE_FAST_GRID_CENTROID_STEP_SITES - 1e-9));
|
|
87
|
+
for (let k = 0; k < cells; k++)
|
|
88
|
+
out.push(own[i] + (own[i + 1] - own[i]) * k / cells);
|
|
89
|
+
}
|
|
90
|
+
out.push(own[own.length - 1]);
|
|
91
|
+
return out;
|
|
92
|
+
};
|
|
93
|
+
return { x: axis(fx, fy, true), y: axis(fy, fx, false) };
|
|
94
|
+
}
|
|
95
|
+
/** The cell [i, i + 1] of an ascending axis holding v (clamped to the ends) and v's weight toward i + 1. */
|
|
96
|
+
function cellOf(axis, v) {
|
|
97
|
+
let lo = 0, hi = axis.length - 2;
|
|
98
|
+
while (lo < hi) {
|
|
99
|
+
const mid = (lo + hi + 1) >> 1;
|
|
100
|
+
if (axis[mid] <= v)
|
|
101
|
+
lo = mid;
|
|
102
|
+
else
|
|
103
|
+
hi = mid - 1;
|
|
104
|
+
}
|
|
105
|
+
const f = (v - axis[lo]) / (axis[lo + 1] - axis[lo]);
|
|
106
|
+
return { i: lo, f: Math.min(1, Math.max(0, f)) };
|
|
107
|
+
}
|
|
108
|
+
let testHook;
|
|
109
|
+
export function setCatalogueLensSignalTestHook(hook) { testHook = hook; }
|
|
110
|
+
/** The fast method's expected signal (see the module comment). */
|
|
111
|
+
export function createCatalogueLensSignal(given) {
|
|
112
|
+
testHook?.onInput?.(given);
|
|
113
|
+
const input = testHook?.exactKernels ? { ...given, exactKernels: true } : given;
|
|
114
|
+
const { nativeWidth: W, nativeHeight: H, psf, wavelengthsNm, electronsPerRadiance: A, scene, channelAt } = input;
|
|
115
|
+
const mmPerSite = input.pitch / 1000, nodes = wavelengthsNm.length;
|
|
116
|
+
const first = psf.profile.nodes[0];
|
|
117
|
+
if (!first)
|
|
118
|
+
throw new InvalidConfigurationError("lens.catalogue fast: the PSF profile has no nodes.");
|
|
119
|
+
const kw = first.kernel.widthSamples, kh = first.kernel.heightSamples, cx = first.kernel.centerSampleX, cy = first.kernel.centerSampleY, taps = kw * kh;
|
|
120
|
+
for (const node of psf.profile.nodes)
|
|
121
|
+
if (node.kernel.widthSamples !== kw || node.kernel.heightSamples !== kh || node.kernel.centerSampleX !== cx || node.kernel.centerSampleY !== cy)
|
|
122
|
+
throw new InvalidConfigurationError("lens.catalogue fast: the PSF profile's kernels differ in size.");
|
|
123
|
+
// Lattice reach of the taps around a site: x from −cx … kw − 1 − cx (as c_x − k_x), y from −cy … kh − 1 − cy.
|
|
124
|
+
const reachLeft = kw - 1 - cx, reachRight = cx, reachUp = cy, reachDown = kh - 1 - cy;
|
|
125
|
+
// Materials: neutral ones by reflectance (class 0, the white radiance), spectral ones by class 1 + index.
|
|
126
|
+
const M = scene.materials.length, reflectance = new Float64Array(M), materialClass = new Int32Array(M);
|
|
127
|
+
const spectral = [];
|
|
128
|
+
scene.materials.forEach((m, index) => { if ("neutral" in m)
|
|
129
|
+
reflectance[index] = m.neutral;
|
|
130
|
+
else {
|
|
131
|
+
materialClass[index] = 1 + spectral.length;
|
|
132
|
+
spectral.push(m.radiance);
|
|
133
|
+
} });
|
|
134
|
+
const classes = 1 + spectral.length, classRadiance = [scene.whiteRadiance, ...spectral];
|
|
135
|
+
const tableSize = 3 * classes * taps;
|
|
136
|
+
// W_{c,m}(k) = Σ_λ A_c(λ)·L_m(λ)·K_λ(k) for the kernels of one field point.
|
|
137
|
+
const foldKernels = (fieldXmm, fieldYmm) => {
|
|
138
|
+
const table = new Float64Array(tableSize);
|
|
139
|
+
for (let n = 0; n < nodes; n++) {
|
|
140
|
+
const kernel = resolveLensSampledPsfKernel({ ...psf, wavelengthNm: wavelengthsNm[n], fieldPointMm: { x: fieldXmm, y: fieldYmm } }).normalizedIntensity;
|
|
141
|
+
if (kernel.length !== taps)
|
|
142
|
+
throw new InvalidConfigurationError("lens.catalogue fast: a resolved kernel differs in size.");
|
|
143
|
+
for (let c = 0; c < 3; c++) {
|
|
144
|
+
const a = A[c][n];
|
|
145
|
+
if (a === 0)
|
|
146
|
+
continue;
|
|
147
|
+
for (let cl = 0; cl < classes; cl++) {
|
|
148
|
+
const weight = a * classRadiance[cl][n], base = (c * classes + cl) * taps;
|
|
149
|
+
if (weight === 0)
|
|
150
|
+
continue;
|
|
151
|
+
for (let k = 0; k < taps; k++)
|
|
152
|
+
table[base + k] = table[base + k] + weight * kernel[k];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return table;
|
|
157
|
+
};
|
|
158
|
+
// The field grid, resolved lazily (a region render resolves only the grid points around it).
|
|
159
|
+
const axes = catalogueFastGridAxes(psf), gx = axes.x, gy = axes.y, GX = gx.length;
|
|
160
|
+
const tables = new Array(GX * gy.length);
|
|
161
|
+
let resolved = 0;
|
|
162
|
+
const tableAt = (i, j) => {
|
|
163
|
+
const k = j * GX + i;
|
|
164
|
+
let t = tables[k];
|
|
165
|
+
if (!t) {
|
|
166
|
+
t = foldKernels(gx[i], gy[j]);
|
|
167
|
+
tables[k] = t;
|
|
168
|
+
resolved++;
|
|
169
|
+
}
|
|
170
|
+
return t;
|
|
171
|
+
};
|
|
172
|
+
// Each native column's and row's grid cell and blend weight (field +Y up: row y sits at −(y + ½ − H/2)·p).
|
|
173
|
+
const cellX = new Int32Array(W), weightX = new Float64Array(W), cellY = new Int32Array(H), weightY = new Float64Array(H);
|
|
174
|
+
for (let x = 0; x < W; x++) {
|
|
175
|
+
const c = cellOf(gx, (x + 0.5 - W / 2) * mmPerSite);
|
|
176
|
+
cellX[x] = c.i;
|
|
177
|
+
weightX[x] = c.f;
|
|
178
|
+
}
|
|
179
|
+
for (let y = 0; y < H; y++) {
|
|
180
|
+
const c = cellOf(gy, -(y + 0.5 - H / 2) * mmPerSite);
|
|
181
|
+
cellY[y] = c.i;
|
|
182
|
+
weightY[y] = c.f;
|
|
183
|
+
}
|
|
184
|
+
// The scene point and field factor of a native sensor point (mm, +Y down); factor 0 where the scene radiance is zero.
|
|
185
|
+
const pd = resolveProjectionDistanceMm(input.focalLengthMm, input.focusDistanceM).projectionDistanceMm;
|
|
186
|
+
const planeScale = input.distanceM / input.metresPerFrameUnit;
|
|
187
|
+
const throughputAt = input.vignetting ? prepareIlluminationVignettingThroughput(input.vignetting) : undefined;
|
|
188
|
+
const distortion = input.distortion, rectilinear = input.projection === "rectilinear";
|
|
189
|
+
let mu = 0, mv = 0, mf = 0;
|
|
190
|
+
const map = (xMm, yMm) => {
|
|
191
|
+
const imageX = xMm, imageY = -yMm;
|
|
192
|
+
let idealX = imageX, idealY = imageY, area = 1;
|
|
193
|
+
if (distortion) {
|
|
194
|
+
const { k1, k2, k3 } = distortion.coefficients, norm = distortion.normalizationRadiusMm, rd = Math.hypot(imageX, imageY) / norm;
|
|
195
|
+
let rho = rd;
|
|
196
|
+
if (rd > 0) {
|
|
197
|
+
// Newton's method on ρ·s(ρ) = ρ_d (monotonic over the profile's validated range), from ρ = ρ_d.
|
|
198
|
+
let converged = false;
|
|
199
|
+
for (let it = 0; it < NEWTON_ITERATIONS; it++) {
|
|
200
|
+
const r2 = rho * rho, value = rho * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2) - rd, slope = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;
|
|
201
|
+
if (!(slope > 0))
|
|
202
|
+
break;
|
|
203
|
+
const step = value / slope;
|
|
204
|
+
rho -= step;
|
|
205
|
+
if (Math.abs(step) <= 4 * Number.EPSILON * rho) {
|
|
206
|
+
converged = true;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (!converged || !(rho > 0))
|
|
211
|
+
throw new InvalidConfigurationError("lens.catalogue fast: the inverse distortion did not converge.");
|
|
212
|
+
}
|
|
213
|
+
const r2 = rho * rho, s = 1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2, derivative = 1 + 3 * k1 * r2 + 5 * k2 * r2 * r2 + 7 * k3 * r2 * r2 * r2;
|
|
214
|
+
if (rd > 0) {
|
|
215
|
+
idealX = imageX / s;
|
|
216
|
+
idealY = imageY / s;
|
|
217
|
+
}
|
|
218
|
+
area = 1 / (s * derivative);
|
|
219
|
+
if (!(area > 0) || !Number.isFinite(area))
|
|
220
|
+
throw new InvalidConfigurationError("lens.catalogue fast: the distortion area factor must stay finite and positive.");
|
|
221
|
+
}
|
|
222
|
+
const factor = (throughputAt ? throughputAt({ x: imageX, y: imageY }) : 1) * area;
|
|
223
|
+
if (rectilinear) {
|
|
224
|
+
mu = idealX / pd * planeScale;
|
|
225
|
+
mv = idealY / pd * planeScale;
|
|
226
|
+
mf = factor;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (input.outsideImageCircleSeesNothing && !insideRadialImageCircle(input.projection, pd, Math.hypot(idealX, idealY))) {
|
|
230
|
+
mf = 0;
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const d = lensProjectionDirectionValue(input.projection, pd, idealX, idealY);
|
|
234
|
+
if (!(d.z > 0)) {
|
|
235
|
+
mf = 0;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
mu = d.x * (input.distanceM / d.z) / input.metresPerFrameUnit;
|
|
239
|
+
mv = d.y * (input.distanceM / d.z) / input.metresPerFrameUnit;
|
|
240
|
+
mf = factor;
|
|
241
|
+
};
|
|
242
|
+
let lookups = 0;
|
|
243
|
+
let lm = 0, lf = 0;
|
|
244
|
+
/** The material and field factor of lattice point (p, q) (sites from the top-left site, the lattice formula). */
|
|
245
|
+
const lattice = (p, q) => {
|
|
246
|
+
map((p + 0.5 - W / 2) * mmPerSite, (q + 0.5 - H / 2) * mmPerSite);
|
|
247
|
+
lf = mf;
|
|
248
|
+
if (mf === 0) {
|
|
249
|
+
lm = 0;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const m = scene.materialAt(mu, mv);
|
|
253
|
+
if (!(m >= 0 && m < M) || !Number.isInteger(m))
|
|
254
|
+
throw new InvalidConfigurationError("Unknown chart material.");
|
|
255
|
+
lm = m;
|
|
256
|
+
lookups++;
|
|
257
|
+
};
|
|
258
|
+
// The window's lattice: points [wx0, wx0 + ww) × [wy0, wy0 + wh).
|
|
259
|
+
let wx0 = 0, wy0 = 0, ww = 0, wh = 0, winMaterial = new Int32Array(0), winFactor = new Float64Array(0);
|
|
260
|
+
const window = (x0, y0, x1, y1) => {
|
|
261
|
+
const w = x1 - x0 + reachLeft + reachRight, h = y1 - y0 + reachUp + reachDown;
|
|
262
|
+
if (!(w > 0 && h > 0) || w * h > WINDOW_LIMIT) {
|
|
263
|
+
ww = 0;
|
|
264
|
+
wh = 0;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
wx0 = x0 - reachLeft;
|
|
268
|
+
wy0 = y0 - reachUp;
|
|
269
|
+
ww = w;
|
|
270
|
+
wh = h;
|
|
271
|
+
if (winMaterial.length < w * h) {
|
|
272
|
+
winMaterial = new Int32Array(w * h);
|
|
273
|
+
winFactor = new Float64Array(w * h);
|
|
274
|
+
}
|
|
275
|
+
for (let q = 0; q < h; q++)
|
|
276
|
+
for (let p = 0; p < w; p++) {
|
|
277
|
+
lattice(wx0 + p, wy0 + q);
|
|
278
|
+
winMaterial[q * w + p] = lm;
|
|
279
|
+
winFactor[q * w + p] = lf;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
const kernelOf = new Float64Array(tableSize);
|
|
283
|
+
let cellI = -1, cellJ = -1, c00 = kernelOf, c10 = kernelOf, c01 = kernelOf, c11 = kernelOf;
|
|
284
|
+
const site = (x, y) => {
|
|
285
|
+
const c = channelAt(x, y);
|
|
286
|
+
let t00, t10, t01, t11, b00, b10, b01, b11;
|
|
287
|
+
if (input.exactKernels) {
|
|
288
|
+
kernelOf.set(foldKernels((x + 0.5 - W / 2) * mmPerSite, -(y + 0.5 - H / 2) * mmPerSite));
|
|
289
|
+
t00 = t10 = t01 = t11 = kernelOf;
|
|
290
|
+
b00 = 1;
|
|
291
|
+
b10 = b01 = b11 = 0;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const i = cellX[x], j = cellY[y], fx = weightX[x], fy = weightY[y];
|
|
295
|
+
// Neighboring sites share a cell: its four tables are kept from the last site.
|
|
296
|
+
if (i !== cellI || j !== cellJ) {
|
|
297
|
+
cellI = i;
|
|
298
|
+
cellJ = j;
|
|
299
|
+
c00 = tableAt(i, j);
|
|
300
|
+
c10 = tableAt(i + 1, j);
|
|
301
|
+
c01 = tableAt(i, j + 1);
|
|
302
|
+
c11 = tableAt(i + 1, j + 1);
|
|
303
|
+
}
|
|
304
|
+
t00 = c00;
|
|
305
|
+
t10 = c10;
|
|
306
|
+
t01 = c01;
|
|
307
|
+
t11 = c11;
|
|
308
|
+
b00 = (1 - fx) * (1 - fy);
|
|
309
|
+
b10 = fx * (1 - fy);
|
|
310
|
+
b01 = (1 - fx) * fy;
|
|
311
|
+
b11 = fx * fy;
|
|
312
|
+
}
|
|
313
|
+
const inside = ww > 0 && x - reachLeft >= wx0 && y - reachUp >= wy0 && x + reachRight < wx0 + ww && y + reachDown < wy0 + wh;
|
|
314
|
+
const channelBase = c * classes * taps;
|
|
315
|
+
let sum = 0;
|
|
316
|
+
for (let ky = 0, k = 0; ky < kh; ky++) {
|
|
317
|
+
const q = y + ky - cy;
|
|
318
|
+
for (let kx = 0; kx < kw; kx++, k++) {
|
|
319
|
+
const p = x + cx - kx;
|
|
320
|
+
let m, f;
|
|
321
|
+
if (inside) {
|
|
322
|
+
const at = (q - wy0) * ww + p - wx0;
|
|
323
|
+
m = winMaterial[at];
|
|
324
|
+
f = winFactor[at];
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
lattice(p, q);
|
|
328
|
+
m = lm;
|
|
329
|
+
f = lf;
|
|
330
|
+
}
|
|
331
|
+
if (f === 0)
|
|
332
|
+
continue;
|
|
333
|
+
const cl = materialClass[m], at = channelBase + cl * taps + k;
|
|
334
|
+
const w = b00 * t00[at] + b10 * t10[at] + b01 * t01[at] + b11 * t11[at];
|
|
335
|
+
sum += f * (cl === 0 ? reflectance[m] * w : w);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return sum;
|
|
339
|
+
};
|
|
340
|
+
return {
|
|
341
|
+
window, site,
|
|
342
|
+
get lookups() { return lookups; },
|
|
343
|
+
get grid() { return { columns: GX, rows: gy.length, resolved }; }
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/** The `raw.expectedSignal` and `fast.lens.catalogue` assumptions of a capture the fast method rendered (kept here, out of the capture bundle's entry). */
|
|
347
|
+
export function catalogueFastAssumptions(run) {
|
|
348
|
+
const tolerance = CATALOGUE_FAST_VERIFICATION_TOLERANCE, cells = CATALOGUE_FAST_GRID_MINIMUM_SUBDIVISIONS, step = CATALOGUE_FAST_GRID_CENTROID_STEP_SITES;
|
|
349
|
+
return [{ id: "raw.expectedSignal", value: { method: "tabulated-expected-electrons-catalogue-psf-field-grid", verificationSites: 4, tolerance,
|
|
350
|
+
verificationRelativeDifference: run.verificationRelativeDifference },
|
|
351
|
+
explanation: "Each native site's expected photo electrons are tabulated: the sum over the PSF taps of the field factor (vignetting × distortion area factor) times the " +
|
|
352
|
+
"grid-blended kernel folded with the channel response and the material radiance (see fast.lens.catalogue). The sensor stage (dark charge, Poisson draws, read noise, ADC) " +
|
|
353
|
+
"is the native one with each site's own seeds. One site of each CFA phase ran the exact chain (the environment PSF quadrature): its readout was reproduced bit for bit and " +
|
|
354
|
+
"its electrons agreed with the fast ones within the tolerance (verificationRelativeDifference: the largest difference there)." },
|
|
355
|
+
{ id: "fast.lens.catalogue", value: { method: "field-grid-bilinear-psf", gridColumns: run.columns, gridRows: run.rows, gridPointsResolved: run.resolved,
|
|
356
|
+
minimumSubdivisions: cells, centroidStepSites: step, verificationTolerance: tolerance },
|
|
357
|
+
explanation: "Fast method (#568), an approximation of the exact one: the catalogue PSF is resolved exactly on a field grid (each interval between the profile's field nodes " +
|
|
358
|
+
"split into at least " + cells + " cells, more where the node kernels' centroids move by over " + step + " site per cell), and each site takes the bilinear blend of its " +
|
|
359
|
+
"four grid points' kernels instead of its own. Its error is at most the kernels' L1 difference times the brightest tap's signal; docs/LENS_CATALOGUE.md (\"Fast method\") " +
|
|
360
|
+
"records it measured against the exact method. Distortion, vignetting and projection are the exact method's (the radial inverse by Newton's method, equal to rounding)." }];
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=catalogue-lens-signal.js.map
|