@photivra/engine 2.1.0 → 2.2.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/dist/capture/export-jpeg.js +7 -1
  6. package/dist/capture/export-jpeg.js.map +1 -1
  7. package/dist/capture/native-environment-lean.js +5 -2
  8. package/dist/capture/native-environment-lean.js.map +1 -1
  9. package/dist/capture/native-environment-raw.js +2 -2
  10. package/dist/capture/native-environment-raw.js.map +1 -1
  11. package/dist/capture/native-raw-development.js +1 -1
  12. package/dist/capture/native-raw-development.js.map +1 -1
  13. package/dist/capture/native-raw.d.ts +16 -0
  14. package/dist/capture/native-raw.js +16 -3
  15. package/dist/capture/native-raw.js.map +1 -1
  16. package/dist/capture/packed-sensor-raw.d.ts +7 -0
  17. package/dist/capture/packed-sensor-raw.js +6 -2
  18. package/dist/capture/packed-sensor-raw.js.map +1 -1
  19. package/dist/core/pixel-limits.d.ts +14 -0
  20. package/dist/core/pixel-limits.js +40 -0
  21. package/dist/core/pixel-limits.js.map +1 -0
  22. package/dist/core/version.d.ts +1 -1
  23. package/dist/core/version.js +1 -1
  24. package/dist/core/version.js.map +1 -1
  25. package/dist/facade/chart-radiance-batch.d.ts +7 -0
  26. package/dist/facade/chart-radiance-batch.js +7 -3
  27. package/dist/facade/chart-radiance-batch.js.map +1 -1
  28. package/dist/facade/simulate-capture.d.ts +7 -0
  29. package/dist/facade/simulate-capture.js +63 -16
  30. package/dist/facade/simulate-capture.js.map +1 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +2 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/motion/temporal-exposure-sum.d.ts +7 -0
  35. package/dist/motion/temporal-exposure-sum.js +6 -2
  36. package/dist/motion/temporal-exposure-sum.js.map +1 -1
  37. package/dist/optics/irradiance-convolution.d.ts +7 -0
  38. package/dist/optics/irradiance-convolution.js +6 -2
  39. package/dist/optics/irradiance-convolution.js.map +1 -1
  40. package/dist/output/native-capture-sdr.d.ts +7 -0
  41. package/dist/output/native-capture-sdr.js +6 -3
  42. package/dist/output/native-capture-sdr.js.map +1 -1
  43. package/dist/output/png-encoder.d.ts +10 -1
  44. package/dist/output/png-encoder.js +10 -4
  45. package/dist/output/png-encoder.js.map +1 -1
  46. package/dist/output/print-jpeg.d.ts +18 -0
  47. package/dist/output/print-jpeg.js +15 -4
  48. package/dist/output/print-jpeg.js.map +1 -1
  49. package/dist/sensor/sampling-aperture.d.ts +15 -0
  50. package/dist/sensor/sampling-aperture.js +31 -10
  51. package/dist/sensor/sampling-aperture.js.map +1 -1
  52. package/docs/API_REFERENCE.md +11 -1
  53. package/docs/QUICK_START.md +107 -3
  54. package/docs/USAGE.md +3 -1
  55. package/docs/api/capture-native-raw.md +11 -2
  56. package/docs/api/capture-packed-sensor-raw.md +7 -1
  57. package/docs/api/core-pixel-limits.md +19 -0
  58. package/docs/api/core-version.md +1 -1
  59. package/docs/api/exports.json +17 -2
  60. package/docs/api/facade-chart-radiance-batch.md +8 -2
  61. package/docs/api/facade-simulate-capture.md +7 -0
  62. package/docs/api/motion-temporal-exposure-sum.md +7 -1
  63. package/docs/api/optics-irradiance-convolution.md +7 -1
  64. package/docs/api/output-native-capture-sdr.md +7 -2
  65. package/docs/api/output-png-encoder.md +8 -1
  66. package/docs/api/output-print-jpeg.md +17 -0
  67. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"sampling-aperture.js","sourceRoot":"","sources":["../../src/sensor/sampling-aperture.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EACL,iBAAiB,EAElB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EACL,gDAAgD,EAEjD,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,wBAAwB,EAGzB,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AA8IzG,SAAS,aAAa,CACpB,KAAc,EACd,IAAY,EACZ,WAAqC;IAErC,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EACpB,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,qBAAqB,CAC7B,CAAC;IACJ,CAAC;IACD,IAAI,WAAW,KAAK,IAAI;QAAE,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;IACxE,OAAO,KAAsB,CAAC;AAChC,CAAC;AAED,SAAS,qBAAqB,CAC5B,KAAc,EACd,IAAY;IAEZ,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EACzB,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,8BAA8B,CACtC,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,qBAAqB,CAC5B,KAAc,EACd,IAAY;IAEZ,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,KAAK,IAAI,CAAC,EACV,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI;YACF,6CAA6C,CAChD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CACpB,KAAc,EACd,IAAY;IAEZ,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EACvB,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,2BAA2B,CACnC,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,sBAAsB,CAC7B,KAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,kDAAkD,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,UAAU,CAAC,CAAC,CAAC;IAElL,IACE,MAAM,CAAC,IAAI;QACX,yCAAyC,EACzC,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,mBAAmB,CAC3B,CAAC;IACJ,CAAC;IACD,IACE,MAAM,CAAC,gBAAgB;QACvB,wBAAwB,EACxB,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI;YACF,qDAAqD,CACxD,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,aAAa,CAC/B,MAAM,CAAC,gDAAgD,EACvD,IAAI;QACF,mDAAmD,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAClE,CAAC;IAEF,MAAM,MAAM,GAAG,aAAa,CAC1B,WAAW,CAAC,CAAC,EACb,IAAI;QACF,qDAAqD,CACxD,CAAC;IACF,MAAM,MAAM,GAAG,aAAa,CAC1B,WAAW,CAAC,CAAC,EACb,IAAI;QACF,qDAAqD,CACxD,CAAC;IAEF,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,yBAAyB,CACjC,IAAI;YACF,yEAAyE,CAC5E,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EACF,yCAAyC;QAC3C,gBAAgB,EACd,wBAAwB;QAC1B,iBAAiB,EACf,qBAAqB,CACnB,MAAM,CAAC,iBAAiB,EACxB,IAAI,GAAG,oBAAoB,CAC5B;QACH,iBAAiB,EACf,qBAAqB,CACnB,MAAM,CAAC,iBAAiB,EACxB,IAAI,GAAG,oBAAoB,CAC5B;QACH,gDAAgD,EAC9C;YACE,CAAC,EAAE,MAAM;YACT,CAAC,EAAE,MAAM;SACV;QACH,QAAQ,EAAE,iBAAiB,CACzB,MAAM,CAAC,QAAQ,EACf,IAAI,GAAG,WAAW,CACnB;KACF,CAAC;AACJ,CAAC;AAED,SAAS,+BAA+B,CACtC,KAAc,EACd,IAAY,EACZ,OAA4C;IAE5C,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,uCAAuC,EAAE,UAAU,CAAC,CAAC,CAAC;IAElJ,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QACjC,IACE,MAAM,CAAC,gBAAgB,KAAK,SAAS;YACrC,MAAM,CAAC,iBAAiB,KAAK,SAAS;YACtC,MAAM,CAAC,qCAAqC;gBAC1C,SAAS,EACX,CAAC;YACD,MAAM,IAAI,yBAAyB,CACjC,IAAI;gBACF,kDAAkD,CACrD,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,QAAQ,EAAE,iBAAiB,CACzB,MAAM,CAAC,QAAQ,EACf,IAAI,GAAG,WAAW,CACnB;SACF,CAAC;IACJ,CAAC;IAED,IACE,MAAM,CAAC,IAAI;QACX,gCAAgC,EAChC,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,mBAAmB,CAC3B,CAAC;IACJ,CAAC;IAED,MAAM,gBAAgB,GACpB,qBAAqB,CACnB,MAAM,CAAC,gBAAgB,EACvB,IAAI,GAAG,mBAAmB,CAC3B,CAAC;IACJ,MAAM,iBAAiB,GACrB,qBAAqB,CACnB,MAAM,CAAC,iBAAiB,EACxB,IAAI,GAAG,oBAAoB,CAC5B,CAAC;IACJ,MAAM,MAAM,GAAG,aAAa,CAC1B,MAAM,CAAC,qCAAqC,EAC5C,IAAI;QACF,wCAAwC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CACvD,CAAC;IACF,MAAM,OAAO,GAAG,aAAa,CAC3B,MAAM,CAAC,CAAC,EACR,IAAI;QACF,0CAA0C,CAC7C,CAAC;IACF,MAAM,OAAO,GAAG,aAAa,CAC3B,MAAM,CAAC,CAAC,EACR,IAAI;QACF,0CAA0C,CAC7C,CAAC;IAEF,MAAM,SAAS,GACb,OAAO,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAChC,MAAM,SAAS,GACb,OAAO,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAEhC,IACE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;QACf,gBAAgB,GAAG,CAAC;QACpB,SAAS;QACX,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;YACf,iBAAiB,GAAG,CAAC;YACrB,SAAS,EACX,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI;YACF,8DAA8D,CACjE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI,EACF,gCAAgC;QAClC,gBAAgB;QAChB,iBAAiB;QACjB,qCAAqC,EACnC;YACE,CAAC,EAAE,OAAO;YACV,CAAC,EAAE,OAAO;SACX;QACH,QAAQ,EAAE,iBAAiB,CACzB,MAAM,CAAC,QAAQ,EACf,IAAI,GAAG,WAAW,CACnB;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kCAAkC,CAChD,KAAc;IAEd,MAAM,OAAO,GAAG,aAAa,CAC3B,KAAK,EACL,wBAAwB,EAAE,CAAC,eAAe,EAAE,mBAAmB,EAAE,WAAW,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,UAAU,EAAE,4BAA4B,CAAC,CAC5K,CAAC;IAEF,IAAI,OAAO,CAAC,aAAa,KAAK,OAAO,EAAE,CAAC;QACtC,MAAM,IAAI,yBAAyB,CACjC,uDAAuD,CACxD,CAAC;IACJ,CAAC;IAED,MAAM,iBAAiB,GACrB,sBAAsB,CACpB,OAAO,CAAC,iBAAiB,EACzB,0CAA0C,CAC3C,CAAC;IAEJ,OAAO;QACL,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,qBAAqB,CAC9B,OAAO,CAAC,SAAS,EACjB,kCAAkC,CACnC;QACD,sBAAsB,EACpB,qBAAqB,CACnB,OAAO,CAAC,sBAAsB,EAC9B,+CAA+C,CAChD;QACH,sBAAsB,EACpB,qBAAqB,CACnB,OAAO,CAAC,sBAAsB,EAC9B,+CAA+C,CAChD;QACH,QAAQ,EAAE,iBAAiB,CACzB,OAAO,CAAC,QAAQ,EAChB,iCAAiC,CAClC;QACD,iBAAiB;QACjB,0BAA0B,EACxB,+BAA+B,CAC7B,OAAO,CAAC,0BAA0B,EAClC,mDAAmD,EACnD,iBAAiB,CAClB;KACJ,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CACvB,KAAc,EACd,IAAY;IAEZ,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC;QAC5B,KAAK,GAAG,CAAC,EACT,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,IAAI;YACF,iEAAiE,CACpE,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,0BAA0B,CACjC,WAA8B;IAE9B,kBAAkB,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IACrD,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC;QACrC,WAAW,CAAC,OAAO,IAAI,CAAC;QACxB,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC;QACtC,WAAW,CAAC,QAAQ,IAAI,CAAC,EACzB,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,gEAAgE,CACjE,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,6BAA6B,CAC3C,KAAyC;IAEzC,gBAAgB,CAAC,KAAK,EAAE,+BAA+B,EAAE,CAAC,aAAa,EAAE,cAAc,EAAE,sBAAsB,EAAE,6BAA6B,EAAE,yBAAyB,EAAE,MAAM,CAAC,CAAC,CAAC;IACpL,0BAA0B,CACxB,KAAK,CAAC,WAAW,CAClB,CAAC;IAEF,MAAM,OAAO,GACX,kCAAkC,CAChC,KAAK,CAAC,uBAAuB,CAC9B,CAAC;IACJ,MAAM,OAAO,GACX,gDAAgD,CAC9C;QACE,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,oBAAoB,EAClB,KAAK,CAAC,oBAAoB;QAC5B,cAAc,EACZ,KAAK,CAAC,2BAA2B;KACpC,CACF,CAAC;IAEJ,IACE,OAAO,CAAC,sBAAsB;QAC9B,OAAO,CAAC,sBAAsB,EAC9B,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IACD,IACE,OAAO,CAAC,sBAAsB;QAC9B,OAAO,CAAC,SAAS,EACjB,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IAED,IACE,OAAO,CAAC,0BAA0B,CAAC,IAAI;QACvC,YAAY,EACZ,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,6FAA6F,CAC9F,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,gBAAgB,CAC5B,KAAK,CAAC,IAAI,CAAC,CAAC,EACZ,QAAQ,CACT,CAAC;IACF,MAAM,KAAK,GAAG,gBAAgB,CAC5B,KAAK,CAAC,IAAI,CAAC,CAAC,EACZ,QAAQ,CACT,CAAC;IAEF,IACE,KAAK;QACH,OAAO,CAAC,qBAAqB,CAAC,UAAU;QAC1C,KAAK;YACH,OAAO,CAAC,qBAAqB,CAAC,WAAW,EAC3C,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,0DAA0D,CAC3D,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GACX,OAAO,CAAC,iBAAiB,CAAC;IAC5B,MAAM,SAAS,GACb,OAAO,CAAC,qBAAqB,CAAC,UAAU,CAAC;IAC3C,MAAM,UAAU,GACd,OAAO,CAAC,qBAAqB,CAAC,WAAW,CAAC;IAC5C,MAAM,uBAAuB,GAC3B,KAAK,CAAC,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;IACnC,MAAM,wBAAwB,GAC5B,KAAK,CAAC,WAAW,CAAC,QAAQ,GAAG,IAAI,CAAC;IAEpC,MAAM,WAAW,GACf,OAAO;SACJ,gDAAgD;SAChD,CAAC;QACJ,CAAC,SAAS,GAAG,CAAC,CAAC;YACb,OAAO,CAAC,iBAAiB,CAAC;IAC9B,MAAM,WAAW,GACf,OAAO;SACJ,gDAAgD;SAChD,CAAC;QACJ,CAAC,UAAU,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,iBAAiB,CAAC;IAE9B,IACE,WAAW,GAAG,uBAAuB;QACrC,WAAW,GAAG,wBAAwB,EACtC,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,iFAAiF,CAClF,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GACZ,OAAO,CAAC,0BAA0B,CAAC;IACrC,MAAM,eAAe,GACnB,QAAQ,CAAC,qCAAqC,CAAC,CAAC,CAAC;IACnD,MAAM,eAAe,GACnB,QAAQ,CAAC,qCAAqC,CAAC,CAAC,CAAC;IACnD,MAAM,iBAAiB,GACrB,QAAQ,CAAC,gBAAgB,GAAG,CAAC,CAAC;IAChC,MAAM,kBAAkB,GACtB,QAAQ,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAEjC,MAAM,iBAAiB,GACrB,OAAO,CAAC,gDAAgD,CAAC,CAAC;QAC1D,eAAe;QACf,iBAAiB,CAAC;IACpB,MAAM,gBAAgB,GACpB,OAAO,CAAC,gDAAgD,CAAC,CAAC;QAC1D,eAAe;QACf,kBAAkB,CAAC;IACrB,MAAM,iBAAiB,GACrB,WAAW;QACX,eAAe;QACf,iBAAiB,CAAC;IACpB,MAAM,kBAAkB,GACtB,WAAW;QACX,eAAe;QACf,kBAAkB,CAAC;IAErB,IACE,iBAAiB,GAAG,CAAC;QACrB,gBAAgB,GAAG,CAAC;QACpB,iBAAiB,GAAG,uBAAuB;QAC3C,kBAAkB,GAAG,wBAAwB,EAC7C,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,6HAA6H,CAC9H,CAAC;IACJ,CAAC;IAED,MAAM,sBAAsB,GAC1B,OAAO;SACJ,gDAAgD;SAChD,CAAC;QACJ,KAAK,GAAG,OAAO,CAAC,iBAAiB,CAAC;IACpC,MAAM,sBAAsB,GAC1B,OAAO;SACJ,gDAAgD;SAChD,CAAC;QACJ,KAAK,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAEpC,MAAM,0BAA0B,GAC9B,sBAAsB;QACtB,QAAQ;aACL,qCAAqC;aACrC,CAAC,CAAC;IACP,MAAM,0BAA0B,GAC9B,sBAAsB;QACtB,QAAQ;aACL,qCAAqC;aACrC,CAAC,CAAC;IACP,MAAM,gBAAgB,GACpB,0BAA0B;QAC1B,QAAQ,CAAC,gBAAgB,GAAG,CAAC,CAAC;IAChC,MAAM,oBAAoB,GACxB,0BAA0B;QAC1B,QAAQ,CAAC,gBAAgB,GAAG,CAAC,CAAC;IAChC,MAAM,gBAAgB,GACpB,0BAA0B;QAC1B,QAAQ,CAAC,iBAAiB,GAAG,CAAC,CAAC;IACjC,MAAM,oBAAoB,GACxB,0BAA0B;QAC1B,QAAQ,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAEjC,IACE,gBAAgB,GAAG,CAAC;QACpB,gBAAgB,GAAG,CAAC;QACpB,oBAAoB;YAClB,uBAAuB;QACzB,oBAAoB;YAClB,wBAAwB,EAC1B,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IAED,MAAM,2BAA2B,GAAG;QAClC,CAAC,EACC,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,GAAG,CAAC;YAC9B,sBAAsB,GAAG,IAAI;QAC/B,CAAC,EACC,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,GAAG,CAAC;YAC/B,sBAAsB,GAAG,IAAI;KAChC,CAAC;IACF,MAAM,+BAA+B,GAAG;QACtC,CAAC,EACC,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,GAAG,CAAC;YAC9B,0BAA0B,GAAG,IAAI;QACnC,CAAC,EACC,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,GAAG,CAAC;YAC/B,0BAA0B,GAAG,IAAI;KACpC,CAAC;IACF,MAAM,uBAAuB,GAAG;QAC9B,IAAI,EACF,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,GAAG,CAAC;YAC9B,gBAAgB,GAAG,IAAI;QACzB,KAAK,EACH,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,GAAG,CAAC;YAC9B,oBAAoB,GAAG,IAAI;QAC7B,GAAG,EACD,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,GAAG,CAAC;YAC/B,gBAAgB,GAAG,IAAI;QACzB,MAAM,EACJ,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,GAAG,CAAC;YAC/B,oBAAoB,GAAG,IAAI;KAC9B,CAAC;IAEF,MAAM,qBAAqB,GACzB,QAAQ,CAAC,gBAAgB;QACzB,QAAQ,CAAC,iBAAiB,CAAC;IAC7B,MAAM,gCAAgC,GACpC,OAAO,CAAC,iBAAiB;QACzB,OAAO,CAAC,iBAAiB,CAAC;IAC5B,MAAM,2CAA2C,GAC/C,qBAAqB;QACrB,gCAAgC,CAAC;IAEnC,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC;QACvC,CAAC,MAAM,CAAC,QAAQ,CACd,gCAAgC,CACjC;QACD,CAAC,MAAM,CAAC,QAAQ,CACd,2CAA2C,CAC5C,EACD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,wDAAwD,CACzD,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GACX,wBAAwB,CAAC;QACvB,OAAO,EAAE,KAAK,CAAC,oBAAoB;QACnC,IAAI,EAAE;YACJ,CAAC,EAAE,KAAK;YACR,CAAC,EAAE,KAAK;SACT;KACF,CAAC,CAAC;IAEL,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,sBAAsB,EACpB,OAAO,CAAC,sBAAsB;QAChC,sBAAsB,EACpB,OAAO,CAAC,sBAAsB;QAChC,gBAAgB,EACd,wBAAwB;QAC1B,IAAI,EAAE;YACJ,CAAC,EAAE,KAAK;YACR,CAAC,EAAE,KAAK;SACT;QACD,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,SAAS;QACpC,QAAQ,EAAE;YACR,UAAU,EAAE,SAAS;YACrB,WAAW,EAAE,UAAU;SACxB;QACD,OAAO,EAAE;YACP,iBAAiB,EACf,OAAO,CAAC,iBAAiB;YAC3B,iBAAiB,EACf,OAAO,CAAC,iBAAiB;YAC3B,gDAAgD,EAC9C;gBACE,GAAG,OAAO;qBACP,gDAAgD;aACpD;YACH,2CAA2C,EACzC;gBACE,CAAC,EAAE,sBAAsB;gBACzB,CAAC,EAAE,sBAAsB;aAC1B;YACH,2BAA2B;YAC3B,gCAAgC;SACjC;QACD,0BAA0B,EAAE;YAC1B,IAAI,EACF,gCAAgC;YAClC,gBAAgB,EACd,QAAQ,CAAC,gBAAgB;YAC3B,iBAAiB,EACf,QAAQ,CAAC,iBAAiB;YAC5B,qCAAqC,EACnC;gBACE,GAAG,QAAQ;qBACR,qCAAqC;aACzC;YACH,uBAAuB,EACrB,+BAA+B;YACjC,uBAAuB;YACvB,qBAAqB;YACrB,2CAA2C;YAC3C,gBAAgB,EACd,2BAA2B;YAC7B,mCAAmC,EACjC,eAAe;SAClB;QACD,qCAAqC,EACnC,KAAK;QACP,4BAA4B,EAAE,KAAK;QACnC,4BAA4B,EAAE,KAAK;QACnC,sCAAsC,EACpC,KAAK;QACP,uBAAuB,EAAE,KAAK;QAC9B,2BAA2B,EAAE,KAAK;QAClC,wBAAwB,EAAE,KAAK;QAC/B,yBAAyB,EAAE,KAAK;QAChC,yBAAyB,EAAE,KAAK;QAChC,oCAAoC,EAClC,KAAK;QACP,qCAAqC,EACnC,KAAK;QACP,UAAU,EAAE;YACV,OAAO,EAAE,OAAO,CAAC,QAAQ;YACzB,OAAO,EAAE,OAAO,CAAC,QAAQ;YACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;SAC5B;KACF,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Sampling aperture: where the colour-sampling sites sit physically on the sensor, and the\n * light-sensitive area of each site as a simple geometric shape.\n *\n * Site pitch and origin are declared facts, never derived from NativeImageRaster. The sensitive\n * region, when known, is a plain geometric rectangle. It does not include microlens effects, charge\n * diffusion, electrical crosstalk, QE (quantum efficiency), throughput, spectral response or an\n * effective light-collection area.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport {\n resolveNativeEffectiveRasterColorSamplingBinding,\n type NativeEffectiveRasterColorSamplingBindingProfile\n} from \"./capture-color-sampling-binding.js\";\nimport {\n resolveColorSamplingSite,\n type NativeColorSamplingSiteIndex,\n type SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport type {\n NativeImageRaster,\n SensorImagingArea\n} from \"./sensor-geometry.js\";\nimport { requireInputObject, requireInputKeys, rejectUnknownFields } from \"../core/record-validation.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport interface SensorSiteCenterLatticeRegistration {\n kind: \"regular-rectangular-site-center-lattice\";\n coordinateSystem: \"native-sensor-physical\";\n /** Horizontal centre-to-centre site spacing (pitch) in micrometres. */\n pitchXMicrometers: number;\n /** Vertical centre-to-centre site spacing (pitch) in micrometres. */\n pitchYMicrometers: number;\n /**\n * Centre of the first site, measured from the imaging area's physical\n * top-left corner. +X right, +Y down. Units: micrometres.\n */\n firstSiteCenterFromImagingAreaTopLeftMicrometers: {\n x: number;\n y: number;\n };\n evidence: readonly EvidenceProvenance[];\n}\n\nexport type SensorGeometricSensitiveAperture =\n | {\n /**\n * The light-sensitive area is known to matter, but its shape is not\n * known well enough to use.\n */\n kind: \"unresolved\";\n evidence: readonly EvidenceProvenance[];\n }\n | {\n /**\n * A simple approximation of the sensitive area as a rectangle.\n *\n * The rectangle must stay inside one grid cell, so neighbouring\n * sensitive areas never overlap in schema 0.1.0.\n */\n kind: \"uniform-axis-aligned-rectangle\";\n widthMicrometers: number;\n heightMicrometers: number;\n centerOffsetFromSiteCenterMicrometers: {\n x: number;\n y: number;\n };\n evidence: readonly EvidenceProvenance[];\n };\n\nexport interface SensorSamplingApertureProfile {\n schemaVersion: \"0.1.0\";\n profileId: string;\n colorSamplingProfileId: string;\n colorSamplingBindingId: string;\n evidence: readonly EvidenceProvenance[];\n siteCenterLattice: SensorSiteCenterLatticeRegistration;\n geometricSensitiveAperture: SensorGeometricSensitiveAperture;\n}\n\nexport interface ResolveSensorSamplingApertureInput {\n imagingArea: SensorImagingArea;\n nativeRaster: NativeImageRaster;\n colorSamplingProfile: SensorColorSamplingProfile;\n colorSamplingBindingProfile: NativeEffectiveRasterColorSamplingBindingProfile;\n samplingApertureProfile: SensorSamplingApertureProfile;\n site: NativeColorSamplingSiteIndex;\n}\n\nexport interface NativeSensorPhysicalPointMm {\n x: number;\n y: number;\n}\n\nexport interface NativeSensorPhysicalBoundsMm {\n left: number;\n right: number;\n top: number;\n bottom: number;\n}\n\nexport interface ResolvedSensorSamplingAperture {\n profileId: string;\n colorSamplingProfileId: string;\n colorSamplingBindingId: string;\n coordinateSystem: \"native-sensor-physical\";\n site: NativeColorSamplingSiteIndex;\n channelId: string;\n siteGrid: {\n widthSites: number;\n heightSites: number;\n };\n lattice: {\n pitchXMicrometers: number;\n pitchYMicrometers: number;\n firstSiteCenterFromImagingAreaTopLeftMicrometers: {\n x: number;\n y: number;\n };\n siteCenterFromImagingAreaTopLeftMicrometers: {\n x: number;\n y: number;\n };\n siteCenterFromOpticalAxisMm: NativeSensorPhysicalPointMm;\n nominalCellAreaSquareMicrometers: number;\n };\n geometricSensitiveAperture: {\n kind: \"uniform-axis-aligned-rectangle\";\n widthMicrometers: number;\n heightMicrometers: number;\n centerOffsetFromSiteCenterMicrometers: {\n x: number;\n y: number;\n };\n centerFromOpticalAxisMm: NativeSensorPhysicalPointMm;\n boundsFromOpticalAxisMm: NativeSensorPhysicalBoundsMm;\n areaSquareMicrometers: number;\n geometricSensitiveAreaFractionOfLatticeCell: number;\n spatialWeighting:\n \"uniform-unit-area-average\";\n neighboringGeometricApertureOverlap:\n \"none-by-model\";\n };\n /**\n * Fixed statements of what this result does not establish.\n */\n sitePitchDerivedFromNativeImageRaster: false;\n apertureDerivedFromSitePitch: false;\n antiAliasingResponseIncluded: false;\n microlensSpatialRedistributionIncluded: false;\n chargeDiffusionIncluded: false;\n electricalCrosstalkIncluded: false;\n spectralResponseIncluded: false;\n quantumEfficiencyIncluded: false;\n opticalThroughputIncluded: false;\n radiometricCollectionAreaEstablished: false;\n physicalPhotodiodeGeometryEstablished: false;\n provenance: {\n profile: readonly EvidenceProvenance[];\n lattice: readonly EvidenceProvenance[];\n aperture: readonly EvidenceProvenance[];\n };\n}\n\nfunction requireRecord(\n value: unknown,\n path: string,\n allowedKeys: readonly string[] | null\n): UnknownRecord {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be an object.\"\n );\n }\n if (allowedKeys !== null) rejectUnknownFields(value, allowedKeys, path);\n return value as UnknownRecord;\n}\n\nfunction requireNonEmptyString(\n value: unknown,\n path: string\n): string {\n if (\n typeof value !== \"string\" ||\n value.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n path + \" must be a non-empty string.\"\n );\n }\n return value;\n}\n\nfunction requirePositiveFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value <= 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must be a finite number greater than zero.\"\n );\n }\n return value;\n}\n\nfunction requireFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be a finite number.\"\n );\n }\n return value;\n}\n\nfunction parseSiteCenterLattice(\n value: unknown,\n path: string\n): SensorSiteCenterLatticeRegistration {\n const record = requireRecord(value, path, [\"kind\", \"coordinateSystem\", \"firstSiteCenterFromImagingAreaTopLeftMicrometers\", \"pitchXMicrometers\", \"pitchYMicrometers\", \"evidence\"]);\n\n if (\n record.kind !==\n \"regular-rectangular-site-center-lattice\"\n ) {\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n }\n if (\n record.coordinateSystem !==\n \"native-sensor-physical\"\n ) {\n throw new InvalidConfigurationError(\n path +\n '.coordinateSystem must be \"native-sensor-physical\".'\n );\n }\n\n const firstCenter = requireRecord(\n record.firstSiteCenterFromImagingAreaTopLeftMicrometers,\n path +\n \".firstSiteCenterFromImagingAreaTopLeftMicrometers\", [\"x\", \"y\"]\n );\n\n const firstX = requireFinite(\n firstCenter.x,\n path +\n \".firstSiteCenterFromImagingAreaTopLeftMicrometers.x\"\n );\n const firstY = requireFinite(\n firstCenter.y,\n path +\n \".firstSiteCenterFromImagingAreaTopLeftMicrometers.y\"\n );\n\n if (firstX < 0 || firstY < 0) {\n throw new InvalidConfigurationError(\n path +\n \".firstSiteCenterFromImagingAreaTopLeftMicrometers must not be negative.\"\n );\n }\n\n return {\n kind:\n \"regular-rectangular-site-center-lattice\",\n coordinateSystem:\n \"native-sensor-physical\",\n pitchXMicrometers:\n requirePositiveFinite(\n record.pitchXMicrometers,\n path + \".pitchXMicrometers\"\n ),\n pitchYMicrometers:\n requirePositiveFinite(\n record.pitchYMicrometers,\n path + \".pitchYMicrometers\"\n ),\n firstSiteCenterFromImagingAreaTopLeftMicrometers:\n {\n x: firstX,\n y: firstY\n },\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\nfunction parseGeometricSensitiveAperture(\n value: unknown,\n path: string,\n lattice: SensorSiteCenterLatticeRegistration\n): SensorGeometricSensitiveAperture {\n const record = requireRecord(value, path, [\"kind\", \"widthMicrometers\", \"heightMicrometers\", \"centerOffsetFromSiteCenterMicrometers\", \"evidence\"]);\n\n if (record.kind === \"unresolved\") {\n if (\n record.widthMicrometers !== undefined ||\n record.heightMicrometers !== undefined ||\n record.centerOffsetFromSiteCenterMicrometers !==\n undefined\n ) {\n throw new InvalidConfigurationError(\n path +\n \" unresolved aperture must omit rectangle fields.\"\n );\n }\n return {\n kind: \"unresolved\",\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n }\n\n if (\n record.kind !==\n \"uniform-axis-aligned-rectangle\"\n ) {\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n }\n\n const widthMicrometers =\n requirePositiveFinite(\n record.widthMicrometers,\n path + \".widthMicrometers\"\n );\n const heightMicrometers =\n requirePositiveFinite(\n record.heightMicrometers,\n path + \".heightMicrometers\"\n );\n const offset = requireRecord(\n record.centerOffsetFromSiteCenterMicrometers,\n path +\n \".centerOffsetFromSiteCenterMicrometers\", [\"x\", \"y\"]\n );\n const offsetX = requireFinite(\n offset.x,\n path +\n \".centerOffsetFromSiteCenterMicrometers.x\"\n );\n const offsetY = requireFinite(\n offset.y,\n path +\n \".centerOffsetFromSiteCenterMicrometers.y\"\n );\n\n const halfCellX =\n lattice.pitchXMicrometers / 2;\n const halfCellY =\n lattice.pitchYMicrometers / 2;\n\n if (\n Math.abs(offsetX) +\n widthMicrometers / 2 >\n halfCellX ||\n Math.abs(offsetY) +\n heightMicrometers / 2 >\n halfCellY\n ) {\n throw new InvalidConfigurationError(\n path +\n \" rectangle must remain within one regular site lattice cell.\"\n );\n }\n\n return {\n kind:\n \"uniform-axis-aligned-rectangle\",\n widthMicrometers,\n heightMicrometers,\n centerOffsetFromSiteCenterMicrometers:\n {\n x: offsetX,\n y: offsetY\n },\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\n/**\n * Parses where the colour-sampling sites sit physically, plus a simple geometric\n * model of each site's light-sensitive area.\n *\n * Site pitch and origin are declared facts, never derived from NativeImageRaster.\n * The rectangle, when given, is a geometric sensitive region only. It does not\n * include microlens effects, charge diffusion, electrical crosstalk, QE,\n * throughput, spectral response or an effective light-collection area.\n */\nexport function parseSensorSamplingApertureProfile(\n value: unknown\n): SensorSamplingApertureProfile {\n const profile = requireRecord(\n value,\n \"sensorSamplingAperture\", [\"schemaVersion\", \"siteCenterLattice\", \"profileId\", \"colorSamplingProfileId\", \"colorSamplingBindingId\", \"evidence\", \"geometricSensitiveAperture\"]\n );\n\n if (profile.schemaVersion !== \"0.1.0\") {\n throw new InvalidConfigurationError(\n 'sensorSamplingAperture.schemaVersion must be \"0.1.0\".'\n );\n }\n\n const siteCenterLattice =\n parseSiteCenterLattice(\n profile.siteCenterLattice,\n \"sensorSamplingAperture.siteCenterLattice\"\n );\n\n return {\n schemaVersion: \"0.1.0\",\n profileId: requireNonEmptyString(\n profile.profileId,\n \"sensorSamplingAperture.profileId\"\n ),\n colorSamplingProfileId:\n requireNonEmptyString(\n profile.colorSamplingProfileId,\n \"sensorSamplingAperture.colorSamplingProfileId\"\n ),\n colorSamplingBindingId:\n requireNonEmptyString(\n profile.colorSamplingBindingId,\n \"sensorSamplingAperture.colorSamplingBindingId\"\n ),\n evidence: parseEvidenceList(\n profile.evidence,\n \"sensorSamplingAperture.evidence\"\n ),\n siteCenterLattice,\n geometricSensitiveAperture:\n parseGeometricSensitiveAperture(\n profile.geometricSensitiveAperture,\n \"sensorSamplingAperture.geometricSensitiveAperture\",\n siteCenterLattice\n )\n };\n}\n\nfunction requireSiteIndex(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isSafeInteger(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be a non-negative safe integer color-sampling-site index.\"\n );\n }\n return value;\n}\n\nfunction requirePositiveImagingArea(\n imagingArea: SensorImagingArea\n): void {\n requireInputObject(imagingArea, \"imagingArea input\");\n if (\n !Number.isFinite(imagingArea.widthMm) ||\n imagingArea.widthMm <= 0 ||\n !Number.isFinite(imagingArea.heightMm) ||\n imagingArea.heightMm <= 0\n ) {\n throw new InvalidScientificInputError(\n \"imagingArea width/height must be finite and greater than zero.\"\n );\n }\n}\n\n/**\n * Resolves one site's geometric sampling aperture in native sensor physical space.\n *\n * The result is a normalized footprint that averages evenly over its area, for\n * image sampling, plus its physical area and fill fraction. That geometric area\n * is deliberately not treated as light-collection efficiency or as the true\n * photodiode shape.\n */\nexport function resolveSensorSamplingAperture(\n input: ResolveSensorSamplingApertureInput\n): ResolvedSensorSamplingAperture {\n requireInputKeys(input, \"resolveSensorSamplingAperture\", [\"imagingArea\", \"nativeRaster\", \"colorSamplingProfile\", \"colorSamplingBindingProfile\", \"samplingApertureProfile\", \"site\"]);\n requirePositiveImagingArea(\n input.imagingArea\n );\n\n const profile =\n parseSensorSamplingApertureProfile(\n input.samplingApertureProfile\n );\n const binding =\n resolveNativeEffectiveRasterColorSamplingBinding(\n {\n nativeRaster: input.nativeRaster,\n colorSamplingProfile:\n input.colorSamplingProfile,\n bindingProfile:\n input.colorSamplingBindingProfile\n }\n );\n\n if (\n profile.colorSamplingProfileId !==\n binding.colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"Sampling-aperture colorSamplingProfileId must match the resolved color-sampling profile.\"\n );\n }\n if (\n profile.colorSamplingBindingId !==\n binding.bindingId\n ) {\n throw new InvalidScientificInputError(\n \"Sampling-aperture colorSamplingBindingId must match the resolved color-sampling binding.\"\n );\n }\n\n if (\n profile.geometricSensitiveAperture.kind ===\n \"unresolved\"\n ) {\n throw new InvalidScientificInputError(\n \"Geometric sensitive aperture is unresolved; a spatial sampling aperture cannot be produced.\"\n );\n }\n\n const siteX = requireSiteIndex(\n input.site.x,\n \"site.x\"\n );\n const siteY = requireSiteIndex(\n input.site.y,\n \"site.y\"\n );\n\n if (\n siteX >=\n binding.colorSamplingSiteGrid.widthSites ||\n siteY >=\n binding.colorSamplingSiteGrid.heightSites\n ) {\n throw new InvalidScientificInputError(\n \"site must lie within the bound color-sampling site grid.\"\n );\n }\n\n const lattice =\n profile.siteCenterLattice;\n const gridWidth =\n binding.colorSamplingSiteGrid.widthSites;\n const gridHeight =\n binding.colorSamplingSiteGrid.heightSites;\n const imagingWidthMicrometers =\n input.imagingArea.widthMm * 1000;\n const imagingHeightMicrometers =\n input.imagingArea.heightMm * 1000;\n\n const lastCenterX =\n lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n .x +\n (gridWidth - 1) *\n lattice.pitchXMicrometers;\n const lastCenterY =\n lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n .y +\n (gridHeight - 1) *\n lattice.pitchYMicrometers;\n\n if (\n lastCenterX > imagingWidthMicrometers ||\n lastCenterY > imagingHeightMicrometers\n ) {\n throw new InvalidScientificInputError(\n \"Declared site-center lattice extends beyond the supplied physical imaging area.\"\n );\n }\n\n const aperture =\n profile.geometricSensitiveAperture;\n const apertureOffsetX =\n aperture.centerOffsetFromSiteCenterMicrometers.x;\n const apertureOffsetY =\n aperture.centerOffsetFromSiteCenterMicrometers.y;\n const apertureHalfWidth =\n aperture.widthMicrometers / 2;\n const apertureHalfHeight =\n aperture.heightMicrometers / 2;\n\n const firstApertureLeft =\n lattice.firstSiteCenterFromImagingAreaTopLeftMicrometers.x +\n apertureOffsetX -\n apertureHalfWidth;\n const firstApertureTop =\n lattice.firstSiteCenterFromImagingAreaTopLeftMicrometers.y +\n apertureOffsetY -\n apertureHalfHeight;\n const lastApertureRight =\n lastCenterX +\n apertureOffsetX +\n apertureHalfWidth;\n const lastApertureBottom =\n lastCenterY +\n apertureOffsetY +\n apertureHalfHeight;\n\n if (\n firstApertureLeft < 0 ||\n firstApertureTop < 0 ||\n lastApertureRight > imagingWidthMicrometers ||\n lastApertureBottom > imagingHeightMicrometers\n ) {\n throw new InvalidScientificInputError(\n \"Declared geometric sensitive aperture does not fit within the supplied physical imaging area across the complete site grid.\"\n );\n }\n\n const siteCenterFromTopLeftX =\n lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n .x +\n siteX * lattice.pitchXMicrometers;\n const siteCenterFromTopLeftY =\n lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n .y +\n siteY * lattice.pitchYMicrometers;\n\n const apertureCenterFromTopLeftX =\n siteCenterFromTopLeftX +\n aperture\n .centerOffsetFromSiteCenterMicrometers\n .x;\n const apertureCenterFromTopLeftY =\n siteCenterFromTopLeftY +\n aperture\n .centerOffsetFromSiteCenterMicrometers\n .y;\n const apertureLeftTopX =\n apertureCenterFromTopLeftX -\n aperture.widthMicrometers / 2;\n const apertureRightBottomX =\n apertureCenterFromTopLeftX +\n aperture.widthMicrometers / 2;\n const apertureLeftTopY =\n apertureCenterFromTopLeftY -\n aperture.heightMicrometers / 2;\n const apertureRightBottomY =\n apertureCenterFromTopLeftY +\n aperture.heightMicrometers / 2;\n\n if (\n apertureLeftTopX < 0 ||\n apertureLeftTopY < 0 ||\n apertureRightBottomX >\n imagingWidthMicrometers ||\n apertureRightBottomY >\n imagingHeightMicrometers\n ) {\n throw new InvalidScientificInputError(\n \"Resolved geometric sensitive aperture extends beyond the supplied physical imaging area.\"\n );\n }\n\n const siteCenterFromOpticalAxisMm = {\n x:\n -input.imagingArea.widthMm / 2 +\n siteCenterFromTopLeftX / 1000,\n y:\n -input.imagingArea.heightMm / 2 +\n siteCenterFromTopLeftY / 1000\n };\n const apertureCenterFromOpticalAxisMm = {\n x:\n -input.imagingArea.widthMm / 2 +\n apertureCenterFromTopLeftX / 1000,\n y:\n -input.imagingArea.heightMm / 2 +\n apertureCenterFromTopLeftY / 1000\n };\n const boundsFromOpticalAxisMm = {\n left:\n -input.imagingArea.widthMm / 2 +\n apertureLeftTopX / 1000,\n right:\n -input.imagingArea.widthMm / 2 +\n apertureRightBottomX / 1000,\n top:\n -input.imagingArea.heightMm / 2 +\n apertureLeftTopY / 1000,\n bottom:\n -input.imagingArea.heightMm / 2 +\n apertureRightBottomY / 1000\n };\n\n const areaSquareMicrometers =\n aperture.widthMicrometers *\n aperture.heightMicrometers;\n const nominalCellAreaSquareMicrometers =\n lattice.pitchXMicrometers *\n lattice.pitchYMicrometers;\n const geometricSensitiveAreaFractionOfLatticeCell =\n areaSquareMicrometers /\n nominalCellAreaSquareMicrometers;\n\n if (\n !Number.isFinite(areaSquareMicrometers) ||\n !Number.isFinite(\n nominalCellAreaSquareMicrometers\n ) ||\n !Number.isFinite(\n geometricSensitiveAreaFractionOfLatticeCell\n )\n ) {\n throw new InvalidScientificInputError(\n \"Derived sampling-aperture geometry must remain finite.\"\n );\n }\n\n const channel =\n resolveColorSamplingSite({\n profile: input.colorSamplingProfile,\n site: {\n x: siteX,\n y: siteY\n }\n });\n\n return {\n profileId: profile.profileId,\n colorSamplingProfileId:\n profile.colorSamplingProfileId,\n colorSamplingBindingId:\n profile.colorSamplingBindingId,\n coordinateSystem:\n \"native-sensor-physical\",\n site: {\n x: siteX,\n y: siteY\n },\n channelId: channel.mapping.channelId,\n siteGrid: {\n widthSites: gridWidth,\n heightSites: gridHeight\n },\n lattice: {\n pitchXMicrometers:\n lattice.pitchXMicrometers,\n pitchYMicrometers:\n lattice.pitchYMicrometers,\n firstSiteCenterFromImagingAreaTopLeftMicrometers:\n {\n ...lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n },\n siteCenterFromImagingAreaTopLeftMicrometers:\n {\n x: siteCenterFromTopLeftX,\n y: siteCenterFromTopLeftY\n },\n siteCenterFromOpticalAxisMm,\n nominalCellAreaSquareMicrometers\n },\n geometricSensitiveAperture: {\n kind:\n \"uniform-axis-aligned-rectangle\",\n widthMicrometers:\n aperture.widthMicrometers,\n heightMicrometers:\n aperture.heightMicrometers,\n centerOffsetFromSiteCenterMicrometers:\n {\n ...aperture\n .centerOffsetFromSiteCenterMicrometers\n },\n centerFromOpticalAxisMm:\n apertureCenterFromOpticalAxisMm,\n boundsFromOpticalAxisMm,\n areaSquareMicrometers,\n geometricSensitiveAreaFractionOfLatticeCell,\n spatialWeighting:\n \"uniform-unit-area-average\",\n neighboringGeometricApertureOverlap:\n \"none-by-model\"\n },\n sitePitchDerivedFromNativeImageRaster:\n false,\n apertureDerivedFromSitePitch: false,\n antiAliasingResponseIncluded: false,\n microlensSpatialRedistributionIncluded:\n false,\n chargeDiffusionIncluded: false,\n electricalCrosstalkIncluded: false,\n spectralResponseIncluded: false,\n quantumEfficiencyIncluded: false,\n opticalThroughputIncluded: false,\n radiometricCollectionAreaEstablished:\n false,\n physicalPhotodiodeGeometryEstablished:\n false,\n provenance: {\n profile: profile.evidence,\n lattice: lattice.evidence,\n aperture: aperture.evidence\n }\n };\n}\n"]}
1
+ {"version":3,"file":"sampling-aperture.js","sourceRoot":"","sources":["../../src/sensor/sampling-aperture.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Sampling aperture: where the colour-sampling sites sit physically on the sensor, and the\n * light-sensitive area of each site as a simple geometric shape.\n *\n * Site pitch and origin are declared facts, never derived from NativeImageRaster. The sensitive\n * region, when known, is a plain geometric rectangle. It does not include microlens effects, charge\n * diffusion, electrical crosstalk, QE (quantum efficiency), throughput, spectral response or an\n * effective light-collection area.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport {\n resolveNativeEffectiveRasterColorSamplingBinding,\n type NativeEffectiveRasterColorSamplingBindingProfile\n} from \"./capture-color-sampling-binding.js\";\nimport {\n resolveColorSamplingSite,\n type NativeColorSamplingSiteIndex,\n type SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport type {\n NativeImageRaster,\n SensorImagingArea\n} from \"./sensor-geometry.js\";\nimport { requireInputObject, requireInputKeys, rejectUnknownFields } from \"../core/record-validation.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport interface SensorSiteCenterLatticeRegistration {\n kind: \"regular-rectangular-site-center-lattice\";\n coordinateSystem: \"native-sensor-physical\";\n /** Horizontal centre-to-centre site spacing (pitch) in micrometres. */\n pitchXMicrometers: number;\n /** Vertical centre-to-centre site spacing (pitch) in micrometres. */\n pitchYMicrometers: number;\n /**\n * Centre of the first site, measured from the imaging area's physical\n * top-left corner. +X right, +Y down. Units: micrometres.\n */\n firstSiteCenterFromImagingAreaTopLeftMicrometers: {\n x: number;\n y: number;\n };\n evidence: readonly EvidenceProvenance[];\n}\n\nexport type SensorGeometricSensitiveAperture =\n | {\n /**\n * The light-sensitive area is known to matter, but its shape is not\n * known well enough to use.\n */\n kind: \"unresolved\";\n evidence: readonly EvidenceProvenance[];\n }\n | {\n /**\n * A simple approximation of the sensitive area as a rectangle.\n *\n * The rectangle must stay inside one grid cell, so neighbouring\n * sensitive areas never overlap in schema 0.1.0.\n */\n kind: \"uniform-axis-aligned-rectangle\";\n widthMicrometers: number;\n heightMicrometers: number;\n centerOffsetFromSiteCenterMicrometers: {\n x: number;\n y: number;\n };\n evidence: readonly EvidenceProvenance[];\n };\n\nexport interface SensorSamplingApertureProfile {\n schemaVersion: \"0.1.0\";\n profileId: string;\n colorSamplingProfileId: string;\n colorSamplingBindingId: string;\n evidence: readonly EvidenceProvenance[];\n siteCenterLattice: SensorSiteCenterLatticeRegistration;\n geometricSensitiveAperture: SensorGeometricSensitiveAperture;\n}\n\nexport interface ResolveSensorSamplingApertureInput {\n imagingArea: SensorImagingArea;\n nativeRaster: NativeImageRaster;\n colorSamplingProfile: SensorColorSamplingProfile;\n colorSamplingBindingProfile: NativeEffectiveRasterColorSamplingBindingProfile;\n samplingApertureProfile: SensorSamplingApertureProfile;\n site: NativeColorSamplingSiteIndex;\n}\n\nexport interface NativeSensorPhysicalPointMm {\n x: number;\n y: number;\n}\n\nexport interface NativeSensorPhysicalBoundsMm {\n left: number;\n right: number;\n top: number;\n bottom: number;\n}\n\nexport interface ResolvedSensorSamplingAperture {\n profileId: string;\n colorSamplingProfileId: string;\n colorSamplingBindingId: string;\n coordinateSystem: \"native-sensor-physical\";\n site: NativeColorSamplingSiteIndex;\n channelId: string;\n siteGrid: {\n widthSites: number;\n heightSites: number;\n };\n lattice: {\n pitchXMicrometers: number;\n pitchYMicrometers: number;\n firstSiteCenterFromImagingAreaTopLeftMicrometers: {\n x: number;\n y: number;\n };\n siteCenterFromImagingAreaTopLeftMicrometers: {\n x: number;\n y: number;\n };\n siteCenterFromOpticalAxisMm: NativeSensorPhysicalPointMm;\n nominalCellAreaSquareMicrometers: number;\n };\n geometricSensitiveAperture: {\n kind: \"uniform-axis-aligned-rectangle\";\n widthMicrometers: number;\n heightMicrometers: number;\n centerOffsetFromSiteCenterMicrometers: {\n x: number;\n y: number;\n };\n centerFromOpticalAxisMm: NativeSensorPhysicalPointMm;\n boundsFromOpticalAxisMm: NativeSensorPhysicalBoundsMm;\n areaSquareMicrometers: number;\n geometricSensitiveAreaFractionOfLatticeCell: number;\n spatialWeighting:\n \"uniform-unit-area-average\";\n neighboringGeometricApertureOverlap:\n \"none-by-model\";\n };\n /**\n * Fixed statements of what this result does not establish.\n */\n sitePitchDerivedFromNativeImageRaster: false;\n apertureDerivedFromSitePitch: false;\n antiAliasingResponseIncluded: false;\n microlensSpatialRedistributionIncluded: false;\n chargeDiffusionIncluded: false;\n electricalCrosstalkIncluded: false;\n spectralResponseIncluded: false;\n quantumEfficiencyIncluded: false;\n opticalThroughputIncluded: false;\n radiometricCollectionAreaEstablished: false;\n physicalPhotodiodeGeometryEstablished: false;\n provenance: {\n profile: readonly EvidenceProvenance[];\n lattice: readonly EvidenceProvenance[];\n aperture: readonly EvidenceProvenance[];\n };\n}\n\nfunction requireRecord(\n value: unknown,\n path: string,\n allowedKeys: readonly string[] | null\n): UnknownRecord {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be an object.\"\n );\n }\n if (allowedKeys !== null) rejectUnknownFields(value, allowedKeys, path);\n return value as UnknownRecord;\n}\n\nfunction requireNonEmptyString(\n value: unknown,\n path: string\n): string {\n if (\n typeof value !== \"string\" ||\n value.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n path + \" must be a non-empty string.\"\n );\n }\n return value;\n}\n\nfunction requirePositiveFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value <= 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must be a finite number greater than zero.\"\n );\n }\n return value;\n}\n\nfunction requireFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be a finite number.\"\n );\n }\n return value;\n}\n\nfunction parseSiteCenterLattice(\n value: unknown,\n path: string\n): SensorSiteCenterLatticeRegistration {\n const record = requireRecord(value, path, [\"kind\", \"coordinateSystem\", \"firstSiteCenterFromImagingAreaTopLeftMicrometers\", \"pitchXMicrometers\", \"pitchYMicrometers\", \"evidence\"]);\n\n if (\n record.kind !==\n \"regular-rectangular-site-center-lattice\"\n ) {\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n }\n if (\n record.coordinateSystem !==\n \"native-sensor-physical\"\n ) {\n throw new InvalidConfigurationError(\n path +\n '.coordinateSystem must be \"native-sensor-physical\".'\n );\n }\n\n const firstCenter = requireRecord(\n record.firstSiteCenterFromImagingAreaTopLeftMicrometers,\n path +\n \".firstSiteCenterFromImagingAreaTopLeftMicrometers\", [\"x\", \"y\"]\n );\n\n const firstX = requireFinite(\n firstCenter.x,\n path +\n \".firstSiteCenterFromImagingAreaTopLeftMicrometers.x\"\n );\n const firstY = requireFinite(\n firstCenter.y,\n path +\n \".firstSiteCenterFromImagingAreaTopLeftMicrometers.y\"\n );\n\n if (firstX < 0 || firstY < 0) {\n throw new InvalidConfigurationError(\n path +\n \".firstSiteCenterFromImagingAreaTopLeftMicrometers must not be negative.\"\n );\n }\n\n return {\n kind:\n \"regular-rectangular-site-center-lattice\",\n coordinateSystem:\n \"native-sensor-physical\",\n pitchXMicrometers:\n requirePositiveFinite(\n record.pitchXMicrometers,\n path + \".pitchXMicrometers\"\n ),\n pitchYMicrometers:\n requirePositiveFinite(\n record.pitchYMicrometers,\n path + \".pitchYMicrometers\"\n ),\n firstSiteCenterFromImagingAreaTopLeftMicrometers:\n {\n x: firstX,\n y: firstY\n },\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\nfunction parseGeometricSensitiveAperture(\n value: unknown,\n path: string,\n lattice: SensorSiteCenterLatticeRegistration\n): SensorGeometricSensitiveAperture {\n const record = requireRecord(value, path, [\"kind\", \"widthMicrometers\", \"heightMicrometers\", \"centerOffsetFromSiteCenterMicrometers\", \"evidence\"]);\n\n if (record.kind === \"unresolved\") {\n if (\n record.widthMicrometers !== undefined ||\n record.heightMicrometers !== undefined ||\n record.centerOffsetFromSiteCenterMicrometers !==\n undefined\n ) {\n throw new InvalidConfigurationError(\n path +\n \" unresolved aperture must omit rectangle fields.\"\n );\n }\n return {\n kind: \"unresolved\",\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n }\n\n if (\n record.kind !==\n \"uniform-axis-aligned-rectangle\"\n ) {\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n }\n\n const widthMicrometers =\n requirePositiveFinite(\n record.widthMicrometers,\n path + \".widthMicrometers\"\n );\n const heightMicrometers =\n requirePositiveFinite(\n record.heightMicrometers,\n path + \".heightMicrometers\"\n );\n const offset = requireRecord(\n record.centerOffsetFromSiteCenterMicrometers,\n path +\n \".centerOffsetFromSiteCenterMicrometers\", [\"x\", \"y\"]\n );\n const offsetX = requireFinite(\n offset.x,\n path +\n \".centerOffsetFromSiteCenterMicrometers.x\"\n );\n const offsetY = requireFinite(\n offset.y,\n path +\n \".centerOffsetFromSiteCenterMicrometers.y\"\n );\n\n const halfCellX =\n lattice.pitchXMicrometers / 2;\n const halfCellY =\n lattice.pitchYMicrometers / 2;\n\n if (\n Math.abs(offsetX) +\n widthMicrometers / 2 >\n halfCellX ||\n Math.abs(offsetY) +\n heightMicrometers / 2 >\n halfCellY\n ) {\n throw new InvalidConfigurationError(\n path +\n \" rectangle must remain within one regular site lattice cell.\"\n );\n }\n\n return {\n kind:\n \"uniform-axis-aligned-rectangle\",\n widthMicrometers,\n heightMicrometers,\n centerOffsetFromSiteCenterMicrometers:\n {\n x: offsetX,\n y: offsetY\n },\n evidence: parseEvidenceList(\n record.evidence,\n path + \".evidence\"\n )\n };\n}\n\n/**\n * Parses where the colour-sampling sites sit physically, plus a simple geometric\n * model of each site's light-sensitive area.\n *\n * Site pitch and origin are declared facts, never derived from NativeImageRaster.\n * The rectangle, when given, is a geometric sensitive region only. It does not\n * include microlens effects, charge diffusion, electrical crosstalk, QE,\n * throughput, spectral response or an effective light-collection area.\n */\nexport function parseSensorSamplingApertureProfile(\n value: unknown\n): SensorSamplingApertureProfile {\n const profile = requireRecord(\n value,\n \"sensorSamplingAperture\", [\"schemaVersion\", \"siteCenterLattice\", \"profileId\", \"colorSamplingProfileId\", \"colorSamplingBindingId\", \"evidence\", \"geometricSensitiveAperture\"]\n );\n\n if (profile.schemaVersion !== \"0.1.0\") {\n throw new InvalidConfigurationError(\n 'sensorSamplingAperture.schemaVersion must be \"0.1.0\".'\n );\n }\n\n const siteCenterLattice =\n parseSiteCenterLattice(\n profile.siteCenterLattice,\n \"sensorSamplingAperture.siteCenterLattice\"\n );\n\n return {\n schemaVersion: \"0.1.0\",\n profileId: requireNonEmptyString(\n profile.profileId,\n \"sensorSamplingAperture.profileId\"\n ),\n colorSamplingProfileId:\n requireNonEmptyString(\n profile.colorSamplingProfileId,\n \"sensorSamplingAperture.colorSamplingProfileId\"\n ),\n colorSamplingBindingId:\n requireNonEmptyString(\n profile.colorSamplingBindingId,\n \"sensorSamplingAperture.colorSamplingBindingId\"\n ),\n evidence: parseEvidenceList(\n profile.evidence,\n \"sensorSamplingAperture.evidence\"\n ),\n siteCenterLattice,\n geometricSensitiveAperture:\n parseGeometricSensitiveAperture(\n profile.geometricSensitiveAperture,\n \"sensorSamplingAperture.geometricSensitiveAperture\",\n siteCenterLattice\n )\n };\n}\n\nfunction requireSiteIndex(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isSafeInteger(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be a non-negative safe integer color-sampling-site index.\"\n );\n }\n return value;\n}\n\n/**\n * Relative rounding allowance for the lattice and aperture fit tests against the imaging area.\n *\n * The imaging extent (`widthMm · 1000`) and the far aperture edge (first centre + (n − 1)·pitch +\n * offset + width/2) reach the same real number through different chains of at most about eight\n * IEEE-754 double operations, each with a relative rounding error of at most ε/2 of a magnitude no\n * larger than the extent. A geometry that fits exactly can therefore miss by a few units in the last\n * place: a 34-site row of 9.287925696594426 µm sites gives 315.78947368421046 µm of imaging width but\n * a far aperture edge at 315.7894736842105 µm. The fit tests allow 8 ε of the extent (about 1.8e-15\n * relative; 5e-11 µm on a 30 mm sensor), far below any physical aperture difference, and still reject\n * every genuinely oversized lattice or aperture.\n */\nexport const APERTURE_FIT_RELATIVE_TOLERANCE = 8 * Number.EPSILON;\n\n/** The fit-test allowance in micrometres for an imaging extent in micrometres. */\nexport function apertureFitAllowanceMicrometers(extentMicrometers: number): number {\n return APERTURE_FIT_RELATIVE_TOLERANCE * extentMicrometers;\n}\n\nfunction requirePositiveImagingArea(\n imagingArea: SensorImagingArea\n): void {\n requireInputObject(imagingArea, \"imagingArea input\");\n if (\n !Number.isFinite(imagingArea.widthMm) ||\n imagingArea.widthMm <= 0 ||\n !Number.isFinite(imagingArea.heightMm) ||\n imagingArea.heightMm <= 0\n ) {\n throw new InvalidScientificInputError(\n \"imagingArea width/height must be finite and greater than zero.\"\n );\n }\n}\n\n/**\n * Resolves one site's geometric sampling aperture in native sensor physical space.\n *\n * The result is a normalized footprint that averages evenly over its area, for\n * image sampling, plus its physical area and fill fraction. That geometric area\n * is deliberately not treated as light-collection efficiency or as the true\n * photodiode shape.\n */\nexport function resolveSensorSamplingAperture(\n input: ResolveSensorSamplingApertureInput\n): ResolvedSensorSamplingAperture {\n requireInputKeys(input, \"resolveSensorSamplingAperture\", [\"imagingArea\", \"nativeRaster\", \"colorSamplingProfile\", \"colorSamplingBindingProfile\", \"samplingApertureProfile\", \"site\"]);\n requirePositiveImagingArea(\n input.imagingArea\n );\n\n const profile =\n parseSensorSamplingApertureProfile(\n input.samplingApertureProfile\n );\n const binding =\n resolveNativeEffectiveRasterColorSamplingBinding(\n {\n nativeRaster: input.nativeRaster,\n colorSamplingProfile:\n input.colorSamplingProfile,\n bindingProfile:\n input.colorSamplingBindingProfile\n }\n );\n\n if (\n profile.colorSamplingProfileId !==\n binding.colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"Sampling-aperture colorSamplingProfileId must match the resolved color-sampling profile.\"\n );\n }\n if (\n profile.colorSamplingBindingId !==\n binding.bindingId\n ) {\n throw new InvalidScientificInputError(\n \"Sampling-aperture colorSamplingBindingId must match the resolved color-sampling binding.\"\n );\n }\n\n if (\n profile.geometricSensitiveAperture.kind ===\n \"unresolved\"\n ) {\n throw new InvalidScientificInputError(\n \"Geometric sensitive aperture is unresolved; a spatial sampling aperture cannot be produced.\"\n );\n }\n\n const siteX = requireSiteIndex(\n input.site.x,\n \"site.x\"\n );\n const siteY = requireSiteIndex(\n input.site.y,\n \"site.y\"\n );\n\n if (\n siteX >=\n binding.colorSamplingSiteGrid.widthSites ||\n siteY >=\n binding.colorSamplingSiteGrid.heightSites\n ) {\n throw new InvalidScientificInputError(\n \"site must lie within the bound color-sampling site grid.\"\n );\n }\n\n const lattice =\n profile.siteCenterLattice;\n const gridWidth =\n binding.colorSamplingSiteGrid.widthSites;\n const gridHeight =\n binding.colorSamplingSiteGrid.heightSites;\n const imagingWidthMicrometers =\n input.imagingArea.widthMm * 1000;\n const imagingHeightMicrometers =\n input.imagingArea.heightMm * 1000;\n\n const lastCenterX =\n lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n .x +\n (gridWidth - 1) *\n lattice.pitchXMicrometers;\n const lastCenterY =\n lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n .y +\n (gridHeight - 1) *\n lattice.pitchYMicrometers;\n\n const allowanceX = apertureFitAllowanceMicrometers(\n imagingWidthMicrometers\n );\n const allowanceY = apertureFitAllowanceMicrometers(\n imagingHeightMicrometers\n );\n\n if (\n lastCenterX > imagingWidthMicrometers + allowanceX ||\n lastCenterY > imagingHeightMicrometers + allowanceY\n ) {\n throw new InvalidScientificInputError(\n \"Declared site-center lattice extends beyond the supplied physical imaging area.\"\n );\n }\n\n const aperture =\n profile.geometricSensitiveAperture;\n const apertureOffsetX =\n aperture.centerOffsetFromSiteCenterMicrometers.x;\n const apertureOffsetY =\n aperture.centerOffsetFromSiteCenterMicrometers.y;\n const apertureHalfWidth =\n aperture.widthMicrometers / 2;\n const apertureHalfHeight =\n aperture.heightMicrometers / 2;\n\n const firstApertureLeft =\n lattice.firstSiteCenterFromImagingAreaTopLeftMicrometers.x +\n apertureOffsetX -\n apertureHalfWidth;\n const firstApertureTop =\n lattice.firstSiteCenterFromImagingAreaTopLeftMicrometers.y +\n apertureOffsetY -\n apertureHalfHeight;\n const lastApertureRight =\n lastCenterX +\n apertureOffsetX +\n apertureHalfWidth;\n const lastApertureBottom =\n lastCenterY +\n apertureOffsetY +\n apertureHalfHeight;\n\n if (\n firstApertureLeft < -allowanceX ||\n firstApertureTop < -allowanceY ||\n lastApertureRight >\n imagingWidthMicrometers + allowanceX ||\n lastApertureBottom >\n imagingHeightMicrometers + allowanceY\n ) {\n throw new InvalidScientificInputError(\n \"Declared geometric sensitive aperture does not fit within the supplied physical imaging area across the complete site grid.\"\n );\n }\n\n const siteCenterFromTopLeftX =\n lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n .x +\n siteX * lattice.pitchXMicrometers;\n const siteCenterFromTopLeftY =\n lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n .y +\n siteY * lattice.pitchYMicrometers;\n\n const apertureCenterFromTopLeftX =\n siteCenterFromTopLeftX +\n aperture\n .centerOffsetFromSiteCenterMicrometers\n .x;\n const apertureCenterFromTopLeftY =\n siteCenterFromTopLeftY +\n aperture\n .centerOffsetFromSiteCenterMicrometers\n .y;\n const apertureLeftTopX =\n apertureCenterFromTopLeftX -\n aperture.widthMicrometers / 2;\n const apertureRightBottomX =\n apertureCenterFromTopLeftX +\n aperture.widthMicrometers / 2;\n const apertureLeftTopY =\n apertureCenterFromTopLeftY -\n aperture.heightMicrometers / 2;\n const apertureRightBottomY =\n apertureCenterFromTopLeftY +\n aperture.heightMicrometers / 2;\n\n if (\n apertureLeftTopX < -allowanceX ||\n apertureLeftTopY < -allowanceY ||\n apertureRightBottomX >\n imagingWidthMicrometers + allowanceX ||\n apertureRightBottomY >\n imagingHeightMicrometers + allowanceY\n ) {\n throw new InvalidScientificInputError(\n \"Resolved geometric sensitive aperture extends beyond the supplied physical imaging area.\"\n );\n }\n\n const siteCenterFromOpticalAxisMm = {\n x:\n -input.imagingArea.widthMm / 2 +\n siteCenterFromTopLeftX / 1000,\n y:\n -input.imagingArea.heightMm / 2 +\n siteCenterFromTopLeftY / 1000\n };\n const apertureCenterFromOpticalAxisMm = {\n x:\n -input.imagingArea.widthMm / 2 +\n apertureCenterFromTopLeftX / 1000,\n y:\n -input.imagingArea.heightMm / 2 +\n apertureCenterFromTopLeftY / 1000\n };\n const boundsFromOpticalAxisMm = {\n left:\n -input.imagingArea.widthMm / 2 +\n apertureLeftTopX / 1000,\n right:\n -input.imagingArea.widthMm / 2 +\n apertureRightBottomX / 1000,\n top:\n -input.imagingArea.heightMm / 2 +\n apertureLeftTopY / 1000,\n bottom:\n -input.imagingArea.heightMm / 2 +\n apertureRightBottomY / 1000\n };\n\n const areaSquareMicrometers =\n aperture.widthMicrometers *\n aperture.heightMicrometers;\n const nominalCellAreaSquareMicrometers =\n lattice.pitchXMicrometers *\n lattice.pitchYMicrometers;\n const geometricSensitiveAreaFractionOfLatticeCell =\n areaSquareMicrometers /\n nominalCellAreaSquareMicrometers;\n\n if (\n !Number.isFinite(areaSquareMicrometers) ||\n !Number.isFinite(\n nominalCellAreaSquareMicrometers\n ) ||\n !Number.isFinite(\n geometricSensitiveAreaFractionOfLatticeCell\n )\n ) {\n throw new InvalidScientificInputError(\n \"Derived sampling-aperture geometry must remain finite.\"\n );\n }\n\n const channel =\n resolveColorSamplingSite({\n profile: input.colorSamplingProfile,\n site: {\n x: siteX,\n y: siteY\n }\n });\n\n return {\n profileId: profile.profileId,\n colorSamplingProfileId:\n profile.colorSamplingProfileId,\n colorSamplingBindingId:\n profile.colorSamplingBindingId,\n coordinateSystem:\n \"native-sensor-physical\",\n site: {\n x: siteX,\n y: siteY\n },\n channelId: channel.mapping.channelId,\n siteGrid: {\n widthSites: gridWidth,\n heightSites: gridHeight\n },\n lattice: {\n pitchXMicrometers:\n lattice.pitchXMicrometers,\n pitchYMicrometers:\n lattice.pitchYMicrometers,\n firstSiteCenterFromImagingAreaTopLeftMicrometers:\n {\n ...lattice\n .firstSiteCenterFromImagingAreaTopLeftMicrometers\n },\n siteCenterFromImagingAreaTopLeftMicrometers:\n {\n x: siteCenterFromTopLeftX,\n y: siteCenterFromTopLeftY\n },\n siteCenterFromOpticalAxisMm,\n nominalCellAreaSquareMicrometers\n },\n geometricSensitiveAperture: {\n kind:\n \"uniform-axis-aligned-rectangle\",\n widthMicrometers:\n aperture.widthMicrometers,\n heightMicrometers:\n aperture.heightMicrometers,\n centerOffsetFromSiteCenterMicrometers:\n {\n ...aperture\n .centerOffsetFromSiteCenterMicrometers\n },\n centerFromOpticalAxisMm:\n apertureCenterFromOpticalAxisMm,\n boundsFromOpticalAxisMm,\n areaSquareMicrometers,\n geometricSensitiveAreaFractionOfLatticeCell,\n spatialWeighting:\n \"uniform-unit-area-average\",\n neighboringGeometricApertureOverlap:\n \"none-by-model\"\n },\n sitePitchDerivedFromNativeImageRaster:\n false,\n apertureDerivedFromSitePitch: false,\n antiAliasingResponseIncluded: false,\n microlensSpatialRedistributionIncluded:\n false,\n chargeDiffusionIncluded: false,\n electricalCrosstalkIncluded: false,\n spectralResponseIncluded: false,\n quantumEfficiencyIncluded: false,\n opticalThroughputIncluded: false,\n radiometricCollectionAreaEstablished:\n false,\n physicalPhotodiodeGeometryEstablished:\n false,\n provenance: {\n profile: profile.evidence,\n lattice: lattice.evidence,\n aperture: aperture.evidence\n }\n };\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  # Public API reference
2
2
 
3
- Generated from the exact root exports for **@photivra/engine 2.1.0 / root contract 2.1.0** by `node scripts/generate-api-reference.mjs`. No module deep import is supported.
3
+ Generated from the exact root exports for **@photivra/engine 2.2.0 / root contract 2.2.0** by `node scripts/generate-api-reference.mjs`. No module deep import is supported.
4
4
 
5
5
  Use [the developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md) for executable paths and scientific boundaries. Types are contracts, not runtime validation: parse untrusted data, preserve units/reference frames, read assessment blockers, and retain evidence/limitations. Optional properties do not imply a universal default. Model/schema IDs remain independent of the distribution.
6
6
 
@@ -722,6 +722,15 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
722
722
  | [`ImageFormationStageId`](api/core-image-formation.md#imageformationstageid) | Type |
723
723
  | [`ImageFormationTemporalContract`](api/core-image-formation.md#imageformationtemporalcontract) | Type |
724
724
 
725
+ ## core/pixel-limits.ts
726
+
727
+ [Detailed contracts](api/core-pixel-limits.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/core/pixel-limits.ts)
728
+
729
+ | Export | Kind |
730
+ | --- | --- |
731
+ | [`FULL_RESOLUTION_CEILING_PIXELS`](api/core-pixel-limits.md#full_resolution_ceiling_pixels) | Runtime |
732
+ | [`FULL_RESOLUTION_DEFAULT_PIXELS`](api/core-pixel-limits.md#full_resolution_default_pixels) | Runtime |
733
+
725
734
  ## core/scientific-assurance.ts
726
735
 
727
736
  [Detailed contracts](api/core-scientific-assurance.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/core/scientific-assurance.ts)
@@ -1945,6 +1954,7 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
1945
1954
  | --- | --- |
1946
1955
  | [`createPrintJpegTask`](api/output-print-jpeg.md#createprintjpegtask) | Runtime |
1947
1956
  | [`parsePrintJpegInput`](api/output-print-jpeg.md#parseprintjpeginput) | Runtime |
1957
+ | [`PRINT_JPEG_LIMITS`](api/output-print-jpeg.md#print_jpeg_limits) | Runtime |
1948
1958
  | [`PRINT_JPEG_SCHEMA_VERSION`](api/output-print-jpeg.md#print_jpeg_schema_version) | Runtime |
1949
1959
  | [`PrintJpegInput`](api/output-print-jpeg.md#printjpeginput) | Type |
1950
1960
  | [`PrintJpegOutput`](api/output-print-jpeg.md#printjpegoutput) | Type |
@@ -80,7 +80,8 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
80
80
  | `subjectDistanceM` | 5 | Distance of the scene plane. |
81
81
  | `illuminant` | Planckian 6500 K | Also `"cie-a"`, `{ kind: "planckian", temperatureKelvin }`, `{ kind: "daylight", correlatedColorTemperatureKelvin, basis }` or `{ kind: "spectrum", spectrum }`. |
82
82
  | `illuminanceLux` | 21 481 (π·80 000/11.7) | See [Exposure](#exposure). |
83
- | `output.width`, `output.height` | 64 × 64 | Output pixels. The sensor has one extra site on each side for demosaicing (see `output.borderHandling`). |
83
+ | `output.width`, `output.height` | 64 × 64 | Output pixels, 1–4094 each. The sensor has one extra site on each side for demosaicing (see `output.borderHandling`). |
84
+ | `output.maximumPixels` | off | Opt-in large images (2.2): the most native sensor sites the capture may allocate, up to `NATIVE_RAW_LIMITS.ceilingPixels` (2^27). Sides may then reach 16 384 sites including the margin (see [Large images](#large-images)). |
84
85
  | `output.format` | `"srgb-8"` | Also `"srgb-16"`, `"jpeg"`, `"png"` and `"dng"` (see [Output formats](#output-formats)). |
85
86
  | `output.colorEncoding` | `"srgb"` | PNG only: also `"display-p3"` and `"rec2020"`. |
86
87
  | `output.bitDepth` | 8 | PNG only: 8 or 16. |
@@ -93,7 +94,7 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
93
94
  | `output.highlightMode` | `"clip"` | Or `"reconstruct"`. |
94
95
  | `output.whiteBalance` | `"scene-illuminant"` | Also `"auto"`, `"none"`, `{ kelvin, tint? }` and `{ channelGains }`. |
95
96
  | `output.jpegQuantizationStep` | 2 | JPEG only: constant step 1–255, not a manufacturer quality scale. |
96
- | `sensor.pixelPitchMicrometers` | 6 | The sensor is `width × pitch` wide (see [Geometry](#geometry)). |
97
+ | `sensor.pixelPitchMicrometers` | 6 | 0.5–100. The sensor is `width × pitch` wide (see [Geometry](#geometry)); the ISO readout scales with the pixel area (see [ISO and pixel size](#iso-and-pixel-size)). |
97
98
  | `sensor.spectralStepNanometers` | 20 | Spectral quadrature over 380–780 nm. |
98
99
  | `sensor.temporalSampleCount` | 1, or 8 when the camera rotates | Midpoints across each site's exposure window. |
99
100
  | `sensor.noiseSeed` | 1 | Output is deterministic for a given seed. |
@@ -121,6 +122,39 @@ pixels is therefore a 0.38 mm crop of a large sensor, not a downscaled full fram
121
122
  signal per pixel, and so the noise and the ISO behaviour, physically consistent with the tier's
122
123
  sensor values.
123
124
 
125
+ ### ISO and pixel size
126
+
127
+ The tier's ISO signal chain (conversion gain, saturation signal and read noise per ISO setting) is
128
+ declared for the default 6 µm pixel. At other pitches the facade keeps the ISO calibration
129
+ independent of pixel size, so the same exposure gives the same image brightness at any pitch:
130
+
131
+ - Standard output sensitivity S = 10/H (CIPA DC-004-2020 §3.2) is defined by the camera's output
132
+ for a focal-plane exposure H; it does not depend on pixel size.
133
+ - The charge a pixel collects is proportional to its area: the photon count is μp = A·E·t/(hν)
134
+ (EMVA Standard 1288 Release 4.0 Linear, §2.1, Eq. 3, free).
135
+ - So the facade multiplies the system conversion gain (e⁻ per code) and the pre-ADC saturation
136
+ signal of every ISO regime by the area ratio pitch²/36 µm². Codes, black level, ADC and digital gain
137
+ are unchanged. At 6 µm the ratio is exactly 1 and nothing is changed (byte-identical to 2.1.0).
138
+ - Read noise (in e⁻) and the synthetic dark current (1 e⁻/s) stay per pixel.
139
+
140
+ What you see: brightness stays the same, and per-pixel noise rises as pixels shrink, because a
141
+ smaller pixel collects fewer electrons for the same exposure (shot-noise SNR = √N_e). At f/8, 1/125 s,
142
+ ISO 100 on an 18 % grey card, the 8-bit sRGB mean is 101.2 at 30, 6 and 3 µm, and the green standard
143
+ deviation is 0.18, 0.93 and 1.83 codes. Below about 1 µm the 8-bit sRGB mean falls slightly (about 1 code at 0.55 µm)
144
+ although the linear RAW mean is unchanged (within 0.3 %): strong noise passed through the concave
145
+ sRGB curve lowers the mean of the encoded values.
146
+
147
+ The charge capacity follows the scaled base-ISO saturation signal (see `sensor.capacityElectrons`),
148
+ so it is proportional to pixel area: 60 000 e⁻ at 6 µm, 15 000 e⁻ at 3 µm, 1 500 000 e⁻ at 30 µm.
149
+ This is a consequence of holding the tier's ISO rating and its half stop of highlight headroom fixed,
150
+ **not a measured full-well model**. Published sensor data show full-well capacity growing with pixel
151
+ size but less than in proportion to area across process generations (for example about 16 000 e⁻ at
152
+ 2 µm and 37 500 e⁻ at 5.2 µm in Farrell, Xiao & Kavusi, *Proc. SPIE* 6069, 2006, Table 1; Chen et al.,
153
+ *Proc. SPIE* 3965, 2000, model the photodiode capacitance as roughly linear in pixel side). A real
154
+ large-pixel sensor may therefore saturate earlier, and a real small-pixel sensor would usually be
155
+ given a higher base ISO. When the pitch is not 6 µm, the assumption `sensor.isoAreaNormalization`
156
+ records the area ratio and this approximation.
157
+
124
158
  ## Exposure
125
159
 
126
160
  The facade meters the scene like a reflected-light meter:
@@ -223,7 +257,8 @@ These choices appear in `assumptions` as `kind: "model"` entries:
223
257
  you supply; diffraction of an ideal lens only with `lens.diffraction` (2.1).
224
258
  - **Sensor.** Square pixels with a fill factor of 1 and no anti-aliasing filter. A global
225
259
  electronic shutter unless `motion.shutter` is `"rolling"`. A synthetic uniform dark current of
226
- 1 e⁻/s at 20 °C (the tiers declare none). The charge capacity equals the body's base-ISO saturation signal.
260
+ 1 e⁻/s at 20 °C (the tiers declare none). The charge capacity equals the body's base-ISO saturation signal,
261
+ scaled with pixel area away from 6 µm (see [ISO and pixel size](#iso-and-pixel-size)).
227
262
  - **Demosaic.** Bilinear RGGB interpolation through the existing linear-kernel contract, unless
228
263
  `output.demosaic` selects a built-in kernel or AHD. (Demosaicing fills in the two missing colours at each
229
264
  pixel of a colour-filter mosaic.) Sharp edges without lens blur therefore show the usual zipper and
@@ -617,6 +652,75 @@ const probe = await simulateCapture({ scene: { preset: "slanted-edge" }, output:
617
652
  console.log(probe.diagnostics.options, probe.diagnostics.timingsMs.total, probe.diagnostics.timingsMs.demosaic);
618
653
  ```
619
654
 
655
+ ## Large images
656
+
657
+ By default a capture is at most 4094 × 4094 output pixels and every full-resolution stage admits at most
658
+ 24 000 000 pixels (PNG: 100 000 000). Large images are slow and memory-hungry, so from 2.2 they are an
659
+ opt-in that the application decides on, for example after checking the device:
660
+
661
+ - `simulateCapture`: `output.maximumPixels`, the most native sensor sites (output plus the demosaic margin
662
+ on each side) the capture may allocate. A 12 252 × 8 168 output with the default margin needs
663
+ 12 254 × 8 170 = 100 115 180; passing `NATIVE_RAW_LIMITS.ceilingPixels` is simplest. Each side may then
664
+ reach 16 384 sites including the margin. `output.demosaic: "ahd"` still admits at most 2^26 = 67 108 864
665
+ native sites, checked before any work.
666
+ - The stages directly: `maximumPixels` on `calculateNativeRawPlan` / `createNativeRawTask` (and so on every
667
+ native environment and development task built from that input), `createNativeCaptureSdrTask`,
668
+ `calculateIrradianceConvolution`, `calculateTemporalExposureSum`, `createPrintJpegTask` and
669
+ `serializePngImage`; `maximumRegionSites` on `simulatePackedSensorRaw`; `maximumQueries` on
670
+ `calculateChartRadianceBatch`.
671
+
672
+ Each value must be an integer from 1 to the stage's ceiling (`ceilingPixels`, `ceilingRegionSites`,
673
+ `ceilingQueries`), all 2^27 = 134 217 728 = 16 384 × 8 192. That covers 12 252 × 8 168 (100.07 MP) and
674
+ 11 552 × 8 664 (100.09 MP) sensors plus their margin with about a third to spare. It is also the size at
675
+ which the pipeline's largest single array, the interleaved float64 RGB of the development (24 bytes per
676
+ pixel), reaches 3 GiB: JavaScript engines cap an `ArrayBuffer` at about 4 GiB or less, so a higher ceiling
677
+ could not be honoured anyway. The per-side limit (16 384) and every byte budget (`maximumOutputBytes` and
678
+ similar) still apply. A raised native RAW limit raises the native environment whole-event query budget
679
+ ceiling in proportion (2 000 000 000 per 24 000 000 pixels), and a raised Print JPEG limit raises its
680
+ `maximumEncodedBytes` ceiling the same way (256 000 000 bytes per 24 000 000 pixels). Leaving the inputs out
681
+ changes nothing: requests, plans and output bytes stay as they were.
682
+
683
+ ```ts
684
+ import { NATIVE_RAW_LIMITS, simulateCapture } from "@photivra/engine";
685
+
686
+ // The same capture with large images allowed; at 16 × 16 the output is identical to the default.
687
+ const large = await simulateCapture({ scene: { preset: "slanted-edge" }, output: { width: 16, height: 16, maximumPixels: NATIVE_RAW_LIMITS.ceilingPixels } });
688
+ console.log(large.plan.maximumPixels, large.plan.pixelCount);
689
+ ```
690
+
691
+ **Measured cost.** One fresh Node process per stage and size (Node 24.21.0, 4-vCPU Intel Xeon 2.80 GHz,
692
+ Linux, one run each, single thread). Synthetic inputs: a gradient with pseudo-random noise for the
693
+ encoders, repeating patterns elsewhere; the PNG and JPEG times depend on the image content. *Peak
694
+ memory* is the process's peak resident memory minus the idle runtime (about 125 MB), per pixel; it
695
+ includes the stage's input and output arrays. Not a performance claim, but it shows how the cost grows.
696
+
697
+ | Stage (as measured) | 1 MP | 24 MP | 100.07 MP | Per MP | Peak memory per pixel |
698
+ | --- | --- | --- | --- | --- | --- |
699
+ | Irradiance convolution, radius 2 (25 taps), float32 in | 0.32 s | 6.5 s | 27.7 s | 0.28 s | 12 B (4 in, 8 out) |
700
+ | Temporal exposure sum, 8 nodes | 0.56 s | 9.7 s | 42.0 s | 0.42 s | 12 B (4 in, 8 out) |
701
+ | Packed sensor RAW (`simulatePackedSensorRaw`) | 0.60 s | 11.9 s | 48.2 s | 0.48 s | 11 B measured; the all-zero dark plane was never touched, so count 16 B of input plus the output |
702
+ | Chart radiance batch, one wavelength | 0.13 s | 2.6 s | 13.0 s | 0.13 s | 17 B (9 in, 8 out) |
703
+ | PNG, 8-bit sRGB | 0.31 s | 2.7 s | 22.7 s | 0.23 s | 8 B (3 in) |
704
+ | PNG, 16-bit sRGB | 0.57 s | 5.1 s | 40.7 s | 0.41 s | 15 B (6 in) |
705
+ | Print JPEG, 1:1, quantization step 2 | 0.84 s | 19.5 s | 69.7 s | 0.70 s | 10 B (3 in; 2.6–2.9 B out) |
706
+ | Native capture SDR, 8-bit (tiles from the caller) | 0.07 s | 1.6 s | 5.8 s | 0.06 s | 4 B (3 out) |
707
+ | Native RAW DNG writer | 0.07 s | 1.7 s | 6.4 s | 0.06 s | 17 B (7 in, 2 out) |
708
+ | Native RAW development to 8-bit sRGB | 0.34 s | 5.2 s | 20.5 s | 0.21 s | 10 B (7 in, 3 out) |
709
+ | `simulateCapture`, default options, slanted edge, sRGB 8-bit | 129 s | — (16 MP: 1 973 s) | about 3.4 h (extrapolated) | 123–129 s | 34 B at 16 MP (fixed part about 360 MB) |
710
+
711
+ **What this means for a 100 MP capture.** The full capture was measured at 0.26, 1, 4 and 16 MP: 133, 129, 125 and 123 s per MP, almost all of it
712
+ the per-site RAW simulation. At that rate a 100 MP capture with default options takes about 12 300 s
713
+ (3.4 h) on one core of this host; the options in [Cost of each option](#cost-of-each-option) multiply
714
+ that. The 100 MP time is extrapolated, not measured. Memory: about 360 MB of fixed working state, then a
715
+ peak during development of about 34 bytes per pixel (7 B packed RAW, 24 B float64 linear RGB, 3 B 8-bit
716
+ samples), measured 541 MB above the idle runtime at 16 MP, so about 3.4 GB at 100 MP. JPEG and PNG
717
+ encoding add their own rows above; `whiteBalance: "auto"` renders the RAW twice.
718
+
719
+ Browsers are tighter than Node: a tab may have a few GB at most, a single `ArrayBuffer` may be limited
720
+ to 2–4 GB, and mobile browsers kill tabs earlier. A 100 MP capture can therefore fail in a browser with a
721
+ `RangeError` or an out-of-memory crash even where it runs in Node. Run large captures in a Web Worker,
722
+ pass `signal` so they can be cancelled, and offer them only on devices that can afford them.
723
+
620
724
  ## Not available
621
725
 
622
726
  The facade offers no atmospheric haze or polarizer (see [Optics](#optics)), flash, video, HDR brackets
package/docs/USAGE.md CHANGED
@@ -1911,6 +1911,8 @@ console.log(metrics.value.sampling.pitchYMicrometers); // 6
1911
1911
 
1912
1912
  The returned X/Y sampling pitches are the geometric spacing of image samples. They are not photodiode active area, fill factor or a photon-collection model, and must not be used as those quantities.
1913
1913
 
1914
+ Passing such a pitch to `simulateCapture()` as `sensor.pixelPitchMicrometers` is a supported use, but the photon model then comes from the facade, not from this pitch: the facade declares its own sensor with sites at that spacing and a square sensitive aperture of the same side (fill factor 1, the `sensor.fillFactor` assumption), and scales its ISO readout with that aperture area (see [Quick start: ISO and pixel size](QUICK_START.md#iso-and-pixel-size)). A real sensor of the same pitch may have a smaller sensitive area, microlenses and a different full-well capacity.
1915
+
1914
1916
  The existing `calculatePixelPitch()` API remains available for callers that only need horizontal pitch from sensor width and horizontal pixel count.
1915
1917
 
1916
1918
  ## Sensor architecture metadata
@@ -2428,7 +2430,7 @@ The site-center lattice is a distinct physical registration contract:
2428
2430
  - coordinates are `native-sensor-physical`: optical-axis origin, +X right, +Y down;
2429
2431
  - `pitchXMicrometers` and `pitchYMicrometers` are explicit evidence-backed site-center spacings;
2430
2432
  - the first site's center is explicitly registered relative to the physical imaging area's top-left edge;
2431
- - the full declared lattice must fit inside the supplied physical imaging area.
2433
+ - the full declared lattice, and the sensitive aperture of every site, must fit inside the supplied physical imaging area. The test allows 8 ε (about 1.8 × 10⁻¹⁵) of the imaging extent for floating-point rounding: an imaging width given as `n · pitch / 1000` mm and a far aperture edge computed as `pitch/2 + (n − 1)·pitch + pitch/2` µm reach the same exact value by different rounded operations and can differ in the last bit. Any larger excess is rejected.
2432
2434
 
2433
2435
  Photivra does **not** derive this site pitch or origin from `NativeImageRaster`. The native effective raster and the colour-site lattice already have a structural binding, but physical placement is a separate fact.
2434
2436
 
@@ -29,10 +29,10 @@ This call is synchronous; any supplied provider must follow its explicit synchro
29
29
 
30
30
  ## NATIVE_RAW_LIMITS
31
31
 
32
- The exact contract is declared below; use the domain guide for assumptions and staged integration.
32
+ `maximumPixels` is the default pixel limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`.
33
33
 
34
34
  ```ts
35
- NATIVE_RAW_LIMITS = Object.freeze({ maximumPixels: 24_000_000, maximumDimension: 16_384,
35
+ NATIVE_RAW_LIMITS = Object.freeze({ maximumPixels: 24_000_000, ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS, maximumDimension: 16_384,
36
36
  tileWidth: 256, tileHeight: 1, outputBytesPerPixel: 7 })
37
37
  ```
38
38
 
@@ -76,6 +76,13 @@ export interface NativeRawInput {
76
76
  maximumOutputBytes: number;
77
77
  /** Optional tile width for execution, in native sites, 1–256. Default 256. It does not affect sampling. */
78
78
  tileWidth?: number;
79
+ /**
80
+ * Optional opt-in pixel limit for large sensors: an integer from 1 to `NATIVE_RAW_LIMITS.ceilingPixels`.
81
+ * Default `NATIVE_RAW_LIMITS.maximumPixels` (24 000 000). `maximumOutputBytes` still applies. A raised limit
82
+ * also raises the native environment tasks' whole-event query budget ceiling in proportion
83
+ * (2 000 000 000 per 24 000 000 pixels). It does not affect sampling.
84
+ */
85
+ maximumPixels?: number;
79
86
  }
80
87
  ```
81
88
 
@@ -115,6 +122,8 @@ export interface NativeRawPlan {
115
122
  tileCount: number;
116
123
  /** Present only when an execution tile width was given. Otherwise the plan keeps its earlier shape. */
117
124
  tileWidth?: number;
125
+ /** Present only when a pixel limit was given. Otherwise the plan keeps its earlier shape. */
126
+ maximumPixels?: number;
118
127
  upstreamRadiometryVerified: false;
119
128
  seedSchedule: "murmur3-capture-seed-native-index-stream-v2";
120
129
  }
@@ -14,11 +14,12 @@ This call is synchronous; any supplied provider must follow its explicit synchro
14
14
 
15
15
  ## PACKED_SENSOR_RAW_LIMITS
16
16
 
17
- The exact contract is declared below; use the domain guide for assumptions and staged integration.
17
+ `maximumRegionSites` is the default region limit; a request may raise it with its own `maximumRegionSites`, up to `ceilingRegionSites`.
18
18
 
19
19
  ```ts
20
20
  PACKED_SENSOR_RAW_LIMITS = Object.freeze({
21
21
  maximumRegionSites: 24_000_000,
22
+ ceilingRegionSites: FULL_RESOLUTION_CEILING_PIXELS,
22
23
  maximumNativeSites: 0xffffffff,
23
24
  maximumDimension: 65_535,
24
25
  maximumAdcBitDepth: 16,
@@ -103,6 +104,11 @@ export interface PackedSensorRawInput {
103
104
  fixedPatternNoise?: PackedSensorFixedPatternNoise;
104
105
  /** Optional (2.0), at most 8. */
105
106
  additionalChargeComponents?: readonly PackedSensorAdditionalChargeComponent[];
107
+ /**
108
+ * Optional opt-in region limit for large sensors: an integer from 1 to `PACKED_SENSOR_RAW_LIMITS.ceilingRegionSites`.
109
+ * Default `PACKED_SENSOR_RAW_LIMITS.maximumRegionSites` (24 000 000). It does not change the result.
110
+ */
111
+ maximumRegionSites?: number;
106
112
  }
107
113
  ```
108
114
 
@@ -0,0 +1,19 @@
1
+ # core/pixel-limits.ts public contracts
2
+
3
+ The package and root API versions are recorded once in [Navigation](../API_REFERENCE.md) · [Developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md). Generated signatures retain independent schema/model versions. Only the exports listed here are root-package contracts; module-local helpers are not supported deep imports.
4
+
5
+ ## FULL_RESOLUTION_CEILING_PIXELS
6
+
7
+ Hard ceiling of an opt-in raised pixel limit: 2^27 = 134 217 728 pixels (16 384 × 8 192).
8
+
9
+ ```ts
10
+ FULL_RESOLUTION_CEILING_PIXELS = 134_217_728
11
+ ```
12
+
13
+ ## FULL_RESOLUTION_DEFAULT_PIXELS
14
+
15
+ Default pixel limit of the full-resolution stages.
16
+
17
+ ```ts
18
+ FULL_RESOLUTION_DEFAULT_PIXELS = 24_000_000
19
+ ```
@@ -11,5 +11,5 @@ version they were made with. Proof-of-concept (POC), schema and model contracts
11
11
  own versions. Release checks confirm it matches package.json and the packed package.
12
12
 
13
13
  ```ts
14
- ENGINE_API_VERSION = "2.1.0" as const
14
+ ENGINE_API_VERSION = "2.2.0" as const
15
15
  ```
@@ -1,6 +1,6 @@
1
1
  {
2
- "packageVersion": "2.1.0",
3
- "engineApiVersion": "2.1.0",
2
+ "packageVersion": "2.2.0",
3
+ "engineApiVersion": "2.2.0",
4
4
  "exports": [
5
5
  {
6
6
  "name": "AbsoluteUncertaintyEstimate",
@@ -3062,6 +3062,16 @@
3062
3062
  "source": "src/optics/front-of-lens-filter.ts",
3063
3063
  "kind": "type"
3064
3064
  },
3065
+ {
3066
+ "name": "FULL_RESOLUTION_CEILING_PIXELS",
3067
+ "source": "src/core/pixel-limits.ts",
3068
+ "kind": "runtime"
3069
+ },
3070
+ {
3071
+ "name": "FULL_RESOLUTION_DEFAULT_PIXELS",
3072
+ "source": "src/core/pixel-limits.ts",
3073
+ "kind": "runtime"
3074
+ },
3065
3075
  {
3066
3076
  "name": "FullAutoExposureModeResolution",
3067
3077
  "source": "src/exposure/exposure-mode-resolver.ts",
@@ -5272,6 +5282,11 @@
5272
5282
  "source": "src/output/print-contrast-reference.ts",
5273
5283
  "kind": "runtime"
5274
5284
  },
5285
+ {
5286
+ "name": "PRINT_JPEG_LIMITS",
5287
+ "source": "src/output/print-jpeg.ts",
5288
+ "kind": "runtime"
5289
+ },
5275
5290
  {
5276
5291
  "name": "PRINT_JPEG_SCHEMA_VERSION",
5277
5292
  "source": "src/output/print-jpeg.ts",
@@ -24,10 +24,11 @@ This call is synchronous; any supplied provider must follow its explicit synchro
24
24
 
25
25
  ## CHART_RADIANCE_BATCH_LIMITS
26
26
 
27
- The exact contract is declared below; use the domain guide for assumptions and staged integration.
27
+ `maximumQueries` is the default batch limit; a request may raise it with its own `maximumQueries`, up to `ceilingQueries`.
28
28
 
29
29
  ```ts
30
- CHART_RADIANCE_BATCH_LIMITS = Object.freeze({ maximumQueries: 24_000_000, maximumWavelengths: 256, maximumChannels: 4 })
30
+ CHART_RADIANCE_BATCH_LIMITS = Object.freeze({ maximumQueries: 24_000_000, ceilingQueries: FULL_RESOLUTION_CEILING_PIXELS, maximumWavelengths: 256,
31
+ maximumChannels: 4 })
31
32
  ```
32
33
 
33
34
  ## CHART_RADIANCE_BATCH_SCHEMA_VERSION
@@ -59,6 +60,11 @@ export interface ChartRadianceBatchInput {
59
60
  wavelengthIndices?: Uint8Array | Uint16Array;
60
61
  /** Spectral integration: per channel (1–4), one finite weight per wavelength. */
61
62
  channelWeights?: readonly (readonly number[])[];
63
+ /**
64
+ * Optional opt-in batch limit: an integer from 1 to `CHART_RADIANCE_BATCH_LIMITS.ceilingQueries`. Default
65
+ * `CHART_RADIANCE_BATCH_LIMITS.maximumQueries` (24 000 000). It does not change the values.
66
+ */
67
+ maximumQueries?: number;
62
68
  }
63
69
  ```
64
70
 
@@ -218,6 +218,13 @@ export interface SimulateCaptureOptions {
218
218
  borderHandling?: SimulateCaptureBorderHandling;
219
219
  /** DNG only: optional capture-event time (YYYY-MM-DDTHH:mm:ss.sssZ); without it no date tags are written. */
220
220
  dng?: { capturedAtUtc?: string };
221
+ /**
222
+ * Opt-in large images: the most native sensor sites (the output plus the demosaic margin on each side) the
223
+ * capture may allocate, an integer up to `NATIVE_RAW_LIMITS.ceilingPixels` (2^27). With it, `width` and
224
+ * `height` may each reach 16 384 sites including the margin; without it they stay at most 4094. Time and
225
+ * memory grow with the pixel count (see "Large images" in the quick start); browsers may fail at large sizes.
226
+ */
227
+ maximumPixels?: number;
221
228
  };
222
229
  /** Camera motion and shutter type. Default: still camera, global electronic shutter. */
223
230
  motion?: SimulateCaptureMotion;
@@ -24,11 +24,12 @@ This call is synchronous; any supplied provider must follow its explicit synchro
24
24
 
25
25
  ## TEMPORAL_EXPOSURE_SUM_LIMITS
26
26
 
27
- The exact contract is declared below; use the domain guide for assumptions and staged integration.
27
+ `maximumPixels` is the default grid limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`.
28
28
 
29
29
  ```ts
30
30
  TEMPORAL_EXPOSURE_SUM_LIMITS = Object.freeze({
31
31
  maximumPixels: 24_000_000,
32
+ ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS,
32
33
  maximumDimension: 16_384,
33
34
  maximumTemporalSamples: 256,
34
35
  maximumDisplacementSites: 4_096,
@@ -63,6 +64,11 @@ export interface TemporalExposureSumInput {
63
64
  /** Quadrature weights `[k][row]` (nodes × height), finite and non-negative, for example seconds of each row's window near t_k. */
64
65
  rowWeights: Float32Array | Float64Array;
65
66
  boundary: "zero-outside-grid";
67
+ /**
68
+ * Optional opt-in grid limit for large images: an integer from 1 to `TEMPORAL_EXPOSURE_SUM_LIMITS.ceilingPixels`.
69
+ * Default `TEMPORAL_EXPOSURE_SUM_LIMITS.maximumPixels` (24 000 000). It does not change the result.
70
+ */
71
+ maximumPixels?: number;
66
72
  }
67
73
  ```
68
74
 
@@ -24,11 +24,12 @@ This call is synchronous; any supplied provider must follow its explicit synchro
24
24
 
25
25
  ## IRRADIANCE_CONVOLUTION_LIMITS
26
26
 
27
- The exact contract is declared below; use the domain guide for assumptions and staged integration.
27
+ `maximumPixels` is the default grid limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`.
28
28
 
29
29
  ```ts
30
30
  IRRADIANCE_CONVOLUTION_LIMITS = Object.freeze({
31
31
  maximumPixels: 24_000_000,
32
+ ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS,
32
33
  maximumDimension: 16_384,
33
34
  maximumKernelRadius: 32,
34
35
  maximumPatchCount: 4_096,
@@ -70,6 +71,11 @@ export interface IrradianceConvolutionInput {
70
71
  */
71
72
  kernels: Float32Array | Float64Array;
72
73
  boundary: "zero-outside-grid";
74
+ /**
75
+ * Optional opt-in grid limit for large images: an integer from 1 to `IRRADIANCE_CONVOLUTION_LIMITS.ceilingPixels`.
76
+ * Default `IRRADIANCE_CONVOLUTION_LIMITS.maximumPixels` (24 000 000). It does not change the result.
77
+ */
78
+ maximumPixels?: number;
73
79
  }
74
80
  ```
75
81
 
@@ -24,10 +24,10 @@ This call is synchronous; any supplied provider must follow its explicit synchro
24
24
 
25
25
  ## NATIVE_CAPTURE_SDR_LIMITS
26
26
 
27
- The exact contract is declared below; use the domain guide for assumptions and staged integration.
27
+ `maximumPixels` is the default pixel limit; a request may raise it with its own `maximumPixels`, up to `ceilingPixels`.
28
28
 
29
29
  ```ts
30
- NATIVE_CAPTURE_SDR_LIMITS = Object.freeze({ maximumPixels: 24_000_000,
30
+ NATIVE_CAPTURE_SDR_LIMITS = Object.freeze({ maximumPixels: 24_000_000, ceilingPixels: FULL_RESOLUTION_CEILING_PIXELS,
31
31
  maximumDimension: 16_384, tileWidth: 256, tileHeight: 32, maximumTileStorageSamples: 32_768 })
32
32
  ```
33
33
 
@@ -51,6 +51,11 @@ export interface NativeCaptureSdrInput {
51
51
  profile: SdrRenderingProfile;
52
52
  /** Limit on the size of the integer output. Checked before anything is allocated or any tile is read. */
53
53
  maximumOutputBytes: number;
54
+ /**
55
+ * Optional opt-in pixel limit for large captures: an integer from 1 to `NATIVE_CAPTURE_SDR_LIMITS.ceilingPixels`.
56
+ * Default `NATIVE_CAPTURE_SDR_LIMITS.maximumPixels` (24 000 000). `maximumOutputBytes` still applies.
57
+ */
58
+ maximumPixels?: number;
54
59
  }
55
60
  ```
56
61