@photivra/engine 2.19.0 → 2.20.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 +112 -1
- package/CITATION.cff +1 -1
- package/NODE_SUPPORT.md +7 -5
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +14 -10
- package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
- package/dist/capture/dng-lens-correction-opcodes.js +119 -0
- package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
- package/dist/capture/native-environment-lean.js +1 -5
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-raw-dng.d.ts +6 -0
- package/dist/capture/native-raw-dng.js +16 -2
- package/dist/capture/native-raw-dng.js.map +1 -1
- package/dist/capture/sensor-raw-producer.js +3 -5
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/core/shared-helpers.d.ts +2 -0
- package/dist/core/shared-helpers.js +4 -0
- package/dist/core/shared-helpers.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/generic-lens-catalogue.d.ts +8 -2
- package/dist/equipment/generic-lens-catalogue.js +31 -2
- package/dist/equipment/generic-lens-catalogue.js.map +1 -1
- package/dist/equipment/generic-tier-assets.js +2 -3
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.js +2 -3
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +7 -4
- package/dist/facade/display-view-gpu.js +4 -4
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.d.ts +1 -1
- package/dist/facade/display-view-lens-signal.js +5 -3
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/display-view.d.ts +3 -1
- package/dist/facade/display-view.js +1 -1
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/fast-methods.d.ts +50 -0
- package/dist/facade/fast-methods.js +50 -0
- package/dist/facade/fast-methods.js.map +1 -0
- package/dist/facade/fast-resolution.d.ts +12 -0
- package/dist/facade/fast-resolution.js +18 -0
- package/dist/facade/fast-resolution.js.map +1 -0
- package/dist/facade/generic-lens-capture.d.ts +3 -2
- package/dist/facade/generic-lens-capture.js +3 -2
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +51 -11
- package/dist/facade/simulate-capture.js +144 -75
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.d.ts +3 -2
- package/dist/facade/simulate-display-view.js +22 -12
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/optics/aperture-diameter.d.ts +47 -0
- package/dist/optics/aperture-diameter.js +50 -0
- package/dist/optics/aperture-diameter.js.map +1 -0
- package/dist/optics/detail-limit.d.ts +182 -0
- package/dist/optics/detail-limit.js +336 -0
- package/dist/optics/detail-limit.js.map +1 -0
- package/dist/optics/generic-lens-psf.d.ts +18 -2
- package/dist/optics/generic-lens-psf.js +25 -2
- package/dist/optics/generic-lens-psf.js.map +1 -1
- package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
- package/dist/optics/scene-to-sensor-irradiance.js +7 -0
- package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
- package/dist/output/crop-to-match-focal-length.d.ts +62 -0
- package/dist/output/crop-to-match-focal-length.js +63 -0
- package/dist/output/crop-to-match-focal-length.js.map +1 -0
- package/dist/output/developed-lens-corrections.js +2 -2
- package/dist/output/developed-lens-corrections.js.map +1 -1
- package/dist/output/generic-lens-corrections.d.ts +3 -1
- package/dist/output/generic-lens-corrections.js +8 -4
- package/dist/output/generic-lens-corrections.js.map +1 -1
- package/dist/output/lens-corrections.d.ts +12 -2
- package/dist/output/lens-corrections.js +22 -7
- package/dist/output/lens-corrections.js.map +1 -1
- package/dist/output/print-contrast-reference.js +1 -1
- package/dist/output/print-contrast-reference.js.map +1 -1
- package/dist/output/print-size-blur-noise.d.ts +110 -0
- package/dist/output/print-size-blur-noise.js +138 -0
- package/dist/output/print-size-blur-noise.js.map +1 -0
- package/dist/sensor/eqe-electron-rate.js +1 -7
- package/dist/sensor/eqe-electron-rate.js.map +1 -1
- package/dist/sensor/exposure-window.d.ts +2 -0
- package/dist/sensor/exposure-window.js +2 -2
- package/dist/sensor/exposure-window.js.map +1 -1
- package/dist/sensor/readout-timing.js +2 -23
- package/dist/sensor/readout-timing.js.map +1 -1
- package/dist/sensor/response-application-compatibility.js +1 -6
- package/dist/sensor/response-application-compatibility.js.map +1 -1
- package/dist/sensor/responsivity-photocurrent.js +1 -7
- package/dist/sensor/responsivity-photocurrent.js.map +1 -1
- package/dist/sensor/sensor-characteristics.d.ts +137 -0
- package/dist/sensor/sensor-characteristics.js +115 -0
- package/dist/sensor/sensor-characteristics.js.map +1 -0
- package/dist/sensor/spatial-sample-reduction.js +1 -5
- package/dist/sensor/spatial-sample-reduction.js.map +1 -1
- package/dist/sensor/spectral-quadrature.js +1 -6
- package/dist/sensor/spectral-quadrature.js.map +1 -1
- package/dist/sensor/temporal-photo-signal.d.ts +3 -0
- package/dist/sensor/temporal-photo-signal.js +5 -2
- package/dist/sensor/temporal-photo-signal.js.map +1 -1
- package/dist/stabilization/system.d.ts +9 -0
- package/dist/stabilization/system.js +5 -6
- package/dist/stabilization/system.js.map +1 -1
- package/docs/AGENT_GUIDE.md +4 -0
- package/docs/API_REFERENCE.md +76 -2
- package/docs/QUICK_START.md +88 -6
- package/docs/USAGE.md +65 -2
- package/docs/api/capture-native-raw-dng.md +6 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +7 -2
- package/docs/api/exports.json +152 -2
- package/docs/api/facade-display-view-gpu.md +5 -2
- package/docs/api/facade-fast-methods.md +91 -0
- package/docs/api/facade-simulate-capture.md +36 -11
- package/docs/api/facade-simulate-display-view.md +3 -2
- package/docs/api/optics-aperture-diameter.md +51 -0
- package/docs/api/optics-detail-limit.md +203 -0
- package/docs/api/output-crop-to-match-focal-length.md +67 -0
- package/docs/api/output-generic-lens-corrections.md +3 -1
- package/docs/api/output-lens-corrections.md +11 -1
- package/docs/api/output-print-size-blur-noise.md +115 -0
- package/docs/api/sensor-sensor-characteristics.md +105 -0
- package/llms.txt +4 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-to-sensor-irradiance.js","sourceRoot":"","sources":["../../src/optics/scene-to-sensor-irradiance.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Scene-to-sensor optical bridge: from spectral scene radiance to the irradiance that reaches the\n * sensor.\n *\n * The profile parser validates untrusted input and returns the normalized typed contract. Unknown\n * enum values, missing required fields and incompatible scientific data are rejected here.\n *\n * The calculation applies the declared pupil acceptance and exactly one transmission or\n * field-throughput path. It returns the irradiance before the sensor stack, with conservative\n * evidence.\n *\n * The optical bridge turns outgoing scene radiance (W/m²/sr/nm) into irradiance (W/m²/nm) arriving\n * at the front of the sensor stack.\n * - Circular paraxial acceptance is pi/(4*Nworking²). At finite focus with unit pupil magnification,\n * the working f-number is N*(1+m).\n * - Spectral transmission and an effective working T-stop are alternative paths; use one.\n * - Field throughput is applied exactly once.\n * - Sensor response and PSF (point-spread function) blur happen later in the pipeline.\n * @see docs/SCENE_SENSOR_QUADRATURE.md for the equations, units, coordinate conventions, known\n * blockers and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { type SpectralWavelengthBasis } from \"../core/spectral.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { composeFrontOfLensFilterTransmission, type FrontOfLensFilterProfile, parseRange, parseResolvedBasis } from \"./front-of-lens-filter.js\";\nimport type { IlluminationVignetting } from \"./illumination-vignetting.js\";\nimport { calculateThinLensImageDistance } from \"./thin-lens.js\";\nimport {\n parseSceneRadianceEvaluationRequest,\n parseSceneRadianceEvaluationResult,\n type SceneRadianceEvaluationRequest,\n type SceneRadianceEvaluationResult\n} from \"../schema/scene-radiance.js\";\nimport { requireInputKeys, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, parseConfigScientificStatus as parseScientificStatus, requireInputPositiveFinite as requirePositiveFiniteScientific, parseConfigLimitations as parseLimitations, requireConfigFiniteAboveZero as requirePositiveFinite } from \"../core/record-validation.js\";\nimport { requireFraction } from \"../color/white-balance.js\";\n\nexport const SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION =\n \"0.1.0\" as const;\n\nexport type OpticalBridgeScientificStatus =\n | \"calibrated\"\n | \"approximation\";\n\nexport type OpticalBridgeUncertainty =\n | {\n kind: \"relative\";\n fraction: number;\n basis: string;\n }\n | {\n kind: \"not-quantified\";\n limitation: string;\n };\n\nexport interface NumericRange {\n minimum: number;\n maximum: number;\n}\n\nexport type OpticalBridgeFocusApplicability =\n | {\n kind: \"any\";\n }\n | {\n kind: \"finite-distance-range\";\n objectDistanceM: NumericRange;\n };\n\nexport interface SpectralTransmissionSample {\n wavelengthNanometers: number;\n linearTransmissionFactor: number;\n}\n\nexport type OpticalTransmissionModel =\n | {\n kind: \"spectral-transmission\";\n scientificStatus:\n OpticalBridgeScientificStatus;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n samples:\n EvidenceBackedFact<\n readonly SpectralTransmissionSample[]\n >;\n uncertainty:\n OpticalBridgeUncertainty;\n }\n | {\n kind:\n \"effective-working-t-stop-approximation\";\n scientificStatus: \"approximation\";\n workingTStop:\n EvidenceBackedFact<number>;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n wavelengthRangeNanometers:\n NumericRange;\n uncertainty:\n OpticalBridgeUncertainty;\n limitation: string;\n };\n\nexport interface SceneToSensorIrradianceProfile {\n schemaVersion:\n typeof SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n lensProfileId: string;\n scientificStatus:\n OpticalBridgeScientificStatus;\n applicability: {\n focalLengthMm: NumericRange;\n nominalFNumber: NumericRange;\n focus:\n OpticalBridgeFocusApplicability;\n };\n transmission:\n OpticalTransmissionModel;\n distortionAreaMappingOwnership:\n \"not-applied-by-bridge\";\n psfRedistributionOwnership:\n \"downstream-normalized-energy-redistribution\";\n sensorOpticalStackIncluded: false;\n strayLightIncluded: false;\n polarizationModeled: false;\n wavelengthChangingBehaviorModeled: false;\n volumetricScatteringModeled: false;\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\nexport type OpticalBridgeFocusContext =\n | {\n kind: \"infinity-focus\";\n }\n | {\n kind:\n \"ideal-symmetric-thin-lens\";\n objectDistanceM: number;\n pupilMagnificationAssumption:\n \"unity\";\n }\n | {\n kind:\n \"supplied-working-f-number\";\n focus:\n | {\n kind: \"infinity\";\n }\n | {\n kind: \"finite\";\n objectDistanceM: number;\n };\n workingFNumber:\n EvidenceBackedFact<number>;\n basis: string;\n };\n\nexport type OpticalBridgeFieldThroughput =\n | {\n kind: \"unity\";\n }\n | {\n kind:\n \"illumination-vignetting-result\";\n result:\n IlluminationVignetting;\n };\n\nexport interface CalculateSceneRadianceToSensorIrradianceInput {\n sceneRadianceRequest:\n SceneRadianceEvaluationRequest;\n sceneRadianceResult:\n SceneRadianceEvaluationResult;\n profile:\n SceneToSensorIrradianceProfile;\n focalLengthMm: number;\n nominalFNumber: number;\n focus:\n OpticalBridgeFocusContext;\n imagePointMm: {\n x: number;\n y: number;\n };\n fieldThroughput:\n OpticalBridgeFieldThroughput;\n frontOfLensFilters?:\n readonly FrontOfLensFilterProfile[];\n}\n\nexport interface SceneToSensorIrradianceResult {\n schemaVersion:\n typeof SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n lensProfileId: string;\n sampleId: string;\n providerProfileId: string;\n sceneId: string;\n timeSecondsFromExposureStart: number;\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n inputQuantity:\n \"outgoing-spectral-radiance\";\n inputUnit:\n \"W/m^2/sr/nm\";\n outputQuantity:\n \"sensor-plane-spectral-irradiance\";\n outputUnit:\n \"W/m^2/nm\";\n sceneSpectralRadianceWattsPerSquareMeterSteradianNanometer:\n number;\n focalLengthMm: number;\n nominalFNumber: number;\n workingFNumber: number;\n focusModel:\n OpticalBridgeFocusContext[\"kind\"];\n paraxialGeometricAcceptanceSolidAngleSteradians:\n number;\n transmissionPath:\n OpticalTransmissionModel[\"kind\"];\n spectralTransmissionFactor:\n number | null;\n effectiveWorkingTStop:\n number | null;\n tStopEquivalentTransmissionFactor:\n number | null;\n frontOfLensFilterCount: number;\n frontOfLensFilterTransmissionFactor:\n number;\n frontOfLensFilterAttenuationStops:\n number;\n frontOfLensFilterTransmissionAppliedExactlyOnce:\n true;\n /**\n * True when a front-of-lens linear polarizer was applied. Scene radiance is unpolarized in this\n * bridge, so a polarizer transmits (k1 + k2) / 2.\n */\n frontOfLensFilterPolarizationModeled:\n boolean;\n fieldThroughputFactor: number;\n effectiveAcceptanceAfterTransmissionAndFieldSteradians:\n number;\n sensorPlaneSpectralIrradianceWattsPerSquareMeterNanometer:\n number;\n scientificStatus: \"approximation\";\n sceneRadianceScientificStatus:\n SceneRadianceEvaluationResult[\"scientificStatus\"];\n opticalProfileScientificStatus:\n OpticalBridgeScientificStatus;\n sceneRadianceUncertainty:\n SceneRadianceEvaluationResult[\"uncertainty\"];\n opticalUncertainty:\n OpticalBridgeUncertainty;\n radianceAmplificationApplied: false;\n fieldThroughputAppliedExactlyOnce:\n true;\n universalCos4FalloffApplied: false;\n distortionAreaCorrectionApplied: false;\n psfRedistributionApplied: false;\n sensorOpticalStackApplied: false;\n cfaApplied: false;\n quantumEfficiencyApplied: false;\n spectralResponsivityApplied: false;\n strayLightApplied: false;\n polarizationModeled: false;\n wavelengthChangingBehaviorModeled:\n false;\n volumetricScatteringModeled: false;\n calibratedSensorPlaneIrradianceClaimAuthorized:\n false;\n componentEvidence: {\n sceneRadiance:\n readonly EvidenceProvenance[];\n opticalProfile:\n readonly EvidenceProvenance[];\n transmission:\n readonly EvidenceProvenance[];\n workingFNumber:\n readonly EvidenceProvenance[];\n frontOfLensFilters:\n readonly EvidenceProvenance[];\n };\n limitations:\n readonly string[];\n}\n\nfunction parseUncertainty(\n value: unknown,\n path: string\n): OpticalBridgeUncertainty {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", UNCERTAINTY_VARIANT_FIELDS, path);\n\n if (record.kind === \"relative\") {\n return {\n kind: \"relative\",\n fraction:\n requireFraction(\n record.fraction,\n path + \".fraction\"\n ),\n basis:\n requireNonEmptyString(\n record.basis,\n path + \".basis\"\n )\n };\n }\n\n if (\n record.kind ===\n \"not-quantified\"\n ) {\n return {\n kind: \"not-quantified\",\n limitation:\n requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\nfunction parseReusableNumericEvidence(\n value: unknown,\n path: string\n): readonly EvidenceProvenance[] {\n const evidence =\n parseEvidenceList(value, path);\n\n if (\n evidence.some(\n (entry) =>\n entry.reuseStatus ===\n \"factual-reference-only\"\n )\n ) {\n throw new InvalidConfigurationError(\n path +\n \" for numeric optical curve data must be reusable-data or photivra-owned.\"\n );\n }\n return evidence;\n}\n\nfunction parseSpectralSamples(\n value: unknown,\n path: string\n): readonly SpectralTransmissionSample[] {\n if (\n !Array.isArray(value) ||\n value.length < 2\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must contain at least two samples.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n return value.map(\n (entry, index) => {\n const samplePath =\n path +\n \"[\" +\n index +\n \"]\";\n const record =\n requireRecord(\n entry,\n samplePath, [\"wavelengthNanometers\", \"linearTransmissionFactor\"]\n );\n const wavelengthNanometers =\n requirePositiveFinite(\n record.wavelengthNanometers,\n samplePath +\n \".wavelengthNanometers\"\n );\n if (\n wavelengthNanometers <=\n previous\n ) {\n throw new InvalidConfigurationError(\n path +\n \" wavelengths must be strictly increasing.\"\n );\n }\n previous =\n wavelengthNanometers;\n return {\n wavelengthNanometers,\n linearTransmissionFactor:\n requireFraction(\n record.linearTransmissionFactor,\n samplePath +\n \".linearTransmissionFactor\"\n )\n };\n }\n );\n}\n\nfunction parseTransmission(\n value: unknown,\n path: string\n): OpticalTransmissionModel {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", {\n // workingTStop and wavelengthRangeNanometers (spectral) and samples (working T-stop) are rejected below with their own messages.\n \"spectral-transmission\": [\"kind\", \"scientificStatus\", \"wavelengthBasis\", \"samples\", \"uncertainty\", \"workingTStop\", \"wavelengthRangeNanometers\"],\n \"effective-working-t-stop-approximation\": [\"kind\", \"scientificStatus\", \"workingTStop\", \"wavelengthBasis\", \"wavelengthRangeNanometers\",\n \"uncertainty\", \"limitation\", \"samples\"]\n }, path);\n\n if (\n record.kind ===\n \"spectral-transmission\"\n ) {\n if (\n record.workingTStop !==\n undefined ||\n record.wavelengthRangeNanometers !==\n undefined\n ) {\n throw new InvalidConfigurationError(\n path +\n \" spectral-transmission must not include T-stop-only fields.\"\n );\n }\n\n const status =\n parseScientificStatus(\n record.scientificStatus,\n path +\n \".scientificStatus\"\n );\n const samplesFact =\n requireRecord(\n record.samples,\n path + \".samples\", [\"value\", \"evidence\"]\n );\n const uncertainty =\n parseUncertainty(\n record.uncertainty,\n path + \".uncertainty\"\n );\n\n if (\n status === \"calibrated\" &&\n uncertainty.kind !==\n \"relative\"\n ) {\n throw new InvalidConfigurationError(\n path +\n \" calibrated spectral transmission requires quantified relative uncertainty.\"\n );\n }\n\n return {\n kind:\n \"spectral-transmission\",\n scientificStatus: status,\n wavelengthBasis:\n parseResolvedBasis(\n record.wavelengthBasis,\n path +\n \".wavelengthBasis\"\n ),\n samples: {\n value:\n parseSpectralSamples(\n samplesFact.value,\n path +\n \".samples.value\"\n ),\n evidence:\n parseReusableNumericEvidence(\n samplesFact.evidence,\n path +\n \".samples.evidence\"\n )\n },\n uncertainty\n };\n }\n\n if (\n record.kind ===\n \"effective-working-t-stop-approximation\"\n ) {\n if (\n record.samples !==\n undefined\n ) {\n throw new InvalidConfigurationError(\n path +\n \" T-stop approximation must not include spectral transmission samples.\"\n );\n }\n if (\n record.scientificStatus !==\n \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n path +\n ' T-stop scientificStatus must be \"approximation\".'\n );\n }\n const workingTStopFact =\n requireRecord(\n record.workingTStop,\n path +\n \".workingTStop\", [\"value\", \"evidence\"]\n );\n return {\n kind:\n \"effective-working-t-stop-approximation\",\n scientificStatus:\n \"approximation\",\n workingTStop: {\n value:\n requirePositiveFinite(\n workingTStopFact.value,\n path +\n \".workingTStop.value\"\n ),\n evidence:\n parseEvidenceList(\n workingTStopFact.evidence,\n path +\n \".workingTStop.evidence\"\n )\n },\n wavelengthBasis:\n parseResolvedBasis(\n record.wavelengthBasis,\n path +\n \".wavelengthBasis\"\n ),\n wavelengthRangeNanometers:\n parseRange(\n record.wavelengthRangeNanometers,\n path +\n \".wavelengthRangeNanometers\"\n ),\n uncertainty:\n parseUncertainty(\n record.uncertainty,\n path + \".uncertainty\"\n ),\n limitation:\n requireNonEmptyString(\n record.limitation,\n path +\n \".limitation\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\nfunction parseFocusApplicability(\n value: unknown,\n path: string\n): OpticalBridgeFocusApplicability {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", { any: [\"kind\"], \"finite-distance-range\": [\"kind\", \"objectDistanceM\"] }, path);\n\n if (record.kind === \"any\") {\n return {\n kind: \"any\"\n };\n }\n if (\n record.kind ===\n \"finite-distance-range\"\n ) {\n return {\n kind:\n \"finite-distance-range\",\n objectDistanceM:\n parseRange(\n record.objectDistanceM,\n path +\n \".objectDistanceM\"\n )\n };\n }\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\n/**\n * Validates an untrusted scene-to-sensor profile and returns the normalized typed contract. Unknown\n * enum values, missing required fields and incompatible scientific data are rejected here.\n *\n * The optical bridge turns outgoing scene radiance (W/m²/sr/nm) into irradiance (W/m²/nm) arriving\n * at the front of the sensor stack.\n * - Circular paraxial acceptance is pi/(4*Nworking²). At finite focus with unit pupil magnification,\n * the working f-number is N*(1+m).\n * - Spectral transmission and an effective working T-stop are alternative paths; use one.\n * - Field throughput is applied exactly once.\n * - Sensor response and PSF (point-spread function) blur happen later in the pipeline.\n * @param value - unknown. Untrusted input; TypeScript types alone are not validation.\n * @returns SceneToSensorIrradianceProfile. The result states its shape and scientific status; a successful call does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the root export and exact type graph.\n */\nexport function parseSceneToSensorIrradianceProfile(\n value: unknown\n): SceneToSensorIrradianceProfile {\n const record =\n requireRecord(\n value,\n \"sceneToSensorIrradianceProfile\", [\"schemaVersion\", \"applicability\", \"scientificStatus\", \"transmission\", \"distortionAreaMappingOwnership\", \"psfRedistributionOwnership\", \"sensorOpticalStackIncluded\", \"strayLightIncluded\", \"polarizationModeled\", \"wavelengthChangingBehaviorModeled\", \"volumetricScatteringModeled\", \"profileId\", \"profileVersion\", \"lensProfileId\", \"evidence\", \"limitations\"]\n );\n if (\n record.schemaVersion !==\n SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n 'sceneToSensorIrradianceProfile.schemaVersion must be \"' +\n SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION +\n '\".'\n );\n }\n\n const applicability =\n requireRecord(\n record.applicability,\n \"sceneToSensorIrradianceProfile.applicability\", [\"focalLengthMm\", \"nominalFNumber\", \"focus\"]\n );\n const status =\n parseScientificStatus(\n record.scientificStatus,\n \"sceneToSensorIrradianceProfile.scientificStatus\"\n );\n const transmission =\n parseTransmission(\n record.transmission,\n \"sceneToSensorIrradianceProfile.transmission\"\n );\n\n if (\n status === \"calibrated\" &&\n transmission.scientificStatus !==\n \"calibrated\"\n ) {\n throw new InvalidConfigurationError(\n \"A calibrated optical bridge profile requires calibrated spectral transmission.\"\n );\n }\n\n if (\n record.distortionAreaMappingOwnership !==\n \"not-applied-by-bridge\" ||\n record.psfRedistributionOwnership !==\n \"downstream-normalized-energy-redistribution\" ||\n record.sensorOpticalStackIncluded !==\n false ||\n record.strayLightIncluded !==\n false ||\n record.polarizationModeled !==\n false ||\n record.wavelengthChangingBehaviorModeled !==\n false ||\n record.volumetricScatteringModeled !==\n false\n ) {\n throw new InvalidConfigurationError(\n \"sceneToSensorIrradianceProfile ownership/completeness flags are invalid for schema 0.1.0.\"\n );\n }\n\n return {\n schemaVersion:\n SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION,\n profileId:\n requireNonEmptyString(\n record.profileId,\n \"sceneToSensorIrradianceProfile.profileId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n \"sceneToSensorIrradianceProfile.profileVersion\"\n ),\n lensProfileId:\n requireNonEmptyString(\n record.lensProfileId,\n \"sceneToSensorIrradianceProfile.lensProfileId\"\n ),\n scientificStatus: status,\n applicability: {\n focalLengthMm:\n parseRange(\n applicability.focalLengthMm,\n \"sceneToSensorIrradianceProfile.applicability.focalLengthMm\"\n ),\n nominalFNumber:\n parseRange(\n applicability.nominalFNumber,\n \"sceneToSensorIrradianceProfile.applicability.nominalFNumber\"\n ),\n focus:\n parseFocusApplicability(\n applicability.focus,\n \"sceneToSensorIrradianceProfile.applicability.focus\"\n )\n },\n transmission,\n distortionAreaMappingOwnership:\n \"not-applied-by-bridge\",\n psfRedistributionOwnership:\n \"downstream-normalized-energy-redistribution\",\n sensorOpticalStackIncluded:\n false,\n strayLightIncluded: false,\n polarizationModeled: false,\n wavelengthChangingBehaviorModeled:\n false,\n volumetricScatteringModeled:\n false,\n evidence:\n parseEvidenceList(\n record.evidence,\n \"sceneToSensorIrradianceProfile.evidence\"\n ),\n limitations:\n parseLimitations(\n record.limitations,\n \"sceneToSensorIrradianceProfile.limitations\"\n )\n };\n}\n\nfunction ensureWithin(\n value: number,\n range: NumericRange,\n path: string\n): void {\n if (\n value < range.minimum ||\n value > range.maximum\n ) {\n throw new InvalidScientificInputError(\n path +\n \" lies outside the optical profile applicability range.\"\n );\n }\n}\n\n/**\n * Internal (2.6; not exported from the package): N_w / N = 1 + m for an ideal symmetric thin lens with unity pupil\n * magnification focused at `objectDistanceM`, m = v/s from `calculateThinLensImageDistance`. The irradiance bridge's\n * `ideal-symmetric-thin-lens` focus uses exactly this factor, and the framing reports derive their close-up light\n * factor 1/(1 + m)² from it, so the two cannot drift apart.\n */\nexport function thinLensWorkingFNumberFactor(focalLengthMm: number, objectDistanceM: number): number {\n return 1 + calculateThinLensImageDistance({ focalLengthMm, objectDistanceM }).value.magnification;\n}\n\nfunction workingFNumber(\n focus:\n OpticalBridgeFocusContext,\n focalLengthMm: number,\n nominalFNumber: number\n): {\n value: number;\n evidence:\n readonly EvidenceProvenance[];\n} {\n if (\n focus.kind === \"infinity-focus\"\n ) {\n return {\n value: nominalFNumber,\n evidence: []\n };\n }\n\n if (\n focus.kind ===\n \"ideal-symmetric-thin-lens\"\n ) {\n if (\n focus\n .pupilMagnificationAssumption !==\n \"unity\"\n ) {\n throw new InvalidScientificInputError(\n 'Ideal symmetric thin-lens focus requires pupilMagnificationAssumption \"unity\".'\n );\n }\n return {\n value:\n nominalFNumber *\n thinLensWorkingFNumberFactor(focalLengthMm, focus.objectDistanceM),\n evidence: []\n };\n }\n\n if (\n focus.kind ===\n \"supplied-working-f-number\"\n ) {\n const value =\n requirePositiveFiniteScientific(\n focus.workingFNumber.value,\n \"focus.workingFNumber.value\"\n );\n return {\n value,\n evidence:\n parseEvidenceList(\n focus.workingFNumber.evidence,\n \"focus.workingFNumber.evidence\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n \"focus.kind is invalid.\"\n );\n}\n\nfunction finiteFocusDistance(\n focus:\n OpticalBridgeFocusContext\n): number | null {\n if (\n focus.kind ===\n \"ideal-symmetric-thin-lens\"\n ) {\n return requirePositiveFiniteScientific(\n focus.objectDistanceM,\n \"focus.objectDistanceM\"\n );\n }\n if (\n focus.kind ===\n \"supplied-working-f-number\" &&\n focus.focus.kind === \"finite\"\n ) {\n return requirePositiveFiniteScientific(\n focus.focus.objectDistanceM,\n \"focus.focus.objectDistanceM\"\n );\n }\n return null;\n}\n\nfunction interpolateTransmission(\n model:\n Extract<\n OpticalTransmissionModel,\n { kind: \"spectral-transmission\" }\n >,\n wavelengthNanometers: number\n): number {\n const samples =\n model.samples.value;\n const first =\n samples[0]!;\n const last =\n samples[\n samples.length - 1\n ]!;\n\n if (\n wavelengthNanometers <\n first.wavelengthNanometers ||\n wavelengthNanometers >\n last.wavelengthNanometers\n ) {\n throw new InvalidScientificInputError(\n \"Scene wavelength lies outside the spectral transmission sample range.\"\n );\n }\n\n if (\n wavelengthNanometers ===\n first.wavelengthNanometers\n ) {\n return first\n .linearTransmissionFactor;\n }\n\n for (\n let index = 1;\n index < samples.length;\n index += 1\n ) {\n const right =\n samples[index]!;\n if (\n wavelengthNanometers <=\n right.wavelengthNanometers\n ) {\n const left =\n samples[index - 1]!;\n if (\n wavelengthNanometers ===\n right.wavelengthNanometers\n ) {\n return right\n .linearTransmissionFactor;\n }\n const phase =\n (wavelengthNanometers -\n left.wavelengthNanometers) /\n (right.wavelengthNanometers -\n left.wavelengthNanometers);\n return (\n left\n .linearTransmissionFactor +\n phase *\n (right\n .linearTransmissionFactor -\n left\n .linearTransmissionFactor)\n );\n }\n }\n\n return last\n .linearTransmissionFactor;\n}\n\nfunction fieldFactor(\n field:\n OpticalBridgeFieldThroughput,\n imagePointMm: {\n x: number;\n y: number;\n }\n): number {\n if (field.kind === \"unity\") {\n return 1;\n }\n if (\n field.kind ===\n \"illumination-vignetting-result\"\n ) {\n const result =\n field.result;\n const scale = Math.max(\n 1,\n Math.abs(imagePointMm.x),\n Math.abs(imagePointMm.y),\n Math.abs(result.imagePointMm.x),\n Math.abs(result.imagePointMm.y)\n );\n const tolerance =\n Number.EPSILON *\n 64 *\n scale;\n if (\n Math.abs(\n result.imagePointMm.x -\n imagePointMm.x\n ) > tolerance ||\n Math.abs(\n result.imagePointMm.y -\n imagePointMm.y\n ) > tolerance\n ) {\n throw new InvalidScientificInputError(\n \"Illumination-vignetting result imagePointMm must match the optical bridge imagePointMm.\"\n );\n }\n const factor =\n result.linearThroughputFactor;\n if (\n !Number.isFinite(factor) ||\n factor <= 0 ||\n factor > 1\n ) {\n throw new InvalidScientificInputError(\n \"Field throughput factor must be finite and in (0, 1].\"\n );\n }\n return factor;\n }\n throw new InvalidConfigurationError(\n \"fieldThroughput.kind is invalid.\"\n );\n}\n\nfunction validateSceneIdentity(\n request:\n SceneRadianceEvaluationRequest,\n result:\n SceneRadianceEvaluationResult\n): void {\n if (\n request.sampleId !==\n result.sampleId ||\n request.providerProfileId !==\n result.providerProfileId ||\n request.sceneId !==\n result.sceneId ||\n request.wavelengthNanometers !==\n result.wavelengthNanometers ||\n request.wavelengthBasis !==\n result.wavelengthBasis\n ) {\n throw new InvalidScientificInputError(\n \"Scene-radiance request/result identity must match exactly.\"\n );\n }\n}\n\n/**\n * The request-independent part of {@link calculateSceneRadianceToSensorIrradiance}: the profile, focal length, f-number,\n * applicability, focus and working f-number checks and the paraxial acceptance, in the public function's order.\n */\ninterface PreparedSceneRadianceToSensorIrradiance {\n profile: ReturnType<typeof parseSceneToSensorIrradianceProfile>;\n focalLengthMm: number;\n nominalFNumber: number;\n working: ReturnType<typeof workingFNumber>;\n workingFNumberValue: number;\n geometricAcceptance: number;\n transmission: ReturnType<typeof parseSceneToSensorIrradianceProfile>[\"transmission\"];\n}\n\nfunction prepareSceneRadianceToSensorIrradiance(\n input: Pick<CalculateSceneRadianceToSensorIrradianceInput, \"profile\" | \"focalLengthMm\" | \"nominalFNumber\" | \"focus\">\n): PreparedSceneRadianceToSensorIrradiance {\n const profile =\n parseSceneToSensorIrradianceProfile(\n input.profile\n );\n const focalLengthMm =\n requirePositiveFiniteScientific(\n input.focalLengthMm,\n \"focalLengthMm\"\n );\n const nominalFNumber =\n requirePositiveFiniteScientific(\n input.nominalFNumber,\n \"nominalFNumber\"\n );\n\n ensureWithin(\n focalLengthMm,\n profile.applicability\n .focalLengthMm,\n \"focalLengthMm\"\n );\n ensureWithin(\n nominalFNumber,\n profile.applicability\n .nominalFNumber,\n \"nominalFNumber\"\n );\n\n const focusDistanceM =\n finiteFocusDistance(input.focus);\n if (\n profile.applicability.focus.kind ===\n \"finite-distance-range\"\n ) {\n if (focusDistanceM === null) {\n throw new InvalidScientificInputError(\n \"Optical profile requires a finite focus distance.\"\n );\n }\n ensureWithin(\n focusDistanceM,\n profile.applicability\n .focus.objectDistanceM,\n \"focus object distance\"\n );\n }\n\n const working =\n workingFNumber(\n input.focus,\n focalLengthMm,\n nominalFNumber\n );\n const workingFNumberValue =\n working.value;\n const geometricAcceptance =\n Math.PI /\n (4 *\n workingFNumberValue *\n workingFNumberValue);\n\n if (\n !Number.isFinite(\n geometricAcceptance\n ) ||\n geometricAcceptance <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Paraxial geometric acceptance must remain finite and greater than zero.\"\n );\n }\n\n const transmission =\n profile.transmission;\n return { profile, focalLengthMm, nominalFNumber, working, workingFNumberValue, geometricAcceptance, transmission };\n}\n\ninterface AppliedSceneRadianceToSensorIrradiance {\n spectralTransmissionFactor: number | null;\n effectiveWorkingTStop: number | null;\n tStopEquivalentTransmissionFactor: number | null;\n effectiveAcceptance: number;\n transmissionEvidence: readonly EvidenceProvenance[];\n frontOfLensFilterCount: number;\n frontOfLensFilterPolarizationModeled: boolean;\n frontOfLensFilterTransmissionFactor: number;\n frontOfLensFilterAttenuationStops: number;\n frontOfLensFilterEvidence: readonly EvidenceProvenance[];\n frontOfLensFilterLimitations: readonly string[];\n fieldThroughputFactor: number;\n sceneRadiance: number;\n irradiance: number;\n}\n\n/**\n * The per-query part of {@link calculateSceneRadianceToSensorIrradiance}, after the request and result are parsed and their\n * identities matched: the wavelength-basis check, the transmission or T-stop path, the front-of-lens filters, the field\n * throughput and the irradiance, with the same operations in the same order.\n */\nfunction applySceneRadianceToSensorIrradiance(\n prepared: PreparedSceneRadianceToSensorIrradiance,\n input: Pick<CalculateSceneRadianceToSensorIrradianceInput, \"imagePointMm\" | \"fieldThroughput\"> &\n { frontOfLensFilters?: readonly FrontOfLensFilterProfile[] | undefined },\n request: SceneRadianceEvaluationRequest,\n sceneResult: SceneRadianceEvaluationResult\n): AppliedSceneRadianceToSensorIrradiance {\n const { workingFNumberValue, geometricAcceptance, transmission } = prepared;\n if (\n request.wavelengthBasis !==\n transmission.wavelengthBasis\n ) {\n throw new InvalidScientificInputError(\n \"Scene-radiance and optical-transmission wavelength bases must match; implicit air/vacuum conversion is not performed.\"\n );\n }\n\n let spectralTransmissionFactor:\n number | null = null;\n let effectiveWorkingTStop:\n number | null = null;\n let tStopEquivalentTransmissionFactor:\n number | null = null;\n let effectiveAcceptance =\n geometricAcceptance;\n let transmissionEvidence:\n readonly EvidenceProvenance[];\n\n if (\n transmission.kind ===\n \"spectral-transmission\"\n ) {\n spectralTransmissionFactor =\n interpolateTransmission(\n transmission,\n request.wavelengthNanometers\n );\n effectiveAcceptance *=\n spectralTransmissionFactor;\n transmissionEvidence =\n transmission.samples.evidence;\n } else {\n ensureWithin(\n request.wavelengthNanometers,\n transmission\n .wavelengthRangeNanometers,\n \"scene wavelength\"\n );\n effectiveWorkingTStop =\n transmission.workingTStop.value;\n if (\n effectiveWorkingTStop <\n workingFNumberValue\n ) {\n throw new InvalidScientificInputError(\n \"effective working T-stop must not be smaller than the geometric working f-number for a passive optical approximation.\"\n );\n }\n tStopEquivalentTransmissionFactor =\n (workingFNumberValue /\n effectiveWorkingTStop) **\n 2;\n effectiveAcceptance =\n Math.PI /\n (4 *\n effectiveWorkingTStop *\n effectiveWorkingTStop);\n transmissionEvidence =\n transmission\n .workingTStop.evidence;\n }\n\n let frontOfLensFilterCount = 0;\n let frontOfLensFilterPolarizationModeled = false;\n let frontOfLensFilterTransmissionFactor = 1;\n let frontOfLensFilterAttenuationStops = 0;\n let frontOfLensFilterEvidence:\n readonly EvidenceProvenance[] = [];\n let frontOfLensFilterLimitations:\n readonly string[] = [];\n\n if (\n input.frontOfLensFilters !==\n undefined\n ) {\n const composed =\n composeFrontOfLensFilterTransmission({\n filters:\n input.frontOfLensFilters,\n wavelengthNanometers:\n request.wavelengthNanometers,\n wavelengthBasis:\n request.wavelengthBasis\n });\n frontOfLensFilterCount =\n composed.appliedFilterCount;\n frontOfLensFilterPolarizationModeled =\n composed.polarizationModeled;\n frontOfLensFilterTransmissionFactor =\n composed\n .combinedLinearTransmissionFactor;\n frontOfLensFilterAttenuationStops =\n composed\n .combinedAttenuationStops;\n frontOfLensFilterEvidence =\n composed.components.flatMap(\n (component) =>\n component.evidence\n );\n frontOfLensFilterLimitations =\n composed.components.flatMap(\n (component) =>\n component.limitations\n );\n effectiveAcceptance *=\n frontOfLensFilterTransmissionFactor;\n }\n\n const fieldThroughputFactor =\n fieldFactor(\n input.fieldThroughput,\n input.imagePointMm\n );\n effectiveAcceptance *=\n fieldThroughputFactor;\n\n const sceneRadiance =\n sceneResult\n .spectralRadianceWattsPerSquareMeterSteradianNanometer;\n const irradiance =\n sceneRadiance *\n effectiveAcceptance;\n\n if (\n !Number.isFinite(irradiance) ||\n irradiance < 0\n ) {\n throw new InvalidScientificInputError(\n \"Sensor-plane spectral irradiance must remain finite and non-negative.\"\n );\n }\n\n return { spectralTransmissionFactor, effectiveWorkingTStop, tStopEquivalentTransmissionFactor, effectiveAcceptance, transmissionEvidence,\n frontOfLensFilterCount, frontOfLensFilterPolarizationModeled, frontOfLensFilterTransmissionFactor, frontOfLensFilterAttenuationStops,\n frontOfLensFilterEvidence, frontOfLensFilterLimitations, fieldThroughputFactor, sceneRadiance, irradiance };\n}\n\n/**\n * Internal (#453; not exported from the package): the sensor-plane spectral irradiance that\n * {@link calculateSceneRadianceToSensorIrradiance} returns, for repeated queries of one optical state. This call runs the\n * request-independent checks once (the public function's own code, in its order); each call of the returned function then\n * runs the public function's per-query steps on an engine-built request and a result already parsed by the caller: the identity\n * match, the wavelength-basis check, the transmission or T-stop path, the front-of-lens filters, the field throughput and\n * the finite, non-negative irradiance, with the same operations, order and errors. It skips only the re-validation of the\n * unchanged optical inputs, the re-parse of the request and result, and the result envelope.\n */\nexport function prepareSceneRadianceToSensorIrradianceValue(\n optics: Pick<CalculateSceneRadianceToSensorIrradianceInput, \"profile\" | \"focalLengthMm\" | \"nominalFNumber\" | \"focus\" | \"frontOfLensFilters\">\n): (request: SceneRadianceEvaluationRequest, result: SceneRadianceEvaluationResult, imagePointMm: { x: number; y: number },\n fieldThroughput: OpticalBridgeFieldThroughput) => number {\n const prepared = prepareSceneRadianceToSensorIrradiance(optics);\n const frontOfLensFilters = optics.frontOfLensFilters;\n return (request, result, imagePointMm, fieldThroughput) => {\n validateSceneIdentity(request, result);\n return applySceneRadianceToSensorIrradiance(prepared, { imagePointMm, fieldThroughput, frontOfLensFilters }, request, result).irradiance;\n };\n}\n\n/**\n * Applies the declared pupil acceptance and exactly one transmission or field-throughput path to\n * spectral scene radiance. Returns the irradiance before the sensor stack, with conservative\n * evidence.\n *\n * The optical bridge turns outgoing scene radiance (W/m²/sr/nm) into irradiance (W/m²/nm) arriving\n * at the front of the sensor stack.\n * - Circular paraxial acceptance is pi/(4*Nworking²). At finite focus with unit pupil magnification,\n * the working f-number is N*(1+m).\n * - Spectral transmission and an effective working T-stop are alternative paths; use one.\n * - Field throughput is applied exactly once.\n * - Sensor response and PSF (point-spread function) blur happen later in the pipeline.\n * @param input - CalculateSceneRadianceToSensorIrradianceInput. See that type for coordinates, units and profile binding.\n * @returns CalculationResult<\n SceneToSensorIrradianceResult\n>. The result states its shape and scientific status; a successful call does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the root export and exact type graph.\n */\nexport function calculateSceneRadianceToSensorIrradiance(\n input:\n CalculateSceneRadianceToSensorIrradianceInput\n): CalculationResult<\n SceneToSensorIrradianceResult\n> {\n requireInputKeys(input, \"calculateSceneRadianceToSensorIrradiance\", [\"sceneRadianceRequest\", \"sceneRadianceResult\", \"profile\", \"focalLengthMm\", \"nominalFNumber\", \"focus\", \"imagePointMm\", \"fieldThroughput\", \"frontOfLensFilters\"]);\n const request =\n parseSceneRadianceEvaluationRequest(\n input.sceneRadianceRequest\n );\n const sceneResult =\n parseSceneRadianceEvaluationResult(\n input.sceneRadianceResult\n );\n validateSceneIdentity(\n request,\n sceneResult\n );\n\n const prepared = prepareSceneRadianceToSensorIrradiance(input);\n const { profile, focalLengthMm, nominalFNumber, working, workingFNumberValue, geometricAcceptance, transmission } = prepared;\n const { spectralTransmissionFactor, effectiveWorkingTStop, tStopEquivalentTransmissionFactor, effectiveAcceptance, transmissionEvidence,\n frontOfLensFilterCount, frontOfLensFilterPolarizationModeled, frontOfLensFilterTransmissionFactor, frontOfLensFilterAttenuationStops,\n frontOfLensFilterEvidence, frontOfLensFilterLimitations, fieldThroughputFactor, sceneRadiance, irradiance } =\n applySceneRadianceToSensorIrradiance(prepared, input, request, sceneResult);\n\n const limitations = [\n ...profile.limitations,\n ...frontOfLensFilterLimitations,\n \"Front-of-lens filter transmission is applied outside the lens throughput path exactly once and does not change geometric working f-number.\",\n \"Paraxial circular-pupil acceptance uses pi/(4*N_working^2); no universal cos^4 field falloff is added.\",\n \"Field throughput is supplied separately and applied exactly once.\",\n \"Geometric distortion area-density/Jacobian correction is not applied by this bridge.\",\n \"PSF/diffraction energy redistribution is downstream and must preserve normalized energy apart from separately owned throughput losses.\",\n \"Sensor optical stack, microlens, CFA, QE/responsivity and charge conversion are downstream.\",\n frontOfLensFilterPolarizationModeled\n ? \"Flare, ghosting, veiling glare, scene-radiance polarization (scene light is treated as unpolarized; the front-of-lens linear polarizer transmits (k1 + k2) / 2), volumetric scattering and wavelength-changing behavior are not modeled.\"\n : \"Flare, ghosting, veiling glare, polarization, volumetric scattering and wavelength-changing behavior are not modeled.\"\n ];\n\n return approximationResult(\n {\n schemaVersion:\n SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION,\n profileId: profile.profileId,\n profileVersion:\n profile.profileVersion,\n lensProfileId:\n profile.lensProfileId,\n sampleId: request.sampleId,\n providerProfileId:\n request.providerProfileId,\n sceneId: request.sceneId,\n timeSecondsFromExposureStart:\n request\n .timeSecondsFromExposureStart,\n wavelengthNanometers:\n request.wavelengthNanometers,\n wavelengthBasis:\n request.wavelengthBasis,\n inputQuantity:\n \"outgoing-spectral-radiance\",\n inputUnit:\n \"W/m^2/sr/nm\",\n outputQuantity:\n \"sensor-plane-spectral-irradiance\",\n outputUnit: \"W/m^2/nm\",\n sceneSpectralRadianceWattsPerSquareMeterSteradianNanometer:\n sceneRadiance,\n focalLengthMm,\n nominalFNumber,\n workingFNumber:\n workingFNumberValue,\n focusModel: input.focus.kind,\n paraxialGeometricAcceptanceSolidAngleSteradians:\n geometricAcceptance,\n transmissionPath:\n transmission.kind,\n spectralTransmissionFactor,\n effectiveWorkingTStop,\n tStopEquivalentTransmissionFactor,\n frontOfLensFilterCount,\n frontOfLensFilterTransmissionFactor,\n frontOfLensFilterAttenuationStops,\n frontOfLensFilterTransmissionAppliedExactlyOnce:\n true,\n frontOfLensFilterPolarizationModeled,\n fieldThroughputFactor,\n effectiveAcceptanceAfterTransmissionAndFieldSteradians:\n effectiveAcceptance,\n sensorPlaneSpectralIrradianceWattsPerSquareMeterNanometer:\n irradiance,\n scientificStatus:\n \"approximation\",\n sceneRadianceScientificStatus:\n sceneResult.scientificStatus,\n opticalProfileScientificStatus:\n profile.scientificStatus,\n sceneRadianceUncertainty:\n sceneResult.uncertainty,\n opticalUncertainty:\n transmission.uncertainty,\n radianceAmplificationApplied:\n false,\n fieldThroughputAppliedExactlyOnce:\n true,\n universalCos4FalloffApplied:\n false,\n distortionAreaCorrectionApplied:\n false,\n psfRedistributionApplied:\n false,\n sensorOpticalStackApplied:\n false,\n cfaApplied: false,\n quantumEfficiencyApplied: false,\n spectralResponsivityApplied:\n false,\n strayLightApplied: false,\n polarizationModeled: false,\n wavelengthChangingBehaviorModeled:\n false,\n volumetricScatteringModeled:\n false,\n calibratedSensorPlaneIrradianceClaimAuthorized:\n false,\n componentEvidence: {\n sceneRadiance:\n sceneResult.evidence,\n opticalProfile:\n profile.evidence,\n transmission:\n transmissionEvidence,\n workingFNumber:\n working.evidence,\n frontOfLensFilters:\n frontOfLensFilterEvidence\n },\n limitations\n },\n \"scene-radiance-to-sensor-irradiance\",\n \"1.1.0\",\n limitations\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scene-to-sensor-irradiance.js","sourceRoot":"","sources":["../../src/optics/scene-to-sensor-irradiance.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Scene-to-sensor optical bridge: from spectral scene radiance to the irradiance that reaches the\n * sensor.\n *\n * The profile parser validates untrusted input and returns the normalized typed contract. Unknown\n * enum values, missing required fields and incompatible scientific data are rejected here.\n *\n * The calculation applies the declared pupil acceptance and exactly one transmission or\n * field-throughput path. It returns the irradiance before the sensor stack, with conservative\n * evidence.\n *\n * The optical bridge turns outgoing scene radiance (W/m²/sr/nm) into irradiance (W/m²/nm) arriving\n * at the front of the sensor stack.\n * - Circular paraxial acceptance is pi/(4*Nworking²). At finite focus with unit pupil magnification,\n * the working f-number is N*(1+m).\n * - Spectral transmission and an effective working T-stop are alternative paths; use one.\n * - Field throughput is applied exactly once.\n * - Sensor response and PSF (point-spread function) blur happen later in the pipeline.\n * @see docs/SCENE_SENSOR_QUADRATURE.md for the equations, units, coordinate conventions, known\n * blockers and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { type SpectralWavelengthBasis } from \"../core/spectral.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { composeFrontOfLensFilterTransmission, type FrontOfLensFilterProfile, parseRange, parseResolvedBasis } from \"./front-of-lens-filter.js\";\nimport type { IlluminationVignetting } from \"./illumination-vignetting.js\";\nimport { calculateThinLensImageDistance } from \"./thin-lens.js\";\nimport {\n parseSceneRadianceEvaluationRequest,\n parseSceneRadianceEvaluationResult,\n type SceneRadianceEvaluationRequest,\n type SceneRadianceEvaluationResult\n} from \"../schema/scene-radiance.js\";\nimport { requireInputKeys, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, parseConfigScientificStatus as parseScientificStatus, requireInputPositiveFinite as requirePositiveFiniteScientific, parseConfigLimitations as parseLimitations, requireConfigFiniteAboveZero as requirePositiveFinite } from \"../core/record-validation.js\";\nimport { requireFraction } from \"../color/white-balance.js\";\n\n/**\n * The scope fields every engine-built scene-to-sensor profile declares, in this order (shared by the capture facade and the\n * generic tier presets): the bridge leaves distortion area mapping and PSF redistribution downstream and models none of the rest.\n */\nexport const SCENE_TO_SENSOR_BRIDGE_SCOPE = /* @__PURE__ */ Object.freeze({ distortionAreaMappingOwnership: \"not-applied-by-bridge\" as const,\n psfRedistributionOwnership: \"downstream-normalized-energy-redistribution\" as const, sensorOpticalStackIncluded: false as const, strayLightIncluded: false as const,\n polarizationModeled: false as const, wavelengthChangingBehaviorModeled: false as const, volumetricScatteringModeled: false as const });\n\nexport const SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION =\n \"0.1.0\" as const;\n\nexport type OpticalBridgeScientificStatus =\n | \"calibrated\"\n | \"approximation\";\n\nexport type OpticalBridgeUncertainty =\n | {\n kind: \"relative\";\n fraction: number;\n basis: string;\n }\n | {\n kind: \"not-quantified\";\n limitation: string;\n };\n\nexport interface NumericRange {\n minimum: number;\n maximum: number;\n}\n\nexport type OpticalBridgeFocusApplicability =\n | {\n kind: \"any\";\n }\n | {\n kind: \"finite-distance-range\";\n objectDistanceM: NumericRange;\n };\n\nexport interface SpectralTransmissionSample {\n wavelengthNanometers: number;\n linearTransmissionFactor: number;\n}\n\nexport type OpticalTransmissionModel =\n | {\n kind: \"spectral-transmission\";\n scientificStatus:\n OpticalBridgeScientificStatus;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n samples:\n EvidenceBackedFact<\n readonly SpectralTransmissionSample[]\n >;\n uncertainty:\n OpticalBridgeUncertainty;\n }\n | {\n kind:\n \"effective-working-t-stop-approximation\";\n scientificStatus: \"approximation\";\n workingTStop:\n EvidenceBackedFact<number>;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n wavelengthRangeNanometers:\n NumericRange;\n uncertainty:\n OpticalBridgeUncertainty;\n limitation: string;\n };\n\nexport interface SceneToSensorIrradianceProfile {\n schemaVersion:\n typeof SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n lensProfileId: string;\n scientificStatus:\n OpticalBridgeScientificStatus;\n applicability: {\n focalLengthMm: NumericRange;\n nominalFNumber: NumericRange;\n focus:\n OpticalBridgeFocusApplicability;\n };\n transmission:\n OpticalTransmissionModel;\n distortionAreaMappingOwnership:\n \"not-applied-by-bridge\";\n psfRedistributionOwnership:\n \"downstream-normalized-energy-redistribution\";\n sensorOpticalStackIncluded: false;\n strayLightIncluded: false;\n polarizationModeled: false;\n wavelengthChangingBehaviorModeled: false;\n volumetricScatteringModeled: false;\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\nexport type OpticalBridgeFocusContext =\n | {\n kind: \"infinity-focus\";\n }\n | {\n kind:\n \"ideal-symmetric-thin-lens\";\n objectDistanceM: number;\n pupilMagnificationAssumption:\n \"unity\";\n }\n | {\n kind:\n \"supplied-working-f-number\";\n focus:\n | {\n kind: \"infinity\";\n }\n | {\n kind: \"finite\";\n objectDistanceM: number;\n };\n workingFNumber:\n EvidenceBackedFact<number>;\n basis: string;\n };\n\nexport type OpticalBridgeFieldThroughput =\n | {\n kind: \"unity\";\n }\n | {\n kind:\n \"illumination-vignetting-result\";\n result:\n IlluminationVignetting;\n };\n\nexport interface CalculateSceneRadianceToSensorIrradianceInput {\n sceneRadianceRequest:\n SceneRadianceEvaluationRequest;\n sceneRadianceResult:\n SceneRadianceEvaluationResult;\n profile:\n SceneToSensorIrradianceProfile;\n focalLengthMm: number;\n nominalFNumber: number;\n focus:\n OpticalBridgeFocusContext;\n imagePointMm: {\n x: number;\n y: number;\n };\n fieldThroughput:\n OpticalBridgeFieldThroughput;\n frontOfLensFilters?:\n readonly FrontOfLensFilterProfile[];\n}\n\nexport interface SceneToSensorIrradianceResult {\n schemaVersion:\n typeof SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n lensProfileId: string;\n sampleId: string;\n providerProfileId: string;\n sceneId: string;\n timeSecondsFromExposureStart: number;\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n inputQuantity:\n \"outgoing-spectral-radiance\";\n inputUnit:\n \"W/m^2/sr/nm\";\n outputQuantity:\n \"sensor-plane-spectral-irradiance\";\n outputUnit:\n \"W/m^2/nm\";\n sceneSpectralRadianceWattsPerSquareMeterSteradianNanometer:\n number;\n focalLengthMm: number;\n nominalFNumber: number;\n workingFNumber: number;\n focusModel:\n OpticalBridgeFocusContext[\"kind\"];\n paraxialGeometricAcceptanceSolidAngleSteradians:\n number;\n transmissionPath:\n OpticalTransmissionModel[\"kind\"];\n spectralTransmissionFactor:\n number | null;\n effectiveWorkingTStop:\n number | null;\n tStopEquivalentTransmissionFactor:\n number | null;\n frontOfLensFilterCount: number;\n frontOfLensFilterTransmissionFactor:\n number;\n frontOfLensFilterAttenuationStops:\n number;\n frontOfLensFilterTransmissionAppliedExactlyOnce:\n true;\n /**\n * True when a front-of-lens linear polarizer was applied. Scene radiance is unpolarized in this\n * bridge, so a polarizer transmits (k1 + k2) / 2.\n */\n frontOfLensFilterPolarizationModeled:\n boolean;\n fieldThroughputFactor: number;\n effectiveAcceptanceAfterTransmissionAndFieldSteradians:\n number;\n sensorPlaneSpectralIrradianceWattsPerSquareMeterNanometer:\n number;\n scientificStatus: \"approximation\";\n sceneRadianceScientificStatus:\n SceneRadianceEvaluationResult[\"scientificStatus\"];\n opticalProfileScientificStatus:\n OpticalBridgeScientificStatus;\n sceneRadianceUncertainty:\n SceneRadianceEvaluationResult[\"uncertainty\"];\n opticalUncertainty:\n OpticalBridgeUncertainty;\n radianceAmplificationApplied: false;\n fieldThroughputAppliedExactlyOnce:\n true;\n universalCos4FalloffApplied: false;\n distortionAreaCorrectionApplied: false;\n psfRedistributionApplied: false;\n sensorOpticalStackApplied: false;\n cfaApplied: false;\n quantumEfficiencyApplied: false;\n spectralResponsivityApplied: false;\n strayLightApplied: false;\n polarizationModeled: false;\n wavelengthChangingBehaviorModeled:\n false;\n volumetricScatteringModeled: false;\n calibratedSensorPlaneIrradianceClaimAuthorized:\n false;\n componentEvidence: {\n sceneRadiance:\n readonly EvidenceProvenance[];\n opticalProfile:\n readonly EvidenceProvenance[];\n transmission:\n readonly EvidenceProvenance[];\n workingFNumber:\n readonly EvidenceProvenance[];\n frontOfLensFilters:\n readonly EvidenceProvenance[];\n };\n limitations:\n readonly string[];\n}\n\nfunction parseUncertainty(\n value: unknown,\n path: string\n): OpticalBridgeUncertainty {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", UNCERTAINTY_VARIANT_FIELDS, path);\n\n if (record.kind === \"relative\") {\n return {\n kind: \"relative\",\n fraction:\n requireFraction(\n record.fraction,\n path + \".fraction\"\n ),\n basis:\n requireNonEmptyString(\n record.basis,\n path + \".basis\"\n )\n };\n }\n\n if (\n record.kind ===\n \"not-quantified\"\n ) {\n return {\n kind: \"not-quantified\",\n limitation:\n requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\nfunction parseReusableNumericEvidence(\n value: unknown,\n path: string\n): readonly EvidenceProvenance[] {\n const evidence =\n parseEvidenceList(value, path);\n\n if (\n evidence.some(\n (entry) =>\n entry.reuseStatus ===\n \"factual-reference-only\"\n )\n ) {\n throw new InvalidConfigurationError(\n path +\n \" for numeric optical curve data must be reusable-data or photivra-owned.\"\n );\n }\n return evidence;\n}\n\nfunction parseSpectralSamples(\n value: unknown,\n path: string\n): readonly SpectralTransmissionSample[] {\n if (\n !Array.isArray(value) ||\n value.length < 2\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must contain at least two samples.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n return value.map(\n (entry, index) => {\n const samplePath =\n path +\n \"[\" +\n index +\n \"]\";\n const record =\n requireRecord(\n entry,\n samplePath, [\"wavelengthNanometers\", \"linearTransmissionFactor\"]\n );\n const wavelengthNanometers =\n requirePositiveFinite(\n record.wavelengthNanometers,\n samplePath +\n \".wavelengthNanometers\"\n );\n if (\n wavelengthNanometers <=\n previous\n ) {\n throw new InvalidConfigurationError(\n path +\n \" wavelengths must be strictly increasing.\"\n );\n }\n previous =\n wavelengthNanometers;\n return {\n wavelengthNanometers,\n linearTransmissionFactor:\n requireFraction(\n record.linearTransmissionFactor,\n samplePath +\n \".linearTransmissionFactor\"\n )\n };\n }\n );\n}\n\nfunction parseTransmission(\n value: unknown,\n path: string\n): OpticalTransmissionModel {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", {\n // workingTStop and wavelengthRangeNanometers (spectral) and samples (working T-stop) are rejected below with their own messages.\n \"spectral-transmission\": [\"kind\", \"scientificStatus\", \"wavelengthBasis\", \"samples\", \"uncertainty\", \"workingTStop\", \"wavelengthRangeNanometers\"],\n \"effective-working-t-stop-approximation\": [\"kind\", \"scientificStatus\", \"workingTStop\", \"wavelengthBasis\", \"wavelengthRangeNanometers\",\n \"uncertainty\", \"limitation\", \"samples\"]\n }, path);\n\n if (\n record.kind ===\n \"spectral-transmission\"\n ) {\n if (\n record.workingTStop !==\n undefined ||\n record.wavelengthRangeNanometers !==\n undefined\n ) {\n throw new InvalidConfigurationError(\n path +\n \" spectral-transmission must not include T-stop-only fields.\"\n );\n }\n\n const status =\n parseScientificStatus(\n record.scientificStatus,\n path +\n \".scientificStatus\"\n );\n const samplesFact =\n requireRecord(\n record.samples,\n path + \".samples\", [\"value\", \"evidence\"]\n );\n const uncertainty =\n parseUncertainty(\n record.uncertainty,\n path + \".uncertainty\"\n );\n\n if (\n status === \"calibrated\" &&\n uncertainty.kind !==\n \"relative\"\n ) {\n throw new InvalidConfigurationError(\n path +\n \" calibrated spectral transmission requires quantified relative uncertainty.\"\n );\n }\n\n return {\n kind:\n \"spectral-transmission\",\n scientificStatus: status,\n wavelengthBasis:\n parseResolvedBasis(\n record.wavelengthBasis,\n path +\n \".wavelengthBasis\"\n ),\n samples: {\n value:\n parseSpectralSamples(\n samplesFact.value,\n path +\n \".samples.value\"\n ),\n evidence:\n parseReusableNumericEvidence(\n samplesFact.evidence,\n path +\n \".samples.evidence\"\n )\n },\n uncertainty\n };\n }\n\n if (\n record.kind ===\n \"effective-working-t-stop-approximation\"\n ) {\n if (\n record.samples !==\n undefined\n ) {\n throw new InvalidConfigurationError(\n path +\n \" T-stop approximation must not include spectral transmission samples.\"\n );\n }\n if (\n record.scientificStatus !==\n \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n path +\n ' T-stop scientificStatus must be \"approximation\".'\n );\n }\n const workingTStopFact =\n requireRecord(\n record.workingTStop,\n path +\n \".workingTStop\", [\"value\", \"evidence\"]\n );\n return {\n kind:\n \"effective-working-t-stop-approximation\",\n scientificStatus:\n \"approximation\",\n workingTStop: {\n value:\n requirePositiveFinite(\n workingTStopFact.value,\n path +\n \".workingTStop.value\"\n ),\n evidence:\n parseEvidenceList(\n workingTStopFact.evidence,\n path +\n \".workingTStop.evidence\"\n )\n },\n wavelengthBasis:\n parseResolvedBasis(\n record.wavelengthBasis,\n path +\n \".wavelengthBasis\"\n ),\n wavelengthRangeNanometers:\n parseRange(\n record.wavelengthRangeNanometers,\n path +\n \".wavelengthRangeNanometers\"\n ),\n uncertainty:\n parseUncertainty(\n record.uncertainty,\n path + \".uncertainty\"\n ),\n limitation:\n requireNonEmptyString(\n record.limitation,\n path +\n \".limitation\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\nfunction parseFocusApplicability(\n value: unknown,\n path: string\n): OpticalBridgeFocusApplicability {\n const record =\n requireRecord(value, path, null);\n rejectUnknownVariantFields(record, \"kind\", { any: [\"kind\"], \"finite-distance-range\": [\"kind\", \"objectDistanceM\"] }, path);\n\n if (record.kind === \"any\") {\n return {\n kind: \"any\"\n };\n }\n if (\n record.kind ===\n \"finite-distance-range\"\n ) {\n return {\n kind:\n \"finite-distance-range\",\n objectDistanceM:\n parseRange(\n record.objectDistanceM,\n path +\n \".objectDistanceM\"\n )\n };\n }\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\n/**\n * Validates an untrusted scene-to-sensor profile and returns the normalized typed contract. Unknown\n * enum values, missing required fields and incompatible scientific data are rejected here.\n *\n * The optical bridge turns outgoing scene radiance (W/m²/sr/nm) into irradiance (W/m²/nm) arriving\n * at the front of the sensor stack.\n * - Circular paraxial acceptance is pi/(4*Nworking²). At finite focus with unit pupil magnification,\n * the working f-number is N*(1+m).\n * - Spectral transmission and an effective working T-stop are alternative paths; use one.\n * - Field throughput is applied exactly once.\n * - Sensor response and PSF (point-spread function) blur happen later in the pipeline.\n * @param value - unknown. Untrusted input; TypeScript types alone are not validation.\n * @returns SceneToSensorIrradianceProfile. The result states its shape and scientific status; a successful call does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the root export and exact type graph.\n */\nexport function parseSceneToSensorIrradianceProfile(\n value: unknown\n): SceneToSensorIrradianceProfile {\n const record =\n requireRecord(\n value,\n \"sceneToSensorIrradianceProfile\", [\"schemaVersion\", \"applicability\", \"scientificStatus\", \"transmission\", \"distortionAreaMappingOwnership\", \"psfRedistributionOwnership\", \"sensorOpticalStackIncluded\", \"strayLightIncluded\", \"polarizationModeled\", \"wavelengthChangingBehaviorModeled\", \"volumetricScatteringModeled\", \"profileId\", \"profileVersion\", \"lensProfileId\", \"evidence\", \"limitations\"]\n );\n if (\n record.schemaVersion !==\n SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n 'sceneToSensorIrradianceProfile.schemaVersion must be \"' +\n SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION +\n '\".'\n );\n }\n\n const applicability =\n requireRecord(\n record.applicability,\n \"sceneToSensorIrradianceProfile.applicability\", [\"focalLengthMm\", \"nominalFNumber\", \"focus\"]\n );\n const status =\n parseScientificStatus(\n record.scientificStatus,\n \"sceneToSensorIrradianceProfile.scientificStatus\"\n );\n const transmission =\n parseTransmission(\n record.transmission,\n \"sceneToSensorIrradianceProfile.transmission\"\n );\n\n if (\n status === \"calibrated\" &&\n transmission.scientificStatus !==\n \"calibrated\"\n ) {\n throw new InvalidConfigurationError(\n \"A calibrated optical bridge profile requires calibrated spectral transmission.\"\n );\n }\n\n if (\n record.distortionAreaMappingOwnership !==\n \"not-applied-by-bridge\" ||\n record.psfRedistributionOwnership !==\n \"downstream-normalized-energy-redistribution\" ||\n record.sensorOpticalStackIncluded !==\n false ||\n record.strayLightIncluded !==\n false ||\n record.polarizationModeled !==\n false ||\n record.wavelengthChangingBehaviorModeled !==\n false ||\n record.volumetricScatteringModeled !==\n false\n ) {\n throw new InvalidConfigurationError(\n \"sceneToSensorIrradianceProfile ownership/completeness flags are invalid for schema 0.1.0.\"\n );\n }\n\n return {\n schemaVersion:\n SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION,\n profileId:\n requireNonEmptyString(\n record.profileId,\n \"sceneToSensorIrradianceProfile.profileId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n \"sceneToSensorIrradianceProfile.profileVersion\"\n ),\n lensProfileId:\n requireNonEmptyString(\n record.lensProfileId,\n \"sceneToSensorIrradianceProfile.lensProfileId\"\n ),\n scientificStatus: status,\n applicability: {\n focalLengthMm:\n parseRange(\n applicability.focalLengthMm,\n \"sceneToSensorIrradianceProfile.applicability.focalLengthMm\"\n ),\n nominalFNumber:\n parseRange(\n applicability.nominalFNumber,\n \"sceneToSensorIrradianceProfile.applicability.nominalFNumber\"\n ),\n focus:\n parseFocusApplicability(\n applicability.focus,\n \"sceneToSensorIrradianceProfile.applicability.focus\"\n )\n },\n transmission,\n distortionAreaMappingOwnership:\n \"not-applied-by-bridge\",\n psfRedistributionOwnership:\n \"downstream-normalized-energy-redistribution\",\n sensorOpticalStackIncluded:\n false,\n strayLightIncluded: false,\n polarizationModeled: false,\n wavelengthChangingBehaviorModeled:\n false,\n volumetricScatteringModeled:\n false,\n evidence:\n parseEvidenceList(\n record.evidence,\n \"sceneToSensorIrradianceProfile.evidence\"\n ),\n limitations:\n parseLimitations(\n record.limitations,\n \"sceneToSensorIrradianceProfile.limitations\"\n )\n };\n}\n\nfunction ensureWithin(\n value: number,\n range: NumericRange,\n path: string\n): void {\n if (\n value < range.minimum ||\n value > range.maximum\n ) {\n throw new InvalidScientificInputError(\n path +\n \" lies outside the optical profile applicability range.\"\n );\n }\n}\n\n/**\n * Internal (2.6; not exported from the package): N_w / N = 1 + m for an ideal symmetric thin lens with unity pupil\n * magnification focused at `objectDistanceM`, m = v/s from `calculateThinLensImageDistance`. The irradiance bridge's\n * `ideal-symmetric-thin-lens` focus uses exactly this factor, and the framing reports derive their close-up light\n * factor 1/(1 + m)² from it, so the two cannot drift apart.\n */\nexport function thinLensWorkingFNumberFactor(focalLengthMm: number, objectDistanceM: number): number {\n return 1 + calculateThinLensImageDistance({ focalLengthMm, objectDistanceM }).value.magnification;\n}\n\nfunction workingFNumber(\n focus:\n OpticalBridgeFocusContext,\n focalLengthMm: number,\n nominalFNumber: number\n): {\n value: number;\n evidence:\n readonly EvidenceProvenance[];\n} {\n if (\n focus.kind === \"infinity-focus\"\n ) {\n return {\n value: nominalFNumber,\n evidence: []\n };\n }\n\n if (\n focus.kind ===\n \"ideal-symmetric-thin-lens\"\n ) {\n if (\n focus\n .pupilMagnificationAssumption !==\n \"unity\"\n ) {\n throw new InvalidScientificInputError(\n 'Ideal symmetric thin-lens focus requires pupilMagnificationAssumption \"unity\".'\n );\n }\n return {\n value:\n nominalFNumber *\n thinLensWorkingFNumberFactor(focalLengthMm, focus.objectDistanceM),\n evidence: []\n };\n }\n\n if (\n focus.kind ===\n \"supplied-working-f-number\"\n ) {\n const value =\n requirePositiveFiniteScientific(\n focus.workingFNumber.value,\n \"focus.workingFNumber.value\"\n );\n return {\n value,\n evidence:\n parseEvidenceList(\n focus.workingFNumber.evidence,\n \"focus.workingFNumber.evidence\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n \"focus.kind is invalid.\"\n );\n}\n\nfunction finiteFocusDistance(\n focus:\n OpticalBridgeFocusContext\n): number | null {\n if (\n focus.kind ===\n \"ideal-symmetric-thin-lens\"\n ) {\n return requirePositiveFiniteScientific(\n focus.objectDistanceM,\n \"focus.objectDistanceM\"\n );\n }\n if (\n focus.kind ===\n \"supplied-working-f-number\" &&\n focus.focus.kind === \"finite\"\n ) {\n return requirePositiveFiniteScientific(\n focus.focus.objectDistanceM,\n \"focus.focus.objectDistanceM\"\n );\n }\n return null;\n}\n\nfunction interpolateTransmission(\n model:\n Extract<\n OpticalTransmissionModel,\n { kind: \"spectral-transmission\" }\n >,\n wavelengthNanometers: number\n): number {\n const samples =\n model.samples.value;\n const first =\n samples[0]!;\n const last =\n samples[\n samples.length - 1\n ]!;\n\n if (\n wavelengthNanometers <\n first.wavelengthNanometers ||\n wavelengthNanometers >\n last.wavelengthNanometers\n ) {\n throw new InvalidScientificInputError(\n \"Scene wavelength lies outside the spectral transmission sample range.\"\n );\n }\n\n if (\n wavelengthNanometers ===\n first.wavelengthNanometers\n ) {\n return first\n .linearTransmissionFactor;\n }\n\n for (\n let index = 1;\n index < samples.length;\n index += 1\n ) {\n const right =\n samples[index]!;\n if (\n wavelengthNanometers <=\n right.wavelengthNanometers\n ) {\n const left =\n samples[index - 1]!;\n if (\n wavelengthNanometers ===\n right.wavelengthNanometers\n ) {\n return right\n .linearTransmissionFactor;\n }\n const phase =\n (wavelengthNanometers -\n left.wavelengthNanometers) /\n (right.wavelengthNanometers -\n left.wavelengthNanometers);\n return (\n left\n .linearTransmissionFactor +\n phase *\n (right\n .linearTransmissionFactor -\n left\n .linearTransmissionFactor)\n );\n }\n }\n\n return last\n .linearTransmissionFactor;\n}\n\nfunction fieldFactor(\n field:\n OpticalBridgeFieldThroughput,\n imagePointMm: {\n x: number;\n y: number;\n }\n): number {\n if (field.kind === \"unity\") {\n return 1;\n }\n if (\n field.kind ===\n \"illumination-vignetting-result\"\n ) {\n const result =\n field.result;\n const scale = Math.max(\n 1,\n Math.abs(imagePointMm.x),\n Math.abs(imagePointMm.y),\n Math.abs(result.imagePointMm.x),\n Math.abs(result.imagePointMm.y)\n );\n const tolerance =\n Number.EPSILON *\n 64 *\n scale;\n if (\n Math.abs(\n result.imagePointMm.x -\n imagePointMm.x\n ) > tolerance ||\n Math.abs(\n result.imagePointMm.y -\n imagePointMm.y\n ) > tolerance\n ) {\n throw new InvalidScientificInputError(\n \"Illumination-vignetting result imagePointMm must match the optical bridge imagePointMm.\"\n );\n }\n const factor =\n result.linearThroughputFactor;\n if (\n !Number.isFinite(factor) ||\n factor <= 0 ||\n factor > 1\n ) {\n throw new InvalidScientificInputError(\n \"Field throughput factor must be finite and in (0, 1].\"\n );\n }\n return factor;\n }\n throw new InvalidConfigurationError(\n \"fieldThroughput.kind is invalid.\"\n );\n}\n\nfunction validateSceneIdentity(\n request:\n SceneRadianceEvaluationRequest,\n result:\n SceneRadianceEvaluationResult\n): void {\n if (\n request.sampleId !==\n result.sampleId ||\n request.providerProfileId !==\n result.providerProfileId ||\n request.sceneId !==\n result.sceneId ||\n request.wavelengthNanometers !==\n result.wavelengthNanometers ||\n request.wavelengthBasis !==\n result.wavelengthBasis\n ) {\n throw new InvalidScientificInputError(\n \"Scene-radiance request/result identity must match exactly.\"\n );\n }\n}\n\n/**\n * The request-independent part of {@link calculateSceneRadianceToSensorIrradiance}: the profile, focal length, f-number,\n * applicability, focus and working f-number checks and the paraxial acceptance, in the public function's order.\n */\ninterface PreparedSceneRadianceToSensorIrradiance {\n profile: ReturnType<typeof parseSceneToSensorIrradianceProfile>;\n focalLengthMm: number;\n nominalFNumber: number;\n working: ReturnType<typeof workingFNumber>;\n workingFNumberValue: number;\n geometricAcceptance: number;\n transmission: ReturnType<typeof parseSceneToSensorIrradianceProfile>[\"transmission\"];\n}\n\nfunction prepareSceneRadianceToSensorIrradiance(\n input: Pick<CalculateSceneRadianceToSensorIrradianceInput, \"profile\" | \"focalLengthMm\" | \"nominalFNumber\" | \"focus\">\n): PreparedSceneRadianceToSensorIrradiance {\n const profile =\n parseSceneToSensorIrradianceProfile(\n input.profile\n );\n const focalLengthMm =\n requirePositiveFiniteScientific(\n input.focalLengthMm,\n \"focalLengthMm\"\n );\n const nominalFNumber =\n requirePositiveFiniteScientific(\n input.nominalFNumber,\n \"nominalFNumber\"\n );\n\n ensureWithin(\n focalLengthMm,\n profile.applicability\n .focalLengthMm,\n \"focalLengthMm\"\n );\n ensureWithin(\n nominalFNumber,\n profile.applicability\n .nominalFNumber,\n \"nominalFNumber\"\n );\n\n const focusDistanceM =\n finiteFocusDistance(input.focus);\n if (\n profile.applicability.focus.kind ===\n \"finite-distance-range\"\n ) {\n if (focusDistanceM === null) {\n throw new InvalidScientificInputError(\n \"Optical profile requires a finite focus distance.\"\n );\n }\n ensureWithin(\n focusDistanceM,\n profile.applicability\n .focus.objectDistanceM,\n \"focus object distance\"\n );\n }\n\n const working =\n workingFNumber(\n input.focus,\n focalLengthMm,\n nominalFNumber\n );\n const workingFNumberValue =\n working.value;\n const geometricAcceptance =\n Math.PI /\n (4 *\n workingFNumberValue *\n workingFNumberValue);\n\n if (\n !Number.isFinite(\n geometricAcceptance\n ) ||\n geometricAcceptance <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Paraxial geometric acceptance must remain finite and greater than zero.\"\n );\n }\n\n const transmission =\n profile.transmission;\n return { profile, focalLengthMm, nominalFNumber, working, workingFNumberValue, geometricAcceptance, transmission };\n}\n\ninterface AppliedSceneRadianceToSensorIrradiance {\n spectralTransmissionFactor: number | null;\n effectiveWorkingTStop: number | null;\n tStopEquivalentTransmissionFactor: number | null;\n effectiveAcceptance: number;\n transmissionEvidence: readonly EvidenceProvenance[];\n frontOfLensFilterCount: number;\n frontOfLensFilterPolarizationModeled: boolean;\n frontOfLensFilterTransmissionFactor: number;\n frontOfLensFilterAttenuationStops: number;\n frontOfLensFilterEvidence: readonly EvidenceProvenance[];\n frontOfLensFilterLimitations: readonly string[];\n fieldThroughputFactor: number;\n sceneRadiance: number;\n irradiance: number;\n}\n\n/**\n * The per-query part of {@link calculateSceneRadianceToSensorIrradiance}, after the request and result are parsed and their\n * identities matched: the wavelength-basis check, the transmission or T-stop path, the front-of-lens filters, the field\n * throughput and the irradiance, with the same operations in the same order.\n */\nfunction applySceneRadianceToSensorIrradiance(\n prepared: PreparedSceneRadianceToSensorIrradiance,\n input: Pick<CalculateSceneRadianceToSensorIrradianceInput, \"imagePointMm\" | \"fieldThroughput\"> &\n { frontOfLensFilters?: readonly FrontOfLensFilterProfile[] | undefined },\n request: SceneRadianceEvaluationRequest,\n sceneResult: SceneRadianceEvaluationResult\n): AppliedSceneRadianceToSensorIrradiance {\n const { workingFNumberValue, geometricAcceptance, transmission } = prepared;\n if (\n request.wavelengthBasis !==\n transmission.wavelengthBasis\n ) {\n throw new InvalidScientificInputError(\n \"Scene-radiance and optical-transmission wavelength bases must match; implicit air/vacuum conversion is not performed.\"\n );\n }\n\n let spectralTransmissionFactor:\n number | null = null;\n let effectiveWorkingTStop:\n number | null = null;\n let tStopEquivalentTransmissionFactor:\n number | null = null;\n let effectiveAcceptance =\n geometricAcceptance;\n let transmissionEvidence:\n readonly EvidenceProvenance[];\n\n if (\n transmission.kind ===\n \"spectral-transmission\"\n ) {\n spectralTransmissionFactor =\n interpolateTransmission(\n transmission,\n request.wavelengthNanometers\n );\n effectiveAcceptance *=\n spectralTransmissionFactor;\n transmissionEvidence =\n transmission.samples.evidence;\n } else {\n ensureWithin(\n request.wavelengthNanometers,\n transmission\n .wavelengthRangeNanometers,\n \"scene wavelength\"\n );\n effectiveWorkingTStop =\n transmission.workingTStop.value;\n if (\n effectiveWorkingTStop <\n workingFNumberValue\n ) {\n throw new InvalidScientificInputError(\n \"effective working T-stop must not be smaller than the geometric working f-number for a passive optical approximation.\"\n );\n }\n tStopEquivalentTransmissionFactor =\n (workingFNumberValue /\n effectiveWorkingTStop) **\n 2;\n effectiveAcceptance =\n Math.PI /\n (4 *\n effectiveWorkingTStop *\n effectiveWorkingTStop);\n transmissionEvidence =\n transmission\n .workingTStop.evidence;\n }\n\n let frontOfLensFilterCount = 0;\n let frontOfLensFilterPolarizationModeled = false;\n let frontOfLensFilterTransmissionFactor = 1;\n let frontOfLensFilterAttenuationStops = 0;\n let frontOfLensFilterEvidence:\n readonly EvidenceProvenance[] = [];\n let frontOfLensFilterLimitations:\n readonly string[] = [];\n\n if (\n input.frontOfLensFilters !==\n undefined\n ) {\n const composed =\n composeFrontOfLensFilterTransmission({\n filters:\n input.frontOfLensFilters,\n wavelengthNanometers:\n request.wavelengthNanometers,\n wavelengthBasis:\n request.wavelengthBasis\n });\n frontOfLensFilterCount =\n composed.appliedFilterCount;\n frontOfLensFilterPolarizationModeled =\n composed.polarizationModeled;\n frontOfLensFilterTransmissionFactor =\n composed\n .combinedLinearTransmissionFactor;\n frontOfLensFilterAttenuationStops =\n composed\n .combinedAttenuationStops;\n frontOfLensFilterEvidence =\n composed.components.flatMap(\n (component) =>\n component.evidence\n );\n frontOfLensFilterLimitations =\n composed.components.flatMap(\n (component) =>\n component.limitations\n );\n effectiveAcceptance *=\n frontOfLensFilterTransmissionFactor;\n }\n\n const fieldThroughputFactor =\n fieldFactor(\n input.fieldThroughput,\n input.imagePointMm\n );\n effectiveAcceptance *=\n fieldThroughputFactor;\n\n const sceneRadiance =\n sceneResult\n .spectralRadianceWattsPerSquareMeterSteradianNanometer;\n const irradiance =\n sceneRadiance *\n effectiveAcceptance;\n\n if (\n !Number.isFinite(irradiance) ||\n irradiance < 0\n ) {\n throw new InvalidScientificInputError(\n \"Sensor-plane spectral irradiance must remain finite and non-negative.\"\n );\n }\n\n return { spectralTransmissionFactor, effectiveWorkingTStop, tStopEquivalentTransmissionFactor, effectiveAcceptance, transmissionEvidence,\n frontOfLensFilterCount, frontOfLensFilterPolarizationModeled, frontOfLensFilterTransmissionFactor, frontOfLensFilterAttenuationStops,\n frontOfLensFilterEvidence, frontOfLensFilterLimitations, fieldThroughputFactor, sceneRadiance, irradiance };\n}\n\n/**\n * Internal (#453; not exported from the package): the sensor-plane spectral irradiance that\n * {@link calculateSceneRadianceToSensorIrradiance} returns, for repeated queries of one optical state. This call runs the\n * request-independent checks once (the public function's own code, in its order); each call of the returned function then\n * runs the public function's per-query steps on an engine-built request and a result already parsed by the caller: the identity\n * match, the wavelength-basis check, the transmission or T-stop path, the front-of-lens filters, the field throughput and\n * the finite, non-negative irradiance, with the same operations, order and errors. It skips only the re-validation of the\n * unchanged optical inputs, the re-parse of the request and result, and the result envelope.\n */\nexport function prepareSceneRadianceToSensorIrradianceValue(\n optics: Pick<CalculateSceneRadianceToSensorIrradianceInput, \"profile\" | \"focalLengthMm\" | \"nominalFNumber\" | \"focus\" | \"frontOfLensFilters\">\n): (request: SceneRadianceEvaluationRequest, result: SceneRadianceEvaluationResult, imagePointMm: { x: number; y: number },\n fieldThroughput: OpticalBridgeFieldThroughput) => number {\n const prepared = prepareSceneRadianceToSensorIrradiance(optics);\n const frontOfLensFilters = optics.frontOfLensFilters;\n return (request, result, imagePointMm, fieldThroughput) => {\n validateSceneIdentity(request, result);\n return applySceneRadianceToSensorIrradiance(prepared, { imagePointMm, fieldThroughput, frontOfLensFilters }, request, result).irradiance;\n };\n}\n\n/**\n * Applies the declared pupil acceptance and exactly one transmission or field-throughput path to\n * spectral scene radiance. Returns the irradiance before the sensor stack, with conservative\n * evidence.\n *\n * The optical bridge turns outgoing scene radiance (W/m²/sr/nm) into irradiance (W/m²/nm) arriving\n * at the front of the sensor stack.\n * - Circular paraxial acceptance is pi/(4*Nworking²). At finite focus with unit pupil magnification,\n * the working f-number is N*(1+m).\n * - Spectral transmission and an effective working T-stop are alternative paths; use one.\n * - Field throughput is applied exactly once.\n * - Sensor response and PSF (point-spread function) blur happen later in the pipeline.\n * @param input - CalculateSceneRadianceToSensorIrradianceInput. See that type for coordinates, units and profile binding.\n * @returns CalculationResult<\n SceneToSensorIrradianceResult\n>. The result states its shape and scientific status; a successful call does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the root export and exact type graph.\n */\nexport function calculateSceneRadianceToSensorIrradiance(\n input:\n CalculateSceneRadianceToSensorIrradianceInput\n): CalculationResult<\n SceneToSensorIrradianceResult\n> {\n requireInputKeys(input, \"calculateSceneRadianceToSensorIrradiance\", [\"sceneRadianceRequest\", \"sceneRadianceResult\", \"profile\", \"focalLengthMm\", \"nominalFNumber\", \"focus\", \"imagePointMm\", \"fieldThroughput\", \"frontOfLensFilters\"]);\n const request =\n parseSceneRadianceEvaluationRequest(\n input.sceneRadianceRequest\n );\n const sceneResult =\n parseSceneRadianceEvaluationResult(\n input.sceneRadianceResult\n );\n validateSceneIdentity(\n request,\n sceneResult\n );\n\n const prepared = prepareSceneRadianceToSensorIrradiance(input);\n const { profile, focalLengthMm, nominalFNumber, working, workingFNumberValue, geometricAcceptance, transmission } = prepared;\n const { spectralTransmissionFactor, effectiveWorkingTStop, tStopEquivalentTransmissionFactor, effectiveAcceptance, transmissionEvidence,\n frontOfLensFilterCount, frontOfLensFilterPolarizationModeled, frontOfLensFilterTransmissionFactor, frontOfLensFilterAttenuationStops,\n frontOfLensFilterEvidence, frontOfLensFilterLimitations, fieldThroughputFactor, sceneRadiance, irradiance } =\n applySceneRadianceToSensorIrradiance(prepared, input, request, sceneResult);\n\n const limitations = [\n ...profile.limitations,\n ...frontOfLensFilterLimitations,\n \"Front-of-lens filter transmission is applied outside the lens throughput path exactly once and does not change geometric working f-number.\",\n \"Paraxial circular-pupil acceptance uses pi/(4*N_working^2); no universal cos^4 field falloff is added.\",\n \"Field throughput is supplied separately and applied exactly once.\",\n \"Geometric distortion area-density/Jacobian correction is not applied by this bridge.\",\n \"PSF/diffraction energy redistribution is downstream and must preserve normalized energy apart from separately owned throughput losses.\",\n \"Sensor optical stack, microlens, CFA, QE/responsivity and charge conversion are downstream.\",\n frontOfLensFilterPolarizationModeled\n ? \"Flare, ghosting, veiling glare, scene-radiance polarization (scene light is treated as unpolarized; the front-of-lens linear polarizer transmits (k1 + k2) / 2), volumetric scattering and wavelength-changing behavior are not modeled.\"\n : \"Flare, ghosting, veiling glare, polarization, volumetric scattering and wavelength-changing behavior are not modeled.\"\n ];\n\n return approximationResult(\n {\n schemaVersion:\n SCENE_TO_SENSOR_IRRADIANCE_PROFILE_SCHEMA_VERSION,\n profileId: profile.profileId,\n profileVersion:\n profile.profileVersion,\n lensProfileId:\n profile.lensProfileId,\n sampleId: request.sampleId,\n providerProfileId:\n request.providerProfileId,\n sceneId: request.sceneId,\n timeSecondsFromExposureStart:\n request\n .timeSecondsFromExposureStart,\n wavelengthNanometers:\n request.wavelengthNanometers,\n wavelengthBasis:\n request.wavelengthBasis,\n inputQuantity:\n \"outgoing-spectral-radiance\",\n inputUnit:\n \"W/m^2/sr/nm\",\n outputQuantity:\n \"sensor-plane-spectral-irradiance\",\n outputUnit: \"W/m^2/nm\",\n sceneSpectralRadianceWattsPerSquareMeterSteradianNanometer:\n sceneRadiance,\n focalLengthMm,\n nominalFNumber,\n workingFNumber:\n workingFNumberValue,\n focusModel: input.focus.kind,\n paraxialGeometricAcceptanceSolidAngleSteradians:\n geometricAcceptance,\n transmissionPath:\n transmission.kind,\n spectralTransmissionFactor,\n effectiveWorkingTStop,\n tStopEquivalentTransmissionFactor,\n frontOfLensFilterCount,\n frontOfLensFilterTransmissionFactor,\n frontOfLensFilterAttenuationStops,\n frontOfLensFilterTransmissionAppliedExactlyOnce:\n true,\n frontOfLensFilterPolarizationModeled,\n fieldThroughputFactor,\n effectiveAcceptanceAfterTransmissionAndFieldSteradians:\n effectiveAcceptance,\n sensorPlaneSpectralIrradianceWattsPerSquareMeterNanometer:\n irradiance,\n scientificStatus:\n \"approximation\",\n sceneRadianceScientificStatus:\n sceneResult.scientificStatus,\n opticalProfileScientificStatus:\n profile.scientificStatus,\n sceneRadianceUncertainty:\n sceneResult.uncertainty,\n opticalUncertainty:\n transmission.uncertainty,\n radianceAmplificationApplied:\n false,\n fieldThroughputAppliedExactlyOnce:\n true,\n universalCos4FalloffApplied:\n false,\n distortionAreaCorrectionApplied:\n false,\n psfRedistributionApplied:\n false,\n sensorOpticalStackApplied:\n false,\n cfaApplied: false,\n quantumEfficiencyApplied: false,\n spectralResponsivityApplied:\n false,\n strayLightApplied: false,\n polarizationModeled: false,\n wavelengthChangingBehaviorModeled:\n false,\n volumetricScatteringModeled:\n false,\n calibratedSensorPlaneIrradianceClaimAuthorized:\n false,\n componentEvidence: {\n sceneRadiance:\n sceneResult.evidence,\n opticalProfile:\n profile.evidence,\n transmission:\n transmissionEvidence,\n workingFNumber:\n working.evidence,\n frontOfLensFilters:\n frontOfLensFilterEvidence\n },\n limitations\n },\n \"scene-radiance-to-sensor-irradiance\",\n \"1.1.0\",\n limitations\n );\n}\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How much to crop an image from a shorter lens so it matches a longer lens's framing, and the
|
|
3
|
+
* pixels that leaves.
|
|
4
|
+
*
|
|
5
|
+
* For an ideal rectilinear lens focused at infinity, a scene point at field angle θ images at
|
|
6
|
+
* height y' = f·tan θ (Greivenkamp, OPTI-502 course notes, sec. 12 (Objectives), free:
|
|
7
|
+
* https://wp.optics.arizona.edu/jgreivenkamp/wp-content/uploads/sites/11/2019/08/502-12-Objectives.pdf).
|
|
8
|
+
* From the same camera position the image of focal length F is the image of f scaled by F/f, so a
|
|
9
|
+
* centred crop by the linear factor F/f gives the same framing and the same perspective: exact at
|
|
10
|
+
* infinity focus, to whole pixels.
|
|
11
|
+
*
|
|
12
|
+
* At close focus the thin-lens magnification is m = f/(d − f) for an object distance d from the
|
|
13
|
+
* lens, so at the same d the factor is F·(d − f)/(f·(d − F)), larger than F/f and tending to it when
|
|
14
|
+
* d is much greater than F. That correction, distortion and focus breathing are not modelled.
|
|
15
|
+
* @see docs/PHYSICS_FOUNDATION.md "Crop to match a longer focal length".
|
|
16
|
+
*/
|
|
17
|
+
import { type CalculationResult } from "../core/calculation-result.js";
|
|
18
|
+
export interface CalculateCropToMatchFocalLengthInput {
|
|
19
|
+
/** Focal length the image was taken with, in millimetres. */
|
|
20
|
+
focalLengthMm: number;
|
|
21
|
+
/** Longer focal length whose framing the crop should match, in millimetres. At least `focalLengthMm`. */
|
|
22
|
+
targetFocalLengthMm: number;
|
|
23
|
+
/** Image width in pixels (for example the sensor's output width). */
|
|
24
|
+
pixelWidth: number;
|
|
25
|
+
/** Image height in pixels. */
|
|
26
|
+
pixelHeight: number;
|
|
27
|
+
}
|
|
28
|
+
export interface CropToMatchFocalLength {
|
|
29
|
+
/** Focal length supplied by the caller, in millimetres. */
|
|
30
|
+
focalLengthMm: number;
|
|
31
|
+
/** Target focal length supplied by the caller, in millimetres. */
|
|
32
|
+
targetFocalLengthMm: number;
|
|
33
|
+
/** Linear crop factor targetFocalLengthMm / focalLengthMm (1 means no crop). */
|
|
34
|
+
cropFactor: number;
|
|
35
|
+
/** Width left after the centred crop, in pixels (as `calculateCenteredCrop`). */
|
|
36
|
+
pixelWidth: number;
|
|
37
|
+
/** Height left after the centred crop, in pixels. */
|
|
38
|
+
pixelHeight: number;
|
|
39
|
+
/** Pixels left, in millions. */
|
|
40
|
+
megapixels: number;
|
|
41
|
+
/** Megapixels of the uncropped image. */
|
|
42
|
+
sourceMegapixels: number;
|
|
43
|
+
/** Fraction of the original pixel area retained, about 1 / cropFactor². */
|
|
44
|
+
retainedAreaFraction: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The centred crop that makes an image from a shorter lens match a longer lens's framing from the
|
|
48
|
+
* same position: crop factor target ÷ focal length, and the pixels and megapixels left.
|
|
49
|
+
*
|
|
50
|
+
* Exact for ideal rectilinear lenses focused at infinity, to whole pixels (the crop is
|
|
51
|
+
* `calculateCenteredCrop`'s, which keeps the aspect ratio). At close focus the matching factor is
|
|
52
|
+
* larger, F·(d − f)/(f·(d − F)) at object distance d (thin lens); that, distortion and focus
|
|
53
|
+
* breathing are not modelled. The crop has the longer lens's framing and perspective but fewer
|
|
54
|
+
* pixels: it records no more detail than the cropped part of the shorter lens's image.
|
|
55
|
+
*
|
|
56
|
+
* Example: a 24 MP (6000 × 4000) image at 50 mm cropped to match 100 mm: factor 2, 3000 × 2000,
|
|
57
|
+
* 6 MP.
|
|
58
|
+
*
|
|
59
|
+
* @param input Focal length, longer target focal length and the image's pixel dimensions.
|
|
60
|
+
* @returns Crop factor and the remaining pixel dimensions.
|
|
61
|
+
*/
|
|
62
|
+
export declare function calculateCropToMatchFocalLength(input: CalculateCropToMatchFocalLengthInput): CalculationResult<CropToMatchFocalLength>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* How much to crop an image from a shorter lens so it matches a longer lens's framing, and the
|
|
4
|
+
* pixels that leaves.
|
|
5
|
+
*
|
|
6
|
+
* For an ideal rectilinear lens focused at infinity, a scene point at field angle θ images at
|
|
7
|
+
* height y' = f·tan θ (Greivenkamp, OPTI-502 course notes, sec. 12 (Objectives), free:
|
|
8
|
+
* https://wp.optics.arizona.edu/jgreivenkamp/wp-content/uploads/sites/11/2019/08/502-12-Objectives.pdf).
|
|
9
|
+
* From the same camera position the image of focal length F is the image of f scaled by F/f, so a
|
|
10
|
+
* centred crop by the linear factor F/f gives the same framing and the same perspective: exact at
|
|
11
|
+
* infinity focus, to whole pixels.
|
|
12
|
+
*
|
|
13
|
+
* At close focus the thin-lens magnification is m = f/(d − f) for an object distance d from the
|
|
14
|
+
* lens, so at the same d the factor is F·(d − f)/(f·(d − F)), larger than F/f and tending to it when
|
|
15
|
+
* d is much greater than F. That correction, distortion and focus breathing are not modelled.
|
|
16
|
+
* @see docs/PHYSICS_FOUNDATION.md "Crop to match a longer focal length".
|
|
17
|
+
*/
|
|
18
|
+
import { calculatedResult } from "../core/calculation-result.js";
|
|
19
|
+
import { requireInputKeys } from "../core/record-validation.js";
|
|
20
|
+
import { InvalidScientificInputError, requirePositiveFinite } from "../core/validation.js";
|
|
21
|
+
import { calculateCenteredCrop } from "./crop.js";
|
|
22
|
+
/**
|
|
23
|
+
* The centred crop that makes an image from a shorter lens match a longer lens's framing from the
|
|
24
|
+
* same position: crop factor target ÷ focal length, and the pixels and megapixels left.
|
|
25
|
+
*
|
|
26
|
+
* Exact for ideal rectilinear lenses focused at infinity, to whole pixels (the crop is
|
|
27
|
+
* `calculateCenteredCrop`'s, which keeps the aspect ratio). At close focus the matching factor is
|
|
28
|
+
* larger, F·(d − f)/(f·(d − F)) at object distance d (thin lens); that, distortion and focus
|
|
29
|
+
* breathing are not modelled. The crop has the longer lens's framing and perspective but fewer
|
|
30
|
+
* pixels: it records no more detail than the cropped part of the shorter lens's image.
|
|
31
|
+
*
|
|
32
|
+
* Example: a 24 MP (6000 × 4000) image at 50 mm cropped to match 100 mm: factor 2, 3000 × 2000,
|
|
33
|
+
* 6 MP.
|
|
34
|
+
*
|
|
35
|
+
* @param input Focal length, longer target focal length and the image's pixel dimensions.
|
|
36
|
+
* @returns Crop factor and the remaining pixel dimensions.
|
|
37
|
+
*/
|
|
38
|
+
export function calculateCropToMatchFocalLength(input) {
|
|
39
|
+
requireInputKeys(input, "calculateCropToMatchFocalLength", ["focalLengthMm", "targetFocalLengthMm", "pixelWidth", "pixelHeight"]);
|
|
40
|
+
requirePositiveFinite("focalLengthMm", input.focalLengthMm);
|
|
41
|
+
requirePositiveFinite("targetFocalLengthMm", input.targetFocalLengthMm);
|
|
42
|
+
if (input.targetFocalLengthMm < input.focalLengthMm) {
|
|
43
|
+
throw new InvalidScientificInputError("targetFocalLengthMm must be greater than or equal to focalLengthMm: a crop cannot widen the framing.");
|
|
44
|
+
}
|
|
45
|
+
const cropFactor = input.targetFocalLengthMm / input.focalLengthMm;
|
|
46
|
+
const crop = calculateCenteredCrop({ pixelWidth: input.pixelWidth, pixelHeight: input.pixelHeight, cropFactor }).value;
|
|
47
|
+
return calculatedResult({
|
|
48
|
+
focalLengthMm: input.focalLengthMm,
|
|
49
|
+
targetFocalLengthMm: input.targetFocalLengthMm,
|
|
50
|
+
cropFactor,
|
|
51
|
+
pixelWidth: crop.pixelWidth,
|
|
52
|
+
pixelHeight: crop.pixelHeight,
|
|
53
|
+
megapixels: crop.megapixels,
|
|
54
|
+
sourceMegapixels: (input.pixelWidth * input.pixelHeight) / 1_000_000,
|
|
55
|
+
retainedAreaFraction: crop.retainedAreaFraction
|
|
56
|
+
}, "crop-to-match-focal-length", "1.0.0", [
|
|
57
|
+
"Crop factor = target focal length / focal length: exact for ideal rectilinear lenses focused at infinity, from the same camera position.",
|
|
58
|
+
"At close focus the thin-lens matching factor is F·(d − f)/(f·(d − F)) at object distance d, larger than F/f; not modelled.",
|
|
59
|
+
"Distortion and focus breathing are not modelled.",
|
|
60
|
+
"Centred crop that keeps the aspect ratio to whole pixels (calculateCenteredCrop)."
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=crop-to-match-focal-length.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crop-to-match-focal-length.js","sourceRoot":"","sources":["../../src/output/crop-to-match-focal-length.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,gBAAgB,EAA0B,MAAM,+BAA+B,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAgClD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,+BAA+B,CAC7C,KAA2C;IAE3C,gBAAgB,CAAC,KAAK,EAAE,iCAAiC,EAAE,CAAC,eAAe,EAAE,qBAAqB,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC;IAClI,qBAAqB,CAAC,eAAe,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAC5D,qBAAqB,CAAC,qBAAqB,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACxE,IAAI,KAAK,CAAC,mBAAmB,GAAG,KAAK,CAAC,aAAa,EAAE,CAAC;QACpD,MAAM,IAAI,2BAA2B,CACnC,sGAAsG,CACvG,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,KAAK,CAAC,mBAAmB,GAAG,KAAK,CAAC,aAAa,CAAC;IACnE,MAAM,IAAI,GAAG,qBAAqB,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC;IACvH,OAAO,gBAAgB,CACrB;QACE,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;QAC9C,UAAU;QACV,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,gBAAgB,EAAE,CAAC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,SAAS;QACpE,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;KAChD,EACD,4BAA4B,EAC5B,OAAO,EACP;QACE,0IAA0I;QAC1I,4HAA4H;QAC5H,kDAAkD;QAClD,mFAAmF;KACpF,CACF,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * How much to crop an image from a shorter lens so it matches a longer lens's framing, and the\n * pixels that leaves.\n *\n * For an ideal rectilinear lens focused at infinity, a scene point at field angle θ images at\n * height y' = f·tan θ (Greivenkamp, OPTI-502 course notes, sec. 12 (Objectives), free:\n * https://wp.optics.arizona.edu/jgreivenkamp/wp-content/uploads/sites/11/2019/08/502-12-Objectives.pdf).\n * From the same camera position the image of focal length F is the image of f scaled by F/f, so a\n * centred crop by the linear factor F/f gives the same framing and the same perspective: exact at\n * infinity focus, to whole pixels.\n *\n * At close focus the thin-lens magnification is m = f/(d − f) for an object distance d from the\n * lens, so at the same d the factor is F·(d − f)/(f·(d − F)), larger than F/f and tending to it when\n * d is much greater than F. That correction, distortion and focus breathing are not modelled.\n * @see docs/PHYSICS_FOUNDATION.md \"Crop to match a longer focal length\".\n */\n\nimport { calculatedResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidScientificInputError, requirePositiveFinite } from \"../core/validation.js\";\nimport { calculateCenteredCrop } from \"./crop.js\";\n\nexport interface CalculateCropToMatchFocalLengthInput {\n /** Focal length the image was taken with, in millimetres. */\n focalLengthMm: number;\n /** Longer focal length whose framing the crop should match, in millimetres. At least `focalLengthMm`. */\n targetFocalLengthMm: number;\n /** Image width in pixels (for example the sensor's output width). */\n pixelWidth: number;\n /** Image height in pixels. */\n pixelHeight: number;\n}\n\nexport interface CropToMatchFocalLength {\n /** Focal length supplied by the caller, in millimetres. */\n focalLengthMm: number;\n /** Target focal length supplied by the caller, in millimetres. */\n targetFocalLengthMm: number;\n /** Linear crop factor targetFocalLengthMm / focalLengthMm (1 means no crop). */\n cropFactor: number;\n /** Width left after the centred crop, in pixels (as `calculateCenteredCrop`). */\n pixelWidth: number;\n /** Height left after the centred crop, in pixels. */\n pixelHeight: number;\n /** Pixels left, in millions. */\n megapixels: number;\n /** Megapixels of the uncropped image. */\n sourceMegapixels: number;\n /** Fraction of the original pixel area retained, about 1 / cropFactor². */\n retainedAreaFraction: number;\n}\n\n/**\n * The centred crop that makes an image from a shorter lens match a longer lens's framing from the\n * same position: crop factor target ÷ focal length, and the pixels and megapixels left.\n *\n * Exact for ideal rectilinear lenses focused at infinity, to whole pixels (the crop is\n * `calculateCenteredCrop`'s, which keeps the aspect ratio). At close focus the matching factor is\n * larger, F·(d − f)/(f·(d − F)) at object distance d (thin lens); that, distortion and focus\n * breathing are not modelled. The crop has the longer lens's framing and perspective but fewer\n * pixels: it records no more detail than the cropped part of the shorter lens's image.\n *\n * Example: a 24 MP (6000 × 4000) image at 50 mm cropped to match 100 mm: factor 2, 3000 × 2000,\n * 6 MP.\n *\n * @param input Focal length, longer target focal length and the image's pixel dimensions.\n * @returns Crop factor and the remaining pixel dimensions.\n */\nexport function calculateCropToMatchFocalLength(\n input: CalculateCropToMatchFocalLengthInput\n): CalculationResult<CropToMatchFocalLength> {\n requireInputKeys(input, \"calculateCropToMatchFocalLength\", [\"focalLengthMm\", \"targetFocalLengthMm\", \"pixelWidth\", \"pixelHeight\"]);\n requirePositiveFinite(\"focalLengthMm\", input.focalLengthMm);\n requirePositiveFinite(\"targetFocalLengthMm\", input.targetFocalLengthMm);\n if (input.targetFocalLengthMm < input.focalLengthMm) {\n throw new InvalidScientificInputError(\n \"targetFocalLengthMm must be greater than or equal to focalLengthMm: a crop cannot widen the framing.\"\n );\n }\n const cropFactor = input.targetFocalLengthMm / input.focalLengthMm;\n const crop = calculateCenteredCrop({ pixelWidth: input.pixelWidth, pixelHeight: input.pixelHeight, cropFactor }).value;\n return calculatedResult(\n {\n focalLengthMm: input.focalLengthMm,\n targetFocalLengthMm: input.targetFocalLengthMm,\n cropFactor,\n pixelWidth: crop.pixelWidth,\n pixelHeight: crop.pixelHeight,\n megapixels: crop.megapixels,\n sourceMegapixels: (input.pixelWidth * input.pixelHeight) / 1_000_000,\n retainedAreaFraction: crop.retainedAreaFraction\n },\n \"crop-to-match-focal-length\",\n \"1.0.0\",\n [\n \"Crop factor = target focal length / focal length: exact for ideal rectilinear lenses focused at infinity, from the same camera position.\",\n \"At close focus the thin-lens matching factor is F·(d − f)/(f·(d − F)) at object distance d, larger than F/f; not modelled.\",\n \"Distortion and focus breathing are not modelled.\",\n \"Centred crop that keeps the aspect ratio to whole pixels (calculateCenteredCrop).\"\n ]\n );\n}\n"]}
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* Vignetting is corrected to a flat field: the sample is multiplied by det J(ρ_i) / E(ρ_s), the inverse of the relative
|
|
15
15
|
* illumination the capture delivered at s: the lens's E(ρ) = (1 − bρ²)² at the sensor point and its distortion's area factor
|
|
16
16
|
* 1/det J, det J = (1 + k1ρ_i²)(1 + 3k1ρ_i²) at the ideal point ρ_i that the lens images to ρ_s (the capture's
|
|
17
|
-
* "inverse-distortion-jacobian"; a relative illumination measured on a flat field contains it too).
|
|
18
|
-
*
|
|
17
|
+
* "inverse-distortion-jacobian"; a relative illumination measured on a flat field contains it too). From profile 1.1.0 (#584) the
|
|
18
|
+
* profile's vignetting component carries the same area factor (`distortionAreaFactor`), and its gain equals this kernel's.
|
|
19
19
|
* Every position is computed in whole-frame pixel coordinates, so a region renders the same values as the whole frame.
|
|
20
20
|
*/
|
|
21
21
|
import { InvalidScientificResultError } from "../core/validation.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"developed-lens-corrections.js","sourceRoot":"","sources":["../../src/output/developed-lens-corrections.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * @internal `output.lensCorrections` (#570): a catalogue lens's generic correction profile (`createGenericLensCorrectionProfile`)\n * applied to the developed linear RGB of `simulateCapture`, after demosaic, colour and white balance and before the tone curve.\n * The RAW and the DNG are never corrected.\n *\n * For an output pixel at p (pixels from the frame centre, +y up) and channel c, the source point is read by inverse mapping,\n * as the profile composes it (lateral colour, then distortion):\n * q = m · a_c · p, s = q · (1 + k1·|q|²/R²),\n * with a_c = 1 + L·D(λ_c) the channel's lateral-colour scale, k1 the lens's corner distortion, R the half-diagonal and m ≤ 1 the\n * fill factor (the largest that keeps every output pixel centre's source inside the frame for every channel, so the output keeps\n * its size with no missing pixels; the image is magnified by 1/m). The channel is sampled bilinearly at s.\n *\n * Vignetting is corrected to a flat field: the sample is multiplied by det J(ρ_i) / E(ρ_s), the inverse of the relative\n * illumination the capture delivered at s: the lens's E(ρ) = (1 − bρ²)² at the sensor point and its distortion's area factor\n * 1/det J, det J = (1 + k1ρ_i²)(1 + 3k1ρ_i²) at the ideal point ρ_i that the lens images to ρ_s (the capture's\n * \"inverse-distortion-jacobian\"; a relative illumination measured on a flat field contains it too). The profile's vignetting\n * component holds E only; without the area factor a PV 16 2.8 corner would come out about 18 % too bright.\n * Every position is computed in whole-frame pixel coordinates, so a region renders the same values as the whole frame.\n */\n\nimport { InvalidScientificResultError } from \"../core/validation.js\";\nimport type { LensCorrectionChannel } from \"./lens-corrections.js\";\n\n/**\n * Effective wavelengths of the red, green and blue channels for the lateral-colour map, nm: a generic approximation of where\n * a camera's channel responses peak, not a measured camera.\n */\nexport const GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM: Readonly<Record<LensCorrectionChannel, number>> = /* @__PURE__ */ Object.freeze({ red: 610, green: 540, blue: 465 });\n\n/** @internal The lateral-colour scale of one channel, 1 + L·D(λ_channel) (`D` the catalogue's normalized dispersion). */\nexport function genericLensChannelScale(fraction: number, dispersion: (wavelengthNm: number) => number, channel: LensCorrectionChannel): number {\n return 1 + fraction * dispersion(GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM[channel]);\n}\n\n/** @internal Which corrections run. */\nexport interface DevelopedLensCorrectionSelection { distortion: boolean; lateralColor: boolean; vignetting: boolean }\n/** @internal A prepared correction for one frame (pixel units). */\nexport interface DevelopedLensCorrection {\n width: number; height: number;\n /** Half-diagonal R in pixels. */\n radius: number;\n /** k1 (0 when distortion is off), the lens's own k1, and the red, green and blue scales a_c (1 when lateral colour is off). */\n k1: number; lensK1: number; scales: readonly [number, number, number];\n /** Fill factor m ≤ 1 (1/m is the magnification). */\n fill: number;\n}\ntype Rect = { x: number; y: number; width: number; height: number };\n\n/** Source position (pixel-centre coordinates of the whole frame, unclamped) of output pixel (x, y), channel c. */\nfunction sourceOf(c: DevelopedLensCorrection, m: number, x: number, y: number, channel: number, out: Float64Array): void {\n const a = m * c.scales[channel]!, px = (x + 0.5 - c.width / 2) * a, py = (c.height / 2 - y - 0.5) * a;\n const s = 1 + c.k1 * (px * px + py * py) / (c.radius * c.radius);\n out[0] = px * s + c.width / 2 - 0.5; out[1] = c.height / 2 - 0.5 - py * s;\n}\n/** Calls `visit` for every pixel centre on the border of `r`. */\nfunction border(r: Rect, visit: (x: number, y: number) => void): void {\n for (let x = r.x; x < r.x + r.width; x++) { visit(x, r.y); visit(x, r.y + r.height - 1); }\n for (let y = r.y; y < r.y + r.height; y++) { visit(r.x, y); visit(r.x + r.width - 1, y); }\n}\n\n/**\n * @internal Prepares the correction of a W × H frame. `lens` is the resolved catalogue lens (`resolveGenericLens`) on the\n * frame; `dispersion` is `genericLensDispersion`. The mapping's source radius grows with the output radius (the catalogue's\n * distortion is monotonic over the frame), so the sources of the border pixel centres bound every source, and the fill factor\n * is found by bisection on them.\n */\nexport function prepareDevelopedLensCorrection(lens: { halfDiagonalMm: number; character: { distortion: { corner: number }; lateralColor: { fraction: number } } },\n selection: DevelopedLensCorrectionSelection, width: number, height: number, pitchMm: number, dispersion: (wavelengthNm: number) => number): DevelopedLensCorrection {\n const fraction = selection.lateralColor ? lens.character.lateralColor.fraction : 0;\n const c: DevelopedLensCorrection = { width, height, radius: lens.halfDiagonalMm / pitchMm,\n k1: selection.distortion ? lens.character.distortion.corner : 0, lensK1: lens.character.distortion.corner,\n scales: [genericLensChannelScale(fraction, dispersion, \"red\"), genericLensChannelScale(fraction, dispersion, \"green\"), genericLensChannelScale(fraction, dispersion, \"blue\")], fill: 1 };\n const p = new Float64Array(2), frame = { x: 0, y: 0, width, height };\n const fits = (m: number): boolean => {\n let ok = true;\n border(frame, (x, y) => {\n for (let k = 0; k < 3 && ok; k++) { sourceOf(c, m, x, y, k, p); ok = p[0]! >= 0 && p[0]! <= width - 1 && p[1]! >= 0 && p[1]! <= height - 1; }\n });\n return ok;\n };\n if (!fits(1)) {\n let lo = 0.5, hi = 1;\n for (let i = 0; i < 52; i++) { const mid = (lo + hi) / 2; if (fits(mid)) lo = mid; else hi = mid; }\n c.fill = lo;\n }\n return c;\n}\n\n/**\n * @internal The developed pixels (a rectangle of the whole frame) that the corrected pixels of `region` read: the bounding box of\n * the region border's sources over the three channels, with their bilinear neighbours, one pixel of margin, clipped to the frame.\n */\nexport function developedLensCorrectionSourceRect(c: DevelopedLensCorrection, region: Rect): Rect {\n const p = new Float64Array(2);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n border(region, (x, y) => {\n for (let k = 0; k < 3; k++) {\n sourceOf(c, c.fill, x, y, k, p);\n x0 = Math.min(x0, p[0]!); x1 = Math.max(x1, p[0]!); y0 = Math.min(y0, p[1]!); y1 = Math.max(y1, p[1]!);\n }\n });\n const left = Math.max(0, Math.floor(x0) - 1), top = Math.max(0, Math.floor(y0) - 1);\n return { x: left, y: top, width: Math.min(c.width - 1, Math.floor(x1) + 2) - left + 1, height: Math.min(c.height - 1, Math.floor(y1) + 2) - top + 1 };\n}\n\n/**\n * @internal Corrects developed linear RGB (`rgb`, interleaved, the frame's pixels in `source`) into the pixels of `region`, both\n * rectangles of the whole frame. `cornerStops` is the lens's vignetting at the capture's f-number, or null to leave it.\n * Positions outside the frame (none after the fill, up to rounding) are clamped to the edge pixel centres.\n */\nexport function applyDevelopedLensCorrection(c: DevelopedLensCorrection, rgb: Float64Array, source: Rect, region: Rect, cornerStops: number | null): Float64Array {\n const out = new Float64Array(region.width * region.height * 3), W = c.width, H = c.height, cx = W / 2 - 0.5, cy = H / 2 - 0.5, r2 = c.radius * c.radius;\n const b = cornerStops === null ? 0 : 1 - 2 ** (-cornerStops / 2), k1 = c.k1, lensK1 = c.lensK1;\n for (let y = 0, o = 0; y < region.height; y++) for (let x = 0; x < region.width; x++) for (let k = 0; k < 3; k++, o++) {\n // sourceOf, inlined (a call per sample is about 5 times slower here): the same arithmetic, so the region bounds hold.\n const a = c.fill * c.scales[k]!, px = (region.x + x + 0.5 - W / 2) * a, py = (H / 2 - (region.y + y) - 0.5) * a, f = 1 + k1 * (px * px + py * py) / r2;\n const su = px * f + W / 2 - 0.5, sv = H / 2 - 0.5 - py * f;\n const u = Math.min(W - 1, Math.max(0, su)), v = Math.min(H - 1, Math.max(0, sv));\n const i = Math.floor(u), j = Math.floor(v), dx = u - i, dy = v - j, i1 = dx === 0 ? i : i + 1, j1 = dy === 0 ? j : j + 1;\n if (i < source.x || j < source.y || i1 >= source.x + source.width || j1 >= source.y + source.height)\n throw new InvalidScientificResultError(\"Lens correction read outside the developed rectangle.\");\n const row = (j - source.y) * source.width, row1 = (j1 - source.y) * source.width, a0 = i - source.x, a1 = i1 - source.x;\n let value = (1 - dy) * ((1 - dx) * rgb[(row + a0) * 3 + k]! + dx * rgb[(row + a1) * 3 + k]!) + dy * ((1 - dx) * rgb[(row1 + a0) * 3 + k]! + dx * rgb[(row1 + a1) * 3 + k]!);\n if (b !== 0) {\n // ρ_s², then ρ_i from ρ_s = ρ_i(1 + k1ρ_i²) by Newton's method (monotonic over the frame; converged to rounding in 4 steps).\n const s2 = ((su - cx) * (su - cx) + (sv - cy) * (sv - cy)) / r2, rs = Math.sqrt(s2), e = 1 - b * s2;\n let ri = rs;\n for (let n = 0; n < 4; n++) ri -= (ri * (1 + lensK1 * ri * ri) - rs) / (1 + 3 * lensK1 * ri * ri);\n value *= (1 + lensK1 * ri * ri) * (1 + 3 * lensK1 * ri * ri) / (e * e);\n }\n out[o] = value;\n }\n return out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"developed-lens-corrections.js","sourceRoot":"","sources":["../../src/output/developed-lens-corrections.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * @internal `output.lensCorrections` (#570): a catalogue lens's generic correction profile (`createGenericLensCorrectionProfile`)\n * applied to the developed linear RGB of `simulateCapture`, after demosaic, colour and white balance and before the tone curve.\n * The RAW and the DNG are never corrected.\n *\n * For an output pixel at p (pixels from the frame centre, +y up) and channel c, the source point is read by inverse mapping,\n * as the profile composes it (lateral colour, then distortion):\n * q = m · a_c · p, s = q · (1 + k1·|q|²/R²),\n * with a_c = 1 + L·D(λ_c) the channel's lateral-colour scale, k1 the lens's corner distortion, R the half-diagonal and m ≤ 1 the\n * fill factor (the largest that keeps every output pixel centre's source inside the frame for every channel, so the output keeps\n * its size with no missing pixels; the image is magnified by 1/m). The channel is sampled bilinearly at s.\n *\n * Vignetting is corrected to a flat field: the sample is multiplied by det J(ρ_i) / E(ρ_s), the inverse of the relative\n * illumination the capture delivered at s: the lens's E(ρ) = (1 − bρ²)² at the sensor point and its distortion's area factor\n * 1/det J, det J = (1 + k1ρ_i²)(1 + 3k1ρ_i²) at the ideal point ρ_i that the lens images to ρ_s (the capture's\n * \"inverse-distortion-jacobian\"; a relative illumination measured on a flat field contains it too). From profile 1.1.0 (#584) the\n * profile's vignetting component carries the same area factor (`distortionAreaFactor`), and its gain equals this kernel's.\n * Every position is computed in whole-frame pixel coordinates, so a region renders the same values as the whole frame.\n */\n\nimport { InvalidScientificResultError } from \"../core/validation.js\";\nimport type { LensCorrectionChannel } from \"./lens-corrections.js\";\n\n/**\n * Effective wavelengths of the red, green and blue channels for the lateral-colour map, nm: a generic approximation of where\n * a camera's channel responses peak, not a measured camera.\n */\nexport const GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM: Readonly<Record<LensCorrectionChannel, number>> = /* @__PURE__ */ Object.freeze({ red: 610, green: 540, blue: 465 });\n\n/** @internal The lateral-colour scale of one channel, 1 + L·D(λ_channel) (`D` the catalogue's normalized dispersion). */\nexport function genericLensChannelScale(fraction: number, dispersion: (wavelengthNm: number) => number, channel: LensCorrectionChannel): number {\n return 1 + fraction * dispersion(GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM[channel]);\n}\n\n/** @internal Which corrections run. */\nexport interface DevelopedLensCorrectionSelection { distortion: boolean; lateralColor: boolean; vignetting: boolean }\n/** @internal A prepared correction for one frame (pixel units). */\nexport interface DevelopedLensCorrection {\n width: number; height: number;\n /** Half-diagonal R in pixels. */\n radius: number;\n /** k1 (0 when distortion is off), the lens's own k1, and the red, green and blue scales a_c (1 when lateral colour is off). */\n k1: number; lensK1: number; scales: readonly [number, number, number];\n /** Fill factor m ≤ 1 (1/m is the magnification). */\n fill: number;\n}\ntype Rect = { x: number; y: number; width: number; height: number };\n\n/** Source position (pixel-centre coordinates of the whole frame, unclamped) of output pixel (x, y), channel c. */\nfunction sourceOf(c: DevelopedLensCorrection, m: number, x: number, y: number, channel: number, out: Float64Array): void {\n const a = m * c.scales[channel]!, px = (x + 0.5 - c.width / 2) * a, py = (c.height / 2 - y - 0.5) * a;\n const s = 1 + c.k1 * (px * px + py * py) / (c.radius * c.radius);\n out[0] = px * s + c.width / 2 - 0.5; out[1] = c.height / 2 - 0.5 - py * s;\n}\n/** Calls `visit` for every pixel centre on the border of `r`. */\nfunction border(r: Rect, visit: (x: number, y: number) => void): void {\n for (let x = r.x; x < r.x + r.width; x++) { visit(x, r.y); visit(x, r.y + r.height - 1); }\n for (let y = r.y; y < r.y + r.height; y++) { visit(r.x, y); visit(r.x + r.width - 1, y); }\n}\n\n/**\n * @internal Prepares the correction of a W × H frame. `lens` is the resolved catalogue lens (`resolveGenericLens`) on the\n * frame; `dispersion` is `genericLensDispersion`. The mapping's source radius grows with the output radius (the catalogue's\n * distortion is monotonic over the frame), so the sources of the border pixel centres bound every source, and the fill factor\n * is found by bisection on them.\n */\nexport function prepareDevelopedLensCorrection(lens: { halfDiagonalMm: number; character: { distortion: { corner: number }; lateralColor: { fraction: number } } },\n selection: DevelopedLensCorrectionSelection, width: number, height: number, pitchMm: number, dispersion: (wavelengthNm: number) => number): DevelopedLensCorrection {\n const fraction = selection.lateralColor ? lens.character.lateralColor.fraction : 0;\n const c: DevelopedLensCorrection = { width, height, radius: lens.halfDiagonalMm / pitchMm,\n k1: selection.distortion ? lens.character.distortion.corner : 0, lensK1: lens.character.distortion.corner,\n scales: [genericLensChannelScale(fraction, dispersion, \"red\"), genericLensChannelScale(fraction, dispersion, \"green\"), genericLensChannelScale(fraction, dispersion, \"blue\")], fill: 1 };\n const p = new Float64Array(2), frame = { x: 0, y: 0, width, height };\n const fits = (m: number): boolean => {\n let ok = true;\n border(frame, (x, y) => {\n for (let k = 0; k < 3 && ok; k++) { sourceOf(c, m, x, y, k, p); ok = p[0]! >= 0 && p[0]! <= width - 1 && p[1]! >= 0 && p[1]! <= height - 1; }\n });\n return ok;\n };\n if (!fits(1)) {\n let lo = 0.5, hi = 1;\n for (let i = 0; i < 52; i++) { const mid = (lo + hi) / 2; if (fits(mid)) lo = mid; else hi = mid; }\n c.fill = lo;\n }\n return c;\n}\n\n/**\n * @internal The developed pixels (a rectangle of the whole frame) that the corrected pixels of `region` read: the bounding box of\n * the region border's sources over the three channels, with their bilinear neighbours, one pixel of margin, clipped to the frame.\n */\nexport function developedLensCorrectionSourceRect(c: DevelopedLensCorrection, region: Rect): Rect {\n const p = new Float64Array(2);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n border(region, (x, y) => {\n for (let k = 0; k < 3; k++) {\n sourceOf(c, c.fill, x, y, k, p);\n x0 = Math.min(x0, p[0]!); x1 = Math.max(x1, p[0]!); y0 = Math.min(y0, p[1]!); y1 = Math.max(y1, p[1]!);\n }\n });\n const left = Math.max(0, Math.floor(x0) - 1), top = Math.max(0, Math.floor(y0) - 1);\n return { x: left, y: top, width: Math.min(c.width - 1, Math.floor(x1) + 2) - left + 1, height: Math.min(c.height - 1, Math.floor(y1) + 2) - top + 1 };\n}\n\n/**\n * @internal Corrects developed linear RGB (`rgb`, interleaved, the frame's pixels in `source`) into the pixels of `region`, both\n * rectangles of the whole frame. `cornerStops` is the lens's vignetting at the capture's f-number, or null to leave it.\n * Positions outside the frame (none after the fill, up to rounding) are clamped to the edge pixel centres.\n */\nexport function applyDevelopedLensCorrection(c: DevelopedLensCorrection, rgb: Float64Array, source: Rect, region: Rect, cornerStops: number | null): Float64Array {\n const out = new Float64Array(region.width * region.height * 3), W = c.width, H = c.height, cx = W / 2 - 0.5, cy = H / 2 - 0.5, r2 = c.radius * c.radius;\n const b = cornerStops === null ? 0 : 1 - 2 ** (-cornerStops / 2), k1 = c.k1, lensK1 = c.lensK1;\n for (let y = 0, o = 0; y < region.height; y++) for (let x = 0; x < region.width; x++) for (let k = 0; k < 3; k++, o++) {\n // sourceOf, inlined (a call per sample is about 5 times slower here): the same arithmetic, so the region bounds hold.\n const a = c.fill * c.scales[k]!, px = (region.x + x + 0.5 - W / 2) * a, py = (H / 2 - (region.y + y) - 0.5) * a, f = 1 + k1 * (px * px + py * py) / r2;\n const su = px * f + W / 2 - 0.5, sv = H / 2 - 0.5 - py * f;\n const u = Math.min(W - 1, Math.max(0, su)), v = Math.min(H - 1, Math.max(0, sv));\n const i = Math.floor(u), j = Math.floor(v), dx = u - i, dy = v - j, i1 = dx === 0 ? i : i + 1, j1 = dy === 0 ? j : j + 1;\n if (i < source.x || j < source.y || i1 >= source.x + source.width || j1 >= source.y + source.height)\n throw new InvalidScientificResultError(\"Lens correction read outside the developed rectangle.\");\n const row = (j - source.y) * source.width, row1 = (j1 - source.y) * source.width, a0 = i - source.x, a1 = i1 - source.x;\n let value = (1 - dy) * ((1 - dx) * rgb[(row + a0) * 3 + k]! + dx * rgb[(row + a1) * 3 + k]!) + dy * ((1 - dx) * rgb[(row1 + a0) * 3 + k]! + dx * rgb[(row1 + a1) * 3 + k]!);\n if (b !== 0) {\n // ρ_s², then ρ_i from ρ_s = ρ_i(1 + k1ρ_i²) by Newton's method (monotonic over the frame; converged to rounding in 4 steps).\n const s2 = ((su - cx) * (su - cx) + (sv - cy) * (sv - cy)) / r2, rs = Math.sqrt(s2), e = 1 - b * s2;\n let ri = rs;\n for (let n = 0; n < 4; n++) ri -= (ri * (1 + lensK1 * ri * ri) - rs) / (1 + 3 * lensK1 * ri * ri);\n value *= (1 + lensK1 * ri * ri) * (1 + 3 * lensK1 * ri * ri) / (e * e);\n }\n out[o] = value;\n }\n return out;\n}\n"]}
|
|
@@ -15,6 +15,8 @@ export interface CreateGenericLensCorrectionProfileInput {
|
|
|
15
15
|
/**
|
|
16
16
|
* Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own
|
|
17
17
|
* r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D(λ_channel), and the
|
|
18
|
-
* relative-illumination gain at the state's aperture (strength 1).
|
|
18
|
+
* relative-illumination gain at the state's aperture (strength 1). From profile version 1.1.0 (#584) the vignetting component
|
|
19
|
+
* (version 1.1.0) carries the distortion's area factor (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's
|
|
20
|
+
* sensor point, so it flattens a flat-field capture. The result passes `parseGenericLensCorrectionProfile`.
|
|
19
21
|
*/
|
|
20
22
|
export declare function createGenericLensCorrectionProfile(input: CreateGenericLensCorrectionProfileInput): GenericLensCorrectionProfile;
|
|
@@ -16,7 +16,9 @@ export { GENERIC_LENS_CORRECTION_CHANNEL_WAVELENGTHS_NM } from "./developed-lens
|
|
|
16
16
|
/**
|
|
17
17
|
* Builds the catalogue lens's generic correction profile for one camera state: a radial distortion map (the lens's own
|
|
18
18
|
* r_d = r(1 + k1ρ²), read at each ideal destination point), per-channel lateral-colour scales 1 + L·D(λ_channel), and the
|
|
19
|
-
* relative-illumination gain at the state's aperture (strength 1).
|
|
19
|
+
* relative-illumination gain at the state's aperture (strength 1). From profile version 1.1.0 (#584) the vignetting component
|
|
20
|
+
* (version 1.1.0) carries the distortion's area factor (`distortionAreaFactor`): its gain is det J(ρ_i)/E(ρ_s) at the capture's
|
|
21
|
+
* sensor point, so it flattens a flat-field capture. The result passes `parseGenericLensCorrectionProfile`.
|
|
20
22
|
*/
|
|
21
23
|
export function createGenericLensCorrectionProfile(input) {
|
|
22
24
|
requireInputObject(input, "createGenericLensCorrectionProfile input");
|
|
@@ -32,7 +34,7 @@ export function createGenericLensCorrectionProfile(input) {
|
|
|
32
34
|
const scale = (channel) => genericLensChannelScale(c.lateralColor.fraction, genericLensDispersion, channel);
|
|
33
35
|
const affine = (channel) => ({ id: lens.lensId + ":lateral-ca:" + channel, version: "1.0.0", domain, purpose: "lateral-ca", kind: "affine",
|
|
34
36
|
matrix: [scale(channel), 0, 0, scale(channel)], offsetMm: { x: 0, y: 0 } });
|
|
35
|
-
return parseGenericLensCorrectionProfile({ schemaVersion: "0.1.0", id: "photivra-" + lens.lensId + ":corrections", version: "1.
|
|
37
|
+
return parseGenericLensCorrectionProfile({ schemaVersion: "0.1.0", id: "photivra-" + lens.lensId + ":corrections", version: "1.1.0", state,
|
|
36
38
|
evidence: { kind: "generic-parametric", basis: "Generic catalogue lens " + lens.name + " (#549): the inverse of its own distortion, lateral colour and vignetting.",
|
|
37
39
|
residualNote: "Generic approximation, not a measured lens; axial colour, coma and the other aberrations stay.", sources: [...GENERIC_LENS_EVIDENCE] },
|
|
38
40
|
components: [
|
|
@@ -41,8 +43,10 @@ export function createGenericLensCorrectionProfile(input) {
|
|
|
41
43
|
profile: { normalizationRadiusMm: lens.halfDiagonalMm, maximumNormalizedRadius: 1.05, coefficients: { k1: c.distortion.corner, k2: 0, k3: 0 } } } },
|
|
42
44
|
{ ...identity, id: "lateral-ca", kind: "lateral-ca", residualNote: "Registers the channels at one effective wavelength each; colour inside a channel's band and axial colour stay.",
|
|
43
45
|
transforms: { red: affine("red"), green: affine("green"), blue: affine("blue") } },
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
// #584 (component 1.1.0): the gain also undoes the distortion's area factor, so a flat field comes out flat.
|
|
47
|
+
{ ...identity, version: "1.1.0", id: "vignetting", kind: "peripheral-illumination",
|
|
48
|
+
residualNote: "Gain det J/E at the capture's sensor point and the profile's aperture (E the lens's relative illumination, 1/det J its distortion's area factor); it raises corner noise with the signal.",
|
|
49
|
+
profile: { ...c.vignetting.profile, maximumNormalizedRadius: 1.05 }, strength: 1, distortionAreaFactor: { ...c.distortion.profile, coefficients: { ...c.distortion.profile.coefficients } } }
|
|
46
50
|
] });
|
|
47
51
|
}
|
|
48
52
|
//# sourceMappingURL=generic-lens-corrections.js.map
|