@photivra/engine 2.17.0 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/CHANGELOG.md +116 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/dist/camera/sensor-equivalence.d.ts +156 -0
  6. package/dist/camera/sensor-equivalence.js +208 -0
  7. package/dist/camera/sensor-equivalence.js.map +1 -0
  8. package/dist/capture/focus-control.js +2 -75
  9. package/dist/capture/focus-control.js.map +1 -1
  10. package/dist/capture/native-environment-lean.d.ts +1 -0
  11. package/dist/capture/native-environment-lean.js +1 -1
  12. package/dist/capture/native-environment-lean.js.map +1 -1
  13. package/dist/capture/native-environment-optical-stage.d.ts +1 -0
  14. package/dist/capture/native-environment-optical-stage.js +2 -5
  15. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  16. package/dist/capture/native-environment-raw.js +1 -5
  17. package/dist/capture/native-environment-raw.js.map +1 -1
  18. package/dist/capture/native-environment-tabulated.d.ts +5 -0
  19. package/dist/capture/native-environment-tabulated.js +1 -1
  20. package/dist/capture/native-environment-tabulated.js.map +1 -1
  21. package/dist/capture/release-sequence.js +2 -47
  22. package/dist/capture/release-sequence.js.map +1 -1
  23. package/dist/color/white-balance.d.ts +2 -0
  24. package/dist/color/white-balance.js +3 -56
  25. package/dist/color/white-balance.js.map +1 -1
  26. package/dist/composition/image-formation-plan.d.ts +1 -0
  27. package/dist/composition/image-formation-plan.js +3 -49
  28. package/dist/composition/image-formation-plan.js.map +1 -1
  29. package/dist/core/evidence-provenance.js +1 -16
  30. package/dist/core/evidence-provenance.js.map +1 -1
  31. package/dist/core/record-validation.d.ts +54 -0
  32. package/dist/core/record-validation.js +176 -0
  33. package/dist/core/record-validation.js.map +1 -1
  34. package/dist/core/scientific-assurance.js +1 -18
  35. package/dist/core/scientific-assurance.js.map +1 -1
  36. package/dist/core/shared-helpers.d.ts +33 -0
  37. package/dist/core/shared-helpers.js +34 -0
  38. package/dist/core/shared-helpers.js.map +1 -0
  39. package/dist/core/spectral-composition.js +1 -36
  40. package/dist/core/spectral-composition.js.map +1 -1
  41. package/dist/core/spectral.js +1 -19
  42. package/dist/core/spectral.js.map +1 -1
  43. package/dist/core/version.d.ts +1 -1
  44. package/dist/core/version.js +1 -1
  45. package/dist/core/version.js.map +1 -1
  46. package/dist/equipment/exposure-capabilities.d.ts +1 -0
  47. package/dist/equipment/exposure-capabilities.js +2 -36
  48. package/dist/equipment/exposure-capabilities.js.map +1 -1
  49. package/dist/equipment/ideal-lens.d.ts +25 -0
  50. package/dist/equipment/ideal-lens.js +21 -0
  51. package/dist/equipment/ideal-lens.js.map +1 -0
  52. package/dist/equipment/iso-capabilities.js +2 -49
  53. package/dist/equipment/iso-capabilities.js.map +1 -1
  54. package/dist/equipment/metering-capabilities.js +1 -26
  55. package/dist/equipment/metering-capabilities.js.map +1 -1
  56. package/dist/equipment/release-capabilities.js +1 -53
  57. package/dist/equipment/release-capabilities.js.map +1 -1
  58. package/dist/exposure/auto-iso-slowest-shutter.js +1 -7
  59. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
  60. package/dist/exposure/exposure-duration-control.js +1 -16
  61. package/dist/exposure/exposure-duration-control.js.map +1 -1
  62. package/dist/exposure/exposure-mode-resolver.js +1 -17
  63. package/dist/exposure/exposure-mode-resolver.js.map +1 -1
  64. package/dist/exposure/filtered-exposure-time.d.ts +93 -0
  65. package/dist/exposure/filtered-exposure-time.js +129 -0
  66. package/dist/exposure/filtered-exposure-time.js.map +1 -0
  67. package/dist/exposure/flash-ttl.d.ts +1 -0
  68. package/dist/exposure/flash-ttl.js +1 -1
  69. package/dist/exposure/flash-ttl.js.map +1 -1
  70. package/dist/exposure/flash.js +1 -49
  71. package/dist/exposure/flash.js.map +1 -1
  72. package/dist/exposure/metering-scene-radiance.js +1 -25
  73. package/dist/exposure/metering-scene-radiance.js.map +1 -1
  74. package/dist/exposure/metering-target.js +1 -30
  75. package/dist/exposure/metering-target.js.map +1 -1
  76. package/dist/exposure/metering-temporal.js +1 -8
  77. package/dist/exposure/metering-temporal.js.map +1 -1
  78. package/dist/exposure/metering.js +1 -72
  79. package/dist/exposure/metering.js.map +1 -1
  80. package/dist/exposure/program-line.js +1 -45
  81. package/dist/exposure/program-line.js.map +1 -1
  82. package/dist/facade/catalogue-lens-signal.d.ts +94 -0
  83. package/dist/facade/catalogue-lens-signal.js +362 -0
  84. package/dist/facade/catalogue-lens-signal.js.map +1 -0
  85. package/dist/facade/defocus-image-space-provider.js +5 -2
  86. package/dist/facade/defocus-image-space-provider.js.map +1 -1
  87. package/dist/facade/develop-joined-parts.d.ts +29 -0
  88. package/dist/facade/develop-joined-parts.js +33 -0
  89. package/dist/facade/develop-joined-parts.js.map +1 -0
  90. package/dist/facade/display-view-lens-signal.d.ts +32 -1
  91. package/dist/facade/display-view-lens-signal.js +85 -10
  92. package/dist/facade/display-view-lens-signal.js.map +1 -1
  93. package/dist/facade/expected-signal-setup.d.ts +8 -0
  94. package/dist/facade/expected-signal-setup.js +7 -0
  95. package/dist/facade/expected-signal-setup.js.map +1 -1
  96. package/dist/facade/expected-signal.d.ts +5 -0
  97. package/dist/facade/expected-signal.js +5 -1
  98. package/dist/facade/expected-signal.js.map +1 -1
  99. package/dist/facade/generic-lens-capture.d.ts +5 -2
  100. package/dist/facade/generic-lens-capture.js +5 -2
  101. package/dist/facade/generic-lens-capture.js.map +1 -1
  102. package/dist/facade/simulate-capture.d.ts +67 -8
  103. package/dist/facade/simulate-capture.js +262 -126
  104. package/dist/facade/simulate-capture.js.map +1 -1
  105. package/dist/facade/simulate-display-view.js +1 -1
  106. package/dist/facade/simulate-display-view.js.map +1 -1
  107. package/dist/gpu/chart-composite-gpu.js +1 -3
  108. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  109. package/dist/gpu/chart-radiance-gpu.js +1 -5
  110. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  111. package/dist/gpu/display-view-quality-gpu.d.ts +13 -1
  112. package/dist/gpu/display-view-quality-gpu.js +4 -8
  113. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  114. package/dist/gpu/display-view-speed-gpu.js +3 -25
  115. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  116. package/dist/gpu/sensor-stage-gpu.js +1 -5
  117. package/dist/gpu/sensor-stage-gpu.js.map +1 -1
  118. package/dist/gpu/webgpu-runtime.d.ts +2 -0
  119. package/dist/gpu/webgpu-runtime.js +3 -0
  120. package/dist/gpu/webgpu-runtime.js.map +1 -1
  121. package/dist/index.d.ts +6 -1
  122. package/dist/index.js +5 -0
  123. package/dist/index.js.map +1 -1
  124. package/dist/motion/camera-rotation.js +1 -6
  125. package/dist/motion/camera-rotation.js.map +1 -1
  126. package/dist/motion/capture-rotation-inverse-mapping.d.ts +12 -0
  127. package/dist/motion/capture-rotation-inverse-mapping.js +2 -2
  128. package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -1
  129. package/dist/motion/capture-rotation-temporal-quadrature.d.ts +1 -0
  130. package/dist/motion/capture-rotation-temporal-quadrature.js +1 -1
  131. package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -1
  132. package/dist/motion/capture-rotation-trajectory.js +2 -17
  133. package/dist/motion/capture-rotation-trajectory.js.map +1 -1
  134. package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts +1 -0
  135. package/dist/motion/capture-translation-parallax-temporal-quadrature.js +3 -14
  136. package/dist/motion/capture-translation-parallax-temporal-quadrature.js.map +1 -1
  137. package/dist/motion/extended-object-projection.js +5 -40
  138. package/dist/motion/extended-object-projection.js.map +1 -1
  139. package/dist/optics/atmospheric-haze.js +1 -7
  140. package/dist/optics/atmospheric-haze.js.map +1 -1
  141. package/dist/optics/brown-conrady-distortion.js +1 -7
  142. package/dist/optics/brown-conrady-distortion.js.map +1 -1
  143. package/dist/optics/complex-pupil-psf.d.ts +1 -0
  144. package/dist/optics/complex-pupil-psf.js +2 -84
  145. package/dist/optics/complex-pupil-psf.js.map +1 -1
  146. package/dist/optics/focus-state.js +1 -11
  147. package/dist/optics/focus-state.js.map +1 -1
  148. package/dist/optics/front-of-lens-filter.d.ts +5 -0
  149. package/dist/optics/front-of-lens-filter.js +3 -65
  150. package/dist/optics/front-of-lens-filter.js.map +1 -1
  151. package/dist/optics/generic-nd-filter-presets.d.ts +12 -0
  152. package/dist/optics/generic-nd-filter-presets.js +48 -0
  153. package/dist/optics/generic-nd-filter-presets.js.map +1 -0
  154. package/dist/optics/illumination-vignetting.js +1 -6
  155. package/dist/optics/illumination-vignetting.js.map +1 -1
  156. package/dist/optics/lens-psf-profile.d.ts +4 -1
  157. package/dist/optics/lens-psf-profile.js +12 -111
  158. package/dist/optics/lens-psf-profile.js.map +1 -1
  159. package/dist/optics/longitudinal-chromatic-aberration.js +1 -7
  160. package/dist/optics/longitudinal-chromatic-aberration.js.map +1 -1
  161. package/dist/optics/pupil-vignetting.js +1 -7
  162. package/dist/optics/pupil-vignetting.js.map +1 -1
  163. package/dist/optics/radial-distortion.js +1 -6
  164. package/dist/optics/radial-distortion.js.map +1 -1
  165. package/dist/optics/scene-to-sensor-irradiance.js +4 -96
  166. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  167. package/dist/optics/stray-light.d.ts +1 -0
  168. package/dist/optics/stray-light.js +1 -1
  169. package/dist/optics/stray-light.js.map +1 -1
  170. package/dist/optics/zernike-wavefront.js +1 -7
  171. package/dist/optics/zernike-wavefront.js.map +1 -1
  172. package/dist/output/capture-geometry.js +1 -8
  173. package/dist/output/capture-geometry.js.map +1 -1
  174. package/dist/output/developed-lens-corrections.d.ts +62 -0
  175. package/dist/output/developed-lens-corrections.js +135 -0
  176. package/dist/output/developed-lens-corrections.js.map +1 -0
  177. package/dist/output/generic-lens-corrections.d.ts +2 -6
  178. package/dist/output/generic-lens-corrections.js +3 -6
  179. package/dist/output/generic-lens-corrections.js.map +1 -1
  180. package/dist/output/geometric-transforms.js +1 -4
  181. package/dist/output/geometric-transforms.js.map +1 -1
  182. package/dist/output/native-capture-sdr.d.ts +2 -2
  183. package/dist/output/native-capture-sdr.js +3 -2
  184. package/dist/output/native-capture-sdr.js.map +1 -1
  185. package/dist/processing/development-detail.d.ts +29 -0
  186. package/dist/processing/development-detail.js +128 -107
  187. package/dist/processing/development-detail.js.map +1 -1
  188. package/dist/schema/illumination-temporal.js +3 -72
  189. package/dist/schema/illumination-temporal.js.map +1 -1
  190. package/dist/schema/illumination.d.ts +1 -0
  191. package/dist/schema/illumination.js +2 -65
  192. package/dist/schema/illumination.js.map +1 -1
  193. package/dist/schema/scene-radiance-temporal.js +2 -14
  194. package/dist/schema/scene-radiance-temporal.js.map +1 -1
  195. package/dist/schema/scene-radiance.js +2 -60
  196. package/dist/schema/scene-radiance.js.map +1 -1
  197. package/dist/schema/validation.d.ts +2 -0
  198. package/dist/schema/validation.js +2 -17
  199. package/dist/schema/validation.js.map +1 -1
  200. package/dist/sensor/accumulated-charge.js +2 -59
  201. package/dist/sensor/accumulated-charge.js.map +1 -1
  202. package/dist/sensor/architecture.js +1 -16
  203. package/dist/sensor/architecture.js.map +1 -1
  204. package/dist/sensor/camera-saturation-capacity.d.ts +1 -0
  205. package/dist/sensor/camera-saturation-capacity.js +3 -55
  206. package/dist/sensor/camera-saturation-capacity.js.map +1 -1
  207. package/dist/sensor/capture-color-sampling-binding.js +1 -26
  208. package/dist/sensor/capture-color-sampling-binding.js.map +1 -1
  209. package/dist/sensor/capture-mode-timing.js +1 -39
  210. package/dist/sensor/capture-mode-timing.js.map +1 -1
  211. package/dist/sensor/capture-mode.js +1 -23
  212. package/dist/sensor/capture-mode.js.map +1 -1
  213. package/dist/sensor/color-sampling.js +1 -26
  214. package/dist/sensor/color-sampling.js.map +1 -1
  215. package/dist/sensor/constant-rate-temporal-integration.js +2 -46
  216. package/dist/sensor/constant-rate-temporal-integration.js.map +1 -1
  217. package/dist/sensor/dark-current.js +2 -45
  218. package/dist/sensor/dark-current.js.map +1 -1
  219. package/dist/sensor/eqe-electron-rate.js +2 -35
  220. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  221. package/dist/sensor/exposure-window.d.ts +5 -0
  222. package/dist/sensor/exposure-window.js +7 -22
  223. package/dist/sensor/exposure-window.js.map +1 -1
  224. package/dist/sensor/iso-gain.js +1 -7
  225. package/dist/sensor/iso-gain.js.map +1 -1
  226. package/dist/sensor/iso-signal-chain.js +1 -43
  227. package/dist/sensor/iso-signal-chain.js.map +1 -1
  228. package/dist/sensor/optical-stack.js +1 -25
  229. package/dist/sensor/optical-stack.js.map +1 -1
  230. package/dist/sensor/photon-energy.js +1 -26
  231. package/dist/sensor/photon-energy.js.map +1 -1
  232. package/dist/sensor/physical-charge-capacity.js +3 -77
  233. package/dist/sensor/physical-charge-capacity.js.map +1 -1
  234. package/dist/sensor/radiometry-readiness.js +2 -23
  235. package/dist/sensor/radiometry-readiness.js.map +1 -1
  236. package/dist/sensor/raw-readout.d.ts +3 -0
  237. package/dist/sensor/raw-readout.js +4 -66
  238. package/dist/sensor/raw-readout.js.map +1 -1
  239. package/dist/sensor/raw-reconstruction.js +2 -64
  240. package/dist/sensor/raw-reconstruction.js.map +1 -1
  241. package/dist/sensor/readout-exposure-linkage.js +1 -9
  242. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  243. package/dist/sensor/readout-timing.js +3 -57
  244. package/dist/sensor/readout-timing.js.map +1 -1
  245. package/dist/sensor/response-application-compatibility.d.ts +2 -0
  246. package/dist/sensor/response-application-compatibility.js +3 -29
  247. package/dist/sensor/response-application-compatibility.js.map +1 -1
  248. package/dist/sensor/response-operating-range.d.ts +1 -0
  249. package/dist/sensor/response-operating-range.js +3 -79
  250. package/dist/sensor/response-operating-range.js.map +1 -1
  251. package/dist/sensor/responsivity-photocurrent.js +4 -108
  252. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  253. package/dist/sensor/sampling-aperture.js +1 -25
  254. package/dist/sensor/sampling-aperture.js.map +1 -1
  255. package/dist/sensor/spatial-sample-reduction.js +2 -19
  256. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  257. package/dist/sensor/spatial-sampling-quadrature.js +1 -3
  258. package/dist/sensor/spatial-sampling-quadrature.js.map +1 -1
  259. package/dist/sensor/spatio-spectral-reduction.js +1 -18
  260. package/dist/sensor/spatio-spectral-reduction.js.map +1 -1
  261. package/dist/sensor/spectral-coverage.d.ts +10 -1
  262. package/dist/sensor/spectral-coverage.js +1 -1
  263. package/dist/sensor/spectral-coverage.js.map +1 -1
  264. package/dist/sensor/spectral-quadrature.js +2 -35
  265. package/dist/sensor/spectral-quadrature.js.map +1 -1
  266. package/dist/sensor/spectral-response.js +1 -47
  267. package/dist/sensor/spectral-response.js.map +1 -1
  268. package/dist/stabilization/camera-shake.js +1 -6
  269. package/dist/stabilization/camera-shake.js.map +1 -1
  270. package/dist/stabilization/system.js +2 -80
  271. package/dist/stabilization/system.js.map +1 -1
  272. package/docs/AGENT_GUIDE.md +4 -0
  273. package/docs/API_REFERENCE.md +69 -2
  274. package/docs/QUICK_START.md +136 -6
  275. package/docs/USAGE.md +89 -3
  276. package/docs/api/camera-sensor-equivalence.md +209 -0
  277. package/docs/api/core-version.md +1 -1
  278. package/docs/api/equipment-ideal-lens.md +26 -0
  279. package/docs/api/exports.json +118 -3
  280. package/docs/api/exposure-filtered-exposure-time.md +105 -0
  281. package/docs/api/facade-develop-joined-parts.md +32 -0
  282. package/docs/api/facade-simulate-capture.md +62 -8
  283. package/docs/api/optics-generic-nd-filter-presets.md +34 -0
  284. package/docs/api/optics-lens-psf-profile.md +3 -1
  285. package/docs/api/output-developed-lens-corrections.md +12 -0
  286. package/docs/api/output-generic-lens-corrections.md +0 -9
  287. package/llms.txt +3 -0
  288. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"raw-reconstruction.js","sourceRoot":"","sources":["../../src/sensor/raw-reconstruction.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * RAW reconstruction (demosaicing): estimates every colour channel at each pixel from the RAW samples\n * of nearby CFA (colour filter array) sites, using declared linear kernels.\n *\n * It uses native RAW samples (after the ADC) that are explicitly bound to their sites, and declared\n * normalized kernels for each channel. The CFA phase is absolute; orientation and crop are applied\n * later. This is a fixed linear reconstruction. It does not infer the sensor's spectral primaries or\n * calibrated colour, and is not a general demosaic algorithm. Built-in bilinear Bayer kernels are\n * provided.\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 { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { computeEffectiveSaturationCode } from \"../capture/raw-white-level.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n ResolvedCaptureModeColorSamplingContributors\n} from \"./capture-color-sampling-binding.js\";\nimport {\n parseSensorColorSamplingProfile,\n resolveColorSamplingSite,\n type SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport type {\n SensorAdcIntegralNonLinearity,\n SensorRawCodeSample\n} from \"./raw-readout.js\";\nimport type {\n SensorSpectralResponseScientificStatus\n} from \"./spectral-response.js\";\nimport { requireInputObject, requireInputKeys, rejectUnknownFields } from \"../core/record-validation.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport const SENSOR_RAW_CAPTURE_SAMPLE_VERSION =\n \"0.1.0\" as const;\nexport const SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION =\n \"0.1.0\" as const;\n\nexport interface CreateSensorRawCaptureSampleInput {\n rawCode:\n SensorRawCodeSample;\n contributors:\n ResolvedCaptureModeColorSamplingContributors;\n /**\n * The selected readout regime's declared ADC integral non-linearity (INL), if any. The code sample\n * records INL only at its own code, but the effective white level needs it at saturation.\n */\n adcIntegralNonLinearity?:\n SensorAdcIntegralNonLinearity;\n}\n\nexport interface SensorRawCaptureSample {\n version:\n typeof SENSOR_RAW_CAPTURE_SAMPLE_VERSION;\n captureModeId: string;\n modeSampleCoordinateSystem:\n \"capture-mode-full-frame-effective-sample-index\";\n modeSampleIndexFullFrame: {\n x: number;\n y: number;\n };\n colorSamplingProfileId: string;\n colorSamplingSite: {\n x: number;\n y: number;\n };\n channelId: string;\n rawCode: number;\n blackLevelCode: number;\n digitalSaturationCode: number;\n blackSubtractedNormalizedCode:\n number;\n /** Full-saturation code from `calculateEffectiveSaturationCode`; constant per CFA phase. */\n effectiveSaturationCode: number;\n /** Regime system conversion gain, e⁻ per code (DNG NoiseProfile input). */\n systemConversionGainElectronsPerCode: number;\n /** Total read noise in e⁻: the square root of the sum of the squared RMS components (DNG NoiseProfile input). */\n readNoiseRmsElectrons: number;\n readoutProfileId: string;\n readoutRegimeId: string;\n sourceChargeSeedUint32: number;\n sourceReadNoiseSeedUint32: number;\n physicalScalarSaturationApplied:\n boolean;\n digitalSaturationApplied:\n boolean;\n cfaPhasePreservedInNativeCoordinates:\n true;\n physicalOrientationApplied:\n false;\n outputRotationApplied:\n false;\n groupedModeCombinationApplied:\n false;\n reconstructionApplied: false;\n aliasingModeled: false;\n moireModeled: false;\n}\n\nexport interface SensorRawReconstructionKernelContribution {\n offsetX: number;\n offsetY: number;\n sourceChannelId: string;\n weight: number;\n}\n\nexport interface SensorRawReconstructionChannelKernel {\n outputChannelId: string;\n contributions:\n readonly SensorRawReconstructionKernelContribution[];\n}\n\nexport interface SensorRawReconstructionProfile {\n schemaVersion:\n typeof SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n captureModeId: string;\n colorSamplingProfileId: string;\n scientificStatus:\n SensorSpectralResponseScientificStatus;\n method:\n \"explicit-linear-native-neighborhood\";\n normalization:\n \"weights-sum-to-one-per-output-channel\";\n negativeBlackSubtractedValuesAllowed:\n true;\n kernels:\n readonly SensorRawReconstructionChannelKernel[];\n evidence:\n readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\nexport interface ResolveSensorRawReconstructionInput {\n profile:\n SensorRawReconstructionProfile;\n colorSamplingProfile:\n SensorColorSamplingProfile;\n centerSite: {\n x: number;\n y: number;\n };\n samples:\n readonly SensorRawCaptureSample[];\n}\n\nexport interface SensorRawReconstructedPixel {\n reconstructionProfileId:\n string;\n reconstructionProfileVersion:\n string;\n captureModeId: string;\n colorSamplingProfileId: string;\n centerSite: {\n x: number;\n y: number;\n };\n outputChannels:\n readonly {\n channelId: string;\n linearBlackSubtractedNormalizedValue:\n number;\n sourceContributions:\n readonly {\n site: {\n x: number;\n y: number;\n };\n sourceChannelId: string;\n weight: number;\n sourceValue: number;\n }[];\n }[];\n method:\n \"explicit-linear-native-neighborhood\";\n cfaAware: true;\n captureModeAware: true;\n physicalOrientationApplied:\n false;\n outputRotationApplied: false;\n sharpeningApplied: false;\n denoisingApplied: false;\n aliasingModeled: false;\n moireModeled: false;\n preSamplingOpticalTransferAdequacyEstablished:\n false;\n reconstructionDoesNotModifyRawSamples:\n true;\n}\n\nfunction requireRecord(\n value: unknown,\n path: string,\n allowedKeys: readonly string[] | null\n): UnknownRecord {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be an object.\"\n );\n }\n if (allowedKeys !== null) rejectUnknownFields(value, allowedKeys, path);\n return value as UnknownRecord;\n}\n\nfunction requireNonEmptyString(\n value: unknown,\n path: string\n): string {\n if (\n typeof value !== \"string\" ||\n value.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must be a non-empty string.\"\n );\n }\n return value.trim();\n}\n\nfunction requireFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be finite.\"\n );\n }\n return value;\n}\n\nfunction requireSafeInteger(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isSafeInteger(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be a safe integer.\"\n );\n }\n return value;\n}\n\nfunction requireNonNegativeSafeIntegerInput(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isSafeInteger(value) ||\n value < 0\n ) {\n throw new InvalidScientificInputError(\n path +\n \" must be a non-negative safe integer.\"\n );\n }\n return value;\n}\n\nfunction parseLimitations(\n value: unknown,\n path: string\n): readonly string[] {\n if (!Array.isArray(value)) {\n throw new InvalidConfigurationError(\n path + \" must be an array.\"\n );\n }\n const parsed =\n value.map((entry, index) =>\n requireNonEmptyString(\n entry,\n path +\n \"[\" +\n index +\n \"]\"\n )\n );\n if (\n new Set(parsed).size !==\n parsed.length\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must not contain duplicates.\"\n );\n }\n return parsed;\n}\n\nfunction parseStatus(\n value: unknown,\n path: string\n): SensorSpectralResponseScientificStatus {\n if (\n value !== \"calibrated\" &&\n value !== \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n path + \" is invalid.\"\n );\n }\n return value;\n}\n\nfunction parseContribution(\n value: unknown,\n path: string\n): SensorRawReconstructionKernelContribution {\n const record =\n requireRecord(value, path, [\"offsetX\", \"offsetY\", \"sourceChannelId\", \"weight\"]);\n return {\n offsetX:\n requireSafeInteger(\n record.offsetX,\n path + \".offsetX\"\n ),\n offsetY:\n requireSafeInteger(\n record.offsetY,\n path + \".offsetY\"\n ),\n sourceChannelId:\n requireNonEmptyString(\n record.sourceChannelId,\n path + \".sourceChannelId\"\n ),\n weight:\n requireFinite(\n record.weight,\n path + \".weight\"\n )\n };\n}\n\nfunction parseKernel(\n value: unknown,\n path: string\n): SensorRawReconstructionChannelKernel {\n const record =\n requireRecord(value, path, [\"contributions\", \"outputChannelId\"]);\n if (\n !Array.isArray(\n record.contributions\n ) ||\n record.contributions\n .length === 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \".contributions must be a non-empty array.\"\n );\n }\n const contributions =\n record.contributions.map(\n (entry, index) =>\n parseContribution(\n entry,\n path +\n \".contributions[\" +\n index +\n \"]\"\n )\n );\n const keys =\n contributions.map(\n (entry) =>\n entry.offsetX +\n \":\" +\n entry.offsetY +\n \":\" +\n entry.sourceChannelId\n );\n if (\n new Set(keys).size !==\n keys.length\n ) {\n throw new InvalidConfigurationError(\n path +\n \".contributions must not contain duplicate site/channel references.\"\n );\n }\n\n const sum =\n contributions.reduce(\n (valueSum, entry) =>\n valueSum + entry.weight,\n 0\n );\n if (\n !Number.isFinite(sum) ||\n Math.abs(sum - 1) >\n 1e-12\n ) {\n throw new InvalidConfigurationError(\n path +\n \".contribution weights must sum to one.\"\n );\n }\n\n return {\n outputChannelId:\n requireNonEmptyString(\n record.outputChannelId,\n path +\n \".outputChannelId\"\n ),\n contributions\n };\n}\n\n/**\n * Validates untrusted input and returns it as a normalized, typed value. Unknown enum values,\n * missing required fields and inconsistent scientific data are rejected.\n *\n * The profile declares a normalized linear kernel for each output channel. It does not infer the\n * sensor's spectral primaries or calibrated colour.\n * @param value - Untrusted data. TypeScript types alone are not validation.\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 parseSensorRawReconstructionProfile(\n value: unknown\n): SensorRawReconstructionProfile {\n const record =\n requireRecord(\n value,\n \"sensorRawReconstructionProfile\", [\"schemaVersion\", \"method\", \"normalization\", \"negativeBlackSubtractedValuesAllowed\", \"kernels\", \"profileId\", \"profileVersion\", \"captureModeId\", \"colorSamplingProfileId\", \"scientificStatus\", \"evidence\", \"limitations\"]\n );\n if (\n record.schemaVersion !==\n SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n 'sensorRawReconstructionProfile.schemaVersion must be \"' +\n SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION +\n '\".'\n );\n }\n if (\n record.method !==\n \"explicit-linear-native-neighborhood\"\n ) {\n throw new InvalidConfigurationError(\n 'sensorRawReconstructionProfile.method must be \"explicit-linear-native-neighborhood\".'\n );\n }\n if (\n record.normalization !==\n \"weights-sum-to-one-per-output-channel\"\n ) {\n throw new InvalidConfigurationError(\n \"sensorRawReconstructionProfile.normalization is invalid.\"\n );\n }\n if (\n record\n .negativeBlackSubtractedValuesAllowed !==\n true\n ) {\n throw new InvalidConfigurationError(\n \"sensorRawReconstructionProfile.negativeBlackSubtractedValuesAllowed must be true.\"\n );\n }\n if (\n !Array.isArray(\n record.kernels\n ) ||\n record.kernels.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"sensorRawReconstructionProfile.kernels must be a non-empty array.\"\n );\n }\n\n const kernels =\n record.kernels.map(\n (entry, index) =>\n parseKernel(\n entry,\n \"sensorRawReconstructionProfile.kernels[\" +\n index +\n \"]\"\n )\n );\n const outputIds =\n kernels.map(\n (kernel) =>\n kernel.outputChannelId\n );\n if (\n new Set(outputIds).size !==\n outputIds.length\n ) {\n throw new InvalidConfigurationError(\n \"sensorRawReconstructionProfile.kernels must not contain duplicate outputChannelId values.\"\n );\n }\n\n return {\n schemaVersion:\n SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION,\n profileId:\n requireNonEmptyString(\n record.profileId,\n \"sensorRawReconstructionProfile.profileId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n \"sensorRawReconstructionProfile.profileVersion\"\n ),\n captureModeId:\n requireNonEmptyString(\n record.captureModeId,\n \"sensorRawReconstructionProfile.captureModeId\"\n ),\n colorSamplingProfileId:\n requireNonEmptyString(\n record.colorSamplingProfileId,\n \"sensorRawReconstructionProfile.colorSamplingProfileId\"\n ),\n scientificStatus:\n parseStatus(\n record.scientificStatus,\n \"sensorRawReconstructionProfile.scientificStatus\"\n ),\n method:\n \"explicit-linear-native-neighborhood\",\n normalization:\n \"weights-sum-to-one-per-output-channel\",\n negativeBlackSubtractedValuesAllowed:\n true,\n kernels,\n evidence:\n parseEvidenceList(\n record.evidence,\n \"sensorRawReconstructionProfile.evidence\"\n ),\n limitations:\n parseLimitations(\n record.limitations,\n \"sensorRawReconstructionProfile.limitations\"\n )\n };\n}\n\n/**\n * Commits one validated native RAW sample, with its exact site, channel and exposure event, for use\n * in reconstruction.\n * @param input - The input; see `CreateSensorRawCaptureSampleInput` 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 createSensorRawCaptureSample(\n input:\n CreateSensorRawCaptureSampleInput\n): SensorRawCaptureSample {\n requireInputKeys(input, \"createSensorRawCaptureSample\", [\"rawCode\", \"contributors\", \"adcIntegralNonLinearity\"]);\n const raw =\n input.rawCode;\n const contributors =\n input.contributors;\n\n requireInputObject(contributors, \"contributors input\");\n if (\n contributors\n .modeSamplingKind !==\n \"native-effective-raster\" ||\n contributors.grouping !==\n null ||\n contributors\n .totalContributorSites !==\n 1 ||\n contributors\n .colorSamplingSiteRect\n .width !== 1 ||\n contributors\n .colorSamplingSiteRect\n .height !== 1\n ) {\n throw new InvalidScientificInputError(\n \"RAW site creation schema 0.1.0 requires one ungrouped native-effective capture-mode sample bound to exactly one color-sampling site; grouped/remosaic modes need explicit signal-combination weights.\"\n );\n }\n\n if (\n contributors\n .colorSamplingProfileId !==\n raw.colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"RAW readout color-sampling profile identity must match the capture-mode contributor binding.\"\n );\n }\n\n if (\n contributors\n .channelComposition.kind !==\n \"single-channel\" ||\n contributors\n .channelComposition\n .channelId !==\n raw.channelId\n ) {\n throw new InvalidScientificInputError(\n \"RAW readout channel must match the capture-mode color-sampling contributor channel.\"\n );\n }\n\n if (\n raw.site.x !==\n contributors\n .colorSamplingSiteRect.x ||\n raw.site.y !==\n contributors\n .colorSamplingSiteRect.y\n ) {\n throw new InvalidScientificInputError(\n \"RAW readout site must match the exact capture-mode color-sampling contributor site.\"\n );\n }\n\n const denominator =\n raw.digitalSaturationCode -\n raw.blackLevelCode;\n if (denominator <= 0) {\n throw new InvalidScientificInputError(\n \"RAW black/white code span must be positive.\"\n );\n }\n\n return {\n version:\n SENSOR_RAW_CAPTURE_SAMPLE_VERSION,\n captureModeId:\n contributors.modeId,\n modeSampleCoordinateSystem:\n \"capture-mode-full-frame-effective-sample-index\",\n modeSampleIndexFullFrame: {\n ...contributors\n .modeSampleIndexFullFrame\n },\n colorSamplingProfileId:\n raw.colorSamplingProfileId,\n colorSamplingSite: {\n ...raw.site\n },\n channelId:\n raw.channelId,\n rawCode:\n raw.rawCode,\n blackLevelCode:\n raw.blackLevelCode,\n digitalSaturationCode:\n raw.digitalSaturationCode,\n blackSubtractedNormalizedCode:\n (\n raw.rawCode -\n raw.blackLevelCode\n ) /\n denominator,\n effectiveSaturationCode:\n computeEffectiveSaturationCode(\n input.adcIntegralNonLinearity ===\n undefined\n ? raw\n : {\n ...raw,\n integralNonLinearity:\n input.adcIntegralNonLinearity\n }\n ),\n systemConversionGainElectronsPerCode:\n raw.systemConversionGainElectronsPerCode,\n readNoiseRmsElectrons:\n Math.sqrt(\n raw.electronicReadNoiseComponents.reduce(\n (sum, component) =>\n sum +\n component.rmsElectrons *\n component.rmsElectrons,\n 0\n )\n ),\n readoutProfileId:\n raw.readoutProfileId,\n readoutRegimeId:\n raw.regimeId,\n sourceChargeSeedUint32:\n raw.chargeSeedUint32,\n sourceReadNoiseSeedUint32:\n raw.readNoiseSeedUint32,\n physicalScalarSaturationApplied:\n raw\n .physicalScalarSaturationApplied,\n digitalSaturationApplied:\n raw\n .digitalSaturationApplied,\n cfaPhasePreservedInNativeCoordinates:\n true,\n physicalOrientationApplied:\n false,\n outputRotationApplied:\n false,\n groupedModeCombinationApplied:\n false,\n reconstructionApplied:\n false,\n aliasingModeled:\n false,\n moireModeled: false\n };\n}\n\nfunction requireCenterSite(\n value: {\n x: number;\n y: number;\n }\n): {\n x: number;\n y: number;\n} {\n return {\n x:\n requireNonNegativeSafeIntegerInput(\n value.x,\n \"centerSite.x\"\n ),\n y:\n requireNonNegativeSafeIntegerInput(\n value.y,\n \"centerSite.y\"\n )\n };\n}\n\n/**\n * Combines the supplied native samples through explicit normalized kernels, keeping CFA identity\n * and signed black-subtracted values.\n *\n * The CFA phase is absolute; orientation and crop come later. This fixed linear reconstruction does\n * not infer spectral primaries or calibrated colour, and is not a general demosaic algorithm.\n * @param input - The input; see `ResolveSensorRawReconstructionInput` 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 resolveSensorRawReconstruction(\n input:\n ResolveSensorRawReconstructionInput\n): CalculationResult<SensorRawReconstructedPixel> {\n requireInputKeys(input, \"resolveSensorRawReconstruction\", [\"profile\", \"colorSamplingProfile\", \"centerSite\", \"samples\"]);\n const profile =\n parseSensorRawReconstructionProfile(\n input.profile\n );\n const center =\n requireCenterSite(\n input.centerSite\n );\n const colorSamplingProfile =\n parseSensorColorSamplingProfile(\n input.colorSamplingProfile\n );\n if (\n colorSamplingProfile.profileId !==\n profile.colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"Reconstruction color-sampling profile identity must match the reconstruction profile.\"\n );\n }\n\n if (\n !Array.isArray(input.samples) ||\n input.samples.length === 0\n ) {\n throw new InvalidScientificInputError(\n \"samples must be a non-empty RAW neighborhood.\"\n );\n }\n\n for (\n const sample of\n input.samples\n ) {\n if (\n sample.captureModeId !==\n profile.captureModeId ||\n sample\n .colorSamplingProfileId !==\n profile\n .colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"Every RAW neighborhood sample must match the reconstruction profile capture-mode and color-sampling identities.\"\n );\n }\n const resolvedSite =\n resolveColorSamplingSite({\n profile:\n colorSamplingProfile,\n site:\n sample\n .colorSamplingSite\n });\n if (\n resolvedSite.mapping\n .channelId !==\n sample.channelId\n ) {\n throw new InvalidScientificInputError(\n \"RAW neighborhood sample channelId must match the declared color-sampling topology at its native site.\"\n );\n }\n }\n\n const sampleMap =\n new Map(\n input.samples.map(\n (sample) => [\n sample.colorSamplingSite.x +\n \":\" +\n sample.colorSamplingSite.y +\n \":\" +\n sample.channelId,\n sample\n ] as const\n )\n );\n if (\n sampleMap.size !==\n input.samples.length\n ) {\n throw new InvalidScientificInputError(\n \"RAW neighborhood must not contain duplicate site/channel samples.\"\n );\n }\n\n const outputChannels = profile.kernels.map(kernel => calculateRawReconstructionChannel(kernel, center, (x, y, channelId) => {\n const sample = sampleMap.get(x + \":\" + y + \":\" + channelId);\n if (sample === undefined) throw new InvalidScientificInputError(\"RAW reconstruction neighborhood is missing a required site/channel contribution.\");\n return sample.blackSubtractedNormalizedCode;\n }));\n\n return approximationResult(\n {\n reconstructionProfileId:\n profile.profileId,\n reconstructionProfileVersion:\n profile.profileVersion,\n captureModeId:\n profile.captureModeId,\n colorSamplingProfileId:\n profile\n .colorSamplingProfileId,\n centerSite:\n center,\n outputChannels,\n method:\n \"explicit-linear-native-neighborhood\",\n cfaAware: true,\n captureModeAware:\n true,\n physicalOrientationApplied:\n false,\n outputRotationApplied:\n false,\n sharpeningApplied:\n false,\n denoisingApplied: false,\n aliasingModeled: false,\n moireModeled: false,\n preSamplingOpticalTransferAdequacyEstablished:\n false,\n reconstructionDoesNotModifyRawSamples:\n true\n },\n \"sensor-raw-explicit-linear-reconstruction\",\n \"1.0.0\",\n [\n \"Reconstruction uses only explicitly declared linear native-neighborhood weights tied to exact capture-mode and color-sampling identities.\",\n \"No manufacturer-specific CFA algorithm or branded demosaic behavior is inferred from topology labels.\",\n \"RAW samples remain in invariant native sensor/CFA coordinates; physical orientation and final output rotation are downstream.\",\n \"Negative black-subtracted values are preserved through the linear reconstruction instead of being silently clamped.\",\n \"Sharpening and denoising are not part of RAW reconstruction.\",\n \"Aliasing and moire are explicitly not modeled because this reconstruction contract does not establish an adequate pre-sampling optical transfer/scene-frequency model.\"\n ]\n );\n}\n\n/**\n * Internal: sums one channel's weighted contributions. The reference and packed paths use the same\n * order of operations. With `mirrorBounds`, a contribution site outside the native frame is read from\n * its mirror image (reflect-101, see `reflectRawSiteIndex`). Without it (the default), such a site is\n * rejected.\n */\nexport function calculateRawReconstructionChannel(kernel: SensorRawReconstructionChannelKernel,\n center: {x:number;y:number}, read: (x:number,y:number,channelId:string)=>number,\n mirrorBounds?: {width:number;height:number}): SensorRawReconstructedPixel[\"outputChannels\"][number] {\n let value = 0;\n const sourceContributions = kernel.contributions.map(contribution => {\n let x = center.x + contribution.offsetX, y = center.y + contribution.offsetY;\n if (mirrorBounds !== undefined) { x = reflectRawSiteIndex(x, mirrorBounds.width); y = reflectRawSiteIndex(y, mirrorBounds.height); }\n if (x < 0 || y < 0) throw new InvalidScientificInputError(\"RAW reconstruction kernel references a site outside the non-negative native sensor coordinate domain.\");\n const sourceValue = read(x,y,contribution.sourceChannelId);\n value += sourceValue*contribution.weight;\n return {site:{x,y},sourceChannelId:contribution.sourceChannelId,weight:contribution.weight,sourceValue};\n });\n if (!Number.isFinite(value)) throw new InvalidScientificInputError(\"Reconstructed linear channel value must remain finite.\");\n return {channelId:kernel.outputChannelId,linearBlackSubtractedNormalizedValue:value,sourceContributions};\n}\n\n// ------------------------------------------------------------------ built-in Bayer kernels and borders\n\n/** How a reconstruction neighbourhood that leaves the native frame is handled. */\nexport type RawReconstructionBorderHandling = \"reject\" | \"mirror\";\n\n/**\n * Mirror index (\"reflect-101\"): −1 → 1, −2 → 2, n → n−2. The edge site itself is not repeated, so\n * for a CFA that repeats every 2 sites (Bayer) each mirrored site has the same channel as the site the\n * repeating pattern would put there. Requires size ≥ 1; for size 1 every index maps to 0.\n */\nexport function reflectRawSiteIndex(index: number, size: number): number {\n if (index >= 0 && index < size) return index;\n if (size === 1) return 0;\n const period = 2 * size - 2, m = ((index % period) + period) % period;\n return m < size ? m : period - m;\n}\n\n/**\n * Internal: proves that mirrored (reflect-101) reads at the border return the channel the repeating\n * CFA would put at every virtual site within `reach` of each frame edge, for every row and column\n * phase. Mirroring works per axis, so checking each axis over every phase also covers the corners.\n * Throws otherwise.\n */\nexport function validateMirrorBorderCfaConsistency(layout: { repeatWidthSites: number; repeatHeightSites: number;\n siteChannelIds: readonly string[] }, width: number, height: number, reach: number): void {\n const rw = layout.repeatWidthSites, rh = layout.repeatHeightSites, ids = layout.siteChannelIds;\n const at = (x: number, y: number): string => ids[(((y % rh) + rh) % rh) * rw + ((x % rw) + rw) % rw]!;\n if (reach >= width || reach >= height) {\n throw new InvalidConfigurationError(\"Mirror border handling requires a native frame larger than the kernel reach.\");\n }\n for (let d = 1; d <= reach; d++) {\n for (let p = 0; p < Math.max(rw, rh); p++) {\n if (at(-d, p) !== at(reflectRawSiteIndex(-d, width), p) || at(width - 1 + d, p) !== at(reflectRawSiteIndex(width - 1 + d, width), p) ||\n at(p, -d) !== at(p, reflectRawSiteIndex(-d, height)) || at(p, height - 1 + d) !== at(p, reflectRawSiteIndex(height - 1 + d, height))) {\n throw new InvalidConfigurationError(\"Mirror border handling would change the CFA channel of a reflected site; this CFA/frame size needs reject handling or a smaller region.\");\n }\n }\n }\n}\n\n/** Internal: largest absolute site offset of any contribution (the neighbourhood reach). */\nexport function rawReconstructionKernelReach(profiles: readonly { profile: SensorRawReconstructionProfile }[]): number {\n let reach = 0;\n for (const { profile } of profiles) for (const kernel of profile.kernels) for (const c of kernel.contributions) {\n reach = Math.max(reach, Math.abs(c.offsetX), Math.abs(c.offsetY));\n }\n return reach;\n}\n\nexport type BuiltInBayerDemosaicKernel = \"bilinear\";\nexport const BUILT_IN_BAYER_DEMOSAIC_MODEL_VERSION = \"1.0.0\" as const;\n\nexport interface CreateBuiltInBayerDemosaicProfilesInput {\n kernel: BuiltInBayerDemosaicKernel;\n /** Capture mode the RAW frame belongs to; copied into every phase profile. */\n captureModeId: string;\n /** Must be a 2×2 periodic Bayer mosaic: one red, one blue and two diagonal green sites. */\n colorSamplingProfile: SensorColorSamplingProfile;\n /** CFA channel IDs for the red, green and blue roles. This is also the output channel order. */\n channelRoles: { red: string; green: string; blue: string };\n}\n\nexport interface BuiltInBayerDemosaicProfiles {\n kernel: BuiltInBayerDemosaicKernel;\n /** One profile per absolute CFA phase, row-major (`phaseY`, then `phaseX`). */\n phaseProfiles: { phaseX: number; phaseY: number; profile: SensorRawReconstructionProfile }[];\n outputChannelIds: [string, string, string];\n /** Largest |offset| of any contribution: 1 for bilinear. */\n kernelReachSites: number;\n}\n\ntype BayerRole = \"R\" | \"G\" | \"B\";\ntype BayerTap = readonly [dx: number, dy: number, role: BayerRole, weight: number];\nconst CROSS1 = [[1, 0], [-1, 0], [0, 1], [0, -1]] as const;\nconst DIAG1 = [[1, 1], [-1, 1], [1, -1], [-1, -1]] as const;\nfunction bayerTaps(points: readonly (readonly [number, number])[], role: BayerRole, weight: number): BayerTap[] {\n return points.map(([x, y]) => [x, y, role, weight] as const);\n}\n\n/**\n * Kernels for one kind of site. `own` is the site's channel; `horizontal` is the colour channel on the\n * same row, for a green site.\n *\n * Bilinear: the average of the nearest sites of the wanted channel. Every kernel sums to one and\n * reproduces signals that vary linearly (affine) exactly.\n */\nfunction bayerSiteKernels(own: BayerRole, horizontal: BayerRole | null): Record<BayerRole, BayerTap[]> {\n if (own !== \"G\") {\n const other: BayerRole = own === \"R\" ? \"B\" : \"R\";\n const green: BayerTap[] = bayerTaps(CROSS1, \"G\", 1 / 4);\n const opposite: BayerTap[] = bayerTaps(DIAG1, other, 1 / 4);\n const result = { G: green } as Record<BayerRole, BayerTap[]>;\n result[own] = [[0, 0, own, 1]]; result[other] = opposite;\n return result;\n }\n const h = horizontal!, v: BayerRole = h === \"R\" ? \"B\" : \"R\";\n const along = (role: BayerRole, horizontalAxis: boolean): BayerTap[] => {\n const near = horizontalAxis ? [[1, 0], [-1, 0]] as const : [[0, 1], [0, -1]] as const;\n return bayerTaps(near, role, 1 / 2);\n };\n const identity: BayerTap[] = [[0, 0, \"G\", 1]];\n const result = { G: identity } as Record<BayerRole, BayerTap[]>;\n result[h] = along(h, true); result[v] = along(v, false);\n return result;\n}\n\nconst BAYER_DEMOSAIC_EVIDENCE: Readonly<Record<BuiltInBayerDemosaicKernel, readonly EvidenceProvenance[]>> = {\n bilinear: [{ sourceOrigin: \"third-party\", reuseStatus: \"factual-reference-only\",\n sourceReference: \"Gunturk, Glotzbach, Altunbasak, Schafer & Mersereau, Demosaicking: color filter array interpolation, IEEE Signal Processing Magazine 22(1), 2005\",\n note: \"Bilinear interpolation of each Bayer channel from its nearest same-channel sites; weights derived in Photivra from that published definition.\" }]\n};\n\n/**\n * Builds the per-phase linear reconstruction profiles of the built-in bilinear Bayer demosaic kernel.\n *\n * The result is the ordinary `explicit-linear-native-neighborhood` profile used by RAW frame\n * reconstruction, native RAW development and `createCfaPlaneReconstructionTask`; there is no special\n * code path. Edge pixels need either a region whose neighbourhood stays inside the frame, or mirror\n * border handling.\n *\n * Approximation: fixed linear interpolation, not a calibrated or adaptive demosaic. Bilinear blurs\n * and shows \"zipper\" artefacts at edges. Aliasing and moiré are not modelled. The adaptive AHD method\n * is `createAhdDemosaicTask`.\n */\nexport function createBuiltInBayerDemosaicProfiles(input: CreateBuiltInBayerDemosaicProfilesInput): CalculationResult<BuiltInBayerDemosaicProfiles> {\n requireInputKeys(input, \"createBuiltInBayerDemosaicProfiles\", [\"kernel\", \"captureModeId\", \"colorSamplingProfile\", \"channelRoles\"]);\n const kernel = input.kernel;\n if (kernel !== \"bilinear\") throw new InvalidConfigurationError(\"Unknown built-in Bayer demosaic kernel.\");\n const captureModeId = requireNonEmptyString(input.captureModeId, \"captureModeId\");\n const cfa = parseSensorColorSamplingProfile(input.colorSamplingProfile), layout = cfa.layout;\n const roles = requireRecord(input.channelRoles, \"channelRoles\", [\"red\", \"green\", \"blue\"]);\n const ids: Record<BayerRole, string> = { R: requireNonEmptyString(roles.red, \"channelRoles.red\"),\n G: requireNonEmptyString(roles.green, \"channelRoles.green\"), B: requireNonEmptyString(roles.blue, \"channelRoles.blue\") };\n if (new Set(Object.values(ids)).size !== 3) throw new InvalidConfigurationError(\"channelRoles must name three distinct channels.\");\n if (layout.kind !== \"periodic-mosaic\" || layout.repeatWidthSites !== 2 || layout.repeatHeightSites !== 2) {\n throw new InvalidScientificInputError(\"Built-in Bayer kernels require a 2×2 periodic mosaic.\");\n }\n const tile = layout.siteChannelIds.map((id): BayerRole => {\n const found = ([\"R\", \"G\", \"B\"] as const).find((r) => ids[r] === id);\n if (found === undefined) throw new InvalidScientificInputError(\"Bayer CFA channel is not one of channelRoles.\");\n return found;\n });\n const red = tile.indexOf(\"R\"), blue = tile.indexOf(\"B\");\n if (tile.filter((r) => r === \"G\").length !== 2 || red < 0 || blue < 0 || red + blue !== 3) {\n throw new InvalidScientificInputError(\"Built-in Bayer kernels require one red, one blue and two diagonal green sites.\");\n }\n const phaseProfiles = [0, 1, 2, 3].map((i) => {\n const phaseX = i % 2, phaseY = (i - phaseX) / 2, own = tile[i]!;\n const byRole = bayerSiteKernels(own, own === \"G\" ? tile[phaseY * 2 + (1 - phaseX)]! : null);\n const profile = parseSensorRawReconstructionProfile({\n schemaVersion: SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION, profileId: \"photivra-builtin-\" + kernel + \"-phase-\" + phaseX + \"-\" + phaseY,\n profileVersion: BUILT_IN_BAYER_DEMOSAIC_MODEL_VERSION, captureModeId, colorSamplingProfileId: cfa.profileId, scientificStatus: \"approximation\",\n method: \"explicit-linear-native-neighborhood\", normalization: \"weights-sum-to-one-per-output-channel\", negativeBlackSubtractedValuesAllowed: true,\n kernels: ([\"R\", \"G\", \"B\"] as const).map((r) => ({ outputChannelId: ids[r], contributions: byRole[r].map(([offsetX, offsetY, source, weight]) =>\n ({ offsetX, offsetY, sourceChannelId: ids[source], weight })) })),\n evidence: BAYER_DEMOSAIC_EVIDENCE[kernel].map((e) => ({ ...e })),\n limitations: [\"Fixed linear Bayer interpolation; not adaptive, calibrated or manufacturer-specific.\", \"Aliasing, moire and colour fringing are not modelled or suppressed.\"]\n });\n return { phaseX, phaseY, profile };\n });\n return approximationResult({ kernel, phaseProfiles, outputChannelIds: [ids.R, ids.G, ids.B], kernelReachSites: 1 },\n \"built-in-bayer-demosaic-\" + kernel, BUILT_IN_BAYER_DEMOSAIC_MODEL_VERSION, [\n \"Nearest same-channel bilinear interpolation.\",\n \"Weights sum to one per output channel and reproduce affine channel signals exactly.\",\n \"Linear kernels only; adaptive (edge-directed) reconstruction is a separate non-linear path.\"\n ]);\n}\n"]}
1
+ {"version":3,"file":"raw-reconstruction.js","sourceRoot":"","sources":["../../src/sensor/raw-reconstruction.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * RAW reconstruction (demosaicing): estimates every colour channel at each pixel from the RAW samples\n * of nearby CFA (colour filter array) sites, using declared linear kernels.\n *\n * It uses native RAW samples (after the ADC) that are explicitly bound to their sites, and declared\n * normalized kernels for each channel. The CFA phase is absolute; orientation and crop are applied\n * later. This is a fixed linear reconstruction. It does not infer the sensor's spectral primaries or\n * calibrated colour, and is not a general demosaic algorithm. Built-in bilinear Bayer kernels are\n * provided.\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 { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { computeEffectiveSaturationCode } from \"../capture/raw-white-level.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n ResolvedCaptureModeColorSamplingContributors\n} from \"./capture-color-sampling-binding.js\";\nimport {\n parseSensorColorSamplingProfile,\n resolveColorSamplingSite,\n type SensorColorSamplingProfile\n} from \"./color-sampling.js\";\nimport type {\n SensorAdcIntegralNonLinearity,\n SensorRawCodeSample\n} from \"./raw-readout.js\";\nimport type {\n SensorSpectralResponseScientificStatus\n} from \"./spectral-response.js\";\nimport { requireInputObject, requireInputKeys, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, requireConfigFinite as requireFinite, parseConfigScientificStatus as parseStatus, requireInputNonNegativeSafeInteger as requireNonNegativeSafeIntegerInput, parseConfigLimitations as parseLimitations } from \"../core/record-validation.js\";\nimport { requireSafeInteger } from \"./raw-readout.js\";\n\nexport const SENSOR_RAW_CAPTURE_SAMPLE_VERSION =\n \"0.1.0\" as const;\nexport const SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION =\n \"0.1.0\" as const;\n\nexport interface CreateSensorRawCaptureSampleInput {\n rawCode:\n SensorRawCodeSample;\n contributors:\n ResolvedCaptureModeColorSamplingContributors;\n /**\n * The selected readout regime's declared ADC integral non-linearity (INL), if any. The code sample\n * records INL only at its own code, but the effective white level needs it at saturation.\n */\n adcIntegralNonLinearity?:\n SensorAdcIntegralNonLinearity;\n}\n\nexport interface SensorRawCaptureSample {\n version:\n typeof SENSOR_RAW_CAPTURE_SAMPLE_VERSION;\n captureModeId: string;\n modeSampleCoordinateSystem:\n \"capture-mode-full-frame-effective-sample-index\";\n modeSampleIndexFullFrame: {\n x: number;\n y: number;\n };\n colorSamplingProfileId: string;\n colorSamplingSite: {\n x: number;\n y: number;\n };\n channelId: string;\n rawCode: number;\n blackLevelCode: number;\n digitalSaturationCode: number;\n blackSubtractedNormalizedCode:\n number;\n /** Full-saturation code from `calculateEffectiveSaturationCode`; constant per CFA phase. */\n effectiveSaturationCode: number;\n /** Regime system conversion gain, e⁻ per code (DNG NoiseProfile input). */\n systemConversionGainElectronsPerCode: number;\n /** Total read noise in e⁻: the square root of the sum of the squared RMS components (DNG NoiseProfile input). */\n readNoiseRmsElectrons: number;\n readoutProfileId: string;\n readoutRegimeId: string;\n sourceChargeSeedUint32: number;\n sourceReadNoiseSeedUint32: number;\n physicalScalarSaturationApplied:\n boolean;\n digitalSaturationApplied:\n boolean;\n cfaPhasePreservedInNativeCoordinates:\n true;\n physicalOrientationApplied:\n false;\n outputRotationApplied:\n false;\n groupedModeCombinationApplied:\n false;\n reconstructionApplied: false;\n aliasingModeled: false;\n moireModeled: false;\n}\n\nexport interface SensorRawReconstructionKernelContribution {\n offsetX: number;\n offsetY: number;\n sourceChannelId: string;\n weight: number;\n}\n\nexport interface SensorRawReconstructionChannelKernel {\n outputChannelId: string;\n contributions:\n readonly SensorRawReconstructionKernelContribution[];\n}\n\nexport interface SensorRawReconstructionProfile {\n schemaVersion:\n typeof SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION;\n profileId: string;\n profileVersion: string;\n captureModeId: string;\n colorSamplingProfileId: string;\n scientificStatus:\n SensorSpectralResponseScientificStatus;\n method:\n \"explicit-linear-native-neighborhood\";\n normalization:\n \"weights-sum-to-one-per-output-channel\";\n negativeBlackSubtractedValuesAllowed:\n true;\n kernels:\n readonly SensorRawReconstructionChannelKernel[];\n evidence:\n readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\nexport interface ResolveSensorRawReconstructionInput {\n profile:\n SensorRawReconstructionProfile;\n colorSamplingProfile:\n SensorColorSamplingProfile;\n centerSite: {\n x: number;\n y: number;\n };\n samples:\n readonly SensorRawCaptureSample[];\n}\n\nexport interface SensorRawReconstructedPixel {\n reconstructionProfileId:\n string;\n reconstructionProfileVersion:\n string;\n captureModeId: string;\n colorSamplingProfileId: string;\n centerSite: {\n x: number;\n y: number;\n };\n outputChannels:\n readonly {\n channelId: string;\n linearBlackSubtractedNormalizedValue:\n number;\n sourceContributions:\n readonly {\n site: {\n x: number;\n y: number;\n };\n sourceChannelId: string;\n weight: number;\n sourceValue: number;\n }[];\n }[];\n method:\n \"explicit-linear-native-neighborhood\";\n cfaAware: true;\n captureModeAware: true;\n physicalOrientationApplied:\n false;\n outputRotationApplied: false;\n sharpeningApplied: false;\n denoisingApplied: false;\n aliasingModeled: false;\n moireModeled: false;\n preSamplingOpticalTransferAdequacyEstablished:\n false;\n reconstructionDoesNotModifyRawSamples:\n true;\n}\n\nfunction parseContribution(\n value: unknown,\n path: string\n): SensorRawReconstructionKernelContribution {\n const record =\n requireRecord(value, path, [\"offsetX\", \"offsetY\", \"sourceChannelId\", \"weight\"]);\n return {\n offsetX:\n requireSafeInteger(\n record.offsetX,\n path + \".offsetX\"\n ),\n offsetY:\n requireSafeInteger(\n record.offsetY,\n path + \".offsetY\"\n ),\n sourceChannelId:\n requireNonEmptyString(\n record.sourceChannelId,\n path + \".sourceChannelId\"\n ),\n weight:\n requireFinite(\n record.weight,\n path + \".weight\"\n )\n };\n}\n\nfunction parseKernel(\n value: unknown,\n path: string\n): SensorRawReconstructionChannelKernel {\n const record =\n requireRecord(value, path, [\"contributions\", \"outputChannelId\"]);\n if (\n !Array.isArray(\n record.contributions\n ) ||\n record.contributions\n .length === 0\n ) {\n throw new InvalidConfigurationError(\n path +\n \".contributions must be a non-empty array.\"\n );\n }\n const contributions =\n record.contributions.map(\n (entry, index) =>\n parseContribution(\n entry,\n path +\n \".contributions[\" +\n index +\n \"]\"\n )\n );\n const keys =\n contributions.map(\n (entry) =>\n entry.offsetX +\n \":\" +\n entry.offsetY +\n \":\" +\n entry.sourceChannelId\n );\n if (\n new Set(keys).size !==\n keys.length\n ) {\n throw new InvalidConfigurationError(\n path +\n \".contributions must not contain duplicate site/channel references.\"\n );\n }\n\n const sum =\n contributions.reduce(\n (valueSum, entry) =>\n valueSum + entry.weight,\n 0\n );\n if (\n !Number.isFinite(sum) ||\n Math.abs(sum - 1) >\n 1e-12\n ) {\n throw new InvalidConfigurationError(\n path +\n \".contribution weights must sum to one.\"\n );\n }\n\n return {\n outputChannelId:\n requireNonEmptyString(\n record.outputChannelId,\n path +\n \".outputChannelId\"\n ),\n contributions\n };\n}\n\n/**\n * Validates untrusted input and returns it as a normalized, typed value. Unknown enum values,\n * missing required fields and inconsistent scientific data are rejected.\n *\n * The profile declares a normalized linear kernel for each output channel. It does not infer the\n * sensor's spectral primaries or calibrated colour.\n * @param value - Untrusted data. TypeScript types alone are not validation.\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 parseSensorRawReconstructionProfile(\n value: unknown\n): SensorRawReconstructionProfile {\n const record =\n requireRecord(\n value,\n \"sensorRawReconstructionProfile\", [\"schemaVersion\", \"method\", \"normalization\", \"negativeBlackSubtractedValuesAllowed\", \"kernels\", \"profileId\", \"profileVersion\", \"captureModeId\", \"colorSamplingProfileId\", \"scientificStatus\", \"evidence\", \"limitations\"]\n );\n if (\n record.schemaVersion !==\n SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n 'sensorRawReconstructionProfile.schemaVersion must be \"' +\n SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION +\n '\".'\n );\n }\n if (\n record.method !==\n \"explicit-linear-native-neighborhood\"\n ) {\n throw new InvalidConfigurationError(\n 'sensorRawReconstructionProfile.method must be \"explicit-linear-native-neighborhood\".'\n );\n }\n if (\n record.normalization !==\n \"weights-sum-to-one-per-output-channel\"\n ) {\n throw new InvalidConfigurationError(\n \"sensorRawReconstructionProfile.normalization is invalid.\"\n );\n }\n if (\n record\n .negativeBlackSubtractedValuesAllowed !==\n true\n ) {\n throw new InvalidConfigurationError(\n \"sensorRawReconstructionProfile.negativeBlackSubtractedValuesAllowed must be true.\"\n );\n }\n if (\n !Array.isArray(\n record.kernels\n ) ||\n record.kernels.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"sensorRawReconstructionProfile.kernels must be a non-empty array.\"\n );\n }\n\n const kernels =\n record.kernels.map(\n (entry, index) =>\n parseKernel(\n entry,\n \"sensorRawReconstructionProfile.kernels[\" +\n index +\n \"]\"\n )\n );\n const outputIds =\n kernels.map(\n (kernel) =>\n kernel.outputChannelId\n );\n if (\n new Set(outputIds).size !==\n outputIds.length\n ) {\n throw new InvalidConfigurationError(\n \"sensorRawReconstructionProfile.kernels must not contain duplicate outputChannelId values.\"\n );\n }\n\n return {\n schemaVersion:\n SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION,\n profileId:\n requireNonEmptyString(\n record.profileId,\n \"sensorRawReconstructionProfile.profileId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n \"sensorRawReconstructionProfile.profileVersion\"\n ),\n captureModeId:\n requireNonEmptyString(\n record.captureModeId,\n \"sensorRawReconstructionProfile.captureModeId\"\n ),\n colorSamplingProfileId:\n requireNonEmptyString(\n record.colorSamplingProfileId,\n \"sensorRawReconstructionProfile.colorSamplingProfileId\"\n ),\n scientificStatus:\n parseStatus(\n record.scientificStatus,\n \"sensorRawReconstructionProfile.scientificStatus\"\n ),\n method:\n \"explicit-linear-native-neighborhood\",\n normalization:\n \"weights-sum-to-one-per-output-channel\",\n negativeBlackSubtractedValuesAllowed:\n true,\n kernels,\n evidence:\n parseEvidenceList(\n record.evidence,\n \"sensorRawReconstructionProfile.evidence\"\n ),\n limitations:\n parseLimitations(\n record.limitations,\n \"sensorRawReconstructionProfile.limitations\"\n )\n };\n}\n\n/**\n * Commits one validated native RAW sample, with its exact site, channel and exposure event, for use\n * in reconstruction.\n * @param input - The input; see `CreateSensorRawCaptureSampleInput` 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 createSensorRawCaptureSample(\n input:\n CreateSensorRawCaptureSampleInput\n): SensorRawCaptureSample {\n requireInputKeys(input, \"createSensorRawCaptureSample\", [\"rawCode\", \"contributors\", \"adcIntegralNonLinearity\"]);\n const raw =\n input.rawCode;\n const contributors =\n input.contributors;\n\n requireInputObject(contributors, \"contributors input\");\n if (\n contributors\n .modeSamplingKind !==\n \"native-effective-raster\" ||\n contributors.grouping !==\n null ||\n contributors\n .totalContributorSites !==\n 1 ||\n contributors\n .colorSamplingSiteRect\n .width !== 1 ||\n contributors\n .colorSamplingSiteRect\n .height !== 1\n ) {\n throw new InvalidScientificInputError(\n \"RAW site creation schema 0.1.0 requires one ungrouped native-effective capture-mode sample bound to exactly one color-sampling site; grouped/remosaic modes need explicit signal-combination weights.\"\n );\n }\n\n if (\n contributors\n .colorSamplingProfileId !==\n raw.colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"RAW readout color-sampling profile identity must match the capture-mode contributor binding.\"\n );\n }\n\n if (\n contributors\n .channelComposition.kind !==\n \"single-channel\" ||\n contributors\n .channelComposition\n .channelId !==\n raw.channelId\n ) {\n throw new InvalidScientificInputError(\n \"RAW readout channel must match the capture-mode color-sampling contributor channel.\"\n );\n }\n\n if (\n raw.site.x !==\n contributors\n .colorSamplingSiteRect.x ||\n raw.site.y !==\n contributors\n .colorSamplingSiteRect.y\n ) {\n throw new InvalidScientificInputError(\n \"RAW readout site must match the exact capture-mode color-sampling contributor site.\"\n );\n }\n\n const denominator =\n raw.digitalSaturationCode -\n raw.blackLevelCode;\n if (denominator <= 0) {\n throw new InvalidScientificInputError(\n \"RAW black/white code span must be positive.\"\n );\n }\n\n return {\n version:\n SENSOR_RAW_CAPTURE_SAMPLE_VERSION,\n captureModeId:\n contributors.modeId,\n modeSampleCoordinateSystem:\n \"capture-mode-full-frame-effective-sample-index\",\n modeSampleIndexFullFrame: {\n ...contributors\n .modeSampleIndexFullFrame\n },\n colorSamplingProfileId:\n raw.colorSamplingProfileId,\n colorSamplingSite: {\n ...raw.site\n },\n channelId:\n raw.channelId,\n rawCode:\n raw.rawCode,\n blackLevelCode:\n raw.blackLevelCode,\n digitalSaturationCode:\n raw.digitalSaturationCode,\n blackSubtractedNormalizedCode:\n (\n raw.rawCode -\n raw.blackLevelCode\n ) /\n denominator,\n effectiveSaturationCode:\n computeEffectiveSaturationCode(\n input.adcIntegralNonLinearity ===\n undefined\n ? raw\n : {\n ...raw,\n integralNonLinearity:\n input.adcIntegralNonLinearity\n }\n ),\n systemConversionGainElectronsPerCode:\n raw.systemConversionGainElectronsPerCode,\n readNoiseRmsElectrons:\n Math.sqrt(\n raw.electronicReadNoiseComponents.reduce(\n (sum, component) =>\n sum +\n component.rmsElectrons *\n component.rmsElectrons,\n 0\n )\n ),\n readoutProfileId:\n raw.readoutProfileId,\n readoutRegimeId:\n raw.regimeId,\n sourceChargeSeedUint32:\n raw.chargeSeedUint32,\n sourceReadNoiseSeedUint32:\n raw.readNoiseSeedUint32,\n physicalScalarSaturationApplied:\n raw\n .physicalScalarSaturationApplied,\n digitalSaturationApplied:\n raw\n .digitalSaturationApplied,\n cfaPhasePreservedInNativeCoordinates:\n true,\n physicalOrientationApplied:\n false,\n outputRotationApplied:\n false,\n groupedModeCombinationApplied:\n false,\n reconstructionApplied:\n false,\n aliasingModeled:\n false,\n moireModeled: false\n };\n}\n\nfunction requireCenterSite(\n value: {\n x: number;\n y: number;\n }\n): {\n x: number;\n y: number;\n} {\n return {\n x:\n requireNonNegativeSafeIntegerInput(\n value.x,\n \"centerSite.x\"\n ),\n y:\n requireNonNegativeSafeIntegerInput(\n value.y,\n \"centerSite.y\"\n )\n };\n}\n\n/**\n * Combines the supplied native samples through explicit normalized kernels, keeping CFA identity\n * and signed black-subtracted values.\n *\n * The CFA phase is absolute; orientation and crop come later. This fixed linear reconstruction does\n * not infer spectral primaries or calibrated colour, and is not a general demosaic algorithm.\n * @param input - The input; see `ResolveSensorRawReconstructionInput` 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 resolveSensorRawReconstruction(\n input:\n ResolveSensorRawReconstructionInput\n): CalculationResult<SensorRawReconstructedPixel> {\n requireInputKeys(input, \"resolveSensorRawReconstruction\", [\"profile\", \"colorSamplingProfile\", \"centerSite\", \"samples\"]);\n const profile =\n parseSensorRawReconstructionProfile(\n input.profile\n );\n const center =\n requireCenterSite(\n input.centerSite\n );\n const colorSamplingProfile =\n parseSensorColorSamplingProfile(\n input.colorSamplingProfile\n );\n if (\n colorSamplingProfile.profileId !==\n profile.colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"Reconstruction color-sampling profile identity must match the reconstruction profile.\"\n );\n }\n\n if (\n !Array.isArray(input.samples) ||\n input.samples.length === 0\n ) {\n throw new InvalidScientificInputError(\n \"samples must be a non-empty RAW neighborhood.\"\n );\n }\n\n for (\n const sample of\n input.samples\n ) {\n if (\n sample.captureModeId !==\n profile.captureModeId ||\n sample\n .colorSamplingProfileId !==\n profile\n .colorSamplingProfileId\n ) {\n throw new InvalidScientificInputError(\n \"Every RAW neighborhood sample must match the reconstruction profile capture-mode and color-sampling identities.\"\n );\n }\n const resolvedSite =\n resolveColorSamplingSite({\n profile:\n colorSamplingProfile,\n site:\n sample\n .colorSamplingSite\n });\n if (\n resolvedSite.mapping\n .channelId !==\n sample.channelId\n ) {\n throw new InvalidScientificInputError(\n \"RAW neighborhood sample channelId must match the declared color-sampling topology at its native site.\"\n );\n }\n }\n\n const sampleMap =\n new Map(\n input.samples.map(\n (sample) => [\n sample.colorSamplingSite.x +\n \":\" +\n sample.colorSamplingSite.y +\n \":\" +\n sample.channelId,\n sample\n ] as const\n )\n );\n if (\n sampleMap.size !==\n input.samples.length\n ) {\n throw new InvalidScientificInputError(\n \"RAW neighborhood must not contain duplicate site/channel samples.\"\n );\n }\n\n const outputChannels = profile.kernels.map(kernel => calculateRawReconstructionChannel(kernel, center, (x, y, channelId) => {\n const sample = sampleMap.get(x + \":\" + y + \":\" + channelId);\n if (sample === undefined) throw new InvalidScientificInputError(\"RAW reconstruction neighborhood is missing a required site/channel contribution.\");\n return sample.blackSubtractedNormalizedCode;\n }));\n\n return approximationResult(\n {\n reconstructionProfileId:\n profile.profileId,\n reconstructionProfileVersion:\n profile.profileVersion,\n captureModeId:\n profile.captureModeId,\n colorSamplingProfileId:\n profile\n .colorSamplingProfileId,\n centerSite:\n center,\n outputChannels,\n method:\n \"explicit-linear-native-neighborhood\",\n cfaAware: true,\n captureModeAware:\n true,\n physicalOrientationApplied:\n false,\n outputRotationApplied:\n false,\n sharpeningApplied:\n false,\n denoisingApplied: false,\n aliasingModeled: false,\n moireModeled: false,\n preSamplingOpticalTransferAdequacyEstablished:\n false,\n reconstructionDoesNotModifyRawSamples:\n true\n },\n \"sensor-raw-explicit-linear-reconstruction\",\n \"1.0.0\",\n [\n \"Reconstruction uses only explicitly declared linear native-neighborhood weights tied to exact capture-mode and color-sampling identities.\",\n \"No manufacturer-specific CFA algorithm or branded demosaic behavior is inferred from topology labels.\",\n \"RAW samples remain in invariant native sensor/CFA coordinates; physical orientation and final output rotation are downstream.\",\n \"Negative black-subtracted values are preserved through the linear reconstruction instead of being silently clamped.\",\n \"Sharpening and denoising are not part of RAW reconstruction.\",\n \"Aliasing and moire are explicitly not modeled because this reconstruction contract does not establish an adequate pre-sampling optical transfer/scene-frequency model.\"\n ]\n );\n}\n\n/**\n * Internal: sums one channel's weighted contributions. The reference and packed paths use the same\n * order of operations. With `mirrorBounds`, a contribution site outside the native frame is read from\n * its mirror image (reflect-101, see `reflectRawSiteIndex`). Without it (the default), such a site is\n * rejected.\n */\nexport function calculateRawReconstructionChannel(kernel: SensorRawReconstructionChannelKernel,\n center: {x:number;y:number}, read: (x:number,y:number,channelId:string)=>number,\n mirrorBounds?: {width:number;height:number}): SensorRawReconstructedPixel[\"outputChannels\"][number] {\n let value = 0;\n const sourceContributions = kernel.contributions.map(contribution => {\n let x = center.x + contribution.offsetX, y = center.y + contribution.offsetY;\n if (mirrorBounds !== undefined) { x = reflectRawSiteIndex(x, mirrorBounds.width); y = reflectRawSiteIndex(y, mirrorBounds.height); }\n if (x < 0 || y < 0) throw new InvalidScientificInputError(\"RAW reconstruction kernel references a site outside the non-negative native sensor coordinate domain.\");\n const sourceValue = read(x,y,contribution.sourceChannelId);\n value += sourceValue*contribution.weight;\n return {site:{x,y},sourceChannelId:contribution.sourceChannelId,weight:contribution.weight,sourceValue};\n });\n if (!Number.isFinite(value)) throw new InvalidScientificInputError(\"Reconstructed linear channel value must remain finite.\");\n return {channelId:kernel.outputChannelId,linearBlackSubtractedNormalizedValue:value,sourceContributions};\n}\n\n// ------------------------------------------------------------------ built-in Bayer kernels and borders\n\n/** How a reconstruction neighbourhood that leaves the native frame is handled. */\nexport type RawReconstructionBorderHandling = \"reject\" | \"mirror\";\n\n/**\n * Mirror index (\"reflect-101\"): −1 → 1, −2 → 2, n → n−2. The edge site itself is not repeated, so\n * for a CFA that repeats every 2 sites (Bayer) each mirrored site has the same channel as the site the\n * repeating pattern would put there. Requires size ≥ 1; for size 1 every index maps to 0.\n */\nexport function reflectRawSiteIndex(index: number, size: number): number {\n if (index >= 0 && index < size) return index;\n if (size === 1) return 0;\n const period = 2 * size - 2, m = ((index % period) + period) % period;\n return m < size ? m : period - m;\n}\n\n/**\n * Internal: proves that mirrored (reflect-101) reads at the border return the channel the repeating\n * CFA would put at every virtual site within `reach` of each frame edge, for every row and column\n * phase. Mirroring works per axis, so checking each axis over every phase also covers the corners.\n * Throws otherwise.\n */\nexport function validateMirrorBorderCfaConsistency(layout: { repeatWidthSites: number; repeatHeightSites: number;\n siteChannelIds: readonly string[] }, width: number, height: number, reach: number): void {\n const rw = layout.repeatWidthSites, rh = layout.repeatHeightSites, ids = layout.siteChannelIds;\n const at = (x: number, y: number): string => ids[(((y % rh) + rh) % rh) * rw + ((x % rw) + rw) % rw]!;\n if (reach >= width || reach >= height) {\n throw new InvalidConfigurationError(\"Mirror border handling requires a native frame larger than the kernel reach.\");\n }\n for (let d = 1; d <= reach; d++) {\n for (let p = 0; p < Math.max(rw, rh); p++) {\n if (at(-d, p) !== at(reflectRawSiteIndex(-d, width), p) || at(width - 1 + d, p) !== at(reflectRawSiteIndex(width - 1 + d, width), p) ||\n at(p, -d) !== at(p, reflectRawSiteIndex(-d, height)) || at(p, height - 1 + d) !== at(p, reflectRawSiteIndex(height - 1 + d, height))) {\n throw new InvalidConfigurationError(\"Mirror border handling would change the CFA channel of a reflected site; this CFA/frame size needs reject handling or a smaller region.\");\n }\n }\n }\n}\n\n/** Internal: largest absolute site offset of any contribution (the neighbourhood reach). */\nexport function rawReconstructionKernelReach(profiles: readonly { profile: SensorRawReconstructionProfile }[]): number {\n let reach = 0;\n for (const { profile } of profiles) for (const kernel of profile.kernels) for (const c of kernel.contributions) {\n reach = Math.max(reach, Math.abs(c.offsetX), Math.abs(c.offsetY));\n }\n return reach;\n}\n\nexport type BuiltInBayerDemosaicKernel = \"bilinear\";\nexport const BUILT_IN_BAYER_DEMOSAIC_MODEL_VERSION = \"1.0.0\" as const;\n\nexport interface CreateBuiltInBayerDemosaicProfilesInput {\n kernel: BuiltInBayerDemosaicKernel;\n /** Capture mode the RAW frame belongs to; copied into every phase profile. */\n captureModeId: string;\n /** Must be a 2×2 periodic Bayer mosaic: one red, one blue and two diagonal green sites. */\n colorSamplingProfile: SensorColorSamplingProfile;\n /** CFA channel IDs for the red, green and blue roles. This is also the output channel order. */\n channelRoles: { red: string; green: string; blue: string };\n}\n\nexport interface BuiltInBayerDemosaicProfiles {\n kernel: BuiltInBayerDemosaicKernel;\n /** One profile per absolute CFA phase, row-major (`phaseY`, then `phaseX`). */\n phaseProfiles: { phaseX: number; phaseY: number; profile: SensorRawReconstructionProfile }[];\n outputChannelIds: [string, string, string];\n /** Largest |offset| of any contribution: 1 for bilinear. */\n kernelReachSites: number;\n}\n\ntype BayerRole = \"R\" | \"G\" | \"B\";\ntype BayerTap = readonly [dx: number, dy: number, role: BayerRole, weight: number];\nconst CROSS1 = [[1, 0], [-1, 0], [0, 1], [0, -1]] as const;\nconst DIAG1 = [[1, 1], [-1, 1], [1, -1], [-1, -1]] as const;\nfunction bayerTaps(points: readonly (readonly [number, number])[], role: BayerRole, weight: number): BayerTap[] {\n return points.map(([x, y]) => [x, y, role, weight] as const);\n}\n\n/**\n * Kernels for one kind of site. `own` is the site's channel; `horizontal` is the colour channel on the\n * same row, for a green site.\n *\n * Bilinear: the average of the nearest sites of the wanted channel. Every kernel sums to one and\n * reproduces signals that vary linearly (affine) exactly.\n */\nfunction bayerSiteKernels(own: BayerRole, horizontal: BayerRole | null): Record<BayerRole, BayerTap[]> {\n if (own !== \"G\") {\n const other: BayerRole = own === \"R\" ? \"B\" : \"R\";\n const green: BayerTap[] = bayerTaps(CROSS1, \"G\", 1 / 4);\n const opposite: BayerTap[] = bayerTaps(DIAG1, other, 1 / 4);\n const result = { G: green } as Record<BayerRole, BayerTap[]>;\n result[own] = [[0, 0, own, 1]]; result[other] = opposite;\n return result;\n }\n const h = horizontal!, v: BayerRole = h === \"R\" ? \"B\" : \"R\";\n const along = (role: BayerRole, horizontalAxis: boolean): BayerTap[] => {\n const near = horizontalAxis ? [[1, 0], [-1, 0]] as const : [[0, 1], [0, -1]] as const;\n return bayerTaps(near, role, 1 / 2);\n };\n const identity: BayerTap[] = [[0, 0, \"G\", 1]];\n const result = { G: identity } as Record<BayerRole, BayerTap[]>;\n result[h] = along(h, true); result[v] = along(v, false);\n return result;\n}\n\nconst BAYER_DEMOSAIC_EVIDENCE: Readonly<Record<BuiltInBayerDemosaicKernel, readonly EvidenceProvenance[]>> = {\n bilinear: [{ sourceOrigin: \"third-party\", reuseStatus: \"factual-reference-only\",\n sourceReference: \"Gunturk, Glotzbach, Altunbasak, Schafer & Mersereau, Demosaicking: color filter array interpolation, IEEE Signal Processing Magazine 22(1), 2005\",\n note: \"Bilinear interpolation of each Bayer channel from its nearest same-channel sites; weights derived in Photivra from that published definition.\" }]\n};\n\n/**\n * Builds the per-phase linear reconstruction profiles of the built-in bilinear Bayer demosaic kernel.\n *\n * The result is the ordinary `explicit-linear-native-neighborhood` profile used by RAW frame\n * reconstruction, native RAW development and `createCfaPlaneReconstructionTask`; there is no special\n * code path. Edge pixels need either a region whose neighbourhood stays inside the frame, or mirror\n * border handling.\n *\n * Approximation: fixed linear interpolation, not a calibrated or adaptive demosaic. Bilinear blurs\n * and shows \"zipper\" artefacts at edges. Aliasing and moiré are not modelled. The adaptive AHD method\n * is `createAhdDemosaicTask`.\n */\nexport function createBuiltInBayerDemosaicProfiles(input: CreateBuiltInBayerDemosaicProfilesInput): CalculationResult<BuiltInBayerDemosaicProfiles> {\n requireInputKeys(input, \"createBuiltInBayerDemosaicProfiles\", [\"kernel\", \"captureModeId\", \"colorSamplingProfile\", \"channelRoles\"]);\n const kernel = input.kernel;\n if (kernel !== \"bilinear\") throw new InvalidConfigurationError(\"Unknown built-in Bayer demosaic kernel.\");\n const captureModeId = requireNonEmptyString(input.captureModeId, \"captureModeId\");\n const cfa = parseSensorColorSamplingProfile(input.colorSamplingProfile), layout = cfa.layout;\n const roles = requireRecord(input.channelRoles, \"channelRoles\", [\"red\", \"green\", \"blue\"]);\n const ids: Record<BayerRole, string> = { R: requireNonEmptyString(roles.red, \"channelRoles.red\"),\n G: requireNonEmptyString(roles.green, \"channelRoles.green\"), B: requireNonEmptyString(roles.blue, \"channelRoles.blue\") };\n if (new Set(Object.values(ids)).size !== 3) throw new InvalidConfigurationError(\"channelRoles must name three distinct channels.\");\n if (layout.kind !== \"periodic-mosaic\" || layout.repeatWidthSites !== 2 || layout.repeatHeightSites !== 2) {\n throw new InvalidScientificInputError(\"Built-in Bayer kernels require a 2×2 periodic mosaic.\");\n }\n const tile = layout.siteChannelIds.map((id): BayerRole => {\n const found = ([\"R\", \"G\", \"B\"] as const).find((r) => ids[r] === id);\n if (found === undefined) throw new InvalidScientificInputError(\"Bayer CFA channel is not one of channelRoles.\");\n return found;\n });\n const red = tile.indexOf(\"R\"), blue = tile.indexOf(\"B\");\n if (tile.filter((r) => r === \"G\").length !== 2 || red < 0 || blue < 0 || red + blue !== 3) {\n throw new InvalidScientificInputError(\"Built-in Bayer kernels require one red, one blue and two diagonal green sites.\");\n }\n const phaseProfiles = [0, 1, 2, 3].map((i) => {\n const phaseX = i % 2, phaseY = (i - phaseX) / 2, own = tile[i]!;\n const byRole = bayerSiteKernels(own, own === \"G\" ? tile[phaseY * 2 + (1 - phaseX)]! : null);\n const profile = parseSensorRawReconstructionProfile({\n schemaVersion: SENSOR_RAW_RECONSTRUCTION_PROFILE_SCHEMA_VERSION, profileId: \"photivra-builtin-\" + kernel + \"-phase-\" + phaseX + \"-\" + phaseY,\n profileVersion: BUILT_IN_BAYER_DEMOSAIC_MODEL_VERSION, captureModeId, colorSamplingProfileId: cfa.profileId, scientificStatus: \"approximation\",\n method: \"explicit-linear-native-neighborhood\", normalization: \"weights-sum-to-one-per-output-channel\", negativeBlackSubtractedValuesAllowed: true,\n kernels: ([\"R\", \"G\", \"B\"] as const).map((r) => ({ outputChannelId: ids[r], contributions: byRole[r].map(([offsetX, offsetY, source, weight]) =>\n ({ offsetX, offsetY, sourceChannelId: ids[source], weight })) })),\n evidence: BAYER_DEMOSAIC_EVIDENCE[kernel].map((e) => ({ ...e })),\n limitations: [\"Fixed linear Bayer interpolation; not adaptive, calibrated or manufacturer-specific.\", \"Aliasing, moire and colour fringing are not modelled or suppressed.\"]\n });\n return { phaseX, phaseY, profile };\n });\n return approximationResult({ kernel, phaseProfiles, outputChannelIds: [ids.R, ids.G, ids.B], kernelReachSites: 1 },\n \"built-in-bayer-demosaic-\" + kernel, BUILT_IN_BAYER_DEMOSAIC_MODEL_VERSION, [\n \"Nearest same-channel bilinear interpolation.\",\n \"Weights sum to one per output channel and reproduce affine channel signals exactly.\",\n \"Linear kernels only; adaptive (edge-directed) reconstruction is a separate non-linear path.\"\n ]);\n}\n"]}
@@ -16,7 +16,7 @@ import { parseEvidenceList } from "../core/evidence-provenance.js";
16
16
  import { InvalidScientificInputError } from "../core/validation.js";
17
17
  import { calculateCaptureExposureWindows, parseBoundarySchedule, parseSecondsFact } from "./exposure-window.js";
18
18
  import { calculateSensorReadoutTiming } from "./readout-timing.js";
19
- import { requireInputKeys, rejectUnknownFields } from "../core/record-validation.js";
19
+ import { requireInputKeys, requireConfigRecord as requireRecord } from "../core/record-validation.js";
20
20
  import { InvalidConfigurationError } from "../core/configuration-error.js";
21
21
  const BOUNDARIES = new Set([
22
22
  "opening",
@@ -26,14 +26,6 @@ const PHASE_ORIENTATIONS = new Set([
26
26
  "same",
27
27
  "reversed"
28
28
  ]);
29
- function requireRecord(value, path, allowedKeys) {
30
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
31
- throw new InvalidConfigurationError(path + " must be an object.");
32
- }
33
- if (allowedKeys !== null)
34
- rejectUnknownFields(value, allowedKeys, path);
35
- return value;
36
- }
37
29
  function parseSpatialLink(value, path) {
38
30
  const record = requireRecord(value, path, ["boundary", "phaseOrientation", "evidence"]);
39
31
  if (typeof record.boundary !== "string" ||
@@ -1 +1 @@
1
- {"version":3,"file":"readout-exposure-linkage.js","sourceRoot":"","sources":["../../src/sensor/readout-exposure-linkage.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Links rolling sensor readout to electronic shutter scans, and derives the exposure schedule of a\n * fully electronic rolling shutter.\n *\n * A declared link says that the readout scan and one or more exposure-boundary scans move across the\n * sensor in the same order (or in reverse). That is deliberately weaker than time synchronization: it\n * does not say when readout happens relative to the start or end of exposure. The total data-readout\n * time is kept for diagnostics but never used to check the link. The readout skew and the boundary\n * sweep times may differ; their ratio is reported without implying a shared clock.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type { RasterPoint, RasterRect } from \"../output/capture-geometry.js\";\nimport {\n calculateCaptureExposureWindows,\n parseBoundarySchedule,\n parseSecondsFact,\n type CalculateCaptureExposureWindowsInput,\n type ExposureBoundarySchedule,\n type UniformLinearExposureBoundarySchedule,\n type SourcedCaptureTimingSeconds\n} from \"./exposure-window.js\";\nimport {\n calculateSensorReadoutTiming,\n type CaptureShutterMechanism,\n type NativeSensorReadoutScanDirection,\n type RollingSensorReadoutTimingDeclaration,\n type SensorReadoutTimingDeclaration,\n type SourcedSensorTimingSeconds\n} from \"./readout-timing.js\";\nimport type { NativeImageRaster } from \"./sensor-geometry.js\";\nimport { requireInputKeys, rejectUnknownFields } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport type ReadoutExposureBoundaryId = \"opening\" | \"closing\";\n\nexport type ReadoutExposureSpatialPhaseOrientation =\n | \"same\"\n | \"reversed\";\n\nexport interface ReadoutExposureBoundarySpatialLink {\n boundary: ReadoutExposureBoundaryId;\n /**\n * How the normalized position of the rolling readout scan relates to that\n * of the selected electronic exposure-boundary scan.\n *\n * \"same\": boundary normalized phase = readout normalized phase.\n * \"reversed\": boundary normalized phase = 1 - readout normalized phase.\n *\n * This does not establish absolute temporal synchronization.\n */\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation;\n /**\n * Evidence for the link itself.\n *\n * The readout direction and timing, and the exposure-boundary direction and\n * timing, keep their own separate evidence.\n */\n evidence: readonly EvidenceProvenance[];\n}\n\nexport type ReadoutExposureTimingLinkageDeclaration =\n | {\n /**\n * No link between readout and exposure is stated for this capture.\n * This is not evidence that the physical processes are independent.\n */\n kind: \"unlinked\";\n links?: never;\n }\n | {\n /**\n * One or more exposure boundaries are explicitly linked to the rolling\n * readout by their normalized position across the native sensor.\n */\n kind: \"spatial-phase-linked\";\n links: readonly ReadoutExposureBoundarySpatialLink[];\n };\n\nexport interface AssessReadoutExposureTimingLinkageInput {\n nativeRaster: NativeImageRaster;\n activeCaptureRect?: RasterRect;\n shutterMechanism: CaptureShutterMechanism;\n readout: SensorReadoutTimingDeclaration;\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n opening: ExposureBoundarySchedule;\n closing: ExposureBoundarySchedule;\n linkage: ReadoutExposureTimingLinkageDeclaration;\n samplePointsNative?: readonly RasterPoint[];\n}\n\nexport interface ReadoutExposureBoundarySpatialLinkAssessment {\n boundary: ReadoutExposureBoundaryId;\n boundaryActuator: \"electronic\";\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation;\n normalizedPhaseRelationship:\n | \"boundary-phase-equals-readout-phase\"\n | \"boundary-phase-equals-one-minus-readout-phase\";\n relationshipEvidence: readonly EvidenceProvenance[];\n readoutDirectionNative: NativeSensorReadoutScanDirection;\n boundaryDirectionNative: NativeSensorReadoutScanDirection;\n /**\n * Rolling readout skew: time from the first to the last scan position.\n */\n readoutSpatialSamplingSkewSeconds: SourcedSensorTimingSeconds;\n /**\n * Time for the exposure boundary to sweep from first to last position.\n */\n boundaryTraversalDurationSeconds: SourcedCaptureTimingSeconds;\n /**\n * A descriptive ratio only. A value of 1 does not prove time\n * synchronization or a common clock origin.\n */\n boundaryTraversalToReadoutSpatialSkewRatio: number;\n absoluteTemporalAlignment: \"not-established\";\n}\n\nexport interface ReadoutExposureTimingLinkageAssessment {\n linkageKind: ReadoutExposureTimingLinkageDeclaration[\"kind\"];\n relationshipMeaning:\n | \"no-readout-exposure-relationship-asserted\"\n | \"normalized-spatial-phase-relationship-asserted\";\n readoutMode: \"rolling\" | \"global\";\n shutterMechanism: CaptureShutterMechanism;\n activeCaptureRect: RasterRect;\n absoluteTemporalAlignment: \"not-established\";\n /**\n * Data-readout time for the capture. Kept for diagnostics, but never used to\n * check an exposure-boundary link.\n */\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n links: readonly ReadoutExposureBoundarySpatialLinkAssessment[];\n componentProvenance: {\n sensorReadout: CalculationProvenance;\n exposureWindows: CalculationProvenance;\n };\n}\n\ntype UnknownRecord = Record<string, unknown>;\n\nconst BOUNDARIES = new Set<ReadoutExposureBoundaryId>([\n \"opening\",\n \"closing\"\n]);\n\nconst PHASE_ORIENTATIONS =\n new Set<ReadoutExposureSpatialPhaseOrientation>([\n \"same\",\n \"reversed\"\n ]);\n\nfunction requireRecord(\n value: unknown,\n path: string,\n allowedKeys: readonly string[] | null\n): UnknownRecord {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new InvalidConfigurationError(path + \" must be an object.\");\n }\n if (allowedKeys !== null) rejectUnknownFields(value, allowedKeys, path);\n return value as UnknownRecord;\n}\n\nfunction parseSpatialLink(\n value: unknown,\n path: string\n): ReadoutExposureBoundarySpatialLink {\n const record = requireRecord(value, path, [\"boundary\", \"phaseOrientation\", \"evidence\"]);\n\n if (\n typeof record.boundary !== \"string\" ||\n !BOUNDARIES.has(record.boundary as ReadoutExposureBoundaryId)\n ) {\n throw new InvalidConfigurationError(path + \".boundary is invalid.\");\n }\n\n if (\n typeof record.phaseOrientation !== \"string\" ||\n !PHASE_ORIENTATIONS.has(\n record.phaseOrientation as ReadoutExposureSpatialPhaseOrientation\n )\n ) {\n throw new InvalidConfigurationError(\n path + \".phaseOrientation is invalid.\"\n );\n }\n\n return {\n boundary: record.boundary as ReadoutExposureBoundaryId,\n phaseOrientation:\n record.phaseOrientation as ReadoutExposureSpatialPhaseOrientation,\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseLinkageDeclaration(\n value: unknown\n): ReadoutExposureTimingLinkageDeclaration {\n const record = requireRecord(value, \"linkage\", [\"kind\", \"links\"]);\n\n if (record.kind === \"unlinked\") {\n if (record.links !== undefined) {\n throw new InvalidScientificInputError(\n \"linkage.links must be omitted when linkage.kind is unlinked.\"\n );\n }\n return { kind: \"unlinked\" };\n }\n\n if (record.kind !== \"spatial-phase-linked\") {\n throw new InvalidConfigurationError(\"linkage.kind is invalid.\");\n }\n\n if (!Array.isArray(record.links) || record.links.length === 0) {\n throw new InvalidConfigurationError(\n \"linkage.links must be a non-empty array for spatial-phase-linked linkage.\"\n );\n }\n\n const links = record.links.map((entry, index) =>\n parseSpatialLink(entry, \"linkage.links[\" + index + \"]\")\n );\n\n const seen = new Set<ReadoutExposureBoundaryId>();\n for (const link of links) {\n if (seen.has(link.boundary)) {\n throw new InvalidScientificInputError(\n \"linkage.links must not contain duplicate boundary links.\"\n );\n }\n seen.add(link.boundary);\n }\n\n return {\n kind: \"spatial-phase-linked\",\n links\n };\n}\n\nfunction oppositeDirection(\n direction: NativeSensorReadoutScanDirection\n): NativeSensorReadoutScanDirection {\n switch (direction) {\n case \"top-to-bottom\":\n return \"bottom-to-top\";\n case \"bottom-to-top\":\n return \"top-to-bottom\";\n case \"left-to-right\":\n return \"right-to-left\";\n case \"right-to-left\":\n return \"left-to-right\";\n }\n}\n\nfunction expectedBoundaryDirection(\n readoutDirection: NativeSensorReadoutScanDirection,\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation\n): NativeSensorReadoutScanDirection {\n return phaseOrientation === \"same\"\n ? readoutDirection\n : oppositeDirection(readoutDirection);\n}\n\n/**\n * Checks an explicitly declared link between the rolling readout scan and one\n * or more electronic exposure-boundary scans.\n *\n * This is deliberately weaker than time synchronization. It can show that two\n * schedules sweep the native sensor in the same order (or in reverse), but not\n * when readout happens relative to the start or end of exposure.\n *\n * The total data-readout time is kept for diagnostics and never used to check\n * the link. The readout skew and the boundary sweep time may differ; their\n * ratio is reported without implying a shared clock.\n */\nexport function assessReadoutExposureTimingLinkage(\n input: AssessReadoutExposureTimingLinkageInput\n): CalculationResult<ReadoutExposureTimingLinkageAssessment> {\n requireInputKeys(input, \"assessReadoutExposureTimingLinkage\", [\"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"readout\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"linkage\", \"samplePointsNative\"]);\n const linkage = parseLinkageDeclaration(input.linkage);\n\n const sensorReadout = calculateSensorReadoutTiming({\n nativeRaster: input.nativeRaster,\n ...(input.activeCaptureRect === undefined\n ? {}\n : { activeCaptureRect: input.activeCaptureRect }),\n shutterMechanism: input.shutterMechanism,\n readout: input.readout,\n ...(input.samplePointsNative === undefined\n ? {}\n : { samplePointsNative: input.samplePointsNative })\n });\n\n const exposureWindows = calculateCaptureExposureWindows({\n nativeRaster: input.nativeRaster,\n ...(input.activeCaptureRect === undefined\n ? {}\n : { activeCaptureRect: input.activeCaptureRect }),\n shutterMechanism: input.shutterMechanism,\n nominalExposureDurationSeconds:\n input.nominalExposureDurationSeconds,\n opening: input.opening,\n closing: input.closing,\n ...(input.samplePointsNative === undefined\n ? {}\n : { samplePointsNative: input.samplePointsNative })\n });\n\n if (linkage.kind === \"unlinked\") {\n return approximationResult(\n {\n linkageKind: \"unlinked\",\n relationshipMeaning:\n \"no-readout-exposure-relationship-asserted\",\n readoutMode: sensorReadout.value.readoutMode,\n shutterMechanism: input.shutterMechanism,\n activeCaptureRect: {\n ...sensorReadout.value.activeCaptureRect\n },\n absoluteTemporalAlignment: \"not-established\",\n captureReadoutDurationSeconds:\n sensorReadout.value.captureReadoutDurationSeconds,\n links: [],\n componentProvenance: {\n sensorReadout: sensorReadout.provenance,\n exposureWindows: exposureWindows.provenance\n }\n },\n \"readout-exposure-spatial-linkage\",\n \"1.0.0\",\n [\n \"Unlinked means Photivra asserts no readout-to-exposure relationship for this capture; it does not prove the physical processes are independent.\",\n \"Sensor data-readout timing and exposure-window timing retain independent provenance and semantics.\",\n \"Absolute temporal alignment between data readout and exposure boundaries is not established.\"\n ]\n );\n }\n\n const readoutScan = sensorReadout.value.scan;\n if (readoutScan === null) {\n throw new InvalidScientificInputError(\n \"spatial-phase-linked linkage requires rolling sensor readout with a spatial scan.\"\n );\n }\n\n const assessments =\n linkage.links.map<ReadoutExposureBoundarySpatialLinkAssessment>(\n (link) => {\n const boundary =\n link.boundary === \"opening\"\n ? exposureWindows.value.opening\n : exposureWindows.value.closing;\n\n if (boundary.actuator !== \"electronic\") {\n throw new InvalidScientificInputError(\n \"A readout spatial-phase link may target only an electronic exposure boundary.\"\n );\n }\n\n if (\n boundary.schedule.kind !==\n \"uniform-linear-native-scan\"\n ) {\n throw new InvalidScientificInputError(\n \"A readout spatial-phase link requires the selected exposure boundary to use a uniform-linear native scan.\"\n );\n }\n\n const readoutDirection =\n readoutScan.directionNative.value;\n const boundaryDirection =\n boundary.schedule.directionNative.value;\n const expectedDirection = expectedBoundaryDirection(\n readoutDirection,\n link.phaseOrientation\n );\n\n if (boundaryDirection !== expectedDirection) {\n throw new InvalidScientificInputError(\n \"Declared readout/exposure spatial phase orientation contradicts the readout and exposure-boundary scan directions.\"\n );\n }\n\n const readoutSpatialSamplingSkewSeconds =\n readoutScan.spatialSamplingSkewSeconds;\n const boundaryTraversalDurationSeconds =\n boundary.schedule.traversalDurationSeconds;\n\n return {\n boundary: link.boundary,\n boundaryActuator: \"electronic\",\n phaseOrientation: link.phaseOrientation,\n normalizedPhaseRelationship:\n link.phaseOrientation === \"same\"\n ? \"boundary-phase-equals-readout-phase\"\n : \"boundary-phase-equals-one-minus-readout-phase\",\n relationshipEvidence: link.evidence,\n readoutDirectionNative: readoutDirection,\n boundaryDirectionNative: boundaryDirection,\n readoutSpatialSamplingSkewSeconds,\n boundaryTraversalDurationSeconds,\n boundaryTraversalToReadoutSpatialSkewRatio:\n boundaryTraversalDurationSeconds.value /\n readoutSpatialSamplingSkewSeconds.value,\n absoluteTemporalAlignment: \"not-established\"\n };\n }\n );\n\n return approximationResult(\n {\n linkageKind: \"spatial-phase-linked\",\n relationshipMeaning:\n \"normalized-spatial-phase-relationship-asserted\",\n readoutMode: sensorReadout.value.readoutMode,\n shutterMechanism: input.shutterMechanism,\n activeCaptureRect: {\n ...sensorReadout.value.activeCaptureRect\n },\n absoluteTemporalAlignment: \"not-established\",\n captureReadoutDurationSeconds:\n sensorReadout.value.captureReadoutDurationSeconds,\n links: assessments,\n componentProvenance: {\n sensorReadout: sensorReadout.provenance,\n exposureWindows: exposureWindows.provenance\n }\n },\n \"readout-exposure-spatial-linkage\",\n \"1.0.0\",\n [\n \"The linkage establishes normalized native-sensor spatial phase/order only, not absolute temporal synchronization.\",\n \"Every asserted boundary link carries evidence separate from the evidence for the underlying readout and exposure timing facts.\",\n \"Only electronic exposure boundaries may be linked to rolling sensor readout spatial phase.\",\n \"Same phase orientation requires matching native scan direction; reversed phase orientation requires the exact opposite direction on the same native axis.\",\n \"Rolling readout spatial skew and exposure-boundary traversal duration may differ; their ratio is descriptive and does not establish a common clock origin.\",\n \"Capture data-readout duration is not used to validate exposure-boundary timing linkage.\",\n \"Active capture, native coordinate semantics and shutter mechanism are shared because both component schedules are resolved from the same input.\",\n \"Sensor readout and exposure-window component provenance remain visible; linkage does not upgrade either component to calibrated status.\",\n \"No camera-specific timing relationship is inferred from shutter mechanism, sensor architecture metadata, output resolution or adjacent specifications.\"\n ]\n );\n}\n\n/** Model identity of the electronic rolling-shutter (line reset → line read) exposure schedule. */\nexport const ELECTRONIC_ROLLING_SHUTTER_MODEL = /* @__PURE__ */ Object.freeze({\n id: \"electronic-rolling-shutter-line-reset-read\",\n version: \"1.0.0\"\n} as const);\n\nexport interface ResolveElectronicRollingShutterExposureScheduleInput {\n /** Rolling readout declaration whose scan direction and spatial skew drive both exposure boundaries. */\n readout: RollingSensorReadoutTimingDeclaration;\n /** Per-line integration time between line reset and line read. */\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n /**\n * Evidence that this capture uses a fully electronic, line-by-line (rolling) shutter whose reset\n * scan runs at the readout line rate. This explicit link is required before sensor readout timing\n * may set the exposure-window timing.\n */\n linkageEvidence: readonly EvidenceProvenance[];\n}\n\n/**\n * Exposure-window schedule of an electronic rolling shutter. Its `shutterMechanism`,\n * `nominalExposureDurationSeconds`, `opening` and `closing` fields form a complete exposure schedule, so\n * they can be passed as a native RAW / environment `exposureWindow` or spread into a production\n * `temporalCapture.exposureWindowInput` (see {@link createRollingShutterCaptureExposureSchedule}).\n */\nexport interface ElectronicRollingShutterExposureSchedule {\n modelId: typeof ELECTRONIC_ROLLING_SHUTTER_MODEL.id;\n modelVersion: typeof ELECTRONIC_ROLLING_SHUTTER_MODEL.version;\n shutterMechanism: \"electronic\";\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n /** Line-reset scan: a native point at normalized scan position s starts integrating at s·τ. */\n opening: UniformLinearExposureBoundarySchedule;\n /** Line-read scan: the same point stops integrating at T + s·τ. */\n closing: UniformLinearExposureBoundarySchedule;\n /** Explicit linkage declaration accepted by {@link assessReadoutExposureTimingLinkage}. */\n linkage: Extract<ReadoutExposureTimingLinkageDeclaration, { kind: \"spatial-phase-linked\" }>;\n /** τ: offset between the first and last exposure starts (equal to the readout skew). */\n lineScanSkewSeconds: number;\n /** Every point integrates for exactly the nominal exposure duration T. */\n localExposureDurationSeconds: number;\n limitations: readonly string[];\n}\n\n/** The exposure-schedule fields of a derived rolling-shutter schedule, without model metadata. */\nexport function createRollingShutterCaptureExposureSchedule(\n schedule: ElectronicRollingShutterExposureSchedule\n): Pick<CalculateCaptureExposureWindowsInput, \"shutterMechanism\" | \"nominalExposureDurationSeconds\" | \"opening\" | \"closing\"> {\n // 2.0.0: the whole schedule is checked, so an unknown field at any level is rejected.\n const record = requireRecord(schedule, \"rollingShutterSchedule\", [\"modelId\", \"modelVersion\", \"shutterMechanism\",\n \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"linkage\", \"lineScanSkewSeconds\", \"localExposureDurationSeconds\",\n \"limitations\"]);\n parseSecondsFact(record.nominalExposureDurationSeconds, \"rollingShutterSchedule.nominalExposureDurationSeconds\");\n parseBoundarySchedule(record.opening, \"rollingShutterSchedule.opening\");\n parseBoundarySchedule(record.closing, \"rollingShutterSchedule.closing\");\n if (record.linkage !== undefined) parseLinkageDeclaration(record.linkage);\n return structuredClone({ shutterMechanism: schedule.shutterMechanism,\n nominalExposureDurationSeconds: schedule.nominalExposureDurationSeconds,\n opening: schedule.opening, closing: schedule.closing });\n}\n\n/**\n * Derives the exposure-window schedule of a fully electronic rolling shutter from its rolling readout\n * declaration.\n *\n * In a CMOS rolling shutter, each line is reset, collects light for the exposure time T and is then\n * read. The reset pointer runs T ahead of the read pointer, and both move at the line rate (El Gamal &\n * Eltoukhy 2005; Liang, Chang & Chen 2008). With the uniform linear scan used throughout this engine, a\n * native point at normalized scan position s ∈ [0, 1] (along the declared scan direction, over the\n * active capture) collects light over [s·τ, s·τ + T], where τ is the declared first-to-last readout\n * skew. So both boundaries become uniform linear scans in the readout direction that take τ to sweep.\n *\n * When this schedule feeds the per-site exposure windows, everything that samples motion, moving\n * sources or time-varying light at local exposure times (environment RAW, rotation quadrature,\n * temporal illumination integration) produces rolling-shutter skew, wobble, partial exposure and\n * flicker banding.\n *\n * Approximation: constant line rate, no line-reset jitter, no hybrid of global reset with a mechanical\n * closing shutter, and the active-capture skew is declared by the caller (never scaled from a\n * full-frame value).\n * @see docs/MOTION_AND_SIGNAL.md#electronic-rolling-shutter-image-formation\n */\nexport function resolveElectronicRollingShutterExposureSchedule(\n input: ResolveElectronicRollingShutterExposureScheduleInput\n): ElectronicRollingShutterExposureSchedule {\n requireInputKeys(input, \"resolveElectronicRollingShutterExposureSchedule\", [\"readout\", \"nominalExposureDurationSeconds\", \"linkageEvidence\"]);\n // The input keys are checked above; calculateSensorReadoutTiming checks the readout's own fields.\n const record = requireRecord(input, \"rollingShutter\", null);\n const readout = requireRecord(record.readout, \"rollingShutter.readout\", null);\n if (readout.readoutMode !== \"rolling\") {\n throw new InvalidScientificInputError(\n \"An electronic rolling-shutter exposure schedule requires a rolling sensor readout declaration.\"\n );\n }\n const linkageEvidence = parseEvidenceList(input.linkageEvidence, \"rollingShutter.linkageEvidence\");\n const rolling = input.readout;\n // Check the declaration and the exposure fact with the existing readout and exposure-window calculators.\n const probeRaster = { pixelWidth: 2, pixelHeight: 2 };\n calculateSensorReadoutTiming({ nativeRaster: probeRaster, shutterMechanism: \"electronic\", readout: rolling });\n const opening: UniformLinearExposureBoundarySchedule = {\n kind: \"uniform-linear-native-scan\",\n directionNative: { value: rolling.scanDirectionNative.value, evidence: [...rolling.scanDirectionNative.evidence] },\n traversalDurationSeconds: { value: rolling.spatialSamplingSkewSeconds.value, unit: \"s\",\n evidence: [...rolling.spatialSamplingSkewSeconds.evidence] }\n };\n const closing: UniformLinearExposureBoundarySchedule = structuredClone(opening);\n const nominal = calculateCaptureExposureWindows({ nativeRaster: probeRaster, shutterMechanism: \"electronic\",\n nominalExposureDurationSeconds: input.nominalExposureDurationSeconds, opening, closing }).value.nominalExposureDurationSeconds;\n return {\n modelId: ELECTRONIC_ROLLING_SHUTTER_MODEL.id,\n modelVersion: ELECTRONIC_ROLLING_SHUTTER_MODEL.version,\n shutterMechanism: \"electronic\",\n nominalExposureDurationSeconds: { value: nominal.value, unit: \"s\", evidence: [...nominal.evidence] },\n opening,\n closing,\n linkage: { kind: \"spatial-phase-linked\", links: [\n { boundary: \"opening\", phaseOrientation: \"same\", evidence: linkageEvidence },\n { boundary: \"closing\", phaseOrientation: \"same\", evidence: linkageEvidence }] },\n lineScanSkewSeconds: rolling.spatialSamplingSkewSeconds.value,\n localExposureDurationSeconds: nominal.value,\n limitations: [\n \"Uniform-linear line timing: each native point integrates over [s·τ, s·τ + T] with s its normalized scan position.\",\n \"Line-reset jitter, non-uniform line timing, global-reset release and mechanical-close hybrids are not modeled.\",\n \"The skew τ is the caller-declared active-capture spatial sampling skew; it is never scaled from a full-frame value.\"\n ]\n };\n}\n\n/**\n * True when an exposure schedule is exactly the electronic rolling-shutter schedule derived from this\n * readout declaration: an electronic mechanism, with both boundaries scanning in the readout direction\n * and taking the readout skew to sweep.\n */\nexport function isElectronicRollingShutterExposureSchedule(\n schedule: Pick<CalculateCaptureExposureWindowsInput, \"shutterMechanism\" | \"opening\" | \"closing\">,\n readout: SensorReadoutTimingDeclaration\n): boolean {\n if (readout.readoutMode !== \"rolling\" || schedule.shutterMechanism !== \"electronic\") return false;\n return [schedule.opening, schedule.closing].every((boundary) => boundary.kind === \"uniform-linear-native-scan\" &&\n boundary.directionNative.value === readout.scanDirectionNative.value &&\n boundary.traversalDurationSeconds.value === readout.spatialSamplingSkewSeconds.value);\n}\n"]}
1
+ {"version":3,"file":"readout-exposure-linkage.js","sourceRoot":"","sources":["../../src/sensor/readout-exposure-linkage.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Links rolling sensor readout to electronic shutter scans, and derives the exposure schedule of a\n * fully electronic rolling shutter.\n *\n * A declared link says that the readout scan and one or more exposure-boundary scans move across the\n * sensor in the same order (or in reverse). That is deliberately weaker than time synchronization: it\n * does not say when readout happens relative to the start or end of exposure. The total data-readout\n * time is kept for diagnostics but never used to check the link. The readout skew and the boundary\n * sweep times may differ; their ratio is reported without implying a shared clock.\n * @see docs/MOTION_AND_SIGNAL.md for equations, coordinate/unit conventions, blockers and support\n * limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type { RasterPoint, RasterRect } from \"../output/capture-geometry.js\";\nimport {\n calculateCaptureExposureWindows,\n parseBoundarySchedule,\n parseSecondsFact,\n type CalculateCaptureExposureWindowsInput,\n type ExposureBoundarySchedule,\n type UniformLinearExposureBoundarySchedule,\n type SourcedCaptureTimingSeconds\n} from \"./exposure-window.js\";\nimport {\n calculateSensorReadoutTiming,\n type CaptureShutterMechanism,\n type NativeSensorReadoutScanDirection,\n type RollingSensorReadoutTimingDeclaration,\n type SensorReadoutTimingDeclaration,\n type SourcedSensorTimingSeconds\n} from \"./readout-timing.js\";\nimport type { NativeImageRaster } from \"./sensor-geometry.js\";\nimport { requireInputKeys, requireConfigRecord as requireRecord } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport type ReadoutExposureBoundaryId = \"opening\" | \"closing\";\n\nexport type ReadoutExposureSpatialPhaseOrientation =\n | \"same\"\n | \"reversed\";\n\nexport interface ReadoutExposureBoundarySpatialLink {\n boundary: ReadoutExposureBoundaryId;\n /**\n * How the normalized position of the rolling readout scan relates to that\n * of the selected electronic exposure-boundary scan.\n *\n * \"same\": boundary normalized phase = readout normalized phase.\n * \"reversed\": boundary normalized phase = 1 - readout normalized phase.\n *\n * This does not establish absolute temporal synchronization.\n */\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation;\n /**\n * Evidence for the link itself.\n *\n * The readout direction and timing, and the exposure-boundary direction and\n * timing, keep their own separate evidence.\n */\n evidence: readonly EvidenceProvenance[];\n}\n\nexport type ReadoutExposureTimingLinkageDeclaration =\n | {\n /**\n * No link between readout and exposure is stated for this capture.\n * This is not evidence that the physical processes are independent.\n */\n kind: \"unlinked\";\n links?: never;\n }\n | {\n /**\n * One or more exposure boundaries are explicitly linked to the rolling\n * readout by their normalized position across the native sensor.\n */\n kind: \"spatial-phase-linked\";\n links: readonly ReadoutExposureBoundarySpatialLink[];\n };\n\nexport interface AssessReadoutExposureTimingLinkageInput {\n nativeRaster: NativeImageRaster;\n activeCaptureRect?: RasterRect;\n shutterMechanism: CaptureShutterMechanism;\n readout: SensorReadoutTimingDeclaration;\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n opening: ExposureBoundarySchedule;\n closing: ExposureBoundarySchedule;\n linkage: ReadoutExposureTimingLinkageDeclaration;\n samplePointsNative?: readonly RasterPoint[];\n}\n\nexport interface ReadoutExposureBoundarySpatialLinkAssessment {\n boundary: ReadoutExposureBoundaryId;\n boundaryActuator: \"electronic\";\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation;\n normalizedPhaseRelationship:\n | \"boundary-phase-equals-readout-phase\"\n | \"boundary-phase-equals-one-minus-readout-phase\";\n relationshipEvidence: readonly EvidenceProvenance[];\n readoutDirectionNative: NativeSensorReadoutScanDirection;\n boundaryDirectionNative: NativeSensorReadoutScanDirection;\n /**\n * Rolling readout skew: time from the first to the last scan position.\n */\n readoutSpatialSamplingSkewSeconds: SourcedSensorTimingSeconds;\n /**\n * Time for the exposure boundary to sweep from first to last position.\n */\n boundaryTraversalDurationSeconds: SourcedCaptureTimingSeconds;\n /**\n * A descriptive ratio only. A value of 1 does not prove time\n * synchronization or a common clock origin.\n */\n boundaryTraversalToReadoutSpatialSkewRatio: number;\n absoluteTemporalAlignment: \"not-established\";\n}\n\nexport interface ReadoutExposureTimingLinkageAssessment {\n linkageKind: ReadoutExposureTimingLinkageDeclaration[\"kind\"];\n relationshipMeaning:\n | \"no-readout-exposure-relationship-asserted\"\n | \"normalized-spatial-phase-relationship-asserted\";\n readoutMode: \"rolling\" | \"global\";\n shutterMechanism: CaptureShutterMechanism;\n activeCaptureRect: RasterRect;\n absoluteTemporalAlignment: \"not-established\";\n /**\n * Data-readout time for the capture. Kept for diagnostics, but never used to\n * check an exposure-boundary link.\n */\n captureReadoutDurationSeconds: SourcedSensorTimingSeconds;\n links: readonly ReadoutExposureBoundarySpatialLinkAssessment[];\n componentProvenance: {\n sensorReadout: CalculationProvenance;\n exposureWindows: CalculationProvenance;\n };\n}\n\n\nconst BOUNDARIES = new Set<ReadoutExposureBoundaryId>([\n \"opening\",\n \"closing\"\n]);\n\nconst PHASE_ORIENTATIONS =\n new Set<ReadoutExposureSpatialPhaseOrientation>([\n \"same\",\n \"reversed\"\n ]);\n\nfunction parseSpatialLink(\n value: unknown,\n path: string\n): ReadoutExposureBoundarySpatialLink {\n const record = requireRecord(value, path, [\"boundary\", \"phaseOrientation\", \"evidence\"]);\n\n if (\n typeof record.boundary !== \"string\" ||\n !BOUNDARIES.has(record.boundary as ReadoutExposureBoundaryId)\n ) {\n throw new InvalidConfigurationError(path + \".boundary is invalid.\");\n }\n\n if (\n typeof record.phaseOrientation !== \"string\" ||\n !PHASE_ORIENTATIONS.has(\n record.phaseOrientation as ReadoutExposureSpatialPhaseOrientation\n )\n ) {\n throw new InvalidConfigurationError(\n path + \".phaseOrientation is invalid.\"\n );\n }\n\n return {\n boundary: record.boundary as ReadoutExposureBoundaryId,\n phaseOrientation:\n record.phaseOrientation as ReadoutExposureSpatialPhaseOrientation,\n evidence: parseEvidenceList(record.evidence, path + \".evidence\")\n };\n}\n\nfunction parseLinkageDeclaration(\n value: unknown\n): ReadoutExposureTimingLinkageDeclaration {\n const record = requireRecord(value, \"linkage\", [\"kind\", \"links\"]);\n\n if (record.kind === \"unlinked\") {\n if (record.links !== undefined) {\n throw new InvalidScientificInputError(\n \"linkage.links must be omitted when linkage.kind is unlinked.\"\n );\n }\n return { kind: \"unlinked\" };\n }\n\n if (record.kind !== \"spatial-phase-linked\") {\n throw new InvalidConfigurationError(\"linkage.kind is invalid.\");\n }\n\n if (!Array.isArray(record.links) || record.links.length === 0) {\n throw new InvalidConfigurationError(\n \"linkage.links must be a non-empty array for spatial-phase-linked linkage.\"\n );\n }\n\n const links = record.links.map((entry, index) =>\n parseSpatialLink(entry, \"linkage.links[\" + index + \"]\")\n );\n\n const seen = new Set<ReadoutExposureBoundaryId>();\n for (const link of links) {\n if (seen.has(link.boundary)) {\n throw new InvalidScientificInputError(\n \"linkage.links must not contain duplicate boundary links.\"\n );\n }\n seen.add(link.boundary);\n }\n\n return {\n kind: \"spatial-phase-linked\",\n links\n };\n}\n\nfunction oppositeDirection(\n direction: NativeSensorReadoutScanDirection\n): NativeSensorReadoutScanDirection {\n switch (direction) {\n case \"top-to-bottom\":\n return \"bottom-to-top\";\n case \"bottom-to-top\":\n return \"top-to-bottom\";\n case \"left-to-right\":\n return \"right-to-left\";\n case \"right-to-left\":\n return \"left-to-right\";\n }\n}\n\nfunction expectedBoundaryDirection(\n readoutDirection: NativeSensorReadoutScanDirection,\n phaseOrientation: ReadoutExposureSpatialPhaseOrientation\n): NativeSensorReadoutScanDirection {\n return phaseOrientation === \"same\"\n ? readoutDirection\n : oppositeDirection(readoutDirection);\n}\n\n/**\n * Checks an explicitly declared link between the rolling readout scan and one\n * or more electronic exposure-boundary scans.\n *\n * This is deliberately weaker than time synchronization. It can show that two\n * schedules sweep the native sensor in the same order (or in reverse), but not\n * when readout happens relative to the start or end of exposure.\n *\n * The total data-readout time is kept for diagnostics and never used to check\n * the link. The readout skew and the boundary sweep time may differ; their\n * ratio is reported without implying a shared clock.\n */\nexport function assessReadoutExposureTimingLinkage(\n input: AssessReadoutExposureTimingLinkageInput\n): CalculationResult<ReadoutExposureTimingLinkageAssessment> {\n requireInputKeys(input, \"assessReadoutExposureTimingLinkage\", [\"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"readout\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"linkage\", \"samplePointsNative\"]);\n const linkage = parseLinkageDeclaration(input.linkage);\n\n const sensorReadout = calculateSensorReadoutTiming({\n nativeRaster: input.nativeRaster,\n ...(input.activeCaptureRect === undefined\n ? {}\n : { activeCaptureRect: input.activeCaptureRect }),\n shutterMechanism: input.shutterMechanism,\n readout: input.readout,\n ...(input.samplePointsNative === undefined\n ? {}\n : { samplePointsNative: input.samplePointsNative })\n });\n\n const exposureWindows = calculateCaptureExposureWindows({\n nativeRaster: input.nativeRaster,\n ...(input.activeCaptureRect === undefined\n ? {}\n : { activeCaptureRect: input.activeCaptureRect }),\n shutterMechanism: input.shutterMechanism,\n nominalExposureDurationSeconds:\n input.nominalExposureDurationSeconds,\n opening: input.opening,\n closing: input.closing,\n ...(input.samplePointsNative === undefined\n ? {}\n : { samplePointsNative: input.samplePointsNative })\n });\n\n if (linkage.kind === \"unlinked\") {\n return approximationResult(\n {\n linkageKind: \"unlinked\",\n relationshipMeaning:\n \"no-readout-exposure-relationship-asserted\",\n readoutMode: sensorReadout.value.readoutMode,\n shutterMechanism: input.shutterMechanism,\n activeCaptureRect: {\n ...sensorReadout.value.activeCaptureRect\n },\n absoluteTemporalAlignment: \"not-established\",\n captureReadoutDurationSeconds:\n sensorReadout.value.captureReadoutDurationSeconds,\n links: [],\n componentProvenance: {\n sensorReadout: sensorReadout.provenance,\n exposureWindows: exposureWindows.provenance\n }\n },\n \"readout-exposure-spatial-linkage\",\n \"1.0.0\",\n [\n \"Unlinked means Photivra asserts no readout-to-exposure relationship for this capture; it does not prove the physical processes are independent.\",\n \"Sensor data-readout timing and exposure-window timing retain independent provenance and semantics.\",\n \"Absolute temporal alignment between data readout and exposure boundaries is not established.\"\n ]\n );\n }\n\n const readoutScan = sensorReadout.value.scan;\n if (readoutScan === null) {\n throw new InvalidScientificInputError(\n \"spatial-phase-linked linkage requires rolling sensor readout with a spatial scan.\"\n );\n }\n\n const assessments =\n linkage.links.map<ReadoutExposureBoundarySpatialLinkAssessment>(\n (link) => {\n const boundary =\n link.boundary === \"opening\"\n ? exposureWindows.value.opening\n : exposureWindows.value.closing;\n\n if (boundary.actuator !== \"electronic\") {\n throw new InvalidScientificInputError(\n \"A readout spatial-phase link may target only an electronic exposure boundary.\"\n );\n }\n\n if (\n boundary.schedule.kind !==\n \"uniform-linear-native-scan\"\n ) {\n throw new InvalidScientificInputError(\n \"A readout spatial-phase link requires the selected exposure boundary to use a uniform-linear native scan.\"\n );\n }\n\n const readoutDirection =\n readoutScan.directionNative.value;\n const boundaryDirection =\n boundary.schedule.directionNative.value;\n const expectedDirection = expectedBoundaryDirection(\n readoutDirection,\n link.phaseOrientation\n );\n\n if (boundaryDirection !== expectedDirection) {\n throw new InvalidScientificInputError(\n \"Declared readout/exposure spatial phase orientation contradicts the readout and exposure-boundary scan directions.\"\n );\n }\n\n const readoutSpatialSamplingSkewSeconds =\n readoutScan.spatialSamplingSkewSeconds;\n const boundaryTraversalDurationSeconds =\n boundary.schedule.traversalDurationSeconds;\n\n return {\n boundary: link.boundary,\n boundaryActuator: \"electronic\",\n phaseOrientation: link.phaseOrientation,\n normalizedPhaseRelationship:\n link.phaseOrientation === \"same\"\n ? \"boundary-phase-equals-readout-phase\"\n : \"boundary-phase-equals-one-minus-readout-phase\",\n relationshipEvidence: link.evidence,\n readoutDirectionNative: readoutDirection,\n boundaryDirectionNative: boundaryDirection,\n readoutSpatialSamplingSkewSeconds,\n boundaryTraversalDurationSeconds,\n boundaryTraversalToReadoutSpatialSkewRatio:\n boundaryTraversalDurationSeconds.value /\n readoutSpatialSamplingSkewSeconds.value,\n absoluteTemporalAlignment: \"not-established\"\n };\n }\n );\n\n return approximationResult(\n {\n linkageKind: \"spatial-phase-linked\",\n relationshipMeaning:\n \"normalized-spatial-phase-relationship-asserted\",\n readoutMode: sensorReadout.value.readoutMode,\n shutterMechanism: input.shutterMechanism,\n activeCaptureRect: {\n ...sensorReadout.value.activeCaptureRect\n },\n absoluteTemporalAlignment: \"not-established\",\n captureReadoutDurationSeconds:\n sensorReadout.value.captureReadoutDurationSeconds,\n links: assessments,\n componentProvenance: {\n sensorReadout: sensorReadout.provenance,\n exposureWindows: exposureWindows.provenance\n }\n },\n \"readout-exposure-spatial-linkage\",\n \"1.0.0\",\n [\n \"The linkage establishes normalized native-sensor spatial phase/order only, not absolute temporal synchronization.\",\n \"Every asserted boundary link carries evidence separate from the evidence for the underlying readout and exposure timing facts.\",\n \"Only electronic exposure boundaries may be linked to rolling sensor readout spatial phase.\",\n \"Same phase orientation requires matching native scan direction; reversed phase orientation requires the exact opposite direction on the same native axis.\",\n \"Rolling readout spatial skew and exposure-boundary traversal duration may differ; their ratio is descriptive and does not establish a common clock origin.\",\n \"Capture data-readout duration is not used to validate exposure-boundary timing linkage.\",\n \"Active capture, native coordinate semantics and shutter mechanism are shared because both component schedules are resolved from the same input.\",\n \"Sensor readout and exposure-window component provenance remain visible; linkage does not upgrade either component to calibrated status.\",\n \"No camera-specific timing relationship is inferred from shutter mechanism, sensor architecture metadata, output resolution or adjacent specifications.\"\n ]\n );\n}\n\n/** Model identity of the electronic rolling-shutter (line reset → line read) exposure schedule. */\nexport const ELECTRONIC_ROLLING_SHUTTER_MODEL = /* @__PURE__ */ Object.freeze({\n id: \"electronic-rolling-shutter-line-reset-read\",\n version: \"1.0.0\"\n} as const);\n\nexport interface ResolveElectronicRollingShutterExposureScheduleInput {\n /** Rolling readout declaration whose scan direction and spatial skew drive both exposure boundaries. */\n readout: RollingSensorReadoutTimingDeclaration;\n /** Per-line integration time between line reset and line read. */\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n /**\n * Evidence that this capture uses a fully electronic, line-by-line (rolling) shutter whose reset\n * scan runs at the readout line rate. This explicit link is required before sensor readout timing\n * may set the exposure-window timing.\n */\n linkageEvidence: readonly EvidenceProvenance[];\n}\n\n/**\n * Exposure-window schedule of an electronic rolling shutter. Its `shutterMechanism`,\n * `nominalExposureDurationSeconds`, `opening` and `closing` fields form a complete exposure schedule, so\n * they can be passed as a native RAW / environment `exposureWindow` or spread into a production\n * `temporalCapture.exposureWindowInput` (see {@link createRollingShutterCaptureExposureSchedule}).\n */\nexport interface ElectronicRollingShutterExposureSchedule {\n modelId: typeof ELECTRONIC_ROLLING_SHUTTER_MODEL.id;\n modelVersion: typeof ELECTRONIC_ROLLING_SHUTTER_MODEL.version;\n shutterMechanism: \"electronic\";\n nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;\n /** Line-reset scan: a native point at normalized scan position s starts integrating at s·τ. */\n opening: UniformLinearExposureBoundarySchedule;\n /** Line-read scan: the same point stops integrating at T + s·τ. */\n closing: UniformLinearExposureBoundarySchedule;\n /** Explicit linkage declaration accepted by {@link assessReadoutExposureTimingLinkage}. */\n linkage: Extract<ReadoutExposureTimingLinkageDeclaration, { kind: \"spatial-phase-linked\" }>;\n /** τ: offset between the first and last exposure starts (equal to the readout skew). */\n lineScanSkewSeconds: number;\n /** Every point integrates for exactly the nominal exposure duration T. */\n localExposureDurationSeconds: number;\n limitations: readonly string[];\n}\n\n/** The exposure-schedule fields of a derived rolling-shutter schedule, without model metadata. */\nexport function createRollingShutterCaptureExposureSchedule(\n schedule: ElectronicRollingShutterExposureSchedule\n): Pick<CalculateCaptureExposureWindowsInput, \"shutterMechanism\" | \"nominalExposureDurationSeconds\" | \"opening\" | \"closing\"> {\n // 2.0.0: the whole schedule is checked, so an unknown field at any level is rejected.\n const record = requireRecord(schedule, \"rollingShutterSchedule\", [\"modelId\", \"modelVersion\", \"shutterMechanism\",\n \"nominalExposureDurationSeconds\", \"opening\", \"closing\", \"linkage\", \"lineScanSkewSeconds\", \"localExposureDurationSeconds\",\n \"limitations\"]);\n parseSecondsFact(record.nominalExposureDurationSeconds, \"rollingShutterSchedule.nominalExposureDurationSeconds\");\n parseBoundarySchedule(record.opening, \"rollingShutterSchedule.opening\");\n parseBoundarySchedule(record.closing, \"rollingShutterSchedule.closing\");\n if (record.linkage !== undefined) parseLinkageDeclaration(record.linkage);\n return structuredClone({ shutterMechanism: schedule.shutterMechanism,\n nominalExposureDurationSeconds: schedule.nominalExposureDurationSeconds,\n opening: schedule.opening, closing: schedule.closing });\n}\n\n/**\n * Derives the exposure-window schedule of a fully electronic rolling shutter from its rolling readout\n * declaration.\n *\n * In a CMOS rolling shutter, each line is reset, collects light for the exposure time T and is then\n * read. The reset pointer runs T ahead of the read pointer, and both move at the line rate (El Gamal &\n * Eltoukhy 2005; Liang, Chang & Chen 2008). With the uniform linear scan used throughout this engine, a\n * native point at normalized scan position s ∈ [0, 1] (along the declared scan direction, over the\n * active capture) collects light over [s·τ, s·τ + T], where τ is the declared first-to-last readout\n * skew. So both boundaries become uniform linear scans in the readout direction that take τ to sweep.\n *\n * When this schedule feeds the per-site exposure windows, everything that samples motion, moving\n * sources or time-varying light at local exposure times (environment RAW, rotation quadrature,\n * temporal illumination integration) produces rolling-shutter skew, wobble, partial exposure and\n * flicker banding.\n *\n * Approximation: constant line rate, no line-reset jitter, no hybrid of global reset with a mechanical\n * closing shutter, and the active-capture skew is declared by the caller (never scaled from a\n * full-frame value).\n * @see docs/MOTION_AND_SIGNAL.md#electronic-rolling-shutter-image-formation\n */\nexport function resolveElectronicRollingShutterExposureSchedule(\n input: ResolveElectronicRollingShutterExposureScheduleInput\n): ElectronicRollingShutterExposureSchedule {\n requireInputKeys(input, \"resolveElectronicRollingShutterExposureSchedule\", [\"readout\", \"nominalExposureDurationSeconds\", \"linkageEvidence\"]);\n // The input keys are checked above; calculateSensorReadoutTiming checks the readout's own fields.\n const record = requireRecord(input, \"rollingShutter\", null);\n const readout = requireRecord(record.readout, \"rollingShutter.readout\", null);\n if (readout.readoutMode !== \"rolling\") {\n throw new InvalidScientificInputError(\n \"An electronic rolling-shutter exposure schedule requires a rolling sensor readout declaration.\"\n );\n }\n const linkageEvidence = parseEvidenceList(input.linkageEvidence, \"rollingShutter.linkageEvidence\");\n const rolling = input.readout;\n // Check the declaration and the exposure fact with the existing readout and exposure-window calculators.\n const probeRaster = { pixelWidth: 2, pixelHeight: 2 };\n calculateSensorReadoutTiming({ nativeRaster: probeRaster, shutterMechanism: \"electronic\", readout: rolling });\n const opening: UniformLinearExposureBoundarySchedule = {\n kind: \"uniform-linear-native-scan\",\n directionNative: { value: rolling.scanDirectionNative.value, evidence: [...rolling.scanDirectionNative.evidence] },\n traversalDurationSeconds: { value: rolling.spatialSamplingSkewSeconds.value, unit: \"s\",\n evidence: [...rolling.spatialSamplingSkewSeconds.evidence] }\n };\n const closing: UniformLinearExposureBoundarySchedule = structuredClone(opening);\n const nominal = calculateCaptureExposureWindows({ nativeRaster: probeRaster, shutterMechanism: \"electronic\",\n nominalExposureDurationSeconds: input.nominalExposureDurationSeconds, opening, closing }).value.nominalExposureDurationSeconds;\n return {\n modelId: ELECTRONIC_ROLLING_SHUTTER_MODEL.id,\n modelVersion: ELECTRONIC_ROLLING_SHUTTER_MODEL.version,\n shutterMechanism: \"electronic\",\n nominalExposureDurationSeconds: { value: nominal.value, unit: \"s\", evidence: [...nominal.evidence] },\n opening,\n closing,\n linkage: { kind: \"spatial-phase-linked\", links: [\n { boundary: \"opening\", phaseOrientation: \"same\", evidence: linkageEvidence },\n { boundary: \"closing\", phaseOrientation: \"same\", evidence: linkageEvidence }] },\n lineScanSkewSeconds: rolling.spatialSamplingSkewSeconds.value,\n localExposureDurationSeconds: nominal.value,\n limitations: [\n \"Uniform-linear line timing: each native point integrates over [s·τ, s·τ + T] with s its normalized scan position.\",\n \"Line-reset jitter, non-uniform line timing, global-reset release and mechanical-close hybrids are not modeled.\",\n \"The skew τ is the caller-declared active-capture spatial sampling skew; it is never scaled from a full-frame value.\"\n ]\n };\n}\n\n/**\n * True when an exposure schedule is exactly the electronic rolling-shutter schedule derived from this\n * readout declaration: an electronic mechanism, with both boundaries scanning in the readout direction\n * and taking the readout skew to sweep.\n */\nexport function isElectronicRollingShutterExposureSchedule(\n schedule: Pick<CalculateCaptureExposureWindowsInput, \"shutterMechanism\" | \"opening\" | \"closing\">,\n readout: SensorReadoutTimingDeclaration\n): boolean {\n if (readout.readoutMode !== \"rolling\" || schedule.shutterMechanism !== \"electronic\") return false;\n return [schedule.opening, schedule.closing].every((boundary) => boundary.kind === \"uniform-linear-native-scan\" &&\n boundary.directionNative.value === readout.scanDirectionNative.value &&\n boundary.traversalDurationSeconds.value === readout.spatialSamplingSkewSeconds.value);\n}\n"]}
@@ -17,8 +17,10 @@
17
17
  import { approximationResult } from "../core/calculation-result.js";
18
18
  import { parseEvidenceList } from "../core/evidence-provenance.js";
19
19
  import { InvalidScientificInputError, requirePositiveInteger } from "../core/validation.js";
20
- import { requireInputObject, requireInputKeys, rejectUnknownFields } from "../core/record-validation.js";
20
+ import { requireInputKeys, requireConfigRecord as requireRecord } from "../core/record-validation.js";
21
21
  import { InvalidConfigurationError } from "../core/configuration-error.js";
22
+ import { fullRect } from "../core/shared-helpers.js";
23
+ import { normalizedScanPosition, validateNativeRaster, scanVector, requirePositiveFinite, requireNonNegativeInteger } from "./exposure-window.js";
22
24
  const READOUT_MODES = new Set(["rolling", "global"]);
23
25
  const SCAN_DIRECTIONS = new Set([
24
26
  "top-to-bottom",
@@ -31,31 +33,12 @@ const SHUTTER_MECHANISMS = new Set([
31
33
  "electronic-first-curtain",
32
34
  "electronic"
33
35
  ]);
34
- function requireRecord(value, path, allowedKeys) {
35
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
36
- throw new InvalidConfigurationError(path + " must be an object.");
37
- }
38
- if (allowedKeys !== null)
39
- rejectUnknownFields(value, allowedKeys, path);
40
- return value;
41
- }
42
- function requireNonNegativeInteger(name, value) {
43
- if (!Number.isSafeInteger(value) || value < 0) {
44
- throw new InvalidScientificInputError(name + " must be a non-negative safe integer.");
45
- }
46
- }
47
36
  function requireNonNegativeFinite(name, value) {
48
37
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
49
38
  throw new InvalidScientificInputError(name + " must be a finite number greater than or equal to zero.");
50
39
  }
51
40
  return value;
52
41
  }
53
- function requirePositiveFinite(name, value) {
54
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
55
- throw new InvalidScientificInputError(name + " must be a finite number greater than zero.");
56
- }
57
- return value;
58
- }
59
42
  function parseSecondsFact(value, path, positive) {
60
43
  const record = requireRecord(value, path, ["unit", "value", "evidence"]);
61
44
  if (record.unit !== "s") {
@@ -107,19 +90,6 @@ function parseReadoutDeclaration(value) {
107
90
  spatialSamplingSkewSeconds: parseSecondsFact(record.spatialSamplingSkewSeconds, "readout.spatialSamplingSkewSeconds", true)
108
91
  };
109
92
  }
110
- function validateNativeRaster(raster) {
111
- requireInputObject(raster, "raster input");
112
- requirePositiveInteger("nativeRaster.pixelWidth", raster.pixelWidth);
113
- requirePositiveInteger("nativeRaster.pixelHeight", raster.pixelHeight);
114
- }
115
- function fullRect(raster) {
116
- return {
117
- x: 0,
118
- y: 0,
119
- width: raster.pixelWidth,
120
- height: raster.pixelHeight
121
- };
122
- }
123
93
  function validateActiveCaptureRect(rect, raster) {
124
94
  requireNonNegativeInteger("activeCaptureRect.x", rect.x);
125
95
  requireNonNegativeInteger("activeCaptureRect.y", rect.y);
@@ -149,30 +119,6 @@ function validatePointWithinActiveCapture(point, rect, index) {
149
119
  "] must lie within activeCaptureRect edge-coordinate bounds.");
150
120
  }
151
121
  }
152
- function scanVector(direction) {
153
- switch (direction) {
154
- case "top-to-bottom":
155
- return { x: 0, y: 1 };
156
- case "bottom-to-top":
157
- return { x: 0, y: -1 };
158
- case "left-to-right":
159
- return { x: 1, y: 0 };
160
- case "right-to-left":
161
- return { x: -1, y: 0 };
162
- }
163
- }
164
- function normalizedScanPosition(point, rect, direction) {
165
- switch (direction) {
166
- case "top-to-bottom":
167
- return (point.y - rect.y) / rect.height;
168
- case "bottom-to-top":
169
- return 1 - (point.y - rect.y) / rect.height;
170
- case "left-to-right":
171
- return (point.x - rect.x) / rect.width;
172
- case "right-to-left":
173
- return 1 - (point.x - rect.x) / rect.width;
174
- }
175
- }
176
122
  /**
177
123
  * Resolves the sensor readout scan schedule for one capture.
178
124
  *