@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
@@ -26,50 +26,10 @@ import { InvalidConfigurationError } from "../core/configuration-error.js";
26
26
  import { parseEvidenceList } from "../core/evidence-provenance.js";
27
27
  import { parseSpectralWavelengthBasis } from "../core/spectral.js";
28
28
  import { InvalidScientificInputError } from "../core/validation.js";
29
- import { requireInputKeys, rejectUnknownFields, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS } from "../core/record-validation.js";
29
+ import { requireInputKeys, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, requireConfigFinite as requireFinite, parseConfigScientificStatus as parseScientificStatus, parseConfigLimitations as parseLimitations, requireConfigFiniteAboveZero as requirePositiveFinite, requireConfigFiniteAtLeastZero as requireNonNegativeFinite } from "../core/record-validation.js";
30
30
  export const FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION = "0.1.0";
31
31
  /** Schema that also allows the `linear-polarizer` transmission kind. */
32
32
  export const FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION = "0.2.0";
33
- function requireRecord(value, path, allowedKeys) {
34
- if (typeof value !== "object" ||
35
- value === null ||
36
- Array.isArray(value)) {
37
- throw new InvalidConfigurationError(path + " must be an object.");
38
- }
39
- if (allowedKeys !== null)
40
- rejectUnknownFields(value, allowedKeys, path);
41
- return value;
42
- }
43
- function requireNonEmptyString(value, path) {
44
- if (typeof value !== "string" ||
45
- value.trim().length === 0) {
46
- throw new InvalidConfigurationError(path + " must be a non-empty string.");
47
- }
48
- return value.trim();
49
- }
50
- function requireFinite(value, path) {
51
- if (typeof value !== "number" ||
52
- !Number.isFinite(value)) {
53
- throw new InvalidConfigurationError(path + " must be finite.");
54
- }
55
- return value;
56
- }
57
- function requirePositiveFinite(value, path) {
58
- const parsed = requireFinite(value, path);
59
- if (parsed <= 0) {
60
- throw new InvalidConfigurationError(path +
61
- " must be greater than zero.");
62
- }
63
- return parsed;
64
- }
65
- function requireNonNegativeFinite(value, path) {
66
- const parsed = requireFinite(value, path);
67
- if (parsed < 0) {
68
- throw new InvalidConfigurationError(path +
69
- " must be greater than or equal to zero.");
70
- }
71
- return parsed;
72
- }
73
33
  function requirePassiveTransmission(value, path) {
74
34
  const parsed = requirePositiveFinite(value, path);
75
35
  if (parsed > 1) {
@@ -78,7 +38,7 @@ function requirePassiveTransmission(value, path) {
78
38
  }
79
39
  return parsed;
80
40
  }
81
- function parseRange(value, path) {
41
+ export function parseRange(value, path) {
82
42
  const record = requireRecord(value, path, ["minimum", "maximum"]);
83
43
  const minimum = requirePositiveFinite(record.minimum, path + ".minimum");
84
44
  const maximum = requirePositiveFinite(record.maximum, path + ".maximum");
@@ -91,7 +51,7 @@ function parseRange(value, path) {
91
51
  maximum
92
52
  };
93
53
  }
94
- function parseResolvedBasis(value, path) {
54
+ export function parseResolvedBasis(value, path) {
95
55
  const parsed = parseSpectralWavelengthBasis(value, path);
96
56
  if (parsed === "unspecified") {
97
57
  throw new InvalidConfigurationError(path +
@@ -99,13 +59,6 @@ function parseResolvedBasis(value, path) {
99
59
  }
100
60
  return parsed;
101
61
  }
102
- function parseScientificStatus(value, path) {
103
- if (value !== "calibrated" &&
104
- value !== "approximation") {
105
- throw new InvalidConfigurationError(path + " is invalid.");
106
- }
107
- return value;
108
- }
109
62
  function parseUncertainty(value, path) {
110
63
  const record = requireRecord(value, path, null);
111
64
  rejectUnknownVariantFields(record, "kind", UNCERTAINTY_VARIANT_FIELDS, path);
@@ -130,21 +83,6 @@ function parseUncertainty(value, path) {
130
83
  }
131
84
  throw new InvalidConfigurationError(path + ".kind is invalid.");
132
85
  }
133
- function parseLimitations(value, path) {
134
- if (!Array.isArray(value)) {
135
- throw new InvalidConfigurationError(path + " must be an array.");
136
- }
137
- const parsed = value.map((entry, index) => requireNonEmptyString(entry, path +
138
- "[" +
139
- index +
140
- "]"));
141
- if (new Set(parsed).size !==
142
- parsed.length) {
143
- throw new InvalidConfigurationError(path +
144
- " must not contain duplicates.");
145
- }
146
- return parsed;
147
- }
148
86
  function parseReusableNumericEvidence(value, path) {
149
87
  const evidence = parseEvidenceList(value, path);
150
88
  if (evidence.some((entry) => entry.reuseStatus ===
@@ -1 +1 @@
1
- {"version":3,"file":"front-of-lens-filter.js","sourceRoot":"","sources":["../../src/optics/front-of-lens-filter.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic filters that screw onto the front of the lens.\n *\n * Schema 0.1.0 is explicitly unbranded and unpolarized. It models only passive transmission in front\n * of the lens. It does not cover reflections between stacked filters, flare or ghosting, or filters\n * that change wavelength.\n *\n * Schema 0.2.0 adds one polarizing kind: a linear (partial) polarizer. It has a principal\n * transmittance k1, an extinction ratio k1/k2 and a transmission-axis angle. Polarization is carried\n * as linear Stokes parameters (S0, S1, S2). Filters are applied in order using the Mueller matrix of\n * a linear diattenuator (Gil, arXiv:2002.04105, eqs. 134-135, free; Malus's law: OpenStax University\n * Physics Vol. 3, sec. 1.7, free. Also see: Chipman, Handbook of Optics vol. I ch. 22, 3rd ed., 2010;\n * Goldstein, Polarized Light, 3rd ed., ch. 4-5; Born & Wolf, Principles of Optics, sec. 1.4.2):\n * T = (k1 + k2)/2 + (k1 - k2)/2 * P * cos(2 (psi - theta))\n * Here P is the incident light's degree of linear polarization, psi its polarization angle and theta\n * the filter axis angle.\n * - For P = 1 this is Malus's law: k1 cos^2(psi - theta) + k2 sin^2(psi - theta).\n * - For unpolarized light it is (k1 + k2)/2, about half of k1.\n * Not modelled: circular polarization, retardance (for example the quarter-wave plate in a circular\n * polarizer), dependence on the angle of incidence, and depolarization.\n * @see docs/PHYSICS_FOUNDATION.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport {\n parseSpectralWavelengthBasis,\n type SpectralWavelengthBasis\n} from \"../core/spectral.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, rejectUnknownFields, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS } from \"../core/record-validation.js\";\n\ntype UnknownRecord = Record<string, unknown>;\n\nexport const FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION =\n \"0.1.0\" as const;\n/** Schema that also allows the `linear-polarizer` transmission kind. */\nexport const FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION =\n \"0.2.0\" as const;\n\nexport type FrontOfLensFilterProfileSchemaVersion =\n | typeof FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION\n | typeof FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION;\n\n/** Partially linearly polarized light: degree P in [0, 1] and polarization angle psi. */\nexport interface LinearPolarizationState {\n /** Degree of linear polarization P in [0, 1]; 0 is unpolarized. */\n degreeOfLinearPolarization: number;\n /** Electric-field polarization angle, degrees counter-clockwise from image +X. */\n angleDegrees: number;\n}\n\nexport type FrontOfLensFilterScientificStatus =\n | \"calibrated\"\n | \"approximation\";\n\nexport type FrontOfLensFilterUncertainty =\n | {\n kind: \"relative\";\n fraction: number;\n basis: string;\n }\n | {\n kind: \"not-quantified\";\n limitation: string;\n };\n\nexport interface FrontOfLensFilterSpectralSample {\n wavelengthNanometers: number;\n linearTransmissionFactor: number;\n}\n\nexport type FrontOfLensFilterTransmissionModel =\n | {\n kind: \"neutral-linear-transmission\";\n linearTransmissionFactor:\n EvidenceBackedFact<number>;\n }\n | {\n kind: \"neutral-optical-density\";\n opticalDensityBase10:\n EvidenceBackedFact<number>;\n }\n | {\n kind: \"spectral-transmission\";\n samples:\n EvidenceBackedFact<\n readonly FrontOfLensFilterSpectralSample[]\n >;\n }\n | {\n /** Schema 0.2.0 only. Neutral (wavelength-independent) within the declared range. */\n kind: \"linear-polarizer\";\n /** k1: intensity transmittance for light polarized along the transmission axis, (0, 1]. */\n principalTransmittance:\n EvidenceBackedFact<number>;\n /** k1 / k2. Finite and >= 1. Use a large finite ratio for a near-ideal polarizer. */\n extinctionRatio:\n EvidenceBackedFact<number>;\n /** Transmission-axis angle as mounted, degrees counter-clockwise from image +X. */\n transmissionAxisDegrees: number;\n };\n\nexport interface FrontOfLensFilterProfile {\n schemaVersion:\n FrontOfLensFilterProfileSchemaVersion;\n filterId: string;\n profileVersion: string;\n identityScope:\n \"photivra-generic-unbranded\";\n position: \"front-of-lens\";\n scientificStatus:\n FrontOfLensFilterScientificStatus;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n wavelengthRangeNanometers: {\n minimum: number;\n maximum: number;\n };\n transmission:\n FrontOfLensFilterTransmissionModel;\n uncertainty:\n FrontOfLensFilterUncertainty;\n /** True exactly when the transmission kind is `linear-polarizer` (schema 0.2.0). */\n polarizationModeled: boolean;\n wavelengthChangingBehaviorModeled:\n false;\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\nexport interface ResolveFrontOfLensFilterTransmissionInput {\n profile: FrontOfLensFilterProfile;\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n /** Incident polarization; omitted means unpolarized. */\n incidentPolarization?: LinearPolarizationState;\n}\n\nexport interface ResolvedFrontOfLensFilterTransmission {\n schemaVersion:\n FrontOfLensFilterProfileSchemaVersion;\n filterId: string;\n profileVersion: string;\n identityScope:\n \"photivra-generic-unbranded\";\n position: \"front-of-lens\";\n scientificStatus:\n FrontOfLensFilterScientificStatus;\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n transmissionKind:\n FrontOfLensFilterTransmissionModel[\"kind\"];\n /** Intensity transmittance for the declared incident polarization. */\n linearTransmissionFactor: number;\n attenuationStops: number;\n opticalDensityBase10: number;\n polarizationModeled: boolean;\n incidentPolarization: LinearPolarizationState;\n transmittedPolarization: LinearPolarizationState;\n wavelengthChangingBehaviorModeled:\n false;\n lensTransmissionIncluded: false;\n sensorOpticalStackIncluded: false;\n fieldThroughputIncluded: false;\n uncertainty:\n FrontOfLensFilterUncertainty;\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\nexport interface ComposeFrontOfLensFilterTransmissionInput {\n /** Filters in light-path order (scene side first). */\n filters:\n readonly FrontOfLensFilterProfile[];\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n /** Incident scene polarization; omitted means unpolarized. */\n incidentPolarization?: LinearPolarizationState;\n}\n\nexport interface ComposedFrontOfLensFilterTransmission {\n schemaVersion:\n FrontOfLensFilterProfileSchemaVersion;\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n appliedFilterCount: number;\n components:\n readonly ResolvedFrontOfLensFilterTransmission[];\n combinedLinearTransmissionFactor:\n number;\n combinedAttenuationStops: number;\n scientificStatus:\n FrontOfLensFilterScientificStatus;\n uncertaintyPropagation:\n \"not-propagated\";\n uncertaintyLimitation: string;\n compositionModel:\n | \"independent-multiplicative-unpolarized-transmission-no-inter-filter-reflections\"\n | \"ordered-linear-stokes-mueller-no-inter-filter-reflections\";\n polarizationModeled: boolean;\n incidentPolarization: LinearPolarizationState;\n transmittedPolarization: LinearPolarizationState;\n wavelengthChangingBehaviorModeled:\n false;\n lensTransmissionIncluded: false;\n sensorOpticalStackIncluded: false;\n fieldThroughputIncluded: false;\n}\n\nfunction requireRecord(\n value: unknown,\n path: string,\n allowedKeys: readonly string[] | null\n): UnknownRecord {\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be an object.\"\n );\n }\n if (allowedKeys !== null) rejectUnknownFields(value, allowedKeys, path);\n return value as UnknownRecord;\n}\n\nfunction requireNonEmptyString(\n value: unknown,\n path: string\n): string {\n if (\n typeof value !== \"string\" ||\n value.trim().length === 0\n ) {\n throw new InvalidConfigurationError(\n path + \" must be a non-empty string.\"\n );\n }\n return value.trim();\n}\n\nfunction requireFinite(\n value: unknown,\n path: string\n): number {\n if (\n typeof value !== \"number\" ||\n !Number.isFinite(value)\n ) {\n throw new InvalidConfigurationError(\n path + \" must be finite.\"\n );\n }\n return value;\n}\n\nfunction requirePositiveFinite(\n value: unknown,\n path: string\n): number {\n const parsed = requireFinite(\n value,\n path\n );\n if (parsed <= 0) {\n throw new InvalidConfigurationError(\n path +\n \" must be greater than zero.\"\n );\n }\n return parsed;\n}\n\nfunction requireNonNegativeFinite(\n value: unknown,\n path: string\n): number {\n const parsed = requireFinite(\n value,\n path\n );\n if (parsed < 0) {\n throw new InvalidConfigurationError(\n path +\n \" must be greater than or equal to zero.\"\n );\n }\n return parsed;\n}\n\nfunction requirePassiveTransmission(\n value: unknown,\n path: string\n): number {\n const parsed = requirePositiveFinite(\n value,\n path\n );\n if (parsed > 1) {\n throw new InvalidConfigurationError(\n path +\n \" must be less than or equal to one for a passive filter.\"\n );\n }\n return parsed;\n}\n\nfunction parseRange(\n value: unknown,\n path: string\n): {\n minimum: number;\n maximum: number;\n} {\n const record = requireRecord(\n value,\n path, [\"minimum\", \"maximum\"]\n );\n const minimum =\n requirePositiveFinite(\n record.minimum,\n path + \".minimum\"\n );\n const maximum =\n requirePositiveFinite(\n record.maximum,\n path + \".maximum\"\n );\n if (minimum > maximum) {\n throw new InvalidConfigurationError(\n path +\n \".minimum must be less than or equal to maximum.\"\n );\n }\n return {\n minimum,\n maximum\n };\n}\n\nfunction parseResolvedBasis(\n value: unknown,\n path: string\n): Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n> {\n const parsed =\n parseSpectralWavelengthBasis(\n value,\n path\n );\n if (parsed === \"unspecified\") {\n throw new InvalidConfigurationError(\n path +\n ' must resolve to \"air\" or \"vacuum\".'\n );\n }\n return parsed;\n}\n\nfunction parseScientificStatus(\n value: unknown,\n path: string\n): FrontOfLensFilterScientificStatus {\n if (\n value !== \"calibrated\" &&\n value !== \"approximation\"\n ) {\n throw new InvalidConfigurationError(\n path + \" is invalid.\"\n );\n }\n return value;\n}\n\nfunction parseUncertainty(\n value: unknown,\n path: string\n): FrontOfLensFilterUncertainty {\n const record = requireRecord(\n value,\n path, null\n );\n rejectUnknownVariantFields(record, \"kind\", UNCERTAINTY_VARIANT_FIELDS, path);\n\n if (record.kind === \"relative\") {\n const fraction =\n requireNonNegativeFinite(\n record.fraction,\n path + \".fraction\"\n );\n if (fraction > 1) {\n throw new InvalidConfigurationError(\n path +\n \".fraction must be less than or equal to one.\"\n );\n }\n return {\n kind: \"relative\",\n fraction,\n basis:\n requireNonEmptyString(\n record.basis,\n path + \".basis\"\n )\n };\n }\n\n if (\n record.kind ===\n \"not-quantified\"\n ) {\n return {\n kind: \"not-quantified\",\n limitation:\n requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\nfunction parseLimitations(\n value: unknown,\n path: string\n): readonly string[] {\n if (!Array.isArray(value)) {\n throw new InvalidConfigurationError(\n path + \" must be an array.\"\n );\n }\n const parsed = value.map(\n (entry, index) =>\n requireNonEmptyString(\n entry,\n path +\n \"[\" +\n index +\n \"]\"\n )\n );\n if (\n new Set(parsed).size !==\n parsed.length\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must not contain duplicates.\"\n );\n }\n return parsed;\n}\n\nfunction parseReusableNumericEvidence(\n value: unknown,\n path: string\n): readonly EvidenceProvenance[] {\n const evidence =\n parseEvidenceList(\n value,\n path\n );\n if (\n evidence.some(\n (entry) =>\n entry.reuseStatus ===\n \"factual-reference-only\"\n )\n ) {\n throw new InvalidConfigurationError(\n path +\n \" for embedded numeric filter data must be reusable-data or photivra-owned.\"\n );\n }\n return evidence;\n}\n\nfunction parseSpectralSamples(\n value: unknown,\n path: string\n): readonly FrontOfLensFilterSpectralSample[] {\n if (\n !Array.isArray(value) ||\n value.length < 2\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must contain at least two samples.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n return value.map(\n (entry, index) => {\n const samplePath =\n path +\n \"[\" +\n index +\n \"]\";\n const record =\n requireRecord(\n entry,\n samplePath, [\"wavelengthNanometers\", \"linearTransmissionFactor\"]\n );\n const wavelengthNanometers =\n requirePositiveFinite(\n record.wavelengthNanometers,\n samplePath +\n \".wavelengthNanometers\"\n );\n if (\n wavelengthNanometers <=\n previous\n ) {\n throw new InvalidConfigurationError(\n path +\n \" wavelengths must be strictly increasing.\"\n );\n }\n previous =\n wavelengthNanometers;\n return {\n wavelengthNanometers,\n linearTransmissionFactor:\n requirePassiveTransmission(\n record.linearTransmissionFactor,\n samplePath +\n \".linearTransmissionFactor\"\n )\n };\n }\n );\n}\n\nfunction parseTransmission(\n value: unknown,\n path: string,\n polarizerAllowed: boolean\n): FrontOfLensFilterTransmissionModel {\n const record = requireRecord(\n value,\n path, null\n );\n rejectUnknownVariantFields(record, \"kind\", {\n \"neutral-linear-transmission\": [\"kind\", \"linearTransmissionFactor\"],\n \"neutral-optical-density\": [\"kind\", \"opticalDensityBase10\"],\n \"spectral-transmission\": [\"kind\", \"samples\"],\n \"linear-polarizer\": [\"kind\", \"principalTransmittance\", \"extinctionRatio\", \"transmissionAxisDegrees\"]\n }, path);\n\n if (record.kind === \"linear-polarizer\") {\n if (!polarizerAllowed) {\n throw new InvalidConfigurationError(\n path + '.kind \"linear-polarizer\" requires schemaVersion \"' +\n FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION + '\".'\n );\n }\n const principal = requireRecord(\n record.principalTransmittance,\n path + \".principalTransmittance\", [\"value\", \"evidence\"]\n );\n const extinction = requireRecord(\n record.extinctionRatio,\n path + \".extinctionRatio\", [\"value\", \"evidence\"]\n );\n const ratio = requireFinite(\n extinction.value,\n path + \".extinctionRatio.value\"\n );\n if (ratio < 1) {\n throw new InvalidConfigurationError(\n path + \".extinctionRatio.value must be at least one.\"\n );\n }\n return {\n kind: \"linear-polarizer\",\n principalTransmittance: {\n value: requirePassiveTransmission(\n principal.value,\n path + \".principalTransmittance.value\"\n ),\n evidence: parseEvidenceList(\n principal.evidence,\n path + \".principalTransmittance.evidence\"\n )\n },\n extinctionRatio: {\n value: ratio,\n evidence: parseEvidenceList(\n extinction.evidence,\n path + \".extinctionRatio.evidence\"\n )\n },\n transmissionAxisDegrees: requireFinite(\n record.transmissionAxisDegrees,\n path + \".transmissionAxisDegrees\"\n )\n };\n }\n\n if (\n record.kind ===\n \"neutral-linear-transmission\"\n ) {\n const fact = requireRecord(\n record.linearTransmissionFactor,\n path +\n \".linearTransmissionFactor\", [\"value\", \"evidence\"]\n );\n return {\n kind:\n \"neutral-linear-transmission\",\n linearTransmissionFactor: {\n value:\n requirePassiveTransmission(\n fact.value,\n path +\n \".linearTransmissionFactor.value\"\n ),\n evidence:\n parseEvidenceList(\n fact.evidence,\n path +\n \".linearTransmissionFactor.evidence\"\n )\n }\n };\n }\n\n if (\n record.kind ===\n \"neutral-optical-density\"\n ) {\n const fact = requireRecord(\n record.opticalDensityBase10,\n path +\n \".opticalDensityBase10\", [\"value\", \"evidence\"]\n );\n return {\n kind:\n \"neutral-optical-density\",\n opticalDensityBase10: {\n value:\n requireNonNegativeFinite(\n fact.value,\n path +\n \".opticalDensityBase10.value\"\n ),\n evidence:\n parseEvidenceList(\n fact.evidence,\n path +\n \".opticalDensityBase10.evidence\"\n )\n }\n };\n }\n\n if (\n record.kind ===\n \"spectral-transmission\"\n ) {\n const fact = requireRecord(\n record.samples,\n path + \".samples\", [\"value\", \"evidence\"]\n );\n return {\n kind:\n \"spectral-transmission\",\n samples: {\n value:\n parseSpectralSamples(\n fact.value,\n path +\n \".samples.value\"\n ),\n evidence:\n parseReusableNumericEvidence(\n fact.evidence,\n path +\n \".samples.evidence\"\n )\n }\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\n/**\n * Parses one generic front-of-lens transmission filter.\n *\n * Schema 0.1.0 is explicitly unbranded and unpolarized. It models only\n * passive transmission in front of the lens. It does not cover reflections\n * between stacked filters, polarization, flare or ghosting, or filters that\n * change wavelength.\n */\nexport function parseFrontOfLensFilterProfile(\n value: unknown\n): FrontOfLensFilterProfile {\n const record = requireRecord(\n value,\n \"frontOfLensFilterProfile\", [\"schemaVersion\", \"identityScope\", \"position\", \"wavelengthChangingBehaviorModeled\", \"polarizationModeled\", \"scientificStatus\", \"uncertainty\", \"wavelengthRangeNanometers\", \"transmission\", \"filterId\", \"profileVersion\", \"wavelengthBasis\", \"evidence\", \"limitations\"]\n );\n\n if (\n record.schemaVersion !==\n FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION &&\n record.schemaVersion !==\n FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n 'frontOfLensFilterProfile.schemaVersion must be \"' +\n FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION +\n '\" or \"' +\n FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION +\n '\".'\n );\n }\n const schemaVersion =\n record.schemaVersion as FrontOfLensFilterProfileSchemaVersion;\n if (\n record.identityScope !==\n \"photivra-generic-unbranded\"\n ) {\n throw new InvalidConfigurationError(\n 'frontOfLensFilterProfile.identityScope must be \"photivra-generic-unbranded\".'\n );\n }\n if (\n record.position !==\n \"front-of-lens\"\n ) {\n throw new InvalidConfigurationError(\n 'frontOfLensFilterProfile.position must be \"front-of-lens\".'\n );\n }\n if (\n record.wavelengthChangingBehaviorModeled !==\n false ||\n (schemaVersion ===\n FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION &&\n record.polarizationModeled !== false)\n ) {\n throw new InvalidConfigurationError(\n \"frontOfLensFilterProfile must explicitly keep polarization and wavelength-changing behavior unmodeled in schema 0.1.0.\"\n );\n }\n\n const status =\n parseScientificStatus(\n record.scientificStatus,\n \"frontOfLensFilterProfile.scientificStatus\"\n );\n const uncertainty =\n parseUncertainty(\n record.uncertainty,\n \"frontOfLensFilterProfile.uncertainty\"\n );\n\n if (\n status === \"calibrated\" &&\n uncertainty.kind !==\n \"relative\"\n ) {\n throw new InvalidConfigurationError(\n \"A calibrated filter profile requires quantified relative uncertainty.\"\n );\n }\n\n const range =\n parseRange(\n record.wavelengthRangeNanometers,\n \"frontOfLensFilterProfile.wavelengthRangeNanometers\"\n );\n const transmission =\n parseTransmission(\n record.transmission,\n \"frontOfLensFilterProfile.transmission\",\n schemaVersion ===\n FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION\n );\n const polarizationModeled =\n transmission.kind === \"linear-polarizer\";\n if (record.polarizationModeled !== polarizationModeled) {\n throw new InvalidConfigurationError(\n \"frontOfLensFilterProfile.polarizationModeled must be true exactly for a linear-polarizer transmission.\"\n );\n }\n\n if (\n transmission.kind ===\n \"spectral-transmission\"\n ) {\n const samples =\n transmission.samples.value;\n if (\n samples[0]!\n .wavelengthNanometers >\n range.minimum ||\n samples[\n samples.length - 1\n ]!.wavelengthNanometers <\n range.maximum\n ) {\n throw new InvalidConfigurationError(\n \"Spectral filter samples must cover the declared wavelength range.\"\n );\n }\n }\n\n return {\n schemaVersion,\n filterId:\n requireNonEmptyString(\n record.filterId,\n \"frontOfLensFilterProfile.filterId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n \"frontOfLensFilterProfile.profileVersion\"\n ),\n identityScope:\n \"photivra-generic-unbranded\",\n position:\n \"front-of-lens\",\n scientificStatus:\n status,\n wavelengthBasis:\n parseResolvedBasis(\n record.wavelengthBasis,\n \"frontOfLensFilterProfile.wavelengthBasis\"\n ),\n wavelengthRangeNanometers:\n range,\n transmission,\n uncertainty,\n polarizationModeled,\n wavelengthChangingBehaviorModeled:\n false,\n evidence:\n parseEvidenceList(\n record.evidence,\n \"frontOfLensFilterProfile.evidence\"\n ),\n limitations:\n parseLimitations(\n record.limitations,\n \"frontOfLensFilterProfile.limitations\"\n )\n };\n}\n\nfunction interpolateSpectralTransmission(\n samples:\n readonly FrontOfLensFilterSpectralSample[],\n wavelengthNanometers: number\n): number {\n const first =\n samples[0]!;\n const last =\n samples[\n samples.length - 1\n ]!;\n\n if (\n wavelengthNanometers ===\n first.wavelengthNanometers\n ) {\n return first\n .linearTransmissionFactor;\n }\n\n for (\n let index = 1;\n index < samples.length;\n index += 1\n ) {\n const right =\n samples[index]!;\n if (\n wavelengthNanometers <=\n right.wavelengthNanometers\n ) {\n const left =\n samples[\n index - 1\n ]!;\n if (\n wavelengthNanometers ===\n right.wavelengthNanometers\n ) {\n return right\n .linearTransmissionFactor;\n }\n const phase =\n (wavelengthNanometers -\n left.wavelengthNanometers) /\n (right.wavelengthNanometers -\n left.wavelengthNanometers);\n return (\n left\n .linearTransmissionFactor +\n phase *\n (right\n .linearTransmissionFactor -\n left\n .linearTransmissionFactor)\n );\n }\n }\n\n return last\n .linearTransmissionFactor;\n}\n\nfunction scientificWavelength(\n value: number\n): number {\n if (\n !Number.isFinite(value) ||\n value <= 0\n ) {\n throw new InvalidScientificInputError(\n \"wavelengthNanometers must be finite and greater than zero.\"\n );\n }\n return value;\n}\n\nfunction transmissionEvidence(\n transmission:\n FrontOfLensFilterTransmissionModel\n): readonly EvidenceProvenance[] {\n if (\n transmission.kind ===\n \"neutral-linear-transmission\"\n ) {\n return transmission\n .linearTransmissionFactor\n .evidence;\n }\n if (\n transmission.kind ===\n \"neutral-optical-density\"\n ) {\n return transmission\n .opticalDensityBase10\n .evidence;\n }\n if (transmission.kind === \"linear-polarizer\") {\n return [\n ...transmission.principalTransmittance.evidence,\n ...transmission.extinctionRatio.evidence\n ];\n }\n return transmission\n .samples.evidence;\n}\n\n/** Linear Stokes vector (S0, S1, S2); circular S3 is not modeled. */\ninterface LinearStokes {\n s0: number;\n s1: number;\n s2: number;\n}\n\nconst UNPOLARIZED: LinearPolarizationState = /* @__PURE__ */ Object.freeze({\n degreeOfLinearPolarization: 0,\n angleDegrees: 0\n});\n\nfunction parsePolarizationState(\n value: LinearPolarizationState | undefined,\n path: string\n): LinearPolarizationState {\n if (value === undefined) {\n return { ...UNPOLARIZED };\n }\n if (typeof value !== \"object\" || value === null) {\n throw new InvalidConfigurationError(path + \" must be an object.\");\n }\n const degree = value.degreeOfLinearPolarization;\n const angle = value.angleDegrees;\n if (\n typeof degree !== \"number\" ||\n !Number.isFinite(degree) ||\n degree < 0 ||\n degree > 1\n ) {\n throw new InvalidScientificInputError(\n path + \".degreeOfLinearPolarization must lie in [0, 1].\"\n );\n }\n if (typeof angle !== \"number\" || !Number.isFinite(angle)) {\n throw new InvalidScientificInputError(\n path + \".angleDegrees must be finite.\"\n );\n }\n return { degreeOfLinearPolarization: degree, angleDegrees: angle };\n}\n\nfunction stokesFromState(state: LinearPolarizationState): LinearStokes {\n const twice = (2 * state.angleDegrees * Math.PI) / 180;\n return {\n s0: 1,\n s1: state.degreeOfLinearPolarization * Math.cos(twice),\n s2: state.degreeOfLinearPolarization * Math.sin(twice)\n };\n}\n\nfunction stateFromStokes(stokes: LinearStokes): LinearPolarizationState {\n const polarized = Math.hypot(stokes.s1, stokes.s2);\n const degree = Math.min(1, polarized / stokes.s0);\n if (degree === 0) {\n return { ...UNPOLARIZED };\n }\n let angle = (Math.atan2(stokes.s2, stokes.s1) * 90) / Math.PI;\n if (angle < 0) angle += 180;\n return { degreeOfLinearPolarization: degree, angleDegrees: angle };\n}\n\n/**\n * Mueller matrix of a linear diattenuator (principal intensity transmittances k1 along the axis\n * and k2 across it, axis at theta) applied to a linear Stokes vector.\n */\nfunction applyLinearPolarizer(\n stokes: LinearStokes,\n k1: number,\n k2: number,\n axisDegrees: number\n): LinearStokes {\n const twice = (2 * axisDegrees * Math.PI) / 180;\n const c = Math.cos(twice);\n const sn = Math.sin(twice);\n const sum = (k1 + k2) / 2;\n const difference = (k1 - k2) / 2;\n const geometric = Math.sqrt(k1 * k2);\n return {\n s0: sum * stokes.s0 + difference * (c * stokes.s1 + sn * stokes.s2),\n s1:\n difference * c * stokes.s0 +\n (sum * c * c + geometric * sn * sn) * stokes.s1 +\n (sum - geometric) * c * sn * stokes.s2,\n s2:\n difference * sn * stokes.s0 +\n (sum - geometric) * c * sn * stokes.s1 +\n (sum * sn * sn + geometric * c * c) * stokes.s2\n };\n}\n\ninterface ResolvedComponent {\n resolved: ResolvedFrontOfLensFilterTransmission;\n outgoing: LinearStokes;\n}\n\nfunction resolveComponent(\n input: ResolveFrontOfLensFilterTransmissionInput,\n incoming: LinearStokes\n): ResolvedComponent {\n const profile =\n parseFrontOfLensFilterProfile(\n input.profile\n );\n const wavelength =\n scientificWavelength(\n input.wavelengthNanometers\n );\n\n if (\n input.wavelengthBasis !==\n profile.wavelengthBasis\n ) {\n throw new InvalidScientificInputError(\n \"Filter and requested wavelength bases must match; implicit air/vacuum conversion is not performed.\"\n );\n }\n if (\n wavelength <\n profile\n .wavelengthRangeNanometers\n .minimum ||\n wavelength >\n profile\n .wavelengthRangeNanometers\n .maximum\n ) {\n throw new InvalidScientificInputError(\n \"Requested wavelength lies outside the filter applicability range.\"\n );\n }\n\n let factor: number;\n let outgoing: LinearStokes;\n const transmission =\n profile.transmission;\n\n if (\n transmission.kind ===\n \"linear-polarizer\"\n ) {\n const k1 = transmission.principalTransmittance.value;\n const k2 = k1 / transmission.extinctionRatio.value;\n outgoing = applyLinearPolarizer(\n incoming,\n k1,\n k2,\n transmission.transmissionAxisDegrees\n );\n factor = outgoing.s0 / incoming.s0;\n } else {\n if (\n transmission.kind ===\n \"neutral-linear-transmission\"\n ) {\n factor =\n transmission\n .linearTransmissionFactor\n .value;\n } else if (\n transmission.kind ===\n \"neutral-optical-density\"\n ) {\n factor =\n Math.pow(\n 10,\n -transmission\n .opticalDensityBase10\n .value\n );\n } else {\n factor =\n interpolateSpectralTransmission(\n transmission.samples.value,\n wavelength\n );\n }\n outgoing = {\n s0: incoming.s0 * factor,\n s1: incoming.s1 * factor,\n s2: incoming.s2 * factor\n };\n }\n\n if (\n !Number.isFinite(factor) ||\n factor <= 0 ||\n factor > 1\n ) {\n throw new InvalidScientificInputError(\n \"Resolved passive filter transmission must remain finite and in (0, 1].\"\n );\n }\n\n const attenuationStops =\n -Math.log2(factor);\n const opticalDensityBase10 =\n -Math.log10(factor);\n\n if (\n !Number.isFinite(\n attenuationStops\n ) ||\n !Number.isFinite(\n opticalDensityBase10\n )\n ) {\n throw new InvalidScientificInputError(\n \"Resolved filter attenuation diagnostics must remain finite.\"\n );\n }\n\n const polarizer = transmission.kind === \"linear-polarizer\";\n return {\n outgoing,\n resolved: {\n schemaVersion:\n profile.schemaVersion,\n filterId:\n profile.filterId,\n profileVersion:\n profile.profileVersion,\n identityScope:\n \"photivra-generic-unbranded\",\n position:\n \"front-of-lens\",\n scientificStatus:\n profile.scientificStatus,\n wavelengthNanometers:\n wavelength,\n wavelengthBasis:\n input.wavelengthBasis,\n transmissionKind:\n transmission.kind,\n linearTransmissionFactor:\n factor,\n attenuationStops,\n opticalDensityBase10,\n polarizationModeled:\n polarizer,\n incidentPolarization:\n stateFromStokes(incoming),\n transmittedPolarization:\n stateFromStokes(outgoing),\n wavelengthChangingBehaviorModeled:\n false,\n lensTransmissionIncluded:\n false,\n sensorOpticalStackIncluded:\n false,\n fieldThroughputIncluded:\n false,\n uncertainty:\n profile.uncertainty,\n evidence: [\n ...profile.evidence,\n ...transmissionEvidence(\n transmission\n )\n ],\n limitations: [\n ...profile.limitations,\n \"Front-of-lens transmission is independent of lens aperture geometry, focus/DOF, diffraction and sensor optical-stack response.\",\n polarizer\n ? \"Linear diattenuator on linear Stokes parameters; circular polarization, retardance, angle-of-incidence dependence and inter-filter reflections/flare/ghosting are not modeled.\"\n : \"Polarization and inter-filter reflections/flare/ghosting are not modeled.\"\n ]\n }\n };\n}\n\n/**\n * Resolves one passive filter at one wavelength. Non-polarizing filters scale all Stokes\n * components equally, so their transmission does not depend on the incident polarization.\n */\nexport function resolveFrontOfLensFilterTransmission(\n input:\n ResolveFrontOfLensFilterTransmissionInput\n): ResolvedFrontOfLensFilterTransmission {\n requireInputKeys(input, \"resolveFrontOfLensFilterTransmission\", [\"profile\", \"wavelengthNanometers\", \"wavelengthBasis\", \"incidentPolarization\"]);\n return resolveComponent(\n input,\n stokesFromState(\n parsePolarizationState(\n input.incidentPolarization,\n \"incidentPolarization\"\n )\n )\n ).resolved;\n}\n\n/**\n * Combines several passive front-of-lens filters at one wavelength, in light-path order, and keeps\n * each filter's identity. Without a polarizer the result is the product of the transmissions. With\n * one, linear Stokes parameters pass through the Mueller matrices in order.\n */\nexport function composeFrontOfLensFilterTransmission(\n input:\n ComposeFrontOfLensFilterTransmissionInput\n): ComposedFrontOfLensFilterTransmission {\n requireInputKeys(input, \"composeFrontOfLensFilterTransmission\", [\"filters\", \"wavelengthNanometers\", \"wavelengthBasis\", \"incidentPolarization\"]);\n if (\n !Array.isArray(\n input.filters\n ) ||\n input.filters.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"filters must be a non-empty array.\"\n );\n }\n\n const incidentPolarization = parsePolarizationState(\n input.incidentPolarization,\n \"incidentPolarization\"\n );\n const incident = stokesFromState(incidentPolarization);\n let stokes = incident;\n const components: ResolvedFrontOfLensFilterTransmission[] = [];\n for (const profile of input.filters) {\n const component = resolveComponent(\n {\n profile,\n wavelengthNanometers:\n input.wavelengthNanometers,\n wavelengthBasis:\n input.wavelengthBasis\n },\n stokes\n );\n components.push(component.resolved);\n stokes = component.outgoing;\n }\n\n const combined = stokes.s0 / incident.s0;\n if (\n !Number.isFinite(combined) ||\n combined <= 0 ||\n combined > 1\n ) {\n throw new InvalidScientificInputError(\n \"Combined passive filter transmission must remain finite and in (0, 1].\"\n );\n }\n\n const stops =\n -Math.log2(combined);\n if (!Number.isFinite(stops)) {\n throw new InvalidScientificInputError(\n \"Combined filter attenuation must remain finite.\"\n );\n }\n\n const polarizationModeled = components.some(\n (component) => component.polarizationModeled\n );\n return {\n schemaVersion: polarizationModeled\n ? FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION\n : FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION,\n wavelengthNanometers:\n scientificWavelength(\n input.wavelengthNanometers\n ),\n wavelengthBasis:\n input.wavelengthBasis,\n appliedFilterCount:\n components.length,\n components,\n combinedLinearTransmissionFactor:\n combined,\n combinedAttenuationStops:\n stops,\n scientificStatus:\n components.some(\n (component) =>\n component\n .scientificStatus ===\n \"approximation\"\n )\n ? \"approximation\"\n : \"calibrated\",\n uncertaintyPropagation:\n \"not-propagated\",\n uncertaintyLimitation:\n \"Component filter uncertainties are preserved individually; aggregate numeric uncertainty is not propagated because independence/correlation is not assumed.\",\n compositionModel: polarizationModeled\n ? \"ordered-linear-stokes-mueller-no-inter-filter-reflections\"\n : \"independent-multiplicative-unpolarized-transmission-no-inter-filter-reflections\",\n polarizationModeled,\n incidentPolarization,\n transmittedPolarization:\n stateFromStokes(stokes),\n wavelengthChangingBehaviorModeled:\n false,\n lensTransmissionIncluded:\n false,\n sensorOpticalStackIncluded:\n false,\n fieldThroughputIncluded:\n false\n };\n}\n"]}
1
+ {"version":3,"file":"front-of-lens-filter.js","sourceRoot":"","sources":["../../src/optics/front-of-lens-filter.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EACL,iBAAiB,EAGlB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,4BAA4B,EAE7B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,0BAA0B,EAAE,mBAAmB,IAAI,aAAa,EAAE,0BAA0B,IAAI,qBAAqB,EAAE,mBAAmB,IAAI,aAAa,EAAE,2BAA2B,IAAI,qBAAqB,EAAE,sBAAsB,IAAI,gBAAgB,EAAE,4BAA4B,IAAI,qBAAqB,EAAE,8BAA8B,IAAI,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAE9c,MAAM,CAAC,MAAM,2CAA2C,GACtD,OAAgB,CAAC;AACnB,wEAAwE;AACxE,MAAM,CAAC,MAAM,8CAA8C,GACzD,OAAgB,CAAC;AA+LnB,SAAS,0BAA0B,CACjC,KAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,qBAAqB,CAClC,KAAK,EACL,IAAI,CACL,CAAC;IACF,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,MAAM,IAAI,yBAAyB,CACjC,IAAI;YACF,0DAA0D,CAC7D,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,KAAc,EACd,IAAY;IAKZ,MAAM,MAAM,GAAG,aAAa,CAC1B,KAAK,EACL,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAC7B,CAAC;IACF,MAAM,OAAO,GACX,qBAAqB,CACnB,MAAM,CAAC,OAAO,EACd,IAAI,GAAG,UAAU,CAClB,CAAC;IACJ,MAAM,OAAO,GACX,qBAAqB,CACnB,MAAM,CAAC,OAAO,EACd,IAAI,GAAG,UAAU,CAClB,CAAC;IACJ,IAAI,OAAO,GAAG,OAAO,EAAE,CAAC;QACtB,MAAM,IAAI,yBAAyB,CACjC,IAAI;YACF,iDAAiD,CACpD,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO;QACP,OAAO;KACR,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,KAAc,EACd,IAAY;IAKZ,MAAM,MAAM,GACV,4BAA4B,CAC1B,KAAK,EACL,IAAI,CACL,CAAC;IACJ,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;QAC7B,MAAM,IAAI,yBAAyB,CACjC,IAAI;YACF,qCAAqC,CACxC,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CACvB,KAAc,EACd,IAAY;IAEZ,MAAM,MAAM,GAAG,aAAa,CAC1B,KAAK,EACL,IAAI,EAAE,IAAI,CACX,CAAC;IACF,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,0BAA0B,EAAE,IAAI,CAAC,CAAC;IAE7E,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,MAAM,QAAQ,GACZ,wBAAwB,CACtB,MAAM,CAAC,QAAQ,EACf,IAAI,GAAG,WAAW,CACnB,CAAC;QACJ,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YACjB,MAAM,IAAI,yBAAyB,CACjC,IAAI;gBACF,8CAA8C,CACjD,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,UAAU;YAChB,QAAQ;YACR,KAAK,EACH,qBAAqB,CACnB,MAAM,CAAC,KAAK,EACZ,IAAI,GAAG,QAAQ,CAChB;SACJ,CAAC;IACJ,CAAC;IAED,IACE,MAAM,CAAC,IAAI;QACX,gBAAgB,EAChB,CAAC;QACD,OAAO;YACL,IAAI,EAAE,gBAAgB;YACtB,UAAU,EACR,qBAAqB,CACnB,MAAM,CAAC,UAAU,EACjB,IAAI,GAAG,aAAa,CACrB;SACJ,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,mBAAmB,CAC3B,CAAC;AACJ,CAAC;AAED,SAAS,4BAA4B,CACnC,KAAc,EACd,IAAY;IAEZ,MAAM,QAAQ,GACZ,iBAAiB,CACf,KAAK,EACL,IAAI,CACL,CAAC;IACJ,IACE,QAAQ,CAAC,IAAI,CACX,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,WAAW;QACjB,wBAAwB,CAC3B,EACD,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI;YACF,4EAA4E,CAC/E,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,oBAAoB,CAC3B,KAAc,EACd,IAAY;IAEZ,IACE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACrB,KAAK,CAAC,MAAM,GAAG,CAAC,EAChB,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,IAAI;YACF,qCAAqC,CACxC,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,GACV,MAAM,CAAC,iBAAiB,CAAC;IAC3B,OAAO,KAAK,CAAC,GAAG,CACd,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACf,MAAM,UAAU,GACd,IAAI;YACJ,GAAG;YACH,KAAK;YACL,GAAG,CAAC;QACN,MAAM,MAAM,GACV,aAAa,CACX,KAAK,EACL,UAAU,EAAE,CAAC,sBAAsB,EAAE,0BAA0B,CAAC,CACjE,CAAC;QACJ,MAAM,oBAAoB,GACxB,qBAAqB,CACnB,MAAM,CAAC,oBAAoB,EAC3B,UAAU;YACR,uBAAuB,CAC1B,CAAC;QACJ,IACE,oBAAoB;YACpB,QAAQ,EACR,CAAC;YACD,MAAM,IAAI,yBAAyB,CACjC,IAAI;gBACF,2CAA2C,CAC9C,CAAC;QACJ,CAAC;QACD,QAAQ;YACN,oBAAoB,CAAC;QACvB,OAAO;YACL,oBAAoB;YACpB,wBAAwB,EACtB,0BAA0B,CACxB,MAAM,CAAC,wBAAwB,EAC/B,UAAU;gBACR,2BAA2B,CAC9B;SACJ,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CACxB,KAAc,EACd,IAAY,EACZ,gBAAyB;IAEzB,MAAM,MAAM,GAAG,aAAa,CAC1B,KAAK,EACL,IAAI,EAAE,IAAI,CACX,CAAC;IACF,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE;QACzC,6BAA6B,EAAE,CAAC,MAAM,EAAE,0BAA0B,CAAC;QACnE,yBAAyB,EAAE,CAAC,MAAM,EAAE,sBAAsB,CAAC;QAC3D,uBAAuB,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;QAC5C,kBAAkB,EAAE,CAAC,MAAM,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,yBAAyB,CAAC;KACrG,EAAE,IAAI,CAAC,CAAC;IAET,IAAI,MAAM,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QACvC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,mDAAmD;gBACxD,8CAA8C,GAAG,IAAI,CACxD,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,aAAa,CAC7B,MAAM,CAAC,sBAAsB,EAC7B,IAAI,GAAG,yBAAyB,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,CACxD,CAAC;QACF,MAAM,UAAU,GAAG,aAAa,CAC9B,MAAM,CAAC,eAAe,EACtB,IAAI,GAAG,kBAAkB,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,CACjD,CAAC;QACF,MAAM,KAAK,GAAG,aAAa,CACzB,UAAU,CAAC,KAAK,EAChB,IAAI,GAAG,wBAAwB,CAChC,CAAC;QACF,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,8CAA8C,CACtD,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,kBAAkB;YACxB,sBAAsB,EAAE;gBACtB,KAAK,EAAE,0BAA0B,CAC/B,SAAS,CAAC,KAAK,EACf,IAAI,GAAG,+BAA+B,CACvC;gBACD,QAAQ,EAAE,iBAAiB,CACzB,SAAS,CAAC,QAAQ,EAClB,IAAI,GAAG,kCAAkC,CAC1C;aACF;YACD,eAAe,EAAE;gBACf,KAAK,EAAE,KAAK;gBACZ,QAAQ,EAAE,iBAAiB,CACzB,UAAU,CAAC,QAAQ,EACnB,IAAI,GAAG,2BAA2B,CACnC;aACF;YACD,uBAAuB,EAAE,aAAa,CACpC,MAAM,CAAC,uBAAuB,EAC9B,IAAI,GAAG,0BAA0B,CAClC;SACF,CAAC;IACJ,CAAC;IAED,IACE,MAAM,CAAC,IAAI;QACX,6BAA6B,EAC7B,CAAC;QACD,MAAM,IAAI,GAAG,aAAa,CACxB,MAAM,CAAC,wBAAwB,EAC/B,IAAI;YACF,2BAA2B,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,CACrD,CAAC;QACF,OAAO;YACL,IAAI,EACF,6BAA6B;YAC/B,wBAAwB,EAAE;gBACxB,KAAK,EACH,0BAA0B,CACxB,IAAI,CAAC,KAAK,EACV,IAAI;oBACF,iCAAiC,CACpC;gBACH,QAAQ,EACN,iBAAiB,CACf,IAAI,CAAC,QAAQ,EACb,IAAI;oBACF,oCAAoC,CACvC;aACJ;SACF,CAAC;IACJ,CAAC;IAED,IACE,MAAM,CAAC,IAAI;QACX,yBAAyB,EACzB,CAAC;QACD,MAAM,IAAI,GAAG,aAAa,CACxB,MAAM,CAAC,oBAAoB,EAC3B,IAAI;YACF,uBAAuB,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,CACjD,CAAC;QACF,OAAO;YACL,IAAI,EACF,yBAAyB;YAC3B,oBAAoB,EAAE;gBACpB,KAAK,EACH,wBAAwB,CACtB,IAAI,CAAC,KAAK,EACV,IAAI;oBACF,6BAA6B,CAChC;gBACH,QAAQ,EACN,iBAAiB,CACf,IAAI,CAAC,QAAQ,EACb,IAAI;oBACF,gCAAgC,CACnC;aACJ;SACF,CAAC;IACJ,CAAC;IAED,IACE,MAAM,CAAC,IAAI;QACX,uBAAuB,EACvB,CAAC;QACD,MAAM,IAAI,GAAG,aAAa,CACxB,MAAM,CAAC,OAAO,EACd,IAAI,GAAG,UAAU,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,CACzC,CAAC;QACF,OAAO;YACL,IAAI,EACF,uBAAuB;YACzB,OAAO,EAAE;gBACP,KAAK,EACH,oBAAoB,CAClB,IAAI,CAAC,KAAK,EACV,IAAI;oBACF,gBAAgB,CACnB;gBACH,QAAQ,EACN,4BAA4B,CAC1B,IAAI,CAAC,QAAQ,EACb,IAAI;oBACF,mBAAmB,CACtB;aACJ;SACF,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,yBAAyB,CACjC,IAAI,GAAG,mBAAmB,CAC3B,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,6BAA6B,CAC3C,KAAc;IAEd,MAAM,MAAM,GAAG,aAAa,CAC1B,KAAK,EACL,0BAA0B,EAAE,CAAC,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,mCAAmC,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,aAAa,EAAE,2BAA2B,EAAE,cAAc,EAAE,UAAU,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,UAAU,EAAE,aAAa,CAAC,CACnS,CAAC;IAEF,IACE,MAAM,CAAC,aAAa;QAClB,2CAA2C;QAC7C,MAAM,CAAC,aAAa;YAClB,8CAA8C,EAChD,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,kDAAkD;YAChD,2CAA2C;YAC3C,QAAQ;YACR,8CAA8C;YAC9C,IAAI,CACP,CAAC;IACJ,CAAC;IACD,MAAM,aAAa,GACjB,MAAM,CAAC,aAAsD,CAAC;IAChE,IACE,MAAM,CAAC,aAAa;QACpB,4BAA4B,EAC5B,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,8EAA8E,CAC/E,CAAC;IACJ,CAAC;IACD,IACE,MAAM,CAAC,QAAQ;QACf,eAAe,EACf,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IACD,IACE,MAAM,CAAC,iCAAiC;QACtC,KAAK;QACP,CAAC,aAAa;YACZ,2CAA2C;YAC3C,MAAM,CAAC,mBAAmB,KAAK,KAAK,CAAC,EACvC,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,wHAAwH,CACzH,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GACV,qBAAqB,CACnB,MAAM,CAAC,gBAAgB,EACvB,2CAA2C,CAC5C,CAAC;IACJ,MAAM,WAAW,GACf,gBAAgB,CACd,MAAM,CAAC,WAAW,EAClB,sCAAsC,CACvC,CAAC;IAEJ,IACE,MAAM,KAAK,YAAY;QACvB,WAAW,CAAC,IAAI;YACd,UAAU,EACZ,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,uEAAuE,CACxE,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GACT,UAAU,CACR,MAAM,CAAC,yBAAyB,EAChC,oDAAoD,CACrD,CAAC;IACJ,MAAM,YAAY,GAChB,iBAAiB,CACf,MAAM,CAAC,YAAY,EACnB,uCAAuC,EACvC,aAAa;QACX,8CAA8C,CACjD,CAAC;IACJ,MAAM,mBAAmB,GACvB,YAAY,CAAC,IAAI,KAAK,kBAAkB,CAAC;IAC3C,IAAI,MAAM,CAAC,mBAAmB,KAAK,mBAAmB,EAAE,CAAC;QACvD,MAAM,IAAI,yBAAyB,CACjC,wGAAwG,CACzG,CAAC;IACJ,CAAC;IAED,IACE,YAAY,CAAC,IAAI;QACjB,uBAAuB,EACvB,CAAC;QACD,MAAM,OAAO,GACX,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC;QAC7B,IACE,OAAO,CAAC,CAAC,CAAE;aACR,oBAAoB;YACrB,KAAK,CAAC,OAAO;YACf,OAAO,CACL,OAAO,CAAC,MAAM,GAAG,CAAC,CAClB,CAAC,oBAAoB;gBACrB,KAAK,CAAC,OAAO,EACf,CAAC;YACD,MAAM,IAAI,yBAAyB,CACjC,mEAAmE,CACpE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,aAAa;QACb,QAAQ,EACN,qBAAqB,CACnB,MAAM,CAAC,QAAQ,EACf,mCAAmC,CACpC;QACH,cAAc,EACZ,qBAAqB,CACnB,MAAM,CAAC,cAAc,EACrB,yCAAyC,CAC1C;QACH,aAAa,EACX,4BAA4B;QAC9B,QAAQ,EACN,eAAe;QACjB,gBAAgB,EACd,MAAM;QACR,eAAe,EACb,kBAAkB,CAChB,MAAM,CAAC,eAAe,EACtB,0CAA0C,CAC3C;QACH,yBAAyB,EACvB,KAAK;QACP,YAAY;QACZ,WAAW;QACX,mBAAmB;QACnB,iCAAiC,EAC/B,KAAK;QACP,QAAQ,EACN,iBAAiB,CACf,MAAM,CAAC,QAAQ,EACf,mCAAmC,CACpC;QACH,WAAW,EACT,gBAAgB,CACd,MAAM,CAAC,WAAW,EAClB,sCAAsC,CACvC;KACJ,CAAC;AACJ,CAAC;AAED,SAAS,+BAA+B,CACtC,OAC4C,EAC5C,oBAA4B;IAE5B,MAAM,KAAK,GACT,OAAO,CAAC,CAAC,CAAE,CAAC;IACd,MAAM,IAAI,GACR,OAAO,CACL,OAAO,CAAC,MAAM,GAAG,CAAC,CAClB,CAAC;IAEL,IACE,oBAAoB;QACpB,KAAK,CAAC,oBAAoB,EAC1B,CAAC;QACD,OAAO,KAAK;aACT,wBAAwB,CAAC;IAC9B,CAAC;IAED,KACE,IAAI,KAAK,GAAG,CAAC,EACb,KAAK,GAAG,OAAO,CAAC,MAAM,EACtB,KAAK,IAAI,CAAC,EACV,CAAC;QACD,MAAM,KAAK,GACT,OAAO,CAAC,KAAK,CAAE,CAAC;QAClB,IACE,oBAAoB;YACpB,KAAK,CAAC,oBAAoB,EAC1B,CAAC;YACD,MAAM,IAAI,GACR,OAAO,CACL,KAAK,GAAG,CAAC,CACT,CAAC;YACL,IACE,oBAAoB;gBACpB,KAAK,CAAC,oBAAoB,EAC1B,CAAC;gBACD,OAAO,KAAK;qBACT,wBAAwB,CAAC;YAC9B,CAAC;YACD,MAAM,KAAK,GACT,CAAC,oBAAoB;gBACnB,IAAI,CAAC,oBAAoB,CAAC;gBAC5B,CAAC,KAAK,CAAC,oBAAoB;oBACzB,IAAI,CAAC,oBAAoB,CAAC,CAAC;YAC/B,OAAO,CACL,IAAI;iBACD,wBAAwB;gBAC3B,KAAK;oBACH,CAAC,KAAK;yBACD,wBAAwB;wBAC3B,IAAI;6BACD,wBAAwB,CAAC,CACjC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,IAAI;SACR,wBAAwB,CAAC;AAC9B,CAAC;AAED,SAAS,oBAAoB,CAC3B,KAAa;IAEb,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,KAAK,IAAI,CAAC,EACV,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,oBAAoB,CAC3B,YACoC;IAEpC,IACE,YAAY,CAAC,IAAI;QACjB,6BAA6B,EAC7B,CAAC;QACD,OAAO,YAAY;aAChB,wBAAwB;aACxB,QAAQ,CAAC;IACd,CAAC;IACD,IACE,YAAY,CAAC,IAAI;QACjB,yBAAyB,EACzB,CAAC;QACD,OAAO,YAAY;aAChB,oBAAoB;aACpB,QAAQ,CAAC;IACd,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QAC7C,OAAO;YACL,GAAG,YAAY,CAAC,sBAAsB,CAAC,QAAQ;YAC/C,GAAG,YAAY,CAAC,eAAe,CAAC,QAAQ;SACzC,CAAC;IACJ,CAAC;IACD,OAAO,YAAY;SAChB,OAAO,CAAC,QAAQ,CAAC;AACtB,CAAC;AASD,MAAM,WAAW,GAA4B,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;IACzE,0BAA0B,EAAE,CAAC;IAC7B,YAAY,EAAE,CAAC;CAChB,CAAC,CAAC;AAEH,SAAS,sBAAsB,CAC7B,KAA0C,EAC1C,IAAY;IAEZ,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,GAAG,WAAW,EAAE,CAAC;IAC5B,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,IAAI,yBAAyB,CAAC,IAAI,GAAG,qBAAqB,CAAC,CAAC;IACpE,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,0BAA0B,CAAC;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC;IACjC,IACE,OAAO,MAAM,KAAK,QAAQ;QAC1B,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QACxB,MAAM,GAAG,CAAC;QACV,MAAM,GAAG,CAAC,EACV,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,IAAI,GAAG,iDAAiD,CACzD,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,2BAA2B,CACnC,IAAI,GAAG,+BAA+B,CACvC,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,0BAA0B,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;AACrE,CAAC;AAED,SAAS,eAAe,CAAC,KAA8B;IACrD,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;IACvD,OAAO;QACL,EAAE,EAAE,CAAC;QACL,EAAE,EAAE,KAAK,CAAC,0BAA0B,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;QACtD,EAAE,EAAE,KAAK,CAAC,0BAA0B,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;KACvD,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,MAAoB;IAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IAClD,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACjB,OAAO,EAAE,GAAG,WAAW,EAAE,CAAC;IAC5B,CAAC;IACD,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;IAC9D,IAAI,KAAK,GAAG,CAAC;QAAE,KAAK,IAAI,GAAG,CAAC;IAC5B,OAAO,EAAE,0BAA0B,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;AACrE,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB,CAC3B,MAAoB,EACpB,EAAU,EACV,EAAU,EACV,WAAmB;IAEnB,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;IAChD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3B,MAAM,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,UAAU,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;IACrC,OAAO;QACL,EAAE,EAAE,GAAG,GAAG,MAAM,CAAC,EAAE,GAAG,UAAU,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;QACnE,EAAE,EACA,UAAU,GAAG,CAAC,GAAG,MAAM,CAAC,EAAE;YAC1B,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,SAAS,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE;YAC/C,CAAC,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE;QACxC,EAAE,EACA,UAAU,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE;YAC3B,CAAC,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,EAAE;YACtC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,SAAS,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,EAAE;KAClD,CAAC;AACJ,CAAC;AAOD,SAAS,gBAAgB,CACvB,KAAgD,EAChD,QAAsB;IAEtB,MAAM,OAAO,GACX,6BAA6B,CAC3B,KAAK,CAAC,OAAO,CACd,CAAC;IACJ,MAAM,UAAU,GACd,oBAAoB,CAClB,KAAK,CAAC,oBAAoB,CAC3B,CAAC;IAEJ,IACE,KAAK,CAAC,eAAe;QACrB,OAAO,CAAC,eAAe,EACvB,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,oGAAoG,CACrG,CAAC;IACJ,CAAC;IACD,IACE,UAAU;QACR,OAAO;aACJ,yBAAyB;aACzB,OAAO;QACZ,UAAU;YACR,OAAO;iBACJ,yBAAyB;iBACzB,OAAO,EACZ,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,mEAAmE,CACpE,CAAC;IACJ,CAAC;IAED,IAAI,MAAc,CAAC;IACnB,IAAI,QAAsB,CAAC;IAC3B,MAAM,YAAY,GAChB,OAAO,CAAC,YAAY,CAAC;IAEvB,IACE,YAAY,CAAC,IAAI;QACjB,kBAAkB,EAClB,CAAC;QACD,MAAM,EAAE,GAAG,YAAY,CAAC,sBAAsB,CAAC,KAAK,CAAC;QACrD,MAAM,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC,eAAe,CAAC,KAAK,CAAC;QACnD,QAAQ,GAAG,oBAAoB,CAC7B,QAAQ,EACR,EAAE,EACF,EAAE,EACF,YAAY,CAAC,uBAAuB,CACrC,CAAC;QACF,MAAM,GAAG,QAAQ,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,IACE,YAAY,CAAC,IAAI;YACjB,6BAA6B,EAC7B,CAAC;YACD,MAAM;gBACJ,YAAY;qBACT,wBAAwB;qBACxB,KAAK,CAAC;QACb,CAAC;aAAM,IACL,YAAY,CAAC,IAAI;YACjB,yBAAyB,EACzB,CAAC;YACD,MAAM;gBACJ,IAAI,CAAC,GAAG,CACN,EAAE,EACF,CAAC,YAAY;qBACV,oBAAoB;qBACpB,KAAK,CACT,CAAC;QACN,CAAC;aAAM,CAAC;YACN,MAAM;gBACJ,+BAA+B,CAC7B,YAAY,CAAC,OAAO,CAAC,KAAK,EAC1B,UAAU,CACX,CAAC;QACN,CAAC;QACD,QAAQ,GAAG;YACT,EAAE,EAAE,QAAQ,CAAC,EAAE,GAAG,MAAM;YACxB,EAAE,EAAE,QAAQ,CAAC,EAAE,GAAG,MAAM;YACxB,EAAE,EAAE,QAAQ,CAAC,EAAE,GAAG,MAAM;SACzB,CAAC;IACJ,CAAC;IAED,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QACxB,MAAM,IAAI,CAAC;QACX,MAAM,GAAG,CAAC,EACV,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,wEAAwE,CACzE,CAAC;IACJ,CAAC;IAED,MAAM,gBAAgB,GACpB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,oBAAoB,GACxB,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAEtB,IACE,CAAC,MAAM,CAAC,QAAQ,CACd,gBAAgB,CACjB;QACD,CAAC,MAAM,CAAC,QAAQ,CACd,oBAAoB,CACrB,EACD,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,6DAA6D,CAC9D,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,KAAK,kBAAkB,CAAC;IAC3D,OAAO;QACL,QAAQ;QACR,QAAQ,EAAE;YACR,aAAa,EACX,OAAO,CAAC,aAAa;YACvB,QAAQ,EACN,OAAO,CAAC,QAAQ;YAClB,cAAc,EACZ,OAAO,CAAC,cAAc;YACxB,aAAa,EACX,4BAA4B;YAC9B,QAAQ,EACN,eAAe;YACjB,gBAAgB,EACd,OAAO,CAAC,gBAAgB;YAC1B,oBAAoB,EAClB,UAAU;YACZ,eAAe,EACb,KAAK,CAAC,eAAe;YACvB,gBAAgB,EACd,YAAY,CAAC,IAAI;YACnB,wBAAwB,EACtB,MAAM;YACR,gBAAgB;YAChB,oBAAoB;YACpB,mBAAmB,EACjB,SAAS;YACX,oBAAoB,EAClB,eAAe,CAAC,QAAQ,CAAC;YAC3B,uBAAuB,EACrB,eAAe,CAAC,QAAQ,CAAC;YAC3B,iCAAiC,EAC/B,KAAK;YACP,wBAAwB,EACtB,KAAK;YACP,0BAA0B,EACxB,KAAK;YACP,uBAAuB,EACrB,KAAK;YACP,WAAW,EACT,OAAO,CAAC,WAAW;YACrB,QAAQ,EAAE;gBACR,GAAG,OAAO,CAAC,QAAQ;gBACnB,GAAG,oBAAoB,CACrB,YAAY,CACb;aACF;YACD,WAAW,EAAE;gBACX,GAAG,OAAO,CAAC,WAAW;gBACtB,gIAAgI;gBAChI,SAAS;oBACP,CAAC,CAAC,gLAAgL;oBAClL,CAAC,CAAC,2EAA2E;aAChF;SACF;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oCAAoC,CAClD,KAC2C;IAE3C,gBAAgB,CAAC,KAAK,EAAE,sCAAsC,EAAE,CAAC,SAAS,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAChJ,OAAO,gBAAgB,CACrB,KAAK,EACL,eAAe,CACb,sBAAsB,CACpB,KAAK,CAAC,oBAAoB,EAC1B,sBAAsB,CACvB,CACF,CACF,CAAC,QAAQ,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oCAAoC,CAClD,KAC2C;IAE3C,gBAAgB,CAAC,KAAK,EAAE,sCAAsC,EAAE,CAAC,SAAS,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAChJ,IACE,CAAC,KAAK,CAAC,OAAO,CACZ,KAAK,CAAC,OAAO,CACd;QACD,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAC1B,CAAC;QACD,MAAM,IAAI,yBAAyB,CACjC,oCAAoC,CACrC,CAAC;IACJ,CAAC;IAED,MAAM,oBAAoB,GAAG,sBAAsB,CACjD,KAAK,CAAC,oBAAoB,EAC1B,sBAAsB,CACvB,CAAC;IACF,MAAM,QAAQ,GAAG,eAAe,CAAC,oBAAoB,CAAC,CAAC;IACvD,IAAI,MAAM,GAAG,QAAQ,CAAC;IACtB,MAAM,UAAU,GAA4C,EAAE,CAAC;IAC/D,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,gBAAgB,CAChC;YACE,OAAO;YACP,oBAAoB,EAClB,KAAK,CAAC,oBAAoB;YAC5B,eAAe,EACb,KAAK,CAAC,eAAe;SACxB,EACD,MAAM,CACP,CAAC;QACF,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC;IAC9B,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;IACzC,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC1B,QAAQ,IAAI,CAAC;QACb,QAAQ,GAAG,CAAC,EACZ,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,wEAAwE,CACzE,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GACT,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,2BAA2B,CACnC,iDAAiD,CAClD,CAAC;IACJ,CAAC;IAED,MAAM,mBAAmB,GAAG,UAAU,CAAC,IAAI,CACzC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,mBAAmB,CAC7C,CAAC;IACF,OAAO;QACL,aAAa,EAAE,mBAAmB;YAChC,CAAC,CAAC,8CAA8C;YAChD,CAAC,CAAC,2CAA2C;QAC/C,oBAAoB,EAClB,oBAAoB,CAClB,KAAK,CAAC,oBAAoB,CAC3B;QACH,eAAe,EACb,KAAK,CAAC,eAAe;QACvB,kBAAkB,EAChB,UAAU,CAAC,MAAM;QACnB,UAAU;QACV,gCAAgC,EAC9B,QAAQ;QACV,wBAAwB,EACtB,KAAK;QACP,gBAAgB,EACd,UAAU,CAAC,IAAI,CACb,CAAC,SAAS,EAAE,EAAE,CACZ,SAAS;aACN,gBAAgB;YACnB,eAAe,CAClB;YACC,CAAC,CAAC,eAAe;YACjB,CAAC,CAAC,YAAY;QAClB,sBAAsB,EACpB,gBAAgB;QAClB,qBAAqB,EACnB,6JAA6J;QAC/J,gBAAgB,EAAE,mBAAmB;YACnC,CAAC,CAAC,2DAA2D;YAC7D,CAAC,CAAC,iFAAiF;QACrF,mBAAmB;QACnB,oBAAoB;QACpB,uBAAuB,EACrB,eAAe,CAAC,MAAM,CAAC;QACzB,iCAAiC,EAC/B,KAAK;QACP,wBAAwB,EACtB,KAAK;QACP,0BAA0B,EACxB,KAAK;QACP,uBAAuB,EACrB,KAAK;KACR,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic filters that screw onto the front of the lens.\n *\n * Schema 0.1.0 is explicitly unbranded and unpolarized. It models only passive transmission in front\n * of the lens. It does not cover reflections between stacked filters, flare or ghosting, or filters\n * that change wavelength.\n *\n * Schema 0.2.0 adds one polarizing kind: a linear (partial) polarizer. It has a principal\n * transmittance k1, an extinction ratio k1/k2 and a transmission-axis angle. Polarization is carried\n * as linear Stokes parameters (S0, S1, S2). Filters are applied in order using the Mueller matrix of\n * a linear diattenuator (Gil, arXiv:2002.04105, eqs. 134-135, free; Malus's law: OpenStax University\n * Physics Vol. 3, sec. 1.7, free. Also see: Chipman, Handbook of Optics vol. I ch. 22, 3rd ed., 2010;\n * Goldstein, Polarized Light, 3rd ed., ch. 4-5; Born & Wolf, Principles of Optics, sec. 1.4.2):\n * T = (k1 + k2)/2 + (k1 - k2)/2 * P * cos(2 (psi - theta))\n * Here P is the incident light's degree of linear polarization, psi its polarization angle and theta\n * the filter axis angle.\n * - For P = 1 this is Malus's law: k1 cos^2(psi - theta) + k2 sin^2(psi - theta).\n * - For unpolarized light it is (k1 + k2)/2, about half of k1.\n * Not modelled: circular polarization, retardance (for example the quarter-wave plate in a circular\n * polarizer), dependence on the angle of incidence, and depolarization.\n * @see docs/PHYSICS_FOUNDATION.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport {\n parseEvidenceList,\n type EvidenceBackedFact,\n type EvidenceProvenance\n} from \"../core/evidence-provenance.js\";\nimport {\n parseSpectralWavelengthBasis,\n type SpectralWavelengthBasis\n} from \"../core/spectral.js\";\nimport { InvalidScientificInputError } from \"../core/validation.js\";\nimport { requireInputKeys, rejectUnknownVariantFields, UNCERTAINTY_VARIANT_FIELDS, requireConfigRecord as requireRecord, requireTrimmedConfigString as requireNonEmptyString, requireConfigFinite as requireFinite, parseConfigScientificStatus as parseScientificStatus, parseConfigLimitations as parseLimitations, requireConfigFiniteAboveZero as requirePositiveFinite, requireConfigFiniteAtLeastZero as requireNonNegativeFinite } from \"../core/record-validation.js\";\n\nexport const FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION =\n \"0.1.0\" as const;\n/** Schema that also allows the `linear-polarizer` transmission kind. */\nexport const FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION =\n \"0.2.0\" as const;\n\nexport type FrontOfLensFilterProfileSchemaVersion =\n | typeof FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION\n | typeof FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION;\n\n/** Partially linearly polarized light: degree P in [0, 1] and polarization angle psi. */\nexport interface LinearPolarizationState {\n /** Degree of linear polarization P in [0, 1]; 0 is unpolarized. */\n degreeOfLinearPolarization: number;\n /** Electric-field polarization angle, degrees counter-clockwise from image +X. */\n angleDegrees: number;\n}\n\nexport type FrontOfLensFilterScientificStatus =\n | \"calibrated\"\n | \"approximation\";\n\nexport type FrontOfLensFilterUncertainty =\n | {\n kind: \"relative\";\n fraction: number;\n basis: string;\n }\n | {\n kind: \"not-quantified\";\n limitation: string;\n };\n\nexport interface FrontOfLensFilterSpectralSample {\n wavelengthNanometers: number;\n linearTransmissionFactor: number;\n}\n\nexport type FrontOfLensFilterTransmissionModel =\n | {\n kind: \"neutral-linear-transmission\";\n linearTransmissionFactor:\n EvidenceBackedFact<number>;\n }\n | {\n kind: \"neutral-optical-density\";\n opticalDensityBase10:\n EvidenceBackedFact<number>;\n }\n | {\n kind: \"spectral-transmission\";\n samples:\n EvidenceBackedFact<\n readonly FrontOfLensFilterSpectralSample[]\n >;\n }\n | {\n /** Schema 0.2.0 only. Neutral (wavelength-independent) within the declared range. */\n kind: \"linear-polarizer\";\n /** k1: intensity transmittance for light polarized along the transmission axis, (0, 1]. */\n principalTransmittance:\n EvidenceBackedFact<number>;\n /** k1 / k2. Finite and >= 1. Use a large finite ratio for a near-ideal polarizer. */\n extinctionRatio:\n EvidenceBackedFact<number>;\n /** Transmission-axis angle as mounted, degrees counter-clockwise from image +X. */\n transmissionAxisDegrees: number;\n };\n\nexport interface FrontOfLensFilterProfile {\n schemaVersion:\n FrontOfLensFilterProfileSchemaVersion;\n filterId: string;\n profileVersion: string;\n identityScope:\n \"photivra-generic-unbranded\";\n position: \"front-of-lens\";\n scientificStatus:\n FrontOfLensFilterScientificStatus;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n wavelengthRangeNanometers: {\n minimum: number;\n maximum: number;\n };\n transmission:\n FrontOfLensFilterTransmissionModel;\n uncertainty:\n FrontOfLensFilterUncertainty;\n /** True exactly when the transmission kind is `linear-polarizer` (schema 0.2.0). */\n polarizationModeled: boolean;\n wavelengthChangingBehaviorModeled:\n false;\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\nexport interface ResolveFrontOfLensFilterTransmissionInput {\n profile: FrontOfLensFilterProfile;\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n /** Incident polarization; omitted means unpolarized. */\n incidentPolarization?: LinearPolarizationState;\n}\n\nexport interface ResolvedFrontOfLensFilterTransmission {\n schemaVersion:\n FrontOfLensFilterProfileSchemaVersion;\n filterId: string;\n profileVersion: string;\n identityScope:\n \"photivra-generic-unbranded\";\n position: \"front-of-lens\";\n scientificStatus:\n FrontOfLensFilterScientificStatus;\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n transmissionKind:\n FrontOfLensFilterTransmissionModel[\"kind\"];\n /** Intensity transmittance for the declared incident polarization. */\n linearTransmissionFactor: number;\n attenuationStops: number;\n opticalDensityBase10: number;\n polarizationModeled: boolean;\n incidentPolarization: LinearPolarizationState;\n transmittedPolarization: LinearPolarizationState;\n wavelengthChangingBehaviorModeled:\n false;\n lensTransmissionIncluded: false;\n sensorOpticalStackIncluded: false;\n fieldThroughputIncluded: false;\n uncertainty:\n FrontOfLensFilterUncertainty;\n evidence: readonly EvidenceProvenance[];\n limitations: readonly string[];\n}\n\nexport interface ComposeFrontOfLensFilterTransmissionInput {\n /** Filters in light-path order (scene side first). */\n filters:\n readonly FrontOfLensFilterProfile[];\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n /** Incident scene polarization; omitted means unpolarized. */\n incidentPolarization?: LinearPolarizationState;\n}\n\nexport interface ComposedFrontOfLensFilterTransmission {\n schemaVersion:\n FrontOfLensFilterProfileSchemaVersion;\n wavelengthNanometers: number;\n wavelengthBasis:\n Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n >;\n appliedFilterCount: number;\n components:\n readonly ResolvedFrontOfLensFilterTransmission[];\n combinedLinearTransmissionFactor:\n number;\n combinedAttenuationStops: number;\n scientificStatus:\n FrontOfLensFilterScientificStatus;\n uncertaintyPropagation:\n \"not-propagated\";\n uncertaintyLimitation: string;\n compositionModel:\n | \"independent-multiplicative-unpolarized-transmission-no-inter-filter-reflections\"\n | \"ordered-linear-stokes-mueller-no-inter-filter-reflections\";\n polarizationModeled: boolean;\n incidentPolarization: LinearPolarizationState;\n transmittedPolarization: LinearPolarizationState;\n wavelengthChangingBehaviorModeled:\n false;\n lensTransmissionIncluded: false;\n sensorOpticalStackIncluded: false;\n fieldThroughputIncluded: false;\n}\n\nfunction requirePassiveTransmission(\n value: unknown,\n path: string\n): number {\n const parsed = requirePositiveFinite(\n value,\n path\n );\n if (parsed > 1) {\n throw new InvalidConfigurationError(\n path +\n \" must be less than or equal to one for a passive filter.\"\n );\n }\n return parsed;\n}\n\nexport function parseRange(\n value: unknown,\n path: string\n): {\n minimum: number;\n maximum: number;\n} {\n const record = requireRecord(\n value,\n path, [\"minimum\", \"maximum\"]\n );\n const minimum =\n requirePositiveFinite(\n record.minimum,\n path + \".minimum\"\n );\n const maximum =\n requirePositiveFinite(\n record.maximum,\n path + \".maximum\"\n );\n if (minimum > maximum) {\n throw new InvalidConfigurationError(\n path +\n \".minimum must be less than or equal to maximum.\"\n );\n }\n return {\n minimum,\n maximum\n };\n}\n\nexport function parseResolvedBasis(\n value: unknown,\n path: string\n): Exclude<\n SpectralWavelengthBasis,\n \"unspecified\"\n> {\n const parsed =\n parseSpectralWavelengthBasis(\n value,\n path\n );\n if (parsed === \"unspecified\") {\n throw new InvalidConfigurationError(\n path +\n ' must resolve to \"air\" or \"vacuum\".'\n );\n }\n return parsed;\n}\n\nfunction parseUncertainty(\n value: unknown,\n path: string\n): FrontOfLensFilterUncertainty {\n const record = requireRecord(\n value,\n path, null\n );\n rejectUnknownVariantFields(record, \"kind\", UNCERTAINTY_VARIANT_FIELDS, path);\n\n if (record.kind === \"relative\") {\n const fraction =\n requireNonNegativeFinite(\n record.fraction,\n path + \".fraction\"\n );\n if (fraction > 1) {\n throw new InvalidConfigurationError(\n path +\n \".fraction must be less than or equal to one.\"\n );\n }\n return {\n kind: \"relative\",\n fraction,\n basis:\n requireNonEmptyString(\n record.basis,\n path + \".basis\"\n )\n };\n }\n\n if (\n record.kind ===\n \"not-quantified\"\n ) {\n return {\n kind: \"not-quantified\",\n limitation:\n requireNonEmptyString(\n record.limitation,\n path + \".limitation\"\n )\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\nfunction parseReusableNumericEvidence(\n value: unknown,\n path: string\n): readonly EvidenceProvenance[] {\n const evidence =\n parseEvidenceList(\n value,\n path\n );\n if (\n evidence.some(\n (entry) =>\n entry.reuseStatus ===\n \"factual-reference-only\"\n )\n ) {\n throw new InvalidConfigurationError(\n path +\n \" for embedded numeric filter data must be reusable-data or photivra-owned.\"\n );\n }\n return evidence;\n}\n\nfunction parseSpectralSamples(\n value: unknown,\n path: string\n): readonly FrontOfLensFilterSpectralSample[] {\n if (\n !Array.isArray(value) ||\n value.length < 2\n ) {\n throw new InvalidConfigurationError(\n path +\n \" must contain at least two samples.\"\n );\n }\n\n let previous =\n Number.NEGATIVE_INFINITY;\n return value.map(\n (entry, index) => {\n const samplePath =\n path +\n \"[\" +\n index +\n \"]\";\n const record =\n requireRecord(\n entry,\n samplePath, [\"wavelengthNanometers\", \"linearTransmissionFactor\"]\n );\n const wavelengthNanometers =\n requirePositiveFinite(\n record.wavelengthNanometers,\n samplePath +\n \".wavelengthNanometers\"\n );\n if (\n wavelengthNanometers <=\n previous\n ) {\n throw new InvalidConfigurationError(\n path +\n \" wavelengths must be strictly increasing.\"\n );\n }\n previous =\n wavelengthNanometers;\n return {\n wavelengthNanometers,\n linearTransmissionFactor:\n requirePassiveTransmission(\n record.linearTransmissionFactor,\n samplePath +\n \".linearTransmissionFactor\"\n )\n };\n }\n );\n}\n\nfunction parseTransmission(\n value: unknown,\n path: string,\n polarizerAllowed: boolean\n): FrontOfLensFilterTransmissionModel {\n const record = requireRecord(\n value,\n path, null\n );\n rejectUnknownVariantFields(record, \"kind\", {\n \"neutral-linear-transmission\": [\"kind\", \"linearTransmissionFactor\"],\n \"neutral-optical-density\": [\"kind\", \"opticalDensityBase10\"],\n \"spectral-transmission\": [\"kind\", \"samples\"],\n \"linear-polarizer\": [\"kind\", \"principalTransmittance\", \"extinctionRatio\", \"transmissionAxisDegrees\"]\n }, path);\n\n if (record.kind === \"linear-polarizer\") {\n if (!polarizerAllowed) {\n throw new InvalidConfigurationError(\n path + '.kind \"linear-polarizer\" requires schemaVersion \"' +\n FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION + '\".'\n );\n }\n const principal = requireRecord(\n record.principalTransmittance,\n path + \".principalTransmittance\", [\"value\", \"evidence\"]\n );\n const extinction = requireRecord(\n record.extinctionRatio,\n path + \".extinctionRatio\", [\"value\", \"evidence\"]\n );\n const ratio = requireFinite(\n extinction.value,\n path + \".extinctionRatio.value\"\n );\n if (ratio < 1) {\n throw new InvalidConfigurationError(\n path + \".extinctionRatio.value must be at least one.\"\n );\n }\n return {\n kind: \"linear-polarizer\",\n principalTransmittance: {\n value: requirePassiveTransmission(\n principal.value,\n path + \".principalTransmittance.value\"\n ),\n evidence: parseEvidenceList(\n principal.evidence,\n path + \".principalTransmittance.evidence\"\n )\n },\n extinctionRatio: {\n value: ratio,\n evidence: parseEvidenceList(\n extinction.evidence,\n path + \".extinctionRatio.evidence\"\n )\n },\n transmissionAxisDegrees: requireFinite(\n record.transmissionAxisDegrees,\n path + \".transmissionAxisDegrees\"\n )\n };\n }\n\n if (\n record.kind ===\n \"neutral-linear-transmission\"\n ) {\n const fact = requireRecord(\n record.linearTransmissionFactor,\n path +\n \".linearTransmissionFactor\", [\"value\", \"evidence\"]\n );\n return {\n kind:\n \"neutral-linear-transmission\",\n linearTransmissionFactor: {\n value:\n requirePassiveTransmission(\n fact.value,\n path +\n \".linearTransmissionFactor.value\"\n ),\n evidence:\n parseEvidenceList(\n fact.evidence,\n path +\n \".linearTransmissionFactor.evidence\"\n )\n }\n };\n }\n\n if (\n record.kind ===\n \"neutral-optical-density\"\n ) {\n const fact = requireRecord(\n record.opticalDensityBase10,\n path +\n \".opticalDensityBase10\", [\"value\", \"evidence\"]\n );\n return {\n kind:\n \"neutral-optical-density\",\n opticalDensityBase10: {\n value:\n requireNonNegativeFinite(\n fact.value,\n path +\n \".opticalDensityBase10.value\"\n ),\n evidence:\n parseEvidenceList(\n fact.evidence,\n path +\n \".opticalDensityBase10.evidence\"\n )\n }\n };\n }\n\n if (\n record.kind ===\n \"spectral-transmission\"\n ) {\n const fact = requireRecord(\n record.samples,\n path + \".samples\", [\"value\", \"evidence\"]\n );\n return {\n kind:\n \"spectral-transmission\",\n samples: {\n value:\n parseSpectralSamples(\n fact.value,\n path +\n \".samples.value\"\n ),\n evidence:\n parseReusableNumericEvidence(\n fact.evidence,\n path +\n \".samples.evidence\"\n )\n }\n };\n }\n\n throw new InvalidConfigurationError(\n path + \".kind is invalid.\"\n );\n}\n\n/**\n * Parses one generic front-of-lens transmission filter.\n *\n * Schema 0.1.0 is explicitly unbranded and unpolarized. It models only\n * passive transmission in front of the lens. It does not cover reflections\n * between stacked filters, polarization, flare or ghosting, or filters that\n * change wavelength.\n */\nexport function parseFrontOfLensFilterProfile(\n value: unknown\n): FrontOfLensFilterProfile {\n const record = requireRecord(\n value,\n \"frontOfLensFilterProfile\", [\"schemaVersion\", \"identityScope\", \"position\", \"wavelengthChangingBehaviorModeled\", \"polarizationModeled\", \"scientificStatus\", \"uncertainty\", \"wavelengthRangeNanometers\", \"transmission\", \"filterId\", \"profileVersion\", \"wavelengthBasis\", \"evidence\", \"limitations\"]\n );\n\n if (\n record.schemaVersion !==\n FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION &&\n record.schemaVersion !==\n FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION\n ) {\n throw new InvalidConfigurationError(\n 'frontOfLensFilterProfile.schemaVersion must be \"' +\n FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION +\n '\" or \"' +\n FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION +\n '\".'\n );\n }\n const schemaVersion =\n record.schemaVersion as FrontOfLensFilterProfileSchemaVersion;\n if (\n record.identityScope !==\n \"photivra-generic-unbranded\"\n ) {\n throw new InvalidConfigurationError(\n 'frontOfLensFilterProfile.identityScope must be \"photivra-generic-unbranded\".'\n );\n }\n if (\n record.position !==\n \"front-of-lens\"\n ) {\n throw new InvalidConfigurationError(\n 'frontOfLensFilterProfile.position must be \"front-of-lens\".'\n );\n }\n if (\n record.wavelengthChangingBehaviorModeled !==\n false ||\n (schemaVersion ===\n FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION &&\n record.polarizationModeled !== false)\n ) {\n throw new InvalidConfigurationError(\n \"frontOfLensFilterProfile must explicitly keep polarization and wavelength-changing behavior unmodeled in schema 0.1.0.\"\n );\n }\n\n const status =\n parseScientificStatus(\n record.scientificStatus,\n \"frontOfLensFilterProfile.scientificStatus\"\n );\n const uncertainty =\n parseUncertainty(\n record.uncertainty,\n \"frontOfLensFilterProfile.uncertainty\"\n );\n\n if (\n status === \"calibrated\" &&\n uncertainty.kind !==\n \"relative\"\n ) {\n throw new InvalidConfigurationError(\n \"A calibrated filter profile requires quantified relative uncertainty.\"\n );\n }\n\n const range =\n parseRange(\n record.wavelengthRangeNanometers,\n \"frontOfLensFilterProfile.wavelengthRangeNanometers\"\n );\n const transmission =\n parseTransmission(\n record.transmission,\n \"frontOfLensFilterProfile.transmission\",\n schemaVersion ===\n FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION\n );\n const polarizationModeled =\n transmission.kind === \"linear-polarizer\";\n if (record.polarizationModeled !== polarizationModeled) {\n throw new InvalidConfigurationError(\n \"frontOfLensFilterProfile.polarizationModeled must be true exactly for a linear-polarizer transmission.\"\n );\n }\n\n if (\n transmission.kind ===\n \"spectral-transmission\"\n ) {\n const samples =\n transmission.samples.value;\n if (\n samples[0]!\n .wavelengthNanometers >\n range.minimum ||\n samples[\n samples.length - 1\n ]!.wavelengthNanometers <\n range.maximum\n ) {\n throw new InvalidConfigurationError(\n \"Spectral filter samples must cover the declared wavelength range.\"\n );\n }\n }\n\n return {\n schemaVersion,\n filterId:\n requireNonEmptyString(\n record.filterId,\n \"frontOfLensFilterProfile.filterId\"\n ),\n profileVersion:\n requireNonEmptyString(\n record.profileVersion,\n \"frontOfLensFilterProfile.profileVersion\"\n ),\n identityScope:\n \"photivra-generic-unbranded\",\n position:\n \"front-of-lens\",\n scientificStatus:\n status,\n wavelengthBasis:\n parseResolvedBasis(\n record.wavelengthBasis,\n \"frontOfLensFilterProfile.wavelengthBasis\"\n ),\n wavelengthRangeNanometers:\n range,\n transmission,\n uncertainty,\n polarizationModeled,\n wavelengthChangingBehaviorModeled:\n false,\n evidence:\n parseEvidenceList(\n record.evidence,\n \"frontOfLensFilterProfile.evidence\"\n ),\n limitations:\n parseLimitations(\n record.limitations,\n \"frontOfLensFilterProfile.limitations\"\n )\n };\n}\n\nfunction interpolateSpectralTransmission(\n samples:\n readonly FrontOfLensFilterSpectralSample[],\n wavelengthNanometers: number\n): number {\n const first =\n samples[0]!;\n const last =\n samples[\n samples.length - 1\n ]!;\n\n if (\n wavelengthNanometers ===\n first.wavelengthNanometers\n ) {\n return first\n .linearTransmissionFactor;\n }\n\n for (\n let index = 1;\n index < samples.length;\n index += 1\n ) {\n const right =\n samples[index]!;\n if (\n wavelengthNanometers <=\n right.wavelengthNanometers\n ) {\n const left =\n samples[\n index - 1\n ]!;\n if (\n wavelengthNanometers ===\n right.wavelengthNanometers\n ) {\n return right\n .linearTransmissionFactor;\n }\n const phase =\n (wavelengthNanometers -\n left.wavelengthNanometers) /\n (right.wavelengthNanometers -\n left.wavelengthNanometers);\n return (\n left\n .linearTransmissionFactor +\n phase *\n (right\n .linearTransmissionFactor -\n left\n .linearTransmissionFactor)\n );\n }\n }\n\n return last\n .linearTransmissionFactor;\n}\n\nfunction scientificWavelength(\n value: number\n): number {\n if (\n !Number.isFinite(value) ||\n value <= 0\n ) {\n throw new InvalidScientificInputError(\n \"wavelengthNanometers must be finite and greater than zero.\"\n );\n }\n return value;\n}\n\nfunction transmissionEvidence(\n transmission:\n FrontOfLensFilterTransmissionModel\n): readonly EvidenceProvenance[] {\n if (\n transmission.kind ===\n \"neutral-linear-transmission\"\n ) {\n return transmission\n .linearTransmissionFactor\n .evidence;\n }\n if (\n transmission.kind ===\n \"neutral-optical-density\"\n ) {\n return transmission\n .opticalDensityBase10\n .evidence;\n }\n if (transmission.kind === \"linear-polarizer\") {\n return [\n ...transmission.principalTransmittance.evidence,\n ...transmission.extinctionRatio.evidence\n ];\n }\n return transmission\n .samples.evidence;\n}\n\n/** Linear Stokes vector (S0, S1, S2); circular S3 is not modeled. */\ninterface LinearStokes {\n s0: number;\n s1: number;\n s2: number;\n}\n\nconst UNPOLARIZED: LinearPolarizationState = /* @__PURE__ */ Object.freeze({\n degreeOfLinearPolarization: 0,\n angleDegrees: 0\n});\n\nfunction parsePolarizationState(\n value: LinearPolarizationState | undefined,\n path: string\n): LinearPolarizationState {\n if (value === undefined) {\n return { ...UNPOLARIZED };\n }\n if (typeof value !== \"object\" || value === null) {\n throw new InvalidConfigurationError(path + \" must be an object.\");\n }\n const degree = value.degreeOfLinearPolarization;\n const angle = value.angleDegrees;\n if (\n typeof degree !== \"number\" ||\n !Number.isFinite(degree) ||\n degree < 0 ||\n degree > 1\n ) {\n throw new InvalidScientificInputError(\n path + \".degreeOfLinearPolarization must lie in [0, 1].\"\n );\n }\n if (typeof angle !== \"number\" || !Number.isFinite(angle)) {\n throw new InvalidScientificInputError(\n path + \".angleDegrees must be finite.\"\n );\n }\n return { degreeOfLinearPolarization: degree, angleDegrees: angle };\n}\n\nfunction stokesFromState(state: LinearPolarizationState): LinearStokes {\n const twice = (2 * state.angleDegrees * Math.PI) / 180;\n return {\n s0: 1,\n s1: state.degreeOfLinearPolarization * Math.cos(twice),\n s2: state.degreeOfLinearPolarization * Math.sin(twice)\n };\n}\n\nfunction stateFromStokes(stokes: LinearStokes): LinearPolarizationState {\n const polarized = Math.hypot(stokes.s1, stokes.s2);\n const degree = Math.min(1, polarized / stokes.s0);\n if (degree === 0) {\n return { ...UNPOLARIZED };\n }\n let angle = (Math.atan2(stokes.s2, stokes.s1) * 90) / Math.PI;\n if (angle < 0) angle += 180;\n return { degreeOfLinearPolarization: degree, angleDegrees: angle };\n}\n\n/**\n * Mueller matrix of a linear diattenuator (principal intensity transmittances k1 along the axis\n * and k2 across it, axis at theta) applied to a linear Stokes vector.\n */\nfunction applyLinearPolarizer(\n stokes: LinearStokes,\n k1: number,\n k2: number,\n axisDegrees: number\n): LinearStokes {\n const twice = (2 * axisDegrees * Math.PI) / 180;\n const c = Math.cos(twice);\n const sn = Math.sin(twice);\n const sum = (k1 + k2) / 2;\n const difference = (k1 - k2) / 2;\n const geometric = Math.sqrt(k1 * k2);\n return {\n s0: sum * stokes.s0 + difference * (c * stokes.s1 + sn * stokes.s2),\n s1:\n difference * c * stokes.s0 +\n (sum * c * c + geometric * sn * sn) * stokes.s1 +\n (sum - geometric) * c * sn * stokes.s2,\n s2:\n difference * sn * stokes.s0 +\n (sum - geometric) * c * sn * stokes.s1 +\n (sum * sn * sn + geometric * c * c) * stokes.s2\n };\n}\n\ninterface ResolvedComponent {\n resolved: ResolvedFrontOfLensFilterTransmission;\n outgoing: LinearStokes;\n}\n\nfunction resolveComponent(\n input: ResolveFrontOfLensFilterTransmissionInput,\n incoming: LinearStokes\n): ResolvedComponent {\n const profile =\n parseFrontOfLensFilterProfile(\n input.profile\n );\n const wavelength =\n scientificWavelength(\n input.wavelengthNanometers\n );\n\n if (\n input.wavelengthBasis !==\n profile.wavelengthBasis\n ) {\n throw new InvalidScientificInputError(\n \"Filter and requested wavelength bases must match; implicit air/vacuum conversion is not performed.\"\n );\n }\n if (\n wavelength <\n profile\n .wavelengthRangeNanometers\n .minimum ||\n wavelength >\n profile\n .wavelengthRangeNanometers\n .maximum\n ) {\n throw new InvalidScientificInputError(\n \"Requested wavelength lies outside the filter applicability range.\"\n );\n }\n\n let factor: number;\n let outgoing: LinearStokes;\n const transmission =\n profile.transmission;\n\n if (\n transmission.kind ===\n \"linear-polarizer\"\n ) {\n const k1 = transmission.principalTransmittance.value;\n const k2 = k1 / transmission.extinctionRatio.value;\n outgoing = applyLinearPolarizer(\n incoming,\n k1,\n k2,\n transmission.transmissionAxisDegrees\n );\n factor = outgoing.s0 / incoming.s0;\n } else {\n if (\n transmission.kind ===\n \"neutral-linear-transmission\"\n ) {\n factor =\n transmission\n .linearTransmissionFactor\n .value;\n } else if (\n transmission.kind ===\n \"neutral-optical-density\"\n ) {\n factor =\n Math.pow(\n 10,\n -transmission\n .opticalDensityBase10\n .value\n );\n } else {\n factor =\n interpolateSpectralTransmission(\n transmission.samples.value,\n wavelength\n );\n }\n outgoing = {\n s0: incoming.s0 * factor,\n s1: incoming.s1 * factor,\n s2: incoming.s2 * factor\n };\n }\n\n if (\n !Number.isFinite(factor) ||\n factor <= 0 ||\n factor > 1\n ) {\n throw new InvalidScientificInputError(\n \"Resolved passive filter transmission must remain finite and in (0, 1].\"\n );\n }\n\n const attenuationStops =\n -Math.log2(factor);\n const opticalDensityBase10 =\n -Math.log10(factor);\n\n if (\n !Number.isFinite(\n attenuationStops\n ) ||\n !Number.isFinite(\n opticalDensityBase10\n )\n ) {\n throw new InvalidScientificInputError(\n \"Resolved filter attenuation diagnostics must remain finite.\"\n );\n }\n\n const polarizer = transmission.kind === \"linear-polarizer\";\n return {\n outgoing,\n resolved: {\n schemaVersion:\n profile.schemaVersion,\n filterId:\n profile.filterId,\n profileVersion:\n profile.profileVersion,\n identityScope:\n \"photivra-generic-unbranded\",\n position:\n \"front-of-lens\",\n scientificStatus:\n profile.scientificStatus,\n wavelengthNanometers:\n wavelength,\n wavelengthBasis:\n input.wavelengthBasis,\n transmissionKind:\n transmission.kind,\n linearTransmissionFactor:\n factor,\n attenuationStops,\n opticalDensityBase10,\n polarizationModeled:\n polarizer,\n incidentPolarization:\n stateFromStokes(incoming),\n transmittedPolarization:\n stateFromStokes(outgoing),\n wavelengthChangingBehaviorModeled:\n false,\n lensTransmissionIncluded:\n false,\n sensorOpticalStackIncluded:\n false,\n fieldThroughputIncluded:\n false,\n uncertainty:\n profile.uncertainty,\n evidence: [\n ...profile.evidence,\n ...transmissionEvidence(\n transmission\n )\n ],\n limitations: [\n ...profile.limitations,\n \"Front-of-lens transmission is independent of lens aperture geometry, focus/DOF, diffraction and sensor optical-stack response.\",\n polarizer\n ? \"Linear diattenuator on linear Stokes parameters; circular polarization, retardance, angle-of-incidence dependence and inter-filter reflections/flare/ghosting are not modeled.\"\n : \"Polarization and inter-filter reflections/flare/ghosting are not modeled.\"\n ]\n }\n };\n}\n\n/**\n * Resolves one passive filter at one wavelength. Non-polarizing filters scale all Stokes\n * components equally, so their transmission does not depend on the incident polarization.\n */\nexport function resolveFrontOfLensFilterTransmission(\n input:\n ResolveFrontOfLensFilterTransmissionInput\n): ResolvedFrontOfLensFilterTransmission {\n requireInputKeys(input, \"resolveFrontOfLensFilterTransmission\", [\"profile\", \"wavelengthNanometers\", \"wavelengthBasis\", \"incidentPolarization\"]);\n return resolveComponent(\n input,\n stokesFromState(\n parsePolarizationState(\n input.incidentPolarization,\n \"incidentPolarization\"\n )\n )\n ).resolved;\n}\n\n/**\n * Combines several passive front-of-lens filters at one wavelength, in light-path order, and keeps\n * each filter's identity. Without a polarizer the result is the product of the transmissions. With\n * one, linear Stokes parameters pass through the Mueller matrices in order.\n */\nexport function composeFrontOfLensFilterTransmission(\n input:\n ComposeFrontOfLensFilterTransmissionInput\n): ComposedFrontOfLensFilterTransmission {\n requireInputKeys(input, \"composeFrontOfLensFilterTransmission\", [\"filters\", \"wavelengthNanometers\", \"wavelengthBasis\", \"incidentPolarization\"]);\n if (\n !Array.isArray(\n input.filters\n ) ||\n input.filters.length === 0\n ) {\n throw new InvalidConfigurationError(\n \"filters must be a non-empty array.\"\n );\n }\n\n const incidentPolarization = parsePolarizationState(\n input.incidentPolarization,\n \"incidentPolarization\"\n );\n const incident = stokesFromState(incidentPolarization);\n let stokes = incident;\n const components: ResolvedFrontOfLensFilterTransmission[] = [];\n for (const profile of input.filters) {\n const component = resolveComponent(\n {\n profile,\n wavelengthNanometers:\n input.wavelengthNanometers,\n wavelengthBasis:\n input.wavelengthBasis\n },\n stokes\n );\n components.push(component.resolved);\n stokes = component.outgoing;\n }\n\n const combined = stokes.s0 / incident.s0;\n if (\n !Number.isFinite(combined) ||\n combined <= 0 ||\n combined > 1\n ) {\n throw new InvalidScientificInputError(\n \"Combined passive filter transmission must remain finite and in (0, 1].\"\n );\n }\n\n const stops =\n -Math.log2(combined);\n if (!Number.isFinite(stops)) {\n throw new InvalidScientificInputError(\n \"Combined filter attenuation must remain finite.\"\n );\n }\n\n const polarizationModeled = components.some(\n (component) => component.polarizationModeled\n );\n return {\n schemaVersion: polarizationModeled\n ? FRONT_OF_LENS_POLARIZER_PROFILE_SCHEMA_VERSION\n : FRONT_OF_LENS_FILTER_PROFILE_SCHEMA_VERSION,\n wavelengthNanometers:\n scientificWavelength(\n input.wavelengthNanometers\n ),\n wavelengthBasis:\n input.wavelengthBasis,\n appliedFilterCount:\n components.length,\n components,\n combinedLinearTransmissionFactor:\n combined,\n combinedAttenuationStops:\n stops,\n scientificStatus:\n components.some(\n (component) =>\n component\n .scientificStatus ===\n \"approximation\"\n )\n ? \"approximation\"\n : \"calibrated\",\n uncertaintyPropagation:\n \"not-propagated\",\n uncertaintyLimitation:\n \"Component filter uncertainties are preserved individually; aggregate numeric uncertainty is not propagated because independence/correlation is not assumed.\",\n compositionModel: polarizationModeled\n ? \"ordered-linear-stokes-mueller-no-inter-filter-reflections\"\n : \"independent-multiplicative-unpolarized-transmission-no-inter-filter-reflections\",\n polarizationModeled,\n incidentPolarization,\n transmittedPolarization:\n stateFromStokes(stokes),\n wavelengthChangingBehaviorModeled:\n false,\n lensTransmissionIncluded:\n false,\n sensorOpticalStackIncluded:\n false,\n fieldThroughputIncluded:\n false\n };\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import type { FrontOfLensFilterProfile } from "./front-of-lens-filter.js";
2
+ /** Attenuations, in stops, of the generic ND presets. */
3
+ export declare const GENERIC_ND_FILTER_STOPS: readonly [3, 6, 10];
4
+ /** One of the generic ND preset attenuations: 3, 6 or 10 stops. */
5
+ export type GenericNdFilterStops = (typeof GENERIC_ND_FILTER_STOPS)[number];
6
+ /**
7
+ * Frozen generic ND filter profiles keyed by stops: `GENERIC_ND_FILTER_PROFILES[10]` transmits
8
+ * exactly 1/1024. Spectrally flat over 380–780 nm (air), idealized (`scientificStatus:
9
+ * "approximation"`), unbranded. 1/125 s with the 10-stop preset gives 1024/125 ≈ 8.19 s in
10
+ * `calculateFilteredExposureTime`.
11
+ */
12
+ export declare const GENERIC_ND_FILTER_PROFILES: Readonly<Record<GenericNdFilterStops, FrontOfLensFilterProfile>>;
@@ -0,0 +1,48 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { freezeOwnedData } from "../core/owned-data.js";
3
+ /** Attenuations, in stops, of the generic ND presets. */
4
+ export const GENERIC_ND_FILTER_STOPS = /* @__PURE__ */ Object.freeze([3, 6, 10]);
5
+ function idealNdProfile(stops) {
6
+ const evidence = [{
7
+ sourceOrigin: "photivra",
8
+ sourceReference: "photivra:generic-nd-filter-presets:578",
9
+ reuseStatus: "photivra-owned",
10
+ note: `Idealized: transmittance exactly 2^-${stops} by the definition of a ${stops}-stop filter (owner decision 2026-11-09); not measured, not modelled on any product.`
11
+ }];
12
+ return {
13
+ schemaVersion: "0.1.0",
14
+ filterId: `generic-nd-${stops}-stop`,
15
+ profileVersion: "1.0.0",
16
+ identityScope: "photivra-generic-unbranded",
17
+ position: "front-of-lens",
18
+ scientificStatus: "approximation",
19
+ wavelengthBasis: "air",
20
+ wavelengthRangeNanometers: { minimum: 380, maximum: 780 },
21
+ transmission: {
22
+ kind: "neutral-linear-transmission",
23
+ linearTransmissionFactor: { value: 2 ** -stops, evidence }
24
+ },
25
+ uncertainty: {
26
+ kind: "not-quantified",
27
+ limitation: "Idealized nominal filter: real ND filters deviate from their rating and are not perfectly flat across wavelengths."
28
+ },
29
+ polarizationModeled: false,
30
+ wavelengthChangingBehaviorModeled: false,
31
+ evidence,
32
+ limitations: [
33
+ `Idealized spectrally flat ${stops}-stop filter (transmittance exactly 2^-${stops}); not a measured filter.`
34
+ ]
35
+ };
36
+ }
37
+ /**
38
+ * Frozen generic ND filter profiles keyed by stops: `GENERIC_ND_FILTER_PROFILES[10]` transmits
39
+ * exactly 1/1024. Spectrally flat over 380–780 nm (air), idealized (`scientificStatus:
40
+ * "approximation"`), unbranded. 1/125 s with the 10-stop preset gives 1024/125 ≈ 8.19 s in
41
+ * `calculateFilteredExposureTime`.
42
+ */
43
+ export const GENERIC_ND_FILTER_PROFILES = /* @__PURE__ */ freezeOwnedData({
44
+ 3: idealNdProfile(3),
45
+ 6: idealNdProfile(6),
46
+ 10: idealNdProfile(10)
47
+ });
48
+ //# sourceMappingURL=generic-nd-filter-presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generic-nd-filter-presets.js","sourceRoot":"","sources":["../../src/optics/generic-nd-filter-presets.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAetC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,yDAAyD;AACzD,MAAM,CAAC,MAAM,uBAAuB,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAU,CAAC,CAAC;AAK1F,SAAS,cAAc,CAAC,KAA2B;IACjD,MAAM,QAAQ,GAAkC,CAAC;YAC/C,YAAY,EAAE,UAAU;YACxB,eAAe,EAAE,wCAAwC;YACzD,WAAW,EAAE,gBAAgB;YAC7B,IAAI,EAAE,uCAAuC,KAAK,2BAA2B,KAAK,sFAAsF;SACzK,CAAC,CAAC;IACH,OAAO;QACL,aAAa,EAAE,OAAO;QACtB,QAAQ,EAAE,cAAc,KAAK,OAAO;QACpC,cAAc,EAAE,OAAO;QACvB,aAAa,EAAE,4BAA4B;QAC3C,QAAQ,EAAE,eAAe;QACzB,gBAAgB,EAAE,eAAe;QACjC,eAAe,EAAE,KAAK;QACtB,yBAAyB,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE;QACzD,YAAY,EAAE;YACZ,IAAI,EAAE,6BAA6B;YACnC,wBAAwB,EAAE,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE;SAC3D;QACD,WAAW,EAAE;YACX,IAAI,EAAE,gBAAgB;YACtB,UAAU,EAAE,oHAAoH;SACjI;QACD,mBAAmB,EAAE,KAAK;QAC1B,iCAAiC,EAAE,KAAK;QACxC,QAAQ;QACR,WAAW,EAAE;YACX,6BAA6B,KAAK,0CAA0C,KAAK,2BAA2B;SAC7G;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAqE,eAAe,CAAC,eAAe,CAAC;IAC1I,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;IACpB,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;IACpB,EAAE,EAAE,cAAc,CAAC,EAAE,CAAC;CACvB,CAAC,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic, unbranded neutral-density (ND) filter presets: idealized 3-, 6- and 10-stop filters.\n *\n * \"N-stop\" means a transmittance of exactly 2^-N (owner decision 2026-11-09, #578): 3 stops is\n * 1/8, 6 stops 1/64 and 10 stops 1/1024, each exact in binary floating point. The presets are\n * spectrally flat (`neutral-linear-transmission`) over 380–780 nm. They are idealizations, not\n * measurements: `scientificStatus` is `\"approximation\"` and the uncertainty is not quantified,\n * because real ND filters deviate from their nominal rating and are not perfectly flat. Pass one to\n * `calculateFilteredExposureTime` or to any function that takes a front-of-lens filter profile.\n * @see docs/PHYSICS_FOUNDATION.md \"Exposure time with an ND filter\".\n */\n\nimport type { EvidenceProvenance } from \"../core/evidence-provenance.js\";\nimport { freezeOwnedData } from \"../core/owned-data.js\";\nimport type { FrontOfLensFilterProfile } from \"./front-of-lens-filter.js\";\n\n/** Attenuations, in stops, of the generic ND presets. */\nexport const GENERIC_ND_FILTER_STOPS = /* @__PURE__ */ Object.freeze([3, 6, 10] as const);\n\n/** One of the generic ND preset attenuations: 3, 6 or 10 stops. */\nexport type GenericNdFilterStops = (typeof GENERIC_ND_FILTER_STOPS)[number];\n\nfunction idealNdProfile(stops: GenericNdFilterStops): FrontOfLensFilterProfile {\n const evidence: readonly EvidenceProvenance[] = [{\n sourceOrigin: \"photivra\",\n sourceReference: \"photivra:generic-nd-filter-presets:578\",\n reuseStatus: \"photivra-owned\",\n note: `Idealized: transmittance exactly 2^-${stops} by the definition of a ${stops}-stop filter (owner decision 2026-11-09); not measured, not modelled on any product.`\n }];\n return {\n schemaVersion: \"0.1.0\",\n filterId: `generic-nd-${stops}-stop`,\n profileVersion: \"1.0.0\",\n identityScope: \"photivra-generic-unbranded\",\n position: \"front-of-lens\",\n scientificStatus: \"approximation\",\n wavelengthBasis: \"air\",\n wavelengthRangeNanometers: { minimum: 380, maximum: 780 },\n transmission: {\n kind: \"neutral-linear-transmission\",\n linearTransmissionFactor: { value: 2 ** -stops, evidence }\n },\n uncertainty: {\n kind: \"not-quantified\",\n limitation: \"Idealized nominal filter: real ND filters deviate from their rating and are not perfectly flat across wavelengths.\"\n },\n polarizationModeled: false,\n wavelengthChangingBehaviorModeled: false,\n evidence,\n limitations: [\n `Idealized spectrally flat ${stops}-stop filter (transmittance exactly 2^-${stops}); not a measured filter.`\n ]\n };\n}\n\n/**\n * Frozen generic ND filter profiles keyed by stops: `GENERIC_ND_FILTER_PROFILES[10]` transmits\n * exactly 1/1024. Spectrally flat over 380–780 nm (air), idealized (`scientificStatus:\n * \"approximation\"`), unbranded. 1/125 s with the 10-stop preset gives 1024/125 ≈ 8.19 s in\n * `calculateFilteredExposureTime`.\n */\nexport const GENERIC_ND_FILTER_PROFILES: Readonly<Record<GenericNdFilterStops, FrontOfLensFilterProfile>> = /* @__PURE__ */ freezeOwnedData({\n 3: idealNdProfile(3),\n 6: idealNdProfile(6),\n 10: idealNdProfile(10)\n});\n"]}
@@ -10,12 +10,7 @@
10
10
  */
11
11
  import { approximationResult } from "../core/calculation-result.js";
12
12
  import { InvalidScientificInputError, requirePositiveFinite } from "../core/validation.js";
13
- import { requireInputObject, requireInputKeys } from "../core/record-validation.js";
14
- function requireFinite(name, value) {
15
- if (!Number.isFinite(value)) {
16
- throw new InvalidScientificInputError(`${name} must be finite.`);
17
- }
18
- }
13
+ import { requireInputObject, requireInputKeys, assertNamedFinite as requireFinite } from "../core/record-validation.js";
19
14
  function throughputFromSquaredRadius(squaredRadius, coefficients) {
20
15
  return (1 +
21
16
  coefficients.r2 * squaredRadius +
@@ -1 +1 @@
1
- {"version":3,"file":"illumination-vignetting.js","sourceRoot":"","sources":["../../src/optics/illumination-vignetting.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC;;;;;;;;GAQG;AAEH,OAAO,EACL,mBAAmB,EAEpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,2BAA2B,EAC3B,qBAAqB,EACtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AA6DpF,SAAS,aAAa,CAAC,IAAY,EAAE,KAAa;IAChD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,2BAA2B,CAAC,GAAG,IAAI,kBAAkB,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AAED,SAAS,2BAA2B,CAClC,aAAqB,EACrB,YAAgD;IAEhD,OAAO,CACL,CAAC;QACD,YAAY,CAAC,EAAE,GAAG,aAAa;QAC/B,YAAY,CAAC,EAAE,GAAG,aAAa,GAAG,aAAa;QAC/C,YAAY,CAAC,EAAE;YACb,aAAa;YACb,aAAa;YACb,aAAa,CAChB,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,8BAA8B,CACrC,YAAgD;IAEhD,kCAAkC;IAClC,gCAAgC;IAChC,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC;IAC9B,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC;IAC9B,MAAM,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC;IAC1B,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,IAAI,gBAAgB,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,GAAG,gBAAgB,CAAC;IACrC,MAAM,OAAO,GAAG,CAAC,GAAG,gBAAgB,CAAC;IACrC,MAAM,OAAO,GAAG,CAAC,GAAG,gBAAgB,CAAC;IAErC,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,OAAO,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,CAAC,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/D,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACrC,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,OAAO,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACZ,iDAAiD;QACjD,OAAO,CAAC,CAAC,CAAC,CAAC;IACb,CAAC;IACD,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,eAAe,CACtB,OAAsC;IAEtC,qBAAqB,CACnB,+BAA+B,EAC/B,OAAO,CAAC,qBAAqB,CAC9B,CAAC;IACF,qBAAqB,CACnB,iCAAiC,EACjC,OAAO,CAAC,uBAAuB,CAChC,CAAC;IACF,aAAa,CAAC,yBAAyB,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAClE,aAAa,CAAC,yBAAyB,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAClE,aAAa,CAAC,yBAAyB,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAElE,MAAM,oBAAoB,GACxB,OAAO,CAAC,uBAAuB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IACpE,MAAM,UAAU,GAAG;QACjB,CAAC;QACD,oBAAoB;QACpB,GAAG,8BAA8B,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,MAAM,CAC5D,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,oBAAoB,CACrD;KACF,CAAC;IACF,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,aAAa,EAAE,EAAE,CACnD,2BAA2B,CAAC,aAAa,EAAE,OAAO,CAAC,YAAY,CAAC,CACjE,CAAC;IACF,MAAM,uBAAuB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC;IACzD,MAAM,uBAAuB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC;IAEzD,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QACzC,uBAAuB,IAAI,CAAC,EAC5B,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,+HAA+H,CAChI,CAAC;IACJ,CAAC;IAED,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAC;QACzC,uBAAuB,GAAG,CAAC,EAC3B,CAAC;QACD,MAAM,IAAI,2BAA2B,CACnC,gHAAgH,CACjH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,qBAAqB,EAAE,OAAO,CAAC,qBAAqB;QACpD,uBAAuB,EAAE,OAAO,CAAC,uBAAuB;QACxD,YAAY,EAAE,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE;QACzC,uBAAuB;QACvB,uBAAuB;KACxB,CAAC;AACJ,CAAC;AAED,kJAAkJ;AAClJ,SAAS,YAAY,CAAC,OAAqC,EAAE,YAA8B;IACzF,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC5D,MAAM,gBAAgB,GAAG,QAAQ,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAElE,IAAI,gBAAgB,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;QACvD,MAAM,IAAI,2BAA2B,CACnC,wFAAwF,CACzF,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;IAC1D,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,2BAA2B,CAAC,aAAa,EAAE,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;AACxH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uCAAuC,CAAC,OAAsC;IAC5F,MAAM,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAC3C,OAAO,CAAC,YAAY,EAAE,EAAE;QACtB,aAAa,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;QAChD,aAAa,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;QAChD,OAAO,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,sBAAsB,CAAC;IACtE,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,WAAW;IAClB,OAAO;QACL,oHAAoH;QACpH,mEAAmE;QACnE,gHAAgH;QAChH,iHAAiH;QACjH,gFAAgF;QAChF,sHAAsH;KACvH,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAC7C,KAA2C;IAE3C,gBAAgB,CAAC,KAAK,EAAE,iCAAiC,EAAE,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC,CAAC;IACxF,kBAAkB,CAAC,KAAK,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;IACvD,aAAa,CAAC,gBAAgB,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACtD,aAAa,CAAC,gBAAgB,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAEtD,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAE/F,MAAM,gBAAgB,GACpB,sBAAsB,KAAK,CAAC;QAC1B,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAEzC,OAAO,mBAAmB,CACxB;QACE,YAAY,EAAE,EAAE,GAAG,KAAK,CAAC,YAAY,EAAE;QACvC,gBAAgB;QAChB,sBAAsB;QACtB,gBAAgB;QAChB,8BAA8B,EAAE,OAAO,CAAC,uBAAuB;QAC/D,8BAA8B,EAAE,OAAO,CAAC,uBAAuB;KAChE,EACD,wCAAwC,EACxC,OAAO,EACP,WAAW,EAAE,CACd,CAAC;AACJ,CAAC","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic illumination falloff across the image (vignetting), as a factor that multiplies linear\n * light.\n *\n * A profile is accepted only if its relative throughput stays strictly positive and never exceeds 1\n * (the value on the optical axis) anywhere in its declared range.\n * @see docs/PHYSICS_FOUNDATION.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport type { LensFieldPointMm } from \"./radial-distortion.js\";\nimport { requireInputObject, requireInputKeys } from \"../core/record-validation.js\";\n\nexport interface IlluminationVignettingCoefficients {\n /** Coefficient on normalized radius squared. */\n r2: number;\n /** Coefficient on normalized radius to the fourth power. */\n r4: number;\n /** Coefficient on normalized radius to the sixth power. */\n r6: number;\n}\n\nexport interface IlluminationVignettingProfile {\n /** Physical image-plane radius used to normalize field position, in mm. */\n normalizationRadiusMm: number;\n /** Maximum normalized image-plane radius over which the profile is valid. */\n maximumNormalizedRadius: number;\n /**\n * Generic radial relative-illumination coefficients.\n *\n * Relative linear throughput is:\n * 1 + r2 * rho^2 + r4 * rho^4 + r6 * rho^6\n *\n * where rho is physical image-plane radius / normalizationRadiusMm.\n */\n coefficients: IlluminationVignettingCoefficients;\n}\n\nexport interface CalculateIlluminationVignettingInput {\n /**\n * Ideal image-plane field position relative to the optical axis, in mm.\n *\n * Throughput is evaluated at this ideal field coordinate. Geometric\n * distortion is neither applied nor inverted.\n */\n imagePointMm: LensFieldPointMm;\n profile: IlluminationVignettingProfile;\n}\n\nexport interface IlluminationVignetting {\n imagePointMm: LensFieldPointMm;\n normalizedRadius: number;\n /**\n * Factor that multiplies scene-linear or channel-linear signal.\n *\n * 1 means no attenuation. Values must stay in (0, 1].\n */\n linearThroughputFactor: number;\n /** Positive exposure loss relative to the optical axis, in stops. */\n attenuationStops: number;\n profileMinimumThroughputFactor: number;\n profileMaximumThroughputFactor: number;\n}\n\ninterface ValidatedIlluminationProfile {\n normalizationRadiusMm: number;\n maximumNormalizedRadius: number;\n coefficients: IlluminationVignettingCoefficients;\n minimumThroughputFactor: number;\n maximumThroughputFactor: 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 throughputFromSquaredRadius(\n squaredRadius: number,\n coefficients: IlluminationVignettingCoefficients\n): number {\n return (\n 1 +\n coefficients.r2 * squaredRadius +\n coefficients.r4 * squaredRadius * squaredRadius +\n coefficients.r6 *\n squaredRadius *\n squaredRadius *\n squaredRadius\n );\n}\n\n/**\n * Real roots of dT/ds, where s = rho². Every interior maximum or minimum of the throughput lies at\n * one of these roots. The validator checks them along with both ends of the range. So a profile\n * that dips below zero, or rises above one, mid-field is rejected even when both ends are valid.\n *\n * The quadratic is scaled before solving and uses the numerically stable q-form roots. This stops a\n * tiny cubic term from erasing the smaller root through cancellation. The radial-distortion\n * validator uses the same scheme.\n */\nfunction throughputCriticalSquaredRadii(\n coefficients: IlluminationVignettingCoefficients\n): number[] {\n // T(s) = 1 + r2*s + r4*s² + r6*s³\n // dT/ds = r2 + 2*r4*s + 3*r6*s²\n const a = 3 * coefficients.r6;\n const b = 2 * coefficients.r4;\n const c = coefficients.r2;\n const coefficientScale = Math.max(Math.abs(a), Math.abs(b), Math.abs(c));\n if (coefficientScale === 0) {\n return [];\n }\n const scaledA = a / coefficientScale;\n const scaledB = b / coefficientScale;\n const scaledC = c / coefficientScale;\n\n if (scaledA === 0) {\n return scaledB === 0 ? [] : [-scaledC / scaledB];\n }\n\n const discriminant = scaledB * scaledB - 4 * scaledA * scaledC;\n if (discriminant < 0) {\n return [];\n }\n const root = Math.sqrt(discriminant);\n const q = -0.5 * (scaledB + (scaledB >= 0 ? root : -root));\n if (q === 0) {\n // Only when b = c = 0: the double root is s = 0.\n return [0];\n }\n return [q / scaledA, scaledC / q];\n}\n\nfunction validateProfile(\n profile: IlluminationVignettingProfile\n): ValidatedIlluminationProfile {\n requirePositiveFinite(\n \"profile.normalizationRadiusMm\",\n profile.normalizationRadiusMm\n );\n requirePositiveFinite(\n \"profile.maximumNormalizedRadius\",\n profile.maximumNormalizedRadius\n );\n requireFinite(\"profile.coefficients.r2\", profile.coefficients.r2);\n requireFinite(\"profile.coefficients.r4\", profile.coefficients.r4);\n requireFinite(\"profile.coefficients.r6\", profile.coefficients.r6);\n\n const maximumSquaredRadius =\n profile.maximumNormalizedRadius * profile.maximumNormalizedRadius;\n const candidates = [\n 0,\n maximumSquaredRadius,\n ...throughputCriticalSquaredRadii(profile.coefficients).filter(\n (value) => value > 0 && value < maximumSquaredRadius\n )\n ];\n const throughputs = candidates.map((squaredRadius) =>\n throughputFromSquaredRadius(squaredRadius, profile.coefficients)\n );\n const minimumThroughputFactor = Math.min(...throughputs);\n const maximumThroughputFactor = Math.max(...throughputs);\n\n if (\n !Number.isFinite(minimumThroughputFactor) ||\n minimumThroughputFactor <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Illumination vignetting profile must keep relative linear throughput strictly greater than zero over maximumNormalizedRadius.\"\n );\n }\n\n if (\n !Number.isFinite(maximumThroughputFactor) ||\n maximumThroughputFactor > 1\n ) {\n throw new InvalidScientificInputError(\n \"Illumination vignetting profile must not amplify relative linear throughput above the optical-axis value of 1.\"\n );\n }\n\n return {\n normalizationRadiusMm: profile.normalizationRadiusMm,\n maximumNormalizedRadius: profile.maximumNormalizedRadius,\n coefficients: { ...profile.coefficients },\n minimumThroughputFactor,\n maximumThroughputFactor\n };\n}\n\n/** The normalized radius and throughput factor at an image point, for a validated profile (the arithmetic of calculateIlluminationVignetting). */\nfunction throughputAt(profile: ValidatedIlluminationProfile, imagePointMm: LensFieldPointMm): { normalizedRadius: number; linearThroughputFactor: number } {\n const radiusMm = Math.hypot(imagePointMm.x, imagePointMm.y);\n const normalizedRadius = radiusMm / profile.normalizationRadiusMm;\n\n if (normalizedRadius > profile.maximumNormalizedRadius) {\n throw new InvalidScientificInputError(\n \"imagePointMm lies outside the illumination vignetting profile maximumNormalizedRadius.\"\n );\n }\n\n const squaredRadius = normalizedRadius * normalizedRadius;\n return { normalizedRadius, linearThroughputFactor: throughputFromSquaredRadius(squaredRadius, profile.coefficients) };\n}\n\n/**\n * @internal (#563) The `linearThroughputFactor` that {@link calculateIlluminationVignetting} returns, from a profile validated once:\n * the same arithmetic and the same errors, without the result envelope, for callers that evaluate one profile at many points\n * (the image-space field factor of `prepareDefocusImageSpaceGeometry`).\n */\nexport function prepareIlluminationVignettingThroughput(profile: IlluminationVignettingProfile): (imagePointMm: LensFieldPointMm) => number {\n const validated = validateProfile(profile);\n return (imagePointMm) => {\n requireFinite(\"imagePointMm.x\", imagePointMm.x);\n requireFinite(\"imagePointMm.y\", imagePointMm.y);\n return throughputAt(validated, imagePointMm).linearThroughputFactor;\n };\n}\n\nfunction assumptions(): readonly string[] {\n return [\n \"Generic rotationally symmetric illumination-vignetting approximation evaluated from ideal image-plane field radius\",\n \"Relative linear throughput is normalized to 1 on the optical axis\",\n \"The model attenuates scene-linear/channel-linear signal only and must be applied before display/gamma encoding\",\n \"The model changes throughput only; it does not alter field coordinates, pupil shape, PSF shape, bokeh, or focus\",\n \"Mechanical/pupil vignetting and cat's-eye bokeh are separate pupil/PSF effects\",\n \"The profile is a generic caller-supplied radial approximation, not calibrated radiometry or a named-lens measurement\"\n ];\n}\n\n/**\n * Calculates generic illumination falloff across the image (vignetting) as a\n * factor that multiplies linear light.\n *\n * A profile is accepted only if its relative throughput stays strictly\n * positive and never exceeds 1 (the value on the optical axis) anywhere in\n * its declared range.\n */\nexport function calculateIlluminationVignetting(\n input: CalculateIlluminationVignettingInput\n): CalculationResult<IlluminationVignetting> {\n requireInputKeys(input, \"calculateIlluminationVignetting\", [\"imagePointMm\", \"profile\"]);\n requireInputObject(input.imagePointMm, \"imagePointMm\");\n requireFinite(\"imagePointMm.x\", input.imagePointMm.x);\n requireFinite(\"imagePointMm.y\", input.imagePointMm.y);\n\n const profile = validateProfile(input.profile);\n const { normalizedRadius, linearThroughputFactor } = throughputAt(profile, input.imagePointMm);\n\n const attenuationStops =\n linearThroughputFactor === 1\n ? 0\n : -Math.log2(linearThroughputFactor);\n\n return approximationResult(\n {\n imagePointMm: { ...input.imagePointMm },\n normalizedRadius,\n linearThroughputFactor,\n attenuationStops,\n profileMinimumThroughputFactor: profile.minimumThroughputFactor,\n profileMaximumThroughputFactor: profile.maximumThroughputFactor\n },\n \"generic-radial-illumination-vignetting\",\n \"1.0.0\",\n assumptions()\n );\n}\n"]}
1
+ {"version":3,"file":"illumination-vignetting.js","sourceRoot":"","sources":["../../src/optics/illumination-vignetting.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generic illumination falloff across the image (vignetting), as a factor that multiplies linear\n * light.\n *\n * A profile is accepted only if its relative throughput stays strictly positive and never exceeds 1\n * (the value on the optical axis) anywhere in its declared range.\n * @see docs/PHYSICS_FOUNDATION.md for the equations, units, coordinate conventions, known blockers\n * and support limits.\n */\n\nimport {\n approximationResult,\n type CalculationResult\n} from \"../core/calculation-result.js\";\nimport {\n InvalidScientificInputError,\n requirePositiveFinite\n} from \"../core/validation.js\";\nimport type { LensFieldPointMm } from \"./radial-distortion.js\";\nimport { requireInputObject, requireInputKeys, assertNamedFinite as requireFinite } from \"../core/record-validation.js\";\n\nexport interface IlluminationVignettingCoefficients {\n /** Coefficient on normalized radius squared. */\n r2: number;\n /** Coefficient on normalized radius to the fourth power. */\n r4: number;\n /** Coefficient on normalized radius to the sixth power. */\n r6: number;\n}\n\nexport interface IlluminationVignettingProfile {\n /** Physical image-plane radius used to normalize field position, in mm. */\n normalizationRadiusMm: number;\n /** Maximum normalized image-plane radius over which the profile is valid. */\n maximumNormalizedRadius: number;\n /**\n * Generic radial relative-illumination coefficients.\n *\n * Relative linear throughput is:\n * 1 + r2 * rho^2 + r4 * rho^4 + r6 * rho^6\n *\n * where rho is physical image-plane radius / normalizationRadiusMm.\n */\n coefficients: IlluminationVignettingCoefficients;\n}\n\nexport interface CalculateIlluminationVignettingInput {\n /**\n * Ideal image-plane field position relative to the optical axis, in mm.\n *\n * Throughput is evaluated at this ideal field coordinate. Geometric\n * distortion is neither applied nor inverted.\n */\n imagePointMm: LensFieldPointMm;\n profile: IlluminationVignettingProfile;\n}\n\nexport interface IlluminationVignetting {\n imagePointMm: LensFieldPointMm;\n normalizedRadius: number;\n /**\n * Factor that multiplies scene-linear or channel-linear signal.\n *\n * 1 means no attenuation. Values must stay in (0, 1].\n */\n linearThroughputFactor: number;\n /** Positive exposure loss relative to the optical axis, in stops. */\n attenuationStops: number;\n profileMinimumThroughputFactor: number;\n profileMaximumThroughputFactor: number;\n}\n\ninterface ValidatedIlluminationProfile {\n normalizationRadiusMm: number;\n maximumNormalizedRadius: number;\n coefficients: IlluminationVignettingCoefficients;\n minimumThroughputFactor: number;\n maximumThroughputFactor: number;\n}\n\nfunction throughputFromSquaredRadius(\n squaredRadius: number,\n coefficients: IlluminationVignettingCoefficients\n): number {\n return (\n 1 +\n coefficients.r2 * squaredRadius +\n coefficients.r4 * squaredRadius * squaredRadius +\n coefficients.r6 *\n squaredRadius *\n squaredRadius *\n squaredRadius\n );\n}\n\n/**\n * Real roots of dT/ds, where s = rho². Every interior maximum or minimum of the throughput lies at\n * one of these roots. The validator checks them along with both ends of the range. So a profile\n * that dips below zero, or rises above one, mid-field is rejected even when both ends are valid.\n *\n * The quadratic is scaled before solving and uses the numerically stable q-form roots. This stops a\n * tiny cubic term from erasing the smaller root through cancellation. The radial-distortion\n * validator uses the same scheme.\n */\nfunction throughputCriticalSquaredRadii(\n coefficients: IlluminationVignettingCoefficients\n): number[] {\n // T(s) = 1 + r2*s + r4*s² + r6*s³\n // dT/ds = r2 + 2*r4*s + 3*r6*s²\n const a = 3 * coefficients.r6;\n const b = 2 * coefficients.r4;\n const c = coefficients.r2;\n const coefficientScale = Math.max(Math.abs(a), Math.abs(b), Math.abs(c));\n if (coefficientScale === 0) {\n return [];\n }\n const scaledA = a / coefficientScale;\n const scaledB = b / coefficientScale;\n const scaledC = c / coefficientScale;\n\n if (scaledA === 0) {\n return scaledB === 0 ? [] : [-scaledC / scaledB];\n }\n\n const discriminant = scaledB * scaledB - 4 * scaledA * scaledC;\n if (discriminant < 0) {\n return [];\n }\n const root = Math.sqrt(discriminant);\n const q = -0.5 * (scaledB + (scaledB >= 0 ? root : -root));\n if (q === 0) {\n // Only when b = c = 0: the double root is s = 0.\n return [0];\n }\n return [q / scaledA, scaledC / q];\n}\n\nfunction validateProfile(\n profile: IlluminationVignettingProfile\n): ValidatedIlluminationProfile {\n requirePositiveFinite(\n \"profile.normalizationRadiusMm\",\n profile.normalizationRadiusMm\n );\n requirePositiveFinite(\n \"profile.maximumNormalizedRadius\",\n profile.maximumNormalizedRadius\n );\n requireFinite(\"profile.coefficients.r2\", profile.coefficients.r2);\n requireFinite(\"profile.coefficients.r4\", profile.coefficients.r4);\n requireFinite(\"profile.coefficients.r6\", profile.coefficients.r6);\n\n const maximumSquaredRadius =\n profile.maximumNormalizedRadius * profile.maximumNormalizedRadius;\n const candidates = [\n 0,\n maximumSquaredRadius,\n ...throughputCriticalSquaredRadii(profile.coefficients).filter(\n (value) => value > 0 && value < maximumSquaredRadius\n )\n ];\n const throughputs = candidates.map((squaredRadius) =>\n throughputFromSquaredRadius(squaredRadius, profile.coefficients)\n );\n const minimumThroughputFactor = Math.min(...throughputs);\n const maximumThroughputFactor = Math.max(...throughputs);\n\n if (\n !Number.isFinite(minimumThroughputFactor) ||\n minimumThroughputFactor <= 0\n ) {\n throw new InvalidScientificInputError(\n \"Illumination vignetting profile must keep relative linear throughput strictly greater than zero over maximumNormalizedRadius.\"\n );\n }\n\n if (\n !Number.isFinite(maximumThroughputFactor) ||\n maximumThroughputFactor > 1\n ) {\n throw new InvalidScientificInputError(\n \"Illumination vignetting profile must not amplify relative linear throughput above the optical-axis value of 1.\"\n );\n }\n\n return {\n normalizationRadiusMm: profile.normalizationRadiusMm,\n maximumNormalizedRadius: profile.maximumNormalizedRadius,\n coefficients: { ...profile.coefficients },\n minimumThroughputFactor,\n maximumThroughputFactor\n };\n}\n\n/** The normalized radius and throughput factor at an image point, for a validated profile (the arithmetic of calculateIlluminationVignetting). */\nfunction throughputAt(profile: ValidatedIlluminationProfile, imagePointMm: LensFieldPointMm): { normalizedRadius: number; linearThroughputFactor: number } {\n const radiusMm = Math.hypot(imagePointMm.x, imagePointMm.y);\n const normalizedRadius = radiusMm / profile.normalizationRadiusMm;\n\n if (normalizedRadius > profile.maximumNormalizedRadius) {\n throw new InvalidScientificInputError(\n \"imagePointMm lies outside the illumination vignetting profile maximumNormalizedRadius.\"\n );\n }\n\n const squaredRadius = normalizedRadius * normalizedRadius;\n return { normalizedRadius, linearThroughputFactor: throughputFromSquaredRadius(squaredRadius, profile.coefficients) };\n}\n\n/**\n * @internal (#563) The `linearThroughputFactor` that {@link calculateIlluminationVignetting} returns, from a profile validated once:\n * the same arithmetic and the same errors, without the result envelope, for callers that evaluate one profile at many points\n * (the image-space field factor of `prepareDefocusImageSpaceGeometry`).\n */\nexport function prepareIlluminationVignettingThroughput(profile: IlluminationVignettingProfile): (imagePointMm: LensFieldPointMm) => number {\n const validated = validateProfile(profile);\n return (imagePointMm) => {\n requireFinite(\"imagePointMm.x\", imagePointMm.x);\n requireFinite(\"imagePointMm.y\", imagePointMm.y);\n return throughputAt(validated, imagePointMm).linearThroughputFactor;\n };\n}\n\nfunction assumptions(): readonly string[] {\n return [\n \"Generic rotationally symmetric illumination-vignetting approximation evaluated from ideal image-plane field radius\",\n \"Relative linear throughput is normalized to 1 on the optical axis\",\n \"The model attenuates scene-linear/channel-linear signal only and must be applied before display/gamma encoding\",\n \"The model changes throughput only; it does not alter field coordinates, pupil shape, PSF shape, bokeh, or focus\",\n \"Mechanical/pupil vignetting and cat's-eye bokeh are separate pupil/PSF effects\",\n \"The profile is a generic caller-supplied radial approximation, not calibrated radiometry or a named-lens measurement\"\n ];\n}\n\n/**\n * Calculates generic illumination falloff across the image (vignetting) as a\n * factor that multiplies linear light.\n *\n * A profile is accepted only if its relative throughput stays strictly\n * positive and never exceeds 1 (the value on the optical axis) anywhere in\n * its declared range.\n */\nexport function calculateIlluminationVignetting(\n input: CalculateIlluminationVignettingInput\n): CalculationResult<IlluminationVignetting> {\n requireInputKeys(input, \"calculateIlluminationVignetting\", [\"imagePointMm\", \"profile\"]);\n requireInputObject(input.imagePointMm, \"imagePointMm\");\n requireFinite(\"imagePointMm.x\", input.imagePointMm.x);\n requireFinite(\"imagePointMm.y\", input.imagePointMm.y);\n\n const profile = validateProfile(input.profile);\n const { normalizedRadius, linearThroughputFactor } = throughputAt(profile, input.imagePointMm);\n\n const attenuationStops =\n linearThroughputFactor === 1\n ? 0\n : -Math.log2(linearThroughputFactor);\n\n return approximationResult(\n {\n imagePointMm: { ...input.imagePointMm },\n normalizedRadius,\n linearThroughputFactor,\n attenuationStops,\n profileMinimumThroughputFactor: profile.minimumThroughputFactor,\n profileMaximumThroughputFactor: profile.maximumThroughputFactor\n },\n \"generic-radial-illumination-vignetting\",\n \"1.0.0\",\n assumptions()\n );\n}\n"]}
@@ -38,7 +38,9 @@ export type LensPsfUncertainty = {
38
38
  * - `rotationally-symmetric-radial-field-centroid-aligned`: for a rotationally symmetric lens. Nodes
39
39
  * are declared only along the +X field axis (fieldYmm = [0], fieldXmm >= 0). A query at field
40
40
  * (x, y) is interpolated, with centroid alignment, at radius hypot(x, y). The kernel is then
41
- * rotated by atan2(y, x) about its centre. The sample pitch must be square.
41
+ * rotated by atan2(y, x) about its centre, with the kernel row index increasing with image +Y (the
42
+ * convention the environment PSF quadrature applies), so an asymmetric kernel points the same way
43
+ * as in the 2-D grid modes. The sample pitch must be square.
42
44
  * Shifts and rotations use bilinear resampling. This smooths the kernel slightly (by at most about
43
45
  * one sample). Samples outside the kernel are zero, and the result is renormalised to unit energy.
44
46
  */
@@ -175,6 +177,7 @@ export interface MtfOnlyPsfRenderabilityAssessment {
175
177
  reason: "mtf-magnitude-lacks-phase-and-does-not-uniquely-determine-psf";
176
178
  diagnosticUseAuthorized: true;
177
179
  }
180
+ export declare function parseUncertainty(value: unknown, path: string): LensPsfUncertainty;
178
181
  /**
179
182
  * Internal: registers a private profile declaration owned by the engine, so that repeated
180
183
  * resolveLensSampledPsf calls reuse one parse. The declaration is a structured clone that caller code