@photivra/engine 2.19.0 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +112 -1
  2. package/CITATION.cff +1 -1
  3. package/NODE_SUPPORT.md +7 -5
  4. package/README.md +6 -6
  5. package/SECURITY.md +1 -1
  6. package/THIRD_PARTY.md +14 -10
  7. package/dist/capture/dng-lens-correction-opcodes.d.ts +32 -0
  8. package/dist/capture/dng-lens-correction-opcodes.js +119 -0
  9. package/dist/capture/dng-lens-correction-opcodes.js.map +1 -0
  10. package/dist/capture/native-environment-lean.js +1 -5
  11. package/dist/capture/native-environment-lean.js.map +1 -1
  12. package/dist/capture/native-raw-dng.d.ts +6 -0
  13. package/dist/capture/native-raw-dng.js +16 -2
  14. package/dist/capture/native-raw-dng.js.map +1 -1
  15. package/dist/capture/sensor-raw-producer.js +3 -5
  16. package/dist/capture/sensor-raw-producer.js.map +1 -1
  17. package/dist/core/shared-helpers.d.ts +2 -0
  18. package/dist/core/shared-helpers.js +4 -0
  19. package/dist/core/shared-helpers.js.map +1 -1
  20. package/dist/core/version.d.ts +1 -1
  21. package/dist/core/version.js +1 -1
  22. package/dist/core/version.js.map +1 -1
  23. package/dist/equipment/generic-lens-catalogue.d.ts +8 -2
  24. package/dist/equipment/generic-lens-catalogue.js +31 -2
  25. package/dist/equipment/generic-lens-catalogue.js.map +1 -1
  26. package/dist/equipment/generic-tier-assets.js +2 -3
  27. package/dist/equipment/generic-tier-assets.js.map +1 -1
  28. package/dist/equipment/generic-tier-presets.js +2 -3
  29. package/dist/equipment/generic-tier-presets.js.map +1 -1
  30. package/dist/facade/display-view-gpu.d.ts +7 -4
  31. package/dist/facade/display-view-gpu.js +4 -4
  32. package/dist/facade/display-view-gpu.js.map +1 -1
  33. package/dist/facade/display-view-lens-signal.d.ts +1 -1
  34. package/dist/facade/display-view-lens-signal.js +5 -3
  35. package/dist/facade/display-view-lens-signal.js.map +1 -1
  36. package/dist/facade/display-view.d.ts +3 -1
  37. package/dist/facade/display-view.js +1 -1
  38. package/dist/facade/display-view.js.map +1 -1
  39. package/dist/facade/fast-methods.d.ts +50 -0
  40. package/dist/facade/fast-methods.js +50 -0
  41. package/dist/facade/fast-methods.js.map +1 -0
  42. package/dist/facade/fast-resolution.d.ts +12 -0
  43. package/dist/facade/fast-resolution.js +18 -0
  44. package/dist/facade/fast-resolution.js.map +1 -0
  45. package/dist/facade/generic-lens-capture.d.ts +3 -2
  46. package/dist/facade/generic-lens-capture.js +3 -2
  47. package/dist/facade/generic-lens-capture.js.map +1 -1
  48. package/dist/facade/simulate-capture.d.ts +51 -11
  49. package/dist/facade/simulate-capture.js +144 -75
  50. package/dist/facade/simulate-capture.js.map +1 -1
  51. package/dist/facade/simulate-display-view.d.ts +3 -2
  52. package/dist/facade/simulate-display-view.js +22 -12
  53. package/dist/facade/simulate-display-view.js.map +1 -1
  54. package/dist/index.d.ts +7 -0
  55. package/dist/index.js +5 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/optics/aperture-diameter.d.ts +47 -0
  58. package/dist/optics/aperture-diameter.js +50 -0
  59. package/dist/optics/aperture-diameter.js.map +1 -0
  60. package/dist/optics/detail-limit.d.ts +182 -0
  61. package/dist/optics/detail-limit.js +336 -0
  62. package/dist/optics/detail-limit.js.map +1 -0
  63. package/dist/optics/generic-lens-psf.d.ts +18 -2
  64. package/dist/optics/generic-lens-psf.js +25 -2
  65. package/dist/optics/generic-lens-psf.js.map +1 -1
  66. package/dist/optics/scene-to-sensor-irradiance.d.ts +13 -0
  67. package/dist/optics/scene-to-sensor-irradiance.js +7 -0
  68. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  69. package/dist/output/crop-to-match-focal-length.d.ts +62 -0
  70. package/dist/output/crop-to-match-focal-length.js +63 -0
  71. package/dist/output/crop-to-match-focal-length.js.map +1 -0
  72. package/dist/output/developed-lens-corrections.js +2 -2
  73. package/dist/output/developed-lens-corrections.js.map +1 -1
  74. package/dist/output/generic-lens-corrections.d.ts +3 -1
  75. package/dist/output/generic-lens-corrections.js +8 -4
  76. package/dist/output/generic-lens-corrections.js.map +1 -1
  77. package/dist/output/lens-corrections.d.ts +12 -2
  78. package/dist/output/lens-corrections.js +22 -7
  79. package/dist/output/lens-corrections.js.map +1 -1
  80. package/dist/output/print-contrast-reference.js +1 -1
  81. package/dist/output/print-contrast-reference.js.map +1 -1
  82. package/dist/output/print-size-blur-noise.d.ts +110 -0
  83. package/dist/output/print-size-blur-noise.js +138 -0
  84. package/dist/output/print-size-blur-noise.js.map +1 -0
  85. package/dist/sensor/eqe-electron-rate.js +1 -7
  86. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  87. package/dist/sensor/exposure-window.d.ts +2 -0
  88. package/dist/sensor/exposure-window.js +2 -2
  89. package/dist/sensor/exposure-window.js.map +1 -1
  90. package/dist/sensor/readout-timing.js +2 -23
  91. package/dist/sensor/readout-timing.js.map +1 -1
  92. package/dist/sensor/response-application-compatibility.js +1 -6
  93. package/dist/sensor/response-application-compatibility.js.map +1 -1
  94. package/dist/sensor/responsivity-photocurrent.js +1 -7
  95. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  96. package/dist/sensor/sensor-characteristics.d.ts +137 -0
  97. package/dist/sensor/sensor-characteristics.js +115 -0
  98. package/dist/sensor/sensor-characteristics.js.map +1 -0
  99. package/dist/sensor/spatial-sample-reduction.js +1 -5
  100. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  101. package/dist/sensor/spectral-quadrature.js +1 -6
  102. package/dist/sensor/spectral-quadrature.js.map +1 -1
  103. package/dist/sensor/temporal-photo-signal.d.ts +3 -0
  104. package/dist/sensor/temporal-photo-signal.js +5 -2
  105. package/dist/sensor/temporal-photo-signal.js.map +1 -1
  106. package/dist/stabilization/system.d.ts +9 -0
  107. package/dist/stabilization/system.js +5 -6
  108. package/dist/stabilization/system.js.map +1 -1
  109. package/docs/AGENT_GUIDE.md +4 -0
  110. package/docs/API_REFERENCE.md +76 -2
  111. package/docs/QUICK_START.md +88 -6
  112. package/docs/USAGE.md +65 -2
  113. package/docs/api/capture-native-raw-dng.md +6 -0
  114. package/docs/api/core-version.md +1 -1
  115. package/docs/api/equipment-generic-lens-catalogue.md +7 -2
  116. package/docs/api/exports.json +152 -2
  117. package/docs/api/facade-display-view-gpu.md +5 -2
  118. package/docs/api/facade-fast-methods.md +91 -0
  119. package/docs/api/facade-simulate-capture.md +36 -11
  120. package/docs/api/facade-simulate-display-view.md +3 -2
  121. package/docs/api/optics-aperture-diameter.md +51 -0
  122. package/docs/api/optics-detail-limit.md +203 -0
  123. package/docs/api/output-crop-to-match-focal-length.md +67 -0
  124. package/docs/api/output-generic-lens-corrections.md +3 -1
  125. package/docs/api/output-lens-corrections.md +11 -1
  126. package/docs/api/output-print-size-blur-noise.md +115 -0
  127. package/docs/api/sensor-sensor-characteristics.md +105 -0
  128. package/llms.txt +4 -0
  129. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"spatial-sample-reduction.js","sourceRoot":"","sources":["../../src/sensor/spatial-sample-reduction.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spatial sample reduction: combines values sampled at the spatial quadrature nodes of one sensor site\n * (its aperture plus the anti-aliasing (AA) split) into a site average and an area integral.\n *\n * Each supplied value is matched to its node by an explicit identity key. Average weights and\n * geometric area measures (in square micrometres) are kept separate. Negative or non-finite irradiance,\n * and missing or duplicate nodes, are rejected. The geometry does not establish QE (quantum\n * efficiency), an effective light-collection area or calibrated light transport.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n SensorSpatialSamplingQuadrature,\n SensorSpatialSamplingQuadratureNode\n} from \"./spatial-sampling-quadrature.js\";\nimport { requireInputKeys, requireInputNonNegativeSafeInteger as requireSafeNonNegativeInteger } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requirePositive as requirePositiveFinite } from \"../exposure/flash-ttl.js\";\n\nconst SQUARE_MICROMETERS_TO_SQUARE_METERS = 1e-12;\nconst WEIGHT_TOLERANCE = 1e-10;\nconst AREA_TOLERANCE = 1e-10;\n\nexport type SensorSpatialSampleValueDomain =\n | {\n kind: \"relative-linear\";\n unit: \"relative\";\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\";\n }\n | {\n kind: \"radiometric-irradiance\";\n unit: \"W/m^2\";\n semantic: \"sensor-plane-irradiance\";\n };\n\nexport interface SensorSpatialQuadratureNodeIdentity {\n antiAliasingComponentIndex: number;\n apertureSampleXIndex: number;\n apertureSampleYIndex: number;\n}\n\nexport interface SensorSpatialQuadratureNodeValue {\n node: SensorSpatialQuadratureNodeIdentity;\n /**\n * A nonnegative, finite number in the declared value domain.\n *\n * It is evaluated at the matching node's preAntiAliasingSourcePointMm.\n */\n value: number;\n}\n\nexport interface ReduceSensorSpatialSamplingQuadratureInput {\n quadrature: SensorSpatialSamplingQuadrature;\n valueDomain: SensorSpatialSampleValueDomain;\n /**\n * Exactly one value per quadrature node, each with its node identity.\n *\n * Order does not matter; the node identity does.\n */\n nodeValues: readonly SensorSpatialQuadratureNodeValue[];\n}\n\nexport type SensorSpatialSampleReductionValue =\n | {\n kind: \"relative-linear\";\n unit: \"relative\";\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\";\n /**\n * Weighted spatial average (dimensionless), in the same relative domain.\n */\n normalizedSpatialAverageRelative: number;\n /**\n * Relative value × square micrometres.\n *\n * A geometric, area-weighted diagnostic, not a radiometric power.\n */\n geometricAreaIntegralRelativeSquareMicrometers: number;\n }\n | {\n kind: \"radiometric-irradiance\";\n unit: \"W/m^2\";\n semantic: \"sensor-plane-irradiance\";\n /**\n * Weighted mean irradiance over the geometric aperture, after the\n * declared normalized AA spreading.\n */\n normalizedSpatialAverageIrradianceWattsPerSquareMeter: number;\n /**\n * Incident radiant flux: the supplied irradiance integrated over the\n * geometric aperture, with the declared normalized AA spreading.\n * Units: watts.\n *\n * It is before any CFA response, QE or microlens response, and excludes\n * any optical-stack throughput not already in the caller's irradiance values.\n */\n geometricApertureIncidentFluxWatts: number;\n };\n\nexport interface SensorSpatialSampleReduction {\n site: {\n x: number;\n y: number;\n };\n /**\n * Label of the destination CFA (colour filter array) site only.\n *\n * This reducer applies no spectral filter response.\n */\n channelId: string;\n outputMeaning:\n \"channel-tagged-pre-response-spatial-sample\";\n valueDomain: SensorSpatialSampleValueDomain;\n nodeCount: number;\n sourceValuesMatchedBy:\n \"quadrature-node-identity\";\n sourceValuesSuppliedForAllNodes: true;\n outsideImagingAreaSourceValuesRequired: boolean;\n normalizedSpatialWeightSum: number;\n geometricApertureAreaSquareMicrometers: number;\n reducedValue: SensorSpatialSampleReductionValue;\n cfaSpectralFilteringApplied: false;\n channelIdIsSpectralResponse: false;\n temporalIntegrationApplied: false;\n exposureDurationApplied: false;\n quantumEfficiencyApplied: false;\n microlensResponseApplied: false;\n opticalThroughputAppliedByReducer: false;\n photonsCalculated: false;\n electronsCalculated: false;\n shotNoiseApplied: false;\n readNoiseApplied: false;\n adcQuantizationApplied: false;\n blackLevelApplied: false;\n saturationApplied: false;\n rawCodeValueProduced: false;\n demosaicOrReconstructionApplied: false;\n}\n\nfunction requireNonNegativeFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be a finite number greater than or equal to zero.\"\n );\n }\n return value;\n}\n\nfunction parseValueDomain(\n value: unknown\n): SensorSpatialSampleValueDomain {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value)\n ) {\n throw new InvalidConfigurationError(\n \"valueDomain must be an object.\"\n );\n }\n\n const record = value as Record<string, unknown>;\n\n if (record.kind === \"relative-linear\") {\n if (\n record.unit !== \"relative\" ||\n record.semantic !==\n \"nonnegative-sensor-plane-irradiance-proxy\"\n ) {\n throw new InvalidScientificInputError(\n \"relative-linear valueDomain must use unit \\\"relative\\\" and the canonical nonnegative sensor-plane irradiance-proxy semantic.\"\n );\n }\n return {\n kind: \"relative-linear\",\n unit: \"relative\",\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\"\n };\n }\n\n if (\n record.kind === \"radiometric-irradiance\"\n ) {\n if (\n record.unit !== \"W/m^2\" ||\n record.semantic !==\n \"sensor-plane-irradiance\"\n ) {\n throw new InvalidScientificInputError(\n \"radiometric-irradiance valueDomain must use unit \\\"W/m^2\\\" and semantic \\\"sensor-plane-irradiance\\\".\"\n );\n }\n return {\n kind: \"radiometric-irradiance\",\n unit: \"W/m^2\",\n semantic: \"sensor-plane-irradiance\"\n };\n }\n\n throw new InvalidConfigurationError(\n \"valueDomain.kind is invalid.\"\n );\n}\n\n/**\n * Makes the deterministic key used to match a value to its spatial quadrature node. It identifies the\n * sample; it is not a cryptographic checksum.\n * @param identity - The input; see `SensorSpatialQuadratureNodeIdentity` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function sensorSpatialQuadratureNodeIdentityKey(\n identity: SensorSpatialQuadratureNodeIdentity\n): string {\n return (\n identity.antiAliasingComponentIndex +\n \":\" +\n identity.apertureSampleXIndex +\n \":\" +\n identity.apertureSampleYIndex\n );\n}\n\nfunction validateQuadratureNode(\n node: SensorSpatialSamplingQuadratureNode,\n index: number\n): SensorSpatialQuadratureNodeIdentity {\n const identity = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n node.antiAliasingComponentIndex,\n \"quadrature.nodes[\" +\n index +\n \"].antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n node.apertureSampleXIndex,\n \"quadrature.nodes[\" +\n index +\n \"].apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n node.apertureSampleYIndex,\n \"quadrature.nodes[\" +\n index +\n \"].apertureSampleYIndex\"\n )\n };\n\n requireNonNegativeFinite(\n node.combinedNormalizedSpatialWeight,\n \"quadrature.nodes[\" +\n index +\n \"].combinedNormalizedSpatialWeight\"\n );\n requireNonNegativeFinite(\n node.combinedAreaMeasureSquareMicrometers,\n \"quadrature.nodes[\" +\n index +\n \"].combinedAreaMeasureSquareMicrometers\"\n );\n\n return identity;\n}\n\nfunction approximatelyEqual(\n actual: number,\n expected: number,\n tolerance: number\n): boolean {\n const scale = Math.max(\n 1,\n Math.abs(actual),\n Math.abs(expected)\n );\n return (\n Math.abs(actual - expected) <=\n tolerance * scale\n );\n}\n\n/**\n * Rechecks node coordinates, weights, geometric measures and coverage before a spatial plan is trusted\n * as scientific input.\n *\n * Average weights and geometric area measures are kept separate. The geometry does not establish QE,\n * an effective light-collection area or calibrated light transport.\n * @param quadrature - The input; see `SensorSpatialSamplingQuadrature` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function validateSensorSpatialSamplingQuadrature(\n quadrature: SensorSpatialSamplingQuadrature\n): Map<string, SensorSpatialSamplingQuadratureNode> {\n if (\n typeof quadrature !== \"object\" ||\n quadrature === null ||\n !Array.isArray(quadrature.nodes)\n ) {\n throw new InvalidConfigurationError(\n \"quadrature must contain a nodes array.\"\n );\n }\n\n const totalNodeCount =\n requireSafeNonNegativeInteger(\n quadrature.totalNodeCount,\n \"quadrature.totalNodeCount\"\n );\n\n if (\n totalNodeCount === 0 ||\n quadrature.nodes.length !==\n totalNodeCount\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.totalNodeCount must be positive and exactly match quadrature.nodes.length.\"\n );\n }\n\n if (\n quadrature.colorSamplingProfileId !== undefined &&\n (\n typeof quadrature.colorSamplingProfileId !== \"string\" ||\n quadrature.colorSamplingProfileId.trim().length === 0\n )\n ) {\n throw new InvalidConfigurationError(\n \"quadrature.colorSamplingProfileId must be a non-empty string when supplied.\"\n );\n }\n\n if (\n typeof quadrature.channelId !== \"string\" ||\n quadrature.channelId.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"quadrature.channelId must be a non-empty string.\"\n );\n }\n\n requireSafeNonNegativeInteger(\n quadrature.site.x,\n \"quadrature.site.x\"\n );\n requireSafeNonNegativeInteger(\n quadrature.site.y,\n \"quadrature.site.y\"\n );\n\n const geometricArea =\n requirePositiveFinite(\n quadrature.geometricApertureAreaSquareMicrometers,\n \"quadrature.geometricApertureAreaSquareMicrometers\"\n );\n\n const nodesByKey =\n new Map<\n string,\n SensorSpatialSamplingQuadratureNode\n >();\n let weightSum = 0;\n let areaSum = 0;\n\n quadrature.nodes.forEach(\n (node, index) => {\n const identity =\n validateQuadratureNode(node, index);\n const key = sensorSpatialQuadratureNodeIdentityKey(identity);\n\n if (nodesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"quadrature contains duplicate node identities.\"\n );\n }\n\n nodesByKey.set(key, node);\n weightSum +=\n node.combinedNormalizedSpatialWeight;\n areaSum +=\n node.combinedAreaMeasureSquareMicrometers;\n\n if (\n !Number.isFinite(weightSum) ||\n !Number.isFinite(areaSum)\n ) {\n throw new InvalidScientificInputError(\n \"quadrature aggregate weights/measures must remain finite.\"\n );\n }\n }\n );\n\n if (\n !approximatelyEqual(\n weightSum,\n 1,\n WEIGHT_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature combined normalized spatial weights must sum to 1.\"\n );\n }\n\n if (\n !approximatelyEqual(\n areaSum,\n geometricArea,\n AREA_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature combined geometric area measures must sum to the geometric aperture area.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.normalizedSpatialWeightSum,\n weightSum,\n WEIGHT_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.normalizedSpatialWeightSum does not match node weights.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.combinedAreaMeasureSumSquareMicrometers,\n areaSum,\n AREA_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.combinedAreaMeasureSumSquareMicrometers does not match node measures.\"\n );\n }\n\n return nodesByKey;\n}\n\nfunction parseNodeValues(\n nodeValues: readonly SensorSpatialQuadratureNodeValue[],\n nodesByKey: ReadonlyMap<\n string,\n SensorSpatialSamplingQuadratureNode\n >\n): Map<string, number> {\n if (!Array.isArray(nodeValues)) {\n throw new InvalidConfigurationError(\n \"nodeValues must be an array.\"\n );\n }\n\n if (\n nodeValues.length !==\n nodesByKey.size\n ) {\n throw new InvalidScientificInputError(\n \"nodeValues must contain exactly one value for every quadrature node.\"\n );\n }\n\n const valuesByKey = new Map<string, number>();\n\n nodeValues.forEach((entry, index) => {\n if (\n typeof entry !== \"object\" ||\n entry === null ||\n typeof entry.node !== \"object\" ||\n entry.node === null\n ) {\n throw new InvalidScientificInputError(\n \"nodeValues[\" +\n index +\n \"] must contain a node identity object.\"\n );\n }\n\n const identity = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n entry.node\n .antiAliasingComponentIndex,\n \"nodeValues[\" +\n index +\n \"].node.antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n entry.node.apertureSampleXIndex,\n \"nodeValues[\" +\n index +\n \"].node.apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n entry.node.apertureSampleYIndex,\n \"nodeValues[\" +\n index +\n \"].node.apertureSampleYIndex\"\n )\n };\n const key = sensorSpatialQuadratureNodeIdentityKey(identity);\n\n if (!nodesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"nodeValues[\" +\n index +\n \"] identifies a node that is not present in quadrature.\"\n );\n }\n if (valuesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"nodeValues contains duplicate node identities.\"\n );\n }\n\n valuesByKey.set(\n key,\n requireNonNegativeFinite(\n entry.value,\n \"nodeValues[\" +\n index +\n \"].value\"\n )\n );\n });\n\n return valuesByKey;\n}\n\n/**\n * Combines nonnegative linear values, each tagged with its node, over a\n * validated sensor spatial quadrature.\n *\n * It does not sample a renderer or optical model itself. Callers evaluate one\n * value at every node's preAntiAliasingSourcePointMm and pass the values with\n * their exact node identity. Order does not matter.\n *\n * Two value domains are supported:\n *\n * - relative-linear: a dimensionless, nonnegative stand-in for irradiance, for\n * deterministic renderer and science regression tests;\n * - radiometric-irradiance: physical irradiance on the sensor plane in W/m^2.\n *\n * The destination site's channelId is a label only. No CFA spectral\n * transmittance or sensitivity is applied, so do not call the output a\n * colour-filtered mosaic sample or a RAW value.\n */\nexport function reduceSensorSpatialSamplingQuadrature(\n input: ReduceSensorSpatialSamplingQuadratureInput\n): CalculationResult<SensorSpatialSampleReduction> {\n requireInputKeys(input, \"reduceSensorSpatialSamplingQuadrature\", [\"quadrature\", \"valueDomain\", \"nodeValues\"]);\n const domain = parseValueDomain(\n input.valueDomain\n );\n const nodesByKey = validateSensorSpatialSamplingQuadrature(\n input.quadrature\n );\n const valuesByKey = parseNodeValues(\n input.nodeValues,\n nodesByKey\n );\n\n let weightedAverage = 0;\n let areaWeightedIntegral = 0;\n\n nodesByKey.forEach((node, key) => {\n const value = valuesByKey.get(key);\n if (value === undefined) {\n throw new InvalidScientificInputError(\n \"A quadrature node is missing its explicit source value.\"\n );\n }\n\n weightedAverage +=\n value *\n node.combinedNormalizedSpatialWeight;\n areaWeightedIntegral +=\n value *\n node.combinedAreaMeasureSquareMicrometers;\n\n if (\n !Number.isFinite(weightedAverage) ||\n !Number.isFinite(areaWeightedIntegral)\n ) {\n throw new InvalidScientificInputError(\n \"Reduced spatial sample must remain finite.\"\n );\n }\n });\n\n const reducedValue:\n SensorSpatialSampleReductionValue =\n domain.kind === \"relative-linear\"\n ? {\n kind: \"relative-linear\",\n unit: \"relative\",\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\",\n normalizedSpatialAverageRelative:\n weightedAverage,\n geometricAreaIntegralRelativeSquareMicrometers:\n areaWeightedIntegral\n }\n : {\n kind: \"radiometric-irradiance\",\n unit: \"W/m^2\",\n semantic:\n \"sensor-plane-irradiance\",\n normalizedSpatialAverageIrradianceWattsPerSquareMeter:\n weightedAverage,\n geometricApertureIncidentFluxWatts:\n areaWeightedIntegral *\n SQUARE_MICROMETERS_TO_SQUARE_METERS\n };\n\n return approximationResult(\n {\n site: {\n ...input.quadrature.site\n },\n channelId:\n input.quadrature.channelId,\n outputMeaning:\n \"channel-tagged-pre-response-spatial-sample\",\n valueDomain: domain,\n nodeCount:\n input.quadrature.totalNodeCount,\n sourceValuesMatchedBy:\n \"quadrature-node-identity\",\n sourceValuesSuppliedForAllNodes: true,\n outsideImagingAreaSourceValuesRequired:\n input.quadrature\n .preAntiAliasingSourceOutsideImagingAreaNodeCount >\n 0,\n normalizedSpatialWeightSum:\n input.quadrature\n .normalizedSpatialWeightSum,\n geometricApertureAreaSquareMicrometers:\n input.quadrature\n .geometricApertureAreaSquareMicrometers,\n reducedValue,\n cfaSpectralFilteringApplied: false,\n channelIdIsSpectralResponse: false,\n temporalIntegrationApplied: false,\n exposureDurationApplied: false,\n quantumEfficiencyApplied: false,\n microlensResponseApplied: false,\n opticalThroughputAppliedByReducer:\n false,\n photonsCalculated: false,\n electronsCalculated: false,\n shotNoiseApplied: false,\n readNoiseApplied: false,\n adcQuantizationApplied: false,\n blackLevelApplied: false,\n saturationApplied: false,\n rawCodeValueProduced: false,\n demosaicOrReconstructionApplied: false\n },\n \"sensor-spatial-sample-reduction\",\n \"1.0.0\",\n [\n \"Input values are nonnegative linear scalars evaluated at each quadrature node's pre-AA source coordinate.\",\n \"Node values are matched by explicit quadrature-node identity rather than positional array order.\",\n \"The destination CFA/color channel is metadata only; no spectral transmittance, sensor spectral responsivity or quantum efficiency is applied.\",\n \"Relative-linear values are dimensionless irradiance-like proxies for deterministic testing and do not become physical radiometry.\",\n \"Radiometric-irradiance values use W/m^2; geometric aperture integration converts square micrometres to square metres to report incident radiant flux in watts.\",\n \"The physical irradiance branch reports geometric-aperture incident flux under the caller-supplied irradiance field and normalized AA spatial redistribution; it does not add omitted optical-stack throughput, microlens response, CFA response or sensor conversion.\",\n \"Every quadrature node requires an explicit source value, including pre-AA source points outside the active imaging area; no automatic clamping, zero-fill, extrapolation or renormalization occurs.\",\n \"Negative, NaN and infinite source values are rejected; signed intermediate color-transform values are outside this pre-response sensor-plane contract.\",\n \"Temporal exposure integration, wavelength integration, photons/electrons, noise, saturation, black level, ADC quantization, RAW code values and reconstruction remain separate future work.\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"spatial-sample-reduction.js","sourceRoot":"","sources":["../../src/sensor/spatial-sample-reduction.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spatial sample reduction: combines values sampled at the spatial quadrature nodes of one sensor site\n * (its aperture plus the anti-aliasing (AA) split) into a site average and an area integral.\n *\n * Each supplied value is matched to its node by an explicit identity key. Average weights and\n * geometric area measures (in square micrometres) are kept separate. Negative or non-finite irradiance,\n * and missing or duplicate nodes, are rejected. The geometry does not establish QE (quantum\n * efficiency), an effective light-collection area or calibrated light transport.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n SensorSpatialSamplingQuadrature,\n SensorSpatialSamplingQuadratureNode\n} from \"./spatial-sampling-quadrature.js\";\nimport { requireInputKeys, requireInputNonNegativeSafeInteger as requireSafeNonNegativeInteger } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { requirePositive as requirePositiveFinite } from \"../exposure/flash-ttl.js\";\nimport { approximatelyEqual } from \"../core/shared-helpers.js\";\n\nconst SQUARE_MICROMETERS_TO_SQUARE_METERS = 1e-12;\nconst WEIGHT_TOLERANCE = 1e-10;\nconst AREA_TOLERANCE = 1e-10;\n\nexport type SensorSpatialSampleValueDomain =\n | {\n kind: \"relative-linear\";\n unit: \"relative\";\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\";\n }\n | {\n kind: \"radiometric-irradiance\";\n unit: \"W/m^2\";\n semantic: \"sensor-plane-irradiance\";\n };\n\nexport interface SensorSpatialQuadratureNodeIdentity {\n antiAliasingComponentIndex: number;\n apertureSampleXIndex: number;\n apertureSampleYIndex: number;\n}\n\nexport interface SensorSpatialQuadratureNodeValue {\n node: SensorSpatialQuadratureNodeIdentity;\n /**\n * A nonnegative, finite number in the declared value domain.\n *\n * It is evaluated at the matching node's preAntiAliasingSourcePointMm.\n */\n value: number;\n}\n\nexport interface ReduceSensorSpatialSamplingQuadratureInput {\n quadrature: SensorSpatialSamplingQuadrature;\n valueDomain: SensorSpatialSampleValueDomain;\n /**\n * Exactly one value per quadrature node, each with its node identity.\n *\n * Order does not matter; the node identity does.\n */\n nodeValues: readonly SensorSpatialQuadratureNodeValue[];\n}\n\nexport type SensorSpatialSampleReductionValue =\n | {\n kind: \"relative-linear\";\n unit: \"relative\";\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\";\n /**\n * Weighted spatial average (dimensionless), in the same relative domain.\n */\n normalizedSpatialAverageRelative: number;\n /**\n * Relative value × square micrometres.\n *\n * A geometric, area-weighted diagnostic, not a radiometric power.\n */\n geometricAreaIntegralRelativeSquareMicrometers: number;\n }\n | {\n kind: \"radiometric-irradiance\";\n unit: \"W/m^2\";\n semantic: \"sensor-plane-irradiance\";\n /**\n * Weighted mean irradiance over the geometric aperture, after the\n * declared normalized AA spreading.\n */\n normalizedSpatialAverageIrradianceWattsPerSquareMeter: number;\n /**\n * Incident radiant flux: the supplied irradiance integrated over the\n * geometric aperture, with the declared normalized AA spreading.\n * Units: watts.\n *\n * It is before any CFA response, QE or microlens response, and excludes\n * any optical-stack throughput not already in the caller's irradiance values.\n */\n geometricApertureIncidentFluxWatts: number;\n };\n\nexport interface SensorSpatialSampleReduction {\n site: {\n x: number;\n y: number;\n };\n /**\n * Label of the destination CFA (colour filter array) site only.\n *\n * This reducer applies no spectral filter response.\n */\n channelId: string;\n outputMeaning:\n \"channel-tagged-pre-response-spatial-sample\";\n valueDomain: SensorSpatialSampleValueDomain;\n nodeCount: number;\n sourceValuesMatchedBy:\n \"quadrature-node-identity\";\n sourceValuesSuppliedForAllNodes: true;\n outsideImagingAreaSourceValuesRequired: boolean;\n normalizedSpatialWeightSum: number;\n geometricApertureAreaSquareMicrometers: number;\n reducedValue: SensorSpatialSampleReductionValue;\n cfaSpectralFilteringApplied: false;\n channelIdIsSpectralResponse: false;\n temporalIntegrationApplied: false;\n exposureDurationApplied: false;\n quantumEfficiencyApplied: false;\n microlensResponseApplied: false;\n opticalThroughputAppliedByReducer: false;\n photonsCalculated: false;\n electronsCalculated: false;\n shotNoiseApplied: false;\n readNoiseApplied: false;\n adcQuantizationApplied: false;\n blackLevelApplied: false;\n saturationApplied: false;\n rawCodeValueProduced: false;\n demosaicOrReconstructionApplied: false;\n}\n\nfunction requireNonNegativeFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be a finite number greater than or equal to zero.\"\n );\n }\n return value;\n}\n\nfunction parseValueDomain(\n value: unknown\n): SensorSpatialSampleValueDomain {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value)\n ) {\n throw new InvalidConfigurationError(\n \"valueDomain must be an object.\"\n );\n }\n\n const record = value as Record<string, unknown>;\n\n if (record.kind === \"relative-linear\") {\n if (\n record.unit !== \"relative\" ||\n record.semantic !==\n \"nonnegative-sensor-plane-irradiance-proxy\"\n ) {\n throw new InvalidScientificInputError(\n \"relative-linear valueDomain must use unit \\\"relative\\\" and the canonical nonnegative sensor-plane irradiance-proxy semantic.\"\n );\n }\n return {\n kind: \"relative-linear\",\n unit: \"relative\",\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\"\n };\n }\n\n if (\n record.kind === \"radiometric-irradiance\"\n ) {\n if (\n record.unit !== \"W/m^2\" ||\n record.semantic !==\n \"sensor-plane-irradiance\"\n ) {\n throw new InvalidScientificInputError(\n \"radiometric-irradiance valueDomain must use unit \\\"W/m^2\\\" and semantic \\\"sensor-plane-irradiance\\\".\"\n );\n }\n return {\n kind: \"radiometric-irradiance\",\n unit: \"W/m^2\",\n semantic: \"sensor-plane-irradiance\"\n };\n }\n\n throw new InvalidConfigurationError(\n \"valueDomain.kind is invalid.\"\n );\n}\n\n/**\n * Makes the deterministic key used to match a value to its spatial quadrature node. It identifies the\n * sample; it is not a cryptographic checksum.\n * @param identity - The input; see `SensorSpatialQuadratureNodeIdentity` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function sensorSpatialQuadratureNodeIdentityKey(\n identity: SensorSpatialQuadratureNodeIdentity\n): string {\n return (\n identity.antiAliasingComponentIndex +\n \":\" +\n identity.apertureSampleXIndex +\n \":\" +\n identity.apertureSampleYIndex\n );\n}\n\nfunction validateQuadratureNode(\n node: SensorSpatialSamplingQuadratureNode,\n index: number\n): SensorSpatialQuadratureNodeIdentity {\n const identity = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n node.antiAliasingComponentIndex,\n \"quadrature.nodes[\" +\n index +\n \"].antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n node.apertureSampleXIndex,\n \"quadrature.nodes[\" +\n index +\n \"].apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n node.apertureSampleYIndex,\n \"quadrature.nodes[\" +\n index +\n \"].apertureSampleYIndex\"\n )\n };\n\n requireNonNegativeFinite(\n node.combinedNormalizedSpatialWeight,\n \"quadrature.nodes[\" +\n index +\n \"].combinedNormalizedSpatialWeight\"\n );\n requireNonNegativeFinite(\n node.combinedAreaMeasureSquareMicrometers,\n \"quadrature.nodes[\" +\n index +\n \"].combinedAreaMeasureSquareMicrometers\"\n );\n\n return identity;\n}\n\n\n/**\n * Rechecks node coordinates, weights, geometric measures and coverage before a spatial plan is trusted\n * as scientific input.\n *\n * Average weights and geometric area measures are kept separate. The geometry does not establish QE,\n * an effective light-collection area or calibrated light transport.\n * @param quadrature - The input; see `SensorSpatialSamplingQuadrature` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function validateSensorSpatialSamplingQuadrature(\n quadrature: SensorSpatialSamplingQuadrature\n): Map<string, SensorSpatialSamplingQuadratureNode> {\n if (\n typeof quadrature !== \"object\" ||\n quadrature === null ||\n !Array.isArray(quadrature.nodes)\n ) {\n throw new InvalidConfigurationError(\n \"quadrature must contain a nodes array.\"\n );\n }\n\n const totalNodeCount =\n requireSafeNonNegativeInteger(\n quadrature.totalNodeCount,\n \"quadrature.totalNodeCount\"\n );\n\n if (\n totalNodeCount === 0 ||\n quadrature.nodes.length !==\n totalNodeCount\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.totalNodeCount must be positive and exactly match quadrature.nodes.length.\"\n );\n }\n\n if (\n quadrature.colorSamplingProfileId !== undefined &&\n (\n typeof quadrature.colorSamplingProfileId !== \"string\" ||\n quadrature.colorSamplingProfileId.trim().length === 0\n )\n ) {\n throw new InvalidConfigurationError(\n \"quadrature.colorSamplingProfileId must be a non-empty string when supplied.\"\n );\n }\n\n if (\n typeof quadrature.channelId !== \"string\" ||\n quadrature.channelId.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"quadrature.channelId must be a non-empty string.\"\n );\n }\n\n requireSafeNonNegativeInteger(\n quadrature.site.x,\n \"quadrature.site.x\"\n );\n requireSafeNonNegativeInteger(\n quadrature.site.y,\n \"quadrature.site.y\"\n );\n\n const geometricArea =\n requirePositiveFinite(\n quadrature.geometricApertureAreaSquareMicrometers,\n \"quadrature.geometricApertureAreaSquareMicrometers\"\n );\n\n const nodesByKey =\n new Map<\n string,\n SensorSpatialSamplingQuadratureNode\n >();\n let weightSum = 0;\n let areaSum = 0;\n\n quadrature.nodes.forEach(\n (node, index) => {\n const identity =\n validateQuadratureNode(node, index);\n const key = sensorSpatialQuadratureNodeIdentityKey(identity);\n\n if (nodesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"quadrature contains duplicate node identities.\"\n );\n }\n\n nodesByKey.set(key, node);\n weightSum +=\n node.combinedNormalizedSpatialWeight;\n areaSum +=\n node.combinedAreaMeasureSquareMicrometers;\n\n if (\n !Number.isFinite(weightSum) ||\n !Number.isFinite(areaSum)\n ) {\n throw new InvalidScientificInputError(\n \"quadrature aggregate weights/measures must remain finite.\"\n );\n }\n }\n );\n\n if (\n !approximatelyEqual(\n weightSum,\n 1,\n WEIGHT_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature combined normalized spatial weights must sum to 1.\"\n );\n }\n\n if (\n !approximatelyEqual(\n areaSum,\n geometricArea,\n AREA_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature combined geometric area measures must sum to the geometric aperture area.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.normalizedSpatialWeightSum,\n weightSum,\n WEIGHT_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.normalizedSpatialWeightSum does not match node weights.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.combinedAreaMeasureSumSquareMicrometers,\n areaSum,\n AREA_TOLERANCE\n )\n ) {\n throw new InvalidScientificInputError(\n \"quadrature.combinedAreaMeasureSumSquareMicrometers does not match node measures.\"\n );\n }\n\n return nodesByKey;\n}\n\nfunction parseNodeValues(\n nodeValues: readonly SensorSpatialQuadratureNodeValue[],\n nodesByKey: ReadonlyMap<\n string,\n SensorSpatialSamplingQuadratureNode\n >\n): Map<string, number> {\n if (!Array.isArray(nodeValues)) {\n throw new InvalidConfigurationError(\n \"nodeValues must be an array.\"\n );\n }\n\n if (\n nodeValues.length !==\n nodesByKey.size\n ) {\n throw new InvalidScientificInputError(\n \"nodeValues must contain exactly one value for every quadrature node.\"\n );\n }\n\n const valuesByKey = new Map<string, number>();\n\n nodeValues.forEach((entry, index) => {\n if (\n typeof entry !== \"object\" ||\n entry === null ||\n typeof entry.node !== \"object\" ||\n entry.node === null\n ) {\n throw new InvalidScientificInputError(\n \"nodeValues[\" +\n index +\n \"] must contain a node identity object.\"\n );\n }\n\n const identity = {\n antiAliasingComponentIndex:\n requireSafeNonNegativeInteger(\n entry.node\n .antiAliasingComponentIndex,\n \"nodeValues[\" +\n index +\n \"].node.antiAliasingComponentIndex\"\n ),\n apertureSampleXIndex:\n requireSafeNonNegativeInteger(\n entry.node.apertureSampleXIndex,\n \"nodeValues[\" +\n index +\n \"].node.apertureSampleXIndex\"\n ),\n apertureSampleYIndex:\n requireSafeNonNegativeInteger(\n entry.node.apertureSampleYIndex,\n \"nodeValues[\" +\n index +\n \"].node.apertureSampleYIndex\"\n )\n };\n const key = sensorSpatialQuadratureNodeIdentityKey(identity);\n\n if (!nodesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"nodeValues[\" +\n index +\n \"] identifies a node that is not present in quadrature.\"\n );\n }\n if (valuesByKey.has(key)) {\n throw new InvalidScientificInputError(\n \"nodeValues contains duplicate node identities.\"\n );\n }\n\n valuesByKey.set(\n key,\n requireNonNegativeFinite(\n entry.value,\n \"nodeValues[\" +\n index +\n \"].value\"\n )\n );\n });\n\n return valuesByKey;\n}\n\n/**\n * Combines nonnegative linear values, each tagged with its node, over a\n * validated sensor spatial quadrature.\n *\n * It does not sample a renderer or optical model itself. Callers evaluate one\n * value at every node's preAntiAliasingSourcePointMm and pass the values with\n * their exact node identity. Order does not matter.\n *\n * Two value domains are supported:\n *\n * - relative-linear: a dimensionless, nonnegative stand-in for irradiance, for\n * deterministic renderer and science regression tests;\n * - radiometric-irradiance: physical irradiance on the sensor plane in W/m^2.\n *\n * The destination site's channelId is a label only. No CFA spectral\n * transmittance or sensitivity is applied, so do not call the output a\n * colour-filtered mosaic sample or a RAW value.\n */\nexport function reduceSensorSpatialSamplingQuadrature(\n input: ReduceSensorSpatialSamplingQuadratureInput\n): CalculationResult<SensorSpatialSampleReduction> {\n requireInputKeys(input, \"reduceSensorSpatialSamplingQuadrature\", [\"quadrature\", \"valueDomain\", \"nodeValues\"]);\n const domain = parseValueDomain(\n input.valueDomain\n );\n const nodesByKey = validateSensorSpatialSamplingQuadrature(\n input.quadrature\n );\n const valuesByKey = parseNodeValues(\n input.nodeValues,\n nodesByKey\n );\n\n let weightedAverage = 0;\n let areaWeightedIntegral = 0;\n\n nodesByKey.forEach((node, key) => {\n const value = valuesByKey.get(key);\n if (value === undefined) {\n throw new InvalidScientificInputError(\n \"A quadrature node is missing its explicit source value.\"\n );\n }\n\n weightedAverage +=\n value *\n node.combinedNormalizedSpatialWeight;\n areaWeightedIntegral +=\n value *\n node.combinedAreaMeasureSquareMicrometers;\n\n if (\n !Number.isFinite(weightedAverage) ||\n !Number.isFinite(areaWeightedIntegral)\n ) {\n throw new InvalidScientificInputError(\n \"Reduced spatial sample must remain finite.\"\n );\n }\n });\n\n const reducedValue:\n SensorSpatialSampleReductionValue =\n domain.kind === \"relative-linear\"\n ? {\n kind: \"relative-linear\",\n unit: \"relative\",\n semantic:\n \"nonnegative-sensor-plane-irradiance-proxy\",\n normalizedSpatialAverageRelative:\n weightedAverage,\n geometricAreaIntegralRelativeSquareMicrometers:\n areaWeightedIntegral\n }\n : {\n kind: \"radiometric-irradiance\",\n unit: \"W/m^2\",\n semantic:\n \"sensor-plane-irradiance\",\n normalizedSpatialAverageIrradianceWattsPerSquareMeter:\n weightedAverage,\n geometricApertureIncidentFluxWatts:\n areaWeightedIntegral *\n SQUARE_MICROMETERS_TO_SQUARE_METERS\n };\n\n return approximationResult(\n {\n site: {\n ...input.quadrature.site\n },\n channelId:\n input.quadrature.channelId,\n outputMeaning:\n \"channel-tagged-pre-response-spatial-sample\",\n valueDomain: domain,\n nodeCount:\n input.quadrature.totalNodeCount,\n sourceValuesMatchedBy:\n \"quadrature-node-identity\",\n sourceValuesSuppliedForAllNodes: true,\n outsideImagingAreaSourceValuesRequired:\n input.quadrature\n .preAntiAliasingSourceOutsideImagingAreaNodeCount >\n 0,\n normalizedSpatialWeightSum:\n input.quadrature\n .normalizedSpatialWeightSum,\n geometricApertureAreaSquareMicrometers:\n input.quadrature\n .geometricApertureAreaSquareMicrometers,\n reducedValue,\n cfaSpectralFilteringApplied: false,\n channelIdIsSpectralResponse: false,\n temporalIntegrationApplied: false,\n exposureDurationApplied: false,\n quantumEfficiencyApplied: false,\n microlensResponseApplied: false,\n opticalThroughputAppliedByReducer:\n false,\n photonsCalculated: false,\n electronsCalculated: false,\n shotNoiseApplied: false,\n readNoiseApplied: false,\n adcQuantizationApplied: false,\n blackLevelApplied: false,\n saturationApplied: false,\n rawCodeValueProduced: false,\n demosaicOrReconstructionApplied: false\n },\n \"sensor-spatial-sample-reduction\",\n \"1.0.0\",\n [\n \"Input values are nonnegative linear scalars evaluated at each quadrature node's pre-AA source coordinate.\",\n \"Node values are matched by explicit quadrature-node identity rather than positional array order.\",\n \"The destination CFA/color channel is metadata only; no spectral transmittance, sensor spectral responsivity or quantum efficiency is applied.\",\n \"Relative-linear values are dimensionless irradiance-like proxies for deterministic testing and do not become physical radiometry.\",\n \"Radiometric-irradiance values use W/m^2; geometric aperture integration converts square micrometres to square metres to report incident radiant flux in watts.\",\n \"The physical irradiance branch reports geometric-aperture incident flux under the caller-supplied irradiance field and normalized AA spatial redistribution; it does not add omitted optical-stack throughput, microlens response, CFA response or sensor conversion.\",\n \"Every quadrature node requires an explicit source value, including pre-AA source points outside the active imaging area; no automatic clamping, zero-fill, extrapolation or renormalization occurs.\",\n \"Negative, NaN and infinite source values are rejected; signed intermediate color-transform values are outside this pre-response sensor-plane contract.\",\n \"Temporal exposure integration, wavelength integration, photons/electrons, noise, saturation, black level, ADC quantization, RAW code values and reconstruction remain separate future work.\"\n ]\n );\n}\n"]}
@@ -17,6 +17,7 @@ import { createSensorSpectralResponseChannelBinding, parseSensorSpectralResponse
17
17
  import { requireInputKeys, requireInputPositiveFinite as requirePositiveFinite, requireInputNonNegativeSafeInteger as requireNonNegativeSafeInteger } from "../core/record-validation.js";
18
18
  import { InvalidConfigurationError } from "../core/configuration-error.js";
19
19
  import { responseCurves } from "./spectral-coverage.js";
20
+ import { approximatelyEqual } from "../core/shared-helpers.js";
20
21
  const MAX_SPECTRAL_QUADRATURE_NODES = 100_000;
21
22
  function channelResponse(profile, channelId) {
22
23
  if (typeof channelId !== "string" ||
@@ -142,12 +143,6 @@ function planSegments(boundaries, maximumSubintervalWidthNanometers) {
142
143
  totalNodeCount
143
144
  };
144
145
  }
145
- const SPECTRAL_VALIDATION_TOLERANCE = 1e-10;
146
- function approximatelyEqual(actual, expected, tolerance = SPECTRAL_VALIDATION_TOLERANCE) {
147
- const scale = Math.max(1, Math.abs(actual), Math.abs(expected));
148
- return (Math.abs(actual - expected) <=
149
- tolerance * scale);
150
- }
151
146
  /**
152
147
  * Rechecks a supplied wavelength plan (node identities, coverage and widths in nanometres) before it
153
148
  * is used.
@@ -1 +1 @@
1
- {"version":3,"file":"spectral-quadrature.js","sourceRoot":"","sources":["../../src/sensor/spectral-quadrature.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spectral quadrature: the wavelength sample points (nodes) and step widths used to integrate light\n * over wavelength for one sensor response channel.\n *\n * Each node is the midpoint of a wavelength interval, with its width dλ in nanometres. The intervals\n * stay inside the range the response supports. Lining nodes up with curve breakpoints does not prove\n * the integral has converged. Discrete (delta-like) emission lines need their own integration and are\n * never silently widened into continuous bins.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport type { EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n SpectralWavelengthBasis,\n SpectralWavelengthRangeNanometers\n} from \"../core/spectral.js\";\nimport {\n parseSensorColorSamplingProfile,\n type SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport {\n createSensorSpectralResponseChannelBinding,\n parseSensorSpectralResponseProfile,\n resolveSensorSpectralResponseAtWavelength,\n type SensorSpectralReferenceConditions,\n type SensorSpectralResponseProfile,\n type SensorSpectralResponseScientificStatus,\n type SensorSpectralResponseUncertainty\n} from \"./spectral-response.js\";\nimport { requireInputKeys, requireInputPositiveFinite as requirePositiveFinite, requireInputNonNegativeSafeInteger as requireNonNegativeSafeInteger } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { responseCurves } from \"./spectral-coverage.js\";\n\nconst MAX_SPECTRAL_QUADRATURE_NODES = 100_000;\n\nexport type SensorSpectralWavelengthRangeNanometers =\n SpectralWavelengthRangeNanometers;\n\nexport interface CalculateSensorSpectralQuadratureInput {\n colorSamplingProfile: SensorColorSamplingProfile;\n spectralResponseProfile: SensorSpectralResponseProfile;\n channelId: string;\n wavelengthBasis: SpectralWavelengthBasis;\n /**\n * Wavelength interval to sample. It is checked only against the range the\n * sensor response supports, not against scene-spectrum or optical-transmission\n * coverage.\n */\n wavelengthRangeNanometers:\n SensorSpectralWavelengthRangeNanometers;\n /**\n * Strict upper limit on the width of each midpoint subinterval.\n *\n * Response breakpoints and any additional breakpoints split the requested\n * range first. Each segment is then subdivided until every subinterval is\n * no wider than this value.\n */\n maximumSubintervalWidthNanometers: number;\n /**\n * Optional strictly increasing wavelength breakpoints from other continuous\n * spectral inputs, such as the interpolation points of a scene spectrum or of\n * optical transmission. Values must be strictly inside the requested range.\n *\n * Passing them does not claim those other inputs are available or valid over\n * the requested range.\n */\n additionalBreakpointsNanometers?:\n readonly number[];\n}\n\nexport interface SensorSpectralQuadratureNode {\n spectralSampleIndex: number;\n segmentIndex: number;\n subdivisionIndex: number;\n segmentSubdivisionCount: number;\n /**\n * Midpoint wavelength for this subinterval. Units: nm in wavelengthBasis.\n */\n wavelengthNanometers: number;\n /**\n * dλ (wavelength width) this node stands for. Units: nm.\n *\n * It is only a wavelength width. It is not response-weighted throughput,\n * a photon count, radiant energy, current or a dimensionless probability.\n */\n wavelengthMeasureNanometers: number;\n /**\n * The wavelength width divided by the requested range width (dimensionless).\n *\n * Useful for averages, but it is not a sensor-response weight.\n */\n normalizedWavelengthWeight: number;\n}\n\nexport interface SensorSpectralQuadrature {\n profileId: string;\n colorSamplingProfileId: string;\n channelId: string;\n sourceResponseKind:\n SensorSpectralResponseProfile[\"channels\"][number][\"kind\"];\n responseScope:\n SensorSpectralResponseProfile[\"channels\"][number][\"responseScope\"];\n responseScientificStatus:\n SensorSpectralResponseScientificStatus;\n responseUncertainty:\n SensorSpectralResponseUncertainty;\n responseChannelBinding?: import(\"./spectral-response.js\").SensorSpectralResponseChannelBinding;\n responseReferenceConditions?:\n SensorSpectralReferenceConditions;\n wavelengthBasis: SpectralWavelengthBasis;\n wavelengthBasisResolved: boolean;\n wavelengthRangeNanometers:\n SensorSpectralWavelengthRangeNanometers;\n responseDeclaredWavelengthRangeNanometers:\n SensorSpectralWavelengthRangeNanometers;\n quadratureScheme:\n \"response-breakpoint-aware-bounded-midpoint\";\n segmentBoundarySource:\n \"requested-range-plus-response-knots-plus-optional-additional-breakpoints\";\n /**\n * Sorted, unique segment boundaries, including the ends of the requested range.\n */\n segmentBoundariesNanometers:\n readonly number[];\n maximumSubintervalWidthNanometers: number;\n segmentCount: number;\n totalNodeCount: number;\n normalizedWavelengthWeightSum: number;\n wavelengthMeasureSumNanometers: number;\n nodes: readonly SensorSpectralQuadratureNode[];\n responseKnotAlignmentIncluded: true;\n componentEvidence: {\n colorSamplingProfile: readonly EvidenceProvenance[];\n profile: readonly EvidenceProvenance[];\n channel: readonly EvidenceProvenance[];\n curves:\n readonly (readonly EvidenceProvenance[])[];\n };\n /**\n * Fixed statements of what this result does not include.\n */\n responseValuesIncluded: false;\n responseApplicationPerformed: false;\n sourceSpectralValuesIncluded: false;\n spectralIrradianceIncluded: false;\n spectralPhotonIrradianceIncluded: false;\n commonSpectralCoverageValidated: false;\n continuousSpectralDensityQuadratureOnly: true;\n discreteLineSpectrumIncluded: false;\n opticalTransmissionIncludedByQuadrature: false;\n wavelengthIntegrationPerformed: false;\n spatialIntegrationPerformed: false;\n temporalIntegrationPerformed: false;\n photonsCalculated: false;\n electronsCalculated: false;\n rawCodeValueProduced: false;\n convergenceErrorEstimated: false;\n}\n\ninterface ResponseCurveSupport {\n wavelengthBasis: SpectralWavelengthBasis;\n samples: readonly {\n wavelengthNanometers: number;\n }[];\n evidence: readonly EvidenceProvenance[];\n}\n\ninterface SegmentPlan {\n minimum: number;\n maximum: number;\n subdivisionCount: number;\n subintervalWidthNanometers: number;\n}\n\nfunction channelResponse(\n profile: SensorSpectralResponseProfile,\n channelId: string\n): SensorSpectralResponseProfile[\"channels\"][number] {\n if (\n typeof channelId !== \"string\" ||\n channelId.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"channelId must be a non-empty string.\"\n );\n }\n\n const response = profile.channels.find(\n (entry) => entry.channelId === channelId\n );\n if (response === undefined) {\n throw new InvalidScientificInputError(\n \"No spectral response is declared for the requested channelId.\"\n );\n }\n return response;\n}\n\nfunction usableResponseRange(\n curves: readonly ResponseCurveSupport[]\n): SensorSpectralWavelengthRangeNanometers {\n const minimum = Math.max(\n ...curves.map(\n (curve) =>\n curve.samples[0]!\n .wavelengthNanometers\n )\n );\n const maximum = Math.min(\n ...curves.map(\n (curve) =>\n curve.samples[\n curve.samples.length - 1\n ]!.wavelengthNanometers\n )\n );\n\n if (!(minimum < maximum)) {\n throw new InvalidScientificInputError(\n \"Spectral-response curves do not have a usable overlapping wavelength interval.\"\n );\n }\n\n return { minimum, maximum };\n}\n\nfunction parseRequestedRange(\n input:\n SensorSpectralWavelengthRangeNanometers,\n usable:\n SensorSpectralWavelengthRangeNanometers\n): SensorSpectralWavelengthRangeNanometers {\n if (\n typeof input !== \"object\" ||\n input === null\n ) {\n throw new InvalidConfigurationError(\n \"wavelengthRangeNanometers must be an object.\"\n );\n }\n\n const minimum = requirePositiveFinite(\n input.minimum,\n \"wavelengthRangeNanometers.minimum\"\n );\n const maximum = requirePositiveFinite(\n input.maximum,\n \"wavelengthRangeNanometers.maximum\"\n );\n\n if (!(minimum < maximum)) {\n throw new InvalidScientificInputError(\n \"wavelengthRangeNanometers.minimum must be less than maximum.\"\n );\n }\n\n if (\n minimum < usable.minimum ||\n maximum > usable.maximum\n ) {\n throw new InvalidScientificInputError(\n \"Requested wavelength range must remain inside the usable spectral-response range; clipping, extrapolation, and implicit zero-fill are not permitted.\"\n );\n }\n\n return { minimum, maximum };\n}\n\nfunction additionalBreakpoints(\n value: readonly number[] | undefined,\n range:\n SensorSpectralWavelengthRangeNanometers\n): readonly number[] {\n if (value === undefined) {\n return [];\n }\n if (!Array.isArray(value)) {\n throw new InvalidConfigurationError(\n \"additionalBreakpointsNanometers must be an array when supplied.\"\n );\n }\n if (\n value.length >=\n MAX_SPECTRAL_QUADRATURE_NODES\n ) {\n throw new InvalidScientificInputError(\n \"additionalBreakpointsNanometers contains too many entries for the spectral quadrature safety limit.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n\n return value.map(\n (entry, index) => {\n const wavelength =\n requirePositiveFinite(\n entry,\n \"additionalBreakpointsNanometers[\" +\n index +\n \"]\"\n );\n if (\n wavelength <= range.minimum ||\n wavelength >= range.maximum\n ) {\n throw new InvalidScientificInputError(\n \"Additional spectral breakpoints must lie strictly inside the requested wavelength range.\"\n );\n }\n if (wavelength <= previous) {\n throw new InvalidScientificInputError(\n \"additionalBreakpointsNanometers must be strictly increasing with no duplicates.\"\n );\n }\n previous = wavelength;\n return wavelength;\n }\n );\n}\n\nfunction partitionBoundaries(\n curves: readonly ResponseCurveSupport[],\n range:\n SensorSpectralWavelengthRangeNanometers,\n additional:\n readonly number[]\n): readonly number[] {\n const boundaries = new Set<number>([\n range.minimum,\n range.maximum,\n ...additional\n ]);\n\n for (const curve of curves) {\n for (const sample of curve.samples) {\n if (\n sample.wavelengthNanometers >\n range.minimum &&\n sample.wavelengthNanometers <\n range.maximum\n ) {\n boundaries.add(\n sample.wavelengthNanometers\n );\n }\n }\n }\n\n return [...boundaries].sort(\n (a, b) => a - b\n );\n}\n\nfunction planSegments(\n boundaries: readonly number[],\n maximumSubintervalWidthNanometers: number\n): {\n segments: readonly SegmentPlan[];\n totalNodeCount: number;\n} {\n const segments: SegmentPlan[] = [];\n let totalNodeCount = 0;\n\n for (\n let segmentIndex = 0;\n segmentIndex < boundaries.length - 1;\n segmentIndex += 1\n ) {\n const minimum =\n boundaries[segmentIndex]!;\n const maximum =\n boundaries[segmentIndex + 1]!;\n const width =\n maximum - minimum;\n\n if (\n !Number.isFinite(width) ||\n width <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature segment widths must be finite and greater than zero.\"\n );\n }\n\n const subdivisionCount =\n Math.ceil(\n width /\n maximumSubintervalWidthNanometers\n );\n\n if (\n !Number.isSafeInteger(\n subdivisionCount\n ) ||\n subdivisionCount <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature segment subdivision count must be a positive safe integer.\"\n );\n }\n\n totalNodeCount +=\n subdivisionCount;\n\n if (\n !Number.isSafeInteger(\n totalNodeCount\n )\n ) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature node count must remain a safe integer.\"\n );\n }\n if (\n totalNodeCount >\n MAX_SPECTRAL_QUADRATURE_NODES\n ) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature node count exceeds the safety limit of \" +\n MAX_SPECTRAL_QUADRATURE_NODES +\n \".\"\n );\n }\n\n segments.push({\n minimum,\n maximum,\n subdivisionCount,\n subintervalWidthNanometers:\n width / subdivisionCount\n });\n }\n\n if (segments.length === 0) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature requires at least one non-empty segment.\"\n );\n }\n\n return {\n segments,\n totalNodeCount\n };\n}\n\nconst SPECTRAL_VALIDATION_TOLERANCE = 1e-10;\n\nfunction approximatelyEqual(\n actual: number,\n expected: number,\n tolerance = SPECTRAL_VALIDATION_TOLERANCE\n): boolean {\n const scale = Math.max(\n 1,\n Math.abs(actual),\n Math.abs(expected)\n );\n return (\n Math.abs(actual - expected) <=\n tolerance * scale\n );\n}\n\n/**\n * Rechecks a supplied wavelength plan (node identities, coverage and widths in nanometres) before it\n * is used.\n *\n * Lining nodes up with breakpoints does not prove convergence. Discrete (delta-like) lines need their\n * own integration and are never silently widened into continuous bins.\n * @param quadrature - The input; see `SensorSpectralQuadrature` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function validateSensorSpectralQuadrature(\n quadrature: SensorSpectralQuadrature\n): Map<number, SensorSpectralQuadratureNode> {\n if (\n typeof quadrature !== \"object\" ||\n quadrature === null ||\n !Array.isArray(quadrature.nodes)\n ) {\n throw new InvalidConfigurationError(\n \"spectralQuadrature must contain a nodes array.\"\n );\n }\n\n for (const [field, value] of [\n [\"profileId\", quadrature.profileId],\n [\n \"colorSamplingProfileId\",\n quadrature.colorSamplingProfileId\n ],\n [\"channelId\", quadrature.channelId]\n ] as const) {\n if (\n typeof value !== \"string\" ||\n value.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"spectralQuadrature.\" +\n field +\n \" must be a non-empty string.\"\n );\n }\n }\n\n if (\n quadrature.wavelengthBasis !== \"air\" &&\n quadrature.wavelengthBasis !== \"vacuum\" &&\n quadrature.wavelengthBasis !== \"unspecified\"\n ) {\n throw new InvalidConfigurationError(\n \"spectralQuadrature.wavelengthBasis is invalid.\"\n );\n }\n\n const rangeMinimum = requirePositiveFinite(\n quadrature.wavelengthRangeNanometers.minimum,\n \"spectralQuadrature.wavelengthRangeNanometers.minimum\"\n );\n const rangeMaximum = requirePositiveFinite(\n quadrature.wavelengthRangeNanometers.maximum,\n \"spectralQuadrature.wavelengthRangeNanometers.maximum\"\n );\n if (!(rangeMinimum < rangeMaximum)) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature wavelength range minimum must be less than maximum.\"\n );\n }\n\n const maximumSubintervalWidthNanometers =\n requirePositiveFinite(\n quadrature.maximumSubintervalWidthNanometers,\n \"spectralQuadrature.maximumSubintervalWidthNanometers\"\n );\n\n if (\n !Array.isArray(\n quadrature.segmentBoundariesNanometers\n ) ||\n quadrature.segmentBoundariesNanometers.length <\n 2\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.segmentBoundariesNanometers must contain at least two boundaries.\"\n );\n }\n\n let previousBoundary =\n Number.NEGATIVE_INFINITY;\n quadrature.segmentBoundariesNanometers.forEach(\n (boundary, index) => {\n const parsed = requirePositiveFinite(\n boundary,\n \"spectralQuadrature.segmentBoundariesNanometers[\" +\n index +\n \"]\"\n );\n if (parsed <= previousBoundary) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.segmentBoundariesNanometers must be strictly increasing.\"\n );\n }\n previousBoundary = parsed;\n }\n );\n\n const segmentCount =\n requireNonNegativeSafeInteger(\n quadrature.segmentCount,\n \"spectralQuadrature.segmentCount\"\n );\n if (\n segmentCount <= 0 ||\n segmentCount !==\n quadrature.segmentBoundariesNanometers.length -\n 1\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.segmentCount must be positive and match the boundary count.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.segmentBoundariesNanometers[0]!,\n rangeMinimum\n ) ||\n !approximatelyEqual(\n quadrature.segmentBoundariesNanometers[\n quadrature.segmentBoundariesNanometers.length -\n 1\n ]!,\n rangeMaximum\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature segment boundaries must exactly span the requested wavelength range.\"\n );\n }\n\n const totalNodeCount =\n requireNonNegativeSafeInteger(\n quadrature.totalNodeCount,\n \"spectralQuadrature.totalNodeCount\"\n );\n if (\n totalNodeCount <= 0 ||\n totalNodeCount !== quadrature.nodes.length\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.totalNodeCount must be positive and exactly match spectralQuadrature.nodes.length.\"\n );\n }\n if (\n totalNodeCount >\n MAX_SPECTRAL_QUADRATURE_NODES\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.totalNodeCount exceeds the spectral quadrature safety limit.\"\n );\n }\n\n if (\n quadrature.responseApplicationPerformed !==\n false ||\n quadrature.responseValuesIncluded !== false ||\n quadrature.wavelengthIntegrationPerformed !==\n false ||\n quadrature.continuousSpectralDensityQuadratureOnly !==\n true ||\n quadrature.discreteLineSpectrumIncluded !==\n false\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature semantics are incompatible with pre-response continuous-density composition.\"\n );\n }\n\n const span =\n rangeMaximum - rangeMinimum;\n const nodesByIndex =\n new Map<number, SensorSpectralQuadratureNode>();\n const seenSegmentSubdivisions =\n new Set<string>();\n const segmentSubdivisionCounts =\n new Map<number, number>();\n const segmentSeenCounts =\n new Map<number, number>();\n let measureSum = 0;\n let normalizedWeightSum = 0;\n\n quadrature.nodes.forEach(\n (node, nodeArrayIndex) => {\n const spectralSampleIndex =\n requireNonNegativeSafeInteger(\n node.spectralSampleIndex,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].spectralSampleIndex\"\n );\n if (\n nodesByIndex.has(\n spectralSampleIndex\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature contains duplicate spectralSampleIndex values.\"\n );\n }\n\n const segmentIndex =\n requireNonNegativeSafeInteger(\n node.segmentIndex,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].segmentIndex\"\n );\n if (segmentIndex >= segmentCount) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature node segmentIndex is outside the declared segment range.\"\n );\n }\n\n const subdivisionIndex =\n requireNonNegativeSafeInteger(\n node.subdivisionIndex,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].subdivisionIndex\"\n );\n const segmentSubdivisionCount =\n requireNonNegativeSafeInteger(\n node.segmentSubdivisionCount,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].segmentSubdivisionCount\"\n );\n if (\n segmentSubdivisionCount <= 0 ||\n subdivisionIndex >=\n segmentSubdivisionCount\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature node subdivision identity is invalid.\"\n );\n }\n\n const priorSegmentCount =\n segmentSubdivisionCounts.get(\n segmentIndex\n );\n if (\n priorSegmentCount !== undefined &&\n priorSegmentCount !==\n segmentSubdivisionCount\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature nodes disagree on a segment subdivision count.\"\n );\n }\n segmentSubdivisionCounts.set(\n segmentIndex,\n segmentSubdivisionCount\n );\n\n const subdivisionKey =\n segmentIndex +\n \":\" +\n subdivisionIndex;\n if (\n seenSegmentSubdivisions.has(\n subdivisionKey\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature contains duplicate segment/subdivision identities.\"\n );\n }\n seenSegmentSubdivisions.add(\n subdivisionKey\n );\n segmentSeenCounts.set(\n segmentIndex,\n (segmentSeenCounts.get(\n segmentIndex\n ) ?? 0) + 1\n );\n\n const wavelengthNanometers =\n requirePositiveFinite(\n node.wavelengthNanometers,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].wavelengthNanometers\"\n );\n const wavelengthMeasureNanometers =\n requirePositiveFinite(\n node.wavelengthMeasureNanometers,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].wavelengthMeasureNanometers\"\n );\n const normalizedWavelengthWeight =\n requirePositiveFinite(\n node.normalizedWavelengthWeight,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].normalizedWavelengthWeight\"\n );\n\n const segmentMinimum =\n quadrature.segmentBoundariesNanometers[\n segmentIndex\n ]!;\n const segmentMaximum =\n quadrature.segmentBoundariesNanometers[\n segmentIndex + 1\n ]!;\n const expectedMeasure =\n (segmentMaximum -\n segmentMinimum) /\n segmentSubdivisionCount;\n const expectedWavelength =\n segmentMinimum +\n (subdivisionIndex + 0.5) *\n expectedMeasure;\n const expectedNormalizedWeight =\n expectedMeasure / span;\n\n if (\n !approximatelyEqual(\n wavelengthMeasureNanometers,\n expectedMeasure\n ) ||\n !approximatelyEqual(\n wavelengthNanometers,\n expectedWavelength\n ) ||\n !approximatelyEqual(\n normalizedWavelengthWeight,\n expectedNormalizedWeight\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature node geometry/measure does not match its declared segment/subdivision identity.\"\n );\n }\n\n if (\n wavelengthMeasureNanometers >\n maximumSubintervalWidthNanometers &&\n !approximatelyEqual(\n wavelengthMeasureNanometers,\n maximumSubintervalWidthNanometers\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature node width exceeds maximumSubintervalWidthNanometers.\"\n );\n }\n\n measureSum +=\n wavelengthMeasureNanometers;\n normalizedWeightSum +=\n normalizedWavelengthWeight;\n if (\n !Number.isFinite(measureSum) ||\n !Number.isFinite(\n normalizedWeightSum\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature aggregate measures must remain finite.\"\n );\n }\n\n nodesByIndex.set(\n spectralSampleIndex,\n node\n );\n }\n );\n\n for (\n let spectralSampleIndex = 0;\n spectralSampleIndex < totalNodeCount;\n spectralSampleIndex += 1\n ) {\n if (\n !nodesByIndex.has(\n spectralSampleIndex\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature spectralSampleIndex values must form the exact contiguous range 0..totalNodeCount-1.\"\n );\n }\n }\n\n for (\n let segmentIndex = 0;\n segmentIndex < segmentCount;\n segmentIndex += 1\n ) {\n const expected =\n segmentSubdivisionCounts.get(\n segmentIndex\n );\n const seen =\n segmentSeenCounts.get(\n segmentIndex\n ) ?? 0;\n if (\n expected === undefined ||\n seen !== expected\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature must contain every declared subdivision exactly once.\"\n );\n }\n }\n\n if (\n !approximatelyEqual(\n measureSum,\n span\n ) ||\n !approximatelyEqual(\n normalizedWeightSum,\n 1\n ) ||\n !approximatelyEqual(\n quadrature.wavelengthMeasureSumNanometers,\n measureSum\n ) ||\n !approximatelyEqual(\n quadrature.normalizedWavelengthWeightSum,\n normalizedWeightSum\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature aggregate wavelength measures/weights are inconsistent.\"\n );\n }\n\n return nodesByIndex;\n}\n\n/**\n * Builds deterministic wavelength sample points for one exact sensor response\n * channel. It does not apply the response to any spectrum.\n *\n * The requested range must lie fully inside the channel's declared usable\n * range. The response curve's breakpoints, plus any caller-supplied breakpoints\n * from the source spectrum or optics, split that range into segments. Each\n * segment is then subdivided so no midpoint subinterval is wider than\n * maximumSubintervalWidthNanometers.\n *\n * wavelengthMeasureNanometers is dλ in nanometres, not a dimensionless response\n * weight. Multiply it only by a spectral density per nanometre, or convert\n * units explicitly first.\n *\n * QE and A/W responsivity are deliberately not applied. They lead to different\n * signals: QE is photon-to-electron efficiency, while A/W responsivity relates\n * incident radiant power to electrical current. A later step must also match\n * responseScope to the source plane and collection area before integrating any\n * signal.\n */\nexport function calculateSensorSpectralQuadrature(\n input:\n CalculateSensorSpectralQuadratureInput\n): CalculationResult<SensorSpectralQuadrature> {\n requireInputKeys(input, \"calculateSensorSpectralQuadrature\", [\"colorSamplingProfile\", \"spectralResponseProfile\", \"channelId\", \"wavelengthBasis\", \"wavelengthRangeNanometers\", \"maximumSubintervalWidthNanometers\", \"additionalBreakpointsNanometers\"]);\n const maximumSubintervalWidthNanometers =\n requirePositiveFinite(\n input.maximumSubintervalWidthNanometers,\n \"maximumSubintervalWidthNanometers\"\n );\n\n if (\n input.wavelengthBasis !== \"air\" &&\n input.wavelengthBasis !== \"vacuum\" &&\n input.wavelengthBasis !== \"unspecified\"\n ) {\n throw new InvalidConfigurationError(\n \"wavelengthBasis is invalid.\"\n );\n }\n\n const colorProfile =\n parseSensorColorSamplingProfile(\n input.colorSamplingProfile\n );\n const responseProfile =\n parseSensorSpectralResponseProfile(\n input.spectralResponseProfile\n );\n const response = channelResponse(\n responseProfile,\n input.channelId\n );\n const curves =\n responseCurves(response);\n\n if (\n !curves.every(\n (curve) =>\n curve.wavelengthBasis ===\n input.wavelengthBasis\n )\n ) {\n throw new InvalidScientificInputError(\n \"wavelengthBasis must exactly match every response curve used by the requested channel.\"\n );\n }\n\n const usable = usableResponseRange(\n curves\n );\n const range = parseRequestedRange(\n input.wavelengthRangeNanometers,\n usable\n );\n const validationWavelength =\n range.minimum +\n (range.maximum - range.minimum) /\n 2;\n const representative =\n resolveSensorSpectralResponseAtWavelength(\n {\n colorSamplingProfile:\n colorProfile,\n spectralResponseProfile:\n responseProfile,\n channelId: input.channelId,\n wavelengthNanometers:\n validationWavelength,\n wavelengthBasis:\n input.wavelengthBasis\n }\n ).value;\n\n const additional =\n additionalBreakpoints(\n input.additionalBreakpointsNanometers,\n range\n );\n const boundaries =\n partitionBoundaries(\n curves,\n range,\n additional\n );\n const {\n segments,\n totalNodeCount\n } = planSegments(\n boundaries,\n maximumSubintervalWidthNanometers\n );\n\n const totalSpan =\n range.maximum - range.minimum;\n const nodes:\n SensorSpectralQuadratureNode[] = [];\n let spectralSampleIndex = 0;\n\n for (\n let segmentIndex = 0;\n segmentIndex < segments.length;\n segmentIndex += 1\n ) {\n const segment =\n segments[segmentIndex]!;\n\n for (\n let subdivisionIndex = 0;\n subdivisionIndex <\n segment.subdivisionCount;\n subdivisionIndex += 1\n ) {\n const wavelengthNanometers =\n segment.minimum +\n (subdivisionIndex + 0.5) *\n segment\n .subintervalWidthNanometers;\n\n nodes.push({\n spectralSampleIndex,\n segmentIndex,\n subdivisionIndex,\n segmentSubdivisionCount:\n segment.subdivisionCount,\n wavelengthNanometers,\n wavelengthMeasureNanometers:\n segment\n .subintervalWidthNanometers,\n normalizedWavelengthWeight:\n segment\n .subintervalWidthNanometers /\n totalSpan\n });\n\n spectralSampleIndex += 1;\n }\n }\n\n const normalizedWavelengthWeightSum =\n nodes.reduce(\n (sum, node) =>\n sum +\n node.normalizedWavelengthWeight,\n 0\n );\n const wavelengthMeasureSumNanometers =\n nodes.reduce(\n (sum, node) =>\n sum +\n node.wavelengthMeasureNanometers,\n 0\n );\n\n return approximationResult(\n {\n profileId:\n representative.profileId,\n colorSamplingProfileId:\n representative\n .colorSamplingProfileId,\n channelId:\n representative.channelId,\n sourceResponseKind:\n representative\n .sourceResponseKind,\n responseScope:\n representative.responseScope,\n responseScientificStatus:\n representative\n .scientificStatus,\n responseUncertainty:\n representative.uncertainty,\n responseChannelBinding:\n createSensorSpectralResponseChannelBinding(\n response\n ),\n ...(representative.referenceConditions ===\n undefined\n ? {}\n : {\n responseReferenceConditions:\n representative.referenceConditions\n }),\n wavelengthBasis:\n input.wavelengthBasis,\n wavelengthBasisResolved:\n representative\n .wavelengthBasisResolved,\n wavelengthRangeNanometers:\n range,\n responseDeclaredWavelengthRangeNanometers:\n usable,\n quadratureScheme:\n \"response-breakpoint-aware-bounded-midpoint\",\n segmentBoundarySource:\n \"requested-range-plus-response-knots-plus-optional-additional-breakpoints\",\n segmentBoundariesNanometers:\n [...boundaries],\n maximumSubintervalWidthNanometers,\n segmentCount:\n segments.length,\n totalNodeCount,\n normalizedWavelengthWeightSum,\n wavelengthMeasureSumNanometers,\n nodes,\n responseKnotAlignmentIncluded:\n true,\n componentEvidence: {\n colorSamplingProfile:\n colorProfile.evidence,\n profile:\n representative\n .componentEvidence.profile,\n channel:\n representative\n .componentEvidence.channel,\n curves:\n representative\n .componentEvidence.curves\n },\n responseValuesIncluded: false,\n responseApplicationPerformed:\n false,\n sourceSpectralValuesIncluded:\n false,\n spectralIrradianceIncluded:\n false,\n spectralPhotonIrradianceIncluded:\n false,\n commonSpectralCoverageValidated:\n false,\n continuousSpectralDensityQuadratureOnly:\n true,\n discreteLineSpectrumIncluded:\n false,\n opticalTransmissionIncludedByQuadrature:\n false,\n wavelengthIntegrationPerformed:\n false,\n spatialIntegrationPerformed:\n false,\n temporalIntegrationPerformed:\n false,\n photonsCalculated: false,\n electronsCalculated: false,\n rawCodeValueProduced: false,\n convergenceErrorEstimated: false\n },\n \"sensor-spectral-quadrature\",\n \"1.0.0\",\n [\n \"The partition includes requested range edges, response-curve knots, and optional caller-supplied continuous-spectrum/optics breakpoints.\",\n \"Every segment uses deterministic equal-width midpoint subintervals whose width is bounded by maximumSubintervalWidthNanometers.\",\n \"Wavelength measures are d-lambda in nanometres and normalized wavelength weights are geometric wavelength measures only; neither is sensor-response weighting.\",\n \"The requested range is validated against sensor-response support only. Scene-spectrum, optical-transmission, and other spectral-factor coverage are not established by this API.\",\n \"The response profile, exact channel linkage, wavelength basis, response scope, and response-data provenance are validated and preserved, but response values are not applied at quadrature nodes.\",\n \"QE and A/W responsivity remain different physical representations and require different compatible downstream photon/power semantics.\",\n \"Future signal composition must match responseScope to the source plane and collection-area model so upstream transmission is not omitted or double-counted.\",\n \"This midpoint plan targets continuous spectral-density integration. Discrete/delta-like line spectra require a separate explicit representation rather than being approximated by hidden point masses.\",\n \"No source spectral values, spectral irradiance, spectral photon irradiance, optical transmission, spatial integration, temporal integration, photon/electron conversion, current calculation, or RAW output is calculated.\",\n \"Response-knot alignment and a bounded wavelength step do not prove convergence for arbitrary source spectra or other wavelength-dependent factors.\",\n \"No source-independent convergence/error estimate is reported because integration error depends on the downstream integrand.\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"spectral-quadrature.js","sourceRoot":"","sources":["../../src/sensor/spectral-quadrature.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Spectral quadrature: the wavelength sample points (nodes) and step widths used to integrate light\n * over wavelength for one sensor response channel.\n *\n * Each node is the midpoint of a wavelength interval, with its width dλ in nanometres. The intervals\n * stay inside the range the response supports. Lining nodes up with curve breakpoints does not prove\n * the integral has converged. Discrete (delta-like) emission lines need their own integration and are\n * never silently widened into continuous bins.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport type { EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n SpectralWavelengthBasis,\n SpectralWavelengthRangeNanometers\n} from \"../core/spectral.js\";\nimport {\n parseSensorColorSamplingProfile,\n type SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport {\n createSensorSpectralResponseChannelBinding,\n parseSensorSpectralResponseProfile,\n resolveSensorSpectralResponseAtWavelength,\n type SensorSpectralReferenceConditions,\n type SensorSpectralResponseProfile,\n type SensorSpectralResponseScientificStatus,\n type SensorSpectralResponseUncertainty\n} from \"./spectral-response.js\";\nimport { requireInputKeys, requireInputPositiveFinite as requirePositiveFinite, requireInputNonNegativeSafeInteger as requireNonNegativeSafeInteger } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { responseCurves } from \"./spectral-coverage.js\";\nimport { approximatelyEqual } from \"../core/shared-helpers.js\";\n\nconst MAX_SPECTRAL_QUADRATURE_NODES = 100_000;\n\nexport type SensorSpectralWavelengthRangeNanometers =\n SpectralWavelengthRangeNanometers;\n\nexport interface CalculateSensorSpectralQuadratureInput {\n colorSamplingProfile: SensorColorSamplingProfile;\n spectralResponseProfile: SensorSpectralResponseProfile;\n channelId: string;\n wavelengthBasis: SpectralWavelengthBasis;\n /**\n * Wavelength interval to sample. It is checked only against the range the\n * sensor response supports, not against scene-spectrum or optical-transmission\n * coverage.\n */\n wavelengthRangeNanometers:\n SensorSpectralWavelengthRangeNanometers;\n /**\n * Strict upper limit on the width of each midpoint subinterval.\n *\n * Response breakpoints and any additional breakpoints split the requested\n * range first. Each segment is then subdivided until every subinterval is\n * no wider than this value.\n */\n maximumSubintervalWidthNanometers: number;\n /**\n * Optional strictly increasing wavelength breakpoints from other continuous\n * spectral inputs, such as the interpolation points of a scene spectrum or of\n * optical transmission. Values must be strictly inside the requested range.\n *\n * Passing them does not claim those other inputs are available or valid over\n * the requested range.\n */\n additionalBreakpointsNanometers?:\n readonly number[];\n}\n\nexport interface SensorSpectralQuadratureNode {\n spectralSampleIndex: number;\n segmentIndex: number;\n subdivisionIndex: number;\n segmentSubdivisionCount: number;\n /**\n * Midpoint wavelength for this subinterval. Units: nm in wavelengthBasis.\n */\n wavelengthNanometers: number;\n /**\n * dλ (wavelength width) this node stands for. Units: nm.\n *\n * It is only a wavelength width. It is not response-weighted throughput,\n * a photon count, radiant energy, current or a dimensionless probability.\n */\n wavelengthMeasureNanometers: number;\n /**\n * The wavelength width divided by the requested range width (dimensionless).\n *\n * Useful for averages, but it is not a sensor-response weight.\n */\n normalizedWavelengthWeight: number;\n}\n\nexport interface SensorSpectralQuadrature {\n profileId: string;\n colorSamplingProfileId: string;\n channelId: string;\n sourceResponseKind:\n SensorSpectralResponseProfile[\"channels\"][number][\"kind\"];\n responseScope:\n SensorSpectralResponseProfile[\"channels\"][number][\"responseScope\"];\n responseScientificStatus:\n SensorSpectralResponseScientificStatus;\n responseUncertainty:\n SensorSpectralResponseUncertainty;\n responseChannelBinding?: import(\"./spectral-response.js\").SensorSpectralResponseChannelBinding;\n responseReferenceConditions?:\n SensorSpectralReferenceConditions;\n wavelengthBasis: SpectralWavelengthBasis;\n wavelengthBasisResolved: boolean;\n wavelengthRangeNanometers:\n SensorSpectralWavelengthRangeNanometers;\n responseDeclaredWavelengthRangeNanometers:\n SensorSpectralWavelengthRangeNanometers;\n quadratureScheme:\n \"response-breakpoint-aware-bounded-midpoint\";\n segmentBoundarySource:\n \"requested-range-plus-response-knots-plus-optional-additional-breakpoints\";\n /**\n * Sorted, unique segment boundaries, including the ends of the requested range.\n */\n segmentBoundariesNanometers:\n readonly number[];\n maximumSubintervalWidthNanometers: number;\n segmentCount: number;\n totalNodeCount: number;\n normalizedWavelengthWeightSum: number;\n wavelengthMeasureSumNanometers: number;\n nodes: readonly SensorSpectralQuadratureNode[];\n responseKnotAlignmentIncluded: true;\n componentEvidence: {\n colorSamplingProfile: readonly EvidenceProvenance[];\n profile: readonly EvidenceProvenance[];\n channel: readonly EvidenceProvenance[];\n curves:\n readonly (readonly EvidenceProvenance[])[];\n };\n /**\n * Fixed statements of what this result does not include.\n */\n responseValuesIncluded: false;\n responseApplicationPerformed: false;\n sourceSpectralValuesIncluded: false;\n spectralIrradianceIncluded: false;\n spectralPhotonIrradianceIncluded: false;\n commonSpectralCoverageValidated: false;\n continuousSpectralDensityQuadratureOnly: true;\n discreteLineSpectrumIncluded: false;\n opticalTransmissionIncludedByQuadrature: false;\n wavelengthIntegrationPerformed: false;\n spatialIntegrationPerformed: false;\n temporalIntegrationPerformed: false;\n photonsCalculated: false;\n electronsCalculated: false;\n rawCodeValueProduced: false;\n convergenceErrorEstimated: false;\n}\n\ninterface ResponseCurveSupport {\n wavelengthBasis: SpectralWavelengthBasis;\n samples: readonly {\n wavelengthNanometers: number;\n }[];\n evidence: readonly EvidenceProvenance[];\n}\n\ninterface SegmentPlan {\n minimum: number;\n maximum: number;\n subdivisionCount: number;\n subintervalWidthNanometers: number;\n}\n\nfunction channelResponse(\n profile: SensorSpectralResponseProfile,\n channelId: string\n): SensorSpectralResponseProfile[\"channels\"][number] {\n if (\n typeof channelId !== \"string\" ||\n channelId.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"channelId must be a non-empty string.\"\n );\n }\n\n const response = profile.channels.find(\n (entry) => entry.channelId === channelId\n );\n if (response === undefined) {\n throw new InvalidScientificInputError(\n \"No spectral response is declared for the requested channelId.\"\n );\n }\n return response;\n}\n\nfunction usableResponseRange(\n curves: readonly ResponseCurveSupport[]\n): SensorSpectralWavelengthRangeNanometers {\n const minimum = Math.max(\n ...curves.map(\n (curve) =>\n curve.samples[0]!\n .wavelengthNanometers\n )\n );\n const maximum = Math.min(\n ...curves.map(\n (curve) =>\n curve.samples[\n curve.samples.length - 1\n ]!.wavelengthNanometers\n )\n );\n\n if (!(minimum < maximum)) {\n throw new InvalidScientificInputError(\n \"Spectral-response curves do not have a usable overlapping wavelength interval.\"\n );\n }\n\n return { minimum, maximum };\n}\n\nfunction parseRequestedRange(\n input:\n SensorSpectralWavelengthRangeNanometers,\n usable:\n SensorSpectralWavelengthRangeNanometers\n): SensorSpectralWavelengthRangeNanometers {\n if (\n typeof input !== \"object\" ||\n input === null\n ) {\n throw new InvalidConfigurationError(\n \"wavelengthRangeNanometers must be an object.\"\n );\n }\n\n const minimum = requirePositiveFinite(\n input.minimum,\n \"wavelengthRangeNanometers.minimum\"\n );\n const maximum = requirePositiveFinite(\n input.maximum,\n \"wavelengthRangeNanometers.maximum\"\n );\n\n if (!(minimum < maximum)) {\n throw new InvalidScientificInputError(\n \"wavelengthRangeNanometers.minimum must be less than maximum.\"\n );\n }\n\n if (\n minimum < usable.minimum ||\n maximum > usable.maximum\n ) {\n throw new InvalidScientificInputError(\n \"Requested wavelength range must remain inside the usable spectral-response range; clipping, extrapolation, and implicit zero-fill are not permitted.\"\n );\n }\n\n return { minimum, maximum };\n}\n\nfunction additionalBreakpoints(\n value: readonly number[] | undefined,\n range:\n SensorSpectralWavelengthRangeNanometers\n): readonly number[] {\n if (value === undefined) {\n return [];\n }\n if (!Array.isArray(value)) {\n throw new InvalidConfigurationError(\n \"additionalBreakpointsNanometers must be an array when supplied.\"\n );\n }\n if (\n value.length >=\n MAX_SPECTRAL_QUADRATURE_NODES\n ) {\n throw new InvalidScientificInputError(\n \"additionalBreakpointsNanometers contains too many entries for the spectral quadrature safety limit.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n\n return value.map(\n (entry, index) => {\n const wavelength =\n requirePositiveFinite(\n entry,\n \"additionalBreakpointsNanometers[\" +\n index +\n \"]\"\n );\n if (\n wavelength <= range.minimum ||\n wavelength >= range.maximum\n ) {\n throw new InvalidScientificInputError(\n \"Additional spectral breakpoints must lie strictly inside the requested wavelength range.\"\n );\n }\n if (wavelength <= previous) {\n throw new InvalidScientificInputError(\n \"additionalBreakpointsNanometers must be strictly increasing with no duplicates.\"\n );\n }\n previous = wavelength;\n return wavelength;\n }\n );\n}\n\nfunction partitionBoundaries(\n curves: readonly ResponseCurveSupport[],\n range:\n SensorSpectralWavelengthRangeNanometers,\n additional:\n readonly number[]\n): readonly number[] {\n const boundaries = new Set<number>([\n range.minimum,\n range.maximum,\n ...additional\n ]);\n\n for (const curve of curves) {\n for (const sample of curve.samples) {\n if (\n sample.wavelengthNanometers >\n range.minimum &&\n sample.wavelengthNanometers <\n range.maximum\n ) {\n boundaries.add(\n sample.wavelengthNanometers\n );\n }\n }\n }\n\n return [...boundaries].sort(\n (a, b) => a - b\n );\n}\n\nfunction planSegments(\n boundaries: readonly number[],\n maximumSubintervalWidthNanometers: number\n): {\n segments: readonly SegmentPlan[];\n totalNodeCount: number;\n} {\n const segments: SegmentPlan[] = [];\n let totalNodeCount = 0;\n\n for (\n let segmentIndex = 0;\n segmentIndex < boundaries.length - 1;\n segmentIndex += 1\n ) {\n const minimum =\n boundaries[segmentIndex]!;\n const maximum =\n boundaries[segmentIndex + 1]!;\n const width =\n maximum - minimum;\n\n if (\n !Number.isFinite(width) ||\n width <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature segment widths must be finite and greater than zero.\"\n );\n }\n\n const subdivisionCount =\n Math.ceil(\n width /\n maximumSubintervalWidthNanometers\n );\n\n if (\n !Number.isSafeInteger(\n subdivisionCount\n ) ||\n subdivisionCount <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature segment subdivision count must be a positive safe integer.\"\n );\n }\n\n totalNodeCount +=\n subdivisionCount;\n\n if (\n !Number.isSafeInteger(\n totalNodeCount\n )\n ) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature node count must remain a safe integer.\"\n );\n }\n if (\n totalNodeCount >\n MAX_SPECTRAL_QUADRATURE_NODES\n ) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature node count exceeds the safety limit of \" +\n MAX_SPECTRAL_QUADRATURE_NODES +\n \".\"\n );\n }\n\n segments.push({\n minimum,\n maximum,\n subdivisionCount,\n subintervalWidthNanometers:\n width / subdivisionCount\n });\n }\n\n if (segments.length === 0) {\n throw new InvalidScientificInputError(\n \"Spectral quadrature requires at least one non-empty segment.\"\n );\n }\n\n return {\n segments,\n totalNodeCount\n };\n}\n\n\n\n/**\n * Rechecks a supplied wavelength plan (node identities, coverage and widths in nanometres) before it\n * is used.\n *\n * Lining nodes up with breakpoints does not prove convergence. Discrete (delta-like) lines need their\n * own integration and are never silently widened into continuous bins.\n * @param quadrature - The input; see `SensorSpectralQuadrature` for its fields, units and coordinates.\n * @returns The result. Success does not imply calibration.\n *\n * @see docs/API_REFERENCE.md for the package export and its full type definitions.\n */\nexport function validateSensorSpectralQuadrature(\n quadrature: SensorSpectralQuadrature\n): Map<number, SensorSpectralQuadratureNode> {\n if (\n typeof quadrature !== \"object\" ||\n quadrature === null ||\n !Array.isArray(quadrature.nodes)\n ) {\n throw new InvalidConfigurationError(\n \"spectralQuadrature must contain a nodes array.\"\n );\n }\n\n for (const [field, value] of [\n [\"profileId\", quadrature.profileId],\n [\n \"colorSamplingProfileId\",\n quadrature.colorSamplingProfileId\n ],\n [\"channelId\", quadrature.channelId]\n ] as const) {\n if (\n typeof value !== \"string\" ||\n value.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n \"spectralQuadrature.\" +\n field +\n \" must be a non-empty string.\"\n );\n }\n }\n\n if (\n quadrature.wavelengthBasis !== \"air\" &&\n quadrature.wavelengthBasis !== \"vacuum\" &&\n quadrature.wavelengthBasis !== \"unspecified\"\n ) {\n throw new InvalidConfigurationError(\n \"spectralQuadrature.wavelengthBasis is invalid.\"\n );\n }\n\n const rangeMinimum = requirePositiveFinite(\n quadrature.wavelengthRangeNanometers.minimum,\n \"spectralQuadrature.wavelengthRangeNanometers.minimum\"\n );\n const rangeMaximum = requirePositiveFinite(\n quadrature.wavelengthRangeNanometers.maximum,\n \"spectralQuadrature.wavelengthRangeNanometers.maximum\"\n );\n if (!(rangeMinimum < rangeMaximum)) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature wavelength range minimum must be less than maximum.\"\n );\n }\n\n const maximumSubintervalWidthNanometers =\n requirePositiveFinite(\n quadrature.maximumSubintervalWidthNanometers,\n \"spectralQuadrature.maximumSubintervalWidthNanometers\"\n );\n\n if (\n !Array.isArray(\n quadrature.segmentBoundariesNanometers\n ) ||\n quadrature.segmentBoundariesNanometers.length <\n 2\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.segmentBoundariesNanometers must contain at least two boundaries.\"\n );\n }\n\n let previousBoundary =\n Number.NEGATIVE_INFINITY;\n quadrature.segmentBoundariesNanometers.forEach(\n (boundary, index) => {\n const parsed = requirePositiveFinite(\n boundary,\n \"spectralQuadrature.segmentBoundariesNanometers[\" +\n index +\n \"]\"\n );\n if (parsed <= previousBoundary) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.segmentBoundariesNanometers must be strictly increasing.\"\n );\n }\n previousBoundary = parsed;\n }\n );\n\n const segmentCount =\n requireNonNegativeSafeInteger(\n quadrature.segmentCount,\n \"spectralQuadrature.segmentCount\"\n );\n if (\n segmentCount <= 0 ||\n segmentCount !==\n quadrature.segmentBoundariesNanometers.length -\n 1\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.segmentCount must be positive and match the boundary count.\"\n );\n }\n\n if (\n !approximatelyEqual(\n quadrature.segmentBoundariesNanometers[0]!,\n rangeMinimum\n ) ||\n !approximatelyEqual(\n quadrature.segmentBoundariesNanometers[\n quadrature.segmentBoundariesNanometers.length -\n 1\n ]!,\n rangeMaximum\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature segment boundaries must exactly span the requested wavelength range.\"\n );\n }\n\n const totalNodeCount =\n requireNonNegativeSafeInteger(\n quadrature.totalNodeCount,\n \"spectralQuadrature.totalNodeCount\"\n );\n if (\n totalNodeCount <= 0 ||\n totalNodeCount !== quadrature.nodes.length\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.totalNodeCount must be positive and exactly match spectralQuadrature.nodes.length.\"\n );\n }\n if (\n totalNodeCount >\n MAX_SPECTRAL_QUADRATURE_NODES\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature.totalNodeCount exceeds the spectral quadrature safety limit.\"\n );\n }\n\n if (\n quadrature.responseApplicationPerformed !==\n false ||\n quadrature.responseValuesIncluded !== false ||\n quadrature.wavelengthIntegrationPerformed !==\n false ||\n quadrature.continuousSpectralDensityQuadratureOnly !==\n true ||\n quadrature.discreteLineSpectrumIncluded !==\n false\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature semantics are incompatible with pre-response continuous-density composition.\"\n );\n }\n\n const span =\n rangeMaximum - rangeMinimum;\n const nodesByIndex =\n new Map<number, SensorSpectralQuadratureNode>();\n const seenSegmentSubdivisions =\n new Set<string>();\n const segmentSubdivisionCounts =\n new Map<number, number>();\n const segmentSeenCounts =\n new Map<number, number>();\n let measureSum = 0;\n let normalizedWeightSum = 0;\n\n quadrature.nodes.forEach(\n (node, nodeArrayIndex) => {\n const spectralSampleIndex =\n requireNonNegativeSafeInteger(\n node.spectralSampleIndex,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].spectralSampleIndex\"\n );\n if (\n nodesByIndex.has(\n spectralSampleIndex\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature contains duplicate spectralSampleIndex values.\"\n );\n }\n\n const segmentIndex =\n requireNonNegativeSafeInteger(\n node.segmentIndex,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].segmentIndex\"\n );\n if (segmentIndex >= segmentCount) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature node segmentIndex is outside the declared segment range.\"\n );\n }\n\n const subdivisionIndex =\n requireNonNegativeSafeInteger(\n node.subdivisionIndex,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].subdivisionIndex\"\n );\n const segmentSubdivisionCount =\n requireNonNegativeSafeInteger(\n node.segmentSubdivisionCount,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].segmentSubdivisionCount\"\n );\n if (\n segmentSubdivisionCount <= 0 ||\n subdivisionIndex >=\n segmentSubdivisionCount\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature node subdivision identity is invalid.\"\n );\n }\n\n const priorSegmentCount =\n segmentSubdivisionCounts.get(\n segmentIndex\n );\n if (\n priorSegmentCount !== undefined &&\n priorSegmentCount !==\n segmentSubdivisionCount\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature nodes disagree on a segment subdivision count.\"\n );\n }\n segmentSubdivisionCounts.set(\n segmentIndex,\n segmentSubdivisionCount\n );\n\n const subdivisionKey =\n segmentIndex +\n \":\" +\n subdivisionIndex;\n if (\n seenSegmentSubdivisions.has(\n subdivisionKey\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature contains duplicate segment/subdivision identities.\"\n );\n }\n seenSegmentSubdivisions.add(\n subdivisionKey\n );\n segmentSeenCounts.set(\n segmentIndex,\n (segmentSeenCounts.get(\n segmentIndex\n ) ?? 0) + 1\n );\n\n const wavelengthNanometers =\n requirePositiveFinite(\n node.wavelengthNanometers,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].wavelengthNanometers\"\n );\n const wavelengthMeasureNanometers =\n requirePositiveFinite(\n node.wavelengthMeasureNanometers,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].wavelengthMeasureNanometers\"\n );\n const normalizedWavelengthWeight =\n requirePositiveFinite(\n node.normalizedWavelengthWeight,\n \"spectralQuadrature.nodes[\" +\n nodeArrayIndex +\n \"].normalizedWavelengthWeight\"\n );\n\n const segmentMinimum =\n quadrature.segmentBoundariesNanometers[\n segmentIndex\n ]!;\n const segmentMaximum =\n quadrature.segmentBoundariesNanometers[\n segmentIndex + 1\n ]!;\n const expectedMeasure =\n (segmentMaximum -\n segmentMinimum) /\n segmentSubdivisionCount;\n const expectedWavelength =\n segmentMinimum +\n (subdivisionIndex + 0.5) *\n expectedMeasure;\n const expectedNormalizedWeight =\n expectedMeasure / span;\n\n if (\n !approximatelyEqual(\n wavelengthMeasureNanometers,\n expectedMeasure\n ) ||\n !approximatelyEqual(\n wavelengthNanometers,\n expectedWavelength\n ) ||\n !approximatelyEqual(\n normalizedWavelengthWeight,\n expectedNormalizedWeight\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature node geometry/measure does not match its declared segment/subdivision identity.\"\n );\n }\n\n if (\n wavelengthMeasureNanometers >\n maximumSubintervalWidthNanometers &&\n !approximatelyEqual(\n wavelengthMeasureNanometers,\n maximumSubintervalWidthNanometers\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature node width exceeds maximumSubintervalWidthNanometers.\"\n );\n }\n\n measureSum +=\n wavelengthMeasureNanometers;\n normalizedWeightSum +=\n normalizedWavelengthWeight;\n if (\n !Number.isFinite(measureSum) ||\n !Number.isFinite(\n normalizedWeightSum\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature aggregate measures must remain finite.\"\n );\n }\n\n nodesByIndex.set(\n spectralSampleIndex,\n node\n );\n }\n );\n\n for (\n let spectralSampleIndex = 0;\n spectralSampleIndex < totalNodeCount;\n spectralSampleIndex += 1\n ) {\n if (\n !nodesByIndex.has(\n spectralSampleIndex\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature spectralSampleIndex values must form the exact contiguous range 0..totalNodeCount-1.\"\n );\n }\n }\n\n for (\n let segmentIndex = 0;\n segmentIndex < segmentCount;\n segmentIndex += 1\n ) {\n const expected =\n segmentSubdivisionCounts.get(\n segmentIndex\n );\n const seen =\n segmentSeenCounts.get(\n segmentIndex\n ) ?? 0;\n if (\n expected === undefined ||\n seen !== expected\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature must contain every declared subdivision exactly once.\"\n );\n }\n }\n\n if (\n !approximatelyEqual(\n measureSum,\n span\n ) ||\n !approximatelyEqual(\n normalizedWeightSum,\n 1\n ) ||\n !approximatelyEqual(\n quadrature.wavelengthMeasureSumNanometers,\n measureSum\n ) ||\n !approximatelyEqual(\n quadrature.normalizedWavelengthWeightSum,\n normalizedWeightSum\n )\n ) {\n throw new InvalidScientificInputError(\n \"spectralQuadrature aggregate wavelength measures/weights are inconsistent.\"\n );\n }\n\n return nodesByIndex;\n}\n\n/**\n * Builds deterministic wavelength sample points for one exact sensor response\n * channel. It does not apply the response to any spectrum.\n *\n * The requested range must lie fully inside the channel's declared usable\n * range. The response curve's breakpoints, plus any caller-supplied breakpoints\n * from the source spectrum or optics, split that range into segments. Each\n * segment is then subdivided so no midpoint subinterval is wider than\n * maximumSubintervalWidthNanometers.\n *\n * wavelengthMeasureNanometers is dλ in nanometres, not a dimensionless response\n * weight. Multiply it only by a spectral density per nanometre, or convert\n * units explicitly first.\n *\n * QE and A/W responsivity are deliberately not applied. They lead to different\n * signals: QE is photon-to-electron efficiency, while A/W responsivity relates\n * incident radiant power to electrical current. A later step must also match\n * responseScope to the source plane and collection area before integrating any\n * signal.\n */\nexport function calculateSensorSpectralQuadrature(\n input:\n CalculateSensorSpectralQuadratureInput\n): CalculationResult<SensorSpectralQuadrature> {\n requireInputKeys(input, \"calculateSensorSpectralQuadrature\", [\"colorSamplingProfile\", \"spectralResponseProfile\", \"channelId\", \"wavelengthBasis\", \"wavelengthRangeNanometers\", \"maximumSubintervalWidthNanometers\", \"additionalBreakpointsNanometers\"]);\n const maximumSubintervalWidthNanometers =\n requirePositiveFinite(\n input.maximumSubintervalWidthNanometers,\n \"maximumSubintervalWidthNanometers\"\n );\n\n if (\n input.wavelengthBasis !== \"air\" &&\n input.wavelengthBasis !== \"vacuum\" &&\n input.wavelengthBasis !== \"unspecified\"\n ) {\n throw new InvalidConfigurationError(\n \"wavelengthBasis is invalid.\"\n );\n }\n\n const colorProfile =\n parseSensorColorSamplingProfile(\n input.colorSamplingProfile\n );\n const responseProfile =\n parseSensorSpectralResponseProfile(\n input.spectralResponseProfile\n );\n const response = channelResponse(\n responseProfile,\n input.channelId\n );\n const curves =\n responseCurves(response);\n\n if (\n !curves.every(\n (curve) =>\n curve.wavelengthBasis ===\n input.wavelengthBasis\n )\n ) {\n throw new InvalidScientificInputError(\n \"wavelengthBasis must exactly match every response curve used by the requested channel.\"\n );\n }\n\n const usable = usableResponseRange(\n curves\n );\n const range = parseRequestedRange(\n input.wavelengthRangeNanometers,\n usable\n );\n const validationWavelength =\n range.minimum +\n (range.maximum - range.minimum) /\n 2;\n const representative =\n resolveSensorSpectralResponseAtWavelength(\n {\n colorSamplingProfile:\n colorProfile,\n spectralResponseProfile:\n responseProfile,\n channelId: input.channelId,\n wavelengthNanometers:\n validationWavelength,\n wavelengthBasis:\n input.wavelengthBasis\n }\n ).value;\n\n const additional =\n additionalBreakpoints(\n input.additionalBreakpointsNanometers,\n range\n );\n const boundaries =\n partitionBoundaries(\n curves,\n range,\n additional\n );\n const {\n segments,\n totalNodeCount\n } = planSegments(\n boundaries,\n maximumSubintervalWidthNanometers\n );\n\n const totalSpan =\n range.maximum - range.minimum;\n const nodes:\n SensorSpectralQuadratureNode[] = [];\n let spectralSampleIndex = 0;\n\n for (\n let segmentIndex = 0;\n segmentIndex < segments.length;\n segmentIndex += 1\n ) {\n const segment =\n segments[segmentIndex]!;\n\n for (\n let subdivisionIndex = 0;\n subdivisionIndex <\n segment.subdivisionCount;\n subdivisionIndex += 1\n ) {\n const wavelengthNanometers =\n segment.minimum +\n (subdivisionIndex + 0.5) *\n segment\n .subintervalWidthNanometers;\n\n nodes.push({\n spectralSampleIndex,\n segmentIndex,\n subdivisionIndex,\n segmentSubdivisionCount:\n segment.subdivisionCount,\n wavelengthNanometers,\n wavelengthMeasureNanometers:\n segment\n .subintervalWidthNanometers,\n normalizedWavelengthWeight:\n segment\n .subintervalWidthNanometers /\n totalSpan\n });\n\n spectralSampleIndex += 1;\n }\n }\n\n const normalizedWavelengthWeightSum =\n nodes.reduce(\n (sum, node) =>\n sum +\n node.normalizedWavelengthWeight,\n 0\n );\n const wavelengthMeasureSumNanometers =\n nodes.reduce(\n (sum, node) =>\n sum +\n node.wavelengthMeasureNanometers,\n 0\n );\n\n return approximationResult(\n {\n profileId:\n representative.profileId,\n colorSamplingProfileId:\n representative\n .colorSamplingProfileId,\n channelId:\n representative.channelId,\n sourceResponseKind:\n representative\n .sourceResponseKind,\n responseScope:\n representative.responseScope,\n responseScientificStatus:\n representative\n .scientificStatus,\n responseUncertainty:\n representative.uncertainty,\n responseChannelBinding:\n createSensorSpectralResponseChannelBinding(\n response\n ),\n ...(representative.referenceConditions ===\n undefined\n ? {}\n : {\n responseReferenceConditions:\n representative.referenceConditions\n }),\n wavelengthBasis:\n input.wavelengthBasis,\n wavelengthBasisResolved:\n representative\n .wavelengthBasisResolved,\n wavelengthRangeNanometers:\n range,\n responseDeclaredWavelengthRangeNanometers:\n usable,\n quadratureScheme:\n \"response-breakpoint-aware-bounded-midpoint\",\n segmentBoundarySource:\n \"requested-range-plus-response-knots-plus-optional-additional-breakpoints\",\n segmentBoundariesNanometers:\n [...boundaries],\n maximumSubintervalWidthNanometers,\n segmentCount:\n segments.length,\n totalNodeCount,\n normalizedWavelengthWeightSum,\n wavelengthMeasureSumNanometers,\n nodes,\n responseKnotAlignmentIncluded:\n true,\n componentEvidence: {\n colorSamplingProfile:\n colorProfile.evidence,\n profile:\n representative\n .componentEvidence.profile,\n channel:\n representative\n .componentEvidence.channel,\n curves:\n representative\n .componentEvidence.curves\n },\n responseValuesIncluded: false,\n responseApplicationPerformed:\n false,\n sourceSpectralValuesIncluded:\n false,\n spectralIrradianceIncluded:\n false,\n spectralPhotonIrradianceIncluded:\n false,\n commonSpectralCoverageValidated:\n false,\n continuousSpectralDensityQuadratureOnly:\n true,\n discreteLineSpectrumIncluded:\n false,\n opticalTransmissionIncludedByQuadrature:\n false,\n wavelengthIntegrationPerformed:\n false,\n spatialIntegrationPerformed:\n false,\n temporalIntegrationPerformed:\n false,\n photonsCalculated: false,\n electronsCalculated: false,\n rawCodeValueProduced: false,\n convergenceErrorEstimated: false\n },\n \"sensor-spectral-quadrature\",\n \"1.0.0\",\n [\n \"The partition includes requested range edges, response-curve knots, and optional caller-supplied continuous-spectrum/optics breakpoints.\",\n \"Every segment uses deterministic equal-width midpoint subintervals whose width is bounded by maximumSubintervalWidthNanometers.\",\n \"Wavelength measures are d-lambda in nanometres and normalized wavelength weights are geometric wavelength measures only; neither is sensor-response weighting.\",\n \"The requested range is validated against sensor-response support only. Scene-spectrum, optical-transmission, and other spectral-factor coverage are not established by this API.\",\n \"The response profile, exact channel linkage, wavelength basis, response scope, and response-data provenance are validated and preserved, but response values are not applied at quadrature nodes.\",\n \"QE and A/W responsivity remain different physical representations and require different compatible downstream photon/power semantics.\",\n \"Future signal composition must match responseScope to the source plane and collection-area model so upstream transmission is not omitted or double-counted.\",\n \"This midpoint plan targets continuous spectral-density integration. Discrete/delta-like line spectra require a separate explicit representation rather than being approximated by hidden point masses.\",\n \"No source spectral values, spectral irradiance, spectral photon irradiance, optical transmission, spatial integration, temporal integration, photon/electron conversion, current calculation, or RAW output is calculated.\",\n \"Response-knot alignment and a bounded wavelength step do not prove convergence for arbitrary source spectra or other wavelength-dependent factors.\",\n \"No source-independent convergence/error estimate is reported because integration error depends on the downstream integrand.\"\n ]\n );\n}\n"]}
@@ -21,6 +21,9 @@ export interface SensorEqeTemporalPhotoSignal extends Omit<SensorEqeExposureInte
21
21
  };
22
22
  }
23
23
  export type SensorEqePhotoExposure = SensorEqeExposureIntegration | SensorEqeTemporalPhotoSignal;
24
+ /** The photo-signal flags that must be true, and (after the first) false, shared with the RAW producer's checks. */
25
+ export declare const PHOTO_SIGNAL_TRUE_FIELDS: readonly ["temporalIntegrationApplied", "exposureDurationApplied", "countsAreExpectationValues"];
26
+ export declare const PHOTO_SIGNAL_DOWNSTREAM_FALSE_FIELDS: readonly ["multiFrameSequenceIntegrated", "darkChargeIncluded", "otherChargeIncluded", "physicalFullWellAssessmentAuthorized", "cameraSaturationAssessmentAuthorized", "saturationAssessed", "shotNoiseApplied", "readNoiseApplied", "adcQuantizationApplied", "rawCodeValueProduced", "integerPhotonCountSampled", "integerElectronCountSampled", "chargeCalculated", "currentCalculated"];
24
27
  /** Parses a time-resolved photo signal and recomputes its midpoint sums exactly. Declarations do not prove the source is correct. */
25
28
  export declare function parseSensorEqeTemporalPhotoSignal(value: unknown): SensorEqeTemporalPhotoSignal;
26
29
  /**
@@ -15,11 +15,14 @@ import { InvalidConfigurationError } from "../core/configuration-error.js";
15
15
  import { parseEvidenceList } from "../core/evidence-provenance.js";
16
16
  import { freezeOwnedData } from "../core/owned-data.js";
17
17
  import { calculateSensorEqeTemporalExposure } from "./eqe-temporal-exposure.js";
18
- const trueFields = ["temporalIntegrationApplied", "exposureDurationApplied", "countsAreExpectationValues"];
19
- const falseFields = ["timeStationarityEstablished", "multiFrameSequenceIntegrated", "darkChargeIncluded", "otherChargeIncluded",
18
+ /** The photo-signal flags that must be true, and (after the first) false, shared with the RAW producer's checks. */
19
+ export const PHOTO_SIGNAL_TRUE_FIELDS = ["temporalIntegrationApplied", "exposureDurationApplied", "countsAreExpectationValues"];
20
+ export const PHOTO_SIGNAL_DOWNSTREAM_FALSE_FIELDS = ["multiFrameSequenceIntegrated", "darkChargeIncluded", "otherChargeIncluded",
20
21
  "physicalFullWellAssessmentAuthorized", "cameraSaturationAssessmentAuthorized", "saturationAssessed", "shotNoiseApplied",
21
22
  "readNoiseApplied", "adcQuantizationApplied", "rawCodeValueProduced", "integerPhotonCountSampled", "integerElectronCountSampled",
22
23
  "chargeCalculated", "currentCalculated"];
24
+ const trueFields = PHOTO_SIGNAL_TRUE_FIELDS;
25
+ const falseFields = ["timeStationarityEstablished", ...PHOTO_SIGNAL_DOWNSTREAM_FALSE_FIELDS];
23
26
  function record(value, keys) {
24
27
  return requireAllowlistedRecord(value, keys, "Invalid temporal photo-signal fields.");
25
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"temporal-photo-signal.js","sourceRoot":"","sources":["../../src/sensor/temporal-photo-signal.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Time-resolved photo signal: a compact record of the expected photon and electron counts for one site\n * over one exposure, with the rate at each midpoint time.\n *\n * Creating it re-evaluates the physical input at every time. Parsing it recomputes the midpoint sums\n * exactly. Neither proves the light source is correct.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport { requireAllowlistedRecord, requirePublicOpaqueId, requireInputKeys } from \"../core/record-validation.js\";\nimport { sumTemporalEqeRateExpectations } from \"./temporal-eqe-sum.js\";\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { parseEvidenceList, type EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport { freezeOwnedData } from \"../core/owned-data.js\";\nimport type { SensorEqeExposureIntegration } from \"./constant-rate-temporal-integration.js\";\nimport { calculateSensorEqeTemporalExposure, type CalculateSensorEqeTemporalExposureInput } from \"./eqe-temporal-exposure.js\";\n\n/** Compact expected photo signal, before any processing. Being tied to a time window is not evidence that the light was constant. */\nexport interface SensorEqeTemporalPhotoSignal extends Omit<SensorEqeExposureIntegration,\n \"kind\" | \"stationarityProfileId\" | \"stationarityStatus\" | \"integrationMethod\" | \"timeStationarityEstablished\" |\n \"timeVaryingSignalIntegrated\" | \"incidentPhotonRatePerSecond\" | \"expectedGeneratedElectronRatePerSecond\" | \"componentEvidence\"> {\n kind: \"eqe-temporal-photo-signal\";\n temporalIntegrationId: string;\n stationarityProfileId?: never;\n integrationMethod: \"uniform-midpoint-rate-quadrature\";\n timeStationarityEstablished: false;\n timeVaryingSignalIntegrated: true;\n temporalSamples: readonly {\n timeSecondsFromOpeningReference: number;\n incidentPhotonRatePerSecond: number;\n expectedGeneratedElectronRatePerSecond: number;\n }[];\n componentEvidence: {\n temporalResponse: readonly EvidenceProvenance[];\n exposureBinding: SensorEqeExposureIntegration[\"componentEvidence\"][\"exposureBinding\"];\n };\n}\n\nexport type SensorEqePhotoExposure = SensorEqeExposureIntegration | SensorEqeTemporalPhotoSignal;\n\nconst trueFields = [\"temporalIntegrationApplied\", \"exposureDurationApplied\", \"countsAreExpectationValues\"] as const;\nconst falseFields = [\"timeStationarityEstablished\", \"multiFrameSequenceIntegrated\", \"darkChargeIncluded\", \"otherChargeIncluded\",\n \"physicalFullWellAssessmentAuthorized\", \"cameraSaturationAssessmentAuthorized\", \"saturationAssessed\", \"shotNoiseApplied\",\n \"readNoiseApplied\", \"adcQuantizationApplied\", \"rawCodeValueProduced\", \"integerPhotonCountSampled\", \"integerElectronCountSampled\",\n \"chargeCalculated\", \"currentCalculated\"] as const;\nfunction record(value: unknown, keys: readonly string[]): Record<string, unknown> {\n return requireAllowlistedRecord(value, keys, \"Invalid temporal photo-signal fields.\");\n}\nfunction identity(value: unknown): string {\n return requirePublicOpaqueId(value, \"Temporal photo signal requires public identities.\");\n}\nfunction finite(value: unknown, nonnegative = true): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || (nonnegative && value < 0)) {\n throw new InvalidConfigurationError(\"Temporal photo-signal numeric quantity is invalid.\");\n }\n return value;\n}\n/** Parses a time-resolved photo signal and recomputes its midpoint sums exactly. Declarations do not prove the source is correct. */\nexport function parseSensorEqeTemporalPhotoSignal(value: unknown): SensorEqeTemporalPhotoSignal {\n const r = record(value, [...trueFields, ...falseFields, \"kind\", \"temporalIntegrationId\", \"colorSamplingProfileId\", \"channelId\", \"site\",\n \"bindingId\", \"timeReference\", \"startOffsetSecondsFromOpeningReference\", \"endOffsetSecondsFromOpeningReference\",\n \"localExposureDurationSeconds\", \"integrationMethod\", \"timeVaryingSignalIntegrated\", \"accumulatedSignalCompleteness\",\n \"expectedIncidentPhotonCount\", \"expectedGeneratedElectronCount\", \"temporalSamples\", \"componentEvidence\"]);\n if (r.kind !== \"eqe-temporal-photo-signal\" || r.integrationMethod !== \"uniform-midpoint-rate-quadrature\" ||\n r.timeReference !== \"first-opening-boundary-phase\" || r.timeVaryingSignalIntegrated !== true ||\n r.accumulatedSignalCompleteness !== \"photo-signal-only\" || trueFields.some(k => r[k] !== true) || falseFields.some(k => r[k] !== false)) {\n throw new InvalidConfigurationError(\"Temporal photo signal requires untreated midpoint-integrated EQE expectations.\");\n }\n const site = record(r.site, [\"x\", \"y\"]);\n for (const v of [site.x, site.y]) if (!Number.isSafeInteger(v) || (v as number) < 0) throw new InvalidConfigurationError(\"Invalid temporal photo site.\");\n const start = finite(r.startOffsetSecondsFromOpeningReference), end = finite(r.endOffsetSecondsFromOpeningReference);\n const duration = finite(r.localExposureDurationSeconds);\n if (!(end > start) || duration !== end-start) throw new InvalidConfigurationError(\"Temporal photo signal requires exact local duration.\");\n if (!Array.isArray(r.temporalSamples) || r.temporalSamples.length < 1 || r.temporalSamples.length > 256) {\n throw new InvalidConfigurationError(\"Temporal photo signal requires 1 through 256 dense midpoint samples.\");\n }\n const measure = duration / r.temporalSamples.length;\n let previous = -Infinity;\n const temporalSamples = Array.from(r.temporalSamples, (v, i) => {\n const s = record(v, [\"timeSecondsFromOpeningReference\", \"incidentPhotonRatePerSecond\", \"expectedGeneratedElectronRatePerSecond\"]);\n const time = finite(s.timeSecondsFromOpeningReference), pr = finite(s.incidentPhotonRatePerSecond), er = finite(s.expectedGeneratedElectronRatePerSecond);\n if (time !== start + (i+0.5)*measure || !(time > start && time > previous && time < end) || er > pr) {\n throw new InvalidConfigurationError(\"Temporal photo midpoint time or EQE rate domain is invalid.\");\n }\n previous = time;\n return { timeSecondsFromOpeningReference: time, incidentPhotonRatePerSecond: pr, expectedGeneratedElectronRatePerSecond: er };\n });\n const { photonCount: photons, electronCount: electrons } = sumTemporalEqeRateExpectations(temporalSamples, measure);\n if (!Number.isFinite(photons) || !Number.isFinite(electrons) ||\n finite(r.expectedIncidentPhotonCount) !== photons || finite(r.expectedGeneratedElectronCount) !== electrons) {\n throw new InvalidConfigurationError(\"Temporal photo counts do not match the exact midpoint rate integral.\");\n }\n const e = record(r.componentEvidence, [\"temporalResponse\", \"exposureBinding\"]);\n const b = record(e.exposureBinding, [\"binding\", \"colorSamplingProfile\", \"nominalExposureDuration\", \"openingBoundary\", \"closingBoundary\"]);\n const optional = (v: unknown): readonly EvidenceProvenance[] => Array.isArray(v) && v.length === 0 ? [] : parseEvidenceList(v, \"temporalExposureEvidence\");\n return { ...r, colorSamplingProfileId: identity(r.colorSamplingProfileId), channelId: identity(r.channelId),\n bindingId: identity(r.bindingId), temporalIntegrationId: identity(r.temporalIntegrationId), site: { x: site.x as number, y: site.y as number }, temporalSamples,\n componentEvidence: { temporalResponse: parseEvidenceList(e.temporalResponse, \"temporalResponse\"), exposureBinding: {\n binding: parseEvidenceList(b.binding, \"binding\"), colorSamplingProfile: parseEvidenceList(b.colorSamplingProfile, \"colorSamplingProfile\"),\n nominalExposureDuration: parseEvidenceList(b.nominalExposureDuration, \"nominalExposureDuration\"),\n openingBoundary: optional(b.openingBoundary), closingBoundary: optional(b.closingBoundary) } }\n } as unknown as SensorEqeTemporalPhotoSignal;\n}\n\n/**\n * Re-evaluates the physical input at every time, then commits a compact photo\n * signal. Detailed per-step evidence and uncertainty stay in the returned\n * exposure diagnostics. This does not show that a renderer or source actually ran.\n */\nexport function createSensorEqeTemporalPhotoSignal(input: {\n temporalIntegrationId: string;\n exposure: CalculateSensorEqeTemporalExposureInput;\n}): CalculationResult<{ photoSignal: SensorEqeTemporalPhotoSignal; exposure: ReturnType<typeof calculateSensorEqeTemporalExposure> }> {\n requireInputKeys(input, \"createSensorEqeTemporalPhotoSignal\", [\"temporalIntegrationId\", \"exposure\"]);\n identity(input.temporalIntegrationId);\n const exposure = calculateSensorEqeTemporalExposure(input.exposure), v = exposure.value;\n const p = { kind: \"eqe-temporal-photo-signal\", temporalIntegrationId: input.temporalIntegrationId,\n colorSamplingProfileId: v.colorSamplingProfileId, channelId: v.channelId, site: v.site, bindingId: v.bindingId,\n timeReference: v.timeReference, startOffsetSecondsFromOpeningReference: v.startOffsetSecondsFromOpeningReference,\n endOffsetSecondsFromOpeningReference: v.endOffsetSecondsFromOpeningReference, localExposureDurationSeconds: v.localExposureDurationSeconds,\n expectedIncidentPhotonCount: v.expectedIncidentPhotonCount, expectedGeneratedElectronCount: v.expectedGeneratedElectronCount,\n integrationMethod: v.integrationMethod, timeVaryingSignalIntegrated: true, accumulatedSignalCompleteness: \"photo-signal-only\",\n temporalSamples: v.samples.map(s => ({ timeSecondsFromOpeningReference: s.timeSecondsFromOpeningReference,\n incidentPhotonRatePerSecond: s.stages.electronRate.value.incidentPhotonRatePerSecond,\n expectedGeneratedElectronRatePerSecond: s.stages.electronRate.value.expectedGeneratedElectronRatePerSecond })),\n componentEvidence: { temporalResponse: v.samples[0]!.stages.spectralQuadrature.value.componentEvidence.profile,\n exposureBinding: v.samples[0]!.stages.exposureBinding.value.componentEvidence },\n ...Object.fromEntries(trueFields.map(k => [k, true])), ...Object.fromEntries(falseFields.map(k => [k, false])) };\n const photoSignal = freezeOwnedData(parseSensorEqeTemporalPhotoSignal(p));\n return approximationResult({ photoSignal, exposure }, \"sensor-eqe-temporal-photo-signal\", \"0.1.0\", [\n \"Photo signal commits explicit midpoint EQE rate expectations, not stationarity or source-truth evidence.\",\n \"Child diagnostics retain separate validity, evidence and uncombined uncertainty; no convergence bound is inferred.\"\n ]);\n}\n"]}
1
+ {"version":3,"file":"temporal-photo-signal.js","sourceRoot":"","sources":["../../src/sensor/temporal-photo-signal.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Time-resolved photo signal: a compact record of the expected photon and electron counts for one site\n * over one exposure, with the rate at each midpoint time.\n *\n * Creating it re-evaluates the physical input at every time. Parsing it recomputes the midpoint sums\n * exactly. Neither proves the light source is correct.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport { requireAllowlistedRecord, requirePublicOpaqueId, requireInputKeys } from \"../core/record-validation.js\";\nimport { sumTemporalEqeRateExpectations } from \"./temporal-eqe-sum.js\";\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { parseEvidenceList, type EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport { freezeOwnedData } from \"../core/owned-data.js\";\nimport type { SensorEqeExposureIntegration } from \"./constant-rate-temporal-integration.js\";\nimport { calculateSensorEqeTemporalExposure, type CalculateSensorEqeTemporalExposureInput } from \"./eqe-temporal-exposure.js\";\n\n/** Compact expected photo signal, before any processing. Being tied to a time window is not evidence that the light was constant. */\nexport interface SensorEqeTemporalPhotoSignal extends Omit<SensorEqeExposureIntegration,\n \"kind\" | \"stationarityProfileId\" | \"stationarityStatus\" | \"integrationMethod\" | \"timeStationarityEstablished\" |\n \"timeVaryingSignalIntegrated\" | \"incidentPhotonRatePerSecond\" | \"expectedGeneratedElectronRatePerSecond\" | \"componentEvidence\"> {\n kind: \"eqe-temporal-photo-signal\";\n temporalIntegrationId: string;\n stationarityProfileId?: never;\n integrationMethod: \"uniform-midpoint-rate-quadrature\";\n timeStationarityEstablished: false;\n timeVaryingSignalIntegrated: true;\n temporalSamples: readonly {\n timeSecondsFromOpeningReference: number;\n incidentPhotonRatePerSecond: number;\n expectedGeneratedElectronRatePerSecond: number;\n }[];\n componentEvidence: {\n temporalResponse: readonly EvidenceProvenance[];\n exposureBinding: SensorEqeExposureIntegration[\"componentEvidence\"][\"exposureBinding\"];\n };\n}\n\nexport type SensorEqePhotoExposure = SensorEqeExposureIntegration | SensorEqeTemporalPhotoSignal;\n\n/** The photo-signal flags that must be true, and (after the first) false, shared with the RAW producer's checks. */\nexport const PHOTO_SIGNAL_TRUE_FIELDS = [\"temporalIntegrationApplied\", \"exposureDurationApplied\", \"countsAreExpectationValues\"] as const;\nexport const PHOTO_SIGNAL_DOWNSTREAM_FALSE_FIELDS = [\"multiFrameSequenceIntegrated\", \"darkChargeIncluded\", \"otherChargeIncluded\",\n \"physicalFullWellAssessmentAuthorized\", \"cameraSaturationAssessmentAuthorized\", \"saturationAssessed\", \"shotNoiseApplied\",\n \"readNoiseApplied\", \"adcQuantizationApplied\", \"rawCodeValueProduced\", \"integerPhotonCountSampled\", \"integerElectronCountSampled\",\n \"chargeCalculated\", \"currentCalculated\"] as const;\nconst trueFields = PHOTO_SIGNAL_TRUE_FIELDS;\nconst falseFields = [\"timeStationarityEstablished\", ...PHOTO_SIGNAL_DOWNSTREAM_FALSE_FIELDS] as const;\nfunction record(value: unknown, keys: readonly string[]): Record<string, unknown> {\n return requireAllowlistedRecord(value, keys, \"Invalid temporal photo-signal fields.\");\n}\nfunction identity(value: unknown): string {\n return requirePublicOpaqueId(value, \"Temporal photo signal requires public identities.\");\n}\nfunction finite(value: unknown, nonnegative = true): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || (nonnegative && value < 0)) {\n throw new InvalidConfigurationError(\"Temporal photo-signal numeric quantity is invalid.\");\n }\n return value;\n}\n/** Parses a time-resolved photo signal and recomputes its midpoint sums exactly. Declarations do not prove the source is correct. */\nexport function parseSensorEqeTemporalPhotoSignal(value: unknown): SensorEqeTemporalPhotoSignal {\n const r = record(value, [...trueFields, ...falseFields, \"kind\", \"temporalIntegrationId\", \"colorSamplingProfileId\", \"channelId\", \"site\",\n \"bindingId\", \"timeReference\", \"startOffsetSecondsFromOpeningReference\", \"endOffsetSecondsFromOpeningReference\",\n \"localExposureDurationSeconds\", \"integrationMethod\", \"timeVaryingSignalIntegrated\", \"accumulatedSignalCompleteness\",\n \"expectedIncidentPhotonCount\", \"expectedGeneratedElectronCount\", \"temporalSamples\", \"componentEvidence\"]);\n if (r.kind !== \"eqe-temporal-photo-signal\" || r.integrationMethod !== \"uniform-midpoint-rate-quadrature\" ||\n r.timeReference !== \"first-opening-boundary-phase\" || r.timeVaryingSignalIntegrated !== true ||\n r.accumulatedSignalCompleteness !== \"photo-signal-only\" || trueFields.some(k => r[k] !== true) || falseFields.some(k => r[k] !== false)) {\n throw new InvalidConfigurationError(\"Temporal photo signal requires untreated midpoint-integrated EQE expectations.\");\n }\n const site = record(r.site, [\"x\", \"y\"]);\n for (const v of [site.x, site.y]) if (!Number.isSafeInteger(v) || (v as number) < 0) throw new InvalidConfigurationError(\"Invalid temporal photo site.\");\n const start = finite(r.startOffsetSecondsFromOpeningReference), end = finite(r.endOffsetSecondsFromOpeningReference);\n const duration = finite(r.localExposureDurationSeconds);\n if (!(end > start) || duration !== end-start) throw new InvalidConfigurationError(\"Temporal photo signal requires exact local duration.\");\n if (!Array.isArray(r.temporalSamples) || r.temporalSamples.length < 1 || r.temporalSamples.length > 256) {\n throw new InvalidConfigurationError(\"Temporal photo signal requires 1 through 256 dense midpoint samples.\");\n }\n const measure = duration / r.temporalSamples.length;\n let previous = -Infinity;\n const temporalSamples = Array.from(r.temporalSamples, (v, i) => {\n const s = record(v, [\"timeSecondsFromOpeningReference\", \"incidentPhotonRatePerSecond\", \"expectedGeneratedElectronRatePerSecond\"]);\n const time = finite(s.timeSecondsFromOpeningReference), pr = finite(s.incidentPhotonRatePerSecond), er = finite(s.expectedGeneratedElectronRatePerSecond);\n if (time !== start + (i+0.5)*measure || !(time > start && time > previous && time < end) || er > pr) {\n throw new InvalidConfigurationError(\"Temporal photo midpoint time or EQE rate domain is invalid.\");\n }\n previous = time;\n return { timeSecondsFromOpeningReference: time, incidentPhotonRatePerSecond: pr, expectedGeneratedElectronRatePerSecond: er };\n });\n const { photonCount: photons, electronCount: electrons } = sumTemporalEqeRateExpectations(temporalSamples, measure);\n if (!Number.isFinite(photons) || !Number.isFinite(electrons) ||\n finite(r.expectedIncidentPhotonCount) !== photons || finite(r.expectedGeneratedElectronCount) !== electrons) {\n throw new InvalidConfigurationError(\"Temporal photo counts do not match the exact midpoint rate integral.\");\n }\n const e = record(r.componentEvidence, [\"temporalResponse\", \"exposureBinding\"]);\n const b = record(e.exposureBinding, [\"binding\", \"colorSamplingProfile\", \"nominalExposureDuration\", \"openingBoundary\", \"closingBoundary\"]);\n const optional = (v: unknown): readonly EvidenceProvenance[] => Array.isArray(v) && v.length === 0 ? [] : parseEvidenceList(v, \"temporalExposureEvidence\");\n return { ...r, colorSamplingProfileId: identity(r.colorSamplingProfileId), channelId: identity(r.channelId),\n bindingId: identity(r.bindingId), temporalIntegrationId: identity(r.temporalIntegrationId), site: { x: site.x as number, y: site.y as number }, temporalSamples,\n componentEvidence: { temporalResponse: parseEvidenceList(e.temporalResponse, \"temporalResponse\"), exposureBinding: {\n binding: parseEvidenceList(b.binding, \"binding\"), colorSamplingProfile: parseEvidenceList(b.colorSamplingProfile, \"colorSamplingProfile\"),\n nominalExposureDuration: parseEvidenceList(b.nominalExposureDuration, \"nominalExposureDuration\"),\n openingBoundary: optional(b.openingBoundary), closingBoundary: optional(b.closingBoundary) } }\n } as unknown as SensorEqeTemporalPhotoSignal;\n}\n\n/**\n * Re-evaluates the physical input at every time, then commits a compact photo\n * signal. Detailed per-step evidence and uncertainty stay in the returned\n * exposure diagnostics. This does not show that a renderer or source actually ran.\n */\nexport function createSensorEqeTemporalPhotoSignal(input: {\n temporalIntegrationId: string;\n exposure: CalculateSensorEqeTemporalExposureInput;\n}): CalculationResult<{ photoSignal: SensorEqeTemporalPhotoSignal; exposure: ReturnType<typeof calculateSensorEqeTemporalExposure> }> {\n requireInputKeys(input, \"createSensorEqeTemporalPhotoSignal\", [\"temporalIntegrationId\", \"exposure\"]);\n identity(input.temporalIntegrationId);\n const exposure = calculateSensorEqeTemporalExposure(input.exposure), v = exposure.value;\n const p = { kind: \"eqe-temporal-photo-signal\", temporalIntegrationId: input.temporalIntegrationId,\n colorSamplingProfileId: v.colorSamplingProfileId, channelId: v.channelId, site: v.site, bindingId: v.bindingId,\n timeReference: v.timeReference, startOffsetSecondsFromOpeningReference: v.startOffsetSecondsFromOpeningReference,\n endOffsetSecondsFromOpeningReference: v.endOffsetSecondsFromOpeningReference, localExposureDurationSeconds: v.localExposureDurationSeconds,\n expectedIncidentPhotonCount: v.expectedIncidentPhotonCount, expectedGeneratedElectronCount: v.expectedGeneratedElectronCount,\n integrationMethod: v.integrationMethod, timeVaryingSignalIntegrated: true, accumulatedSignalCompleteness: \"photo-signal-only\",\n temporalSamples: v.samples.map(s => ({ timeSecondsFromOpeningReference: s.timeSecondsFromOpeningReference,\n incidentPhotonRatePerSecond: s.stages.electronRate.value.incidentPhotonRatePerSecond,\n expectedGeneratedElectronRatePerSecond: s.stages.electronRate.value.expectedGeneratedElectronRatePerSecond })),\n componentEvidence: { temporalResponse: v.samples[0]!.stages.spectralQuadrature.value.componentEvidence.profile,\n exposureBinding: v.samples[0]!.stages.exposureBinding.value.componentEvidence },\n ...Object.fromEntries(trueFields.map(k => [k, true])), ...Object.fromEntries(falseFields.map(k => [k, false])) };\n const photoSignal = freezeOwnedData(parseSensorEqeTemporalPhotoSignal(p));\n return approximationResult({ photoSignal, exposure }, \"sensor-eqe-temporal-photo-signal\", \"0.1.0\", [\n \"Photo signal commits explicit midpoint EQE rate expectations, not stationarity or source-truth evidence.\",\n \"Child diagnostics retain separate validity, evidence and uncombined uncertainty; no convergence bound is inferred.\"\n ]);\n}\n"]}
@@ -16,6 +16,15 @@ import { type CalculationResult } from "../core/calculation-result.js";
16
16
  import { type EvidenceBackedFact, type EvidenceProvenance } from "../core/evidence-provenance.js";
17
17
  import type { RasterPoint } from "../output/capture-geometry.js";
18
18
  import type { ResolvedCaptureModeTiming } from "../sensor/capture-mode-timing.js";
19
+ /** The scope flags every stabilization profile declares false, in this order (shared with the generic tier assets). */
20
+ export declare const STABILIZATION_UNMODELED_SCOPE: Readonly<{
21
+ controlTransientModeled: false;
22
+ spontaneousDriftModeled: false;
23
+ cameraTranslationCorrectionModeled: false;
24
+ digitalStabilizationIncluded: false;
25
+ supportPolicyIncluded: false;
26
+ stopRatingUsedAsDynamicResponse: false;
27
+ }>;
19
28
  export declare const STABILIZATION_SYSTEM_PROFILE_SCHEMA_VERSION: "0.1.0";
20
29
  export declare const STABILIZATION_DISTURBANCE_TRAJECTORY_VERSION: "0.1.0";
21
30
  export type StabilizationRotationAxis = "pitch" | "yaw" | "roll";
@@ -19,6 +19,10 @@ import { parseEvidenceList } from "../core/evidence-provenance.js";
19
19
  import { InvalidScientificInputError, requirePositiveInteger } from "../core/validation.js";
20
20
  import { requireInputKeys, requireInputObject, rejectUnknownVariantFields, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, requireConfigFinite as requireFinite, parseConfigLimitations as parseLimitations, requireConfigFiniteAtLeastZero as requireNonNegativeFinite, requireConfigUnitInterval as requireFraction } from "../core/record-validation.js";
21
21
  import { parseNonNegativeFact, parsePositiveFact } from "../sensor/raw-readout.js";
22
+ /** The scope flags every stabilization profile declares false, in this order (shared with the generic tier assets). */
23
+ export const STABILIZATION_UNMODELED_SCOPE = /* @__PURE__ */ Object.freeze({ controlTransientModeled: false, spontaneousDriftModeled: false,
24
+ cameraTranslationCorrectionModeled: false, digitalStabilizationIncluded: false, supportPolicyIncluded: false,
25
+ stopRatingUsedAsDynamicResponse: false });
22
26
  export const STABILIZATION_SYSTEM_PROFILE_SCHEMA_VERSION = "0.1.0";
23
27
  export const STABILIZATION_DISTURBANCE_TRAJECTORY_VERSION = "0.1.0";
24
28
  const AXES = new Set([
@@ -219,12 +223,7 @@ export function parseStabilizationSystemProfile(value) {
219
223
  : {
220
224
  coordinatedAllocation
221
225
  }),
222
- controlTransientModeled: false,
223
- spontaneousDriftModeled: false,
224
- cameraTranslationCorrectionModeled: false,
225
- digitalStabilizationIncluded: false,
226
- supportPolicyIncluded: false,
227
- stopRatingUsedAsDynamicResponse: false,
226
+ ...STABILIZATION_UNMODELED_SCOPE,
228
227
  evidence: parseEvidenceList(record.evidence, "stabilizationSystemProfile.evidence"),
229
228
  limitations: parseLimitations(record.limitations, "stabilizationSystemProfile.limitations")
230
229
  };