@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.
- package/CHANGELOG.md +32 -0
- package/CITATION.cff +1 -1
- package/README.md +37 -3
- package/dist/index.d.ts +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/motion/camera-rotation.d.ts +60 -0
- package/dist/motion/camera-rotation.d.ts.map +1 -1
- package/dist/motion/camera-rotation.js +95 -0
- package/dist/motion/camera-rotation.js.map +1 -1
- package/dist/motion/capture-rotation-inverse-mapping.d.ts +100 -0
- package/dist/motion/capture-rotation-inverse-mapping.d.ts.map +1 -0
- package/dist/motion/capture-rotation-inverse-mapping.js +171 -0
- package/dist/motion/capture-rotation-inverse-mapping.js.map +1 -0
- package/dist/motion/capture-rotation-temporal-quadrature.d.ts +89 -0
- package/dist/motion/capture-rotation-temporal-quadrature.d.ts.map +1 -0
- package/dist/motion/capture-rotation-temporal-quadrature.js +116 -0
- package/dist/motion/capture-rotation-temporal-quadrature.js.map +1 -0
- package/dist/motion/capture-rotation-trajectory.d.ts +98 -0
- package/dist/motion/capture-rotation-trajectory.d.ts.map +1 -0
- package/dist/motion/capture-rotation-trajectory.js +153 -0
- package/dist/motion/capture-rotation-trajectory.js.map +1 -0
- package/dist/optics/lateral-chromatic-aberration.d.ts.map +1 -1
- package/dist/optics/lateral-chromatic-aberration.js +12 -4
- package/dist/optics/lateral-chromatic-aberration.js.map +1 -1
- package/dist/optics/radial-distortion.d.ts +4 -2
- package/dist/optics/radial-distortion.d.ts.map +1 -1
- package/dist/optics/radial-distortion.js +83 -23
- package/dist/optics/radial-distortion.js.map +1 -1
- package/dist/output/capture-geometry.d.ts.map +1 -1
- package/dist/output/capture-geometry.js +24 -11
- package/dist/output/capture-geometry.js.map +1 -1
- package/dist/sensor/capture-color-sampling-binding.d.ts +211 -0
- package/dist/sensor/capture-color-sampling-binding.d.ts.map +1 -0
- package/dist/sensor/capture-color-sampling-binding.js +423 -0
- package/dist/sensor/capture-color-sampling-binding.js.map +1 -0
- package/dist/sensor/capture-mode.d.ts +157 -0
- package/dist/sensor/capture-mode.d.ts.map +1 -0
- package/dist/sensor/capture-mode.js +434 -0
- package/dist/sensor/capture-mode.js.map +1 -0
- package/dist/sensor/color-sampling.d.ts +133 -0
- package/dist/sensor/color-sampling.d.ts.map +1 -0
- package/dist/sensor/color-sampling.js +226 -0
- package/dist/sensor/color-sampling.js.map +1 -0
- package/dist/sensor/exposure-window.d.ts +103 -0
- package/dist/sensor/exposure-window.d.ts.map +1 -0
- package/dist/sensor/exposure-window.js +291 -0
- package/dist/sensor/exposure-window.js.map +1 -0
- package/dist/sensor/optical-stack.d.ts +132 -0
- package/dist/sensor/optical-stack.d.ts.map +1 -0
- package/dist/sensor/optical-stack.js +286 -0
- package/dist/sensor/optical-stack.js.map +1 -0
- package/dist/sensor/readout-exposure-linkage.d.ts +111 -0
- package/dist/sensor/readout-exposure-linkage.d.ts.map +1 -0
- package/dist/sensor/readout-exposure-linkage.js +210 -0
- package/dist/sensor/readout-exposure-linkage.js.map +1 -0
- package/dist/sensor/readout-timing.d.ts +124 -0
- package/dist/sensor/readout-timing.d.ts.map +1 -0
- package/dist/sensor/readout-timing.js +230 -0
- package/dist/sensor/readout-timing.js.map +1 -0
- package/dist/sensor/sampling-aperture.d.ts +154 -0
- package/dist/sensor/sampling-aperture.d.ts.map +1 -0
- package/dist/sensor/sampling-aperture.js +374 -0
- package/dist/sensor/sampling-aperture.js.map +1 -0
- package/dist/sensor/spatial-sample-reduction.d.ts +128 -0
- package/dist/sensor/spatial-sample-reduction.d.ts.map +1 -0
- package/dist/sensor/spatial-sample-reduction.js +299 -0
- package/dist/sensor/spatial-sample-reduction.js.map +1 -0
- package/dist/sensor/spatial-sampling-quadrature.d.ts +155 -0
- package/dist/sensor/spatial-sampling-quadrature.d.ts.map +1 -0
- package/dist/sensor/spatial-sampling-quadrature.js +197 -0
- package/dist/sensor/spatial-sampling-quadrature.js.map +1 -0
- package/docs/ARCHITECTURE.md +17 -0
- package/docs/AUTOMATION_COORDINATION.md +188 -0
- package/docs/IMAGE_FORMATION.md +26 -0
- package/docs/MOTION_AND_SIGNAL.md +142 -0
- package/docs/USAGE.md +1273 -3
- 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
|