@photivra/engine 2.17.0 → 2.19.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 +116 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/dist/camera/sensor-equivalence.d.ts +156 -0
- package/dist/camera/sensor-equivalence.js +208 -0
- package/dist/camera/sensor-equivalence.js.map +1 -0
- package/dist/capture/focus-control.js +2 -75
- package/dist/capture/focus-control.js.map +1 -1
- package/dist/capture/native-environment-lean.d.ts +1 -0
- package/dist/capture/native-environment-lean.js +1 -1
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-optical-stage.d.ts +1 -0
- package/dist/capture/native-environment-optical-stage.js +2 -5
- package/dist/capture/native-environment-optical-stage.js.map +1 -1
- package/dist/capture/native-environment-raw.js +1 -5
- package/dist/capture/native-environment-raw.js.map +1 -1
- package/dist/capture/native-environment-tabulated.d.ts +5 -0
- package/dist/capture/native-environment-tabulated.js +1 -1
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/capture/release-sequence.js +2 -47
- package/dist/capture/release-sequence.js.map +1 -1
- package/dist/color/white-balance.d.ts +2 -0
- package/dist/color/white-balance.js +3 -56
- package/dist/color/white-balance.js.map +1 -1
- package/dist/composition/image-formation-plan.d.ts +1 -0
- package/dist/composition/image-formation-plan.js +3 -49
- package/dist/composition/image-formation-plan.js.map +1 -1
- package/dist/core/evidence-provenance.js +1 -16
- package/dist/core/evidence-provenance.js.map +1 -1
- package/dist/core/record-validation.d.ts +54 -0
- package/dist/core/record-validation.js +176 -0
- package/dist/core/record-validation.js.map +1 -1
- package/dist/core/scientific-assurance.js +1 -18
- package/dist/core/scientific-assurance.js.map +1 -1
- package/dist/core/shared-helpers.d.ts +33 -0
- package/dist/core/shared-helpers.js +34 -0
- package/dist/core/shared-helpers.js.map +1 -0
- package/dist/core/spectral-composition.js +1 -36
- package/dist/core/spectral-composition.js.map +1 -1
- package/dist/core/spectral.js +1 -19
- package/dist/core/spectral.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/exposure-capabilities.d.ts +1 -0
- package/dist/equipment/exposure-capabilities.js +2 -36
- package/dist/equipment/exposure-capabilities.js.map +1 -1
- package/dist/equipment/ideal-lens.d.ts +25 -0
- package/dist/equipment/ideal-lens.js +21 -0
- package/dist/equipment/ideal-lens.js.map +1 -0
- package/dist/equipment/iso-capabilities.js +2 -49
- package/dist/equipment/iso-capabilities.js.map +1 -1
- package/dist/equipment/metering-capabilities.js +1 -26
- package/dist/equipment/metering-capabilities.js.map +1 -1
- package/dist/equipment/release-capabilities.js +1 -53
- package/dist/equipment/release-capabilities.js.map +1 -1
- package/dist/exposure/auto-iso-slowest-shutter.js +1 -7
- package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
- package/dist/exposure/exposure-duration-control.js +1 -16
- package/dist/exposure/exposure-duration-control.js.map +1 -1
- package/dist/exposure/exposure-mode-resolver.js +1 -17
- package/dist/exposure/exposure-mode-resolver.js.map +1 -1
- package/dist/exposure/filtered-exposure-time.d.ts +93 -0
- package/dist/exposure/filtered-exposure-time.js +129 -0
- package/dist/exposure/filtered-exposure-time.js.map +1 -0
- package/dist/exposure/flash-ttl.d.ts +1 -0
- package/dist/exposure/flash-ttl.js +1 -1
- package/dist/exposure/flash-ttl.js.map +1 -1
- package/dist/exposure/flash.js +1 -49
- package/dist/exposure/flash.js.map +1 -1
- package/dist/exposure/metering-scene-radiance.js +1 -25
- package/dist/exposure/metering-scene-radiance.js.map +1 -1
- package/dist/exposure/metering-target.js +1 -30
- package/dist/exposure/metering-target.js.map +1 -1
- package/dist/exposure/metering-temporal.js +1 -8
- package/dist/exposure/metering-temporal.js.map +1 -1
- package/dist/exposure/metering.js +1 -72
- package/dist/exposure/metering.js.map +1 -1
- package/dist/exposure/program-line.js +1 -45
- package/dist/exposure/program-line.js.map +1 -1
- package/dist/facade/catalogue-lens-signal.d.ts +94 -0
- package/dist/facade/catalogue-lens-signal.js +362 -0
- package/dist/facade/catalogue-lens-signal.js.map +1 -0
- package/dist/facade/defocus-image-space-provider.js +5 -2
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/develop-joined-parts.d.ts +29 -0
- package/dist/facade/develop-joined-parts.js +33 -0
- package/dist/facade/develop-joined-parts.js.map +1 -0
- package/dist/facade/display-view-lens-signal.d.ts +32 -1
- package/dist/facade/display-view-lens-signal.js +85 -10
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +8 -0
- package/dist/facade/expected-signal-setup.js +7 -0
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +5 -0
- package/dist/facade/expected-signal.js +5 -1
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +5 -2
- package/dist/facade/generic-lens-capture.js +5 -2
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +67 -8
- package/dist/facade/simulate-capture.js +262 -126
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +1 -1
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.js +1 -3
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/chart-radiance-gpu.js +1 -5
- package/dist/gpu/chart-radiance-gpu.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +13 -1
- package/dist/gpu/display-view-quality-gpu.js +4 -8
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.js +3 -25
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/sensor-stage-gpu.js +1 -5
- package/dist/gpu/sensor-stage-gpu.js.map +1 -1
- package/dist/gpu/webgpu-runtime.d.ts +2 -0
- package/dist/gpu/webgpu-runtime.js +3 -0
- package/dist/gpu/webgpu-runtime.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/motion/camera-rotation.js +1 -6
- package/dist/motion/camera-rotation.js.map +1 -1
- package/dist/motion/capture-rotation-inverse-mapping.d.ts +12 -0
- package/dist/motion/capture-rotation-inverse-mapping.js +2 -2
- package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -1
- package/dist/motion/capture-rotation-temporal-quadrature.d.ts +1 -0
- package/dist/motion/capture-rotation-temporal-quadrature.js +1 -1
- package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -1
- package/dist/motion/capture-rotation-trajectory.js +2 -17
- package/dist/motion/capture-rotation-trajectory.js.map +1 -1
- package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts +1 -0
- package/dist/motion/capture-translation-parallax-temporal-quadrature.js +3 -14
- package/dist/motion/capture-translation-parallax-temporal-quadrature.js.map +1 -1
- package/dist/motion/extended-object-projection.js +5 -40
- package/dist/motion/extended-object-projection.js.map +1 -1
- package/dist/optics/atmospheric-haze.js +1 -7
- package/dist/optics/atmospheric-haze.js.map +1 -1
- package/dist/optics/brown-conrady-distortion.js +1 -7
- package/dist/optics/brown-conrady-distortion.js.map +1 -1
- package/dist/optics/complex-pupil-psf.d.ts +1 -0
- package/dist/optics/complex-pupil-psf.js +2 -84
- package/dist/optics/complex-pupil-psf.js.map +1 -1
- package/dist/optics/focus-state.js +1 -11
- package/dist/optics/focus-state.js.map +1 -1
- package/dist/optics/front-of-lens-filter.d.ts +5 -0
- package/dist/optics/front-of-lens-filter.js +3 -65
- package/dist/optics/front-of-lens-filter.js.map +1 -1
- package/dist/optics/generic-nd-filter-presets.d.ts +12 -0
- package/dist/optics/generic-nd-filter-presets.js +48 -0
- package/dist/optics/generic-nd-filter-presets.js.map +1 -0
- package/dist/optics/illumination-vignetting.js +1 -6
- package/dist/optics/illumination-vignetting.js.map +1 -1
- package/dist/optics/lens-psf-profile.d.ts +4 -1
- package/dist/optics/lens-psf-profile.js +12 -111
- package/dist/optics/lens-psf-profile.js.map +1 -1
- package/dist/optics/longitudinal-chromatic-aberration.js +1 -7
- package/dist/optics/longitudinal-chromatic-aberration.js.map +1 -1
- package/dist/optics/pupil-vignetting.js +1 -7
- package/dist/optics/pupil-vignetting.js.map +1 -1
- package/dist/optics/radial-distortion.js +1 -6
- package/dist/optics/radial-distortion.js.map +1 -1
- package/dist/optics/scene-to-sensor-irradiance.js +4 -96
- package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
- package/dist/optics/stray-light.d.ts +1 -0
- package/dist/optics/stray-light.js +1 -1
- package/dist/optics/stray-light.js.map +1 -1
- package/dist/optics/zernike-wavefront.js +1 -7
- package/dist/optics/zernike-wavefront.js.map +1 -1
- package/dist/output/capture-geometry.js +1 -8
- package/dist/output/capture-geometry.js.map +1 -1
- package/dist/output/developed-lens-corrections.d.ts +62 -0
- package/dist/output/developed-lens-corrections.js +135 -0
- package/dist/output/developed-lens-corrections.js.map +1 -0
- package/dist/output/generic-lens-corrections.d.ts +2 -6
- package/dist/output/generic-lens-corrections.js +3 -6
- package/dist/output/generic-lens-corrections.js.map +1 -1
- package/dist/output/geometric-transforms.js +1 -4
- package/dist/output/geometric-transforms.js.map +1 -1
- package/dist/output/native-capture-sdr.d.ts +2 -2
- package/dist/output/native-capture-sdr.js +3 -2
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/processing/development-detail.d.ts +29 -0
- package/dist/processing/development-detail.js +128 -107
- package/dist/processing/development-detail.js.map +1 -1
- package/dist/schema/illumination-temporal.js +3 -72
- package/dist/schema/illumination-temporal.js.map +1 -1
- package/dist/schema/illumination.d.ts +1 -0
- package/dist/schema/illumination.js +2 -65
- package/dist/schema/illumination.js.map +1 -1
- package/dist/schema/scene-radiance-temporal.js +2 -14
- package/dist/schema/scene-radiance-temporal.js.map +1 -1
- package/dist/schema/scene-radiance.js +2 -60
- package/dist/schema/scene-radiance.js.map +1 -1
- package/dist/schema/validation.d.ts +2 -0
- package/dist/schema/validation.js +2 -17
- package/dist/schema/validation.js.map +1 -1
- package/dist/sensor/accumulated-charge.js +2 -59
- package/dist/sensor/accumulated-charge.js.map +1 -1
- package/dist/sensor/architecture.js +1 -16
- package/dist/sensor/architecture.js.map +1 -1
- package/dist/sensor/camera-saturation-capacity.d.ts +1 -0
- package/dist/sensor/camera-saturation-capacity.js +3 -55
- package/dist/sensor/camera-saturation-capacity.js.map +1 -1
- package/dist/sensor/capture-color-sampling-binding.js +1 -26
- package/dist/sensor/capture-color-sampling-binding.js.map +1 -1
- package/dist/sensor/capture-mode-timing.js +1 -39
- package/dist/sensor/capture-mode-timing.js.map +1 -1
- package/dist/sensor/capture-mode.js +1 -23
- package/dist/sensor/capture-mode.js.map +1 -1
- package/dist/sensor/color-sampling.js +1 -26
- package/dist/sensor/color-sampling.js.map +1 -1
- package/dist/sensor/constant-rate-temporal-integration.js +2 -46
- package/dist/sensor/constant-rate-temporal-integration.js.map +1 -1
- package/dist/sensor/dark-current.js +2 -45
- package/dist/sensor/dark-current.js.map +1 -1
- package/dist/sensor/eqe-electron-rate.js +2 -35
- package/dist/sensor/eqe-electron-rate.js.map +1 -1
- package/dist/sensor/exposure-window.d.ts +5 -0
- package/dist/sensor/exposure-window.js +7 -22
- package/dist/sensor/exposure-window.js.map +1 -1
- package/dist/sensor/iso-gain.js +1 -7
- package/dist/sensor/iso-gain.js.map +1 -1
- package/dist/sensor/iso-signal-chain.js +1 -43
- package/dist/sensor/iso-signal-chain.js.map +1 -1
- package/dist/sensor/optical-stack.js +1 -25
- package/dist/sensor/optical-stack.js.map +1 -1
- package/dist/sensor/photon-energy.js +1 -26
- package/dist/sensor/photon-energy.js.map +1 -1
- package/dist/sensor/physical-charge-capacity.js +3 -77
- package/dist/sensor/physical-charge-capacity.js.map +1 -1
- package/dist/sensor/radiometry-readiness.js +2 -23
- package/dist/sensor/radiometry-readiness.js.map +1 -1
- package/dist/sensor/raw-readout.d.ts +3 -0
- package/dist/sensor/raw-readout.js +4 -66
- package/dist/sensor/raw-readout.js.map +1 -1
- package/dist/sensor/raw-reconstruction.js +2 -64
- package/dist/sensor/raw-reconstruction.js.map +1 -1
- package/dist/sensor/readout-exposure-linkage.js +1 -9
- package/dist/sensor/readout-exposure-linkage.js.map +1 -1
- package/dist/sensor/readout-timing.js +3 -57
- package/dist/sensor/readout-timing.js.map +1 -1
- package/dist/sensor/response-application-compatibility.d.ts +2 -0
- package/dist/sensor/response-application-compatibility.js +3 -29
- package/dist/sensor/response-application-compatibility.js.map +1 -1
- package/dist/sensor/response-operating-range.d.ts +1 -0
- package/dist/sensor/response-operating-range.js +3 -79
- package/dist/sensor/response-operating-range.js.map +1 -1
- package/dist/sensor/responsivity-photocurrent.js +4 -108
- package/dist/sensor/responsivity-photocurrent.js.map +1 -1
- package/dist/sensor/sampling-aperture.js +1 -25
- package/dist/sensor/sampling-aperture.js.map +1 -1
- package/dist/sensor/spatial-sample-reduction.js +2 -19
- package/dist/sensor/spatial-sample-reduction.js.map +1 -1
- package/dist/sensor/spatial-sampling-quadrature.js +1 -3
- package/dist/sensor/spatial-sampling-quadrature.js.map +1 -1
- package/dist/sensor/spatio-spectral-reduction.js +1 -18
- package/dist/sensor/spatio-spectral-reduction.js.map +1 -1
- package/dist/sensor/spectral-coverage.d.ts +10 -1
- package/dist/sensor/spectral-coverage.js +1 -1
- package/dist/sensor/spectral-coverage.js.map +1 -1
- package/dist/sensor/spectral-quadrature.js +2 -35
- package/dist/sensor/spectral-quadrature.js.map +1 -1
- package/dist/sensor/spectral-response.js +1 -47
- package/dist/sensor/spectral-response.js.map +1 -1
- package/dist/stabilization/camera-shake.js +1 -6
- package/dist/stabilization/camera-shake.js.map +1 -1
- package/dist/stabilization/system.js +2 -80
- package/dist/stabilization/system.js.map +1 -1
- package/docs/AGENT_GUIDE.md +4 -0
- package/docs/API_REFERENCE.md +69 -2
- package/docs/QUICK_START.md +136 -6
- package/docs/USAGE.md +89 -3
- package/docs/api/camera-sensor-equivalence.md +209 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-ideal-lens.md +26 -0
- package/docs/api/exports.json +118 -3
- package/docs/api/exposure-filtered-exposure-time.md +105 -0
- package/docs/api/facade-develop-joined-parts.md +32 -0
- package/docs/api/facade-simulate-capture.md +62 -8
- package/docs/api/optics-generic-nd-filter-presets.md +34 -0
- package/docs/api/optics-lens-psf-profile.md +3 -1
- package/docs/api/output-developed-lens-corrections.md +12 -0
- package/docs/api/output-generic-lens-corrections.md +0 -9
- package/llms.txt +3 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spatial-sample-reduction.js","sourceRoot":"","sources":["../../src/sensor/spatial-sample-reduction.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;GAUG;AAEH,OAAO,EACL,mBAAmB,EAEpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAKpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,MAAM,mCAAmC,GAAG,KAAK,CAAC;AAClD,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAC/B,MAAM,cAAc,GAAG,KAAK,CAAC;AAuH7B,SAAS,6BAA6B,CACpC,KAAc,EACd,IAAY;IAEZ,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC;QAC5B,KAAK,GAAG,CAAC,EACT,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,IAAI;YACF,uCAAuC,CAC1C,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,wBAAwB,CAC/B,KAAc,EACd,IAAY;IAEZ,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,KAAK,GAAG,CAAC,EACT,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,IAAI;YACF,yDAAyD,CAC5D,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,qBAAqB,CAC5B,KAAc,EACd,IAAY;IAEZ,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,KAAK,IAAI,CAAC,EACV,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,IAAI;YACF,6CAA6C,CAChD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,gBAAgB,CACvB,KAAc;IAEd,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EACpB,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,gCAAgC,CACjC,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,KAAgC,CAAC;IAEhD,IAAI,MAAM,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;QACtC,IACE,MAAM,CAAC,IAAI,KAAK,UAAU;YAC1B,MAAM,CAAC,QAAQ;gBACb,2CAA2C,EAC7C,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,8HAA8H,CAC/H,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,iBAAiB;YACvB,IAAI,EAAE,UAAU;YAChB,QAAQ,EACN,2CAA2C;SAC9C,CAAC;IACJ,CAAC;IAED,IACE,MAAM,CAAC,IAAI,KAAK,wBAAwB,EACxC,CAAC;QACD,IACE,MAAM,CAAC,IAAI,KAAK,OAAO;YACvB,MAAM,CAAC,QAAQ;gBACb,yBAAyB,EAC3B,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,sGAAsG,CACvG,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,wBAAwB;YAC9B,IAAI,EAAE,OAAO;YACb,QAAQ,EAAE,yBAAyB;SACpC,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,yBAAyB,CACjC,8BAA8B,CAC/B,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sCAAsC,CACpD,QAA6C;IAE7C,OAAO,CACL,QAAQ,CAAC,0BAA0B;QACnC,GAAG;QACH,QAAQ,CAAC,oBAAoB;QAC7B,GAAG;QACH,QAAQ,CAAC,oBAAoB,CAC9B,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAC7B,IAAyC,EACzC,KAAa;IAEb,MAAM,QAAQ,GAAG;QACf,0BAA0B,EACxB,6BAA6B,CAC3B,IAAI,CAAC,0BAA0B,EAC/B,mBAAmB;YACjB,KAAK;YACL,8BAA8B,CACjC;QACH,oBAAoB,EAClB,6BAA6B,CAC3B,IAAI,CAAC,oBAAoB,EACzB,mBAAmB;YACjB,KAAK;YACL,wBAAwB,CAC3B;QACH,oBAAoB,EAClB,6BAA6B,CAC3B,IAAI,CAAC,oBAAoB,EACzB,mBAAmB;YACjB,KAAK;YACL,wBAAwB,CAC3B;KACJ,CAAC;IAEF,wBAAwB,CACtB,IAAI,CAAC,+BAA+B,EACpC,mBAAmB;QACjB,KAAK;QACL,mCAAmC,CACtC,CAAC;IACF,wBAAwB,CACtB,IAAI,CAAC,oCAAoC,EACzC,mBAAmB;QACjB,KAAK;QACL,wCAAwC,CAC3C,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,kBAAkB,CACzB,MAAc,EACd,QAAgB,EAChB,SAAiB;IAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAChB,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CACnB,CAAC;IACF,OAAO,CACL,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC;QAC3B,SAAS,GAAG,KAAK,CAClB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uCAAuC,CACrD,UAA2C;IAE3C,IACE,OAAO,UAAU,KAAK,QAAQ;QAC9B,UAAU,KAAK,IAAI;QACnB,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAChC,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,wCAAwC,CACzC,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAClB,6BAA6B,CAC3B,UAAU,CAAC,cAAc,EACzB,2BAA2B,CAC5B,CAAC;IAEJ,IACE,cAAc,KAAK,CAAC;QACpB,UAAU,CAAC,KAAK,CAAC,MAAM;YACrB,cAAc,EAChB,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,uFAAuF,CACxF,CAAC;IACJ,CAAC;IAED,IACE,UAAU,CAAC,sBAAsB,KAAK,SAAS;QAC/C,CACE,OAAO,UAAU,CAAC,sBAAsB,KAAK,QAAQ;YACrD,UAAU,CAAC,sBAAsB,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CACtD,EACD,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IAED,IACE,OAAO,UAAU,CAAC,SAAS,KAAK,QAAQ;QACxC,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EACxC,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,kDAAkD,CACnD,CAAC;IACJ,CAAC;IAED,6BAA6B,CAC3B,UAAU,CAAC,IAAI,CAAC,CAAC,EACjB,mBAAmB,CACpB,CAAC;IACF,6BAA6B,CAC3B,UAAU,CAAC,IAAI,CAAC,CAAC,EACjB,mBAAmB,CACpB,CAAC;IAEF,MAAM,aAAa,GACjB,qBAAqB,CACnB,UAAU,CAAC,sCAAsC,EACjD,mDAAmD,CACpD,CAAC;IAEJ,MAAM,UAAU,GACd,IAAI,GAAG,EAGJ,CAAC;IACN,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,UAAU,CAAC,KAAK,CAAC,OAAO,CACtB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACd,MAAM,QAAQ,GACZ,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,sCAAsC,CAAC,QAAQ,CAAC,CAAC;QAE7D,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,2BAA2B,CACnC,gDAAgD,CACjD,CAAC;QACJ,CAAC;QAED,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC1B,SAAS;YACP,IAAI,CAAC,+BAA+B,CAAC;QACvC,OAAO;YACL,IAAI,CAAC,oCAAoC,CAAC;QAE5C,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;YAC3B,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EACzB,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,2DAA2D,CAC5D,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,IACE,CAAC,kBAAkB,CACjB,SAAS,EACT,CAAC,EACD,gBAAgB,CACjB,EACD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,+DAA+D,CAChE,CAAC;IACJ,CAAC;IAED,IACE,CAAC,kBAAkB,CACjB,OAAO,EACP,aAAa,EACb,cAAc,CACf,EACD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,sFAAsF,CACvF,CAAC;IACJ,CAAC;IAED,IACE,CAAC,kBAAkB,CACjB,UAAU,CAAC,0BAA0B,EACrC,SAAS,EACT,gBAAgB,CACjB,EACD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,oEAAoE,CACrE,CAAC;IACJ,CAAC;IAED,IACE,CAAC,kBAAkB,CACjB,UAAU,CAAC,uCAAuC,EAClD,OAAO,EACP,cAAc,CACf,EACD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,kFAAkF,CACnF,CAAC;IACJ,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,eAAe,CACtB,UAAuD,EACvD,UAGC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,yBAAyB,CACjC,8BAA8B,CAC/B,CAAC;IACJ,CAAC;IAED,IACE,UAAU,CAAC,MAAM;QACjB,UAAU,CAAC,IAAI,EACf,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,sEAAsE,CACvE,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE9C,UAAU,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAClC,IACE,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,KAAK,IAAI;YACd,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;YAC9B,KAAK,CAAC,IAAI,KAAK,IAAI,EACnB,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,aAAa;gBACX,KAAK;gBACL,wCAAwC,CAC3C,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG;YACf,0BAA0B,EACxB,6BAA6B,CAC3B,KAAK,CAAC,IAAI;iBACP,0BAA0B,EAC7B,aAAa;gBACX,KAAK;gBACL,mCAAmC,CACtC;YACH,oBAAoB,EAClB,6BAA6B,CAC3B,KAAK,CAAC,IAAI,CAAC,oBAAoB,EAC/B,aAAa;gBACX,KAAK;gBACL,6BAA6B,CAChC;YACH,oBAAoB,EAClB,6BAA6B,CAC3B,KAAK,CAAC,IAAI,CAAC,oBAAoB,EAC/B,aAAa;gBACX,KAAK;gBACL,6BAA6B,CAChC;SACJ,CAAC;QACF,MAAM,GAAG,GAAG,sCAAsC,CAAC,QAAQ,CAAC,CAAC;QAE7D,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,2BAA2B,CACnC,aAAa;gBACX,KAAK;gBACL,wDAAwD,CAC3D,CAAC;QACJ,CAAC;QACD,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,2BAA2B,CACnC,gDAAgD,CACjD,CAAC;QACJ,CAAC;QAED,WAAW,CAAC,GAAG,CACb,GAAG,EACH,wBAAwB,CACtB,KAAK,CAAC,KAAK,EACX,aAAa;YACX,KAAK;YACL,SAAS,CACZ,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,qCAAqC,CACnD,KAAiD;IAEjD,gBAAgB,CAAC,KAAK,EAAE,uCAAuC,EAAE,CAAC,YAAY,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC;IAC9G,MAAM,MAAM,GAAG,gBAAgB,CAC7B,KAAK,CAAC,WAAW,CAClB,CAAC;IACF,MAAM,UAAU,GAAG,uCAAuC,CACxD,KAAK,CAAC,UAAU,CACjB,CAAC;IACF,MAAM,WAAW,GAAG,eAAe,CACjC,KAAK,CAAC,UAAU,EAChB,UAAU,CACX,CAAC;IAEF,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,IAAI,oBAAoB,GAAG,CAAC,CAAC;IAE7B,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QAC/B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,2BAA2B,CACnC,yDAAyD,CAC1D,CAAC;QACJ,CAAC;QAED,eAAe;YACb,KAAK;gBACL,IAAI,CAAC,+BAA+B,CAAC;QACvC,oBAAoB;YAClB,KAAK;gBACL,IAAI,CAAC,oCAAoC,CAAC;QAE5C,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC;YACjC,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EACtC,CAAC;YACD,MAAM,IAAI,2BAA2B,CACnC,4CAA4C,CAC7C,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAEhB,MAAM,CAAC,IAAI,KAAK,iBAAiB;QAC/B,CAAC,CAAC;YACE,IAAI,EAAE,iBAAiB;YACvB,IAAI,EAAE,UAAU;YAChB,QAAQ,EACN,2CAA2C;YAC7C,gCAAgC,EAC9B,eAAe;YACjB,8CAA8C,EAC5C,oBAAoB;SACvB;QACH,CAAC,CAAC;YACE,IAAI,EAAE,wBAAwB;YAC9B,IAAI,EAAE,OAAO;YACb,QAAQ,EACN,yBAAyB;YAC3B,qDAAqD,EACnD,eAAe;YACjB,kCAAkC,EAChC,oBAAoB;gBACpB,mCAAmC;SACtC,CAAC;IAER,OAAO,mBAAmB,CACxB;QACE,IAAI,EAAE;YACJ,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI;SACzB;QACD,SAAS,EACP,KAAK,CAAC,UAAU,CAAC,SAAS;QAC5B,aAAa,EACX,4CAA4C;QAC9C,WAAW,EAAE,MAAM;QACnB,SAAS,EACP,KAAK,CAAC,UAAU,CAAC,cAAc;QACjC,qBAAqB,EACnB,0BAA0B;QAC5B,+BAA+B,EAAE,IAAI;QACrC,sCAAsC,EACpC,KAAK,CAAC,UAAU;aACb,gDAAgD;YACnD,CAAC;QACH,0BAA0B,EACxB,KAAK,CAAC,UAAU;aACb,0BAA0B;QAC/B,sCAAsC,EACpC,KAAK,CAAC,UAAU;aACb,sCAAsC;QAC3C,YAAY;QACZ,2BAA2B,EAAE,KAAK;QAClC,2BAA2B,EAAE,KAAK;QAClC,0BAA0B,EAAE,KAAK;QACjC,uBAAuB,EAAE,KAAK;QAC9B,wBAAwB,EAAE,KAAK;QAC/B,wBAAwB,EAAE,KAAK;QAC/B,iCAAiC,EAC/B,KAAK;QACP,iBAAiB,EAAE,KAAK;QACxB,mBAAmB,EAAE,KAAK;QAC1B,gBAAgB,EAAE,KAAK;QACvB,gBAAgB,EAAE,KAAK;QACvB,sBAAsB,EAAE,KAAK;QAC7B,iBAAiB,EAAE,KAAK;QACxB,iBAAiB,EAAE,KAAK;QACxB,oBAAoB,EAAE,KAAK;QAC3B,+BAA+B,EAAE,KAAK;KACvC,EACD,iCAAiC,EACjC,OAAO,EACP;QACE,2GAA2G;QAC3G,kGAAkG;QAClG,+IAA+I;QAC/I,mIAAmI;QACnI,gKAAgK;QAChK,uQAAuQ;QACvQ,qMAAqM;QACrM,wJAAwJ;QACxJ,6LAA6L;KAC9L,CACF,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spatial sample reduction: combines values sampled at the spatial quadrature nodes of one sensor site\n * (its aperture plus the anti-aliasing (AA) split) into a site average and an area integral.\n *\n * Each supplied value is matched to its node by an explicit identity key. Average weights and\n * geometric area measures (in square micrometres) are kept separate. Negative or non-finite irradiance,\n * and missing or duplicate nodes, are rejected. The geometry does not establish QE (quantum\n * efficiency), an effective light-collection area or calibrated light transport.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n SensorSpatialSamplingQuadrature,\n SensorSpatialSamplingQuadratureNode\n} from \"./spatial-sampling-quadrature.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nconst SQUARE_MICROMETERS_TO_SQUARE_METERS = 1e-12;\nconst WEIGHT_TOLERANCE = 1e-10;\nconst AREA_TOLERANCE = 1e-10;\n\nexport type SensorSpatialSampleValueDomain =\n | {\n kind: \"relative-linear\";\n unit: \"relative\";\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\";\n }\n | {\n kind: \"radiometric-irradiance\";\n unit: \"W/m^2\";\n semantic: \"sensor-plane-irradiance\";\n };\n\nexport interface SensorSpatialQuadratureNodeIdentity {\n antiAliasingComponentIndex: number;\n apertureSampleXIndex: number;\n apertureSampleYIndex: number;\n}\n\nexport interface SensorSpatialQuadratureNodeValue {\n node: SensorSpatialQuadratureNodeIdentity;\n /**\n * A nonnegative, finite number in the declared value domain.\n *\n * It is evaluated at the matching node's preAntiAliasingSourcePointMm.\n */\n value: number;\n}\n\nexport interface ReduceSensorSpatialSamplingQuadratureInput {\n quadrature: SensorSpatialSamplingQuadrature;\n valueDomain: SensorSpatialSampleValueDomain;\n /**\n * Exactly one value per quadrature node, each with its node identity.\n *\n * Order does not matter; the node identity does.\n */\n nodeValues: readonly SensorSpatialQuadratureNodeValue[];\n}\n\nexport type SensorSpatialSampleReductionValue =\n | {\n kind: \"relative-linear\";\n unit: \"relative\";\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\";\n /**\n * Weighted spatial average (dimensionless), in the same relative domain.\n */\n normalizedSpatialAverageRelative: number;\n /**\n * Relative value × square micrometres.\n *\n * A geometric, area-weighted diagnostic, not a radiometric power.\n */\n geometricAreaIntegralRelativeSquareMicrometers: number;\n }\n | {\n kind: \"radiometric-irradiance\";\n unit: \"W/m^2\";\n semantic: \"sensor-plane-irradiance\";\n /**\n * Weighted mean irradiance over the geometric aperture, after the\n * declared normalized AA spreading.\n */\n normalizedSpatialAverageIrradianceWattsPerSquareMeter: number;\n /**\n * Incident radiant flux: the supplied irradiance integrated over the\n * geometric aperture, with the declared normalized AA spreading.\n * Units: watts.\n *\n * It is before any CFA response, QE or microlens response, and excludes\n * any optical-stack throughput not already in the caller's irradiance values.\n */\n geometricApertureIncidentFluxWatts: number;\n };\n\nexport interface SensorSpatialSampleReduction {\n site: {\n x: number;\n y: number;\n };\n /**\n * Label of the destination CFA (colour filter array) site only.\n *\n * This reducer applies no spectral filter response.\n */\n channelId: string;\n outputMeaning:\n \"channel-tagged-pre-response-spatial-sample\";\n valueDomain: SensorSpatialSampleValueDomain;\n nodeCount: number;\n sourceValuesMatchedBy:\n \"quadrature-node-identity\";\n sourceValuesSuppliedForAllNodes: true;\n outsideImagingAreaSourceValuesRequired: boolean;\n normalizedSpatialWeightSum: number;\n geometricApertureAreaSquareMicrometers: number;\n reducedValue: SensorSpatialSampleReductionValue;\n cfaSpectralFilteringApplied: false;\n channelIdIsSpectralResponse: false;\n temporalIntegrationApplied: false;\n exposureDurationApplied: false;\n quantumEfficiencyApplied: false;\n microlensResponseApplied: false;\n opticalThroughputAppliedByReducer: false;\n photonsCalculated: false;\n electronsCalculated: false;\n shotNoiseApplied: false;\n readNoiseApplied: false;\n adcQuantizationApplied: false;\n blackLevelApplied: false;\n saturationApplied: false;\n rawCodeValueProduced: false;\n demosaicOrReconstructionApplied: false;\n}\n\nfunction requireSafeNonNegativeInteger(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isSafeInteger(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be a non-negative safe integer.\"\n );\n }\n return value;\n}\n\nfunction requireNonNegativeFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be a finite number greater than or equal to zero.\"\n );\n }\n return value;\n}\n\nfunction requirePositiveFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value <= 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be a finite number greater than zero.\"\n );\n }\n return value;\n}\n\nfunction parseValueDomain(\n value: unknown\n): SensorSpatialSampleValueDomain {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value)\n ) {\n throw new InvalidConfigurationError(\n \"valueDomain must be an object.\"\n );\n }\n\n const record = value as Record<string, unknown>;\n\n if (record.kind === \"relative-linear\") {\n if (\n record.unit !== \"relative\" ||\n record.semantic !==\n \"nonnegative-sensor-plane-irradiance-proxy\"\n ) {\n throw new InvalidScientificInputError(\n \"relative-linear valueDomain must use unit \\\"relative\\\" and the canonical nonnegative sensor-plane irradiance-proxy semantic.\"\n );\n }\n return {\n kind: \"relative-linear\",\n unit: \"relative\",\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\"\n };\n }\n\n if (\n record.kind === \"radiometric-irradiance\"\n ) {\n if (\n record.unit !== \"W/m^2\" ||\n record.semantic !==\n \"sensor-plane-irradiance\"\n ) {\n throw new InvalidScientificInputError(\n \"radiometric-irradiance valueDomain must use unit \\\"W/m^2\\\" and semantic \\\"sensor-plane-irradiance\\\".\"\n );\n }\n return {\n kind: \"radiometric-irradiance\",\n unit: \"W/m^2\",\n semantic: \"sensor-plane-irradiance\"\n };\n }\n\n throw new InvalidConfigurationError(\n \"valueDomain.kind is invalid.\"\n );\n}\n\n/**\n * Makes the deterministic key used to match a value to its spatial quadrature node. It identifies the\n * sample; it is not a cryptographic checksum.\n * @param identity - The input; see `SensorSpatialQuadratureNodeIdentity` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function sensorSpatialQuadratureNodeIdentityKey(\n identity: SensorSpatialQuadratureNodeIdentity\n): string {\n return (\n identity.antiAliasingComponentIndex +\n \":\" +\n identity.apertureSampleXIndex +\n \":\" +\n identity.apertureSampleYIndex\n );\n}\n\nfunction validateQuadratureNode(\n node: SensorSpatialSamplingQuadratureNode,\n index: number\n): SensorSpatialQuadratureNodeIdentity {\n const identity = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n node.antiAliasingComponentIndex,\n \"quadrature.nodes[\" +\n index +\n \"].antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n node.apertureSampleXIndex,\n \"quadrature.nodes[\" +\n index +\n \"].apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n node.apertureSampleYIndex,\n \"quadrature.nodes[\" +\n index +\n \"].apertureSampleYIndex\"\n )\n };\n\n requireNonNegativeFinite(\n node.combinedNormalizedSpatialWeight,\n \"quadrature.nodes[\" +\n index +\n \"].combinedNormalizedSpatialWeight\"\n );\n requireNonNegativeFinite(\n node.combinedAreaMeasureSquareMicrometers,\n \"quadrature.nodes[\" +\n index +\n \"].combinedAreaMeasureSquareMicrometers\"\n );\n\n return identity;\n}\n\nfunction approximatelyEqual(\n actual: number,\n expected: number,\n tolerance: number\n): boolean {\n const scale = Math.max(\n 1,\n Math.abs(actual),\n Math.abs(expected)\n );\n return (\n Math.abs(actual - expected) <=\n tolerance * scale\n );\n}\n\n/**\n * Rechecks node coordinates, weights, geometric measures and coverage before a spatial plan is trusted\n * as scientific input.\n *\n * Average weights and geometric area measures are kept separate. The geometry does not establish QE,\n * an effective light-collection area or calibrated light transport.\n * @param quadrature - The input; see `SensorSpatialSamplingQuadrature` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function validateSensorSpatialSamplingQuadrature(\n quadrature: SensorSpatialSamplingQuadrature\n): Map<string, SensorSpatialSamplingQuadratureNode> {\n if (\n typeof quadrature !== \"object\" ||\n quadrature === null ||\n !Array.isArray(quadrature.nodes)\n ) {\n throw new InvalidConfigurationError(\n \"quadrature must contain a nodes array.\"\n );\n }\n\n const totalNodeCount =\n requireSafeNonNegativeInteger(\n quadrature.totalNodeCount,\n \"quadrature.totalNodeCount\"\n );\n\n if (\n totalNodeCount === 0 ||\n quadrature.nodes.length !==\n totalNodeCount\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.totalNodeCount must be positive and exactly match quadrature.nodes.length.\"\n );\n }\n\n if (\n quadrature.colorSamplingProfileId !== undefined &&\n (\n typeof quadrature.colorSamplingProfileId !== \"string\" ||\n quadrature.colorSamplingProfileId.trim().length === 0\n )\n ) {\n throw new InvalidConfigurationError(\n \"quadrature.colorSamplingProfileId must be a non-empty string when supplied.\"\n );\n }\n\n if (\n typeof quadrature.channelId !== \"string\" ||\n quadrature.channelId.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"quadrature.channelId must be a non-empty string.\"\n );\n }\n\n requireSafeNonNegativeInteger(\n quadrature.site.x,\n \"quadrature.site.x\"\n );\n requireSafeNonNegativeInteger(\n quadrature.site.y,\n \"quadrature.site.y\"\n );\n\n const geometricArea =\n requirePositiveFinite(\n quadrature.geometricApertureAreaSquareMicrometers,\n \"quadrature.geometricApertureAreaSquareMicrometers\"\n );\n\n const nodesByKey =\n new Map<\n string,\n SensorSpatialSamplingQuadratureNode\n >();\n let weightSum = 0;\n let areaSum = 0;\n\n quadrature.nodes.forEach(\n (node, index) => {\n const identity =\n validateQuadratureNode(node, index);\n const key = sensorSpatialQuadratureNodeIdentityKey(identity);\n\n if (nodesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"quadrature contains duplicate node identities.\"\n );\n }\n\n nodesByKey.set(key, node);\n weightSum +=\n node.combinedNormalizedSpatialWeight;\n areaSum +=\n node.combinedAreaMeasureSquareMicrometers;\n\n if (\n !Number.isFinite(weightSum) ||\n !Number.isFinite(areaSum)\n ) {\n throw new InvalidScientificInputError(\n \"quadrature aggregate weights/measures must remain finite.\"\n );\n }\n }\n );\n\n if (\n !approximatelyEqual(\n weightSum,\n 1,\n WEIGHT_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature combined normalized spatial weights must sum to 1.\"\n );\n }\n\n if (\n !approximatelyEqual(\n areaSum,\n geometricArea,\n AREA_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature combined geometric area measures must sum to the geometric aperture area.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.normalizedSpatialWeightSum,\n weightSum,\n WEIGHT_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.normalizedSpatialWeightSum does not match node weights.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.combinedAreaMeasureSumSquareMicrometers,\n areaSum,\n AREA_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.combinedAreaMeasureSumSquareMicrometers does not match node measures.\"\n );\n }\n\n return nodesByKey;\n}\n\nfunction parseNodeValues(\n nodeValues: readonly SensorSpatialQuadratureNodeValue[],\n nodesByKey: ReadonlyMap<\n string,\n SensorSpatialSamplingQuadratureNode\n >\n): Map<string, number> {\n if (!Array.isArray(nodeValues)) {\n throw new InvalidConfigurationError(\n \"nodeValues must be an array.\"\n );\n }\n\n if (\n nodeValues.length !==\n nodesByKey.size\n ) {\n throw new InvalidScientificInputError(\n \"nodeValues must contain exactly one value for every quadrature node.\"\n );\n }\n\n const valuesByKey = new Map<string, number>();\n\n nodeValues.forEach((entry, index) => {\n if (\n typeof entry !== \"object\" ||\n entry === null ||\n typeof entry.node !== \"object\" ||\n entry.node === null\n ) {\n throw new InvalidScientificInputError(\n \"nodeValues[\" +\n index +\n \"] must contain a node identity object.\"\n );\n }\n\n const identity = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n entry.node\n .antiAliasingComponentIndex,\n \"nodeValues[\" +\n index +\n \"].node.antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n entry.node.apertureSampleXIndex,\n \"nodeValues[\" +\n index +\n \"].node.apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n entry.node.apertureSampleYIndex,\n \"nodeValues[\" +\n index +\n \"].node.apertureSampleYIndex\"\n )\n };\n const key = sensorSpatialQuadratureNodeIdentityKey(identity);\n\n if (!nodesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"nodeValues[\" +\n index +\n \"] identifies a node that is not present in quadrature.\"\n );\n }\n if (valuesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"nodeValues contains duplicate node identities.\"\n );\n }\n\n valuesByKey.set(\n key,\n requireNonNegativeFinite(\n entry.value,\n \"nodeValues[\" +\n index +\n \"].value\"\n )\n );\n });\n\n return valuesByKey;\n}\n\n/**\n * Combines nonnegative linear values, each tagged with its node, over a\n * validated sensor spatial quadrature.\n *\n * It does not sample a renderer or optical model itself. Callers evaluate one\n * value at every node's preAntiAliasingSourcePointMm and pass the values with\n * their exact node identity. Order does not matter.\n *\n * Two value domains are supported:\n *\n * - relative-linear: a dimensionless, nonnegative stand-in for irradiance, for\n * deterministic renderer and science regression tests;\n * - radiometric-irradiance: physical irradiance on the sensor plane in W/m^2.\n *\n * The destination site's channelId is a label only. No CFA spectral\n * transmittance or sensitivity is applied, so do not call the output a\n * colour-filtered mosaic sample or a RAW value.\n */\nexport function reduceSensorSpatialSamplingQuadrature(\n input: ReduceSensorSpatialSamplingQuadratureInput\n): CalculationResult<SensorSpatialSampleReduction> {\n requireInputKeys(input, \"reduceSensorSpatialSamplingQuadrature\", [\"quadrature\", \"valueDomain\", \"nodeValues\"]);\n const domain = parseValueDomain(\n input.valueDomain\n );\n const nodesByKey = validateSensorSpatialSamplingQuadrature(\n input.quadrature\n );\n const valuesByKey = parseNodeValues(\n input.nodeValues,\n nodesByKey\n );\n\n let weightedAverage = 0;\n let areaWeightedIntegral = 0;\n\n nodesByKey.forEach((node, key) => {\n const value = valuesByKey.get(key);\n if (value === undefined) {\n throw new InvalidScientificInputError(\n \"A quadrature node is missing its explicit source value.\"\n );\n }\n\n weightedAverage +=\n value *\n node.combinedNormalizedSpatialWeight;\n areaWeightedIntegral +=\n value *\n node.combinedAreaMeasureSquareMicrometers;\n\n if (\n !Number.isFinite(weightedAverage) ||\n !Number.isFinite(areaWeightedIntegral)\n ) {\n throw new InvalidScientificInputError(\n \"Reduced spatial sample must remain finite.\"\n );\n }\n });\n\n const reducedValue:\n SensorSpatialSampleReductionValue =\n domain.kind === \"relative-linear\"\n ? {\n kind: \"relative-linear\",\n unit: \"relative\",\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\",\n normalizedSpatialAverageRelative:\n weightedAverage,\n geometricAreaIntegralRelativeSquareMicrometers:\n areaWeightedIntegral\n }\n : {\n kind: \"radiometric-irradiance\",\n unit: \"W/m^2\",\n semantic:\n \"sensor-plane-irradiance\",\n normalizedSpatialAverageIrradianceWattsPerSquareMeter:\n weightedAverage,\n geometricApertureIncidentFluxWatts:\n areaWeightedIntegral *\n SQUARE_MICROMETERS_TO_SQUARE_METERS\n };\n\n return approximationResult(\n {\n site: {\n ...input.quadrature.site\n },\n channelId:\n input.quadrature.channelId,\n outputMeaning:\n \"channel-tagged-pre-response-spatial-sample\",\n valueDomain: domain,\n nodeCount:\n input.quadrature.totalNodeCount,\n sourceValuesMatchedBy:\n \"quadrature-node-identity\",\n sourceValuesSuppliedForAllNodes: true,\n outsideImagingAreaSourceValuesRequired:\n input.quadrature\n .preAntiAliasingSourceOutsideImagingAreaNodeCount >\n 0,\n normalizedSpatialWeightSum:\n input.quadrature\n .normalizedSpatialWeightSum,\n geometricApertureAreaSquareMicrometers:\n input.quadrature\n .geometricApertureAreaSquareMicrometers,\n reducedValue,\n cfaSpectralFilteringApplied: false,\n channelIdIsSpectralResponse: false,\n temporalIntegrationApplied: false,\n exposureDurationApplied: false,\n quantumEfficiencyApplied: false,\n microlensResponseApplied: false,\n opticalThroughputAppliedByReducer:\n false,\n photonsCalculated: false,\n electronsCalculated: false,\n shotNoiseApplied: false,\n readNoiseApplied: false,\n adcQuantizationApplied: false,\n blackLevelApplied: false,\n saturationApplied: false,\n rawCodeValueProduced: false,\n demosaicOrReconstructionApplied: false\n },\n \"sensor-spatial-sample-reduction\",\n \"1.0.0\",\n [\n \"Input values are nonnegative linear scalars evaluated at each quadrature node's pre-AA source coordinate.\",\n \"Node values are matched by explicit quadrature-node identity rather than positional array order.\",\n \"The destination CFA/color channel is metadata only; no spectral transmittance, sensor spectral responsivity or quantum efficiency is applied.\",\n \"Relative-linear values are dimensionless irradiance-like proxies for deterministic testing and do not become physical radiometry.\",\n \"Radiometric-irradiance values use W/m^2; geometric aperture integration converts square micrometres to square metres to report incident radiant flux in watts.\",\n \"The physical irradiance branch reports geometric-aperture incident flux under the caller-supplied irradiance field and normalized AA spatial redistribution; it does not add omitted optical-stack throughput, microlens response, CFA response or sensor conversion.\",\n \"Every quadrature node requires an explicit source value, including pre-AA source points outside the active imaging area; no automatic clamping, zero-fill, extrapolation or renormalization occurs.\",\n \"Negative, NaN and infinite source values are rejected; signed intermediate color-transform values are outside this pre-response sensor-plane contract.\",\n \"Temporal exposure integration, wavelength integration, photons/electrons, noise, saturation, black level, ADC quantization, RAW code values and reconstruction remain separate future work.\"\n ]\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"spatial-sample-reduction.js","sourceRoot":"","sources":["../../src/sensor/spatial-sample-reduction.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spatial sample reduction: combines values sampled at the spatial quadrature nodes of one sensor site\n * (its aperture plus the anti-aliasing (AA) split) into a site average and an area integral.\n *\n * Each supplied value is matched to its node by an explicit identity key. Average weights and\n * geometric area measures (in square micrometres) are kept separate. Negative or non-finite irradiance,\n * and missing or duplicate nodes, are rejected. The geometry does not establish QE (quantum\n * efficiency), an effective light-collection area or calibrated light transport.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n SensorSpatialSamplingQuadrature,\n SensorSpatialSamplingQuadratureNode\n} from \"./spatial-sampling-quadrature.js\";\nimport { requireInputKeys, requireInputNonNegativeSafeInteger as requireSafeNonNegativeInteger } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requirePositive as requirePositiveFinite } from \"../exposure/flash-ttl.js\";\n\nconst SQUARE_MICROMETERS_TO_SQUARE_METERS = 1e-12;\nconst WEIGHT_TOLERANCE = 1e-10;\nconst AREA_TOLERANCE = 1e-10;\n\nexport type SensorSpatialSampleValueDomain =\n | {\n kind: \"relative-linear\";\n unit: \"relative\";\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\";\n }\n | {\n kind: \"radiometric-irradiance\";\n unit: \"W/m^2\";\n semantic: \"sensor-plane-irradiance\";\n };\n\nexport interface SensorSpatialQuadratureNodeIdentity {\n antiAliasingComponentIndex: number;\n apertureSampleXIndex: number;\n apertureSampleYIndex: number;\n}\n\nexport interface SensorSpatialQuadratureNodeValue {\n node: SensorSpatialQuadratureNodeIdentity;\n /**\n * A nonnegative, finite number in the declared value domain.\n *\n * It is evaluated at the matching node's preAntiAliasingSourcePointMm.\n */\n value: number;\n}\n\nexport interface ReduceSensorSpatialSamplingQuadratureInput {\n quadrature: SensorSpatialSamplingQuadrature;\n valueDomain: SensorSpatialSampleValueDomain;\n /**\n * Exactly one value per quadrature node, each with its node identity.\n *\n * Order does not matter; the node identity does.\n */\n nodeValues: readonly SensorSpatialQuadratureNodeValue[];\n}\n\nexport type SensorSpatialSampleReductionValue =\n | {\n kind: \"relative-linear\";\n unit: \"relative\";\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\";\n /**\n * Weighted spatial average (dimensionless), in the same relative domain.\n */\n normalizedSpatialAverageRelative: number;\n /**\n * Relative value × square micrometres.\n *\n * A geometric, area-weighted diagnostic, not a radiometric power.\n */\n geometricAreaIntegralRelativeSquareMicrometers: number;\n }\n | {\n kind: \"radiometric-irradiance\";\n unit: \"W/m^2\";\n semantic: \"sensor-plane-irradiance\";\n /**\n * Weighted mean irradiance over the geometric aperture, after the\n * declared normalized AA spreading.\n */\n normalizedSpatialAverageIrradianceWattsPerSquareMeter: number;\n /**\n * Incident radiant flux: the supplied irradiance integrated over the\n * geometric aperture, with the declared normalized AA spreading.\n * Units: watts.\n *\n * It is before any CFA response, QE or microlens response, and excludes\n * any optical-stack throughput not already in the caller's irradiance values.\n */\n geometricApertureIncidentFluxWatts: number;\n };\n\nexport interface SensorSpatialSampleReduction {\n site: {\n x: number;\n y: number;\n };\n /**\n * Label of the destination CFA (colour filter array) site only.\n *\n * This reducer applies no spectral filter response.\n */\n channelId: string;\n outputMeaning:\n \"channel-tagged-pre-response-spatial-sample\";\n valueDomain: SensorSpatialSampleValueDomain;\n nodeCount: number;\n sourceValuesMatchedBy:\n \"quadrature-node-identity\";\n sourceValuesSuppliedForAllNodes: true;\n outsideImagingAreaSourceValuesRequired: boolean;\n normalizedSpatialWeightSum: number;\n geometricApertureAreaSquareMicrometers: number;\n reducedValue: SensorSpatialSampleReductionValue;\n cfaSpectralFilteringApplied: false;\n channelIdIsSpectralResponse: false;\n temporalIntegrationApplied: false;\n exposureDurationApplied: false;\n quantumEfficiencyApplied: false;\n microlensResponseApplied: false;\n opticalThroughputAppliedByReducer: false;\n photonsCalculated: false;\n electronsCalculated: false;\n shotNoiseApplied: false;\n readNoiseApplied: false;\n adcQuantizationApplied: false;\n blackLevelApplied: false;\n saturationApplied: false;\n rawCodeValueProduced: false;\n demosaicOrReconstructionApplied: false;\n}\n\nfunction requireNonNegativeFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be a finite number greater than or equal to zero.\"\n );\n }\n return value;\n}\n\nfunction parseValueDomain(\n value: unknown\n): SensorSpatialSampleValueDomain {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value)\n ) {\n throw new InvalidConfigurationError(\n \"valueDomain must be an object.\"\n );\n }\n\n const record = value as Record<string, unknown>;\n\n if (record.kind === \"relative-linear\") {\n if (\n record.unit !== \"relative\" ||\n record.semantic !==\n \"nonnegative-sensor-plane-irradiance-proxy\"\n ) {\n throw new InvalidScientificInputError(\n \"relative-linear valueDomain must use unit \\\"relative\\\" and the canonical nonnegative sensor-plane irradiance-proxy semantic.\"\n );\n }\n return {\n kind: \"relative-linear\",\n unit: \"relative\",\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\"\n };\n }\n\n if (\n record.kind === \"radiometric-irradiance\"\n ) {\n if (\n record.unit !== \"W/m^2\" ||\n record.semantic !==\n \"sensor-plane-irradiance\"\n ) {\n throw new InvalidScientificInputError(\n \"radiometric-irradiance valueDomain must use unit \\\"W/m^2\\\" and semantic \\\"sensor-plane-irradiance\\\".\"\n );\n }\n return {\n kind: \"radiometric-irradiance\",\n unit: \"W/m^2\",\n semantic: \"sensor-plane-irradiance\"\n };\n }\n\n throw new InvalidConfigurationError(\n \"valueDomain.kind is invalid.\"\n );\n}\n\n/**\n * Makes the deterministic key used to match a value to its spatial quadrature node. It identifies the\n * sample; it is not a cryptographic checksum.\n * @param identity - The input; see `SensorSpatialQuadratureNodeIdentity` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function sensorSpatialQuadratureNodeIdentityKey(\n identity: SensorSpatialQuadratureNodeIdentity\n): string {\n return (\n identity.antiAliasingComponentIndex +\n \":\" +\n identity.apertureSampleXIndex +\n \":\" +\n identity.apertureSampleYIndex\n );\n}\n\nfunction validateQuadratureNode(\n node: SensorSpatialSamplingQuadratureNode,\n index: number\n): SensorSpatialQuadratureNodeIdentity {\n const identity = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n node.antiAliasingComponentIndex,\n \"quadrature.nodes[\" +\n index +\n \"].antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n node.apertureSampleXIndex,\n \"quadrature.nodes[\" +\n index +\n \"].apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n node.apertureSampleYIndex,\n \"quadrature.nodes[\" +\n index +\n \"].apertureSampleYIndex\"\n )\n };\n\n requireNonNegativeFinite(\n node.combinedNormalizedSpatialWeight,\n \"quadrature.nodes[\" +\n index +\n \"].combinedNormalizedSpatialWeight\"\n );\n requireNonNegativeFinite(\n node.combinedAreaMeasureSquareMicrometers,\n \"quadrature.nodes[\" +\n index +\n \"].combinedAreaMeasureSquareMicrometers\"\n );\n\n return identity;\n}\n\nfunction approximatelyEqual(\n actual: number,\n expected: number,\n tolerance: number\n): boolean {\n const scale = Math.max(\n 1,\n Math.abs(actual),\n Math.abs(expected)\n );\n return (\n Math.abs(actual - expected) <=\n tolerance * scale\n );\n}\n\n/**\n * Rechecks node coordinates, weights, geometric measures and coverage before a spatial plan is trusted\n * as scientific input.\n *\n * Average weights and geometric area measures are kept separate. The geometry does not establish QE,\n * an effective light-collection area or calibrated light transport.\n * @param quadrature - The input; see `SensorSpatialSamplingQuadrature` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function validateSensorSpatialSamplingQuadrature(\n quadrature: SensorSpatialSamplingQuadrature\n): Map<string, SensorSpatialSamplingQuadratureNode> {\n if (\n typeof quadrature !== \"object\" ||\n quadrature === null ||\n !Array.isArray(quadrature.nodes)\n ) {\n throw new InvalidConfigurationError(\n \"quadrature must contain a nodes array.\"\n );\n }\n\n const totalNodeCount =\n requireSafeNonNegativeInteger(\n quadrature.totalNodeCount,\n \"quadrature.totalNodeCount\"\n );\n\n if (\n totalNodeCount === 0 ||\n quadrature.nodes.length !==\n totalNodeCount\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.totalNodeCount must be positive and exactly match quadrature.nodes.length.\"\n );\n }\n\n if (\n quadrature.colorSamplingProfileId !== undefined &&\n (\n typeof quadrature.colorSamplingProfileId !== \"string\" ||\n quadrature.colorSamplingProfileId.trim().length === 0\n )\n ) {\n throw new InvalidConfigurationError(\n \"quadrature.colorSamplingProfileId must be a non-empty string when supplied.\"\n );\n }\n\n if (\n typeof quadrature.channelId !== \"string\" ||\n quadrature.channelId.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"quadrature.channelId must be a non-empty string.\"\n );\n }\n\n requireSafeNonNegativeInteger(\n quadrature.site.x,\n \"quadrature.site.x\"\n );\n requireSafeNonNegativeInteger(\n quadrature.site.y,\n \"quadrature.site.y\"\n );\n\n const geometricArea =\n requirePositiveFinite(\n quadrature.geometricApertureAreaSquareMicrometers,\n \"quadrature.geometricApertureAreaSquareMicrometers\"\n );\n\n const nodesByKey =\n new Map<\n string,\n SensorSpatialSamplingQuadratureNode\n >();\n let weightSum = 0;\n let areaSum = 0;\n\n quadrature.nodes.forEach(\n (node, index) => {\n const identity =\n validateQuadratureNode(node, index);\n const key = sensorSpatialQuadratureNodeIdentityKey(identity);\n\n if (nodesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"quadrature contains duplicate node identities.\"\n );\n }\n\n nodesByKey.set(key, node);\n weightSum +=\n node.combinedNormalizedSpatialWeight;\n areaSum +=\n node.combinedAreaMeasureSquareMicrometers;\n\n if (\n !Number.isFinite(weightSum) ||\n !Number.isFinite(areaSum)\n ) {\n throw new InvalidScientificInputError(\n \"quadrature aggregate weights/measures must remain finite.\"\n );\n }\n }\n );\n\n if (\n !approximatelyEqual(\n weightSum,\n 1,\n WEIGHT_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature combined normalized spatial weights must sum to 1.\"\n );\n }\n\n if (\n !approximatelyEqual(\n areaSum,\n geometricArea,\n AREA_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature combined geometric area measures must sum to the geometric aperture area.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.normalizedSpatialWeightSum,\n weightSum,\n WEIGHT_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.normalizedSpatialWeightSum does not match node weights.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.combinedAreaMeasureSumSquareMicrometers,\n areaSum,\n AREA_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.combinedAreaMeasureSumSquareMicrometers does not match node measures.\"\n );\n }\n\n return nodesByKey;\n}\n\nfunction parseNodeValues(\n nodeValues: readonly SensorSpatialQuadratureNodeValue[],\n nodesByKey: ReadonlyMap<\n string,\n SensorSpatialSamplingQuadratureNode\n >\n): Map<string, number> {\n if (!Array.isArray(nodeValues)) {\n throw new InvalidConfigurationError(\n \"nodeValues must be an array.\"\n );\n }\n\n if (\n nodeValues.length !==\n nodesByKey.size\n ) {\n throw new InvalidScientificInputError(\n \"nodeValues must contain exactly one value for every quadrature node.\"\n );\n }\n\n const valuesByKey = new Map<string, number>();\n\n nodeValues.forEach((entry, index) => {\n if (\n typeof entry !== \"object\" ||\n entry === null ||\n typeof entry.node !== \"object\" ||\n entry.node === null\n ) {\n throw new InvalidScientificInputError(\n \"nodeValues[\" +\n index +\n \"] must contain a node identity object.\"\n );\n }\n\n const identity = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n entry.node\n .antiAliasingComponentIndex,\n \"nodeValues[\" +\n index +\n \"].node.antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n entry.node.apertureSampleXIndex,\n \"nodeValues[\" +\n index +\n \"].node.apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n entry.node.apertureSampleYIndex,\n \"nodeValues[\" +\n index +\n \"].node.apertureSampleYIndex\"\n )\n };\n const key = sensorSpatialQuadratureNodeIdentityKey(identity);\n\n if (!nodesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"nodeValues[\" +\n index +\n \"] identifies a node that is not present in quadrature.\"\n );\n }\n if (valuesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"nodeValues contains duplicate node identities.\"\n );\n }\n\n valuesByKey.set(\n key,\n requireNonNegativeFinite(\n entry.value,\n \"nodeValues[\" +\n index +\n \"].value\"\n )\n );\n });\n\n return valuesByKey;\n}\n\n/**\n * Combines nonnegative linear values, each tagged with its node, over a\n * validated sensor spatial quadrature.\n *\n * It does not sample a renderer or optical model itself. Callers evaluate one\n * value at every node's preAntiAliasingSourcePointMm and pass the values with\n * their exact node identity. Order does not matter.\n *\n * Two value domains are supported:\n *\n * - relative-linear: a dimensionless, nonnegative stand-in for irradiance, for\n * deterministic renderer and science regression tests;\n * - radiometric-irradiance: physical irradiance on the sensor plane in W/m^2.\n *\n * The destination site's channelId is a label only. No CFA spectral\n * transmittance or sensitivity is applied, so do not call the output a\n * colour-filtered mosaic sample or a RAW value.\n */\nexport function reduceSensorSpatialSamplingQuadrature(\n input: ReduceSensorSpatialSamplingQuadratureInput\n): CalculationResult<SensorSpatialSampleReduction> {\n requireInputKeys(input, \"reduceSensorSpatialSamplingQuadrature\", [\"quadrature\", \"valueDomain\", \"nodeValues\"]);\n const domain = parseValueDomain(\n input.valueDomain\n );\n const nodesByKey = validateSensorSpatialSamplingQuadrature(\n input.quadrature\n );\n const valuesByKey = parseNodeValues(\n input.nodeValues,\n nodesByKey\n );\n\n let weightedAverage = 0;\n let areaWeightedIntegral = 0;\n\n nodesByKey.forEach((node, key) => {\n const value = valuesByKey.get(key);\n if (value === undefined) {\n throw new InvalidScientificInputError(\n \"A quadrature node is missing its explicit source value.\"\n );\n }\n\n weightedAverage +=\n value *\n node.combinedNormalizedSpatialWeight;\n areaWeightedIntegral +=\n value *\n node.combinedAreaMeasureSquareMicrometers;\n\n if (\n !Number.isFinite(weightedAverage) ||\n !Number.isFinite(areaWeightedIntegral)\n ) {\n throw new InvalidScientificInputError(\n \"Reduced spatial sample must remain finite.\"\n );\n }\n });\n\n const reducedValue:\n SensorSpatialSampleReductionValue =\n domain.kind === \"relative-linear\"\n ? {\n kind: \"relative-linear\",\n unit: \"relative\",\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\",\n normalizedSpatialAverageRelative:\n weightedAverage,\n geometricAreaIntegralRelativeSquareMicrometers:\n areaWeightedIntegral\n }\n : {\n kind: \"radiometric-irradiance\",\n unit: \"W/m^2\",\n semantic:\n \"sensor-plane-irradiance\",\n normalizedSpatialAverageIrradianceWattsPerSquareMeter:\n weightedAverage,\n geometricApertureIncidentFluxWatts:\n areaWeightedIntegral *\n SQUARE_MICROMETERS_TO_SQUARE_METERS\n };\n\n return approximationResult(\n {\n site: {\n ...input.quadrature.site\n },\n channelId:\n input.quadrature.channelId,\n outputMeaning:\n \"channel-tagged-pre-response-spatial-sample\",\n valueDomain: domain,\n nodeCount:\n input.quadrature.totalNodeCount,\n sourceValuesMatchedBy:\n \"quadrature-node-identity\",\n sourceValuesSuppliedForAllNodes: true,\n outsideImagingAreaSourceValuesRequired:\n input.quadrature\n .preAntiAliasingSourceOutsideImagingAreaNodeCount >\n 0,\n normalizedSpatialWeightSum:\n input.quadrature\n .normalizedSpatialWeightSum,\n geometricApertureAreaSquareMicrometers:\n input.quadrature\n .geometricApertureAreaSquareMicrometers,\n reducedValue,\n cfaSpectralFilteringApplied: false,\n channelIdIsSpectralResponse: false,\n temporalIntegrationApplied: false,\n exposureDurationApplied: false,\n quantumEfficiencyApplied: false,\n microlensResponseApplied: false,\n opticalThroughputAppliedByReducer:\n false,\n photonsCalculated: false,\n electronsCalculated: false,\n shotNoiseApplied: false,\n readNoiseApplied: false,\n adcQuantizationApplied: false,\n blackLevelApplied: false,\n saturationApplied: false,\n rawCodeValueProduced: false,\n demosaicOrReconstructionApplied: false\n },\n \"sensor-spatial-sample-reduction\",\n \"1.0.0\",\n [\n \"Input values are nonnegative linear scalars evaluated at each quadrature node's pre-AA source coordinate.\",\n \"Node values are matched by explicit quadrature-node identity rather than positional array order.\",\n \"The destination CFA/color channel is metadata only; no spectral transmittance, sensor spectral responsivity or quantum efficiency is applied.\",\n \"Relative-linear values are dimensionless irradiance-like proxies for deterministic testing and do not become physical radiometry.\",\n \"Radiometric-irradiance values use W/m^2; geometric aperture integration converts square micrometres to square metres to report incident radiant flux in watts.\",\n \"The physical irradiance branch reports geometric-aperture incident flux under the caller-supplied irradiance field and normalized AA spatial redistribution; it does not add omitted optical-stack throughput, microlens response, CFA response or sensor conversion.\",\n \"Every quadrature node requires an explicit source value, including pre-AA source points outside the active imaging area; no automatic clamping, zero-fill, extrapolation or renormalization occurs.\",\n \"Negative, NaN and infinite source values are rejected; signed intermediate color-transform values are outside this pre-response sensor-plane contract.\",\n \"Temporal exposure integration, wavelength integration, photons/electrons, noise, saturation, black level, ADC quantization, RAW code values and reconstruction remain separate future work.\"\n ]\n );\n}\n"]}
|
|
@@ -18,10 +18,8 @@ import { InvalidScientificInputError, requirePositiveInteger } from "../core/val
|
|
|
18
18
|
import { resolveAntiAliasingSpatialKernel } from "./optical-stack.js";
|
|
19
19
|
import { resolveSensorSamplingAperture } from "./sampling-aperture.js";
|
|
20
20
|
import { requireInputKeys } from "../core/record-validation.js";
|
|
21
|
+
import { midpointPhase as midpointCoordinate } from "../motion/capture-rotation-temporal-quadrature.js";
|
|
21
22
|
const MAX_SPATIAL_QUADRATURE_NODES = 100_000;
|
|
22
|
-
function midpointCoordinate(index, count) {
|
|
23
|
-
return (index + 0.5) / count;
|
|
24
|
-
}
|
|
25
23
|
function insideImagingArea(pointMm, imagingArea) {
|
|
26
24
|
const halfWidth = imagingArea.widthMm / 2;
|
|
27
25
|
const halfHeight = imagingArea.heightMm / 2;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spatial-sampling-quadrature.js","sourceRoot":"","sources":["../../src/sensor/spatial-sampling-quadrature.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spatial sampling quadrature: the sample points (nodes) and weights used to average light over one\n * sensor site.\n *\n * It combines the site's geometric light-sensitive aperture (sampling-aperture) with the anti-aliasing\n * (AA) point-splitting kernel (optical-stack). The aperture is integrated with a grid of evenly spaced\n * midpoints. If an AA component moves light by +delta on the sensor, a destination point samples the\n * light field before AA at destination − delta, so the shift is never applied twice. Normalized weights\n * give a spatial average; square-micrometre measures give a geometric area integral. Neither includes\n * throughput, QE (quantum efficiency), photon conversion, microlenses, diffusion, crosstalk or\n * integration over time.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveInteger\n} from \"../core/validation.js\";\nimport type {\n NativeEffectiveRasterColorSamplingBindingProfile\n} from \"./capture-color-sampling-binding.js\";\nimport type {\n NativeColorSamplingSiteIndex,\n SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport {\n resolveAntiAliasingSpatialKernel,\n type SensorOpticalStackProfile\n} from \"./optical-stack.js\";\nimport {\n resolveSensorSamplingAperture,\n type SensorSamplingApertureProfile\n} from \"./sampling-aperture.js\";\nimport type {\n NativeImageRaster,\n SensorImagingArea\n} from \"./sensor-geometry.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\n\nconst MAX_SPATIAL_QUADRATURE_NODES = 100_000;\n\nexport interface CalculateSensorSpatialSamplingQuadratureInput {\n imagingArea: SensorImagingArea;\n nativeRaster: NativeImageRaster;\n colorSamplingProfile: SensorColorSamplingProfile;\n colorSamplingBindingProfile: NativeEffectiveRasterColorSamplingBindingProfile;\n samplingApertureProfile: SensorSamplingApertureProfile;\n opticalStackProfile: SensorOpticalStackProfile;\n site: NativeColorSamplingSiteIndex;\n /** Number of midpoint cells across the geometric aperture width. */\n spatialSampleCountX: number;\n /** Number of midpoint cells across the geometric aperture height. */\n spatialSampleCountY: number;\n}\n\nexport interface SensorSpatialSamplingQuadratureNode {\n antiAliasingComponentIndex: number;\n apertureSampleXIndex: number;\n apertureSampleYIndex: number;\n /**\n * Destination point inside the geometric sensitive aperture, after the\n * declared AA point splitting.\n *\n * Native sensor physical coordinates, in millimetres from the optical axis.\n */\n destinationAperturePointMm: {\n x: number;\n y: number;\n };\n /**\n * How far the declared AA component moves light on the sensor plane.\n *\n * +X right, +Y down; micrometres.\n */\n antiAliasingOffsetMicrometers: {\n x: number;\n y: number;\n };\n /**\n * The point in the light field before AA that lands on\n * destinationAperturePointMm through this split component:\n *\n * source = destination - AA offset\n */\n preAntiAliasingSourcePointMm: {\n x: number;\n y: number;\n };\n /**\n * Whether the pre-AA source point is inside the active physical imaging\n * area. False is not an error; downstream code decides how to handle valid\n * light from outside the active sampling area.\n */\n preAntiAliasingSourceInsideImagingArea: boolean;\n /** Dimensionless midpoint weight for the aperture-area average. */\n normalizedApertureWeight: number;\n /** Dimensionless normalized weight from the AA split component. */\n normalizedAntiAliasingWeight: number;\n /**\n * Product of the two weights (dimensionless), used to average a light field\n * over the site with this quadrature.\n */\n combinedNormalizedSpatialWeight: number;\n /**\n * Geometric area this aperture midpoint stands for, before the AA component\n * weight is applied. Units: square micrometres.\n */\n apertureAreaMeasureSquareMicrometers: number;\n /**\n * Geometric area multiplied by the (dimensionless) AA component weight.\n * Units are still square micrometres.\n *\n * This is not an effective light-collection area.\n */\n combinedAreaMeasureSquareMicrometers: number;\n}\n\nexport interface SensorSpatialSamplingQuadrature {\n coordinateSystem: \"native-sensor-physical\";\n colorSamplingProfileId?: string;\n samplingApertureProfileId?: string;\n opticalStackProfileId?: string;\n site: NativeColorSamplingSiteIndex;\n channelId: string;\n quadratureScheme:\n \"tensor-product-uniform-midpoint\";\n outputMeaning:\n \"pre-aa-optical-field-spatial-quadrature-nodes\";\n spatialSampleCountX: number;\n spatialSampleCountY: number;\n antiAliasingComponentCount: number;\n totalNodeCount: number;\n geometricApertureAreaSquareMicrometers: number;\n nominalSiteCellAreaSquareMicrometers?: number;\n geometricSensitiveAreaFractionOfLatticeCell?: number;\n normalizedSpatialWeightSum: number;\n combinedAreaMeasureSumSquareMicrometers: number;\n preAntiAliasingSourceOutsideImagingAreaNodeCount: number;\n sourcePointRule:\n \"pre-aa-source-equals-destination-minus-aa-offset\";\n destinationCfaChannelAppliesToAllNodes: true;\n cfaReassignmentBySourceCoordinate: false;\n sourceCoveragePolicy: \"not-applied\";\n nodes: readonly SensorSpatialSamplingQuadratureNode[];\n /**\n * Fixed statements of what this result does not include.\n */\n opticalFieldValuesIncluded: false;\n radianceIncluded: false;\n photonsIncluded: false;\n electronsIncluded: false;\n spectralResponseIncluded: false;\n quantumEfficiencyIncluded: false;\n opticalThroughputIncluded: false;\n microlensResponseIncluded: false;\n chargeDiffusionIncluded: false;\n electricalCrosstalkIncluded: false;\n temporalIntegrationIncluded: false;\n rawSampleValueIncluded: false;\n reconstructedPixelValueIncluded: false;\n geometricAreaMeasureIsRadiometricCollectionArea: false;\n componentEvidence: {\n samplingApertureProfile: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n samplingApertureLattice: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n geometricSensitiveAperture: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n opticalStackProfile: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n antiAliasingResponse: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n };\n}\n\nfunction midpointCoordinate(\n index: number,\n count: number\n): number {\n return (index + 0.5) / count;\n}\n\nfunction insideImagingArea(\n pointMm: { x: number; y: number },\n imagingArea: SensorImagingArea\n): boolean {\n const halfWidth = imagingArea.widthMm / 2;\n const halfHeight = imagingArea.heightMm / 2;\n\n return (\n pointMm.x >= -halfWidth &&\n pointMm.x <= halfWidth &&\n pointMm.y >= -halfHeight &&\n pointMm.y <= halfHeight\n );\n}\n\n/**\n * Builds the deterministic spatial sample points (quadrature nodes) for one CFA\n * (colour filter array) site.\n *\n * It combines:\n *\n * 1. the site's geometric light-sensitive aperture (from sampling-aperture); and\n * 2. the anti-aliasing (AA) point-splitting kernel (from optical-stack).\n *\n * The aperture is integrated with a grid of evenly spaced midpoints (a\n * tensor-product midpoint rule). If an AA component moves light by +delta on\n * the sensor, a destination point samples the light field before AA at\n * destination − delta. Working backwards like this means the shift is never\n * applied twice.\n *\n * The destination site's CFA channel applies to every node. The pre-AA source\n * point is a position in the light field, not another CFA site, so a shifted\n * source point never changes the destination channel.\n *\n * Nodes whose pre-AA source point falls outside the active imaging area are\n * kept. They are neither clamped nor dropped (and weights are not\n * renormalized). The renderer or light-field provider decides how to cover them.\n *\n * Normalized weights approximate a spatial average. Square-micrometre measures\n * approximate a geometric area integral. Neither includes throughput, QE,\n * photon conversion, microlenses, diffusion, crosstalk or integration over time.\n */\nexport function calculateSensorSpatialSamplingQuadrature(\n input: CalculateSensorSpatialSamplingQuadratureInput\n): CalculationResult<SensorSpatialSamplingQuadrature> {\n requireInputKeys(input, \"calculateSensorSpatialSamplingQuadrature\", [\"imagingArea\", \"nativeRaster\", \"colorSamplingProfile\", \"colorSamplingBindingProfile\", \"samplingApertureProfile\", \"opticalStackProfile\", \"site\", \"spatialSampleCountX\", \"spatialSampleCountY\"]);\n requirePositiveInteger(\n \"spatialSampleCountX\",\n input.spatialSampleCountX\n );\n requirePositiveInteger(\n \"spatialSampleCountY\",\n input.spatialSampleCountY\n );\n\n const samplingAperture =\n resolveSensorSamplingAperture({\n imagingArea: input.imagingArea,\n nativeRaster: input.nativeRaster,\n colorSamplingProfile:\n input.colorSamplingProfile,\n colorSamplingBindingProfile:\n input.colorSamplingBindingProfile,\n samplingApertureProfile:\n input.samplingApertureProfile,\n site: input.site\n });\n\n const antiAliasing =\n resolveAntiAliasingSpatialKernel(\n input.opticalStackProfile\n );\n\n const apertureNodeCount =\n input.spatialSampleCountX *\n input.spatialSampleCountY;\n if (!Number.isSafeInteger(apertureNodeCount)) {\n throw new InvalidScientificInputError(\n \"spatialSampleCountX × spatialSampleCountY must be a safe integer.\"\n );\n }\n\n const totalNodeCount =\n apertureNodeCount *\n antiAliasing.components.length;\n if (!Number.isSafeInteger(totalNodeCount)) {\n throw new InvalidScientificInputError(\n \"Spatial quadrature node count must be a safe integer.\"\n );\n }\n if (\n totalNodeCount >\n MAX_SPATIAL_QUADRATURE_NODES\n ) {\n throw new InvalidScientificInputError(\n \"Spatial quadrature node count exceeds the per-site safety limit of \" +\n MAX_SPATIAL_QUADRATURE_NODES +\n \". Increase fidelity by tiling or batching downstream work rather than allocating one oversized node set.\"\n );\n }\n\n const aperture =\n samplingAperture.geometricSensitiveAperture;\n const normalizedApertureWeight =\n 1 / apertureNodeCount;\n const apertureAreaMeasureSquareMicrometers =\n aperture.areaSquareMicrometers /\n apertureNodeCount;\n\n const apertureCenterMm =\n aperture.centerFromOpticalAxisMm;\n const widthMm =\n aperture.widthMicrometers / 1000;\n const heightMm =\n aperture.heightMicrometers / 1000;\n const leftMm =\n apertureCenterMm.x - widthMm / 2;\n const topMm =\n apertureCenterMm.y - heightMm / 2;\n\n const nodes: SensorSpatialSamplingQuadratureNode[] = [];\n let outsideCount = 0;\n\n antiAliasing.components.forEach(\n (aaComponent, antiAliasingComponentIndex) => {\n const aaOffsetMm = {\n x:\n aaComponent.offsetMicrometers.x /\n 1000,\n y:\n aaComponent.offsetMicrometers.y /\n 1000\n };\n\n for (\n let apertureSampleYIndex = 0;\n apertureSampleYIndex <\n input.spatialSampleCountY;\n apertureSampleYIndex += 1\n ) {\n const yPhase = midpointCoordinate(\n apertureSampleYIndex,\n input.spatialSampleCountY\n );\n const destinationY =\n topMm + yPhase * heightMm;\n\n for (\n let apertureSampleXIndex = 0;\n apertureSampleXIndex <\n input.spatialSampleCountX;\n apertureSampleXIndex += 1\n ) {\n const xPhase = midpointCoordinate(\n apertureSampleXIndex,\n input.spatialSampleCountX\n );\n const destinationX =\n leftMm + xPhase * widthMm;\n const destinationAperturePointMm = {\n x: destinationX,\n y: destinationY\n };\n const preAntiAliasingSourcePointMm = {\n x:\n destinationX -\n aaOffsetMm.x,\n y:\n destinationY -\n aaOffsetMm.y\n };\n const sourceInside = insideImagingArea(\n preAntiAliasingSourcePointMm,\n input.imagingArea\n );\n if (!sourceInside) {\n outsideCount += 1;\n }\n\n const combinedNormalizedSpatialWeight =\n normalizedApertureWeight *\n aaComponent.normalizedWeight;\n const combinedAreaMeasureSquareMicrometers =\n apertureAreaMeasureSquareMicrometers *\n aaComponent.normalizedWeight;\n\n nodes.push({\n antiAliasingComponentIndex,\n apertureSampleXIndex,\n apertureSampleYIndex,\n destinationAperturePointMm,\n antiAliasingOffsetMicrometers: {\n ...aaComponent.offsetMicrometers\n },\n preAntiAliasingSourcePointMm,\n preAntiAliasingSourceInsideImagingArea:\n sourceInside,\n normalizedApertureWeight,\n normalizedAntiAliasingWeight:\n aaComponent.normalizedWeight,\n combinedNormalizedSpatialWeight,\n apertureAreaMeasureSquareMicrometers,\n combinedAreaMeasureSquareMicrometers\n });\n }\n }\n }\n );\n\n const normalizedSpatialWeightSum =\n nodes.reduce(\n (sum, node) =>\n sum +\n node.combinedNormalizedSpatialWeight,\n 0\n );\n const combinedAreaMeasureSumSquareMicrometers =\n nodes.reduce(\n (sum, node) =>\n sum +\n node.combinedAreaMeasureSquareMicrometers,\n 0\n );\n\n return approximationResult(\n {\n coordinateSystem:\n \"native-sensor-physical\",\n colorSamplingProfileId:\n samplingAperture\n .colorSamplingProfileId,\n samplingApertureProfileId:\n samplingAperture.profileId,\n opticalStackProfileId:\n antiAliasing.profileId,\n site: {\n ...samplingAperture.site\n },\n channelId:\n samplingAperture.channelId,\n quadratureScheme:\n \"tensor-product-uniform-midpoint\",\n outputMeaning:\n \"pre-aa-optical-field-spatial-quadrature-nodes\",\n spatialSampleCountX:\n input.spatialSampleCountX,\n spatialSampleCountY:\n input.spatialSampleCountY,\n antiAliasingComponentCount:\n antiAliasing.components.length,\n totalNodeCount,\n geometricApertureAreaSquareMicrometers:\n aperture.areaSquareMicrometers,\n nominalSiteCellAreaSquareMicrometers:\n samplingAperture.lattice\n .nominalCellAreaSquareMicrometers,\n geometricSensitiveAreaFractionOfLatticeCell:\n aperture\n .geometricSensitiveAreaFractionOfLatticeCell,\n normalizedSpatialWeightSum,\n combinedAreaMeasureSumSquareMicrometers,\n preAntiAliasingSourceOutsideImagingAreaNodeCount:\n outsideCount,\n sourcePointRule:\n \"pre-aa-source-equals-destination-minus-aa-offset\",\n destinationCfaChannelAppliesToAllNodes:\n true,\n cfaReassignmentBySourceCoordinate:\n false,\n sourceCoveragePolicy:\n \"not-applied\",\n nodes,\n opticalFieldValuesIncluded: false,\n radianceIncluded: false,\n photonsIncluded: false,\n electronsIncluded: false,\n spectralResponseIncluded: false,\n quantumEfficiencyIncluded: false,\n opticalThroughputIncluded: false,\n microlensResponseIncluded: false,\n chargeDiffusionIncluded: false,\n electricalCrosstalkIncluded: false,\n temporalIntegrationIncluded: false,\n rawSampleValueIncluded: false,\n reconstructedPixelValueIncluded: false,\n geometricAreaMeasureIsRadiometricCollectionArea:\n false,\n componentEvidence: {\n samplingApertureProfile:\n samplingAperture.provenance.profile,\n samplingApertureLattice:\n samplingAperture.provenance.lattice,\n geometricSensitiveAperture:\n samplingAperture.provenance.aperture,\n opticalStackProfile:\n antiAliasing.provenance.profile,\n antiAliasingResponse:\n antiAliasing.provenance\n .antiAliasingResponse\n }\n },\n \"sensor-spatial-sampling-quadrature\",\n \"1.0.0\",\n [\n \"Spatial aperture integration uses a deterministic tensor-product uniform midpoint rule.\",\n \"The destination sensor site's semantic CFA/color channel applies to every quadrature node; pre-AA source coordinates never reassign the destination channel.\",\n \"AA point-splitting is inverse-sampled: a +delta output displacement reads the pre-AA optical field at destination - delta.\",\n \"AA component weights and aperture midpoint weights are dimensionless normalized spatial-average weights.\",\n \"Square-micrometre measures are geometric integration measures only and are not effective radiometric collection area, throughput, QE, or photon count.\",\n \"Pre-AA source points may leave the active physical imaging area and are not clamped, dropped, or renormalized.\",\n \"The AA kernel remains the optical-stack foundation's field/wavelength/polarization-invariant approximation where applicable.\",\n \"Microlens spatial response, charge diffusion, electrical/optical crosstalk, spectral response, radiometry, temporal integration, RAW sample generation, and reconstruction remain separate future work.\",\n \"No geometry-only spatial quadrature error estimate is reported because downstream optical-field spatial frequency/content determines integration error.\"\n ]\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"spatial-sampling-quadrature.js","sourceRoot":"","sources":["../../src/sensor/spatial-sampling-quadrature.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spatial sampling quadrature: the sample points (nodes) and weights used to average light over one\n * sensor site.\n *\n * It combines the site's geometric light-sensitive aperture (sampling-aperture) with the anti-aliasing\n * (AA) point-splitting kernel (optical-stack). The aperture is integrated with a grid of evenly spaced\n * midpoints. If an AA component moves light by +delta on the sensor, a destination point samples the\n * light field before AA at destination − delta, so the shift is never applied twice. Normalized weights\n * give a spatial average; square-micrometre measures give a geometric area integral. Neither includes\n * throughput, QE (quantum efficiency), photon conversion, microlenses, diffusion, crosstalk or\n * integration over time.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveInteger\n} from \"../core/validation.js\";\nimport type {\n NativeEffectiveRasterColorSamplingBindingProfile\n} from \"./capture-color-sampling-binding.js\";\nimport type {\n NativeColorSamplingSiteIndex,\n SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport {\n resolveAntiAliasingSpatialKernel,\n type SensorOpticalStackProfile\n} from \"./optical-stack.js\";\nimport {\n resolveSensorSamplingAperture,\n type SensorSamplingApertureProfile\n} from \"./sampling-aperture.js\";\nimport type {\n NativeImageRaster,\n SensorImagingArea\n} from \"./sensor-geometry.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport { midpointPhase as midpointCoordinate } from \"../motion/capture-rotation-temporal-quadrature.js\";\n\nconst MAX_SPATIAL_QUADRATURE_NODES = 100_000;\n\nexport interface CalculateSensorSpatialSamplingQuadratureInput {\n imagingArea: SensorImagingArea;\n nativeRaster: NativeImageRaster;\n colorSamplingProfile: SensorColorSamplingProfile;\n colorSamplingBindingProfile: NativeEffectiveRasterColorSamplingBindingProfile;\n samplingApertureProfile: SensorSamplingApertureProfile;\n opticalStackProfile: SensorOpticalStackProfile;\n site: NativeColorSamplingSiteIndex;\n /** Number of midpoint cells across the geometric aperture width. */\n spatialSampleCountX: number;\n /** Number of midpoint cells across the geometric aperture height. */\n spatialSampleCountY: number;\n}\n\nexport interface SensorSpatialSamplingQuadratureNode {\n antiAliasingComponentIndex: number;\n apertureSampleXIndex: number;\n apertureSampleYIndex: number;\n /**\n * Destination point inside the geometric sensitive aperture, after the\n * declared AA point splitting.\n *\n * Native sensor physical coordinates, in millimetres from the optical axis.\n */\n destinationAperturePointMm: {\n x: number;\n y: number;\n };\n /**\n * How far the declared AA component moves light on the sensor plane.\n *\n * +X right, +Y down; micrometres.\n */\n antiAliasingOffsetMicrometers: {\n x: number;\n y: number;\n };\n /**\n * The point in the light field before AA that lands on\n * destinationAperturePointMm through this split component:\n *\n * source = destination - AA offset\n */\n preAntiAliasingSourcePointMm: {\n x: number;\n y: number;\n };\n /**\n * Whether the pre-AA source point is inside the active physical imaging\n * area. False is not an error; downstream code decides how to handle valid\n * light from outside the active sampling area.\n */\n preAntiAliasingSourceInsideImagingArea: boolean;\n /** Dimensionless midpoint weight for the aperture-area average. */\n normalizedApertureWeight: number;\n /** Dimensionless normalized weight from the AA split component. */\n normalizedAntiAliasingWeight: number;\n /**\n * Product of the two weights (dimensionless), used to average a light field\n * over the site with this quadrature.\n */\n combinedNormalizedSpatialWeight: number;\n /**\n * Geometric area this aperture midpoint stands for, before the AA component\n * weight is applied. Units: square micrometres.\n */\n apertureAreaMeasureSquareMicrometers: number;\n /**\n * Geometric area multiplied by the (dimensionless) AA component weight.\n * Units are still square micrometres.\n *\n * This is not an effective light-collection area.\n */\n combinedAreaMeasureSquareMicrometers: number;\n}\n\nexport interface SensorSpatialSamplingQuadrature {\n coordinateSystem: \"native-sensor-physical\";\n colorSamplingProfileId?: string;\n samplingApertureProfileId?: string;\n opticalStackProfileId?: string;\n site: NativeColorSamplingSiteIndex;\n channelId: string;\n quadratureScheme:\n \"tensor-product-uniform-midpoint\";\n outputMeaning:\n \"pre-aa-optical-field-spatial-quadrature-nodes\";\n spatialSampleCountX: number;\n spatialSampleCountY: number;\n antiAliasingComponentCount: number;\n totalNodeCount: number;\n geometricApertureAreaSquareMicrometers: number;\n nominalSiteCellAreaSquareMicrometers?: number;\n geometricSensitiveAreaFractionOfLatticeCell?: number;\n normalizedSpatialWeightSum: number;\n combinedAreaMeasureSumSquareMicrometers: number;\n preAntiAliasingSourceOutsideImagingAreaNodeCount: number;\n sourcePointRule:\n \"pre-aa-source-equals-destination-minus-aa-offset\";\n destinationCfaChannelAppliesToAllNodes: true;\n cfaReassignmentBySourceCoordinate: false;\n sourceCoveragePolicy: \"not-applied\";\n nodes: readonly SensorSpatialSamplingQuadratureNode[];\n /**\n * Fixed statements of what this result does not include.\n */\n opticalFieldValuesIncluded: false;\n radianceIncluded: false;\n photonsIncluded: false;\n electronsIncluded: false;\n spectralResponseIncluded: false;\n quantumEfficiencyIncluded: false;\n opticalThroughputIncluded: false;\n microlensResponseIncluded: false;\n chargeDiffusionIncluded: false;\n electricalCrosstalkIncluded: false;\n temporalIntegrationIncluded: false;\n rawSampleValueIncluded: false;\n reconstructedPixelValueIncluded: false;\n geometricAreaMeasureIsRadiometricCollectionArea: false;\n componentEvidence: {\n samplingApertureProfile: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n samplingApertureLattice: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n geometricSensitiveAperture: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n opticalStackProfile: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n antiAliasingResponse: readonly import(\"../core/evidence-provenance.js\").EvidenceProvenance[];\n };\n}\n\nfunction insideImagingArea(\n pointMm: { x: number; y: number },\n imagingArea: SensorImagingArea\n): boolean {\n const halfWidth = imagingArea.widthMm / 2;\n const halfHeight = imagingArea.heightMm / 2;\n\n return (\n pointMm.x >= -halfWidth &&\n pointMm.x <= halfWidth &&\n pointMm.y >= -halfHeight &&\n pointMm.y <= halfHeight\n );\n}\n\n/**\n * Builds the deterministic spatial sample points (quadrature nodes) for one CFA\n * (colour filter array) site.\n *\n * It combines:\n *\n * 1. the site's geometric light-sensitive aperture (from sampling-aperture); and\n * 2. the anti-aliasing (AA) point-splitting kernel (from optical-stack).\n *\n * The aperture is integrated with a grid of evenly spaced midpoints (a\n * tensor-product midpoint rule). If an AA component moves light by +delta on\n * the sensor, a destination point samples the light field before AA at\n * destination − delta. Working backwards like this means the shift is never\n * applied twice.\n *\n * The destination site's CFA channel applies to every node. The pre-AA source\n * point is a position in the light field, not another CFA site, so a shifted\n * source point never changes the destination channel.\n *\n * Nodes whose pre-AA source point falls outside the active imaging area are\n * kept. They are neither clamped nor dropped (and weights are not\n * renormalized). The renderer or light-field provider decides how to cover them.\n *\n * Normalized weights approximate a spatial average. Square-micrometre measures\n * approximate a geometric area integral. Neither includes throughput, QE,\n * photon conversion, microlenses, diffusion, crosstalk or integration over time.\n */\nexport function calculateSensorSpatialSamplingQuadrature(\n input: CalculateSensorSpatialSamplingQuadratureInput\n): CalculationResult<SensorSpatialSamplingQuadrature> {\n requireInputKeys(input, \"calculateSensorSpatialSamplingQuadrature\", [\"imagingArea\", \"nativeRaster\", \"colorSamplingProfile\", \"colorSamplingBindingProfile\", \"samplingApertureProfile\", \"opticalStackProfile\", \"site\", \"spatialSampleCountX\", \"spatialSampleCountY\"]);\n requirePositiveInteger(\n \"spatialSampleCountX\",\n input.spatialSampleCountX\n );\n requirePositiveInteger(\n \"spatialSampleCountY\",\n input.spatialSampleCountY\n );\n\n const samplingAperture =\n resolveSensorSamplingAperture({\n imagingArea: input.imagingArea,\n nativeRaster: input.nativeRaster,\n colorSamplingProfile:\n input.colorSamplingProfile,\n colorSamplingBindingProfile:\n input.colorSamplingBindingProfile,\n samplingApertureProfile:\n input.samplingApertureProfile,\n site: input.site\n });\n\n const antiAliasing =\n resolveAntiAliasingSpatialKernel(\n input.opticalStackProfile\n );\n\n const apertureNodeCount =\n input.spatialSampleCountX *\n input.spatialSampleCountY;\n if (!Number.isSafeInteger(apertureNodeCount)) {\n throw new InvalidScientificInputError(\n \"spatialSampleCountX × spatialSampleCountY must be a safe integer.\"\n );\n }\n\n const totalNodeCount =\n apertureNodeCount *\n antiAliasing.components.length;\n if (!Number.isSafeInteger(totalNodeCount)) {\n throw new InvalidScientificInputError(\n \"Spatial quadrature node count must be a safe integer.\"\n );\n }\n if (\n totalNodeCount >\n MAX_SPATIAL_QUADRATURE_NODES\n ) {\n throw new InvalidScientificInputError(\n \"Spatial quadrature node count exceeds the per-site safety limit of \" +\n MAX_SPATIAL_QUADRATURE_NODES +\n \". Increase fidelity by tiling or batching downstream work rather than allocating one oversized node set.\"\n );\n }\n\n const aperture =\n samplingAperture.geometricSensitiveAperture;\n const normalizedApertureWeight =\n 1 / apertureNodeCount;\n const apertureAreaMeasureSquareMicrometers =\n aperture.areaSquareMicrometers /\n apertureNodeCount;\n\n const apertureCenterMm =\n aperture.centerFromOpticalAxisMm;\n const widthMm =\n aperture.widthMicrometers / 1000;\n const heightMm =\n aperture.heightMicrometers / 1000;\n const leftMm =\n apertureCenterMm.x - widthMm / 2;\n const topMm =\n apertureCenterMm.y - heightMm / 2;\n\n const nodes: SensorSpatialSamplingQuadratureNode[] = [];\n let outsideCount = 0;\n\n antiAliasing.components.forEach(\n (aaComponent, antiAliasingComponentIndex) => {\n const aaOffsetMm = {\n x:\n aaComponent.offsetMicrometers.x /\n 1000,\n y:\n aaComponent.offsetMicrometers.y /\n 1000\n };\n\n for (\n let apertureSampleYIndex = 0;\n apertureSampleYIndex <\n input.spatialSampleCountY;\n apertureSampleYIndex += 1\n ) {\n const yPhase = midpointCoordinate(\n apertureSampleYIndex,\n input.spatialSampleCountY\n );\n const destinationY =\n topMm + yPhase * heightMm;\n\n for (\n let apertureSampleXIndex = 0;\n apertureSampleXIndex <\n input.spatialSampleCountX;\n apertureSampleXIndex += 1\n ) {\n const xPhase = midpointCoordinate(\n apertureSampleXIndex,\n input.spatialSampleCountX\n );\n const destinationX =\n leftMm + xPhase * widthMm;\n const destinationAperturePointMm = {\n x: destinationX,\n y: destinationY\n };\n const preAntiAliasingSourcePointMm = {\n x:\n destinationX -\n aaOffsetMm.x,\n y:\n destinationY -\n aaOffsetMm.y\n };\n const sourceInside = insideImagingArea(\n preAntiAliasingSourcePointMm,\n input.imagingArea\n );\n if (!sourceInside) {\n outsideCount += 1;\n }\n\n const combinedNormalizedSpatialWeight =\n normalizedApertureWeight *\n aaComponent.normalizedWeight;\n const combinedAreaMeasureSquareMicrometers =\n apertureAreaMeasureSquareMicrometers *\n aaComponent.normalizedWeight;\n\n nodes.push({\n antiAliasingComponentIndex,\n apertureSampleXIndex,\n apertureSampleYIndex,\n destinationAperturePointMm,\n antiAliasingOffsetMicrometers: {\n ...aaComponent.offsetMicrometers\n },\n preAntiAliasingSourcePointMm,\n preAntiAliasingSourceInsideImagingArea:\n sourceInside,\n normalizedApertureWeight,\n normalizedAntiAliasingWeight:\n aaComponent.normalizedWeight,\n combinedNormalizedSpatialWeight,\n apertureAreaMeasureSquareMicrometers,\n combinedAreaMeasureSquareMicrometers\n });\n }\n }\n }\n );\n\n const normalizedSpatialWeightSum =\n nodes.reduce(\n (sum, node) =>\n sum +\n node.combinedNormalizedSpatialWeight,\n 0\n );\n const combinedAreaMeasureSumSquareMicrometers =\n nodes.reduce(\n (sum, node) =>\n sum +\n node.combinedAreaMeasureSquareMicrometers,\n 0\n );\n\n return approximationResult(\n {\n coordinateSystem:\n \"native-sensor-physical\",\n colorSamplingProfileId:\n samplingAperture\n .colorSamplingProfileId,\n samplingApertureProfileId:\n samplingAperture.profileId,\n opticalStackProfileId:\n antiAliasing.profileId,\n site: {\n ...samplingAperture.site\n },\n channelId:\n samplingAperture.channelId,\n quadratureScheme:\n \"tensor-product-uniform-midpoint\",\n outputMeaning:\n \"pre-aa-optical-field-spatial-quadrature-nodes\",\n spatialSampleCountX:\n input.spatialSampleCountX,\n spatialSampleCountY:\n input.spatialSampleCountY,\n antiAliasingComponentCount:\n antiAliasing.components.length,\n totalNodeCount,\n geometricApertureAreaSquareMicrometers:\n aperture.areaSquareMicrometers,\n nominalSiteCellAreaSquareMicrometers:\n samplingAperture.lattice\n .nominalCellAreaSquareMicrometers,\n geometricSensitiveAreaFractionOfLatticeCell:\n aperture\n .geometricSensitiveAreaFractionOfLatticeCell,\n normalizedSpatialWeightSum,\n combinedAreaMeasureSumSquareMicrometers,\n preAntiAliasingSourceOutsideImagingAreaNodeCount:\n outsideCount,\n sourcePointRule:\n \"pre-aa-source-equals-destination-minus-aa-offset\",\n destinationCfaChannelAppliesToAllNodes:\n true,\n cfaReassignmentBySourceCoordinate:\n false,\n sourceCoveragePolicy:\n \"not-applied\",\n nodes,\n opticalFieldValuesIncluded: false,\n radianceIncluded: false,\n photonsIncluded: false,\n electronsIncluded: false,\n spectralResponseIncluded: false,\n quantumEfficiencyIncluded: false,\n opticalThroughputIncluded: false,\n microlensResponseIncluded: false,\n chargeDiffusionIncluded: false,\n electricalCrosstalkIncluded: false,\n temporalIntegrationIncluded: false,\n rawSampleValueIncluded: false,\n reconstructedPixelValueIncluded: false,\n geometricAreaMeasureIsRadiometricCollectionArea:\n false,\n componentEvidence: {\n samplingApertureProfile:\n samplingAperture.provenance.profile,\n samplingApertureLattice:\n samplingAperture.provenance.lattice,\n geometricSensitiveAperture:\n samplingAperture.provenance.aperture,\n opticalStackProfile:\n antiAliasing.provenance.profile,\n antiAliasingResponse:\n antiAliasing.provenance\n .antiAliasingResponse\n }\n },\n \"sensor-spatial-sampling-quadrature\",\n \"1.0.0\",\n [\n \"Spatial aperture integration uses a deterministic tensor-product uniform midpoint rule.\",\n \"The destination sensor site's semantic CFA/color channel applies to every quadrature node; pre-AA source coordinates never reassign the destination channel.\",\n \"AA point-splitting is inverse-sampled: a +delta output displacement reads the pre-AA optical field at destination - delta.\",\n \"AA component weights and aperture midpoint weights are dimensionless normalized spatial-average weights.\",\n \"Square-micrometre measures are geometric integration measures only and are not effective radiometric collection area, throughput, QE, or photon count.\",\n \"Pre-AA source points may leave the active physical imaging area and are not clamped, dropped, or renormalized.\",\n \"The AA kernel remains the optical-stack foundation's field/wavelength/polarization-invariant approximation where applicable.\",\n \"Microlens spatial response, charge diffusion, electrical/optical crosstalk, spectral response, radiometry, temporal integration, RAW sample generation, and reconstruction remain separate future work.\",\n \"No geometry-only spatial quadrature error estimate is reported because downstream optical-field spatial frequency/content determines integration error.\"\n ]\n );\n}\n"]}
|
|
@@ -12,27 +12,10 @@ import { approximationResult } from "../core/calculation-result.js";
|
|
|
12
12
|
import { InvalidScientificInputError } from "../core/validation.js";
|
|
13
13
|
import { sensorSpatialQuadratureNodeIdentityKey, validateSensorSpatialSamplingQuadrature } from "./spatial-sample-reduction.js";
|
|
14
14
|
import { validateSensorSpectralQuadrature } from "./spectral-quadrature.js";
|
|
15
|
-
import { requireInputKeys } from "../core/record-validation.js";
|
|
15
|
+
import { requireInputKeys, requireInputNonNegativeFinite as requireNonNegativeFinite, requireInputNonNegativeSafeInteger as requireSafeNonNegativeInteger } from "../core/record-validation.js";
|
|
16
16
|
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
17
17
|
const SQUARE_MICROMETERS_TO_SQUARE_METERS = 1e-12;
|
|
18
18
|
const MAX_SPATIO_SPECTRAL_SAMPLE_VALUES = 100_000;
|
|
19
|
-
function requireSafeNonNegativeInteger(value, path) {
|
|
20
|
-
if (typeof value !== "number" ||
|
|
21
|
-
!Number.isSafeInteger(value) ||
|
|
22
|
-
value < 0) {
|
|
23
|
-
throw new InvalidScientificInputError(path + " must be a non-negative safe integer.");
|
|
24
|
-
}
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
function requireNonNegativeFinite(value, path) {
|
|
28
|
-
if (typeof value !== "number" ||
|
|
29
|
-
!Number.isFinite(value) ||
|
|
30
|
-
value < 0) {
|
|
31
|
-
throw new InvalidScientificInputError(path +
|
|
32
|
-
" must be finite and greater than or equal to zero.");
|
|
33
|
-
}
|
|
34
|
-
return value;
|
|
35
|
-
}
|
|
36
19
|
function sampleKey(spatialNode, spectralSampleIndex) {
|
|
37
20
|
return (sensorSpatialQuadratureNodeIdentityKey(spatialNode) +
|
|
38
21
|
"|" +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spatio-spectral-reduction.js","sourceRoot":"","sources":["../../src/sensor/spatio-spectral-reduction.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spatio-spectral reduction: integrates spectral irradiance on the sensor over one site's area and\n * over wavelength, giving the incident light power on the site's geometric aperture.\n *\n * It stops before the sensor response: QE (quantum efficiency), A/W responsivity and colour-filter\n * transmission are applied in later steps.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport {\n sensorSpatialQuadratureNodeIdentityKey,\n validateSensorSpatialSamplingQuadrature,\n type SensorSpatialQuadratureNodeIdentity\n} from \"./spatial-sample-reduction.js\";\nimport type {\n SensorSpatialSamplingQuadrature\n} from \"./spatial-sampling-quadrature.js\";\nimport {\n validateSensorSpectralQuadrature,\n type SensorSpectralQuadrature,\n type SensorSpectralQuadratureNode\n} from \"./spectral-quadrature.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nconst SQUARE_MICROMETERS_TO_SQUARE_METERS = 1e-12;\nconst MAX_SPATIO_SPECTRAL_SAMPLE_VALUES = 100_000;\n\nexport interface SensorSpatioSpectralNodeIdentity {\n spatialNode: SensorSpatialQuadratureNodeIdentity;\n spectralSampleIndex: number;\n wavelengthNanometers: number;\n}\n\nexport interface SensorSpatioSpectralIrradianceSample {\n node: SensorSpatioSpectralNodeIdentity;\n /**\n * Spectral irradiance at the spatial node's preAntiAliasingSourcePointMm\n * and this spectral node's wavelength.\n *\n * Units: W/m^2/nm.\n */\n spectralIrradianceWattsPerSquareMeterPerNanometer: number;\n}\n\nexport interface ReduceSensorSpatioSpectralIrradianceInput {\n spatialQuadrature: SensorSpatialSamplingQuadrature;\n spectralQuadrature: SensorSpectralQuadrature;\n /**\n * Exactly one E_lambda(x,y) value, with its node identity, for every\n * combination of spatial node and spectral node.\n *\n * Order does not matter; the node identity does.\n */\n sampleValues: readonly SensorSpatioSpectralIrradianceSample[];\n}\n\nexport interface SensorSpatioSpectralWavelengthReduction {\n spectralSampleIndex: number;\n wavelengthNanometers: number;\n wavelengthMeasureNanometers: number;\n normalizedWavelengthWeight: number;\n /**\n * Spatially reduced spectral irradiance at this wavelength.\n * Units: W/m^2/nm.\n */\n normalizedSpatialAverageSpectralIrradianceWattsPerSquareMeterPerNanometer:\n number;\n /**\n * Spectral radiant flux reaching the geometric sensitive aperture, after the\n * declared normalized AA spreading. Units: W/nm.\n *\n * This uses geometric area, not an established light-collection area.\n */\n geometricApertureIncidentSpectralFluxWattsPerNanometer: number;\n /**\n * Contribution to the wavelength-integrated spatial average.\n * Units: W/m^2.\n */\n wavelengthIntegratedSpatialAverageContributionWattsPerSquareMeter: number;\n /**\n * Contribution to wavelength-integrated geometric-aperture incident flux.\n * Units: W.\n */\n wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts: number;\n}\n\nexport interface SensorSpatioSpectralIrradianceReduction {\n colorSamplingProfileId: string;\n samplingApertureProfileId?: string;\n opticalStackProfileId?: string;\n site: {\n x: number;\n y: number;\n };\n channelId: string;\n responseProfileId: string;\n sourceResponseKind:\n SensorSpectralQuadrature[\"sourceResponseKind\"];\n responseScope:\n SensorSpectralQuadrature[\"responseScope\"];\n responseScientificStatus:\n SensorSpectralQuadrature[\"responseScientificStatus\"];\n responseUncertainty:\n SensorSpectralQuadrature[\"responseUncertainty\"];\n responseChannelBinding?:\n SensorSpectralQuadrature[\"responseChannelBinding\"];\n responseReferenceConditions?:\n SensorSpectralQuadrature[\"responseReferenceConditions\"];\n wavelengthBasis:\n SensorSpectralQuadrature[\"wavelengthBasis\"];\n wavelengthBasisResolved: boolean;\n wavelengthRangeNanometers:\n SensorSpectralQuadrature[\"wavelengthRangeNanometers\"];\n responseDeclaredWavelengthRangeNanometers:\n SensorSpectralQuadrature[\"responseDeclaredWavelengthRangeNanometers\"];\n outputMeaning:\n \"pre-response-spatio-spectral-radiometric-reduction\";\n inputValueDomain: {\n kind: \"radiometric-spectral-irradiance\";\n unit: \"W/m^2/nm\";\n semantic:\n \"pre-aa-pre-response-sensor-plane-spectral-irradiance\";\n };\n spatialNodeCount: number;\n spectralNodeCount: number;\n combinedSampleCount: number;\n sourceValuesMatchedBy:\n \"spatial-node-identity-plus-spectral-sample-index-and-wavelength\";\n sourceValuesSuppliedForAllCombinedNodes: true;\n outsideImagingAreaSourceValuesRequired: boolean;\n geometricApertureAreaSquareMicrometers?: number;\n nominalSiteCellAreaSquareMicrometers?: number;\n geometricSensitiveAreaFractionOfLatticeCell?: number;\n perWavelength:\n readonly SensorSpatioSpectralWavelengthReduction[];\n /**\n * Integral over the requested wavelength interval of the spatially averaged\n * spectral irradiance. Units: W/m^2.\n */\n wavelengthIntegratedSpatialAverageIrradianceWattsPerSquareMeter:\n number;\n /**\n * Integral over the requested wavelength interval and the geometric\n * aperture area. Units: W.\n *\n * This is the incident radiant flux before the sensor response, over the\n * declared wavelength interval. It is not collected power or generated charge.\n */\n wavelengthIntegratedGeometricApertureIncidentFluxWatts:\n number;\n spatialIntegrationApplied: true;\n wavelengthIntegrationApplied: true;\n spectralResponsePlanUsedForWavelengthSupport: true;\n spectralResponseApplicationPerformed: false;\n responseScopeMatchedToSourcePlane: false;\n quantumEfficiencyApplied: false;\n spectralResponsivityApplied: false;\n channelFilterTransmissionApplied: false;\n opticalTransmissionAppliedByReducer: false;\n radiometricCollectionAreaEstablished: false;\n temporalIntegrationApplied: false;\n exposureDurationApplied: false;\n photonsCalculated: false;\n electronsCalculated: false;\n currentCalculated: false;\n shotNoiseApplied: false;\n readNoiseApplied: false;\n adcQuantizationApplied: false;\n rawCodeValueProduced: false;\n demosaicOrReconstructionApplied: false;\n convergenceErrorEstimated: false;\n componentEvidence: {\n spatial:\n SensorSpatialSamplingQuadrature[\"componentEvidence\"];\n spectral:\n SensorSpectralQuadrature[\"componentEvidence\"];\n };\n}\n\nfunction requireSafeNonNegativeInteger(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isSafeInteger(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path + \" must be a non-negative safe integer.\"\n );\n }\n return value;\n}\n\nfunction requireNonNegativeFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be finite and greater than or equal to zero.\"\n );\n }\n return value;\n}\n\nfunction sampleKey(\n spatialNode:\n SensorSpatialQuadratureNodeIdentity,\n spectralSampleIndex: number\n): string {\n return (\n sensorSpatialQuadratureNodeIdentityKey(\n spatialNode\n ) +\n \"|\" +\n spectralSampleIndex\n );\n}\n\nfunction parseSamples<T extends { node: SensorSpatioSpectralNodeIdentity }, V>(\n sampleValues:\n readonly T[],\n spatialNodesByKey: ReadonlyMap<\n string,\n SensorSpatialSamplingQuadrature[\"nodes\"][number]\n >,\n spectralNodesByIndex: ReadonlyMap<\n number,\n SensorSpectralQuadratureNode\n >,\n combinedSampleCount: number,\n valueForSample: (sample: T, sampleIndex: number) => V\n): Map<string, V> {\n if (!Array.isArray(sampleValues)) {\n throw new InvalidConfigurationError(\n \"sampleValues must be an array.\"\n );\n }\n if (\n sampleValues.length !==\n combinedSampleCount\n ) {\n throw new InvalidScientificInputError(\n \"sampleValues must contain exactly one value for every spatial × spectral quadrature node.\"\n );\n }\n\n const valuesByKey =\n new Map<string, V>();\n\n sampleValues.forEach(\n (sample, sampleIndex) => {\n if (\n typeof sample !== \"object\" ||\n sample === null ||\n typeof sample.node !== \"object\" ||\n sample.node === null ||\n typeof sample.node.spatialNode !==\n \"object\" ||\n sample.node.spatialNode === null\n ) {\n throw new InvalidScientificInputError(\n \"sampleValues[\" +\n sampleIndex +\n \"] must contain a complete spatio-spectral node identity.\"\n );\n }\n\n const spatialNode = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n sample.node.spatialNode\n .antiAliasingComponentIndex,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.spatialNode.antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n sample.node.spatialNode\n .apertureSampleXIndex,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.spatialNode.apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n sample.node.spatialNode\n .apertureSampleYIndex,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.spatialNode.apertureSampleYIndex\"\n )\n };\n const spatialKey =\n sensorSpatialQuadratureNodeIdentityKey(\n spatialNode\n );\n if (\n !spatialNodesByKey.has(\n spatialKey\n )\n ) {\n throw new InvalidScientificInputError(\n \"sampleValues identifies a spatial node that is not present in spatialQuadrature.\"\n );\n }\n\n const spectralSampleIndex =\n requireSafeNonNegativeInteger(\n sample.node.spectralSampleIndex,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.spectralSampleIndex\"\n );\n const spectralNode =\n spectralNodesByIndex.get(\n spectralSampleIndex\n );\n if (spectralNode === undefined) {\n throw new InvalidScientificInputError(\n \"sampleValues identifies a spectral node that is not present in spectralQuadrature.\"\n );\n }\n\n const wavelengthNanometers =\n requireNonNegativeFinite(\n sample.node.wavelengthNanometers,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.wavelengthNanometers\"\n );\n if (\n wavelengthNanometers !==\n spectralNode.wavelengthNanometers\n ) {\n throw new InvalidScientificInputError(\n \"sampleValues wavelengthNanometers must exactly match the identified spectral quadrature node.\"\n );\n }\n\n const key = sampleKey(\n spatialNode,\n spectralSampleIndex\n );\n if (valuesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"sampleValues contains duplicate spatio-spectral node identities.\"\n );\n }\n\n valuesByKey.set(\n key,\n valueForSample(sample, sampleIndex)\n );\n }\n );\n\n if (valuesByKey.size !== combinedSampleCount) {\n throw new InvalidScientificInputError(\"sampleValues must not contain sparse or missing node entries.\");\n }\n return valuesByKey;\n}\n\n/** Internal: matches samples to their spatial × spectral nodes. Each caller still validates values and applies its own equations. */\nexport function bindSensorSpatioSpectralSamples<T extends { node: SensorSpatioSpectralNodeIdentity }, V>(\n input: { spatialQuadrature: SensorSpatialSamplingQuadrature; spectralQuadrature: SensorSpectralQuadrature; sampleValues: readonly T[] },\n valueForSample: (sample: T, sampleIndex: number) => V\n): { spatialNodesByKey: Map<string, SensorSpatialSamplingQuadrature[\"nodes\"][number]>;\n spectralNodesByIndex: Map<number, SensorSpectralQuadratureNode>; colorSamplingProfileId: string;\n combinedSampleCount: number; valuesByKey: Map<string, V> } {\n const spatialNodesByKey =\n validateSensorSpatialSamplingQuadrature(\n input.spatialQuadrature\n );\n const spectralNodesByIndex =\n validateSensorSpectralQuadrature(\n input.spectralQuadrature\n );\n\n const colorSamplingProfileId =\n input.spatialQuadrature\n .colorSamplingProfileId;\n if (\n typeof colorSamplingProfileId !==\n \"string\" ||\n colorSamplingProfileId.trim().length ===\n 0\n ) {\n throw new InvalidScientificInputError(\n \"spatialQuadrature.colorSamplingProfileId is required for spatio-spectral composition.\"\n );\n }\n if (\n colorSamplingProfileId !==\n input.spectralQuadrature\n .colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"Spatial and spectral quadratures must reference the same colorSamplingProfileId.\"\n );\n }\n if (\n input.spatialQuadrature.channelId !==\n input.spectralQuadrature.channelId\n ) {\n throw new InvalidScientificInputError(\n \"Spatial and spectral quadratures must reference the same exact channelId.\"\n );\n }\n\n if (\n input.spatialQuadrature.outputMeaning !==\n \"pre-aa-optical-field-spatial-quadrature-nodes\" ||\n input.spatialQuadrature\n .spectralResponseIncluded !== false ||\n input.spatialQuadrature\n .temporalIntegrationIncluded !== false\n ) {\n throw new InvalidScientificInputError(\n \"spatialQuadrature semantics are incompatible with pre-response spatio-spectral reduction.\"\n );\n }\n\n const combinedSampleCount =\n spatialNodesByKey.size *\n spectralNodesByIndex.size;\n if (\n !Number.isSafeInteger(\n combinedSampleCount\n ) ||\n combinedSampleCount <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Spatial × spectral quadrature sample count must be a positive safe integer.\"\n );\n }\n if (\n combinedSampleCount >\n MAX_SPATIO_SPECTRAL_SAMPLE_VALUES\n ) {\n throw new InvalidScientificInputError(\n \"Spatial × spectral quadrature sample count exceeds the safety limit of \" +\n MAX_SPATIO_SPECTRAL_SAMPLE_VALUES +\n \". Tile/batch the evaluation instead of materializing one oversized Cartesian product.\"\n );\n }\n\n const valuesByKey =\n parseSamples(\n input.sampleValues,\n spatialNodesByKey,\n spectralNodesByIndex,\n combinedSampleCount,\n valueForSample\n );\n\n return { spatialNodesByKey, spectralNodesByIndex, colorSamplingProfileId, combinedSampleCount, valuesByKey };\n}\n\n/**\n * Integrates supplied spectral irradiance on the sensor plane, E_lambda(x,y),\n * over every combination of an existing spatial quadrature and spectral\n * quadrature.\n *\n * Units: the density is in W/m^2/nm and wavelengthMeasureNanometers is dλ in\n * nm, so their product is in W/m^2. Do not convert dλ to metres unless the\n * density's per-nanometre unit is converted to match.\n *\n * Area integration uses the spatial quadrature's geometric area measures,\n * converted from square micrometres to square metres. The result is the\n * incident radiant flux on the geometric aperture only; geometric area is not\n * treated as an effective light-collection area.\n *\n * It deliberately stops before the sensor response. The spectral plan comes\n * from a specific response channel, so its wavelength range and breakpoints\n * fit that channel, but QE, A/W responsivity and colour-filter transmission are\n * not applied here. Matching the response's scope and source plane is a\n * separate later step.\n */\nexport function reduceSensorSpatioSpectralIrradiance(\n input:\n ReduceSensorSpatioSpectralIrradianceInput\n): CalculationResult<SensorSpatioSpectralIrradianceReduction> {\n requireInputKeys(input, \"reduceSensorSpatioSpectralIrradiance\", [\"spatialQuadrature\", \"spectralQuadrature\", \"sampleValues\"]);\n const { spatialNodesByKey, spectralNodesByIndex, colorSamplingProfileId, combinedSampleCount, valuesByKey } =\n bindSensorSpatioSpectralSamples(input, (sample, sampleIndex) => requireNonNegativeFinite(\n sample.spectralIrradianceWattsPerSquareMeterPerNanometer,\n \"sampleValues[\" + sampleIndex + \"].spectralIrradianceWattsPerSquareMeterPerNanometer\"\n ));\n\n const perWavelength:\n SensorSpatioSpectralWavelengthReduction[] =\n [];\n let wavelengthIntegratedSpatialAverage = 0;\n let wavelengthIntegratedGeometricFlux = 0;\n\n for (\n let spectralSampleIndex = 0;\n spectralSampleIndex <\n input.spectralQuadrature.totalNodeCount;\n spectralSampleIndex += 1\n ) {\n const spectralNode =\n spectralNodesByIndex.get(\n spectralSampleIndex\n );\n if (spectralNode === undefined) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature is missing a validated contiguous spectral sample.\"\n );\n }\n\n let spatialAverageSpectralIrradiance = 0;\n let geometricSpectralFluxWattsPerNanometer =\n 0;\n\n spatialNodesByKey.forEach(\n (spatialNode, spatialKey) => {\n const value =\n valuesByKey.get(\n spatialKey +\n \"|\" +\n spectralSampleIndex\n );\n if (value === undefined) {\n throw new InvalidScientificInputError(\n \"A spatio-spectral quadrature node is missing its explicit source value.\"\n );\n }\n\n spatialAverageSpectralIrradiance +=\n value *\n spatialNode\n .combinedNormalizedSpatialWeight;\n geometricSpectralFluxWattsPerNanometer +=\n value *\n (\n spatialNode\n .combinedAreaMeasureSquareMicrometers *\n SQUARE_MICROMETERS_TO_SQUARE_METERS\n );\n\n if (\n !Number.isFinite(\n spatialAverageSpectralIrradiance\n ) ||\n !Number.isFinite(\n geometricSpectralFluxWattsPerNanometer\n )\n ) {\n throw new InvalidScientificInputError(\n \"Reduced spatio-spectral values must remain finite.\"\n );\n }\n }\n );\n\n const spatialAverageContribution =\n spatialAverageSpectralIrradiance *\n spectralNode\n .wavelengthMeasureNanometers;\n const geometricFluxContribution =\n geometricSpectralFluxWattsPerNanometer *\n spectralNode\n .wavelengthMeasureNanometers;\n\n wavelengthIntegratedSpatialAverage +=\n spatialAverageContribution;\n wavelengthIntegratedGeometricFlux +=\n geometricFluxContribution;\n\n if (\n !Number.isFinite(\n wavelengthIntegratedSpatialAverage\n ) ||\n !Number.isFinite(\n wavelengthIntegratedGeometricFlux\n )\n ) {\n throw new InvalidScientificInputError(\n \"Wavelength-integrated spatio-spectral values must remain finite.\"\n );\n }\n\n perWavelength.push({\n spectralSampleIndex,\n wavelengthNanometers:\n spectralNode.wavelengthNanometers,\n wavelengthMeasureNanometers:\n spectralNode\n .wavelengthMeasureNanometers,\n normalizedWavelengthWeight:\n spectralNode\n .normalizedWavelengthWeight,\n normalizedSpatialAverageSpectralIrradianceWattsPerSquareMeterPerNanometer:\n spatialAverageSpectralIrradiance,\n geometricApertureIncidentSpectralFluxWattsPerNanometer:\n geometricSpectralFluxWattsPerNanometer,\n wavelengthIntegratedSpatialAverageContributionWattsPerSquareMeter:\n spatialAverageContribution,\n wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts:\n geometricFluxContribution\n });\n }\n\n return approximationResult(\n {\n colorSamplingProfileId,\n ...(input.spatialQuadrature\n .samplingApertureProfileId ===\n undefined\n ? {}\n : {\n samplingApertureProfileId:\n input.spatialQuadrature\n .samplingApertureProfileId\n }),\n ...(input.spatialQuadrature\n .opticalStackProfileId ===\n undefined\n ? {}\n : {\n opticalStackProfileId:\n input.spatialQuadrature\n .opticalStackProfileId\n }),\n site: {\n ...input.spatialQuadrature.site\n },\n channelId:\n input.spatialQuadrature.channelId,\n responseProfileId:\n input.spectralQuadrature.profileId,\n sourceResponseKind:\n input.spectralQuadrature\n .sourceResponseKind,\n responseScope:\n input.spectralQuadrature\n .responseScope,\n responseScientificStatus:\n input.spectralQuadrature\n .responseScientificStatus,\n responseUncertainty:\n input.spectralQuadrature\n .responseUncertainty,\n ...(input.spectralQuadrature\n .responseChannelBinding ===\n undefined\n ? {}\n : {\n responseChannelBinding:\n input.spectralQuadrature\n .responseChannelBinding\n }),\n ...(input.spectralQuadrature\n .responseReferenceConditions ===\n undefined\n ? {}\n : {\n responseReferenceConditions:\n input.spectralQuadrature\n .responseReferenceConditions\n }),\n wavelengthBasis:\n input.spectralQuadrature\n .wavelengthBasis,\n wavelengthBasisResolved:\n input.spectralQuadrature\n .wavelengthBasisResolved,\n wavelengthRangeNanometers: {\n ...input.spectralQuadrature\n .wavelengthRangeNanometers\n },\n responseDeclaredWavelengthRangeNanometers:\n {\n ...input.spectralQuadrature\n .responseDeclaredWavelengthRangeNanometers\n },\n outputMeaning:\n \"pre-response-spatio-spectral-radiometric-reduction\",\n inputValueDomain: {\n kind:\n \"radiometric-spectral-irradiance\",\n unit: \"W/m^2/nm\",\n semantic:\n \"pre-aa-pre-response-sensor-plane-spectral-irradiance\"\n },\n spatialNodeCount:\n input.spatialQuadrature\n .totalNodeCount,\n spectralNodeCount:\n input.spectralQuadrature\n .totalNodeCount,\n combinedSampleCount,\n sourceValuesMatchedBy:\n \"spatial-node-identity-plus-spectral-sample-index-and-wavelength\",\n sourceValuesSuppliedForAllCombinedNodes:\n true,\n outsideImagingAreaSourceValuesRequired:\n input.spatialQuadrature\n .preAntiAliasingSourceOutsideImagingAreaNodeCount >\n 0,\n geometricApertureAreaSquareMicrometers:\n input.spatialQuadrature\n .geometricApertureAreaSquareMicrometers,\n ...(input.spatialQuadrature\n .nominalSiteCellAreaSquareMicrometers ===\n undefined\n ? {}\n : {\n nominalSiteCellAreaSquareMicrometers:\n input.spatialQuadrature\n .nominalSiteCellAreaSquareMicrometers\n }),\n ...(input.spatialQuadrature\n .geometricSensitiveAreaFractionOfLatticeCell ===\n undefined\n ? {}\n : {\n geometricSensitiveAreaFractionOfLatticeCell:\n input.spatialQuadrature\n .geometricSensitiveAreaFractionOfLatticeCell\n }),\n perWavelength,\n wavelengthIntegratedSpatialAverageIrradianceWattsPerSquareMeter:\n wavelengthIntegratedSpatialAverage,\n wavelengthIntegratedGeometricApertureIncidentFluxWatts:\n wavelengthIntegratedGeometricFlux,\n spatialIntegrationApplied: true,\n wavelengthIntegrationApplied: true,\n spectralResponsePlanUsedForWavelengthSupport:\n true,\n spectralResponseApplicationPerformed:\n false,\n responseScopeMatchedToSourcePlane:\n false,\n quantumEfficiencyApplied: false,\n spectralResponsivityApplied: false,\n channelFilterTransmissionApplied:\n false,\n opticalTransmissionAppliedByReducer:\n false,\n radiometricCollectionAreaEstablished:\n false,\n temporalIntegrationApplied: false,\n exposureDurationApplied: false,\n photonsCalculated: false,\n electronsCalculated: false,\n currentCalculated: false,\n shotNoiseApplied: false,\n readNoiseApplied: false,\n adcQuantizationApplied: false,\n rawCodeValueProduced: false,\n demosaicOrReconstructionApplied:\n false,\n convergenceErrorEstimated: false,\n componentEvidence: {\n spatial:\n input.spatialQuadrature\n .componentEvidence,\n spectral:\n input.spectralQuadrature\n .componentEvidence\n }\n },\n \"sensor-spatio-spectral-irradiance-reduction\",\n \"1.0.0\",\n [\n \"Input values are nonnegative sensor-plane spectral irradiance E_lambda(x,y) in W/m^2/nm, evaluated at every spatial pre-AA source coordinate and spectral quadrature wavelength.\",\n \"Spatial and spectral plans must share the exact colorSamplingProfileId and channelId; a matching channel label alone is insufficient linkage.\",\n \"The full Cartesian product is explicit and capped at 100,000 supplied sample values to avoid multiplicative memory blowups.\",\n \"Spatial normalized weights form the per-wavelength average; geometric square-micrometre area measures are converted to square metres for incident spectral flux density.\",\n \"Physical wavelength integration multiplies W/m^2/nm or W/nm by d-lambda in nm. No extra 1e-9 factor is applied because the spectral density is already per nanometre.\",\n \"The spatial AA kernel is the existing normalized throughput-free spatial redistribution; this reducer adds no omitted optical-stack or microlens throughput.\",\n \"The spectral quadrature's response-derived support and breakpoints are consumed, but QE, A/W responsivity and channel-filter transmission are not applied.\",\n \"Response scope is preserved for downstream work but is not matched to a declared source plane in this version.\",\n \"Geometric aperture area is not promoted to effective radiometric collection area.\",\n \"Temporal exposure integration, photon/electron conversion, electrical current, noise, saturation, ADC/RAW conversion and reconstruction remain separate future work.\",\n \"No source-independent spatio-spectral convergence/error estimate is reported; downstream integrand structure controls numerical error.\"\n ]\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"spatio-spectral-reduction.js","sourceRoot":"","sources":["../../src/sensor/spatio-spectral-reduction.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spatio-spectral reduction: integrates spectral irradiance on the sensor over one site's area and\n * over wavelength, giving the incident light power on the site's geometric aperture.\n *\n * It stops before the sensor response: QE (quantum efficiency), A/W responsivity and colour-filter\n * transmission are applied in later steps.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport {\n sensorSpatialQuadratureNodeIdentityKey,\n validateSensorSpatialSamplingQuadrature,\n type SensorSpatialQuadratureNodeIdentity\n} from \"./spatial-sample-reduction.js\";\nimport type {\n SensorSpatialSamplingQuadrature\n} from \"./spatial-sampling-quadrature.js\";\nimport {\n validateSensorSpectralQuadrature,\n type SensorSpectralQuadrature,\n type SensorSpectralQuadratureNode\n} from \"./spectral-quadrature.js\";\nimport { requireInputKeys, requireInputNonNegativeFinite as requireNonNegativeFinite, requireInputNonNegativeSafeInteger as requireSafeNonNegativeInteger } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nconst SQUARE_MICROMETERS_TO_SQUARE_METERS = 1e-12;\nconst MAX_SPATIO_SPECTRAL_SAMPLE_VALUES = 100_000;\n\nexport interface SensorSpatioSpectralNodeIdentity {\n spatialNode: SensorSpatialQuadratureNodeIdentity;\n spectralSampleIndex: number;\n wavelengthNanometers: number;\n}\n\nexport interface SensorSpatioSpectralIrradianceSample {\n node: SensorSpatioSpectralNodeIdentity;\n /**\n * Spectral irradiance at the spatial node's preAntiAliasingSourcePointMm\n * and this spectral node's wavelength.\n *\n * Units: W/m^2/nm.\n */\n spectralIrradianceWattsPerSquareMeterPerNanometer: number;\n}\n\nexport interface ReduceSensorSpatioSpectralIrradianceInput {\n spatialQuadrature: SensorSpatialSamplingQuadrature;\n spectralQuadrature: SensorSpectralQuadrature;\n /**\n * Exactly one E_lambda(x,y) value, with its node identity, for every\n * combination of spatial node and spectral node.\n *\n * Order does not matter; the node identity does.\n */\n sampleValues: readonly SensorSpatioSpectralIrradianceSample[];\n}\n\nexport interface SensorSpatioSpectralWavelengthReduction {\n spectralSampleIndex: number;\n wavelengthNanometers: number;\n wavelengthMeasureNanometers: number;\n normalizedWavelengthWeight: number;\n /**\n * Spatially reduced spectral irradiance at this wavelength.\n * Units: W/m^2/nm.\n */\n normalizedSpatialAverageSpectralIrradianceWattsPerSquareMeterPerNanometer:\n number;\n /**\n * Spectral radiant flux reaching the geometric sensitive aperture, after the\n * declared normalized AA spreading. Units: W/nm.\n *\n * This uses geometric area, not an established light-collection area.\n */\n geometricApertureIncidentSpectralFluxWattsPerNanometer: number;\n /**\n * Contribution to the wavelength-integrated spatial average.\n * Units: W/m^2.\n */\n wavelengthIntegratedSpatialAverageContributionWattsPerSquareMeter: number;\n /**\n * Contribution to wavelength-integrated geometric-aperture incident flux.\n * Units: W.\n */\n wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts: number;\n}\n\nexport interface SensorSpatioSpectralIrradianceReduction {\n colorSamplingProfileId: string;\n samplingApertureProfileId?: string;\n opticalStackProfileId?: string;\n site: {\n x: number;\n y: number;\n };\n channelId: string;\n responseProfileId: string;\n sourceResponseKind:\n SensorSpectralQuadrature[\"sourceResponseKind\"];\n responseScope:\n SensorSpectralQuadrature[\"responseScope\"];\n responseScientificStatus:\n SensorSpectralQuadrature[\"responseScientificStatus\"];\n responseUncertainty:\n SensorSpectralQuadrature[\"responseUncertainty\"];\n responseChannelBinding?:\n SensorSpectralQuadrature[\"responseChannelBinding\"];\n responseReferenceConditions?:\n SensorSpectralQuadrature[\"responseReferenceConditions\"];\n wavelengthBasis:\n SensorSpectralQuadrature[\"wavelengthBasis\"];\n wavelengthBasisResolved: boolean;\n wavelengthRangeNanometers:\n SensorSpectralQuadrature[\"wavelengthRangeNanometers\"];\n responseDeclaredWavelengthRangeNanometers:\n SensorSpectralQuadrature[\"responseDeclaredWavelengthRangeNanometers\"];\n outputMeaning:\n \"pre-response-spatio-spectral-radiometric-reduction\";\n inputValueDomain: {\n kind: \"radiometric-spectral-irradiance\";\n unit: \"W/m^2/nm\";\n semantic:\n \"pre-aa-pre-response-sensor-plane-spectral-irradiance\";\n };\n spatialNodeCount: number;\n spectralNodeCount: number;\n combinedSampleCount: number;\n sourceValuesMatchedBy:\n \"spatial-node-identity-plus-spectral-sample-index-and-wavelength\";\n sourceValuesSuppliedForAllCombinedNodes: true;\n outsideImagingAreaSourceValuesRequired: boolean;\n geometricApertureAreaSquareMicrometers?: number;\n nominalSiteCellAreaSquareMicrometers?: number;\n geometricSensitiveAreaFractionOfLatticeCell?: number;\n perWavelength:\n readonly SensorSpatioSpectralWavelengthReduction[];\n /**\n * Integral over the requested wavelength interval of the spatially averaged\n * spectral irradiance. Units: W/m^2.\n */\n wavelengthIntegratedSpatialAverageIrradianceWattsPerSquareMeter:\n number;\n /**\n * Integral over the requested wavelength interval and the geometric\n * aperture area. Units: W.\n *\n * This is the incident radiant flux before the sensor response, over the\n * declared wavelength interval. It is not collected power or generated charge.\n */\n wavelengthIntegratedGeometricApertureIncidentFluxWatts:\n number;\n spatialIntegrationApplied: true;\n wavelengthIntegrationApplied: true;\n spectralResponsePlanUsedForWavelengthSupport: true;\n spectralResponseApplicationPerformed: false;\n responseScopeMatchedToSourcePlane: false;\n quantumEfficiencyApplied: false;\n spectralResponsivityApplied: false;\n channelFilterTransmissionApplied: false;\n opticalTransmissionAppliedByReducer: false;\n radiometricCollectionAreaEstablished: false;\n temporalIntegrationApplied: false;\n exposureDurationApplied: false;\n photonsCalculated: false;\n electronsCalculated: false;\n currentCalculated: false;\n shotNoiseApplied: false;\n readNoiseApplied: false;\n adcQuantizationApplied: false;\n rawCodeValueProduced: false;\n demosaicOrReconstructionApplied: false;\n convergenceErrorEstimated: false;\n componentEvidence: {\n spatial:\n SensorSpatialSamplingQuadrature[\"componentEvidence\"];\n spectral:\n SensorSpectralQuadrature[\"componentEvidence\"];\n };\n}\n\nfunction sampleKey(\n spatialNode:\n SensorSpatialQuadratureNodeIdentity,\n spectralSampleIndex: number\n): string {\n return (\n sensorSpatialQuadratureNodeIdentityKey(\n spatialNode\n ) +\n \"|\" +\n spectralSampleIndex\n );\n}\n\nfunction parseSamples<T extends { node: SensorSpatioSpectralNodeIdentity }, V>(\n sampleValues:\n readonly T[],\n spatialNodesByKey: ReadonlyMap<\n string,\n SensorSpatialSamplingQuadrature[\"nodes\"][number]\n >,\n spectralNodesByIndex: ReadonlyMap<\n number,\n SensorSpectralQuadratureNode\n >,\n combinedSampleCount: number,\n valueForSample: (sample: T, sampleIndex: number) => V\n): Map<string, V> {\n if (!Array.isArray(sampleValues)) {\n throw new InvalidConfigurationError(\n \"sampleValues must be an array.\"\n );\n }\n if (\n sampleValues.length !==\n combinedSampleCount\n ) {\n throw new InvalidScientificInputError(\n \"sampleValues must contain exactly one value for every spatial × spectral quadrature node.\"\n );\n }\n\n const valuesByKey =\n new Map<string, V>();\n\n sampleValues.forEach(\n (sample, sampleIndex) => {\n if (\n typeof sample !== \"object\" ||\n sample === null ||\n typeof sample.node !== \"object\" ||\n sample.node === null ||\n typeof sample.node.spatialNode !==\n \"object\" ||\n sample.node.spatialNode === null\n ) {\n throw new InvalidScientificInputError(\n \"sampleValues[\" +\n sampleIndex +\n \"] must contain a complete spatio-spectral node identity.\"\n );\n }\n\n const spatialNode = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n sample.node.spatialNode\n .antiAliasingComponentIndex,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.spatialNode.antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n sample.node.spatialNode\n .apertureSampleXIndex,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.spatialNode.apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n sample.node.spatialNode\n .apertureSampleYIndex,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.spatialNode.apertureSampleYIndex\"\n )\n };\n const spatialKey =\n sensorSpatialQuadratureNodeIdentityKey(\n spatialNode\n );\n if (\n !spatialNodesByKey.has(\n spatialKey\n )\n ) {\n throw new InvalidScientificInputError(\n \"sampleValues identifies a spatial node that is not present in spatialQuadrature.\"\n );\n }\n\n const spectralSampleIndex =\n requireSafeNonNegativeInteger(\n sample.node.spectralSampleIndex,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.spectralSampleIndex\"\n );\n const spectralNode =\n spectralNodesByIndex.get(\n spectralSampleIndex\n );\n if (spectralNode === undefined) {\n throw new InvalidScientificInputError(\n \"sampleValues identifies a spectral node that is not present in spectralQuadrature.\"\n );\n }\n\n const wavelengthNanometers =\n requireNonNegativeFinite(\n sample.node.wavelengthNanometers,\n \"sampleValues[\" +\n sampleIndex +\n \"].node.wavelengthNanometers\"\n );\n if (\n wavelengthNanometers !==\n spectralNode.wavelengthNanometers\n ) {\n throw new InvalidScientificInputError(\n \"sampleValues wavelengthNanometers must exactly match the identified spectral quadrature node.\"\n );\n }\n\n const key = sampleKey(\n spatialNode,\n spectralSampleIndex\n );\n if (valuesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"sampleValues contains duplicate spatio-spectral node identities.\"\n );\n }\n\n valuesByKey.set(\n key,\n valueForSample(sample, sampleIndex)\n );\n }\n );\n\n if (valuesByKey.size !== combinedSampleCount) {\n throw new InvalidScientificInputError(\"sampleValues must not contain sparse or missing node entries.\");\n }\n return valuesByKey;\n}\n\n/** Internal: matches samples to their spatial × spectral nodes. Each caller still validates values and applies its own equations. */\nexport function bindSensorSpatioSpectralSamples<T extends { node: SensorSpatioSpectralNodeIdentity }, V>(\n input: { spatialQuadrature: SensorSpatialSamplingQuadrature; spectralQuadrature: SensorSpectralQuadrature; sampleValues: readonly T[] },\n valueForSample: (sample: T, sampleIndex: number) => V\n): { spatialNodesByKey: Map<string, SensorSpatialSamplingQuadrature[\"nodes\"][number]>;\n spectralNodesByIndex: Map<number, SensorSpectralQuadratureNode>; colorSamplingProfileId: string;\n combinedSampleCount: number; valuesByKey: Map<string, V> } {\n const spatialNodesByKey =\n validateSensorSpatialSamplingQuadrature(\n input.spatialQuadrature\n );\n const spectralNodesByIndex =\n validateSensorSpectralQuadrature(\n input.spectralQuadrature\n );\n\n const colorSamplingProfileId =\n input.spatialQuadrature\n .colorSamplingProfileId;\n if (\n typeof colorSamplingProfileId !==\n \"string\" ||\n colorSamplingProfileId.trim().length ===\n 0\n ) {\n throw new InvalidScientificInputError(\n \"spatialQuadrature.colorSamplingProfileId is required for spatio-spectral composition.\"\n );\n }\n if (\n colorSamplingProfileId !==\n input.spectralQuadrature\n .colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"Spatial and spectral quadratures must reference the same colorSamplingProfileId.\"\n );\n }\n if (\n input.spatialQuadrature.channelId !==\n input.spectralQuadrature.channelId\n ) {\n throw new InvalidScientificInputError(\n \"Spatial and spectral quadratures must reference the same exact channelId.\"\n );\n }\n\n if (\n input.spatialQuadrature.outputMeaning !==\n \"pre-aa-optical-field-spatial-quadrature-nodes\" ||\n input.spatialQuadrature\n .spectralResponseIncluded !== false ||\n input.spatialQuadrature\n .temporalIntegrationIncluded !== false\n ) {\n throw new InvalidScientificInputError(\n \"spatialQuadrature semantics are incompatible with pre-response spatio-spectral reduction.\"\n );\n }\n\n const combinedSampleCount =\n spatialNodesByKey.size *\n spectralNodesByIndex.size;\n if (\n !Number.isSafeInteger(\n combinedSampleCount\n ) ||\n combinedSampleCount <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Spatial × spectral quadrature sample count must be a positive safe integer.\"\n );\n }\n if (\n combinedSampleCount >\n MAX_SPATIO_SPECTRAL_SAMPLE_VALUES\n ) {\n throw new InvalidScientificInputError(\n \"Spatial × spectral quadrature sample count exceeds the safety limit of \" +\n MAX_SPATIO_SPECTRAL_SAMPLE_VALUES +\n \". Tile/batch the evaluation instead of materializing one oversized Cartesian product.\"\n );\n }\n\n const valuesByKey =\n parseSamples(\n input.sampleValues,\n spatialNodesByKey,\n spectralNodesByIndex,\n combinedSampleCount,\n valueForSample\n );\n\n return { spatialNodesByKey, spectralNodesByIndex, colorSamplingProfileId, combinedSampleCount, valuesByKey };\n}\n\n/**\n * Integrates supplied spectral irradiance on the sensor plane, E_lambda(x,y),\n * over every combination of an existing spatial quadrature and spectral\n * quadrature.\n *\n * Units: the density is in W/m^2/nm and wavelengthMeasureNanometers is dλ in\n * nm, so their product is in W/m^2. Do not convert dλ to metres unless the\n * density's per-nanometre unit is converted to match.\n *\n * Area integration uses the spatial quadrature's geometric area measures,\n * converted from square micrometres to square metres. The result is the\n * incident radiant flux on the geometric aperture only; geometric area is not\n * treated as an effective light-collection area.\n *\n * It deliberately stops before the sensor response. The spectral plan comes\n * from a specific response channel, so its wavelength range and breakpoints\n * fit that channel, but QE, A/W responsivity and colour-filter transmission are\n * not applied here. Matching the response's scope and source plane is a\n * separate later step.\n */\nexport function reduceSensorSpatioSpectralIrradiance(\n input:\n ReduceSensorSpatioSpectralIrradianceInput\n): CalculationResult<SensorSpatioSpectralIrradianceReduction> {\n requireInputKeys(input, \"reduceSensorSpatioSpectralIrradiance\", [\"spatialQuadrature\", \"spectralQuadrature\", \"sampleValues\"]);\n const { spatialNodesByKey, spectralNodesByIndex, colorSamplingProfileId, combinedSampleCount, valuesByKey } =\n bindSensorSpatioSpectralSamples(input, (sample, sampleIndex) => requireNonNegativeFinite(\n sample.spectralIrradianceWattsPerSquareMeterPerNanometer,\n \"sampleValues[\" + sampleIndex + \"].spectralIrradianceWattsPerSquareMeterPerNanometer\"\n ));\n\n const perWavelength:\n SensorSpatioSpectralWavelengthReduction[] =\n [];\n let wavelengthIntegratedSpatialAverage = 0;\n let wavelengthIntegratedGeometricFlux = 0;\n\n for (\n let spectralSampleIndex = 0;\n spectralSampleIndex <\n input.spectralQuadrature.totalNodeCount;\n spectralSampleIndex += 1\n ) {\n const spectralNode =\n spectralNodesByIndex.get(\n spectralSampleIndex\n );\n if (spectralNode === undefined) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature is missing a validated contiguous spectral sample.\"\n );\n }\n\n let spatialAverageSpectralIrradiance = 0;\n let geometricSpectralFluxWattsPerNanometer =\n 0;\n\n spatialNodesByKey.forEach(\n (spatialNode, spatialKey) => {\n const value =\n valuesByKey.get(\n spatialKey +\n \"|\" +\n spectralSampleIndex\n );\n if (value === undefined) {\n throw new InvalidScientificInputError(\n \"A spatio-spectral quadrature node is missing its explicit source value.\"\n );\n }\n\n spatialAverageSpectralIrradiance +=\n value *\n spatialNode\n .combinedNormalizedSpatialWeight;\n geometricSpectralFluxWattsPerNanometer +=\n value *\n (\n spatialNode\n .combinedAreaMeasureSquareMicrometers *\n SQUARE_MICROMETERS_TO_SQUARE_METERS\n );\n\n if (\n !Number.isFinite(\n spatialAverageSpectralIrradiance\n ) ||\n !Number.isFinite(\n geometricSpectralFluxWattsPerNanometer\n )\n ) {\n throw new InvalidScientificInputError(\n \"Reduced spatio-spectral values must remain finite.\"\n );\n }\n }\n );\n\n const spatialAverageContribution =\n spatialAverageSpectralIrradiance *\n spectralNode\n .wavelengthMeasureNanometers;\n const geometricFluxContribution =\n geometricSpectralFluxWattsPerNanometer *\n spectralNode\n .wavelengthMeasureNanometers;\n\n wavelengthIntegratedSpatialAverage +=\n spatialAverageContribution;\n wavelengthIntegratedGeometricFlux +=\n geometricFluxContribution;\n\n if (\n !Number.isFinite(\n wavelengthIntegratedSpatialAverage\n ) ||\n !Number.isFinite(\n wavelengthIntegratedGeometricFlux\n )\n ) {\n throw new InvalidScientificInputError(\n \"Wavelength-integrated spatio-spectral values must remain finite.\"\n );\n }\n\n perWavelength.push({\n spectralSampleIndex,\n wavelengthNanometers:\n spectralNode.wavelengthNanometers,\n wavelengthMeasureNanometers:\n spectralNode\n .wavelengthMeasureNanometers,\n normalizedWavelengthWeight:\n spectralNode\n .normalizedWavelengthWeight,\n normalizedSpatialAverageSpectralIrradianceWattsPerSquareMeterPerNanometer:\n spatialAverageSpectralIrradiance,\n geometricApertureIncidentSpectralFluxWattsPerNanometer:\n geometricSpectralFluxWattsPerNanometer,\n wavelengthIntegratedSpatialAverageContributionWattsPerSquareMeter:\n spatialAverageContribution,\n wavelengthIntegratedGeometricApertureIncidentFluxContributionWatts:\n geometricFluxContribution\n });\n }\n\n return approximationResult(\n {\n colorSamplingProfileId,\n ...(input.spatialQuadrature\n .samplingApertureProfileId ===\n undefined\n ? {}\n : {\n samplingApertureProfileId:\n input.spatialQuadrature\n .samplingApertureProfileId\n }),\n ...(input.spatialQuadrature\n .opticalStackProfileId ===\n undefined\n ? {}\n : {\n opticalStackProfileId:\n input.spatialQuadrature\n .opticalStackProfileId\n }),\n site: {\n ...input.spatialQuadrature.site\n },\n channelId:\n input.spatialQuadrature.channelId,\n responseProfileId:\n input.spectralQuadrature.profileId,\n sourceResponseKind:\n input.spectralQuadrature\n .sourceResponseKind,\n responseScope:\n input.spectralQuadrature\n .responseScope,\n responseScientificStatus:\n input.spectralQuadrature\n .responseScientificStatus,\n responseUncertainty:\n input.spectralQuadrature\n .responseUncertainty,\n ...(input.spectralQuadrature\n .responseChannelBinding ===\n undefined\n ? {}\n : {\n responseChannelBinding:\n input.spectralQuadrature\n .responseChannelBinding\n }),\n ...(input.spectralQuadrature\n .responseReferenceConditions ===\n undefined\n ? {}\n : {\n responseReferenceConditions:\n input.spectralQuadrature\n .responseReferenceConditions\n }),\n wavelengthBasis:\n input.spectralQuadrature\n .wavelengthBasis,\n wavelengthBasisResolved:\n input.spectralQuadrature\n .wavelengthBasisResolved,\n wavelengthRangeNanometers: {\n ...input.spectralQuadrature\n .wavelengthRangeNanometers\n },\n responseDeclaredWavelengthRangeNanometers:\n {\n ...input.spectralQuadrature\n .responseDeclaredWavelengthRangeNanometers\n },\n outputMeaning:\n \"pre-response-spatio-spectral-radiometric-reduction\",\n inputValueDomain: {\n kind:\n \"radiometric-spectral-irradiance\",\n unit: \"W/m^2/nm\",\n semantic:\n \"pre-aa-pre-response-sensor-plane-spectral-irradiance\"\n },\n spatialNodeCount:\n input.spatialQuadrature\n .totalNodeCount,\n spectralNodeCount:\n input.spectralQuadrature\n .totalNodeCount,\n combinedSampleCount,\n sourceValuesMatchedBy:\n \"spatial-node-identity-plus-spectral-sample-index-and-wavelength\",\n sourceValuesSuppliedForAllCombinedNodes:\n true,\n outsideImagingAreaSourceValuesRequired:\n input.spatialQuadrature\n .preAntiAliasingSourceOutsideImagingAreaNodeCount >\n 0,\n geometricApertureAreaSquareMicrometers:\n input.spatialQuadrature\n .geometricApertureAreaSquareMicrometers,\n ...(input.spatialQuadrature\n .nominalSiteCellAreaSquareMicrometers ===\n undefined\n ? {}\n : {\n nominalSiteCellAreaSquareMicrometers:\n input.spatialQuadrature\n .nominalSiteCellAreaSquareMicrometers\n }),\n ...(input.spatialQuadrature\n .geometricSensitiveAreaFractionOfLatticeCell ===\n undefined\n ? {}\n : {\n geometricSensitiveAreaFractionOfLatticeCell:\n input.spatialQuadrature\n .geometricSensitiveAreaFractionOfLatticeCell\n }),\n perWavelength,\n wavelengthIntegratedSpatialAverageIrradianceWattsPerSquareMeter:\n wavelengthIntegratedSpatialAverage,\n wavelengthIntegratedGeometricApertureIncidentFluxWatts:\n wavelengthIntegratedGeometricFlux,\n spatialIntegrationApplied: true,\n wavelengthIntegrationApplied: true,\n spectralResponsePlanUsedForWavelengthSupport:\n true,\n spectralResponseApplicationPerformed:\n false,\n responseScopeMatchedToSourcePlane:\n false,\n quantumEfficiencyApplied: false,\n spectralResponsivityApplied: false,\n channelFilterTransmissionApplied:\n false,\n opticalTransmissionAppliedByReducer:\n false,\n radiometricCollectionAreaEstablished:\n false,\n temporalIntegrationApplied: false,\n exposureDurationApplied: false,\n photonsCalculated: false,\n electronsCalculated: false,\n currentCalculated: false,\n shotNoiseApplied: false,\n readNoiseApplied: false,\n adcQuantizationApplied: false,\n rawCodeValueProduced: false,\n demosaicOrReconstructionApplied:\n false,\n convergenceErrorEstimated: false,\n componentEvidence: {\n spatial:\n input.spatialQuadrature\n .componentEvidence,\n spectral:\n input.spectralQuadrature\n .componentEvidence\n }\n },\n \"sensor-spatio-spectral-irradiance-reduction\",\n \"1.0.0\",\n [\n \"Input values are nonnegative sensor-plane spectral irradiance E_lambda(x,y) in W/m^2/nm, evaluated at every spatial pre-AA source coordinate and spectral quadrature wavelength.\",\n \"Spatial and spectral plans must share the exact colorSamplingProfileId and channelId; a matching channel label alone is insufficient linkage.\",\n \"The full Cartesian product is explicit and capped at 100,000 supplied sample values to avoid multiplicative memory blowups.\",\n \"Spatial normalized weights form the per-wavelength average; geometric square-micrometre area measures are converted to square metres for incident spectral flux density.\",\n \"Physical wavelength integration multiplies W/m^2/nm or W/nm by d-lambda in nm. No extra 1e-9 factor is applied because the spectral density is already per nanometre.\",\n \"The spatial AA kernel is the existing normalized throughput-free spatial redistribution; this reducer adds no omitted optical-stack or microlens throughput.\",\n \"The spectral quadrature's response-derived support and breakpoints are consumed, but QE, A/W responsivity and channel-filter transmission are not applied.\",\n \"Response scope is preserved for downstream work but is not matched to a declared source plane in this version.\",\n \"Geometric aperture area is not promoted to effective radiometric collection area.\",\n \"Temporal exposure integration, photon/electron conversion, electrical current, noise, saturation, ADC/RAW conversion and reconstruction remain separate future work.\",\n \"No source-independent spatio-spectral convergence/error estimate is reported; downstream integrand structure controls numerical error.\"\n ]\n );\n}\n"]}
|
|
@@ -5,12 +5,20 @@
|
|
|
5
5
|
* limits.
|
|
6
6
|
*/
|
|
7
7
|
import type { SpectralCoverageParticipant } from "../core/spectral-composition.js";
|
|
8
|
-
import { type SensorSpectralResponseProfile } from "./spectral-response.js";
|
|
8
|
+
import { type SensorSpectralChannelResponse, type SensorSpectralResponseProfile } from "./spectral-response.js";
|
|
9
9
|
export interface CreateSensorSpectralCoverageParticipantInput {
|
|
10
10
|
spectralResponseProfile: SensorSpectralResponseProfile;
|
|
11
11
|
channelId: string;
|
|
12
12
|
participantId?: string;
|
|
13
13
|
}
|
|
14
|
+
type ResponseCurve = Extract<SensorSpectralChannelResponse, {
|
|
15
|
+
kind: "effective-external-quantum-efficiency";
|
|
16
|
+
}>["externalQuantumEfficiency"] | Extract<SensorSpectralChannelResponse, {
|
|
17
|
+
kind: "effective-spectral-responsivity";
|
|
18
|
+
}>["spectralResponsivity"] | Extract<SensorSpectralChannelResponse, {
|
|
19
|
+
kind: "separable-channel-filter-and-detector-eqe";
|
|
20
|
+
}>["channelFilterTransmittance"];
|
|
21
|
+
export declare function responseCurves(response: SensorSpectralChannelResponse): readonly ResponseCurve[];
|
|
14
22
|
/**
|
|
15
23
|
* Converts one parsed sensor response channel into the shared continuous
|
|
16
24
|
* spectral-coverage format.
|
|
@@ -19,3 +27,4 @@ export interface CreateSensorSpectralCoverageParticipantInput {
|
|
|
19
27
|
* apply QE or A/W responsivity, and says nothing about scene or optics coverage.
|
|
20
28
|
*/
|
|
21
29
|
export declare function createSensorSpectralCoverageParticipant(input: CreateSensorSpectralCoverageParticipantInput): SpectralCoverageParticipant;
|
|
30
|
+
export {};
|
|
@@ -3,7 +3,7 @@ import { InvalidScientificInputError } from "../core/validation.js";
|
|
|
3
3
|
import { parseSensorSpectralResponseProfile } from "./spectral-response.js";
|
|
4
4
|
import { requireInputKeys } from "../core/record-validation.js";
|
|
5
5
|
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
6
|
-
function responseCurves(response) {
|
|
6
|
+
export function responseCurves(response) {
|
|
7
7
|
if (response.kind ===
|
|
8
8
|
"effective-external-quantum-efficiency") {
|
|
9
9
|
return [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spectral-coverage.js","sourceRoot":"","sources":["../../src/sensor/spectral-coverage.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAYtC,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EACL,kCAAkC,EAGnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAgC3E,
|
|
1
|
+
{"version":3,"file":"spectral-coverage.js","sourceRoot":"","sources":["../../src/sensor/spectral-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Describes a sensor response channel's wavelength coverage in the shared spectral-coverage format\n * (core/spectral-composition), so it can be intersected with the light source and optics.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport type {\n SpectralCoverageParticipant\n} from \"../core/spectral-composition.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport {\n parseSensorSpectralResponseProfile,\n type SensorSpectralChannelResponse,\n type SensorSpectralResponseProfile\n} from \"./spectral-response.js\";\nimport { requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport interface CreateSensorSpectralCoverageParticipantInput {\n spectralResponseProfile:\n SensorSpectralResponseProfile;\n channelId: string;\n participantId?: string;\n}\n\ntype ResponseCurve =\n | Extract<\n SensorSpectralChannelResponse,\n {\n kind:\n \"effective-external-quantum-efficiency\";\n }\n >[\"externalQuantumEfficiency\"]\n | Extract<\n SensorSpectralChannelResponse,\n {\n kind:\n \"effective-spectral-responsivity\";\n }\n >[\"spectralResponsivity\"]\n | Extract<\n SensorSpectralChannelResponse,\n {\n kind:\n \"separable-channel-filter-and-detector-eqe\";\n }\n >[\"channelFilterTransmittance\"];\n\nexport function responseCurves(\n response:\n SensorSpectralChannelResponse\n): readonly ResponseCurve[] {\n if (\n response.kind ===\n \"effective-external-quantum-efficiency\"\n ) {\n return [\n response.externalQuantumEfficiency\n ];\n }\n if (\n response.kind ===\n \"effective-spectral-responsivity\"\n ) {\n return [\n response.spectralResponsivity\n ];\n }\n return [\n response.channelFilterTransmittance,\n response.detectorExternalQuantumEfficiency\n ];\n}\n\n/**\n * Converts one parsed sensor response channel into the shared continuous\n * spectral-coverage format.\n *\n * It gives only the wavelength range and interpolation breakpoints. It does not\n * apply QE or A/W responsivity, and says nothing about scene or optics coverage.\n */\nexport function createSensorSpectralCoverageParticipant(\n input:\n CreateSensorSpectralCoverageParticipantInput\n): SpectralCoverageParticipant {\n requireInputKeys(input, \"createSensorSpectralCoverageParticipant\", [\"spectralResponseProfile\", \"channelId\", \"participantId\"]);\n const profile =\n parseSensorSpectralResponseProfile(\n input.spectralResponseProfile\n );\n\n if (\n typeof input.channelId !==\n \"string\" ||\n input.channelId.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"channelId must be a non-empty string.\"\n );\n }\n\n const response =\n profile.channels.find(\n (entry) =>\n entry.channelId ===\n input.channelId\n );\n if (response === undefined) {\n throw new InvalidScientificInputError(\n \"No spectral response is declared for the requested channelId.\"\n );\n }\n\n const curves =\n responseCurves(response);\n const wavelengthBasis =\n curves[0]!.wavelengthBasis;\n if (wavelengthBasis === \"unspecified\") {\n throw new InvalidScientificInputError(\n \"Sensor spectral coverage requires a resolved air or vacuum wavelength basis.\"\n );\n }\n if (\n curves.some(\n (curve) =>\n curve.wavelengthBasis !==\n wavelengthBasis\n )\n ) {\n throw new InvalidScientificInputError(\n \"Sensor response component curves must share one wavelengthBasis before coverage composition.\"\n );\n }\n\n const minimum = Math.max(\n ...curves.map(\n (curve) =>\n curve.samples[0]!\n .wavelengthNanometers\n )\n );\n const maximum = Math.min(\n ...curves.map(\n (curve) =>\n curve.samples[\n curve.samples.length - 1\n ]!.wavelengthNanometers\n )\n );\n if (!(minimum < maximum)) {\n throw new InvalidScientificInputError(\n \"Sensor response component curves do not share a non-empty wavelength interval.\"\n );\n }\n\n const breakpoints =\n new Set<number>();\n for (const curve of curves) {\n for (const sample of curve.samples) {\n if (\n sample.wavelengthNanometers >\n minimum &&\n sample.wavelengthNanometers <\n maximum\n ) {\n breakpoints.add(\n sample.wavelengthNanometers\n );\n }\n }\n }\n\n const participantId =\n input.participantId ??\n \"sensor-response:\" +\n profile.profileId +\n \":\" +\n input.channelId;\n\n if (\n participantId.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"participantId must be a non-empty string when supplied.\"\n );\n }\n\n return {\n participantId,\n role: \"sensor-response\",\n wavelengthBasis,\n wavelengthRangeNanometers: {\n minimum,\n maximum\n },\n breakpointsNanometers:\n [...breakpoints].sort(\n (a, b) => a - b\n )\n };\n}\n"]}
|