@photivra/engine 2.20.0 → 2.21.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 +178 -1
- package/CITATION.cff +1 -1
- package/README.md +7 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +7 -0
- package/dist/capture/dng-lens-correction-opcodes.d.ts +2 -2
- package/dist/capture/dng-lens-correction-opcodes.js +2 -2
- package/dist/capture/dng-lens-correction-opcodes.js.map +1 -1
- package/dist/capture/exposure-build-up-stage.d.ts +16 -0
- package/dist/capture/exposure-build-up-stage.js +4 -0
- package/dist/capture/exposure-build-up-stage.js.map +1 -0
- package/dist/capture/native-environment-lean.d.ts +2 -1
- package/dist/capture/native-environment-lean.js +41 -73
- package/dist/capture/native-environment-lean.js.map +1 -1
- package/dist/capture/native-environment-tabulated.js +4 -3
- package/dist/capture/native-environment-tabulated.js.map +1 -1
- package/dist/capture/native-raw.d.ts +3 -2
- package/dist/capture/native-raw.js +5 -4
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/capture/sensor-raw-producer.d.ts +4 -1
- package/dist/capture/sensor-raw-producer.js +12 -3
- package/dist/capture/sensor-raw-producer.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/generic-lens-catalogue.d.ts +3 -2
- package/dist/equipment/generic-lens-catalogue.js +18 -5
- package/dist/equipment/generic-lens-catalogue.js.map +1 -1
- package/dist/equipment/generic-tier-assets.d.ts +32 -0
- package/dist/equipment/generic-tier-assets.js +16 -11
- package/dist/equipment/generic-tier-assets.js.map +1 -1
- package/dist/equipment/sensor-profile-body.d.ts +78 -0
- package/dist/equipment/sensor-profile-body.js +77 -0
- package/dist/equipment/sensor-profile-body.js.map +1 -0
- package/dist/equipment/sensor-profiles.d.ts +8 -0
- package/dist/equipment/sensor-profiles.js +82 -0
- package/dist/equipment/sensor-profiles.js.map +1 -0
- package/dist/facade/catalogue-lens-signal.d.ts +2 -2
- package/dist/facade/catalogue-lens-signal.js +58 -71
- package/dist/facade/catalogue-lens-signal.js.map +1 -1
- package/dist/facade/chart-radiance-batch.js +2 -1
- package/dist/facade/chart-radiance-batch.js.map +1 -1
- package/dist/facade/defocus-image-space-provider.d.ts +7 -0
- package/dist/facade/defocus-image-space-provider.js +64 -76
- package/dist/facade/defocus-image-space-provider.js.map +1 -1
- package/dist/facade/display-view-gpu-session.js +2 -0
- package/dist/facade/display-view-gpu-session.js.map +1 -1
- package/dist/facade/display-view-gpu.d.ts +6 -1
- package/dist/facade/display-view-gpu.js.map +1 -1
- package/dist/facade/display-view-lens-signal.js +25 -23
- package/dist/facade/display-view-lens-signal.js.map +1 -1
- package/dist/facade/display-view.js +5 -23
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/expected-signal-setup.d.ts +3 -1
- package/dist/facade/expected-signal-setup.js +1 -1
- package/dist/facade/expected-signal-setup.js.map +1 -1
- package/dist/facade/expected-signal.d.ts +47 -14
- package/dist/facade/expected-signal.js +117 -112
- package/dist/facade/expected-signal.js.map +1 -1
- package/dist/facade/exposure-build-up.d.ts +70 -0
- package/dist/facade/exposure-build-up.js +164 -0
- package/dist/facade/exposure-build-up.js.map +1 -0
- package/dist/facade/fast-methods.js +9 -1
- package/dist/facade/fast-methods.js.map +1 -1
- package/dist/facade/generic-lens-capture.d.ts +2 -2
- package/dist/facade/generic-lens-capture.js +2 -2
- package/dist/facade/generic-lens-capture.js.map +1 -1
- package/dist/facade/image-plane-enable.d.ts +7 -0
- package/dist/facade/image-plane-enable.js +17 -0
- package/dist/facade/image-plane-enable.js.map +1 -0
- package/dist/facade/image-plane-lattice-data.d.ts +6 -0
- package/dist/facade/image-plane-lattice-data.js +10 -0
- package/dist/facade/image-plane-lattice-data.js.map +1 -0
- package/dist/facade/image-plane-support.d.ts +33 -0
- package/dist/facade/image-plane-support.js +11 -0
- package/dist/facade/image-plane-support.js.map +1 -0
- package/dist/facade/image-plane.d.ts +36 -0
- package/dist/facade/image-plane.js +353 -0
- package/dist/facade/image-plane.js.map +1 -0
- package/dist/facade/mixed-illumination.d.ts +108 -0
- package/dist/facade/mixed-illumination.js +174 -0
- package/dist/facade/mixed-illumination.js.map +1 -0
- package/dist/facade/representative-light-sources.d.ts +72 -0
- package/dist/facade/representative-light-sources.js +124 -0
- package/dist/facade/representative-light-sources.js.map +1 -0
- package/dist/facade/scene-radiance.d.ts +129 -4
- package/dist/facade/scene-radiance.js +48 -13
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +39 -10
- package/dist/facade/simulate-capture.js +252 -92
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +14 -6
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/chart-composite-gpu.d.ts +13 -1
- package/dist/gpu/chart-composite-gpu.js +48 -9
- package/dist/gpu/chart-composite-gpu.js.map +1 -1
- package/dist/gpu/chart-radiance-gpu.js +4 -30
- package/dist/gpu/chart-radiance-gpu.js.map +1 -1
- package/dist/gpu/display-view-composite-material-module.d.ts +10 -4
- package/dist/gpu/display-view-composite-material-module.js +39 -13
- package/dist/gpu/display-view-composite-material-module.js.map +1 -1
- package/dist/gpu/display-view-image-material-module.d.ts +86 -0
- package/dist/gpu/display-view-image-material-module.js +339 -0
- package/dist/gpu/display-view-image-material-module.js.map +1 -0
- package/dist/gpu/display-view-image-quality.d.ts +45 -0
- package/dist/gpu/display-view-image-quality.js +380 -0
- package/dist/gpu/display-view-image-quality.js.map +1 -0
- package/dist/gpu/display-view-quality-gpu.d.ts +67 -1
- package/dist/gpu/display-view-quality-gpu.js +136 -205
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/display-view-speed-gpu.d.ts +51 -3
- package/dist/gpu/display-view-speed-gpu.js +131 -93
- package/dist/gpu/display-view-speed-gpu.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +29 -15
- package/dist/gpu/wgsl-display-view-quality-kernel.js +58 -36
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/gpu/wgsl-display-view-speed-kernel.d.ts +21 -6
- package/dist/gpu/wgsl-display-view-speed-kernel.js +46 -11
- package/dist/gpu/wgsl-display-view-speed-kernel.js.map +1 -1
- package/dist/gpu/wgsl-exact-sum.d.ts +20 -0
- package/dist/gpu/wgsl-exact-sum.js +42 -0
- package/dist/gpu/wgsl-exact-sum.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/motion/subject-motion.d.ts +66 -0
- package/dist/motion/subject-motion.js +52 -0
- package/dist/motion/subject-motion.js.map +1 -0
- package/dist/optics/generic-lens-lateral-color.d.ts +83 -0
- package/dist/optics/generic-lens-lateral-color.js +77 -0
- package/dist/optics/generic-lens-lateral-color.js.map +1 -0
- package/dist/optics/generic-lens-psf.js +54 -46
- package/dist/optics/generic-lens-psf.js.map +1 -1
- package/dist/optics/lens-psf-profile.d.ts +7 -0
- package/dist/optics/lens-psf-profile.js +2 -1
- package/dist/optics/lens-psf-profile.js.map +1 -1
- package/dist/output/developed-lens-corrections.d.ts +19 -5
- package/dist/output/developed-lens-corrections.js +28 -9
- package/dist/output/developed-lens-corrections.js.map +1 -1
- package/dist/output/generic-lens-corrections.d.ts +41 -5
- package/dist/output/generic-lens-corrections.js +52 -8
- package/dist/output/generic-lens-corrections.js.map +1 -1
- package/dist/output/print-plan.d.ts +41 -5
- package/dist/output/print-plan.js +59 -14
- package/dist/output/print-plan.js.map +1 -1
- package/dist/sensor/binomial-sample.d.ts +3 -0
- package/dist/sensor/binomial-sample.js +86 -0
- package/dist/sensor/binomial-sample.js.map +1 -0
- package/dist/sensor/sensor-characteristics.d.ts +10 -0
- package/dist/sensor/sensor-characteristics.js +20 -9
- package/dist/sensor/sensor-characteristics.js.map +1 -1
- package/docs/AGENT_GUIDE.md +1 -0
- package/docs/API_REFERENCE.md +98 -1
- package/docs/QUICK_START.md +125 -0
- package/docs/USAGE.md +92 -1
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-lens-catalogue.md +10 -5
- package/docs/api/equipment-sensor-profile-body.md +64 -0
- package/docs/api/equipment-sensor-profiles.md +86 -0
- package/docs/api/exports.json +207 -2
- package/docs/api/facade-exposure-build-up.md +105 -0
- package/docs/api/facade-fast-methods.md +9 -1
- package/docs/api/facade-image-plane-enable.md +16 -0
- package/docs/api/facade-mixed-illumination.md +67 -0
- package/docs/api/facade-representative-light-sources.md +131 -0
- package/docs/api/facade-scene-radiance.md +54 -3
- package/docs/api/facade-simulate-capture.md +26 -6
- package/docs/api/motion-subject-motion.md +23 -0
- package/docs/api/optics-generic-lens-lateral-color.md +79 -0
- package/docs/api/output-developed-lens-corrections.md +10 -1
- package/docs/api/output-generic-lens-corrections.md +40 -4
- package/docs/api/output-print-plan.md +35 -1
- package/llms.txt +2 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"print-plan.js","sourceRoot":"","sources":["../../src/output/print-plan.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;GAGG;AACH,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAA0B,MAAM,+BAA+B,CAAC;AAC9G,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAoC,MAAM,uBAAuB,CAAC;AA2DjG,MAAM,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAW,CAAC;AAClE,MAAM,cAAc,GAAG,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC;AACvC,SAAS,MAAM,CAAC,KAAc,EAAE,IAAuB;IACrD,OAAO,wBAAwB,CAAC,KAAK,EAAE,IAAI,EAAE,2CAA2C,CAAC,CAAC;AAC5F,CAAC;AACD,SAAS,MAAM,CAAC,KAAc,EAAE,IAAY;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,MAAM,IAAI,yBAAyB,CAAC,GAAG,IAAI,mBAAmB,CAAC,CAAC;IAC/F,qBAAqB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAAC,OAAO,KAAK,CAAC;AACnD,CAAC;AACD,SAAS,IAAI,CAAC,KAAc;IAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,MAAM,IAAI,yBAAyB,CAAC,4BAA4B,CAAC,CAAC;IACnI,OAAO,KAA4B,CAAC;AACtC,CAAC;AACD,SAAS,MAAM,CAAC,KAAc;IAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,cAAc,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;AACxE,CAAC;AACD,SAAS,MAAM,CAAC,KAAc;IAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC;IACvD,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAC1G,sBAAsB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAAC,sBAAsB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IACrG,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;AACrC,CAAC;AACD,SAAS,QAAQ,CAAC,KAAc;IAC9B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,aAAa,EAAE,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,cAAc,CAAC,CAAC,CAAC;IAC/H,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;IAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACvE,KAAK,MAAM,GAAG,IAAI,CAAC,mBAAmB,EAAE,gBAAgB,CAAU,EAAE,CAAC;QACnE,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,CAAC,CAAC,YAAY,KAAK,SAAS;QAAE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACzD,wFAAwF;IACxF,MAAM,MAAM,GAAG,CAA2C,CAAC;IAC3D,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,uBAAuB,CAAC,MAAM,CAAC,CAAC;AACzC,CAAC;AACD,SAAS,uBAAuB,CAAC,CAA8B;IAC7D,OAAO,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,YAAY,EAAE,EAAE,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW;QACzG,GAAG,CAAC,CAAC,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,EAAE,GAAG,CAAC,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC/F,GAAG,CAAC,CAAC,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC;QACtF,GAAG,CAAC,CAAC,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,EAAE,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,EAAE,CAAC;AACvF,CAAC;AACD,SAAS,QAAQ,CAAC,KAAc;IAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,eAAe,CAAC,CAAC,CAAC;IACvK,IAAI,MAA8B,CAAC;IACnC,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;YACnD,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE,eAAe,CAAC,EAAE,CAAC;QACzF,CAAC;QACD,KAAK,qBAAqB,CAAC,CAAC,CAAC;YAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC,CAAC;YAC/D,MAAM,GAAG,EAAE,IAAI,EAAE,qBAAqB,EAAE,yBAAyB,EAAE,MAAM,CAAC,CAAC,CAAC,yBAAyB,EAAE,2BAA2B,CAAC,EAAE,CAAC;YAAC,MAAM;QAC/I,CAAC;QACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;YACtB,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,uBAAuB,EAAE,kBAAkB,CAAC,CAAC,CAAC;YAC/E,MAAM,gBAAgB,GAAG,MAAM,CAAC,CAAC,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;YAAC,sBAAsB,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,CAAC;YACtI,MAAM,GAAG,EAAE,IAAI,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,qBAAqB,EAAE,uBAAuB,CAAC,EAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QAChJ,CAAC;QACD,KAAK,mBAAmB,CAAC,CAAC,CAAC;YACzB,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,kBAAkB,CAAC,CAAC,CAAC;YAC1E,MAAM,gBAAgB,GAAG,MAAM,CAAC,CAAC,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;YAAC,sBAAsB,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,CAAC;YACtI,IAAI,gBAAgB,GAAG,CAAC;gBAAE,MAAM,IAAI,yBAAyB,CAAC,mDAAmD,CAAC,CAAC;YACnH,MAAM,GAAG,EAAE,IAAI,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,CAAC,CAAC,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,EAAE,gBAAgB,EAAE,CAAC;YAAC,MAAM;QACpI,CAAC;QACD,OAAO,CAAC,CAAC,MAAM,IAAI,yBAAyB,CAAC,oCAAoC,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,KAAK;QAAE,MAAM,IAAI,yBAAyB,CAAC,kEAAkE,CAAC,CAAC;IAC1I,OAAO,MAAM,CAAC;AAChB,CAAC;AACD,SAAS,YAAY,CAAC,CAA0D;IAC9E,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,qBAAqB,CAAC,CAAC,OAAO,CAAC,CAAC,yBAAyB,CAAC;QAC/D,KAAK,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,qBAAqB,GAAG,CAAC,CAAC,gBAAgB,CAAC;QAC3E,KAAK,mBAAmB,CAAC,CAAC,OAAO,CAAC,CAAC,gBAAgB,GAAG,CAAC,CAAC,gBAAgB,CAAC;IAC3E,CAAC;AACH,CAAC;AAED,sHAAsH;AACtH,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IACtG,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC7E,IAAI,YAA+B,CAAC;IACpC,IAAI,MAAM,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;QACpD,YAAY,GAAG,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,qBAAqB,CAAC,MAAM,CAAC,SAAS,EAAE,4BAA4B,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;IACpK,CAAC;SAAM,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QACzC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;QACrC,IAAI,MAAM,CAAC,MAAM,KAAK,uBAAuB,IAAI,MAAM,CAAC,MAAM,KAAK,4BAA4B;YAAE,MAAM,IAAI,yBAAyB,CAAC,oCAAoC,CAAC,CAAC;QAC3K,YAAY,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAChE,CAAC;;QAAM,MAAM,IAAI,yBAAyB,CAAC,6BAA6B,CAAC,CAAC;IAC1E,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAC,CAAC;IAClE,IAAI,GAAG,CAAC,IAAI,KAAK,yBAAyB,IAAI,OAAO,GAAG,CAAC,0BAA0B,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,0BAA0B,CAAC,IAAI,GAAG,CAAC,0BAA0B,GAAG,CAAC,IAAI,GAAG,CAAC,0BAA0B,GAAG,IAAI;QAAE,MAAM,IAAI,yBAAyB,CAAC,qDAAqD,CAAC,CAAC;IAChU,MAAM,MAAM,GAAmB,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,oBAAoB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,qBAAqB,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,GAAG,CAAC,0BAA0B,EAAE,EAAE,CAAC;IAC3X,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC,CAAC;QACzF,MAAM,CAAC,QAAQ,GAAG;YAChB,GAAG,CAAC,CAAC,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC;YACpF,GAAG,CAAC,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC;YAC9E,GAAG,CAAC,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC;YACzF,GAAG,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;SAClF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AACD,SAAS,GAAG,CAAC,CAAS,EAAE,CAAS,IAAY,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;IAAC,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC,GAAG,CAAC,CAAC;IAAC,CAAC,GAAG,IAAI,CAAC;AAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACjH,SAAS,iBAAiB,CAAC,GAAG,MAAgB,IAAa,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACnH,0FAA0F;AAC1F,SAAS,gBAAgB,CAAC,KAAa,EAAE,SAAwB;IAC/D,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACzE,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC;AACD,SAAS,UAAU,CAAC,CAAmB,EAAE,CAAmB;IAC1D,OAAO,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;AACvG,CAAC;AACD,SAAS,aAAa,CAAC,MAAwB,EAAE,KAAa,EAAE,MAAc;IAC5E,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IAC3D,MAAM,CAAC,GAAG,MAAM,CAAC,UAAU,GAAG,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,WAAW,GAAG,OAAO,CAAC;IACxE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACvE,MAAM,UAAU,GAAG,KAAK,GAAG,CAAC,EAAE,WAAW,GAAG,KAAK,GAAG,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACpH,CAAC;AACD,SAAS,WAAW,CAAC,MAAwB,EAAE,KAAa,EAAE,MAAc,EAAE,QAAgB;IAC5F,MAAM,EAAE,GAAG,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,EAAE,GAAG,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC;IACvE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,2BAA2B,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,cAAc,EAAE,EAAE,CAAC;AACpN,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAqB;IACtD,MAAM,KAAK,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,IAAI,GAAc,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,sBAAsB,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,wBAAwB,EAAE,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,CAAC;IACpa,MAAM,MAAM,GAAG,GAAiC,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,CAAC,wEAAwE,EAAE,sEAAsE,EAAE,+FAA+F,CAAC,CAAC,CAAC;IAClW,MAAM,WAAW,GAAG,GAAiC,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,IAAI,CAAC,sBAAsB,GAAG,eAAe,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IACzM,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC,eAAe,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IAC/N,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC;QAAE,OAAO,WAAW,EAAE,CAAC;IACtE,IAAI,CAAC,cAAc,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,iBAAiB,GAAG,QAAQ,CAAC;IAC3E,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzF,MAAM,WAAW,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;IACvF,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;IACzH,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;IAC/F,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,GAAG,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,OAAO,WAAW,EAAE,CAAC;IACxF,MAAM,QAAQ,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE,yBAAyB,EAAE,KAAK,EAAE,CAAC;IAC7G,IAAI,CAAC,QAAQ,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,QAAQ,EAAE,sBAAsB,EAAE,OAAO,EAAE,CAAC,8DAA8D,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,QAAQ,EAAE,iCAAiC,EAAE,OAAO,EAAE,CAAC,4FAA4F,EAAE,2EAA2E,CAAC,CAAC,CAAC;IACxZ,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;IAAC,CAAC;IACrG,MAAM,QAAQ,GAAG,sBAAsB,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC;IACrE,MAAM,MAAM,GAAG,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;IAC5G,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9F,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,WAAW,EAAE,CAAC;IACxD,IAAI,CAAC,mBAAmB,GAAG,WAAW,CAAC;IACvC,MAAM,iBAAiB,GAAG,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvE,IAAI,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC,EAAE,iBAAiB,CAAC,aAAa,CAAC,CAAC,EAAE,iBAAiB,CAAC,2BAA2B,CAAC,CAAC,EAAE,iBAAiB,CAAC,2BAA2B,CAAC,CAAC,CAAC;QAAE,OAAO,WAAW,EAAE,CAAC;IACrN,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAAC;IAChC,IAAI,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,0BAA0B,EAAE,CAAC;QAAC,IAAI,CAAC,MAAM,GAAG,4BAA4B,CAAC;QAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;IAAC,CAAC;IAC/L,MAAM,WAAW,GAAG,KAAK,CAAC,YAAY,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC;IACnG,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC;IACtG,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;QACnD,CAAC,CAAC,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,GAAG,EAAE,YAAY,GAAG,GAAG,CAAC;QAC9D,CAAC,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC;IACzD,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,WAAW,EAAE,CAAC;IAC5C,IAAI,CAAC,qBAAqB,GAAG,QAAQ,CAAC;IACtC,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,IAAI,QAAQ,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC;IAC1G,IAAI,CAAC,sBAAsB,GAAG,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC;IACzE,IAAI,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,GAAG,KAAK,GAAG,KAAK,EAAE,MAAM,CAAC,WAAW,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;IAChG,IAAI,eAAe,GAAG,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC;IACnI,IAAI,QAAQ,GAAG,KAAK,GAAG,QAAQ,EAAE,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC/D,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,eAAe,KAAK,IAAI,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,CAAC;QAAE,OAAO,WAAW,EAAE,CAAC;IACvI,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS,EAAW,EAAE;QACxD,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACrJ,OAAO,SAAS,KAAK,IAAI,IAAI,SAAS,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,IAAI,SAAS,CAAC,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC;IACxH,CAAC,CAAC;IACF,8EAA8E;IAC9E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAChE,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAAC,QAAQ,GAAG,KAAK,GAAG,QAAQ,CAAC;QAAC,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC5G,CAAC;IACD,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC;QAAE,OAAO,WAAW,EAAE,CAAC;IACvG,IAAI,eAAe,KAAK,IAAI,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACrD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,GAAG,WAAW,CAAC,EAAE,CAAC,EAAE;YAAE,eAAe,GAAG,gBAAgB,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;QAC7I,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,GAAG,WAAW,CAAC;YAAE,OAAO,WAAW,EAAE,CAAC;IACvH,CAAC;IACD,IAAI,CAAC,YAAY,GAAG,EAAE,+BAA+B,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,qCAAqC,EAAE,eAAe,EAAE,CAAC;IACxJ,IAAI,CAAC,UAAU,EAAE,CAAC;QAAC,IAAI,CAAC,MAAM,GAAG,4BAA4B,CAAC;QAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;IAAC,CAAC;IACvH,IAAI,IAAI,GAAG,QAAQ,CAAC;IACpB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAChC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,IAAI,CAAC,EAAE,QAAQ,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;QAC3E,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,aAAa,EAAE,UAAU,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,aAAa,EAAE,WAAW,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC;QAC3O,IAAI,WAAW,KAAK,IAAI;YAAE,OAAO,WAAW,EAAE,CAAC;QAC/C,IAAI,GAAG,QAAQ,CAAC,WAAW,IAAI,WAAW,CAAC;QAC3C,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,EAAE,UAAU,EAAE,WAAW,GAAG,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,YAAY,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC7G,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;iBACtJ,IAAI,QAAQ,CAAC,WAAW,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI,CAAC,WAAW,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;;gBAC1K,IAAI,GAAG,KAAK,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QACpF,IAAI,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,UAAU,IAAI,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC,WAAW;YAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;QAC1J,IAAI,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW;YAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACxI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC;YAAC,IAAI,CAAC,MAAM,GAAG,mBAAmB,CAAC;YAAC,OAAO,MAAM,EAAE,CAAC;QAAC,CAAC;IACnJ,CAAC;IACD,IAAI,CAAC,UAAU;QAAE,OAAO,MAAM,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5D,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC,EAAE,QAAQ,CAAC,2BAA2B,CAAC,CAAC,EAAE,QAAQ,CAAC,2BAA2B,CAAC,CAAC,CAAC;QAAE,OAAO,WAAW,EAAE,CAAC;IACjL,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;IAAC,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC;IACnD,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC,EAAE,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAC5G,OAAO,MAAM,EAAE,CAAC;AAClB,CAAC;AAiBD;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAA0B;IAChE,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC,CAAC;IAC3D,IAAI,CAAC,CAAC,qBAAqB,KAAK,OAAO,IAAI,CAAC,CAAC,qBAAqB,KAAK,4BAA4B;QAAE,MAAM,IAAI,yBAAyB,CAAC,0CAA0C,CAAC,CAAC;IACrL,MAAM,aAAa,GAAG,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5E,MAAM,MAAM,GAAmB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3K,IAAI,aAAa,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvG,MAAM,CAAC,MAAM,GAAG,aAAa,CAAC;QAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;IACzG,CAAC;SAAM,IAAI,aAAa,CAAC,MAAM,KAAK,aAAa;QAAE,MAAM,CAAC,MAAM,GAAG,aAAa,CAAC;SAC5E,IAAI,aAAa,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,CAAC,qBAAqB,KAAK,4BAA4B,IAAI,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACnH,MAAM,CAAC,MAAM,GAAG,aAAa,CAAC,sBAAsB,KAAK,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,kBAAkB,CAAC;QAC3G,CAAC;aAAM,CAAC;YAAC,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC;YAAC,MAAM,CAAC,cAAc,GAAG,aAAa,CAAC,YAAY,CAAC,+BAA+B,CAAC;QAAC,CAAC;IAC1H,CAAC;IACD,OAAO,gBAAgB,CAAC,MAAM,EAAE,qCAAqC,EAAE,OAAO,EAAE,CAAC,gFAAgF,EAAE,uEAAuE,CAAC,CAAC,CAAC;AAC/O,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/** Print planning: how many native pixels a print of a given size and viewing distance needs.\n * It uses native capture pixels only. It does not make an image or certify perceived quality.\n * @see docs/PRINT_PLANNING.md for equations, rounding and support limits.\n */\nimport { calculatedResult, approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord, requirePublicOpaqueId } from \"../core/record-validation.js\";\nimport { requirePositiveFinite, requirePositiveInteger } from \"../core/validation.js\";\nimport { ENGINE_API_VERSION } from \"../core/version.js\";\nimport { resolveCaptureGeometry, type ResolveCaptureGeometryInput } from \"./capture-geometry.js\";\nimport type { RasterDimensions } from \"../sensor/sensor-geometry.js\";\n\n/** A physical length. Not to be confused with image pixels or printer dots. */\nexport interface PrintLength { value: number; unit: \"mm\" | \"cm\" | \"inches\" | \"m\" }\n/** The flat printed image area. It excludes paper margins, borders and canvas wraps. */\nexport interface PrintedImageSize { width: number; height: number; unit: PrintLength[\"unit\"] }\n/** How to choose pixel density: one of three angular criteria at the viewing distance, or a manual PPI (pixels per inch), which gives a different kind of plan. */\nexport type PrintSamplingCriterion =\n | { kind: \"angular-pixel-pitch\"; maximumArcminutesPerPixel: number }\n | { kind: \"angular-stroke\"; strokeWidthArcminutes: number; samplesPerStroke: number }\n | { kind: \"angular-line-pair\"; periodArcminutes: number; samplesPerPeriod: number }\n | { kind: \"manual-ppi\"; pixelsPerInch: number };\n/** The declared native source geometry. Not a preview or a resampled pixel count. */\nexport type PrintNativeSource =\n | { kind: \"native-retained\"; captureId: string; geometry: ResolveCaptureGeometryInput }\n | { kind: \"unavailable\"; reason: \"missing-native-raster\" | \"unverified-native-identity\" };\n/** Print lab requirements supplied by the caller. There is no vendor catalogue and no guarantee of what a lab will accept. */\nexport interface PrintProviderConstraints {\n minimumRaster?: RasterDimensions;\n exactRaster?: RasterDimensions;\n minimumPpi?: number;\n exactPpi?: number;\n}\n/** Plan input. The output keeps the exact native aspect ratio; the allowed physical aspect error is explicit and reported. */\nexport interface PrintPlanInput {\n source: PrintNativeSource;\n printedImage: PrintedImageSize;\n viewingDistance: PrintLength;\n sampling: PrintSamplingCriterion;\n fit: { kind: \"confirmed-native-aspect\"; maximumRelativeAspectError: number };\n provider?: PrintProviderConstraints;\n}\n/** Pixel density and angular pixel pitch for each axis. The angles are pure geometry, not a measure of visual acuity. */\nexport interface PrintRasterSampling {\n raster: RasterDimensions;\n pixelsPerInch: { x: number; y: number };\n angularPixelPitchArcminutes: { x: number; y: number };\n}\n/** A plan, valid only under its stated conditions. A null recommendation never means it is fine to upscale. */\nexport interface PrintPlan {\n engineApiVersion: string;\n input: PrintPlanInput;\n status: \"ready\" | \"insufficient-native-pixels\" | \"provider-conflict\" | \"crop-confirmation-required\" | \"cannot-assess\" | \"unsupported\";\n nativePixelSufficiency: \"sufficient\" | \"insufficient\" | \"cannot-assess\";\n reasons: string[];\n printedImageMm: { width: number; height: number } | null;\n viewingDistanceMm: number | null;\n guidance: CalculationResult<{ derivedPixelsPerInch: number; maximumPixelPitchMm: number; maximumArcminutesPerPixel: number | null }> | null;\n relativeAspectError: number | null;\n native: PrintRasterSampling | null;\n minimumSamplingRaster: RasterDimensions | null;\n recommended: PrintRasterSampling | null;\n resamplingScale: { x: number; y: number } | null;\n alternatives: { maximumImageMmAtCurrentDistance: { width: number; height: number }; minimumViewingDistanceMmAtCurrentSize: number | null } | null;\n capturedDetailAssessment: \"unassessed\";\n deliveryAssessment: \"unassessed\";\n}\n\nconst UNIT_MM = { mm: 1, cm: 10, inches: 25.4, m: 1000 } as const;\nconst RAD_PER_ARCMIN = Math.PI / 10800;\nfunction record(value: unknown, keys: readonly string[]): Record<string, unknown> {\n return requireAllowlistedRecord(value, keys, \"Invalid or unknown Print planning fields.\");\n}\nfunction number(value: unknown, name: string): number {\n if (typeof value !== \"number\") throw new InvalidConfigurationError(`${name} must be numeric.`);\n requirePositiveFinite(name, value); return value;\n}\nfunction unit(value: unknown): PrintLength[\"unit\"] {\n if (typeof value !== \"string\" || !Object.hasOwn(UNIT_MM, value)) throw new InvalidConfigurationError(\"Invalid Print length unit.\");\n return value as PrintLength[\"unit\"];\n}\nfunction length(value: unknown): PrintLength {\n const r = record(value, [\"value\", \"unit\"]);\n return { value: number(r.value, \"length.value\"), unit: unit(r.unit) };\n}\nfunction raster(value: unknown): RasterDimensions {\n const r = record(value, [\"pixelWidth\", \"pixelHeight\"]);\n const pixelWidth = number(r.pixelWidth, \"pixelWidth\"), pixelHeight = number(r.pixelHeight, \"pixelHeight\");\n requirePositiveInteger(\"pixelWidth\", pixelWidth); requirePositiveInteger(\"pixelHeight\", pixelHeight);\n return { pixelWidth, pixelHeight };\n}\nfunction geometry(value: unknown): ResolveCaptureGeometryInput {\n const g = record(value, [\"imagingArea\", \"nativeRaster\", \"orientation\", \"activeCaptureRect\", \"outputCropRect\", \"outputRaster\"]);\n record(g.imagingArea, [\"widthMm\", \"heightMm\"]); raster(g.nativeRaster);\n for (const key of [\"activeCaptureRect\", \"outputCropRect\"] as const) {\n if (g[key] !== undefined) record(g[key], [\"x\", \"y\", \"width\", \"height\"]);\n }\n if (g.outputRaster !== undefined) raster(g.outputRaster);\n // The capture geometry module defines units, orientations, bounds and the optical axis.\n const parsed = g as unknown as ResolveCaptureGeometryInput;\n resolveCaptureGeometry(parsed);\n return structuredCloneGeometry(parsed);\n}\nfunction structuredCloneGeometry(g: ResolveCaptureGeometryInput): ResolveCaptureGeometryInput {\n return { imagingArea: { ...g.imagingArea }, nativeRaster: { ...g.nativeRaster }, orientation: g.orientation,\n ...(g.activeCaptureRect === undefined ? {} : { activeCaptureRect: { ...g.activeCaptureRect } }),\n ...(g.outputCropRect === undefined ? {} : { outputCropRect: { ...g.outputCropRect } }),\n ...(g.outputRaster === undefined ? {} : { outputRaster: { ...g.outputRaster } }) };\n}\nfunction sampling(value: unknown): PrintSamplingCriterion {\n const tag = record(value, [\"kind\", \"maximumArcminutesPerPixel\", \"strokeWidthArcminutes\", \"samplesPerStroke\", \"periodArcminutes\", \"samplesPerPeriod\", \"pixelsPerInch\"]);\n let result: PrintSamplingCriterion;\n switch (tag.kind) {\n case \"manual-ppi\": {\n const r = record(value, [\"kind\", \"pixelsPerInch\"]);\n return { kind: \"manual-ppi\", pixelsPerInch: number(r.pixelsPerInch, \"pixelsPerInch\") };\n }\n case \"angular-pixel-pitch\": {\n const r = record(value, [\"kind\", \"maximumArcminutesPerPixel\"]);\n result = { kind: \"angular-pixel-pitch\", maximumArcminutesPerPixel: number(r.maximumArcminutesPerPixel, \"maximumArcminutesPerPixel\") }; break;\n }\n case \"angular-stroke\": {\n const r = record(value, [\"kind\", \"strokeWidthArcminutes\", \"samplesPerStroke\"]);\n const samplesPerStroke = number(r.samplesPerStroke, \"samplesPerStroke\"); requirePositiveInteger(\"samplesPerStroke\", samplesPerStroke);\n result = { kind: \"angular-stroke\", strokeWidthArcminutes: number(r.strokeWidthArcminutes, \"strokeWidthArcminutes\"), samplesPerStroke }; break;\n }\n case \"angular-line-pair\": {\n const r = record(value, [\"kind\", \"periodArcminutes\", \"samplesPerPeriod\"]);\n const samplesPerPeriod = number(r.samplesPerPeriod, \"samplesPerPeriod\"); requirePositiveInteger(\"samplesPerPeriod\", samplesPerPeriod);\n if (samplesPerPeriod < 2) throw new InvalidConfigurationError(\"A line-pair period requires at least two samples.\");\n result = { kind: \"angular-line-pair\", periodArcminutes: number(r.periodArcminutes, \"periodArcminutes\"), samplesPerPeriod }; break;\n }\n default: throw new InvalidConfigurationError(\"Invalid Print sampling convention.\");\n }\n const angle = angularLimit(result);\n if (angle <= 0 || angle >= 10800) throw new InvalidConfigurationError(\"Effective angular pixel pitch must lie in (0, 10800) arcminutes.\");\n return result;\n}\nfunction angularLimit(s: Exclude<PrintSamplingCriterion, { kind: \"manual-ppi\" }>): number {\n switch (s.kind) {\n case \"angular-pixel-pitch\": return s.maximumArcminutesPerPixel;\n case \"angular-stroke\": return s.strokeWidthArcminutes / s.samplesPerStroke;\n case \"angular-line-pair\": return s.periodArcminutes / s.samplesPerPeriod;\n }\n}\n\n/** Validates and copies an untrusted print request. It never assumes the source is native if that is not declared. */\nexport function parsePrintPlanInput(value: unknown): PrintPlanInput {\n const r = record(value, [\"source\", \"printedImage\", \"viewingDistance\", \"sampling\", \"fit\", \"provider\"]);\n const source = record(r.source, [\"kind\", \"captureId\", \"geometry\", \"reason\"]);\n let parsedSource: PrintNativeSource;\n if (source.kind === \"native-retained\") {\n record(r.source, [\"kind\", \"captureId\", \"geometry\"]);\n parsedSource = { kind: \"native-retained\", captureId: requirePublicOpaqueId(source.captureId, \"Invalid public capture ID.\"), geometry: geometry(source.geometry) };\n } else if (source.kind === \"unavailable\") {\n record(r.source, [\"kind\", \"reason\"]);\n if (source.reason !== \"missing-native-raster\" && source.reason !== \"unverified-native-identity\") throw new InvalidConfigurationError(\"Invalid native-unavailable reason.\");\n parsedSource = { kind: \"unavailable\", reason: source.reason };\n } else throw new InvalidConfigurationError(\"Invalid native source kind.\");\n const image = record(r.printedImage, [\"width\", \"height\", \"unit\"]);\n const fit = record(r.fit, [\"kind\", \"maximumRelativeAspectError\"]);\n if (fit.kind !== \"confirmed-native-aspect\" || typeof fit.maximumRelativeAspectError !== \"number\" || !Number.isFinite(fit.maximumRelativeAspectError) || fit.maximumRelativeAspectError < 0 || fit.maximumRelativeAspectError > 0.01) throw new InvalidConfigurationError(\"Declare a native-aspect fit tolerance in [0, 0.01].\");\n const parsed: PrintPlanInput = { source: parsedSource, printedImage: { width: number(image.width, \"printedImage.width\"), height: number(image.height, \"printedImage.height\"), unit: unit(image.unit) }, viewingDistance: length(r.viewingDistance), sampling: sampling(r.sampling), fit: { kind: \"confirmed-native-aspect\", maximumRelativeAspectError: fit.maximumRelativeAspectError } };\n if (r.provider !== undefined) {\n const p = record(r.provider, [\"minimumRaster\", \"exactRaster\", \"minimumPpi\", \"exactPpi\"]);\n parsed.provider = {\n ...(p.minimumRaster === undefined ? {} : { minimumRaster: raster(p.minimumRaster) }),\n ...(p.exactRaster === undefined ? {} : { exactRaster: raster(p.exactRaster) }),\n ...(p.minimumPpi === undefined ? {} : { minimumPpi: number(p.minimumPpi, \"minimumPpi\") }),\n ...(p.exactPpi === undefined ? {} : { exactPpi: number(p.exactPpi, \"exactPpi\") })\n };\n }\n return parsed;\n}\nfunction gcd(a: number, b: number): number { while (b !== 0) { const next = a % b; a = b; b = next; } return a; }\nfunction positiveSupported(...values: number[]): boolean { return values.every(v => Number.isFinite(v) && v > 0); }\n// Stepping to the adjacent double rounds outward. It is not a tolerance on the criterion.\nfunction adjacentPositive(value: number, direction: \"up\" | \"down\"): number {\n const view = new DataView(new ArrayBuffer(8)); view.setFloat64(0, value);\n view.setBigUint64(0, view.getBigUint64(0) + (direction === \"up\" ? 1n : -1n));\n return view.getFloat64(0);\n}\nfunction exactRatio(a: RasterDimensions, b: RasterDimensions): boolean {\n return BigInt(a.pixelWidth) * BigInt(b.pixelHeight) === BigInt(a.pixelHeight) * BigInt(b.pixelWidth);\n}\nfunction minimumRaster(native: RasterDimensions, width: number, height: number): RasterDimensions | null {\n const divisor = gcd(native.pixelWidth, native.pixelHeight);\n const x = native.pixelWidth / divisor, y = native.pixelHeight / divisor;\n const count = Math.max(1, Math.ceil(width / x), Math.ceil(height / y));\n const pixelWidth = count * x, pixelHeight = count * y;\n return Number.isSafeInteger(pixelWidth) && Number.isSafeInteger(pixelHeight) ? { pixelWidth, pixelHeight } : null;\n}\nfunction diagnostics(raster: RasterDimensions, width: number, height: number, distance: number): PrintRasterSampling {\n const px = width / raster.pixelWidth, py = height / raster.pixelHeight;\n return { raster, pixelsPerInch: { x: 25.4 / px, y: 25.4 / py }, angularPixelPitchArcminutes: { x: 2 * Math.atan((px / distance) / 2) / RAD_PER_ARCMIN, y: 2 * Math.atan((py / distance) / 2) / RAD_PER_ARCMIN } };\n}\n\n/** Finds the smallest file, at the exact native aspect ratio, that meets the declared sampling and lab constraints.\n * The angular guidance is an approximate rule of thumb; the geometry itself is calculated exactly. A common criterion,\n * one arcminute per pixel, is normal (20/20) visual acuity under optimal conditions: the minimum angle of resolution of an\n * ideal eye is about 1 minute of arc, and 20/20 letters are built from elements that each subtend 1 arcmin (Z. Alomar, K. D. Bojikian, \"Visual Acuity Testing in Adults\", EyeWiki, American Academy of Ophthalmology,\n * https://eyewiki.aao.org/Visual_Acuity_Testing_in_Adults, last edited June 16, 2026).\n * Some viewers resolve finer detail and many resolve coarser, so it stays a rule of thumb, and the criterion is the caller's.\n * It does not assess source detail, perceived quality, file writing or available resources.\n */\nexport function calculatePrintPlan(value: PrintPlanInput): CalculationResult<PrintPlan> {\n const input = parsePrintPlanInput(value);\n const plan: PrintPlan = { engineApiVersion: ENGINE_API_VERSION, input, status: \"cannot-assess\", nativePixelSufficiency: \"cannot-assess\", reasons: [], printedImageMm: null, viewingDistanceMm: null, guidance: null, relativeAspectError: null, native: null, minimumSamplingRaster: null, recommended: null, resamplingScale: null, alternatives: null, capturedDetailAssessment: \"unassessed\", deliveryAssessment: \"unassessed\" };\n const finish = (): CalculationResult<PrintPlan> => calculatedResult(plan, \"native-only-print-plan\", \"0.1.0\", [\"Declared native authority; flat image, normal viewing at image center.\", \"Output preserves the exact retained native aspect ratio; no upscale.\", \"Angular sampling is conditional guidance, not captured-detail or print-quality certification.\"]);\n const unsupported = (): CalculationResult<PrintPlan> => { plan.status = \"unsupported\"; plan.nativePixelSufficiency = \"cannot-assess\"; plan.reasons.push(\"unsupported-numeric-range\"); return finish(); };\n const width = input.printedImage.width * UNIT_MM[input.printedImage.unit], height = input.printedImage.height * UNIT_MM[input.printedImage.unit], distance = input.viewingDistance.value * UNIT_MM[input.viewingDistance.unit];\n if (!positiveSupported(width, height, distance)) return unsupported();\n plan.printedImageMm = { width, height }; plan.viewingDistanceMm = distance;\n const angle = input.sampling.kind === \"manual-ppi\" ? null : angularLimit(input.sampling);\n const pitchFactor = angle === null ? null : 2 * Math.tan((angle * RAD_PER_ARCMIN) / 2);\n const pitch = input.sampling.kind === \"manual-ppi\" ? 25.4 / input.sampling.pixelsPerInch : distance * (pitchFactor ?? 0);\n const ppi = input.sampling.kind === \"manual-ppi\" ? input.sampling.pixelsPerInch : 25.4 / pitch;\n if (!positiveSupported(pitch, ppi, width / pitch, height / pitch)) return unsupported();\n const guidance = { derivedPixelsPerInch: ppi, maximumPixelPitchMm: pitch, maximumArcminutesPerPixel: angle };\n plan.guidance = angle === null ? calculatedResult(guidance, \"manual-print-density\", \"0.1.0\", [\"Explicit manual PPI plan; no angular criterion is certified.\"]) : approximationResult(guidance, \"declared-angular-print-sampling\", \"0.1.0\", [\"Exact centered pixel angle uses 2 atan(pitch / (2 distance)); no small-angle substitution.\", \"Declared angular sampling convention is an uncalibrated usage assumption.\"]);\n if (input.source.kind === \"unavailable\") { plan.reasons.push(input.source.reason); return finish(); }\n const resolved = resolveCaptureGeometry(input.source.geometry).value;\n const native = { pixelWidth: resolved.output.cropRect.width, pixelHeight: resolved.output.cropRect.height };\n const aspectError = Math.abs((width / height) / (native.pixelWidth / native.pixelHeight) - 1);\n if (!Number.isFinite(aspectError)) return unsupported();\n plan.relativeAspectError = aspectError;\n const nativeDiagnostics = diagnostics(native, width, height, distance);\n if (!positiveSupported(nativeDiagnostics.pixelsPerInch.x, nativeDiagnostics.pixelsPerInch.y, nativeDiagnostics.angularPixelPitchArcminutes.x, nativeDiagnostics.angularPixelPitchArcminutes.y)) return unsupported();\n plan.native = nativeDiagnostics;\n if (aspectError > input.fit.maximumRelativeAspectError) { plan.status = \"crop-confirmation-required\"; plan.reasons.push(\"printed-image-and-confirmed-crop-aspect-mismatch\"); return finish(); }\n const widthInches = input.printedImage.unit === \"inches\" ? input.printedImage.width : width / 25.4;\n const heightInches = input.printedImage.unit === \"inches\" ? input.printedImage.height : height / 25.4;\n const required = input.sampling.kind === \"manual-ppi\"\n ? minimumRaster(native, widthInches * ppi, heightInches * ppi)\n : minimumRaster(native, width / pitch, height / pitch);\n if (required === null) return unsupported();\n plan.minimumSamplingRaster = required;\n const sufficient = required.pixelWidth <= native.pixelWidth && required.pixelHeight <= native.pixelHeight;\n plan.nativePixelSufficiency = sufficient ? \"sufficient\" : \"insufficient\";\n let maxScale = Math.min(native.pixelWidth * pitch / width, native.pixelHeight * pitch / height);\n let minimumDistance = pitchFactor === null ? null : Math.max(width / native.pixelWidth, height / native.pixelHeight) / pitchFactor;\n let maxWidth = width * maxScale, maxHeight = height * maxScale;\n if (!positiveSupported(maxWidth, maxHeight) || (minimumDistance !== null && !positiveSupported(minimumDistance))) return unsupported();\n const fits = (w: number, h: number, p: number): boolean => {\n const candidate = input.sampling.kind === \"manual-ppi\" ? minimumRaster(native, w / 25.4 * ppi, h / 25.4 * ppi) : minimumRaster(native, w / p, h / p);\n return candidate !== null && candidate.pixelWidth <= native.pixelWidth && candidate.pixelHeight <= native.pixelHeight;\n };\n // Return only alternatives that still pass their own documented integer rule.\n for (let i = 0; i < 8 && !fits(maxWidth, maxHeight, pitch); i++) {\n maxScale = adjacentPositive(maxScale, \"down\"); maxWidth = width * maxScale; maxHeight = height * maxScale;\n }\n if (!positiveSupported(maxWidth, maxHeight) || !fits(maxWidth, maxHeight, pitch)) return unsupported();\n if (minimumDistance !== null && pitchFactor !== null) {\n for (let i = 0; i < 8 && !fits(width, height, minimumDistance * pitchFactor); i++) minimumDistance = adjacentPositive(minimumDistance, \"up\");\n if (!positiveSupported(minimumDistance) || !fits(width, height, minimumDistance * pitchFactor)) return unsupported();\n }\n plan.alternatives = { maximumImageMmAtCurrentDistance: { width: maxWidth, height: maxHeight }, minimumViewingDistanceMmAtCurrentSize: minimumDistance };\n if (!sufficient) { plan.status = \"insufficient-native-pixels\"; plan.reasons.push(\"sampling-requires-native-upscale\"); }\n let file = required;\n const provider = input.provider;\n if (provider) {\n const density = Math.max(provider.minimumPpi ?? 0, provider.exactPpi ?? 0);\n const constrained = minimumRaster(native, Math.max(required.pixelWidth, provider.minimumRaster?.pixelWidth ?? 0, widthInches * density), Math.max(required.pixelHeight, provider.minimumRaster?.pixelHeight ?? 0, heightInches * density));\n if (constrained === null) return unsupported();\n file = provider.exactRaster ?? constrained;\n if (provider.exactPpi !== undefined) {\n const exact = { pixelWidth: widthInches * provider.exactPpi, pixelHeight: heightInches * provider.exactPpi };\n if (!Number.isSafeInteger(exact.pixelWidth) || !Number.isSafeInteger(exact.pixelHeight)) plan.reasons.push(\"exact-provider-density-has-no-integer-raster\");\n else if (provider.exactRaster && (exact.pixelWidth !== file.pixelWidth || exact.pixelHeight !== file.pixelHeight)) plan.reasons.push(\"provider-exact-raster-density-conflict\");\n else file = exact;\n }\n if (!exactRatio(file, native)) plan.reasons.push(\"provider-raster-aspect-conflict\");\n if (file.pixelWidth < constrained.pixelWidth || file.pixelHeight < constrained.pixelHeight) plan.reasons.push(\"provider-exact-requirement-below-minimum\");\n if (file.pixelWidth > native.pixelWidth || file.pixelHeight > native.pixelHeight) plan.reasons.push(\"provider-requires-native-upscale\");\n if (plan.reasons.some(r => r.startsWith(\"provider-\") || r.startsWith(\"exact-provider-\"))) { plan.status = \"provider-conflict\"; return finish(); }\n }\n if (!sufficient) return finish();\n const achieved = diagnostics(file, width, height, distance);\n if (!positiveSupported(achieved.pixelsPerInch.x, achieved.pixelsPerInch.y, achieved.angularPixelPitchArcminutes.x, achieved.angularPixelPitchArcminutes.y)) return unsupported();\n plan.status = \"ready\"; plan.recommended = achieved;\n plan.resamplingScale = { x: file.pixelWidth / native.pixelWidth, y: file.pixelHeight / native.pixelHeight };\n return finish();\n}\n\n/** Input for the largest-print-size query. It keeps the reference plan's aspect ratio and criterion.\n * With proportional viewing, the reference viewing distance scales with the image size.\n */\nexport interface PrintSizeLimitInput {\n plan: PrintPlanInput;\n viewingDistancePolicy: \"fixed\" | \"proportional-to-image-size\";\n}\n/** Result of the size query. A status says \"unbounded\" rather than using Infinity. \"no-positive-size\" is a valid answer, not an input error. */\nexport interface PrintSizeLimit {\n engineApiVersion: string;\n status: \"finite\" | \"unbounded\" | \"no-positive-size\" | \"cannot-assess\" | \"unsupported\";\n maximumImageMm: { width: number; height: number } | null;\n reasons: string[];\n referencePlan: PrintPlan;\n}\n/** Calculates the largest print size under the stated conditions. Lab constraints need individual plans,\n * so this size query reports them as unsupported rather than ignoring them.\n * As in `calculatePrintPlan`, an angular criterion is an approximate rule of thumb: one arcminute per pixel is normal\n * (20/20) acuity under optimal conditions, which some viewers exceed and many do not (Z. Alomar, K. D. Bojikian, \"Visual Acuity Testing in Adults\", EyeWiki, American Academy of Ophthalmology,\n * https://eyewiki.aao.org/Visual_Acuity_Testing_in_Adults, last edited June 16, 2026).\n */\nexport function calculatePrintSizeLimit(value: PrintSizeLimitInput): CalculationResult<PrintSizeLimit> {\n const r = record(value, [\"plan\", \"viewingDistancePolicy\"]);\n if (r.viewingDistancePolicy !== \"fixed\" && r.viewingDistancePolicy !== \"proportional-to-image-size\") throw new InvalidConfigurationError(\"Invalid viewing-distance scaling policy.\");\n const referencePlan = calculatePrintPlan(parsePrintPlanInput(r.plan)).value;\n const result: PrintSizeLimit = { engineApiVersion: ENGINE_API_VERSION, status: \"cannot-assess\", maximumImageMm: null, reasons: [...referencePlan.reasons], referencePlan };\n if (referencePlan.input.provider !== undefined && Object.keys(referencePlan.input.provider).length > 0) {\n result.status = \"unsupported\"; result.reasons.push(\"size-query-with-provider-constraints-unsupported\");\n } else if (referencePlan.status === \"unsupported\") result.status = \"unsupported\";\n else if (referencePlan.alternatives !== null) {\n if (r.viewingDistancePolicy === \"proportional-to-image-size\" && referencePlan.input.sampling.kind !== \"manual-ppi\") {\n result.status = referencePlan.nativePixelSufficiency === \"sufficient\" ? \"unbounded\" : \"no-positive-size\";\n } else { result.status = \"finite\"; result.maximumImageMm = referencePlan.alternatives.maximumImageMmAtCurrentDistance; }\n }\n return calculatedResult(result, \"conditional-native-print-size-limit\", \"0.1.0\", [\"Native sampling capacity only; no perceived-quality or physical-printer bound.\", \"Proportional distance and image scaling preserve angular pixel pitch.\"]);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"print-plan.js","sourceRoot":"","sources":["../../src/output/print-plan.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/** Print planning: how many native pixels a print of a given size and viewing distance needs.\n * It uses native capture pixels only. It does not make an image or certify perceived quality.\n * @see docs/PRINT_PLANNING.md for equations, rounding and support limits.\n */\nimport { calculatedResult, approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireAllowlistedRecord, requirePublicOpaqueId } from \"../core/record-validation.js\";\nimport { requirePositiveFinite, requirePositiveInteger } from \"../core/validation.js\";\nimport { ENGINE_API_VERSION } from \"../core/version.js\";\nimport { resolveCaptureGeometry, type ResolveCaptureGeometryInput } from \"./capture-geometry.js\";\nimport type { RasterDimensions } from \"../sensor/sensor-geometry.js\";\n\n/** A physical length. Not to be confused with image pixels or printer dots. */\nexport interface PrintLength { value: number; unit: \"mm\" | \"cm\" | \"inches\" | \"m\" }\n/** The flat printed image area. It excludes paper margins, borders and canvas wraps.\n * With a `contain` fit it is instead the paper, or the printable area of it, that the image is fitted inside.\n */\nexport interface PrintedImageSize { width: number; height: number; unit: PrintLength[\"unit\"] }\n/** How to choose pixel density: one of three angular criteria at the viewing distance, or a manual PPI (pixels per inch), which gives a different kind of plan. */\nexport type PrintSamplingCriterion =\n | { kind: \"angular-pixel-pitch\"; maximumArcminutesPerPixel: number }\n | { kind: \"angular-stroke\"; strokeWidthArcminutes: number; samplesPerStroke: number }\n | { kind: \"angular-line-pair\"; periodArcminutes: number; samplesPerPeriod: number }\n | { kind: \"manual-ppi\"; pixelsPerInch: number };\n/** The declared native source geometry. Not a preview or a resampled pixel count. */\nexport type PrintNativeSource =\n | { kind: \"native-retained\"; captureId: string; geometry: ResolveCaptureGeometryInput }\n | { kind: \"unavailable\"; reason: \"missing-native-raster\" | \"unverified-native-identity\" };\n/** Print lab requirements supplied by the caller. There is no vendor catalogue and no guarantee of what a lab will accept. */\nexport interface PrintProviderConstraints {\n minimumRaster?: RasterDimensions;\n exactRaster?: RasterDimensions;\n minimumPpi?: number;\n exactPpi?: number;\n}\n/** How the printed image relates to `printedImage`.\n * - `confirmed-native-aspect`: `printedImage` is the printed image itself and must already have the native aspect ratio,\n * within the stated relative error.\n * - `contain`: `printedImage` is the paper. The image keeps the native aspect ratio and is scaled to the largest size that\n * fits inside it, centered; the rest is unprinted border. `match-image` turns the paper a quarter turn when its\n * orientation (portrait or landscape) differs from the image's; `as-given` keeps it. Layout only: nothing is cropped or resampled.\n */\nexport type PrintFit =\n | { kind: \"confirmed-native-aspect\"; maximumRelativeAspectError: number }\n | { kind: \"contain\"; paperOrientation: \"as-given\" | \"match-image\" };\n/** The layout of a `contain` fit, in millimetres. The printed image itself is the plan's `printedImageMm`. */\nexport interface PrintContainLayout {\n /** The paper as placed: after the quarter turn when `paperRotated`. */\n paperMm: { width: number; height: number };\n paperRotated: boolean;\n /** Unprinted border on each side of the centered image; zero when the paper has the image's aspect ratio. */\n bordersMm: { left: number; right: number; top: number; bottom: number };\n}\n/** Plan input. The output keeps the exact native aspect ratio; the allowed physical aspect error is explicit and reported. */\nexport interface PrintPlanInput {\n source: PrintNativeSource;\n printedImage: PrintedImageSize;\n viewingDistance: PrintLength;\n sampling: PrintSamplingCriterion;\n fit: PrintFit;\n provider?: PrintProviderConstraints;\n}\n/** Pixel density and angular pixel pitch for each axis. The angles are pure geometry, not a measure of visual acuity. */\nexport interface PrintRasterSampling {\n raster: RasterDimensions;\n pixelsPerInch: { x: number; y: number };\n angularPixelPitchArcminutes: { x: number; y: number };\n}\n/** A plan, valid only under its stated conditions. A null recommendation never means it is fine to upscale. */\nexport interface PrintPlan {\n engineApiVersion: string;\n input: PrintPlanInput;\n status: \"ready\" | \"insufficient-native-pixels\" | \"provider-conflict\" | \"crop-confirmation-required\" | \"cannot-assess\" | \"unsupported\";\n nativePixelSufficiency: \"sufficient\" | \"insufficient\" | \"cannot-assess\";\n reasons: string[];\n printedImageMm: { width: number; height: number } | null;\n viewingDistanceMm: number | null;\n guidance: CalculationResult<{ derivedPixelsPerInch: number; maximumPixelPitchMm: number; maximumArcminutesPerPixel: number | null }> | null;\n relativeAspectError: number | null;\n native: PrintRasterSampling | null;\n minimumSamplingRaster: RasterDimensions | null;\n recommended: PrintRasterSampling | null;\n resamplingScale: { x: number; y: number } | null;\n alternatives: { maximumImageMmAtCurrentDistance: { width: number; height: number }; minimumViewingDistanceMmAtCurrentSize: number | null } | null;\n capturedDetailAssessment: \"unassessed\";\n deliveryAssessment: \"unassessed\";\n /** Present only with a `contain` fit; null when the native raster is unknown. */\n contain?: PrintContainLayout | null;\n}\n\nconst UNIT_MM = { mm: 1, cm: 10, inches: 25.4, m: 1000 } as const;\nconst RAD_PER_ARCMIN = Math.PI / 10800;\nfunction record(value: unknown, keys: readonly string[]): Record<string, unknown> {\n return requireAllowlistedRecord(value, keys, \"Invalid or unknown Print planning fields.\");\n}\nfunction number(value: unknown, name: string): number {\n if (typeof value !== \"number\") throw new InvalidConfigurationError(`${name} must be numeric.`);\n requirePositiveFinite(name, value); return value;\n}\nfunction unit(value: unknown): PrintLength[\"unit\"] {\n if (typeof value !== \"string\" || !Object.hasOwn(UNIT_MM, value)) throw new InvalidConfigurationError(\"Invalid Print length unit.\");\n return value as PrintLength[\"unit\"];\n}\nfunction length(value: unknown): PrintLength {\n const r = record(value, [\"value\", \"unit\"]);\n return { value: number(r.value, \"length.value\"), unit: unit(r.unit) };\n}\nfunction raster(value: unknown): RasterDimensions {\n const r = record(value, [\"pixelWidth\", \"pixelHeight\"]);\n const pixelWidth = number(r.pixelWidth, \"pixelWidth\"), pixelHeight = number(r.pixelHeight, \"pixelHeight\");\n requirePositiveInteger(\"pixelWidth\", pixelWidth); requirePositiveInteger(\"pixelHeight\", pixelHeight);\n return { pixelWidth, pixelHeight };\n}\nfunction geometry(value: unknown): ResolveCaptureGeometryInput {\n const g = record(value, [\"imagingArea\", \"nativeRaster\", \"orientation\", \"activeCaptureRect\", \"outputCropRect\", \"outputRaster\"]);\n record(g.imagingArea, [\"widthMm\", \"heightMm\"]); raster(g.nativeRaster);\n for (const key of [\"activeCaptureRect\", \"outputCropRect\"] as const) {\n if (g[key] !== undefined) record(g[key], [\"x\", \"y\", \"width\", \"height\"]);\n }\n if (g.outputRaster !== undefined) raster(g.outputRaster);\n // The capture geometry module defines units, orientations, bounds and the optical axis.\n const parsed = g as unknown as ResolveCaptureGeometryInput;\n resolveCaptureGeometry(parsed);\n return structuredCloneGeometry(parsed);\n}\nfunction structuredCloneGeometry(g: ResolveCaptureGeometryInput): ResolveCaptureGeometryInput {\n return { imagingArea: { ...g.imagingArea }, nativeRaster: { ...g.nativeRaster }, orientation: g.orientation,\n ...(g.activeCaptureRect === undefined ? {} : { activeCaptureRect: { ...g.activeCaptureRect } }),\n ...(g.outputCropRect === undefined ? {} : { outputCropRect: { ...g.outputCropRect } }),\n ...(g.outputRaster === undefined ? {} : { outputRaster: { ...g.outputRaster } }) };\n}\nfunction sampling(value: unknown): PrintSamplingCriterion {\n const tag = record(value, [\"kind\", \"maximumArcminutesPerPixel\", \"strokeWidthArcminutes\", \"samplesPerStroke\", \"periodArcminutes\", \"samplesPerPeriod\", \"pixelsPerInch\"]);\n let result: PrintSamplingCriterion;\n switch (tag.kind) {\n case \"manual-ppi\": {\n const r = record(value, [\"kind\", \"pixelsPerInch\"]);\n return { kind: \"manual-ppi\", pixelsPerInch: number(r.pixelsPerInch, \"pixelsPerInch\") };\n }\n case \"angular-pixel-pitch\": {\n const r = record(value, [\"kind\", \"maximumArcminutesPerPixel\"]);\n result = { kind: \"angular-pixel-pitch\", maximumArcminutesPerPixel: number(r.maximumArcminutesPerPixel, \"maximumArcminutesPerPixel\") }; break;\n }\n case \"angular-stroke\": {\n const r = record(value, [\"kind\", \"strokeWidthArcminutes\", \"samplesPerStroke\"]);\n const samplesPerStroke = number(r.samplesPerStroke, \"samplesPerStroke\"); requirePositiveInteger(\"samplesPerStroke\", samplesPerStroke);\n result = { kind: \"angular-stroke\", strokeWidthArcminutes: number(r.strokeWidthArcminutes, \"strokeWidthArcminutes\"), samplesPerStroke }; break;\n }\n case \"angular-line-pair\": {\n const r = record(value, [\"kind\", \"periodArcminutes\", \"samplesPerPeriod\"]);\n const samplesPerPeriod = number(r.samplesPerPeriod, \"samplesPerPeriod\"); requirePositiveInteger(\"samplesPerPeriod\", samplesPerPeriod);\n if (samplesPerPeriod < 2) throw new InvalidConfigurationError(\"A line-pair period requires at least two samples.\");\n result = { kind: \"angular-line-pair\", periodArcminutes: number(r.periodArcminutes, \"periodArcminutes\"), samplesPerPeriod }; break;\n }\n default: throw new InvalidConfigurationError(\"Invalid Print sampling convention.\");\n }\n const angle = angularLimit(result);\n if (angle <= 0 || angle >= 10800) throw new InvalidConfigurationError(\"Effective angular pixel pitch must lie in (0, 10800) arcminutes.\");\n return result;\n}\nfunction angularLimit(s: Exclude<PrintSamplingCriterion, { kind: \"manual-ppi\" }>): number {\n switch (s.kind) {\n case \"angular-pixel-pitch\": return s.maximumArcminutesPerPixel;\n case \"angular-stroke\": return s.strokeWidthArcminutes / s.samplesPerStroke;\n case \"angular-line-pair\": return s.periodArcminutes / s.samplesPerPeriod;\n }\n}\n\n/** Validates and copies an untrusted print request. It never assumes the source is native if that is not declared. */\nexport function parsePrintPlanInput(value: unknown): PrintPlanInput {\n const r = record(value, [\"source\", \"printedImage\", \"viewingDistance\", \"sampling\", \"fit\", \"provider\"]);\n const source = record(r.source, [\"kind\", \"captureId\", \"geometry\", \"reason\"]);\n let parsedSource: PrintNativeSource;\n if (source.kind === \"native-retained\") {\n record(r.source, [\"kind\", \"captureId\", \"geometry\"]);\n parsedSource = { kind: \"native-retained\", captureId: requirePublicOpaqueId(source.captureId, \"Invalid public capture ID.\"), geometry: geometry(source.geometry) };\n } else if (source.kind === \"unavailable\") {\n record(r.source, [\"kind\", \"reason\"]);\n if (source.reason !== \"missing-native-raster\" && source.reason !== \"unverified-native-identity\") throw new InvalidConfigurationError(\"Invalid native-unavailable reason.\");\n parsedSource = { kind: \"unavailable\", reason: source.reason };\n } else throw new InvalidConfigurationError(\"Invalid native source kind.\");\n const image = record(r.printedImage, [\"width\", \"height\", \"unit\"]);\n const parsed: PrintPlanInput = { source: parsedSource, printedImage: { width: number(image.width, \"printedImage.width\"), height: number(image.height, \"printedImage.height\"), unit: unit(image.unit) }, viewingDistance: length(r.viewingDistance), sampling: sampling(r.sampling), fit: parseFit(r.fit) };\n if (r.provider !== undefined) {\n const p = record(r.provider, [\"minimumRaster\", \"exactRaster\", \"minimumPpi\", \"exactPpi\"]);\n parsed.provider = {\n ...(p.minimumRaster === undefined ? {} : { minimumRaster: raster(p.minimumRaster) }),\n ...(p.exactRaster === undefined ? {} : { exactRaster: raster(p.exactRaster) }),\n ...(p.minimumPpi === undefined ? {} : { minimumPpi: number(p.minimumPpi, \"minimumPpi\") }),\n ...(p.exactPpi === undefined ? {} : { exactPpi: number(p.exactPpi, \"exactPpi\") })\n };\n }\n return parsed;\n}\nfunction parseFit(value: unknown): PrintFit {\n const fit = record(value, [\"kind\", \"maximumRelativeAspectError\", \"paperOrientation\"]);\n if (fit.kind === \"contain\") {\n record(value, [\"kind\", \"paperOrientation\"]);\n if (fit.paperOrientation !== \"as-given\" && fit.paperOrientation !== \"match-image\") throw new InvalidConfigurationError(\"Declare a contain-fit paper orientation: as-given or match-image.\");\n return { kind: \"contain\", paperOrientation: fit.paperOrientation };\n }\n if (fit.kind !== \"confirmed-native-aspect\" || fit.paperOrientation !== undefined || typeof fit.maximumRelativeAspectError !== \"number\" || !Number.isFinite(fit.maximumRelativeAspectError) || fit.maximumRelativeAspectError < 0 || fit.maximumRelativeAspectError > 0.01) throw new InvalidConfigurationError(\"Declare a native-aspect fit tolerance in [0, 0.01].\");\n return { kind: \"confirmed-native-aspect\", maximumRelativeAspectError: fit.maximumRelativeAspectError };\n}\n/** The largest image at the native aspect ratio inside the paper, in the paper's unit, and its centered layout.\n * The binding axis equals the paper exactly; the other is clamped so rounding never makes a negative border.\n */\nfunction containImage(native: RasterDimensions, paperWidth: number, paperHeight: number, orientation: \"as-given\" | \"match-image\"): { width: number; height: number; paperWidth: number; paperHeight: number; paperRotated: boolean } {\n const imageLandscape = native.pixelWidth > native.pixelHeight, imagePortrait = native.pixelWidth < native.pixelHeight;\n const paperRotated = orientation === \"match-image\" && ((imageLandscape && paperWidth < paperHeight) || (imagePortrait && paperWidth > paperHeight));\n const pw = paperRotated ? paperHeight : paperWidth, ph = paperRotated ? paperWidth : paperHeight;\n const widthBinds = native.pixelWidth * ph >= native.pixelHeight * pw;\n const width = widthBinds ? pw : Math.min(pw, ph * native.pixelWidth / native.pixelHeight);\n const height = widthBinds ? Math.min(ph, pw * native.pixelHeight / native.pixelWidth) : ph;\n return { width, height, paperWidth: pw, paperHeight: ph, paperRotated };\n}\nfunction gcd(a: number, b: number): number { while (b !== 0) { const next = a % b; a = b; b = next; } return a; }\nfunction positiveSupported(...values: number[]): boolean { return values.every(v => Number.isFinite(v) && v > 0); }\n// Stepping to the adjacent double rounds outward. It is not a tolerance on the criterion.\nfunction adjacentPositive(value: number, direction: \"up\" | \"down\"): number {\n const view = new DataView(new ArrayBuffer(8)); view.setFloat64(0, value);\n view.setBigUint64(0, view.getBigUint64(0) + (direction === \"up\" ? 1n : -1n));\n return view.getFloat64(0);\n}\nfunction exactRatio(a: RasterDimensions, b: RasterDimensions): boolean {\n return BigInt(a.pixelWidth) * BigInt(b.pixelHeight) === BigInt(a.pixelHeight) * BigInt(b.pixelWidth);\n}\nfunction minimumRaster(native: RasterDimensions, width: number, height: number): RasterDimensions | null {\n const divisor = gcd(native.pixelWidth, native.pixelHeight);\n const x = native.pixelWidth / divisor, y = native.pixelHeight / divisor;\n const count = Math.max(1, Math.ceil(width / x), Math.ceil(height / y));\n const pixelWidth = count * x, pixelHeight = count * y;\n return Number.isSafeInteger(pixelWidth) && Number.isSafeInteger(pixelHeight) ? { pixelWidth, pixelHeight } : null;\n}\nfunction diagnostics(raster: RasterDimensions, width: number, height: number, distance: number): PrintRasterSampling {\n const px = width / raster.pixelWidth, py = height / raster.pixelHeight;\n return { raster, pixelsPerInch: { x: 25.4 / px, y: 25.4 / py }, angularPixelPitchArcminutes: { x: 2 * Math.atan((px / distance) / 2) / RAD_PER_ARCMIN, y: 2 * Math.atan((py / distance) / 2) / RAD_PER_ARCMIN } };\n}\n\n/** Finds the smallest file, at the exact native aspect ratio, that meets the declared sampling and lab constraints.\n * The angular guidance is an approximate rule of thumb; the geometry itself is calculated exactly. A common criterion,\n * one arcminute per pixel, is normal (20/20) visual acuity under optimal conditions: the minimum angle of resolution of an\n * ideal eye is about 1 minute of arc, and 20/20 letters are built from elements that each subtend 1 arcmin (Z. Alomar, K. D. Bojikian, \"Visual Acuity Testing in Adults\", EyeWiki, American Academy of Ophthalmology,\n * https://eyewiki.aao.org/Visual_Acuity_Testing_in_Adults, last edited June 16, 2026).\n * Some viewers resolve finer detail and many resolve coarser, so it stays a rule of thumb, and the criterion is the caller's.\n * It does not assess source detail, perceived quality, file writing or available resources.\n */\nexport function calculatePrintPlan(value: PrintPlanInput): CalculationResult<PrintPlan> {\n const input = parsePrintPlanInput(value);\n const plan: PrintPlan = { engineApiVersion: ENGINE_API_VERSION, input, status: \"cannot-assess\", nativePixelSufficiency: \"cannot-assess\", reasons: [], printedImageMm: null, viewingDistanceMm: null, guidance: null, relativeAspectError: null, native: null, minimumSamplingRaster: null, recommended: null, resamplingScale: null, alternatives: null, capturedDetailAssessment: \"unassessed\", deliveryAssessment: \"unassessed\" };\n const contain = input.fit.kind === \"contain\" ? input.fit : null;\n if (contain !== null) plan.contain = null;\n const finish = (): CalculationResult<PrintPlan> => calculatedResult(plan, \"native-only-print-plan\", \"0.1.0\", [\"Declared native authority; flat image, normal viewing at image center.\", \"Output preserves the exact retained native aspect ratio; no upscale.\", \"Angular sampling is conditional guidance, not captured-detail or print-quality certification.\", ...(contain === null ? [] : [\"Contain fit: the image is centered inside the paper; the borders are unprinted and the paper is not a print area.\"])]);\n const unsupported = (): CalculationResult<PrintPlan> => { plan.status = \"unsupported\"; plan.nativePixelSufficiency = \"cannot-assess\"; plan.reasons.push(\"unsupported-numeric-range\"); return finish(); };\n const unitMm = UNIT_MM[input.printedImage.unit], distance = input.viewingDistance.value * UNIT_MM[input.viewingDistance.unit];\n // A contain fit needs the native aspect ratio before the printed image size is known.\n const resolved = input.source.kind === \"native-retained\" ? resolveCaptureGeometry(input.source.geometry).value : null;\n const native = resolved === null ? null : { pixelWidth: resolved.output.cropRect.width, pixelHeight: resolved.output.cropRect.height };\n const fitted = contain === null || native === null ? null : containImage(native, input.printedImage.width, input.printedImage.height, contain.paperOrientation);\n const imageWidth = fitted?.width ?? input.printedImage.width, imageHeight = fitted?.height ?? input.printedImage.height;\n const width = imageWidth * unitMm, height = imageHeight * unitMm;\n if (!positiveSupported(width, height, distance)) return unsupported();\n if (contain === null || fitted !== null) plan.printedImageMm = { width, height };\n plan.viewingDistanceMm = distance;\n const angle = input.sampling.kind === \"manual-ppi\" ? null : angularLimit(input.sampling);\n const pitchFactor = angle === null ? null : 2 * Math.tan((angle * RAD_PER_ARCMIN) / 2);\n const pitch = input.sampling.kind === \"manual-ppi\" ? 25.4 / input.sampling.pixelsPerInch : distance * (pitchFactor ?? 0);\n const ppi = input.sampling.kind === \"manual-ppi\" ? input.sampling.pixelsPerInch : 25.4 / pitch;\n if (!positiveSupported(pitch, ppi, width / pitch, height / pitch)) return unsupported();\n const guidance = { derivedPixelsPerInch: ppi, maximumPixelPitchMm: pitch, maximumArcminutesPerPixel: angle };\n plan.guidance = angle === null ? calculatedResult(guidance, \"manual-print-density\", \"0.1.0\", [\"Explicit manual PPI plan; no angular criterion is certified.\"]) : approximationResult(guidance, \"declared-angular-print-sampling\", \"0.1.0\", [\"Exact centered pixel angle uses 2 atan(pitch / (2 distance)); no small-angle substitution.\", \"Declared angular sampling convention is an uncalibrated usage assumption.\"]);\n if (input.source.kind === \"unavailable\" || native === null) { if (input.source.kind === \"unavailable\") plan.reasons.push(input.source.reason); return finish(); }\n if (fitted !== null) {\n const paperWidth = fitted.paperWidth * unitMm, paperHeight = fitted.paperHeight * unitMm;\n // Borders are taken in the paper's unit, then converted, so whole-unit borders stay exact.\n const side = (paper: number, image: number): number => Math.max(0, (paper - image) / 2) * unitMm;\n const x = side(fitted.paperWidth, imageWidth), y = side(fitted.paperHeight, imageHeight);\n if (!positiveSupported(paperWidth, paperHeight) || !Number.isFinite(x) || !Number.isFinite(y)) return unsupported();\n plan.contain = { paperMm: { width: paperWidth, height: paperHeight }, paperRotated: fitted.paperRotated, bordersMm: { left: x, right: x, top: y, bottom: y } };\n }\n const aspectError = Math.abs((width / height) / (native.pixelWidth / native.pixelHeight) - 1);\n if (!Number.isFinite(aspectError)) return unsupported();\n plan.relativeAspectError = aspectError;\n const nativeDiagnostics = diagnostics(native, width, height, distance);\n if (!positiveSupported(nativeDiagnostics.pixelsPerInch.x, nativeDiagnostics.pixelsPerInch.y, nativeDiagnostics.angularPixelPitchArcminutes.x, nativeDiagnostics.angularPixelPitchArcminutes.y)) return unsupported();\n plan.native = nativeDiagnostics;\n // A contain fit has the native aspect ratio by construction; its rounding error is reported, not checked.\n if (input.fit.kind === \"confirmed-native-aspect\" && aspectError > input.fit.maximumRelativeAspectError) { plan.status = \"crop-confirmation-required\"; plan.reasons.push(\"printed-image-and-confirmed-crop-aspect-mismatch\"); return finish(); }\n const widthInches = input.printedImage.unit === \"inches\" ? imageWidth : width / 25.4;\n const heightInches = input.printedImage.unit === \"inches\" ? imageHeight : height / 25.4;\n const required = input.sampling.kind === \"manual-ppi\"\n ? minimumRaster(native, widthInches * ppi, heightInches * ppi)\n : minimumRaster(native, width / pitch, height / pitch);\n if (required === null) return unsupported();\n plan.minimumSamplingRaster = required;\n const sufficient = required.pixelWidth <= native.pixelWidth && required.pixelHeight <= native.pixelHeight;\n plan.nativePixelSufficiency = sufficient ? \"sufficient\" : \"insufficient\";\n let maxScale = Math.min(native.pixelWidth * pitch / width, native.pixelHeight * pitch / height);\n let minimumDistance = pitchFactor === null ? null : Math.max(width / native.pixelWidth, height / native.pixelHeight) / pitchFactor;\n let maxWidth = width * maxScale, maxHeight = height * maxScale;\n if (!positiveSupported(maxWidth, maxHeight) || (minimumDistance !== null && !positiveSupported(minimumDistance))) return unsupported();\n const fits = (w: number, h: number, p: number): boolean => {\n const candidate = input.sampling.kind === \"manual-ppi\" ? minimumRaster(native, w / 25.4 * ppi, h / 25.4 * ppi) : minimumRaster(native, w / p, h / p);\n return candidate !== null && candidate.pixelWidth <= native.pixelWidth && candidate.pixelHeight <= native.pixelHeight;\n };\n // Return only alternatives that still pass their own documented integer rule.\n for (let i = 0; i < 8 && !fits(maxWidth, maxHeight, pitch); i++) {\n maxScale = adjacentPositive(maxScale, \"down\"); maxWidth = width * maxScale; maxHeight = height * maxScale;\n }\n if (!positiveSupported(maxWidth, maxHeight) || !fits(maxWidth, maxHeight, pitch)) return unsupported();\n if (minimumDistance !== null && pitchFactor !== null) {\n for (let i = 0; i < 8 && !fits(width, height, minimumDistance * pitchFactor); i++) minimumDistance = adjacentPositive(minimumDistance, \"up\");\n if (!positiveSupported(minimumDistance) || !fits(width, height, minimumDistance * pitchFactor)) return unsupported();\n }\n plan.alternatives = { maximumImageMmAtCurrentDistance: { width: maxWidth, height: maxHeight }, minimumViewingDistanceMmAtCurrentSize: minimumDistance };\n if (!sufficient) { plan.status = \"insufficient-native-pixels\"; plan.reasons.push(\"sampling-requires-native-upscale\"); }\n let file = required;\n const provider = input.provider;\n if (provider) {\n const density = Math.max(provider.minimumPpi ?? 0, provider.exactPpi ?? 0);\n const constrained = minimumRaster(native, Math.max(required.pixelWidth, provider.minimumRaster?.pixelWidth ?? 0, widthInches * density), Math.max(required.pixelHeight, provider.minimumRaster?.pixelHeight ?? 0, heightInches * density));\n if (constrained === null) return unsupported();\n file = provider.exactRaster ?? constrained;\n if (provider.exactPpi !== undefined) {\n const exact = { pixelWidth: widthInches * provider.exactPpi, pixelHeight: heightInches * provider.exactPpi };\n if (!Number.isSafeInteger(exact.pixelWidth) || !Number.isSafeInteger(exact.pixelHeight)) plan.reasons.push(\"exact-provider-density-has-no-integer-raster\");\n else if (provider.exactRaster && (exact.pixelWidth !== file.pixelWidth || exact.pixelHeight !== file.pixelHeight)) plan.reasons.push(\"provider-exact-raster-density-conflict\");\n else file = exact;\n }\n if (!exactRatio(file, native)) plan.reasons.push(\"provider-raster-aspect-conflict\");\n if (file.pixelWidth < constrained.pixelWidth || file.pixelHeight < constrained.pixelHeight) plan.reasons.push(\"provider-exact-requirement-below-minimum\");\n if (file.pixelWidth > native.pixelWidth || file.pixelHeight > native.pixelHeight) plan.reasons.push(\"provider-requires-native-upscale\");\n if (plan.reasons.some(r => r.startsWith(\"provider-\") || r.startsWith(\"exact-provider-\"))) { plan.status = \"provider-conflict\"; return finish(); }\n }\n if (!sufficient) return finish();\n const achieved = diagnostics(file, width, height, distance);\n if (!positiveSupported(achieved.pixelsPerInch.x, achieved.pixelsPerInch.y, achieved.angularPixelPitchArcminutes.x, achieved.angularPixelPitchArcminutes.y)) return unsupported();\n plan.status = \"ready\"; plan.recommended = achieved;\n plan.resamplingScale = { x: file.pixelWidth / native.pixelWidth, y: file.pixelHeight / native.pixelHeight };\n return finish();\n}\n\n/** Input for the largest-print-size query. It keeps the reference plan's aspect ratio and criterion.\n * With proportional viewing, the reference viewing distance scales with the image size.\n */\nexport interface PrintSizeLimitInput {\n plan: PrintPlanInput;\n viewingDistancePolicy: \"fixed\" | \"proportional-to-image-size\";\n}\n/** Result of the size query. A status says \"unbounded\" rather than using Infinity. \"no-positive-size\" is a valid answer, not an input error. */\nexport interface PrintSizeLimit {\n engineApiVersion: string;\n status: \"finite\" | \"unbounded\" | \"no-positive-size\" | \"cannot-assess\" | \"unsupported\";\n maximumImageMm: { width: number; height: number } | null;\n reasons: string[];\n referencePlan: PrintPlan;\n /** Present only with a `contain` fit: the paper that holds `maximumImageMm` with the same layout (borders scale with it). */\n maximumPaperMm?: { width: number; height: number } | null;\n}\n/** Calculates the largest print size under the stated conditions. Lab constraints need individual plans,\n * so this size query reports them as unsupported rather than ignoring them.\n * As in `calculatePrintPlan`, an angular criterion is an approximate rule of thumb: one arcminute per pixel is normal\n * (20/20) acuity under optimal conditions, which some viewers exceed and many do not (Z. Alomar, K. D. Bojikian, \"Visual Acuity Testing in Adults\", EyeWiki, American Academy of Ophthalmology,\n * https://eyewiki.aao.org/Visual_Acuity_Testing_in_Adults, last edited June 16, 2026).\n */\nexport function calculatePrintSizeLimit(value: PrintSizeLimitInput): CalculationResult<PrintSizeLimit> {\n const r = record(value, [\"plan\", \"viewingDistancePolicy\"]);\n if (r.viewingDistancePolicy !== \"fixed\" && r.viewingDistancePolicy !== \"proportional-to-image-size\") throw new InvalidConfigurationError(\"Invalid viewing-distance scaling policy.\");\n const referencePlan = calculatePrintPlan(parsePrintPlanInput(r.plan)).value;\n const result: PrintSizeLimit = { engineApiVersion: ENGINE_API_VERSION, status: \"cannot-assess\", maximumImageMm: null, reasons: [...referencePlan.reasons], referencePlan };\n if (referencePlan.input.provider !== undefined && Object.keys(referencePlan.input.provider).length > 0) {\n result.status = \"unsupported\"; result.reasons.push(\"size-query-with-provider-constraints-unsupported\");\n } else if (referencePlan.status === \"unsupported\") result.status = \"unsupported\";\n else if (referencePlan.alternatives !== null) {\n if (r.viewingDistancePolicy === \"proportional-to-image-size\" && referencePlan.input.sampling.kind !== \"manual-ppi\") {\n result.status = referencePlan.nativePixelSufficiency === \"sufficient\" ? \"unbounded\" : \"no-positive-size\";\n } else { result.status = \"finite\"; result.maximumImageMm = referencePlan.alternatives.maximumImageMmAtCurrentDistance; }\n }\n if (referencePlan.input.fit.kind === \"contain\") {\n const layout = referencePlan.contain, image = referencePlan.printedImageMm, maximum = result.maximumImageMm;\n const scale = layout && image && maximum ? maximum.width / image.width : null;\n result.maximumPaperMm = layout && scale !== null && positiveSupported(layout.paperMm.width * scale, layout.paperMm.height * scale) ? { width: layout.paperMm.width * scale, height: layout.paperMm.height * scale } : null;\n }\n return calculatedResult(result, \"conditional-native-print-size-limit\", \"0.1.0\", [\"Native sampling capacity only; no perceived-quality or physical-printer bound.\", \"Proportional distance and image scaling preserve angular pixel pitch.\"]);\n}\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Internal (#534): a seeded binomial draw, for splitting a realized charge count between an earlier and a later part of one
|
|
4
|
+
* exposure. Not exported from the package.
|
|
5
|
+
*
|
|
6
|
+
* - Mean n·min(p, 1 − p) below 10: inversion, the sequential search of the cumulative distribution from 0 with the recurrence
|
|
7
|
+
* f(x) = f(x − 1)·((n + 1)·s / x − s), s = p/(1 − p) (L. Devroye, Non-Uniform Random Variate Generation, Springer 1986, free
|
|
8
|
+
* from the author's site, chapter X, section 4 "The binomial distribution").
|
|
9
|
+
* - Otherwise: BTRS, transformed rejection with squeeze (W. Hörmann, "The generation of binomial random variates", Preprint Series
|
|
10
|
+
* of the Department of Applied Statistics and Data Processing, WU Vienna, no. 1, April 1992, free,
|
|
11
|
+
* doi:10.57938/79ec156b-9c3d-4b5d-a939-07a1846443fd), the binomial companion of the PTRS Poisson sampler `samplePoisson` uses.
|
|
12
|
+
* Its acceptance test compares log(f(k)/f(m)) at the mode m with Stirling's series for log x! (tail terms 1/12, −1/360, 1/1260
|
|
13
|
+
* in 1/(x + 1); exact sums below 10). The hat constants (b, a, c, v_r, α) are BTRS's as implemented in widely used libraries;
|
|
14
|
+
* they could not be checked against the preprint's text here, so the chi-square tests of the probability mass function in
|
|
15
|
+
* test/binomial-sample.test.ts are the evidence that the draws follow the binomial distribution.
|
|
16
|
+
* - p > 1/2 draws n − Binomial(n, 1 − p).
|
|
17
|
+
*
|
|
18
|
+
* Both methods are exact (they draw from the binomial distribution itself, not an approximation of it) up to floating-point
|
|
19
|
+
* rounding of the probabilities. The same n, p and stream reproduce the same draw. The tests check the probability mass
|
|
20
|
+
* function by a chi-square test and the moments.
|
|
21
|
+
*/
|
|
22
|
+
import { InvalidScientificInputError } from "../core/validation.js";
|
|
23
|
+
const HALF_LOG_TWO_PI = 0.5 * Math.log(2 * Math.PI);
|
|
24
|
+
/** log x! − ((x + ½)·log(x + 1) − (x + 1) + ½·log 2π) for x = 0 … 9, from the exact sums. */
|
|
25
|
+
const SMALL_TAILS = (() => {
|
|
26
|
+
const table = new Float64Array(10);
|
|
27
|
+
let logFactorial = 0;
|
|
28
|
+
for (let x = 0; x < 10; x++) {
|
|
29
|
+
if (x > 1)
|
|
30
|
+
logFactorial += Math.log(x);
|
|
31
|
+
table[x] = logFactorial - ((x + 0.5) * Math.log(x + 1) - (x + 1) + HALF_LOG_TWO_PI);
|
|
32
|
+
}
|
|
33
|
+
return table;
|
|
34
|
+
})();
|
|
35
|
+
function stirlingTail(x) {
|
|
36
|
+
if (x < 10)
|
|
37
|
+
return SMALL_TAILS[x];
|
|
38
|
+
const z = x + 1, z2 = z * z;
|
|
39
|
+
return (1 / 12 - (1 / 360 - 1 / 1260 / z2) / z2) / z;
|
|
40
|
+
}
|
|
41
|
+
/** Draws Binomial(n, p): n a non-negative safe integer, 0 ≤ p ≤ 1. */
|
|
42
|
+
export function sampleBinomial(n, p, random) {
|
|
43
|
+
if (!Number.isSafeInteger(n) || n < 0 || !(p >= 0 && p <= 1))
|
|
44
|
+
throw new InvalidScientificInputError("Binomial draws need a non-negative integer count and a probability in [0, 1].");
|
|
45
|
+
if (n === 0 || p === 0)
|
|
46
|
+
return 0;
|
|
47
|
+
if (p === 1)
|
|
48
|
+
return n;
|
|
49
|
+
if (p > 0.5)
|
|
50
|
+
return n - sampleBinomial(n, 1 - p, random);
|
|
51
|
+
const q = 1 - p;
|
|
52
|
+
if (n * p < 10) {
|
|
53
|
+
const s = p / q, a = (n + 1) * s, start = Math.exp(n * Math.log(q));
|
|
54
|
+
for (;;) {
|
|
55
|
+
let u = random.next(), f = start, x = 0;
|
|
56
|
+
// Rounding can leave a remainder above the last probability: draw again (the remainder is of the order of 1e-16).
|
|
57
|
+
while (u > f && x < n) {
|
|
58
|
+
u -= f;
|
|
59
|
+
x++;
|
|
60
|
+
f *= a / x - s;
|
|
61
|
+
}
|
|
62
|
+
if (u <= f)
|
|
63
|
+
return x;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const spq = Math.sqrt(n * p * q), b = 1.15 + 2.53 * spq, a = -0.0873 + 0.0248 * b + 0.01 * p, c = n * p + 0.5, vr = 0.92 - 4.2 / b;
|
|
67
|
+
const r = p / q, alpha = (2.83 + 5.1 / b) * spq, m = Math.floor((n + 1) * p);
|
|
68
|
+
const modeTerm = (m + 0.5) * Math.log((m + 1) / (r * (n - m + 1))) + stirlingTail(m) + stirlingTail(n - m);
|
|
69
|
+
for (;;) {
|
|
70
|
+
const u = random.next() - 0.5, us = 0.5 - Math.abs(u);
|
|
71
|
+
let v = random.next();
|
|
72
|
+
if (us <= 0)
|
|
73
|
+
continue;
|
|
74
|
+
const k = Math.floor((2 * a / us + b) * u + c);
|
|
75
|
+
if (k < 0 || k > n)
|
|
76
|
+
continue;
|
|
77
|
+
if (us >= 0.07 && v <= vr)
|
|
78
|
+
return k;
|
|
79
|
+
v = Math.log(v * alpha / (a / (us * us) + b));
|
|
80
|
+
// log(f(k)/f(m)) by Stirling's series, in a form without the cancellation of large log-factorials.
|
|
81
|
+
const bound = modeTerm + (n + 1) * Math.log((n - m + 1) / (n - k + 1)) + (k + 0.5) * Math.log(r * (n - k + 1) / (k + 1)) - stirlingTail(k) - stirlingTail(n - k);
|
|
82
|
+
if (v <= bound)
|
|
83
|
+
return k;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=binomial-sample.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binomial-sample.js","sourceRoot":"","sources":["../../src/sensor/binomial-sample.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Internal (#534): a seeded binomial draw, for splitting a realized charge count between an earlier and a later part of one\n * exposure. Not exported from the package.\n *\n * - Mean n·min(p, 1 − p) below 10: inversion, the sequential search of the cumulative distribution from 0 with the recurrence\n * f(x) = f(x − 1)·((n + 1)·s / x − s), s = p/(1 − p) (L. Devroye, Non-Uniform Random Variate Generation, Springer 1986, free\n * from the author's site, chapter X, section 4 \"The binomial distribution\").\n * - Otherwise: BTRS, transformed rejection with squeeze (W. Hörmann, \"The generation of binomial random variates\", Preprint Series\n * of the Department of Applied Statistics and Data Processing, WU Vienna, no. 1, April 1992, free,\n * doi:10.57938/79ec156b-9c3d-4b5d-a939-07a1846443fd), the binomial companion of the PTRS Poisson sampler `samplePoisson` uses.\n * Its acceptance test compares log(f(k)/f(m)) at the mode m with Stirling's series for log x! (tail terms 1/12, −1/360, 1/1260\n * in 1/(x + 1); exact sums below 10). The hat constants (b, a, c, v_r, α) are BTRS's as implemented in widely used libraries;\n * they could not be checked against the preprint's text here, so the chi-square tests of the probability mass function in\n * test/binomial-sample.test.ts are the evidence that the draws follow the binomial distribution.\n * - p > 1/2 draws n − Binomial(n, 1 − p).\n *\n * Both methods are exact (they draw from the binomial distribution itself, not an approximation of it) up to floating-point\n * rounding of the probabilities. The same n, p and stream reproduce the same draw. The tests check the probability mass\n * function by a chi-square test and the moments.\n */\n\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type { DeterministicUniformStream } from \"./stochastic-seed.js\";\n\nconst HALF_LOG_TWO_PI = 0.5 * Math.log(2 * Math.PI);\n/** log x! − ((x + ½)·log(x + 1) − (x + 1) + ½·log 2π) for x = 0 … 9, from the exact sums. */\nconst SMALL_TAILS = ((): Float64Array => {\n const table = new Float64Array(10);\n let logFactorial = 0;\n for (let x = 0; x < 10; x++) {\n if (x > 1) logFactorial += Math.log(x);\n table[x] = logFactorial - ((x + 0.5) * Math.log(x + 1) - (x + 1) + HALF_LOG_TWO_PI);\n }\n return table;\n})();\nfunction stirlingTail(x: number): number {\n if (x < 10) return SMALL_TAILS[x]!;\n const z = x + 1, z2 = z * z;\n return (1 / 12 - (1 / 360 - 1 / 1260 / z2) / z2) / z;\n}\n\n/** Draws Binomial(n, p): n a non-negative safe integer, 0 ≤ p ≤ 1. */\nexport function sampleBinomial(n: number, p: number, random: DeterministicUniformStream): number {\n if (!Number.isSafeInteger(n) || n < 0 || !(p >= 0 && p <= 1)) throw new InvalidScientificInputError(\"Binomial draws need a non-negative integer count and a probability in [0, 1].\");\n if (n === 0 || p === 0) return 0;\n if (p === 1) return n;\n if (p > 0.5) return n - sampleBinomial(n, 1 - p, random);\n const q = 1 - p;\n if (n * p < 10) {\n const s = p / q, a = (n + 1) * s, start = Math.exp(n * Math.log(q));\n for (;;) {\n let u = random.next(), f = start, x = 0;\n // Rounding can leave a remainder above the last probability: draw again (the remainder is of the order of 1e-16).\n while (u > f && x < n) { u -= f; x++; f *= a / x - s; }\n if (u <= f) return x;\n }\n }\n const spq = Math.sqrt(n * p * q), b = 1.15 + 2.53 * spq, a = -0.0873 + 0.0248 * b + 0.01 * p, c = n * p + 0.5, vr = 0.92 - 4.2 / b;\n const r = p / q, alpha = (2.83 + 5.1 / b) * spq, m = Math.floor((n + 1) * p);\n const modeTerm = (m + 0.5) * Math.log((m + 1) / (r * (n - m + 1))) + stirlingTail(m) + stirlingTail(n - m);\n for (;;) {\n const u = random.next() - 0.5, us = 0.5 - Math.abs(u);\n let v = random.next();\n if (us <= 0) continue;\n const k = Math.floor((2 * a / us + b) * u + c);\n if (k < 0 || k > n) continue;\n if (us >= 0.07 && v <= vr) return k;\n v = Math.log(v * alpha / (a / (us * us) + b));\n // log(f(k)/f(m)) by Stirling's series, in a form without the cancellation of large log-factorials.\n const bound = modeTerm + (n + 1) * Math.log((n - m + 1) / (n - k + 1)) + (k + 0.5) * Math.log(r * (n - k + 1) / (k + 1)) - stirlingTail(k) - stirlingTail(n - k);\n if (v <= bound) return k;\n }\n}\n"]}
|
|
@@ -117,6 +117,16 @@ export interface SimulateCaptureSensorReport {
|
|
|
117
117
|
export type ParsedSensorCharacteristics = SimulateCaptureSensorCharacteristics & {
|
|
118
118
|
qeScale: number;
|
|
119
119
|
};
|
|
120
|
+
/**
|
|
121
|
+
* A field's check: [minimum, maximum] (a number), the values allowed, 0 (a nested record) or a predicate. #554 shares the checker
|
|
122
|
+
* with the sensor profile parser.
|
|
123
|
+
*/
|
|
124
|
+
export type FieldRange = readonly [number, number] | readonly unknown[] | 0 | ((value: unknown) => boolean);
|
|
125
|
+
/**
|
|
126
|
+
* Checks `keys` of `record` against `ranges` (by path below `root`; nested records by "a.b"). An undefined field is skipped, or
|
|
127
|
+
* with `required` refused, and with `required` every field `ranges` lists at that level is checked.
|
|
128
|
+
*/
|
|
129
|
+
export declare function checkFieldRanges(record: Record<string, unknown>, keys: readonly string[], prefix: string, ranges?: Readonly<Record<string, FieldRange>>, root?: string, required?: boolean): void;
|
|
120
130
|
/** Checks the characteristics of the request's `sensor` record (its other keys already checked); undefined when it sets none. */
|
|
121
131
|
export declare function parseSensorCharacteristics(sensor: Record<string, unknown>): ParsedSensorCharacteristics | undefined;
|
|
122
132
|
/** The body's dual-gain read noise: the base regime's, and the first high-conversion-gain regime's with its ISO (none: the low gain throughout). */
|
|
@@ -19,21 +19,32 @@ const RANGES = { peakQuantumEfficiency: [0.05, 0.9], fullWell: 0, "fullWell.elec
|
|
|
19
19
|
"fullWell.link": ["pixel-area", "none"], "fullWell.referencePixelPitchMicrometers": [0.5, 1000], readNoise: 0, "readNoise.lowGainElectrons": [0, 100],
|
|
20
20
|
"readNoise.highGainElectrons": [0, 100], "readNoise.highGainFromIso": [100, 1e7], readoutSeconds: [1e-6, 1], adcBitDepth: [12, 14, 16], fixedPatternNoise: [true, false] };
|
|
21
21
|
function fail(message) { throw new InvalidConfigurationError(message); }
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Checks `keys` of `record` against `ranges` (by path below `root`; nested records by "a.b"). An undefined field is skipped, or
|
|
24
|
+
* with `required` refused, and with `required` every field `ranges` lists at that level is checked.
|
|
25
|
+
*/
|
|
26
|
+
export function checkFieldRanges(record, keys, prefix, ranges = RANGES, root = "sensor.", required = false) {
|
|
27
|
+
const all = required ? [...new Set([...keys, ...Object.keys(ranges).filter((k) => k.startsWith(prefix) && !k.slice(prefix.length).includes(".")).map((k) => k.slice(prefix.length))])] : keys;
|
|
28
|
+
for (const key of all) {
|
|
29
|
+
const path = prefix + key, value = record[key], range = ranges[path];
|
|
30
|
+
if (value === undefined && !required)
|
|
26
31
|
continue;
|
|
27
32
|
if (range === undefined)
|
|
28
|
-
fail("Unknown
|
|
33
|
+
fail("Unknown " + (root + prefix).slice(0, -1) + " field: " + key + ".");
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
fail(root + path + " is required.");
|
|
29
36
|
if (range === 0) {
|
|
30
|
-
requireInputObject(value,
|
|
31
|
-
|
|
37
|
+
requireInputObject(value, root + path);
|
|
38
|
+
checkFieldRanges(value, Object.keys(value), path + ".", ranges, root, required);
|
|
39
|
+
}
|
|
40
|
+
else if (typeof range === "function") {
|
|
41
|
+
if (!range(value))
|
|
42
|
+
fail(root + path + " is invalid.");
|
|
32
43
|
}
|
|
33
44
|
else {
|
|
34
45
|
const choice = range.length > 2 || typeof range[0] !== "number", [minimum, maximum] = range;
|
|
35
46
|
if (choice ? !range.includes(value) : typeof value !== "number" || !(value >= minimum && value <= maximum)) {
|
|
36
|
-
fail(
|
|
47
|
+
fail(root + path + " must be " + (choice ? range.map(String).join(" or ") : "a number from " + minimum + " to " + maximum) + ".");
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
50
|
}
|
|
@@ -42,7 +53,7 @@ function check(record, keys, prefix) {
|
|
|
42
53
|
export function parseSensorCharacteristics(sensor) {
|
|
43
54
|
if (!SENSOR_CHARACTERISTIC_KEYS.some((key) => sensor[key] !== undefined))
|
|
44
55
|
return undefined;
|
|
45
|
-
|
|
56
|
+
checkFieldRanges(sensor, SENSOR_CHARACTERISTIC_KEYS, "");
|
|
46
57
|
const c = sensor, fullWell = c.fullWell;
|
|
47
58
|
if (fullWell && fullWell.electrons === undefined)
|
|
48
59
|
fail("sensor.fullWell.electrons is required.");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sensor-characteristics.js","sourceRoot":"","sources":["../../src/sensor/sensor-characteristics.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Adjustable sensor characteristics for `simulateCapture` (#555) and the capture's noise figure (#535): the request\n * options that replace a camera body's sensor values one at a time, the readout they give, and the calculated dynamic\n * range and mid-grey signal-to-noise ratio the result reports.\n *\n * Everything here works on the body's declared readout regimes, so it applies to a generic tier and to a caller's\n * preset record alike, and a later sensor profile (#554) can supply the same options as data.\n * @see docs/QUICK_START.md, \"Sensor characteristics (#555)\", for the options, the equations and their sources.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireInputObject } from \"../core/record-validation.js\";\nimport { calculateSiteEffectiveSaturationCode } from \"../capture/raw-white-level.js\";\nimport { parseSensorReadoutConversionProfile, type SensorReadoutConversionProfile, type SensorReadoutConversionRegime } from \"./raw-readout.js\";\nimport type { SensorFixedPatternNoiseProfile } from \"./fixed-pattern-noise.js\";\n\n/**\n * #555: sensor characteristics a capture can set one at a time (`simulateCapture({ sensor })`). Each is off by default: an\n * omitted field keeps the camera body's value, and a request with none of them is byte-identical to one before 2.20.0.\n * Every value set is listed in the result's `assumptions`.\n */\nexport interface SimulateCaptureSensorCharacteristics {\n /**\n * Peak external quantum efficiency of the green channel (its value at 555 nm), 0.05–0.9. Default 0.5, the facade's\n * idealized sensor. Every channel's EQE curve is multiplied by `peakQuantumEfficiency / 0.5`, so the curves keep their\n * shape (and the colour matrix is unchanged) and the collected charge scales in proportion.\n */\n peakQuantumEfficiency?: number;\n /**\n * Full-well capacity: the charge, e⁻, that reaches the RAW white level at base ISO. Default: the body's base-ISO saturation\n * at 6 µm, scaled by the pixel area (the `sensor.isoAreaNormalization` approximation). `link` `\"pixel-area\"` (default)\n * scales `electrons`, the full well at `referencePixelPitchMicrometers` (default 6), by (pitch / reference)²; `\"none\"`\n * uses `electrons` at any pitch. The result is 100 e⁻ to 1e10 e⁻.\n */\n fullWell?: { electrons: number; link?: \"pixel-area\" | \"none\"; referencePixelPitchMicrometers?: number };\n /**\n * Read noise, RMS e⁻ (0–100), of the low and the high conversion gain, and the dual-gain switch: the ISO setting from\n * which the high conversion gain is used (any ISO from 100; above the body's highest ISO, the low gain is used throughout).\n * An omitted field keeps the body's value. Needs readout regimes that declare their exposure index (the tiers do).\n */\n readNoise?: { lowGainElectrons?: number; highGainElectrons?: number; highGainFromIso?: number };\n /** Readout time, s (1e-6 to 1): the rolling shutter's top-to-bottom skew. Default: the body's (tiers 8, 15 and 30 ms). */\n readoutSeconds?: number;\n /**\n * ADC bit depth: 12, 14 or 16. Default: the body's (consumer 12, the others 14). The black level scales with the code range\n * (×2^Δbits), and the conversion gain follows so the same charge reaches the same fraction of the range.\n */\n adcBitDepth?: 12 | 14 | 16;\n /**\n * Opt-in (advanced): the body's synthetic fixed-pattern noise profile (PRNU, DSNU, hot pixels, row and column offsets; EMVA\n * 1288 Release 4.0 non-uniformities), the same pattern in every capture. Default false. Not in `simulateDisplayView`.\n */\n fixedPatternNoise?: boolean;\n}\n\n/** The option keys of {@link SimulateCaptureSensorCharacteristics}, in `sensor`. */\nexport const SENSOR_CHARACTERISTIC_KEYS = [\"peakQuantumEfficiency\", \"fullWell\", \"readNoise\", \"readoutSeconds\", \"adcBitDepth\", \"fixedPatternNoise\"] as const;\n\n/** One RMS noise component of the mid-grey figure, e⁻ (input-referred). */\nexport interface SimulateCaptureNoiseComponents {\n /** Photon shot noise √S (Poisson). */\n photonShot: number;\n /** Read noise of the capture's readout regime. */\n read: number;\n /** Dark-current shot noise √(D·t), D = 1 e⁻/s at 20 °C. */\n darkCurrent: number;\n /** ADC quantization, one code (and one more after a digital gain) as a uniform 1/12 code² variance, in e⁻. */\n quantization: number;\n /** With `sensor.fixedPatternNoise`: √((PRNU·S)² + (DSNU·D·t)² + row² + column²), hot pixels excluded; a spatial, not temporal, noise. */\n fixedPattern?: number;\n /** √ of the sum of the squares. */\n total: number;\n}\n\n/**\n * #555, #535: the capture's sensor characteristics as used, and the figures calculated from them (reported, not settable).\n * Model figures from the engine's own noise model, not measurements.\n */\nexport interface SimulateCaptureSensorReport {\n pixelPitchMicrometers: number;\n /** The green channel's peak EQE (at 555 nm). */\n peakQuantumEfficiency: number;\n /** Full-well capacity, e⁻: the base-ISO saturation charge (`sensor.capacityElectrons`). */\n fullWellElectrons: number;\n /** Read noise, RMS e⁻, of each conversion gain, and the ISO from which the high gain is used (null: one gain throughout). */\n readNoise: { lowGainElectrons: number; highGainElectrons: number; highGainFromIso: number | null };\n /** The body's rolling readout skew, s (null when it declares no rolling readout). */\n readoutSeconds: number | null;\n /** ADC bit depth of the capture's readout regime. */\n adcBitDepth: number;\n fixedPatternNoise: boolean;\n /**\n * Engineering dynamic range at the capture's ISO, stops: log₂(S_sat / σ_floor), with S_sat the charge at the RAW white\n * level (full well over the analog gain, limited by the charge capacity and the ADC range) and σ_floor the read noise and\n * ADC quantization added in quadrature (the temporal dark noise of EMVA 1288 Release 4.0 without dark current). An ideal\n * noiseless readout is limited by the ADC alone: log₂((white − black)·√12) for unit digital gain.\n */\n dynamicRangeStops: number;\n /** The same per ISO setting of the body, in its order. */\n dynamicRangeByIso: readonly { iso: number; stops: number }[];\n /**\n * #535: the expected signal-to-noise ratio of an 18 % Lambertian grey patch under the request's illuminant and illuminance\n * at the capture's exposure, ISO and sensor, on the green channel next to the optical axis (no vignetting, PSF or motion).\n * `snr` = S/σ_total and `snrDecibels` = 20·log₁₀(snr). `clipped`: S is at or above the white level, where the RAW clips and\n * the figure no longer describes the image. Computed only when the request resolves to exact (`fast: false`, the default for\n * DNG, JPEG and PNG exports; omitted for a fast request).\n */\n midGrey?: { reflectance: 0.18; channel: \"green\"; signalElectrons: number; noiseElectrons: SimulateCaptureNoiseComponents; snr: number; snrDecibels: number; clipped: boolean };\n}\n\n/** The request's characteristics, range-checked, with the EQE scale peakQuantumEfficiency / 0.5. */\nexport type ParsedSensorCharacteristics = SimulateCaptureSensorCharacteristics & { qeScale: number };\n\n/** Accepted ranges (design limits) and values of each characteristic, by path below `sensor`. */\nconst RANGES: Readonly<Record<string, readonly [number, number] | readonly unknown[] | 0>> = { peakQuantumEfficiency: [0.05, 0.9], fullWell: 0, \"fullWell.electrons\": [100, 1e10],\n \"fullWell.link\": [\"pixel-area\", \"none\"], \"fullWell.referencePixelPitchMicrometers\": [0.5, 1000], readNoise: 0, \"readNoise.lowGainElectrons\": [0, 100],\n \"readNoise.highGainElectrons\": [0, 100], \"readNoise.highGainFromIso\": [100, 1e7], readoutSeconds: [1e-6, 1], adcBitDepth: [12, 14, 16], fixedPatternNoise: [true, false] };\nfunction fail(message: string): never { throw new InvalidConfigurationError(message); }\nfunction check(record: Record<string, unknown>, keys: readonly string[], prefix: string): void {\n for (const key of keys) {\n const path = prefix + key, value = record[key], range = RANGES[path];\n if (value === undefined) continue;\n if (range === undefined) fail(\"Unknown sensor.\" + prefix.slice(0, -1) + \" field: \" + key + \".\");\n if (range === 0) {\n requireInputObject(value, \"sensor.\" + path);\n check(value as Record<string, unknown>, Object.keys(value as object), path + \".\");\n } else {\n const choice = range.length > 2 || typeof range[0] !== \"number\", [minimum, maximum] = range as readonly [number, number];\n if (choice ? !(range as readonly unknown[]).includes(value) : typeof value !== \"number\" || !(value >= minimum && value <= maximum)) {\n fail(\"sensor.\" + path + \" must be \" + (choice ? range.join(\" or \") : \"a number from \" + minimum + \" to \" + maximum) + \".\");\n }\n }\n }\n}\n\n/** Checks the characteristics of the request's `sensor` record (its other keys already checked); undefined when it sets none. */\nexport function parseSensorCharacteristics(sensor: Record<string, unknown>): ParsedSensorCharacteristics | undefined {\n if (!SENSOR_CHARACTERISTIC_KEYS.some((key) => sensor[key] !== undefined)) return undefined;\n check(sensor, SENSOR_CHARACTERISTIC_KEYS, \"\");\n const c = sensor as SimulateCaptureSensorCharacteristics, fullWell = c.fullWell;\n if (fullWell && fullWell.electrons === undefined) fail(\"sensor.fullWell.electrons is required.\");\n if (fullWell?.link === \"none\" && fullWell.referencePixelPitchMicrometers !== undefined) fail(\"sensor.fullWell.referencePixelPitchMicrometers applies only to link \\\"pixel-area\\\".\");\n return { ...c, qeScale: (c.peakQuantumEfficiency ?? 0.5) / 0.5 };\n}\n\nconst noiseOf = (regime: SensorReadoutConversionRegime): number => Math.hypot(...regime.readNoiseComponents.map((c) => c.rmsElectrons.value));\n/** The body's dual-gain read noise: the base regime's, and the first high-conversion-gain regime's with its ISO (none: the low gain throughout). */\nexport function declaredReadNoise(profile: SensorReadoutConversionProfile): SimulateCaptureSensorReport[\"readNoise\"] {\n const regimes = profile.regimes, high = regimes.find((r) => r.exposureIndexGain?.conversionGainMode === \"high-conversion-gain\"), low = noiseOf(regimes[0]!);\n return { lowGainElectrons: low, highGainElectrons: high ? noiseOf(high) : low, highGainFromIso: high ? high.exposureIndexGain!.exposureIndex : null };\n}\n\n/**\n * The readout profile with the characteristics applied: every regime's charge-domain transfer (system conversion gain,\n * e⁻ per code, and pre-ADC saturation, e⁻ equivalent) multiplied by `chargeScale`, so a full well `chargeScale` times the\n * body's reaches the same white code; with `adcBitDepth`, the black level ×2^Δbits (rounded), the saturation code 2^bits − 1\n * and the conversion gain over the new code span; with `readNoise`, one electronic read-noise component per regime, the high\n * conversion gain's from `highGainFromIso`. Codes per unit charge of the base regime otherwise, digital gain and the ISO\n * selection are unchanged. Re-checked by the readout parser. With nothing to change, the profile itself.\n */\nexport function characterizeReadout(profile: SensorReadoutConversionProfile, chargeScale: number, c?: ParsedSensorCharacteristics): SensorReadoutConversionProfile {\n const bits = c?.adcBitDepth, n = c?.readNoise, declared = n && declaredReadNoise(profile);\n if (chargeScale === 1 && !bits && !n) return profile;\n return parseSensorReadoutConversionProfile({ ...profile, regimes: profile.regimes.map((regime) => {\n const adc = regime.adc, rescale = bits !== undefined && bits !== adc.bitDepth;\n const black = rescale ? Math.round(adc.blackLevelCode * 2 ** (bits - adc.bitDepth)) : adc.blackLevelCode, saturation = rescale ? 2 ** bits - 1 : adc.digitalSaturationCode;\n const out = { ...regime, systemConversionGainElectronsPerCode: { ...regime.systemConversionGainElectronsPerCode, value: rescale ?\n regime.systemConversionGainElectronsPerCode.value * chargeScale * (adc.digitalSaturationCode - adc.blackLevelCode) / (saturation - black) : regime.systemConversionGainElectronsPerCode.value * chargeScale },\n preAdcSaturationElectronEquivalent: { ...regime.preAdcSaturationElectronEquivalent, value: regime.preAdcSaturationElectronEquivalent.value * chargeScale },\n ...(rescale ? { adc: { ...adc, bitDepth: bits, blackLevelCode: black, digitalSaturationCode: saturation } } : {}) };\n if (n) {\n const gain = regime.exposureIndexGain ?? fail(\"sensor.readNoise needs readout regimes that declare their exposure index.\");\n const from = n.highGainFromIso ?? declared!.highGainFromIso ?? Infinity, high = gain.exposureIndex >= from;\n const value = high ? n.highGainElectrons ?? declared!.highGainElectrons : n.lowGainElectrons ?? declared!.lowGainElectrons;\n out.readNoiseComponents = [{ componentId: \"electronic\", rmsElectrons: { value, evidence: regime.evidence }, evidence: regime.evidence }];\n out.exposureIndexGain = { ...gain, conversionGainMode: high ? \"high-conversion-gain\" : \"low-conversion-gain\" };\n }\n return out;\n }) });\n}\n\n/** Charge at the RAW white level and the read-plus-quantization floor of one regime, e⁻. */\nfunction regimeRange(profile: SensorReadoutConversionProfile, regime: SensorReadoutConversionRegime, capacityElectrons: number): { saturation: number; quantization: number; read: number } {\n const adc = regime.adc, gain = regime.systemConversionGainElectronsPerCode.value, digitalGain = adc.digitalGain ?? 1;\n const white = calculateSiteEffectiveSaturationCode({ physicalChargeCapacityElectrons: capacityElectrons, preAdcSaturationElectronEquivalent: regime.preAdcSaturationElectronEquivalent.value,\n systemConversionGainElectronsPerCode: gain, blackLevelCode: adc.blackLevelCode, digitalSaturationCode: adc.digitalSaturationCode, adcMaximumCode: 2 ** adc.bitDepth - 1, digitalGain }, profile, regime.regimeId);\n // Output codes per electron d/G; quantization 1/12 code² of the ADC times d², plus 1/12 of the rounding after a digital gain.\n const perCode = gain / digitalGain;\n return { saturation: (white - adc.blackLevelCode) * perCode, quantization: perCode * Math.sqrt(digitalGain * digitalGain / 12 + (digitalGain === 1 ? 0 : 1 / 12)), read: noiseOf(regime) };\n}\n\n/**\n * The report: the characteristics used, the dynamic range per ISO, and the mid-grey noise figure for `greyElectrons`, the\n * green channel's expected charge from an 18 % grey patch, after a `darkElectrons` dark charge.\n */\nexport function sensorReport(profile: SensorReadoutConversionProfile, regimeId: string, capacityElectrons: number, greyElectrons: number | undefined, darkElectrons: number,\n base: Pick<SimulateCaptureSensorReport, \"pixelPitchMicrometers\" | \"peakQuantumEfficiency\" | \"readoutSeconds\">, pattern?: SensorFixedPatternNoiseProfile): SimulateCaptureSensorReport {\n const stops = (r: SensorReadoutConversionRegime): number => { const q = regimeRange(profile, r, capacityElectrons); return Math.log2(q.saturation / Math.hypot(q.read, q.quantization)); };\n const regime = profile.regimes.find((r) => r.regimeId === regimeId)!, range = regimeRange(profile, regime, capacityElectrons);\n const report: SimulateCaptureSensorReport = { ...base, fullWellElectrons: capacityElectrons, readNoise: declaredReadNoise(profile), adcBitDepth: regime.adc.bitDepth, fixedPatternNoise: !!pattern,\n dynamicRangeStops: stops(regime), dynamicRangeByIso: profile.regimes.map((r) => ({ iso: r.exposureIndexGain?.exposureIndex ?? NaN, stops: stops(r) })).filter((e) => e.iso > 0) };\n if (greyElectrons === undefined) return report;\n const fixedPattern = pattern && Math.hypot(pattern.photoResponseNonUniformity.relativeStandardDeviation.value * greyElectrons,\n pattern.darkSignalNonUniformity.relativeStandardDeviation.value * darkElectrons, pattern.rowOffset.rmsElectrons.value, pattern.columnOffset.rmsElectrons.value);\n const noise = { photonShot: Math.sqrt(greyElectrons), read: range.read, darkCurrent: Math.sqrt(darkElectrons), quantization: range.quantization, ...(fixedPattern === undefined ? {} : { fixedPattern }), total: 0 };\n noise.total = Math.hypot(noise.photonShot, noise.read, noise.darkCurrent, noise.quantization, fixedPattern ?? 0);\n const snr = greyElectrons / noise.total;\n report.midGrey = { reflectance: 0.18, channel: \"green\", signalElectrons: greyElectrons, noiseElectrons: noise, snr, snrDecibels: 20 * Math.log10(snr || Number.MIN_VALUE), clipped: greyElectrons + darkElectrons >= range.saturation };\n return report;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sensor-characteristics.js","sourceRoot":"","sources":["../../src/sensor/sensor-characteristics.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Adjustable sensor characteristics for `simulateCapture` (#555) and the capture's noise figure (#535): the request\n * options that replace a camera body's sensor values one at a time, the readout they give, and the calculated dynamic\n * range and mid-grey signal-to-noise ratio the result reports.\n *\n * Everything here works on the body's declared readout regimes, so it applies to a generic tier and to a caller's\n * preset record alike, and a later sensor profile (#554) can supply the same options as data.\n * @see docs/QUICK_START.md, \"Sensor characteristics (#555)\", for the options, the equations and their sources.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requireInputObject } from \"../core/record-validation.js\";\nimport { calculateSiteEffectiveSaturationCode } from \"../capture/raw-white-level.js\";\nimport { parseSensorReadoutConversionProfile, type SensorReadoutConversionProfile, type SensorReadoutConversionRegime } from \"./raw-readout.js\";\nimport type { SensorFixedPatternNoiseProfile } from \"./fixed-pattern-noise.js\";\n\n/**\n * #555: sensor characteristics a capture can set one at a time (`simulateCapture({ sensor })`). Each is off by default: an\n * omitted field keeps the camera body's value, and a request with none of them is byte-identical to one before 2.20.0.\n * Every value set is listed in the result's `assumptions`.\n */\nexport interface SimulateCaptureSensorCharacteristics {\n /**\n * Peak external quantum efficiency of the green channel (its value at 555 nm), 0.05–0.9. Default 0.5, the facade's\n * idealized sensor. Every channel's EQE curve is multiplied by `peakQuantumEfficiency / 0.5`, so the curves keep their\n * shape (and the colour matrix is unchanged) and the collected charge scales in proportion.\n */\n peakQuantumEfficiency?: number;\n /**\n * Full-well capacity: the charge, e⁻, that reaches the RAW white level at base ISO. Default: the body's base-ISO saturation\n * at 6 µm, scaled by the pixel area (the `sensor.isoAreaNormalization` approximation). `link` `\"pixel-area\"` (default)\n * scales `electrons`, the full well at `referencePixelPitchMicrometers` (default 6), by (pitch / reference)²; `\"none\"`\n * uses `electrons` at any pitch. The result is 100 e⁻ to 1e10 e⁻.\n */\n fullWell?: { electrons: number; link?: \"pixel-area\" | \"none\"; referencePixelPitchMicrometers?: number };\n /**\n * Read noise, RMS e⁻ (0–100), of the low and the high conversion gain, and the dual-gain switch: the ISO setting from\n * which the high conversion gain is used (any ISO from 100; above the body's highest ISO, the low gain is used throughout).\n * An omitted field keeps the body's value. Needs readout regimes that declare their exposure index (the tiers do).\n */\n readNoise?: { lowGainElectrons?: number; highGainElectrons?: number; highGainFromIso?: number };\n /** Readout time, s (1e-6 to 1): the rolling shutter's top-to-bottom skew. Default: the body's (tiers 8, 15 and 30 ms). */\n readoutSeconds?: number;\n /**\n * ADC bit depth: 12, 14 or 16. Default: the body's (consumer 12, the others 14). The black level scales with the code range\n * (×2^Δbits), and the conversion gain follows so the same charge reaches the same fraction of the range.\n */\n adcBitDepth?: 12 | 14 | 16;\n /**\n * Opt-in (advanced): the body's synthetic fixed-pattern noise profile (PRNU, DSNU, hot pixels, row and column offsets; EMVA\n * 1288 Release 4.0 non-uniformities), the same pattern in every capture. Default false. Not in `simulateDisplayView`.\n */\n fixedPatternNoise?: boolean;\n}\n\n/** The option keys of {@link SimulateCaptureSensorCharacteristics}, in `sensor`. */\nexport const SENSOR_CHARACTERISTIC_KEYS = [\"peakQuantumEfficiency\", \"fullWell\", \"readNoise\", \"readoutSeconds\", \"adcBitDepth\", \"fixedPatternNoise\"] as const;\n\n/** One RMS noise component of the mid-grey figure, e⁻ (input-referred). */\nexport interface SimulateCaptureNoiseComponents {\n /** Photon shot noise √S (Poisson). */\n photonShot: number;\n /** Read noise of the capture's readout regime. */\n read: number;\n /** Dark-current shot noise √(D·t), D = 1 e⁻/s at 20 °C. */\n darkCurrent: number;\n /** ADC quantization, one code (and one more after a digital gain) as a uniform 1/12 code² variance, in e⁻. */\n quantization: number;\n /** With `sensor.fixedPatternNoise`: √((PRNU·S)² + (DSNU·D·t)² + row² + column²), hot pixels excluded; a spatial, not temporal, noise. */\n fixedPattern?: number;\n /** √ of the sum of the squares. */\n total: number;\n}\n\n/**\n * #555, #535: the capture's sensor characteristics as used, and the figures calculated from them (reported, not settable).\n * Model figures from the engine's own noise model, not measurements.\n */\nexport interface SimulateCaptureSensorReport {\n pixelPitchMicrometers: number;\n /** The green channel's peak EQE (at 555 nm). */\n peakQuantumEfficiency: number;\n /** Full-well capacity, e⁻: the base-ISO saturation charge (`sensor.capacityElectrons`). */\n fullWellElectrons: number;\n /** Read noise, RMS e⁻, of each conversion gain, and the ISO from which the high gain is used (null: one gain throughout). */\n readNoise: { lowGainElectrons: number; highGainElectrons: number; highGainFromIso: number | null };\n /** The body's rolling readout skew, s (null when it declares no rolling readout). */\n readoutSeconds: number | null;\n /** ADC bit depth of the capture's readout regime. */\n adcBitDepth: number;\n fixedPatternNoise: boolean;\n /**\n * Engineering dynamic range at the capture's ISO, stops: log₂(S_sat / σ_floor), with S_sat the charge at the RAW white\n * level (full well over the analog gain, limited by the charge capacity and the ADC range) and σ_floor the read noise and\n * ADC quantization added in quadrature (the temporal dark noise of EMVA 1288 Release 4.0 without dark current). An ideal\n * noiseless readout is limited by the ADC alone: log₂((white − black)·√12) for unit digital gain.\n */\n dynamicRangeStops: number;\n /** The same per ISO setting of the body, in its order. */\n dynamicRangeByIso: readonly { iso: number; stops: number }[];\n /**\n * #535: the expected signal-to-noise ratio of an 18 % Lambertian grey patch under the request's illuminant and illuminance\n * at the capture's exposure, ISO and sensor, on the green channel next to the optical axis (no vignetting, PSF or motion).\n * `snr` = S/σ_total and `snrDecibels` = 20·log₁₀(snr). `clipped`: S is at or above the white level, where the RAW clips and\n * the figure no longer describes the image. Computed only when the request resolves to exact (`fast: false`, the default for\n * DNG, JPEG and PNG exports; omitted for a fast request).\n */\n midGrey?: { reflectance: 0.18; channel: \"green\"; signalElectrons: number; noiseElectrons: SimulateCaptureNoiseComponents; snr: number; snrDecibels: number; clipped: boolean };\n}\n\n/** The request's characteristics, range-checked, with the EQE scale peakQuantumEfficiency / 0.5. */\nexport type ParsedSensorCharacteristics = SimulateCaptureSensorCharacteristics & { qeScale: number };\n\n/**\n * A field's check: [minimum, maximum] (a number), the values allowed, 0 (a nested record) or a predicate. #554 shares the checker\n * with the sensor profile parser.\n */\nexport type FieldRange = readonly [number, number] | readonly unknown[] | 0 | ((value: unknown) => boolean);\n/** Accepted ranges (design limits) and values of each characteristic, by path below `sensor`. */\nconst RANGES: Readonly<Record<string, FieldRange>> = { peakQuantumEfficiency: [0.05, 0.9], fullWell: 0, \"fullWell.electrons\": [100, 1e10],\n \"fullWell.link\": [\"pixel-area\", \"none\"], \"fullWell.referencePixelPitchMicrometers\": [0.5, 1000], readNoise: 0, \"readNoise.lowGainElectrons\": [0, 100],\n \"readNoise.highGainElectrons\": [0, 100], \"readNoise.highGainFromIso\": [100, 1e7], readoutSeconds: [1e-6, 1], adcBitDepth: [12, 14, 16], fixedPatternNoise: [true, false] };\nfunction fail(message: string): never { throw new InvalidConfigurationError(message); }\n/**\n * Checks `keys` of `record` against `ranges` (by path below `root`; nested records by \"a.b\"). An undefined field is skipped, or\n * with `required` refused, and with `required` every field `ranges` lists at that level is checked.\n */\nexport function checkFieldRanges(record: Record<string, unknown>, keys: readonly string[], prefix: string, ranges: Readonly<Record<string, FieldRange>> = RANGES, root = \"sensor.\", required = false): void {\n const all = required ? [...new Set([...keys, ...Object.keys(ranges).filter((k) => k.startsWith(prefix) && !k.slice(prefix.length).includes(\".\")).map((k) => k.slice(prefix.length))])] : keys;\n for (const key of all) {\n const path = prefix + key, value = record[key], range = ranges[path];\n if (value === undefined && !required) continue;\n if (range === undefined) fail(\"Unknown \" + (root + prefix).slice(0, -1) + \" field: \" + key + \".\");\n if (value === undefined) fail(root + path + \" is required.\");\n if (range === 0) {\n requireInputObject(value, root + path);\n checkFieldRanges(value as Record<string, unknown>, Object.keys(value as object), path + \".\", ranges, root, required);\n } else if (typeof range === \"function\") {\n if (!range(value)) fail(root + path + \" is invalid.\");\n } else {\n const choice = range.length > 2 || typeof range[0] !== \"number\", [minimum, maximum] = range as readonly [number, number];\n if (choice ? !(range as readonly unknown[]).includes(value) : typeof value !== \"number\" || !(value >= minimum && value <= maximum)) {\n fail(root + path + \" must be \" + (choice ? range.map(String).join(\" or \") : \"a number from \" + minimum + \" to \" + maximum) + \".\");\n }\n }\n }\n}\n\n/** Checks the characteristics of the request's `sensor` record (its other keys already checked); undefined when it sets none. */\nexport function parseSensorCharacteristics(sensor: Record<string, unknown>): ParsedSensorCharacteristics | undefined {\n if (!SENSOR_CHARACTERISTIC_KEYS.some((key) => sensor[key] !== undefined)) return undefined;\n checkFieldRanges(sensor, SENSOR_CHARACTERISTIC_KEYS, \"\");\n const c = sensor as SimulateCaptureSensorCharacteristics, fullWell = c.fullWell;\n if (fullWell && fullWell.electrons === undefined) fail(\"sensor.fullWell.electrons is required.\");\n if (fullWell?.link === \"none\" && fullWell.referencePixelPitchMicrometers !== undefined) fail(\"sensor.fullWell.referencePixelPitchMicrometers applies only to link \\\"pixel-area\\\".\");\n return { ...c, qeScale: (c.peakQuantumEfficiency ?? 0.5) / 0.5 };\n}\n\nconst noiseOf = (regime: SensorReadoutConversionRegime): number => Math.hypot(...regime.readNoiseComponents.map((c) => c.rmsElectrons.value));\n/** The body's dual-gain read noise: the base regime's, and the first high-conversion-gain regime's with its ISO (none: the low gain throughout). */\nexport function declaredReadNoise(profile: SensorReadoutConversionProfile): SimulateCaptureSensorReport[\"readNoise\"] {\n const regimes = profile.regimes, high = regimes.find((r) => r.exposureIndexGain?.conversionGainMode === \"high-conversion-gain\"), low = noiseOf(regimes[0]!);\n return { lowGainElectrons: low, highGainElectrons: high ? noiseOf(high) : low, highGainFromIso: high ? high.exposureIndexGain!.exposureIndex : null };\n}\n\n/**\n * The readout profile with the characteristics applied: every regime's charge-domain transfer (system conversion gain,\n * e⁻ per code, and pre-ADC saturation, e⁻ equivalent) multiplied by `chargeScale`, so a full well `chargeScale` times the\n * body's reaches the same white code; with `adcBitDepth`, the black level ×2^Δbits (rounded), the saturation code 2^bits − 1\n * and the conversion gain over the new code span; with `readNoise`, one electronic read-noise component per regime, the high\n * conversion gain's from `highGainFromIso`. Codes per unit charge of the base regime otherwise, digital gain and the ISO\n * selection are unchanged. Re-checked by the readout parser. With nothing to change, the profile itself.\n */\nexport function characterizeReadout(profile: SensorReadoutConversionProfile, chargeScale: number, c?: ParsedSensorCharacteristics): SensorReadoutConversionProfile {\n const bits = c?.adcBitDepth, n = c?.readNoise, declared = n && declaredReadNoise(profile);\n if (chargeScale === 1 && !bits && !n) return profile;\n return parseSensorReadoutConversionProfile({ ...profile, regimes: profile.regimes.map((regime) => {\n const adc = regime.adc, rescale = bits !== undefined && bits !== adc.bitDepth;\n const black = rescale ? Math.round(adc.blackLevelCode * 2 ** (bits - adc.bitDepth)) : adc.blackLevelCode, saturation = rescale ? 2 ** bits - 1 : adc.digitalSaturationCode;\n const out = { ...regime, systemConversionGainElectronsPerCode: { ...regime.systemConversionGainElectronsPerCode, value: rescale ?\n regime.systemConversionGainElectronsPerCode.value * chargeScale * (adc.digitalSaturationCode - adc.blackLevelCode) / (saturation - black) : regime.systemConversionGainElectronsPerCode.value * chargeScale },\n preAdcSaturationElectronEquivalent: { ...regime.preAdcSaturationElectronEquivalent, value: regime.preAdcSaturationElectronEquivalent.value * chargeScale },\n ...(rescale ? { adc: { ...adc, bitDepth: bits, blackLevelCode: black, digitalSaturationCode: saturation } } : {}) };\n if (n) {\n const gain = regime.exposureIndexGain ?? fail(\"sensor.readNoise needs readout regimes that declare their exposure index.\");\n const from = n.highGainFromIso ?? declared!.highGainFromIso ?? Infinity, high = gain.exposureIndex >= from;\n const value = high ? n.highGainElectrons ?? declared!.highGainElectrons : n.lowGainElectrons ?? declared!.lowGainElectrons;\n out.readNoiseComponents = [{ componentId: \"electronic\", rmsElectrons: { value, evidence: regime.evidence }, evidence: regime.evidence }];\n out.exposureIndexGain = { ...gain, conversionGainMode: high ? \"high-conversion-gain\" : \"low-conversion-gain\" };\n }\n return out;\n }) });\n}\n\n/** Charge at the RAW white level and the read-plus-quantization floor of one regime, e⁻. */\nfunction regimeRange(profile: SensorReadoutConversionProfile, regime: SensorReadoutConversionRegime, capacityElectrons: number): { saturation: number; quantization: number; read: number } {\n const adc = regime.adc, gain = regime.systemConversionGainElectronsPerCode.value, digitalGain = adc.digitalGain ?? 1;\n const white = calculateSiteEffectiveSaturationCode({ physicalChargeCapacityElectrons: capacityElectrons, preAdcSaturationElectronEquivalent: regime.preAdcSaturationElectronEquivalent.value,\n systemConversionGainElectronsPerCode: gain, blackLevelCode: adc.blackLevelCode, digitalSaturationCode: adc.digitalSaturationCode, adcMaximumCode: 2 ** adc.bitDepth - 1, digitalGain }, profile, regime.regimeId);\n // Output codes per electron d/G; quantization 1/12 code² of the ADC times d², plus 1/12 of the rounding after a digital gain.\n const perCode = gain / digitalGain;\n return { saturation: (white - adc.blackLevelCode) * perCode, quantization: perCode * Math.sqrt(digitalGain * digitalGain / 12 + (digitalGain === 1 ? 0 : 1 / 12)), read: noiseOf(regime) };\n}\n\n/**\n * The report: the characteristics used, the dynamic range per ISO, and the mid-grey noise figure for `greyElectrons`, the\n * green channel's expected charge from an 18 % grey patch, after a `darkElectrons` dark charge.\n */\nexport function sensorReport(profile: SensorReadoutConversionProfile, regimeId: string, capacityElectrons: number, greyElectrons: number | undefined, darkElectrons: number,\n base: Pick<SimulateCaptureSensorReport, \"pixelPitchMicrometers\" | \"peakQuantumEfficiency\" | \"readoutSeconds\">, pattern?: SensorFixedPatternNoiseProfile): SimulateCaptureSensorReport {\n const stops = (r: SensorReadoutConversionRegime): number => { const q = regimeRange(profile, r, capacityElectrons); return Math.log2(q.saturation / Math.hypot(q.read, q.quantization)); };\n const regime = profile.regimes.find((r) => r.regimeId === regimeId)!, range = regimeRange(profile, regime, capacityElectrons);\n const report: SimulateCaptureSensorReport = { ...base, fullWellElectrons: capacityElectrons, readNoise: declaredReadNoise(profile), adcBitDepth: regime.adc.bitDepth, fixedPatternNoise: !!pattern,\n dynamicRangeStops: stops(regime), dynamicRangeByIso: profile.regimes.map((r) => ({ iso: r.exposureIndexGain?.exposureIndex ?? NaN, stops: stops(r) })).filter((e) => e.iso > 0) };\n if (greyElectrons === undefined) return report;\n const fixedPattern = pattern && Math.hypot(pattern.photoResponseNonUniformity.relativeStandardDeviation.value * greyElectrons,\n pattern.darkSignalNonUniformity.relativeStandardDeviation.value * darkElectrons, pattern.rowOffset.rmsElectrons.value, pattern.columnOffset.rmsElectrons.value);\n const noise = { photonShot: Math.sqrt(greyElectrons), read: range.read, darkCurrent: Math.sqrt(darkElectrons), quantization: range.quantization, ...(fixedPattern === undefined ? {} : { fixedPattern }), total: 0 };\n noise.total = Math.hypot(noise.photonShot, noise.read, noise.darkCurrent, noise.quantization, fixedPattern ?? 0);\n const snr = greyElectrons / noise.total;\n report.midGrey = { reflectance: 0.18, channel: \"green\", signalElectrons: greyElectrons, noiseElectrons: noise, snr, snrDecibels: 20 * Math.log10(snr || Number.MIN_VALUE), clipped: greyElectrons + darkElectrons >= range.saturation };\n return report;\n}\n"]}
|
package/docs/AGENT_GUIDE.md
CHANGED
|
@@ -46,6 +46,7 @@ one-call capture, see the [Quick Start](QUICK_START.md). For every export, see t
|
|
|
46
46
|
| Sensor noise and signal-to-noise ratio (SNR) | [`calculateSignalToNoise`](api/sensor-signal-noise.md#calculatesignaltonoise) | Shot noise plus read noise, in electrons. |
|
|
47
47
|
| Colour temperature to a white point | [`calculateChromaticityFromCorrelatedColorTemperature`](api/color-illuminants.md#calculatechromaticityfromcorrelatedcolortemperature) | Kelvin and Duv (tint) to CIE 1931 xy. |
|
|
48
48
|
| White point to colour temperature | [`calculateCorrelatedColorTemperature`](api/color-illuminants.md#calculatecorrelatedcolortemperature) | CIE 1931 xy to kelvin and Duv. |
|
|
49
|
+
| Fluorescent or LED light with realistic color rendering | [`createRepresentativeLightSourceIlluminant`](api/facade-representative-light-sources.md#createrepresentativelightsourceilluminant) | Photivra's own representative spectra (not named products), passed as `illuminant`; same-CCT LEDs render colors differently. Use `sensor.spectralStepNanometers: 5`. |
|
|
49
50
|
| White balance from Kelvin and tint | [`createColorTemperatureWhiteBalanceIntent`](api/color-white-balance.md#createcolortemperaturewhitebalanceintent), then [`resolveColorTemperatureWhiteBalance`](api/color-white-balance.md#resolvecolortemperaturewhitebalance) | Needs an explicit camera colour matrix. In `simulateCapture()`, use `output.whiteBalance: { kelvin, tint }` instead. |
|
|
50
51
|
|
|
51
52
|
If a task is not in this table, search the [API reference](API_REFERENCE.md) or the
|