@photivra/engine 2.18.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 (261) hide show
  1. package/CHANGELOG.md +68 -1
  2. package/CITATION.cff +1 -1
  3. package/README.md +6 -6
  4. package/SECURITY.md +1 -1
  5. package/dist/capture/focus-control.js +2 -75
  6. package/dist/capture/focus-control.js.map +1 -1
  7. package/dist/capture/native-environment-lean.d.ts +1 -0
  8. package/dist/capture/native-environment-lean.js +1 -1
  9. package/dist/capture/native-environment-lean.js.map +1 -1
  10. package/dist/capture/native-environment-optical-stage.d.ts +1 -0
  11. package/dist/capture/native-environment-optical-stage.js +2 -5
  12. package/dist/capture/native-environment-optical-stage.js.map +1 -1
  13. package/dist/capture/native-environment-raw.js +1 -5
  14. package/dist/capture/native-environment-raw.js.map +1 -1
  15. package/dist/capture/release-sequence.js +2 -47
  16. package/dist/capture/release-sequence.js.map +1 -1
  17. package/dist/color/white-balance.d.ts +2 -0
  18. package/dist/color/white-balance.js +3 -56
  19. package/dist/color/white-balance.js.map +1 -1
  20. package/dist/composition/image-formation-plan.d.ts +1 -0
  21. package/dist/composition/image-formation-plan.js +3 -49
  22. package/dist/composition/image-formation-plan.js.map +1 -1
  23. package/dist/core/evidence-provenance.js +1 -16
  24. package/dist/core/evidence-provenance.js.map +1 -1
  25. package/dist/core/record-validation.d.ts +54 -0
  26. package/dist/core/record-validation.js +176 -0
  27. package/dist/core/record-validation.js.map +1 -1
  28. package/dist/core/scientific-assurance.js +1 -18
  29. package/dist/core/scientific-assurance.js.map +1 -1
  30. package/dist/core/shared-helpers.d.ts +33 -0
  31. package/dist/core/shared-helpers.js +34 -0
  32. package/dist/core/shared-helpers.js.map +1 -0
  33. package/dist/core/spectral-composition.js +1 -36
  34. package/dist/core/spectral-composition.js.map +1 -1
  35. package/dist/core/spectral.js +1 -19
  36. package/dist/core/spectral.js.map +1 -1
  37. package/dist/core/version.d.ts +1 -1
  38. package/dist/core/version.js +1 -1
  39. package/dist/core/version.js.map +1 -1
  40. package/dist/equipment/exposure-capabilities.d.ts +1 -0
  41. package/dist/equipment/exposure-capabilities.js +2 -36
  42. package/dist/equipment/exposure-capabilities.js.map +1 -1
  43. package/dist/equipment/iso-capabilities.js +2 -49
  44. package/dist/equipment/iso-capabilities.js.map +1 -1
  45. package/dist/equipment/metering-capabilities.js +1 -26
  46. package/dist/equipment/metering-capabilities.js.map +1 -1
  47. package/dist/equipment/release-capabilities.js +1 -53
  48. package/dist/equipment/release-capabilities.js.map +1 -1
  49. package/dist/exposure/auto-iso-slowest-shutter.js +1 -7
  50. package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -1
  51. package/dist/exposure/exposure-duration-control.js +1 -16
  52. package/dist/exposure/exposure-duration-control.js.map +1 -1
  53. package/dist/exposure/exposure-mode-resolver.js +1 -17
  54. package/dist/exposure/exposure-mode-resolver.js.map +1 -1
  55. package/dist/exposure/filtered-exposure-time.d.ts +93 -0
  56. package/dist/exposure/filtered-exposure-time.js +129 -0
  57. package/dist/exposure/filtered-exposure-time.js.map +1 -0
  58. package/dist/exposure/flash-ttl.d.ts +1 -0
  59. package/dist/exposure/flash-ttl.js +1 -1
  60. package/dist/exposure/flash-ttl.js.map +1 -1
  61. package/dist/exposure/flash.js +1 -49
  62. package/dist/exposure/flash.js.map +1 -1
  63. package/dist/exposure/metering-scene-radiance.js +1 -25
  64. package/dist/exposure/metering-scene-radiance.js.map +1 -1
  65. package/dist/exposure/metering-target.js +1 -30
  66. package/dist/exposure/metering-target.js.map +1 -1
  67. package/dist/exposure/metering-temporal.js +1 -8
  68. package/dist/exposure/metering-temporal.js.map +1 -1
  69. package/dist/exposure/metering.js +1 -72
  70. package/dist/exposure/metering.js.map +1 -1
  71. package/dist/exposure/program-line.js +1 -45
  72. package/dist/exposure/program-line.js.map +1 -1
  73. package/dist/facade/develop-joined-parts.d.ts +29 -0
  74. package/dist/facade/develop-joined-parts.js +33 -0
  75. package/dist/facade/develop-joined-parts.js.map +1 -0
  76. package/dist/facade/generic-lens-capture.d.ts +4 -2
  77. package/dist/facade/generic-lens-capture.js +4 -2
  78. package/dist/facade/generic-lens-capture.js.map +1 -1
  79. package/dist/facade/simulate-capture.d.ts +37 -5
  80. package/dist/facade/simulate-capture.js +153 -96
  81. package/dist/facade/simulate-capture.js.map +1 -1
  82. package/dist/gpu/chart-composite-gpu.js +1 -3
  83. package/dist/gpu/chart-composite-gpu.js.map +1 -1
  84. package/dist/gpu/chart-radiance-gpu.js +1 -5
  85. package/dist/gpu/chart-radiance-gpu.js.map +1 -1
  86. package/dist/gpu/display-view-quality-gpu.d.ts +13 -1
  87. package/dist/gpu/display-view-quality-gpu.js +4 -8
  88. package/dist/gpu/display-view-quality-gpu.js.map +1 -1
  89. package/dist/gpu/display-view-speed-gpu.js +3 -25
  90. package/dist/gpu/display-view-speed-gpu.js.map +1 -1
  91. package/dist/gpu/sensor-stage-gpu.js +1 -5
  92. package/dist/gpu/sensor-stage-gpu.js.map +1 -1
  93. package/dist/gpu/webgpu-runtime.d.ts +2 -0
  94. package/dist/gpu/webgpu-runtime.js +3 -0
  95. package/dist/gpu/webgpu-runtime.js.map +1 -1
  96. package/dist/index.d.ts +4 -1
  97. package/dist/index.js +3 -0
  98. package/dist/index.js.map +1 -1
  99. package/dist/motion/camera-rotation.js +1 -6
  100. package/dist/motion/camera-rotation.js.map +1 -1
  101. package/dist/motion/capture-rotation-inverse-mapping.d.ts +12 -0
  102. package/dist/motion/capture-rotation-inverse-mapping.js +2 -2
  103. package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -1
  104. package/dist/motion/capture-rotation-temporal-quadrature.d.ts +1 -0
  105. package/dist/motion/capture-rotation-temporal-quadrature.js +1 -1
  106. package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -1
  107. package/dist/motion/capture-rotation-trajectory.js +2 -17
  108. package/dist/motion/capture-rotation-trajectory.js.map +1 -1
  109. package/dist/motion/capture-translation-parallax-temporal-quadrature.d.ts +1 -0
  110. package/dist/motion/capture-translation-parallax-temporal-quadrature.js +3 -14
  111. package/dist/motion/capture-translation-parallax-temporal-quadrature.js.map +1 -1
  112. package/dist/motion/extended-object-projection.js +5 -40
  113. package/dist/motion/extended-object-projection.js.map +1 -1
  114. package/dist/optics/atmospheric-haze.js +1 -7
  115. package/dist/optics/atmospheric-haze.js.map +1 -1
  116. package/dist/optics/brown-conrady-distortion.js +1 -7
  117. package/dist/optics/brown-conrady-distortion.js.map +1 -1
  118. package/dist/optics/complex-pupil-psf.d.ts +1 -0
  119. package/dist/optics/complex-pupil-psf.js +2 -84
  120. package/dist/optics/complex-pupil-psf.js.map +1 -1
  121. package/dist/optics/focus-state.js +1 -11
  122. package/dist/optics/focus-state.js.map +1 -1
  123. package/dist/optics/front-of-lens-filter.d.ts +5 -0
  124. package/dist/optics/front-of-lens-filter.js +3 -65
  125. package/dist/optics/front-of-lens-filter.js.map +1 -1
  126. package/dist/optics/generic-nd-filter-presets.d.ts +12 -0
  127. package/dist/optics/generic-nd-filter-presets.js +48 -0
  128. package/dist/optics/generic-nd-filter-presets.js.map +1 -0
  129. package/dist/optics/illumination-vignetting.js +1 -6
  130. package/dist/optics/illumination-vignetting.js.map +1 -1
  131. package/dist/optics/lens-psf-profile.d.ts +4 -1
  132. package/dist/optics/lens-psf-profile.js +12 -111
  133. package/dist/optics/lens-psf-profile.js.map +1 -1
  134. package/dist/optics/longitudinal-chromatic-aberration.js +1 -7
  135. package/dist/optics/longitudinal-chromatic-aberration.js.map +1 -1
  136. package/dist/optics/pupil-vignetting.js +1 -7
  137. package/dist/optics/pupil-vignetting.js.map +1 -1
  138. package/dist/optics/radial-distortion.js +1 -6
  139. package/dist/optics/radial-distortion.js.map +1 -1
  140. package/dist/optics/scene-to-sensor-irradiance.js +4 -96
  141. package/dist/optics/scene-to-sensor-irradiance.js.map +1 -1
  142. package/dist/optics/stray-light.d.ts +1 -0
  143. package/dist/optics/stray-light.js +1 -1
  144. package/dist/optics/stray-light.js.map +1 -1
  145. package/dist/optics/zernike-wavefront.js +1 -7
  146. package/dist/optics/zernike-wavefront.js.map +1 -1
  147. package/dist/output/capture-geometry.js +1 -8
  148. package/dist/output/capture-geometry.js.map +1 -1
  149. package/dist/output/developed-lens-corrections.d.ts +62 -0
  150. package/dist/output/developed-lens-corrections.js +135 -0
  151. package/dist/output/developed-lens-corrections.js.map +1 -0
  152. package/dist/output/generic-lens-corrections.d.ts +2 -6
  153. package/dist/output/generic-lens-corrections.js +3 -6
  154. package/dist/output/generic-lens-corrections.js.map +1 -1
  155. package/dist/output/geometric-transforms.js +1 -4
  156. package/dist/output/geometric-transforms.js.map +1 -1
  157. package/dist/output/native-capture-sdr.d.ts +2 -2
  158. package/dist/output/native-capture-sdr.js +3 -2
  159. package/dist/output/native-capture-sdr.js.map +1 -1
  160. package/dist/processing/development-detail.d.ts +29 -0
  161. package/dist/processing/development-detail.js +128 -107
  162. package/dist/processing/development-detail.js.map +1 -1
  163. package/dist/schema/illumination-temporal.js +3 -72
  164. package/dist/schema/illumination-temporal.js.map +1 -1
  165. package/dist/schema/illumination.d.ts +1 -0
  166. package/dist/schema/illumination.js +2 -65
  167. package/dist/schema/illumination.js.map +1 -1
  168. package/dist/schema/scene-radiance-temporal.js +2 -14
  169. package/dist/schema/scene-radiance-temporal.js.map +1 -1
  170. package/dist/schema/scene-radiance.js +2 -60
  171. package/dist/schema/scene-radiance.js.map +1 -1
  172. package/dist/schema/validation.d.ts +2 -0
  173. package/dist/schema/validation.js +2 -17
  174. package/dist/schema/validation.js.map +1 -1
  175. package/dist/sensor/accumulated-charge.js +2 -59
  176. package/dist/sensor/accumulated-charge.js.map +1 -1
  177. package/dist/sensor/architecture.js +1 -16
  178. package/dist/sensor/architecture.js.map +1 -1
  179. package/dist/sensor/camera-saturation-capacity.d.ts +1 -0
  180. package/dist/sensor/camera-saturation-capacity.js +3 -55
  181. package/dist/sensor/camera-saturation-capacity.js.map +1 -1
  182. package/dist/sensor/capture-color-sampling-binding.js +1 -26
  183. package/dist/sensor/capture-color-sampling-binding.js.map +1 -1
  184. package/dist/sensor/capture-mode-timing.js +1 -39
  185. package/dist/sensor/capture-mode-timing.js.map +1 -1
  186. package/dist/sensor/capture-mode.js +1 -23
  187. package/dist/sensor/capture-mode.js.map +1 -1
  188. package/dist/sensor/color-sampling.js +1 -26
  189. package/dist/sensor/color-sampling.js.map +1 -1
  190. package/dist/sensor/constant-rate-temporal-integration.js +2 -46
  191. package/dist/sensor/constant-rate-temporal-integration.js.map +1 -1
  192. package/dist/sensor/dark-current.js +2 -45
  193. package/dist/sensor/dark-current.js.map +1 -1
  194. package/dist/sensor/eqe-electron-rate.js +2 -35
  195. package/dist/sensor/eqe-electron-rate.js.map +1 -1
  196. package/dist/sensor/exposure-window.d.ts +5 -0
  197. package/dist/sensor/exposure-window.js +7 -22
  198. package/dist/sensor/exposure-window.js.map +1 -1
  199. package/dist/sensor/iso-gain.js +1 -7
  200. package/dist/sensor/iso-gain.js.map +1 -1
  201. package/dist/sensor/iso-signal-chain.js +1 -43
  202. package/dist/sensor/iso-signal-chain.js.map +1 -1
  203. package/dist/sensor/optical-stack.js +1 -25
  204. package/dist/sensor/optical-stack.js.map +1 -1
  205. package/dist/sensor/photon-energy.js +1 -26
  206. package/dist/sensor/photon-energy.js.map +1 -1
  207. package/dist/sensor/physical-charge-capacity.js +3 -77
  208. package/dist/sensor/physical-charge-capacity.js.map +1 -1
  209. package/dist/sensor/radiometry-readiness.js +2 -23
  210. package/dist/sensor/radiometry-readiness.js.map +1 -1
  211. package/dist/sensor/raw-readout.d.ts +3 -0
  212. package/dist/sensor/raw-readout.js +4 -66
  213. package/dist/sensor/raw-readout.js.map +1 -1
  214. package/dist/sensor/raw-reconstruction.js +2 -64
  215. package/dist/sensor/raw-reconstruction.js.map +1 -1
  216. package/dist/sensor/readout-exposure-linkage.js +1 -9
  217. package/dist/sensor/readout-exposure-linkage.js.map +1 -1
  218. package/dist/sensor/readout-timing.js +3 -57
  219. package/dist/sensor/readout-timing.js.map +1 -1
  220. package/dist/sensor/response-application-compatibility.d.ts +2 -0
  221. package/dist/sensor/response-application-compatibility.js +3 -29
  222. package/dist/sensor/response-application-compatibility.js.map +1 -1
  223. package/dist/sensor/response-operating-range.d.ts +1 -0
  224. package/dist/sensor/response-operating-range.js +3 -79
  225. package/dist/sensor/response-operating-range.js.map +1 -1
  226. package/dist/sensor/responsivity-photocurrent.js +4 -108
  227. package/dist/sensor/responsivity-photocurrent.js.map +1 -1
  228. package/dist/sensor/sampling-aperture.js +1 -25
  229. package/dist/sensor/sampling-aperture.js.map +1 -1
  230. package/dist/sensor/spatial-sample-reduction.js +2 -19
  231. package/dist/sensor/spatial-sample-reduction.js.map +1 -1
  232. package/dist/sensor/spatial-sampling-quadrature.js +1 -3
  233. package/dist/sensor/spatial-sampling-quadrature.js.map +1 -1
  234. package/dist/sensor/spatio-spectral-reduction.js +1 -18
  235. package/dist/sensor/spatio-spectral-reduction.js.map +1 -1
  236. package/dist/sensor/spectral-coverage.d.ts +10 -1
  237. package/dist/sensor/spectral-coverage.js +1 -1
  238. package/dist/sensor/spectral-coverage.js.map +1 -1
  239. package/dist/sensor/spectral-quadrature.js +2 -35
  240. package/dist/sensor/spectral-quadrature.js.map +1 -1
  241. package/dist/sensor/spectral-response.js +1 -47
  242. package/dist/sensor/spectral-response.js.map +1 -1
  243. package/dist/stabilization/camera-shake.js +1 -6
  244. package/dist/stabilization/camera-shake.js.map +1 -1
  245. package/dist/stabilization/system.js +2 -80
  246. package/dist/stabilization/system.js.map +1 -1
  247. package/docs/AGENT_GUIDE.md +1 -0
  248. package/docs/API_REFERENCE.md +41 -2
  249. package/docs/QUICK_START.md +90 -8
  250. package/docs/USAGE.md +47 -2
  251. package/docs/api/core-version.md +1 -1
  252. package/docs/api/exports.json +53 -3
  253. package/docs/api/exposure-filtered-exposure-time.md +105 -0
  254. package/docs/api/facade-develop-joined-parts.md +32 -0
  255. package/docs/api/facade-simulate-capture.md +36 -5
  256. package/docs/api/optics-generic-nd-filter-presets.md +34 -0
  257. package/docs/api/optics-lens-psf-profile.md +3 -1
  258. package/docs/api/output-developed-lens-corrections.md +12 -0
  259. package/docs/api/output-generic-lens-corrections.md +0 -9
  260. package/llms.txt +1 -0
  261. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"auto-iso-slowest-shutter.js","sourceRoot":"","sources":["../../src/exposure/auto-iso-slowest-shutter.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #504: the automatic slowest preferred shutter for Auto ISO (\"Auto\" minimum shutter speed), derived\n * from the engine's camera-shake model (`estimateCameraShakeBlur()`), not from the 1/focal-length rule\n * of thumb.\n *\n * The camera turns at a constant angular velocity ω (yaw, pitch) during the exposure, reduced by the\n * stabilization factor r = 2^−stops. A still point on the optical axis then moves\n * b(t) = v·hypot(tan(r·ω_yaw·t), tan(r·ω_pitch·t)) on the sensor, where v is the projection distance (the\n * focal length, or the thin-lens image distance with a focus distance). The derived shutter is the\n * exposure time at which b(t) equals the caller's blur budget. b(t) grows strictly with t, so the\n * solution is unique; the whole-stop bias (as in makers' Auto minimum-shutter settings: faster or\n * slower in 1 EV steps) is applied after the derivation, and the optional shutter grid last.\n *\n * This is an approximation: the hand's motion is a constant-velocity profile supplied by the caller,\n * and stabilization stops scale the motion by 2^−stops (the educational model of\n * `estimateCameraShakeBlur()`, not a CIPA DC-011 rating).\n * @see docs/USAGE.md (\"Automatic slowest shutter (#504)\") for the equations, the cited generic\n * hand-shake default and the support limits.\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { rejectUnknownFields, requireInputKeys, requireInputObject } from \"../core/record-validation.js\";\nimport { InvalidScientificInputError, InvalidScientificResultError } from \"../core/validation.js\";\nimport { portableHypot } from \"../core/portable-math.js\";\nimport type { ResolvedGenericEquipmentExposureCapabilities } from \"../equipment/exposure-capabilities.js\";\nimport { estimateCameraShakeBlur, type CameraShakeEstimate } from \"../stabilization/camera-shake.js\";\n\nexport const AUTO_ISO_SLOWEST_SHUTTER_MODEL_VERSION = \"1.0.0\" as const;\n\n/**\n * A generic handheld angular velocity, labelled as a default and not a measurement of any camera or\n * photographer: 4.39 °/s (≈ 0.0766 rad/s), the linear camera-motion rate Xiao, Farrell, Catrysse and\n * Wandell use for \"an average user\" holding \"a typical 100g mobile handset with two hands\", from their\n * earlier gyroscope measurements (F. Xiao, J. Farrell, P. Catrysse, B. Wandell, \"Mobile imaging: the\n * big challenge of the small pixel\", Proc. SPIE 7250, Digital Photography V, 2009, §2; author copy:\n * https://web.stanford.edu/~wandell/data/papers/2009-CameraShake-Xiao.pdf; they cite F. Xiao et al.,\n * \"Camera-motion and mobile imaging\", Proc. SPIE 6502, 2007). The value is one rate magnitude; it is\n * placed on the yaw axis (for small angles the blur length does not depend on the direction). A\n * heavier camera, another grip or another photographer moves differently: pass your own profile.\n */\nexport const GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC: Readonly<{ yaw: number; pitch: number }> =\n /* @__PURE__ */ Object.freeze({ yaw: (4.39 * Math.PI) / 180, pitch: 0 });\n\n/** The largest whole-stop bias accepted, either way (makers offer up to 2 stops faster or slower). */\nexport const AUTO_ISO_SLOWEST_SHUTTER_MAXIMUM_BIAS_STOPS = 2 as const;\n\n/** The blur allowed during the exposure, on the sensor. */\nexport type AutoIsoSlowestShutterBlurBudget =\n | {\n /** The blur trace length in pixels of the given pitch. */\n kind: \"pixels\";\n pixels: number;\n pixelPitchMicrometers: number;\n }\n | {\n /** The blur trace length as a fraction of the image diagonal (width × height in millimetres). */\n kind: \"image-diagonal-fraction\";\n fraction: number;\n imageWidthMm: number;\n imageHeightMm: number;\n };\n\nexport interface EstimateAutoIsoSlowestShutterInput {\n focalLengthMm: number;\n /** Optional focus distance in metres: the thin-lens image distance becomes the projection distance, as in `estimateCameraShakeBlur()`. */\n focusDistanceM?: number;\n /**\n * The hand's constant angular velocity in radians per second, the explicit shake profile (signs as in\n * `estimateCameraShakeBlur()`; only the magnitude matters here). At least one axis must be non-zero.\n * `GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC` is a cited generic default, not a measurement of you.\n */\n angularVelocityRadPerSec: { yaw: number; pitch: number };\n /** Stabilization in equivalent stops (≥ 0); the motion is scaled by 2^−stops. Default 0. */\n stabilizationStopsEquivalent?: number;\n blurBudget: AutoIsoSlowestShutterBlurBudget;\n /**\n * The bias in whole stops, applied after the derivation: +1 is one stop faster (half the time), −1 one\n * stop slower (twice the time). An integer from −2 to +2. Default 0.\n */\n fasterByStops?: number;\n /** Optional: the body's resolved shutter capability. The biased shutter then snaps to it, never longer than the biased value, and is held at its ends. */\n shutterCapability?: ResolvedGenericEquipmentExposureCapabilities[\"shutter\"];\n}\n\nexport interface AutoIsoSlowestShutterEstimate {\n /** The exposure time whose shake blur equals the budget, before the bias and the grid. */\n derivedShutterSeconds: number;\n fasterByStops: number;\n /** derivedShutterSeconds · 2^−fasterByStops. */\n biasedShutterSeconds: number;\n /** The slowest preferred shutter: the biased value, snapped to `shutterCapability` when given. */\n shutterSeconds: number;\n shutterResolution: {\n kind: \"unconstrained\" | \"continuous\" | \"discrete\";\n selectionPolicy: \"not-longer-than-target\";\n quantized: boolean;\n clamped: false | \"minimum\" | \"maximum\";\n };\n blurBudgetMm: number;\n /** `estimateCameraShakeBlur()`'s stabilized blur at the derived shutter (equal to the budget within rounding). */\n blurAtDerivedShutterMm: number;\n /** `estimateCameraShakeBlur()`'s stabilized blur at `shutterSeconds`. */\n blurAtShutterMm: number;\n /** The same blur lengths in pixels, for a pixel budget. */\n blurBudgetPixels?: number;\n blurAtShutterPixels?: number;\n projectionDistanceMm: number;\n residualMotionFactor: number;\n angularVelocityRadPerSec: { yaw: number; pitch: number };\n stabilizationStopsEquivalent: number;\n}\n\nfunction requirePositive(value: unknown, path: string): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value <= 0) {\n throw new InvalidScientificInputError(path + \" must be finite and greater than zero.\");\n }\n return value;\n}\n\nfunction parseBudget(value: unknown): { budgetMm: number; budgetPixels?: number; pixelPitchMicrometers?: number } {\n requireInputObject(value, \"blurBudget\");\n const record = value as Record<string, unknown>;\n if (record.kind === \"pixels\") {\n rejectUnknownFields(record, [\"kind\", \"pixels\", \"pixelPitchMicrometers\"], \"blurBudget\");\n const pixels = requirePositive(record.pixels, \"blurBudget.pixels\");\n const pitch = requirePositive(record.pixelPitchMicrometers, \"blurBudget.pixelPitchMicrometers\");\n return { budgetMm: (pixels * pitch) / 1000, budgetPixels: pixels, pixelPitchMicrometers: pitch };\n }\n if (record.kind === \"image-diagonal-fraction\") {\n rejectUnknownFields(record, [\"kind\", \"fraction\", \"imageWidthMm\", \"imageHeightMm\"], \"blurBudget\");\n const fraction = requirePositive(record.fraction, \"blurBudget.fraction\");\n if (fraction >= 1) throw new InvalidScientificInputError(\"blurBudget.fraction must be less than 1.\");\n const width = requirePositive(record.imageWidthMm, \"blurBudget.imageWidthMm\");\n const height = requirePositive(record.imageHeightMm, \"blurBudget.imageHeightMm\");\n return { budgetMm: fraction * portableHypot(width, height) };\n }\n throw new InvalidConfigurationError('blurBudget.kind must be \"pixels\" or \"image-diagonal-fraction\".');\n}\n\n/**\n * Solves v·hypot(tan(a·t), tan(b·t)) = budget for t, with a, b ≥ 0 the stabilized angular speeds. One\n * axis: t = atan(budget/v)/a exactly. Two axes: bisection on the strictly increasing blur over\n * (0, π/(2·max(a, b))), a fixed number of halvings (deterministic, to the last bit of the bracket).\n */\nfunction solveShutterForBudget(projectionMm: number, a: number, b: number, budgetMm: number): number {\n const ratio = budgetMm / projectionMm;\n if (a === 0 || b === 0) return Math.atan(ratio) / Math.max(a, b);\n let low = 0, high = Math.PI / 2 / Math.max(a, b);\n for (let i = 0; i < 200 && high - low > 0; i++) {\n const mid = low + (high - low) / 2;\n if (mid <= low || mid >= high) break;\n if (portableHypot(Math.tan(a * mid), Math.tan(b * mid)) < ratio) low = mid;\n else high = mid;\n }\n return low + (high - low) / 2;\n}\n\nfunction snapNotLonger(\n target: number,\n shutter: ResolvedGenericEquipmentExposureCapabilities[\"shutter\"]\n): { shutterSeconds: number; kind: \"continuous\" | \"discrete\"; quantized: boolean; clamped: false | \"minimum\" | \"maximum\" } {\n const grid = shutter.settingGrid;\n if (grid.kind === \"continuous-within-range\") {\n if (target < shutter.minimumSeconds) return { shutterSeconds: shutter.minimumSeconds, kind: \"continuous\", quantized: false, clamped: \"minimum\" };\n if (target > shutter.maximumSeconds) return { shutterSeconds: shutter.maximumSeconds, kind: \"continuous\", quantized: false, clamped: \"maximum\" };\n return { shutterSeconds: target, kind: \"continuous\", quantized: false, clamped: false };\n }\n const values = grid.values;\n const tolerance = (x: number): number => Number.EPSILON * 32 * Math.max(Math.abs(x), 1);\n const exact = values.find((v) => Math.abs(v - target) <= tolerance(Math.max(v, target)));\n if (exact !== undefined) return { shutterSeconds: exact, kind: \"discrete\", quantized: false, clamped: false };\n if (target < values[0]!) return { shutterSeconds: values[0]!, kind: \"discrete\", quantized: true, clamped: \"minimum\" };\n const last = values[values.length - 1]!;\n if (target > last) return { shutterSeconds: last, kind: \"discrete\", quantized: true, clamped: \"maximum\" };\n let selected = values[0]!;\n for (const v of values) if (v <= target) selected = v;\n return { shutterSeconds: selected, kind: \"discrete\", quantized: true, clamped: false };\n}\n\n/**\n * Estimates the slowest preferred shutter for Auto ISO from the lens, the sensor and the hand's\n * shake: the exposure time at which `estimateCameraShakeBlur()`'s stabilized blur equals the blur\n * budget, then shifted by a whole-stop bias and optionally snapped to the body's shutter grid (never\n * longer than the biased value). Longer focal lengths give faster shutters; each stabilization stop\n * doubles the time; each bias stop halves (+1) or doubles (−1) it.\n *\n * An approximation: constant angular velocity (a caller profile, or the labelled generic default\n * `GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC`), stabilization as 2^−stops, blur on the optical axis\n * only. The 1/focal-length \"reciprocal rule\" is a rule of thumb and is not used.\n * @param input - EstimateAutoIsoSlowestShutterInput.\n * @returns The derived, biased and snapped shutter with the blur at each, as an approximation result.\n */\nexport function estimateAutoIsoSlowestShutter(\n input: EstimateAutoIsoSlowestShutterInput\n): CalculationResult<AutoIsoSlowestShutterEstimate> {\n requireInputKeys(input, \"estimateAutoIsoSlowestShutter\", [\"focalLengthMm\", \"focusDistanceM\", \"angularVelocityRadPerSec\", \"stabilizationStopsEquivalent\",\n \"blurBudget\", \"fasterByStops\", \"shutterCapability\"]);\n const focalLengthMm = requirePositive(input.focalLengthMm, \"focalLengthMm\");\n requireInputObject(input.angularVelocityRadPerSec, \"angularVelocityRadPerSec\");\n rejectUnknownFields(input.angularVelocityRadPerSec, [\"yaw\", \"pitch\"], \"angularVelocityRadPerSec\");\n const { yaw, pitch } = input.angularVelocityRadPerSec;\n for (const [name, v] of [[\"yaw\", yaw], [\"pitch\", pitch]] as const) {\n if (typeof v !== \"number\" || !Number.isFinite(v)) throw new InvalidScientificInputError(\"angularVelocityRadPerSec.\" + name + \" must be finite.\");\n }\n if (yaw === 0 && pitch === 0) {\n throw new InvalidScientificInputError(\"angularVelocityRadPerSec must be non-zero on at least one axis: without shake no shutter limit follows.\");\n }\n const stops = input.stabilizationStopsEquivalent ?? 0;\n if (typeof stops !== \"number\" || !Number.isFinite(stops) || stops < 0) {\n throw new InvalidScientificInputError(\"stabilizationStopsEquivalent must be finite and greater than or equal to zero.\");\n }\n const bias = input.fasterByStops ?? 0;\n if (typeof bias !== \"number\" || !Number.isInteger(bias) || Math.abs(bias) > AUTO_ISO_SLOWEST_SHUTTER_MAXIMUM_BIAS_STOPS) {\n throw new InvalidScientificInputError(\"fasterByStops must be a whole number of stops from -2 to +2.\");\n }\n const budget = parseBudget(input.blurBudget);\n if (input.focusDistanceM !== undefined) requirePositive(input.focusDistanceM, \"focusDistanceM\");\n if (input.shutterCapability !== undefined) {\n requireInputObject(input.shutterCapability, \"shutterCapability\");\n const s = input.shutterCapability;\n requirePositive(s.minimumSeconds, \"shutterCapability.minimumSeconds\");\n requirePositive(s.maximumSeconds, \"shutterCapability.maximumSeconds\");\n if (s.minimumSeconds > s.maximumSeconds) throw new InvalidScientificInputError(\"shutterCapability.minimumSeconds must not exceed shutterCapability.maximumSeconds.\");\n requireInputObject(s.settingGrid, \"shutterCapability.settingGrid\");\n if (s.settingGrid.kind === \"discrete-values\") {\n const values = s.settingGrid.values;\n if (!Array.isArray(values) || values.length === 0 || values.some((v, i) => typeof v !== \"number\" || !Number.isFinite(v) || v <= 0 || (i > 0 && v <= values[i - 1]!))) {\n throw new InvalidScientificInputError(\"shutterCapability.settingGrid.values must be strictly increasing positive finite seconds.\");\n }\n } else if (s.settingGrid.kind !== \"continuous-within-range\") {\n throw new InvalidConfigurationError(\"shutterCapability.settingGrid.kind is invalid.\");\n }\n }\n\n // The projection distance and the residual factor come from the shake model itself (a probe at a tiny time).\n const shake = (shutterSeconds: number): CameraShakeEstimate => estimateCameraShakeBlur({ focalLengthMm, shutterSeconds, angularVelocityRadPerSec: { yaw, pitch },\n stabilizationStopsEquivalent: stops, ...(input.focusDistanceM === undefined ? {} : { focusDistanceM: input.focusDistanceM }),\n ...(budget.pixelPitchMicrometers === undefined ? {} : { pixelPitchMicrometers: budget.pixelPitchMicrometers }) }).value;\n const probe = shake(Number.MIN_VALUE);\n const r = probe.residualMotionFactor, v = probe.projectionDistanceMm;\n const derived = solveShutterForBudget(v, Math.abs(yaw) * r, Math.abs(pitch) * r, budget.budgetMm);\n const biased = derived * 2 ** -bias;\n if (!Number.isFinite(derived) || derived <= 0 || !Number.isFinite(biased) || biased <= 0) {\n throw new InvalidScientificResultError(\"The derived slowest shutter is not a positive finite time.\");\n }\n const snapped = input.shutterCapability === undefined ? undefined : snapNotLonger(biased, input.shutterCapability);\n const shutterSeconds = snapped?.shutterSeconds ?? biased;\n // estimateCameraShakeBlur rejects a quarter turn or more on an axis of the *unstabilized* motion, which\n // a long biased or clamped time with stabilization can reach; the blur there is not reported.\n const quarterTurnSafe = (t: number): boolean => Math.max(Math.abs(yaw), Math.abs(pitch)) * t < Math.PI / 2;\n if (!quarterTurnSafe(derived) || !quarterTurnSafe(shutterSeconds)) {\n throw new InvalidScientificInputError(\n \"The shake over the slowest shutter would reach a quarter turn (|angular velocity × shutter| ≥ π/2 rad) before stabilization; the constant-velocity model does not apply.\");\n }\n const atDerived = shake(derived).stabilized, atShutter = shake(shutterSeconds).stabilized;\n\n return approximationResult<AutoIsoSlowestShutterEstimate>({\n derivedShutterSeconds: derived,\n fasterByStops: bias,\n biasedShutterSeconds: biased,\n shutterSeconds,\n shutterResolution: snapped === undefined\n ? { kind: \"unconstrained\", selectionPolicy: \"not-longer-than-target\", quantized: false, clamped: false }\n : { kind: snapped.kind, selectionPolicy: \"not-longer-than-target\", quantized: snapped.quantized, clamped: snapped.clamped },\n blurBudgetMm: budget.budgetMm,\n blurAtDerivedShutterMm: atDerived.distanceMm,\n blurAtShutterMm: atShutter.distanceMm,\n ...(budget.budgetPixels === undefined ? {} : { blurBudgetPixels: budget.budgetPixels, blurAtShutterPixels: atShutter.distancePixels! }),\n projectionDistanceMm: v,\n residualMotionFactor: r,\n angularVelocityRadPerSec: { yaw, pitch },\n stabilizationStopsEquivalent: stops\n }, \"camera-shake-blur-budget-slowest-shutter\", AUTO_ISO_SLOWEST_SHUTTER_MODEL_VERSION, [\n \"The slowest shutter is the exposure time at which estimateCameraShakeBlur()'s stabilized on-axis blur equals the blur budget\",\n \"The hand turns the camera at a constant angular velocity during the exposure (a caller-supplied profile)\",\n \"Stabilization attenuates the angular motion by 2^-stops (educational model, not a CIPA DC-011 rating)\",\n \"The whole-stop bias multiplies the derived time by 2^-fasterByStops, after the derivation\",\n \"Snapping to the shutter grid never lengthens the time, so the blur stays within the budget unless held at the body's fastest shutter\",\n \"The 1/focal-length reciprocal rule is a rule of thumb and is not used\",\n \"Roll, translation, off-axis field dependence and subject motion are not modelled\"\n ]);\n}\n"]}
1
+ {"version":3,"file":"auto-iso-slowest-shutter.js","sourceRoot":"","sources":["../../src/exposure/auto-iso-slowest-shutter.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * #504: the automatic slowest preferred shutter for Auto ISO (\"Auto\" minimum shutter speed), derived\n * from the engine's camera-shake model (`estimateCameraShakeBlur()`), not from the 1/focal-length rule\n * of thumb.\n *\n * The camera turns at a constant angular velocity ω (yaw, pitch) during the exposure, reduced by the\n * stabilization factor r = 2^−stops. A still point on the optical axis then moves\n * b(t) = v·hypot(tan(r·ω_yaw·t), tan(r·ω_pitch·t)) on the sensor, where v is the projection distance (the\n * focal length, or the thin-lens image distance with a focus distance). The derived shutter is the\n * exposure time at which b(t) equals the caller's blur budget. b(t) grows strictly with t, so the\n * solution is unique; the whole-stop bias (as in makers' Auto minimum-shutter settings: faster or\n * slower in 1 EV steps) is applied after the derivation, and the optional shutter grid last.\n *\n * This is an approximation: the hand's motion is a constant-velocity profile supplied by the caller,\n * and stabilization stops scale the motion by 2^−stops (the educational model of\n * `estimateCameraShakeBlur()`, not a CIPA DC-011 rating).\n * @see docs/USAGE.md (\"Automatic slowest shutter (#504)\") for the equations, the cited generic\n * hand-shake default and the support limits.\n */\n\nimport { approximationResult, type CalculationResult } from \"../core/calculation-result.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { rejectUnknownFields, requireInputKeys, requireInputObject, requireInputPositiveFinite as requirePositive } from \"../core/record-validation.js\";\nimport { InvalidScientificInputError, InvalidScientificResultError } from \"../core/validation.js\";\nimport { portableHypot } from \"../core/portable-math.js\";\nimport type { ResolvedGenericEquipmentExposureCapabilities } from \"../equipment/exposure-capabilities.js\";\nimport { estimateCameraShakeBlur, type CameraShakeEstimate } from \"../stabilization/camera-shake.js\";\n\nexport const AUTO_ISO_SLOWEST_SHUTTER_MODEL_VERSION = \"1.0.0\" as const;\n\n/**\n * A generic handheld angular velocity, labelled as a default and not a measurement of any camera or\n * photographer: 4.39 °/s (≈ 0.0766 rad/s), the linear camera-motion rate Xiao, Farrell, Catrysse and\n * Wandell use for \"an average user\" holding \"a typical 100g mobile handset with two hands\", from their\n * earlier gyroscope measurements (F. Xiao, J. Farrell, P. Catrysse, B. Wandell, \"Mobile imaging: the\n * big challenge of the small pixel\", Proc. SPIE 7250, Digital Photography V, 2009, §2; author copy:\n * https://web.stanford.edu/~wandell/data/papers/2009-CameraShake-Xiao.pdf; they cite F. Xiao et al.,\n * \"Camera-motion and mobile imaging\", Proc. SPIE 6502, 2007). The value is one rate magnitude; it is\n * placed on the yaw axis (for small angles the blur length does not depend on the direction). A\n * heavier camera, another grip or another photographer moves differently: pass your own profile.\n */\nexport const GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC: Readonly<{ yaw: number; pitch: number }> =\n /* @__PURE__ */ Object.freeze({ yaw: (4.39 * Math.PI) / 180, pitch: 0 });\n\n/** The largest whole-stop bias accepted, either way (makers offer up to 2 stops faster or slower). */\nexport const AUTO_ISO_SLOWEST_SHUTTER_MAXIMUM_BIAS_STOPS = 2 as const;\n\n/** The blur allowed during the exposure, on the sensor. */\nexport type AutoIsoSlowestShutterBlurBudget =\n | {\n /** The blur trace length in pixels of the given pitch. */\n kind: \"pixels\";\n pixels: number;\n pixelPitchMicrometers: number;\n }\n | {\n /** The blur trace length as a fraction of the image diagonal (width × height in millimetres). */\n kind: \"image-diagonal-fraction\";\n fraction: number;\n imageWidthMm: number;\n imageHeightMm: number;\n };\n\nexport interface EstimateAutoIsoSlowestShutterInput {\n focalLengthMm: number;\n /** Optional focus distance in metres: the thin-lens image distance becomes the projection distance, as in `estimateCameraShakeBlur()`. */\n focusDistanceM?: number;\n /**\n * The hand's constant angular velocity in radians per second, the explicit shake profile (signs as in\n * `estimateCameraShakeBlur()`; only the magnitude matters here). At least one axis must be non-zero.\n * `GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC` is a cited generic default, not a measurement of you.\n */\n angularVelocityRadPerSec: { yaw: number; pitch: number };\n /** Stabilization in equivalent stops (≥ 0); the motion is scaled by 2^−stops. Default 0. */\n stabilizationStopsEquivalent?: number;\n blurBudget: AutoIsoSlowestShutterBlurBudget;\n /**\n * The bias in whole stops, applied after the derivation: +1 is one stop faster (half the time), −1 one\n * stop slower (twice the time). An integer from −2 to +2. Default 0.\n */\n fasterByStops?: number;\n /** Optional: the body's resolved shutter capability. The biased shutter then snaps to it, never longer than the biased value, and is held at its ends. */\n shutterCapability?: ResolvedGenericEquipmentExposureCapabilities[\"shutter\"];\n}\n\nexport interface AutoIsoSlowestShutterEstimate {\n /** The exposure time whose shake blur equals the budget, before the bias and the grid. */\n derivedShutterSeconds: number;\n fasterByStops: number;\n /** derivedShutterSeconds · 2^−fasterByStops. */\n biasedShutterSeconds: number;\n /** The slowest preferred shutter: the biased value, snapped to `shutterCapability` when given. */\n shutterSeconds: number;\n shutterResolution: {\n kind: \"unconstrained\" | \"continuous\" | \"discrete\";\n selectionPolicy: \"not-longer-than-target\";\n quantized: boolean;\n clamped: false | \"minimum\" | \"maximum\";\n };\n blurBudgetMm: number;\n /** `estimateCameraShakeBlur()`'s stabilized blur at the derived shutter (equal to the budget within rounding). */\n blurAtDerivedShutterMm: number;\n /** `estimateCameraShakeBlur()`'s stabilized blur at `shutterSeconds`. */\n blurAtShutterMm: number;\n /** The same blur lengths in pixels, for a pixel budget. */\n blurBudgetPixels?: number;\n blurAtShutterPixels?: number;\n projectionDistanceMm: number;\n residualMotionFactor: number;\n angularVelocityRadPerSec: { yaw: number; pitch: number };\n stabilizationStopsEquivalent: number;\n}\n\nfunction parseBudget(value: unknown): { budgetMm: number; budgetPixels?: number; pixelPitchMicrometers?: number } {\n requireInputObject(value, \"blurBudget\");\n const record = value as Record<string, unknown>;\n if (record.kind === \"pixels\") {\n rejectUnknownFields(record, [\"kind\", \"pixels\", \"pixelPitchMicrometers\"], \"blurBudget\");\n const pixels = requirePositive(record.pixels, \"blurBudget.pixels\");\n const pitch = requirePositive(record.pixelPitchMicrometers, \"blurBudget.pixelPitchMicrometers\");\n return { budgetMm: (pixels * pitch) / 1000, budgetPixels: pixels, pixelPitchMicrometers: pitch };\n }\n if (record.kind === \"image-diagonal-fraction\") {\n rejectUnknownFields(record, [\"kind\", \"fraction\", \"imageWidthMm\", \"imageHeightMm\"], \"blurBudget\");\n const fraction = requirePositive(record.fraction, \"blurBudget.fraction\");\n if (fraction >= 1) throw new InvalidScientificInputError(\"blurBudget.fraction must be less than 1.\");\n const width = requirePositive(record.imageWidthMm, \"blurBudget.imageWidthMm\");\n const height = requirePositive(record.imageHeightMm, \"blurBudget.imageHeightMm\");\n return { budgetMm: fraction * portableHypot(width, height) };\n }\n throw new InvalidConfigurationError('blurBudget.kind must be \"pixels\" or \"image-diagonal-fraction\".');\n}\n\n/**\n * Solves v·hypot(tan(a·t), tan(b·t)) = budget for t, with a, b ≥ 0 the stabilized angular speeds. One\n * axis: t = atan(budget/v)/a exactly. Two axes: bisection on the strictly increasing blur over\n * (0, π/(2·max(a, b))), a fixed number of halvings (deterministic, to the last bit of the bracket).\n */\nfunction solveShutterForBudget(projectionMm: number, a: number, b: number, budgetMm: number): number {\n const ratio = budgetMm / projectionMm;\n if (a === 0 || b === 0) return Math.atan(ratio) / Math.max(a, b);\n let low = 0, high = Math.PI / 2 / Math.max(a, b);\n for (let i = 0; i < 200 && high - low > 0; i++) {\n const mid = low + (high - low) / 2;\n if (mid <= low || mid >= high) break;\n if (portableHypot(Math.tan(a * mid), Math.tan(b * mid)) < ratio) low = mid;\n else high = mid;\n }\n return low + (high - low) / 2;\n}\n\nfunction snapNotLonger(\n target: number,\n shutter: ResolvedGenericEquipmentExposureCapabilities[\"shutter\"]\n): { shutterSeconds: number; kind: \"continuous\" | \"discrete\"; quantized: boolean; clamped: false | \"minimum\" | \"maximum\" } {\n const grid = shutter.settingGrid;\n if (grid.kind === \"continuous-within-range\") {\n if (target < shutter.minimumSeconds) return { shutterSeconds: shutter.minimumSeconds, kind: \"continuous\", quantized: false, clamped: \"minimum\" };\n if (target > shutter.maximumSeconds) return { shutterSeconds: shutter.maximumSeconds, kind: \"continuous\", quantized: false, clamped: \"maximum\" };\n return { shutterSeconds: target, kind: \"continuous\", quantized: false, clamped: false };\n }\n const values = grid.values;\n const tolerance = (x: number): number => Number.EPSILON * 32 * Math.max(Math.abs(x), 1);\n const exact = values.find((v) => Math.abs(v - target) <= tolerance(Math.max(v, target)));\n if (exact !== undefined) return { shutterSeconds: exact, kind: \"discrete\", quantized: false, clamped: false };\n if (target < values[0]!) return { shutterSeconds: values[0]!, kind: \"discrete\", quantized: true, clamped: \"minimum\" };\n const last = values[values.length - 1]!;\n if (target > last) return { shutterSeconds: last, kind: \"discrete\", quantized: true, clamped: \"maximum\" };\n let selected = values[0]!;\n for (const v of values) if (v <= target) selected = v;\n return { shutterSeconds: selected, kind: \"discrete\", quantized: true, clamped: false };\n}\n\n/**\n * Estimates the slowest preferred shutter for Auto ISO from the lens, the sensor and the hand's\n * shake: the exposure time at which `estimateCameraShakeBlur()`'s stabilized blur equals the blur\n * budget, then shifted by a whole-stop bias and optionally snapped to the body's shutter grid (never\n * longer than the biased value). Longer focal lengths give faster shutters; each stabilization stop\n * doubles the time; each bias stop halves (+1) or doubles (−1) it.\n *\n * An approximation: constant angular velocity (a caller profile, or the labelled generic default\n * `GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC`), stabilization as 2^−stops, blur on the optical axis\n * only. The 1/focal-length \"reciprocal rule\" is a rule of thumb and is not used.\n * @param input - EstimateAutoIsoSlowestShutterInput.\n * @returns The derived, biased and snapped shutter with the blur at each, as an approximation result.\n */\nexport function estimateAutoIsoSlowestShutter(\n input: EstimateAutoIsoSlowestShutterInput\n): CalculationResult<AutoIsoSlowestShutterEstimate> {\n requireInputKeys(input, \"estimateAutoIsoSlowestShutter\", [\"focalLengthMm\", \"focusDistanceM\", \"angularVelocityRadPerSec\", \"stabilizationStopsEquivalent\",\n \"blurBudget\", \"fasterByStops\", \"shutterCapability\"]);\n const focalLengthMm = requirePositive(input.focalLengthMm, \"focalLengthMm\");\n requireInputObject(input.angularVelocityRadPerSec, \"angularVelocityRadPerSec\");\n rejectUnknownFields(input.angularVelocityRadPerSec, [\"yaw\", \"pitch\"], \"angularVelocityRadPerSec\");\n const { yaw, pitch } = input.angularVelocityRadPerSec;\n for (const [name, v] of [[\"yaw\", yaw], [\"pitch\", pitch]] as const) {\n if (typeof v !== \"number\" || !Number.isFinite(v)) throw new InvalidScientificInputError(\"angularVelocityRadPerSec.\" + name + \" must be finite.\");\n }\n if (yaw === 0 && pitch === 0) {\n throw new InvalidScientificInputError(\"angularVelocityRadPerSec must be non-zero on at least one axis: without shake no shutter limit follows.\");\n }\n const stops = input.stabilizationStopsEquivalent ?? 0;\n if (typeof stops !== \"number\" || !Number.isFinite(stops) || stops < 0) {\n throw new InvalidScientificInputError(\"stabilizationStopsEquivalent must be finite and greater than or equal to zero.\");\n }\n const bias = input.fasterByStops ?? 0;\n if (typeof bias !== \"number\" || !Number.isInteger(bias) || Math.abs(bias) > AUTO_ISO_SLOWEST_SHUTTER_MAXIMUM_BIAS_STOPS) {\n throw new InvalidScientificInputError(\"fasterByStops must be a whole number of stops from -2 to +2.\");\n }\n const budget = parseBudget(input.blurBudget);\n if (input.focusDistanceM !== undefined) requirePositive(input.focusDistanceM, \"focusDistanceM\");\n if (input.shutterCapability !== undefined) {\n requireInputObject(input.shutterCapability, \"shutterCapability\");\n const s = input.shutterCapability;\n requirePositive(s.minimumSeconds, \"shutterCapability.minimumSeconds\");\n requirePositive(s.maximumSeconds, \"shutterCapability.maximumSeconds\");\n if (s.minimumSeconds > s.maximumSeconds) throw new InvalidScientificInputError(\"shutterCapability.minimumSeconds must not exceed shutterCapability.maximumSeconds.\");\n requireInputObject(s.settingGrid, \"shutterCapability.settingGrid\");\n if (s.settingGrid.kind === \"discrete-values\") {\n const values = s.settingGrid.values;\n if (!Array.isArray(values) || values.length === 0 || values.some((v, i) => typeof v !== \"number\" || !Number.isFinite(v) || v <= 0 || (i > 0 && v <= values[i - 1]!))) {\n throw new InvalidScientificInputError(\"shutterCapability.settingGrid.values must be strictly increasing positive finite seconds.\");\n }\n } else if (s.settingGrid.kind !== \"continuous-within-range\") {\n throw new InvalidConfigurationError(\"shutterCapability.settingGrid.kind is invalid.\");\n }\n }\n\n // The projection distance and the residual factor come from the shake model itself (a probe at a tiny time).\n const shake = (shutterSeconds: number): CameraShakeEstimate => estimateCameraShakeBlur({ focalLengthMm, shutterSeconds, angularVelocityRadPerSec: { yaw, pitch },\n stabilizationStopsEquivalent: stops, ...(input.focusDistanceM === undefined ? {} : { focusDistanceM: input.focusDistanceM }),\n ...(budget.pixelPitchMicrometers === undefined ? {} : { pixelPitchMicrometers: budget.pixelPitchMicrometers }) }).value;\n const probe = shake(Number.MIN_VALUE);\n const r = probe.residualMotionFactor, v = probe.projectionDistanceMm;\n const derived = solveShutterForBudget(v, Math.abs(yaw) * r, Math.abs(pitch) * r, budget.budgetMm);\n const biased = derived * 2 ** -bias;\n if (!Number.isFinite(derived) || derived <= 0 || !Number.isFinite(biased) || biased <= 0) {\n throw new InvalidScientificResultError(\"The derived slowest shutter is not a positive finite time.\");\n }\n const snapped = input.shutterCapability === undefined ? undefined : snapNotLonger(biased, input.shutterCapability);\n const shutterSeconds = snapped?.shutterSeconds ?? biased;\n // estimateCameraShakeBlur rejects a quarter turn or more on an axis of the *unstabilized* motion, which\n // a long biased or clamped time with stabilization can reach; the blur there is not reported.\n const quarterTurnSafe = (t: number): boolean => Math.max(Math.abs(yaw), Math.abs(pitch)) * t < Math.PI / 2;\n if (!quarterTurnSafe(derived) || !quarterTurnSafe(shutterSeconds)) {\n throw new InvalidScientificInputError(\n \"The shake over the slowest shutter would reach a quarter turn (|angular velocity × shutter| ≥ π/2 rad) before stabilization; the constant-velocity model does not apply.\");\n }\n const atDerived = shake(derived).stabilized, atShutter = shake(shutterSeconds).stabilized;\n\n return approximationResult<AutoIsoSlowestShutterEstimate>({\n derivedShutterSeconds: derived,\n fasterByStops: bias,\n biasedShutterSeconds: biased,\n shutterSeconds,\n shutterResolution: snapped === undefined\n ? { kind: \"unconstrained\", selectionPolicy: \"not-longer-than-target\", quantized: false, clamped: false }\n : { kind: snapped.kind, selectionPolicy: \"not-longer-than-target\", quantized: snapped.quantized, clamped: snapped.clamped },\n blurBudgetMm: budget.budgetMm,\n blurAtDerivedShutterMm: atDerived.distanceMm,\n blurAtShutterMm: atShutter.distanceMm,\n ...(budget.budgetPixels === undefined ? {} : { blurBudgetPixels: budget.budgetPixels, blurAtShutterPixels: atShutter.distancePixels! }),\n projectionDistanceMm: v,\n residualMotionFactor: r,\n angularVelocityRadPerSec: { yaw, pitch },\n stabilizationStopsEquivalent: stops\n }, \"camera-shake-blur-budget-slowest-shutter\", AUTO_ISO_SLOWEST_SHUTTER_MODEL_VERSION, [\n \"The slowest shutter is the exposure time at which estimateCameraShakeBlur()'s stabilized on-axis blur equals the blur budget\",\n \"The hand turns the camera at a constant angular velocity during the exposure (a caller-supplied profile)\",\n \"Stabilization attenuates the angular motion by 2^-stops (educational model, not a CIPA DC-011 rating)\",\n \"The whole-stop bias multiplies the derived time by 2^-fasterByStops, after the derivation\",\n \"Snapping to the shutter grid never lengthens the time, so the blur stays within the budget unless held at the body's fastest shutter\",\n \"The 1/focal-length reciprocal rule is a rule of thumb and is not used\",\n \"Roll, translation, off-axis field dependence and subject motion are not modelled\"\n ]);\n}\n"]}
@@ -14,24 +14,9 @@
14
14
  */
15
15
  import { parseEvidenceList } from "../core/evidence-provenance.js";
16
16
  import { InvalidScientificInputError } from "../core/validation.js";
17
- import { requireInputObject, requireInputKeys } from "../core/record-validation.js";
17
+ import { requireInputObject, requireInputKeys, requireInputFinite as requireFiniteSeconds, requireInputFiniteAboveZero as requirePositiveSeconds } from "../core/record-validation.js";
18
18
  import { InvalidConfigurationError } from "../core/configuration-error.js";
19
19
  export const EXPOSURE_DURATION_CONTROL_VERSION = "0.1.0";
20
- function requireFiniteSeconds(value, path) {
21
- if (typeof value !== "number" ||
22
- !Number.isFinite(value)) {
23
- throw new InvalidScientificInputError(path + " must be finite.");
24
- }
25
- return value;
26
- }
27
- function requirePositiveSeconds(value, path) {
28
- const parsed = requireFiniteSeconds(value, path);
29
- if (parsed <= 0) {
30
- throw new InvalidScientificInputError(path +
31
- " must be greater than zero.");
32
- }
33
- return parsed;
34
- }
35
20
  function resolved(kind, durationSeconds) {
36
21
  return {
37
22
  version: EXPOSURE_DURATION_CONTROL_VERSION,
@@ -1 +1 @@
1
- {"version":3,"file":"exposure-duration-control.js","sourceRoot":"","sources":["../../src/exposure/exposure-duration-control.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Shutter-duration control. Turns a control setting (a timed value, Bulb or Time) into a concrete,\n * positive elapsed duration, before the physical exposure is integrated.\n *\n * Bulb and Time are only control behaviours. Neither implies a shutter mechanism, an exposure-boundary\n * layout, a tripod, a stabilization state, long-exposure noise reduction (NR) or a temperature model.\n *\n * The binding function puts a resolved duration into the capture exposure-window input. The existing\n * shutter mechanism and opening/closing schedules are left as they are. Only the nominal duration, in\n * seconds, is supplied.\n * @see docs/USAGE.md for the equations, units, coordinate conventions, known blockers and support\n * limits.\n */\n\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n CalculateCaptureExposureWindowsInput\n} from \"../sensor/exposure-window.js\";\nimport { requireInputObject, requireInputKeys } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport const EXPOSURE_DURATION_CONTROL_VERSION =\n \"0.1.0\" as const;\n\nexport type ExposureDurationControl =\n | {\n kind: \"fixed-duration\";\n durationSeconds: number;\n }\n | {\n kind: \"bulb\";\n timeReference:\n \"control-monotonic-seconds\";\n pressSeconds: number;\n releaseSeconds?: number;\n }\n | {\n kind: \"time\";\n timeReference:\n \"control-monotonic-seconds\";\n startSeconds: number;\n stopSeconds?: number;\n };\n\ninterface ExposureDurationControlResolutionBase {\n version:\n typeof EXPOSURE_DURATION_CONTROL_VERSION;\n controlKind:\n ExposureDurationControl[\"kind\"];\n shutterMechanismInferred: false;\n exposureBoundaryTopologyInferred:\n false;\n tripodSupportInferred: false;\n stabilizationStateInferred: false;\n longExposureNoiseReductionInferred:\n false;\n sensorTemperatureBehaviorInferred:\n false;\n}\n\nexport type ExposureDurationControlResolution =\n | (ExposureDurationControlResolutionBase & {\n status: \"resolved\";\n durationSeconds: number;\n physicalExposureIntegrationAuthorized:\n true;\n })\n | (ExposureDurationControlResolutionBase & {\n status: \"active\";\n durationSeconds: null;\n physicalExposureIntegrationAuthorized:\n false;\n waitingFor:\n | \"bulb-release\"\n | \"time-stop\";\n });\n\nexport interface BindResolvedExposureDurationToCaptureExposureInput {\n resolution:\n Extract<\n ExposureDurationControlResolution,\n { status: \"resolved\" }\n >;\n durationEvidence:\n readonly EvidenceProvenance[];\n exposureWindowInput:\n Omit<\n CalculateCaptureExposureWindowsInput,\n \"nominalExposureDurationSeconds\"\n >;\n}\n\nfunction requireFiniteSeconds(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value)\n ) {\n throw new InvalidScientificInputError(\n path + \" must be finite.\"\n );\n }\n return value;\n}\n\nfunction requirePositiveSeconds(\n value: unknown,\n path: string\n): number {\n const parsed =\n requireFiniteSeconds(\n value,\n path\n );\n if (parsed <= 0) {\n throw new InvalidScientificInputError(\n path +\n \" must be greater than zero.\"\n );\n }\n return parsed;\n}\n\nfunction resolved(\n kind: ExposureDurationControl[\"kind\"],\n durationSeconds: number\n): Extract<\n ExposureDurationControlResolution,\n { status: \"resolved\" }\n> {\n return {\n version:\n EXPOSURE_DURATION_CONTROL_VERSION,\n controlKind: kind,\n status: \"resolved\",\n durationSeconds,\n physicalExposureIntegrationAuthorized:\n true,\n shutterMechanismInferred: false,\n exposureBoundaryTopologyInferred:\n false,\n tripodSupportInferred: false,\n stabilizationStateInferred: false,\n longExposureNoiseReductionInferred:\n false,\n sensorTemperatureBehaviorInferred:\n false\n };\n}\n\nfunction active(\n kind: \"bulb\" | \"time\"\n): Extract<\n ExposureDurationControlResolution,\n { status: \"active\" }\n> {\n return {\n version:\n EXPOSURE_DURATION_CONTROL_VERSION,\n controlKind: kind,\n status: \"active\",\n durationSeconds: null,\n physicalExposureIntegrationAuthorized:\n false,\n waitingFor:\n kind === \"bulb\"\n ? \"bulb-release\"\n : \"time-stop\",\n shutterMechanismInferred: false,\n exposureBoundaryTopologyInferred:\n false,\n tripodSupportInferred: false,\n stabilizationStateInferred: false,\n longExposureNoiseReductionInferred:\n false,\n sensorTemperatureBehaviorInferred:\n false\n };\n}\n\n/**\n * Turns a shutter-duration control setting into a concrete, positive elapsed\n * duration, before the physical exposure is integrated.\n *\n * Bulb and Time are only control behaviours. Neither implies a shutter\n * mechanism, an exposure-boundary layout, a tripod, a stabilization state,\n * long-exposure noise reduction (NR) or a temperature model.\n */\nexport function resolveExposureDurationControl(\n control: ExposureDurationControl\n): ExposureDurationControlResolution {\n requireInputKeys(control, \"resolveExposureDurationControl\", [\"kind\", \"durationSeconds\", \"timeReference\", \"pressSeconds\", \"releaseSeconds\", \"startSeconds\", \"stopSeconds\"]);\n\n if (\n control.kind ===\n \"fixed-duration\"\n ) {\n return resolved(\n \"fixed-duration\",\n requirePositiveSeconds(\n control.durationSeconds,\n \"control.durationSeconds\"\n )\n );\n }\n\n if (\n control.kind === \"bulb\"\n ) {\n if (\n control.timeReference !==\n \"control-monotonic-seconds\"\n ) {\n throw new InvalidConfigurationError(\n 'Bulb control timeReference must be \"control-monotonic-seconds\".'\n );\n }\n const start =\n requireFiniteSeconds(\n control.pressSeconds,\n \"control.pressSeconds\"\n );\n if (\n control.releaseSeconds ===\n undefined\n ) {\n return active(\"bulb\");\n }\n const stop =\n requireFiniteSeconds(\n control.releaseSeconds,\n \"control.releaseSeconds\"\n );\n const duration = stop - start;\n if (duration <= 0) {\n throw new InvalidScientificInputError(\n \"Bulb releaseSeconds must be greater than pressSeconds.\"\n );\n }\n return resolved(\n \"bulb\",\n duration\n );\n }\n\n if (\n control.kind === \"time\"\n ) {\n if (\n control.timeReference !==\n \"control-monotonic-seconds\"\n ) {\n throw new InvalidConfigurationError(\n 'Time control timeReference must be \"control-monotonic-seconds\".'\n );\n }\n const start =\n requireFiniteSeconds(\n control.startSeconds,\n \"control.startSeconds\"\n );\n if (\n control.stopSeconds ===\n undefined\n ) {\n return active(\"time\");\n }\n const stop =\n requireFiniteSeconds(\n control.stopSeconds,\n \"control.stopSeconds\"\n );\n const duration = stop - start;\n if (duration <= 0) {\n throw new InvalidScientificInputError(\n \"Time stopSeconds must be greater than startSeconds.\"\n );\n }\n return resolved(\n \"time\",\n duration\n );\n }\n\n throw new InvalidConfigurationError(\n \"control.kind is invalid.\"\n );\n}\n\n/**\n * Puts a resolved duration into the capture exposure-window input.\n *\n * The existing shutter mechanism and opening/closing schedules are left as\n * they are. Only the nominal duration, in seconds, is supplied.\n */\nexport function bindResolvedExposureDurationToCaptureExposureInput(\n input:\n BindResolvedExposureDurationToCaptureExposureInput\n): CalculateCaptureExposureWindowsInput {\n requireInputKeys(input, \"bindResolvedExposureDurationToCaptureExposureInput\", [\"resolution\", \"durationEvidence\", \"exposureWindowInput\"]);\n const resolution =\n input.resolution;\n\n requireInputObject(resolution, \"resolution input\");\n if (\n resolution.version !==\n EXPOSURE_DURATION_CONTROL_VERSION ||\n resolution.status !==\n \"resolved\" ||\n resolution\n .physicalExposureIntegrationAuthorized !==\n true\n ) {\n throw new InvalidScientificInputError(\n \"resolution must be a resolved exposure-duration control result authorized for physical integration.\"\n );\n }\n\n const durationSeconds =\n requirePositiveSeconds(\n resolution.durationSeconds,\n \"resolution.durationSeconds\"\n );\n\n if (\n resolution.shutterMechanismInferred !==\n false ||\n resolution\n .exposureBoundaryTopologyInferred !==\n false ||\n resolution.tripodSupportInferred !==\n false ||\n resolution\n .stabilizationStateInferred !==\n false ||\n resolution\n .longExposureNoiseReductionInferred !==\n false ||\n resolution\n .sensorTemperatureBehaviorInferred !==\n false\n ) {\n throw new InvalidScientificInputError(\n \"Resolved exposure duration must not carry inferred downstream camera/long-exposure state.\"\n );\n }\n\n return {\n ...input.exposureWindowInput,\n nominalExposureDurationSeconds: {\n value: durationSeconds,\n unit: \"s\",\n evidence: parseEvidenceList(\n input.durationEvidence,\n \"durationEvidence\"\n )\n }\n };\n}\n"]}
1
+ {"version":3,"file":"exposure-duration-control.js","sourceRoot":"","sources":["../../src/exposure/exposure-duration-control.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Shutter-duration control. Turns a control setting (a timed value, Bulb or Time) into a concrete,\n * positive elapsed duration, before the physical exposure is integrated.\n *\n * Bulb and Time are only control behaviours. Neither implies a shutter mechanism, an exposure-boundary\n * layout, a tripod, a stabilization state, long-exposure noise reduction (NR) or a temperature model.\n *\n * The binding function puts a resolved duration into the capture exposure-window input. The existing\n * shutter mechanism and opening/closing schedules are left as they are. Only the nominal duration, in\n * seconds, is supplied.\n * @see docs/USAGE.md for the equations, units, coordinate conventions, known blockers and support\n * limits.\n */\n\nimport {\n parseEvidenceList,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport type {\n CalculateCaptureExposureWindowsInput\n} from \"../sensor/exposure-window.js\";\nimport { requireInputObject, requireInputKeys, requireInputFinite as requireFiniteSeconds, requireInputFiniteAboveZero as requirePositiveSeconds } from \"../core/record-validation.js\";\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\n\nexport const EXPOSURE_DURATION_CONTROL_VERSION =\n \"0.1.0\" as const;\n\nexport type ExposureDurationControl =\n | {\n kind: \"fixed-duration\";\n durationSeconds: number;\n }\n | {\n kind: \"bulb\";\n timeReference:\n \"control-monotonic-seconds\";\n pressSeconds: number;\n releaseSeconds?: number;\n }\n | {\n kind: \"time\";\n timeReference:\n \"control-monotonic-seconds\";\n startSeconds: number;\n stopSeconds?: number;\n };\n\ninterface ExposureDurationControlResolutionBase {\n version:\n typeof EXPOSURE_DURATION_CONTROL_VERSION;\n controlKind:\n ExposureDurationControl[\"kind\"];\n shutterMechanismInferred: false;\n exposureBoundaryTopologyInferred:\n false;\n tripodSupportInferred: false;\n stabilizationStateInferred: false;\n longExposureNoiseReductionInferred:\n false;\n sensorTemperatureBehaviorInferred:\n false;\n}\n\nexport type ExposureDurationControlResolution =\n | (ExposureDurationControlResolutionBase & {\n status: \"resolved\";\n durationSeconds: number;\n physicalExposureIntegrationAuthorized:\n true;\n })\n | (ExposureDurationControlResolutionBase & {\n status: \"active\";\n durationSeconds: null;\n physicalExposureIntegrationAuthorized:\n false;\n waitingFor:\n | \"bulb-release\"\n | \"time-stop\";\n });\n\nexport interface BindResolvedExposureDurationToCaptureExposureInput {\n resolution:\n Extract<\n ExposureDurationControlResolution,\n { status: \"resolved\" }\n >;\n durationEvidence:\n readonly EvidenceProvenance[];\n exposureWindowInput:\n Omit<\n CalculateCaptureExposureWindowsInput,\n \"nominalExposureDurationSeconds\"\n >;\n}\n\nfunction resolved(\n kind: ExposureDurationControl[\"kind\"],\n durationSeconds: number\n): Extract<\n ExposureDurationControlResolution,\n { status: \"resolved\" }\n> {\n return {\n version:\n EXPOSURE_DURATION_CONTROL_VERSION,\n controlKind: kind,\n status: \"resolved\",\n durationSeconds,\n physicalExposureIntegrationAuthorized:\n true,\n shutterMechanismInferred: false,\n exposureBoundaryTopologyInferred:\n false,\n tripodSupportInferred: false,\n stabilizationStateInferred: false,\n longExposureNoiseReductionInferred:\n false,\n sensorTemperatureBehaviorInferred:\n false\n };\n}\n\nfunction active(\n kind: \"bulb\" | \"time\"\n): Extract<\n ExposureDurationControlResolution,\n { status: \"active\" }\n> {\n return {\n version:\n EXPOSURE_DURATION_CONTROL_VERSION,\n controlKind: kind,\n status: \"active\",\n durationSeconds: null,\n physicalExposureIntegrationAuthorized:\n false,\n waitingFor:\n kind === \"bulb\"\n ? \"bulb-release\"\n : \"time-stop\",\n shutterMechanismInferred: false,\n exposureBoundaryTopologyInferred:\n false,\n tripodSupportInferred: false,\n stabilizationStateInferred: false,\n longExposureNoiseReductionInferred:\n false,\n sensorTemperatureBehaviorInferred:\n false\n };\n}\n\n/**\n * Turns a shutter-duration control setting into a concrete, positive elapsed\n * duration, before the physical exposure is integrated.\n *\n * Bulb and Time are only control behaviours. Neither implies a shutter\n * mechanism, an exposure-boundary layout, a tripod, a stabilization state,\n * long-exposure noise reduction (NR) or a temperature model.\n */\nexport function resolveExposureDurationControl(\n control: ExposureDurationControl\n): ExposureDurationControlResolution {\n requireInputKeys(control, \"resolveExposureDurationControl\", [\"kind\", \"durationSeconds\", \"timeReference\", \"pressSeconds\", \"releaseSeconds\", \"startSeconds\", \"stopSeconds\"]);\n\n if (\n control.kind ===\n \"fixed-duration\"\n ) {\n return resolved(\n \"fixed-duration\",\n requirePositiveSeconds(\n control.durationSeconds,\n \"control.durationSeconds\"\n )\n );\n }\n\n if (\n control.kind === \"bulb\"\n ) {\n if (\n control.timeReference !==\n \"control-monotonic-seconds\"\n ) {\n throw new InvalidConfigurationError(\n 'Bulb control timeReference must be \"control-monotonic-seconds\".'\n );\n }\n const start =\n requireFiniteSeconds(\n control.pressSeconds,\n \"control.pressSeconds\"\n );\n if (\n control.releaseSeconds ===\n undefined\n ) {\n return active(\"bulb\");\n }\n const stop =\n requireFiniteSeconds(\n control.releaseSeconds,\n \"control.releaseSeconds\"\n );\n const duration = stop - start;\n if (duration <= 0) {\n throw new InvalidScientificInputError(\n \"Bulb releaseSeconds must be greater than pressSeconds.\"\n );\n }\n return resolved(\n \"bulb\",\n duration\n );\n }\n\n if (\n control.kind === \"time\"\n ) {\n if (\n control.timeReference !==\n \"control-monotonic-seconds\"\n ) {\n throw new InvalidConfigurationError(\n 'Time control timeReference must be \"control-monotonic-seconds\".'\n );\n }\n const start =\n requireFiniteSeconds(\n control.startSeconds,\n \"control.startSeconds\"\n );\n if (\n control.stopSeconds ===\n undefined\n ) {\n return active(\"time\");\n }\n const stop =\n requireFiniteSeconds(\n control.stopSeconds,\n \"control.stopSeconds\"\n );\n const duration = stop - start;\n if (duration <= 0) {\n throw new InvalidScientificInputError(\n \"Time stopSeconds must be greater than startSeconds.\"\n );\n }\n return resolved(\n \"time\",\n duration\n );\n }\n\n throw new InvalidConfigurationError(\n \"control.kind is invalid.\"\n );\n}\n\n/**\n * Puts a resolved duration into the capture exposure-window input.\n *\n * The existing shutter mechanism and opening/closing schedules are left as\n * they are. Only the nominal duration, in seconds, is supplied.\n */\nexport function bindResolvedExposureDurationToCaptureExposureInput(\n input:\n BindResolvedExposureDurationToCaptureExposureInput\n): CalculateCaptureExposureWindowsInput {\n requireInputKeys(input, \"bindResolvedExposureDurationToCaptureExposureInput\", [\"resolution\", \"durationEvidence\", \"exposureWindowInput\"]);\n const resolution =\n input.resolution;\n\n requireInputObject(resolution, \"resolution input\");\n if (\n resolution.version !==\n EXPOSURE_DURATION_CONTROL_VERSION ||\n resolution.status !==\n \"resolved\" ||\n resolution\n .physicalExposureIntegrationAuthorized !==\n true\n ) {\n throw new InvalidScientificInputError(\n \"resolution must be a resolved exposure-duration control result authorized for physical integration.\"\n );\n }\n\n const durationSeconds =\n requirePositiveSeconds(\n resolution.durationSeconds,\n \"resolution.durationSeconds\"\n );\n\n if (\n resolution.shutterMechanismInferred !==\n false ||\n resolution\n .exposureBoundaryTopologyInferred !==\n false ||\n resolution.tripodSupportInferred !==\n false ||\n resolution\n .stabilizationStateInferred !==\n false ||\n resolution\n .longExposureNoiseReductionInferred !==\n false ||\n resolution\n .sensorTemperatureBehaviorInferred !==\n false\n ) {\n throw new InvalidScientificInputError(\n \"Resolved exposure duration must not carry inferred downstream camera/long-exposure state.\"\n );\n }\n\n return {\n ...input.exposureWindowInput,\n nominalExposureDurationSeconds: {\n value: durationSeconds,\n unit: \"s\",\n evidence: parseEvidenceList(\n input.durationEvidence,\n \"durationEvidence\"\n )\n }\n };\n}\n"]}
@@ -17,7 +17,7 @@
17
17
  import { InvalidScientificInputError } from "../core/validation.js";
18
18
  import { EXPOSURE_METER_TARGET_SCHEMA_VERSION } from "./metering-target.js";
19
19
  import { parseExposureProgramLineProfile } from "./program-line.js";
20
- import { rejectUnknownFields, requireInputKeys } from "../core/record-validation.js";
20
+ import { rejectUnknownFields, requireInputKeys, requireTrimmedConfigString as requireNonEmptyString, requireInputPositiveFinite as requirePositiveFinite } from "../core/record-validation.js";
21
21
  import { InvalidConfigurationError } from "../core/configuration-error.js";
22
22
  export const EXPOSURE_MODE_RESOLVER_VERSION = "0.5.0";
23
23
  function requireRecord(value, path) {
@@ -28,15 +28,6 @@ function requireRecord(value, path) {
28
28
  }
29
29
  return value;
30
30
  }
31
- function requirePositiveFinite(value, path) {
32
- if (typeof value !== "number" ||
33
- !Number.isFinite(value) ||
34
- value <= 0) {
35
- throw new InvalidScientificInputError(path +
36
- " must be finite and greater than zero.");
37
- }
38
- return value;
39
- }
40
31
  function settingEquals(left, right) {
41
32
  const scale = Math.max(Math.abs(left), Math.abs(right), 1);
42
33
  return (Math.abs(left - right) <=
@@ -97,13 +88,6 @@ function validateTarget(value) {
97
88
  }
98
89
  return value;
99
90
  }
100
- function requireNonEmptyString(value, path) {
101
- if (typeof value !== "string" ||
102
- value.trim().length === 0) {
103
- throw new InvalidConfigurationError(path + " must be a non-empty string.");
104
- }
105
- return value.trim();
106
- }
107
91
  function validateResolvedGrid(grid, minimum, maximum, path) {
108
92
  requireRecord(grid, path);
109
93
  if (grid.kind ===