@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":"camera-rotation.js","sourceRoot":"","sources":["../../src/motion/camera-rotation.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Where a still point in the world appears on the image plane while the camera rotates.\n *\n * Forward mapping: given the point's image position at exposure start (t=0), find its image position\n * at a later time t.\n * - The starting image point defines the world ray in the camera frame at t=0.\n * - Constant angular velocity is integrated as a single axis-angle rotation vector. This avoids any\n * dependence on Euler-angle order.\n * - The world ray is expressed in the rotated camera frame with the inverse camera rotation, then\n * projected rectilinearly back onto the image plane.\n * Only rotation is modelled. Camera translation is deliberately left out, because the image motion it\n * causes (optical flow) depends on scene depth.\n *\n * Inverse mapping: given a ray captured at a non-negative time, find where it was at exposure start.\n * For this constant-axis, pure-rotation model the inverse is analytic: apply the forward camera\n * rotation to the captured ray, then project rectilinearly. No iterative solver is needed.\n * @see docs/MOTION_AND_SIGNAL.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n calculatedResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport { calculateThinLensImageDistance } from \"../optics/thin-lens.js\";\nimport { rejectUnknownFields, requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\n\n/** Checks the nested objects of a rotation-mapping input before any of their fields are read. */\nfunction requireRotationMappingSubObjects(input: {\n imagePointMm: unknown; angularVelocityRadPerSec: unknown; samplingPitchMicrometers?: unknown;\n}): void {\n requireInputObject(input.imagePointMm, \"imagePointMm\");\n rejectUnknownFields(input.imagePointMm as object, [\"x\", \"y\"], \"imagePointMm\");\n requireInputObject(input.angularVelocityRadPerSec, \"angularVelocityRadPerSec\");\n rejectUnknownFields(input.angularVelocityRadPerSec as object, [\"pitch\", \"yaw\", \"roll\"], \"angularVelocityRadPerSec\");\n if (input.samplingPitchMicrometers !== undefined) {\n requireInputObject(input.samplingPitchMicrometers, \"samplingPitchMicrometers\");\n rejectUnknownFields(input.samplingPitchMicrometers as object, [\"x\", \"y\"], \"samplingPitchMicrometers\");\n }\n}\n\n/**\n * Camera angular velocity in radians per second about the exposure-start\n * camera axes (+X right, +Y up, +Z forward along the optical axis).\n *\n * Signs follow the physical right-hand rule about each named axis. These axes\n * form a left-handed basis. So a positive rotation is the mirror image of what\n * the plain component Rodrigues (cross-product) formula would give in it:\n *\n * - Positive pitch tilts the optical axis up. Still scene content moves\n * toward image -Y (down).\n * - Positive yaw turns the optical axis left (counter-clockwise seen from\n * above). Still scene content moves toward image +X (right).\n * - Positive roll turns the camera clockwise, as seen by the photographer\n * looking along +Z. Still scene content rotates counter-clockwise in the\n * +X-right/+Y-up image plane.\n */\nexport interface CameraAngularVelocityRadPerSec {\n /** Right-hand rotation rate about camera +X (right): positive tilts the optical axis up. */\n pitch: number;\n /** Right-hand rotation rate about camera +Y (up): positive turns the optical axis left. */\n yaw: number;\n /** Right-hand rotation rate about camera +Z (forward): positive rolls the camera clockwise as seen from behind it. */\n roll: number;\n}\n\nexport interface ImagePlanePointMm {\n /** Image-plane X coordinate relative to the optical axis, in millimetres. */\n x: number;\n /** Image-plane Y coordinate relative to the optical axis, in millimetres. */\n y: number;\n}\n\nexport interface AxisSamplingPitchMicrometers {\n x: number;\n y: number;\n}\n\nexport interface CalculateCameraRotationImageMappingInput {\n /** Physical focal length in millimetres. */\n focalLengthMm: number;\n /**\n * Stationary-world-ray image-plane position at exposure start.\n *\n * Image-plane coordinates have their origin on the optical axis, +X right, +Y up.\n */\n imagePointMm: ImagePlanePointMm;\n /**\n * Time in seconds from exposure start. Must be greater than or equal to zero.\n */\n timeSecondsFromExposureStart: number;\n /**\n * Constant camera angular-velocity vector in radians per second.\n *\n * Components are given in the camera axes at exposure start and follow the\n * physical right-hand rule:\n * - pitch about +X: positive tilts up; content moves toward image -Y;\n * - yaw about +Y: positive turns left; content moves toward image +X;\n * - roll about +Z: positive is clockwise seen from behind the camera.\n * See CameraAngularVelocityRadPerSec.\n */\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;\n /** Optional focus distance in metres for thin-lens projection distance. */\n focusDistanceM?: number;\n /**\n * Optional sampling pitch per axis, used to also report the displacement in\n * samples. This is the geometric spacing of samples, not the light-collecting\n * area of a photosite.\n */\n samplingPitchMicrometers?: AxisSamplingPitchMicrometers;\n}\n\nexport interface CameraRotationImageMapping {\n projectionDistanceMm: number;\n timeSecondsFromExposureStart: number;\n angularDisplacementRad: {\n pitch: number;\n yaw: number;\n roll: number;\n magnitude: number;\n };\n startImagePointMm: ImagePlanePointMm;\n mappedImagePointMm: ImagePlanePointMm;\n deltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n /**\n * Optional displacement in samples, in image-plane axes (+X right, +Y up).\n * This is not a vector in native raster axes.\n */\n deltaImagePlaneSamples?: {\n x: number;\n y: number;\n distance: number;\n };\n}\n\nexport interface CalculateInverseCameraRotationImageMappingInput {\n /** Physical focal length in millimetres. */\n focalLengthMm: number;\n /**\n * Captured image-plane location of a stationary world ray at the requested\n * time. Coordinates have their origin on the optical axis, +X right, +Y up.\n */\n imagePointMm: ImagePlanePointMm;\n /**\n * Capture time in seconds from exposure start. Must be greater than or equal\n * to zero.\n */\n timeSecondsFromExposureStart: number;\n /**\n * Constant camera angular-velocity vector in radians per second, with the\n * same sign/axis semantics as calculateCameraRotationImageMapping().\n */\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;\n /** Optional focus distance in metres for thin-lens projection distance. */\n focusDistanceM?: number;\n /**\n * Optional sampling pitch per axis, used to also report the inverse\n * displacement in samples.\n */\n samplingPitchMicrometers?: AxisSamplingPitchMicrometers;\n}\n\nexport interface InverseCameraRotationImageMapping {\n projectionDistanceMm: number;\n timeSecondsFromExposureStart: number;\n angularDisplacementRad: {\n pitch: number;\n yaw: number;\n roll: number;\n magnitude: number;\n };\n capturedImagePointMm: ImagePlanePointMm;\n referenceImagePointMm: ImagePlanePointMm;\n /**\n * Reference-minus-captured displacement in the image-plane basis.\n */\n deltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n deltaImagePlaneSamples?: {\n x: number;\n y: number;\n distance: number;\n };\n}\n\ninterface Vector3 {\n x: number;\n y: number;\n z: number;\n}\n\nfunction requireFinite(name: string, value: number): void {\n if (!Number.isFinite(value)) {\n throw new InvalidScientificInputError(`${name} must be finite.`);\n }\n}\n\nfunction requireNonNegativeFinite(name: string, value: number): void {\n if (!Number.isFinite(value) || value < 0) {\n throw new InvalidScientificInputError(\n `${name} must be finite and greater than or equal to zero.`\n );\n }\n}\n\n/**\n * Internal Rodrigues rotation in component form:\n * v cos a + (k x v) sin a + k (k . v)(1 - cos a), with the cross product\n * computed by its usual component formula. In the left-handed camera basis\n * this is a physical left-hand rotation. Camera-rotation code must call\n * applyPhysicalCameraRotation() instead, so the right-hand sign convention\n * lives in one place.\n */\nexport function rotateVectorByAxisAngle(\n vector: Vector3,\n axis: Vector3,\n angleRad: number\n): Vector3 {\n if (angleRad === 0) {\n return { ...vector };\n }\n\n const cosine = Math.cos(angleRad);\n const sine = Math.sin(angleRad);\n const oneMinusCosine = 1 - cosine;\n const dot =\n axis.x * vector.x +\n axis.y * vector.y +\n axis.z * vector.z;\n const cross = {\n x: axis.y * vector.z - axis.z * vector.y,\n y: axis.z * vector.x - axis.x * vector.z,\n z: axis.x * vector.y - axis.y * vector.x\n };\n\n return {\n x:\n vector.x * cosine +\n cross.x * sine +\n axis.x * dot * oneMinusCosine,\n y:\n vector.y * cosine +\n cross.y * sine +\n axis.y * dot * oneMinusCosine,\n z:\n vector.z * cosine +\n cross.z * sine +\n axis.z * dot * oneMinusCosine\n };\n}\n\n/**\n * Internal: applies the physical camera rotation for one right-hand angular\n * displacement (pitch/yaw/roll about camera +X/+Y/+Z, radians) to a vector in\n * camera components.\n *\n * - \"camera-to-reference\" applies R: a ray expressed in the rotated camera\n * frame is re-expressed in the exposure-start (reference) camera frame.\n * - \"reference-to-camera\" applies R^-1: a stationary reference ray is\n * expressed in the rotated camera frame.\n *\n * Because the camera basis is left-handed, the physical right-hand rotation R\n * is the component Rodrigues formula with the angle negated.\n */\nexport function applyPhysicalCameraRotation(\n vector: Vector3,\n angularDisplacementRad: { pitch: number; yaw: number; roll: number },\n direction: \"camera-to-reference\" | \"reference-to-camera\"\n): Vector3 {\n const magnitude = Math.hypot(\n angularDisplacementRad.pitch,\n angularDisplacementRad.yaw,\n angularDisplacementRad.roll\n );\n if (magnitude === 0) {\n return { ...vector };\n }\n const axis = {\n x: angularDisplacementRad.pitch / magnitude,\n y: angularDisplacementRad.yaw / magnitude,\n z: angularDisplacementRad.roll / magnitude\n };\n return rotateVectorByAxisAngle(\n vector,\n axis,\n direction === \"camera-to-reference\" ? -magnitude : magnitude\n );\n}\n\n/** Internal: projection distance, shared with the lean native environment executor. Not exported from the package root. */\nexport function resolveProjectionDistanceMm(\n focalLengthMm: number,\n focusDistanceM: number | undefined\n): {\n projectionDistanceMm: number;\n focusAware: boolean;\n} {\n const projection =\n focusDistanceM === undefined\n ? undefined\n : calculateThinLensImageDistance({\n focalLengthMm,\n objectDistanceM: focusDistanceM\n });\n\n return {\n projectionDistanceMm:\n projection?.value.imageDistanceMm ?? focalLengthMm,\n focusAware: projection !== undefined\n };\n}\n\n/**\n * Maps one stationary-world ray to its image-plane location after pure camera\n * rotation at an arbitrary physical time from exposure start.\n *\n * The starting image point defines the world ray in the camera frame at t=0.\n * Constant angular velocity is integrated as a single axis-angle rotation\n * vector, which avoids any dependence on Euler-angle order. The world ray is\n * then expressed in the rotated camera frame with the inverse camera rotation,\n * and projected rectilinearly back onto the image plane.\n *\n * Only rotation is modelled. Camera translation is deliberately left out,\n * because the image motion it causes depends on scene depth.\n *\n * @returns Field-position-dependent image-plane mapping and displacement.\n */\nexport function calculateCameraRotationImageMapping(\n input: CalculateCameraRotationImageMappingInput\n): CalculationResult<CameraRotationImageMapping> {\n requireInputKeys(input, \"calculateCameraRotationImageMapping\", [\"focalLengthMm\", \"imagePointMm\", \"timeSecondsFromExposureStart\", \"angularVelocityRadPerSec\", \"focusDistanceM\", \"samplingPitchMicrometers\"]);\n requirePositiveFinite(\"focalLengthMm\", input.focalLengthMm);\n requireRotationMappingSubObjects(input);\n requireFinite(\"imagePointMm.x\", input.imagePointMm.x);\n requireFinite(\"imagePointMm.y\", input.imagePointMm.y);\n requireNonNegativeFinite(\n \"timeSecondsFromExposureStart\",\n input.timeSecondsFromExposureStart\n );\n requireFinite(\n \"angularVelocityRadPerSec.pitch\",\n input.angularVelocityRadPerSec.pitch\n );\n requireFinite(\n \"angularVelocityRadPerSec.yaw\",\n input.angularVelocityRadPerSec.yaw\n );\n requireFinite(\n \"angularVelocityRadPerSec.roll\",\n input.angularVelocityRadPerSec.roll\n );\n\n if (input.samplingPitchMicrometers !== undefined) {\n requirePositiveFinite(\n \"samplingPitchMicrometers.x\",\n input.samplingPitchMicrometers.x\n );\n requirePositiveFinite(\n \"samplingPitchMicrometers.y\",\n input.samplingPitchMicrometers.y\n );\n }\n\n const { projectionDistanceMm, focusAware } =\n resolveProjectionDistanceMm(\n input.focalLengthMm,\n input.focusDistanceM\n );\n\n const angularDisplacementRad = {\n pitch:\n input.angularVelocityRadPerSec.pitch *\n input.timeSecondsFromExposureStart,\n yaw:\n input.angularVelocityRadPerSec.yaw *\n input.timeSecondsFromExposureStart,\n roll:\n input.angularVelocityRadPerSec.roll *\n input.timeSecondsFromExposureStart\n };\n const angularMagnitude = Math.hypot(\n angularDisplacementRad.pitch,\n angularDisplacementRad.yaw,\n angularDisplacementRad.roll\n );\n\n const initialRay: Vector3 = {\n x: input.imagePointMm.x / projectionDistanceMm,\n y: input.imagePointMm.y / projectionDistanceMm,\n z: 1\n };\n\n const mappedRay = applyPhysicalCameraRotation(\n initialRay,\n angularDisplacementRad,\n \"reference-to-camera\"\n );\n\n if (!Number.isFinite(mappedRay.z) || mappedRay.z <= 0) {\n throw new InvalidScientificInputError(\n \"Camera rotation maps the requested world ray to or behind the camera plane.\"\n );\n }\n\n const mappedImagePointMm = {\n x: projectionDistanceMm * (mappedRay.x / mappedRay.z),\n y: projectionDistanceMm * (mappedRay.y / mappedRay.z)\n };\n const deltaMm = {\n x: mappedImagePointMm.x - input.imagePointMm.x,\n y: mappedImagePointMm.y - input.imagePointMm.y,\n distance: Math.hypot(\n mappedImagePointMm.x - input.imagePointMm.x,\n mappedImagePointMm.y - input.imagePointMm.y\n )\n };\n\n let deltaImagePlaneSamples:\n | {\n x: number;\n y: number;\n distance: number;\n }\n | undefined;\n if (input.samplingPitchMicrometers !== undefined) {\n const pitchXmm = input.samplingPitchMicrometers.x / 1000;\n const pitchYmm = input.samplingPitchMicrometers.y / 1000;\n const sampleX = deltaMm.x / pitchXmm;\n const sampleY = deltaMm.y / pitchYmm;\n deltaImagePlaneSamples = {\n x: sampleX,\n y: sampleY,\n distance: Math.hypot(sampleX, sampleY)\n };\n }\n\n return calculatedResult(\n {\n projectionDistanceMm,\n timeSecondsFromExposureStart:\n input.timeSecondsFromExposureStart,\n angularDisplacementRad: {\n ...angularDisplacementRad,\n magnitude: angularMagnitude\n },\n startImagePointMm: { ...input.imagePointMm },\n mappedImagePointMm,\n deltaMm,\n ...(deltaImagePlaneSamples === undefined\n ? {}\n : { deltaImagePlaneSamples })\n },\n focusAware\n ? \"focus-aware-spatial-camera-rotation-mapping\"\n : \"spatial-camera-rotation-mapping\",\n \"2.0.0\",\n [\n \"Ideal rectilinear projection\",\n focusAware\n ? \"Thin-lens image distance for the selected focus plane is used as the projection distance\"\n : \"Nominal focal length is used as the infinity-focus projection distance\",\n \"Camera angular velocity is constant during the evaluated interval\",\n \"Angular velocity components are resolved in the camera axes at exposure start and integrated as one axis-angle vector\",\n \"Positive pitch/yaw/roll follow the physical right-hand rule about camera +X/+Y/+Z (pitch up, yaw left, roll clockwise seen from behind); the camera basis is left-handed\",\n \"The world ray is stationary; subject motion is not included\",\n \"Camera translation and depth-dependent parallax are not modeled\",\n \"The stationary world ray is transformed by the inverse camera rotation before rectilinear projection\"\n ]\n );\n}\n\n\n/**\n * Maps a captured stationary-world ray at one non-negative capture time back\n * to its image-plane location at exposure start.\n *\n * For this constant-axis, pure-rotation model the inverse is analytic: apply\n * the forward camera rotation to the captured ray, then project\n * rectilinearly. No iterative solver is needed.\n */\nexport function calculateInverseCameraRotationImageMapping(\n input: CalculateInverseCameraRotationImageMappingInput\n): CalculationResult<InverseCameraRotationImageMapping> {\n requireInputKeys(input, \"calculateInverseCameraRotationImageMapping\", [\"focalLengthMm\", \"imagePointMm\", \"timeSecondsFromExposureStart\", \"angularVelocityRadPerSec\", \"focusDistanceM\", \"samplingPitchMicrometers\"]);\n requirePositiveFinite(\"focalLengthMm\", input.focalLengthMm);\n requireRotationMappingSubObjects(input);\n requireFinite(\"imagePointMm.x\", input.imagePointMm.x);\n requireFinite(\"imagePointMm.y\", input.imagePointMm.y);\n requireNonNegativeFinite(\n \"timeSecondsFromExposureStart\",\n input.timeSecondsFromExposureStart\n );\n requireFinite(\n \"angularVelocityRadPerSec.pitch\",\n input.angularVelocityRadPerSec.pitch\n );\n requireFinite(\n \"angularVelocityRadPerSec.yaw\",\n input.angularVelocityRadPerSec.yaw\n );\n requireFinite(\n \"angularVelocityRadPerSec.roll\",\n input.angularVelocityRadPerSec.roll\n );\n\n if (input.samplingPitchMicrometers !== undefined) {\n requirePositiveFinite(\n \"samplingPitchMicrometers.x\",\n input.samplingPitchMicrometers.x\n );\n requirePositiveFinite(\n \"samplingPitchMicrometers.y\",\n input.samplingPitchMicrometers.y\n );\n }\n\n const { projectionDistanceMm, focusAware } =\n resolveProjectionDistanceMm(\n input.focalLengthMm,\n input.focusDistanceM\n );\n\n const angularDisplacementRad = {\n pitch:\n input.angularVelocityRadPerSec.pitch *\n input.timeSecondsFromExposureStart,\n yaw:\n input.angularVelocityRadPerSec.yaw *\n input.timeSecondsFromExposureStart,\n roll:\n input.angularVelocityRadPerSec.roll *\n input.timeSecondsFromExposureStart\n };\n const angularMagnitude = Math.hypot(\n angularDisplacementRad.pitch,\n angularDisplacementRad.yaw,\n angularDisplacementRad.roll\n );\n\n const capturedRay: Vector3 = {\n x: input.imagePointMm.x / projectionDistanceMm,\n y: input.imagePointMm.y / projectionDistanceMm,\n z: 1\n };\n\n const referenceRay = applyPhysicalCameraRotation(\n capturedRay,\n angularDisplacementRad,\n \"camera-to-reference\"\n );\n\n if (!Number.isFinite(referenceRay.z) || referenceRay.z <= 0) {\n throw new InvalidScientificInputError(\n \"Inverse camera rotation maps the requested captured ray to or behind the reference camera plane.\"\n );\n }\n\n const referenceImagePointMm = {\n x: projectionDistanceMm * (referenceRay.x / referenceRay.z),\n y: projectionDistanceMm * (referenceRay.y / referenceRay.z)\n };\n const deltaMm = {\n x: referenceImagePointMm.x - input.imagePointMm.x,\n y: referenceImagePointMm.y - input.imagePointMm.y,\n distance: Math.hypot(\n referenceImagePointMm.x - input.imagePointMm.x,\n referenceImagePointMm.y - input.imagePointMm.y\n )\n };\n\n let deltaImagePlaneSamples:\n | {\n x: number;\n y: number;\n distance: number;\n }\n | undefined;\n if (input.samplingPitchMicrometers !== undefined) {\n const pitchXmm = input.samplingPitchMicrometers.x / 1000;\n const pitchYmm = input.samplingPitchMicrometers.y / 1000;\n const sampleX = deltaMm.x / pitchXmm;\n const sampleY = deltaMm.y / pitchYmm;\n deltaImagePlaneSamples = {\n x: sampleX,\n y: sampleY,\n distance: Math.hypot(sampleX, sampleY)\n };\n }\n\n return calculatedResult(\n {\n projectionDistanceMm,\n timeSecondsFromExposureStart:\n input.timeSecondsFromExposureStart,\n angularDisplacementRad: {\n ...angularDisplacementRad,\n magnitude: angularMagnitude\n },\n capturedImagePointMm: { ...input.imagePointMm },\n referenceImagePointMm,\n deltaMm,\n ...(deltaImagePlaneSamples === undefined\n ? {}\n : { deltaImagePlaneSamples })\n },\n focusAware\n ? \"focus-aware-inverse-spatial-camera-rotation-mapping\"\n : \"inverse-spatial-camera-rotation-mapping\",\n \"2.0.0\",\n [\n \"Ideal rectilinear projection\",\n focusAware\n ? \"Thin-lens image distance for the selected focus plane is used as the projection distance\"\n : \"Nominal focal length is used as the infinity-focus projection distance\",\n \"Camera angular velocity is constant during the evaluated interval\",\n \"Angular velocity components are resolved in the camera axes at exposure start and integrated as one axis-angle vector\",\n \"Positive pitch/yaw/roll follow the physical right-hand rule about camera +X/+Y/+Z (pitch up, yaw left, roll clockwise seen from behind); the camera basis is left-handed\",\n \"The inverse is analytic under the same constant-axis pure-rotation model; no iterative solve is used\",\n \"The captured stationary-world ray is transformed by the forward camera rotation to recover its exposure-start reference ray\",\n \"Camera translation, scene depth parallax and subject motion are not modeled\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"camera-rotation.js","sourceRoot":"","sources":["../../src/motion/camera-rotation.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Where a still point in the world appears on the image plane while the camera rotates.\n *\n * Forward mapping: given the point's image position at exposure start (t=0), find its image position\n * at a later time t.\n * - The starting image point defines the world ray in the camera frame at t=0.\n * - Constant angular velocity is integrated as a single axis-angle rotation vector. This avoids any\n * dependence on Euler-angle order.\n * - The world ray is expressed in the rotated camera frame with the inverse camera rotation, then\n * projected rectilinearly back onto the image plane.\n * Only rotation is modelled. Camera translation is deliberately left out, because the image motion it\n * causes (optical flow) depends on scene depth.\n *\n * Inverse mapping: given a ray captured at a non-negative time, find where it was at exposure start.\n * For this constant-axis, pure-rotation model the inverse is analytic: apply the forward camera\n * rotation to the captured ray, then project rectilinearly. No iterative solver is needed.\n * @see docs/MOTION_AND_SIGNAL.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n calculatedResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport { calculateThinLensImageDistance } from \"../optics/thin-lens.js\";\nimport { rejectUnknownFields, requireInputKeys, requireInputObject, assertNamedFinite as requireFinite } from \"../core/record-validation.js\";\n\n/** Checks the nested objects of a rotation-mapping input before any of their fields are read. */\nfunction requireRotationMappingSubObjects(input: {\n imagePointMm: unknown; angularVelocityRadPerSec: unknown; samplingPitchMicrometers?: unknown;\n}): void {\n requireInputObject(input.imagePointMm, \"imagePointMm\");\n rejectUnknownFields(input.imagePointMm as object, [\"x\", \"y\"], \"imagePointMm\");\n requireInputObject(input.angularVelocityRadPerSec, \"angularVelocityRadPerSec\");\n rejectUnknownFields(input.angularVelocityRadPerSec as object, [\"pitch\", \"yaw\", \"roll\"], \"angularVelocityRadPerSec\");\n if (input.samplingPitchMicrometers !== undefined) {\n requireInputObject(input.samplingPitchMicrometers, \"samplingPitchMicrometers\");\n rejectUnknownFields(input.samplingPitchMicrometers as object, [\"x\", \"y\"], \"samplingPitchMicrometers\");\n }\n}\n\n/**\n * Camera angular velocity in radians per second about the exposure-start\n * camera axes (+X right, +Y up, +Z forward along the optical axis).\n *\n * Signs follow the physical right-hand rule about each named axis. These axes\n * form a left-handed basis. So a positive rotation is the mirror image of what\n * the plain component Rodrigues (cross-product) formula would give in it:\n *\n * - Positive pitch tilts the optical axis up. Still scene content moves\n * toward image -Y (down).\n * - Positive yaw turns the optical axis left (counter-clockwise seen from\n * above). Still scene content moves toward image +X (right).\n * - Positive roll turns the camera clockwise, as seen by the photographer\n * looking along +Z. Still scene content rotates counter-clockwise in the\n * +X-right/+Y-up image plane.\n */\nexport interface CameraAngularVelocityRadPerSec {\n /** Right-hand rotation rate about camera +X (right): positive tilts the optical axis up. */\n pitch: number;\n /** Right-hand rotation rate about camera +Y (up): positive turns the optical axis left. */\n yaw: number;\n /** Right-hand rotation rate about camera +Z (forward): positive rolls the camera clockwise as seen from behind it. */\n roll: number;\n}\n\nexport interface ImagePlanePointMm {\n /** Image-plane X coordinate relative to the optical axis, in millimetres. */\n x: number;\n /** Image-plane Y coordinate relative to the optical axis, in millimetres. */\n y: number;\n}\n\nexport interface AxisSamplingPitchMicrometers {\n x: number;\n y: number;\n}\n\nexport interface CalculateCameraRotationImageMappingInput {\n /** Physical focal length in millimetres. */\n focalLengthMm: number;\n /**\n * Stationary-world-ray image-plane position at exposure start.\n *\n * Image-plane coordinates have their origin on the optical axis, +X right, +Y up.\n */\n imagePointMm: ImagePlanePointMm;\n /**\n * Time in seconds from exposure start. Must be greater than or equal to zero.\n */\n timeSecondsFromExposureStart: number;\n /**\n * Constant camera angular-velocity vector in radians per second.\n *\n * Components are given in the camera axes at exposure start and follow the\n * physical right-hand rule:\n * - pitch about +X: positive tilts up; content moves toward image -Y;\n * - yaw about +Y: positive turns left; content moves toward image +X;\n * - roll about +Z: positive is clockwise seen from behind the camera.\n * See CameraAngularVelocityRadPerSec.\n */\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;\n /** Optional focus distance in metres for thin-lens projection distance. */\n focusDistanceM?: number;\n /**\n * Optional sampling pitch per axis, used to also report the displacement in\n * samples. This is the geometric spacing of samples, not the light-collecting\n * area of a photosite.\n */\n samplingPitchMicrometers?: AxisSamplingPitchMicrometers;\n}\n\nexport interface CameraRotationImageMapping {\n projectionDistanceMm: number;\n timeSecondsFromExposureStart: number;\n angularDisplacementRad: {\n pitch: number;\n yaw: number;\n roll: number;\n magnitude: number;\n };\n startImagePointMm: ImagePlanePointMm;\n mappedImagePointMm: ImagePlanePointMm;\n deltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n /**\n * Optional displacement in samples, in image-plane axes (+X right, +Y up).\n * This is not a vector in native raster axes.\n */\n deltaImagePlaneSamples?: {\n x: number;\n y: number;\n distance: number;\n };\n}\n\nexport interface CalculateInverseCameraRotationImageMappingInput {\n /** Physical focal length in millimetres. */\n focalLengthMm: number;\n /**\n * Captured image-plane location of a stationary world ray at the requested\n * time. Coordinates have their origin on the optical axis, +X right, +Y up.\n */\n imagePointMm: ImagePlanePointMm;\n /**\n * Capture time in seconds from exposure start. Must be greater than or equal\n * to zero.\n */\n timeSecondsFromExposureStart: number;\n /**\n * Constant camera angular-velocity vector in radians per second, with the\n * same sign/axis semantics as calculateCameraRotationImageMapping().\n */\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;\n /** Optional focus distance in metres for thin-lens projection distance. */\n focusDistanceM?: number;\n /**\n * Optional sampling pitch per axis, used to also report the inverse\n * displacement in samples.\n */\n samplingPitchMicrometers?: AxisSamplingPitchMicrometers;\n}\n\nexport interface InverseCameraRotationImageMapping {\n projectionDistanceMm: number;\n timeSecondsFromExposureStart: number;\n angularDisplacementRad: {\n pitch: number;\n yaw: number;\n roll: number;\n magnitude: number;\n };\n capturedImagePointMm: ImagePlanePointMm;\n referenceImagePointMm: ImagePlanePointMm;\n /**\n * Reference-minus-captured displacement in the image-plane basis.\n */\n deltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n deltaImagePlaneSamples?: {\n x: number;\n y: number;\n distance: number;\n };\n}\n\ninterface Vector3 {\n x: number;\n y: number;\n z: number;\n}\n\nfunction requireNonNegativeFinite(name: string, value: number): void {\n if (!Number.isFinite(value) || value < 0) {\n throw new InvalidScientificInputError(\n `${name} must be finite and greater than or equal to zero.`\n );\n }\n}\n\n/**\n * Internal Rodrigues rotation in component form:\n * v cos a + (k x v) sin a + k (k . v)(1 - cos a), with the cross product\n * computed by its usual component formula. In the left-handed camera basis\n * this is a physical left-hand rotation. Camera-rotation code must call\n * applyPhysicalCameraRotation() instead, so the right-hand sign convention\n * lives in one place.\n */\nexport function rotateVectorByAxisAngle(\n vector: Vector3,\n axis: Vector3,\n angleRad: number\n): Vector3 {\n if (angleRad === 0) {\n return { ...vector };\n }\n\n const cosine = Math.cos(angleRad);\n const sine = Math.sin(angleRad);\n const oneMinusCosine = 1 - cosine;\n const dot =\n axis.x * vector.x +\n axis.y * vector.y +\n axis.z * vector.z;\n const cross = {\n x: axis.y * vector.z - axis.z * vector.y,\n y: axis.z * vector.x - axis.x * vector.z,\n z: axis.x * vector.y - axis.y * vector.x\n };\n\n return {\n x:\n vector.x * cosine +\n cross.x * sine +\n axis.x * dot * oneMinusCosine,\n y:\n vector.y * cosine +\n cross.y * sine +\n axis.y * dot * oneMinusCosine,\n z:\n vector.z * cosine +\n cross.z * sine +\n axis.z * dot * oneMinusCosine\n };\n}\n\n/**\n * Internal: applies the physical camera rotation for one right-hand angular\n * displacement (pitch/yaw/roll about camera +X/+Y/+Z, radians) to a vector in\n * camera components.\n *\n * - \"camera-to-reference\" applies R: a ray expressed in the rotated camera\n * frame is re-expressed in the exposure-start (reference) camera frame.\n * - \"reference-to-camera\" applies R^-1: a stationary reference ray is\n * expressed in the rotated camera frame.\n *\n * Because the camera basis is left-handed, the physical right-hand rotation R\n * is the component Rodrigues formula with the angle negated.\n */\nexport function applyPhysicalCameraRotation(\n vector: Vector3,\n angularDisplacementRad: { pitch: number; yaw: number; roll: number },\n direction: \"camera-to-reference\" | \"reference-to-camera\"\n): Vector3 {\n const magnitude = Math.hypot(\n angularDisplacementRad.pitch,\n angularDisplacementRad.yaw,\n angularDisplacementRad.roll\n );\n if (magnitude === 0) {\n return { ...vector };\n }\n const axis = {\n x: angularDisplacementRad.pitch / magnitude,\n y: angularDisplacementRad.yaw / magnitude,\n z: angularDisplacementRad.roll / magnitude\n };\n return rotateVectorByAxisAngle(\n vector,\n axis,\n direction === \"camera-to-reference\" ? -magnitude : magnitude\n );\n}\n\n/** Internal: projection distance, shared with the lean native environment executor. Not exported from the package root. */\nexport function resolveProjectionDistanceMm(\n focalLengthMm: number,\n focusDistanceM: number | undefined\n): {\n projectionDistanceMm: number;\n focusAware: boolean;\n} {\n const projection =\n focusDistanceM === undefined\n ? undefined\n : calculateThinLensImageDistance({\n focalLengthMm,\n objectDistanceM: focusDistanceM\n });\n\n return {\n projectionDistanceMm:\n projection?.value.imageDistanceMm ?? focalLengthMm,\n focusAware: projection !== undefined\n };\n}\n\n/**\n * Maps one stationary-world ray to its image-plane location after pure camera\n * rotation at an arbitrary physical time from exposure start.\n *\n * The starting image point defines the world ray in the camera frame at t=0.\n * Constant angular velocity is integrated as a single axis-angle rotation\n * vector, which avoids any dependence on Euler-angle order. The world ray is\n * then expressed in the rotated camera frame with the inverse camera rotation,\n * and projected rectilinearly back onto the image plane.\n *\n * Only rotation is modelled. Camera translation is deliberately left out,\n * because the image motion it causes depends on scene depth.\n *\n * @returns Field-position-dependent image-plane mapping and displacement.\n */\nexport function calculateCameraRotationImageMapping(\n input: CalculateCameraRotationImageMappingInput\n): CalculationResult<CameraRotationImageMapping> {\n requireInputKeys(input, \"calculateCameraRotationImageMapping\", [\"focalLengthMm\", \"imagePointMm\", \"timeSecondsFromExposureStart\", \"angularVelocityRadPerSec\", \"focusDistanceM\", \"samplingPitchMicrometers\"]);\n requirePositiveFinite(\"focalLengthMm\", input.focalLengthMm);\n requireRotationMappingSubObjects(input);\n requireFinite(\"imagePointMm.x\", input.imagePointMm.x);\n requireFinite(\"imagePointMm.y\", input.imagePointMm.y);\n requireNonNegativeFinite(\n \"timeSecondsFromExposureStart\",\n input.timeSecondsFromExposureStart\n );\n requireFinite(\n \"angularVelocityRadPerSec.pitch\",\n input.angularVelocityRadPerSec.pitch\n );\n requireFinite(\n \"angularVelocityRadPerSec.yaw\",\n input.angularVelocityRadPerSec.yaw\n );\n requireFinite(\n \"angularVelocityRadPerSec.roll\",\n input.angularVelocityRadPerSec.roll\n );\n\n if (input.samplingPitchMicrometers !== undefined) {\n requirePositiveFinite(\n \"samplingPitchMicrometers.x\",\n input.samplingPitchMicrometers.x\n );\n requirePositiveFinite(\n \"samplingPitchMicrometers.y\",\n input.samplingPitchMicrometers.y\n );\n }\n\n const { projectionDistanceMm, focusAware } =\n resolveProjectionDistanceMm(\n input.focalLengthMm,\n input.focusDistanceM\n );\n\n const angularDisplacementRad = {\n pitch:\n input.angularVelocityRadPerSec.pitch *\n input.timeSecondsFromExposureStart,\n yaw:\n input.angularVelocityRadPerSec.yaw *\n input.timeSecondsFromExposureStart,\n roll:\n input.angularVelocityRadPerSec.roll *\n input.timeSecondsFromExposureStart\n };\n const angularMagnitude = Math.hypot(\n angularDisplacementRad.pitch,\n angularDisplacementRad.yaw,\n angularDisplacementRad.roll\n );\n\n const initialRay: Vector3 = {\n x: input.imagePointMm.x / projectionDistanceMm,\n y: input.imagePointMm.y / projectionDistanceMm,\n z: 1\n };\n\n const mappedRay = applyPhysicalCameraRotation(\n initialRay,\n angularDisplacementRad,\n \"reference-to-camera\"\n );\n\n if (!Number.isFinite(mappedRay.z) || mappedRay.z <= 0) {\n throw new InvalidScientificInputError(\n \"Camera rotation maps the requested world ray to or behind the camera plane.\"\n );\n }\n\n const mappedImagePointMm = {\n x: projectionDistanceMm * (mappedRay.x / mappedRay.z),\n y: projectionDistanceMm * (mappedRay.y / mappedRay.z)\n };\n const deltaMm = {\n x: mappedImagePointMm.x - input.imagePointMm.x,\n y: mappedImagePointMm.y - input.imagePointMm.y,\n distance: Math.hypot(\n mappedImagePointMm.x - input.imagePointMm.x,\n mappedImagePointMm.y - input.imagePointMm.y\n )\n };\n\n let deltaImagePlaneSamples:\n | {\n x: number;\n y: number;\n distance: number;\n }\n | undefined;\n if (input.samplingPitchMicrometers !== undefined) {\n const pitchXmm = input.samplingPitchMicrometers.x / 1000;\n const pitchYmm = input.samplingPitchMicrometers.y / 1000;\n const sampleX = deltaMm.x / pitchXmm;\n const sampleY = deltaMm.y / pitchYmm;\n deltaImagePlaneSamples = {\n x: sampleX,\n y: sampleY,\n distance: Math.hypot(sampleX, sampleY)\n };\n }\n\n return calculatedResult(\n {\n projectionDistanceMm,\n timeSecondsFromExposureStart:\n input.timeSecondsFromExposureStart,\n angularDisplacementRad: {\n ...angularDisplacementRad,\n magnitude: angularMagnitude\n },\n startImagePointMm: { ...input.imagePointMm },\n mappedImagePointMm,\n deltaMm,\n ...(deltaImagePlaneSamples === undefined\n ? {}\n : { deltaImagePlaneSamples })\n },\n focusAware\n ? \"focus-aware-spatial-camera-rotation-mapping\"\n : \"spatial-camera-rotation-mapping\",\n \"2.0.0\",\n [\n \"Ideal rectilinear projection\",\n focusAware\n ? \"Thin-lens image distance for the selected focus plane is used as the projection distance\"\n : \"Nominal focal length is used as the infinity-focus projection distance\",\n \"Camera angular velocity is constant during the evaluated interval\",\n \"Angular velocity components are resolved in the camera axes at exposure start and integrated as one axis-angle vector\",\n \"Positive pitch/yaw/roll follow the physical right-hand rule about camera +X/+Y/+Z (pitch up, yaw left, roll clockwise seen from behind); the camera basis is left-handed\",\n \"The world ray is stationary; subject motion is not included\",\n \"Camera translation and depth-dependent parallax are not modeled\",\n \"The stationary world ray is transformed by the inverse camera rotation before rectilinear projection\"\n ]\n );\n}\n\n/**\n * Maps a captured stationary-world ray at one non-negative capture time back\n * to its image-plane location at exposure start.\n *\n * For this constant-axis, pure-rotation model the inverse is analytic: apply\n * the forward camera rotation to the captured ray, then project\n * rectilinearly. No iterative solver is needed.\n */\nexport function calculateInverseCameraRotationImageMapping(\n input: CalculateInverseCameraRotationImageMappingInput\n): CalculationResult<InverseCameraRotationImageMapping> {\n requireInputKeys(input, \"calculateInverseCameraRotationImageMapping\", [\"focalLengthMm\", \"imagePointMm\", \"timeSecondsFromExposureStart\", \"angularVelocityRadPerSec\", \"focusDistanceM\", \"samplingPitchMicrometers\"]);\n requirePositiveFinite(\"focalLengthMm\", input.focalLengthMm);\n requireRotationMappingSubObjects(input);\n requireFinite(\"imagePointMm.x\", input.imagePointMm.x);\n requireFinite(\"imagePointMm.y\", input.imagePointMm.y);\n requireNonNegativeFinite(\n \"timeSecondsFromExposureStart\",\n input.timeSecondsFromExposureStart\n );\n requireFinite(\n \"angularVelocityRadPerSec.pitch\",\n input.angularVelocityRadPerSec.pitch\n );\n requireFinite(\n \"angularVelocityRadPerSec.yaw\",\n input.angularVelocityRadPerSec.yaw\n );\n requireFinite(\n \"angularVelocityRadPerSec.roll\",\n input.angularVelocityRadPerSec.roll\n );\n\n if (input.samplingPitchMicrometers !== undefined) {\n requirePositiveFinite(\n \"samplingPitchMicrometers.x\",\n input.samplingPitchMicrometers.x\n );\n requirePositiveFinite(\n \"samplingPitchMicrometers.y\",\n input.samplingPitchMicrometers.y\n );\n }\n\n const { projectionDistanceMm, focusAware } =\n resolveProjectionDistanceMm(\n input.focalLengthMm,\n input.focusDistanceM\n );\n\n const angularDisplacementRad = {\n pitch:\n input.angularVelocityRadPerSec.pitch *\n input.timeSecondsFromExposureStart,\n yaw:\n input.angularVelocityRadPerSec.yaw *\n input.timeSecondsFromExposureStart,\n roll:\n input.angularVelocityRadPerSec.roll *\n input.timeSecondsFromExposureStart\n };\n const angularMagnitude = Math.hypot(\n angularDisplacementRad.pitch,\n angularDisplacementRad.yaw,\n angularDisplacementRad.roll\n );\n\n const capturedRay: Vector3 = {\n x: input.imagePointMm.x / projectionDistanceMm,\n y: input.imagePointMm.y / projectionDistanceMm,\n z: 1\n };\n\n const referenceRay = applyPhysicalCameraRotation(\n capturedRay,\n angularDisplacementRad,\n \"camera-to-reference\"\n );\n\n if (!Number.isFinite(referenceRay.z) || referenceRay.z <= 0) {\n throw new InvalidScientificInputError(\n \"Inverse camera rotation maps the requested captured ray to or behind the reference camera plane.\"\n );\n }\n\n const referenceImagePointMm = {\n x: projectionDistanceMm * (referenceRay.x / referenceRay.z),\n y: projectionDistanceMm * (referenceRay.y / referenceRay.z)\n };\n const deltaMm = {\n x: referenceImagePointMm.x - input.imagePointMm.x,\n y: referenceImagePointMm.y - input.imagePointMm.y,\n distance: Math.hypot(\n referenceImagePointMm.x - input.imagePointMm.x,\n referenceImagePointMm.y - input.imagePointMm.y\n )\n };\n\n let deltaImagePlaneSamples:\n | {\n x: number;\n y: number;\n distance: number;\n }\n | undefined;\n if (input.samplingPitchMicrometers !== undefined) {\n const pitchXmm = input.samplingPitchMicrometers.x / 1000;\n const pitchYmm = input.samplingPitchMicrometers.y / 1000;\n const sampleX = deltaMm.x / pitchXmm;\n const sampleY = deltaMm.y / pitchYmm;\n deltaImagePlaneSamples = {\n x: sampleX,\n y: sampleY,\n distance: Math.hypot(sampleX, sampleY)\n };\n }\n\n return calculatedResult(\n {\n projectionDistanceMm,\n timeSecondsFromExposureStart:\n input.timeSecondsFromExposureStart,\n angularDisplacementRad: {\n ...angularDisplacementRad,\n magnitude: angularMagnitude\n },\n capturedImagePointMm: { ...input.imagePointMm },\n referenceImagePointMm,\n deltaMm,\n ...(deltaImagePlaneSamples === undefined\n ? {}\n : { deltaImagePlaneSamples })\n },\n focusAware\n ? \"focus-aware-inverse-spatial-camera-rotation-mapping\"\n : \"inverse-spatial-camera-rotation-mapping\",\n \"2.0.0\",\n [\n \"Ideal rectilinear projection\",\n focusAware\n ? \"Thin-lens image distance for the selected focus plane is used as the projection distance\"\n : \"Nominal focal length is used as the infinity-focus projection distance\",\n \"Camera angular velocity is constant during the evaluated interval\",\n \"Angular velocity components are resolved in the camera axes at exposure start and integrated as one axis-angle vector\",\n \"Positive pitch/yaw/roll follow the physical right-hand rule about camera +X/+Y/+Z (pitch up, yaw left, roll clockwise seen from behind); the camera basis is left-handed\",\n \"The inverse is analytic under the same constant-axis pure-rotation model; no iterative solve is used\",\n \"The captured stationary-world ray is transformed by the forward camera rotation to recover its exposure-start reference ray\",\n \"Camera translation, scene depth parallax and subject motion are not modeled\"\n ]\n );\n}\n"]}
@@ -96,6 +96,18 @@ export interface CaptureRotationInverseMappings {
96
96
  inverseCameraRotation: CalculationProvenance;
97
97
  };
98
98
  }
99
+ export declare function requireNonEmptySamples(value: readonly RasterPoint[]): void;
100
+ export declare function nativePointToImagePlane(point: RasterPoint, activeCaptureRect: {
101
+ x: number;
102
+ y: number;
103
+ width: number;
104
+ height: number;
105
+ }, activePhysicalBoundsFromOpticalAxisMm: {
106
+ left: number;
107
+ right: number;
108
+ top: number;
109
+ bottom: number;
110
+ }): ImagePlanePointMm;
99
111
  /**
100
112
  * Calculates an instantaneous inverse capture-scan mapping for still world
101
113
  * rays under pure camera rotation.
@@ -28,13 +28,13 @@ function requireExposurePhase(value) {
28
28
  throw new InvalidScientificInputError("localExposurePhase must be finite and within [0, 1].");
29
29
  }
30
30
  }
31
- function requireNonEmptySamples(value) {
31
+ export function requireNonEmptySamples(value) {
32
32
  if (!Array.isArray(value) || value.length === 0) {
33
33
  throw new InvalidConfigurationError("samplePointsNative must be a non-empty array.");
34
34
  }
35
35
  value.forEach((point, index) => requireInputObject(point, "samplePointsNative[" + index + "]"));
36
36
  }
37
- function nativePointToImagePlane(point, activeCaptureRect, activePhysicalBoundsFromOpticalAxisMm) {
37
+ export function nativePointToImagePlane(point, activeCaptureRect, activePhysicalBoundsFromOpticalAxisMm) {
38
38
  const u = (point.x - activeCaptureRect.x) / activeCaptureRect.width;
39
39
  const v = (point.y - activeCaptureRect.y) / activeCaptureRect.height;
40
40
  return mapOrientedPhysicalUvToImagePlanePoint({
@@ -1 +1 @@
1
- {"version":3,"file":"capture-rotation-inverse-mapping.js","sourceRoot":"","sources":["../../src/motion/capture-rotation-inverse-mapping.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Instantaneous inverse mapping for a rotating camera whose sensor exposes different points at\n * different times (for example a rolling shutter). Applies to still world rays under pure camera\n * rotation.\n *\n * The destination point on the native sensor sets the local exposure timing. At a chosen phase within\n * that local window, the captured image-plane ray is mapped back analytically to the reference image\n * plane at the first opening-boundary phase (when the shutter starts to open). No iterative solve is\n * needed: once the destination and local capture time are known, pure rotation under this\n * constant-axis model has an analytic inverse.\n *\n * This is not a renderer for a finite exposure, and it does not integrate blur. Sensor data-readout\n * timing is deliberately not used; exposure boundaries are the only timing source.\n * @see docs/MOTION_AND_SIGNAL.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport {\n mapOrientedPhysicalUvToImagePlanePoint,\n resolveCaptureGeometry,\n transformNativeRasterVectorToOriented,\n type CaptureOrientation,\n type RasterPoint,\n type RasterVector\n} from \"../output/capture-geometry.js\";\nimport {\n calculateCaptureExposureWindows,\n type CalculateCaptureExposureWindowsInput,\n type CaptureExposureWindowSample\n} from \"../sensor/exposure-window.js\";\nimport {\n calculateSensorGeometryMetrics,\n type SensorImagingArea\n} from \"../sensor/sensor-geometry.js\";\nimport {\n calculateInverseCameraRotationImageMapping,\n type CameraAngularVelocityRadPerSec,\n type ImagePlanePointMm\n} from \"./camera-rotation.js\";\nimport { requireInputObject, requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport interface CalculateCaptureRotationInverseMappingsInput\n extends CalculateCaptureExposureWindowsInput {\n imagingArea: SensorImagingArea;\n focalLengthMm: number;\n focusDistanceM?: number;\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;\n /**\n * Where in each point's local exposure interval to evaluate the\n * instantaneous mapping, as a fraction.\n *\n * 0 = local exposure start, 0.5 = local midpoint, 1 = local exposure end.\n * A phase is required because a finite exposure has no single sharp\n * geometry.\n */\n localExposurePhase: number;\n /**\n * Physical capture orientation. It is used only to rotate the reported\n * native inverse displacement (in samples) into oriented-capture\n * coordinates. It does not change the native exposure schedule.\n */\n orientation: CaptureOrientation;\n samplePointsNative: readonly RasterPoint[];\n}\n\nexport interface CaptureRotationInverseMappingSample {\n destinationPointNative: RasterPoint;\n destinationImagePointMm: ImagePlanePointMm;\n localExposureWindow: {\n startSecondsFromCaptureReference: number;\n endSecondsFromCaptureReference: number;\n durationSeconds: number;\n };\n localExposurePhase: number;\n captureTimeSecondsFromReference: number;\n /**\n * The same still world ray on the reference image plane at exposure start.\n * It may lie outside the active capture area; it is deliberately not\n * clamped.\n */\n referenceImagePointMm: ImagePlanePointMm;\n inverseDisplacementImagePlaneMm: {\n x: number;\n y: number;\n distance: number;\n };\n /**\n * Reference minus destination displacement, in continuous native effective\n * raster samples (+X right, +Y down).\n */\n inverseDisplacementNativeSamples: RasterVector & {\n distance: number;\n };\n /**\n * The same displacement after physical capture orientation.\n */\n inverseDisplacementOrientedSamples: RasterVector & {\n distance: number;\n };\n}\n\nexport interface CaptureRotationInverseMappings {\n timeReference: \"first-opening-boundary-phase\";\n referenceTimeSecondsFromCaptureReference: 0;\n mappingMeaning:\n \"instantaneous-destination-to-exposure-start-reference-ray\";\n orientation: CaptureOrientation;\n localExposurePhase: number;\n imagingArea: SensorImagingArea;\n activeCaptureRect: {\n x: number;\n y: number;\n width: number;\n height: number;\n };\n samples: readonly CaptureRotationInverseMappingSample[];\n componentProvenance: {\n exposureWindows: CalculationProvenance;\n sensorGeometry: CalculationProvenance;\n inverseCameraRotation: CalculationProvenance;\n };\n}\n\nfunction requireExposurePhase(value: number): void {\n if (!Number.isFinite(value) || value < 0 || value > 1) {\n throw new InvalidScientificInputError(\n \"localExposurePhase must be finite and within [0, 1].\"\n );\n }\n}\n\nfunction requireNonEmptySamples(\n value: readonly RasterPoint[]\n): void {\n if (!Array.isArray(value) || value.length === 0) {\n throw new InvalidConfigurationError(\n \"samplePointsNative must be a non-empty array.\"\n );\n }\n value.forEach((point, index) => requireInputObject(point, \"samplePointsNative[\" + index + \"]\"));\n}\n\nfunction nativePointToImagePlane(\n point: RasterPoint,\n activeCaptureRect: {\n x: number;\n y: number;\n width: number;\n height: number;\n },\n activePhysicalBoundsFromOpticalAxisMm: {\n left: number;\n right: number;\n top: number;\n bottom: number;\n }\n): ImagePlanePointMm {\n const u = (point.x - activeCaptureRect.x) / activeCaptureRect.width;\n const v = (point.y - activeCaptureRect.y) / activeCaptureRect.height;\n\n return mapOrientedPhysicalUvToImagePlanePoint({\n uv: { u, v },\n orientedPhysicalBoundsFromOpticalAxisMm:\n activePhysicalBoundsFromOpticalAxisMm,\n orientation: \"landscape\"\n });\n}\n\nfunction captureTimeAtPhase(\n exposure: CaptureExposureWindowSample,\n phase: number\n): number {\n return (\n exposure.startOffsetSecondsFromOpeningReference +\n phase * exposure.localExposureDurationSeconds\n );\n}\n\n/**\n * Calculates an instantaneous inverse capture-scan mapping for still world\n * rays under pure camera rotation.\n *\n * The destination point on the native sensor sets the local exposure timing.\n * At a chosen phase within that local window, the captured image-plane ray is\n * mapped back analytically to the reference image plane at the first\n * opening-boundary phase.\n *\n * No iterative solve is needed. Once the destination and local capture time\n * are known, pure rotation under this constant-axis model has an analytic\n * inverse.\n *\n * This is not a renderer for a finite exposure, and it does not integrate\n * blur. Sensor data-readout timing is deliberately not used; exposure\n * boundaries are the only timing source.\n */\nexport function calculateCaptureRotationInverseMappings(\n input: CalculateCaptureRotationInverseMappingsInput\n): CalculationResult<CaptureRotationInverseMappings> {\n requireInputKeys(input, \"calculateCaptureRotationInverseMappings\", [\"imagingArea\", \"focalLengthMm\", \"focusDistanceM\", \"angularVelocityRadPerSec\", \"localExposurePhase\", \"orientation\", \"samplePointsNative\", \"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\"]);\n requireInputObject(input.imagingArea, \"imagingArea\");\n requirePositiveFinite(\"imagingArea.widthMm\", input.imagingArea.widthMm);\n requirePositiveFinite(\"imagingArea.heightMm\", input.imagingArea.heightMm);\n requirePositiveFinite(\"focalLengthMm\", input.focalLengthMm);\n requireExposurePhase(input.localExposurePhase);\n requireNonEmptySamples(input.samplePointsNative);\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 samplePointsNative: input.samplePointsNative\n });\n\n const geometry = resolveCaptureGeometry({\n imagingArea: input.imagingArea,\n nativeRaster: input.nativeRaster,\n orientation: input.orientation,\n activeCaptureRect: exposureWindows.value.activeCaptureRect\n }).value;\n\n const sensorGeometry = calculateSensorGeometryMetrics({\n imagingArea: input.imagingArea,\n nativeRaster: input.nativeRaster\n });\n\n const pitchXmm =\n sensorGeometry.value.sampling.pitchXMicrometers / 1000;\n const pitchYmm =\n sensorGeometry.value.sampling.pitchYMicrometers / 1000;\n\n let inverseRotationProvenance: CalculationProvenance | undefined;\n const samples = input.samplePointsNative.map((point, index) => {\n const exposure = exposureWindows.value.samples[index];\n if (exposure === undefined) {\n throw new InvalidScientificInputError(\n \"Exposure-window sample alignment failed.\"\n );\n }\n\n const destinationImagePointMm = nativePointToImagePlane(\n point,\n geometry.activeCapture.nativeRect,\n geometry.activeCapture.physicalBoundsFromOpticalAxisMm\n );\n const captureTimeSecondsFromReference = captureTimeAtPhase(\n exposure,\n input.localExposurePhase\n );\n\n const inverse = calculateInverseCameraRotationImageMapping({\n focalLengthMm: input.focalLengthMm,\n imagePointMm: destinationImagePointMm,\n timeSecondsFromExposureStart:\n captureTimeSecondsFromReference,\n angularVelocityRadPerSec: input.angularVelocityRadPerSec,\n ...(input.focusDistanceM === undefined\n ? {}\n : { focusDistanceM: input.focusDistanceM }),\n samplingPitchMicrometers: {\n x: sensorGeometry.value.sampling.pitchXMicrometers,\n y: sensorGeometry.value.sampling.pitchYMicrometers\n }\n });\n inverseRotationProvenance ??= inverse.provenance;\n\n const nativeX = inverse.value.deltaMm.x / pitchXmm;\n const nativeY = -inverse.value.deltaMm.y / pitchYmm;\n const inverseDisplacementNativeSamples = {\n x: nativeX,\n y: nativeY,\n distance: Math.hypot(nativeX, nativeY)\n };\n const orientedVector = transformNativeRasterVectorToOriented({\n vector: {\n x: inverseDisplacementNativeSamples.x,\n y: inverseDisplacementNativeSamples.y\n },\n orientation: input.orientation\n });\n const inverseDisplacementOrientedSamples = {\n ...orientedVector,\n distance: Math.hypot(orientedVector.x, orientedVector.y)\n };\n\n return {\n destinationPointNative: { ...point },\n destinationImagePointMm,\n localExposureWindow: {\n startSecondsFromCaptureReference:\n exposure.startOffsetSecondsFromOpeningReference,\n endSecondsFromCaptureReference:\n exposure.endOffsetSecondsFromOpeningReference,\n durationSeconds: exposure.localExposureDurationSeconds\n },\n localExposurePhase: input.localExposurePhase,\n captureTimeSecondsFromReference,\n referenceImagePointMm: {\n ...inverse.value.referenceImagePointMm\n },\n inverseDisplacementImagePlaneMm: {\n ...inverse.value.deltaMm\n },\n inverseDisplacementNativeSamples,\n inverseDisplacementOrientedSamples\n };\n });\n\n if (inverseRotationProvenance === undefined) {\n throw new InvalidScientificInputError(\n \"At least one inverse rotation mapping sample is required.\"\n );\n }\n\n return approximationResult(\n {\n timeReference: \"first-opening-boundary-phase\",\n referenceTimeSecondsFromCaptureReference: 0,\n mappingMeaning:\n \"instantaneous-destination-to-exposure-start-reference-ray\",\n orientation: input.orientation,\n localExposurePhase: input.localExposurePhase,\n imagingArea: { ...input.imagingArea },\n activeCaptureRect: {\n ...exposureWindows.value.activeCaptureRect\n },\n samples,\n componentProvenance: {\n exposureWindows: exposureWindows.provenance,\n sensorGeometry: sensorGeometry.provenance,\n inverseCameraRotation: inverseRotationProvenance\n }\n },\n \"capture-rotation-instantaneous-inverse-mapping\",\n \"1.0.0\",\n [\n \"Destination native capture location determines the local exposure window.\",\n \"The caller explicitly selects one phase within each local exposure interval; no midpoint or other phase is inferred.\",\n \"The first opening-boundary phase is the reference image time and is bound to t=0 of the camera-rotation model for this composition.\",\n \"Pure rotation is inverted analytically after local capture time is known; no iterative or fixed-point solver is used.\",\n \"Sensor data-readout timing is not an input and is not assumed to equal any exposure boundary.\",\n \"Reference image-plane coordinates are not clamped to the active capture and may fall outside it under motion.\",\n \"Native geometric sample displacement uses the full physical imaging-area/native-raster pitch and is not output-resolution dependent.\",\n \"Physical capture orientation rotates reported native displacement only; it does not alter native exposure timing.\",\n \"The mapping lives in the ideal pre-lens image plane and does not compose radial distortion, chromatic aberration, PSF, or output resampling.\",\n \"This is an instantaneous mapping, not finite-exposure integration or a blur kernel.\",\n \"Camera translation, depth parallax, subject motion, deformation, occlusion changes, flash/flicker and shutter shock are excluded.\",\n \"The model does not claim the global rolling/capture-scan picture-taking map is one-to-one for arbitrary motion.\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"capture-rotation-inverse-mapping.js","sourceRoot":"","sources":["../../src/motion/capture-rotation-inverse-mapping.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Instantaneous inverse mapping for a rotating camera whose sensor exposes different points at\n * different times (for example a rolling shutter). Applies to still world rays under pure camera\n * rotation.\n *\n * The destination point on the native sensor sets the local exposure timing. At a chosen phase within\n * that local window, the captured image-plane ray is mapped back analytically to the reference image\n * plane at the first opening-boundary phase (when the shutter starts to open). No iterative solve is\n * needed: once the destination and local capture time are known, pure rotation under this\n * constant-axis model has an analytic inverse.\n *\n * This is not a renderer for a finite exposure, and it does not integrate blur. Sensor data-readout\n * timing is deliberately not used; exposure boundaries are the only timing source.\n * @see docs/MOTION_AND_SIGNAL.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport {\n mapOrientedPhysicalUvToImagePlanePoint,\n resolveCaptureGeometry,\n transformNativeRasterVectorToOriented,\n type CaptureOrientation,\n type RasterPoint,\n type RasterVector\n} from \"../output/capture-geometry.js\";\nimport {\n calculateCaptureExposureWindows,\n type CalculateCaptureExposureWindowsInput,\n type CaptureExposureWindowSample\n} from \"../sensor/exposure-window.js\";\nimport {\n calculateSensorGeometryMetrics,\n type SensorImagingArea\n} from \"../sensor/sensor-geometry.js\";\nimport {\n calculateInverseCameraRotationImageMapping,\n type CameraAngularVelocityRadPerSec,\n type ImagePlanePointMm\n} from \"./camera-rotation.js\";\nimport { requireInputObject, requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport interface CalculateCaptureRotationInverseMappingsInput\n extends CalculateCaptureExposureWindowsInput {\n imagingArea: SensorImagingArea;\n focalLengthMm: number;\n focusDistanceM?: number;\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;\n /**\n * Where in each point's local exposure interval to evaluate the\n * instantaneous mapping, as a fraction.\n *\n * 0 = local exposure start, 0.5 = local midpoint, 1 = local exposure end.\n * A phase is required because a finite exposure has no single sharp\n * geometry.\n */\n localExposurePhase: number;\n /**\n * Physical capture orientation. It is used only to rotate the reported\n * native inverse displacement (in samples) into oriented-capture\n * coordinates. It does not change the native exposure schedule.\n */\n orientation: CaptureOrientation;\n samplePointsNative: readonly RasterPoint[];\n}\n\nexport interface CaptureRotationInverseMappingSample {\n destinationPointNative: RasterPoint;\n destinationImagePointMm: ImagePlanePointMm;\n localExposureWindow: {\n startSecondsFromCaptureReference: number;\n endSecondsFromCaptureReference: number;\n durationSeconds: number;\n };\n localExposurePhase: number;\n captureTimeSecondsFromReference: number;\n /**\n * The same still world ray on the reference image plane at exposure start.\n * It may lie outside the active capture area; it is deliberately not\n * clamped.\n */\n referenceImagePointMm: ImagePlanePointMm;\n inverseDisplacementImagePlaneMm: {\n x: number;\n y: number;\n distance: number;\n };\n /**\n * Reference minus destination displacement, in continuous native effective\n * raster samples (+X right, +Y down).\n */\n inverseDisplacementNativeSamples: RasterVector & {\n distance: number;\n };\n /**\n * The same displacement after physical capture orientation.\n */\n inverseDisplacementOrientedSamples: RasterVector & {\n distance: number;\n };\n}\n\nexport interface CaptureRotationInverseMappings {\n timeReference: \"first-opening-boundary-phase\";\n referenceTimeSecondsFromCaptureReference: 0;\n mappingMeaning:\n \"instantaneous-destination-to-exposure-start-reference-ray\";\n orientation: CaptureOrientation;\n localExposurePhase: number;\n imagingArea: SensorImagingArea;\n activeCaptureRect: {\n x: number;\n y: number;\n width: number;\n height: number;\n };\n samples: readonly CaptureRotationInverseMappingSample[];\n componentProvenance: {\n exposureWindows: CalculationProvenance;\n sensorGeometry: CalculationProvenance;\n inverseCameraRotation: CalculationProvenance;\n };\n}\n\nfunction requireExposurePhase(value: number): void {\n if (!Number.isFinite(value) || value < 0 || value > 1) {\n throw new InvalidScientificInputError(\n \"localExposurePhase must be finite and within [0, 1].\"\n );\n }\n}\n\nexport function requireNonEmptySamples(\n value: readonly RasterPoint[]\n): void {\n if (!Array.isArray(value) || value.length === 0) {\n throw new InvalidConfigurationError(\n \"samplePointsNative must be a non-empty array.\"\n );\n }\n value.forEach((point, index) => requireInputObject(point, \"samplePointsNative[\" + index + \"]\"));\n}\n\nexport function nativePointToImagePlane(\n point: RasterPoint,\n activeCaptureRect: {\n x: number;\n y: number;\n width: number;\n height: number;\n },\n activePhysicalBoundsFromOpticalAxisMm: {\n left: number;\n right: number;\n top: number;\n bottom: number;\n }\n): ImagePlanePointMm {\n const u = (point.x - activeCaptureRect.x) / activeCaptureRect.width;\n const v = (point.y - activeCaptureRect.y) / activeCaptureRect.height;\n\n return mapOrientedPhysicalUvToImagePlanePoint({\n uv: { u, v },\n orientedPhysicalBoundsFromOpticalAxisMm:\n activePhysicalBoundsFromOpticalAxisMm,\n orientation: \"landscape\"\n });\n}\n\nfunction captureTimeAtPhase(\n exposure: CaptureExposureWindowSample,\n phase: number\n): number {\n return (\n exposure.startOffsetSecondsFromOpeningReference +\n phase * exposure.localExposureDurationSeconds\n );\n}\n\n/**\n * Calculates an instantaneous inverse capture-scan mapping for still world\n * rays under pure camera rotation.\n *\n * The destination point on the native sensor sets the local exposure timing.\n * At a chosen phase within that local window, the captured image-plane ray is\n * mapped back analytically to the reference image plane at the first\n * opening-boundary phase.\n *\n * No iterative solve is needed. Once the destination and local capture time\n * are known, pure rotation under this constant-axis model has an analytic\n * inverse.\n *\n * This is not a renderer for a finite exposure, and it does not integrate\n * blur. Sensor data-readout timing is deliberately not used; exposure\n * boundaries are the only timing source.\n */\nexport function calculateCaptureRotationInverseMappings(\n input: CalculateCaptureRotationInverseMappingsInput\n): CalculationResult<CaptureRotationInverseMappings> {\n requireInputKeys(input, \"calculateCaptureRotationInverseMappings\", [\"imagingArea\", \"focalLengthMm\", \"focusDistanceM\", \"angularVelocityRadPerSec\", \"localExposurePhase\", \"orientation\", \"samplePointsNative\", \"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\"]);\n requireInputObject(input.imagingArea, \"imagingArea\");\n requirePositiveFinite(\"imagingArea.widthMm\", input.imagingArea.widthMm);\n requirePositiveFinite(\"imagingArea.heightMm\", input.imagingArea.heightMm);\n requirePositiveFinite(\"focalLengthMm\", input.focalLengthMm);\n requireExposurePhase(input.localExposurePhase);\n requireNonEmptySamples(input.samplePointsNative);\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 samplePointsNative: input.samplePointsNative\n });\n\n const geometry = resolveCaptureGeometry({\n imagingArea: input.imagingArea,\n nativeRaster: input.nativeRaster,\n orientation: input.orientation,\n activeCaptureRect: exposureWindows.value.activeCaptureRect\n }).value;\n\n const sensorGeometry = calculateSensorGeometryMetrics({\n imagingArea: input.imagingArea,\n nativeRaster: input.nativeRaster\n });\n\n const pitchXmm =\n sensorGeometry.value.sampling.pitchXMicrometers / 1000;\n const pitchYmm =\n sensorGeometry.value.sampling.pitchYMicrometers / 1000;\n\n let inverseRotationProvenance: CalculationProvenance | undefined;\n const samples = input.samplePointsNative.map((point, index) => {\n const exposure = exposureWindows.value.samples[index];\n if (exposure === undefined) {\n throw new InvalidScientificInputError(\n \"Exposure-window sample alignment failed.\"\n );\n }\n\n const destinationImagePointMm = nativePointToImagePlane(\n point,\n geometry.activeCapture.nativeRect,\n geometry.activeCapture.physicalBoundsFromOpticalAxisMm\n );\n const captureTimeSecondsFromReference = captureTimeAtPhase(\n exposure,\n input.localExposurePhase\n );\n\n const inverse = calculateInverseCameraRotationImageMapping({\n focalLengthMm: input.focalLengthMm,\n imagePointMm: destinationImagePointMm,\n timeSecondsFromExposureStart:\n captureTimeSecondsFromReference,\n angularVelocityRadPerSec: input.angularVelocityRadPerSec,\n ...(input.focusDistanceM === undefined\n ? {}\n : { focusDistanceM: input.focusDistanceM }),\n samplingPitchMicrometers: {\n x: sensorGeometry.value.sampling.pitchXMicrometers,\n y: sensorGeometry.value.sampling.pitchYMicrometers\n }\n });\n inverseRotationProvenance ??= inverse.provenance;\n\n const nativeX = inverse.value.deltaMm.x / pitchXmm;\n const nativeY = -inverse.value.deltaMm.y / pitchYmm;\n const inverseDisplacementNativeSamples = {\n x: nativeX,\n y: nativeY,\n distance: Math.hypot(nativeX, nativeY)\n };\n const orientedVector = transformNativeRasterVectorToOriented({\n vector: {\n x: inverseDisplacementNativeSamples.x,\n y: inverseDisplacementNativeSamples.y\n },\n orientation: input.orientation\n });\n const inverseDisplacementOrientedSamples = {\n ...orientedVector,\n distance: Math.hypot(orientedVector.x, orientedVector.y)\n };\n\n return {\n destinationPointNative: { ...point },\n destinationImagePointMm,\n localExposureWindow: {\n startSecondsFromCaptureReference:\n exposure.startOffsetSecondsFromOpeningReference,\n endSecondsFromCaptureReference:\n exposure.endOffsetSecondsFromOpeningReference,\n durationSeconds: exposure.localExposureDurationSeconds\n },\n localExposurePhase: input.localExposurePhase,\n captureTimeSecondsFromReference,\n referenceImagePointMm: {\n ...inverse.value.referenceImagePointMm\n },\n inverseDisplacementImagePlaneMm: {\n ...inverse.value.deltaMm\n },\n inverseDisplacementNativeSamples,\n inverseDisplacementOrientedSamples\n };\n });\n\n if (inverseRotationProvenance === undefined) {\n throw new InvalidScientificInputError(\n \"At least one inverse rotation mapping sample is required.\"\n );\n }\n\n return approximationResult(\n {\n timeReference: \"first-opening-boundary-phase\",\n referenceTimeSecondsFromCaptureReference: 0,\n mappingMeaning:\n \"instantaneous-destination-to-exposure-start-reference-ray\",\n orientation: input.orientation,\n localExposurePhase: input.localExposurePhase,\n imagingArea: { ...input.imagingArea },\n activeCaptureRect: {\n ...exposureWindows.value.activeCaptureRect\n },\n samples,\n componentProvenance: {\n exposureWindows: exposureWindows.provenance,\n sensorGeometry: sensorGeometry.provenance,\n inverseCameraRotation: inverseRotationProvenance\n }\n },\n \"capture-rotation-instantaneous-inverse-mapping\",\n \"1.0.0\",\n [\n \"Destination native capture location determines the local exposure window.\",\n \"The caller explicitly selects one phase within each local exposure interval; no midpoint or other phase is inferred.\",\n \"The first opening-boundary phase is the reference image time and is bound to t=0 of the camera-rotation model for this composition.\",\n \"Pure rotation is inverted analytically after local capture time is known; no iterative or fixed-point solver is used.\",\n \"Sensor data-readout timing is not an input and is not assumed to equal any exposure boundary.\",\n \"Reference image-plane coordinates are not clamped to the active capture and may fall outside it under motion.\",\n \"Native geometric sample displacement uses the full physical imaging-area/native-raster pitch and is not output-resolution dependent.\",\n \"Physical capture orientation rotates reported native displacement only; it does not alter native exposure timing.\",\n \"The mapping lives in the ideal pre-lens image plane and does not compose radial distortion, chromatic aberration, PSF, or output resampling.\",\n \"This is an instantaneous mapping, not finite-exposure integration or a blur kernel.\",\n \"Camera translation, depth parallax, subject motion, deformation, occlusion changes, flash/flicker and shutter shock are excluded.\",\n \"The model does not claim the global rolling/capture-scan picture-taking map is one-to-one for arbitrary motion.\"\n ]\n );\n}\n"]}
@@ -86,6 +86,7 @@ export interface CaptureRotationTemporalQuadrature {
86
86
  instantaneousInverseMapping: CalculationProvenance;
87
87
  };
88
88
  }
89
+ export declare function midpointPhase(index: number, sampleCount: number): number;
89
90
  /**
90
91
  * Builds deterministic time samples (temporal quadrature nodes) for a
91
92
  * rotating camera, across each destination point's local exposure interval.
@@ -24,7 +24,7 @@ import { InvalidScientificInputError, requirePositiveInteger } from "../core/val
24
24
  import { calculateCaptureRotationInverseMappings } from "./capture-rotation-inverse-mapping.js";
25
25
  import { requireInputKeys, requireInputObject } from "../core/record-validation.js";
26
26
  import { InvalidConfigurationError } from "../core/configuration-error.js";
27
- function midpointPhase(index, sampleCount) {
27
+ export function midpointPhase(index, sampleCount) {
28
28
  return (index + 0.5) / sampleCount;
29
29
  }
30
30
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"capture-rotation-temporal-quadrature.js","sourceRoot":"","sources":["../../src/motion/capture-rotation-temporal-quadrature.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Time samples (temporal quadrature nodes) across each destination point's local exposure, for a\n * rotating camera. The samples are deterministic.\n *\n * The first model uses an equal-width midpoint rule. Every node calls\n * calculateCaptureRotationInverseMappings(), so the instantaneous geometry has a single\n * implementation.\n *\n * No radiance is evaluated here. Two weights are returned:\n * - normalizedTimeWeight: for a time average, assuming a uniform response over time.\n * - timeMeasureSeconds: the local dt, for a time integral.\n * Neither weight includes shutter transmission, scene flicker, sensor response, radiometric\n * calibration or any other throughput term.\n *\n * No numerical integration error estimate is reported, on purpose. Geometry alone cannot tell the\n * error in a scene-radiance integral. A renderer that needs evidence of convergence can compare\n * results for increasing sample counts in its own radiance domain.\n * @see docs/MOTION_AND_SIGNAL.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveInteger\n} from \"../core/validation.js\";\nimport type { RasterPoint, RasterVector } from \"../output/capture-geometry.js\";\nimport type { ImagePlanePointMm } from \"./camera-rotation.js\";\nimport {\n calculateCaptureRotationInverseMappings,\n type CalculateCaptureRotationInverseMappingsInput\n} from \"./capture-rotation-inverse-mapping.js\";\nimport { requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport interface CalculateCaptureRotationTemporalQuadratureInput\n extends Omit<\n CalculateCaptureRotationInverseMappingsInput,\n \"localExposurePhase\"\n > {\n /**\n * Number of time samples (quadrature nodes) per local exposure interval.\n * Nodes follow the uniform midpoint rule.\n */\n temporalSampleCount: number;\n}\n\nexport interface CaptureRotationTemporalQuadratureNode {\n temporalSampleIndex: number;\n /**\n * Normalized phase in the local exposure interval, strictly inside (0, 1).\n */\n localExposurePhase: number;\n captureTimeSecondsFromReference: number;\n /**\n * Dimensionless weight for approximating the time average of a quantity,\n * assuming a uniform response over time.\n */\n normalizedTimeWeight: number;\n /**\n * Quadrature measure in seconds, for approximating a time integral.\n *\n * This is a measure of time only. It is not shutter transmission,\n * radiometric throughput, photon count, sensor response or scene radiance.\n */\n timeMeasureSeconds: number;\n referenceImagePointMm: ImagePlanePointMm;\n inverseDisplacementImagePlaneMm: {\n x: number;\n y: number;\n distance: number;\n };\n inverseDisplacementNativeSamples: RasterVector & {\n distance: number;\n };\n inverseDisplacementOrientedSamples: RasterVector & {\n distance: number;\n };\n}\n\nexport interface CaptureRotationTemporalQuadraturePoint {\n destinationPointNative: RasterPoint;\n destinationImagePointMm: ImagePlanePointMm;\n localExposureWindow: {\n startSecondsFromCaptureReference: number;\n endSecondsFromCaptureReference: number;\n durationSeconds: number;\n };\n nodes: readonly CaptureRotationTemporalQuadratureNode[];\n}\n\nexport interface CaptureRotationTemporalQuadrature {\n timeReference: \"first-opening-boundary-phase\";\n quadratureScheme: \"uniform-midpoint\";\n temporalResponseModel: \"uniform-over-local-exposure\";\n temporalSampleCount: number;\n /**\n * The quadrature returns geometry and temporal measure only. It does not\n * integrate radiance or produce a blur kernel.\n */\n outputMeaning: \"temporal-geometry-quadrature-nodes\";\n points: readonly CaptureRotationTemporalQuadraturePoint[];\n componentProvenance: {\n instantaneousInverseMapping: CalculationProvenance;\n };\n}\n\nfunction midpointPhase(index: number, sampleCount: number): number {\n return (index + 0.5) / sampleCount;\n}\n\n/**\n * Builds deterministic time samples (temporal quadrature nodes) for a\n * rotating camera, across each destination point's local exposure interval.\n *\n * The first model uses an equal-width midpoint rule. Every node calls\n * calculateCaptureRotationInverseMappings(), so the instantaneous geometry\n * has a single implementation.\n *\n * No radiance is evaluated here. normalizedTimeWeight is for a time average,\n * assuming a uniform response over time. timeMeasureSeconds is the local dt\n * for a time integral. Neither weight includes shutter transmission, scene\n * flicker, sensor response, radiometric calibration or any other throughput\n * term.\n *\n * No numerical integration error estimate is reported, on purpose. Geometry\n * alone cannot tell the error in a scene-radiance integral. A renderer that\n * needs evidence of convergence can compare results for increasing sample\n * counts in its own radiance domain.\n */\nexport function calculateCaptureRotationTemporalQuadrature(\n input: CalculateCaptureRotationTemporalQuadratureInput\n): CalculationResult<CaptureRotationTemporalQuadrature> {\n requireInputKeys(input, \"calculateCaptureRotationTemporalQuadrature\", [\"temporalSampleCount\", \"imagingArea\", \"focalLengthMm\", \"focusDistanceM\", \"angularVelocityRadPerSec\", \"orientation\", \"samplePointsNative\", \"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\"]);\n requirePositiveInteger(\n \"temporalSampleCount\",\n input.temporalSampleCount\n );\n\n if (\n !Array.isArray(input.samplePointsNative) ||\n input.samplePointsNative.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"samplePointsNative must be a non-empty array.\"\n );\n }\n input.samplePointsNative.forEach((point, index) => requireInputObject(point, \"samplePointsNative[\" + index + \"]\"));\n\n const totalNodeCount =\n input.temporalSampleCount * input.samplePointsNative.length;\n if (!Number.isSafeInteger(totalNodeCount)) {\n throw new InvalidScientificInputError(\n \"temporalSampleCount × samplePointsNative.length must be a safe integer.\"\n );\n }\n\n const {\n temporalSampleCount,\n ...instantaneousInput\n } = input;\n\n const phaseResults = Array.from(\n { length: temporalSampleCount },\n (_, temporalSampleIndex) => {\n const localExposurePhase = midpointPhase(\n temporalSampleIndex,\n temporalSampleCount\n );\n\n return calculateCaptureRotationInverseMappings({\n ...instantaneousInput,\n localExposurePhase\n });\n }\n );\n\n const firstResult = phaseResults[0];\n if (firstResult === undefined) {\n throw new InvalidScientificInputError(\n \"At least one temporal quadrature node is required.\"\n );\n }\n\n const normalizedTimeWeight = 1 / temporalSampleCount;\n\n const points = firstResult.value.samples.map(\n (firstSample, pointIndex) => {\n const durationSeconds =\n firstSample.localExposureWindow.durationSeconds;\n const timeMeasureSeconds =\n durationSeconds / temporalSampleCount;\n\n const nodes = phaseResults.map(\n (phaseResult, temporalSampleIndex) => {\n const sample = phaseResult.value.samples[pointIndex];\n if (sample === undefined) {\n throw new InvalidScientificInputError(\n \"Instantaneous inverse-mapping sample alignment failed.\"\n );\n }\n\n const localExposurePhase = midpointPhase(\n temporalSampleIndex,\n temporalSampleCount\n );\n\n return {\n temporalSampleIndex,\n localExposurePhase,\n captureTimeSecondsFromReference:\n sample.captureTimeSecondsFromReference,\n normalizedTimeWeight,\n timeMeasureSeconds,\n referenceImagePointMm: {\n ...sample.referenceImagePointMm\n },\n inverseDisplacementImagePlaneMm: {\n ...sample.inverseDisplacementImagePlaneMm\n },\n inverseDisplacementNativeSamples: {\n ...sample.inverseDisplacementNativeSamples\n },\n inverseDisplacementOrientedSamples: {\n ...sample.inverseDisplacementOrientedSamples\n }\n };\n }\n );\n\n return {\n destinationPointNative: {\n ...firstSample.destinationPointNative\n },\n destinationImagePointMm: {\n ...firstSample.destinationImagePointMm\n },\n localExposureWindow: {\n ...firstSample.localExposureWindow\n },\n nodes\n };\n }\n );\n\n return approximationResult(\n {\n timeReference: \"first-opening-boundary-phase\",\n quadratureScheme: \"uniform-midpoint\",\n temporalResponseModel: \"uniform-over-local-exposure\",\n temporalSampleCount,\n outputMeaning: \"temporal-geometry-quadrature-nodes\",\n points,\n componentProvenance: {\n instantaneousInverseMapping: firstResult.provenance\n }\n },\n \"capture-rotation-temporal-quadrature\",\n \"1.0.0\",\n [\n \"Each destination native point retains its own validated local exposure interval.\",\n \"Temporal nodes use a deterministic equal-width midpoint rule with phases (i + 0.5) / N and therefore do not sample the exact opening/closing boundaries.\",\n \"Normalized temporal weights sum approximately to one and represent a uniform time-average measure only.\",\n \"Seconds-valued temporal measures sum approximately to the local exposure duration and represent dt only.\",\n \"Uniform temporal response is an explicit approximation; shutter transmission ramps, scene flicker, sensor response and radiometric throughput are not included.\",\n \"Every node delegates instantaneous geometry to calculateCaptureRotationInverseMappings(); no second camera-motion equation is introduced.\",\n \"Sensor data-readout timing and readout/exposure linkage metadata do not alter the exposure integral unless a future explicit image-formation model consumes them.\",\n \"Reference rays may leave the active source frame under motion and are not clamped.\",\n \"No radiance, photon count, blur kernel, PSF, source resampling or output pixel value is calculated.\",\n \"No geometry-only convergence/error estimate is reported because temporal integration error depends on the downstream radiance/visibility function as well as motion.\",\n \"Camera translation, scene-depth parallax, subject motion/deformation, occlusion changes, flash/flicker, shutter shock and non-uniform temporal response remain separate future work.\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"capture-rotation-temporal-quadrature.js","sourceRoot":"","sources":["../../src/motion/capture-rotation-temporal-quadrature.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Time samples (temporal quadrature nodes) across each destination point's local exposure, for a\n * rotating camera. The samples are deterministic.\n *\n * The first model uses an equal-width midpoint rule. Every node calls\n * calculateCaptureRotationInverseMappings(), so the instantaneous geometry has a single\n * implementation.\n *\n * No radiance is evaluated here. Two weights are returned:\n * - normalizedTimeWeight: for a time average, assuming a uniform response over time.\n * - timeMeasureSeconds: the local dt, for a time integral.\n * Neither weight includes shutter transmission, scene flicker, sensor response, radiometric\n * calibration or any other throughput term.\n *\n * No numerical integration error estimate is reported, on purpose. Geometry alone cannot tell the\n * error in a scene-radiance integral. A renderer that needs evidence of convergence can compare\n * results for increasing sample counts in its own radiance domain.\n * @see docs/MOTION_AND_SIGNAL.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveInteger\n} from \"../core/validation.js\";\nimport type { RasterPoint, RasterVector } from \"../output/capture-geometry.js\";\nimport type { ImagePlanePointMm } from \"./camera-rotation.js\";\nimport {\n calculateCaptureRotationInverseMappings,\n type CalculateCaptureRotationInverseMappingsInput\n} from \"./capture-rotation-inverse-mapping.js\";\nimport { requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport interface CalculateCaptureRotationTemporalQuadratureInput\n extends Omit<\n CalculateCaptureRotationInverseMappingsInput,\n \"localExposurePhase\"\n > {\n /**\n * Number of time samples (quadrature nodes) per local exposure interval.\n * Nodes follow the uniform midpoint rule.\n */\n temporalSampleCount: number;\n}\n\nexport interface CaptureRotationTemporalQuadratureNode {\n temporalSampleIndex: number;\n /**\n * Normalized phase in the local exposure interval, strictly inside (0, 1).\n */\n localExposurePhase: number;\n captureTimeSecondsFromReference: number;\n /**\n * Dimensionless weight for approximating the time average of a quantity,\n * assuming a uniform response over time.\n */\n normalizedTimeWeight: number;\n /**\n * Quadrature measure in seconds, for approximating a time integral.\n *\n * This is a measure of time only. It is not shutter transmission,\n * radiometric throughput, photon count, sensor response or scene radiance.\n */\n timeMeasureSeconds: number;\n referenceImagePointMm: ImagePlanePointMm;\n inverseDisplacementImagePlaneMm: {\n x: number;\n y: number;\n distance: number;\n };\n inverseDisplacementNativeSamples: RasterVector & {\n distance: number;\n };\n inverseDisplacementOrientedSamples: RasterVector & {\n distance: number;\n };\n}\n\nexport interface CaptureRotationTemporalQuadraturePoint {\n destinationPointNative: RasterPoint;\n destinationImagePointMm: ImagePlanePointMm;\n localExposureWindow: {\n startSecondsFromCaptureReference: number;\n endSecondsFromCaptureReference: number;\n durationSeconds: number;\n };\n nodes: readonly CaptureRotationTemporalQuadratureNode[];\n}\n\nexport interface CaptureRotationTemporalQuadrature {\n timeReference: \"first-opening-boundary-phase\";\n quadratureScheme: \"uniform-midpoint\";\n temporalResponseModel: \"uniform-over-local-exposure\";\n temporalSampleCount: number;\n /**\n * The quadrature returns geometry and temporal measure only. It does not\n * integrate radiance or produce a blur kernel.\n */\n outputMeaning: \"temporal-geometry-quadrature-nodes\";\n points: readonly CaptureRotationTemporalQuadraturePoint[];\n componentProvenance: {\n instantaneousInverseMapping: CalculationProvenance;\n };\n}\n\nexport function midpointPhase(index: number, sampleCount: number): number {\n return (index + 0.5) / sampleCount;\n}\n\n/**\n * Builds deterministic time samples (temporal quadrature nodes) for a\n * rotating camera, across each destination point's local exposure interval.\n *\n * The first model uses an equal-width midpoint rule. Every node calls\n * calculateCaptureRotationInverseMappings(), so the instantaneous geometry\n * has a single implementation.\n *\n * No radiance is evaluated here. normalizedTimeWeight is for a time average,\n * assuming a uniform response over time. timeMeasureSeconds is the local dt\n * for a time integral. Neither weight includes shutter transmission, scene\n * flicker, sensor response, radiometric calibration or any other throughput\n * term.\n *\n * No numerical integration error estimate is reported, on purpose. Geometry\n * alone cannot tell the error in a scene-radiance integral. A renderer that\n * needs evidence of convergence can compare results for increasing sample\n * counts in its own radiance domain.\n */\nexport function calculateCaptureRotationTemporalQuadrature(\n input: CalculateCaptureRotationTemporalQuadratureInput\n): CalculationResult<CaptureRotationTemporalQuadrature> {\n requireInputKeys(input, \"calculateCaptureRotationTemporalQuadrature\", [\"temporalSampleCount\", \"imagingArea\", \"focalLengthMm\", \"focusDistanceM\", \"angularVelocityRadPerSec\", \"orientation\", \"samplePointsNative\", \"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\"]);\n requirePositiveInteger(\n \"temporalSampleCount\",\n input.temporalSampleCount\n );\n\n if (\n !Array.isArray(input.samplePointsNative) ||\n input.samplePointsNative.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"samplePointsNative must be a non-empty array.\"\n );\n }\n input.samplePointsNative.forEach((point, index) => requireInputObject(point, \"samplePointsNative[\" + index + \"]\"));\n\n const totalNodeCount =\n input.temporalSampleCount * input.samplePointsNative.length;\n if (!Number.isSafeInteger(totalNodeCount)) {\n throw new InvalidScientificInputError(\n \"temporalSampleCount × samplePointsNative.length must be a safe integer.\"\n );\n }\n\n const {\n temporalSampleCount,\n ...instantaneousInput\n } = input;\n\n const phaseResults = Array.from(\n { length: temporalSampleCount },\n (_, temporalSampleIndex) => {\n const localExposurePhase = midpointPhase(\n temporalSampleIndex,\n temporalSampleCount\n );\n\n return calculateCaptureRotationInverseMappings({\n ...instantaneousInput,\n localExposurePhase\n });\n }\n );\n\n const firstResult = phaseResults[0];\n if (firstResult === undefined) {\n throw new InvalidScientificInputError(\n \"At least one temporal quadrature node is required.\"\n );\n }\n\n const normalizedTimeWeight = 1 / temporalSampleCount;\n\n const points = firstResult.value.samples.map(\n (firstSample, pointIndex) => {\n const durationSeconds =\n firstSample.localExposureWindow.durationSeconds;\n const timeMeasureSeconds =\n durationSeconds / temporalSampleCount;\n\n const nodes = phaseResults.map(\n (phaseResult, temporalSampleIndex) => {\n const sample = phaseResult.value.samples[pointIndex];\n if (sample === undefined) {\n throw new InvalidScientificInputError(\n \"Instantaneous inverse-mapping sample alignment failed.\"\n );\n }\n\n const localExposurePhase = midpointPhase(\n temporalSampleIndex,\n temporalSampleCount\n );\n\n return {\n temporalSampleIndex,\n localExposurePhase,\n captureTimeSecondsFromReference:\n sample.captureTimeSecondsFromReference,\n normalizedTimeWeight,\n timeMeasureSeconds,\n referenceImagePointMm: {\n ...sample.referenceImagePointMm\n },\n inverseDisplacementImagePlaneMm: {\n ...sample.inverseDisplacementImagePlaneMm\n },\n inverseDisplacementNativeSamples: {\n ...sample.inverseDisplacementNativeSamples\n },\n inverseDisplacementOrientedSamples: {\n ...sample.inverseDisplacementOrientedSamples\n }\n };\n }\n );\n\n return {\n destinationPointNative: {\n ...firstSample.destinationPointNative\n },\n destinationImagePointMm: {\n ...firstSample.destinationImagePointMm\n },\n localExposureWindow: {\n ...firstSample.localExposureWindow\n },\n nodes\n };\n }\n );\n\n return approximationResult(\n {\n timeReference: \"first-opening-boundary-phase\",\n quadratureScheme: \"uniform-midpoint\",\n temporalResponseModel: \"uniform-over-local-exposure\",\n temporalSampleCount,\n outputMeaning: \"temporal-geometry-quadrature-nodes\",\n points,\n componentProvenance: {\n instantaneousInverseMapping: firstResult.provenance\n }\n },\n \"capture-rotation-temporal-quadrature\",\n \"1.0.0\",\n [\n \"Each destination native point retains its own validated local exposure interval.\",\n \"Temporal nodes use a deterministic equal-width midpoint rule with phases (i + 0.5) / N and therefore do not sample the exact opening/closing boundaries.\",\n \"Normalized temporal weights sum approximately to one and represent a uniform time-average measure only.\",\n \"Seconds-valued temporal measures sum approximately to the local exposure duration and represent dt only.\",\n \"Uniform temporal response is an explicit approximation; shutter transmission ramps, scene flicker, sensor response and radiometric throughput are not included.\",\n \"Every node delegates instantaneous geometry to calculateCaptureRotationInverseMappings(); no second camera-motion equation is introduced.\",\n \"Sensor data-readout timing and readout/exposure linkage metadata do not alter the exposure integral unless a future explicit image-formation model consumes them.\",\n \"Reference rays may leave the active source frame under motion and are not clamped.\",\n \"No radiance, photon count, blur kernel, PSF, source resampling or output pixel value is calculated.\",\n \"No geometry-only convergence/error estimate is reported because temporal integration error depends on the downstream radiance/visibility function as well as motion.\",\n \"Camera translation, scene-depth parallax, subject motion/deformation, occlusion changes, flash/flicker, shutter shock and non-uniform temporal response remain separate future work.\"\n ]\n );\n}\n"]}
@@ -21,26 +21,11 @@
21
21
  */
22
22
  import { approximationResult } from "../core/calculation-result.js";
23
23
  import { InvalidScientificInputError, requirePositiveFinite } from "../core/validation.js";
24
- import { mapOrientedPhysicalUvToImagePlanePoint, resolveCaptureGeometry } from "../output/capture-geometry.js";
24
+ import { resolveCaptureGeometry } from "../output/capture-geometry.js";
25
25
  import { calculateCaptureExposureWindows } from "../sensor/exposure-window.js";
26
26
  import { calculateCameraRotationImageMapping } from "./camera-rotation.js";
27
27
  import { requireInputObject, requireInputKeys } from "../core/record-validation.js";
28
- import { InvalidConfigurationError } from "../core/configuration-error.js";
29
- function requireNonEmptySamples(samplePointsNative) {
30
- if (!Array.isArray(samplePointsNative) || samplePointsNative.length === 0) {
31
- throw new InvalidConfigurationError("samplePointsNative must be a non-empty array.");
32
- }
33
- samplePointsNative.forEach((point, index) => requireInputObject(point, "samplePointsNative[" + index + "]"));
34
- }
35
- function nativePointToReferenceImagePlane(point, activeCaptureRect, activePhysicalBoundsFromOpticalAxisMm) {
36
- const u = (point.x - activeCaptureRect.x) / activeCaptureRect.width;
37
- const v = (point.y - activeCaptureRect.y) / activeCaptureRect.height;
38
- return mapOrientedPhysicalUvToImagePlanePoint({
39
- uv: { u, v },
40
- orientedPhysicalBoundsFromOpticalAxisMm: activePhysicalBoundsFromOpticalAxisMm,
41
- orientation: "landscape"
42
- });
43
- }
28
+ import { requireNonEmptySamples, nativePointToImagePlane as nativePointToReferenceImagePlane } from "./capture-rotation-inverse-mapping.js";
44
29
  function endpointFromMapping(mapping) {
45
30
  return {
46
31
  timeSecondsFromCaptureReference: mapping.timeSecondsFromExposureStart,
@@ -1 +1 @@
1
- {"version":3,"file":"capture-rotation-trajectory.js","sourceRoot":"","sources":["../../src/motion/capture-rotation-trajectory.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Paths of still world rays across each point's local exposure window, for a camera that only\n * rotates.\n *\n * This links timing to geometry. It is not a finished rolling-shutter image mapping.\n * - The native sample point sets where a still world ray would land in the reference\n * global-shutter image, at the first opening-boundary phase (when the shutter starts to open).\n * - The existing camera-rotation model is then evaluated at that point's local exposure start and\n * end.\n *\n * Sensor data-readout timing is deliberately not used. How readout relates to exposure boundaries\n * depends on the camera and mode, and needs its own explicit contract. This function never assumes\n * one.\n *\n * A future rolling-shutter warp for renderers must solve the image mapping with time that depends on\n * capture location, and must integrate over the local exposure interval. Do not treat the endpoint\n * chord returned here as a blur kernel or as an inverse destination-to-source sampling map.\n * @see docs/MOTION_AND_SIGNAL.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport {\n mapOrientedPhysicalUvToImagePlanePoint,\n resolveCaptureGeometry,\n type RasterPoint\n} from \"../output/capture-geometry.js\";\nimport {\n calculateCaptureExposureWindows,\n type CalculateCaptureExposureWindowsInput,\n type CaptureExposureWindowSample\n} from \"../sensor/exposure-window.js\";\nimport type { SensorImagingArea } from \"../sensor/sensor-geometry.js\";\nimport {\n calculateCameraRotationImageMapping,\n type CameraAngularVelocityRadPerSec,\n type CameraRotationImageMapping,\n type ImagePlanePointMm\n} from \"./camera-rotation.js\";\nimport { requireInputObject, requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport interface CalculateCaptureRotationTrajectoriesInput\n extends CalculateCaptureExposureWindowsInput {\n /**\n * Physical photosensitive imaging area associated with nativeRaster.\n */\n imagingArea: SensorImagingArea;\n /** Physical focal length in millimetres. */\n focalLengthMm: number;\n /** Optional focus distance in metres for focus-aware projection distance. */\n focusDistanceM?: number;\n /**\n * Constant physical camera angular velocity resolved in camera axes at the\n * first opening-boundary phase.\n */\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;\n /**\n * Native points whose still-ray paths to evaluate. Required, and must not be\n * empty.\n */\n samplePointsNative: readonly RasterPoint[];\n}\n\nexport interface CaptureRotationTrajectoryEndpoint {\n timeSecondsFromCaptureReference: number;\n mappedImagePointMm: ImagePlanePointMm;\n deltaFromReferenceMm: {\n x: number;\n y: number;\n distance: number;\n };\n}\n\nexport interface CaptureRotationTrajectorySample {\n pointNative: RasterPoint;\n /**\n * Image-plane position of the stationary world ray at the capture reference\n * time (the first opening-boundary phase).\n */\n referenceImagePointMm: ImagePlanePointMm;\n localExposureWindow: {\n startSecondsFromCaptureReference: number;\n endSecondsFromCaptureReference: number;\n durationSeconds: number;\n openingNormalizedScanPosition: number | null;\n closingNormalizedScanPosition: number | null;\n };\n atLocalExposureStart: CaptureRotationTrajectoryEndpoint;\n atLocalExposureEnd: CaptureRotationTrajectoryEndpoint;\n /**\n * Chord between the two trajectory endpoints.\n *\n * This is not a blur kernel, integrated exposure, or rolling-shutter warp.\n */\n localExposureTrajectoryEndpointDeltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n}\n\nexport interface CaptureRotationTrajectories {\n timeReference: \"first-opening-boundary-phase\";\n trajectoryMeaning: \"forward-stationary-reference-ray\";\n imagingArea: SensorImagingArea;\n activeCaptureRect: {\n x: number;\n y: number;\n width: number;\n height: number;\n };\n samples: readonly CaptureRotationTrajectorySample[];\n componentProvenance: {\n exposureWindows: CalculationProvenance;\n cameraRotation: CalculationProvenance;\n };\n}\n\nfunction requireNonEmptySamples(\n samplePointsNative: readonly RasterPoint[]\n): void {\n if (!Array.isArray(samplePointsNative) || samplePointsNative.length === 0) {\n throw new InvalidConfigurationError(\n \"samplePointsNative must be a non-empty array.\"\n );\n }\n samplePointsNative.forEach((point, index) => requireInputObject(point, \"samplePointsNative[\" + index + \"]\"));\n}\n\nfunction nativePointToReferenceImagePlane(\n point: RasterPoint,\n activeCaptureRect: {\n x: number;\n y: number;\n width: number;\n height: number;\n },\n activePhysicalBoundsFromOpticalAxisMm: {\n left: number;\n right: number;\n top: number;\n bottom: number;\n }\n): ImagePlanePointMm {\n const u = (point.x - activeCaptureRect.x) / activeCaptureRect.width;\n const v = (point.y - activeCaptureRect.y) / activeCaptureRect.height;\n\n return mapOrientedPhysicalUvToImagePlanePoint({\n uv: { u, v },\n orientedPhysicalBoundsFromOpticalAxisMm:\n activePhysicalBoundsFromOpticalAxisMm,\n orientation: \"landscape\"\n });\n}\n\nfunction endpointFromMapping(\n mapping: CameraRotationImageMapping\n): CaptureRotationTrajectoryEndpoint {\n return {\n timeSecondsFromCaptureReference:\n mapping.timeSecondsFromExposureStart,\n mappedImagePointMm: { ...mapping.mappedImagePointMm },\n deltaFromReferenceMm: { ...mapping.deltaMm }\n };\n}\n\nfunction trajectorySample(\n point: RasterPoint,\n exposure: CaptureExposureWindowSample,\n referenceImagePointMm: ImagePlanePointMm,\n input: CalculateCaptureRotationTrajectoriesInput\n): {\n sample: CaptureRotationTrajectorySample;\n rotationProvenance: CalculationProvenance;\n} {\n const common = {\n focalLengthMm: input.focalLengthMm,\n imagePointMm: referenceImagePointMm,\n angularVelocityRadPerSec: input.angularVelocityRadPerSec,\n ...(input.focusDistanceM === undefined\n ? {}\n : { focusDistanceM: input.focusDistanceM })\n };\n\n const start = calculateCameraRotationImageMapping({\n ...common,\n timeSecondsFromExposureStart:\n exposure.startOffsetSecondsFromOpeningReference\n });\n const end = calculateCameraRotationImageMapping({\n ...common,\n timeSecondsFromExposureStart:\n exposure.endOffsetSecondsFromOpeningReference\n });\n\n const endpointDeltaX =\n end.value.mappedImagePointMm.x -\n start.value.mappedImagePointMm.x;\n const endpointDeltaY =\n end.value.mappedImagePointMm.y -\n start.value.mappedImagePointMm.y;\n\n return {\n sample: {\n pointNative: { ...point },\n referenceImagePointMm: { ...referenceImagePointMm },\n localExposureWindow: {\n startSecondsFromCaptureReference:\n exposure.startOffsetSecondsFromOpeningReference,\n endSecondsFromCaptureReference:\n exposure.endOffsetSecondsFromOpeningReference,\n durationSeconds: exposure.localExposureDurationSeconds,\n openingNormalizedScanPosition:\n exposure.openingNormalizedScanPosition,\n closingNormalizedScanPosition:\n exposure.closingNormalizedScanPosition\n },\n atLocalExposureStart: endpointFromMapping(start.value),\n atLocalExposureEnd: endpointFromMapping(end.value),\n localExposureTrajectoryEndpointDeltaMm: {\n x: endpointDeltaX,\n y: endpointDeltaY,\n distance: Math.hypot(endpointDeltaX, endpointDeltaY)\n }\n },\n rotationProvenance: start.provenance\n };\n}\n\n/**\n * Evaluates pure-camera-rotation trajectories for stationary reference rays\n * over each point's local exposure window.\n *\n * This links timing to geometry. It is not a finished rolling-shutter image\n * mapping. The native sample point sets where a still world ray would land in\n * the reference global-shutter image at the first opening-boundary phase. The\n * existing camera-rotation model is then evaluated at that point's local\n * exposure start and end.\n *\n * Sensor data-readout timing is deliberately not used. How readout relates to\n * exposure boundaries depends on the camera and mode, and needs its own\n * explicit contract. This function never assumes one.\n *\n * A future rolling-shutter warp for renderers must solve the image mapping\n * with time that depends on capture location, and must integrate over the\n * local exposure interval. Do not treat the endpoint chord returned here as a\n * blur kernel or as an inverse destination-to-source sampling map.\n */\nexport function calculateCaptureRotationTrajectories(\n input: CalculateCaptureRotationTrajectoriesInput\n): CalculationResult<CaptureRotationTrajectories> {\n requireInputKeys(input, \"calculateCaptureRotationTrajectories\", [\"imagingArea\", \"focalLengthMm\", \"focusDistanceM\", \"angularVelocityRadPerSec\", \"samplePointsNative\", \"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\"]);\n requireInputObject(input.imagingArea, \"imagingArea\");\n requirePositiveFinite(\"imagingArea.widthMm\", input.imagingArea.widthMm);\n requirePositiveFinite(\"imagingArea.heightMm\", input.imagingArea.heightMm);\n requirePositiveFinite(\"focalLengthMm\", input.focalLengthMm);\n requireNonEmptySamples(input.samplePointsNative);\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 samplePointsNative: input.samplePointsNative\n });\n\n const geometry = resolveCaptureGeometry({\n imagingArea: input.imagingArea,\n nativeRaster: input.nativeRaster,\n orientation: \"landscape\",\n activeCaptureRect: exposureWindows.value.activeCaptureRect\n }).value;\n\n const exposureByPoint = exposureWindows.value.samples;\n const samples: CaptureRotationTrajectorySample[] = [];\n let rotationProvenance: CalculationProvenance | undefined;\n\n input.samplePointsNative.forEach((point, index) => {\n const exposure = exposureByPoint[index];\n if (exposure === undefined) {\n throw new InvalidScientificInputError(\n \"Exposure-window sample alignment failed.\"\n );\n }\n\n const referenceImagePointMm = nativePointToReferenceImagePlane(\n point,\n geometry.activeCapture.nativeRect,\n geometry.activeCapture.physicalBoundsFromOpticalAxisMm\n );\n\n const resolved = trajectorySample(\n point,\n exposure,\n referenceImagePointMm,\n input\n );\n samples.push(resolved.sample);\n rotationProvenance ??= resolved.rotationProvenance;\n });\n\n if (rotationProvenance === undefined) {\n throw new InvalidScientificInputError(\n \"At least one rotation trajectory sample is required.\"\n );\n }\n\n return approximationResult(\n {\n timeReference: \"first-opening-boundary-phase\",\n trajectoryMeaning: \"forward-stationary-reference-ray\",\n imagingArea: { ...input.imagingArea },\n activeCaptureRect: {\n ...exposureWindows.value.activeCaptureRect\n },\n samples,\n componentProvenance: {\n exposureWindows: exposureWindows.provenance,\n cameraRotation: rotationProvenance\n }\n },\n \"capture-rotation-exposure-trajectory\",\n \"1.0.0\",\n [\n \"The first opening-boundary phase is explicitly bound to t=0 of the low-level camera-rotation model for this composition only.\",\n \"Each native point identifies a stationary world ray by its reference image-plane location at the capture reference time.\",\n \"Local exposure start/end times come only from the capture exposure-window contract.\",\n \"Sensor data-readout timing does not participate and is never assumed to equal an exposure boundary.\",\n \"Camera angular velocity is constant and resolved in camera axes at the first opening-boundary phase.\",\n \"Only pure camera rotation is modeled; camera translation, scene depth parallax, subject motion, deformation and occlusion changes are excluded.\",\n \"The returned start/end mappings are a forward stationary-ray trajectory, not a rolling-shutter image warp.\",\n \"The trajectory endpoint chord is not an integrated blur kernel and may not represent the curved path between endpoints.\",\n \"A later inverse renderer warp must solve capture-location-dependent timing consistently rather than evaluating one forward displacement at the destination coordinate.\",\n \"Exposure integration over the full local interval remains future work.\"\n ]\n );\n}\n"]}
1
+ {"version":3,"file":"capture-rotation-trajectory.js","sourceRoot":"","sources":["../../src/motion/capture-rotation-trajectory.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Paths of still world rays across each point's local exposure window, for a camera that only\n * rotates.\n *\n * This links timing to geometry. It is not a finished rolling-shutter image mapping.\n * - The native sample point sets where a still world ray would land in the reference\n * global-shutter image, at the first opening-boundary phase (when the shutter starts to open).\n * - The existing camera-rotation model is then evaluated at that point's local exposure start and\n * end.\n *\n * Sensor data-readout timing is deliberately not used. How readout relates to exposure boundaries\n * depends on the camera and mode, and needs its own explicit contract. This function never assumes\n * one.\n *\n * A future rolling-shutter warp for renderers must solve the image mapping with time that depends on\n * capture location, and must integrate over the local exposure interval. Do not treat the endpoint\n * chord returned here as a blur kernel or as an inverse destination-to-source sampling map.\n * @see docs/MOTION_AND_SIGNAL.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationProvenance,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport { resolveCaptureGeometry, type RasterPoint } from \"../output/capture-geometry.js\";\nimport {\n calculateCaptureExposureWindows,\n type CalculateCaptureExposureWindowsInput,\n type CaptureExposureWindowSample\n} from \"../sensor/exposure-window.js\";\nimport type { SensorImagingArea } from \"../sensor/sensor-geometry.js\";\nimport {\n calculateCameraRotationImageMapping,\n type CameraAngularVelocityRadPerSec,\n type CameraRotationImageMapping,\n type ImagePlanePointMm\n} from \"./camera-rotation.js\";\nimport { requireInputObject, requireInputKeys } from \"../core/record-validation.js\";\nimport { requireNonEmptySamples, nativePointToImagePlane as nativePointToReferenceImagePlane } from \"./capture-rotation-inverse-mapping.js\";\n\nexport interface CalculateCaptureRotationTrajectoriesInput\n extends CalculateCaptureExposureWindowsInput {\n /**\n * Physical photosensitive imaging area associated with nativeRaster.\n */\n imagingArea: SensorImagingArea;\n /** Physical focal length in millimetres. */\n focalLengthMm: number;\n /** Optional focus distance in metres for focus-aware projection distance. */\n focusDistanceM?: number;\n /**\n * Constant physical camera angular velocity resolved in camera axes at the\n * first opening-boundary phase.\n */\n angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;\n /**\n * Native points whose still-ray paths to evaluate. Required, and must not be\n * empty.\n */\n samplePointsNative: readonly RasterPoint[];\n}\n\nexport interface CaptureRotationTrajectoryEndpoint {\n timeSecondsFromCaptureReference: number;\n mappedImagePointMm: ImagePlanePointMm;\n deltaFromReferenceMm: {\n x: number;\n y: number;\n distance: number;\n };\n}\n\nexport interface CaptureRotationTrajectorySample {\n pointNative: RasterPoint;\n /**\n * Image-plane position of the stationary world ray at the capture reference\n * time (the first opening-boundary phase).\n */\n referenceImagePointMm: ImagePlanePointMm;\n localExposureWindow: {\n startSecondsFromCaptureReference: number;\n endSecondsFromCaptureReference: number;\n durationSeconds: number;\n openingNormalizedScanPosition: number | null;\n closingNormalizedScanPosition: number | null;\n };\n atLocalExposureStart: CaptureRotationTrajectoryEndpoint;\n atLocalExposureEnd: CaptureRotationTrajectoryEndpoint;\n /**\n * Chord between the two trajectory endpoints.\n *\n * This is not a blur kernel, integrated exposure, or rolling-shutter warp.\n */\n localExposureTrajectoryEndpointDeltaMm: {\n x: number;\n y: number;\n distance: number;\n };\n}\n\nexport interface CaptureRotationTrajectories {\n timeReference: \"first-opening-boundary-phase\";\n trajectoryMeaning: \"forward-stationary-reference-ray\";\n imagingArea: SensorImagingArea;\n activeCaptureRect: {\n x: number;\n y: number;\n width: number;\n height: number;\n };\n samples: readonly CaptureRotationTrajectorySample[];\n componentProvenance: {\n exposureWindows: CalculationProvenance;\n cameraRotation: CalculationProvenance;\n };\n}\n\nfunction endpointFromMapping(\n mapping: CameraRotationImageMapping\n): CaptureRotationTrajectoryEndpoint {\n return {\n timeSecondsFromCaptureReference:\n mapping.timeSecondsFromExposureStart,\n mappedImagePointMm: { ...mapping.mappedImagePointMm },\n deltaFromReferenceMm: { ...mapping.deltaMm }\n };\n}\n\nfunction trajectorySample(\n point: RasterPoint,\n exposure: CaptureExposureWindowSample,\n referenceImagePointMm: ImagePlanePointMm,\n input: CalculateCaptureRotationTrajectoriesInput\n): {\n sample: CaptureRotationTrajectorySample;\n rotationProvenance: CalculationProvenance;\n} {\n const common = {\n focalLengthMm: input.focalLengthMm,\n imagePointMm: referenceImagePointMm,\n angularVelocityRadPerSec: input.angularVelocityRadPerSec,\n ...(input.focusDistanceM === undefined\n ? {}\n : { focusDistanceM: input.focusDistanceM })\n };\n\n const start = calculateCameraRotationImageMapping({\n ...common,\n timeSecondsFromExposureStart:\n exposure.startOffsetSecondsFromOpeningReference\n });\n const end = calculateCameraRotationImageMapping({\n ...common,\n timeSecondsFromExposureStart:\n exposure.endOffsetSecondsFromOpeningReference\n });\n\n const endpointDeltaX =\n end.value.mappedImagePointMm.x -\n start.value.mappedImagePointMm.x;\n const endpointDeltaY =\n end.value.mappedImagePointMm.y -\n start.value.mappedImagePointMm.y;\n\n return {\n sample: {\n pointNative: { ...point },\n referenceImagePointMm: { ...referenceImagePointMm },\n localExposureWindow: {\n startSecondsFromCaptureReference:\n exposure.startOffsetSecondsFromOpeningReference,\n endSecondsFromCaptureReference:\n exposure.endOffsetSecondsFromOpeningReference,\n durationSeconds: exposure.localExposureDurationSeconds,\n openingNormalizedScanPosition:\n exposure.openingNormalizedScanPosition,\n closingNormalizedScanPosition:\n exposure.closingNormalizedScanPosition\n },\n atLocalExposureStart: endpointFromMapping(start.value),\n atLocalExposureEnd: endpointFromMapping(end.value),\n localExposureTrajectoryEndpointDeltaMm: {\n x: endpointDeltaX,\n y: endpointDeltaY,\n distance: Math.hypot(endpointDeltaX, endpointDeltaY)\n }\n },\n rotationProvenance: start.provenance\n };\n}\n\n/**\n * Evaluates pure-camera-rotation trajectories for stationary reference rays\n * over each point's local exposure window.\n *\n * This links timing to geometry. It is not a finished rolling-shutter image\n * mapping. The native sample point sets where a still world ray would land in\n * the reference global-shutter image at the first opening-boundary phase. The\n * existing camera-rotation model is then evaluated at that point's local\n * exposure start and end.\n *\n * Sensor data-readout timing is deliberately not used. How readout relates to\n * exposure boundaries depends on the camera and mode, and needs its own\n * explicit contract. This function never assumes one.\n *\n * A future rolling-shutter warp for renderers must solve the image mapping\n * with time that depends on capture location, and must integrate over the\n * local exposure interval. Do not treat the endpoint chord returned here as a\n * blur kernel or as an inverse destination-to-source sampling map.\n */\nexport function calculateCaptureRotationTrajectories(\n input: CalculateCaptureRotationTrajectoriesInput\n): CalculationResult<CaptureRotationTrajectories> {\n requireInputKeys(input, \"calculateCaptureRotationTrajectories\", [\"imagingArea\", \"focalLengthMm\", \"focusDistanceM\", \"angularVelocityRadPerSec\", \"samplePointsNative\", \"nativeRaster\", \"activeCaptureRect\", \"shutterMechanism\", \"nominalExposureDurationSeconds\", \"opening\", \"closing\"]);\n requireInputObject(input.imagingArea, \"imagingArea\");\n requirePositiveFinite(\"imagingArea.widthMm\", input.imagingArea.widthMm);\n requirePositiveFinite(\"imagingArea.heightMm\", input.imagingArea.heightMm);\n requirePositiveFinite(\"focalLengthMm\", input.focalLengthMm);\n requireNonEmptySamples(input.samplePointsNative);\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 samplePointsNative: input.samplePointsNative\n });\n\n const geometry = resolveCaptureGeometry({\n imagingArea: input.imagingArea,\n nativeRaster: input.nativeRaster,\n orientation: \"landscape\",\n activeCaptureRect: exposureWindows.value.activeCaptureRect\n }).value;\n\n const exposureByPoint = exposureWindows.value.samples;\n const samples: CaptureRotationTrajectorySample[] = [];\n let rotationProvenance: CalculationProvenance | undefined;\n\n input.samplePointsNative.forEach((point, index) => {\n const exposure = exposureByPoint[index];\n if (exposure === undefined) {\n throw new InvalidScientificInputError(\n \"Exposure-window sample alignment failed.\"\n );\n }\n\n const referenceImagePointMm = nativePointToReferenceImagePlane(\n point,\n geometry.activeCapture.nativeRect,\n geometry.activeCapture.physicalBoundsFromOpticalAxisMm\n );\n\n const resolved = trajectorySample(\n point,\n exposure,\n referenceImagePointMm,\n input\n );\n samples.push(resolved.sample);\n rotationProvenance ??= resolved.rotationProvenance;\n });\n\n if (rotationProvenance === undefined) {\n throw new InvalidScientificInputError(\n \"At least one rotation trajectory sample is required.\"\n );\n }\n\n return approximationResult(\n {\n timeReference: \"first-opening-boundary-phase\",\n trajectoryMeaning: \"forward-stationary-reference-ray\",\n imagingArea: { ...input.imagingArea },\n activeCaptureRect: {\n ...exposureWindows.value.activeCaptureRect\n },\n samples,\n componentProvenance: {\n exposureWindows: exposureWindows.provenance,\n cameraRotation: rotationProvenance\n }\n },\n \"capture-rotation-exposure-trajectory\",\n \"1.0.0\",\n [\n \"The first opening-boundary phase is explicitly bound to t=0 of the low-level camera-rotation model for this composition only.\",\n \"Each native point identifies a stationary world ray by its reference image-plane location at the capture reference time.\",\n \"Local exposure start/end times come only from the capture exposure-window contract.\",\n \"Sensor data-readout timing does not participate and is never assumed to equal an exposure boundary.\",\n \"Camera angular velocity is constant and resolved in camera axes at the first opening-boundary phase.\",\n \"Only pure camera rotation is modeled; camera translation, scene depth parallax, subject motion, deformation and occlusion changes are excluded.\",\n \"The returned start/end mappings are a forward stationary-ray trajectory, not a rolling-shutter image warp.\",\n \"The trajectory endpoint chord is not an integrated blur kernel and may not represent the curved path between endpoints.\",\n \"A later inverse renderer warp must solve capture-location-dependent timing consistently rather than evaluating one forward displacement at the destination coordinate.\",\n \"Exposure integration over the full local interval remains future work.\"\n ]\n );\n}\n"]}
@@ -109,6 +109,7 @@ export interface CaptureTranslationParallaxTemporalQuadrature {
109
109
  visibilityOcclusionModeled: false;
110
110
  sensorReadoutTimingUsedAsExposureTiming: false;
111
111
  }
112
+ export declare function requireFiniteVector(value: Vector3, path: string): Vector3;
112
113
  /**
113
114
  * Evaluates depth-dependent parallax from camera movement at deterministic
114
115
  * midpoint time samples across each scene point's local exposure window.
@@ -14,9 +14,10 @@ import { approximationResult } from "../core/calculation-result.js";
14
14
  import { InvalidScientificInputError, requirePositiveFinite, requirePositiveInteger } from "../core/validation.js";
15
15
  import { transformNativeRasterVectorToOriented } from "../output/capture-geometry.js";
16
16
  import { calculateThinLensImageDistance } from "../optics/thin-lens.js";
17
- import { requireInputObject, requireInputKeys } from "../core/record-validation.js";
17
+ import { requireInputObject, requireInputKeys, requireTrimmedConfigString as requireNonEmptyString } from "../core/record-validation.js";
18
18
  import { InvalidConfigurationError } from "../core/configuration-error.js";
19
- function requireFiniteVector(value, path) {
19
+ import { sameSite as samePoint } from "../core/shared-helpers.js";
20
+ export function requireFiniteVector(value, path) {
20
21
  requireInputObject(value, path);
21
22
  for (const axis of ["x", "y", "z"]) {
22
23
  if (!Number.isFinite(value[axis])) {
@@ -30,18 +31,6 @@ function requireFiniteVector(value, path) {
30
31
  ...value
31
32
  };
32
33
  }
33
- function requireNonEmptyString(value, path) {
34
- if (typeof value !== "string" ||
35
- value.trim().length === 0) {
36
- throw new InvalidConfigurationError(path +
37
- " must be a non-empty string.");
38
- }
39
- return value.trim();
40
- }
41
- function samePoint(a, b) {
42
- return (a.x === b.x &&
43
- a.y === b.y);
44
- }
45
34
  function projectPoint(positionM, projectionDistanceMm, path) {
46
35
  if (positionM.z <= 0) {
47
36
  throw new InvalidScientificInputError(path +