@photivra/engine 0.5.0 → 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 +38 -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 +103 -28
  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,171 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { approximationResult } from "../core/calculation-result.js";
3
+ import { InvalidScientificInputError, requirePositiveFinite } from "../core/validation.js";
4
+ import { mapOrientedPhysicalUvToImagePlanePoint, resolveCaptureGeometry, transformNativeRasterVectorToOriented } from "../output/capture-geometry.js";
5
+ import { calculateCaptureExposureWindows } from "../sensor/exposure-window.js";
6
+ import { calculateSensorGeometryMetrics } from "../sensor/sensor-geometry.js";
7
+ import { calculateInverseCameraRotationImageMapping } from "./camera-rotation.js";
8
+ function requireExposurePhase(value) {
9
+ if (!Number.isFinite(value) || value < 0 || value > 1) {
10
+ throw new InvalidScientificInputError("localExposurePhase must be finite and within [0, 1].");
11
+ }
12
+ }
13
+ function requireNonEmptySamples(value) {
14
+ if (!Array.isArray(value) || value.length === 0) {
15
+ throw new InvalidScientificInputError("samplePointsNative must be a non-empty array.");
16
+ }
17
+ }
18
+ function nativePointToImagePlane(point, activeCaptureRect, activePhysicalBoundsFromOpticalAxisMm) {
19
+ const u = (point.x - activeCaptureRect.x) / activeCaptureRect.width;
20
+ const v = (point.y - activeCaptureRect.y) / activeCaptureRect.height;
21
+ return mapOrientedPhysicalUvToImagePlanePoint({
22
+ uv: { u, v },
23
+ orientedPhysicalBoundsFromOpticalAxisMm: activePhysicalBoundsFromOpticalAxisMm,
24
+ orientation: "landscape"
25
+ });
26
+ }
27
+ function captureTimeAtPhase(exposure, phase) {
28
+ return (exposure.startOffsetSecondsFromOpeningReference +
29
+ phase * exposure.localExposureDurationSeconds);
30
+ }
31
+ /**
32
+ * Calculates an instantaneous inverse capture-scan mapping for stationary
33
+ * world rays under pure camera rotation.
34
+ *
35
+ * The destination native sensor location is authoritative for local exposure
36
+ * timing. At an explicit phase within that local window, the captured
37
+ * image-plane ray is analytically mapped back to the reference image plane at
38
+ * the first opening-boundary phase.
39
+ *
40
+ * No fixed-point/iterative solve is used because pure rotation under the
41
+ * existing constant-axis model has an analytic inverse once destination
42
+ * location and local capture time are known.
43
+ *
44
+ * This function is not a finite-exposure renderer and does not integrate blur.
45
+ * Sensor data-readout timing is deliberately absent; exposure boundaries are
46
+ * the only timing source.
47
+ */
48
+ export function calculateCaptureRotationInverseMappings(input) {
49
+ requirePositiveFinite("imagingArea.widthMm", input.imagingArea.widthMm);
50
+ requirePositiveFinite("imagingArea.heightMm", input.imagingArea.heightMm);
51
+ requirePositiveFinite("focalLengthMm", input.focalLengthMm);
52
+ requireExposurePhase(input.localExposurePhase);
53
+ requireNonEmptySamples(input.samplePointsNative);
54
+ const exposureWindows = calculateCaptureExposureWindows({
55
+ nativeRaster: input.nativeRaster,
56
+ ...(input.activeCaptureRect === undefined
57
+ ? {}
58
+ : { activeCaptureRect: input.activeCaptureRect }),
59
+ shutterMechanism: input.shutterMechanism,
60
+ nominalExposureDurationSeconds: input.nominalExposureDurationSeconds,
61
+ opening: input.opening,
62
+ closing: input.closing,
63
+ samplePointsNative: input.samplePointsNative
64
+ });
65
+ const geometry = resolveCaptureGeometry({
66
+ imagingArea: input.imagingArea,
67
+ nativeRaster: input.nativeRaster,
68
+ orientation: input.orientation,
69
+ activeCaptureRect: exposureWindows.value.activeCaptureRect
70
+ }).value;
71
+ const sensorGeometry = calculateSensorGeometryMetrics({
72
+ imagingArea: input.imagingArea,
73
+ nativeRaster: input.nativeRaster
74
+ });
75
+ const pitchXmm = sensorGeometry.value.sampling.pitchXMicrometers / 1000;
76
+ const pitchYmm = sensorGeometry.value.sampling.pitchYMicrometers / 1000;
77
+ let inverseRotationProvenance;
78
+ const samples = input.samplePointsNative.map((point, index) => {
79
+ const exposure = exposureWindows.value.samples[index];
80
+ if (exposure === undefined) {
81
+ throw new InvalidScientificInputError("Exposure-window sample alignment failed.");
82
+ }
83
+ const destinationImagePointMm = nativePointToImagePlane(point, geometry.activeCapture.nativeRect, geometry.activeCapture.physicalBoundsFromOpticalAxisMm);
84
+ const captureTimeSecondsFromReference = captureTimeAtPhase(exposure, input.localExposurePhase);
85
+ const inverse = calculateInverseCameraRotationImageMapping({
86
+ focalLengthMm: input.focalLengthMm,
87
+ imagePointMm: destinationImagePointMm,
88
+ timeSecondsFromExposureStart: captureTimeSecondsFromReference,
89
+ angularVelocityRadPerSec: input.angularVelocityRadPerSec,
90
+ ...(input.focusDistanceM === undefined
91
+ ? {}
92
+ : { focusDistanceM: input.focusDistanceM }),
93
+ samplingPitchMicrometers: {
94
+ x: sensorGeometry.value.sampling.pitchXMicrometers,
95
+ y: sensorGeometry.value.sampling.pitchYMicrometers
96
+ }
97
+ });
98
+ inverseRotationProvenance ??= inverse.provenance;
99
+ const nativeX = inverse.value.deltaMm.x / pitchXmm;
100
+ const nativeY = -inverse.value.deltaMm.y / pitchYmm;
101
+ const inverseDisplacementNativeSamples = {
102
+ x: nativeX,
103
+ y: nativeY,
104
+ distance: Math.hypot(nativeX, nativeY)
105
+ };
106
+ const orientedVector = transformNativeRasterVectorToOriented({
107
+ vector: {
108
+ x: inverseDisplacementNativeSamples.x,
109
+ y: inverseDisplacementNativeSamples.y
110
+ },
111
+ orientation: input.orientation
112
+ });
113
+ const inverseDisplacementOrientedSamples = {
114
+ ...orientedVector,
115
+ distance: Math.hypot(orientedVector.x, orientedVector.y)
116
+ };
117
+ return {
118
+ destinationPointNative: { ...point },
119
+ destinationImagePointMm,
120
+ localExposureWindow: {
121
+ startSecondsFromCaptureReference: exposure.startOffsetSecondsFromOpeningReference,
122
+ endSecondsFromCaptureReference: exposure.endOffsetSecondsFromOpeningReference,
123
+ durationSeconds: exposure.localExposureDurationSeconds
124
+ },
125
+ localExposurePhase: input.localExposurePhase,
126
+ captureTimeSecondsFromReference,
127
+ referenceImagePointMm: {
128
+ ...inverse.value.referenceImagePointMm
129
+ },
130
+ inverseDisplacementImagePlaneMm: {
131
+ ...inverse.value.deltaMm
132
+ },
133
+ inverseDisplacementNativeSamples,
134
+ inverseDisplacementOrientedSamples
135
+ };
136
+ });
137
+ if (inverseRotationProvenance === undefined) {
138
+ throw new InvalidScientificInputError("At least one inverse rotation mapping sample is required.");
139
+ }
140
+ return approximationResult({
141
+ timeReference: "first-opening-boundary-phase",
142
+ referenceTimeSecondsFromCaptureReference: 0,
143
+ mappingMeaning: "instantaneous-destination-to-exposure-start-reference-ray",
144
+ orientation: input.orientation,
145
+ localExposurePhase: input.localExposurePhase,
146
+ imagingArea: { ...input.imagingArea },
147
+ activeCaptureRect: {
148
+ ...exposureWindows.value.activeCaptureRect
149
+ },
150
+ samples,
151
+ componentProvenance: {
152
+ exposureWindows: exposureWindows.provenance,
153
+ sensorGeometry: sensorGeometry.provenance,
154
+ inverseCameraRotation: inverseRotationProvenance
155
+ }
156
+ }, "capture-rotation-instantaneous-inverse-mapping", "1.0.0", [
157
+ "Destination native capture location determines the local exposure window.",
158
+ "The caller explicitly selects one phase within each local exposure interval; no midpoint or other phase is inferred.",
159
+ "The first opening-boundary phase is the reference image time and is bound to t=0 of the camera-rotation model for this composition.",
160
+ "Pure rotation is inverted analytically after local capture time is known; no iterative or fixed-point solver is used.",
161
+ "Sensor data-readout timing is not an input and is not assumed to equal any exposure boundary.",
162
+ "Reference image-plane coordinates are not clamped to the active capture and may fall outside it under motion.",
163
+ "Native geometric sample displacement uses the full physical imaging-area/native-raster pitch and is not output-resolution dependent.",
164
+ "Physical capture orientation rotates reported native displacement only; it does not alter native exposure timing.",
165
+ "The mapping lives in the ideal pre-lens image plane and does not compose radial distortion, chromatic aberration, PSF, or output resampling.",
166
+ "This is an instantaneous mapping, not finite-exposure integration or a blur kernel.",
167
+ "Camera translation, depth parallax, subject motion, deformation, occlusion changes, flash/flicker and shutter shock are excluded.",
168
+ "The model does not claim the global rolling/capture-scan picture-taking map is one-to-one for arbitrary motion."
169
+ ]);
170
+ }
171
+ //# sourceMappingURL=capture-rotation-inverse-mapping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-rotation-inverse-mapping.js","sourceRoot":"","sources":["../../src/motion/capture-rotation-inverse-mapping.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { type CalculationProvenance, type CalculationResult } from "../core/calculation-result.js";
2
+ import type { RasterPoint, RasterVector } from "../output/capture-geometry.js";
3
+ import type { ImagePlanePointMm } from "./camera-rotation.js";
4
+ import { type CalculateCaptureRotationInverseMappingsInput } from "./capture-rotation-inverse-mapping.js";
5
+ export interface CalculateCaptureRotationTemporalQuadratureInput extends Omit<CalculateCaptureRotationInverseMappingsInput, "localExposurePhase"> {
6
+ /**
7
+ * Number of deterministic temporal quadrature nodes per local exposure
8
+ * interval. Nodes use the uniform midpoint rule.
9
+ */
10
+ temporalSampleCount: number;
11
+ }
12
+ export interface CaptureRotationTemporalQuadratureNode {
13
+ temporalSampleIndex: number;
14
+ /**
15
+ * Normalized phase in the local exposure interval, strictly inside (0, 1).
16
+ */
17
+ localExposurePhase: number;
18
+ captureTimeSecondsFromReference: number;
19
+ /**
20
+ * Dimensionless weight for approximating the time-average of a downstream
21
+ * quantity under a uniform temporal-response assumption.
22
+ */
23
+ normalizedTimeWeight: number;
24
+ /**
25
+ * Seconds-valued quadrature measure for approximating a time integral.
26
+ *
27
+ * This is temporal measure only. It is not shutter transmission, radiometric
28
+ * throughput, photon count, sensor response, or scene radiance.
29
+ */
30
+ timeMeasureSeconds: number;
31
+ referenceImagePointMm: ImagePlanePointMm;
32
+ inverseDisplacementImagePlaneMm: {
33
+ x: number;
34
+ y: number;
35
+ distance: number;
36
+ };
37
+ inverseDisplacementNativeSamples: RasterVector & {
38
+ distance: number;
39
+ };
40
+ inverseDisplacementOrientedSamples: RasterVector & {
41
+ distance: number;
42
+ };
43
+ }
44
+ export interface CaptureRotationTemporalQuadraturePoint {
45
+ destinationPointNative: RasterPoint;
46
+ destinationImagePointMm: ImagePlanePointMm;
47
+ localExposureWindow: {
48
+ startSecondsFromCaptureReference: number;
49
+ endSecondsFromCaptureReference: number;
50
+ durationSeconds: number;
51
+ };
52
+ nodes: readonly CaptureRotationTemporalQuadratureNode[];
53
+ }
54
+ export interface CaptureRotationTemporalQuadrature {
55
+ timeReference: "first-opening-boundary-phase";
56
+ quadratureScheme: "uniform-midpoint";
57
+ temporalResponseModel: "uniform-over-local-exposure";
58
+ temporalSampleCount: number;
59
+ /**
60
+ * The quadrature returns geometry and temporal measure only. It does not
61
+ * integrate radiance or produce a blur kernel.
62
+ */
63
+ outputMeaning: "temporal-geometry-quadrature-nodes";
64
+ points: readonly CaptureRotationTemporalQuadraturePoint[];
65
+ componentProvenance: {
66
+ instantaneousInverseMapping: CalculationProvenance;
67
+ };
68
+ }
69
+ /**
70
+ * Builds deterministic temporal quadrature nodes for pure-camera-rotation
71
+ * capture geometry over each destination point's local exposure interval.
72
+ *
73
+ * The first model uses an equal-width midpoint rule. Every node reuses
74
+ * calculateCaptureRotationInverseMappings(), so the instantaneous geometry has
75
+ * one authoritative implementation.
76
+ *
77
+ * No radiance is evaluated here. normalizedTimeWeight is suitable for a
78
+ * downstream time-average under a uniform temporal-response assumption, while
79
+ * timeMeasureSeconds is the local dt measure for a downstream time integral.
80
+ * Neither weight includes shutter transmission, scene flicker, sensor
81
+ * response, radiometric calibration or any other throughput term.
82
+ *
83
+ * The function intentionally reports no numerical integration error estimate:
84
+ * geometry alone cannot determine the error in a scene-radiance integral.
85
+ * Downstream renderers may compare increasing temporal sample counts in their
86
+ * own radiance domain when convergence evidence is required.
87
+ */
88
+ export declare function calculateCaptureRotationTemporalQuadrature(input: CalculateCaptureRotationTemporalQuadratureInput): CalculationResult<CaptureRotationTemporalQuadrature>;
89
+ //# sourceMappingURL=capture-rotation-temporal-quadrature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-rotation-temporal-quadrature.d.ts","sourceRoot":"","sources":["../../src/motion/capture-rotation-temporal-quadrature.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAKvC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAEL,KAAK,4CAA4C,EAClD,MAAM,uCAAuC,CAAC;AAE/C,MAAM,WAAW,+CACf,SAAQ,IAAI,CACV,4CAA4C,EAC5C,oBAAoB,CACrB;IACD;;;OAGG;IACH,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,qCAAqC;IACpD,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;OAEG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,+BAA+B,EAAE,MAAM,CAAC;IACxC;;;OAGG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qBAAqB,EAAE,iBAAiB,CAAC;IACzC,+BAA+B,EAAE;QAC/B,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,gCAAgC,EAAE,YAAY,GAAG;QAC/C,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,kCAAkC,EAAE,YAAY,GAAG;QACjD,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,sCAAsC;IACrD,sBAAsB,EAAE,WAAW,CAAC;IACpC,uBAAuB,EAAE,iBAAiB,CAAC;IAC3C,mBAAmB,EAAE;QACnB,gCAAgC,EAAE,MAAM,CAAC;QACzC,8BAA8B,EAAE,MAAM,CAAC;QACvC,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,KAAK,EAAE,SAAS,qCAAqC,EAAE,CAAC;CACzD;AAED,MAAM,WAAW,iCAAiC;IAChD,aAAa,EAAE,8BAA8B,CAAC;IAC9C,gBAAgB,EAAE,kBAAkB,CAAC;IACrC,qBAAqB,EAAE,6BAA6B,CAAC;IACrD,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,aAAa,EAAE,oCAAoC,CAAC;IACpD,MAAM,EAAE,SAAS,sCAAsC,EAAE,CAAC;IAC1D,mBAAmB,EAAE;QACnB,2BAA2B,EAAE,qBAAqB,CAAC;KACpD,CAAC;CACH;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0CAA0C,CACxD,KAAK,EAAE,+CAA+C,GACrD,iBAAiB,CAAC,iCAAiC,CAAC,CA2ItD"}
@@ -0,0 +1,116 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { approximationResult } from "../core/calculation-result.js";
3
+ import { InvalidScientificInputError, requirePositiveInteger } from "../core/validation.js";
4
+ import { calculateCaptureRotationInverseMappings } from "./capture-rotation-inverse-mapping.js";
5
+ function midpointPhase(index, sampleCount) {
6
+ return (index + 0.5) / sampleCount;
7
+ }
8
+ /**
9
+ * Builds deterministic temporal quadrature nodes for pure-camera-rotation
10
+ * capture geometry over each destination point's local exposure interval.
11
+ *
12
+ * The first model uses an equal-width midpoint rule. Every node reuses
13
+ * calculateCaptureRotationInverseMappings(), so the instantaneous geometry has
14
+ * one authoritative implementation.
15
+ *
16
+ * No radiance is evaluated here. normalizedTimeWeight is suitable for a
17
+ * downstream time-average under a uniform temporal-response assumption, while
18
+ * timeMeasureSeconds is the local dt measure for a downstream time integral.
19
+ * Neither weight includes shutter transmission, scene flicker, sensor
20
+ * response, radiometric calibration or any other throughput term.
21
+ *
22
+ * The function intentionally reports no numerical integration error estimate:
23
+ * geometry alone cannot determine the error in a scene-radiance integral.
24
+ * Downstream renderers may compare increasing temporal sample counts in their
25
+ * own radiance domain when convergence evidence is required.
26
+ */
27
+ export function calculateCaptureRotationTemporalQuadrature(input) {
28
+ requirePositiveInteger("temporalSampleCount", input.temporalSampleCount);
29
+ if (!Array.isArray(input.samplePointsNative) ||
30
+ input.samplePointsNative.length === 0) {
31
+ throw new InvalidScientificInputError("samplePointsNative must be a non-empty array.");
32
+ }
33
+ const totalNodeCount = input.temporalSampleCount * input.samplePointsNative.length;
34
+ if (!Number.isSafeInteger(totalNodeCount)) {
35
+ throw new InvalidScientificInputError("temporalSampleCount × samplePointsNative.length must be a safe integer.");
36
+ }
37
+ const { temporalSampleCount, ...instantaneousInput } = input;
38
+ const phaseResults = Array.from({ length: temporalSampleCount }, (_, temporalSampleIndex) => {
39
+ const localExposurePhase = midpointPhase(temporalSampleIndex, temporalSampleCount);
40
+ return calculateCaptureRotationInverseMappings({
41
+ ...instantaneousInput,
42
+ localExposurePhase
43
+ });
44
+ });
45
+ const firstResult = phaseResults[0];
46
+ if (firstResult === undefined) {
47
+ throw new InvalidScientificInputError("At least one temporal quadrature node is required.");
48
+ }
49
+ const normalizedTimeWeight = 1 / temporalSampleCount;
50
+ const points = firstResult.value.samples.map((firstSample, pointIndex) => {
51
+ const durationSeconds = firstSample.localExposureWindow.durationSeconds;
52
+ const timeMeasureSeconds = durationSeconds / temporalSampleCount;
53
+ const nodes = phaseResults.map((phaseResult, temporalSampleIndex) => {
54
+ const sample = phaseResult.value.samples[pointIndex];
55
+ if (sample === undefined) {
56
+ throw new InvalidScientificInputError("Instantaneous inverse-mapping sample alignment failed.");
57
+ }
58
+ const localExposurePhase = midpointPhase(temporalSampleIndex, temporalSampleCount);
59
+ return {
60
+ temporalSampleIndex,
61
+ localExposurePhase,
62
+ captureTimeSecondsFromReference: sample.captureTimeSecondsFromReference,
63
+ normalizedTimeWeight,
64
+ timeMeasureSeconds,
65
+ referenceImagePointMm: {
66
+ ...sample.referenceImagePointMm
67
+ },
68
+ inverseDisplacementImagePlaneMm: {
69
+ ...sample.inverseDisplacementImagePlaneMm
70
+ },
71
+ inverseDisplacementNativeSamples: {
72
+ ...sample.inverseDisplacementNativeSamples
73
+ },
74
+ inverseDisplacementOrientedSamples: {
75
+ ...sample.inverseDisplacementOrientedSamples
76
+ }
77
+ };
78
+ });
79
+ return {
80
+ destinationPointNative: {
81
+ ...firstSample.destinationPointNative
82
+ },
83
+ destinationImagePointMm: {
84
+ ...firstSample.destinationImagePointMm
85
+ },
86
+ localExposureWindow: {
87
+ ...firstSample.localExposureWindow
88
+ },
89
+ nodes
90
+ };
91
+ });
92
+ return approximationResult({
93
+ timeReference: "first-opening-boundary-phase",
94
+ quadratureScheme: "uniform-midpoint",
95
+ temporalResponseModel: "uniform-over-local-exposure",
96
+ temporalSampleCount,
97
+ outputMeaning: "temporal-geometry-quadrature-nodes",
98
+ points,
99
+ componentProvenance: {
100
+ instantaneousInverseMapping: firstResult.provenance
101
+ }
102
+ }, "capture-rotation-temporal-quadrature", "1.0.0", [
103
+ "Each destination native point retains its own validated local exposure interval.",
104
+ "Temporal nodes use a deterministic equal-width midpoint rule with phases (i + 0.5) / N and therefore do not sample the exact opening/closing boundaries.",
105
+ "Normalized temporal weights sum approximately to one and represent a uniform time-average measure only.",
106
+ "Seconds-valued temporal measures sum approximately to the local exposure duration and represent dt only.",
107
+ "Uniform temporal response is an explicit approximation; shutter transmission ramps, scene flicker, sensor response and radiometric throughput are not included.",
108
+ "Every node delegates instantaneous geometry to calculateCaptureRotationInverseMappings(); no second camera-motion equation is introduced.",
109
+ "Sensor data-readout timing and readout/exposure linkage metadata do not alter the exposure integral unless a future explicit image-formation model consumes them.",
110
+ "Reference rays may leave the active source frame under motion and are not clamped.",
111
+ "No radiance, photon count, blur kernel, PSF, source resampling or output pixel value is calculated.",
112
+ "No geometry-only convergence/error estimate is reported because temporal integration error depends on the downstream radiance/visibility function as well as motion.",
113
+ "Camera translation, scene-depth parallax, subject motion/deformation, occlusion changes, flash/flicker, shutter shock and non-uniform temporal response remain separate future work."
114
+ ]);
115
+ }
116
+ //# sourceMappingURL=capture-rotation-temporal-quadrature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-rotation-temporal-quadrature.js","sourceRoot":"","sources":["../../src/motion/capture-rotation-temporal-quadrature.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ import { type CalculationProvenance, type CalculationResult } from "../core/calculation-result.js";
2
+ import { type RasterPoint } from "../output/capture-geometry.js";
3
+ import { type CalculateCaptureExposureWindowsInput } from "../sensor/exposure-window.js";
4
+ import type { SensorImagingArea } from "../sensor/sensor-geometry.js";
5
+ import { type CameraAngularVelocityRadPerSec, type ImagePlanePointMm } from "./camera-rotation.js";
6
+ export interface CalculateCaptureRotationTrajectoriesInput extends CalculateCaptureExposureWindowsInput {
7
+ /**
8
+ * Physical photosensitive imaging area associated with nativeRaster.
9
+ */
10
+ imagingArea: SensorImagingArea;
11
+ /** Physical focal length in millimetres. */
12
+ focalLengthMm: number;
13
+ /** Optional focus distance in metres for focus-aware projection distance. */
14
+ focusDistanceM?: number;
15
+ /**
16
+ * Constant physical camera angular velocity resolved in camera axes at the
17
+ * first opening-boundary phase.
18
+ */
19
+ angularVelocityRadPerSec: CameraAngularVelocityRadPerSec;
20
+ /**
21
+ * Native points whose stationary-reference-ray trajectories should be
22
+ * evaluated. Required and non-empty for this trajectory API.
23
+ */
24
+ samplePointsNative: readonly RasterPoint[];
25
+ }
26
+ export interface CaptureRotationTrajectoryEndpoint {
27
+ timeSecondsFromCaptureReference: number;
28
+ mappedImagePointMm: ImagePlanePointMm;
29
+ deltaFromReferenceMm: {
30
+ x: number;
31
+ y: number;
32
+ distance: number;
33
+ };
34
+ }
35
+ export interface CaptureRotationTrajectorySample {
36
+ pointNative: RasterPoint;
37
+ /**
38
+ * Image-plane position of the stationary world ray at the capture reference
39
+ * time (the first opening-boundary phase).
40
+ */
41
+ referenceImagePointMm: ImagePlanePointMm;
42
+ localExposureWindow: {
43
+ startSecondsFromCaptureReference: number;
44
+ endSecondsFromCaptureReference: number;
45
+ durationSeconds: number;
46
+ openingNormalizedScanPosition: number | null;
47
+ closingNormalizedScanPosition: number | null;
48
+ };
49
+ atLocalExposureStart: CaptureRotationTrajectoryEndpoint;
50
+ atLocalExposureEnd: CaptureRotationTrajectoryEndpoint;
51
+ /**
52
+ * Chord between the two trajectory endpoints.
53
+ *
54
+ * This is not a blur kernel, integrated exposure, or rolling-shutter warp.
55
+ */
56
+ localExposureTrajectoryEndpointDeltaMm: {
57
+ x: number;
58
+ y: number;
59
+ distance: number;
60
+ };
61
+ }
62
+ export interface CaptureRotationTrajectories {
63
+ timeReference: "first-opening-boundary-phase";
64
+ trajectoryMeaning: "forward-stationary-reference-ray";
65
+ imagingArea: SensorImagingArea;
66
+ activeCaptureRect: {
67
+ x: number;
68
+ y: number;
69
+ width: number;
70
+ height: number;
71
+ };
72
+ samples: readonly CaptureRotationTrajectorySample[];
73
+ componentProvenance: {
74
+ exposureWindows: CalculationProvenance;
75
+ cameraRotation: CalculationProvenance;
76
+ };
77
+ }
78
+ /**
79
+ * Evaluates pure-camera-rotation trajectories for stationary reference rays
80
+ * over each point's local exposure window.
81
+ *
82
+ * This is a temporal-geometry bridge, not a finished rolling-shutter image
83
+ * mapping. The native sample point defines where a stationary world ray would
84
+ * land in the reference global-shutter image at the first opening-boundary
85
+ * phase. The existing camera-rotation model is then evaluated at that point's
86
+ * local exposure start and end.
87
+ *
88
+ * Sensor data-readout timing is intentionally absent. A camera/mode-specific
89
+ * relationship between sensor readout and exposure boundaries requires a
90
+ * separate explicit contract; this function never assumes one.
91
+ *
92
+ * A later renderer-facing rolling-shutter warp must solve the image mapping
93
+ * consistently with capture-location-dependent time and integrate over the
94
+ * local exposure interval. The endpoint chord returned here must not be treated
95
+ * as a blur kernel or as an inverse destination-to-source sampling map.
96
+ */
97
+ export declare function calculateCaptureRotationTrajectories(input: CalculateCaptureRotationTrajectoriesInput): CalculationResult<CaptureRotationTrajectories>;
98
+ //# sourceMappingURL=capture-rotation-trajectory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-rotation-trajectory.d.ts","sourceRoot":"","sources":["../../src/motion/capture-rotation-trajectory.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAKvC,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAEL,KAAK,oCAAoC,EAE1C,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAEL,KAAK,8BAA8B,EAEnC,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,yCACf,SAAQ,oCAAoC;IAC5C;;OAEG;IACH,WAAW,EAAE,iBAAiB,CAAC;IAC/B,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,wBAAwB,EAAE,8BAA8B,CAAC;IACzD;;;OAGG;IACH,kBAAkB,EAAE,SAAS,WAAW,EAAE,CAAC;CAC5C;AAED,MAAM,WAAW,iCAAiC;IAChD,+BAA+B,EAAE,MAAM,CAAC;IACxC,kBAAkB,EAAE,iBAAiB,CAAC;IACtC,oBAAoB,EAAE;QACpB,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,+BAA+B;IAC9C,WAAW,EAAE,WAAW,CAAC;IACzB;;;OAGG;IACH,qBAAqB,EAAE,iBAAiB,CAAC;IACzC,mBAAmB,EAAE;QACnB,gCAAgC,EAAE,MAAM,CAAC;QACzC,8BAA8B,EAAE,MAAM,CAAC;QACvC,eAAe,EAAE,MAAM,CAAC;QACxB,6BAA6B,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7C,6BAA6B,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9C,CAAC;IACF,oBAAoB,EAAE,iCAAiC,CAAC;IACxD,kBAAkB,EAAE,iCAAiC,CAAC;IACtD;;;;OAIG;IACH,sCAAsC,EAAE;QACtC,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,8BAA8B,CAAC;IAC9C,iBAAiB,EAAE,kCAAkC,CAAC;IACtD,WAAW,EAAE,iBAAiB,CAAC;IAC/B,iBAAiB,EAAE;QACjB,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,EAAE,SAAS,+BAA+B,EAAE,CAAC;IACpD,mBAAmB,EAAE;QACnB,eAAe,EAAE,qBAAqB,CAAC;QACvC,cAAc,EAAE,qBAAqB,CAAC;KACvC,CAAC;CACH;AAgHD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,yCAAyC,GAC/C,iBAAiB,CAAC,2BAA2B,CAAC,CAyFhD"}
@@ -0,0 +1,153 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ import { approximationResult } from "../core/calculation-result.js";
3
+ import { InvalidScientificInputError, requirePositiveFinite } from "../core/validation.js";
4
+ import { mapOrientedPhysicalUvToImagePlanePoint, resolveCaptureGeometry } from "../output/capture-geometry.js";
5
+ import { calculateCaptureExposureWindows } from "../sensor/exposure-window.js";
6
+ import { calculateCameraRotationImageMapping } from "./camera-rotation.js";
7
+ function requireNonEmptySamples(samplePointsNative) {
8
+ if (!Array.isArray(samplePointsNative) || samplePointsNative.length === 0) {
9
+ throw new InvalidScientificInputError("samplePointsNative must be a non-empty array.");
10
+ }
11
+ }
12
+ function nativePointToReferenceImagePlane(point, activeCaptureRect, activePhysicalBoundsFromOpticalAxisMm) {
13
+ const u = (point.x - activeCaptureRect.x) / activeCaptureRect.width;
14
+ const v = (point.y - activeCaptureRect.y) / activeCaptureRect.height;
15
+ return mapOrientedPhysicalUvToImagePlanePoint({
16
+ uv: { u, v },
17
+ orientedPhysicalBoundsFromOpticalAxisMm: activePhysicalBoundsFromOpticalAxisMm,
18
+ orientation: "landscape"
19
+ });
20
+ }
21
+ function endpointFromMapping(mapping) {
22
+ return {
23
+ timeSecondsFromCaptureReference: mapping.timeSecondsFromExposureStart,
24
+ mappedImagePointMm: { ...mapping.mappedImagePointMm },
25
+ deltaFromReferenceMm: { ...mapping.deltaMm }
26
+ };
27
+ }
28
+ function trajectorySample(point, exposure, referenceImagePointMm, input) {
29
+ const common = {
30
+ focalLengthMm: input.focalLengthMm,
31
+ imagePointMm: referenceImagePointMm,
32
+ angularVelocityRadPerSec: input.angularVelocityRadPerSec,
33
+ ...(input.focusDistanceM === undefined
34
+ ? {}
35
+ : { focusDistanceM: input.focusDistanceM })
36
+ };
37
+ const start = calculateCameraRotationImageMapping({
38
+ ...common,
39
+ timeSecondsFromExposureStart: exposure.startOffsetSecondsFromOpeningReference
40
+ });
41
+ const end = calculateCameraRotationImageMapping({
42
+ ...common,
43
+ timeSecondsFromExposureStart: exposure.endOffsetSecondsFromOpeningReference
44
+ });
45
+ const endpointDeltaX = end.value.mappedImagePointMm.x -
46
+ start.value.mappedImagePointMm.x;
47
+ const endpointDeltaY = end.value.mappedImagePointMm.y -
48
+ start.value.mappedImagePointMm.y;
49
+ return {
50
+ sample: {
51
+ pointNative: { ...point },
52
+ referenceImagePointMm: { ...referenceImagePointMm },
53
+ localExposureWindow: {
54
+ startSecondsFromCaptureReference: exposure.startOffsetSecondsFromOpeningReference,
55
+ endSecondsFromCaptureReference: exposure.endOffsetSecondsFromOpeningReference,
56
+ durationSeconds: exposure.localExposureDurationSeconds,
57
+ openingNormalizedScanPosition: exposure.openingNormalizedScanPosition,
58
+ closingNormalizedScanPosition: exposure.closingNormalizedScanPosition
59
+ },
60
+ atLocalExposureStart: endpointFromMapping(start.value),
61
+ atLocalExposureEnd: endpointFromMapping(end.value),
62
+ localExposureTrajectoryEndpointDeltaMm: {
63
+ x: endpointDeltaX,
64
+ y: endpointDeltaY,
65
+ distance: Math.hypot(endpointDeltaX, endpointDeltaY)
66
+ }
67
+ },
68
+ rotationProvenance: start.provenance
69
+ };
70
+ }
71
+ /**
72
+ * Evaluates pure-camera-rotation trajectories for stationary reference rays
73
+ * over each point's local exposure window.
74
+ *
75
+ * This is a temporal-geometry bridge, not a finished rolling-shutter image
76
+ * mapping. The native sample point defines where a stationary world ray would
77
+ * land in the reference global-shutter image at the first opening-boundary
78
+ * phase. The existing camera-rotation model is then evaluated at that point's
79
+ * local exposure start and end.
80
+ *
81
+ * Sensor data-readout timing is intentionally absent. A camera/mode-specific
82
+ * relationship between sensor readout and exposure boundaries requires a
83
+ * separate explicit contract; this function never assumes one.
84
+ *
85
+ * A later renderer-facing rolling-shutter warp must solve the image mapping
86
+ * consistently with capture-location-dependent time and integrate over the
87
+ * local exposure interval. The endpoint chord returned here must not be treated
88
+ * as a blur kernel or as an inverse destination-to-source sampling map.
89
+ */
90
+ export function calculateCaptureRotationTrajectories(input) {
91
+ requirePositiveFinite("imagingArea.widthMm", input.imagingArea.widthMm);
92
+ requirePositiveFinite("imagingArea.heightMm", input.imagingArea.heightMm);
93
+ requirePositiveFinite("focalLengthMm", input.focalLengthMm);
94
+ requireNonEmptySamples(input.samplePointsNative);
95
+ const exposureWindows = calculateCaptureExposureWindows({
96
+ nativeRaster: input.nativeRaster,
97
+ ...(input.activeCaptureRect === undefined
98
+ ? {}
99
+ : { activeCaptureRect: input.activeCaptureRect }),
100
+ shutterMechanism: input.shutterMechanism,
101
+ nominalExposureDurationSeconds: input.nominalExposureDurationSeconds,
102
+ opening: input.opening,
103
+ closing: input.closing,
104
+ samplePointsNative: input.samplePointsNative
105
+ });
106
+ const geometry = resolveCaptureGeometry({
107
+ imagingArea: input.imagingArea,
108
+ nativeRaster: input.nativeRaster,
109
+ orientation: "landscape",
110
+ activeCaptureRect: exposureWindows.value.activeCaptureRect
111
+ }).value;
112
+ const exposureByPoint = exposureWindows.value.samples;
113
+ const samples = [];
114
+ let rotationProvenance;
115
+ input.samplePointsNative.forEach((point, index) => {
116
+ const exposure = exposureByPoint[index];
117
+ if (exposure === undefined) {
118
+ throw new InvalidScientificInputError("Exposure-window sample alignment failed.");
119
+ }
120
+ const referenceImagePointMm = nativePointToReferenceImagePlane(point, geometry.activeCapture.nativeRect, geometry.activeCapture.physicalBoundsFromOpticalAxisMm);
121
+ const resolved = trajectorySample(point, exposure, referenceImagePointMm, input);
122
+ samples.push(resolved.sample);
123
+ rotationProvenance ??= resolved.rotationProvenance;
124
+ });
125
+ if (rotationProvenance === undefined) {
126
+ throw new InvalidScientificInputError("At least one rotation trajectory sample is required.");
127
+ }
128
+ return approximationResult({
129
+ timeReference: "first-opening-boundary-phase",
130
+ trajectoryMeaning: "forward-stationary-reference-ray",
131
+ imagingArea: { ...input.imagingArea },
132
+ activeCaptureRect: {
133
+ ...exposureWindows.value.activeCaptureRect
134
+ },
135
+ samples,
136
+ componentProvenance: {
137
+ exposureWindows: exposureWindows.provenance,
138
+ cameraRotation: rotationProvenance
139
+ }
140
+ }, "capture-rotation-exposure-trajectory", "1.0.0", [
141
+ "The first opening-boundary phase is explicitly bound to t=0 of the low-level camera-rotation model for this composition only.",
142
+ "Each native point identifies a stationary world ray by its reference image-plane location at the capture reference time.",
143
+ "Local exposure start/end times come only from the capture exposure-window contract.",
144
+ "Sensor data-readout timing does not participate and is never assumed to equal an exposure boundary.",
145
+ "Camera angular velocity is constant and resolved in camera axes at the first opening-boundary phase.",
146
+ "Only pure camera rotation is modeled; camera translation, scene depth parallax, subject motion, deformation and occlusion changes are excluded.",
147
+ "The returned start/end mappings are a forward stationary-ray trajectory, not a rolling-shutter image warp.",
148
+ "The trajectory endpoint chord is not an integrated blur kernel and may not represent the curved path between endpoints.",
149
+ "A later inverse renderer warp must solve capture-location-dependent timing consistently rather than evaluating one forward displacement at the destination coordinate.",
150
+ "Exposure integration over the full local interval remains future work."
151
+ ]);
152
+ }
153
+ //# sourceMappingURL=capture-rotation-trajectory.js.map