@photivra/engine 0.5.1 → 0.6.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 (79) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CITATION.cff +1 -1
  3. package/README.md +37 -3
  4. package/dist/index.d.ts +15 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +15 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/motion/camera-rotation.d.ts +60 -0
  9. package/dist/motion/camera-rotation.d.ts.map +1 -1
  10. package/dist/motion/camera-rotation.js +95 -0
  11. package/dist/motion/camera-rotation.js.map +1 -1
  12. package/dist/motion/capture-rotation-inverse-mapping.d.ts +100 -0
  13. package/dist/motion/capture-rotation-inverse-mapping.d.ts.map +1 -0
  14. package/dist/motion/capture-rotation-inverse-mapping.js +171 -0
  15. package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -0
  16. package/dist/motion/capture-rotation-temporal-quadrature.d.ts +89 -0
  17. package/dist/motion/capture-rotation-temporal-quadrature.d.ts.map +1 -0
  18. package/dist/motion/capture-rotation-temporal-quadrature.js +116 -0
  19. package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -0
  20. package/dist/motion/capture-rotation-trajectory.d.ts +98 -0
  21. package/dist/motion/capture-rotation-trajectory.d.ts.map +1 -0
  22. package/dist/motion/capture-rotation-trajectory.js +153 -0
  23. package/dist/motion/capture-rotation-trajectory.js.map +1 -0
  24. package/dist/optics/lateral-chromatic-aberration.d.ts.map +1 -1
  25. package/dist/optics/lateral-chromatic-aberration.js +12 -4
  26. package/dist/optics/lateral-chromatic-aberration.js.map +1 -1
  27. package/dist/optics/radial-distortion.d.ts +4 -2
  28. package/dist/optics/radial-distortion.d.ts.map +1 -1
  29. package/dist/optics/radial-distortion.js +83 -23
  30. package/dist/optics/radial-distortion.js.map +1 -1
  31. package/dist/output/capture-geometry.d.ts.map +1 -1
  32. package/dist/output/capture-geometry.js +24 -11
  33. package/dist/output/capture-geometry.js.map +1 -1
  34. package/dist/sensor/capture-color-sampling-binding.d.ts +211 -0
  35. package/dist/sensor/capture-color-sampling-binding.d.ts.map +1 -0
  36. package/dist/sensor/capture-color-sampling-binding.js +423 -0
  37. package/dist/sensor/capture-color-sampling-binding.js.map +1 -0
  38. package/dist/sensor/capture-mode.d.ts +157 -0
  39. package/dist/sensor/capture-mode.d.ts.map +1 -0
  40. package/dist/sensor/capture-mode.js +434 -0
  41. package/dist/sensor/capture-mode.js.map +1 -0
  42. package/dist/sensor/color-sampling.d.ts +133 -0
  43. package/dist/sensor/color-sampling.d.ts.map +1 -0
  44. package/dist/sensor/color-sampling.js +226 -0
  45. package/dist/sensor/color-sampling.js.map +1 -0
  46. package/dist/sensor/exposure-window.d.ts +103 -0
  47. package/dist/sensor/exposure-window.d.ts.map +1 -0
  48. package/dist/sensor/exposure-window.js +291 -0
  49. package/dist/sensor/exposure-window.js.map +1 -0
  50. package/dist/sensor/optical-stack.d.ts +132 -0
  51. package/dist/sensor/optical-stack.d.ts.map +1 -0
  52. package/dist/sensor/optical-stack.js +286 -0
  53. package/dist/sensor/optical-stack.js.map +1 -0
  54. package/dist/sensor/readout-exposure-linkage.d.ts +111 -0
  55. package/dist/sensor/readout-exposure-linkage.d.ts.map +1 -0
  56. package/dist/sensor/readout-exposure-linkage.js +210 -0
  57. package/dist/sensor/readout-exposure-linkage.js.map +1 -0
  58. package/dist/sensor/readout-timing.d.ts +124 -0
  59. package/dist/sensor/readout-timing.d.ts.map +1 -0
  60. package/dist/sensor/readout-timing.js +230 -0
  61. package/dist/sensor/readout-timing.js.map +1 -0
  62. package/dist/sensor/sampling-aperture.d.ts +154 -0
  63. package/dist/sensor/sampling-aperture.d.ts.map +1 -0
  64. package/dist/sensor/sampling-aperture.js +374 -0
  65. package/dist/sensor/sampling-aperture.js.map +1 -0
  66. package/dist/sensor/spatial-sample-reduction.d.ts +128 -0
  67. package/dist/sensor/spatial-sample-reduction.d.ts.map +1 -0
  68. package/dist/sensor/spatial-sample-reduction.js +299 -0
  69. package/dist/sensor/spatial-sample-reduction.js.map +1 -0
  70. package/dist/sensor/spatial-sampling-quadrature.d.ts +155 -0
  71. package/dist/sensor/spatial-sampling-quadrature.d.ts.map +1 -0
  72. package/dist/sensor/spatial-sampling-quadrature.js +197 -0
  73. package/dist/sensor/spatial-sampling-quadrature.js.map +1 -0
  74. package/docs/ARCHITECTURE.md +17 -0
  75. package/docs/AUTOMATION_COORDINATION.md +188 -0
  76. package/docs/IMAGE_FORMATION.md +26 -0
  77. package/docs/MOTION_AND_SIGNAL.md +142 -0
  78. package/docs/USAGE.md +1273 -3
  79. package/package.json +5 -3
@@ -0,0 +1,286 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { InvalidConfigurationError } from "../core/configuration-error.js";
3
+ import { parseEvidenceList } from "../core/evidence-provenance.js";
4
+ import { InvalidScientificInputError } from "../core/validation.js";
5
+ const COMPONENT_ROLES = new Set([
6
+ "cover-glass",
7
+ "infrared-cut",
8
+ "ultraviolet-cut",
9
+ "anti-reflection",
10
+ "birefringent-low-pass",
11
+ "wave-plate",
12
+ "other-optical-filter"
13
+ ]);
14
+ function requireRecord(value, path) {
15
+ if (typeof value !== "object" ||
16
+ value === null ||
17
+ Array.isArray(value)) {
18
+ throw new InvalidConfigurationError(path + " must be an object.");
19
+ }
20
+ return value;
21
+ }
22
+ function requireNonEmptyString(value, path) {
23
+ if (typeof value !== "string" ||
24
+ value.trim().length === 0) {
25
+ throw new InvalidConfigurationError(path + " must be a non-empty string.");
26
+ }
27
+ return value;
28
+ }
29
+ function parseComponentRoles(value, path) {
30
+ if (!Array.isArray(value) || value.length === 0) {
31
+ throw new InvalidConfigurationError(path + " must be a non-empty array.");
32
+ }
33
+ const roles = value.map((entry, index) => {
34
+ if (typeof entry !== "string" ||
35
+ !COMPONENT_ROLES.has(entry)) {
36
+ throw new InvalidConfigurationError(path + "[" + index + "] is invalid.");
37
+ }
38
+ return entry;
39
+ });
40
+ if (new Set(roles).size !== roles.length) {
41
+ throw new InvalidConfigurationError(path + " must not contain duplicate roles.");
42
+ }
43
+ return roles;
44
+ }
45
+ function parseOrderedComponents(value, path) {
46
+ if (!Array.isArray(value) || value.length === 0) {
47
+ throw new InvalidConfigurationError(path + " must be a non-empty array.");
48
+ }
49
+ const components = value.map((entry, index) => {
50
+ const record = requireRecord(entry, path + "[" + index + "]");
51
+ return {
52
+ componentId: requireNonEmptyString(record.componentId, path + "[" + index + "].componentId"),
53
+ roles: parseComponentRoles(record.roles, path + "[" + index + "].roles"),
54
+ evidence: parseEvidenceList(record.evidence, path + "[" + index + "].evidence")
55
+ };
56
+ });
57
+ const ids = components.map((component) => component.componentId);
58
+ if (new Set(ids).size !== ids.length) {
59
+ throw new InvalidConfigurationError(path + " must not contain duplicate componentId values.");
60
+ }
61
+ return components;
62
+ }
63
+ function requireFiniteNumber(value, path) {
64
+ if (typeof value !== "number" ||
65
+ !Number.isFinite(value)) {
66
+ throw new InvalidConfigurationError(path + " must be a finite number.");
67
+ }
68
+ return value;
69
+ }
70
+ function parsePointSplitComponents(value, path) {
71
+ if (!Array.isArray(value) || value.length === 0) {
72
+ throw new InvalidConfigurationError(path + " must be a non-empty array.");
73
+ }
74
+ const components = value.map((entry, index) => {
75
+ const record = requireRecord(entry, path + "[" + index + "]");
76
+ const offset = requireRecord(record.offsetMicrometers, path + "[" + index + "].offsetMicrometers");
77
+ const normalizedWeight = requireFiniteNumber(record.normalizedWeight, path + "[" + index + "].normalizedWeight");
78
+ if (normalizedWeight <= 0) {
79
+ throw new InvalidConfigurationError(path +
80
+ "[" +
81
+ index +
82
+ "].normalizedWeight must be greater than zero.");
83
+ }
84
+ return {
85
+ offsetMicrometers: {
86
+ x: requireFiniteNumber(offset.x, path +
87
+ "[" +
88
+ index +
89
+ "].offsetMicrometers.x"),
90
+ y: requireFiniteNumber(offset.y, path +
91
+ "[" +
92
+ index +
93
+ "].offsetMicrometers.y")
94
+ },
95
+ normalizedWeight
96
+ };
97
+ });
98
+ const weightSum = components.reduce((sum, component) => sum + component.normalizedWeight, 0);
99
+ if (!Number.isFinite(weightSum) ||
100
+ Math.abs(weightSum - 1) > 1e-12) {
101
+ throw new InvalidConfigurationError(path +
102
+ " normalizedWeight values must sum to 1 within 1e-12.");
103
+ }
104
+ return components;
105
+ }
106
+ function parseAntiAliasingResponse(value, path) {
107
+ const record = requireRecord(value, path);
108
+ if (record.kind === "absent" ||
109
+ record.kind === "present-unresolved") {
110
+ if (record.coordinateSystem !== undefined ||
111
+ record.scope !== undefined ||
112
+ record.components !== undefined) {
113
+ throw new InvalidConfigurationError(path +
114
+ " absent/present-unresolved responses must omit kernel fields.");
115
+ }
116
+ return {
117
+ kind: record.kind,
118
+ evidence: parseEvidenceList(record.evidence, path + ".evidence")
119
+ };
120
+ }
121
+ if (record.kind ===
122
+ "normalized-point-splitting-kernel") {
123
+ if (record.coordinateSystem !==
124
+ "native-sensor-physical") {
125
+ throw new InvalidConfigurationError(path +
126
+ '.coordinateSystem must be "native-sensor-physical".');
127
+ }
128
+ if (record.scope !==
129
+ "field-wavelength-polarization-invariant-approximation") {
130
+ throw new InvalidConfigurationError(path + ".scope is invalid.");
131
+ }
132
+ return {
133
+ kind: "normalized-point-splitting-kernel",
134
+ evidence: parseEvidenceList(record.evidence, path + ".evidence"),
135
+ coordinateSystem: "native-sensor-physical",
136
+ scope: "field-wavelength-polarization-invariant-approximation",
137
+ components: parsePointSplitComponents(record.components, path + ".components")
138
+ };
139
+ }
140
+ throw new InvalidConfigurationError(path + ".kind is invalid.");
141
+ }
142
+ function parseMicrolens(value, path) {
143
+ const record = requireRecord(value, path);
144
+ if (record.presence !== "present" &&
145
+ record.presence !== "absent") {
146
+ throw new InvalidConfigurationError(path + ".presence is invalid.");
147
+ }
148
+ if (record.opticalEffectModel !== "unresolved") {
149
+ throw new InvalidConfigurationError(path +
150
+ '.opticalEffectModel must be "unresolved" in schema 0.1.0.');
151
+ }
152
+ return {
153
+ presence: record.presence,
154
+ evidence: parseEvidenceList(record.evidence, path + ".evidence"),
155
+ opticalEffectModel: "unresolved"
156
+ };
157
+ }
158
+ /**
159
+ * Parses descriptive sensor optical-stack metadata plus an optional explicitly
160
+ * declared effective anti-aliasing spatial response.
161
+ *
162
+ * Unknown facts are omitted instead of inferred. Physical component presence
163
+ * is intentionally separate from effective anti-aliasing response so the
164
+ * schema can represent cancellation/neutralization designs without claiming
165
+ * the physical assembly is absent.
166
+ *
167
+ * Cover/filter-stack and microlens presence do not create optical effects in
168
+ * schema 0.1.0. Thickness, refractive index, spectral transmission, angular
169
+ * acceptance, focus shift and microlens collection behavior require later
170
+ * explicit models/calibration.
171
+ */
172
+ export function parseSensorOpticalStackProfile(value) {
173
+ const profile = requireRecord(value, "sensorOpticalStack");
174
+ if (profile.schemaVersion !== "0.1.0") {
175
+ throw new InvalidConfigurationError('sensorOpticalStack.schemaVersion must be "0.1.0".');
176
+ }
177
+ const orderedComponents = profile.orderedComponents === undefined
178
+ ? undefined
179
+ : parseOrderedComponents(profile.orderedComponents, "sensorOpticalStack.orderedComponents");
180
+ const effectiveAntiAliasingSpatialResponse = profile.effectiveAntiAliasingSpatialResponse ===
181
+ undefined
182
+ ? undefined
183
+ : parseAntiAliasingResponse(profile.effectiveAntiAliasingSpatialResponse, "sensorOpticalStack.effectiveAntiAliasingSpatialResponse");
184
+ const microlens = profile.microlens === undefined
185
+ ? undefined
186
+ : parseMicrolens(profile.microlens, "sensorOpticalStack.microlens");
187
+ if (orderedComponents === undefined &&
188
+ effectiveAntiAliasingSpatialResponse ===
189
+ undefined &&
190
+ microlens === undefined) {
191
+ throw new InvalidConfigurationError("sensorOpticalStack must assert at least one optical-stack fact.");
192
+ }
193
+ return {
194
+ schemaVersion: "0.1.0",
195
+ profileId: requireNonEmptyString(profile.profileId, "sensorOpticalStack.profileId"),
196
+ evidence: parseEvidenceList(profile.evidence, "sensorOpticalStack.evidence"),
197
+ ...(orderedComponents === undefined
198
+ ? {}
199
+ : { orderedComponents }),
200
+ ...(effectiveAntiAliasingSpatialResponse ===
201
+ undefined
202
+ ? {}
203
+ : {
204
+ effectiveAntiAliasingSpatialResponse
205
+ }),
206
+ ...(microlens === undefined
207
+ ? {}
208
+ : { microlens })
209
+ };
210
+ }
211
+ /**
212
+ * Resolves only the effective anti-aliasing spatial kernel.
213
+ *
214
+ * This is not a whole optical-stack PSF. Cover/filter transmission, refraction,
215
+ * microlens behavior, field/wavelength/polarization dependence, and total
216
+ * throughput remain excluded.
217
+ *
218
+ * A documented absent effective anti-aliasing response resolves to an identity
219
+ * component at zero offset. An unknown response (field omitted) or a
220
+ * present-but-unresolved response fails closed.
221
+ */
222
+ export function resolveAntiAliasingSpatialKernel(profileInput) {
223
+ const profile = parseSensorOpticalStackProfile(profileInput);
224
+ const response = profile.effectiveAntiAliasingSpatialResponse;
225
+ if (response === undefined) {
226
+ throw new InvalidScientificInputError("Effective anti-aliasing spatial response is unknown/unasserted for this optical-stack profile.");
227
+ }
228
+ if (response.kind === "present-unresolved") {
229
+ throw new InvalidScientificInputError("Effective anti-aliasing spatial response is present but unresolved; a spatial kernel cannot be produced.");
230
+ }
231
+ if (response.kind === "absent") {
232
+ return {
233
+ profileId: profile.profileId,
234
+ effectiveResponse: "absent",
235
+ coordinateSystem: "native-sensor-physical",
236
+ components: [
237
+ {
238
+ offsetMicrometers: {
239
+ x: 0,
240
+ y: 0
241
+ },
242
+ normalizedWeight: 1
243
+ }
244
+ ],
245
+ normalizedWeightSum: 1,
246
+ throughputIncluded: false,
247
+ spectralTransmissionIncluded: false,
248
+ fieldDependenceIncluded: false,
249
+ wavelengthDependenceIncluded: false,
250
+ polarizationDependenceIncluded: false,
251
+ microlensResponseIncluded: false,
252
+ coverFilterStackEffectsIncluded: false,
253
+ wholeSensorOpticalStackResponse: false,
254
+ provenance: {
255
+ profile: profile.evidence,
256
+ antiAliasingResponse: response.evidence
257
+ }
258
+ };
259
+ }
260
+ const normalizedWeightSum = response.components.reduce((sum, component) => sum + component.normalizedWeight, 0);
261
+ return {
262
+ profileId: profile.profileId,
263
+ effectiveResponse: "normalized-point-splitting-kernel",
264
+ coordinateSystem: "native-sensor-physical",
265
+ components: response.components.map((component) => ({
266
+ offsetMicrometers: {
267
+ ...component.offsetMicrometers
268
+ },
269
+ normalizedWeight: component.normalizedWeight
270
+ })),
271
+ normalizedWeightSum,
272
+ throughputIncluded: false,
273
+ spectralTransmissionIncluded: false,
274
+ fieldDependenceIncluded: false,
275
+ wavelengthDependenceIncluded: false,
276
+ polarizationDependenceIncluded: false,
277
+ microlensResponseIncluded: false,
278
+ coverFilterStackEffectsIncluded: false,
279
+ wholeSensorOpticalStackResponse: false,
280
+ provenance: {
281
+ profile: profile.evidence,
282
+ antiAliasingResponse: response.evidence
283
+ }
284
+ };
285
+ }
286
+ //# sourceMappingURL=optical-stack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optical-stack.js","sourceRoot":"","sources":["../../src/sensor/optical-stack.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ import { type CalculationProvenance, type CalculationResult } from "../core/calculation-result.js";
2
+ import { type EvidenceProvenance } from "../core/evidence-provenance.js";
3
+ import type { RasterPoint, RasterRect } from "../output/capture-geometry.js";
4
+ import { type ExposureBoundarySchedule, type SourcedCaptureTimingSeconds } from "./exposure-window.js";
5
+ import { type CaptureShutterMechanism, type NativeSensorReadoutScanDirection, type SensorReadoutTimingDeclaration, type SourcedSensorTimingSeconds } from "./readout-timing.js";
6
+ import type { NativeImageRaster } from "./sensor-geometry.js";
7
+ export type ReadoutExposureBoundaryId = "opening" | "closing";
8
+ export type ReadoutExposureSpatialPhaseOrientation = "same" | "reversed";
9
+ export interface ReadoutExposureBoundarySpatialLink {
10
+ boundary: ReadoutExposureBoundaryId;
11
+ /**
12
+ * Relationship between normalized rolling-readout scan phase and the
13
+ * selected electronic exposure-boundary scan phase.
14
+ *
15
+ * "same": boundary normalized phase = readout normalized phase.
16
+ * "reversed": boundary normalized phase = 1 - readout normalized phase.
17
+ *
18
+ * This does not establish absolute temporal synchronization.
19
+ */
20
+ phaseOrientation: ReadoutExposureSpatialPhaseOrientation;
21
+ /**
22
+ * Evidence supporting the relationship itself.
23
+ *
24
+ * The readout direction/timing and exposure-boundary direction/timing retain
25
+ * their own separate evidence.
26
+ */
27
+ evidence: readonly EvidenceProvenance[];
28
+ }
29
+ export type ReadoutExposureTimingLinkageDeclaration = {
30
+ /**
31
+ * Photivra asserts no readout↔exposure relationship for this capture.
32
+ * This is not evidence that the physical processes are independent.
33
+ */
34
+ kind: "unlinked";
35
+ links?: never;
36
+ } | {
37
+ /**
38
+ * One or more exposure boundaries are explicitly related to rolling
39
+ * readout by normalized native-sensor spatial phase.
40
+ */
41
+ kind: "spatial-phase-linked";
42
+ links: readonly ReadoutExposureBoundarySpatialLink[];
43
+ };
44
+ export interface AssessReadoutExposureTimingLinkageInput {
45
+ nativeRaster: NativeImageRaster;
46
+ activeCaptureRect?: RasterRect;
47
+ shutterMechanism: CaptureShutterMechanism;
48
+ readout: SensorReadoutTimingDeclaration;
49
+ nominalExposureDurationSeconds: SourcedCaptureTimingSeconds;
50
+ opening: ExposureBoundarySchedule;
51
+ closing: ExposureBoundarySchedule;
52
+ linkage: ReadoutExposureTimingLinkageDeclaration;
53
+ samplePointsNative?: readonly RasterPoint[];
54
+ }
55
+ export interface ReadoutExposureBoundarySpatialLinkAssessment {
56
+ boundary: ReadoutExposureBoundaryId;
57
+ boundaryActuator: "electronic";
58
+ phaseOrientation: ReadoutExposureSpatialPhaseOrientation;
59
+ normalizedPhaseRelationship: "boundary-phase-equals-readout-phase" | "boundary-phase-equals-one-minus-readout-phase";
60
+ relationshipEvidence: readonly EvidenceProvenance[];
61
+ readoutDirectionNative: NativeSensorReadoutScanDirection;
62
+ boundaryDirectionNative: NativeSensorReadoutScanDirection;
63
+ /**
64
+ * First-to-last rolling sensor spatial sampling skew.
65
+ */
66
+ readoutSpatialSamplingSkewSeconds: SourcedSensorTimingSeconds;
67
+ /**
68
+ * First-to-last exposure-boundary traversal duration.
69
+ */
70
+ boundaryTraversalDurationSeconds: SourcedCaptureTimingSeconds;
71
+ /**
72
+ * Derived descriptive ratio only. A value of 1 does not prove temporal
73
+ * synchronization or a common clock origin.
74
+ */
75
+ boundaryTraversalToReadoutSpatialSkewRatio: number;
76
+ absoluteTemporalAlignment: "not-established";
77
+ }
78
+ export interface ReadoutExposureTimingLinkageAssessment {
79
+ linkageKind: ReadoutExposureTimingLinkageDeclaration["kind"];
80
+ relationshipMeaning: "no-readout-exposure-relationship-asserted" | "normalized-spatial-phase-relationship-asserted";
81
+ readoutMode: "rolling" | "global";
82
+ shutterMechanism: CaptureShutterMechanism;
83
+ activeCaptureRect: RasterRect;
84
+ absoluteTemporalAlignment: "not-established";
85
+ /**
86
+ * Capture data-readout duration is preserved for diagnostics but is not used
87
+ * to validate any exposure-boundary linkage.
88
+ */
89
+ captureReadoutDurationSeconds: SourcedSensorTimingSeconds;
90
+ links: readonly ReadoutExposureBoundarySpatialLinkAssessment[];
91
+ componentProvenance: {
92
+ sensorReadout: CalculationProvenance;
93
+ exposureWindows: CalculationProvenance;
94
+ };
95
+ }
96
+ /**
97
+ * Assesses an explicitly declared relationship between rolling sensor readout
98
+ * spatial phase and one or more electronic exposure-boundary scans.
99
+ *
100
+ * This contract is deliberately weaker than temporal synchronization. It can
101
+ * establish that two schedules traverse the same native spatial phase ordering
102
+ * (or its reverse), but it does not establish when sensor data readout occurs
103
+ * relative to exposure start/end.
104
+ *
105
+ * The total capture data-readout duration is preserved diagnostically and is
106
+ * never used to validate the relationship. Different rolling-readout spatial
107
+ * skew and exposure-boundary traversal durations are allowed; their ratio is
108
+ * reported without implying a shared clock origin.
109
+ */
110
+ export declare function assessReadoutExposureTimingLinkage(input: AssessReadoutExposureTimingLinkageInput): CalculationResult<ReadoutExposureTimingLinkageAssessment>;
111
+ //# sourceMappingURL=readout-exposure-linkage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readout-exposure-linkage.d.ts","sourceRoot":"","sources":["../../src/sensor/readout-exposure-linkage.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EACjC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,gCAAgC,EACrC,KAAK,8BAA8B,EACnC,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,sCAAsC,GAC9C,MAAM,GACN,UAAU,CAAC;AAEf,MAAM,WAAW,kCAAkC;IACjD,QAAQ,EAAE,yBAAyB,CAAC;IACpC;;;;;;;;OAQG;IACH,gBAAgB,EAAE,sCAAsC,CAAC;IACzD;;;;;OAKG;IACH,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACzC;AAED,MAAM,MAAM,uCAAuC,GAC/C;IACE;;;OAGG;IACH,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,GACD;IACE;;;OAGG;IACH,IAAI,EAAE,sBAAsB,CAAC;IAC7B,KAAK,EAAE,SAAS,kCAAkC,EAAE,CAAC;CACtD,CAAC;AAEN,MAAM,WAAW,uCAAuC;IACtD,YAAY,EAAE,iBAAiB,CAAC;IAChC,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAC/B,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,OAAO,EAAE,8BAA8B,CAAC;IACxC,8BAA8B,EAAE,2BAA2B,CAAC;IAC5D,OAAO,EAAE,wBAAwB,CAAC;IAClC,OAAO,EAAE,wBAAwB,CAAC;IAClC,OAAO,EAAE,uCAAuC,CAAC;IACjD,kBAAkB,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,4CAA4C;IAC3D,QAAQ,EAAE,yBAAyB,CAAC;IACpC,gBAAgB,EAAE,YAAY,CAAC;IAC/B,gBAAgB,EAAE,sCAAsC,CAAC;IACzD,2BAA2B,EACvB,qCAAqC,GACrC,+CAA+C,CAAC;IACpD,oBAAoB,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACpD,sBAAsB,EAAE,gCAAgC,CAAC;IACzD,uBAAuB,EAAE,gCAAgC,CAAC;IAC1D;;OAEG;IACH,iCAAiC,EAAE,0BAA0B,CAAC;IAC9D;;OAEG;IACH,gCAAgC,EAAE,2BAA2B,CAAC;IAC9D;;;OAGG;IACH,0CAA0C,EAAE,MAAM,CAAC;IACnD,yBAAyB,EAAE,iBAAiB,CAAC;CAC9C;AAED,MAAM,WAAW,sCAAsC;IACrD,WAAW,EAAE,uCAAuC,CAAC,MAAM,CAAC,CAAC;IAC7D,mBAAmB,EACf,2CAA2C,GAC3C,gDAAgD,CAAC;IACrD,WAAW,EAAE,SAAS,GAAG,QAAQ,CAAC;IAClC,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,iBAAiB,EAAE,UAAU,CAAC;IAC9B,yBAAyB,EAAE,iBAAiB,CAAC;IAC7C;;;OAGG;IACH,6BAA6B,EAAE,0BAA0B,CAAC;IAC1D,KAAK,EAAE,SAAS,4CAA4C,EAAE,CAAC;IAC/D,mBAAmB,EAAE;QACnB,aAAa,EAAE,qBAAqB,CAAC;QACrC,eAAe,EAAE,qBAAqB,CAAC;KACxC,CAAC;CACH;AA0HD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,uCAAuC,GAC7C,iBAAiB,CAAC,sCAAsC,CAAC,CAoK3D"}
@@ -0,0 +1,210 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { approximationResult } from "../core/calculation-result.js";
3
+ import { parseEvidenceList } from "../core/evidence-provenance.js";
4
+ import { InvalidScientificInputError } from "../core/validation.js";
5
+ import { calculateCaptureExposureWindows } from "./exposure-window.js";
6
+ import { calculateSensorReadoutTiming } from "./readout-timing.js";
7
+ const BOUNDARIES = new Set([
8
+ "opening",
9
+ "closing"
10
+ ]);
11
+ const PHASE_ORIENTATIONS = new Set([
12
+ "same",
13
+ "reversed"
14
+ ]);
15
+ function requireRecord(value, path) {
16
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
17
+ throw new InvalidScientificInputError(path + " must be an object.");
18
+ }
19
+ return value;
20
+ }
21
+ function parseSpatialLink(value, path) {
22
+ const record = requireRecord(value, path);
23
+ if (typeof record.boundary !== "string" ||
24
+ !BOUNDARIES.has(record.boundary)) {
25
+ throw new InvalidScientificInputError(path + ".boundary is invalid.");
26
+ }
27
+ if (typeof record.phaseOrientation !== "string" ||
28
+ !PHASE_ORIENTATIONS.has(record.phaseOrientation)) {
29
+ throw new InvalidScientificInputError(path + ".phaseOrientation is invalid.");
30
+ }
31
+ return {
32
+ boundary: record.boundary,
33
+ phaseOrientation: record.phaseOrientation,
34
+ evidence: parseEvidenceList(record.evidence, path + ".evidence")
35
+ };
36
+ }
37
+ function parseLinkageDeclaration(value) {
38
+ const record = requireRecord(value, "linkage");
39
+ if (record.kind === "unlinked") {
40
+ if (record.links !== undefined) {
41
+ throw new InvalidScientificInputError("linkage.links must be omitted when linkage.kind is unlinked.");
42
+ }
43
+ return { kind: "unlinked" };
44
+ }
45
+ if (record.kind !== "spatial-phase-linked") {
46
+ throw new InvalidScientificInputError("linkage.kind is invalid.");
47
+ }
48
+ if (!Array.isArray(record.links) || record.links.length === 0) {
49
+ throw new InvalidScientificInputError("linkage.links must be a non-empty array for spatial-phase-linked linkage.");
50
+ }
51
+ const links = record.links.map((entry, index) => parseSpatialLink(entry, "linkage.links[" + index + "]"));
52
+ const seen = new Set();
53
+ for (const link of links) {
54
+ if (seen.has(link.boundary)) {
55
+ throw new InvalidScientificInputError("linkage.links must not contain duplicate boundary links.");
56
+ }
57
+ seen.add(link.boundary);
58
+ }
59
+ return {
60
+ kind: "spatial-phase-linked",
61
+ links
62
+ };
63
+ }
64
+ function oppositeDirection(direction) {
65
+ switch (direction) {
66
+ case "top-to-bottom":
67
+ return "bottom-to-top";
68
+ case "bottom-to-top":
69
+ return "top-to-bottom";
70
+ case "left-to-right":
71
+ return "right-to-left";
72
+ case "right-to-left":
73
+ return "left-to-right";
74
+ }
75
+ }
76
+ function expectedBoundaryDirection(readoutDirection, phaseOrientation) {
77
+ return phaseOrientation === "same"
78
+ ? readoutDirection
79
+ : oppositeDirection(readoutDirection);
80
+ }
81
+ /**
82
+ * Assesses an explicitly declared relationship between rolling sensor readout
83
+ * spatial phase and one or more electronic exposure-boundary scans.
84
+ *
85
+ * This contract is deliberately weaker than temporal synchronization. It can
86
+ * establish that two schedules traverse the same native spatial phase ordering
87
+ * (or its reverse), but it does not establish when sensor data readout occurs
88
+ * relative to exposure start/end.
89
+ *
90
+ * The total capture data-readout duration is preserved diagnostically and is
91
+ * never used to validate the relationship. Different rolling-readout spatial
92
+ * skew and exposure-boundary traversal durations are allowed; their ratio is
93
+ * reported without implying a shared clock origin.
94
+ */
95
+ export function assessReadoutExposureTimingLinkage(input) {
96
+ const linkage = parseLinkageDeclaration(input.linkage);
97
+ const sensorReadout = calculateSensorReadoutTiming({
98
+ nativeRaster: input.nativeRaster,
99
+ ...(input.activeCaptureRect === undefined
100
+ ? {}
101
+ : { activeCaptureRect: input.activeCaptureRect }),
102
+ shutterMechanism: input.shutterMechanism,
103
+ readout: input.readout,
104
+ ...(input.samplePointsNative === undefined
105
+ ? {}
106
+ : { samplePointsNative: input.samplePointsNative })
107
+ });
108
+ const exposureWindows = calculateCaptureExposureWindows({
109
+ nativeRaster: input.nativeRaster,
110
+ ...(input.activeCaptureRect === undefined
111
+ ? {}
112
+ : { activeCaptureRect: input.activeCaptureRect }),
113
+ shutterMechanism: input.shutterMechanism,
114
+ nominalExposureDurationSeconds: input.nominalExposureDurationSeconds,
115
+ opening: input.opening,
116
+ closing: input.closing,
117
+ ...(input.samplePointsNative === undefined
118
+ ? {}
119
+ : { samplePointsNative: input.samplePointsNative })
120
+ });
121
+ if (linkage.kind === "unlinked") {
122
+ return approximationResult({
123
+ linkageKind: "unlinked",
124
+ relationshipMeaning: "no-readout-exposure-relationship-asserted",
125
+ readoutMode: sensorReadout.value.readoutMode,
126
+ shutterMechanism: input.shutterMechanism,
127
+ activeCaptureRect: {
128
+ ...sensorReadout.value.activeCaptureRect
129
+ },
130
+ absoluteTemporalAlignment: "not-established",
131
+ captureReadoutDurationSeconds: sensorReadout.value.captureReadoutDurationSeconds,
132
+ links: [],
133
+ componentProvenance: {
134
+ sensorReadout: sensorReadout.provenance,
135
+ exposureWindows: exposureWindows.provenance
136
+ }
137
+ }, "readout-exposure-spatial-linkage", "1.0.0", [
138
+ "Unlinked means Photivra asserts no readout-to-exposure relationship for this capture; it does not prove the physical processes are independent.",
139
+ "Sensor data-readout timing and exposure-window timing retain independent provenance and semantics.",
140
+ "Absolute temporal alignment between data readout and exposure boundaries is not established."
141
+ ]);
142
+ }
143
+ const readoutScan = sensorReadout.value.scan;
144
+ if (readoutScan === null) {
145
+ throw new InvalidScientificInputError("spatial-phase-linked linkage requires rolling sensor readout with a spatial scan.");
146
+ }
147
+ const assessments = linkage.links.map((link) => {
148
+ const boundary = link.boundary === "opening"
149
+ ? exposureWindows.value.opening
150
+ : exposureWindows.value.closing;
151
+ if (boundary.actuator !== "electronic") {
152
+ throw new InvalidScientificInputError("A readout spatial-phase link may target only an electronic exposure boundary.");
153
+ }
154
+ if (boundary.schedule.kind !==
155
+ "uniform-linear-native-scan") {
156
+ throw new InvalidScientificInputError("A readout spatial-phase link requires the selected exposure boundary to use a uniform-linear native scan.");
157
+ }
158
+ const readoutDirection = readoutScan.directionNative.value;
159
+ const boundaryDirection = boundary.schedule.directionNative.value;
160
+ const expectedDirection = expectedBoundaryDirection(readoutDirection, link.phaseOrientation);
161
+ if (boundaryDirection !== expectedDirection) {
162
+ throw new InvalidScientificInputError("Declared readout/exposure spatial phase orientation contradicts the readout and exposure-boundary scan directions.");
163
+ }
164
+ const readoutSpatialSamplingSkewSeconds = readoutScan.spatialSamplingSkewSeconds;
165
+ const boundaryTraversalDurationSeconds = boundary.schedule.traversalDurationSeconds;
166
+ return {
167
+ boundary: link.boundary,
168
+ boundaryActuator: "electronic",
169
+ phaseOrientation: link.phaseOrientation,
170
+ normalizedPhaseRelationship: link.phaseOrientation === "same"
171
+ ? "boundary-phase-equals-readout-phase"
172
+ : "boundary-phase-equals-one-minus-readout-phase",
173
+ relationshipEvidence: link.evidence,
174
+ readoutDirectionNative: readoutDirection,
175
+ boundaryDirectionNative: boundaryDirection,
176
+ readoutSpatialSamplingSkewSeconds,
177
+ boundaryTraversalDurationSeconds,
178
+ boundaryTraversalToReadoutSpatialSkewRatio: boundaryTraversalDurationSeconds.value /
179
+ readoutSpatialSamplingSkewSeconds.value,
180
+ absoluteTemporalAlignment: "not-established"
181
+ };
182
+ });
183
+ return approximationResult({
184
+ linkageKind: "spatial-phase-linked",
185
+ relationshipMeaning: "normalized-spatial-phase-relationship-asserted",
186
+ readoutMode: sensorReadout.value.readoutMode,
187
+ shutterMechanism: input.shutterMechanism,
188
+ activeCaptureRect: {
189
+ ...sensorReadout.value.activeCaptureRect
190
+ },
191
+ absoluteTemporalAlignment: "not-established",
192
+ captureReadoutDurationSeconds: sensorReadout.value.captureReadoutDurationSeconds,
193
+ links: assessments,
194
+ componentProvenance: {
195
+ sensorReadout: sensorReadout.provenance,
196
+ exposureWindows: exposureWindows.provenance
197
+ }
198
+ }, "readout-exposure-spatial-linkage", "1.0.0", [
199
+ "The linkage establishes normalized native-sensor spatial phase/order only, not absolute temporal synchronization.",
200
+ "Every asserted boundary link carries evidence separate from the evidence for the underlying readout and exposure timing facts.",
201
+ "Only electronic exposure boundaries may be linked to rolling sensor readout spatial phase.",
202
+ "Same phase orientation requires matching native scan direction; reversed phase orientation requires the exact opposite direction on the same native axis.",
203
+ "Rolling readout spatial skew and exposure-boundary traversal duration may differ; their ratio is descriptive and does not establish a common clock origin.",
204
+ "Capture data-readout duration is not used to validate exposure-boundary timing linkage.",
205
+ "Active capture, native coordinate semantics and shutter mechanism are shared because both component schedules are resolved from the same input.",
206
+ "Sensor readout and exposure-window component provenance remain visible; linkage does not upgrade either component to calibrated status.",
207
+ "No camera-specific timing relationship is inferred from shutter mechanism, sensor architecture metadata, output resolution or adjacent specifications."
208
+ ]);
209
+ }
210
+ //# sourceMappingURL=readout-exposure-linkage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readout-exposure-linkage.js","sourceRoot":"","sources":["../../src/sensor/readout-exposure-linkage.ts"],"names":[],"mappings":"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"}