@photivra/engine 1.5.0 → 2.0.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 +393 -6
- package/CITATION.cff +1 -1
- package/NODE_SUPPORT.md +64 -12
- package/README.md +301 -182
- package/SECURITY.md +37 -28
- package/THIRD_PARTY.md +148 -4
- package/dist/camera/equivalent-focal-length.d.ts +13 -14
- package/dist/camera/equivalent-focal-length.js +15 -13
- package/dist/camera/equivalent-focal-length.js.map +1 -1
- package/dist/camera/field-of-view.d.ts +30 -19
- package/dist/camera/field-of-view.js +67 -18
- package/dist/camera/field-of-view.js.map +1 -1
- package/dist/camera/lens-projection.d.ts +170 -0
- package/dist/camera/lens-projection.js +444 -0
- package/dist/camera/lens-projection.js.map +1 -0
- package/dist/camera/projected-object-size.d.ts +10 -11
- package/dist/camera/projected-object-size.js +12 -10
- package/dist/camera/projected-object-size.js.map +1 -1
- package/dist/capture/capture-export-metadata.d.ts +9 -10
- package/dist/capture/capture-export-metadata.js +8 -5
- package/dist/capture/capture-export-metadata.js.map +1 -1
- package/dist/capture/dng-import.d.ts +91 -0
- package/dist/capture/dng-import.js +343 -0
- package/dist/capture/dng-import.js.map +1 -0
- package/dist/capture/environment-raw-producer.d.ts +37 -17
- package/dist/capture/environment-raw-producer.js +43 -19
- package/dist/capture/environment-raw-producer.js.map +1 -1
- package/dist/capture/export-jpeg.d.ts +35 -4
- package/dist/capture/export-jpeg.js +133 -47
- package/dist/capture/export-jpeg.js.map +1 -1
- package/dist/capture/export-tiff.d.ts +48 -24
- package/dist/capture/export-tiff.js +91 -31
- package/dist/capture/export-tiff.js.map +1 -1
- package/dist/capture/focus-control.d.ts +42 -65
- package/dist/capture/focus-control.js +56 -75
- package/dist/capture/focus-control.js.map +1 -1
- package/dist/capture/host-yield-cadence.d.ts +25 -0
- package/dist/capture/host-yield-cadence.js +25 -0
- package/dist/capture/host-yield-cadence.js.map +1 -0
- package/dist/capture/linear-encoding.d.ts +14 -12
- package/dist/capture/linear-encoding.js +14 -13
- package/dist/capture/linear-encoding.js.map +1 -1
- package/dist/capture/lossless-jpeg.d.ts +27 -0
- package/dist/capture/lossless-jpeg.js +237 -0
- package/dist/capture/lossless-jpeg.js.map +1 -0
- package/dist/capture/native-environment-lean.d.ts +227 -0
- package/dist/capture/native-environment-lean.js +974 -0
- package/dist/capture/native-environment-lean.js.map +1 -0
- package/dist/capture/native-environment-optical-stage.d.ts +97 -0
- package/dist/capture/native-environment-optical-stage.js +340 -0
- package/dist/capture/native-environment-optical-stage.js.map +1 -0
- package/dist/capture/native-environment-raw.d.ts +140 -6
- package/dist/capture/native-environment-raw.js +435 -34
- package/dist/capture/native-environment-raw.js.map +1 -1
- package/dist/capture/native-raw-development.d.ts +116 -9
- package/dist/capture/native-raw-development.js +221 -62
- package/dist/capture/native-raw-development.js.map +1 -1
- package/dist/capture/native-raw-dng.d.ts +60 -0
- package/dist/capture/native-raw-dng.js +167 -0
- package/dist/capture/native-raw-dng.js.map +1 -0
- package/dist/capture/native-raw.d.ts +65 -12
- package/dist/capture/native-raw.js +81 -19
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/packed-sensor-raw.d.ts +160 -0
- package/dist/capture/packed-sensor-raw.js +221 -0
- package/dist/capture/packed-sensor-raw.js.map +1 -0
- package/dist/capture/photographic-export.d.ts +58 -35
- package/dist/capture/photographic-export.js +177 -70
- package/dist/capture/photographic-export.js.map +1 -1
- package/dist/capture/raw-development-kernel.d.ts +52 -0
- package/dist/capture/raw-development-kernel.js +88 -0
- package/dist/capture/raw-development-kernel.js.map +1 -0
- package/dist/capture/raw-frame-limits.d.ts +7 -7
- package/dist/capture/raw-frame-limits.js +7 -6
- package/dist/capture/raw-frame-limits.js.map +1 -1
- package/dist/capture/raw-frame-reconstruction.d.ts +8 -9
- package/dist/capture/raw-frame-reconstruction.js +9 -8
- package/dist/capture/raw-frame-reconstruction.js.map +1 -1
- package/dist/capture/raw-white-level.d.ts +55 -0
- package/dist/capture/raw-white-level.js +165 -0
- package/dist/capture/raw-white-level.js.map +1 -0
- package/dist/capture/release-sequence.d.ts +5 -7
- package/dist/capture/release-sequence.js +15 -13
- package/dist/capture/release-sequence.js.map +1 -1
- package/dist/capture/sensor-raw-frame.d.ts +5 -6
- package/dist/capture/sensor-raw-frame.js +26 -9
- package/dist/capture/sensor-raw-frame.js.map +1 -1
- package/dist/capture/sensor-raw-producer.d.ts +21 -13
- package/dist/capture/sensor-raw-producer.js +86 -25
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/capture/simulated-capture.d.ts +15 -16
- package/dist/capture/simulated-capture.js +22 -12
- package/dist/capture/simulated-capture.js.map +1 -1
- package/dist/color/camera-color-matrix.d.ts +69 -0
- package/dist/color/camera-color-matrix.js +132 -0
- package/dist/color/camera-color-matrix.js.map +1 -0
- package/dist/color/capture-color.d.ts +17 -10
- package/dist/color/capture-color.js +50 -17
- package/dist/color/capture-color.js.map +1 -1
- package/dist/color/chromatic-adaptation.d.ts +63 -0
- package/dist/color/chromatic-adaptation.js +91 -0
- package/dist/color/chromatic-adaptation.js.map +1 -0
- package/dist/color/color-difference.d.ts +104 -0
- package/dist/color/color-difference.js +205 -0
- package/dist/color/color-difference.js.map +1 -0
- package/dist/color/color-matching-functions.d.ts +168 -0
- package/dist/color/color-matching-functions.js +309 -0
- package/dist/color/color-matching-functions.js.map +1 -0
- package/dist/color/colorimetry-math.d.ts +52 -0
- package/dist/color/colorimetry-math.js +144 -0
- package/dist/color/colorimetry-math.js.map +1 -0
- package/dist/color/gamut-mapping.d.ts +54 -0
- package/dist/color/gamut-mapping.js +107 -0
- package/dist/color/gamut-mapping.js.map +1 -0
- package/dist/color/illuminants.d.ts +182 -0
- package/dist/color/illuminants.js +434 -0
- package/dist/color/illuminants.js.map +1 -0
- package/dist/color/rgb-color-spaces.d.ts +79 -0
- package/dist/color/rgb-color-spaces.js +115 -0
- package/dist/color/rgb-color-spaces.js.map +1 -0
- package/dist/color/sensor-color-development.d.ts +33 -11
- package/dist/color/sensor-color-development.js +104 -30
- package/dist/color/sensor-color-development.js.map +1 -1
- package/dist/color/srgb-icc.d.ts +9 -2
- package/dist/color/srgb-icc.js +16 -5
- package/dist/color/srgb-icc.js.map +1 -1
- package/dist/color/srgb-transfer.d.ts +1 -2
- package/dist/color/srgb-transfer.js +1 -1
- package/dist/color/srgb-transfer.js.map +1 -1
- package/dist/color/white-balance.d.ts +68 -19
- package/dist/color/white-balance.js +168 -35
- package/dist/color/white-balance.js.map +1 -1
- package/dist/composition/image-formation-plan.d.ts +152 -128
- package/dist/composition/image-formation-plan.js +292 -138
- package/dist/composition/image-formation-plan.js.map +1 -1
- package/dist/composition/plan-consumer.d.ts +6 -7
- package/dist/composition/plan-consumer.js +14 -11
- package/dist/composition/plan-consumer.js.map +1 -1
- package/dist/core/calculation-result.d.ts +21 -14
- package/dist/core/calculation-result.js +209 -11
- package/dist/core/calculation-result.js.map +1 -1
- package/dist/core/canonical-json.d.ts +13 -8
- package/dist/core/canonical-json.js +34 -15
- package/dist/core/canonical-json.js.map +1 -1
- package/dist/core/configuration-error.d.ts +5 -4
- package/dist/core/configuration-error.js +5 -3
- package/dist/core/configuration-error.js.map +1 -1
- package/dist/core/evidence-provenance.d.ts +12 -12
- package/dist/core/evidence-provenance.js +10 -9
- package/dist/core/evidence-provenance.js.map +1 -1
- package/dist/core/image-formation.d.ts +11 -10
- package/dist/core/image-formation.js +11 -9
- package/dist/core/image-formation.js.map +1 -1
- package/dist/core/owned-data.d.ts +9 -17
- package/dist/core/owned-data.js +9 -16
- package/dist/core/owned-data.js.map +1 -1
- package/dist/core/portable-math.d.ts +15 -0
- package/dist/core/portable-math.js +56 -0
- package/dist/core/portable-math.js.map +1 -0
- package/dist/core/record-validation.d.ts +48 -9
- package/dist/core/record-validation.js +93 -19
- package/dist/core/record-validation.js.map +1 -1
- package/dist/core/scientific-assurance.d.ts +16 -14
- package/dist/core/scientific-assurance.js +22 -18
- package/dist/core/scientific-assurance.js.map +1 -1
- package/dist/core/spectral-composition.d.ts +13 -14
- package/dist/core/spectral-composition.js +30 -27
- package/dist/core/spectral-composition.js.map +1 -1
- package/dist/core/spectral.d.ts +5 -6
- package/dist/core/spectral.js +9 -7
- package/dist/core/spectral.js.map +1 -1
- package/dist/core/validation.d.ts +3 -6
- package/dist/core/validation.js +3 -5
- package/dist/core/validation.js.map +1 -1
- package/dist/core/version.d.ts +6 -6
- package/dist/core/version.js +6 -5
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/exposure-capabilities.d.ts +10 -10
- package/dist/equipment/exposure-capabilities.js +40 -30
- package/dist/equipment/exposure-capabilities.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +13 -6
- package/dist/equipment/generic-tier-assets.js +34 -15
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/generic-tier-presets.d.ts +5 -6
- package/dist/equipment/generic-tier-presets.js +15 -13
- package/dist/equipment/generic-tier-presets.js.map +1 -1
- package/dist/equipment/iso-capabilities.d.ts +21 -22
- package/dist/equipment/iso-capabilities.js +49 -44
- package/dist/equipment/iso-capabilities.js.map +1 -1
- package/dist/equipment/metering-capabilities.d.ts +5 -6
- package/dist/equipment/metering-capabilities.js +22 -19
- package/dist/equipment/metering-capabilities.js.map +1 -1
- package/dist/equipment/release-capabilities.d.ts +3 -4
- package/dist/equipment/release-capabilities.js +18 -14
- package/dist/equipment/release-capabilities.js.map +1 -1
- package/dist/exposure/exposure-duration-control.d.ts +19 -17
- package/dist/exposure/exposure-duration-control.js +27 -23
- package/dist/exposure/exposure-duration-control.js.map +1 -1
- package/dist/exposure/exposure-mode-resolver.d.ts +42 -39
- package/dist/exposure/exposure-mode-resolver.js +127 -82
- package/dist/exposure/exposure-mode-resolver.js.map +1 -1
- package/dist/exposure/exposure.d.ts +17 -16
- package/dist/exposure/exposure.js +22 -15
- package/dist/exposure/exposure.js.map +1 -1
- package/dist/exposure/flash-ttl.d.ts +135 -0
- package/dist/exposure/flash-ttl.js +193 -0
- package/dist/exposure/flash-ttl.js.map +1 -0
- package/dist/exposure/flash.d.ts +144 -36
- package/dist/exposure/flash.js +259 -59
- package/dist/exposure/flash.js.map +1 -1
- package/dist/exposure/metering-scene-radiance.d.ts +10 -10
- package/dist/exposure/metering-scene-radiance.js +42 -23
- package/dist/exposure/metering-scene-radiance.js.map +1 -1
- package/dist/exposure/metering-target.d.ts +8 -8
- package/dist/exposure/metering-target.js +30 -20
- package/dist/exposure/metering-target.js.map +1 -1
- package/dist/exposure/metering-temporal.d.ts +16 -15
- package/dist/exposure/metering-temporal.js +41 -19
- package/dist/exposure/metering-temporal.js.map +1 -1
- package/dist/exposure/metering.d.ts +64 -17
- package/dist/exposure/metering.js +159 -30
- package/dist/exposure/metering.js.map +1 -1
- package/dist/exposure/program-line.d.ts +5 -5
- package/dist/exposure/program-line.js +20 -13
- package/dist/exposure/program-line.js.map +1 -1
- package/dist/facade/camera-preset.d.ts +8 -0
- package/dist/facade/camera-preset.js +102 -0
- package/dist/facade/camera-preset.js.map +1 -0
- package/dist/facade/chart-radiance-batch.d.ts +57 -0
- package/dist/facade/chart-radiance-batch.js +121 -0
- package/dist/facade/chart-radiance-batch.js.map +1 -0
- package/dist/facade/chart-scenes.d.ts +177 -0
- package/dist/facade/chart-scenes.js +232 -0
- package/dist/facade/chart-scenes.js.map +1 -0
- package/dist/facade/host-yield.d.ts +7 -0
- package/dist/facade/host-yield.js +23 -0
- package/dist/facade/host-yield.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +117 -0
- package/dist/facade/scene-radiance.js +205 -0
- package/dist/facade/scene-radiance.js.map +1 -0
- package/dist/facade/simulate-capture.d.ts +284 -0
- package/dist/facade/simulate-capture.js +1026 -0
- package/dist/facade/simulate-capture.js.map +1 -0
- package/dist/gpu/chart-radiance-gpu.d.ts +17 -0
- package/dist/gpu/chart-radiance-gpu.js +176 -0
- package/dist/gpu/chart-radiance-gpu.js.map +1 -0
- package/dist/gpu/image-kernel-accelerator.d.ts +132 -0
- package/dist/gpu/image-kernel-accelerator.js +720 -0
- package/dist/gpu/image-kernel-accelerator.js.map +1 -0
- package/dist/gpu/image-kernel-execution.d.ts +92 -0
- package/dist/gpu/image-kernel-execution.js +84 -0
- package/dist/gpu/image-kernel-execution.js.map +1 -0
- package/dist/gpu/sensor-stage-gpu.d.ts +44 -0
- package/dist/gpu/sensor-stage-gpu.js +317 -0
- package/dist/gpu/sensor-stage-gpu.js.map +1 -0
- package/dist/gpu/temporal-sum-gpu.d.ts +23 -0
- package/dist/gpu/temporal-sum-gpu.js +150 -0
- package/dist/gpu/temporal-sum-gpu.js.map +1 -0
- package/dist/gpu/webgpu-runtime.d.ts +63 -0
- package/dist/gpu/webgpu-runtime.js +215 -0
- package/dist/gpu/webgpu-runtime.js.map +1 -0
- package/dist/gpu/wgsl-chart-kernel.d.ts +30 -0
- package/dist/gpu/wgsl-chart-kernel.js +165 -0
- package/dist/gpu/wgsl-chart-kernel.js.map +1 -0
- package/dist/gpu/wgsl-kernels.d.ts +53 -0
- package/dist/gpu/wgsl-kernels.js +365 -0
- package/dist/gpu/wgsl-kernels.js.map +1 -0
- package/dist/gpu/wgsl-sensor-kernel.d.ts +64 -0
- package/dist/gpu/wgsl-sensor-kernel.js +409 -0
- package/dist/gpu/wgsl-sensor-kernel.js.map +1 -0
- package/dist/index.d.ts +89 -25
- package/dist/index.js +59 -15
- package/dist/index.js.map +1 -1
- package/dist/motion/camera-rotation.d.ts +92 -40
- package/dist/motion/camera-rotation.js +84 -47
- package/dist/motion/camera-rotation.js.map +1 -1
- package/dist/motion/capture-rotation-inverse-mapping.d.ts +37 -34
- package/dist/motion/capture-rotation-inverse-mapping.js +32 -24
- package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -1
- package/dist/motion/capture-rotation-temporal-quadrature.d.ts +39 -35
- package/dist/motion/capture-rotation-temporal-quadrature.js +37 -28
- package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -1
- package/dist/motion/capture-rotation-trajectory.d.ts +32 -28
- package/dist/motion/capture-rotation-trajectory.js +36 -26
- package/dist/motion/capture-rotation-trajectory.js.map +1 -1
- package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts +23 -21
- package/dist/motion/capture-translation-parallax-temporal-quadrature.js +33 -14
- package/dist/motion/capture-translation-parallax-temporal-quadrature.js.map +1 -1
- package/dist/motion/extended-object-projection.d.ts +19 -16
- package/dist/motion/extended-object-projection.js +38 -18
- package/dist/motion/extended-object-projection.js.map +1 -1
- package/dist/motion/projected-motion.d.ts +18 -17
- package/dist/motion/projected-motion.js +25 -17
- package/dist/motion/projected-motion.js.map +1 -1
- package/dist/motion/temporal-exposure-sum.d.ts +55 -0
- package/dist/motion/temporal-exposure-sum.js +141 -0
- package/dist/motion/temporal-exposure-sum.js.map +1 -0
- package/dist/motion/video-sequence.d.ts +111 -0
- package/dist/motion/video-sequence.js +179 -0
- package/dist/motion/video-sequence.js.map +1 -0
- package/dist/optics/aperture.d.ts +14 -15
- package/dist/optics/aperture.js +13 -11
- package/dist/optics/aperture.js.map +1 -1
- package/dist/optics/atmospheric-haze.d.ts +83 -0
- package/dist/optics/atmospheric-haze.js +113 -0
- package/dist/optics/atmospheric-haze.js.map +1 -0
- package/dist/optics/brown-conrady-distortion.d.ts +116 -0
- package/dist/optics/brown-conrady-distortion.js +291 -0
- package/dist/optics/brown-conrady-distortion.js.map +1 -0
- package/dist/optics/circle-of-confusion.d.ts +10 -10
- package/dist/optics/circle-of-confusion.js +15 -11
- package/dist/optics/circle-of-confusion.js.map +1 -1
- package/dist/optics/complex-pupil-psf.d.ts +64 -33
- package/dist/optics/complex-pupil-psf.js +190 -98
- package/dist/optics/complex-pupil-psf.js.map +1 -1
- package/dist/optics/depth-of-field.d.ts +25 -15
- package/dist/optics/depth-of-field.js +67 -27
- package/dist/optics/depth-of-field.js.map +1 -1
- package/dist/optics/diffraction.d.ts +14 -13
- package/dist/optics/diffraction.js +14 -10
- package/dist/optics/diffraction.js.map +1 -1
- package/dist/optics/focus-breathing.d.ts +88 -27
- package/dist/optics/focus-breathing.js +114 -23
- package/dist/optics/focus-breathing.js.map +1 -1
- package/dist/optics/focus-state.d.ts +18 -18
- package/dist/optics/focus-state.js +21 -15
- package/dist/optics/focus-state.js.map +1 -1
- package/dist/optics/front-of-lens-filter.d.ts +45 -14
- package/dist/optics/front-of-lens-filter.js +259 -88
- package/dist/optics/front-of-lens-filter.js.map +1 -1
- package/dist/optics/illumination-vignetting.d.ts +16 -17
- package/dist/optics/illumination-vignetting.js +40 -17
- package/dist/optics/illumination-vignetting.js.map +1 -1
- package/dist/optics/irradiance-convolution.d.ts +59 -0
- package/dist/optics/irradiance-convolution.js +138 -0
- package/dist/optics/irradiance-convolution.js.map +1 -0
- package/dist/optics/lateral-chromatic-aberration.d.ts +33 -30
- package/dist/optics/lateral-chromatic-aberration.js +33 -23
- package/dist/optics/lateral-chromatic-aberration.js.map +1 -1
- package/dist/optics/lens-psf-kernels.d.ts +89 -0
- package/dist/optics/lens-psf-kernels.js +175 -0
- package/dist/optics/lens-psf-kernels.js.map +1 -0
- package/dist/optics/lens-psf-profile.d.ts +78 -46
- package/dist/optics/lens-psf-profile.js +335 -124
- package/dist/optics/lens-psf-profile.js.map +1 -1
- package/dist/optics/longitudinal-chromatic-aberration.d.ts +102 -0
- package/dist/optics/longitudinal-chromatic-aberration.js +210 -0
- package/dist/optics/longitudinal-chromatic-aberration.js.map +1 -0
- package/dist/optics/polygon-diffraction.d.ts +80 -23
- package/dist/optics/polygon-diffraction.js +130 -25
- package/dist/optics/polygon-diffraction.js.map +1 -1
- package/dist/optics/profile-contract.d.ts +5 -6
- package/dist/optics/profile-contract.js +14 -11
- package/dist/optics/profile-contract.js.map +1 -1
- package/dist/optics/psf-foundation.d.ts +23 -23
- package/dist/optics/psf-foundation.js +24 -21
- package/dist/optics/psf-foundation.js.map +1 -1
- package/dist/optics/pupil-vignetting.d.ts +118 -0
- package/dist/optics/pupil-vignetting.js +264 -0
- package/dist/optics/pupil-vignetting.js.map +1 -0
- package/dist/optics/radial-distortion.d.ts +34 -33
- package/dist/optics/radial-distortion.js +37 -30
- package/dist/optics/radial-distortion.js.map +1 -1
- package/dist/optics/scene-to-sensor-irradiance.d.ts +47 -33
- package/dist/optics/scene-to-sensor-irradiance.js +73 -45
- package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
- package/dist/optics/scene-to-sensor-quadrature.d.ts +23 -18
- package/dist/optics/scene-to-sensor-quadrature.js +16 -11
- package/dist/optics/scene-to-sensor-quadrature.js.map +1 -1
- package/dist/optics/sensor-aperture-rays.d.ts +20 -4
- package/dist/optics/sensor-aperture-rays.js +32 -14
- package/dist/optics/sensor-aperture-rays.js.map +1 -1
- package/dist/optics/sensor-environment-query.d.ts +81 -16
- package/dist/optics/sensor-environment-query.js +145 -12
- package/dist/optics/sensor-environment-query.js.map +1 -1
- package/dist/optics/sensor-psf-quadrature.d.ts +12 -10
- package/dist/optics/sensor-psf-quadrature.js +18 -11
- package/dist/optics/sensor-psf-quadrature.js.map +1 -1
- package/dist/optics/sensor-psf-support.d.ts +8 -6
- package/dist/optics/sensor-psf-support.js +8 -5
- package/dist/optics/sensor-psf-support.js.map +1 -1
- package/dist/optics/stray-light.d.ts +22 -19
- package/dist/optics/stray-light.js +18 -12
- package/dist/optics/stray-light.js.map +1 -1
- package/dist/optics/thin-lens.d.ts +10 -9
- package/dist/optics/thin-lens.js +12 -8
- package/dist/optics/thin-lens.js.map +1 -1
- package/dist/optics/zernike-wavefront.d.ts +154 -0
- package/dist/optics/zernike-wavefront.js +352 -0
- package/dist/optics/zernike-wavefront.js.map +1 -0
- package/dist/output/capture-corrected-sdr.d.ts +16 -8
- package/dist/output/capture-corrected-sdr.js +16 -8
- package/dist/output/capture-corrected-sdr.js.map +1 -1
- package/dist/output/capture-geometry.d.ts +34 -36
- package/dist/output/capture-geometry.js +52 -32
- package/dist/output/capture-geometry.js.map +1 -1
- package/dist/output/capture-sdr.d.ts +7 -6
- package/dist/output/capture-sdr.js +11 -8
- package/dist/output/capture-sdr.js.map +1 -1
- package/dist/output/crop.d.ts +22 -6
- package/dist/output/crop.js +40 -6
- package/dist/output/crop.js.map +1 -1
- package/dist/output/geometric-transforms.d.ts +60 -23
- package/dist/output/geometric-transforms.js +111 -24
- package/dist/output/geometric-transforms.js.map +1 -1
- package/dist/output/lens-corrections.d.ts +27 -19
- package/dist/output/lens-corrections.js +29 -13
- package/dist/output/lens-corrections.js.map +1 -1
- package/dist/output/native-capture-sdr.d.ts +71 -16
- package/dist/output/native-capture-sdr.js +190 -33
- package/dist/output/native-capture-sdr.js.map +1 -1
- package/dist/output/png-encoder.d.ts +65 -0
- package/dist/output/png-encoder.js +461 -0
- package/dist/output/png-encoder.js.map +1 -0
- package/dist/output/print-contrast-reference.d.ts +10 -9
- package/dist/output/print-contrast-reference.js +10 -8
- package/dist/output/print-contrast-reference.js.map +1 -1
- package/dist/output/print-detail.d.ts +18 -17
- package/dist/output/print-detail.js +12 -10
- package/dist/output/print-detail.js.map +1 -1
- package/dist/output/print-jpeg.d.ts +26 -5
- package/dist/output/print-jpeg.js +117 -50
- package/dist/output/print-jpeg.js.map +1 -1
- package/dist/output/print-plan.d.ts +19 -19
- package/dist/output/print-plan.js +11 -10
- package/dist/output/print-plan.js.map +1 -1
- package/dist/output/print-region-statistics.d.ts +14 -15
- package/dist/output/print-region-statistics.js +11 -11
- package/dist/output/print-region-statistics.js.map +1 -1
- package/dist/output/sdr-rendering.d.ts +48 -10
- package/dist/output/sdr-rendering.js +114 -25
- package/dist/output/sdr-rendering.js.map +1 -1
- package/dist/output/subject-framing-crop.d.ts +6 -10
- package/dist/output/subject-framing-crop.js +23 -13
- package/dist/output/subject-framing-crop.js.map +1 -1
- package/dist/processing/demosaic.d.ts +90 -0
- package/dist/processing/demosaic.js +275 -0
- package/dist/processing/demosaic.js.map +1 -0
- package/dist/processing/denoise.d.ts +68 -0
- package/dist/processing/denoise.js +214 -0
- package/dist/processing/denoise.js.map +1 -0
- package/dist/processing/focus-stacking.d.ts +17 -0
- package/dist/processing/focus-stacking.js +80 -0
- package/dist/processing/focus-stacking.js.map +1 -0
- package/dist/processing/hdr.d.ts +54 -0
- package/dist/processing/hdr.js +172 -0
- package/dist/processing/hdr.js.map +1 -0
- package/dist/processing/image-ops.d.ts +66 -0
- package/dist/processing/image-ops.js +192 -0
- package/dist/processing/image-ops.js.map +1 -0
- package/dist/processing/panorama.d.ts +51 -0
- package/dist/processing/panorama.js +305 -0
- package/dist/processing/panorama.js.map +1 -0
- package/dist/processing/processing-task.d.ts +85 -0
- package/dist/processing/processing-task.js +164 -0
- package/dist/processing/processing-task.js.map +1 -0
- package/dist/processing/tone-mapping.d.ts +69 -0
- package/dist/processing/tone-mapping.js +310 -0
- package/dist/processing/tone-mapping.js.map +1 -0
- package/dist/schema/camera.d.ts +5 -4
- package/dist/schema/illumination-spectral.d.ts +13 -19
- package/dist/schema/illumination-spectral.js +23 -19
- package/dist/schema/illumination-spectral.js.map +1 -1
- package/dist/schema/illumination-temporal.d.ts +12 -15
- package/dist/schema/illumination-temporal.js +43 -28
- package/dist/schema/illumination-temporal.js.map +1 -1
- package/dist/schema/illumination.d.ts +5 -4
- package/dist/schema/illumination.js +55 -20
- package/dist/schema/illumination.js.map +1 -1
- package/dist/schema/scene-radiance-temporal.d.ts +9 -11
- package/dist/schema/scene-radiance-temporal.js +26 -23
- package/dist/schema/scene-radiance-temporal.js.map +1 -1
- package/dist/schema/scene-radiance.d.ts +17 -17
- package/dist/schema/scene-radiance.js +56 -34
- package/dist/schema/scene-radiance.js.map +1 -1
- package/dist/schema/scene.d.ts +14 -13
- package/dist/schema/validation.d.ts +0 -1
- package/dist/schema/validation.js +17 -15
- package/dist/schema/validation.js.map +1 -1
- package/dist/sensor/accumulated-charge.d.ts +45 -64
- package/dist/sensor/accumulated-charge.js +41 -47
- package/dist/sensor/accumulated-charge.js.map +1 -1
- package/dist/sensor/architecture.d.ts +22 -25
- package/dist/sensor/architecture.js +17 -16
- package/dist/sensor/architecture.js.map +1 -1
- package/dist/sensor/camera-saturation-capacity.d.ts +21 -28
- package/dist/sensor/camera-saturation-capacity.js +36 -34
- package/dist/sensor/camera-saturation-capacity.js.map +1 -1
- package/dist/sensor/capture-color-sampling-binding.d.ts +46 -51
- package/dist/sensor/capture-color-sampling-binding.js +39 -41
- package/dist/sensor/capture-color-sampling-binding.js.map +1 -1
- package/dist/sensor/capture-mode-timing.d.ts +8 -9
- package/dist/sensor/capture-mode-timing.js +24 -21
- package/dist/sensor/capture-mode-timing.js.map +1 -1
- package/dist/sensor/capture-mode.d.ts +28 -30
- package/dist/sensor/capture-mode.js +33 -31
- package/dist/sensor/capture-mode.js.map +1 -1
- package/dist/sensor/color-sampling.d.ts +30 -33
- package/dist/sensor/color-sampling.js +32 -37
- package/dist/sensor/color-sampling.js.map +1 -1
- package/dist/sensor/constant-rate-temporal-integration.d.ts +19 -23
- package/dist/sensor/constant-rate-temporal-integration.js +28 -26
- package/dist/sensor/constant-rate-temporal-integration.js.map +1 -1
- package/dist/sensor/dark-current.d.ts +53 -49
- package/dist/sensor/dark-current.js +87 -46
- package/dist/sensor/dark-current.js.map +1 -1
- package/dist/sensor/environment-photo-signal.d.ts +71 -30
- package/dist/sensor/environment-photo-signal.js +127 -46
- package/dist/sensor/environment-photo-signal.js.map +1 -1
- package/dist/sensor/eqe-electron-rate.d.ts +14 -13
- package/dist/sensor/eqe-electron-rate.js +34 -14
- package/dist/sensor/eqe-electron-rate.js.map +1 -1
- package/dist/sensor/eqe-local-exposure.d.ts +24 -24
- package/dist/sensor/eqe-local-exposure.js +15 -11
- package/dist/sensor/eqe-local-exposure.js.map +1 -1
- package/dist/sensor/eqe-temporal-exposure.d.ts +11 -11
- package/dist/sensor/eqe-temporal-exposure.js +15 -10
- package/dist/sensor/eqe-temporal-exposure.js.map +1 -1
- package/dist/sensor/exposure-window.d.ts +48 -34
- package/dist/sensor/exposure-window.js +77 -36
- package/dist/sensor/exposure-window.js.map +1 -1
- package/dist/sensor/fixed-pattern-noise.d.ts +108 -0
- package/dist/sensor/fixed-pattern-noise.js +166 -0
- package/dist/sensor/fixed-pattern-noise.js.map +1 -0
- package/dist/sensor/iso-gain.d.ts +44 -0
- package/dist/sensor/iso-gain.js +119 -0
- package/dist/sensor/iso-gain.js.map +1 -0
- package/dist/sensor/iso-signal-chain.d.ts +25 -48
- package/dist/sensor/iso-signal-chain.js +52 -66
- package/dist/sensor/iso-signal-chain.js.map +1 -1
- package/dist/sensor/local-exposure-binding.d.ts +21 -21
- package/dist/sensor/local-exposure-binding.js +22 -18
- package/dist/sensor/local-exposure-binding.js.map +1 -1
- package/dist/sensor/optical-stack.d.ts +36 -40
- package/dist/sensor/optical-stack.js +34 -37
- package/dist/sensor/optical-stack.js.map +1 -1
- package/dist/sensor/photon-energy.d.ts +22 -28
- package/dist/sensor/photon-energy.js +42 -34
- package/dist/sensor/photon-energy.js.map +1 -1
- package/dist/sensor/physical-charge-capacity.d.ts +20 -30
- package/dist/sensor/physical-charge-capacity.js +35 -36
- package/dist/sensor/physical-charge-capacity.js.map +1 -1
- package/dist/sensor/pixel-pitch.d.ts +2 -3
- package/dist/sensor/pixel-pitch.js +4 -2
- package/dist/sensor/pixel-pitch.js.map +1 -1
- package/dist/sensor/radiometry-readiness.d.ts +13 -15
- package/dist/sensor/radiometry-readiness.js +20 -17
- package/dist/sensor/radiometry-readiness.js.map +1 -1
- package/dist/sensor/raw-readout.d.ts +173 -83
- package/dist/sensor/raw-readout.js +425 -160
- package/dist/sensor/raw-readout.js.map +1 -1
- package/dist/sensor/raw-reconstruction.d.ts +112 -44
- package/dist/sensor/raw-reconstruction.js +200 -47
- package/dist/sensor/raw-reconstruction.js.map +1 -1
- package/dist/sensor/readout-exposure-linkage.d.ts +103 -34
- package/dist/sensor/readout-exposure-linkage.js +125 -27
- package/dist/sensor/readout-exposure-linkage.js.map +1 -1
- package/dist/sensor/readout-timing.d.ts +43 -44
- package/dist/sensor/readout-timing.js +38 -30
- package/dist/sensor/readout-timing.js.map +1 -1
- package/dist/sensor/response-application-compatibility.d.ts +21 -28
- package/dist/sensor/response-application-compatibility.js +35 -34
- package/dist/sensor/response-application-compatibility.js.map +1 -1
- package/dist/sensor/response-operating-range.d.ts +26 -35
- package/dist/sensor/response-operating-range.js +40 -39
- package/dist/sensor/response-operating-range.js.map +1 -1
- package/dist/sensor/responsivity-photocurrent.d.ts +21 -29
- package/dist/sensor/responsivity-photocurrent.js +57 -40
- package/dist/sensor/responsivity-photocurrent.js.map +1 -1
- package/dist/sensor/sampling-aperture.d.ts +21 -25
- package/dist/sensor/sampling-aperture.js +29 -28
- package/dist/sensor/sampling-aperture.js.map +1 -1
- package/dist/sensor/scene-eqe-temporal-exposure.d.ts +14 -12
- package/dist/sensor/scene-eqe-temporal-exposure.js +13 -8
- package/dist/sensor/scene-eqe-temporal-exposure.js.map +1 -1
- package/dist/sensor/sensor-geometry.d.ts +28 -32
- package/dist/sensor/sensor-geometry.js +35 -20
- package/dist/sensor/sensor-geometry.js.map +1 -1
- package/dist/sensor/signal-noise.d.ts +14 -16
- package/dist/sensor/signal-noise.js +15 -13
- package/dist/sensor/signal-noise.js.map +1 -1
- package/dist/sensor/spatial-sample-reduction.d.ts +45 -60
- package/dist/sensor/spatial-sample-reduction.js +39 -48
- package/dist/sensor/spatial-sample-reduction.js.map +1 -1
- package/dist/sensor/spatial-sampling-quadrature.d.ts +46 -54
- package/dist/sensor/spatial-sampling-quadrature.js +31 -36
- package/dist/sensor/spatial-sampling-quadrature.js.map +1 -1
- package/dist/sensor/spatio-spectral-reduction.d.ts +32 -42
- package/dist/sensor/spatio-spectral-reduction.js +24 -29
- package/dist/sensor/spatio-spectral-reduction.js.map +1 -1
- package/dist/sensor/spectral-coverage.d.ts +6 -9
- package/dist/sensor/spectral-coverage.js +9 -6
- package/dist/sensor/spectral-coverage.js.map +1 -1
- package/dist/sensor/spectral-quadrature.d.ts +46 -60
- package/dist/sensor/spectral-quadrature.js +38 -48
- package/dist/sensor/spectral-quadrature.js.map +1 -1
- package/dist/sensor/spectral-response.d.ts +53 -41
- package/dist/sensor/spectral-response.js +80 -47
- package/dist/sensor/spectral-response.js.map +1 -1
- package/dist/sensor/stochastic-seed.d.ts +50 -0
- package/dist/sensor/stochastic-seed.js +123 -0
- package/dist/sensor/stochastic-seed.js.map +1 -0
- package/dist/sensor/temporal-eqe-sum.d.ts +2 -4
- package/dist/sensor/temporal-eqe-sum.js +5 -6
- package/dist/sensor/temporal-eqe-sum.js.map +1 -1
- package/dist/sensor/temporal-photo-signal.d.ts +5 -6
- package/dist/sensor/temporal-photo-signal.js +11 -10
- package/dist/sensor/temporal-photo-signal.js.map +1 -1
- package/dist/sensor/temporal-sample-binding.d.ts +1 -2
- package/dist/sensor/temporal-sample-binding.js +3 -3
- package/dist/sensor/temporal-sample-binding.js.map +1 -1
- package/dist/simulation/poc-simulation.d.ts +16 -16
- package/dist/simulation/poc-simulation.js +20 -15
- package/dist/simulation/poc-simulation.js.map +1 -1
- package/dist/stabilization/camera-shake.d.ts +42 -18
- package/dist/stabilization/camera-shake.js +43 -20
- package/dist/stabilization/camera-shake.js.map +1 -1
- package/dist/stabilization/system.d.ts +44 -41
- package/dist/stabilization/system.js +196 -108
- package/dist/stabilization/system.js.map +1 -1
- package/docs/AGENT_GUIDE.md +183 -0
- package/docs/API_REFERENCE.md +761 -2
- package/docs/QUICK_START.md +495 -0
- package/docs/USAGE.md +1719 -714
- package/docs/api/camera-equivalent-focal-length.md +6 -7
- package/docs/api/camera-field-of-view.md +16 -8
- package/docs/api/camera-lens-projection.md +246 -0
- package/docs/api/camera-projected-object-size.md +6 -5
- package/docs/api/capture-capture-export-metadata.md +10 -10
- package/docs/api/capture-dng-import.md +134 -0
- package/docs/api/capture-environment-raw-producer.md +17 -11
- package/docs/api/capture-export-jpeg.md +16 -0
- package/docs/api/capture-focus-control.md +25 -47
- package/docs/api/capture-linear-encoding.md +21 -12
- package/docs/api/capture-lossless-jpeg.md +49 -0
- package/docs/api/capture-native-environment-optical-stage.md +75 -0
- package/docs/api/capture-native-environment-raw.md +161 -4
- package/docs/api/capture-native-raw-development.md +69 -8
- package/docs/api/capture-native-raw-dng.md +84 -0
- package/docs/api/capture-native-raw.md +21 -10
- package/docs/api/capture-packed-sensor-raw.md +156 -0
- package/docs/api/capture-photographic-export.md +69 -31
- package/docs/api/capture-raw-frame-reconstruction.md +8 -8
- package/docs/api/capture-raw-white-level.md +37 -0
- package/docs/api/capture-release-sequence.md +6 -7
- package/docs/api/capture-sensor-raw-frame.md +6 -6
- package/docs/api/capture-sensor-raw-producer.md +17 -11
- package/docs/api/capture-simulated-capture.md +15 -15
- package/docs/api/color-camera-color-matrix.md +103 -0
- package/docs/api/color-capture-color.md +22 -11
- package/docs/api/color-chromatic-adaptation.md +82 -0
- package/docs/api/color-color-difference.md +134 -0
- package/docs/api/color-color-matching-functions.md +259 -0
- package/docs/api/color-colorimetry-math.md +54 -0
- package/docs/api/color-gamut-mapping.md +72 -0
- package/docs/api/color-illuminants.md +280 -0
- package/docs/api/color-rgb-color-spaces.md +151 -0
- package/docs/api/color-sensor-color-development.md +27 -0
- package/docs/api/color-srgb-icc.md +2 -4
- package/docs/api/color-white-balance.md +95 -19
- package/docs/api/composition-image-formation-plan.md +124 -95
- package/docs/api/composition-plan-consumer.md +7 -7
- package/docs/api/core-calculation-result.md +18 -14
- package/docs/api/core-configuration-error.md +1 -1
- package/docs/api/core-image-formation.md +7 -7
- package/docs/api/core-scientific-assurance.md +12 -10
- package/docs/api/core-spectral-composition.md +14 -14
- package/docs/api/core-spectral.md +6 -6
- package/docs/api/core-validation.md +3 -3
- package/docs/api/core-version.md +7 -6
- package/docs/api/equipment-exposure-capabilities.md +11 -10
- package/docs/api/equipment-generic-tier-assets.md +7 -2
- package/docs/api/equipment-generic-tier-presets.md +6 -6
- package/docs/api/equipment-iso-capabilities.md +18 -18
- package/docs/api/equipment-metering-capabilities.md +6 -6
- package/docs/api/equipment-release-capabilities.md +4 -4
- package/docs/api/exports.json +2213 -8
- package/docs/api/exposure-exposure-duration-control.md +9 -9
- package/docs/api/exposure-exposure-mode-resolver.md +35 -31
- package/docs/api/exposure-exposure.md +10 -10
- package/docs/api/exposure-flash-ttl.md +182 -0
- package/docs/api/exposure-flash.md +183 -28
- package/docs/api/exposure-metering-scene-radiance.md +11 -10
- package/docs/api/exposure-metering-target.md +9 -8
- package/docs/api/exposure-metering-temporal.md +8 -8
- package/docs/api/exposure-metering.md +69 -9
- package/docs/api/exposure-program-line.md +6 -5
- package/docs/api/facade-chart-radiance-batch.md +96 -0
- package/docs/api/facade-chart-scenes.md +240 -0
- package/docs/api/facade-scene-radiance.md +122 -0
- package/docs/api/facade-simulate-capture.md +324 -0
- package/docs/api/gpu-image-kernel-accelerator.md +157 -0
- package/docs/api/gpu-image-kernel-execution.md +152 -0
- package/docs/api/motion-camera-rotation.md +43 -26
- package/docs/api/motion-capture-rotation-inverse-mapping.md +24 -23
- package/docs/api/motion-capture-rotation-temporal-quadrature.md +22 -22
- package/docs/api/motion-capture-rotation-trajectory.md +15 -15
- package/docs/api/motion-capture-translation-parallax-temporal-quadrature.md +15 -14
- package/docs/api/motion-extended-object-projection.md +10 -8
- package/docs/api/motion-projected-motion.md +8 -8
- package/docs/api/motion-temporal-exposure-sum.md +98 -0
- package/docs/api/motion-video-sequence.md +159 -0
- package/docs/api/optics-aperture.md +8 -8
- package/docs/api/optics-atmospheric-haze.md +104 -0
- package/docs/api/optics-brown-conrady-distortion.md +123 -0
- package/docs/api/optics-circle-of-confusion.md +5 -4
- package/docs/api/optics-complex-pupil-psf.md +57 -16
- package/docs/api/optics-depth-of-field.md +12 -6
- package/docs/api/optics-diffraction.md +8 -7
- package/docs/api/optics-focus-breathing.md +104 -16
- package/docs/api/optics-focus-state.md +9 -10
- package/docs/api/optics-front-of-lens-filter.md +72 -14
- package/docs/api/optics-illumination-vignetting.md +10 -10
- package/docs/api/optics-irradiance-convolution.md +95 -0
- package/docs/api/optics-lateral-chromatic-aberration.md +22 -21
- package/docs/api/optics-lens-psf-kernels.md +72 -0
- package/docs/api/optics-lens-psf-profile.md +48 -26
- package/docs/api/optics-longitudinal-chromatic-aberration.md +128 -0
- package/docs/api/optics-polygon-diffraction.md +78 -13
- package/docs/api/optics-profile-contract.md +4 -4
- package/docs/api/optics-psf-foundation.md +12 -12
- package/docs/api/optics-pupil-vignetting.md +132 -0
- package/docs/api/optics-radial-distortion.md +12 -12
- package/docs/api/optics-scene-to-sensor-irradiance.md +25 -14
- package/docs/api/optics-scene-to-sensor-quadrature.md +15 -12
- package/docs/api/optics-sensor-aperture-rays.md +3 -2
- package/docs/api/optics-sensor-environment-query.md +52 -9
- package/docs/api/optics-sensor-psf-quadrature.md +13 -10
- package/docs/api/optics-stray-light.md +12 -11
- package/docs/api/optics-thin-lens.md +4 -3
- package/docs/api/optics-zernike-wavefront.md +229 -0
- package/docs/api/output-capture-corrected-sdr.md +10 -7
- package/docs/api/output-capture-geometry.md +35 -36
- package/docs/api/output-capture-sdr.md +4 -4
- package/docs/api/output-crop.md +7 -4
- package/docs/api/output-geometric-transforms.md +65 -19
- package/docs/api/output-lens-corrections.md +17 -15
- package/docs/api/output-native-capture-sdr.md +26 -13
- package/docs/api/output-png-encoder.md +105 -0
- package/docs/api/output-print-contrast-reference.md +8 -8
- package/docs/api/output-print-detail.md +14 -14
- package/docs/api/output-print-jpeg.md +22 -5
- package/docs/api/output-print-plan.md +18 -18
- package/docs/api/output-print-region-statistics.md +14 -14
- package/docs/api/output-sdr-rendering.md +52 -10
- package/docs/api/output-subject-framing-crop.md +5 -6
- package/docs/api/processing-demosaic.md +120 -0
- package/docs/api/processing-denoise.md +148 -0
- package/docs/api/processing-focus-stacking.md +41 -0
- package/docs/api/processing-hdr.md +113 -0
- package/docs/api/processing-panorama.md +82 -0
- package/docs/api/processing-processing-task.md +107 -0
- package/docs/api/processing-tone-mapping.md +142 -0
- package/docs/api/schema-camera.md +6 -4
- package/docs/api/schema-illumination-spectral.md +10 -11
- package/docs/api/schema-illumination-temporal.md +13 -15
- package/docs/api/schema-illumination.md +4 -4
- package/docs/api/schema-scene-radiance-temporal.md +10 -11
- package/docs/api/schema-scene-radiance.md +18 -17
- package/docs/api/schema-scene.md +9 -9
- package/docs/api/schema-validation.md +1 -1
- package/docs/api/sensor-accumulated-charge.md +12 -20
- package/docs/api/sensor-architecture.md +15 -16
- package/docs/api/sensor-camera-saturation-capacity.md +9 -11
- package/docs/api/sensor-capture-color-sampling-binding.md +47 -51
- package/docs/api/sensor-capture-mode-timing.md +9 -9
- package/docs/api/sensor-capture-mode.md +29 -30
- package/docs/api/sensor-color-sampling.md +31 -33
- package/docs/api/sensor-constant-rate-temporal-integration.md +10 -11
- package/docs/api/sensor-dark-current.md +30 -14
- package/docs/api/sensor-environment-photo-signal.md +13 -6
- package/docs/api/sensor-eqe-electron-rate.md +9 -8
- package/docs/api/sensor-eqe-local-exposure.md +18 -17
- package/docs/api/sensor-eqe-temporal-exposure.md +12 -11
- package/docs/api/sensor-exposure-window.md +24 -24
- package/docs/api/sensor-fixed-pattern-noise.md +104 -0
- package/docs/api/sensor-iso-gain.md +63 -0
- package/docs/api/sensor-iso-signal-chain.md +13 -22
- package/docs/api/sensor-local-exposure-binding.md +12 -13
- package/docs/api/sensor-optical-stack.md +37 -40
- package/docs/api/sensor-photon-energy.md +10 -11
- package/docs/api/sensor-physical-charge-capacity.md +8 -13
- package/docs/api/sensor-pixel-pitch.md +1 -1
- package/docs/api/sensor-radiometry-readiness.md +9 -9
- package/docs/api/sensor-raw-readout.md +118 -52
- package/docs/api/sensor-raw-reconstruction.md +97 -20
- package/docs/api/sensor-readout-exposure-linkage.md +120 -24
- package/docs/api/sensor-readout-timing.md +33 -35
- package/docs/api/sensor-response-application-compatibility.md +9 -11
- package/docs/api/sensor-response-operating-range.md +14 -18
- package/docs/api/sensor-responsivity-photocurrent.md +9 -12
- package/docs/api/sensor-sampling-aperture.md +22 -25
- package/docs/api/sensor-scene-eqe-temporal-exposure.md +9 -8
- package/docs/api/sensor-sensor-geometry.md +23 -24
- package/docs/api/sensor-signal-noise.md +10 -10
- package/docs/api/sensor-spatial-sample-reduction.md +27 -30
- package/docs/api/sensor-spatial-sampling-quadrature.md +37 -39
- package/docs/api/sensor-spatio-spectral-reduction.md +27 -29
- package/docs/api/sensor-spectral-coverage.md +5 -5
- package/docs/api/sensor-spectral-quadrature.md +33 -35
- package/docs/api/sensor-spectral-response.md +28 -16
- package/docs/api/sensor-temporal-photo-signal.md +6 -6
- package/docs/api/simulation-poc-simulation.md +17 -16
- package/docs/api/stabilization-camera-shake.md +29 -10
- package/docs/api/stabilization-system.md +25 -21
- package/docs/examples/production-request.json +3821 -2157
- package/docs/examples/simulate-capture.mjs +14 -0
- package/llms.txt +35 -0
- package/package.json +32 -12
- package/AGENTS.md +0 -674
- package/CONTRIBUTING.md +0 -76
- package/DCO +0 -33
- package/dist/camera/equivalent-focal-length.d.ts.map +0 -1
- package/dist/camera/field-of-view.d.ts.map +0 -1
- package/dist/camera/projected-object-size.d.ts.map +0 -1
- package/dist/capture/capture-export-metadata.d.ts.map +0 -1
- package/dist/capture/environment-raw-producer.d.ts.map +0 -1
- package/dist/capture/export-jpeg.d.ts.map +0 -1
- package/dist/capture/export-tiff.d.ts.map +0 -1
- package/dist/capture/focus-control.d.ts.map +0 -1
- package/dist/capture/linear-encoding.d.ts.map +0 -1
- package/dist/capture/native-environment-raw.d.ts.map +0 -1
- package/dist/capture/native-raw-development.d.ts.map +0 -1
- package/dist/capture/native-raw.d.ts.map +0 -1
- package/dist/capture/photographic-export.d.ts.map +0 -1
- package/dist/capture/raw-frame-limits.d.ts.map +0 -1
- package/dist/capture/raw-frame-reconstruction.d.ts.map +0 -1
- package/dist/capture/release-sequence.d.ts.map +0 -1
- package/dist/capture/sensor-raw-frame.d.ts.map +0 -1
- package/dist/capture/sensor-raw-producer.d.ts.map +0 -1
- package/dist/capture/simulated-capture.d.ts.map +0 -1
- package/dist/color/capture-color.d.ts.map +0 -1
- package/dist/color/sensor-color-development.d.ts.map +0 -1
- package/dist/color/srgb-icc.d.ts.map +0 -1
- package/dist/color/srgb-transfer.d.ts.map +0 -1
- package/dist/color/white-balance.d.ts.map +0 -1
- package/dist/composition/image-formation-plan.d.ts.map +0 -1
- package/dist/composition/plan-consumer.d.ts.map +0 -1
- package/dist/core/calculation-result.d.ts.map +0 -1
- package/dist/core/canonical-json.d.ts.map +0 -1
- package/dist/core/configuration-error.d.ts.map +0 -1
- package/dist/core/evidence-provenance.d.ts.map +0 -1
- package/dist/core/image-formation.d.ts.map +0 -1
- package/dist/core/owned-data.d.ts.map +0 -1
- package/dist/core/record-validation.d.ts.map +0 -1
- package/dist/core/scientific-assurance.d.ts.map +0 -1
- package/dist/core/spectral-composition.d.ts.map +0 -1
- package/dist/core/spectral.d.ts.map +0 -1
- package/dist/core/validation.d.ts.map +0 -1
- package/dist/core/version.d.ts.map +0 -1
- package/dist/equipment/exposure-capabilities.d.ts.map +0 -1
- package/dist/equipment/generic-tier-assets.d.ts.map +0 -1
- package/dist/equipment/generic-tier-presets.d.ts.map +0 -1
- package/dist/equipment/iso-capabilities.d.ts.map +0 -1
- package/dist/equipment/metering-capabilities.d.ts.map +0 -1
- package/dist/equipment/release-capabilities.d.ts.map +0 -1
- package/dist/exposure/exposure-duration-control.d.ts.map +0 -1
- package/dist/exposure/exposure-mode-resolver.d.ts.map +0 -1
- package/dist/exposure/exposure.d.ts.map +0 -1
- package/dist/exposure/flash.d.ts.map +0 -1
- package/dist/exposure/metering-scene-radiance.d.ts.map +0 -1
- package/dist/exposure/metering-target.d.ts.map +0 -1
- package/dist/exposure/metering-temporal.d.ts.map +0 -1
- package/dist/exposure/metering.d.ts.map +0 -1
- package/dist/exposure/program-line.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/motion/camera-rotation.d.ts.map +0 -1
- package/dist/motion/capture-rotation-inverse-mapping.d.ts.map +0 -1
- package/dist/motion/capture-rotation-temporal-quadrature.d.ts.map +0 -1
- package/dist/motion/capture-rotation-trajectory.d.ts.map +0 -1
- package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts.map +0 -1
- package/dist/motion/extended-object-projection.d.ts.map +0 -1
- package/dist/motion/projected-motion.d.ts.map +0 -1
- package/dist/optics/aperture.d.ts.map +0 -1
- package/dist/optics/circle-of-confusion.d.ts.map +0 -1
- package/dist/optics/complex-pupil-psf.d.ts.map +0 -1
- package/dist/optics/depth-of-field.d.ts.map +0 -1
- package/dist/optics/diffraction.d.ts.map +0 -1
- package/dist/optics/focus-breathing.d.ts.map +0 -1
- package/dist/optics/focus-state.d.ts.map +0 -1
- package/dist/optics/front-of-lens-filter.d.ts.map +0 -1
- package/dist/optics/illumination-vignetting.d.ts.map +0 -1
- package/dist/optics/lateral-chromatic-aberration.d.ts.map +0 -1
- package/dist/optics/lens-psf-profile.d.ts.map +0 -1
- package/dist/optics/polygon-diffraction.d.ts.map +0 -1
- package/dist/optics/profile-contract.d.ts.map +0 -1
- package/dist/optics/psf-foundation.d.ts.map +0 -1
- package/dist/optics/radial-distortion.d.ts.map +0 -1
- package/dist/optics/scene-to-sensor-irradiance.d.ts.map +0 -1
- package/dist/optics/scene-to-sensor-quadrature.d.ts.map +0 -1
- package/dist/optics/sensor-aperture-rays.d.ts.map +0 -1
- package/dist/optics/sensor-environment-query.d.ts.map +0 -1
- package/dist/optics/sensor-psf-quadrature.d.ts.map +0 -1
- package/dist/optics/sensor-psf-support.d.ts.map +0 -1
- package/dist/optics/stray-light.d.ts.map +0 -1
- package/dist/optics/thin-lens.d.ts.map +0 -1
- package/dist/output/capture-corrected-sdr.d.ts.map +0 -1
- package/dist/output/capture-geometry.d.ts.map +0 -1
- package/dist/output/capture-sdr.d.ts.map +0 -1
- package/dist/output/crop.d.ts.map +0 -1
- package/dist/output/geometric-transforms.d.ts.map +0 -1
- package/dist/output/lens-corrections.d.ts.map +0 -1
- package/dist/output/native-capture-sdr.d.ts.map +0 -1
- package/dist/output/print-contrast-reference.d.ts.map +0 -1
- package/dist/output/print-detail.d.ts.map +0 -1
- package/dist/output/print-jpeg.d.ts.map +0 -1
- package/dist/output/print-plan.d.ts.map +0 -1
- package/dist/output/print-region-statistics.d.ts.map +0 -1
- package/dist/output/sdr-rendering.d.ts.map +0 -1
- package/dist/output/subject-framing-crop.d.ts.map +0 -1
- package/dist/schema/camera.d.ts.map +0 -1
- package/dist/schema/camera.js +0 -3
- package/dist/schema/camera.js.map +0 -1
- package/dist/schema/illumination-spectral.d.ts.map +0 -1
- package/dist/schema/illumination-temporal.d.ts.map +0 -1
- package/dist/schema/illumination.d.ts.map +0 -1
- package/dist/schema/scene-radiance-temporal.d.ts.map +0 -1
- package/dist/schema/scene-radiance.d.ts.map +0 -1
- package/dist/schema/scene.d.ts.map +0 -1
- package/dist/schema/scene.js +0 -3
- package/dist/schema/scene.js.map +0 -1
- package/dist/schema/validation.d.ts.map +0 -1
- package/dist/sensor/accumulated-charge.d.ts.map +0 -1
- package/dist/sensor/architecture.d.ts.map +0 -1
- package/dist/sensor/camera-saturation-capacity.d.ts.map +0 -1
- package/dist/sensor/capture-color-sampling-binding.d.ts.map +0 -1
- package/dist/sensor/capture-mode-timing.d.ts.map +0 -1
- package/dist/sensor/capture-mode.d.ts.map +0 -1
- package/dist/sensor/color-sampling.d.ts.map +0 -1
- package/dist/sensor/constant-rate-temporal-integration.d.ts.map +0 -1
- package/dist/sensor/dark-current.d.ts.map +0 -1
- package/dist/sensor/environment-photo-signal.d.ts.map +0 -1
- package/dist/sensor/eqe-electron-rate.d.ts.map +0 -1
- package/dist/sensor/eqe-local-exposure.d.ts.map +0 -1
- package/dist/sensor/eqe-temporal-exposure.d.ts.map +0 -1
- package/dist/sensor/exposure-window.d.ts.map +0 -1
- package/dist/sensor/iso-signal-chain.d.ts.map +0 -1
- package/dist/sensor/local-exposure-binding.d.ts.map +0 -1
- package/dist/sensor/optical-stack.d.ts.map +0 -1
- package/dist/sensor/photon-energy.d.ts.map +0 -1
- package/dist/sensor/physical-charge-capacity.d.ts.map +0 -1
- package/dist/sensor/pixel-pitch.d.ts.map +0 -1
- package/dist/sensor/radiometry-readiness.d.ts.map +0 -1
- package/dist/sensor/raw-readout.d.ts.map +0 -1
- package/dist/sensor/raw-reconstruction.d.ts.map +0 -1
- package/dist/sensor/readout-exposure-linkage.d.ts.map +0 -1
- package/dist/sensor/readout-timing.d.ts.map +0 -1
- package/dist/sensor/response-application-compatibility.d.ts.map +0 -1
- package/dist/sensor/response-operating-range.d.ts.map +0 -1
- package/dist/sensor/responsivity-photocurrent.d.ts.map +0 -1
- package/dist/sensor/sampling-aperture.d.ts.map +0 -1
- package/dist/sensor/scene-eqe-temporal-exposure.d.ts.map +0 -1
- package/dist/sensor/sensor-geometry.d.ts.map +0 -1
- package/dist/sensor/signal-noise.d.ts.map +0 -1
- package/dist/sensor/spatial-sample-reduction.d.ts.map +0 -1
- package/dist/sensor/spatial-sampling-quadrature.d.ts.map +0 -1
- package/dist/sensor/spatio-spectral-reduction.d.ts.map +0 -1
- package/dist/sensor/spectral-coverage.d.ts.map +0 -1
- package/dist/sensor/spectral-quadrature.d.ts.map +0 -1
- package/dist/sensor/spectral-response.d.ts.map +0 -1
- package/dist/sensor/temporal-eqe-sum.d.ts.map +0 -1
- package/dist/sensor/temporal-photo-signal.d.ts.map +0 -1
- package/dist/sensor/temporal-sample-binding.d.ts.map +0 -1
- package/dist/simulation/poc-simulation.d.ts.map +0 -1
- package/dist/stabilization/camera-shake.d.ts.map +0 -1
- package/dist/stabilization/system.d.ts.map +0 -1
- package/docs/API_STYLE.md +0 -235
- package/docs/ARCHITECTURE.md +0 -356
- package/docs/AUTOMATION_COORDINATION.md +0 -190
- package/docs/CAPTURE_COLOR.md +0 -25
- package/docs/CAPTURE_CORRECTED_SDR.md +0 -25
- package/docs/CAPTURE_EXPORT_METADATA.md +0 -27
- package/docs/CAPTURE_SDR.md +0 -22
- package/docs/DENSE_PUPIL_QUALIFICATION.md +0 -121
- package/docs/DEVELOPERS.md +0 -68
- package/docs/DIGITAL_OPTICS_FOUNDATION.md +0 -69
- package/docs/ENVIRONMENT_RAW_CAPTURE.md +0 -208
- package/docs/EXPORT_ACCEPTANCE.md +0 -133
- package/docs/GENERIC_TIER_PRESETS.md +0 -269
- package/docs/GEOMETRIC_PREPARATION_REUSE.md +0 -141
- package/docs/IMAGE_FORMATION.md +0 -247
- package/docs/LINEAR_CAPTURE_ENCODING.md +0 -25
- package/docs/MOTION_AND_SIGNAL.md +0 -250
- package/docs/NATIVE_CAPTURE_SDR.md +0 -70
- package/docs/NATIVE_CAPTURE_SDR_REVIEW.md +0 -37
- package/docs/NATIVE_CAPTURE_SDR_VALIDATION.md +0 -34
- package/docs/NATIVE_RAW_AND_PRINT.md +0 -54
- package/docs/NUMERICAL_CORRECTNESS.md +0 -191
- package/docs/PERFORMANCE_BASELINE.md +0 -56
- package/docs/PHOTOGRAPHIC_EXPORT.md +0 -206
- package/docs/PHYSICS_FOUNDATION.md +0 -426
- package/docs/POC_API.md +0 -278
- package/docs/POC_PROJECTION_PLATFORM_REFERENCE.md +0 -12
- package/docs/POC_PROJECTION_REUSE.md +0 -124
- package/docs/PRINT_195_196_REVIEW.md +0 -93
- package/docs/PRINT_196_ENGINE_ACCEPTANCE.md +0 -45
- package/docs/PRINT_CONTRAST_REFERENCE.md +0 -43
- package/docs/PRINT_DETAIL_ASSESSMENT.md +0 -81
- package/docs/PRINT_DETAIL_JPEG_EVIDENCE.md +0 -36
- package/docs/PRINT_DETAIL_SDR_EVIDENCE.md +0 -29
- package/docs/PRINT_DETAIL_VALIDATION.md +0 -36
- package/docs/PRINT_PLANNING.md +0 -75
- package/docs/PRINT_REGION_STATISTICS.md +0 -38
- package/docs/PROCESSED_OUTPUT.md +0 -157
- package/docs/PRODUCTION_COMPOSITION.md +0 -373
- package/docs/PRODUCTION_ENVIRONMENT_CAPTURE.md +0 -131
- package/docs/PROVENANCE.md +0 -115
- package/docs/PSF_FOUNDATION.md +0 -165
- package/docs/RAW_FRAME_ENVELOPE.md +0 -56
- package/docs/RAW_FRAME_RECONSTRUCTION.md +0 -30
- package/docs/RELEASE_1_0.md +0 -66
- package/docs/RELEASE_1_0_1.md +0 -61
- package/docs/RELEASE_1_1_0.md +0 -25
- package/docs/RELEASE_1_2_0.md +0 -51
- package/docs/RELEASE_1_3_0.md +0 -15
- package/docs/RELEASE_1_4_0.md +0 -23
- package/docs/RELEASE_1_5_0.md +0 -105
- package/docs/RELEASE_AUDIT.md +0 -39
- package/docs/SCENE_RADIANCE_AND_ILLUMINATION.md +0 -298
- package/docs/SCENE_SENSOR_QUADRATURE.md +0 -98
- package/docs/SCENE_SENSOR_TEMPORAL_EXPOSURE.md +0 -86
- package/docs/SCIENTIFIC_ASSURANCE.md +0 -280
- package/docs/SCIENTIFIC_CONFORMANCE.md +0 -83
- package/docs/SDR_RENDERING.md +0 -33
- package/docs/SENSOR_APERTURE_RAYS.md +0 -23
- package/docs/SENSOR_ENVIRONMENT_QUERY.md +0 -86
- package/docs/SENSOR_EQE_LOCAL_EXPOSURE.md +0 -104
- package/docs/SENSOR_EQE_TEMPORAL_EXPOSURE.md +0 -74
- package/docs/SENSOR_PSF_QUADRATURE.md +0 -90
- package/docs/SENSOR_RAW_FRAME.md +0 -20
- package/docs/SENSOR_RAW_PRODUCER.md +0 -77
- package/docs/SEPARABLE_EMISSION_DRAFT.md +0 -103
- package/docs/SIGNAL_CONFORMANCE.md +0 -23
- package/docs/SIMULATED_CAPTURE.md +0 -65
- package/docs/STABILIZATION.md +0 -234
- package/docs/TEMPORAL_PHOTO_RAW.md +0 -83
- package/docs/TIER_ACCEPTANCE.md +0 -129
- package/docs/TIER_PRESET_ACCEPTANCE.md +0 -59
- package/docs/V1_COMPOSITION_MAP.md +0 -123
- package/docs/V1_HELPER_REUSE_REVIEW.md +0 -60
- package/docs/V1_PERFORMANCE_DISPOSITION.md +0 -88
- package/docs/benchmarks/2026-10-01-main-baseline.json +0 -694
- package/docs/benchmarks/2026-10-02-geometric-preparation-reuse.json +0 -2190
- package/docs/benchmarks/2026-10-02-poc-projection-reuse.json +0 -2423
- package/docs/benchmarks/2026-10-02-v1-environment-gc-profile.json +0 -210
- package/docs/benchmarks/2026-10-02-v1-environment-ordinary.json +0 -187
- package/docs/benchmarks/2026-10-02-v1-geometry-gc-profile.json +0 -756
- package/docs/benchmarks/2026-10-02-v1-geometry-ordinary-1.json +0 -711
- package/docs/benchmarks/2026-10-02-v1-geometry-ordinary-2.json +0 -711
- package/docs/benchmarks/2026-10-02-v1-poc-gc-profile.json +0 -885
- package/docs/benchmarks/2026-10-02-v1-poc-ordinary-1.json +0 -868
- package/docs/benchmarks/2026-10-02-v1-poc-ordinary-2.json +0 -868
- package/docs/licenses/castleCSF-MIT.txt +0 -21
- package/docs/validation/dense-pupil-2026-10-06/pilot.jsonl +0 -6
- package/docs/validation/documentation-fragments.json +0 -212
- package/docs/validation/native-capture-sdr-darwin-arm64-node26-review.jsonl +0 -4
- package/docs/validation/native-capture-sdr-darwin-arm64.jsonl +0 -3
- package/docs/validation/native-raw-print-2026-10-04/fractional-asymmetric-crop.jpg +0 -0
- package/docs/validation/native-raw-print-2026-10-04/gray-ramp.jpg +0 -0
- package/docs/validation/native-raw-print-2026-10-04/linear-area-checker.jpg +0 -0
- package/docs/validation/native-raw-print-2026-10-04/manifest.json +0 -218
- package/docs/validation/native-raw-print-2026-10-04/megapixel-native.jpg +0 -0
- package/docs/validation/native-raw-print-2026-10-04/native-color.jpg +0 -0
- package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel-validation.json +0 -9
- package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel.json +0 -29
- package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel.json.codes.u16le.gz +0 -0
- package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel.json.jpg +0 -0
- package/docs/validation/native-raw-print-2026-10-04/photivra-native-full-megapixel.json.rgb.gz +0 -0
- package/docs/validation/native-raw-print-2026-10-04/srgb.icc +0 -0
- package/docs/validation/native-raw-print-2026-10-04/validation.json +0 -41
- package/docs/validation/print-196-candidate-capabilities.json +0 -28
- package/docs/validation/print-196-candidate-evidence.json +0 -356
- package/docs/validation/print-196-engine-acceptance.json +0 -81
- package/docs/validation/print-detail-capabilities.json +0 -103
- package/docs/validation/print-detail-jpeg-evidence.json +0 -116
- package/docs/validation/print-detail-sdr-evidence.json +0 -81
- package/docs/validation/release-audit-inventory.json +0 -1306
- package/docs/validation/tier-acceptance-report-v1.json +0 -2538
package/docs/USAGE.md
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
# Usage Guide
|
|
2
2
|
|
|
3
|
-
Release context: **package 1.2.0 candidate / root API 1.2.0
|
|
3
|
+
Release context (dated historical note): this guide was written for the **package 1.2.0 candidate / root API 1.2.0** and has not been re-reviewed for later releases; see the [changelog](../CHANGELOG.md) for the current release. Subsystem and model versions, and dated introduction or measurement records below, are independent historical identities. They are not distribution versions. See [developer navigation](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md) and the [release contract](https://github.com/Photivra/photivra/blob/main/docs/RELEASE_1_2_0.md).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
[
|
|
7
|
-
remain declared; the returned frame can feed the same-RAW photographic exporter.
|
|
5
|
+
New to the engine? Start with [`simulateCapture()`](#simulate-a-capture-with-the-facade). It builds
|
|
6
|
+
and runs a complete capture from a few options. The [Quick Start](QUICK_START.md) explains it in full.
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
[Capture-bound corrected SDR](CAPTURE_CORRECTED_SDR.md). It returns a derived output
|
|
11
|
-
view without changing physical capture geometry or activating production stages.
|
|
8
|
+
This guide shows how to call the capabilities exported by the root `@photivra/engine` package.
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
[Photographic export](PHOTOGRAPHIC_EXPORT.md). It requires an explicit approximate
|
|
15
|
-
sensor-color profile; bounded external acceptance and its evidence limits are recorded in [Export acceptance](EXPORT_ACCEPTANCE.md).
|
|
10
|
+
Related guides for later pipeline stages:
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
- **Charge, noise and ADC to native RAW:** see [Sensor RAW producer](https://github.com/Photivra/photivra/blob/main/docs/SENSOR_RAW_PRODUCER.md).
|
|
13
|
+
It generates RAW within stated limits for one capture. The upstream EQE and dark-exposure results stay
|
|
14
|
+
as declared inputs. The returned frame can feed the photographic exporter, which writes DNG and JPEG from the same RAW.
|
|
15
|
+
- **Committed capture → correction → SDR output:** see [Capture-bound corrected SDR](https://github.com/Photivra/photivra/blob/main/docs/CAPTURE_CORRECTED_SDR.md).
|
|
16
|
+
It returns a derived output view without changing the physical capture geometry or turning on production stages.
|
|
17
|
+
- **Paired DNG/JPEG byte export from the same RAW:** see [Photographic export](https://github.com/Photivra/photivra/blob/main/docs/PHOTOGRAPHIC_EXPORT.md).
|
|
18
|
+
It needs an explicit, approximate sensor-colour profile. Bounded external acceptance, and the limits of that evidence, are recorded in [Export acceptance](https://github.com/Photivra/photivra/blob/main/docs/EXPORT_ACCEPTANCE.md).
|
|
19
|
+
|
|
20
|
+
Common abbreviations: FOV (field of view), PSF (point-spread function — how a lens spreads one point of light), CFA (colour filter array, such as a Bayer mosaic), QE/EQE (quantum efficiency / external quantum efficiency), ADC (analog-to-digital converter), AE/AF/WB (auto-exposure / autofocus / white balance), SDR (standard dynamic range).
|
|
18
21
|
|
|
19
|
-
The package is ESM
|
|
22
|
+
The package is ESM. CommonJS code on Node 22.12 or later can also `require()` it. Primitive scientific calculations usually return a `CalculationResult<T>`:
|
|
20
23
|
|
|
21
24
|
```ts
|
|
22
25
|
const result = calculateSomething(input);
|
|
@@ -26,11 +29,13 @@ console.log(result.provenance);
|
|
|
26
29
|
console.log(result.quality);
|
|
27
30
|
```
|
|
28
31
|
|
|
29
|
-
`quality` is optional
|
|
32
|
+
`quality` is optional. It is left out when the engine has no defensible, quantified uncertainty or accuracy to report.
|
|
33
|
+
|
|
34
|
+
From 2.0.0 results are read-only: the envelope, `provenance` and `quality` are frozen, and so is the top level of `value`. Copy a value (`{ ...result.value }`) before changing it. Every calculator that takes an input object rejects unknown fields with `InvalidConfigurationError`. A wrong shape throws `InvalidConfigurationError` and a bad number throws `InvalidScientificInputError`; see [Error classes](https://github.com/Photivra/photivra/blob/main/docs/API_STYLE.md#error-classes) and [Read-only results](https://github.com/Photivra/photivra/blob/main/docs/API_STYLE.md#read-only-results).
|
|
30
35
|
|
|
31
36
|
## Standalone digital optics
|
|
32
37
|
|
|
33
|
-
See [Digital optics foundations](DIGITAL_OPTICS_FOUNDATION.md) for
|
|
38
|
+
See [Digital optics foundations](https://github.com/Photivra/photivra/blob/main/docs/DIGITAL_OPTICS_FOUNDATION.md) for stray-light and correction profiles tied to an exact lens state, and for their scientific limits. You can prepare a compatible geometric map separately from its sampling filter:
|
|
34
39
|
|
|
35
40
|
```ts
|
|
36
41
|
import {
|
|
@@ -67,7 +72,12 @@ const warped = calculateGeometricResampling({
|
|
|
67
72
|
// plan.jointCrop and validSourceMask preserve the actual support.
|
|
68
73
|
```
|
|
69
74
|
|
|
70
|
-
This warps captured
|
|
75
|
+
This warps samples that were already captured. It does not recalculate the physical lens PSF or regenerate noise.
|
|
76
|
+
|
|
77
|
+
- Compression (shrinking) needs source data that was explicitly prefiltered.
|
|
78
|
+
- Incompatible domains need separate stages.
|
|
79
|
+
- Stray light is different: it adds to the incident spectral irradiance before the sensor.
|
|
80
|
+
- A correction intended for RAW-like data can stay as metadata only.
|
|
71
81
|
|
|
72
82
|
## Image-formation ownership
|
|
73
83
|
|
|
@@ -87,11 +97,12 @@ console.log(
|
|
|
87
97
|
);
|
|
88
98
|
```
|
|
89
99
|
|
|
90
|
-
The contract is descriptive metadata. It does not
|
|
100
|
+
The contract is descriptive metadata. It does not mean every reserved stage is implemented, and it does not change the composed POC (proof-of-concept) request/response contract.
|
|
91
101
|
|
|
92
|
-
|
|
102
|
+
- `requiredUpstreamStages` lists hard scientific dependencies.
|
|
103
|
+
- `coupledStages` lists shared state and interactions. Do not treat coupled stages as independent renderer filters.
|
|
93
104
|
|
|
94
|
-
See [Image-Formation Contract](IMAGE_FORMATION.md) for coordinate, temporal, renderer, and reserved sensor-stage semantics.
|
|
105
|
+
See [Image-Formation Contract](https://github.com/Photivra/photivra/blob/main/docs/IMAGE_FORMATION.md) for coordinate, temporal, renderer, and reserved sensor-stage semantics.
|
|
95
106
|
|
|
96
107
|
## Ideal regular-polygon diffraction
|
|
97
108
|
|
|
@@ -110,11 +121,18 @@ const polygon = calculateIdealPolygonDiffractionPsf({
|
|
|
110
121
|
console.log(polygon.value.samples[0]?.intensityDensityPerSquareMicrometer);
|
|
111
122
|
```
|
|
112
123
|
|
|
113
|
-
This is a uniform
|
|
124
|
+
This is a scalar approximation: uniform amplitude, zero phase, on-axis and in focus.
|
|
125
|
+
|
|
126
|
+
- The equal-area diameter is not the polygon's circumdiameter.
|
|
127
|
+
- Image points are displacements within the PSF (+X right / +Y up), not positions in the source field.
|
|
128
|
+
- The density integrates to one over the **infinite** plane. The samples you request do not sum to one.
|
|
129
|
+
- Integrating over a finite image area (quadrature) needs explicit area measures, support and convergence checks.
|
|
130
|
+
- Nothing is inferred: no air/vacuum conversion, clipping, aberration, throughput or sensor response.
|
|
131
|
+
- The circular Airy diagnostic and the real-lens kernels (which already include diffraction) are separate. Do not apply diffraction twice.
|
|
114
132
|
|
|
115
133
|
## Explicit finite and infinity focus state
|
|
116
134
|
|
|
117
|
-
Use `FocusPlane` when
|
|
135
|
+
Use `FocusPlane` when you must tell a real finite focus plane apart from optical infinity, without using JavaScript `Infinity` or an arbitrary very large distance.
|
|
118
136
|
|
|
119
137
|
```ts
|
|
120
138
|
import {
|
|
@@ -140,23 +158,23 @@ console.log(projection.value.imageDistanceMm); // 50
|
|
|
140
158
|
console.log(projection.value.infinityProjectionScale); // 1
|
|
141
159
|
```
|
|
142
160
|
|
|
143
|
-
For finite focus, `distanceM` is the **
|
|
144
|
-
|
|
145
|
-
The finite branch delegates to the existing Gaussian thin-lens calculation, so existing finite-focus numeric behavior and provenance are preserved. The infinity branch resolves exactly to nominal focal length with zero limiting magnification.
|
|
161
|
+
For finite focus, `distanceM` is the **distance of the object plane along the camera's optical axis, measured from the ideal lens principal plane**. It is not the straight-line ray length. A renderer or raycaster must not pass the raw Euclidean ray length for an off-axis hit. In the current paraxial model, points on the same plane facing the camera (fronto-parallel) resolve to the same finite focus distance.
|
|
146
162
|
|
|
147
|
-
|
|
163
|
+
- The finite branch uses the existing Gaussian thin-lens calculation, so existing finite-focus numbers and provenance are unchanged.
|
|
164
|
+
- The infinity branch resolves exactly to the nominal focal length, with zero limiting magnification.
|
|
165
|
+
- Missing or unknown focus is never treated as infinity. Supply it explicitly as `{ kind: "finite", distanceM }` or `{ kind: "infinity" }`.
|
|
148
166
|
|
|
149
|
-
Existing APIs that
|
|
167
|
+
Existing APIs that take `focusDistanceM` are still supported. This tagged contract is additive. It is meant for newer composition and control paths that need an explicit infinity state.
|
|
150
168
|
|
|
151
169
|
## Focus control: MF, single AF, continuous AF, and lock
|
|
152
170
|
|
|
153
|
-
The
|
|
171
|
+
The focus-control layer (#104) sits on top of the optical `FocusPlane` contract described above (#103). (MF = manual focus, AF = autofocus.)
|
|
154
172
|
|
|
155
|
-
It
|
|
173
|
+
It keeps three things separate:
|
|
156
174
|
|
|
157
|
-
1. **target observation** — which scene surface
|
|
158
|
-
2. **focus-control policy** — manual, single AF, continuous AF, or locked
|
|
159
|
-
3. **resolved optical focus** — the finite
|
|
175
|
+
1. **target observation** — which scene surface or environment target was measured, and its distance along the camera axis;
|
|
176
|
+
2. **focus-control policy** — manual, single AF, continuous AF, or locked;
|
|
177
|
+
3. **resolved optical focus** — the finite or infinity `FocusPlane` passed on to the optics.
|
|
160
178
|
|
|
161
179
|
Use a generic profile:
|
|
162
180
|
|
|
@@ -223,13 +241,13 @@ const acquired =
|
|
|
223
241
|
});
|
|
224
242
|
```
|
|
225
243
|
|
|
226
|
-
Target observations contain no screen
|
|
244
|
+
Target observations contain no screen or canvas coordinates. `focusAreaId` is an opaque selection ID that does not depend on the application or renderer. A finite target distance is the `FocusPlane` **distance along the camera axis (the conjugate distance)**, not the Euclidean ray length.
|
|
227
245
|
|
|
228
246
|
### Manual focus
|
|
229
247
|
|
|
230
248
|
`setManualFocusState()` changes an explicit finite/infinity focus state only when the active mode is `manual`.
|
|
231
249
|
|
|
232
|
-
No universal focus-ring scale or lens-specific stepping curve is invented. The caller supplies the next explicit
|
|
250
|
+
No universal focus-ring scale or lens-specific stepping curve is invented. The caller supplies the next explicit `FocusPlane` state. Future equipment profiles may map real focus-ring or actuator behaviour onto that same contract.
|
|
233
251
|
|
|
234
252
|
MF does not automatically consume autofocus target observations.
|
|
235
253
|
|
|
@@ -237,7 +255,7 @@ MF does not automatically consume autofocus target observations.
|
|
|
237
255
|
|
|
238
256
|
A `single-af` `acquire` event resolves the target once and then holds that focus plane.
|
|
239
257
|
|
|
240
|
-
Later `update` observations do not follow subject motion. If the subject moves after acquisition,
|
|
258
|
+
Later `update` observations do not follow subject motion. If the subject moves after acquisition, its depth changes while the focus plane stays fixed, so the defocus calculated downstream changes.
|
|
241
259
|
|
|
242
260
|
An unavailable target during acquisition produces explicit `target-lost` state without moving focus.
|
|
243
261
|
|
|
@@ -245,13 +263,13 @@ An unavailable target during acquisition produces explicit `target-lost` state w
|
|
|
245
263
|
|
|
246
264
|
`continuous-af` requires an explicit initial `acquire` event.
|
|
247
265
|
|
|
248
|
-
|
|
266
|
+
After that, `update` events may move focus only for the same active `targetId`. A different target cannot silently replace the subject.
|
|
249
267
|
|
|
250
268
|
The first actuator is:
|
|
251
269
|
|
|
252
270
|
`ideal-instantaneous`
|
|
253
271
|
|
|
254
|
-
This is a declared approximation
|
|
272
|
+
This is a declared approximation that uses the known simulation geometry. It does **not** claim realistic AF motor speed, phase or contrast detection, hunting, subject recognition, low-light failure, or the tracking behaviour of any commercial camera.
|
|
255
273
|
|
|
256
274
|
When the target becomes unavailable, the first policy is:
|
|
257
275
|
|
|
@@ -261,23 +279,23 @@ Focus is held, state becomes `target-lost`, and an ordinary update cannot silent
|
|
|
261
279
|
|
|
262
280
|
### Focus lock
|
|
263
281
|
|
|
264
|
-
`setFocusLock()` freezes the resolved focus plane
|
|
282
|
+
`setFocusLock()` freezes the resolved focus plane. It keeps the active mode and the acquisition state, so they can resume later.
|
|
265
283
|
|
|
266
|
-
Target updates while locked do not change focus. Unlocking restores the
|
|
284
|
+
Target updates while locked do not change focus. Unlocking restores the previous acquisition state; a later continuous-AF update may then move focus again.
|
|
267
285
|
|
|
268
|
-
Focus lock does not engage AE lock, move a metering spot, change WB, or
|
|
286
|
+
Focus lock does not engage AE lock, move a metering spot, change WB, or change aperture, shutter or ISO.
|
|
269
287
|
|
|
270
288
|
### Focus vs release priority
|
|
271
289
|
|
|
272
|
-
Use `assessFocusReleaseGate()`
|
|
290
|
+
Use `assessFocusReleaseGate()` to decide whether the shutter may release, as input to [release sequencing](#logical-release-sequences) (#105).
|
|
273
291
|
|
|
274
|
-
- `focus-priority`
|
|
275
|
-
- `release-priority`
|
|
276
|
-
- `balanced` is reserved
|
|
292
|
+
- `focus-priority` allows release for manual focus, acquired AF or locked focus, and blocks otherwise.
|
|
293
|
+
- `release-priority` allows release without changing focus.
|
|
294
|
+
- `balanced` is reserved. In schema 0.1.0 it fails closed (blocks) until a specific balanced policy is defined.
|
|
277
295
|
|
|
278
|
-
The gate never changes the optical focus calculation or exposure state.
|
|
296
|
+
The gate never changes the optical focus calculation or the exposure state.
|
|
279
297
|
|
|
280
|
-
The acquisition-state type also reserves `acquiring` for future
|
|
298
|
+
The acquisition-state type also reserves `acquiring` for future actuator models that take time. The current ideal actuator moves an available acquisition straight to `acquired`.
|
|
281
299
|
|
|
282
300
|
## Field of view
|
|
283
301
|
|
|
@@ -307,7 +325,7 @@ console.log(focused.value.projectionDistanceMm);
|
|
|
307
325
|
console.log(focused.value.degrees);
|
|
308
326
|
```
|
|
309
327
|
|
|
310
|
-
Without `focusDistanceM`, nominal focal length is used as the infinity
|
|
328
|
+
Without `focusDistanceM`, the nominal focal length is used as the projection distance (infinity focus, like a pinhole). Neither mode models real-lens distortion or focus breathing.
|
|
311
329
|
|
|
312
330
|
For off-center sensor regions, use `calculateFieldOfViewBounds()` with signed sensor-plane coordinates relative to the optical axis:
|
|
313
331
|
|
|
@@ -325,13 +343,13 @@ console.log(leftHalf.value.maximumDegrees);
|
|
|
325
343
|
console.log(leftHalf.value.degrees);
|
|
326
344
|
```
|
|
327
345
|
|
|
328
|
-
Unlike the centered `2 × atan(size / 2d)` form, the bounds API
|
|
346
|
+
Unlike the centered `2 × atan(size / 2d)` form, the bounds API keeps asymmetric angular limits. Active-capture geometry uses it for off-center crops.
|
|
329
347
|
|
|
330
|
-
See [Physics Foundation](PHYSICS_FOUNDATION.md#rectilinear-field-of-view).
|
|
348
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#rectilinear-field-of-view).
|
|
331
349
|
|
|
332
350
|
## Actual and 35 mm-equivalent focal length
|
|
333
351
|
|
|
334
|
-
Use `calculateEquivalentFocalLength35Mm()` when you need the conventional
|
|
352
|
+
Use `calculateEquivalentFocalLength35Mm()` when you need the conventional 35 mm-equivalent value (based on the diagonal), while keeping the physical optical focal length:
|
|
335
353
|
|
|
336
354
|
```ts
|
|
337
355
|
import { calculateEquivalentFocalLength35Mm } from "@photivra/engine";
|
|
@@ -349,17 +367,18 @@ console.log(equivalent.value.equivalentFocalLength35Mm);
|
|
|
349
367
|
console.log(equivalent.value.basis); // "diagonal"
|
|
350
368
|
```
|
|
351
369
|
|
|
352
|
-
The actual focal length
|
|
353
|
-
|
|
354
|
-
If an active sensor crop is in use, pass the physical active area resolved by `resolveCaptureGeometry()`. Physical camera orientation does not change the equivalent value because the active diagonal is unchanged.
|
|
370
|
+
The actual focal length stays the physical optical quantity used by projection and depth-of-field calculations. The equivalent value comes from the diagonal of the active physical capture area, compared with a 36 × 24 mm reference frame.
|
|
355
371
|
|
|
356
|
-
|
|
372
|
+
- If an active sensor crop is in use, pass the physical active area resolved by `resolveCaptureGeometry()`.
|
|
373
|
+
- Physical camera orientation does not change the equivalent value, because the active diagonal does not change.
|
|
374
|
+
- Focus distance is not part of conventional 35 mm-equivalent focal length. A later digital/output crop and the output resolution are also excluded.
|
|
375
|
+
- If a product wants to describe the final digital framing, it should label it as output framing. It should not silently replace the optical focal length or the capture-equivalent focal length.
|
|
357
376
|
|
|
358
|
-
`calculateImagingAreaMetrics()`
|
|
377
|
+
`calculateImagingAreaMetrics()` provides the same canonical diagonal crop-factor calculation, independent of raster density, so other engine modules do not need to repeat that formula.
|
|
359
378
|
|
|
360
379
|
## Focus-breathing projection
|
|
361
380
|
|
|
362
|
-
Use `calculateFocusBreathingProjection()` when
|
|
381
|
+
Focus breathing is the change in framing that some lenses show when focus changes. Use `calculateFocusBreathingProjection()` when you have an explicit projection-scale value for the selected focus state.
|
|
363
382
|
|
|
364
383
|
```ts
|
|
365
384
|
import {
|
|
@@ -391,18 +410,19 @@ console.log(horizontalFov.value.effectiveDegrees);
|
|
|
391
410
|
- values above `1` narrow framing / increase magnification;
|
|
392
411
|
- values below `1` widen framing / reduce magnification.
|
|
393
412
|
|
|
394
|
-
Photivra does **not** infer this scale from focal length, focus distance, lens identity, sensor format
|
|
413
|
+
Photivra does **not** infer this scale from focal length, focus distance, lens identity, sensor format or marketing specifications. If you supply the same scale at two focus distances, that is exactly what you get: the same relative projection scale at both states.
|
|
395
414
|
|
|
396
|
-
The physical focal length
|
|
415
|
+
The physical focal length stays authoritative. The effective projection distance is not a renamed focal length, and a later digital/output crop does not change the breathing scale.
|
|
397
416
|
|
|
398
|
-
This generic model is returned as an `approximation`. A future calibrated lens profile would need defensible provenance, reuse rights, and uncertainty
|
|
417
|
+
This generic model is returned as an `approximation`. A future calibrated lens profile would need defensible provenance, reuse rights, and uncertainty and limitation metadata.
|
|
399
418
|
|
|
400
419
|
## Oriented raster to image-plane coordinates
|
|
401
420
|
|
|
402
|
-
Use
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
421
|
+
Use this coordinate bridge before a renderer working in oriented/output coordinates calls
|
|
422
|
+
an image-plane lens or camera-mapping API. The two sides use different conventions:
|
|
423
|
+
|
|
424
|
+
- the capture/output physical region uses a raster-style basis, +Y down;
|
|
425
|
+
- lens-field APIs use the optical image plane before orientation, +Y up.
|
|
406
426
|
|
|
407
427
|
```ts
|
|
408
428
|
import {
|
|
@@ -425,14 +445,17 @@ const roundTrip = mapImagePlanePointToOrientedPhysicalUv({
|
|
|
425
445
|
});
|
|
426
446
|
```
|
|
427
447
|
|
|
428
|
-
These functions only
|
|
429
|
-
distortion, focus breathing, crop
|
|
430
|
-
bounds are
|
|
448
|
+
These functions only change the coordinate basis. They do not apply projection,
|
|
449
|
+
distortion, focus breathing, crop or resampling. Asymmetric or off-axis physical
|
|
450
|
+
bounds are kept as they are, not recentered. The reverse mapping fails closed
|
|
431
451
|
when the image-plane point lies outside the supplied physical region.
|
|
432
452
|
|
|
433
453
|
## Radial lens-distortion mapping
|
|
434
454
|
|
|
435
|
-
|
|
455
|
+
Two functions cover generic, rotationally symmetric distortion:
|
|
456
|
+
|
|
457
|
+
- `calculateRadialDistortionMapping()` maps an ideal image-plane point to its distorted position.
|
|
458
|
+
- `calculateInverseRadialDistortionMapping()` goes the other way, from destination to source. This is what a renderer needs for inverse sampling.
|
|
436
459
|
|
|
437
460
|
```ts
|
|
438
461
|
import {
|
|
@@ -472,17 +495,22 @@ scale = 1 + k1 r² + k2 r⁴ + k3 r⁶
|
|
|
472
495
|
p_distorted = p_ideal × scale
|
|
473
496
|
```
|
|
474
497
|
|
|
475
|
-
The coefficients are dimensionless **only together with the declared physical normalization radius**. Reusing coefficients with a different normalization
|
|
498
|
+
The coefficients are dimensionless **only together with the declared physical normalization radius**. Reusing the coefficients with a different normalization radius gives a different model.
|
|
476
499
|
|
|
477
|
-
`maximumNormalizedRadius` is the
|
|
500
|
+
`maximumNormalizedRadius` is the valid operating range you declare.
|
|
478
501
|
|
|
479
|
-
|
|
502
|
+
- Photivra checks analytically that the radial distance stays strictly monotonic over that range, including cases where higher-order coefficients make it nearly linear.
|
|
503
|
+
- Profiles that fold back on themselves are rejected, so the inverse mapping is unique.
|
|
504
|
+
- Derived normalized radii must stay finite.
|
|
505
|
+
- Malformed point or batch inputs fail closed before they reach the inverse solver.
|
|
480
506
|
|
|
481
|
-
|
|
507
|
+
This first field mapping is radial only and centered on the optical axis. It does not model tangential/decentered distortion, anamorphic mapping, wavelength dependence or a calibrated named lens.
|
|
508
|
+
|
|
509
|
+
The engine returns an `approximation`, because the polynomial is a generic lens model with caller-chosen parameters. A test-fixture grid or fiducials can provide regression evidence for a renderer, but they do not calibrate real-lens coefficients.
|
|
482
510
|
|
|
483
511
|
## Lateral chromatic-aberration mapping
|
|
484
512
|
|
|
485
|
-
Use `calculateLateralChromaticAberrationMapping()` to map one ideal image-plane point through a shared **
|
|
513
|
+
Lateral chromatic aberration (CA) is the sideways colour fringing caused by red, green and blue light landing at slightly different image positions. Use `calculateLateralChromaticAberrationMapping()` to map one ideal image-plane point through a shared **base distortion, referenced to green**, plus radial coefficient offsets for red and blue.
|
|
486
514
|
|
|
487
515
|
Use `calculateInverseLateralChromaticAberrationMapping()` when a renderer needs the ideal source coordinate for each output channel at one distorted destination.
|
|
488
516
|
|
|
@@ -530,19 +558,19 @@ console.log(inverse.value.channels.blue.sourceImagePointMm);
|
|
|
530
558
|
|
|
531
559
|
This is **field mapping, not a blur kernel**.
|
|
532
560
|
|
|
533
|
-
The green channel carries the common base geometric distortion. Red and blue offsets are added to that base before mapping.
|
|
561
|
+
The green channel carries the common base geometric distortion. Red and blue offsets are added to that base before mapping. So:
|
|
534
562
|
|
|
535
|
-
- zero red/blue offsets mean no lateral CA,
|
|
536
|
-
-
|
|
537
|
-
- all three combined channel profiles share one physical normalization radius and operating
|
|
538
|
-
-
|
|
563
|
+
- zero red/blue offsets mean no lateral CA, though base distortion can still exist;
|
|
564
|
+
- do not apply the same base distortion again as a second renderer warp;
|
|
565
|
+
- all three combined channel profiles share one physical normalization radius and operating range;
|
|
566
|
+
- each combined channel profile must be invertible on its own over that range.
|
|
539
567
|
|
|
540
|
-
The channel labels
|
|
568
|
+
The channel labels stand for renderer RGB channels. They are not wavelength samples, a spectral lens model, sensor CFA primaries or a camera colour profile. Longitudinal chromatic aberration and wavelength-dependent PSF behaviour are separate future work.
|
|
541
569
|
|
|
542
|
-
A renderer should inverse-map each destination channel to the source coordinate
|
|
570
|
+
A renderer should inverse-map each destination channel to the source coordinate the engine returns. It should not add its own RGB offset or blur fringes onto the finished image.
|
|
543
571
|
|
|
544
|
-
For renderer grids or other multi-point evaluation, use the batch inverse APIs
|
|
545
|
-
one
|
|
572
|
+
For renderer grids or other multi-point evaluation, use the batch inverse APIs. The profile is
|
|
573
|
+
then validated once, with one provenance record, for the whole point set:
|
|
546
574
|
|
|
547
575
|
Composition fragment: requires previously validated bindings `caProfile`, `points`, `radialProfile`. This is an integration sketch, not a standalone runnable program.
|
|
548
576
|
|
|
@@ -563,13 +591,13 @@ const caGrid = calculateInverseLateralChromaticAberrationMappings({
|
|
|
563
591
|
});
|
|
564
592
|
```
|
|
565
593
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
594
|
+
Each value in a batch matches what the single-point inverse API returns. Batching only
|
|
595
|
+
avoids repeated validation and allocation. It does not change the lens model, operating
|
|
596
|
+
range, coordinate basis or numerical solver.
|
|
569
597
|
|
|
570
598
|
## Illumination vignetting
|
|
571
599
|
|
|
572
|
-
Use `calculateIlluminationVignetting()` when a generic virtual lens needs
|
|
600
|
+
Vignetting is the darkening towards the edges of the frame. Use `calculateIlluminationVignetting()` when a generic virtual lens needs a **relative linear throughput** that varies across the field, without changing geometry or the pupil/PSF shape.
|
|
573
601
|
|
|
574
602
|
```ts
|
|
575
603
|
import { calculateIlluminationVignetting } from "@photivra/engine";
|
|
@@ -598,9 +626,9 @@ rho = image-plane radius / normalizationRadiusMm
|
|
|
598
626
|
T(rho) = 1 + r2*rho² + r4*rho⁴ + r6*rho⁶
|
|
599
627
|
```
|
|
600
628
|
|
|
601
|
-
`T` is a
|
|
629
|
+
`T` is a throughput factor that multiplies **linear** scene light, per channel. Apply it before display transfer or gamma encoding.
|
|
602
630
|
|
|
603
|
-
The profile declares `maximumNormalizedRadius`. Photivra
|
|
631
|
+
The profile declares `maximumNormalizedRadius`. Photivra checks the whole range analytically, including interior minima and maxima, and rejects profiles that:
|
|
604
632
|
|
|
605
633
|
- reach zero or negative throughput; or
|
|
606
634
|
- amplify above the optical-axis normalization of `1`.
|
|
@@ -620,11 +648,11 @@ This model changes throughput only. It does not change:
|
|
|
620
648
|
- bokeh;
|
|
621
649
|
- channel geometry.
|
|
622
650
|
|
|
623
|
-
Mechanical/pupil vignetting and cat's-eye bokeh belong to the later pupil/PSF foundation. This generic profile is
|
|
651
|
+
Mechanical/pupil vignetting and cat's-eye bokeh belong to the later pupil/PSF foundation. This generic profile is not calibrated radiometry, and not a measurement of a named lens.
|
|
624
652
|
|
|
625
653
|
## Production image-formation plan
|
|
626
654
|
|
|
627
|
-
The production composition API is separate from `simulatePocCamera()`.
|
|
655
|
+
The production composition API is separate from `simulatePocCamera()`. It works in three steps: prepare static context, commit one capture, then create the plan.
|
|
628
656
|
|
|
629
657
|
Prepare static context once:
|
|
630
658
|
|
|
@@ -747,7 +775,7 @@ const capture =
|
|
|
747
775
|
|
|
748
776
|
### Capture directly from a resolved release frame
|
|
749
777
|
|
|
750
|
-
When #
|
|
778
|
+
When [release sequencing](#logical-release-sequences) has already resolved a logical frame, use this dedicated handoff instead of copying exposure and seed values by hand:
|
|
751
779
|
|
|
752
780
|
Composition fragment: requires previously validated bindings `lockedWhiteBalance`, `physicalSceneSample`, `sequence`. This is an integration sketch, not a standalone runnable program.
|
|
753
781
|
|
|
@@ -768,7 +796,7 @@ const capture =
|
|
|
768
796
|
});
|
|
769
797
|
```
|
|
770
798
|
|
|
771
|
-
The helper binds the release frame's exposure, focus, automation policy, timing, release identity and
|
|
799
|
+
The helper binds the release frame's exposure, focus, automation policy, timing, release identity and random seed. If the frame names a WB state, the complete committed [white-balance state](#white-balance-and-auto-wb) must match that ID. Both are frozen into the capture fingerprint and later shown in `plan.captureIdentity`.
|
|
772
800
|
|
|
773
801
|
Finally create the semantic plan:
|
|
774
802
|
|
|
@@ -802,7 +830,7 @@ scene-ray-projection
|
|
|
802
830
|
-> lens-field-pupil-evaluation
|
|
803
831
|
```
|
|
804
832
|
|
|
805
|
-
The order comes from `getImageFormationContract()
|
|
833
|
+
The order comes from `getImageFormationContract()`. The planner keeps no second stage list of its own.
|
|
806
834
|
|
|
807
835
|
### Explicit stage/effect state
|
|
808
836
|
|
|
@@ -814,22 +842,22 @@ Every stage/effect is visible as one of:
|
|
|
814
842
|
- `unsupported`;
|
|
815
843
|
- `blocked`.
|
|
816
844
|
|
|
817
|
-
For example, unity illumination throughput
|
|
845
|
+
For example, a unity illumination throughput stays visible as an explicitly modelled zero-loss vignetting effect, instead of disappearing from provenance.
|
|
818
846
|
|
|
819
847
|
### Structured blockers
|
|
820
848
|
|
|
821
|
-
|
|
849
|
+
A request that is structurally valid but scientifically incomplete returns a blocked plan.
|
|
822
850
|
|
|
823
851
|
Examples:
|
|
824
852
|
|
|
825
|
-
- required stage not yet
|
|
826
|
-
- renderer lacks required stage
|
|
827
|
-
- renderer lacks wavelength
|
|
828
|
-
-
|
|
829
|
-
-
|
|
830
|
-
-
|
|
853
|
+
- a required stage is not composed yet;
|
|
854
|
+
- the renderer lacks a required stage or effect capability;
|
|
855
|
+
- the renderer lacks wavelength, depth or sensor-domain fidelity;
|
|
856
|
+
- the optical profile for the [scene-radiance-to-sensor bridge](#scene-radiance-to-sensor-irradiance) is missing;
|
|
857
|
+
- the physical scene sample is missing;
|
|
858
|
+
- that bridge fails a wavelength or applicability check.
|
|
831
859
|
|
|
832
|
-
Malformed
|
|
860
|
+
Malformed configuration, invalid selected camera settings, identity drift and tampered fingerprints still fail fast (throw).
|
|
833
861
|
|
|
834
862
|
### Deterministic identity
|
|
835
863
|
|
|
@@ -841,7 +869,7 @@ The algorithm is explicitly:
|
|
|
841
869
|
fnv1a-32-non-cryptographic
|
|
842
870
|
```
|
|
843
871
|
|
|
844
|
-
This is a reproducibility key
|
|
872
|
+
This is a reproducibility key. It is not a security or integrity checksum.
|
|
845
873
|
|
|
846
874
|
Use:
|
|
847
875
|
|
|
@@ -855,7 +883,7 @@ when a canonical deterministic serialization is needed.
|
|
|
855
883
|
|
|
856
884
|
### Temporal capture input
|
|
857
885
|
|
|
858
|
-
A production capture snapshot may include authoritative timing
|
|
886
|
+
A production capture snapshot may include authoritative timing and readout state:
|
|
859
887
|
|
|
860
888
|
```ts
|
|
861
889
|
const capture =
|
|
@@ -906,11 +934,11 @@ The plan may then expose:
|
|
|
906
934
|
- optional `sensorReadoutTiming`;
|
|
907
935
|
- optional `rotationQuadrature`.
|
|
908
936
|
|
|
909
|
-
Sensor readout is
|
|
937
|
+
Sensor readout is never silently synchronized to exposure boundaries.
|
|
910
938
|
|
|
911
|
-
If
|
|
939
|
+
If the requested temporal quadrature (time sampling) has more nodes than the renderer says it can handle, the plan reports `renderer-temporal-sampling-insufficient`. It does not change the committed quadrature.
|
|
912
940
|
|
|
913
|
-
A zero rotation model and global readout
|
|
941
|
+
A zero rotation model and global readout stay visible as explicit `modeled-zero` effects.
|
|
914
942
|
|
|
915
943
|
### Consumer manifests
|
|
916
944
|
|
|
@@ -931,21 +959,21 @@ const reference =
|
|
|
931
959
|
});
|
|
932
960
|
```
|
|
933
961
|
|
|
934
|
-
Both views
|
|
962
|
+
Both views keep the same plan fingerprint, stage and effect identities, physical and temporal results, blockers and capture seed.
|
|
935
963
|
|
|
936
|
-
The consumer role does not
|
|
964
|
+
The consumer role does not let the backend change scientific inputs or reduce the committed temporal samples.
|
|
937
965
|
|
|
938
966
|
### Renderer/reference split
|
|
939
967
|
|
|
940
968
|
Renderer declarations contain semantic capabilities only. They do not include WebGPU, Three.js, React, DOM or server objects.
|
|
941
969
|
|
|
942
|
-
|
|
970
|
+
A fast interactive renderer and a slower reference evaluator read the same engine-owned stage/effect plan, and each declares what it can faithfully represent.
|
|
943
971
|
|
|
944
972
|
### Current first-slice boundary
|
|
945
973
|
|
|
946
|
-
Plan schema `0.1.0` deliberately composes only the
|
|
974
|
+
Plan schema `0.1.0` deliberately composes only the first primary spectral path, through the [scene-radiance-to-sensor bridge](#scene-radiance-to-sensor-irradiance).
|
|
947
975
|
|
|
948
|
-
Later
|
|
976
|
+
Later stages stay explicit blockers until they are deliberately integrated, including:
|
|
949
977
|
|
|
950
978
|
- PSF;
|
|
951
979
|
- temporal exposure/readout composition;
|
|
@@ -956,15 +984,15 @@ Later requested stages remain explicit blockers until intentionally integrated,
|
|
|
956
984
|
- reconstruction;
|
|
957
985
|
- final output/display processing.
|
|
958
986
|
|
|
959
|
-
|
|
987
|
+
So the production plan never silently turns standalone foundations into active pipeline stages.
|
|
960
988
|
|
|
961
|
-
See [Production Image-Formation Plan](PRODUCTION_COMPOSITION.md).
|
|
989
|
+
See [Production Image-Formation Plan](https://github.com/Photivra/photivra/blob/main/docs/PRODUCTION_COMPOSITION.md).
|
|
962
990
|
|
|
963
991
|
## Front-of-lens filter transmission
|
|
964
992
|
|
|
965
993
|
Use `parseFrontOfLensFilterProfile()`, `resolveFrontOfLensFilterTransmission()`, and `composeFrontOfLensFilterTransmission()` for generic passive filters placed **in front of the lens**.
|
|
966
994
|
|
|
967
|
-
OpenSource V1 supports three generic transmission
|
|
995
|
+
These cover neutral-density (ND) and wavelength-dependent filters. OpenSource V1 supports three generic ways to declare transmission:
|
|
968
996
|
|
|
969
997
|
- `neutral-linear-transmission` — explicit wavelength-independent transmission over a declared wavelength range;
|
|
970
998
|
- `neutral-optical-density` — base-10 optical density with `T = 10^-OD`;
|
|
@@ -1018,7 +1046,7 @@ const nd = parseFrontOfLensFilterProfile({
|
|
|
1018
1046
|
});
|
|
1019
1047
|
```
|
|
1020
1048
|
|
|
1021
|
-
A 0.5
|
|
1049
|
+
A transmission factor of 0.5 is exactly one stop of attenuation. Base-10 optical density is reported next to the stop attenuation, for diagnostics.
|
|
1022
1050
|
|
|
1023
1051
|
Multiple filters compose multiplicatively:
|
|
1024
1052
|
|
|
@@ -1026,19 +1054,19 @@ Multiple filters compose multiplicatively:
|
|
|
1026
1054
|
T_stack = T_1 * T_2 * ... * T_n
|
|
1027
1055
|
```
|
|
1028
1056
|
|
|
1029
|
-
The stack
|
|
1057
|
+
The stack keeps each filter's identity and evidence. The combined numeric uncertainty is deliberately reported as **not propagated**, unless it is established whether the filters' errors are correlated or independent. Each filter's own uncertainty stays attached to that filter.
|
|
1030
1058
|
|
|
1031
1059
|
### Spectral boundary
|
|
1032
1060
|
|
|
1033
|
-
Spectral filter data
|
|
1061
|
+
Spectral filter data stays wavelength-resolved all the way through the [scene-radiance-to-sensor bridge](#scene-radiance-to-sensor-irradiance). Photivra does not turn a spectral filter into an undocumented RGB tint.
|
|
1034
1062
|
|
|
1035
|
-
The requested scene wavelength and filter profile must use the same resolved air/vacuum basis and lie inside the declared
|
|
1063
|
+
The requested scene wavelength and the filter profile must use the same resolved air/vacuum basis, and the wavelength must lie inside the declared range.
|
|
1036
1064
|
|
|
1037
|
-
Embedded numeric spectral curves
|
|
1065
|
+
Embedded numeric spectral curves need `reusable-data` or `photivra-owned` provenance. A curve that was only published as a factual reference may not be copied into the open-source package.
|
|
1038
1066
|
|
|
1039
1067
|
### Optical integration
|
|
1040
1068
|
|
|
1041
|
-
When `frontOfLensFilters`
|
|
1069
|
+
When you pass `frontOfLensFilters` to `calculateSceneRadianceToSensorIrradiance()`, the bridge applies the filter stack **exactly once**, separately from:
|
|
1042
1070
|
|
|
1043
1071
|
- lens spectral transmission or effective T-stop;
|
|
1044
1072
|
- field/vignetting throughput;
|
|
@@ -1062,19 +1090,21 @@ A selected front filter does **not** change:
|
|
|
1062
1090
|
- geometric defocus;
|
|
1063
1091
|
- diffraction.
|
|
1064
1092
|
|
|
1065
|
-
The production capture snapshot
|
|
1093
|
+
The production capture snapshot and plan also carry the filter stack. Changing the selected filter changes the immutable capture/plan identity and the composed physical result of the bridge.
|
|
1066
1094
|
|
|
1067
1095
|
### Polarization boundary
|
|
1068
1096
|
|
|
1069
|
-
CPL
|
|
1097
|
+
Circular polarizer (CPL) and polarizer behaviour is not modelled in schema 0.1.0. Correct polarization would need polarization to be represented end to end: scene radiance, materials and reflections, atmosphere, optics, and possibly sensor response.
|
|
1070
1098
|
|
|
1071
|
-
Schema 0.1.0 therefore rejects filter profiles that claim `polarizationModeled: true`.
|
|
1099
|
+
Schema 0.1.0 therefore rejects filter profiles that claim `polarizationModeled: true`. (Filter schema 0.2.0 adds a linear polarizer; see [Polarizer and haze](#polarizer-and-haze).)
|
|
1072
1100
|
|
|
1073
1101
|
## Scene radiance to sensor irradiance
|
|
1074
1102
|
|
|
1075
|
-
|
|
1103
|
+
`calculateSceneRadianceToSensorIrradiance()` (#110) is the first physical optics bridge. It takes one validated sample of spectral radiance leaving the scene (from a [scene-radiance provider](#scene-radiance-provider-and-material-response), #85) and returns the **spectral irradiance at the sensor plane, before the sensor's own optical stack**.
|
|
1104
|
+
|
|
1105
|
+
(Radiance is light leaving a surface in a given direction; irradiance is light arriving on a surface.)
|
|
1076
1106
|
|
|
1077
|
-
The bridge
|
|
1107
|
+
The bridge works on one explicit wavelength/time/field sample at a time.
|
|
1078
1108
|
|
|
1079
1109
|
Composition fragment: requires previously validated bindings `sceneRadianceRequest`, `sceneRadianceResult`. This is an integration sketch, not a standalone runnable program.
|
|
1080
1110
|
|
|
@@ -1196,20 +1226,20 @@ sensor spectral irradiance =
|
|
|
1196
1226
|
* field throughput
|
|
1197
1227
|
```
|
|
1198
1228
|
|
|
1199
|
-
where the effective optical acceptance includes exactly one optical-transmission path.
|
|
1229
|
+
where the effective optical acceptance includes exactly one optical-transmission path. (N_working is the working f-number.)
|
|
1200
1230
|
|
|
1201
1231
|
This is a **paraxial approximation**, not a ray-traced pupil solution.
|
|
1202
1232
|
|
|
1203
1233
|
### f-number, working f-number, transmission, and T-stop
|
|
1204
1234
|
|
|
1205
|
-
These quantities
|
|
1235
|
+
These quantities stay separate:
|
|
1206
1236
|
|
|
1207
|
-
- nominal f-number describes geometric aperture;
|
|
1208
|
-
- working f-number
|
|
1209
|
-
- spectral transmission is
|
|
1210
|
-
- effective working T-stop is an alternative approximation that already
|
|
1237
|
+
- the nominal f-number describes the geometric aperture;
|
|
1238
|
+
- the working f-number is the geometric aperture relation at finite focus that the bridge uses;
|
|
1239
|
+
- spectral transmission is the fraction of light passed, per wavelength;
|
|
1240
|
+
- an effective working T-stop is an alternative approximation that already includes the transmission loss.
|
|
1211
1241
|
|
|
1212
|
-
Do not apply a spectral transmission curve and T-stop
|
|
1242
|
+
Do not apply a spectral transmission curve and a T-stop together.
|
|
1213
1243
|
|
|
1214
1244
|
For a spectral-transmission profile:
|
|
1215
1245
|
|
|
@@ -1244,9 +1274,9 @@ N_working = N_nominal * (1 + magnification)
|
|
|
1244
1274
|
|
|
1245
1275
|
but only when unity pupil magnification is explicitly declared.
|
|
1246
1276
|
|
|
1247
|
-
This is not assumed to be
|
|
1277
|
+
This is not assumed to be correct for all real lenses.
|
|
1248
1278
|
|
|
1249
|
-
A generic
|
|
1279
|
+
A generic or real lens profile may instead supply an evidence-backed working f-number:
|
|
1250
1280
|
|
|
1251
1281
|
```ts
|
|
1252
1282
|
focus: {
|
|
@@ -1283,7 +1313,7 @@ Supply either:
|
|
|
1283
1313
|
|
|
1284
1314
|
That factor is applied **exactly once**.
|
|
1285
1315
|
|
|
1286
|
-
Mechanical pupil clipping
|
|
1316
|
+
Mechanical pupil clipping and cat's-eye behaviour belong to later pupil/PSF work. Do not stack them as a second, hidden illumination loss.
|
|
1287
1317
|
|
|
1288
1318
|
### Wavelength basis and provenance
|
|
1289
1319
|
|
|
@@ -1294,7 +1324,7 @@ Scene radiance and optical transmission must use the same resolved wavelength ba
|
|
|
1294
1324
|
|
|
1295
1325
|
Photivra does not silently convert between them.
|
|
1296
1326
|
|
|
1297
|
-
Numeric wavelength-transmission curves are reusable data, not
|
|
1327
|
+
Numeric wavelength-transmission curves are reusable data, not just factual metadata. Their evidence must therefore be `reusable-data` or `photivra-owned`. `factual-reference-only` is not enough for copied numeric curves.
|
|
1298
1328
|
|
|
1299
1329
|
### Boundary of the result
|
|
1300
1330
|
|
|
@@ -1326,11 +1356,11 @@ The bridge also does not apply:
|
|
|
1326
1356
|
- volumetric scattering;
|
|
1327
1357
|
- wavelength-changing transport.
|
|
1328
1358
|
|
|
1329
|
-
|
|
1359
|
+
Each of those has its own owner, so transmission or energy loss cannot be counted twice.
|
|
1330
1360
|
|
|
1331
1361
|
### Calibration status
|
|
1332
1362
|
|
|
1333
|
-
Schema 0.1.0 can
|
|
1363
|
+
Schema 0.1.0 can carry calibrated optical transmission metadata. But scene-radiance evaluation (#85) is still approximation-only.
|
|
1334
1364
|
|
|
1335
1365
|
Therefore the combined result currently reports:
|
|
1336
1366
|
|
|
@@ -1342,7 +1372,7 @@ even when the optical profile itself is calibrated.
|
|
|
1342
1372
|
|
|
1343
1373
|
## Thin-lens image distance and magnification
|
|
1344
1374
|
|
|
1345
|
-
Use `calculateThinLensImageDistance()` to calculate ideal Gaussian thin-lens image distance for an object
|
|
1375
|
+
Use `calculateThinLensImageDistance()` to calculate the ideal Gaussian thin-lens image distance for an object (focus) plane.
|
|
1346
1376
|
|
|
1347
1377
|
```ts
|
|
1348
1378
|
import { calculateThinLensImageDistance } from "@photivra/engine";
|
|
@@ -1357,13 +1387,13 @@ console.log(projection.value.magnification);
|
|
|
1357
1387
|
console.log(projection.value.infinityProjectionScale);
|
|
1358
1388
|
```
|
|
1359
1389
|
|
|
1360
|
-
This is a paraxial ideal-lens model. It does not model real-lens focus breathing, pupil magnification, principal-plane movement
|
|
1390
|
+
This is a paraxial, ideal-lens model ("paraxial" means it assumes rays close to the optical axis). It does not model real-lens focus breathing, pupil magnification, principal-plane movement or aberrations.
|
|
1361
1391
|
|
|
1362
|
-
See [Physics Foundation](PHYSICS_FOUNDATION.md#projection-focus-extension-object-size-and-motion).
|
|
1392
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#projection-focus-extension-object-size-and-motion).
|
|
1363
1393
|
|
|
1364
1394
|
## Projected object size and sensor sampling
|
|
1365
1395
|
|
|
1366
|
-
Use `calculateProjectedObjectSize()` for
|
|
1396
|
+
Use `calculateProjectedObjectSize()` for an object facing the camera squarely (fronto-parallel), with known physical dimensions and distance.
|
|
1367
1397
|
|
|
1368
1398
|
```ts
|
|
1369
1399
|
import { calculateProjectedObjectSize } from "@photivra/engine";
|
|
@@ -1387,11 +1417,11 @@ Omit `pixelPitchMicrometers` when only physical image-plane dimensions are neede
|
|
|
1387
1417
|
|
|
1388
1418
|
The object plane is assumed to be fronto-parallel to the sensor.
|
|
1389
1419
|
|
|
1390
|
-
See [Physics Foundation](PHYSICS_FOUNDATION.md#projection-focus-extension-object-size-and-motion).
|
|
1420
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#projection-focus-extension-object-size-and-motion).
|
|
1391
1421
|
|
|
1392
1422
|
## Depth of field and defocus
|
|
1393
1423
|
|
|
1394
|
-
Use `calculateDepthOfField()` for
|
|
1424
|
+
Use `calculateDepthOfField()` for the exact thin-lens near/far limits for a caller-supplied circle of confusion (it must be smaller than the aperture diameter `focalLengthMm / aperture`):
|
|
1395
1425
|
|
|
1396
1426
|
```ts
|
|
1397
1427
|
import { calculateDepthOfField } from "@photivra/engine";
|
|
@@ -1423,34 +1453,34 @@ const blur = calculateDefocusCircle({
|
|
|
1423
1453
|
console.log(blur.value.diameterMm);
|
|
1424
1454
|
```
|
|
1425
1455
|
|
|
1426
|
-
|
|
1456
|
+
You supply the circle-of-confusion criterion used for depth of field (DOF). These are ideal geometric thin-lens models. They do not include diffraction or real-lens aberrations in the DOF criterion.
|
|
1427
1457
|
|
|
1428
|
-
See [Physics Foundation](PHYSICS_FOUNDATION.md#thin-lens-focus-and-depth-of-field).
|
|
1458
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#thin-lens-focus-and-depth-of-field).
|
|
1429
1459
|
|
|
1430
1460
|
## Real-lens PSF profiles and complex-pupil reference
|
|
1431
1461
|
|
|
1432
|
-
|
|
1462
|
+
There are two explicit ways to describe the response of a real lens: sampled PSF profiles, and a complex-pupil (wavefront) reference. Both were added in #113. The legacy defocus and Airy diagnostics are kept unchanged.
|
|
1433
1463
|
|
|
1434
1464
|
### Sampled 2D PSF profiles
|
|
1435
1465
|
|
|
1436
|
-
Use `parseLensSampledPsfProfile()` and `resolveLensSampledPsf()` for measured, simulated
|
|
1466
|
+
Use `parseLensSampledPsfProfile()` and `resolveLensSampledPsf()` for PSF kernels that are measured, simulated or owned by Photivra. Each kernel has unit energy, and kernels sit on a bounded regular grid.
|
|
1437
1467
|
|
|
1438
1468
|
The grid axes are:
|
|
1439
1469
|
|
|
1440
1470
|
- physical focal length;
|
|
1441
|
-
- focus in diopters, where `0`
|
|
1471
|
+
- focus in diopters, where `0` is the explicit infinity state of [`FocusPlane`](#explicit-finite-and-infinity-focus-state);
|
|
1442
1472
|
- f-number;
|
|
1443
1473
|
- image-plane field X/Y in millimetres;
|
|
1444
1474
|
- wavelength in nanometres;
|
|
1445
1475
|
- signed image-plane defocus in micrometres.
|
|
1446
1476
|
|
|
1447
|
-
Resolution is either `exact-grid-sample` or `interpolated`.
|
|
1477
|
+
Resolution is either `exact-grid-sample` or `interpolated`. Any request outside a declared axis fails closed; there is no extrapolation.
|
|
1448
1478
|
|
|
1449
|
-
All nodes must use the same physical kernel sampling grid so multilinear interpolation
|
|
1479
|
+
All nodes must use the same physical kernel sampling grid, so multilinear interpolation never silently mixes different PSF coordinate systems. The returned full 2D kernel keeps the +X right / +Y up orientation. No radial or sagittal/tangential averaging is forced.
|
|
1450
1480
|
|
|
1451
|
-
Every kernel is a **unit-energy PSF shape**. `relativePupilThroughputFactor`
|
|
1481
|
+
Every kernel is a **unit-energy PSF shape**. `relativePupilThroughputFactor` is kept separate and belongs to the optical-throughput path. So a mechanically clipped (cat's-eye) pupil can change both the PSF shape and the throughput, without the throughput being multiplied into the normalized PSF and then applied a second time in the [scene-radiance-to-sensor bridge](#scene-radiance-to-sensor-irradiance).
|
|
1452
1482
|
|
|
1453
|
-
`bestFocusImagePlaneOffsetMicrometers` may vary across field and wavelength. This is
|
|
1483
|
+
`bestFocusImagePlaneOffsetMicrometers` may vary across the field and with wavelength. This is where a profile describes field curvature and longitudinal (axial) chromatic focus shift. It does not change geometric distortion or shift channels sideways.
|
|
1454
1484
|
|
|
1455
1485
|
Signed defocus is preserved, so foreground and background defocus may use different profiled PSFs.
|
|
1456
1486
|
|
|
@@ -1461,32 +1491,37 @@ Use `parseLensComplexPupilProfile()` and `calculateLensComplexPupilPsf()` when t
|
|
|
1461
1491
|
- relative pupil amplitude; plus
|
|
1462
1492
|
- optical-path difference in micrometres.
|
|
1463
1493
|
|
|
1464
|
-
The reference evaluator
|
|
1494
|
+
The reference evaluator runs deterministic scalar Fraunhofer propagation on the explicit pupil grid.
|
|
1495
|
+
|
|
1496
|
+
- Each axis is zero-padded to at least 2S − 1 samples, where S is the number of samples in the non-zero support. This makes the intensity PSF meet Goodman's sampling condition Q = λN/pitch > 2 (stated freely in Ban et al., [arXiv:2511.06936](https://arxiv.org/abs/2511.06936), eqs. 8–9). `result.value.sampling` reports the achieved Q and padding.
|
|
1497
|
+
- A pupil support wider than the declared f/N aperture is rejected.
|
|
1498
|
+
- Diffraction and aberration phase are evaluated **together** in that pupil calculation. Do not add a separate diffraction blur to the result.
|
|
1465
1499
|
|
|
1466
|
-
The resulting intensity PSF is normalized to unit energy. The profile's relative pupil throughput is
|
|
1500
|
+
The resulting intensity PSF is normalized to unit energy. The profile's relative pupil throughput is kept separately and is not multiplied into the kernel.
|
|
1467
1501
|
|
|
1468
|
-
The complex-pupil evaluator is a bounded scalar reference model. It does not include polarization
|
|
1502
|
+
The complex-pupil evaluator is a bounded, scalar reference model. It does not include polarization or vector diffraction, ghosting or flare, the sensor's OLPF (optical low-pass filter), microlenses or CFA, sampling, demosaicing, sharpening or display processing.
|
|
1469
1503
|
|
|
1470
1504
|
### MTF magnitude is not a PSF
|
|
1471
1505
|
|
|
1472
|
-
`parseLensMtfDiagnosticProfile()` stores sagittal/tangential MTF magnitude diagnostics.
|
|
1506
|
+
The MTF (modulation transfer function) describes how much contrast a lens keeps at each level of detail. `parseLensMtfDiagnosticProfile()` stores sagittal/tangential MTF magnitude diagnostics.
|
|
1473
1507
|
|
|
1474
|
-
`assessMtfOnlyPsfRenderability()` always reports that unique PSF
|
|
1508
|
+
`assessMtfOnlyPsfRenderability()` always reports that a unique PSF cannot be reconstructed, because the MTF magnitude has no phase information. MTF is useful for validation, not as a way to generate a blur kernel.
|
|
1475
1509
|
|
|
1476
1510
|
### Ownership boundaries
|
|
1477
1511
|
|
|
1478
|
-
-
|
|
1479
|
-
-
|
|
1480
|
-
-
|
|
1481
|
-
-
|
|
1482
|
-
-
|
|
1483
|
-
-
|
|
1484
|
-
-
|
|
1485
|
-
- no scalar `lensSharpness` or `bokehQuality`
|
|
1512
|
+
- Geometric distortion stays a field mapping.
|
|
1513
|
+
- Lateral CA stays a wavelength/channel field mapping.
|
|
1514
|
+
- Field curvature and longitudinal CA belong to focus/PSF.
|
|
1515
|
+
- Pupil clipping can change PSF shape; throughput is owned separately.
|
|
1516
|
+
- Regular-polygon and other non-circular diffraction is owned by [ideal polygon diffraction](#ideal-regular-polygon-diffraction) (#1).
|
|
1517
|
+
- Stray light, ghosting and veiling glare are owned separately (#114; see [Digital optics foundations](https://github.com/Photivra/photivra/blob/main/docs/DIGITAL_OPTICS_FOUNDATION.md)).
|
|
1518
|
+
- Sensor OLPF, CFA and reconstruction come later in the pipeline.
|
|
1519
|
+
- There is no scalar `lensSharpness` or `bokehQuality` primitive; such scores are not scientific quantities.
|
|
1486
1520
|
|
|
1487
1521
|
## PSF and pupil foundation
|
|
1488
1522
|
|
|
1489
|
-
|
|
1523
|
+
- `getPsfFoundationContract()` shows which PSF contributions exist or are reserved, and who owns them.
|
|
1524
|
+
- `calculatePsfFoundationComponents()` evaluates the diagnostics implemented today, in one explicit field/depth/spectral/pupil context.
|
|
1490
1525
|
|
|
1491
1526
|
```ts
|
|
1492
1527
|
import {
|
|
@@ -1515,17 +1550,17 @@ console.log(components.value.contributions.circularDiffraction);
|
|
|
1515
1550
|
console.log(components.value.composition.status); // "not-composed"
|
|
1516
1551
|
```
|
|
1517
1552
|
|
|
1518
|
-
The
|
|
1553
|
+
The foundation deliberately **does not combine** geometric defocus and circular diffraction into a synthetic PSF, MTF, convolution kernel, combined blur radius or "lens sharpness" score.
|
|
1519
1554
|
|
|
1520
|
-
It records field position
|
|
1555
|
+
It already records field position, even though the current defocus and Airy diagnostics do not vary across the field. Future field-curvature, pupil-clipping, bokeh and aberration contributions can then use the same context without reinterpreting existing outputs.
|
|
1521
1556
|
|
|
1522
|
-
The
|
|
1557
|
+
The spectral basis is explicitly monochromatic (one wavelength) for the Airy diagnostic. This does not create a spectral lens or sensor-colour model.
|
|
1523
1558
|
|
|
1524
|
-
See [PSF and Pupil Foundation](PSF_FOUNDATION.md).
|
|
1559
|
+
See [PSF and Pupil Foundation](https://github.com/Photivra/photivra/blob/main/docs/PSF_FOUNDATION.md).
|
|
1525
1560
|
|
|
1526
1561
|
## Circular-aperture diffraction
|
|
1527
1562
|
|
|
1528
|
-
Use `calculateAiryDisk()` for the
|
|
1563
|
+
Use `calculateAiryDisk()` for the diameter of the Airy disk to its first dark ring (first zero), for an ideal circular pupil at one wavelength:
|
|
1529
1564
|
|
|
1530
1565
|
```ts
|
|
1531
1566
|
import { calculateAiryDisk } from "@photivra/engine";
|
|
@@ -1538,9 +1573,9 @@ const diffraction = calculateAiryDisk({
|
|
|
1538
1573
|
console.log(diffraction.value.firstZeroDiameterMicrometers);
|
|
1539
1574
|
```
|
|
1540
1575
|
|
|
1541
|
-
This calculation uses `d = 2.44 λ N
|
|
1576
|
+
This calculation uses `d = 2.44 λ N` (λ = wavelength, N = f-number). It is not a diffraction PSF for a polygon aperture.
|
|
1542
1577
|
|
|
1543
|
-
See [Physics Foundation](PHYSICS_FOUNDATION.md#diffraction).
|
|
1578
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#diffraction).
|
|
1544
1579
|
|
|
1545
1580
|
## Aperture geometry and sunstar directions
|
|
1546
1581
|
|
|
@@ -1559,9 +1594,9 @@ console.log(aperture.value.sunstarRayCount);
|
|
|
1559
1594
|
console.log(aperture.value.sunstarRayAnglesDegrees);
|
|
1560
1595
|
```
|
|
1561
1596
|
|
|
1562
|
-
The output describes normalized aperture
|
|
1597
|
+
The output describes only the normalized aperture shape and the ideal symmetry of the sunstar ray directions. It does not calculate diffraction intensity, star length, flare, coatings or a full PSF.
|
|
1563
1598
|
|
|
1564
|
-
See [Physics Foundation](PHYSICS_FOUNDATION.md#ideal-diaphragm-geometry-and-sunstar-symmetry).
|
|
1599
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#ideal-diaphragm-geometry-and-sunstar-symmetry).
|
|
1565
1600
|
|
|
1566
1601
|
## Pixel pitch
|
|
1567
1602
|
|
|
@@ -1579,25 +1614,30 @@ console.log(pitch.value.micrometers);
|
|
|
1579
1614
|
console.log(pitch.value.millimeters);
|
|
1580
1615
|
```
|
|
1581
1616
|
|
|
1582
|
-
|
|
1617
|
+
This primitive calculates horizontal pixel pitch. It assumes the sensor width and pixel count you supply describe the same active dimension.
|
|
1583
1618
|
|
|
1584
|
-
See [Physics Foundation](PHYSICS_FOUNDATION.md#sensor-geometry-sampling-capture-and-crop).
|
|
1619
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#sensor-geometry-sampling-capture-and-crop).
|
|
1585
1620
|
|
|
1586
1621
|
## White balance and Auto WB
|
|
1587
1622
|
|
|
1588
|
-
Photivra keeps **scene illumination**, **camera white-balance state**, and **render/export
|
|
1623
|
+
Photivra keeps three things separate: **scene illumination**, **camera white-balance state**, and **render/export colour transforms**.
|
|
1589
1624
|
|
|
1590
|
-
The
|
|
1625
|
+
The white-balance foundation (#108) works on an explicit domain: the linear camera RGB a camera could observe before white balance:
|
|
1591
1626
|
|
|
1592
1627
|
```text
|
|
1593
1628
|
relative-pre-wb-camera-linear-rgb
|
|
1594
1629
|
```
|
|
1595
1630
|
|
|
1596
|
-
It does not
|
|
1631
|
+
It does not:
|
|
1632
|
+
|
|
1633
|
+
- peek at the authoritative scene-illuminant metadata to "cheat" auto white balance (AWB);
|
|
1634
|
+
- change the scene lighting;
|
|
1635
|
+
- destructively alter the RAW-like sensor capture;
|
|
1636
|
+
- change aperture, shutter, ISO or focus.
|
|
1597
1637
|
|
|
1598
1638
|
### Generic presets
|
|
1599
1639
|
|
|
1600
|
-
Preset labels are resolved through a declared profile
|
|
1640
|
+
Preset labels are resolved through a declared profile, not through universal hard-coded Kelvin values:
|
|
1601
1641
|
|
|
1602
1642
|
Composition fragment: requires previously validated bindings `evidence`. This is an integration sketch, not a standalone runnable program.
|
|
1603
1643
|
|
|
@@ -1665,7 +1705,7 @@ const daylight =
|
|
|
1665
1705
|
});
|
|
1666
1706
|
```
|
|
1667
1707
|
|
|
1668
|
-
The label, nominal CCT, tint
|
|
1708
|
+
The label, the nominal CCT (correlated colour temperature), the tint and the actual channel gains are separate facts in the profile. A camera-specific profile needs defensible evidence. No manufacturer-specific WB behaviour is guessed from a common preset name.
|
|
1669
1709
|
|
|
1670
1710
|
### Manual gains and CCT+tint intent
|
|
1671
1711
|
|
|
@@ -1682,13 +1722,22 @@ const intent =
|
|
|
1682
1722
|
});
|
|
1683
1723
|
```
|
|
1684
1724
|
|
|
1685
|
-
The
|
|
1725
|
+
The intent keeps `channelGainsResolved: false` and `requiresCameraProfileColorimetry: true`. CCT alone is not treated as a complete chromatic adaptation model, and tint stays an independent axis.
|
|
1726
|
+
|
|
1727
|
+
`resolveColorTemperatureWhiteBalance()` resolves gains once the intent is paired with an explicit camera colour model:
|
|
1728
|
+
|
|
1729
|
+
1. It takes the white point on the Planckian or CIE-daylight locus.
|
|
1730
|
+
2. It offsets that white by the tint, read as Duv in CIE 1960 uv.
|
|
1731
|
+
3. It maps the white through the camera's XYZ→camera matrix.
|
|
1732
|
+
4. It neutralizes the result.
|
|
1733
|
+
|
|
1734
|
+
The state it returns has `source: "color-temperature"`. See [Kelvin + tint white balance](#kelvin--tint-white-balance) below and [Colorimetry](https://github.com/Photivra/photivra/blob/main/docs/COLORIMETRY.md#kelvin--tint-white-balance).
|
|
1686
1735
|
|
|
1687
1736
|
### Custom measured WB
|
|
1688
1737
|
|
|
1689
|
-
`resolveCustomWhiteBalance()`
|
|
1738
|
+
`resolveCustomWhiteBalance()` takes RGB samples measured before white balance, independent of any renderer, and tied to a stable image-state ID.
|
|
1690
1739
|
|
|
1691
|
-
|
|
1740
|
+
You, the caller, declare that the selected region should be neutral. Photivra does **not** claim that the real-world object you chose is spectrally neutral.
|
|
1692
1741
|
|
|
1693
1742
|
Custom measurement:
|
|
1694
1743
|
|
|
@@ -1699,19 +1748,19 @@ Custom measurement:
|
|
|
1699
1748
|
|
|
1700
1749
|
### Auto White Balance
|
|
1701
1750
|
|
|
1702
|
-
`estimateAutoWhiteBalance()`
|
|
1751
|
+
`estimateAutoWhiteBalance()` uses only linear camera samples from before white balance, plus an AWB policy owned by the profile.
|
|
1703
1752
|
|
|
1704
|
-
The generic
|
|
1753
|
+
The first generic model is a deterministic grey-world-style estimate (it assumes the scene averages to grey). A neutralizing gain is derived from the weighted observed channels. Then the policy's `correctionStrength` is applied in logarithmic gain space:
|
|
1705
1754
|
|
|
1706
1755
|
```text
|
|
1707
1756
|
resolved gain = neutralizing gain ^ correctionStrength
|
|
1708
1757
|
```
|
|
1709
1758
|
|
|
1710
|
-
A strength of `1`
|
|
1759
|
+
A strength of `1` is a generic neutral-priority estimate. Lower strengths keep more of the observed colour cast. This supports explicit generic "standard" or "ambience" behaviour, without pretending there is one universal AWB result.
|
|
1711
1760
|
|
|
1712
|
-
Clipped AWB samples are
|
|
1761
|
+
Clipped AWB samples are left out and reported. If no usable samples remain, estimation fails closed.
|
|
1713
1762
|
|
|
1714
|
-
One AWB result is a **global camera WB state**.
|
|
1763
|
+
One AWB result is a **global camera WB state**. Under mixed lighting, different parts of the image can keep different local casts after the same global gains.
|
|
1715
1764
|
|
|
1716
1765
|
### AWB lock
|
|
1717
1766
|
|
|
@@ -1725,7 +1774,7 @@ const locked =
|
|
|
1725
1774
|
});
|
|
1726
1775
|
```
|
|
1727
1776
|
|
|
1728
|
-
Later scene
|
|
1777
|
+
Later scene or image signals may give a different AWB estimate, but the locked state's gains stay unchanged. AWB lock is independent of AE lock and focus lock.
|
|
1729
1778
|
|
|
1730
1779
|
### Image-state/export boundary
|
|
1731
1780
|
|
|
@@ -1736,7 +1785,100 @@ Every resolved WB state explicitly reports that it did not:
|
|
|
1736
1785
|
- modify physical exposure;
|
|
1737
1786
|
- modify focus.
|
|
1738
1787
|
|
|
1739
|
-
|
|
1788
|
+
How a resolved WB intent is applied or encoded in processed linear images, previews, TIFF, DNG metadata or other outputs is owned by linear capture and colour (#15), not by this foundation.
|
|
1789
|
+
|
|
1790
|
+
## Colorimetry
|
|
1791
|
+
|
|
1792
|
+
The [colorimetry primitives](https://github.com/Photivra/photivra/blob/main/docs/COLORIMETRY.md) cover colour-matching functions (CMFs), illuminants, CCT/Duv, chromatic adaptation, ΔE (colour difference), camera-matrix fitting, wide-gamut/HDR transfers and gamut mapping.
|
|
1793
|
+
|
|
1794
|
+
No CIE or ColorChecker tables are bundled; the CIE datasets are licensed CC BY-SA 4.0. Use the labelled analytic CMF approximation, or pass tables you are licensed to use.
|
|
1795
|
+
|
|
1796
|
+
### Spectrum to XYZ, CCT and colour difference
|
|
1797
|
+
|
|
1798
|
+
```ts
|
|
1799
|
+
import {
|
|
1800
|
+
calculateColorDifference,
|
|
1801
|
+
calculateCielabFromXyz,
|
|
1802
|
+
calculateCorrelatedColorTemperature,
|
|
1803
|
+
createCieIlluminantASpectrum,
|
|
1804
|
+
createColorimetricMidpointQuadrature,
|
|
1805
|
+
integrateSpectrumToXyz
|
|
1806
|
+
} from "@photivra/engine";
|
|
1807
|
+
|
|
1808
|
+
const quadratureNodes = createColorimetricMidpointQuadrature({
|
|
1809
|
+
wavelengthRangeNanometers: { minimum: 360, maximum: 830 },
|
|
1810
|
+
maximumSubintervalWidthNanometers: 1
|
|
1811
|
+
});
|
|
1812
|
+
const colorMatchingFunctions = { kind: "analytic-cie-1931-multi-lobe-fit" } as const;
|
|
1813
|
+
|
|
1814
|
+
const illuminantA = integrateSpectrumToXyz({
|
|
1815
|
+
quadratureNodes,
|
|
1816
|
+
spectralDistribution: createCieIlluminantASpectrum(),
|
|
1817
|
+
colorMatchingFunctions,
|
|
1818
|
+
normalization: { kind: "white-luminance-one" }
|
|
1819
|
+
});
|
|
1820
|
+
console.log(illuminantA.provenance.kind); // "approximation" (analytic CMF fit)
|
|
1821
|
+
|
|
1822
|
+
const cct = calculateCorrelatedColorTemperature({
|
|
1823
|
+
chromaticity: illuminantA.value.chromaticity!,
|
|
1824
|
+
locus: { kind: "planck-integration", colorMatchingFunctions, quadratureNodes }
|
|
1825
|
+
});
|
|
1826
|
+
console.log(cct.value.correlatedColorTemperatureKelvin, cct.value.duv); // ≈ 2855.5 K, ≈ 0
|
|
1827
|
+
|
|
1828
|
+
const white = illuminantA.value.whiteXyz;
|
|
1829
|
+
const reference = calculateCielabFromXyz({ xyz: { x: 0.2, y: 0.18, z: 0.05 }, referenceWhiteXyz: white }).value;
|
|
1830
|
+
const sample = calculateCielabFromXyz({ xyz: { x: 0.21, y: 0.18, z: 0.05 }, referenceWhiteXyz: white }).value;
|
|
1831
|
+
console.log(calculateColorDifference({ reference, sample, formula: "ciede2000" }).value.deltaE);
|
|
1832
|
+
```
|
|
1833
|
+
|
|
1834
|
+
### Kelvin + tint white balance
|
|
1835
|
+
|
|
1836
|
+
```ts
|
|
1837
|
+
import {
|
|
1838
|
+
createColorTemperatureWhiteBalanceIntent,
|
|
1839
|
+
resolveCaptureColorModel,
|
|
1840
|
+
resolveColorTemperatureWhiteBalance
|
|
1841
|
+
} from "@photivra/engine";
|
|
1842
|
+
|
|
1843
|
+
const tungsten = resolveColorTemperatureWhiteBalance({
|
|
1844
|
+
stateId: "wb-3200k",
|
|
1845
|
+
intent: createColorTemperatureWhiteBalanceIntent({ intentId: "k3200", colorTemperatureKelvin: 3200, tint: 0 }),
|
|
1846
|
+
whiteLocus: "planckian",
|
|
1847
|
+
tintInterpretation: "duv-cie-1960-uv",
|
|
1848
|
+
camera: { profileId: "ideal-colorimetric", profileVersion: "0.1.0", xyzToCameraRgb: resolveCaptureColorModel().xyzToCameraRgb }
|
|
1849
|
+
});
|
|
1850
|
+
console.log(tungsten.channelGains); // red < 1 < blue for a warm declared white
|
|
1851
|
+
```
|
|
1852
|
+
|
|
1853
|
+
### Chromatic adaptation, transfer functions and gamut mapping
|
|
1854
|
+
|
|
1855
|
+
```ts
|
|
1856
|
+
import {
|
|
1857
|
+
calculateChromaticAdaptationMatrix,
|
|
1858
|
+
calculateGamutMappedColor,
|
|
1859
|
+
evaluateSrgbEncoding,
|
|
1860
|
+
resolveRgbColorSpace
|
|
1861
|
+
} from "@photivra/engine";
|
|
1862
|
+
|
|
1863
|
+
const bradford = calculateChromaticAdaptationMatrix({
|
|
1864
|
+
transform: "bradford",
|
|
1865
|
+
sourceWhiteXyz: { x: 1.0985, y: 1, z: 0.3558 },
|
|
1866
|
+
destinationWhiteXyz: resolveRgbColorSpace("srgb").value.whiteXyz // D65 from its chromaticity
|
|
1867
|
+
}).value.matrix;
|
|
1868
|
+
|
|
1869
|
+
const rec2020 = resolveRgbColorSpace("rec2020").value.rgbToXyz;
|
|
1870
|
+
const srgbCodeForMidGrey = evaluateSrgbEncoding(0.18); // ≈ 0.4614
|
|
1871
|
+
|
|
1872
|
+
const mapped = calculateGamutMappedColor({
|
|
1873
|
+
colorSpace: "srgb",
|
|
1874
|
+
method: "cielab-hue-preserving-chroma-compression",
|
|
1875
|
+
kneeFraction: 0.9,
|
|
1876
|
+
xyz: { x: rec2020[0][1], y: rec2020[1][1], z: rec2020[2][1] } // Rec.2020 green primary
|
|
1877
|
+
});
|
|
1878
|
+
console.log(bradford, srgbCodeForMidGrey, mapped.value.linearRgb);
|
|
1879
|
+
```
|
|
1880
|
+
|
|
1881
|
+
Scene adaptation inside `calculateCaptureColorTransform()` can opt in to a cone-space transform with `whiteBalance: { kind: "adopted-white-xyz-scaling", transform: "bradford" }`. Leaving out `transform` keeps the original diagonal scaling in XYZ.
|
|
1740
1882
|
|
|
1741
1883
|
## Sensor imaging area and native raster
|
|
1742
1884
|
|
|
@@ -1762,17 +1904,18 @@ console.log(metrics.value.sampling.pitchXMicrometers); // 6
|
|
|
1762
1904
|
console.log(metrics.value.sampling.pitchYMicrometers); // 6
|
|
1763
1905
|
```
|
|
1764
1906
|
|
|
1765
|
-
`SensorImagingArea`
|
|
1907
|
+
- `SensorImagingArea` is the physical light-sensitive area used for image formation. It is not the size of the sensor package or die.
|
|
1908
|
+
- `NativeImageRaster` is the effective native grid of image samples. It does not claim that each image sample is one physical photodiode (photosite).
|
|
1909
|
+
- The 35 mm crop factor is based on the diagonal, compared with a 36 × 24 mm reference frame.
|
|
1910
|
+
- Changing the native raster density does not change the physical crop factor. Changing the physical imaging area does not, by itself, change the native megapixels.
|
|
1766
1911
|
|
|
1767
|
-
The
|
|
1768
|
-
|
|
1769
|
-
The returned X/Y sampling pitches are geometric image-sample spacing. They are not photodiode active area, fill factor, or a photon-collection model and must not be used as those quantities.
|
|
1912
|
+
The returned X/Y sampling pitches are the geometric spacing of image samples. They are not photodiode active area, fill factor or a photon-collection model, and must not be used as those quantities.
|
|
1770
1913
|
|
|
1771
1914
|
The existing `calculatePixelPitch()` API remains available for callers that only need horizontal pitch from sensor width and horizontal pixel count.
|
|
1772
1915
|
|
|
1773
1916
|
## Sensor architecture metadata
|
|
1774
1917
|
|
|
1775
|
-
Use `parseSensorArchitectureProfile()`
|
|
1918
|
+
Use `parseSensorArchitectureProfile()` to read descriptive hardware and capability metadata from untrusted JSON.
|
|
1776
1919
|
|
|
1777
1920
|
The current schema is exported as `SENSOR_ARCHITECTURE_PROFILE_SCHEMA_VERSION` and is `0.3.0`:
|
|
1778
1921
|
|
|
@@ -1839,12 +1982,12 @@ const architecture = parseSensorArchitectureProfile({
|
|
|
1839
1982
|
});
|
|
1840
1983
|
```
|
|
1841
1984
|
|
|
1842
|
-
`technologyFamily` is an optional evidence-backed coarse detector
|
|
1985
|
+
`technologyFamily` is an optional, evidence-backed, coarse detector type:
|
|
1843
1986
|
|
|
1844
1987
|
- `"cmos"`;
|
|
1845
1988
|
- `"ccd"`.
|
|
1846
1989
|
|
|
1847
|
-
It is independent
|
|
1990
|
+
It is independent of illumination architecture (FSI/BSI — front- or back-side illuminated), integration/stacking, readout capability, shutter mechanism, colour sampling, and every performance characteristic. For example:
|
|
1848
1991
|
|
|
1849
1992
|
- CMOS may be FSI or BSI;
|
|
1850
1993
|
- CMOS may expose rolling or global readout;
|
|
@@ -1852,27 +1995,31 @@ It is independent from illumination architecture, integration/stacking, readout
|
|
|
1852
1995
|
- CCD may be monochrome or color-filtered;
|
|
1853
1996
|
- the word CMOS or CCD alone says nothing about QE, read noise, dark current, full-well capacity, dynamic range, high-ISO behavior, frame rate, or image quality.
|
|
1854
1997
|
|
|
1855
|
-
Photivra treats sCMOS as CMOS and EMCCD as CCD
|
|
1998
|
+
At this coarse level, Photivra treats sCMOS as CMOS and EMCCD as CCD. Those labels are not separate top-level values. Detailed CCD transfer architecture, electron multiplication, CMOS pixel/readout topology and other technology-specific mechanisms would need their own explicit profiles or models, if a future scientific use needs them.
|
|
1999
|
+
|
|
2000
|
+
The other architecture axes are also independent. BSI may be stacked or monolithic. Stacking does not imply a global shutter. Global readout capability does not imply any particular stacking or technology family.
|
|
1856
2001
|
|
|
1857
|
-
|
|
2002
|
+
Where evidence came from (source origin) and what Photivra may do with it (reuse rights) are independent too:
|
|
1858
2003
|
|
|
1859
|
-
|
|
2004
|
+
- Manufacturer or third-party material may be factual-reference-only, or explicitly reusable when a suitable licence exists.
|
|
2005
|
+
- `photivra-owned` evidence must originate from Photivra.
|
|
2006
|
+
- Reusable-data evidence needs an explicit licence.
|
|
1860
2007
|
|
|
1861
|
-
Scalar facts may cite
|
|
2008
|
+
Scalar facts may cite several evidence records. Capabilities with several values, such as rolling/global readout, carry evidence for each value separately, so one source cannot silently support a different capability.
|
|
1862
2009
|
|
|
1863
|
-
`readoutCapabilities` describes hardware
|
|
2010
|
+
`readoutCapabilities` describes what the hardware can do, not the mode selected for one exposure. The capture-mode profile can declare that a selected mode needs its own readout timing; the actual timing values belong to the separate readout-timing contract.
|
|
1864
2011
|
|
|
1865
2012
|
### Schema 0.2.0 compatibility
|
|
1866
2013
|
|
|
1867
|
-
Existing schema `0.2.0` profiles
|
|
2014
|
+
Existing schema `0.2.0` profiles are still accepted, and keep their `schemaVersion: "0.2.0"` identity when parsed. They do not gain or infer a technology family.
|
|
1868
2015
|
|
|
1869
|
-
To
|
|
2016
|
+
To declare `technologyFamily`, use schema `0.3.0`. A `0.2.0` document that includes the new field fails closed, instead of silently applying new meaning under an older schema version.
|
|
1870
2017
|
|
|
1871
|
-
|
|
2018
|
+
Leave unknown facts out rather than guessing them. Architecture metadata is descriptive only. CMOS/CCD, BSI/FSI, stacking, readout family and CFA family do not directly change FOV, crop factor, pixel pitch, exposure, noise, readout timing or dynamic range. Any such effect needs a separate, documented physical or calibration model first.
|
|
1872
2019
|
|
|
1873
2020
|
## Color-sampling topology
|
|
1874
2021
|
|
|
1875
|
-
Use `parseSensorColorSamplingProfile()` when
|
|
2022
|
+
The colour-sampling topology says which colour channel each sensor site measures — for example, the 2×2 tile of a Bayer mosaic. Use `parseSensorColorSamplingProfile()` when the exact topology is known, independently of the descriptive sensor-family metadata:
|
|
1876
2023
|
|
|
1877
2024
|
```ts
|
|
1878
2025
|
import {
|
|
@@ -1921,11 +2068,11 @@ The topology coordinate system is a distinct **native sensor color-sampling-site
|
|
|
1921
2068
|
- +X right and +Y down;
|
|
1922
2069
|
- periodic phase anchored to absolute sensor-site indices.
|
|
1923
2070
|
|
|
1924
|
-
It is
|
|
2071
|
+
It is deliberately **not** `NativeImageRaster`. The native image raster is an effective image-sampling grid. It does not guarantee one sample per physical photodiode, or one sample per colour-filter site. Before RAW/CFA sampling can be simulated, a separate explicit binding must say how a selected capture mode's effective raster maps onto this topology.
|
|
1925
2072
|
|
|
1926
|
-
|
|
2073
|
+
This separation also protects the crop phase. If an active crop starts part-way through a repeating tile, keep the original absolute native site indices. The crop's local `(0,0)` must not silently become a new CFA origin.
|
|
1927
2074
|
|
|
1928
|
-
`siteChannelIds` are
|
|
2075
|
+
`siteChannelIds` are only names for measurement channels. Names such as `red-like`, `green-a` or `clear` do not establish:
|
|
1929
2076
|
|
|
1930
2077
|
- wavelength response;
|
|
1931
2078
|
- quantum efficiency;
|
|
@@ -1936,13 +2083,13 @@ That distinction also protects crop phase. If an active crop begins part-way thr
|
|
|
1936
2083
|
|
|
1937
2084
|
Those belong to later spectral/radiometric contracts.
|
|
1938
2085
|
|
|
1939
|
-
The periodic layout is generic. It can represent a 2×2 Bayer-like phase, larger repeating mosaics, grouped-
|
|
2086
|
+
The periodic layout is generic. It can represent a 2×2 Bayer-like phase, larger repeating mosaics, grouped-colour tiles, RGBW-like layouts or other periodic channel arrangements, without a manufacturer-specific layout enum. The two green positions can share one channel ID, or use separate IDs when a later calibration needs to keep them apart.
|
|
1940
2087
|
|
|
1941
|
-
The exact topology is also separate from `SensorArchitectureProfile.colorSamplingFamily`. A family such as `bayer`, `quad-bayer`, `custom-rgb-mosaic
|
|
2088
|
+
The exact topology is also separate from `SensorArchitectureProfile.colorSamplingFamily`. A family such as `bayer`, `quad-bayer`, `custom-rgb-mosaic` or `layered-color` is descriptive metadata. Photivra never invents an exact repeating tile from a family label.
|
|
1942
2089
|
|
|
1943
2090
|
Monochrome is represented separately with one semantic measurement-channel ID and no spatial mosaic.
|
|
1944
2091
|
|
|
1945
|
-
Layered
|
|
2092
|
+
Layered colour (colours separated by stacked layers rather than a side-by-side mosaic) is deliberately **structural-only** in schema 0.1.0. It uses a separate "spatial relationship not resolved" marker, instead of pretending it shares the periodic site lattice:
|
|
1946
2093
|
|
|
1947
2094
|
```ts
|
|
1948
2095
|
const layered = parseSensorColorSamplingProfile({
|
|
@@ -1969,15 +2116,15 @@ const layered = parseSensorColorSamplingProfile({
|
|
|
1969
2116
|
});
|
|
1970
2117
|
```
|
|
1971
2118
|
|
|
1972
|
-
The engine does not
|
|
2119
|
+
The engine does not resolve layered sites one by one yet. Real layered designs can have different spatial density or registration per layer. Assuming an equal-resolution RGB triplet at every site would be scientifically unsafe.
|
|
1973
2120
|
|
|
1974
|
-
Schema 0.1.0 also does not model sparse exceptions to an otherwise periodic layout, such as phase-detect sites, masked sites, sensor defects
|
|
2121
|
+
Schema 0.1.0 also does not model sparse exceptions to an otherwise periodic layout, such as phase-detect sites, masked sites, sensor defects or other non-periodic overrides.
|
|
1975
2122
|
|
|
1976
|
-
Physical orientation
|
|
2123
|
+
Physical orientation is applied later. The native topology is not rotated or re-phased just because the camera is held vertically. Likewise, changing the CFA topology does not change the physical imaging area, crop factor, FOV or output geometry.
|
|
1977
2124
|
|
|
1978
2125
|
## Capture-mode/color-sampling binding
|
|
1979
2126
|
|
|
1980
|
-
Use `parseNativeEffectiveRasterColorSamplingBindingProfile()` when evidence
|
|
2127
|
+
Use `parseNativeEffectiveRasterColorSamplingBindingProfile()` when you have evidence for how one exact canonical `NativeImageRaster` relates to the separate lattice of native colour-sampling sites.
|
|
1981
2128
|
|
|
1982
2129
|
The first binding is deliberately regular and sensor-anchored:
|
|
1983
2130
|
|
|
@@ -2023,9 +2170,9 @@ const resolvedBinding =
|
|
|
2023
2170
|
});
|
|
2024
2171
|
```
|
|
2025
2172
|
|
|
2026
|
-
A `1 × 1` relationship is still
|
|
2173
|
+
A `1 × 1` relationship is still a **binding that must be asserted and evidenced**. Matching dimensions, matching aspect ratio, output megapixels or a family label such as Bayer never prove that one `NativeImageRaster` sample corresponds to one colour-sampling site.
|
|
2027
2174
|
|
|
2028
|
-
The binding applies only to the exact native raster dimensions recorded in the profile. The first schema supports one native effective sample mapping to a regular rectangular block of
|
|
2175
|
+
The binding applies only to the exact native raster dimensions recorded in the profile. The first schema supports one native effective sample mapping to a regular rectangular block of colour-sampling sites, with a shared native top-left anchor. Irregular relationships would need a later, explicit mapping.
|
|
2029
2176
|
|
|
2030
2177
|
To compose that sensor-level relationship with a capture mode, use `resolveCaptureModeColorSamplingContributors()`:
|
|
2031
2178
|
|
|
@@ -2067,9 +2214,9 @@ console.log(sources.channelSiteCounts);
|
|
|
2067
2214
|
console.log(sources.channelComposition);
|
|
2068
2215
|
```
|
|
2069
2216
|
|
|
2070
|
-
`modeSampleIndexFullFrame` is always
|
|
2217
|
+
`modeSampleIndexFullFrame` is always given in the selected mode's **full-frame, per-frame effective sampling raster**. The API deliberately does not accept crop-local coordinates. If an active crop starts away from the native origin, keep or recover the absolute full-frame mode index before resolving the CFA structure. This stops active crops from silently re-phasing the mosaic.
|
|
2071
2218
|
|
|
2072
|
-
For `grouped-native-samples` modes, group width
|
|
2219
|
+
For `grouped-native-samples` modes, the group width and height alone do not prove where the groups start (the group phase). You must separately provide evidence that the groups are anchored at the top-left of the native effective raster. A future contract can add other explicitly evidenced group phases if needed.
|
|
2073
2220
|
|
|
2074
2221
|
The result is a compact **pre-reconstruction structural source region**:
|
|
2075
2222
|
|
|
@@ -2078,19 +2225,19 @@ The result is a compact **pre-reconstruction structural source region**:
|
|
|
2078
2225
|
- `channelSiteCounts` gives exact counts per semantic channel for monochrome/periodic topology;
|
|
2079
2226
|
- `channelComposition` reports whether the structural region contains one channel or multiple channels.
|
|
2080
2227
|
|
|
2081
|
-
The bridge does **not**
|
|
2228
|
+
The bridge does **not** list every contributing site. It also does not define sum or average weights, spectral response, photon or electron values, or a complete graph of which reconstructed pixels depend on which sites.
|
|
2082
2229
|
|
|
2083
|
-
|
|
2230
|
+
So a mixed-channel region stays mixed. Photivra does not collapse a red/green/blue region into one synthetic CFA channel just because a capture mode groups those native samples. The capture mode's `combinationDomain` stays separate from any weighting or combination equation, none of which is implemented.
|
|
2084
2231
|
|
|
2085
|
-
`declared-effective-raster` modes fail closed even when their dimensions happen to match the native raster. Their relationship was
|
|
2232
|
+
`declared-effective-raster` modes fail closed, even when their dimensions happen to match the native raster. Their relationship was deliberately declared as not simple (in the capture-mode work, #13), so they need a mode-specific colour-site mapping, not a guess from dimensions.
|
|
2086
2233
|
|
|
2087
|
-
Layered-
|
|
2234
|
+
Layered-colour topology also stays unbound, because schema 0.1.0 does not yet define per-layer spatial density and registration.
|
|
2088
2235
|
|
|
2089
|
-
Pixel-shift
|
|
2236
|
+
Pixel-shift (sensor-shift) metadata is kept but does not change CFA assignment. The sensor and its filters move together relative to the optical image. The effect of that shift on optical registration belongs to a later image-formation contract.
|
|
2090
2237
|
|
|
2091
2238
|
## Sensor optical stack
|
|
2092
2239
|
|
|
2093
|
-
Use `parseSensorOpticalStackProfile()` for evidence-backed physical stack metadata
|
|
2240
|
+
The sensor optical stack is the set of layers in front of the photosites: cover glass, infrared-cut filter, anti-aliasing (AA, or optical low-pass) filter and so on. Use `parseSensorOpticalStackProfile()` for evidence-backed physical stack metadata, plus a separately declared **effective anti-aliasing spatial response**:
|
|
2094
2241
|
|
|
2095
2242
|
```ts
|
|
2096
2243
|
import {
|
|
@@ -2167,16 +2314,16 @@ const aaKernel =
|
|
|
2167
2314
|
console.log(aaKernel.components);
|
|
2168
2315
|
```
|
|
2169
2316
|
|
|
2170
|
-
The physical component list and effective AA response are
|
|
2317
|
+
The physical component list and the effective AA response are deliberately separate. A stack may contain birefringent or retarder elements while the net intentional low-pass effect is cancelled, or otherwise absent in practice.
|
|
2171
2318
|
|
|
2172
|
-
`effectiveAntiAliasingSpatialResponse`
|
|
2319
|
+
`effectiveAntiAliasingSpatialResponse` can be omitted, or take one of three kinds:
|
|
2173
2320
|
|
|
2174
2321
|
- omitted — **unknown/unasserted**;
|
|
2175
2322
|
- `kind: "absent"` — evidence says this model should add no intentional AA spatial-splitting term;
|
|
2176
2323
|
- `kind: "present-unresolved"` — an AA effect exists but its spatial response is not defensibly known;
|
|
2177
2324
|
- `kind: "normalized-point-splitting-kernel"` — the caller explicitly supplies a normalized spatial point-splitting approximation.
|
|
2178
2325
|
|
|
2179
|
-
Unknown and absent are not interchangeable.
|
|
2326
|
+
Unknown and absent are not interchangeable. And `absent` does **not** mean the whole sensor stack has an identity PSF, nor that aliasing or moiré are impossible. Lens PSF, cover/filter behaviour, microlenses, sampling topology and scene detail still matter.
|
|
2180
2327
|
|
|
2181
2328
|
The point-splitting kernel is deliberately generic:
|
|
2182
2329
|
|
|
@@ -2186,19 +2333,19 @@ The point-splitting kernel is deliberately generic:
|
|
|
2186
2333
|
- normalized weights must sum to one;
|
|
2187
2334
|
- the weights describe **spatial redistribution only**.
|
|
2188
2335
|
|
|
2189
|
-
They do not represent total optical throughput, spectral transmission, photon efficiency, QE
|
|
2336
|
+
They do not represent total optical throughput, spectral transmission, photon efficiency, QE or fill factor. Those belong to separate radiometric and sensor-response contracts.
|
|
2190
2337
|
|
|
2191
|
-
The first point-splitting scope is explicitly `field-wavelength-polarization-invariant-approximation`.
|
|
2338
|
+
The first point-splitting scope is explicitly `field-wavelength-polarization-invariant-approximation`. A real OLPF (optical low-pass filter) may depend on wavelength, angle of incidence/field position, polarization, materials and stack geometry. Do not treat this generic kernel as a calibrated whole-stack PSF.
|
|
2192
2339
|
|
|
2193
|
-
`orderedComponents` is
|
|
2340
|
+
`orderedComponents` is descriptive metadata, listed in order from incoming light to the sensor. Component roles can include cover glass, IR/UV filtering, anti-reflection (AR) treatment, birefringent low-pass elements, wave plates and other optical filters. Schema 0.1.0 does not derive transmission, refractive focus shift, aberration, thickness effects or a spectral response from those roles.
|
|
2194
2341
|
|
|
2195
|
-
Microlens presence or absence is also descriptive only. A `present` microlens does not by itself
|
|
2342
|
+
Microlens presence or absence is also descriptive only. A `present` microlens does not by itself say anything about angular acceptance, light concentration, fill factor, crosstalk, QE improvement or behaviour across the field.
|
|
2196
2343
|
|
|
2197
|
-
The resolver therefore returns only the **effective AA spatial kernel
|
|
2344
|
+
The resolver therefore returns only the **effective AA spatial kernel**. It states explicitly that cover/filter effects, microlens response, throughput, spectral transmission, and field, wavelength and polarization dependence are not included.
|
|
2198
2345
|
|
|
2199
2346
|
## Sensor sampling aperture
|
|
2200
2347
|
|
|
2201
|
-
Use `parseSensorSamplingApertureProfile()` and `resolveSensorSamplingAperture()` when the
|
|
2348
|
+
Use `parseSensorSamplingApertureProfile()` and `resolveSensorSamplingAperture()` when you know where the colour-site lattice physically sits, and have a geometric approximation of each site's light-sensitive region:
|
|
2202
2349
|
|
|
2203
2350
|
Composition fragment: requires previously validated bindings `binding`, `topology`. This is an integration sketch, not a standalone runnable program.
|
|
2204
2351
|
|
|
@@ -2283,9 +2430,9 @@ The site-center lattice is a distinct physical registration contract:
|
|
|
2283
2430
|
- the first site's center is explicitly registered relative to the physical imaging area's top-left edge;
|
|
2284
2431
|
- the full declared lattice must fit inside the supplied physical imaging area.
|
|
2285
2432
|
|
|
2286
|
-
Photivra does **not** derive this site pitch or origin from `NativeImageRaster`. The native effective raster and the
|
|
2433
|
+
Photivra does **not** derive this site pitch or origin from `NativeImageRaster`. The native effective raster and the colour-site lattice already have a structural binding, but physical placement is a separate fact.
|
|
2287
2434
|
|
|
2288
|
-
The geometric sensitive aperture is separate from site pitch. A
|
|
2435
|
+
The geometric sensitive aperture is separate from site pitch. A single fill fraction cannot recover shape or centering, so schema 0.1.0 stores explicit rectangle dimensions and a center offset.
|
|
2289
2436
|
|
|
2290
2437
|
The first resolved footprint is deliberately narrow:
|
|
2291
2438
|
|
|
@@ -2303,9 +2450,9 @@ geometric sensitive rectangle area
|
|
|
2303
2450
|
nominal site lattice cell area
|
|
2304
2451
|
```
|
|
2305
2452
|
|
|
2306
|
-
This is a **geometry diagnostic**, not a calibrated sensitivity result. It does not establish QE, effective radiometric collection area, photon conversion, optical throughput
|
|
2453
|
+
This is a **geometry diagnostic**, not a calibrated sensitivity result. It does not establish QE, effective radiometric collection area, photon conversion, optical throughput or the true photodiode geometry.
|
|
2307
2454
|
|
|
2308
|
-
Use `kind: "unresolved"` when the geometric footprint is
|
|
2455
|
+
Use `kind: "unresolved"` when the geometric footprint is needed but its shape and dimensions are not defensibly known. Resolution then fails closed, instead of making up a rectangle from site pitch, megapixels or a fill-factor percentage.
|
|
2309
2456
|
|
|
2310
2457
|
The first sampling-aperture model explicitly excludes:
|
|
2311
2458
|
|
|
@@ -2324,7 +2471,7 @@ Physical camera orientation remains downstream. The site lattice and aperture st
|
|
|
2324
2471
|
|
|
2325
2472
|
## Sensor spatial-sampling quadrature
|
|
2326
2473
|
|
|
2327
|
-
Use `calculateSensorSpatialSamplingQuadrature()` to build deterministic
|
|
2474
|
+
Use `calculateSensorSpatialSamplingQuadrature()` to build deterministic spatial integration nodes (sample points with weights) for one resolved colour-sampling site. The nodes do not depend on any renderer. They combine:
|
|
2328
2475
|
|
|
2329
2476
|
1. the geometric sensitive aperture from `SensorSamplingApertureProfile`; and
|
|
2330
2477
|
2. the effective anti-aliasing point-splitting response from `SensorOpticalStackProfile`.
|
|
@@ -2355,63 +2502,66 @@ const quadrature =
|
|
|
2355
2502
|
console.log(quadrature.value.nodes);
|
|
2356
2503
|
```
|
|
2357
2504
|
|
|
2358
|
-
The first scheme is a
|
|
2505
|
+
The first scheme is a uniform midpoint rule on a grid (tensor product) over the geometric aperture. Each AA split component multiplies that aperture quadrature.
|
|
2359
2506
|
|
|
2360
|
-
AA composition uses **inverse source sampling**. If the optical stack
|
|
2507
|
+
AA composition uses **inverse source sampling**. If the optical stack says a component shifts optical energy by `+delta` on the sensor plane, a destination aperture point reads the optical field before the AA filter (pre-AA) at:
|
|
2361
2508
|
|
|
2362
2509
|
```text
|
|
2363
2510
|
pre-AA source = destination aperture point - AA displacement
|
|
2364
2511
|
```
|
|
2365
2512
|
|
|
2366
|
-
The destination sensor site's CFA
|
|
2513
|
+
The destination sensor site's CFA colour channel applies to every node. A shifted pre-AA source coordinate is a location in the optical field, not another CFA site, so it never changes the destination CFA channel.
|
|
2367
2514
|
|
|
2368
2515
|
Two measures are returned separately:
|
|
2369
2516
|
|
|
2370
2517
|
- `combinedNormalizedSpatialWeight` — dimensionless weight for approximating a spatial average;
|
|
2371
2518
|
- `combinedAreaMeasureSquareMicrometers` — geometric area measure for approximating a spatial area integral.
|
|
2372
2519
|
|
|
2373
|
-
The area measure is **not** effective radiometric collection area, throughput, QE
|
|
2520
|
+
The area measure is **not** effective radiometric collection area, throughput, QE or photon count.
|
|
2374
2521
|
|
|
2375
|
-
Near sensor edges, AA inverse-source coordinates may
|
|
2522
|
+
Near the sensor edges, AA inverse-source coordinates may fall outside the active physical imaging area. Those nodes stay valid and are kept. Photivra does not clamp them, drop them or renormalize the remaining weights. The renderer or optical-field provider decides what to do about source coverage.
|
|
2376
2523
|
|
|
2377
|
-
The API
|
|
2524
|
+
The API deliberately returns only geometry and measures. It does not evaluate optical-field values, radiance, spectral response, photons/electrons, temporal exposure, RAW values or reconstruction. Spatial and temporal quadrature stay separate until a later, explicit composition evaluates a common field.
|
|
2378
2525
|
|
|
2379
|
-
|
|
2526
|
+
How large the integration error is depends on the detail (spatial frequencies) in the optical field being integrated. So Photivra does not report a convergence or error estimate from geometry alone. Reference rendering can compare increasing spatial sample counts on the quantity actually being integrated.
|
|
2380
2527
|
|
|
2381
|
-
A per-site
|
|
2528
|
+
A per-site safety limit on the number of nodes stops malformed or untrusted counts from creating an oversized array in one call.
|
|
2382
2529
|
|
|
2383
2530
|
## Sensor spatial-sample reduction
|
|
2384
2531
|
|
|
2385
|
-
Use `reduceSensorSpatialSamplingQuadrature()` after a renderer or optical model has evaluated one scalar value at every spatial quadrature node
|
|
2532
|
+
Use `reduceSensorSpatialSamplingQuadrature()` after a renderer or optical model has evaluated one scalar value at the `preAntiAliasingSourcePointMm` of every spatial quadrature node.
|
|
2386
2533
|
|
|
2387
|
-
Node values are
|
|
2534
|
+
Node values are matched by the node's identity — AA component index plus aperture X/Y sample indices — not by array position.
|
|
2388
2535
|
|
|
2389
2536
|
Two input domains are supported:
|
|
2390
2537
|
|
|
2391
2538
|
- `relative-linear`: a nonnegative dimensionless irradiance-like proxy for deterministic regression/testing;
|
|
2392
2539
|
- `radiometric-irradiance`: physical sensor-plane irradiance in W/m².
|
|
2393
2540
|
|
|
2394
|
-
The physical domain
|
|
2541
|
+
The physical domain uses the standard radiometric meaning. Irradiance is incident radiant flux per unit area, so integrating W/m² over the geometric aperture area gives the incident radiant flux in watts. This is still before CFA spectral response, microlens response, QE, exposure-time integration and sensor conversion.
|
|
2395
2542
|
|
|
2396
|
-
The reducer returns both a normalized spatial average and an area-weighted integral. For relative values the area integral is relative
|
|
2543
|
+
The reducer returns both a normalized spatial average and an area-weighted integral. For relative values the area integral is relative value × µm². For physical irradiance it is converted to watts.
|
|
2397
2544
|
|
|
2398
|
-
The CFA `channelId` is descriptive metadata only. No wavelength-dependent filter
|
|
2545
|
+
The CFA `channelId` is descriptive metadata only. No wavelength-dependent filter or sensor response is applied. The output is therefore **not** a physically colour-filtered mosaic value, and must not be called RAW.
|
|
2399
2546
|
|
|
2400
|
-
Every quadrature node
|
|
2547
|
+
Every quadrature node needs an explicit, non-negative, finite input, including nodes whose pre-AA source coordinate lies outside the active imaging area. The reducer never clamps, zero-fills, extrapolates, drops or renormalizes missing edge support.
|
|
2401
2548
|
|
|
2402
|
-
|
|
2549
|
+
Outside this contract: signed colour-transform intermediates, RGB display values, gamma-encoded values, radiance, spectral irradiance, temporal exposure, photons/electrons, noise, saturation, black level, ADC quantization, RAW codes and demosaicing/reconstruction.
|
|
2403
2550
|
|
|
2404
2551
|
## Sensor spectral response
|
|
2405
2552
|
|
|
2406
|
-
|
|
2553
|
+
The spectral response says how strongly each sensor channel responds at each wavelength.
|
|
2407
2554
|
|
|
2408
|
-
|
|
2555
|
+
- `parseSensorSpectralResponseProfile()` declares the wavelength-dependent response for exact channel IDs from a linked `SensorColorSamplingProfile`.
|
|
2556
|
+
- `resolveSensorSpectralResponseAtWavelength()` evaluates one channel at one wavelength.
|
|
2409
2557
|
|
|
2410
|
-
|
|
2411
|
-
- **direct effective spectral responsivity** — A/W as a distinct physical representation;
|
|
2412
|
-
- **separable channel-filter transmittance × detector external QE** — only when both components are independently established and explicitly declared separable.
|
|
2558
|
+
The first contract supports three kinds of evidence:
|
|
2413
2559
|
|
|
2414
|
-
|
|
2560
|
+
- **direct effective external QE** — a dimensionless response (generated charge per incident photon) for the declared response scope;
|
|
2561
|
+
- **direct effective spectral responsivity** — in A/W (amperes per watt), a different physical representation;
|
|
2562
|
+
- **separable channel-filter transmittance × detector external QE** — only when both parts are independently established and explicitly declared separable.
|
|
2563
|
+
|
|
2564
|
+
Do not split a real measured effective response into filter and detector parts when the source does not support that split. And do not apply upstream optical or filter terms again to a response whose scope already includes them.
|
|
2415
2565
|
|
|
2416
2566
|
Curve rules are intentionally strict:
|
|
2417
2567
|
|
|
@@ -2425,13 +2575,13 @@ Curve rules are intentionally strict:
|
|
|
2425
2575
|
- requests outside the declared wavelength range fail closed;
|
|
2426
2576
|
- Photivra never assumes response becomes zero outside the measured range.
|
|
2427
2577
|
|
|
2428
|
-
Embedded
|
|
2578
|
+
Embedded numeric curves with several points need at least one `reusable-data` or `photivra-owned` evidence record. Publicly viewable or manufacturer curves that are only factual reference material may guide research, but cannot be copied into the public engine without reuse rights.
|
|
2429
2579
|
|
|
2430
|
-
Channel IDs
|
|
2580
|
+
Channel IDs stay exact. If two mosaic positions need different green response curves, the colour topology must use distinct IDs such as `green-a` and `green-b`. One shared `green` ID means one shared channel in this contract.
|
|
2431
2581
|
|
|
2432
|
-
The resolver validates the
|
|
2582
|
+
The resolver validates the whole spectral profile against the linked topology. Unknown channels fail closed. Layered-colour topology still fails closed, because its per-layer spatial relationship is unresolved.
|
|
2433
2583
|
|
|
2434
|
-
Optional reference temperature, incidence angle
|
|
2584
|
+
Optional reference temperature, incidence angle and polarization metadata may record the conditions under which a curve applies. Schema 0.1.0 does not model how the response changes with those conditions.
|
|
2435
2585
|
|
|
2436
2586
|
This foundation performs **spectral lookup only**. It does not:
|
|
2437
2587
|
|
|
@@ -2446,7 +2596,7 @@ Those remain explicit downstream steps.
|
|
|
2446
2596
|
|
|
2447
2597
|
## Sensor spectral quadrature
|
|
2448
2598
|
|
|
2449
|
-
Use `calculateSensorSpectralQuadrature()` to build a deterministic wavelength
|
|
2599
|
+
Use `calculateSensorSpectralQuadrature()` to build a deterministic plan of wavelength samples for one exact sensor-response channel. The plan comes before any source spectrum or sensor response is integrated.
|
|
2450
2600
|
|
|
2451
2601
|
Composition fragment: requires previously validated bindings `colorSamplingProfile`, `spectralResponseProfile`. This is an integration sketch, not a standalone runnable program.
|
|
2452
2602
|
|
|
@@ -2470,24 +2620,27 @@ console.log(plan.value.nodes[0]?.wavelengthNanometers);
|
|
|
2470
2620
|
console.log(plan.value.nodes[0]?.wavelengthMeasureNanometers);
|
|
2471
2621
|
```
|
|
2472
2622
|
|
|
2473
|
-
|
|
2623
|
+
How the plan is built:
|
|
2624
|
+
|
|
2625
|
+
1. The segment boundaries are the edges of the requested range, every applicable knot of the sensor-response curve, and any breakpoints you supply.
|
|
2626
|
+
2. Each segment is split until every midpoint subinterval is no wider than `maximumSubintervalWidthNanometers`.
|
|
2474
2627
|
|
|
2475
|
-
`wavelengthMeasureNanometers` is **dλ in nanometres**. `normalizedWavelengthWeight` is
|
|
2628
|
+
`wavelengthMeasureNanometers` is **dλ in nanometres**. `normalizedWavelengthWeight` is just that wavelength measure divided by the total requested span. Neither value includes QE, A/W responsivity, channel-filter transmission, lens transmission, source power, photon flux or any other physical response factor.
|
|
2476
2629
|
|
|
2477
|
-
The requested wavelength range must lie
|
|
2630
|
+
The requested wavelength range must lie entirely inside the selected channel's usable declared range. Photivra does not clip, extrapolate or silently zero-fill the response outside that range. This check proves only that **the sensor response covers the range**. It does not prove that scene spectral data, optical transmission or any other wavelength-dependent input covers it too.
|
|
2478
2631
|
|
|
2479
|
-
`additionalBreakpointsNanometers` is
|
|
2632
|
+
`additionalBreakpointsNanometers` is for known interpolation knots or discontinuities from other **continuous spectral-density** factors. The values must be strictly increasing, with no duplicates, and strictly inside the requested range. This API does not read those external spectra, and makes no claim about their provenance or validity.
|
|
2480
2633
|
|
|
2481
|
-
The plan
|
|
2634
|
+
The plan keeps the selected response kind, response scope, scientific status, uncertainty declaration and response-data evidence. But it deliberately **does not apply response values at the nodes**. That matters because:
|
|
2482
2635
|
|
|
2483
|
-
- external QE is a dimensionless
|
|
2636
|
+
- external QE is a dimensionless efficiency (generated charge per photon);
|
|
2484
2637
|
- spectral responsivity in A/W maps incident radiant power to electrical current;
|
|
2485
|
-
- those representations
|
|
2486
|
-
- response scope must be matched to the source plane and collection
|
|
2638
|
+
- those two representations need different, compatible source and signal meanings downstream, and must not be collapsed into one integration formula;
|
|
2639
|
+
- the response scope must be matched to the source plane and collection area, so upstream transmission is neither left out nor counted twice.
|
|
2487
2640
|
|
|
2488
|
-
The first quadrature contract
|
|
2641
|
+
The first quadrature contract is for continuous spectral densities. Discrete, delta-like emission lines need a separate explicit representation; they must not be hidden inside a midpoint grid.
|
|
2489
2642
|
|
|
2490
|
-
A bounded wavelength step
|
|
2643
|
+
A bounded wavelength step, aligned to the known response knots, makes sampling deterministic, but does not prove convergence for an arbitrary integrand. The planner therefore reports no numerical error estimate that ignores the source.
|
|
2491
2644
|
|
|
2492
2645
|
This foundation still does **not**:
|
|
2493
2646
|
|
|
@@ -2507,7 +2660,7 @@ Use `reduceSensorSpatioSpectralIrradiance()` after a renderer or optical model h
|
|
|
2507
2660
|
- one spatial quadrature node's `preAntiAliasingSourcePointMm`; and
|
|
2508
2661
|
- one spectral quadrature wavelength.
|
|
2509
2662
|
|
|
2510
|
-
The required source value is
|
|
2663
|
+
The required source value is E_λ(x,y), spectral irradiance, in **W/m²/nm**.
|
|
2511
2664
|
|
|
2512
2665
|
Composition fragment: requires previously validated bindings `sampleValues`, `spatialPlan`, `spectralPlan`. This is an integration sketch, not a standalone runnable program.
|
|
2513
2666
|
|
|
@@ -2535,11 +2688,11 @@ console.log(
|
|
|
2535
2688
|
);
|
|
2536
2689
|
```
|
|
2537
2690
|
|
|
2538
|
-
The reducer validates both plans again at runtime and requires
|
|
2691
|
+
The reducer validates both plans again at runtime, and requires `colorSamplingProfileId` and `channelId` to match exactly. A shared label such as `"red"` is not enough to show that two independently created plans belong to the same colour topology.
|
|
2539
2692
|
|
|
2540
|
-
Each supplied sample identity contains the full spatial-node identity, `spectralSampleIndex
|
|
2693
|
+
Each supplied sample identity contains the full spatial-node identity, `spectralSampleIndex` and the exact wavelength. Array order does not matter. Every spatial × spectral pair must be present exactly once.
|
|
2541
2694
|
|
|
2542
|
-
The Cartesian product has its own **100,000
|
|
2695
|
+
The Cartesian product has its own **safety limit of 100,000 values**. The spatial and spectral plans may each be within their own limits while their product is still too large to build safely.
|
|
2543
2696
|
|
|
2544
2697
|
For each wavelength node, Photivra computes:
|
|
2545
2698
|
|
|
@@ -2551,45 +2704,45 @@ It then multiplies those densities by that node's `wavelengthMeasureNanometers`
|
|
|
2551
2704
|
- wavelength-integrated spatial-average irradiance in W/m²;
|
|
2552
2705
|
- wavelength-integrated geometric-aperture incident flux in W.
|
|
2553
2706
|
|
|
2554
|
-
Because the source density is **per nanometre**, `dλ`
|
|
2707
|
+
Because the source density is **per nanometre**, `dλ` stays in nanometres in this multiplication. Do not multiply by 10⁻⁹ unless the spectral density has also been converted from per-nanometre to per-metre.
|
|
2555
2708
|
|
|
2556
|
-
The geometric-aperture flux
|
|
2709
|
+
The geometric-aperture flux stays a **geometric incident-flux** result. The current aperture model does not establish effective collection area, microlens collection efficiency or any other radiometric area correction.
|
|
2557
2710
|
|
|
2558
|
-
This reducer
|
|
2711
|
+
This reducer deliberately stops **before the sensor response**. The wavelength plan is derived from the response, but no QE, A/W responsivity or channel-filter transmission is applied. The response scope is carried along but not yet matched to a declared source plane. That avoids two invalid shortcuts:
|
|
2559
2712
|
|
|
2560
2713
|
- treating QE as though it were an energy weighting on W/m²/nm;
|
|
2561
2714
|
- treating A/W responsivity as though it were a photon-to-electron efficiency.
|
|
2562
2715
|
|
|
2563
2716
|
Temporal exposure integration, photons/electrons, electrical current, noise, ADC/RAW conversion, and reconstruction also remain downstream.
|
|
2564
2717
|
|
|
2565
|
-
The current AA point-splitting kernel is a normalized
|
|
2718
|
+
The current AA point-splitting kernel is a normalized spatial redistribution approximation that does not vary with wavelength. Spectral variation in the supplied E_λ(x,y) can still represent wavelength-dependent optics or scene structure upstream, but this reducer does not invent wavelength dependence in the AA kernel itself.
|
|
2566
2719
|
|
|
2567
|
-
As with the spatial and spectral planners
|
|
2720
|
+
As with the spatial and spectral planners, no convergence or error estimate is reported independently of the source. Reference work should test convergence on the actual integrand by increasing spatial and/or wavelength sampling.
|
|
2568
2721
|
|
|
2569
2722
|
## Sensor response application compatibility
|
|
2570
2723
|
|
|
2571
|
-
Use `assessSensorResponseApplicationCompatibility()` before the typed sensor-response
|
|
2724
|
+
Use `assessSensorResponseApplicationCompatibility()` before the typed sensor-response conversions. The assessment is deliberately a **structural gate** (a check that it is valid to apply the response). It does not apply the response.
|
|
2572
2725
|
|
|
2573
|
-
It
|
|
2726
|
+
It needs an evidence-backed `SensorResponseApplicationProfile`. That profile binds one exact spectral-response profile and channel to the sampling-aperture and optical-stack identities used by the spatial plan. It also declares:
|
|
2574
2727
|
|
|
2575
2728
|
- the response's incident-area normalization;
|
|
2576
2729
|
- whether a single wavelength-only response can be treated as uniform/separable over the geometric sensitive aperture;
|
|
2577
2730
|
- how response reference conditions are handled.
|
|
2578
2731
|
|
|
2579
|
-
The caller separately declares whether the supplied radiometric field is at the `sensor-package-incident` or `site-incident` response reference plane. That declaration carries provenance.
|
|
2732
|
+
The caller separately declares whether the supplied radiometric field is at the `sensor-package-incident` or the `site-incident` response reference plane. That declaration carries provenance.
|
|
2580
2733
|
|
|
2581
|
-
|
|
2734
|
+
The response scope and the source plane must match exactly:
|
|
2582
2735
|
|
|
2583
2736
|
- `sensor-package-incident-effective-channel-response` requires a sensor-package-incident source;
|
|
2584
2737
|
- site-incident effective response and separable channel-filter × detector-EQE require a site-incident source.
|
|
2585
2738
|
|
|
2586
|
-
The current spatio-spectral reducer integrates **geometric
|
|
2739
|
+
The current spatio-spectral reducer integrates flux over the **geometric sensitive aperture** only. So the first compatibility gate accepts a response only when its incident-area basis is that same geometric sensitive aperture, and its declared area matches. A response normalized to the full site cell, or to an effective collection area, is reported as blocked instead of being silently reused.
|
|
2587
2740
|
|
|
2588
|
-
This
|
|
2741
|
+
This matters for common image-sensor QE conventions. A total-pixel QE can be defined against photons landing on the whole pixel area, so it may already include fill-factor and microlens effects. Such a response cannot be swapped for a curve normalized to photons landing only on the geometric sensitive aperture.
|
|
2589
2742
|
|
|
2590
|
-
Spatial response is a separate gate
|
|
2743
|
+
Spatial response is a separate gate. Reducing the field spatially before applying one scalar response R(λ) assumes the response is effectively uniform (separable) across the integrated aperture. If that is not established, the gate blocks applying the response after spatial reduction, because the physically correct model may need R(λ,x,y) at the original spatial nodes.
|
|
2591
2744
|
|
|
2592
|
-
Reference conditions
|
|
2745
|
+
Reference conditions also fail closed. With `exact-match-required`, the response must declare reference conditions, and the caller must supply a matching operating temperature, incidence angle and/or polarization for every condition in that reference. `assume-compatible` is allowed only as an explicit, evidence-backed approximation with a stated limitation.
|
|
2593
2746
|
|
|
2594
2747
|
An unresolved wavelength basis blocks the gate.
|
|
2595
2748
|
|
|
@@ -2598,13 +2751,13 @@ A successful assessment reports the authorized signal path:
|
|
|
2598
2751
|
- EQE and filter×EQE → `photon-rate-to-electrons`;
|
|
2599
2752
|
- A/W responsivity → `radiant-power-to-current`.
|
|
2600
2753
|
|
|
2601
|
-
Even a successful assessment keeps `signalConversionAuthorized: false`.
|
|
2754
|
+
Even a successful assessment keeps `signalConversionAuthorized: false`. Whether the response is linear over the signal level (dynamic range) is checked by the next gate. No response, photon/electron, current, temporal, noise, ADC, RAW or reconstruction calculation is done here.
|
|
2602
2755
|
|
|
2603
2756
|
## Sensor response operating range
|
|
2604
2757
|
|
|
2605
|
-
Use `assessSensorResponseOperatingRange()` after structural response compatibility has passed. This second gate asks a narrower question: is the **instantaneous optical input** inside a
|
|
2758
|
+
Use `assessSensorResponseOperatingRange()` after structural response compatibility has passed. This second gate asks a narrower question: is the **instantaneous optical input** inside a range where the detector's linearity has been explicitly measured?
|
|
2606
2759
|
|
|
2607
|
-
The operating-range profile is evidence-backed and
|
|
2760
|
+
The operating-range profile is evidence-backed, and keeps the quantity that was actually calibrated:
|
|
2608
2761
|
|
|
2609
2762
|
- wavelength-integrated geometric-aperture radiant power in W; or
|
|
2610
2763
|
- wavelength-integrated spatial-average irradiance in W/m².
|
|
@@ -2620,13 +2773,13 @@ The profile also declares:
|
|
|
2620
2773
|
- scientific status and uncertainty;
|
|
2621
2774
|
- reference temperature/incidence-angle/polarization conditions and their matching policy.
|
|
2622
2775
|
|
|
2623
|
-
The assessment first requires the
|
|
2776
|
+
The assessment first requires the earlier structural compatibility result to be unblocked, and to refer to the same response application, spectral response, colour topology and channel. It then checks the wavelength basis and range, the optical input level, and the conditions of the linearity calibration.
|
|
2624
2777
|
|
|
2625
|
-
The
|
|
2778
|
+
The profile also tells **broadband-only** validation apart from **per-spectral-bin** validation. Broadband-only evidence can show that the total optical input is inside a measured range, but it cannot authorize wavelength-dependent EQE or A/W conversion.
|
|
2626
2779
|
|
|
2627
|
-
For `per-spectral-bin` applicability, the profile declares a maximum supported bin width.
|
|
2780
|
+
For `per-spectral-bin` applicability, the profile declares a maximum supported bin width. Each wavelength node's own integrated power or irradiance contribution must stay inside the calibrated range, and no quadrature bin may be wider than that width. This stops one over-range wavelength band from hiding inside an acceptable broadband total. It also stops a wide numerical bin from passing as a narrowband calibration point.
|
|
2628
2781
|
|
|
2629
|
-
Only
|
|
2782
|
+
Only an unblocked per-bin assessment can set `responseRateConversionAuthorized: true`. That authorization is deliberately limited to an **instantaneous conversion of rates**.
|
|
2630
2783
|
|
|
2631
2784
|
It does **not** establish:
|
|
2632
2785
|
|
|
@@ -2637,47 +2790,51 @@ It does **not** establish:
|
|
|
2637
2790
|
- ADC linearity or clipping;
|
|
2638
2791
|
- RAW-code validity.
|
|
2639
2792
|
|
|
2640
|
-
Those depend on
|
|
2793
|
+
Those depend on integration over time and on downstream electronics, and have their own gates. In particular, a detector can be linear over a given range of incident power while a camera exposure still saturates after a long enough integration time.
|
|
2641
2794
|
|
|
2642
|
-
The first operating-range model also does not infer a valid wavelength interval from one or a few isolated linearity measurements. If a source
|
|
2795
|
+
The first operating-range model also does not infer a valid wavelength interval from one or a few isolated linearity measurements. If a source shows linearity only at specific wavelengths, do not make up a continuous wavelength range from them.
|
|
2643
2796
|
|
|
2644
2797
|
## Photon energy wavelength basis
|
|
2645
2798
|
|
|
2646
|
-
Use `calculatePhotonEnergyFromWavelength()` when a later photon-domain calculation needs
|
|
2799
|
+
Use `calculatePhotonEnergyFromWavelength()` when a later photon-domain calculation needs the photon energy hν.
|
|
2647
2800
|
|
|
2648
|
-
For a vacuum
|
|
2801
|
+
For a wavelength measured in vacuum, Photivra uses the exact SI defining constants:
|
|
2649
2802
|
|
|
2650
|
-
-
|
|
2651
|
-
-
|
|
2803
|
+
- h = 6.62607015 × 10⁻³⁴ J·s (Planck constant);
|
|
2804
|
+
- c = 299792458 m/s (speed of light).
|
|
2652
2805
|
|
|
2653
|
-
The calculation is
|
|
2806
|
+
The calculation is ν = c / λ_vac and E_photon = hν.
|
|
2654
2807
|
|
|
2655
|
-
|
|
2808
|
+
A wavelength measured in **air** cannot be put directly into hc/λ, because light is slightly slower in air. The caller must supply a sourced phase refractive index at that **exact wavelength**, using the explicit definition:
|
|
2656
2809
|
|
|
2657
2810
|
`n = λ_vacuum / λ_air`.
|
|
2658
2811
|
|
|
2659
2812
|
Photivra then computes `λ_vacuum = n × λ_air` before deriving frequency and photon energy.
|
|
2660
2813
|
|
|
2661
|
-
The air refractive-index sample carries scientific status, uncertainty, evidence
|
|
2814
|
+
The air refractive-index sample carries a scientific status, uncertainty, evidence and an optional reference atmosphere. Calibrated samples need a quantified relative uncertainty, plus reference temperature and pressure.
|
|
2662
2815
|
|
|
2663
|
-
|
|
2816
|
+
The refractive index of air also depends on atmospheric conditions. So a photon-energy calculation from an air wavelength needs one of two policies:
|
|
2664
2817
|
|
|
2665
2818
|
- `exact-match-required`: the caller supplies operating atmosphere conditions that match the refractive-index reference temperature/pressure and any declared humidity/CO₂ fields;
|
|
2666
2819
|
- `assume-compatible`: an explicit evidence-backed approximation with a limitation.
|
|
2667
2820
|
|
|
2668
|
-
Photivra never silently sets
|
|
2821
|
+
Photivra never silently sets n = 1.
|
|
2669
2822
|
|
|
2670
|
-
The
|
|
2823
|
+
The foundation keeps the refractive-index uncertainty, but does **not** carry it through into the derived photon energy. It also does not apply QE, calculate a photon rate, generate electrons, integrate over exposure time, or model saturation or noise.
|
|
2671
2824
|
|
|
2672
2825
|
## EQE electron-rate conversion
|
|
2673
2826
|
|
|
2674
|
-
Use `calculateSensorEqeElectronRate()` only after both the structural response-application assessment and the instantaneous
|
|
2827
|
+
This converts light into an expected rate of generated electrons. Use `calculateSensorEqeElectronRate()` only after both gates above have passed: the structural response-application assessment, and the instantaneous operating-range assessment.
|
|
2675
2828
|
|
|
2676
|
-
|
|
2829
|
+
Besides those assessments, the converter takes the original colour-sampling and spectral-response profiles.
|
|
2677
2830
|
|
|
2678
|
-
|
|
2831
|
+
- Spectral planning carries an exact, canonical binding to the response-channel data through the reduction.
|
|
2832
|
+
- The converter re-parses the supplied response once and requires that binding to match exactly.
|
|
2833
|
+
- So matching IDs, or even matching evidence references, are not enough if the numeric calibration samples differ.
|
|
2679
2834
|
|
|
2680
|
-
The
|
|
2835
|
+
The canonical binding is a deterministic identity of the exact data, used to detect stale or swapped calibration data. It is not a cryptographic checksum or a security boundary.
|
|
2836
|
+
|
|
2837
|
+
The calculation stays inside the wavelength quadrature. For every spectral node it computes:
|
|
2681
2838
|
|
|
2682
2839
|
1. radiant-power contribution = geometric-aperture spectral flux density × `dλ`;
|
|
2683
2840
|
2. photon energy (hν), using the photon-energy wavelength-basis foundation;
|
|
@@ -2685,15 +2842,15 @@ The calculation stays in the wavelength quadrature. For every spectral node it c
|
|
|
2685
2842
|
4. effective external QE at that exact wavelength through the existing spectral-response resolver;
|
|
2686
2843
|
5. expected generated-electron rate = photon rate × effective QE.
|
|
2687
2844
|
|
|
2688
|
-
Direct effective EQE stays direct. When the response profile explicitly declares channel-filter transmittance × detector EQE as separable, the existing resolver is the only
|
|
2845
|
+
Direct effective EQE stays direct. When the response profile explicitly declares channel-filter transmittance × detector EQE as separable, the existing resolver is the only place those terms are multiplied. The converter does not invent or repeat filter composition.
|
|
2689
2846
|
|
|
2690
|
-
The response is applied **
|
|
2847
|
+
The response is applied **at each wavelength node**. Photivra never multiplies total broadband radiant power by one average QE.
|
|
2691
2848
|
|
|
2692
|
-
|
|
2849
|
+
Applying the response after spatial reduction also needs the operating-range profile to establish **linear superposition over the geometric sensitive aperture**. If that declaration is missing, it stays `not-established` and rate conversion is blocked. This stops spatial averaging from silently hiding a nonlinear response inside the aperture.
|
|
2693
2850
|
|
|
2694
|
-
The EQE converter
|
|
2851
|
+
The EQE converter also requires the operating-range assessment's per-bin identities and calibrated-domain values to match the exact wavelength nodes in the reduction. A stale assessment cannot be reused after the spectral power, node width, wavelength or identity changes.
|
|
2695
2852
|
|
|
2696
|
-
For air-
|
|
2853
|
+
For air-wavelength spectral nodes, the caller supplies exactly one sourced phase-refractive-index record per spectral sample identity, plus the atmosphere compatibility policy used for photon energy. Vacuum-wavelength conversion accepts no air-index context.
|
|
2697
2854
|
|
|
2698
2855
|
The output reports:
|
|
2699
2856
|
|
|
@@ -2702,9 +2859,9 @@ The output reports:
|
|
|
2702
2859
|
- per-wavelength power, photon energy, QE and rate contributions;
|
|
2703
2860
|
- whether channel-filter transmission was part of an explicitly separable response.
|
|
2704
2861
|
|
|
2705
|
-
Kahan compensated summation is used across wavelength nodes.
|
|
2862
|
+
Kahan compensated summation (which limits rounding error) is used across wavelength nodes.
|
|
2706
2863
|
|
|
2707
|
-
This is still **
|
|
2864
|
+
This is still **rates only**. The converter does not:
|
|
2708
2865
|
|
|
2709
2866
|
- multiply by exposure time;
|
|
2710
2867
|
- output photon or electron counts;
|
|
@@ -2713,15 +2870,23 @@ This is still **rate-domain only**. The converter does not:
|
|
|
2713
2870
|
- calculate current from A/W responsivity;
|
|
2714
2871
|
- perform ADC/RAW conversion or reconstruction.
|
|
2715
2872
|
|
|
2716
|
-
A/W spectral responsivity is
|
|
2873
|
+
This API rejects A/W spectral responsivity; that is a separate radiant-power → current path (below).
|
|
2717
2874
|
|
|
2718
|
-
Response, refractive-index and quadrature uncertainties
|
|
2875
|
+
Response, refractive-index and quadrature uncertainties stay visible upstream, but are not yet combined into an electron-rate uncertainty.
|
|
2719
2876
|
|
|
2720
2877
|
## A/W responsivity photocurrent conversion
|
|
2721
2878
|
|
|
2722
|
-
Use `calculateSensorResponsivityPhotocurrent()` for the sibling current
|
|
2879
|
+
Use `calculateSensorResponsivityPhotocurrent()` for the sibling path, which works in electrical current, when the spectral response is explicitly calibrated in **A/W**.
|
|
2880
|
+
|
|
2881
|
+
The same structural and operating-range gates used by EQE still apply:
|
|
2723
2882
|
|
|
2724
|
-
|
|
2883
|
+
- exact linkage between response and profiles;
|
|
2884
|
+
- matching response reference plane and area basis;
|
|
2885
|
+
- spatial response uniformity/separability;
|
|
2886
|
+
- linear superposition over the geometric aperture;
|
|
2887
|
+
- explicit per-spectral-bin operating-range applicability.
|
|
2888
|
+
|
|
2889
|
+
The exact canonical response-channel binding must also match the curve data used upstream. The same IDs and evidence with altered A/W samples are rejected.
|
|
2725
2890
|
|
|
2726
2891
|
For every spectral node Photivra computes:
|
|
2727
2892
|
|
|
@@ -2731,19 +2896,19 @@ For every spectral node Photivra computes:
|
|
|
2731
2896
|
|
|
2732
2897
|
The wavelength contributions are summed with Kahan compensated summation.
|
|
2733
2898
|
|
|
2734
|
-
A/W also has an
|
|
2899
|
+
A/W also has an extra **electrical calibration applicability** contract. `SensorResponsivityElectricalApplicabilityProfile` binds the response pipeline to the electrical conditions of the detector under which the A/W calibration is meant to apply:
|
|
2735
2900
|
|
|
2736
2901
|
- zero-bias photovoltaic operation or reverse bias with an explicit magnitude;
|
|
2737
2902
|
- virtual-ground current readout or a finite declared input impedance;
|
|
2738
2903
|
- exact-match or explicit evidence-backed compatibility-approximation policy.
|
|
2739
2904
|
|
|
2740
|
-
These fields
|
|
2905
|
+
These fields say when the calibration applies; they are not a circuit simulator. In particular, Photivra does not use a declared input impedance to claim that a transimpedance circuit is adequate. It only checks that the operating condition matches the calibration contract.
|
|
2741
2906
|
|
|
2742
|
-
The electrical categories
|
|
2907
|
+
The electrical categories do not overlap. `reverse-biased` needs a strictly positive reverse-bias magnitude; true zero-bias operation uses `zero-bias-photovoltaic`. A finite input impedance must also be strictly greater than zero.
|
|
2743
2908
|
|
|
2744
|
-
The spectral A/W curve is treated as a **quasi-static steady-state** power→current relation
|
|
2909
|
+
The spectral A/W curve is treated only as a **quasi-static, steady-state** power → current relation. This first path does not model detector impulse response, modulation bandwidth, settling time or frequency-dependent responsivity. A future integrator for time-varying current must add a separate temporal-response contract before it can handle rapidly changing optical power.
|
|
2745
2910
|
|
|
2746
|
-
The result is a **
|
|
2911
|
+
The result is a **non-negative photocurrent magnitude at the detector terminal, in amperes**. Photivra deliberately does not assign a circuit direction (polarity) from the optical response curve.
|
|
2747
2912
|
|
|
2748
2913
|
The A/W path does **not**:
|
|
2749
2914
|
|
|
@@ -2761,15 +2926,15 @@ Those remain later explicit stages.
|
|
|
2761
2926
|
|
|
2762
2927
|
## Local sensor-rate/exposure binding
|
|
2763
2928
|
|
|
2764
|
-
Use `bindSensorRateToLocalExposure()` before any response-rate result is integrated over time.
|
|
2929
|
+
Each sensor site can have its own exposure window (for example with a rolling shutter). Use `bindSensorRateToLocalExposure()` to find that window before any response-rate result is integrated over time.
|
|
2765
2930
|
|
|
2766
|
-
|
|
2931
|
+
EQE electron-rate and A/W photocurrent results produced by the engine keep the exact abstract colour-sampling site that generated them. The binding resolves that site against the exact colour topology and the existing native-effective-raster / colour-site binding, then selects the local shutter window.
|
|
2767
2932
|
|
|
2768
|
-
The first
|
|
2933
|
+
The first timing registration is deliberately narrow: `sitesPerNativeSampleX` and `sitesPerNativeSampleY` must both equal 1. Under that evidenced shared-top-left relationship, colour site `(x, y)` maps to the native effective sample center `(x + 0.5, y + 0.5)`.
|
|
2769
2934
|
|
|
2770
|
-
A multi-site
|
|
2935
|
+
A binding of multi-site blocks is **not** enough to infer timing coordinates inside one sample. When one native effective sample spans several colour sites, Photivra fails closed, rather than inventing where each site lies inside that sample for a shutter scan that varies across the sensor.
|
|
2771
2936
|
|
|
2772
|
-
The binding then calls the authoritative capture exposure-window calculation at that exact native point. It
|
|
2937
|
+
The binding then calls the authoritative capture exposure-window calculation at that exact native point. It checks that:
|
|
2773
2938
|
|
|
2774
2939
|
- the rate carries a valid site;
|
|
2775
2940
|
- the bound color site exists and resolves to the same channel as the rate;
|
|
@@ -2786,11 +2951,11 @@ This is a **timing binding only**. It explicitly reports:
|
|
|
2786
2951
|
- no time-stationarity guarantee;
|
|
2787
2952
|
- `constantRateTemporalIntegrationAuthorized: false`.
|
|
2788
2953
|
|
|
2789
|
-
|
|
2954
|
+
So a response rate may **not** yet be multiplied by this duration. Motion, flicker, flash, time-varying illumination or vignetting, shutter modulation and detector transients all need a time-dependent signal model. The constant-rate approximation needs a separate stationarity contract (next section).
|
|
2790
2955
|
|
|
2791
2956
|
## Constant-rate local-exposure integration
|
|
2792
2957
|
|
|
2793
|
-
Use `integrateStationarySensorRateOverLocalExposure()` only when an evidence-backed `SensorRateTemporalStationarityProfile` explicitly states that the **reported rate itself**
|
|
2958
|
+
Use `integrateStationarySensorRateOverLocalExposure()` only when an evidence-backed `SensorRateTemporalStationarityProfile` explicitly states that the **reported rate itself** stays constant through the exact local exposure window.
|
|
2794
2959
|
|
|
2795
2960
|
The stationarity declaration is bound to:
|
|
2796
2961
|
|
|
@@ -2817,37 +2982,40 @@ For the A/W path:
|
|
|
2817
2982
|
- circuit polarity is still not inferred;
|
|
2818
2983
|
- carrier/electron count is deliberately **not** inferred from A/W current.
|
|
2819
2984
|
|
|
2820
|
-
The A/W constant-current approximation also inherits the
|
|
2985
|
+
The A/W constant-current approximation also inherits the quasi-static detector-response limit from upstream. The stationarity evidence must justify treating the reported steady-state photocurrent as valid throughout the window. Photivra still does not model detector impulse response or bandwidth.
|
|
2821
2986
|
|
|
2822
|
-
The result
|
|
2987
|
+
The result accumulates **photo signal only**. It does not include dark current, hot-pixel/defect current, leakage, charge injection or other sources of accumulated charge. So:
|
|
2823
2988
|
|
|
2824
2989
|
- physical full-well assessment is not authorized;
|
|
2825
2990
|
- camera/digital saturation assessment is not authorized;
|
|
2826
2991
|
- no clamp is applied;
|
|
2827
2992
|
- shot/read noise, conversion gain, ADC/RAW and reconstruction remain downstream.
|
|
2828
2993
|
|
|
2829
|
-
Physical full-well capacity and camera saturation capacity
|
|
2994
|
+
Physical full-well capacity and camera saturation capacity are separate, implemented assessment contracts. They are not interchangeable thresholds.
|
|
2830
2995
|
|
|
2831
2996
|
## Dark-current charge
|
|
2832
2997
|
|
|
2833
|
-
Use `calculateSensorDarkCurrentCharge()` with a stationary EQE expected-count exposure result or the validated compact [temporal photo signal](TEMPORAL_PHOTO_RAW.md).
|
|
2998
|
+
Dark current is signal the sensor generates by heat alone, even without light. Use `calculateSensorDarkCurrentCharge()` with either a stationary EQE expected-count exposure result, or the validated compact [temporal photo signal](https://github.com/Photivra/photivra/blob/main/docs/TEMPORAL_PHOTO_RAW.md).
|
|
2999
|
+
|
|
3000
|
+
- Temporal dark charge keeps the exact temporal event identity, without a stationarity ID.
|
|
3001
|
+
- The model is **thermally generated electrons before any compensation**, in e⁻/s. It integrates that rate over the exact local exposure duration already set by the temporal pipeline.
|
|
2834
3002
|
|
|
2835
|
-
Photivra deliberately does not impose one universal dark
|
|
3003
|
+
Photivra deliberately does not impose one universal law for how dark current changes with temperature. A dark-current profile may provide either:
|
|
2836
3004
|
|
|
2837
3005
|
- one exact reference temperature and one dark-current rate; or
|
|
2838
3006
|
- a strictly increasing measured temperature table with piecewise-linear interpolation and fail-closed behavior outside the measured range.
|
|
2839
3007
|
|
|
2840
|
-
A population-mean dark-current profile is allowed only as an explicit approximation
|
|
3008
|
+
A population-mean dark-current profile (one average for all sites) is allowed only as an explicit approximation. It must not claim that site-to-site dark-current variation is modelled. Exact-site profiles stay bound to the exact colour-sampling site.
|
|
2841
3009
|
|
|
2842
|
-
Dark-current compensation, black-level offset, hot-pixel/defect excess, leakage, charge injection and other charge sources are separate. The result is an **expected dark-electron count
|
|
3010
|
+
Dark-current compensation, black-level offset, hot-pixel/defect excess, leakage, charge injection and other charge sources are separate. The result is only an **expected dark-electron count**. No Poisson sampling, and no dark-current shot noise, is applied.
|
|
2843
3011
|
|
|
2844
|
-
This stage still does not
|
|
3012
|
+
This stage still does not allow a physical full-well assessment, because the accumulated sensor charge is not complete yet. It also does not apply to the A/W current path: integrated charge at the detector terminal must not be reinterpreted as stored sensor electrons without a separate carrier/storage mapping.
|
|
2845
3013
|
|
|
2846
3014
|
## Accumulated-charge completeness
|
|
2847
3015
|
|
|
2848
|
-
Use `composeSensorAccumulatedCharge()`
|
|
3016
|
+
This step adds up everything stored in a pixel during one exposure. Use `composeSensorAccumulatedCharge()` once you have an EQE photo-signal exposure and its matching dark-current result for the **same exact local exposure event**.
|
|
2849
3017
|
|
|
2850
|
-
The
|
|
3018
|
+
The photo and dark terms must share:
|
|
2851
3019
|
|
|
2852
3020
|
- color topology/channel/site;
|
|
2853
3021
|
- local-exposure binding ID;
|
|
@@ -2855,11 +3023,11 @@ The composer requires photo and dark terms to share:
|
|
|
2855
3023
|
- exact start/end offsets in the `first-opening-boundary-phase` time basis;
|
|
2856
3024
|
- the same local exposure duration.
|
|
2857
3025
|
|
|
2858
|
-
|
|
3026
|
+
An equal duration alone is not enough.
|
|
2859
3027
|
|
|
2860
|
-
|
|
3028
|
+
Extra stored-electron components may represent explicitly modelled defect/hot-pixel excess, leakage, charge injection, clock-induced charge or another identified source. Every component must declare the accounting meaning `incremental-stored-electrons-beyond-photo-and-modeled-dark-current`. This stops an extra defect or leakage model from silently counting electrons that the dark-current term already includes.
|
|
2861
3029
|
|
|
2862
|
-
A separate evidence-backed `SensorAccumulatedChargeCompletenessProfile` lists the exact
|
|
3030
|
+
A separate, evidence-backed `SensorAccumulatedChargeCompletenessProfile` lists the exact extra-component IDs, and states that every material contributor of stored electrons, relevant to physical charge-storage capacity, is accounted for. An empty list of extra components is therefore **not, by itself, evidence of completeness**.
|
|
2863
3031
|
|
|
2864
3032
|
When all bindings and the completeness declaration pass, the result reports:
|
|
2865
3033
|
|
|
@@ -2868,15 +3036,15 @@ When all bindings and the completeness declaration pass, the result reports:
|
|
|
2868
3036
|
- expected additional stored electrons;
|
|
2869
3037
|
- total expected stored electrons.
|
|
2870
3038
|
|
|
2871
|
-
All
|
|
3039
|
+
All of these are expected values. No photo or dark shot noise is sampled, and no whole-number carrier counts are drawn.
|
|
2872
3040
|
|
|
2873
|
-
Only this complete stored-electron result sets `physicalFullWellAssessmentAuthorized: true`. Camera/digital saturation
|
|
3041
|
+
Only this complete stored-electron result sets `physicalFullWellAssessmentAuthorized: true`. Camera/digital saturation stays unauthorized here, because camera saturation capacity is not the same physical quantity as full-well charge storage.
|
|
2874
3042
|
|
|
2875
|
-
A/W photocharge
|
|
3043
|
+
A/W photocharge stays outside this composition, because coulombs at the detector terminal are not automatically the number of electrons stored in the pixel's charge well.
|
|
2876
3044
|
|
|
2877
3045
|
## Physical charge-capacity
|
|
2878
3046
|
|
|
2879
|
-
Use `assessSensorPhysicalChargeCapacity()` only after accumulated-charge completeness has explicitly authorized physical capacity assessment.
|
|
3047
|
+
Full-well capacity is the most charge a pixel can physically store. Use `assessSensorPhysicalChargeCapacity()` only after accumulated-charge completeness has explicitly authorized a physical capacity assessment.
|
|
2880
3048
|
|
|
2881
3049
|
A `SensorPhysicalChargeCapacityProfile` describes a sourced physical charge-storage limit in **electrons**. The profile is bound to:
|
|
2882
3050
|
|
|
@@ -2885,7 +3053,7 @@ A `SensorPhysicalChargeCapacityProfile` describes a sourced physical charge-stor
|
|
|
2885
3053
|
- an evidence-backed operating-state identifier;
|
|
2886
3054
|
- exact reference temperature, or an explicit approximation stating temperature dependence is not modeled.
|
|
2887
3055
|
|
|
2888
|
-
The operating-state identifier is deliberately opaque. Photivra does not
|
|
3056
|
+
The operating-state identifier is deliberately opaque. Photivra does not guess the physical storage capacity from ISO, gain labels, capture-mode names, pixel pitch or any other nearby specification.
|
|
2889
3057
|
|
|
2890
3058
|
The assessment compares **total expected stored electrons** with the physical capacity and reports:
|
|
2891
3059
|
|
|
@@ -2894,15 +3062,15 @@ The assessment compares **total expected stored electrons** with the physical ca
|
|
|
2894
3062
|
- `below-capacity`, `at-capacity`, or `above-capacity`;
|
|
2895
3063
|
- whether the unsaturated expected charge exceeds the declared capacity.
|
|
2896
3064
|
|
|
2897
|
-
This
|
|
3065
|
+
This compares expected values; it does not simulate random saturation. Expected charge below capacity does not prove that every noisy realization stays below capacity. Expected charge above capacity means the linear expectation, before any saturation, exceeds the physical storage limit.
|
|
2898
3066
|
|
|
2899
|
-
Photivra does **not** clamp the charge at capacity. It does not calculate
|
|
3067
|
+
Photivra does **not** clamp the charge at capacity. It does not calculate stored charge after saturation, overflow charge, recombination, anti-blooming drain behaviour or blooming into neighbours. Those need a separate nonlinear charge-transfer model.
|
|
2900
3068
|
|
|
2901
|
-
Physical charge capacity is also **not camera saturation capacity**.
|
|
3069
|
+
Physical charge capacity is also **not camera saturation capacity**. Clipping in the camera's analog or digital chain can happen at a lower signal level, so camera saturation is a separate contract (next section).
|
|
2902
3070
|
|
|
2903
3071
|
## Camera saturation capacity
|
|
2904
3072
|
|
|
2905
|
-
Use `assessSensorCameraSaturationCapacity()` for a
|
|
3073
|
+
Use `assessSensorCameraSaturationCapacity()` for a **saturation capacity of the camera's response chain**, taken from a specific source and expressed as dark-corrected photo signal in electron equivalents.
|
|
2906
3074
|
|
|
2907
3075
|
This contract intentionally uses a different signal domain from physical charge storage:
|
|
2908
3076
|
|
|
@@ -2910,11 +3078,11 @@ This contract intentionally uses a different signal domain from physical charge
|
|
|
2910
3078
|
- total photo + dark + other stored charge is retained only as a diagnostic;
|
|
2911
3079
|
- total stored charge is **not** added to the camera-signal capacity comparison.
|
|
2912
3080
|
|
|
2913
|
-
That separation
|
|
3081
|
+
That separation follows camera-characterization practice. There, saturation capacity is derived from the camera's saturated photo response, and can be lower than physical full well because a later stage may clip first.
|
|
2914
3082
|
|
|
2915
|
-
A camera saturation profile is bound to the exact
|
|
3083
|
+
A camera saturation profile is bound to the exact colour/channel, site applicability, operating-state identifier, temperature applicability, measurement method, uncertainty and evidence. A mean over a channel's population used for one site stays an approximation.
|
|
2916
3084
|
|
|
2917
|
-
The assessment reports photo-signal/capacity ratio, headroom, and below/at/above-capacity status. It does not:
|
|
3085
|
+
The assessment reports the photo-signal/capacity ratio, the headroom, and a below/at/above-capacity status. It does not:
|
|
2918
3086
|
|
|
2919
3087
|
- identify which analog or digital stage limits the response;
|
|
2920
3088
|
- infer an ADC maximum code or bit-depth clipping threshold;
|
|
@@ -2924,23 +3092,23 @@ The assessment reports photo-signal/capacity ratio, headroom, and below/at/above
|
|
|
2924
3092
|
- calculate a post-saturation transfer curve;
|
|
2925
3093
|
- establish stochastic saturation probability.
|
|
2926
3094
|
|
|
2927
|
-
|
|
3095
|
+
Analog clipping at a specific stage, ADC/digital code limits and RAW-code behaviour need separate, evidence-backed response-chain contracts.
|
|
2928
3096
|
|
|
2929
3097
|
## Stochastic charge, conversion, ADC, and RAW code
|
|
2930
3098
|
|
|
2931
|
-
Use `simulateSensorChargeRealization()` only after the
|
|
3099
|
+
This is where random noise enters, and where charge becomes a RAW number. Use `simulateSensorChargeRealization()` only after the accumulated-charge completeness contract has produced the complete expected stored charge for one exact sensor site and exposure.
|
|
2932
3100
|
|
|
2933
|
-
The first stochastic layer keeps
|
|
3101
|
+
The first random (stochastic) layer keeps expected values separate from realizations (random draws):
|
|
2934
3102
|
|
|
2935
3103
|
- photo-generated electrons use an explicit Poisson model;
|
|
2936
3104
|
- dark-generated electrons use an explicit Poisson model;
|
|
2937
3105
|
- each additional stored-charge component must declare its own Poisson or deterministic electron-equivalent policy;
|
|
2938
|
-
- the realization uses an explicit deterministic 32-bit seed;
|
|
3106
|
+
- the realization uses an explicit deterministic 32-bit seed, scrambled by the MurmurHash3 `fmix32` finalizer so nearby seeds give unrelated streams;
|
|
2939
3107
|
- electronic read noise is still downstream.
|
|
2940
3108
|
|
|
2941
3109
|
The stochastic realization does not change the upstream expected charge or its provenance.
|
|
2942
3110
|
|
|
2943
|
-
Use `parseSensorReadoutConversionProfile()` and `resolveSensorReadoutRegime()` for the
|
|
3111
|
+
Use `parseSensorReadoutConversionProfile()` and `resolveSensorReadoutRegime()` for the electronic chain that follows. A regime declares:
|
|
2944
3112
|
|
|
2945
3113
|
- system conversion gain in electrons per code;
|
|
2946
3114
|
- explicit input-referred electronic read-noise components in RMS electrons;
|
|
@@ -2954,9 +3122,11 @@ Regime selection is explicitly:
|
|
|
2954
3122
|
|
|
2955
3123
|
`explicit-upstream-camera-state-not-inferred-from-iso`
|
|
2956
3124
|
|
|
2957
|
-
ISO
|
|
3125
|
+
So ISO never silently chooses conversion gain, read noise or ADC behaviour. The separate [generic ISO signal-chain contract](#generic-high-iso-signal-chain-profiles) (#7) maps an explicit camera ISO/capture-mode state to a documented readout regime (#14). The sensor readout contract itself still does not infer that mapping from the ISO number.
|
|
3126
|
+
|
|
3127
|
+
`calculateExpectedSensorReadout()` stays an expected-value path. It reports zero-mean read noise and does not quantize. If the upstream expected stored charge is already above physical storage capacity, it fails closed, because the physical-capacity assessment does not define a linear charge value after saturation.
|
|
2958
3128
|
|
|
2959
|
-
|
|
3129
|
+
Both readout paths require the physical-capacity assessment to have been computed for the same charge. Besides a matching identity, its total expected stored electrons must equal the charge's total exactly (the assessment copies that total verbatim). A stale assessment therefore fails closed. On both the expected and the stochastic path, digital saturation is reported when the code reaches (`>=`) the digital saturation code, so a sample quantized exactly to the white level is flagged.
|
|
2960
3130
|
|
|
2961
3131
|
`simulateSensorRawCode()` consumes a stochastic charge realization plus the matching physical-capacity assessment. Its stage order is explicit:
|
|
2962
3132
|
|
|
@@ -2972,15 +3142,58 @@ realized stored charge
|
|
|
2972
3142
|
→ RAW code
|
|
2973
3143
|
```
|
|
2974
3144
|
|
|
2975
|
-
The physical storage clamp is a bounded scalar approximation
|
|
3145
|
+
The physical storage clamp is only a bounded scalar approximation. Overflow is reported as a diagnostic. `bloomingModeled` stays false, because neighbour topology, charge transport and anti-blooming behaviour are not modelled.
|
|
3146
|
+
|
|
3147
|
+
Physical storage capacity, pre-ADC saturation and digital/ADC saturation are independent thresholds, and may disagree.
|
|
3148
|
+
|
|
3149
|
+
Quantization rounds to the nearest code, with exact ties going towards +∞. A regime may also declare:
|
|
2976
3150
|
|
|
2977
|
-
|
|
3151
|
+
- `adc.digitalGain` (≥ 1), applied around black after quantization and then re-quantized;
|
|
3152
|
+
- `adc.integralNonLinearity`, a piecewise-linear INL (integral non-linearity) in codes.
|
|
3153
|
+
|
|
3154
|
+
### ISO-dependent gain regimes
|
|
3155
|
+
|
|
3156
|
+
`createIsoGainReadoutRegimes()` derives one explicit regime per ISO setting from declared sensor parameters. ISO then
|
|
3157
|
+
changes RAW brightness the way analog/digital gain does (exposure index of CIPA DC-004; EMVA 1288 linear model):
|
|
3158
|
+
|
|
3159
|
+
```ts
|
|
3160
|
+
import { createIsoGainReadoutRegimes, parseSensorReadoutConversionProfile } from "@photivra/engine";
|
|
3161
|
+
|
|
3162
|
+
const evidence = [{ sourceOrigin: "photivra", sourceReference: "my-sensor-design", reuseStatus: "photivra-owned" }] as const;
|
|
3163
|
+
const { regimes, standardRegimeBands } = createIsoGainReadoutRegimes({
|
|
3164
|
+
regimeIdPrefix: "", scientificStatus: "approximation", baseExposureIndex: 100,
|
|
3165
|
+
exposureIndices: [100, 200, 400, 800, 1600, 3200],
|
|
3166
|
+
baseSaturationElectronEquivalent: { value: 30000, evidence }, // electrons at ADC white, ISO 100
|
|
3167
|
+
maximumAnalogGain: { value: 16, evidence }, // ISO 3200 adds 2x digital gain
|
|
3168
|
+
adc: { bitDepth: 14, blackLevelCode: 1024, digitalSaturationCode: 16383 },
|
|
3169
|
+
conversionGainModes: [
|
|
3170
|
+
{ mode: "low-conversion-gain", minimumExposureIndex: 100, readNoiseRmsElectrons: { value: 3, evidence } },
|
|
3171
|
+
{ mode: "high-conversion-gain", minimumExposureIndex: 800, readNoiseRmsElectrons: { value: 1.2, evidence } }
|
|
3172
|
+
],
|
|
3173
|
+
evidence, limitations: ["Illustrative design values, not a measured camera."]
|
|
3174
|
+
});
|
|
3175
|
+
const readoutProfile = parseSensorReadoutConversionProfile({ schemaVersion: "0.1.0", profileId: "my-readout",
|
|
3176
|
+
colorSamplingProfileId: "my-cfa", channelId: "green",
|
|
3177
|
+
regimeSelectionOwnedBy: "explicit-upstream-camera-state-not-inferred-from-iso", regimes, evidence });
|
|
3178
|
+
// standardRegimeBands binds each ISO to its regime in a GenericIsoSignalChainProfile.
|
|
3179
|
+
```
|
|
3180
|
+
|
|
3181
|
+
### Fixed-pattern noise
|
|
3182
|
+
|
|
3183
|
+
Fixed-pattern noise is the part of sensor noise that is the same in every frame from one sensor.
|
|
3184
|
+
`parseSensorFixedPatternNoiseProfile()` declares an opt-in pattern for one sensor instance: PRNU and DSNU (pixel-to-pixel
|
|
3185
|
+
variation in light response and in dark signal), hot pixels, and row and column offsets.
|
|
3186
|
+
|
|
3187
|
+
- The pattern is seeded by `sensorInstanceSeedUint32`, independent of the capture noise seed.
|
|
3188
|
+
- Pass the same profile as `fixedPatternNoise` to `simulateSensorChargeRealization()` and `simulateSensorRawCode()`, or on every producer site.
|
|
3189
|
+
- `calculateSensorFixedPatternSite()` returns the deterministic values for one colour-sampling site.
|
|
3190
|
+
- The generic tier presets carry a synthetic profile in `body.fixedPatternNoise`.
|
|
2978
3191
|
|
|
2979
3192
|
## RAW capture samples and reconstruction
|
|
2980
3193
|
|
|
2981
|
-
Use `createSensorRawCaptureSample()` to bind one generated RAW code to the existing capture-mode/
|
|
3194
|
+
Use `createSensorRawCaptureSample()` to bind one generated RAW code to the existing capture-mode / colour-sampling structure.
|
|
2982
3195
|
|
|
2983
|
-
Schema 0.1.0
|
|
3196
|
+
Schema 0.1.0 deliberately allows only an ungrouped native-effective capture-mode sample that maps to exactly one colour-sampling site. Grouped, binning and remosaic modes fail closed, because knowing how many sites contribute does not define the weights or the analog/digital maths for combining them.
|
|
2984
3197
|
|
|
2985
3198
|
The RAW sample preserves:
|
|
2986
3199
|
|
|
@@ -2992,11 +3205,11 @@ The RAW sample preserves:
|
|
|
2992
3205
|
- charge/read-noise seeds;
|
|
2993
3206
|
- physical/digital saturation diagnostics.
|
|
2994
3207
|
|
|
2995
|
-
Physical orientation and output rotation
|
|
3208
|
+
Physical orientation and output rotation come later, so CFA phase is never reset by portrait orientation or an active crop.
|
|
2996
3209
|
|
|
2997
|
-
Use `parseSensorRawReconstructionProfile()` and `resolveSensorRawReconstruction()` for the first reconstruction contract. The profile is tied to one exact capture-mode ID and
|
|
3210
|
+
Use `parseSensorRawReconstructionProfile()` and `resolveSensorRawReconstruction()` for the first reconstruction contract (turning mosaic samples into full-colour pixels). The profile is tied to one exact capture-mode ID and colour-sampling profile ID. It declares explicit linear weights over the native neighbourhood, per output channel.
|
|
2998
3211
|
|
|
2999
|
-
Every input RAW sample is re-
|
|
3212
|
+
Every input RAW sample is re-checked against the declared colour-sampling topology. A stored channel label that disagrees with the CFA at that native site fails closed.
|
|
3000
3213
|
|
|
3001
3214
|
The first reconstruction contract:
|
|
3002
3215
|
|
|
@@ -3006,11 +3219,65 @@ The first reconstruction contract:
|
|
|
3006
3219
|
- does not infer branded Bayer/X-Trans/remosaic behavior from a topology label;
|
|
3007
3220
|
- reports `aliasingModeled: false` and `moireModeled: false`.
|
|
3008
3221
|
|
|
3009
|
-
|
|
3222
|
+
Claims about aliasing or moiré need an adequate model of the spatial frequencies in the scene and optics before sampling. Reconstruction artefacts alone are not treated as proof that such a physical aliasing model exists.
|
|
3223
|
+
|
|
3224
|
+
## RAW development, white level, highlights and tone curves
|
|
3225
|
+
|
|
3226
|
+
RAW samples also carry:
|
|
3227
|
+
|
|
3228
|
+
- `effectiveSaturationCode` — the code at which the site is fully saturated (see `calculateEffectiveSaturationCode`);
|
|
3229
|
+
- `systemConversionGainElectronsPerCode`;
|
|
3230
|
+
- `readNoiseRmsElectrons`.
|
|
3231
|
+
|
|
3232
|
+
Packed native RAW carries `effectiveSaturationCodes` once per CFA phase.
|
|
3233
|
+
|
|
3234
|
+
Development ("developing" a RAW into a viewable image):
|
|
3235
|
+
|
|
3236
|
+
- normalizes to that white level;
|
|
3237
|
+
- clips saturated or flagged sites before white balance;
|
|
3238
|
+
- applies white balance once, for any invertible camera matrix;
|
|
3239
|
+
- keeps clipped highlights neutral.
|
|
3240
|
+
|
|
3241
|
+
See [photographic export](https://github.com/Photivra/photivra/blob/main/docs/PHOTOGRAPHIC_EXPORT.md) for the equations.
|
|
3242
|
+
|
|
3243
|
+
```ts
|
|
3244
|
+
import { calculateEffectiveSaturationCode, createNativeRawDevelopmentTask, type NativeRawDevelopmentInput,
|
|
3245
|
+
type SdrRenderingProfile } from "@photivra/engine";
|
|
3246
|
+
|
|
3247
|
+
// A 20,000 e⁻ well at 1 e⁻/DN over a 512 pedestal saturates at code 20512, not at 65535.
|
|
3248
|
+
const white = calculateEffectiveSaturationCode({ physicalChargeCapacityElectrons: 20000,
|
|
3249
|
+
preAdcSaturationElectronEquivalent: 30000, systemConversionGainElectronsPerCode: 1,
|
|
3250
|
+
blackLevelCode: 512, digitalSaturationCode: 65535 });
|
|
3251
|
+
if (white !== 20512) throw new Error("unexpected white level");
|
|
3252
|
+
|
|
3253
|
+
// Luminance tone curves preserve chromaticity; existing curve ids are unchanged.
|
|
3254
|
+
const filmic: SdrRenderingProfile = { schemaVersion: "0.1.0", profileId: "filmic", profileVersion: "1",
|
|
3255
|
+
renderingExposureEv: 0, toneCurve: "naka-rushton-luminance",
|
|
3256
|
+
toneCurveParameters: { exponent: 1.2, semiSaturation: 0.5, whiteLuminance: 4 },
|
|
3257
|
+
gamutHandling: "clip-components", outputDynamicRange: "sdr", transferFunction: "srgb",
|
|
3258
|
+
bitDepth: 8, rounding: "nearest-ties-up", dither: "none" };
|
|
3259
|
+
|
|
3260
|
+
async function develop(input: NativeRawDevelopmentInput) {
|
|
3261
|
+
// The task takes ownership of input.raw (its arrays are detached); output.raw returns it.
|
|
3262
|
+
const task = createNativeRawDevelopmentTask({ ...input, rendering: filmic, highlightMode: "clip" }, {
|
|
3263
|
+
yieldControl: () => new Promise<void>((resolve) => setTimeout(resolve, 0)),
|
|
3264
|
+
onProgress: ({ completedTileCount, totalTileCount }) => void (completedTileCount / totalTileCount)
|
|
3265
|
+
});
|
|
3266
|
+
await task.run();
|
|
3267
|
+
const output = task.takeOutput();
|
|
3268
|
+
return { pixels: output.integerSamples, raw: output.raw, saturatedPixels: output.highlight.saturatedPixelCount };
|
|
3269
|
+
}
|
|
3270
|
+
void develop;
|
|
3271
|
+
```
|
|
3272
|
+
|
|
3273
|
+
- `highlightMode: "reconstruct"` blends pixels above 0.8 of white towards neutral, instead of clipping
|
|
3274
|
+
each WB-scaled channel. It is an approximation, not a recovery of the clipped signal.
|
|
3275
|
+
- `extended-reinhard-luminance` takes `{ whiteLuminance }`, the luminance mapped to output white.
|
|
3276
|
+
- `createPhotographicExportPair(input, { signal })` accepts an optional `AbortSignal`.
|
|
3010
3277
|
|
|
3011
3278
|
## Capture-mode profiles
|
|
3012
3279
|
|
|
3013
|
-
Use `parseCaptureModeProfile()` and `resolveCaptureMode()` to describe
|
|
3280
|
+
One physical sensor can offer several capture modes, such as pixel shift or binning. Use `parseCaptureModeProfile()` and `resolveCaptureMode()` to describe its acquisition, sampling and reconstruction modes, without changing the sensor's identity:
|
|
3014
3281
|
|
|
3015
3282
|
```ts
|
|
3016
3283
|
import {
|
|
@@ -3106,7 +3373,7 @@ const selected = resolveCaptureMode({
|
|
|
3106
3373
|
console.log(selected.processedImageMegapixels);
|
|
3107
3374
|
```
|
|
3108
3375
|
|
|
3109
|
-
The model is deliberately **orthogonal
|
|
3376
|
+
The model is deliberately **orthogonal**: it is not one list of mutually exclusive, marketing-style modes. A mode describes each of these independently:
|
|
3110
3377
|
|
|
3111
3378
|
- acquisition as single-frame, fixed multi-frame, or variable multi-frame;
|
|
3112
3379
|
- per-frame sampling as the native effective raster, exact integer grouping of native effective samples, or another explicitly evidenced effective raster;
|
|
@@ -3115,23 +3382,23 @@ The model is deliberately **orthogonal**, not one mutually exclusive marketing-s
|
|
|
3115
3382
|
- the processed-image raster before final output crop/resampling;
|
|
3116
3383
|
- explicit downstream model dependencies.
|
|
3117
3384
|
|
|
3118
|
-
|
|
3385
|
+
So a computational multi-frame mode can also use grouped per-frame sampling, instead of being forced into one exclusive category.
|
|
3119
3386
|
|
|
3120
|
-
`NativeImageRaster` keeps its canonical meaning: an **effective native image-sampling grid**, not a physical
|
|
3387
|
+
`NativeImageRaster` keeps its canonical meaning: an **effective native image-sampling grid**, not a raster of physical photosites. Neither native megapixels nor processed/final-output megapixels may be used to infer the physical photodiode count.
|
|
3121
3388
|
|
|
3122
|
-
For grouped sampling, Photivra derives the per-frame sampling raster only when the native dimensions divide exactly by the evidenced grouping factors. The optional `combinationDomain` distinguishes `charge-domain`, `pre-conversion-analog
|
|
3389
|
+
For grouped sampling, Photivra derives the per-frame sampling raster only when the native dimensions divide exactly by the evidenced grouping factors. The optional `combinationDomain` distinguishes `charge-domain`, `pre-conversion-analog` and `post-conversion-digital`. If that fact is not known, leave it out. The word "binning", or a 2×2 resolution ratio, does not show where the combination happened.
|
|
3123
3390
|
|
|
3124
|
-
Inter-frame sensor offsets are
|
|
3391
|
+
Inter-frame sensor offsets are in units of the **native effective sampling pitch**. They are not evidence of the physical photodiode pitch. When supplied, the offset sequence length must match the fixed multi-frame count.
|
|
3125
3392
|
|
|
3126
|
-
`processedImageRaster` belongs to the capture-mode/reconstruction pipeline. It does not change physical imaging area, crop factor, field of view
|
|
3393
|
+
`processedImageRaster` belongs to the capture-mode/reconstruction pipeline. It does not change the physical imaging area, crop factor, field of view or native effective raster. It may be larger than the native raster for a multi-frame reconstruction, without implying a higher-resolution physical sensor.
|
|
3127
3394
|
|
|
3128
|
-
|
|
3395
|
+
The final output crop/resampling is still owned by the existing staged capture/output geometry contract. A processed raster therefore does not, by itself, define final display/export dimensions or the viewing FOV.
|
|
3129
3396
|
|
|
3130
|
-
The capture-mode schema records dependencies such as `color-sampling-model`, `mode-specific-readout-timing`, `radiometric-calibration
|
|
3397
|
+
The capture-mode schema records dependencies such as `color-sampling-model`, `mode-specific-readout-timing`, `radiometric-calibration` and `inter-frame-registration`, without pretending those models are already implemented.
|
|
3131
3398
|
|
|
3132
3399
|
## Sensor readout timing
|
|
3133
3400
|
|
|
3134
|
-
Use `calculateSensorReadoutTiming()` for
|
|
3401
|
+
Use `calculateSensorReadoutTiming()` for the native sensor scan schedule of one capture. This is when data is read off the sensor, which is not the same as when the shutter curtains open and close:
|
|
3135
3402
|
|
|
3136
3403
|
```ts
|
|
3137
3404
|
import {
|
|
@@ -3194,19 +3461,25 @@ const orientedDirection = transformNativeRasterVectorToOriented({
|
|
|
3194
3461
|
});
|
|
3195
3462
|
```
|
|
3196
3463
|
|
|
3197
|
-
The two timing facts are
|
|
3464
|
+
The two timing facts are deliberately separate, and Photivra does not assume they are equal:
|
|
3465
|
+
|
|
3466
|
+
- `captureReadoutDurationSeconds` is the declared time to read the data of the selected capture.
|
|
3467
|
+
- `spatialSamplingSkewSeconds` only sets how much the readout time shifts from one side of the frame to the other, in the current rolling approximation.
|
|
3198
3468
|
|
|
3199
|
-
Global readout declares only the capture data-readout duration. Its spatial phase offset is zero everywhere even when that duration is
|
|
3469
|
+
Global readout declares only the capture's data-readout duration. Its spatial phase offset is zero everywhere, even when that duration is not zero.
|
|
3200
3470
|
|
|
3201
|
-
The rolling schedule is a `uniform-linear-single-axis` approximation in
|
|
3471
|
+
The rolling schedule is a `uniform-linear-single-axis` approximation in the native sensor coordinates, which never change. `NativeImageRaster` is still an effective image-sampling grid, so this API does not claim that each raster row or column is one physical photodiode row or hardware readout line. This first model does not represent non-uniform, segmented, center-out, interleaved or multi-tap readout patterns.
|
|
3202
3472
|
|
|
3203
|
-
Physical camera orientation is deliberately
|
|
3473
|
+
- Physical camera orientation is deliberately left out of the timing input. If a renderer needs the scan direction in oriented capture coordinates, use the existing native ↔ oriented vector transform.
|
|
3474
|
+
- Output crop and resolution are also left out, so they cannot silently change native readout timing.
|
|
3475
|
+
- Timing for an active crop or capture mode must be supplied explicitly. The engine never scales full-frame timing from crop dimensions.
|
|
3476
|
+
- The shutter mechanism is recorded separately and does not change the schedule.
|
|
3204
3477
|
|
|
3205
|
-
|
|
3478
|
+
The separate exposure-window, inverse-rotation/temporal, flash and illumination contracts provide bounded timing and integration. This readout primitive does not run them, and does not infer curtain acceleration, segmented timing or synchronization.
|
|
3206
3479
|
|
|
3207
3480
|
## Readout/exposure spatial linkage
|
|
3208
3481
|
|
|
3209
|
-
Use `assessReadoutExposureTimingLinkage()` when a specific capture
|
|
3482
|
+
Use `assessReadoutExposureTimingLinkage()` when a source for a specific capture mode supports a relationship between the spatial order of rolling sensor readout and an **electronic** exposure boundary:
|
|
3210
3483
|
|
|
3211
3484
|
```ts
|
|
3212
3485
|
import { assessReadoutExposureTimingLinkage } from "@photivra/engine";
|
|
@@ -3329,26 +3602,26 @@ const relationship = assessReadoutExposureTimingLinkage({
|
|
|
3329
3602
|
console.log(relationship.value.links[0]);
|
|
3330
3603
|
```
|
|
3331
3604
|
|
|
3332
|
-
|
|
3605
|
+
A positive link means only that the selected electronic exposure boundary moves across the sensor in the same **normalized native spatial order (phase)** as the rolling readout:
|
|
3333
3606
|
|
|
3334
3607
|
- `same` means boundary phase equals readout phase;
|
|
3335
3608
|
- `reversed` means boundary phase equals `1 - readout phase`.
|
|
3336
3609
|
|
|
3337
3610
|
It does **not** mean the two events occur at the same absolute time.
|
|
3338
3611
|
|
|
3339
|
-
|
|
3612
|
+
So the function reports `absoluteTemporalAlignment: "not-established"`, even when the directions and the first-to-last timing spans happen to match.
|
|
3340
3613
|
|
|
3341
|
-
The readout spatial skew and exposure
|
|
3614
|
+
The readout spatial skew and the exposure boundary's traversal time may differ. Their ratio is reported as `boundaryTraversalToReadoutSpatialSkewRatio`, for information only.
|
|
3342
3615
|
|
|
3343
|
-
The total `captureReadoutDurationSeconds`
|
|
3616
|
+
The total `captureReadoutDurationSeconds` is a separate evidence-backed fact, and plays no part in checking the linkage.
|
|
3344
3617
|
|
|
3345
|
-
Only electronic boundaries can be
|
|
3618
|
+
Only electronic boundaries can be linked to sensor readout in this way. A mechanical opening or closing boundary is rejected. Global readout has no rolling spatial phase, so it cannot take part in this first linkage type.
|
|
3346
3619
|
|
|
3347
|
-
Use `linkage: { kind: "unlinked" }` when Photivra should assert no relationship. This means **no relationship is asserted
|
|
3620
|
+
Use `linkage: { kind: "unlinked" }` when Photivra should assert no relationship. This means **no relationship is asserted**. It does not show that the underlying hardware processes are physically independent.
|
|
3348
3621
|
|
|
3349
3622
|
## Capture exposure-window timing
|
|
3350
3623
|
|
|
3351
|
-
Use `calculateCaptureExposureWindows()` to describe when
|
|
3624
|
+
With a focal-plane shutter, different parts of the sensor start and stop collecting light at slightly different times. Use `calculateCaptureExposureWindows()` to describe when each native-sensor location begins and ends its local exposure. Shutter movement and sensor readout are kept separate:
|
|
3352
3625
|
|
|
3353
3626
|
```ts
|
|
3354
3627
|
import { calculateCaptureExposureWindows } from "@photivra/engine";
|
|
@@ -3426,7 +3699,7 @@ console.log(windows.value.samples);
|
|
|
3426
3699
|
console.log(windows.value.localExposureDurationRangeSeconds);
|
|
3427
3700
|
```
|
|
3428
3701
|
|
|
3429
|
-
The time basis is `first-opening-boundary-phase`: zero
|
|
3702
|
+
The time basis is `first-opening-boundary-phase`: time zero is when the declared opening boundary first starts to cross the sensor. This avoids pretending that a capture scanned across the sensor has one universal exposure-start moment.
|
|
3430
3703
|
|
|
3431
3704
|
Opening and closing schedules are independent. Each may currently be either:
|
|
3432
3705
|
|
|
@@ -3441,11 +3714,11 @@ local end = nominal exposure duration + closing phase
|
|
|
3441
3714
|
local duration = local end - local start
|
|
3442
3715
|
```
|
|
3443
3716
|
|
|
3444
|
-
Matched opening
|
|
3717
|
+
Matched opening and closing scans with equal traversal times give the same local exposure duration everywhere. Different directions or traversal times can deliberately make the local duration vary across the sensor.
|
|
3445
3718
|
|
|
3446
|
-
Photivra
|
|
3719
|
+
Photivra checks the **whole active rectangle**, not only the diagnostic points you supply. Because the first schedules are affine (linear plus offset) over the native rectangle, checking the corners is enough to reject any declaration that would give zero or negative local exposure duration anywhere in the capture.
|
|
3447
3720
|
|
|
3448
|
-
`shutterMechanism`
|
|
3721
|
+
`shutterMechanism` only says what conceptually opens and closes the exposure:
|
|
3449
3722
|
|
|
3450
3723
|
- `mechanical` → mechanical opening + mechanical closing;
|
|
3451
3724
|
- `electronic-first-curtain` → electronic opening + mechanical closing;
|
|
@@ -3453,15 +3726,15 @@ Photivra validates the **entire active rectangle**, not only supplied diagnostic
|
|
|
3453
3726
|
|
|
3454
3727
|
The mechanism does not supply traversal direction or timing.
|
|
3455
3728
|
|
|
3456
|
-
This exposure-window model
|
|
3729
|
+
This exposure-window model stays separate from `calculateSensorReadoutTiming()`. A later integration layer must explicitly bind exposure boundaries, sensor readout and time-parameterized motion. Photivra does not assume that a sensor readout phase is an exposure-start or exposure-end boundary.
|
|
3457
3730
|
|
|
3458
|
-
Physical orientation and digital output geometry
|
|
3731
|
+
Physical orientation and digital output geometry come later. The schedule stays in native sensor coordinates. Use the existing native ↔ oriented vector transforms when you need the direction as displayed.
|
|
3459
3732
|
|
|
3460
|
-
The first model does not cover curtain acceleration, curved
|
|
3733
|
+
The first model does not cover curtain acceleration, curved or nonlinear curtain travel, segmented/center-out/interleaved electronic schedules, flash/flicker interaction, shutter shock, pupil/bokeh effects specific to EFCS (electronic first-curtain shutter), or rolling-shutter image distortion.
|
|
3461
3734
|
|
|
3462
3735
|
## Capture-mode-bound shutter/readout timing
|
|
3463
3736
|
|
|
3464
|
-
Use `parseCaptureModeTimingProfile()` and `resolveCaptureModeTiming()` when exposure/readout timing evidence applies to one exact resolved capture mode.
|
|
3737
|
+
Use `parseCaptureModeTimingProfile()` and `resolveCaptureModeTiming()` when your exposure/readout timing evidence applies to one exact resolved capture mode.
|
|
3465
3738
|
|
|
3466
3739
|
The profile binds:
|
|
3467
3740
|
|
|
@@ -3477,23 +3750,23 @@ Schema `0.1.0` supports only the timing families already modeled by Photivra:
|
|
|
3477
3750
|
- simultaneous/global timing; and
|
|
3478
3751
|
- uniform-linear native-sensor scans.
|
|
3479
3752
|
|
|
3480
|
-
Segmented, center-out, interleaved or other non-uniform schedules fail closed rather than being silently approximated.
|
|
3753
|
+
Segmented, center-out, interleaved or other non-uniform schedules fail closed, rather than being silently approximated.
|
|
3481
3754
|
|
|
3482
|
-
The resolved result carries both authoritative local exposure windows and sensor data-readout timing, but still reports:
|
|
3755
|
+
The resolved result carries both the authoritative local exposure windows and the sensor data-readout timing, but still reports:
|
|
3483
3756
|
|
|
3484
3757
|
`readoutExposureSynchronization: "not-assumed"`
|
|
3485
3758
|
|
|
3486
|
-
|
|
3759
|
+
Sharing a mode/profile identity does not make the readout phase an exposure-start or exposure-end clock.
|
|
3487
3760
|
|
|
3488
|
-
Changing only a processed/output raster does not
|
|
3761
|
+
Changing only a processed/output raster does not change this timing. Timing changes only when the exact capture mode/profile, or the explicit timing facts, change.
|
|
3489
3762
|
|
|
3490
3763
|
## Depth-aware translation/parallax temporal geometry
|
|
3491
3764
|
|
|
3492
|
-
Use `calculateCaptureTranslationParallaxTemporalQuadrature()` for camera translation
|
|
3765
|
+
When the camera moves sideways (translates) during an exposure, near objects shift more in the image than far ones — this is parallax. Use `calculateCaptureTranslationParallaxTemporalQuadrature()` for camera translation and parallax over the exact local exposure windows resolved above.
|
|
3493
3766
|
|
|
3494
|
-
Unlike pure camera rotation, translation
|
|
3767
|
+
Unlike pure camera rotation, translation **depends on depth**. Every scene sample therefore gives a metric camera-space position with positive `z`.
|
|
3495
3768
|
|
|
3496
|
-
The model evaluates each scene point
|
|
3769
|
+
The model evaluates each scene point on its own, at deterministic midpoint times:
|
|
3497
3770
|
|
|
3498
3771
|
```text
|
|
3499
3772
|
relative scene position(t)
|
|
@@ -3501,29 +3774,27 @@ relative scene position(t)
|
|
|
3501
3774
|
+ (subject velocity - camera translation velocity) * t
|
|
3502
3775
|
```
|
|
3503
3776
|
|
|
3504
|
-
This keeps camera motion and subject motion separate while
|
|
3777
|
+
This keeps camera motion and subject motion separate, while letting both happen at once.
|
|
3505
3778
|
|
|
3506
|
-
Important
|
|
3779
|
+
Important limits:
|
|
3507
3780
|
|
|
3508
|
-
-
|
|
3509
|
-
-
|
|
3510
|
-
-
|
|
3511
|
-
-
|
|
3512
|
-
-
|
|
3513
|
-
-
|
|
3781
|
+
- Metric scene depth is required.
|
|
3782
|
+
- One global homography (a single 2D warp) is not allowed for arbitrary 3D translation.
|
|
3783
|
+
- Orientation changes the reported native/oriented sample vector, not the underlying physical projection.
|
|
3784
|
+
- Camera rotation is still a separate model.
|
|
3785
|
+
- Visibility, occlusion and disocclusion, deformation and acceleration are left to later stages or future work.
|
|
3786
|
+
- Sensor data-readout timing is not used as exposure timing.
|
|
3514
3787
|
|
|
3515
|
-
|
|
3788
|
+
So two scene points at different depths, under the same sideways camera translation, move by different amounts in the image.
|
|
3516
3789
|
|
|
3517
3790
|
## Temporal scene-radiance sampling
|
|
3518
3791
|
|
|
3519
|
-
Use `createSceneRadianceTemporalSamplingPlan()` to generate deterministic provider
|
|
3792
|
+
Use `createSceneRadianceTemporalSamplingPlan()` to generate deterministic times at which a provider or renderer should evaluate the scene, over one committed local exposure window.
|
|
3520
3793
|
|
|
3521
|
-
The sampling plan uses the same authoritative:
|
|
3794
|
+
The sampling plan uses the same authoritative time basis as the [exposure-window contract](#capture-exposure-window-timing) (#12):
|
|
3522
3795
|
|
|
3523
3796
|
`first-opening-boundary-phase`
|
|
3524
3797
|
|
|
3525
|
-
time basis as the #12 exposure-window contract.
|
|
3526
|
-
|
|
3527
3798
|
Each node carries:
|
|
3528
3799
|
|
|
3529
3800
|
- deterministic node/result identity;
|
|
@@ -3532,20 +3803,20 @@ Each node carries:
|
|
|
3532
3803
|
- normalized averaging weight;
|
|
3533
3804
|
- seconds-valued integration measure.
|
|
3534
3805
|
|
|
3535
|
-
A scene-radiance provider evaluates the scene
|
|
3806
|
+
A scene-radiance provider evaluates the scene and source state at those times. Then `reduceSceneRadianceTemporalSamples()` checks that every returned result matches the exact node, provider, scene, wavelength and time identity, before integrating.
|
|
3536
3807
|
|
|
3537
3808
|
The reducer reports both:
|
|
3538
3809
|
|
|
3539
3810
|
- average outgoing spectral radiance; and
|
|
3540
3811
|
- time-integrated spectral radiance.
|
|
3541
3812
|
|
|
3542
|
-
Per-node evidence and uncertainty are
|
|
3813
|
+
Per-node evidence and uncertainty are kept. The combined numeric uncertainty is explicitly `not-propagated`, because the engine does not invent assumptions about whether samples at different times are independent or correlated.
|
|
3543
3814
|
|
|
3544
|
-
This is the bridge
|
|
3815
|
+
This is the bridge needed for physically timed flicker, flash and other changes in light sources. It does not use sensor readout phase as a stand-in for exposure time, and it does not itself implement visibility, material transport or a renderer.
|
|
3545
3816
|
|
|
3546
3817
|
## Manual flash and ordinary sync
|
|
3547
3818
|
|
|
3548
|
-
The #106
|
|
3819
|
+
The manual-flash foundation (#106) models a flash as **scene illumination that changes over time**, not as a brightness control applied after rendering.
|
|
3549
3820
|
|
|
3550
3821
|
The flow is:
|
|
3551
3822
|
|
|
@@ -3558,58 +3829,58 @@ manual flash source geometry + spectrum + relative output
|
|
|
3558
3829
|
-> optics and sensor integration
|
|
3559
3830
|
```
|
|
3560
3831
|
|
|
3561
|
-
|
|
3832
|
+
(In the flow above, #12 is the [exposure-window timing](#capture-exposure-window-timing) and #85 is the [scene-radiance provider](#scene-radiance-provider-and-material-response) with its [temporal illumination](#temporal-illumination).)
|
|
3833
|
+
|
|
3834
|
+
Use `parseManualFlashProfile()` for one generic manual flash source, and `parseFlashSyncCapabilityProfile()` for the exact capture-mode/timing profile that allows ordinary synchronization.
|
|
3562
3835
|
|
|
3563
3836
|
A manual flash profile declares:
|
|
3564
3837
|
|
|
3565
|
-
- stable source identity;
|
|
3566
|
-
-
|
|
3567
|
-
- source spectrum;
|
|
3568
|
-
- relative
|
|
3838
|
+
- a stable source identity;
|
|
3839
|
+
- source geometry (point, spot, area and so on), as in [scene illumination source profiles](#scene-illumination-source-profiles);
|
|
3840
|
+
- the source spectrum;
|
|
3841
|
+
- a relative, linear output scale;
|
|
3569
3842
|
- explicit piecewise-linear pulse support and shape;
|
|
3570
|
-
- provenance
|
|
3843
|
+
- provenance and limitations.
|
|
3571
3844
|
|
|
3572
|
-
Schema 0.1.0 uses relative source magnitude only. It does not claim a calibrated guide number, joule value, branded flash range
|
|
3845
|
+
Schema 0.1.0 uses relative source magnitude only. It does not claim a calibrated guide number, joule value, branded flash range or manufacturer-specific pulse curve.
|
|
3573
3846
|
|
|
3574
3847
|
### Front- and rear-curtain timing
|
|
3575
3848
|
|
|
3576
|
-
Call `resolveManualFlashSync()` with the resolved
|
|
3849
|
+
Call `resolveManualFlashSync()` with the resolved capture-mode timing.
|
|
3577
3850
|
|
|
3578
|
-
For ordinary
|
|
3851
|
+
For an ordinary single-pulse flash, Photivra works out the interval during which **the whole active frame is exposed at the same time**:
|
|
3579
3852
|
|
|
3580
3853
|
```text
|
|
3581
3854
|
whole-frame-open start = latest opening-boundary phase
|
|
3582
3855
|
whole-frame-open end = earliest closing-boundary phase
|
|
3583
3856
|
```
|
|
3584
3857
|
|
|
3585
|
-
For a uniform focal-plane opening scan, the latest opening is the opening traversal
|
|
3858
|
+
For a uniform focal-plane opening scan, the latest opening is the opening traversal time. The earliest closing stays at the nominal closing reference. If the interval is zero or negative, an ordinary flash cannot light the whole active frame evenly.
|
|
3586
3859
|
|
|
3587
|
-
Front sync
|
|
3860
|
+
Front-curtain sync puts the pulse at the start of this interval. Rear-curtain sync puts it at the end:
|
|
3588
3861
|
|
|
3589
3862
|
```text
|
|
3590
3863
|
front pulse start = whole-frame-open start
|
|
3591
3864
|
rear pulse start = whole-frame-open end - pulse support duration
|
|
3592
3865
|
```
|
|
3593
3866
|
|
|
3594
|
-
The
|
|
3867
|
+
The whole pulse must fit inside the interval. A shutter/capture timing without a long enough whole-frame-open interval fails closed.
|
|
3595
3868
|
|
|
3596
3869
|
Sensor **data readout timing is never used as flash exposure timing**.
|
|
3597
3870
|
|
|
3598
3871
|
### HSS boundary
|
|
3599
3872
|
|
|
3600
|
-
`high-speed-sync
|
|
3601
|
-
|
|
3602
|
-
Photivra does not make HSS work by allowing an ordinary short pulse above the normal sync regime. Real HSS requires an extended/repeated illumination waveform coordinated with the moving exposure slit and has different energy/range behavior.
|
|
3873
|
+
`resolveManualFlashSync()` still rejects `high-speed-sync`. Photivra does not fake HSS (high-speed sync) by allowing an ordinary short pulse at shutter speeds faster than normal sync. Real HSS needs a long or repeated light waveform, coordinated with the moving exposure slit, and it behaves differently in energy and range. That is handled by the separate `resolveHighSpeedFlashSync()` burst model, for schema 0.2.0 sync profiles that declare HSS (see [Rolling shutter, video, flash TTL/HSS and highlight-priority metering](#rolling-shutter-video-flash-ttlhss-and-highlight-priority-metering-200)).
|
|
3603
3874
|
|
|
3604
3875
|
### Illumination overlay
|
|
3605
3876
|
|
|
3606
|
-
`createManualFlashIlluminationOverlay()`
|
|
3877
|
+
`createManualFlashIlluminationOverlay()` adds the flash as a real scene illumination source, and builds the matching temporal waveform and source binding.
|
|
3607
3878
|
|
|
3608
|
-
If an ambient `SceneIlluminationTemporalProfile` already exists, its waveforms and source bindings are
|
|
3879
|
+
If an ambient `SceneIlluminationTemporalProfile` already exists, its waveforms and source bindings are kept and rebound to the new combined illumination-profile identity. So the flash does not erase ambient flicker or other source timing.
|
|
3609
3880
|
|
|
3610
|
-
When flash is disabled, the base illumination and temporal profiles are returned unchanged
|
|
3881
|
+
When the flash is disabled, the base illumination and temporal profiles are returned with unchanged meaning, and no flash source is added.
|
|
3611
3882
|
|
|
3612
|
-
The flash source
|
|
3883
|
+
The flash source keeps its position, direction and coverage geometry. Distance falloff, surface orientation, shadows, visibility and indirect light remain the job of the scene-radiance provider or renderer. Photivra does not fake those effects with one number for the whole frame.
|
|
3613
3884
|
|
|
3614
3885
|
### Manual flash controls remain separate
|
|
3615
3886
|
|
|
@@ -3624,19 +3895,19 @@ The manual-flash layer does not change:
|
|
|
3624
3895
|
- drive mode;
|
|
3625
3896
|
- stable-support state.
|
|
3626
3897
|
|
|
3627
|
-
The
|
|
3898
|
+
The flash output control is independent of the camera's ISO, aperture and shutter. Those camera settings affect the captured flash contribution only through the normal image-formation chain.
|
|
3628
3899
|
|
|
3629
|
-
|
|
3900
|
+
The flash spectrum is physical scene illumination. A Flash WB preset chosen later, if any, must not change the source spectrum.
|
|
3630
3901
|
|
|
3631
3902
|
### #105 release binding
|
|
3632
3903
|
|
|
3633
|
-
|
|
3904
|
+
You may supply an optional committed `ResolvedReleaseFrame` from [release sequencing](#logical-release-sequences). Its shutter duration must match the exposure-window timing. The resolved flash event then reports pulse start and end in scene time relative to the sequence, without changing the release frame.
|
|
3634
3905
|
|
|
3635
|
-
TTL
|
|
3906
|
+
Generic pre-flash TTL (through-the-lens) power, with flash exposure compensation, is solved separately by `resolveTtlFlashExposure()`, and applied to the manual profile with `createTtlScaledFlashProfile()`. Outside the manual-flash contract: FE/FV lock, recycle/burst/thermal limits, red-eye pre-flashes, modelling lamps, AF-assist and simulation of wireless protocols.
|
|
3636
3907
|
|
|
3637
3908
|
## Capture orientation, active area, and output geometry
|
|
3638
3909
|
|
|
3639
|
-
Use `resolveCaptureGeometry()` to keep physical sensor
|
|
3910
|
+
Use `resolveCaptureGeometry()` to keep four things separate: the physical sensor, the active capture area, how the camera is physically held (orientation), and the final digital output geometry:
|
|
3640
3911
|
|
|
3641
3912
|
```ts
|
|
3642
3913
|
import { resolveCaptureGeometry } from "@photivra/engine";
|
|
@@ -3684,17 +3955,20 @@ import {
|
|
|
3684
3955
|
} from "@photivra/engine";
|
|
3685
3956
|
```
|
|
3686
3957
|
|
|
3687
|
-
Point transforms use continuous
|
|
3958
|
+
- Point transforms use continuous coordinates on pixel edges; pixel centers are at +0.5 offsets.
|
|
3959
|
+
- Vector transforms rotate direction only.
|
|
3960
|
+
- Rectangle transforms keep integer, half-open semantics.
|
|
3961
|
+
- Clockwise and counter-clockwise portrait transforms are distinct, and both round-trip back to native coordinates.
|
|
3688
3962
|
|
|
3689
|
-
`activeCaptureRect` is
|
|
3963
|
+
`activeCaptureRect` is given in native coordinates. The generic model works out the physical bounds of the active area by assuming native image samples are spread evenly across the declared physical imaging area. The result gives the physical bounds and the center offset from the optical axis, so off-center crops stay optically asymmetric.
|
|
3690
3964
|
|
|
3691
|
-
`outputCropRect` is
|
|
3965
|
+
`outputCropRect` is given in oriented active-capture coordinates. `outputRaster` may resample that crop, but must keep its aspect ratio within integer-rounding tolerance. Hidden geometric stretching is rejected.
|
|
3692
3966
|
|
|
3693
|
-
Display
|
|
3967
|
+
Display and file transforms, such as EXIF mirroring, stay separate from the physical `CaptureOrientation`.
|
|
3694
3968
|
|
|
3695
|
-
Use `calculateActiveCaptureFieldOfView()` for active physical capture
|
|
3969
|
+
Use `calculateActiveCaptureFieldOfView()` for the FOV of the active physical capture. It keeps signed horizontal/vertical bounds for off-center crops, and reports both corner-to-corner diagonal spans. `diagonalDegrees` is the larger of the two; centered crops give equal spans.
|
|
3696
3970
|
|
|
3697
|
-
|
|
3971
|
+
The final digital/output crop is deliberately not part of active-capture FOV.
|
|
3698
3972
|
|
|
3699
3973
|
## Centered crop and subject framing crop
|
|
3700
3974
|
|
|
@@ -3715,7 +3989,7 @@ console.log(crop.value.megapixels);
|
|
|
3715
3989
|
console.log(crop.value.retainedAreaFraction);
|
|
3716
3990
|
```
|
|
3717
3991
|
|
|
3718
|
-
Use `calculateSubjectFramingCrop()` to
|
|
3992
|
+
Use `calculateSubjectFramingCrop()` to work out the extra crop needed for a projected subject to fill a target fraction of the frame height:
|
|
3719
3993
|
|
|
3720
3994
|
```ts
|
|
3721
3995
|
import { calculateSubjectFramingCrop } from "@photivra/engine";
|
|
@@ -3732,13 +4006,19 @@ console.log(framing.value.subjectHeightFraction);
|
|
|
3732
4006
|
console.log(framing.value.subjectClipped);
|
|
3733
4007
|
```
|
|
3734
4008
|
|
|
3735
|
-
Subject framing assumes the crop can be
|
|
4009
|
+
Subject framing assumes the crop can be placed around the subject. It does not check the subject's actual position against the image edges.
|
|
4010
|
+
|
|
4011
|
+
Integer rounding:
|
|
3736
4012
|
|
|
3737
|
-
|
|
4013
|
+
- `calculateCenteredCrop()` floors the shorter axis (`floor(dimension / cropFactor)`). It derives the longer axis as the integer nearest to the source aspect ratio, capped at `floor(dimension / cropFactor)`. For example, 6000×4000 at 1.7× gives 3528×2352 (exactly 3:2).
|
|
4014
|
+
- `calculateSubjectFramingCrop()` uses the largest whole-pixel height within the requested crop height (`subjectHeightPixels / targetSubjectHeightFraction`), never fewer than `ceil(subjectHeightPixels)`, and the nearest-integer width for the source ratio.
|
|
4015
|
+
- So `subjectHeightFraction` is at least the target (within one pixel of crop height), and `subjectClipped` is never `true` when the subject fits the source frame.
|
|
4016
|
+
|
|
4017
|
+
See [Physics Foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#sensor-geometry-sampling-capture-and-crop).
|
|
3738
4018
|
|
|
3739
4019
|
## Projected subject motion
|
|
3740
4020
|
|
|
3741
|
-
Use `calculateProjectedMotionBlur()` for representative
|
|
4021
|
+
Use `calculateProjectedMotionBlur()` for how far one representative point moves on the image plane during an exposure:
|
|
3742
4022
|
|
|
3743
4023
|
```ts
|
|
3744
4024
|
import { calculateProjectedMotionBlur } from "@photivra/engine";
|
|
@@ -3758,15 +4038,15 @@ console.log(motion.value.distanceMm);
|
|
|
3758
4038
|
console.log(motion.value.distancePixels);
|
|
3759
4039
|
```
|
|
3760
4040
|
|
|
3761
|
-
The model assumes constant
|
|
4041
|
+
The model assumes constant linear velocity in world space. It follows one representative point. It does not model blur from changing object size, rotation, deformation or acceleration.
|
|
3762
4042
|
|
|
3763
|
-
See [Motion and Signal Foundation](MOTION_AND_SIGNAL.md#projected-subject-motion).
|
|
4043
|
+
See [Motion and Signal Foundation](https://github.com/Photivra/photivra/blob/main/docs/MOTION_AND_SIGNAL.md#projected-subject-motion).
|
|
3764
4044
|
|
|
3765
4045
|
## Extended-object time-varying projection
|
|
3766
4046
|
|
|
3767
|
-
`calculateProjectedMotionBlur()`
|
|
4047
|
+
`calculateProjectedMotionBlur()` stays the compact API for one representative point. Use `calculateExtendedObjectProjectionTrajectory()` when the object's size and shape matter.
|
|
3768
4048
|
|
|
3769
|
-
The extended-object foundation
|
|
4049
|
+
The extended-object foundation takes:
|
|
3770
4050
|
|
|
3771
4051
|
- multiple explicit metric camera-space points belonging to one object/region;
|
|
3772
4052
|
- one shared constant rigid object-translation velocity;
|
|
@@ -3785,13 +4065,13 @@ image x(t) = projection distance * x(t) / z(t)
|
|
|
3785
4065
|
image y(t) = projection distance * y(t) / z(t)
|
|
3786
4066
|
```
|
|
3787
4067
|
|
|
3788
|
-
This
|
|
4068
|
+
This matters for motion towards or away from the camera. An object centered on the optical axis can have almost no movement at its center, while its off-axis corners move outwards or inwards because the magnification changes.
|
|
3789
4069
|
|
|
3790
4070
|
### Front-to-back motion and scale
|
|
3791
4071
|
|
|
3792
|
-
Do not
|
|
4072
|
+
Do not read every result for motion in depth as one global scale.
|
|
3793
4073
|
|
|
3794
|
-
If
|
|
4074
|
+
If you have a known flat patch facing the camera squarely (fronto-parallel), you may provide it explicitly:
|
|
3795
4075
|
|
|
3796
4076
|
```ts
|
|
3797
4077
|
frontoparallelPlane: {
|
|
@@ -3812,7 +4092,7 @@ The scale diagnostic is valid for that declared patch only. It does **not** auth
|
|
|
3812
4092
|
|
|
3813
4093
|
### Mixed lateral + depth motion
|
|
3814
4094
|
|
|
3815
|
-
|
|
4095
|
+
Sideways and depth translation are evaluated together. Because projection depends on each point's depth, different points on the object can follow quite different paths on the image plane.
|
|
3816
4096
|
|
|
3817
4097
|
The result therefore exposes per-point, per-time:
|
|
3818
4098
|
|
|
@@ -3822,15 +4102,15 @@ The result therefore exposes per-point, per-time:
|
|
|
3822
4102
|
- depth;
|
|
3823
4103
|
- optional validated planar scale.
|
|
3824
4104
|
|
|
3825
|
-
Camera and object translation
|
|
4105
|
+
Camera and object translation stay separate inputs, even though the relative projection uses `object - camera` translation.
|
|
3826
4106
|
|
|
3827
4107
|
### #12 local-exposure integration
|
|
3828
4108
|
|
|
3829
|
-
Use `calculateCaptureExtendedObjectTemporalProjection()` when the geometry must follow capture timing.
|
|
4109
|
+
Use `calculateCaptureExtendedObjectTemporalProjection()` when the geometry must follow capture timing (the [exposure windows](#capture-exposure-window-timing), #12).
|
|
3830
4110
|
|
|
3831
|
-
|
|
4111
|
+
Each object point names the native destination point whose committed exposure window applies. The engine creates deterministic midpoint times inside each point's local exposure interval, and evaluates the same extended-object projection there.
|
|
3832
4112
|
|
|
3833
|
-
With rolling
|
|
4113
|
+
With rolling or other local exposure timing, two image locations can therefore record different parts of the same object's path.
|
|
3834
4114
|
|
|
3835
4115
|
Sensor **data readout timing is not substituted for exposure timing**.
|
|
3836
4116
|
|
|
@@ -3849,15 +4129,15 @@ It does not calculate:
|
|
|
3849
4129
|
- camera rotation;
|
|
3850
4130
|
- a universal projective warp.
|
|
3851
4131
|
|
|
3852
|
-
If relative depth changes, the object
|
|
4132
|
+
If the relative depth changes, the object's distance from the focus plane also changes over time. A high-fidelity renderer may therefore need to evaluate defocus/PSF at the same physical times, instead of convolving one motion result with one fixed defocus kernel.
|
|
3853
4133
|
|
|
3854
|
-
A renderer using #
|
|
4134
|
+
A renderer using a [scene-radiance provider](#scene-radiance-provider-and-material-response) should evaluate scene radiance and visibility at each of these geometry states, rather than guessing a complete blur from the endpoints alone.
|
|
3855
4135
|
|
|
3856
4136
|
## Relative pre-exposure metering
|
|
3857
4137
|
|
|
3858
|
-
Use `parseExposureMeteringProfile()` and `meterRelativeExposure()` for the first
|
|
4138
|
+
Metering measures how bright the scene is, so exposure can be set. Use `parseExposureMeteringProfile()` and `meterRelativeExposure()` for the first metering path. It does not depend on any renderer.
|
|
3859
4139
|
|
|
3860
|
-
Schema `0.1.0` deliberately accepts only **relative
|
|
4140
|
+
Schema `0.1.0` deliberately accepts only a **relative, linear signal measured before exposure**. It does not claim calibrated luminance, scene spectral radiance, sensor-plane irradiance or the meter calibration of any commercial camera.
|
|
3861
4141
|
|
|
3862
4142
|
```ts
|
|
3863
4143
|
import {
|
|
@@ -3969,26 +4249,27 @@ if (metered.value.status === "resolved") {
|
|
|
3969
4249
|
}
|
|
3970
4250
|
```
|
|
3971
4251
|
|
|
3972
|
-
For a controlled uniform
|
|
4252
|
+
For a controlled uniform test scene, halving every relative-linear sample from `1.0` to `0.5` gives a **+1 stop** shift to reach the same declared target. The Manual + Auto ISO tests rely on this deliberate, educational rule.
|
|
3973
4253
|
|
|
3974
4254
|
### Metering policies
|
|
3975
4255
|
|
|
3976
|
-
The first generic policies are
|
|
4256
|
+
The first generic policies are spelled out explicitly, not named after brands:
|
|
3977
4257
|
|
|
3978
|
-
- `multi-zone-uniform` — area-weighted average over all supplied zones
|
|
3979
|
-
- `center-weighted-radial` — multiplies area weight by a radial physical
|
|
3980
|
-
- `spot` —
|
|
3981
|
-
- `highlight-weighted` — gives brighter
|
|
4258
|
+
- `multi-zone-uniform` — area-weighted average over all supplied zones.
|
|
4259
|
+
- `center-weighted-radial` — multiplies each area weight by a radial weighting over the physical frame, with an explicit edge weight and exponent.
|
|
4260
|
+
- `spot` — uses only samples inside an explicit spot (center and radius) in the oriented active capture frame.
|
|
4261
|
+
- `highlight-weighted` — gives brighter samples more weight, through an explicit exponent and a minimum weight fraction, then places that weighted mean at the target. It emphasizes highlights; it does not protect them. In a mostly mid-grey frame with a small bright area, it darkens the mid-tones (about 0.5–0.8 EV compared with uniform metering, for a frame that is 95 % at 0.18 and 5 % at 0.9), without placing the highlights relative to saturation.
|
|
4262
|
+
- `highlight-priority` — places the area-weighted `highlightPercentile` quantile of the samples `headroomStops` below a declared `saturationRelativeSignal`. The profile target must equal `saturationRelativeSignal · 2^−headroomStops` (`calculateHighlightPriorityPlacementSignal()`). It is a quantile over space, so the time-averaged scene-radiance meter and TTL flash reject it.
|
|
3982
4263
|
|
|
3983
|
-
The sample producer supplies `areaWeight`, so
|
|
4264
|
+
The sample producer supplies `areaWeight`, so sampling one part of the frame more densely does not automatically give it more influence.
|
|
3984
4265
|
|
|
3985
|
-
Center
|
|
4266
|
+
Center and spot distances use the **physical dimensions of the oriented active capture frame**, not CSS or display pixels. Schema 0.1.0 does not use a final output crop.
|
|
3986
4267
|
|
|
3987
4268
|
### Target and downstream control
|
|
3988
4269
|
|
|
3989
|
-
`targetRelativeSignal` is a
|
|
4270
|
+
`targetRelativeSignal` is a reference defined by the profile. It is not a universal claim about 18% grey, or about any manufacturer's meter calibration.
|
|
3990
4271
|
|
|
3991
|
-
The meter reports the base scene measurement
|
|
4272
|
+
The meter reports the base scene measurement separately from exposure compensation. It does not choose aperture, shutter duration or ISO.
|
|
3992
4273
|
|
|
3993
4274
|
The intended flow is:
|
|
3994
4275
|
|
|
@@ -4001,19 +4282,21 @@ pre-exposure relative scene signal
|
|
|
4001
4282
|
-> aperture / shutter / ISO
|
|
4002
4283
|
```
|
|
4003
4284
|
|
|
4004
|
-
|
|
4285
|
+
(#99 is [exposure control](#manual-and-auto-iso-exposure-resolution): the resolvers that choose aperture, shutter and ISO.)
|
|
4286
|
+
|
|
4287
|
+
A sample set with zero signal returns `status: "no-signal"`, not infinity or NaN.
|
|
4005
4288
|
|
|
4006
|
-
`measurementId` and `sceneStateId`
|
|
4289
|
+
`measurementId` and `sceneStateId` stay in the result, so an application can freeze a meter result for AE lock, or reject stale results after the scene lighting changes.
|
|
4007
4290
|
|
|
4008
|
-
The input must explicitly
|
|
4291
|
+
The input must state explicitly that exposure settings, white balance, tone mapping, display gamma and sharpening have not already been applied. This prevents a feedback loop where auto-exposure meters its own final preview.
|
|
4009
4292
|
|
|
4010
|
-
This path
|
|
4293
|
+
This path covers ambient, relative metering only. Flash/TTL metering, and calibrated camera-specific photometric or radiometric metering, are separate. Exposure compensation and automatic exposure use the frozen meter target through their own contracts; they never change the meter measurement.
|
|
4011
4294
|
|
|
4012
4295
|
### Equipment metering capability binding
|
|
4013
4296
|
|
|
4014
|
-
Use `parseGenericBodyMeteringCapabilityProfile()`
|
|
4297
|
+
Use `parseGenericBodyMeteringCapabilityProfile()` with `assessExposureMeteringProfileCompatibility()` to declare which engine-owned metering profiles and modes a generic camera body may select.
|
|
4015
4298
|
|
|
4016
|
-
The equipment profile does **not**
|
|
4299
|
+
The equipment profile does **not** repeat the meter target or calibration values. Those stay authoritative in the selected `ExposureMeteringProfile`.
|
|
4017
4300
|
|
|
4018
4301
|
Composition fragment: requires previously validated bindings `evidence`, `profile`. This is an integration sketch, not a standalone runnable program.
|
|
4019
4302
|
|
|
@@ -4052,13 +4335,14 @@ const compatibility =
|
|
|
4052
4335
|
});
|
|
4053
4336
|
```
|
|
4054
4337
|
|
|
4055
|
-
The compatibility result
|
|
4338
|
+
The compatibility result keeps `targetPolicyOwnership: "metering-profile"`, so equipment metadata never becomes a second source of calibration truth.
|
|
4056
4339
|
|
|
4057
|
-
|
|
4340
|
+
Linking the metering spot to the focus point is capability metadata only. A future AF/spot-link control must use it explicitly. The meter does not silently move a spot just because a focus point exists.
|
|
4058
4341
|
|
|
4059
4342
|
### Meter target and automatic exposure integration
|
|
4060
4343
|
|
|
4061
|
-
|
|
4344
|
+
- `createExposureMeterTargetFromMeteringResult()` freezes a spatial or temporal meter result into the target used by exposure-mode resolution.
|
|
4345
|
+
- `setExposureCompensationOnMeterTarget()` applies compensation afterwards, without changing the source measurement.
|
|
4062
4346
|
|
|
4063
4347
|
For the controlled relative path:
|
|
4064
4348
|
|
|
@@ -4069,15 +4353,15 @@ uniform scene signal 0.5 -> meter target scale 2 -> ISO 200
|
|
|
4069
4353
|
|
|
4070
4354
|
when aperture and shutter are fixed in Manual + Auto ISO and the ISO capability range permits it.
|
|
4071
4355
|
|
|
4072
|
-
The frozen target
|
|
4356
|
+
The frozen target keeps the original meter measurement, scene and profile identity; this is how AE lock works. Reusing that target after the scene changes deliberately keeps the locked target. Creating a new meter result follows the new scene state.
|
|
4073
4357
|
|
|
4074
|
-
Automatic resolvers
|
|
4358
|
+
Automatic resolvers read the typed target directly, and do not apply exposure compensation again.
|
|
4075
4359
|
|
|
4076
4360
|
## Scene-radiance-derived and temporal metering
|
|
4077
4361
|
|
|
4078
|
-
Use `createSceneRadianceDerivedExposureMeteringSampleSet()` when a renderer
|
|
4362
|
+
Use `createSceneRadianceDerivedExposureMeteringSampleSet()` when a renderer or reference evaluator supplies relative pre-exposure meter zones derived from a [scene-radiance provider](#scene-radiance-provider-and-material-response) (#85).
|
|
4079
4363
|
|
|
4080
|
-
The bridge
|
|
4364
|
+
The bridge checks:
|
|
4081
4365
|
|
|
4082
4366
|
- scene/provider/illumination/material profile identity;
|
|
4083
4367
|
- provider material fidelity against the supplied material profile;
|
|
@@ -4085,7 +4369,7 @@ The bridge validates:
|
|
|
4085
4369
|
- optional temporal-illumination profile identity;
|
|
4086
4370
|
- explicit capture time when the provider is time-varying.
|
|
4087
4371
|
|
|
4088
|
-
The
|
|
4372
|
+
The reduction to one number per zone is still supplied by the renderer or provider. Photivra does **not** automatically turn spectral-radiance samples into luminance or a camera meter signal.
|
|
4089
4373
|
|
|
4090
4374
|
Composition fragment: requires previously validated bindings `captureGeometry`, `illuminationProfile`, `materialResponseProfile`, `profile`, `providerProfile`. This is an integration sketch, not a standalone runnable program.
|
|
4091
4375
|
|
|
@@ -4146,15 +4430,15 @@ const meter = meterRelativeExposure({
|
|
|
4146
4430
|
});
|
|
4147
4431
|
```
|
|
4148
4432
|
|
|
4149
|
-
The source context reports `spectralReductionCalibrated: false
|
|
4433
|
+
The source context reports `spectralReductionCalibrated: false`. Claims of calibrated luminance or scene radiance are not allowed.
|
|
4150
4434
|
|
|
4151
4435
|
### Time-varying illumination
|
|
4152
4436
|
|
|
4153
|
-
If the scene-radiance provider declares `illuminationTemporalProfileId`, the bridge
|
|
4437
|
+
If the scene-radiance provider declares `illuminationTemporalProfileId`, the bridge needs the matching temporal profile, plus a finite `captureTimeSecondsFromReference` on the shared `first-opening-boundary-phase` reference.
|
|
4154
4438
|
|
|
4155
|
-
One such sample set is an explicit
|
|
4439
|
+
One such sample set is an explicit meter snapshot at that instant.
|
|
4156
4440
|
|
|
4157
|
-
To average
|
|
4441
|
+
To average several snapshots, use `meterSceneRadianceTemporalExposure()` with an explicit `weighted-time-average` policy:
|
|
4158
4442
|
|
|
4159
4443
|
Composition fragment: requires previously validated bindings `profile`, `sampleAtTime0`, `sampleAtTime1`. This is an integration sketch, not a standalone runnable program.
|
|
4160
4444
|
|
|
@@ -4186,17 +4470,17 @@ const temporalMeter =
|
|
|
4186
4470
|
});
|
|
4187
4471
|
```
|
|
4188
4472
|
|
|
4189
|
-
The
|
|
4473
|
+
The time weights must be positive and sum to one. Capture times must be strictly increasing. Every sample must share the same committed `sceneStateId`, provider/material/derivation context, temporal profile and active-capture geometry.
|
|
4190
4474
|
|
|
4191
|
-
The meter averages **linear relative signals first**, then computes the stop offset to the target. It never averages EV
|
|
4475
|
+
The meter averages the **linear relative signals first**, then computes the stop offset to the target. It never averages EV or stop offsets.
|
|
4192
4476
|
|
|
4193
|
-
This policy is explicit ambient
|
|
4477
|
+
This policy is explicit ambient metering over time. It does not guess time weights from the flicker frequency, the source waveform shape, the shutter or readout duration, the renderer's frame rate, or the final display brightness. Flash/TTL metering is separate.
|
|
4194
4478
|
|
|
4195
4479
|
## Logical release sequences
|
|
4196
4480
|
|
|
4197
|
-
Use `parseGenericReleaseCapabilityProfile()` and `resolveReleaseSequence()` for deterministic logical camera releases
|
|
4481
|
+
A "release" is one press of the shutter button and the frames it produces. Use `parseGenericReleaseCapabilityProfile()` and `resolveReleaseSequence()` for deterministic logical camera releases: single frames, bursts, self-timer sequences, exposure bracketing and focus bracketing.
|
|
4198
4482
|
|
|
4199
|
-
This is deliberately separate from sensor capture modes
|
|
4483
|
+
This is deliberately separate from sensor capture modes. A burst of several photographs is several logical captures, not one multi-frame sensor reconstruction.
|
|
4200
4484
|
|
|
4201
4485
|
Composition fragment: requires previously validated bindings `evidence`, `exposureCapabilities`. This is an integration sketch, not a standalone runnable program.
|
|
4202
4486
|
|
|
@@ -4297,38 +4581,38 @@ Each frame carries:
|
|
|
4297
4581
|
- deterministic per-frame seed;
|
|
4298
4582
|
- explicit automation/lock policy.
|
|
4299
4583
|
|
|
4300
|
-
|
|
4584
|
+
Neighbouring frames derive different random seeds from the same stable sequence seed. Replaying identical sequence inputs reproduces the same sequence.
|
|
4301
4585
|
|
|
4302
4586
|
### Cadence feasibility
|
|
4303
4587
|
|
|
4304
|
-
|
|
4588
|
+
The requested frames per second (FPS) never override physics or the camera's capabilities.
|
|
4305
4589
|
|
|
4306
|
-
For
|
|
4590
|
+
For ordinary still exposures, which do not overlap, the actual start-to-start interval is limited by:
|
|
4307
4591
|
|
|
4308
4592
|
- requested cadence;
|
|
4309
4593
|
- body maximum cadence;
|
|
4310
4594
|
- previous frame exposure duration;
|
|
4311
4595
|
- minimum inter-frame gap.
|
|
4312
4596
|
|
|
4313
|
-
The resolver reports
|
|
4597
|
+
The resolver reports which timing limit applied, instead of silently scheduling overlapping exposures.
|
|
4314
4598
|
|
|
4315
|
-
|
|
4599
|
+
Schema 0.1.0 does not model slowdown from buffers, media or heat, pre-release capture, or special modes with overlapping acquisition.
|
|
4316
4600
|
|
|
4317
4601
|
### Bracketing
|
|
4318
4602
|
|
|
4319
|
-
Exposure bracketing changes an explicit physical exposure
|
|
4603
|
+
Exposure bracketing changes an explicit physical exposure setting for each frame. It does not add a brightness offset after rendering.
|
|
4320
4604
|
|
|
4321
|
-
Focus bracketing
|
|
4605
|
+
Focus bracketing uses explicit `FocusPlane` states. It does not claim to produce a focus-stacked image, and does not give an abstract focus-step index a universal physical meaning.
|
|
4322
4606
|
|
|
4323
|
-
White-balance bracketing is not forced
|
|
4607
|
+
White-balance bracketing is not forced to mean several physical exposures. Varying WB in rendering or development is work for the colour pipeline later on.
|
|
4324
4608
|
|
|
4325
4609
|
### Cancellation
|
|
4326
4610
|
|
|
4327
|
-
Use `createCancelledReleaseSequence()` to record
|
|
4611
|
+
Use `createCancelledReleaseSequence()` to record explicitly which frames were completed and which were left out, without changing the scheduled source sequence.
|
|
4328
4612
|
|
|
4329
4613
|
## Generic equipment exposure capabilities
|
|
4330
4614
|
|
|
4331
|
-
Use the
|
|
4615
|
+
Use the equipment-capability API (#109) to describe **what a generic Photivra body and lens allow**, before [exposure control](#manual-and-auto-iso-exposure-resolution) (#99) chooses any setting.
|
|
4332
4616
|
|
|
4333
4617
|
```ts
|
|
4334
4618
|
import {
|
|
@@ -4527,20 +4811,20 @@ A setting grid is either:
|
|
|
4527
4811
|
- `continuous-within-range` — a downstream resolver may choose any finite value inside the range; or
|
|
4528
4812
|
- `discrete-values` — only the declared values are valid.
|
|
4529
4813
|
|
|
4530
|
-
For a
|
|
4814
|
+
For a lens whose widest aperture changes with zoom, the discrete aperture list is filtered after the wide-open f-number is resolved at the selected focal length.
|
|
4531
4815
|
|
|
4532
4816
|
### Capability versus selected state
|
|
4533
4817
|
|
|
4534
|
-
These profiles do **not**
|
|
4818
|
+
These profiles do **not** describe the current camera settings. They are immutable inputs that say what is possible.
|
|
4535
4819
|
|
|
4536
|
-
The resolver also does not choose aperture, shutter or ISO.
|
|
4820
|
+
The resolver also does not choose aperture, shutter or ISO. Exposure control owns:
|
|
4537
4821
|
|
|
4538
|
-
- manual
|
|
4539
|
-
-
|
|
4822
|
+
- which settings are manual and which are automatic;
|
|
4823
|
+
- validating and quantizing requested settings;
|
|
4540
4824
|
- clamping to capability limits;
|
|
4541
4825
|
- residual exposure diagnostics.
|
|
4542
4826
|
|
|
4543
|
-
The legacy `CameraConfiguration`
|
|
4827
|
+
The legacy `CameraConfiguration` keeps its meaning unchanged, alongside this newer, versioned capability/state design.
|
|
4544
4828
|
|
|
4545
4829
|
Auto ISO capability preserves three states:
|
|
4546
4830
|
|
|
@@ -4550,13 +4834,13 @@ Auto ISO capability preserves three states:
|
|
|
4550
4834
|
|
|
4551
4835
|
An unknown capability must not be treated as supported.
|
|
4552
4836
|
|
|
4553
|
-
ISO capability is control metadata only. It
|
|
4837
|
+
ISO capability is control metadata only. It says nothing about sensor noise, analog gain topology, photon count, conversion gain or high-ISO quality.
|
|
4554
4838
|
|
|
4555
|
-
Likewise, shutter-duration capability
|
|
4839
|
+
Likewise, shutter-duration capability says nothing about the shutter mechanism, rolling/global readout, exposure-boundary timing or flash sync.
|
|
4556
4840
|
|
|
4557
4841
|
## Meter target and exposure compensation
|
|
4558
4842
|
|
|
4559
|
-
Use `createExposureMeterTargetFromMeteringResult()` to freeze a spatial or temporal metering result into the stable target
|
|
4843
|
+
Use `createExposureMeterTargetFromMeteringResult()` to freeze a spatial or temporal metering result into the stable target that exposure control reads.
|
|
4560
4844
|
|
|
4561
4845
|
Composition fragment: requires previously validated bindings `meterResult`. This is an integration sketch, not a standalone runnable program.
|
|
4562
4846
|
|
|
@@ -4587,11 +4871,11 @@ The frozen target copies the authoritative meter identity by value:
|
|
|
4587
4871
|
- scene-state ID;
|
|
4588
4872
|
- metering-profile ID.
|
|
4589
4873
|
|
|
4590
|
-
This is
|
|
4874
|
+
This is how AE lock works. Holding on to the target object deliberately keeps the earlier meter snapshot, even if the live scene later changes.
|
|
4591
4875
|
|
|
4592
4876
|
### Compensation semantics
|
|
4593
4877
|
|
|
4594
|
-
Exposure compensation is an **absolute
|
|
4878
|
+
Exposure compensation is an **absolute offset applied to the target**, after metering.
|
|
4595
4879
|
|
|
4596
4880
|
For a resolved target:
|
|
4597
4881
|
|
|
@@ -4607,19 +4891,19 @@ compensated exposure scale =
|
|
|
4607
4891
|
|
|
4608
4892
|
Therefore +1 EV compensation requests twice the exposure of the uncompensated target; -1 EV requests half.
|
|
4609
4893
|
|
|
4610
|
-
`setExposureCompensationOnMeterTarget()` always
|
|
4894
|
+
`setExposureCompensationOnMeterTarget()` always works from the stored, uncompensated base values. If you pass a target that is already compensated, with a new compensation value, the old compensation is not added on top. This stops drift when a slider or dial is moved repeatedly.
|
|
4611
4895
|
|
|
4612
|
-
A changed compensation state
|
|
4896
|
+
A changed compensation state needs a new `targetId`. The identity of the source meter snapshot stays the same.
|
|
4613
4897
|
|
|
4614
|
-
The meter measurement itself is never
|
|
4898
|
+
The meter measurement itself is never changed, and the target still reports `automaticExposureResolved: false`. Exposure control makes the later aperture/shutter/ISO decision.
|
|
4615
4899
|
|
|
4616
|
-
A no-signal meter target
|
|
4900
|
+
A `no-signal` meter target stays `no-signal` after compensation. The engine does not turn darkness into infinity, or make up an automatic exposure that could be reached.
|
|
4617
4901
|
|
|
4618
4902
|
## ISO/exposure-index capabilities and generic high-ISO signal chain
|
|
4619
4903
|
|
|
4620
|
-
Photivra keeps **reported ISO/exposure-index
|
|
4904
|
+
Photivra keeps the **reported ISO / exposure-index setting** separate from the captured photons, and from the [electronic readout model](#stochastic-charge-conversion-adc-and-raw-code) (#14).
|
|
4621
4905
|
|
|
4622
|
-
Use `parseIsoCapabilityProfile()` for detailed
|
|
4906
|
+
Use `parseIsoCapabilityProfile()` for detailed ISO capability metadata for a camera body, beyond the older generic numeric range.
|
|
4623
4907
|
|
|
4624
4908
|
The profile can declare:
|
|
4625
4909
|
|
|
@@ -4630,7 +4914,7 @@ The profile can declare:
|
|
|
4630
4914
|
- a global Auto ISO range;
|
|
4631
4915
|
- evidenced per-capture-mode standard ranges, expanded-setting availability, and Auto ISO ranges.
|
|
4632
4916
|
|
|
4633
|
-
The contract describes **
|
|
4917
|
+
The contract describes **which exposure-index settings a camera reports**, not physical sensor gain. Resolving a setting therefore states explicitly that it did not change or infer:
|
|
4634
4918
|
|
|
4635
4919
|
- captured-photon expectation;
|
|
4636
4920
|
- photon shot-noise statistics;
|
|
@@ -4640,36 +4924,36 @@ The contract describes **reported exposure-index capability**, not physical sens
|
|
|
4640
4924
|
- saturation;
|
|
4641
4925
|
- exact commercial-camera behavior.
|
|
4642
4926
|
|
|
4643
|
-
Expanded settings are
|
|
4927
|
+
Expanded settings are deliberately separate from the ordinary standard numeric setting grid. A reported `H1` equivalent does not mean Photivra knows how a real camera implements that setting internally.
|
|
4644
4928
|
|
|
4645
4929
|
### Binding detailed ISO capability into automatic exposure
|
|
4646
4930
|
|
|
4647
|
-
Use `bindIsoCapabilityToExposureCapabilities()` to combine
|
|
4931
|
+
Use `bindIsoCapabilityToExposureCapabilities()` to combine this detailed ISO profile (#7) with the resolved body/lens exposure envelope that exposure control reads.
|
|
4648
4932
|
|
|
4649
|
-
The global standard ISO range
|
|
4933
|
+
The global standard ISO range and grid must agree exactly between the two contracts. Conflicting duplicate facts fail closed.
|
|
4650
4934
|
|
|
4651
4935
|
An evidenced capture-mode policy may narrow the standard ISO range for that mode. Auto ISO can then be narrower still.
|
|
4652
4936
|
|
|
4653
|
-
For example, a mode may allow manual standard ISO 100–12800 while
|
|
4937
|
+
For example, a mode may allow manual standard ISO 100–12800 while limiting Auto ISO to 200–3200. Exposure control checks manual ISO against the mode's standard range and grid, but uses the separate Auto ISO bounds for every automatic-ISO path.
|
|
4654
4938
|
|
|
4655
|
-
Expanded settings
|
|
4939
|
+
Expanded settings stay outside the ordinary numeric Auto ISO resolver. They are selected explicitly through the detailed ISO capability contract.
|
|
4656
4940
|
|
|
4657
4941
|
### Generic high-ISO signal-chain profiles
|
|
4658
4942
|
|
|
4659
|
-
`parseGenericIsoSignalChainProfile()` and `resolveGenericIsoSignalChain()` provide the generic Stage-B model.
|
|
4943
|
+
`parseGenericIsoSignalChainProfile()` and `resolveGenericIsoSignalChain()` provide the generic "Stage-B" model, which links an ISO setting to the sensor's electronics.
|
|
4660
4944
|
|
|
4661
|
-
The profile does **not** define an ISO→noise equation. Instead, for one exact capture mode it maps:
|
|
4945
|
+
The profile does **not** define an ISO → noise equation. Instead, for one exact capture mode, it maps:
|
|
4662
4946
|
|
|
4663
4947
|
- standard ISO/EI bands; and
|
|
4664
4948
|
- explicitly named expanded ISO states
|
|
4665
4949
|
|
|
4666
|
-
to existing
|
|
4950
|
+
to existing `SensorReadoutConversionRegime` IDs (the [readout regimes](#stochastic-charge-conversion-adc-and-raw-code), #14).
|
|
4667
4951
|
|
|
4668
|
-
Those
|
|
4952
|
+
Those regimes own the actual declared conversion gain, electronic read-noise components, pre-ADC saturation, black level, ADC and RAW-code behaviour.
|
|
4669
4953
|
|
|
4670
|
-
|
|
4954
|
+
So changing ISO, or a generic profile, cannot change the expected photoelectrons or photon shot-noise statistics upstream. Any change in read noise or conversion/saturation behaviour comes from the explicitly selected regime.
|
|
4671
4955
|
|
|
4672
|
-
Signal-chain bands may
|
|
4956
|
+
Signal-chain bands may have gaps where only discrete ISO values are supported. Overlapping bands fail closed, rather than letting their order pick a regime.
|
|
4673
4957
|
|
|
4674
4958
|
The first generic profile deliberately does not add:
|
|
4675
4959
|
|
|
@@ -4679,38 +4963,38 @@ The first generic profile deliberately does not add:
|
|
|
4679
4963
|
- processed-image/JPEG behavior;
|
|
4680
4964
|
- manufacturer-specific response curves.
|
|
4681
4965
|
|
|
4682
|
-
Those effects
|
|
4966
|
+
Those effects need their own explicit models and evidence, and must not be hidden inside a quality label.
|
|
4683
4967
|
|
|
4684
4968
|
### Good / Better / Best convenience mapping
|
|
4685
4969
|
|
|
4686
4970
|
`parseGenericIsoSignalChainPresetCatalog()` can map the user-facing convenience labels `good`, `better`, and `best` to explicit signal-chain profile IDs.
|
|
4687
4971
|
|
|
4688
|
-
The labels are not the scientific input
|
|
4972
|
+
The labels are not the scientific input; the selected profile's parameters are.
|
|
4689
4973
|
|
|
4690
4974
|
The catalog explicitly carries:
|
|
4691
4975
|
|
|
4692
4976
|
- `performanceOrderingClaimed: false`;
|
|
4693
4977
|
- `realCameraRankingClaimed: false`.
|
|
4694
4978
|
|
|
4695
|
-
This
|
|
4979
|
+
This stops the convenience labels from turning into a claim that one class of real camera is always better, or that a tier name by itself changes sensor physics.
|
|
4696
4980
|
|
|
4697
4981
|
## Manual and Auto ISO exposure resolution
|
|
4698
4982
|
|
|
4699
|
-
Use `resolveManualExposureMode()` for the first
|
|
4983
|
+
Exposure control (#99) chooses the camera settings from a meter target. Use `resolveManualExposureMode()` for Manual mode, the first part of it.
|
|
4700
4984
|
|
|
4701
|
-
This resolver
|
|
4985
|
+
This resolver takes:
|
|
4702
4986
|
|
|
4703
|
-
- a frozen #100
|
|
4704
|
-
- the resolved
|
|
4987
|
+
- a frozen `ExposureMeterTarget` (from [metering](#meter-target-and-exposure-compensation), #100);
|
|
4988
|
+
- the resolved generic body + lens exposure capabilities (from [equipment capabilities](#generic-equipment-exposure-capabilities), #109);
|
|
4705
4989
|
- an explicit relative reference exposure anchor;
|
|
4706
|
-
-
|
|
4990
|
+
- aperture and shutter settings chosen by the caller;
|
|
4707
4991
|
- either manual ISO or Auto ISO.
|
|
4708
4992
|
|
|
4709
|
-
The resolver does **not** meter the scene, apply exposure compensation, change aperture
|
|
4993
|
+
The resolver does **not** meter the scene, apply exposure compensation, change aperture or shutter, or infer sensor noise or gain behaviour.
|
|
4710
4994
|
|
|
4711
4995
|
### Reference exposure anchor
|
|
4712
4996
|
|
|
4713
|
-
The relative meter target tells the resolver how much exposure change
|
|
4997
|
+
The relative meter target tells the resolver how much the exposure must change. On its own, it does not define an absolute ISO.
|
|
4714
4998
|
|
|
4715
4999
|
Therefore this API requires:
|
|
4716
5000
|
|
|
@@ -4722,9 +5006,9 @@ const referenceExposure = {
|
|
|
4722
5006
|
};
|
|
4723
5007
|
```
|
|
4724
5008
|
|
|
4725
|
-
The anchor means: when the target scale is `1`, this aperture/shutter/ISO combination
|
|
5009
|
+
The anchor means: when the target scale is `1`, this aperture/shutter/ISO combination is the declared relative exposure baseline.
|
|
4726
5010
|
|
|
4727
|
-
It is explicit input, not a universal ISO-100 or
|
|
5011
|
+
It is an explicit input, not a universal ISO-100 or grey-card rule. All three anchor values must be valid in the same resolved equipment capability envelope.
|
|
4728
5012
|
|
|
4729
5013
|
### Manual + Auto ISO
|
|
4730
5014
|
|
|
@@ -4760,9 +5044,9 @@ With aperture/shutter fixed at the reference values:
|
|
|
4760
5044
|
- target scale `2` resolves ideal ISO 200;
|
|
4761
5045
|
- target scale `4` resolves ideal ISO 400.
|
|
4762
5046
|
|
|
4763
|
-
This is the controlled
|
|
5047
|
+
This is the controlled rule for responding to live changes in scene light: if the scene gets 1 stop darker everywhere, the ideal ISO goes up 1 stop, when the limits and grid allow it.
|
|
4764
5048
|
|
|
4765
|
-
If manual optical settings differ from the reference, the resolver compensates only through ISO:
|
|
5049
|
+
If the manual optical settings differ from the reference, the resolver compensates only through ISO:
|
|
4766
5050
|
|
|
4767
5051
|
```text
|
|
4768
5052
|
manual optical factor =
|
|
@@ -4778,7 +5062,7 @@ ideal Auto ISO =
|
|
|
4778
5062
|
|
|
4779
5063
|
### Discrete ISO quantization
|
|
4780
5064
|
|
|
4781
|
-
For a discrete
|
|
5065
|
+
For a discrete ISO grid from the equipment capabilities, the first policy picks the nearest valid ISO in log2 exposure space (that is, nearest in stops).
|
|
4782
5066
|
|
|
4783
5067
|
On an exact midpoint tie, the lower ISO is selected.
|
|
4784
5068
|
|
|
@@ -4805,7 +5089,7 @@ The limiting constraint is reported as one of:
|
|
|
4805
5089
|
|
|
4806
5090
|
If Auto ISO capability is `unsupported` or `unknown`, the resolver returns a blocked result. Unknown is not treated as supported.
|
|
4807
5091
|
|
|
4808
|
-
A `no-signal` meter target also blocks Auto ISO
|
|
5092
|
+
A `no-signal` meter target also blocks Auto ISO, instead of selecting maximum ISO or infinity.
|
|
4809
5093
|
|
|
4810
5094
|
### Manual ISO
|
|
4811
5095
|
|
|
@@ -4826,17 +5110,17 @@ const resolved =
|
|
|
4826
5110
|
});
|
|
4827
5111
|
```
|
|
4828
5112
|
|
|
4829
|
-
Manual aperture, shutter
|
|
5113
|
+
Manual aperture, shutter and ISO are kept exactly, after they are checked against the capabilities.
|
|
4830
5114
|
|
|
4831
|
-
A changed meter target or exposure compensation changes only the target-residual diagnostic. It
|
|
5115
|
+
A changed meter target or exposure compensation changes only the target-residual diagnostic. It never changes a manual setting.
|
|
4832
5116
|
|
|
4833
|
-
This is
|
|
5117
|
+
This is deliberately different from Manual + Auto ISO.
|
|
4834
5118
|
|
|
4835
5119
|
### Boundaries
|
|
4836
5120
|
|
|
4837
|
-
|
|
5121
|
+
Exposure control now covers Manual, the priority modes, Program, Full Auto exposure, and Bulb/Time control.
|
|
4838
5122
|
|
|
4839
|
-
|
|
5123
|
+
These stay deliberately separate, rather than being hidden inside shooting-mode resolution:
|
|
4840
5124
|
|
|
4841
5125
|
- safety shift;
|
|
4842
5126
|
- flash-aware exposure;
|
|
@@ -4845,7 +5129,7 @@ The following remain intentionally separate rather than hidden inside shooting-m
|
|
|
4845
5129
|
|
|
4846
5130
|
## Aperture Priority with manual ISO
|
|
4847
5131
|
|
|
4848
|
-
Use `resolveAperturePriorityExposureMode()` when aperture and ISO
|
|
5132
|
+
Use `resolveAperturePriorityExposureMode()` when you choose aperture and ISO, and shutter is the only automatic setting.
|
|
4849
5133
|
|
|
4850
5134
|
Composition fragment: requires previously validated bindings `capabilities`, `target`. This is an integration sketch, not a standalone runnable program.
|
|
4851
5135
|
|
|
@@ -4870,7 +5154,7 @@ const resolved =
|
|
|
4870
5154
|
});
|
|
4871
5155
|
```
|
|
4872
5156
|
|
|
4873
|
-
This mode uses the same relative target/reference model as Manual + Auto ISO. It
|
|
5157
|
+
This mode uses the same relative target/reference model as Manual + Auto ISO. It adds no exposure equation of its own.
|
|
4874
5158
|
|
|
4875
5159
|
The ideal shutter is:
|
|
4876
5160
|
|
|
@@ -4885,13 +5169,13 @@ ideal shutter =
|
|
|
4885
5169
|
/ fixed-axis factor
|
|
4886
5170
|
```
|
|
4887
5171
|
|
|
4888
|
-
Aperture and ISO are
|
|
5172
|
+
Aperture and ISO are checked against the same resolved capability envelope, then kept exactly.
|
|
4889
5173
|
|
|
4890
5174
|
### Shutter capability and quantization
|
|
4891
5175
|
|
|
4892
|
-
For a continuous shutter grid, an
|
|
5176
|
+
For a continuous shutter grid, an ideal duration inside the range is used as is.
|
|
4893
5177
|
|
|
4894
|
-
For a discrete shutter grid, the first policy
|
|
5178
|
+
For a discrete shutter grid, the first policy picks the nearest duration in log2 exposure-time space (nearest in stops). On an exact tie, the **shorter duration** wins.
|
|
4895
5179
|
|
|
4896
5180
|
The result reports:
|
|
4897
5181
|
|
|
@@ -4917,21 +5201,21 @@ Residual sign is shared with the Manual resolver:
|
|
|
4917
5201
|
|
|
4918
5202
|
### Exposure compensation and no-signal
|
|
4919
5203
|
|
|
4920
|
-
Exposure compensation is already
|
|
5204
|
+
Exposure compensation is already included in the meter target. Aperture Priority uses that target and does not apply compensation again.
|
|
4921
5205
|
|
|
4922
|
-
A no-signal target blocks automatic shutter resolution. The engine does not
|
|
5206
|
+
A no-signal target blocks automatic shutter resolution. The engine does not pick the longest shutter or an infinite duration.
|
|
4923
5207
|
|
|
4924
5208
|
### Boundaries
|
|
4925
5209
|
|
|
4926
|
-
This
|
|
5210
|
+
This resolver deliberately keeps ISO manual.
|
|
4927
5211
|
|
|
4928
|
-
Aperture Priority + Auto ISO is
|
|
5212
|
+
Aperture Priority + Auto ISO is a separate resolver ([next section](#priority-modes-with-auto-iso)), because two automatic settings need an explicit selection policy, such as a minimum-shutter rule. The engine must not assume that ISO always moves before, or after, the shutter.
|
|
4929
5213
|
|
|
4930
|
-
Flash-aware shutter
|
|
5214
|
+
Flash-aware shutter limits, safety shift and manufacturer-specific program behaviour are also separate.
|
|
4931
5215
|
|
|
4932
5216
|
## Priority modes with Auto ISO
|
|
4933
5217
|
|
|
4934
|
-
|
|
5218
|
+
These resolvers add the remaining Aperture (A) and Shutter (S) Priority combinations.
|
|
4935
5219
|
|
|
4936
5220
|
### Aperture Priority + Auto ISO
|
|
4937
5221
|
|
|
@@ -4970,7 +5254,7 @@ The policy is explicit:
|
|
|
4970
5254
|
- `allow-slower-shutter`; or
|
|
4971
5255
|
- `hold-preferred-shutter` and return residual underexposure.
|
|
4972
5256
|
|
|
4973
|
-
For discrete shutter grids, the
|
|
5257
|
+
For discrete shutter grids, the shutter chosen before ISO is raised is never longer than the requested or preferred duration. Otherwise rounding to the grid could overexpose, and undoing that would need an ISO below the supported minimum.
|
|
4974
5258
|
|
|
4975
5259
|
### Shutter Priority + manual ISO
|
|
4976
5260
|
|
|
@@ -4992,9 +5276,9 @@ const resolved =
|
|
|
4992
5276
|
});
|
|
4993
5277
|
```
|
|
4994
5278
|
|
|
4995
|
-
Shutter and ISO
|
|
5279
|
+
Shutter and ISO stay fixed; only the aperture is resolved.
|
|
4996
5280
|
|
|
4997
|
-
|
|
5281
|
+
With continuous aperture capability, the ideal f-number is used as is. Discrete aperture grids pick the nearest f-number in log2 space, with the narrower aperture winning an exact tie.
|
|
4998
5282
|
|
|
4999
5283
|
### Shutter Priority + Auto ISO
|
|
5000
5284
|
|
|
@@ -5030,26 +5314,26 @@ This policy:
|
|
|
5030
5314
|
3. for a discrete aperture grid, chooses an aperture no wider than the ideal;
|
|
5031
5315
|
4. uses Auto ISO to fill the remaining exposure.
|
|
5032
5316
|
|
|
5033
|
-
That direction is deliberate
|
|
5317
|
+
That direction is deliberate. Choosing an aperture wider than the ideal at minimum ISO could overexpose, with no lower ISO left to compensate.
|
|
5034
5318
|
|
|
5035
5319
|
### Shared boundaries
|
|
5036
5320
|
|
|
5037
|
-
These are control policies over the same relative exposure equation. They
|
|
5321
|
+
These are control policies over the same relative exposure equation. They create no new optics or sensor physics.
|
|
5038
5322
|
|
|
5039
5323
|
All modes:
|
|
5040
5324
|
|
|
5041
|
-
-
|
|
5042
|
-
-
|
|
5325
|
+
- use the meter target, with compensation already applied;
|
|
5326
|
+
- use the resolved capability envelope;
|
|
5043
5327
|
- use the explicit reference exposure anchor;
|
|
5044
|
-
-
|
|
5045
|
-
- do not infer ISO noise
|
|
5328
|
+
- keep the same sign convention for residual stops;
|
|
5329
|
+
- do not infer ISO noise or gain topology;
|
|
5046
5330
|
- do not apply flash policy or safety shift.
|
|
5047
5331
|
|
|
5048
|
-
Flash-aware
|
|
5332
|
+
Flash-aware behaviour, safety shift and the program behaviour of named cameras are separate work.
|
|
5049
5333
|
|
|
5050
5334
|
## Program Auto and Full Auto exposure
|
|
5051
5335
|
|
|
5052
|
-
Program Auto and Full Auto exposure both
|
|
5336
|
+
Program Auto and Full Auto exposure both need a declared, versioned **program line**: a curve that says which aperture and shutter to use at each brightness level. The engine does not invent one "correct" aperture/shutter pair.
|
|
5053
5337
|
|
|
5054
5338
|
### Generic program line
|
|
5055
5339
|
|
|
@@ -5108,9 +5392,9 @@ const programLine =
|
|
|
5108
5392
|
});
|
|
5109
5393
|
```
|
|
5110
5394
|
|
|
5111
|
-
Each node's declared stop
|
|
5395
|
+
Each node's declared stop value must match its aperture/shutter optical exposure, relative to the same `referenceExposure` given to the exposure resolver. Nodes outside the resolved aperture/shutter capability envelope fail closed.
|
|
5112
5396
|
|
|
5113
|
-
Between nodes, aperture and shutter interpolate in log2 space.
|
|
5397
|
+
Between nodes, aperture and shutter interpolate in log2 space. That keeps the optical exposure, in stops, linear along each segment, before rounding to the equipment's settings.
|
|
5114
5398
|
|
|
5115
5399
|
### Program Auto with manual ISO
|
|
5116
5400
|
|
|
@@ -5128,9 +5412,9 @@ const resolved =
|
|
|
5128
5412
|
});
|
|
5129
5413
|
```
|
|
5130
5414
|
|
|
5131
|
-
Aperture and shutter are automatic
|
|
5415
|
+
Aperture and shutter are automatic. ISO is kept exactly, after it is checked against the capabilities.
|
|
5132
5416
|
|
|
5133
|
-
If the target falls beyond the first
|
|
5417
|
+
If the target falls beyond the first or last program-line node, the line stops at that endpoint, and the result reports the remaining under- or over-exposure.
|
|
5134
5418
|
|
|
5135
5419
|
### Program Auto with Auto ISO
|
|
5136
5420
|
|
|
@@ -5157,7 +5441,7 @@ The program line is first evaluated at the minimum selectable ISO. Auto ISO then
|
|
|
5157
5441
|
- aperture/shutter quantization;
|
|
5158
5442
|
- capability clamping.
|
|
5159
5443
|
|
|
5160
|
-
If the program line
|
|
5444
|
+
If the program line gives too much exposure even at minimum ISO, ISO stays at the minimum and the residual is reported.
|
|
5161
5445
|
|
|
5162
5446
|
### Full Auto exposure
|
|
5163
5447
|
|
|
@@ -5190,33 +5474,33 @@ It explicitly does **not** resolve:
|
|
|
5190
5474
|
- scene recognition;
|
|
5191
5475
|
- stabilization policy.
|
|
5192
5476
|
|
|
5193
|
-
Those fields
|
|
5477
|
+
Those fields stay false in the result, so an application cannot accidentally treat this mode as automation of the whole camera.
|
|
5194
5478
|
|
|
5195
5479
|
### Program/Full Auto consistency
|
|
5196
5480
|
|
|
5197
5481
|
Given the same:
|
|
5198
5482
|
|
|
5199
|
-
-
|
|
5200
|
-
-
|
|
5483
|
+
- exposure target;
|
|
5484
|
+
- equipment capabilities;
|
|
5201
5485
|
- reference exposure;
|
|
5202
5486
|
- program line;
|
|
5203
5487
|
- Auto ISO policy;
|
|
5204
5488
|
|
|
5205
|
-
Program Auto + Auto ISO and Full Auto exposure should
|
|
5489
|
+
Program Auto + Auto ISO and Full Auto exposure should give identical aperture, shutter and ISO values.
|
|
5206
5490
|
|
|
5207
|
-
Only the control
|
|
5491
|
+
Only the name of the control mode differs.
|
|
5208
5492
|
|
|
5209
5493
|
### Boundaries
|
|
5210
5494
|
|
|
5211
|
-
Program lines are product policy
|
|
5495
|
+
Program lines are approximations of product policy, not physical laws. Do not describe a generic line as reproducing a specific manufacturer unless exact evidence supports that claim.
|
|
5212
5496
|
|
|
5213
|
-
Flash-aware program changes, safety shift
|
|
5497
|
+
Flash-aware program changes, safety shift and wider Full Auto camera decisions are separate work.
|
|
5214
5498
|
|
|
5215
5499
|
## Bulb, Time, and long-exposure control
|
|
5216
5500
|
|
|
5217
|
-
Bulb and Time are **duration
|
|
5501
|
+
Bulb and Time are **ways of controlling duration**, not different exposure equations. (Bulb: the shutter stays open while the button is held. Time: one press opens it, a second press closes it.)
|
|
5218
5502
|
|
|
5219
|
-
Use `resolveExposureDurationControl()` to turn user
|
|
5503
|
+
Use `resolveExposureDurationControl()` to turn user or control events into the concrete, positive number of seconds that image formation needs.
|
|
5220
5504
|
|
|
5221
5505
|
### Fixed duration
|
|
5222
5506
|
|
|
@@ -5275,9 +5559,9 @@ const time =
|
|
|
5275
5559
|
});
|
|
5276
5560
|
```
|
|
5277
5561
|
|
|
5278
|
-
Bulb and Time
|
|
5562
|
+
Bulb and Time need finite timestamps, in order, from the same explicit monotonic-seconds control clock.
|
|
5279
5563
|
|
|
5280
|
-
An unfinished Bulb
|
|
5564
|
+
An unfinished Bulb or Time control cannot enter the physical exposure pipeline.
|
|
5281
5565
|
|
|
5282
5566
|
### Bind into authoritative exposure timing
|
|
5283
5567
|
|
|
@@ -5325,9 +5609,9 @@ It does **not** change or infer:
|
|
|
5325
5609
|
- sensor temperature/heating;
|
|
5326
5610
|
- flash behavior.
|
|
5327
5611
|
|
|
5328
|
-
There is no universal 30-second cap for Bulb
|
|
5612
|
+
There is no universal 30-second cap for Bulb or Time. A completed 300-second Bulb exposure resolves to 300 seconds.
|
|
5329
5613
|
|
|
5330
|
-
Once resolved, that duration
|
|
5614
|
+
Once resolved, that duration follows the same physical path, now and in future, as any fixed duration:
|
|
5331
5615
|
|
|
5332
5616
|
```text
|
|
5333
5617
|
resolved elapsed seconds
|
|
@@ -5343,9 +5627,9 @@ There is no Bulb-specific physics shortcut.
|
|
|
5343
5627
|
|
|
5344
5628
|
### Mode-invariant downstream physics
|
|
5345
5629
|
|
|
5346
|
-
Shooting-mode names are control policy
|
|
5630
|
+
Shooting-mode names are only control policy.
|
|
5347
5631
|
|
|
5348
|
-
If Manual, Program
|
|
5632
|
+
If Manual, Program and Full Auto exposure resolve the same:
|
|
5349
5633
|
|
|
5350
5634
|
- aperture;
|
|
5351
5635
|
- shutter duration;
|
|
@@ -5353,7 +5637,7 @@ If Manual, Program, and Full Auto exposure resolve the same:
|
|
|
5353
5637
|
|
|
5354
5638
|
then `calculateRelativeRenderedExposure()` and all later physical models receive identical physical inputs and must produce identical results.
|
|
5355
5639
|
|
|
5356
|
-
Likewise, equal fixed
|
|
5640
|
+
Likewise, equal elapsed durations from fixed, Bulb or Time control, with the same shutter and timing inputs, give identical authoritative exposure windows.
|
|
5357
5641
|
|
|
5358
5642
|
## Exposure and ISO relations
|
|
5359
5643
|
|
|
@@ -5386,7 +5670,7 @@ console.log(exposure.value.factor);
|
|
|
5386
5670
|
console.log(exposure.value.stops);
|
|
5387
5671
|
```
|
|
5388
5672
|
|
|
5389
|
-
Use `calculateRelativeRenderedExposure()` when a renderer needs a nominal linear brightness multiplier
|
|
5673
|
+
Use `calculateRelativeRenderedExposure()` when a renderer needs a nominal linear brightness multiplier. It combines the change in optical exposure (aperture and shutter) with ISO gain, relative to declared reference settings:
|
|
5390
5674
|
|
|
5391
5675
|
```ts
|
|
5392
5676
|
import { calculateRelativeRenderedExposure } from "@photivra/engine";
|
|
@@ -5406,9 +5690,9 @@ console.log(rendered.value.opticalFactor); // 0.5
|
|
|
5406
5690
|
console.log(rendered.value.isoGainFactor); // 2
|
|
5407
5691
|
```
|
|
5408
5692
|
|
|
5409
|
-
This is a relative rendering relation, not a radiometric sensor model. ISO is treated as nominal rendering gain; it does not create photons
|
|
5693
|
+
This is a relative rendering relation, not a radiometric sensor model. ISO is treated as nominal rendering gain; it does not create photons. The function does not model clipping, tone mapping, lens transmission or sensor-specific noise.
|
|
5410
5694
|
|
|
5411
|
-
Use `calculateEquivalentIso()` to calculate the nominal ISO
|
|
5695
|
+
Use `calculateEquivalentIso()` to calculate the nominal ISO (gain) change needed to keep the rendered exposure the same:
|
|
5412
5696
|
|
|
5413
5697
|
```ts
|
|
5414
5698
|
import { calculateEquivalentIso } from "@photivra/engine";
|
|
@@ -5426,11 +5710,11 @@ console.log(iso.value);
|
|
|
5426
5710
|
|
|
5427
5711
|
These relations do not model scene radiance, lens T-stop/transmission, vignetting, or sensor-specific gain/noise behavior.
|
|
5428
5712
|
|
|
5429
|
-
See [Motion and Signal Foundation](MOTION_AND_SIGNAL.md#exposure-relations).
|
|
5713
|
+
See [Motion and Signal Foundation](https://github.com/Photivra/photivra/blob/main/docs/MOTION_AND_SIGNAL.md#exposure-relations).
|
|
5430
5714
|
|
|
5431
5715
|
## Capture rotation exposure trajectories
|
|
5432
5716
|
|
|
5433
|
-
Use `calculateCaptureRotationTrajectories()`
|
|
5717
|
+
Use `calculateCaptureRotationTrajectories()` to evaluate **pure camera rotation over each native point's own local exposure interval**. Shutter/exposure timing and sensor readout stay separate:
|
|
5434
5718
|
|
|
5435
5719
|
```ts
|
|
5436
5720
|
import { calculateCaptureRotationTrajectories } from "@photivra/engine";
|
|
@@ -5514,27 +5798,27 @@ const trajectories = calculateCaptureRotationTrajectories({
|
|
|
5514
5798
|
console.log(trajectories.value.samples);
|
|
5515
5799
|
```
|
|
5516
5800
|
|
|
5517
|
-
The function maps each native point to the
|
|
5801
|
+
The function maps each native point to the image plane (before orientation) at the capture reference time. It then evaluates the existing `calculateCameraRotationImageMapping()` primitive at that point's local exposure start and end.
|
|
5518
5802
|
|
|
5519
|
-
The explicit time reference is the **first opening-boundary phase**. For this composition only, that reference is
|
|
5803
|
+
The explicit time reference is the **first opening-boundary phase**. For this composition only, that reference is tied to `t = 0` of the low-level rotation model. This does not change the standalone rotation API for other callers.
|
|
5520
5804
|
|
|
5521
|
-
The returned trajectory is **forward
|
|
5805
|
+
The returned trajectory is the **forward path over time of one stationary ray from the world**:
|
|
5522
5806
|
|
|
5523
5807
|
- `referenceImagePointMm` is the ray's image-plane position at the capture reference;
|
|
5524
5808
|
- `atLocalExposureStart` and `atLocalExposureEnd` are its mapped positions at the local window endpoints;
|
|
5525
5809
|
- `localExposureTrajectoryEndpointDeltaMm` is the chord between those endpoints.
|
|
5526
5810
|
|
|
5527
|
-
The chord is not an integrated blur kernel.
|
|
5811
|
+
The chord is not an integrated blur kernel. Nor is it a rolling-shutter warp ready for a renderer.
|
|
5528
5812
|
|
|
5529
|
-
|
|
5813
|
+
When the capture scans across the sensor, the image mapping depends on location, because capture time varies across the image. For the current pure-rotation model, going back from destination to reference is analytic once the destination location has picked its local capture time. More general motion models may need extra solving and checks that the mapping is valid everywhere. Rendering a finite exposure still needs integration over the whole local interval, not one endpoint or midpoint displacement.
|
|
5530
5814
|
|
|
5531
|
-
`calculateSensorReadoutTiming()` is
|
|
5815
|
+
`calculateSensorReadoutTiming()` is deliberately not an input. If a specific capture mode has a defensible relationship between electronic exposure timing and sensor readout timing, a future explicit mode/link contract should represent it. This API does not assume it.
|
|
5532
5816
|
|
|
5533
|
-
The
|
|
5817
|
+
The trajectory model is pure rotation only. It leaves out camera translation/parallax, subject motion, rotating or deforming subjects, occlusion changes, panning intent, exposure integration, flash/flicker, shutter shock and the final inverse rolling-shutter warp.
|
|
5534
5818
|
|
|
5535
5819
|
## Capture rotation temporal quadrature
|
|
5536
5820
|
|
|
5537
|
-
Use `calculateCaptureRotationTemporalQuadrature()` when a
|
|
5821
|
+
Use `calculateCaptureRotationTemporalQuadrature()` when a renderer or reference evaluator needs deterministic geometry samples for pure rotation, spread over each destination point's whole local exposure interval:
|
|
5538
5822
|
|
|
5539
5823
|
```ts
|
|
5540
5824
|
import { calculateCaptureRotationTemporalQuadrature } from "@photivra/engine";
|
|
@@ -5580,18 +5864,18 @@ The first model uses deterministic uniform midpoint quadrature. With `N` samples
|
|
|
5580
5864
|
phase_i = (i + 0.5) / N
|
|
5581
5865
|
```
|
|
5582
5866
|
|
|
5583
|
-
Each phase is passed through `calculateCaptureRotationInverseMappings()`, so instantaneous destination-to-reference geometry
|
|
5867
|
+
Each phase is passed through `calculateCaptureRotationInverseMappings()`, so the instantaneous destination-to-reference geometry is defined by one existing public model.
|
|
5584
5868
|
|
|
5585
|
-
Every node
|
|
5869
|
+
Every node has two different time weights:
|
|
5586
5870
|
|
|
5587
|
-
- `normalizedTimeWeight = 1 / N
|
|
5588
|
-
- `timeMeasureSeconds = localExposureDuration / N
|
|
5871
|
+
- `normalizedTimeWeight = 1 / N`, for a time average under the declared uniform temporal-response approximation;
|
|
5872
|
+
- `timeMeasureSeconds = localExposureDuration / N`, for a time integral.
|
|
5589
5873
|
|
|
5590
|
-
Do not
|
|
5874
|
+
Do not swap them. The first is dimensionless; the second is in seconds. Neither includes shutter transmission, scene radiance, sensor response, photon conversion, vignetting or any other radiometric throughput.
|
|
5591
5875
|
|
|
5592
|
-
Different sensor locations may have different local exposure start
|
|
5876
|
+
Different sensor locations may have different local exposure start and end times. If the opening and closing scans give different local durations, `timeMeasureSeconds` differs between destination points, while the normalized weights still sum to about one for each point.
|
|
5593
5877
|
|
|
5594
|
-
This API returns **
|
|
5878
|
+
This API returns **only time-sampled geometry and its weights**. It does not:
|
|
5595
5879
|
|
|
5596
5880
|
- fetch or sample scene radiance;
|
|
5597
5881
|
- average the reference coordinates;
|
|
@@ -5601,13 +5885,13 @@ This API returns **temporal geometry nodes and measures only**. It does not:
|
|
|
5601
5885
|
- apply flash/flicker or shutter-shock behavior;
|
|
5602
5886
|
- write or resample output pixels.
|
|
5603
5887
|
|
|
5604
|
-
Averaging geometric coordinates is not
|
|
5888
|
+
Averaging geometric coordinates is not the same as integrating radiance along moving rays.
|
|
5605
5889
|
|
|
5606
|
-
Photivra also does not report
|
|
5890
|
+
Photivra also does not report an integration-error estimate from geometry alone. The error in the image depends on radiance, visibility, how fine the texture and edges are, reconstruction and the motion path. When convergence evidence is needed, a reference renderer can compare increasing `temporalSampleCount` values on the radiance or output.
|
|
5607
5891
|
|
|
5608
5892
|
## Instantaneous capture rotation inverse mapping
|
|
5609
5893
|
|
|
5610
|
-
Use `calculateCaptureRotationInverseMappings()` to map captured destination locations back to the **first
|
|
5894
|
+
Use `calculateCaptureRotationInverseMappings()` to map captured destination locations back to the **reference image at the first opening boundary**, under pure camera rotation:
|
|
5611
5895
|
|
|
5612
5896
|
```ts
|
|
5613
5897
|
import {
|
|
@@ -5701,9 +5985,9 @@ console.log(mapping.value.samples);
|
|
|
5701
5985
|
- `0.5` evaluates the local temporal midpoint;
|
|
5702
5986
|
- `1` evaluates local exposure end.
|
|
5703
5987
|
|
|
5704
|
-
Photivra does not choose a default phase because a finite exposure
|
|
5988
|
+
Photivra does not choose a default phase, because a finite exposure has no single correct sharp geometry.
|
|
5705
5989
|
|
|
5706
|
-
Each destination native point first
|
|
5990
|
+
Each destination native point first picks its local exposure time. Under the current model (pure rotation about a constant axis), the captured ray can then be rotated **analytically** back to the exposure-start reference frame with `calculateInverseCameraRotationImageMapping()`. No iterative solver or convergence tolerance is needed.
|
|
5707
5991
|
|
|
5708
5992
|
The result reports:
|
|
5709
5993
|
|
|
@@ -5714,19 +5998,19 @@ The result reports:
|
|
|
5714
5998
|
- equivalent continuous native effective-sample displacement;
|
|
5715
5999
|
- that displacement rotated into the requested physical capture orientation.
|
|
5716
6000
|
|
|
5717
|
-
The mapping deliberately
|
|
6001
|
+
The mapping deliberately stays in the ideal geometry stage, before the lens. It does not apply radial distortion, lateral CA, PSF, output crop or output resampling.
|
|
5718
6002
|
|
|
5719
|
-
Reference rays are **not clamped** to the active source region. Camera motion can
|
|
6003
|
+
Reference rays are **not clamped** to the active source region. Camera motion can mean that a valid captured destination needs reference-image data from outside that region. A renderer must decide how to handle missing source coverage, rather than silently clamping or stretching an edge.
|
|
5720
6004
|
|
|
5721
|
-
This is an **instantaneous** mapping only. It is not a complete finite
|
|
6005
|
+
This is an **instantaneous** mapping only. It is not a complete image of a finite rolling (scanned) exposure. Motion blur needs several time samples, or another defensible way of integrating over each local exposure window, including an explicit exposure-weighting model if the weighting is not uniform.
|
|
5722
6006
|
|
|
5723
|
-
Sensor data-readout timing is
|
|
6007
|
+
Sensor data-readout timing is deliberately not part of this calculation. The capture exposure boundaries stay the authoritative timing input, until a separate capture-mode/link contract says otherwise.
|
|
5724
6008
|
|
|
5725
|
-
The
|
|
6009
|
+
The model is pure rotation only. Outside it: translation/parallax, subject motion, deformation, occlusion changes, checks that the mapping never folds over on itself, flash/flicker, shutter shock and finite-exposure integration.
|
|
5726
6010
|
|
|
5727
6011
|
## Spatial camera-rotation mapping
|
|
5728
6012
|
|
|
5729
|
-
Use `calculateCameraRotationImageMapping()` when you need
|
|
6013
|
+
Use `calculateCameraRotationImageMapping()` when you need to know where a stationary ray from the world lands on the image plane after **pure camera rotation**, at any physical time after exposure start.
|
|
5730
6014
|
|
|
5731
6015
|
```ts
|
|
5732
6016
|
import { calculateCameraRotationImageMapping } from "@photivra/engine";
|
|
@@ -5753,17 +6037,18 @@ console.log(mapped.value.deltaImagePlaneSamples);
|
|
|
5753
6037
|
|
|
5754
6038
|
Coordinate/sign convention:
|
|
5755
6039
|
|
|
5756
|
-
- camera axes at exposure start are +X right, +Y up, +Z forward;
|
|
5757
|
-
- positive pitch/yaw/roll follow the right-hand rule about +X/+Y/+Z;
|
|
6040
|
+
- camera axes at exposure start are +X right, +Y up, +Z forward (a left-handed basis);
|
|
6041
|
+
- positive pitch/yaw/roll follow the physical right-hand rule about +X/+Y/+Z: positive yaw turns the camera left and moves stationary content toward +X; positive pitch tilts it up and moves content toward −Y; positive roll is clockwise as seen from behind the camera and rotates content counter-clockwise;
|
|
5758
6042
|
- the returned image-plane basis is +X right, +Y up;
|
|
5759
|
-
- a stationary world ray is transformed by the **inverse** camera rotation, so
|
|
6043
|
+
- a stationary world ray is transformed by the **inverse** camera rotation, so image content moves opposite to the camera's own turn;
|
|
6044
|
+
- `estimateCameraShakeBlur()` uses the same yaw/pitch signs (from 2.0.0); see [Motion and signal](https://github.com/Photivra/photivra/blob/main/docs/MOTION_AND_SIGNAL.md#spatial-camera-rotation);
|
|
5760
6045
|
- optional `deltaImagePlaneSamples` retains image-plane +Y-up semantics and is **not** a native-raster (+Y-down) vector.
|
|
5761
6046
|
|
|
5762
|
-
The angular-velocity vector is assumed constant and is integrated as one axis-angle rotation. This avoids arbitrary Euler
|
|
6047
|
+
The angular-velocity vector is assumed constant, and is integrated as one axis-angle rotation. This avoids picking an arbitrary Euler-angle order when pitch, yaw and roll happen at the same time.
|
|
5763
6048
|
|
|
5764
|
-
The function is
|
|
6049
|
+
The function is deliberately rotation-only. Camera translation is left out, because image motion from translation depends on scene depth (parallax).
|
|
5765
6050
|
|
|
5766
|
-
It is also separate from stabilization. `estimateCameraShakeBlur()`
|
|
6051
|
+
It is also separate from stabilization. `estimateCameraShakeBlur()` is still the older, educational stabilization-equivalent approximation with one global vector. From 2.0.0, its yaw/pitch signs match this mapping at the optical axis.
|
|
5767
6052
|
|
|
5768
6053
|
## Camera shake and stabilization-equivalent approximation
|
|
5769
6054
|
|
|
@@ -5790,13 +6075,20 @@ console.log(shake.value.residualMotionFactor);
|
|
|
5790
6075
|
console.log(shake.provenance.kind); // "approximation"
|
|
5791
6076
|
```
|
|
5792
6077
|
|
|
5793
|
-
|
|
6078
|
+
`deltaXmm`/`deltaYmm` are the image displacement (+X right, +Y up) of a stationary point on the optical axis.
|
|
6079
|
+
|
|
6080
|
+
- Positive yaw (camera turns left) gives positive `deltaXmm`.
|
|
6081
|
+
- Positive pitch (camera tilts up) gives negative `deltaYmm`.
|
|
6082
|
+
- These signs match `calculateCameraRotationImageMapping()` at the optical axis. For a single axis, the displacements are equal within floating-point rounding.
|
|
6083
|
+
- A per-axis rotation of π/2 rad or more over the exposure is rejected.
|
|
6084
|
+
|
|
6085
|
+
The `2^-stops` attenuation is an educational approximation, not a real IBIS/OIS (in-body / optical image stabilization) or CIPA rating. The output is one global image-plane vector, not image motion that varies across the frame.
|
|
5794
6086
|
|
|
5795
|
-
See [Camera Shake and Stabilization](STABILIZATION.md).
|
|
6087
|
+
See [Camera Shake and Stabilization](https://github.com/Photivra/photivra/blob/main/docs/STABILIZATION.md).
|
|
5796
6088
|
|
|
5797
6089
|
## Radiometry readiness
|
|
5798
6090
|
|
|
5799
|
-
Use `parseRadiometryReadinessProfile()` and `assessRadiometryReadiness()` to
|
|
6091
|
+
Use `parseRadiometryReadinessProfile()` and `assessRadiometryReadiness()` to check whether a declared radiometric calibration package has the prerequisites a future photon estimate would need.
|
|
5800
6092
|
|
|
5801
6093
|
The gate covers six independently declared requirements:
|
|
5802
6094
|
|
|
@@ -5850,21 +6142,21 @@ console.log(readiness.composedPhotonOutputEnabled); // always false
|
|
|
5850
6142
|
|
|
5851
6143
|
Readiness states are:
|
|
5852
6144
|
|
|
5853
|
-
- `not-ready`: one or more required
|
|
5854
|
-
- `approximate-only`: every category is present, but at least one component is approximate or has
|
|
6145
|
+
- `not-ready`: one or more required categories are missing;
|
|
6146
|
+
- `approximate-only`: every category is present, but at least one component is approximate or has no quantified uncertainty;
|
|
5855
6147
|
- `calibrated-ready`: every category is declared calibrated and carries quantified uncertainty.
|
|
5856
6148
|
|
|
5857
|
-
A readiness assessment
|
|
6149
|
+
A readiness assessment checks the declared structure and provenance metadata. It does not check that a source or calibration is scientifically correct.
|
|
5858
6150
|
|
|
5859
|
-
Geometric sample pitch is **not** accepted as photon-collection area
|
|
6151
|
+
Geometric sample pitch alone is **not** accepted as photon-collection area. The profile must declare either an effective collection area, or a geometric cell area plus an explicit fill factor.
|
|
5860
6152
|
|
|
5861
|
-
|
|
6153
|
+
Spectral and spatial calibration inputs that carry data are referenced by an artifact ID plus a SHA-256 checksum. Photivra does not assume reuse rights because something is publicly available. Reusable calibration data needs an explicit licence or Photivra ownership.
|
|
5862
6154
|
|
|
5863
|
-
The readiness API does not calculate photons, does not change the existing `calculatePhotoelectrons()` primitive, and does not add photon/noise output to `simulatePocCamera()`. The bounded [executed environment route](PRODUCTION_ENVIRONMENT_CAPTURE.md)
|
|
6155
|
+
The readiness API does not calculate photons, does not change the existing `calculatePhotoelectrons()` primitive, and does not add photon/noise output to `simulatePocCamera()`. The bounded [executed environment route](https://github.com/Photivra/photivra/blob/main/docs/PRODUCTION_ENVIRONMENT_CAPTURE.md) does explicitly declared EQE photon/electron integration. This readiness gate alone neither turns it on nor checks calibration.
|
|
5864
6156
|
|
|
5865
6157
|
## Photoelectron and SNR primitives
|
|
5866
6158
|
|
|
5867
|
-
Use `calculatePhotoelectrons()` only when you already have a defensible mean incident
|
|
6159
|
+
Use `calculatePhotoelectrons()` only when you already have a defensible mean count of incident photons and a quantum efficiency (the fraction of photons that produce an electron):
|
|
5868
6160
|
|
|
5869
6161
|
```ts
|
|
5870
6162
|
import { calculatePhotoelectrons } from "@photivra/engine";
|
|
@@ -5877,7 +6169,7 @@ const electrons = calculatePhotoelectrons({
|
|
|
5877
6169
|
console.log(electrons.value);
|
|
5878
6170
|
```
|
|
5879
6171
|
|
|
5880
|
-
Use `calculateSignalToNoise()` for the basic
|
|
6172
|
+
Use `calculateSignalToNoise()` for the basic signal-to-noise ratio (SNR) model: Poisson shot noise plus independent RMS read noise:
|
|
5881
6173
|
|
|
5882
6174
|
```ts
|
|
5883
6175
|
import { calculateSignalToNoise } from "@photivra/engine";
|
|
@@ -5895,11 +6187,11 @@ console.log(snr.value.snrDb);
|
|
|
5895
6187
|
|
|
5896
6188
|
These primitives do not derive photons from scene imagery and do not model a complete commercial sensor pipeline.
|
|
5897
6189
|
|
|
5898
|
-
See [Motion and Signal Foundation](MOTION_AND_SIGNAL.md#signalnoise-primitives).
|
|
6190
|
+
See [Motion and Signal Foundation](https://github.com/Photivra/photivra/blob/main/docs/MOTION_AND_SIGNAL.md#signalnoise-primitives).
|
|
5899
6191
|
|
|
5900
6192
|
## Scene illumination source profiles
|
|
5901
6193
|
|
|
5902
|
-
Use `parseSceneIlluminationProfile()`
|
|
6194
|
+
Use `parseSceneIlluminationProfile()` to read light-source metadata that comes from a renderer or another integration:
|
|
5903
6195
|
|
|
5904
6196
|
```ts
|
|
5905
6197
|
import { parseSceneIlluminationProfile } from "@photivra/engine";
|
|
@@ -5953,26 +6245,35 @@ console.log(illumination.sources[0]?.sourceId);
|
|
|
5953
6245
|
console.log(illumination.sceneRadianceCalculated); // false
|
|
5954
6246
|
```
|
|
5955
6247
|
|
|
5956
|
-
The schema
|
|
6248
|
+
The schema deliberately has no generic `intensity` field. How brightness is stated depends on the kind of source:
|
|
5957
6249
|
|
|
5958
6250
|
- point sources may declare radiant intensity in W/sr;
|
|
5959
6251
|
- area sources may declare surface radiance in W/(m²·sr);
|
|
5960
6252
|
- directional sources may declare irradiance in W/m² at an explicit reference plane;
|
|
5961
|
-
- spot and environment sources are relative-only in schema 0.1.0 until their angular
|
|
6253
|
+
- spot and environment sources are relative-only in schema 0.1.0, until their angular and radiance-field meaning is modelled.
|
|
5962
6254
|
|
|
5963
|
-
|
|
6255
|
+
Every kind of source may use an explicitly approximate, relative linear scale. To turn a source off, use `enabled: false`. A zero magnitude is never used as a hidden "off" state.
|
|
5964
6256
|
|
|
5965
|
-
|
|
6257
|
+
Spectra are explicit too:
|
|
5966
6258
|
|
|
5967
|
-
|
|
6259
|
+
- Linear sRGB is for previews only.
|
|
6260
|
+
- A blackbody temperature is a declared blackbody approximation, not a universal CCT control.
|
|
6261
|
+
- Continuous relative spectra need reusable-data provenance. They do not become absolute spectral power or radiance just because a physical broadband magnitude also exists.
|
|
5968
6262
|
|
|
5969
|
-
|
|
6263
|
+
Sources with discrete spectral lines may use `discrete-relative-lines` instead.
|
|
5970
6264
|
|
|
5971
|
-
|
|
6265
|
+
- Each line has an explicit wavelength and a normalized, **wavelength-integrated** line weight.
|
|
6266
|
+
- Line weights must sum to one and use a resolved air/vacuum wavelength basis.
|
|
6267
|
+
- These values are not per-nanometre densities, and a line is never given an arbitrary width.
|
|
6268
|
+
- `resolveSceneIlluminationDiscreteLineMeasure()` can distribute a source's integrated magnitude across those line fractions, keeping the source's unit.
|
|
6269
|
+
|
|
6270
|
+
The parser only sets up light-source metadata. It does not calculate material response, visibility, indirect light, outgoing scene spectral radiance, sensor-plane irradiance, photons/electrons, fluorescence, volumetric spectral transport or polarization.
|
|
6271
|
+
|
|
6272
|
+
See [Scene Radiance and Illumination](https://github.com/Photivra/photivra/blob/main/docs/SCENE_RADIANCE_AND_ILLUMINATION.md).
|
|
5972
6273
|
|
|
5973
6274
|
## Scene-radiance provider and material response
|
|
5974
6275
|
|
|
5975
|
-
|
|
6276
|
+
A scene-radiance provider is the renderer (or reference evaluator) that works out how much light leaves a point in the scene. Use this provider boundary when it needs to hand back one sample of outgoing spectral radiance, without moving its rendering algorithm into the engine.
|
|
5976
6277
|
|
|
5977
6278
|
First define material-response metadata:
|
|
5978
6279
|
|
|
@@ -6067,17 +6368,22 @@ const provider = parseSceneRadianceProviderProfile({
|
|
|
6067
6368
|
});
|
|
6068
6369
|
```
|
|
6069
6370
|
|
|
6070
|
-
|
|
6371
|
+
How an evaluation works:
|
|
6372
|
+
|
|
6373
|
+
1. A request names one exact surface or environment direction, a physical time and a wavelength.
|
|
6374
|
+
2. The renderer/provider computes the radiance outside the engine, and returns a result in `W/m^2/sr/nm`.
|
|
6375
|
+
3. Parse both with `parseSceneRadianceEvaluationRequest()` and `parseSceneRadianceEvaluationResult()`.
|
|
6376
|
+
4. Call `validateSceneRadianceEvaluationBindings()` with the provider, illumination profile, material profile, request and result.
|
|
6071
6377
|
|
|
6072
|
-
The binding validator checks exact scene
|
|
6378
|
+
The binding validator checks the exact scene, profile, material, sample and wavelength identity, and that the provider's material fidelity is consistent. It **does not** recompute the renderer's number, apply optics, calculate sensor-plane irradiance or allow photon output.
|
|
6073
6379
|
|
|
6074
|
-
Provider/result schema 0.1.0 is approximation-only. Calibrated material or illumination inputs cannot silently
|
|
6380
|
+
Provider/result schema 0.1.0 is approximation-only. Calibrated material or illumination inputs cannot silently turn provider output into calibrated scene radiance.
|
|
6075
6381
|
|
|
6076
|
-
See [Scene Radiance and Illumination](SCENE_RADIANCE_AND_ILLUMINATION.md#scene-radiance-provider-and-material-response-boundary).
|
|
6382
|
+
See [Scene Radiance and Illumination](https://github.com/Photivra/photivra/blob/main/docs/SCENE_RADIANCE_AND_ILLUMINATION.md#scene-radiance-provider-and-material-response-boundary).
|
|
6077
6383
|
|
|
6078
6384
|
## Shared spectral composition
|
|
6079
6385
|
|
|
6080
|
-
Use `composeSpectralCoverage()` when
|
|
6386
|
+
Use `composeSpectralCoverage()` when several **continuous** wavelength-dependent factors (scene, lens, sensor) need one shared plan of wavelength coverage and breakpoints.
|
|
6081
6387
|
|
|
6082
6388
|
Composition fragment: requires previously validated bindings `spectralResponseProfile`. This is an integration sketch, not a standalone runnable program.
|
|
6083
6389
|
|
|
@@ -6145,11 +6451,11 @@ The composition:
|
|
|
6145
6451
|
- does not apply scene values, optical transmission, QE, or A/W responsivity;
|
|
6146
6452
|
- does not perform spectral integration or claim convergence.
|
|
6147
6453
|
|
|
6148
|
-
Continuous illumination spectra can be converted to the same participant shape with `createSceneIlluminationSpectralCoverageParticipant()`. RGB, blackbody approximations, unresolved spectra
|
|
6454
|
+
Continuous illumination spectra can be converted to the same participant shape with `createSceneIlluminationSpectralCoverageParticipant()`. That adapter rejects RGB, blackbody approximations, unresolved spectra and discrete line spectra.
|
|
6149
6455
|
|
|
6150
6456
|
### Discrete line measures
|
|
6151
6457
|
|
|
6152
|
-
Discrete lines
|
|
6458
|
+
Discrete spectral lines (as from a laser or some lamps) are integrated a different way.
|
|
6153
6459
|
|
|
6154
6460
|
Composition fragment: requires previously validated bindings `illumination`. This is an integration sketch, not a standalone runnable program.
|
|
6155
6461
|
|
|
@@ -6188,21 +6494,21 @@ if (
|
|
|
6188
6494
|
}
|
|
6189
6495
|
```
|
|
6190
6496
|
|
|
6191
|
-
A discrete line entry already
|
|
6497
|
+
A discrete line entry already is a wavelength-integrated contribution. So integrating lines is just:
|
|
6192
6498
|
|
|
6193
6499
|
```text
|
|
6194
6500
|
total = sum(line integrated quantities)
|
|
6195
6501
|
```
|
|
6196
6502
|
|
|
6197
|
-
There is **no**
|
|
6503
|
+
There is **no** multiplication by `dλ`. Giving a laser or line emitter an arbitrary width, just to reuse continuous quadrature, is invalid.
|
|
6198
6504
|
|
|
6199
|
-
The source-level helper may distribute point radiant intensity (W/sr), area radiance (W/m²/sr), directional reference-plane irradiance (W/m²)
|
|
6505
|
+
The source-level helper may distribute point radiant intensity (W/sr), area radiance (W/m²/sr), directional reference-plane irradiance (W/m²) or an explicitly relative magnitude across normalized line fractions. It does not apply material transport, optics, sensor response, photon conversion or combined uncertainty, and it does not allow a claim of calibrated outgoing radiance.
|
|
6200
6506
|
|
|
6201
|
-
See [Scene Radiance and Illumination](SCENE_RADIANCE_AND_ILLUMINATION.md#shared-spectral-composition).
|
|
6507
|
+
See [Scene Radiance and Illumination](https://github.com/Photivra/photivra/blob/main/docs/SCENE_RADIANCE_AND_ILLUMINATION.md#shared-spectral-composition).
|
|
6202
6508
|
|
|
6203
6509
|
## Temporal illumination
|
|
6204
6510
|
|
|
6205
|
-
Use `parseSceneIlluminationTemporalProfile()` to add explicit flash
|
|
6511
|
+
Use `parseSceneIlluminationTemporalProfile()` to add explicit changes over time — such as a flash pulse or flicker — to light sources, without changing the base illumination schema.
|
|
6206
6512
|
|
|
6207
6513
|
Composition fragment: requires previously validated bindings `illuminationProfile`. This is an integration sketch, not a standalone runnable program.
|
|
6208
6514
|
|
|
@@ -6296,19 +6602,19 @@ console.log(
|
|
|
6296
6602
|
);
|
|
6297
6603
|
```
|
|
6298
6604
|
|
|
6299
|
-
Waveform time is explicitly
|
|
6605
|
+
Waveform time is explicitly tied to seconds from the first opening-boundary phase. Sensor readout duration is not used in place of exposure timing.
|
|
6300
6606
|
|
|
6301
|
-
For finite exposure, use `integrateSceneIlluminationTemporalMultiplierOverExposureWindow()` with the authoritative `CaptureExposureWindows` result and one sample index.
|
|
6607
|
+
For a finite exposure, use `integrateSceneIlluminationTemporalMultiplierOverExposureWindow()` with the authoritative `CaptureExposureWindows` result and one sample index. It uses deterministic midpoint quadrature over that local exposure window, and returns the average and the seconds-integrated relative multipliers.
|
|
6302
6608
|
|
|
6303
|
-
This layer does not apply the source's physical magnitude, evaluate material
|
|
6609
|
+
This layer does not apply the source's physical magnitude, evaluate material or visibility transport, calculate scene radiance, apply sensor response or choose camera exposure settings.
|
|
6304
6610
|
|
|
6305
6611
|
A scene-radiance provider may optionally bind the temporal profile with `illuminationTemporalProfileId`. When declared, `validateSceneRadianceEvaluationBindings()` requires the matching temporal profile.
|
|
6306
6612
|
|
|
6307
|
-
See [Scene Radiance and Illumination](SCENE_RADIANCE_AND_ILLUMINATION.md#temporal-illumination).
|
|
6613
|
+
See [Scene Radiance and Illumination](https://github.com/Photivra/photivra/blob/main/docs/SCENE_RADIANCE_AND_ILLUMINATION.md#temporal-illumination).
|
|
6308
6614
|
|
|
6309
6615
|
## Camera and scene schema validation
|
|
6310
6616
|
|
|
6311
|
-
Use the runtime parsers
|
|
6617
|
+
Use the runtime parsers whenever camera or scene data comes from untrusted JSON.
|
|
6312
6618
|
|
|
6313
6619
|
Composition fragment: requires previously validated bindings `cameraJson`, `sceneJson`. This is an integration sketch, not a standalone runnable program.
|
|
6314
6620
|
|
|
@@ -6322,7 +6628,7 @@ const camera = parseCameraConfiguration(JSON.parse(cameraJson));
|
|
|
6322
6628
|
const scene = parseSceneDefinition(JSON.parse(sceneJson));
|
|
6323
6629
|
```
|
|
6324
6630
|
|
|
6325
|
-
The parsers throw `InvalidConfigurationError` when the
|
|
6631
|
+
The parsers throw `InvalidConfigurationError` when the structure, or one of the values, is invalid or unsupported.
|
|
6326
6632
|
|
|
6327
6633
|
Example camera shape:
|
|
6328
6634
|
|
|
@@ -6353,7 +6659,7 @@ const camera = parseCameraConfiguration({
|
|
|
6353
6659
|
});
|
|
6354
6660
|
```
|
|
6355
6661
|
|
|
6356
|
-
See [Validation boundaries in the README](../README.md#validation-boundaries) and [Architecture](ARCHITECTURE.md).
|
|
6662
|
+
See [Validation boundaries in the README](../README.md#validation-boundaries) and [Architecture](https://github.com/Photivra/photivra/blob/main/docs/ARCHITECTURE.md).
|
|
6357
6663
|
|
|
6358
6664
|
## Provenance, uncertainty, and quality metadata
|
|
6359
6665
|
|
|
@@ -6373,7 +6679,7 @@ console.log(result.provenance.modelVersion);
|
|
|
6373
6679
|
console.log(result.provenance.assumptions);
|
|
6374
6680
|
```
|
|
6375
6681
|
|
|
6376
|
-
|
|
6682
|
+
If your code defines a new scientific result, the package exports helpers such as `calculatedResult()`, `approximationResult()`, `estimatedResult()` and `calibratedResult()`.
|
|
6377
6683
|
|
|
6378
6684
|
```ts
|
|
6379
6685
|
import { calculatedResult } from "@photivra/engine";
|
|
@@ -6397,25 +6703,36 @@ const result = calculatedResult(
|
|
|
6397
6703
|
);
|
|
6398
6704
|
```
|
|
6399
6705
|
|
|
6400
|
-
Do not invent uncertainty values.
|
|
6706
|
+
Do not invent uncertainty values. Supply quality metadata only when the uncertainty, calibration, valid range or quality note has a defensible basis.
|
|
6401
6707
|
|
|
6402
|
-
See [Scientific and Source Provenance](PROVENANCE.md) and [Public API Style](API_STYLE.md#result-metadata).
|
|
6708
|
+
See [Scientific and Source Provenance](https://github.com/Photivra/photivra/blob/main/docs/PROVENANCE.md) and [Public API Style](https://github.com/Photivra/photivra/blob/main/docs/API_STYLE.md#result-metadata).
|
|
6403
6709
|
|
|
6404
6710
|
## Composed POC simulation
|
|
6405
6711
|
|
|
6406
|
-
Use `simulatePocCamera()` when you want one
|
|
6712
|
+
Use `simulatePocCamera()` when you want one combined response with the current POC (proof-of-concept) calculations.
|
|
6713
|
+
|
|
6714
|
+
The root engine and the composed POC are versioned independently. `ENGINE_API_VERSION` describes the root library; `POC_SIMULATION_API_VERSION` describes this composed request/response contract.
|
|
6715
|
+
|
|
6716
|
+
The composed POC reports X/Y geometric sample pitch, but internally it still uses one representative horizontal pitch for blur and sampling calculations, for backwards compatibility. It therefore rejects sensor geometry whose X and Y pitch differ by more than 1%. The lower-level geometry APIs handle each axis separately, for more general sensor layouts.
|
|
6407
6717
|
|
|
6408
|
-
|
|
6718
|
+
POC API 0.20 composes the capture-geometry foundation all the way to final output and viewing. Existing requests remain valid. New callers may supply an optional `capture` object with the physical orientation, an optional native active-capture rectangle, an optional oriented output crop and an optional final output raster.
|
|
6409
6719
|
|
|
6410
|
-
The
|
|
6720
|
+
- The staged `capture` request deliberately keeps the legacy `crop.factor` separate: it must stay `1` when `capture` is present.
|
|
6721
|
+
- This geometry request is separate from the sensor/capture-mode profile contract.
|
|
6722
|
+
- Staged capture can use the existing `subjectCrop` request, but the result appears under `capture.subjectFraming`, because it is a framing stage after output, not a legacy total-crop factor.
|
|
6411
6723
|
|
|
6412
|
-
|
|
6724
|
+
In capture mode, the equivalent-viewing CoC (circle of confusion) uses the final retained physical image region, including subject framing when present. Changing only the output pixel resolution does not change it. An explicit `circleOfConfusionMm` stays unchanged.
|
|
6413
6725
|
|
|
6414
|
-
The
|
|
6726
|
+
The optional `capture` block in the response gives:
|
|
6415
6727
|
|
|
6416
|
-
|
|
6728
|
+
- the resolved geometry;
|
|
6729
|
+
- active and output FOV;
|
|
6730
|
+
- the 35 mm-equivalent focal length of the active capture, based on the diagonal;
|
|
6731
|
+
- the output sampling scale;
|
|
6732
|
+
- optional subject framing;
|
|
6733
|
+
- explicit motion diagnostics in native-raster, oriented and output coordinates.
|
|
6417
6734
|
|
|
6418
|
-
|
|
6735
|
+
Legacy motion Y uses the image plane, +Y up; capture raster Y is +Y down. The conversion is therefore reported, not hidden. The physical focal length stays authoritative. Sensor-architecture metadata and radiometry-readiness profiles are still standalone.
|
|
6419
6736
|
|
|
6420
6737
|
|
|
6421
6738
|
```ts
|
|
@@ -6537,16 +6854,704 @@ console.log(portraitCrop.capture?.focalLength);
|
|
|
6537
6854
|
console.log(portraitCrop.capture?.motion.outputDeltaPixels);
|
|
6538
6855
|
```
|
|
6539
6856
|
|
|
6540
|
-
The focus request must supply exactly one circle-of-confusion criterion: either `circleOfConfusionMm` or `equivalentViewingCircleOfConfusion`. In staged capture mode, the equivalent-viewing approximation uses the final retained physical image region
|
|
6857
|
+
The focus request must supply exactly one circle-of-confusion criterion: either `circleOfConfusionMm` or `equivalentViewingCircleOfConfusion`. In staged capture mode, the equivalent-viewing approximation uses the final retained physical image region. An explicit `circleOfConfusionMm` is not changed by crop or output geometry.
|
|
6541
6858
|
|
|
6542
|
-
|
|
6859
|
+
You can supply extra named defocus, sampling and motion samples when a renderer or analysis tool needs results per object.
|
|
6543
6860
|
|
|
6544
|
-
See [Local POC Simulation API](POC_API.md#simulate) for the
|
|
6861
|
+
See [Local POC Simulation API](https://github.com/Photivra/photivra/blob/main/docs/POC_API.md#simulate) for the full request/response meaning. That HTTP transport is repository-only contributor tooling, and is not shipped as a package subpath. `simulatePocCamera()` itself is part of the browser-safe root package.
|
|
6862
|
+
|
|
6863
|
+
## DNG import and PNG output
|
|
6864
|
+
|
|
6865
|
+
- `parseDngRawImage` reads a DNG or TIFF-EP CFA/LinearRaw file (uncompressed or lossless JPEG, 8–16 bits). It returns the stored raw codes plus the CFA, black/white levels, active area, default crop, colour matrices, as-shot neutral and orientation. Nothing is applied to the codes.
|
|
6866
|
+
- `serializePngImage` writes an 8- or 16-bit RGB PNG with an sRGB, ICC or cICP (Display P3, BT.2020; sRGB, BT.709 or linear transfer) colour description, and optional mDCV/cLLI display metadata. PQ and HLG are not part of 2.0 (see the patent review in `THIRD_PARTY.md`).
|
|
6867
|
+
|
|
6868
|
+
See [DNG import and PNG output](https://github.com/Photivra/photivra/blob/main/docs/IMPORT_AND_PNG.md) for supported subsets, limits and sources.
|
|
6869
|
+
|
|
6870
|
+
```ts
|
|
6871
|
+
import { serializePngImage, parseDngRawImage } from '@photivra/engine';
|
|
6872
|
+
|
|
6873
|
+
// Encode a 1 x 1 Display P3 pixel (sRGB transfer), then show that a PNG is not a DNG.
|
|
6874
|
+
const png = serializePngImage({ width: 1, height: 1, bitDepth: 8, samples: Uint8Array.of(255, 128, 0),
|
|
6875
|
+
colourEncoding: { kind: 'cicp', primaries: 'display-p3', transfer: 'srgb', fullRange: true } });
|
|
6876
|
+
let rejected = false;
|
|
6877
|
+
try { parseDngRawImage({ bytes: png }); } catch { rejected = true; }
|
|
6878
|
+
if (!rejected) throw new Error('A PNG must not parse as a DNG');
|
|
6879
|
+
```
|
|
6545
6880
|
|
|
6546
6881
|
## Native-only Print planning
|
|
6547
6882
|
|
|
6548
|
-
|
|
6883
|
+
- [Registered linear statistics](https://github.com/Photivra/photivra/blob/main/docs/PRINT_REGION_STATISTICS.md) tell apart changes caused by processing the same capture from variation between repeated captures at each site.
|
|
6884
|
+
- The [static neutral Gabor contrast reference](https://github.com/Photivra/photivra/blob/main/docs/PRINT_CONTRAST_REFERENCE.md) needs explicit stimulus, luminance and observer conditions. It does not accept an arbitrary photographic ROI (region of interest) as a valid stimulus.
|
|
6885
|
+
- Read the [#195/#196 candidate evidence review](https://github.com/Photivra/photivra/blob/main/docs/PRINT_195_196_REVIEW.md) before integrating either diagnostic.
|
|
6886
|
+
|
|
6887
|
+
See [Print planning](https://github.com/Photivra/photivra/blob/main/docs/PRINT_PLANNING.md) for a complete, strict packed example and the exact-ratio integer policy.
|
|
6888
|
+
|
|
6889
|
+
- `calculatePrintPlan()` says whether the native sampling is enough, and gives a compatible minimum file raster.
|
|
6890
|
+
- `calculatePrintSizeLimit()` distinguishes finite, unbounded and unavailable sampling limits.
|
|
6891
|
+
- Print quality and delivery are not assessed.
|
|
6892
|
+
|
|
6893
|
+
The [regional Print detail diagnostic](https://github.com/Photivra/photivra/blob/main/docs/PRINT_DETAIL_ASSESSMENT.md) measures one declared, coherent sinusoidal fundamental in a selected linear-luminance ROI, and records the exact pipeline stage it measured. Its [validation protocol](https://github.com/Photivra/photivra/blob/main/docs/PRINT_DETAIL_VALIDATION.md) and [capability ledger](https://github.com/Photivra/photivra/blob/main/docs/validation/print-detail-capabilities.json) separate mathematical conformance from the still-blocked qualification of acquired images, renderers and perceived quality.
|
|
6894
|
+
|
|
6895
|
+
## Rolling shutter, video, flash TTL/HSS and highlight-priority metering (2.0.0)
|
|
6896
|
+
|
|
6897
|
+
These additions are approximations. The equations, derivations and sources are in
|
|
6898
|
+
[Electronic rolling-shutter image formation](https://github.com/Photivra/photivra/blob/main/docs/MOTION_AND_SIGNAL.md#electronic-rolling-shutter-image-formation),
|
|
6899
|
+
[Video frame sequences](https://github.com/Photivra/photivra/blob/main/docs/MOTION_AND_SIGNAL.md#video-frame-sequences) and
|
|
6900
|
+
[Flash: guide number, TTL and high-speed sync](https://github.com/Photivra/photivra/blob/main/docs/SCENE_SENSOR_TEMPORAL_EXPOSURE.md#flash-guide-number-ttl-and-high-speed-sync).
|
|
6901
|
+
|
|
6902
|
+
With an electronic rolling shutter, each sensor line starts its exposure slightly later than the one before.
|
|
6903
|
+
The model turns the rolling readout declaration into an exposure schedule with one offset per line.
|
|
6904
|
+
|
|
6905
|
+
- Pass the schedule wherever an exposure window is accepted: native RAW and environment `exposureWindow`, and
|
|
6906
|
+
production `temporalCapture.exposureWindowInput` with `readoutExposureLinkage`.
|
|
6907
|
+
- Skew, wobble, partial exposure and flicker banding then appear naturally from sampling each site at its own time.
|
|
6908
|
+
- A video clip is a deterministic sequence of ordinary release frames.
|
|
6909
|
+
|
|
6910
|
+
```ts
|
|
6911
|
+
import {
|
|
6912
|
+
assessShutterAngleRule,
|
|
6913
|
+
calculateCaptureExposureWindows,
|
|
6914
|
+
resolveElectronicRollingShutterExposureSchedule,
|
|
6915
|
+
resolveVideoFrameSequence,
|
|
6916
|
+
createRollingShutterCaptureExposureSchedule
|
|
6917
|
+
} from "@photivra/engine";
|
|
6918
|
+
|
|
6919
|
+
const evidence = [{ sourceOrigin: "photivra" as const, sourceReference: "example:rolling-video", reuseStatus: "photivra-owned" as const }];
|
|
6920
|
+
const readout = {
|
|
6921
|
+
readoutMode: "rolling" as const,
|
|
6922
|
+
captureReadoutDurationSeconds: { value: 0.016, unit: "s" as const, evidence },
|
|
6923
|
+
scanDirectionNative: { value: "top-to-bottom" as const, evidence },
|
|
6924
|
+
spatialSamplingSkewSeconds: { value: 0.016, unit: "s" as const, evidence }
|
|
6925
|
+
};
|
|
6926
|
+
const schedule = resolveElectronicRollingShutterExposureSchedule({
|
|
6927
|
+
readout,
|
|
6928
|
+
nominalExposureDurationSeconds: { value: 1 / 250, unit: "s", evidence },
|
|
6929
|
+
linkageEvidence: evidence
|
|
6930
|
+
});
|
|
6931
|
+
const windows = calculateCaptureExposureWindows({
|
|
6932
|
+
...createRollingShutterCaptureExposureSchedule(schedule),
|
|
6933
|
+
nativeRaster: { pixelWidth: 6000, pixelHeight: 4000 },
|
|
6934
|
+
samplePointsNative: [{ x: 3000, y: 0.5 }, { x: 3000, y: 3999.5 }]
|
|
6935
|
+
}).value;
|
|
6936
|
+
// Top and bottom lines open about 16 ms apart: a subject moving 2000 px/s is sheared by ~32 px.
|
|
6937
|
+
console.log(windows.samples.map((window) => window.startOffsetSecondsFromOpeningReference));
|
|
6938
|
+
|
|
6939
|
+
const clip = resolveVideoFrameSequence({
|
|
6940
|
+
sequenceId: "clip-1",
|
|
6941
|
+
opticalState: { bodyId: "generic-body", bodyVersion: "1", lensId: "generic-50", lensVersion: "1", focalLengthMm: 50,
|
|
6942
|
+
aperture: 4, focusDistanceM: 5, captureMode: "video", outputWidth: 1920, outputHeight: 1080, frameRateHz: 24,
|
|
6943
|
+
stabilizationMode: "off" },
|
|
6944
|
+
shutterAngleDegrees: 180,
|
|
6945
|
+
frameCount: 3,
|
|
6946
|
+
sequenceSeedUint32: 7,
|
|
6947
|
+
iso: 400,
|
|
6948
|
+
automation: { ae: "locked", af: "manual", awb: "locked" },
|
|
6949
|
+
timingEvidence: evidence,
|
|
6950
|
+
rollingShutter: { readout, linkageEvidence: evidence }
|
|
6951
|
+
});
|
|
6952
|
+
console.log(clip.exposureSeconds, clip.frames.map((frame) => [frame.exposureStartTimeSeconds, frame.stochasticSeedUint32]));
|
|
6953
|
+
console.log(assessShutterAngleRule(24, 172.8).classification);
|
|
6954
|
+
```
|
|
6955
|
+
|
|
6956
|
+
Highlight-priority metering places a highlight percentile below saturation. TTL (through-the-lens) flash metering
|
|
6957
|
+
works out the main-flash power from a pre-flash, measured with the same linear meter. The result feeds the ordinary
|
|
6958
|
+
front/rear-curtain sync:
|
|
6959
|
+
|
|
6960
|
+
```ts
|
|
6961
|
+
import {
|
|
6962
|
+
calculateFlashPowerForDistance,
|
|
6963
|
+
calculateHighlightPriorityPlacementSignal,
|
|
6964
|
+
meterRelativeExposure,
|
|
6965
|
+
parseExposureMeteringProfile,
|
|
6966
|
+
resolveCaptureGeometry,
|
|
6967
|
+
resolveTtlFlashExposure,
|
|
6968
|
+
type ExposureMeteringSampleSet
|
|
6969
|
+
} from "@photivra/engine";
|
|
6970
|
+
|
|
6971
|
+
const evidence = [{ sourceOrigin: "photivra" as const, sourceReference: "example:flash-meter", reuseStatus: "photivra-owned" as const }];
|
|
6972
|
+
const zones = (signals: readonly number[]): ExposureMeteringSampleSet => ({
|
|
6973
|
+
measurementId: "meter", sceneStateId: "scene", inputDomain: "relative-pre-exposure-linear-signal",
|
|
6974
|
+
captureRegion: "oriented-active-capture",
|
|
6975
|
+
captureGeometry: resolveCaptureGeometry({ imagingArea: { widthMm: 36, heightMm: 24 },
|
|
6976
|
+
nativeRaster: { pixelWidth: 6000, pixelHeight: 4000 }, orientation: "landscape" }).value,
|
|
6977
|
+
processingState: { exposureSettingsApplied: false, whiteBalanceApplied: false, toneMappingApplied: false,
|
|
6978
|
+
displayGammaApplied: false, sharpeningApplied: false },
|
|
6979
|
+
samples: signals.map((relativeLinearSignal, index) => ({ sampleId: "zone-" + index,
|
|
6980
|
+
positionOrientedCaptureUv: { u: (index % 5 + 0.5) / 5, v: (Math.floor(index / 5) + 0.5) / 4 },
|
|
6981
|
+
relativeLinearSignal, areaWeight: 1 }))
|
|
6982
|
+
});
|
|
6983
|
+
const meter = (policy: Parameters<typeof parseExposureMeteringProfile>[0], target: number) => parseExposureMeteringProfile({
|
|
6984
|
+
schemaVersion: "0.1.0", profileId: "meter", scientificStatus: "approximation",
|
|
6985
|
+
inputDomain: "relative-pre-exposure-linear-signal", captureRegion: "oriented-active-capture", policy,
|
|
6986
|
+
target: { kind: "relative-signal-reference", targetRelativeSignal: target, evidence }, evidence,
|
|
6987
|
+
limitations: ["Generic relative meter."]
|
|
6988
|
+
});
|
|
6989
|
+
|
|
6990
|
+
// 95 % mid-grey, 5 % bright: place the 99th percentile half a stop below saturation.
|
|
6991
|
+
const scene = zones([...Array.from({ length: 19 }, () => 0.18), 0.9]);
|
|
6992
|
+
const placement = calculateHighlightPriorityPlacementSignal(1, 0.5);
|
|
6993
|
+
const highlight = meterRelativeExposure({
|
|
6994
|
+
profile: meter({ kind: "highlight-priority", highlightPercentile: 0.99, saturationRelativeSignal: 1, headroomStops: 0.5 }, placement),
|
|
6995
|
+
sampleSet: scene
|
|
6996
|
+
}).value;
|
|
6997
|
+
console.log(highlight.highlightPriority?.percentileRelativeSignal, highlight.status === "resolved" && highlight.requiredExposureScaleToTarget);
|
|
6998
|
+
|
|
6999
|
+
// Manual guide-number exposure: GN 40 m at ISO 100, subject 5 m at f/4 → quarter power.
|
|
7000
|
+
console.log(calculateFlashPowerForDistance({ fullPowerGuideNumberMetersIso100: 40, subjectDistanceM: 5,
|
|
7001
|
+
apertureFNumber: 4, iso: 100, minimumPowerFraction: 1 / 128 }).powerFraction);
|
|
7002
|
+
|
|
7003
|
+
// TTL: ambient and 1/32-power pre-flash readings of the same zones.
|
|
7004
|
+
const ttl = resolveTtlFlashExposure({
|
|
7005
|
+
meteringProfile: meter({ kind: "multi-zone-uniform" }, 0.18),
|
|
7006
|
+
ambientSampleSet: zones([0.02, 0.02, 0.04, 0.04]),
|
|
7007
|
+
preFlashSampleSet: zones([0.12, 0.12, 0.06, 0.06]),
|
|
7008
|
+
preFlashPowerFraction: 1 / 32,
|
|
7009
|
+
ambientScaleToMainExposure: 0.5,
|
|
7010
|
+
flashExposureCompensationStops: 0,
|
|
7011
|
+
minimumPowerFraction: 1 / 128,
|
|
7012
|
+
guideNumber: { fullPowerGuideNumberMetersIso100: 40, iso: 100, apertureFNumber: 4 }
|
|
7013
|
+
}).value;
|
|
7014
|
+
console.log(ttl.status, ttl.powerFraction, ttl.flashRangeAtApertureM);
|
|
7015
|
+
// Apply with createTtlScaledFlashProfile(flashProfile, ttl) and place it with resolveManualFlashSync() as before.
|
|
7016
|
+
```
|
|
7017
|
+
|
|
7018
|
+
High-speed sync (HSS) needs a schema 0.2.0 sync-capability profile that declares HSS and its burst energy fraction.
|
|
7019
|
+
|
|
7020
|
+
- `resolveHighSpeedFlashSync()` returns the burst support, the energy fraction per line `η·t_e/D`, and the
|
|
7021
|
+
guide-number factor `√(η·t_e/D)`.
|
|
7022
|
+
- `createHighSpeedSyncIlluminationOverlay()` registers the burst as a temporal illumination source. Every line
|
|
7023
|
+
integrates it over its own window.
|
|
7024
|
+
- To solve TTL power for HSS, pass `syncEnergyFactor = energyFractionOfSinglePulseRange.minimum` to
|
|
7025
|
+
`resolveTtlFlashExposure()`.
|
|
7026
|
+
|
|
7027
|
+
## Lens projections, aberrations, polarizer and haze (2.0)
|
|
7028
|
+
|
|
7029
|
+
The models below are standalone primitives. See [Physics foundation](https://github.com/Photivra/photivra/blob/main/docs/PHYSICS_FOUNDATION.md#non-rectilinear-lens-projections-20) and [PSF foundation](https://github.com/Photivra/photivra/blob/main/docs/PSF_FOUNDATION.md#zernike-and-seidel-wavefronts-20) for equations, sources and limits.
|
|
7030
|
+
|
|
7031
|
+
### Fisheye and equirectangular projections
|
|
7032
|
+
|
|
7033
|
+
```ts
|
|
7034
|
+
import {
|
|
7035
|
+
calculateFieldOfView,
|
|
7036
|
+
calculateLensProjectionDirection,
|
|
7037
|
+
calculateLensProjectionImagePoint
|
|
7038
|
+
} from "@photivra/engine";
|
|
7039
|
+
|
|
7040
|
+
// 8 mm equidistant fisheye across a 24 mm sensor: 2 * (12 / 8) rad = 171.9 degrees.
|
|
7041
|
+
const fisheye = calculateFieldOfView({
|
|
7042
|
+
focalLengthMm: 8,
|
|
7043
|
+
sensorDimensionMm: 24,
|
|
7044
|
+
projectionModel: "fisheye-equidistant"
|
|
7045
|
+
});
|
|
7046
|
+
console.log(fisheye.value.degrees, fisheye.provenance.model);
|
|
7047
|
+
|
|
7048
|
+
// Omitting projectionModel keeps the rectilinear result unchanged.
|
|
7049
|
+
console.log(calculateFieldOfView({ focalLengthMm: 50, sensorDimensionMm: 36 }).value.degrees);
|
|
6549
7050
|
|
|
6550
|
-
|
|
7051
|
+
const point = calculateLensProjectionImagePoint({
|
|
7052
|
+
projectionModel: "fisheye-stereographic",
|
|
7053
|
+
focalLengthMm: 8,
|
|
7054
|
+
direction: { x: 1, y: 0.2, z: -0.1 } // slightly behind the lens: still mapped
|
|
7055
|
+
});
|
|
7056
|
+
const back = calculateLensProjectionDirection({
|
|
7057
|
+
projectionModel: "fisheye-stereographic",
|
|
7058
|
+
focalLengthMm: 8,
|
|
7059
|
+
imagePointMm: point.value.imagePointMm
|
|
7060
|
+
});
|
|
7061
|
+
console.log(point.value.imagePointMm, back.value.direction);
|
|
7062
|
+
```
|
|
7063
|
+
|
|
7064
|
+
### Brown–Conrady distortion
|
|
7065
|
+
|
|
7066
|
+
```ts
|
|
7067
|
+
import {
|
|
7068
|
+
calculateBrownConradyDistortionMapping,
|
|
7069
|
+
calculateInverseBrownConradyDistortionMapping,
|
|
7070
|
+
type BrownConradyDistortionProfile
|
|
7071
|
+
} from "@photivra/engine";
|
|
7072
|
+
|
|
7073
|
+
const profile: BrownConradyDistortionProfile = {
|
|
7074
|
+
normalizationRadiusMm: 21.6,
|
|
7075
|
+
maximumNormalizedRadius: 1,
|
|
7076
|
+
coefficients: { k1: -0.08, k2: 0.01, k3: 0, p1: 0.002, p2: -0.0015 }
|
|
7077
|
+
};
|
|
7078
|
+
const distorted = calculateBrownConradyDistortionMapping({ imagePointMm: { x: 10, y: -6 }, profile });
|
|
7079
|
+
const ideal = calculateInverseBrownConradyDistortionMapping({
|
|
7080
|
+
distortedImagePointMm: distorted.value.mappedImagePointMm,
|
|
7081
|
+
profile
|
|
7082
|
+
});
|
|
7083
|
+
console.log(distorted.value.mappedImagePointMm, ideal.value.sourceImagePointMm);
|
|
7084
|
+
```
|
|
7085
|
+
|
|
7086
|
+
### Zernike wavefront PSF, longitudinal CA and cat's-eye vignetting
|
|
7087
|
+
|
|
7088
|
+
```ts
|
|
7089
|
+
import {
|
|
7090
|
+
calculateLensComplexPupilPsf,
|
|
7091
|
+
calculateLongitudinalChromaticFocus,
|
|
7092
|
+
calculateMechanicalVignetting,
|
|
7093
|
+
calculateNaturalVignetting,
|
|
7094
|
+
calculatePolygonPupilFraunhoferPsf,
|
|
7095
|
+
createWavefrontPupilGrid,
|
|
7096
|
+
estimateMarechalStrehlRatio,
|
|
7097
|
+
parseLensComplexPupilProfile
|
|
7098
|
+
} from "@photivra/engine";
|
|
7099
|
+
|
|
7100
|
+
// Per-wavelength defocus of an achromat-like secondary spectrum at f/4.
|
|
7101
|
+
const blue = calculateLongitudinalChromaticFocus({
|
|
7102
|
+
focalLengthMm: 50,
|
|
7103
|
+
referenceWavelengthNm: 550,
|
|
7104
|
+
wavelengthNm: 460,
|
|
7105
|
+
apertureFNumber: 4,
|
|
7106
|
+
focusDistanceM: 3,
|
|
7107
|
+
dispersion: {
|
|
7108
|
+
kind: "declared-focal-shift",
|
|
7109
|
+
samples: [
|
|
7110
|
+
{ wavelengthNm: 450, focalLengthShiftMicrometers: 20 },
|
|
7111
|
+
{ wavelengthNm: 550, focalLengthShiftMicrometers: 0 },
|
|
7112
|
+
{ wavelengthNm: 650, focalLengthShiftMicrometers: 15 }
|
|
7113
|
+
]
|
|
7114
|
+
}
|
|
7115
|
+
}).value;
|
|
7116
|
+
|
|
7117
|
+
// Defocus (Z4) plus a little spherical aberration (Z11) on a 12.5 mm pupil.
|
|
7118
|
+
const wavefront = createWavefrontPupilGrid({
|
|
7119
|
+
pupilDiameterMm: 12.5,
|
|
7120
|
+
samplesAcross: 31,
|
|
7121
|
+
zernikeCoefficients: [
|
|
7122
|
+
{ nollIndex: 4, coefficientMicrometers: blue.zernikeZ4Micrometers },
|
|
7123
|
+
{ nollIndex: 11, coefficientMicrometers: 0.02 }
|
|
7124
|
+
]
|
|
7125
|
+
}).value;
|
|
7126
|
+
console.log(estimateMarechalStrehlRatio({
|
|
7127
|
+
rmsWavefrontErrorMicrometers: wavefront.sampledRmsMicrometers,
|
|
7128
|
+
wavelengthNm: 460
|
|
7129
|
+
}).value.strehlRatio);
|
|
7130
|
+
|
|
7131
|
+
const psf = calculateLensComplexPupilPsf({
|
|
7132
|
+
profile: parseLensComplexPupilProfile({
|
|
7133
|
+
schemaVersion: "0.1.0",
|
|
7134
|
+
profileId: "example-wavefront",
|
|
7135
|
+
profileVersion: "1.0.0",
|
|
7136
|
+
scientificStatus: "approximation",
|
|
7137
|
+
opticalDomain: "lens-primary-optical-path-only",
|
|
7138
|
+
representation: "complex-pupil-amplitude-plus-opd",
|
|
7139
|
+
pupilCoordinateSystem: "pupil-plane-metric-aligned-to-image-plane",
|
|
7140
|
+
imageFieldAxes: "+X right, +Y up",
|
|
7141
|
+
amplitudeMeaning: "relative-complex-pupil-amplitude-shape",
|
|
7142
|
+
wavefrontMeaning: "optical-path-difference-micrometers",
|
|
7143
|
+
throughputOwnership: "separate-relative-pupil-throughput-factor",
|
|
7144
|
+
kernelEnergyNormalization: "unit-energy-shape",
|
|
7145
|
+
propagationModel: "scalar-fraunhofer-discrete-reference",
|
|
7146
|
+
context: {
|
|
7147
|
+
focalLengthMm: 50,
|
|
7148
|
+
focus: { kind: "finite", distanceM: 3 },
|
|
7149
|
+
apertureFNumber: 4,
|
|
7150
|
+
fieldPointMm: { x: 0, y: 0 },
|
|
7151
|
+
wavelengthNm: 460,
|
|
7152
|
+
signedDefocusImagePlaneMicrometers: blue.signedDefocusImagePlaneMicrometers
|
|
7153
|
+
},
|
|
7154
|
+
responseIncludes: { diffraction: true, aberration: true, defocus: true, pupilClippingShape: false },
|
|
7155
|
+
relativePupilThroughputFactor: 1,
|
|
7156
|
+
grid: wavefront.grid,
|
|
7157
|
+
sensorOpticalStackIncluded: false,
|
|
7158
|
+
sensorSamplingIncluded: false,
|
|
7159
|
+
reconstructionIncluded: false,
|
|
7160
|
+
strayLightIncluded: false,
|
|
7161
|
+
evidence: [{ sourceOrigin: "photivra", sourceReference: "usage-example", reuseStatus: "photivra-owned" }],
|
|
7162
|
+
uncertainty: { kind: "not-quantified", limitation: "Synthetic example wavefront." },
|
|
7163
|
+
limitations: ["Synthetic example."]
|
|
7164
|
+
})
|
|
7165
|
+
});
|
|
7166
|
+
console.log(psf.value.kernel.widthSamples);
|
|
7167
|
+
|
|
7168
|
+
// Natural cos^4 falloff and a cat's-eye pupil 20 degrees off axis.
|
|
7169
|
+
console.log(calculateNaturalVignetting({
|
|
7170
|
+
model: "cosine-fourth",
|
|
7171
|
+
imagePointMm: { x: 15, y: 10 },
|
|
7172
|
+
exitPupilDistanceMm: 60
|
|
7173
|
+
}).value.relativeIllumination);
|
|
7174
|
+
const catsEye = calculateMechanicalVignetting({
|
|
7175
|
+
entrancePupilDiameterMm: 12.5,
|
|
7176
|
+
fieldAngleDegrees: 20,
|
|
7177
|
+
fieldAzimuthDegrees: 30,
|
|
7178
|
+
apertures: [{ radiusMm: 7, shiftPerTanFieldMm: 30 }]
|
|
7179
|
+
}).value;
|
|
7180
|
+
const bokehPsf = calculatePolygonPupilFraunhoferPsf({
|
|
7181
|
+
pupilVerticesMm: catsEye.openPupilPolygonMm,
|
|
7182
|
+
pupilToImageDistanceMm: 50,
|
|
7183
|
+
wavelengthNm: 550,
|
|
7184
|
+
wavelengthBasis: "air",
|
|
7185
|
+
imagePointsMicrometers: [{ x: 0, y: 0 }, { x: 2, y: 0 }]
|
|
7186
|
+
});
|
|
7187
|
+
console.log(catsEye.openAreaRatio, bokehPsf.value.samples[1]?.peakNormalizedIntensity);
|
|
7188
|
+
```
|
|
7189
|
+
|
|
7190
|
+
### Polarizer and haze
|
|
7191
|
+
|
|
7192
|
+
```ts
|
|
7193
|
+
import {
|
|
7194
|
+
calculateAtmosphericHaze,
|
|
7195
|
+
calculateExtinctionFromVisibility,
|
|
7196
|
+
calculateGeometricFocusBreathing,
|
|
7197
|
+
composeFrontOfLensFilterTransmission,
|
|
7198
|
+
parseFrontOfLensFilterProfile
|
|
7199
|
+
} from "@photivra/engine";
|
|
7200
|
+
|
|
7201
|
+
const polarizer = parseFrontOfLensFilterProfile({
|
|
7202
|
+
schemaVersion: "0.2.0",
|
|
7203
|
+
filterId: "generic-linear-polarizer",
|
|
7204
|
+
profileVersion: "1.0.0",
|
|
7205
|
+
identityScope: "photivra-generic-unbranded",
|
|
7206
|
+
position: "front-of-lens",
|
|
7207
|
+
scientificStatus: "approximation",
|
|
7208
|
+
wavelengthBasis: "air",
|
|
7209
|
+
wavelengthRangeNanometers: { minimum: 400, maximum: 700 },
|
|
7210
|
+
transmission: {
|
|
7211
|
+
kind: "linear-polarizer",
|
|
7212
|
+
principalTransmittance: { value: 0.8, evidence: [{ sourceOrigin: "photivra", sourceReference: "usage-polarizer", reuseStatus: "photivra-owned" }] },
|
|
7213
|
+
extinctionRatio: { value: 1000, evidence: [{ sourceOrigin: "photivra", sourceReference: "usage-polarizer", reuseStatus: "photivra-owned" }] },
|
|
7214
|
+
transmissionAxisDegrees: 90
|
|
7215
|
+
},
|
|
7216
|
+
uncertainty: { kind: "not-quantified", limitation: "Generic educational polarizer." },
|
|
7217
|
+
polarizationModeled: true,
|
|
7218
|
+
wavelengthChangingBehaviorModeled: false,
|
|
7219
|
+
evidence: [{ sourceOrigin: "photivra", sourceReference: "usage-polarizer", reuseStatus: "photivra-owned" }],
|
|
7220
|
+
limitations: ["Generic example."]
|
|
7221
|
+
});
|
|
7222
|
+
// Glare partially polarized horizontally (P = 0.6) is suppressed by a vertical axis.
|
|
7223
|
+
const filtered = composeFrontOfLensFilterTransmission({
|
|
7224
|
+
filters: [polarizer],
|
|
7225
|
+
wavelengthNanometers: 550,
|
|
7226
|
+
wavelengthBasis: "air",
|
|
7227
|
+
incidentPolarization: { degreeOfLinearPolarization: 0.6, angleDegrees: 0 }
|
|
7228
|
+
});
|
|
7229
|
+
console.log(filtered.combinedLinearTransmissionFactor, filtered.transmittedPolarization);
|
|
7230
|
+
|
|
7231
|
+
const beta = calculateExtinctionFromVisibility({ visibilityM: 15_000 }).value.extinctionPerMeter;
|
|
7232
|
+
const hazy = calculateAtmosphericHaze({
|
|
7233
|
+
pathLengthM: 4000,
|
|
7234
|
+
wavelengthNm: 450,
|
|
7235
|
+
sceneRadiance: 0.02,
|
|
7236
|
+
airlightRadiance: 0.08,
|
|
7237
|
+
extinction: {
|
|
7238
|
+
referenceWavelengthNm: 550,
|
|
7239
|
+
rayleighExtinctionPerMeter: 0,
|
|
7240
|
+
aerosolExtinctionPerMeter: beta,
|
|
7241
|
+
angstromExponent: 1.3
|
|
7242
|
+
}
|
|
7243
|
+
});
|
|
7244
|
+
console.log(hazy.value.hazedRadiance, hazy.value.transmittance);
|
|
7245
|
+
|
|
7246
|
+
// Unit-focus breathing of a 100 mm lens focused at 0.6 m (subject to sensor).
|
|
7247
|
+
console.log(calculateGeometricFocusBreathing({
|
|
7248
|
+
focalLengthMm: 100,
|
|
7249
|
+
subjectToSensorDistanceM: 0.6,
|
|
7250
|
+
sensorDimensionMm: 36,
|
|
7251
|
+
focusingMethod: "unit-focus"
|
|
7252
|
+
}).value.deltaDegrees);
|
|
7253
|
+
```
|
|
7254
|
+
|
|
7255
|
+
## Parallel native environment RAW (row-band partitions, 2.0)
|
|
7256
|
+
|
|
7257
|
+
`createNativeEnvironmentRawTask` runs on one thread. For several cores, split the capture into row bands with
|
|
7258
|
+
`calculateNativeRawRowBands`, run each band with `createNativeEnvironmentRawPartitionTask` (one per Web Worker or Node
|
|
7259
|
+
worker thread, each with its own provider), post the partitions back, and join them with
|
|
7260
|
+
`composeNativeEnvironmentRawPartitions`. The merged output is byte-identical to the single task's for the same input
|
|
7261
|
+
and a deterministic provider. The package ships no worker code; the caller owns the workers. See
|
|
7262
|
+
[parallel execution](https://github.com/Photivra/photivra/blob/main/docs/NATIVE_RAW_AND_PRINT.md#opt-in-parallel-execution-row-band-partitions-20) for the checks and limits.
|
|
7263
|
+
|
|
7264
|
+
A provider can also add `evaluateRadianceBatch(queries, signal)`: one call per tile with every query in per-query
|
|
7265
|
+
order, returning the results in the same order (or a promise of them). The output is identical to per-query
|
|
7266
|
+
callbacks with the same results; see
|
|
7267
|
+
[batched light sampling](https://github.com/Photivra/photivra/blob/main/docs/NATIVE_RAW_AND_PRINT.md#batched-light-sampling-evaluateradiancebatch-20).
|
|
7268
|
+
|
|
7269
|
+
```ts
|
|
7270
|
+
import { createNativeEnvironmentRawPartitionTask, composeNativeEnvironmentRawPartitions, calculateNativeRawRowBands, simulateCapture,
|
|
7271
|
+
type NativeEnvironmentRawProvider } from "@photivra/engine";
|
|
7272
|
+
|
|
7273
|
+
const capture = await simulateCapture({ scene: { preset: "uniform" }, output: { width: 8, height: 8 } });
|
|
7274
|
+
const input = capture.request.environmentRaw, width = capture.plan.exposure.geometry.nativeRaster.pixelWidth;
|
|
7275
|
+
// Each worker builds its own provider (functions cannot be posted). Here: a constant radiance.
|
|
7276
|
+
const provider: NativeEnvironmentRawProvider = {
|
|
7277
|
+
readTile: async (r) => ({ ...r, sites: Array.from({ length: r.width }, (_, j) => capture.request.siteInput(r.y * width + r.x + j)) }),
|
|
7278
|
+
evaluateRadiance: (q) => ({ schemaVersion: "0.1.0", sampleId: q.sampleId, sceneId: q.sceneId, providerProfileId: q.providerProfileId,
|
|
7279
|
+
wavelengthNanometers: q.wavelengthNanometers, wavelengthBasis: q.wavelengthBasis, quantity: "outgoing-spectral-radiance", unit: "W/m^2/sr/nm",
|
|
7280
|
+
spectralRadianceWattsPerSquareMeterSteradianNanometer: 0.01, scientificStatus: "approximation",
|
|
7281
|
+
uncertainty: { kind: "not-quantified", limitation: "Example field." },
|
|
7282
|
+
evidence: [{ sourceOrigin: "photivra", sourceReference: "photivra:usage-example", reuseStatus: "photivra-owned" }], limitations: ["Example field."] }),
|
|
7283
|
+
yieldControl: async () => {}
|
|
7284
|
+
};
|
|
7285
|
+
const partitions = await Promise.all(calculateNativeRawRowBands(capture.plan, 2).map(async (band) => {
|
|
7286
|
+
const task = createNativeEnvironmentRawPartitionTask(input, band, provider); // inside a worker
|
|
7287
|
+
await task.run();
|
|
7288
|
+
return task.takeOutput(); // postMessage(partition, [partition.codes.buffer, ...])
|
|
7289
|
+
}));
|
|
7290
|
+
const merged = composeNativeEnvironmentRawPartitions(input, partitions);
|
|
7291
|
+
console.log(merged.raw.codes.length, merged.providerEvaluationCount);
|
|
7292
|
+
```
|
|
7293
|
+
|
|
7294
|
+
## Optional WebGPU image kernels
|
|
7295
|
+
|
|
7296
|
+
`createImageKernelAccelerator()` runs six heavy image operations: isoplanatic PSF convolution (one PSF per image patch), the temporal exposure sum, the sensor stage, native capture SDR, packed RAW development, and chart radiance.
|
|
7297
|
+
|
|
7298
|
+
- By default they run on the float64 CPU reference.
|
|
7299
|
+
- If you opt in, they run on the `webgpu-f32` backend (WebGPU, 32-bit floats).
|
|
7300
|
+
- Every result's `execution` label names the backend that actually ran, any reason for falling back, and the error bound against the reference, stated in advance.
|
|
7301
|
+
|
|
7302
|
+
See [WebGPU backend](https://github.com/Photivra/photivra/blob/main/docs/WEBGPU.md) for the bounds, the supported domain and the browser test.
|
|
7303
|
+
|
|
7304
|
+
```ts
|
|
7305
|
+
import { createImageKernelAccelerator, calculateIrradianceConvolution } from "@photivra/engine";
|
|
7306
|
+
|
|
7307
|
+
const width = 16, height = 8, kernelRadius = 1;
|
|
7308
|
+
const input = { schemaVersion: "0.1.0" as const, width, height, kernelRadius, patchWidth: width, patchHeight: height,
|
|
7309
|
+
boundary: "zero-outside-grid" as const, samples: new Float64Array(width * height).fill(2),
|
|
7310
|
+
kernels: Float64Array.from([0, .1, 0, .1, .6, .1, 0, .1, 0]) };
|
|
6551
7311
|
|
|
6552
|
-
|
|
7312
|
+
const accelerator = await createImageKernelAccelerator({ backend: "webgpu-f32", fallback: "cpu-reference" });
|
|
7313
|
+
const result = await accelerator.convolveIrradiance(input);
|
|
7314
|
+
// Without WebGPU (e.g. Node) this ran the reference and says so.
|
|
7315
|
+
console.log(result.execution.backend, result.execution.fallbackReason, result.execution.bitIdenticalToReference);
|
|
7316
|
+
// Bit-exact identities and fixtures must come from the CPU reference.
|
|
7317
|
+
const reference = calculateIrradianceConvolution(input).values;
|
|
7318
|
+
console.log(result.execution.backend === "cpu-reference" ? result.values.every((v, i) => v === reference[i]) : result.execution.errorBound);
|
|
7319
|
+
accelerator.dispose();
|
|
7320
|
+
```
|
|
7321
|
+
|
|
7322
|
+
Two image-formation stages use the same accelerator:
|
|
7323
|
+
|
|
7324
|
+
- `sumTemporalExposure()` is exposure and motion as a weighted sum of shifted frames, with per-row weights for rolling or global readout.
|
|
7325
|
+
- `simulateSensorRaw()` is the sensor stage: seeded Poisson charge, read noise and ADC to RAW codes. It uses the same noise model and per-site seeds as the per-site producer.
|
|
7326
|
+
|
|
7327
|
+
On `webgpu-f32`, every sensor site not listed in `flaggedSiteIndices` equals the reference exactly. Recompute the listed sites with the reference if you need bit identity.
|
|
7328
|
+
|
|
7329
|
+
```ts
|
|
7330
|
+
import { createImageKernelAccelerator, simulatePackedSensorRaw, type PackedSensorRawInput, type TemporalExposureSumInput } from "@photivra/engine";
|
|
7331
|
+
|
|
7332
|
+
const evidence = [{ sourceOrigin: "photivra" as const, sourceReference: "example", reuseStatus: "photivra-owned" as const }];
|
|
7333
|
+
const width = 8, height = 4;
|
|
7334
|
+
// Static scene, global shutter: three temporal nodes of 1/3000 s each, the image moving 0.5 site to the right per node.
|
|
7335
|
+
const motion: TemporalExposureSumInput = { schemaVersion: "0.1.0", width, height, boundary: "zero-outside-grid",
|
|
7336
|
+
samples: new Float64Array(width * height).fill(9e6), // electrons per second
|
|
7337
|
+
displacements: Float64Array.from([0, 0, 0.5, 0, 1, 0]), rowWeights: new Float64Array(3 * height).fill(1 / 3000) };
|
|
7338
|
+
const sensor = (photoElectrons: Float64Array): PackedSensorRawInput => ({ schemaVersion: "0.1.0", nativeWidth: width, nativeHeight: height,
|
|
7339
|
+
region: { x: 0, y: 0, width, height }, captureSeedUint32: 42, photoElectrons, darkElectrons: new Float64Array(width * height),
|
|
7340
|
+
physicalChargeCapacityElectrons: 30_000, regimeId: "base", readoutProfile: { schemaVersion: "0.1.0", profileId: "readout", colorSamplingProfileId: "cfa",
|
|
7341
|
+
channelId: "green", regimeSelectionOwnedBy: "explicit-upstream-camera-state-not-inferred-from-iso", evidence, regimes: [{ regimeId: "base",
|
|
7342
|
+
scientificStatus: "approximation", systemConversionGainElectronsPerCode: { value: 2, evidence },
|
|
7343
|
+
preAdcSaturationElectronEquivalent: { value: 32_000, evidence }, readNoiseComponents: [{ componentId: "read", rmsElectrons: { value: 2.5, evidence }, evidence }],
|
|
7344
|
+
adc: { bitDepth: 14, blackLevelCode: 512, digitalSaturationCode: 16_383, transfer: "uniform-round-half-up" }, evidence, limitations: ["Example regime."] }] } });
|
|
7345
|
+
|
|
7346
|
+
const accelerator = await createImageKernelAccelerator({ backend: "webgpu-f32", fallback: "cpu-reference" });
|
|
7347
|
+
const exposure = await accelerator.sumTemporalExposure(motion);
|
|
7348
|
+
const raw = await accelerator.simulateSensorRaw(sensor(Float64Array.from(exposure.values)));
|
|
7349
|
+
console.log(exposure.execution.errorBound, raw.execution.errorBound, raw.flaggedSiteIndices.length);
|
|
7350
|
+
// The CPU reference gives the same codes at every unflagged site.
|
|
7351
|
+
const reference = simulatePackedSensorRaw(sensor(Float64Array.from(exposure.values))).codes;
|
|
7352
|
+
console.log(raw.execution.backend === "cpu-reference" ? raw.codes.every((c, i) => c === reference[i]) : raw.diagnostics);
|
|
7353
|
+
accelerator.dispose();
|
|
7354
|
+
```
|
|
7355
|
+
## Image processing operators
|
|
7356
|
+
|
|
7357
|
+
These are standalone operators that can be cancelled: demosaicing, HDR merge and encoding, tone mapping, exposure fusion, focus stacking, panorama stitching from known rotations, denoising, sharpening and synthetic grain.
|
|
7358
|
+
|
|
7359
|
+
- All results are labelled approximations. See [Image processing operators](https://github.com/Photivra/photivra/blob/main/docs/PROCESSING.md) for equations, sources, measured PSNR and limits.
|
|
7360
|
+
- Every factory takes `(input, { yieldControl, onProgress? })` and returns a task that can run once.
|
|
7361
|
+
|
|
7362
|
+
```ts
|
|
7363
|
+
import {
|
|
7364
|
+
createBuiltInBayerDemosaicProfiles,
|
|
7365
|
+
createCfaPlaneReconstructionTask,
|
|
7366
|
+
createAhdDemosaicTask,
|
|
7367
|
+
type SensorColorSamplingProfile
|
|
7368
|
+
} from "@photivra/engine";
|
|
7369
|
+
|
|
7370
|
+
const provider = { yieldControl: async () => {} };
|
|
7371
|
+
const cfa: SensorColorSamplingProfile = {
|
|
7372
|
+
schemaVersion: "0.1.0", profileId: "rggb", coordinateSystem: "native-sensor-color-sampling-site-index",
|
|
7373
|
+
evidence: [{ sourceOrigin: "photivra", sourceReference: "example", reuseStatus: "photivra-owned" }],
|
|
7374
|
+
layout: { kind: "periodic-mosaic", repeatWidthSites: 2, repeatHeightSites: 2,
|
|
7375
|
+
siteChannelIds: ["red", "green", "green", "blue"], anchor: "native-sensor-top-left-site" }
|
|
7376
|
+
};
|
|
7377
|
+
const plane = { width: 64, height: 48, captureModeId: "native", colorSamplingProfile: cfa,
|
|
7378
|
+
samples: new Float32Array(64 * 48).fill(0.25) };
|
|
7379
|
+
const channelRoles = { red: "red", green: "green", blue: "blue" };
|
|
7380
|
+
|
|
7381
|
+
// Bilinear linear kernels with mirror borders (also accepted by createNativeRawDevelopmentTask).
|
|
7382
|
+
const bilinear = createBuiltInBayerDemosaicProfiles({ kernel: "bilinear", captureModeId: "native",
|
|
7383
|
+
colorSamplingProfile: cfa, channelRoles }).value;
|
|
7384
|
+
const linearTask = createCfaPlaneReconstructionTask({ plane, phaseProfiles: bilinear.phaseProfiles, borderHandling: "mirror" }, provider);
|
|
7385
|
+
await linearTask.run();
|
|
7386
|
+
console.log(linearTask.takeOutput().channelIds);
|
|
7387
|
+
|
|
7388
|
+
// Adaptive homogeneity-directed (non-linear) demosaic.
|
|
7389
|
+
const ahd = createAhdDemosaicTask({ plane, channelRoles }, provider);
|
|
7390
|
+
await ahd.run();
|
|
7391
|
+
console.log(ahd.takeOutput().direction.length);
|
|
7392
|
+
```
|
|
7393
|
+
|
|
7394
|
+
Merge linear RAW brackets with noise-optimal weights, convert to linear BT.2020 and tone map:
|
|
7395
|
+
|
|
7396
|
+
```ts
|
|
7397
|
+
import {
|
|
7398
|
+
createHdrBracketMergeTask,
|
|
7399
|
+
createHdrImageEncodingTask,
|
|
7400
|
+
createReinhardGlobalToneMappingTask
|
|
7401
|
+
} from "@photivra/engine";
|
|
7402
|
+
|
|
7403
|
+
const provider = { yieldControl: async () => {} };
|
|
7404
|
+
const n = 32 * 32 * 3;
|
|
7405
|
+
const frame = (seconds: number) => ({ exposureTimeSeconds: seconds, codes: new Uint16Array(n).fill(64 + Math.round(2000 * seconds)),
|
|
7406
|
+
blackLevelCode: 64, whiteLevelCode: 4000, conversionGainElectronsPerCode: 1.5, readNoiseElectrons: 3 });
|
|
7407
|
+
const merge = createHdrBracketMergeTask({ sampleCount: n, frames: [frame(1), frame(1 / 8), frame(1 / 64)] }, provider);
|
|
7408
|
+
await merge.run();
|
|
7409
|
+
const merged = merge.takeOutput();
|
|
7410
|
+
const radiance = { width: 32, height: 32, data: merged.radianceElectronsPerSecond.map((v) => v / 3000) };
|
|
7411
|
+
|
|
7412
|
+
const rec2020 = createHdrImageEncodingTask({ image: radiance, inputColorSpace: "linear-srgb-d65", transfer: "linear-float32" }, provider);
|
|
7413
|
+
await rec2020.run();
|
|
7414
|
+
console.log(rec2020.takeOutput().linear.slice(0, 3));
|
|
7415
|
+
|
|
7416
|
+
const sdr = createReinhardGlobalToneMappingTask({ image: radiance, inputColorSpace: "linear-srgb-d65", key: 0.18 }, provider);
|
|
7417
|
+
await sdr.run();
|
|
7418
|
+
console.log(sdr.takeOutput().logAverageLuminance);
|
|
7419
|
+
```
|
|
7420
|
+
|
|
7421
|
+
`createDurandDorseyToneMappingTask`, `createExposureFusionTask`, `createFocusStackTask`, `createBilateralFilterTask`, `createNonLocalMeansTask`, `createUnsharpMaskTask` and `createSyntheticFilmGrainTask` work the same way. Panorama stitching uses each frame's known rotation, in the engine's pitch/yaw/roll convention:
|
|
7422
|
+
|
|
7423
|
+
```ts
|
|
7424
|
+
import { createPanoramaTask } from "@photivra/engine";
|
|
7425
|
+
|
|
7426
|
+
const image = { width: 48, height: 36, data: new Float32Array(48 * 36 * 3).fill(0.5) };
|
|
7427
|
+
const panorama = createPanoramaTask({
|
|
7428
|
+
frames: [-0.5, 0, 0.5].map((yaw) => ({ image, focalLengthPixels: 40, rotationRad: { pitch: 0, yaw, roll: 0 } })),
|
|
7429
|
+
canvas: { projection: "equirectangular", width: 160, height: 48, longitudeRangeRad: [-1.1, 1.1], latitudeRangeRad: [-0.4, 0.4] },
|
|
7430
|
+
blending: "multi-band", exposureCompensation: "gain"
|
|
7431
|
+
}, { yieldControl: async () => {} });
|
|
7432
|
+
await panorama.run();
|
|
7433
|
+
console.log(panorama.takeOutput().gains);
|
|
7434
|
+
```
|
|
7435
|
+
### Fisheye-to-rectilinear and Brown–Conrady geometric mappings
|
|
7436
|
+
|
|
7437
|
+
```ts
|
|
7438
|
+
import {
|
|
7439
|
+
calculateComposedGeometricMapping,
|
|
7440
|
+
calculateForwardGeometricMapping,
|
|
7441
|
+
prepareGeometricMapping
|
|
7442
|
+
} from "@photivra/engine";
|
|
7443
|
+
|
|
7444
|
+
// Destination-to-source: a rectilinear 10 mm view sampled from an 8 mm equidistant fisheye capture,
|
|
7445
|
+
// after a Brown–Conrady correction of the fisheye's residual distortion.
|
|
7446
|
+
const mapping = prepareGeometricMapping({
|
|
7447
|
+
frameTimeSeconds: 0,
|
|
7448
|
+
transforms: [
|
|
7449
|
+
{
|
|
7450
|
+
id: "fisheye-to-rectilinear", version: "1", domain: "reconstructed-linear", purpose: "projection",
|
|
7451
|
+
kind: "projection-remap",
|
|
7452
|
+
destinationProjection: { projectionModel: "rectilinear", focalLengthMm: 10 },
|
|
7453
|
+
sourceProjection: { projectionModel: "fisheye-equidistant", focalLengthMm: 8 }
|
|
7454
|
+
},
|
|
7455
|
+
{
|
|
7456
|
+
id: "residual-distortion", version: "1", domain: "reconstructed-linear", purpose: "distortion",
|
|
7457
|
+
kind: "brown-conrady",
|
|
7458
|
+
profile: { normalizationRadiusMm: 12, maximumNormalizedRadius: 1,
|
|
7459
|
+
coefficients: { k1: -0.02, k2: 0, k3: 0, p1: 0.001, p2: 0 } }
|
|
7460
|
+
}
|
|
7461
|
+
]
|
|
7462
|
+
}).value;
|
|
7463
|
+
const point = calculateComposedGeometricMapping({ mapping, destinationPointMm: { x: 4, y: -2 } }).value;
|
|
7464
|
+
console.log(mapping.schemaVersion, point.sourcePointMm, point.determinant);
|
|
7465
|
+
console.log(calculateForwardGeometricMapping({ mapping, sourcePointMm: point.sourcePointMm }).value);
|
|
7466
|
+
```
|
|
7467
|
+
## Simulate a capture with the facade
|
|
7468
|
+
|
|
7469
|
+
`simulateCapture()` is a facade: one simple call that combines the generic tier presets, scene metering,
|
|
7470
|
+
the exposure-mode resolver, the native environment RAW task, native RAW development and, optionally, the
|
|
7471
|
+
print JPEG encoder. It returns the image, the packed RAW, the complete request and plan it ran, provenance,
|
|
7472
|
+
and an `assumptions` list naming every default and modelling choice. See the [Quick Start](QUICK_START.md)
|
|
7473
|
+
for every option, the chart catalogue and the limits.
|
|
7474
|
+
|
|
7475
|
+
```ts
|
|
7476
|
+
import { simulateCapture } from "@photivra/engine";
|
|
7477
|
+
|
|
7478
|
+
const star = await simulateCapture({
|
|
7479
|
+
camera: "prosumer",
|
|
7480
|
+
exposure: { mode: "aperture-priority", iso: 200 },
|
|
7481
|
+
lens: { fNumber: 5.6 },
|
|
7482
|
+
scene: { preset: "siemens-star", cycles: 24 },
|
|
7483
|
+
illuminant: { kind: "planckian", temperatureKelvin: 5000 },
|
|
7484
|
+
illuminanceLux: 8000,
|
|
7485
|
+
output: { width: 12, height: 8, toneCurve: "positive-reinhard-per-channel", whiteBalance: { kelvin: 5000 } }
|
|
7486
|
+
});
|
|
7487
|
+
|
|
7488
|
+
console.log(star.exposure.settings.shutterSeconds, star.exposure.ev100);
|
|
7489
|
+
console.log(star.assumptions.filter((a) => a.kind === "default").map((a) => a.id));
|
|
7490
|
+
console.log(star.request.siteInput(0).environment.optics.nominalFNumber);
|
|
7491
|
+
```
|
|
7492
|
+
|
|
7493
|
+
In `aperture-priority` mode, the shutter comes from the meter (CIPA DC-004 relations S = 10/H and H = 0.65·L·t/N²) through
|
|
7494
|
+
`resolveAperturePriorityExposureMode()`.
|
|
7495
|
+
|
|
7496
|
+
The chart presets are also available on their own:
|
|
7497
|
+
|
|
7498
|
+
- `resolveChartScene()` gives the analytic reflectance at any point in the frame.
|
|
7499
|
+
- `createPlanarSceneRadianceProvider()` turns a chart into an `evaluateRadiance` callback for the native
|
|
7500
|
+
environment RAW contract.
|
|
7501
|
+
|
|
7502
|
+
### Facade 2.0 options: output, demosaic, optics and motion
|
|
7503
|
+
|
|
7504
|
+
Each 2.0 option is opt-in and uses an existing public contract. Without them, the facade's output is
|
|
7505
|
+
byte-identical to 1.0.
|
|
7506
|
+
|
|
7507
|
+
- `output.format: "png"` writes `serializePngImage` output. That is either the developed sRGB samples, or,
|
|
7508
|
+
with `colorEncoding`, the unclipped linear development converted through CIE XYZ to Display P3 or Rec. 2020,
|
|
7509
|
+
signalled with cICP.
|
|
7510
|
+
- `output.gamutMapping: "perceptual"` replaces the component clip with `prepareRgbGamutMapping`.
|
|
7511
|
+
- `format: "dng"` writes the packed native RAW with `serializeNativeRawDng`.
|
|
7512
|
+
|
|
7513
|
+
See [Output formats](QUICK_START.md#output-formats).
|
|
7514
|
+
|
|
7515
|
+
```ts
|
|
7516
|
+
import { simulateCapture } from "@photivra/engine";
|
|
7517
|
+
|
|
7518
|
+
const p3 = await simulateCapture({ scene: { preset: "checkerboard" }, output: { width: 8, height: 8, format: "png", colorEncoding: "display-p3", bitDepth: 16 } });
|
|
7519
|
+
console.log(p3.image.png?.length, p3.image.colorEncoding, p3.provenance.composedContracts.includes("serializePngImage"));
|
|
7520
|
+
```
|
|
7521
|
+
|
|
7522
|
+
`output.demosaic` selects kernels from `createBuiltInBayerDemosaicProfiles`. `output.borderHandling:
|
|
7523
|
+
"mirror"` renders no border sites; it reads reflect-101 mirror images at the frame edge instead.
|
|
7524
|
+
|
|
7525
|
+
```ts
|
|
7526
|
+
import { simulateCapture } from "@photivra/engine";
|
|
7527
|
+
|
|
7528
|
+
const mirrored = await simulateCapture({ scene: { preset: "uniform" }, output: { width: 6, height: 4, demosaic: "bilinear", borderHandling: "mirror" } });
|
|
7529
|
+
console.log(mirrored.plan.exposure.geometry.nativeRaster, mirrored.assumptions.find((a) => a.id === "output.demosaic")?.value);
|
|
7530
|
+
```
|
|
7531
|
+
|
|
7532
|
+
`lens.projection` and `lens.distortion` become the environment `lensFieldMapping`. `lens.vignetting`
|
|
7533
|
+
becomes radial field throughput, and `lens.psf` is a sampled PSF you supply. Haze and polarizers are not
|
|
7534
|
+
offered, because the scene-radiance provider contract declares neither volumetric transport nor
|
|
7535
|
+
polarization. See [Optics](QUICK_START.md#optics).
|
|
7536
|
+
|
|
7537
|
+
```ts
|
|
7538
|
+
import { simulateCapture } from "@photivra/engine";
|
|
7539
|
+
|
|
7540
|
+
const barrel = await simulateCapture({
|
|
7541
|
+
scene: { preset: "uniform" },
|
|
7542
|
+
lens: { distortion: { kind: "brown-conrady", profile: { normalizationRadiusMm: 0.05, maximumNormalizedRadius: 1, coefficients: { k1: -0.2, k2: 0, k3: 0, p1: 0.01, p2: 0 } } } },
|
|
7543
|
+
output: { width: 8, height: 8 }
|
|
7544
|
+
});
|
|
7545
|
+
console.log(barrel.request.siteInput(0).environment.lensFieldMapping?.distortion.kind);
|
|
7546
|
+
```
|
|
7547
|
+
|
|
7548
|
+
`motion.angularVelocityRadPerSec` rotates the camera during the exposure (8 time midpoints by
|
|
7549
|
+
default). `motion.shutter: "rolling"` derives each site's exposure window from the body's rolling
|
|
7550
|
+
readout, with `resolveElectronicRollingShutterExposureSchedule`. See [Motion](QUICK_START.md#motion).
|
|
7551
|
+
|
|
7552
|
+
```ts
|
|
7553
|
+
import { simulateCapture } from "@photivra/engine";
|
|
7554
|
+
|
|
7555
|
+
const rolling = await simulateCapture({ scene: { preset: "checkerboard" }, motion: { angularVelocityRadPerSec: { pitch: 0.02 }, shutter: "rolling" }, output: { width: 6, height: 6 } });
|
|
7556
|
+
console.log(rolling.request.siteInput(7 * 8).charge.completenessProfile.startOffsetSecondsFromOpeningReference);
|
|
7557
|
+
```
|