@bubo-squared/gyroview 0.1.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/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +4 -0
- package/dist/index.d.ts +959 -0
- package/dist/index.js +13 -0
- package/dist/packages/adapters/blob/src/BlobRandomAccessSource.js +37 -0
- package/dist/packages/adapters/blob/src/index.js +2 -0
- package/dist/packages/adapters/fetch/src/HttpRangeSource.js +194 -0
- package/dist/packages/adapters/fetch/src/HttpResourceLocator.js +35 -0
- package/dist/packages/adapters/fetch/src/httpRequest.js +126 -0
- package/dist/packages/adapters/fetch/src/index.js +3 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyAudioSegments.js +133 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyDemuxer.js +63 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyVideoTrackReader.js +84 -0
- package/dist/packages/adapters/mediabunny/src/SegmentChannel.js +84 -0
- package/dist/packages/adapters/mediabunny/src/bufferSources.js +12 -0
- package/dist/packages/adapters/mediabunny/src/index.js +2 -0
- package/dist/packages/adapters/mse-audio/src/MediaSourceAudioClock.js +139 -0
- package/dist/packages/adapters/mse-audio/src/SourceBufferFeeder.js +174 -0
- package/dist/packages/adapters/mse-audio/src/events.js +26 -0
- package/dist/packages/adapters/mse-audio/src/index.js +2 -0
- package/dist/packages/adapters/mse-audio/src/mediaSourceSupport.js +82 -0
- package/dist/packages/adapters/three/src/ThreeFrameRenderer.js +274 -0
- package/dist/packages/adapters/three/src/compileAndProve.js +14 -0
- package/dist/packages/adapters/three/src/fullscreenPass.js +52 -0
- package/dist/packages/adapters/three/src/index.js +2 -0
- package/dist/packages/adapters/three/src/lensTextures.js +25 -0
- package/dist/packages/adapters/three/src/pictureMaterials.js +44 -0
- package/dist/packages/adapters/three/src/renderInto.js +15 -0
- package/dist/packages/adapters/three/src/rendererUniforms.js +157 -0
- package/dist/packages/adapters/three/src/samplingStrategies.js +26 -0
- package/dist/packages/adapters/three/src/seamMeter/SeamMeterPass.js +79 -0
- package/dist/packages/adapters/three/src/seamMeter/rowMeans.js +38 -0
- package/dist/packages/adapters/three/src/shaderPrograms.js +66 -0
- package/dist/packages/adapters/three/src/shaders/analysis.frag.js +4 -0
- package/dist/packages/adapters/three/src/shaders/equirectangularRays.js +4 -0
- package/dist/packages/adapters/three/src/shaders/fixedJoin.js +4 -0
- package/dist/packages/adapters/three/src/shaders/fullscreen.vert.js +4 -0
- package/dist/packages/adapters/three/src/shaders/header.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensModels.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensSampling.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensTextures.js +4 -0
- package/dist/packages/adapters/three/src/shaders/rawLenses.frag.js +4 -0
- package/dist/packages/adapters/three/src/shaders/rectilinearRays.js +4 -0
- package/dist/packages/adapters/three/src/shaders/screenAreas.js +4 -0
- package/dist/packages/adapters/three/src/shaders/stitch.frag.js +4 -0
- package/dist/packages/adapters/three/src/webglRenderer.js +37 -0
- package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +135 -0
- package/dist/packages/adapters/webcodecs/src/index.js +2 -0
- package/dist/packages/core/src/application/gainMatching/GainMatching.js +66 -0
- package/dist/packages/core/src/application/gainMatching/GainMatchingFrameSink.js +62 -0
- package/dist/packages/core/src/application/playback/ClockWatch.js +76 -0
- package/dist/packages/core/src/application/playback/DecodePipeline.js +188 -0
- package/dist/packages/core/src/application/playback/DecodeRun.js +93 -0
- package/dist/packages/core/src/application/playback/FramePairQueue.js +93 -0
- package/dist/packages/core/src/application/playback/FramePairer.js +60 -0
- package/dist/packages/core/src/application/playback/PlaybackSession.js +417 -0
- package/dist/packages/core/src/application/playback/SeekOrder.js +36 -0
- package/dist/packages/core/src/application/playback/SessionLifecycle.js +42 -0
- package/dist/packages/core/src/application/playback/StartGate.js +55 -0
- package/dist/packages/core/src/application/playback/TimeUpdates.js +31 -0
- package/dist/packages/core/src/application/playback/WallClock.js +46 -0
- package/dist/packages/core/src/application/playback/keyframeTimeAt.js +15 -0
- package/dist/packages/core/src/application/playback/probeDecoding.js +168 -0
- package/dist/packages/core/src/application/recording/frameTimesOf.js +22 -0
- package/dist/packages/core/src/application/recording/inspectLayout.js +21 -0
- package/dist/packages/core/src/application/recording/inspectRecording.js +124 -0
- package/dist/packages/core/src/application/recording/locateOtherLensFile.js +18 -0
- package/dist/packages/core/src/application/recording/motionOf.js +39 -0
- package/dist/packages/core/src/application/recording/readRecording.js +31 -0
- package/dist/packages/core/src/application/recording/timeRecording.js +57 -0
- package/dist/packages/core/src/application/stabilization/StabilizingFrameSink.js +31 -0
- package/dist/packages/core/src/domain/format/boxes/BoxLayout.js +7 -0
- package/dist/packages/core/src/domain/format/boxes/boxConstants.js +16 -0
- package/dist/packages/core/src/domain/format/boxes/scanBoxes.js +59 -0
- package/dist/packages/core/src/domain/format/calibration/CalibrationVersion.js +13 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/CalibrationStringLayout.js +34 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/LegacyCalibrationLayout.js +44 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/MeiCalibrationLayout.js +45 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/PolynomialCalibrationLayout.js +44 -0
- package/dist/packages/core/src/domain/format/calibration/offsetTokens.js +65 -0
- package/dist/packages/core/src/domain/format/calibration/parseOffsetString.js +52 -0
- package/dist/packages/core/src/domain/format/calibration/selectCalibration.js +46 -0
- package/dist/packages/core/src/domain/format/captureOrigin.js +20 -0
- package/dist/packages/core/src/domain/format/constants.js +33 -0
- package/dist/packages/core/src/domain/format/info/infoFields.js +110 -0
- package/dist/packages/core/src/domain/format/info/parseInfoRecord.js +90 -0
- package/dist/packages/core/src/domain/format/info/readInfoRecord.js +14 -0
- package/dist/packages/core/src/domain/format/layout/detectLensLayout.js +122 -0
- package/dist/packages/core/src/domain/format/naming/RecordingFileName.js +67 -0
- package/dist/packages/core/src/domain/format/records/TrailerRecords.js +83 -0
- package/dist/packages/core/src/domain/format/records/exposure/parseExposureRecord.js +48 -0
- package/dist/packages/core/src/domain/format/records/gyro/FloatGyroSampleLayout.js +39 -0
- package/dist/packages/core/src/domain/format/records/gyro/RawGyroSampleLayout.js +55 -0
- package/dist/packages/core/src/domain/format/records/gyro/SampleColumns.js +82 -0
- package/dist/packages/core/src/domain/format/records/gyro/gyroLayouts.js +12 -0
- package/dist/packages/core/src/domain/format/records/gyro/parseGyroRecord.js +129 -0
- package/dist/packages/core/src/domain/format/trailer/RecordHeader.js +23 -0
- package/dist/packages/core/src/domain/format/trailer/Trailer.js +23 -0
- package/dist/packages/core/src/domain/format/trailer/TrailerFooter.js +27 -0
- package/dist/packages/core/src/domain/format/trailer/locateRecords.js +80 -0
- package/dist/packages/core/src/domain/format/trailer/parseRecordIndex.js +28 -0
- package/dist/packages/core/src/domain/format/trailer/readTrailer.js +32 -0
- package/dist/packages/core/src/domain/motion/exposure/ExposureRecord.js +53 -0
- package/dist/packages/core/src/domain/motion/gyro/GyroTrack.js +60 -0
- package/dist/packages/core/src/domain/motion/gyro/medianStep.js +11 -0
- package/dist/packages/core/src/domain/motion/gyro/repairedTimeline.js +101 -0
- package/dist/packages/core/src/domain/motion/imu/ImuFrame.js +112 -0
- package/dist/packages/core/src/domain/motion/orientation/OrientationTrack.js +146 -0
- package/dist/packages/core/src/domain/motion/orientation/estimateGyroBias.js +64 -0
- package/dist/packages/core/src/domain/motion/orientation/gravity.js +97 -0
- package/dist/packages/core/src/domain/motion/stabilization/Stabilizer.js +13 -0
- package/dist/packages/core/src/domain/motion/stabilization/stabilizers.js +86 -0
- package/dist/packages/core/src/domain/motion/timing/CaptureClock.js +41 -0
- package/dist/packages/core/src/domain/motion/timing/FrameTimes.js +92 -0
- package/dist/packages/core/src/domain/motion/timing/frameTimeSources.js +61 -0
- package/dist/packages/core/src/domain/motion/timing/resolveFrameTimes.js +68 -0
- package/dist/packages/core/src/domain/optics/EquidistantModel.js +39 -0
- package/dist/packages/core/src/domain/optics/MeiModel.js +45 -0
- package/dist/packages/core/src/domain/optics/PolynomialModel.js +44 -0
- package/dist/packages/core/src/domain/optics/gainMatch.js +62 -0
- package/dist/packages/core/src/domain/optics/lensGeometry.js +18 -0
- package/dist/packages/core/src/domain/optics/lensPose.js +35 -0
- package/dist/packages/core/src/domain/optics/opticsConstants.js +5 -0
- package/dist/packages/core/src/domain/playback/PlayerState.js +83 -0
- package/dist/packages/core/src/domain/stitching/LensLayout.js +21 -0
- package/dist/packages/core/src/domain/stitching/StitchingSetup.js +78 -0
- package/dist/packages/core/src/domain/stitching/seamDisparity.js +4 -0
- package/dist/packages/core/src/domain/stitching/seamDisparityField.js +5 -0
- package/dist/packages/core/src/domain/stitching/seamJoin.js +16 -0
- package/dist/packages/core/src/domain/stitching/seamMismatch.js +1 -0
- package/dist/packages/core/src/domain/stitching/seamRing.js +4 -0
- package/dist/packages/core/src/domain/stitching/seamStrip.js +26 -0
- package/dist/packages/core/src/domain/view/Framing.js +31 -0
- package/dist/packages/core/src/domain/view/PictureQuality.js +26 -0
- package/dist/packages/core/src/domain/view/ViewMode.js +9 -0
- package/dist/packages/core/src/domain/view/ViewState.js +48 -0
- package/dist/packages/core/src/domain/view/lensTilesView.js +63 -0
- package/dist/packages/core/src/domain/view/magnification.js +103 -0
- package/dist/packages/core/src/domain/view/normalView.js +29 -0
- package/dist/packages/core/src/domain/view/panoramaView.js +99 -0
- package/dist/packages/core/src/domain/view/rectilinear.js +40 -0
- package/dist/packages/core/src/domain/view/screenLayout.js +113 -0
- package/dist/packages/core/src/domain/view/viewGestures.js +98 -0
- package/dist/packages/core/src/domain/view/viewModes.js +14 -0
- package/dist/packages/core/src/index.js +59 -0
- package/dist/packages/core/src/ports/FramePair.js +6 -0
- package/dist/packages/core/src/shared/async/Deferred.js +42 -0
- package/dist/packages/core/src/shared/async/RunStop.js +40 -0
- package/dist/packages/core/src/shared/binary/ByteRange.js +38 -0
- package/dist/packages/core/src/shared/binary/ByteReader.js +77 -0
- package/dist/packages/core/src/shared/errors/GyroViewError.js +81 -0
- package/dist/packages/core/src/shared/errors/reportLater.js +11 -0
- package/dist/packages/core/src/shared/events/Outbox.js +68 -0
- package/dist/packages/core/src/shared/events/TypedEmitter.js +55 -0
- package/dist/packages/core/src/shared/keysOf.js +10 -0
- package/dist/packages/core/src/shared/lazy.js +11 -0
- package/dist/packages/core/src/shared/math/Matrix3.js +93 -0
- package/dist/packages/core/src/shared/math/Quaternion.js +133 -0
- package/dist/packages/core/src/shared/math/Vector3.js +54 -0
- package/dist/packages/core/src/shared/math/clamp.js +9 -0
- package/dist/packages/core/src/shared/protobuf/ProtobufMessage.js +157 -0
- package/dist/packages/core/src/shared/text/urlPath.js +31 -0
- package/dist/packages/core/src/shared/text/utf8.js +12 -0
- package/dist/packages/core/src/shared/units/angle.js +22 -0
- package/dist/packages/core/src/shared/units/time.js +13 -0
- package/dist/packages/player/src/PlayerSource.js +15 -0
- package/dist/packages/player/src/browserPlayer.js +18 -0
- package/dist/packages/player/src/choices.js +25 -0
- package/dist/packages/player/src/composition/Disposables.js +31 -0
- package/dist/packages/player/src/composition/browserPorts.js +46 -0
- package/dist/packages/player/src/composition/buildPipeline.js +153 -0
- package/dist/packages/player/src/composition/ensureDecodable.js +63 -0
- package/dist/packages/player/src/composition/openInputs.js +179 -0
- package/dist/packages/player/src/composition/openRecording.js +42 -0
- package/dist/packages/player/src/controls/ChoiceMenu.js +113 -0
- package/dist/packages/player/src/controls/PictureMenus.js +47 -0
- package/dist/packages/player/src/controls/SeekBar.js +125 -0
- package/dist/packages/player/src/controls/SoundControls.js +41 -0
- package/dist/packages/player/src/controls/TransportButtons.js +31 -0
- package/dist/packages/player/src/controls/ViewGestures.js +136 -0
- package/dist/packages/player/src/controls/Wording.js +78 -0
- package/dist/packages/player/src/controls/controlParts.js +36 -0
- package/dist/packages/player/src/controls/controls.js +4 -0
- package/dist/packages/player/src/controls/controlsBar.js +23 -0
- package/dist/packages/player/src/controls/controlsMarkup.js +85 -0
- package/dist/packages/player/src/controls/customControls.js +59 -0
- package/dist/packages/player/src/controls/formatTime.js +19 -0
- package/dist/packages/player/src/controls/icons.js +40 -0
- package/dist/packages/player/src/controls/keyboard.js +118 -0
- package/dist/packages/player/src/controls/messages.js +94 -0
- package/dist/packages/player/src/controls/parseMarkup.js +31 -0
- package/dist/packages/player/src/controls/viewButtons.js +14 -0
- package/dist/packages/player/src/element/ElementLoads.js +119 -0
- package/dist/packages/player/src/element/FullscreenToggle.js +39 -0
- package/dist/packages/player/src/element/GyroViewElement.js +313 -0
- package/dist/packages/player/src/element/IdleWatcher.js +71 -0
- package/dist/packages/player/src/element/TypedEventElement.js +22 -0
- package/dist/packages/player/src/element/accessibleRegion.js +19 -0
- package/dist/packages/player/src/element/attributeNames.js +51 -0
- package/dist/packages/player/src/element/attributes.js +101 -0
- package/dist/packages/player/src/element/defineGyroView.js +21 -0
- package/dist/packages/player/src/element/earlyProperties.js +36 -0
- package/dist/packages/player/src/element/elementSource.js +23 -0
- package/dist/packages/player/src/element/liveSettings.js +138 -0
- package/dist/packages/player/src/element/playbackAttributes.js +53 -0
- package/dist/packages/player/src/element/playerEventMirror.js +33 -0
- package/dist/packages/player/src/element/reflectedProperties.js +78 -0
- package/dist/packages/player/src/element/styles.js +4 -0
- package/dist/packages/player/src/element/template.js +47 -0
- package/dist/packages/player/src/index.js +10 -0
- package/dist/packages/player/src/inspectRecording.js +22 -0
- package/dist/packages/player/src/player/DrawingBufferFit.js +77 -0
- package/dist/packages/player/src/player/FrameLoop.js +40 -0
- package/dist/packages/player/src/player/PictureSettings.js +60 -0
- package/dist/packages/player/src/player/Player.js +377 -0
- package/dist/packages/player/src/player/PlayerEvents.js +28 -0
- package/dist/packages/player/src/player/PlayerPhase.js +21 -0
- package/dist/packages/player/src/player/PlayerSound.js +61 -0
- package/dist/packages/player/src/player/PlayerView.js +113 -0
- package/dist/packages/player/src/player/SessionRelay.js +66 -0
- package/dist/packages/player/src/player/ensureFinite.js +26 -0
- package/dist/packages/player/src/player/loadRecording.js +65 -0
- package/dist/packages/player/src/player/transportEvents.js +29 -0
- package/dist/standalone.js +4919 -0
- package/package.json +73 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { microsecondsToSeconds, seconds, secondsToMicroseconds } from "../../../shared/units/time.js";
|
|
2
|
+
import { ZERO_VECTOR3, addVectors, magnitudeOf, scaleVector } from "../../../shared/math/Vector3.js";
|
|
3
|
+
import { toBodyFrame } from "../imu/ImuFrame.js";
|
|
4
|
+
//#region ../../packages/core/src/domain/motion/orientation/estimateGyroBias.ts
|
|
5
|
+
/**
|
|
6
|
+
* A window whose mean rate exceeds this (radians per second, about one degree per second) is
|
|
7
|
+
* motion, not bias: gyro offsets are a fraction of a degree per second.
|
|
8
|
+
*/
|
|
9
|
+
var MAX_BIAS_RATE = .02;
|
|
10
|
+
/**
|
|
11
|
+
* Spacing assumed for a track too short to measure its own.
|
|
12
|
+
*/
|
|
13
|
+
var FALLBACK_INTERVAL = secondsToMicroseconds(seconds(1));
|
|
14
|
+
/**
|
|
15
|
+
* The window of `length` seconds in which the gyro reports the least motion: where the camera
|
|
16
|
+
* rested, or at least moved least.
|
|
17
|
+
*/
|
|
18
|
+
function stillestWindow(gyro, frame, length) {
|
|
19
|
+
const interval = microsecondsToSeconds(gyro.meanSampleInterval ?? FALLBACK_INTERVAL);
|
|
20
|
+
const size = Math.max(1, Math.min(gyro.length, Math.round(length / interval)));
|
|
21
|
+
const magnitudes = Float64Array.from({ length: gyro.length }, (_unused, index) => magnitudeOf(toBodyFrame(frame, gyro.sampleAt(index).angularVelocity)));
|
|
22
|
+
let windowSum = 0;
|
|
23
|
+
for (let index = 0; index < size; index += 1) windowSum += magnitudes[index] ?? 0;
|
|
24
|
+
let best = {
|
|
25
|
+
start: 0,
|
|
26
|
+
sum: windowSum
|
|
27
|
+
};
|
|
28
|
+
for (let start = 1; start + size <= gyro.length; start += 1) {
|
|
29
|
+
windowSum += (magnitudes[start + size - 1] ?? 0) - (magnitudes[start - 1] ?? 0);
|
|
30
|
+
if (windowSum < best.sum) best = {
|
|
31
|
+
start,
|
|
32
|
+
sum: windowSum
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
start: best.start,
|
|
37
|
+
end: best.start + size
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The mean of one vector quantity over a window of samples, in the body frame.
|
|
42
|
+
*/
|
|
43
|
+
function meanOverWindow({ gyro, frame }, window, quantity) {
|
|
44
|
+
let sum = ZERO_VECTOR3;
|
|
45
|
+
for (let index = window.start; index < window.end; index += 1) {
|
|
46
|
+
const measured = quantity(gyro.sampleAt(index));
|
|
47
|
+
sum = addVectors(sum, toBodyFrame(frame, measured));
|
|
48
|
+
}
|
|
49
|
+
return scaleVector(sum, 1 / Math.max(window.end - window.start, 1));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The gyro's constant offset, taken as its mean reading over the stillest window. A camera
|
|
53
|
+
* that never rests (mounted on a moving boat) gets no bias correction rather than having its
|
|
54
|
+
* slowest real motion subtracted.
|
|
55
|
+
*/
|
|
56
|
+
function estimateGyroBias(gyro, frame, window) {
|
|
57
|
+
const mean = meanOverWindow({
|
|
58
|
+
gyro,
|
|
59
|
+
frame
|
|
60
|
+
}, window, (sample) => sample.angularVelocity);
|
|
61
|
+
return magnitudeOf(mean) <= MAX_BIAS_RATE ? mean : ZERO_VECTOR3;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { estimateGyroBias, meanOverWindow, stillestWindow };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { radians } from "../../../shared/units/angle.js";
|
|
2
|
+
import { secondsToMicroseconds } from "../../../shared/units/time.js";
|
|
3
|
+
import { crossProduct, dotProduct, magnitudeOf, scaleVector } from "../../../shared/math/Vector3.js";
|
|
4
|
+
import { meanOverWindow } from "./estimateGyroBias.js";
|
|
5
|
+
import { IDENTITY_QUATERNION, conjugateQuaternion, quaternionFromAxisAngle, rotateVector } from "../../../shared/math/Quaternion.js";
|
|
6
|
+
//#region ../../packages/core/src/domain/motion/orientation/gravity.ts
|
|
7
|
+
/**
|
|
8
|
+
* Gravity points down; the world frame's y axis is down, like the body frame's at rest.
|
|
9
|
+
*/
|
|
10
|
+
var WORLD_DOWN = [
|
|
11
|
+
0,
|
|
12
|
+
1,
|
|
13
|
+
0
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Accelerometer magnitudes outside this band (in g) mean the camera is accelerating, so the
|
|
17
|
+
* reading does not tell where down is.
|
|
18
|
+
*/
|
|
19
|
+
var MIN_RESTING_G = .9;
|
|
20
|
+
var MAX_RESTING_G = 1.1;
|
|
21
|
+
/**
|
|
22
|
+
* Where gravity points in the body frame according to one accelerometer sample, or undefined
|
|
23
|
+
* while the camera accelerates. The accelerometer measures the reaction to gravity, hence the
|
|
24
|
+
* sign flip.
|
|
25
|
+
*/
|
|
26
|
+
function measuredGravity(accelerationInBody) {
|
|
27
|
+
const magnitude = magnitudeOf(accelerationInBody);
|
|
28
|
+
return magnitude >= MIN_RESTING_G && magnitude <= MAX_RESTING_G ? scaleVector(accelerationInBody, -1 / magnitude) : void 0;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Mahony's error term: the rotation, in the body frame, that would swing the estimated gravity
|
|
32
|
+
* onto the measured one. Zero while the camera accelerates.
|
|
33
|
+
*/
|
|
34
|
+
function gravityCorrection(orientation, accelerationInBody) {
|
|
35
|
+
const measured = measuredGravity(accelerationInBody);
|
|
36
|
+
if (!measured) return [
|
|
37
|
+
0,
|
|
38
|
+
0,
|
|
39
|
+
0
|
|
40
|
+
];
|
|
41
|
+
const estimated = rotateVector(conjugateQuaternion(orientation), WORLD_DOWN);
|
|
42
|
+
return crossProduct(measured, estimated);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The samples within the first `length` seconds of the track, at least the first one.
|
|
46
|
+
*/
|
|
47
|
+
function openingWindow(gyro, length) {
|
|
48
|
+
if (gyro.isEmpty) return {
|
|
49
|
+
start: 0,
|
|
50
|
+
end: 0
|
|
51
|
+
};
|
|
52
|
+
const first = gyro.sampleAt(0).captureTime;
|
|
53
|
+
const span = secondsToMicroseconds(length);
|
|
54
|
+
let end = 1;
|
|
55
|
+
while (end < gyro.length && gyro.sampleAt(end).captureTime - first <= span) end += 1;
|
|
56
|
+
return {
|
|
57
|
+
start: 0,
|
|
58
|
+
end
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The pose at the start: the gravity measured over the opening window turned onto world down,
|
|
63
|
+
* with no yaw, so the world's forward is the body's forward at the start. Identity when the
|
|
64
|
+
* camera accelerates in that window; the gravity pull levels the estimate over the next seconds.
|
|
65
|
+
*/
|
|
66
|
+
function initialOrientation(gyro, frame, window) {
|
|
67
|
+
if (gyro.isEmpty) return IDENTITY_QUATERNION;
|
|
68
|
+
const opening = openingWindow(gyro, window);
|
|
69
|
+
const gravity = measuredGravity(meanOverWindow({
|
|
70
|
+
gyro,
|
|
71
|
+
frame
|
|
72
|
+
}, opening, (sample) => sample.acceleration));
|
|
73
|
+
return gravity ? rotationBetween(gravity, WORLD_DOWN) : IDENTITY_QUATERNION;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The shortest rotation taking unit vector `from` onto unit vector `to`.
|
|
77
|
+
*/
|
|
78
|
+
function rotationBetween(from, to) {
|
|
79
|
+
const axis = crossProduct(from, to);
|
|
80
|
+
const dot = dotProduct(from, to);
|
|
81
|
+
const sine = magnitudeOf(axis);
|
|
82
|
+
if (sine > 0) return quaternionFromAxisAngle(axis, radians(Math.atan2(sine, dot)));
|
|
83
|
+
return dot > 0 ? IDENTITY_QUATERNION : quaternionFromAxisAngle(anyPerpendicular(from), radians(Math.PI));
|
|
84
|
+
}
|
|
85
|
+
function anyPerpendicular(v) {
|
|
86
|
+
return Math.abs(v[0]) < Math.abs(v[2]) ? [
|
|
87
|
+
0,
|
|
88
|
+
-v[2],
|
|
89
|
+
v[1]
|
|
90
|
+
] : [
|
|
91
|
+
-v[1],
|
|
92
|
+
v[0],
|
|
93
|
+
0
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { WORLD_DOWN, gravityCorrection, initialOrientation };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region ../../packages/core/src/domain/motion/stabilization/Stabilizer.ts
|
|
2
|
+
var STABILIZATION_MODES = [
|
|
3
|
+
"off",
|
|
4
|
+
"lock",
|
|
5
|
+
"horizon",
|
|
6
|
+
"follow"
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* The world stays put: the most useful default for a camera carried around.
|
|
10
|
+
*/
|
|
11
|
+
var DEFAULT_STABILIZATION_MODE = "lock";
|
|
12
|
+
//#endregion
|
|
13
|
+
export { DEFAULT_STABILIZATION_MODE, STABILIZATION_MODES };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { radians } from "../../../shared/units/angle.js";
|
|
2
|
+
import { seconds } from "../../../shared/units/time.js";
|
|
3
|
+
import { IDENTITY_MATRIX3, multiplyMatrices } from "../../../shared/math/Matrix3.js";
|
|
4
|
+
import { conjugateQuaternion, quaternionFromAxisAngle, quaternionToMatrix, slerpQuaternions } from "../../../shared/math/Quaternion.js";
|
|
5
|
+
import { WORLD_DOWN } from "../orientation/gravity.js";
|
|
6
|
+
//#region ../../packages/core/src/domain/motion/stabilization/stabilizers.ts
|
|
7
|
+
/**
|
|
8
|
+
* The picture moves with the camera, as recorded.
|
|
9
|
+
*/
|
|
10
|
+
var OffStabilization = class {
|
|
11
|
+
nextRotation() {
|
|
12
|
+
return IDENTITY_MATRIX3;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The view is fixed to the world: camera motion disappears entirely.
|
|
17
|
+
*/
|
|
18
|
+
var LockStabilization = class {
|
|
19
|
+
nextRotation(orientation) {
|
|
20
|
+
return quaternionToMatrix(conjugateQuaternion(orientation));
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The horizon stays level but the view turns with the camera's heading.
|
|
25
|
+
*/
|
|
26
|
+
var HorizonStabilization = class {
|
|
27
|
+
nextRotation(orientation) {
|
|
28
|
+
const heading = quaternionFromAxisAngle(WORLD_DOWN, headingOf(orientation));
|
|
29
|
+
return multiplyMatrices(quaternionToMatrix(conjugateQuaternion(orientation)), quaternionToMatrix(heading));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var DEFAULT_FOLLOW_OPTIONS = {
|
|
33
|
+
timeConstant: seconds(1.5),
|
|
34
|
+
maxContinuousGap: seconds(1)
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The view follows the camera's direction smoothly, removing shake but not intended motion.
|
|
38
|
+
*/
|
|
39
|
+
var FollowStabilization = class {
|
|
40
|
+
options;
|
|
41
|
+
smoothed;
|
|
42
|
+
previousTime;
|
|
43
|
+
constructor(options = DEFAULT_FOLLOW_OPTIONS) {
|
|
44
|
+
this.options = options;
|
|
45
|
+
}
|
|
46
|
+
nextRotation(orientation, videoTime) {
|
|
47
|
+
this.smoothed = this.follow(orientation, videoTime);
|
|
48
|
+
this.previousTime = videoTime;
|
|
49
|
+
return multiplyMatrices(quaternionToMatrix(conjugateQuaternion(orientation)), quaternionToMatrix(this.smoothed));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A seek (a jump either way beyond the continuous gap) restarts on the camera; a small step
|
|
53
|
+
* back (frame jitter) holds; ordinary forward time low-passes towards the camera.
|
|
54
|
+
*/
|
|
55
|
+
follow(orientation, videoTime) {
|
|
56
|
+
const gap = this.previousTime === void 0 ? Infinity : videoTime - this.previousTime;
|
|
57
|
+
if (!this.smoothed || Math.abs(gap) > this.options.maxContinuousGap) return orientation;
|
|
58
|
+
if (gap <= 0) return this.smoothed;
|
|
59
|
+
const weight = 1 - Math.exp(-gap / this.options.timeConstant);
|
|
60
|
+
return slerpQuaternions(this.smoothed, orientation, weight);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var STABILIZERS = {
|
|
64
|
+
off: () => new OffStabilization(),
|
|
65
|
+
lock: () => new LockStabilization(),
|
|
66
|
+
horizon: () => new HorizonStabilization(),
|
|
67
|
+
follow: () => new FollowStabilization()
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* A fresh strategy for the mode; stateful ones start from nothing.
|
|
71
|
+
*/
|
|
72
|
+
function stabilizerFor(mode) {
|
|
73
|
+
return STABILIZERS[mode]();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The camera's heading: the twist of its orientation about the world's vertical axis (the
|
|
77
|
+
* swing-twist decomposition). Unlike the yaw of the forward axis, it stays continuous when the
|
|
78
|
+
* camera points straight up or down; it is undefined only for an exact half turn about a
|
|
79
|
+
* horizontal axis, where it reads zero.
|
|
80
|
+
*/
|
|
81
|
+
function headingOf(orientation) {
|
|
82
|
+
const [, y, , w] = orientation;
|
|
83
|
+
return radians(2 * Math.atan2(y, w));
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { FollowStabilization, HorizonStabilization, LockStabilization, OffStabilization, stabilizerFor };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { microseconds, microsecondsToSeconds, milliseconds, millisecondsToSeconds, seconds, secondsToMicroseconds } from "../../../shared/units/time.js";
|
|
2
|
+
//#region ../../packages/core/src/domain/motion/timing/CaptureClock.ts
|
|
3
|
+
/**
|
|
4
|
+
* Relates the camera's capture clock (microseconds, shared by gyro samples and exposure entries)
|
|
5
|
+
* to video time (seconds on the video track, where the first encoded frame shows at
|
|
6
|
+
* `firstFrameVideoTime`). Gyro samples are stamped a little later than the frames they belong
|
|
7
|
+
* to; that gyro offset is subtracted for them.
|
|
8
|
+
*/
|
|
9
|
+
var CaptureClock = class CaptureClock {
|
|
10
|
+
firstFrameCaptureTime;
|
|
11
|
+
gyroOffset;
|
|
12
|
+
firstFrameVideoTime;
|
|
13
|
+
constructor(firstFrameCaptureTime, gyroOffset = milliseconds(0), firstFrameVideoTime = seconds(0)) {
|
|
14
|
+
this.firstFrameCaptureTime = firstFrameCaptureTime;
|
|
15
|
+
this.gyroOffset = gyroOffset;
|
|
16
|
+
this.firstFrameVideoTime = firstFrameVideoTime;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The same clock for a track whose first frame shows at `videoTime`, so frame times and the
|
|
20
|
+
* orientation share the time base of the frames the decoder delivers.
|
|
21
|
+
*/
|
|
22
|
+
withFirstFrameAt(videoTime) {
|
|
23
|
+
return new CaptureClock(this.firstFrameCaptureTime, this.gyroOffset, videoTime);
|
|
24
|
+
}
|
|
25
|
+
videoTimeOf(captureTime) {
|
|
26
|
+
const sinceFirstFrame = microseconds(captureTime - this.firstFrameCaptureTime);
|
|
27
|
+
return seconds(this.firstFrameVideoTime + microsecondsToSeconds(sinceFirstFrame));
|
|
28
|
+
}
|
|
29
|
+
gyroVideoTimeOf(captureTime) {
|
|
30
|
+
return seconds(this.videoTimeOf(captureTime) - millisecondsToSeconds(this.gyroOffset));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Inverse of {@link videoTimeOf}, rounded to whole microseconds like the camera's clock.
|
|
34
|
+
*/
|
|
35
|
+
captureTimeOf(videoTime) {
|
|
36
|
+
const sinceFirstFrame = secondsToMicroseconds(seconds(videoTime - this.firstFrameVideoTime));
|
|
37
|
+
return microseconds(Math.round(this.firstFrameCaptureTime + sinceFirstFrame));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { CaptureClock };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ensureIndexInRange, ensureInvariant } from "../../../shared/errors/GyroViewError.js";
|
|
2
|
+
import { microseconds, seconds } from "../../../shared/units/time.js";
|
|
3
|
+
import { clamp } from "../../../shared/math/clamp.js";
|
|
4
|
+
//#region ../../packages/core/src/domain/motion/timing/FrameTimes.ts
|
|
5
|
+
/**
|
|
6
|
+
* A presentation time this small a part of a frame before its frame's still finds that frame:
|
|
7
|
+
* decoders round timestamps to whole microseconds, a sixty-thousandth of a frame at 60 fps.
|
|
8
|
+
*/
|
|
9
|
+
var GRID_TOLERANCE_FRAMES = .01;
|
|
10
|
+
/**
|
|
11
|
+
* Capture timing of every encoded frame, in frame order.
|
|
12
|
+
*/
|
|
13
|
+
var FrameTimes = class FrameTimes {
|
|
14
|
+
readoutTime;
|
|
15
|
+
clock;
|
|
16
|
+
captureTimes;
|
|
17
|
+
shutterTimes;
|
|
18
|
+
frameDuration;
|
|
19
|
+
constructor(parts) {
|
|
20
|
+
ensureInvariant(parts.shutterTimes === void 0 || parts.shutterTimes.length === parts.captureTimes.length, "frame time arrays disagree on the frame count");
|
|
21
|
+
this.clock = parts.clock;
|
|
22
|
+
this.captureTimes = parts.captureTimes;
|
|
23
|
+
this.shutterTimes = parts.shutterTimes;
|
|
24
|
+
this.readoutTime = parts.readoutTime ?? seconds(0);
|
|
25
|
+
this.frameDuration = parts.frameDuration;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Frame times from a source that knows when frames were captured but not for how long.
|
|
29
|
+
*/
|
|
30
|
+
static withoutShutterTimes(parts) {
|
|
31
|
+
return new FrameTimes({
|
|
32
|
+
...parts,
|
|
33
|
+
shutterTimes: void 0
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
get frameCount() {
|
|
37
|
+
return this.captureTimes.length;
|
|
38
|
+
}
|
|
39
|
+
frameAt(index) {
|
|
40
|
+
ensureIndexInRange(index, this.frameCount, "frame");
|
|
41
|
+
const captureTime = microseconds(this.captureTimes[index] ?? 0);
|
|
42
|
+
const shutter = this.shutterTimes?.[index];
|
|
43
|
+
const shutterTime = shutter === void 0 ? void 0 : seconds(shutter);
|
|
44
|
+
const videoTime = this.clock.videoTimeOf(captureTime);
|
|
45
|
+
return {
|
|
46
|
+
index,
|
|
47
|
+
captureTime,
|
|
48
|
+
videoTime,
|
|
49
|
+
shutterTime,
|
|
50
|
+
midExposureVideoTime: seconds(videoTime + (shutterTime ?? 0) / 2 + this.readoutTime / 2)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* When to sample the orientation for the frame shown at `videoTime`; undefined when there are
|
|
55
|
+
* no frames.
|
|
56
|
+
*/
|
|
57
|
+
midExposureAt(videoTime) {
|
|
58
|
+
const index = this.frameIndexAt(videoTime);
|
|
59
|
+
return index === void 0 ? void 0 : this.frameAt(index).midExposureVideoTime;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Index of the frame the track presents at `videoTime`, clamped to the first and last frame;
|
|
63
|
+
* undefined when there are no frames. Found by its place on the track's frame grid, which
|
|
64
|
+
* neither the camera clock's drift against the track nor timestamps rounded to microseconds
|
|
65
|
+
* move; by capture time only when the track gives no frame spacing.
|
|
66
|
+
*/
|
|
67
|
+
frameIndexAt(videoTime) {
|
|
68
|
+
if (this.frameCount === 0) return void 0;
|
|
69
|
+
const index = this.frameDuration === void 0 ? this.lastCapturedAt(videoTime) : this.onFrameGrid(videoTime, this.frameDuration);
|
|
70
|
+
return clamp(index, 0, this.frameCount - 1);
|
|
71
|
+
}
|
|
72
|
+
onFrameGrid(videoTime, frameDuration) {
|
|
73
|
+
const framesIn = (videoTime - this.clock.firstFrameVideoTime) / frameDuration;
|
|
74
|
+
return Math.floor(framesIn + GRID_TOLERANCE_FRAMES);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The last frame captured at or before `videoTime`.
|
|
78
|
+
*/
|
|
79
|
+
lastCapturedAt(videoTime) {
|
|
80
|
+
const target = this.clock.captureTimeOf(videoTime);
|
|
81
|
+
let low = 0;
|
|
82
|
+
let high = this.frameCount - 1;
|
|
83
|
+
while (low < high) {
|
|
84
|
+
const middle = Math.ceil((low + high) / 2);
|
|
85
|
+
if ((this.captureTimes[middle] ?? Infinity) <= target) low = middle;
|
|
86
|
+
else high = middle - 1;
|
|
87
|
+
}
|
|
88
|
+
return low;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
export { FrameTimes };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { seconds } from "../../../shared/units/time.js";
|
|
2
|
+
import { FrameTimes } from "./FrameTimes.js";
|
|
3
|
+
//#region ../../packages/core/src/domain/motion/timing/frameTimeSources.ts
|
|
4
|
+
/**
|
|
5
|
+
* Preferred source: the exposure record holds one entry per captured frame; entries before the
|
|
6
|
+
* first encoded frame are pre-roll and are skipped.
|
|
7
|
+
*/
|
|
8
|
+
function fromExposureRecord(context) {
|
|
9
|
+
const { exposureRecord, clock, frameCount } = context;
|
|
10
|
+
if (!exposureRecord) return void 0;
|
|
11
|
+
const start = exposureRecord.indexAtOrAfter(clock.firstFrameCaptureTime);
|
|
12
|
+
if (exposureRecord.length - start < frameCount) return void 0;
|
|
13
|
+
const frames = exposureRecord.slice(start, frameCount);
|
|
14
|
+
return new FrameTimes({
|
|
15
|
+
clock,
|
|
16
|
+
captureTimes: Float64Array.from(frames.captureTimes),
|
|
17
|
+
shutterTimes: Float64Array.from(frames.shutterTimes),
|
|
18
|
+
readoutTime: context.readoutTime,
|
|
19
|
+
frameDuration: context.frameDuration
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The video track's own presentation timestamps, which the clock relates to capture time from
|
|
24
|
+
* where the track's first frame shows.
|
|
25
|
+
*/
|
|
26
|
+
function fromTrackTimestamps(context) {
|
|
27
|
+
const { trackTimestamps, clock, frameCount } = context;
|
|
28
|
+
if (!trackTimestamps || trackTimestamps.length < frameCount) return void 0;
|
|
29
|
+
const captureTimes = Float64Array.from(trackTimestamps.slice(0, frameCount), (timestamp) => clock.captureTimeOf(timestamp));
|
|
30
|
+
return FrameTimes.withoutShutterTimes({
|
|
31
|
+
clock,
|
|
32
|
+
captureTimes,
|
|
33
|
+
readoutTime: context.readoutTime,
|
|
34
|
+
frameDuration: context.frameDuration
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Last resort: frames spaced evenly at the nominal frame rate from the first frame's capture
|
|
39
|
+
* time. Exact enough for playback, too coarse for tight stabilization sync.
|
|
40
|
+
*/
|
|
41
|
+
function fromNominalRate(context) {
|
|
42
|
+
const { frameRate, clock, frameCount } = context;
|
|
43
|
+
if (frameRate === void 0 || frameRate <= 0) return void 0;
|
|
44
|
+
const captureTimes = Float64Array.from({ length: frameCount }, (_unused, index) => clock.captureTimeOf(seconds(clock.firstFrameVideoTime + index / frameRate)));
|
|
45
|
+
return FrameTimes.withoutShutterTimes({
|
|
46
|
+
clock,
|
|
47
|
+
captureTimes,
|
|
48
|
+
readoutTime: context.readoutTime,
|
|
49
|
+
frameDuration: context.frameDuration
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Each source by its name; a record, so a new name cannot be left without its source.
|
|
54
|
+
*/
|
|
55
|
+
var FRAME_TIME_SOURCES = {
|
|
56
|
+
"exposure-record": fromExposureRecord,
|
|
57
|
+
"track-timestamps": fromTrackTimestamps,
|
|
58
|
+
nominal: fromNominalRate
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
export { FRAME_TIME_SOURCES };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { FRAME_TIME_SOURCES } from "./frameTimeSources.js";
|
|
2
|
+
//#region ../../packages/core/src/domain/motion/timing/resolveFrameTimes.ts
|
|
3
|
+
var EXPOSURE_FIRST = [
|
|
4
|
+
"exposure-record",
|
|
5
|
+
"track-timestamps",
|
|
6
|
+
"nominal"
|
|
7
|
+
];
|
|
8
|
+
var TRACK_TIMESTAMPS_FIRST = [
|
|
9
|
+
"track-timestamps",
|
|
10
|
+
"exposure-record",
|
|
11
|
+
"nominal"
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Tries the frame time sources in the order the camera suggests, falling back gracefully and
|
|
15
|
+
* saying which source won. Undefined when none can say (not even a nominal frame rate):
|
|
16
|
+
* frame times are optional, the picture still plays at the track's pace.
|
|
17
|
+
*/
|
|
18
|
+
function resolveFrameTimes(context, preferred) {
|
|
19
|
+
const warnings = [];
|
|
20
|
+
for (const source of orderFor(preferred)) {
|
|
21
|
+
const frameTimes = FRAME_TIME_SOURCES[source](context);
|
|
22
|
+
if (frameTimes) return {
|
|
23
|
+
frameTimes,
|
|
24
|
+
source,
|
|
25
|
+
warnings
|
|
26
|
+
};
|
|
27
|
+
warnings.push(`${source} unavailable`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolves what it can without the track's timestamps, which are costly to read, and asks for
|
|
32
|
+
* them when the answer depends on them: their source comes, in the camera's order, before any
|
|
33
|
+
* source that answers without them.
|
|
34
|
+
*/
|
|
35
|
+
function planFrameTimes(context, preferred) {
|
|
36
|
+
const warnings = [];
|
|
37
|
+
for (const source of orderFor(preferred)) {
|
|
38
|
+
if (source === "track-timestamps") return {
|
|
39
|
+
needsTrackTimestamps: true,
|
|
40
|
+
resolveWith: (trackTimestamps) => resolveFrameTimes({
|
|
41
|
+
...context,
|
|
42
|
+
trackTimestamps
|
|
43
|
+
}, preferred)
|
|
44
|
+
};
|
|
45
|
+
const frameTimes = FRAME_TIME_SOURCES[source]({
|
|
46
|
+
...context,
|
|
47
|
+
trackTimestamps: void 0
|
|
48
|
+
});
|
|
49
|
+
if (frameTimes) return {
|
|
50
|
+
needsTrackTimestamps: false,
|
|
51
|
+
resolved: {
|
|
52
|
+
frameTimes,
|
|
53
|
+
source,
|
|
54
|
+
warnings
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
warnings.push(`${source} unavailable`);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
needsTrackTimestamps: false,
|
|
61
|
+
resolved: void 0
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function orderFor(preferred) {
|
|
65
|
+
return preferred === "track-timestamps" ? TRACK_TIMESTAMPS_FIRST : EXPOSURE_FIRST;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
export { planFrameTimes, resolveFrameTimes };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { pixelAtRadius, toPolar } from "./lensGeometry.js";
|
|
2
|
+
import { HALF_FIELD_OF_VIEW } from "./opticsConstants.js";
|
|
3
|
+
//#region ../../packages/core/src/domain/optics/EquidistantModel.ts
|
|
4
|
+
/**
|
|
5
|
+
* Ideal equidistant fisheye (`r = f * theta`) implied by the legacy `offset` string, which stores
|
|
6
|
+
* only a radius and the centre per lens. The reading Insta360's own stitch agrees with on the
|
|
7
|
+
* far field (ADR 0023), so the default; directions past the field edge are refused.
|
|
8
|
+
*/
|
|
9
|
+
var EquidistantModel = class {
|
|
10
|
+
parameters;
|
|
11
|
+
kind = "equidistant";
|
|
12
|
+
halfFieldOfView = HALF_FIELD_OF_VIEW;
|
|
13
|
+
principalPoint;
|
|
14
|
+
constructor(parameters) {
|
|
15
|
+
this.parameters = parameters;
|
|
16
|
+
this.principalPoint = parameters.principalPoint;
|
|
17
|
+
}
|
|
18
|
+
get projection() {
|
|
19
|
+
return {
|
|
20
|
+
kind: "radial-polynomial",
|
|
21
|
+
principalPoint: this.principalPoint,
|
|
22
|
+
coefficients: [
|
|
23
|
+
this.pixelsPerRadian,
|
|
24
|
+
0,
|
|
25
|
+
0,
|
|
26
|
+
0
|
|
27
|
+
]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
project(direction) {
|
|
31
|
+
const { theta, radialUnit } = toPolar(direction);
|
|
32
|
+
return theta > this.halfFieldOfView ? void 0 : pixelAtRadius(this.principalPoint, this.pixelsPerRadian * theta, radialUnit);
|
|
33
|
+
}
|
|
34
|
+
get pixelsPerRadian() {
|
|
35
|
+
return this.parameters.edgeRadius / this.parameters.radiusAngle;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
export { EquidistantModel };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { HALF_FIELD_OF_VIEW } from "./opticsConstants.js";
|
|
2
|
+
//#region ../../packages/core/src/domain/optics/MeiModel.ts
|
|
3
|
+
/**
|
|
4
|
+
* Unified (Mei) omnidirectional camera model with radial-tangential distortion, as stored in
|
|
5
|
+
* `offset_v3`. A direction is projected onto the unit sphere, then from a point `xi` behind the
|
|
6
|
+
* sphere centre onto the normalised image plane, then distorted and scaled.
|
|
7
|
+
*/
|
|
8
|
+
var MeiModel = class {
|
|
9
|
+
parameters;
|
|
10
|
+
kind = "mei";
|
|
11
|
+
halfFieldOfView = HALF_FIELD_OF_VIEW;
|
|
12
|
+
principalPoint;
|
|
13
|
+
constructor(parameters) {
|
|
14
|
+
this.parameters = parameters;
|
|
15
|
+
this.principalPoint = parameters.principalPoint;
|
|
16
|
+
}
|
|
17
|
+
get projection() {
|
|
18
|
+
return {
|
|
19
|
+
kind: "mei",
|
|
20
|
+
...this.parameters
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
project(direction) {
|
|
24
|
+
const [x, y, z] = direction;
|
|
25
|
+
const depth = z + this.parameters.xi;
|
|
26
|
+
if (depth <= 0 || Math.atan2(Math.hypot(x, y), z) > this.halfFieldOfView) return void 0;
|
|
27
|
+
const normalised = [x / depth, y / depth];
|
|
28
|
+
const [distortedX, distortedY] = this.distort(normalised);
|
|
29
|
+
return {
|
|
30
|
+
x: this.parameters.focal[0] * distortedX + this.principalPoint.x,
|
|
31
|
+
y: this.parameters.focal[1] * distortedY + this.principalPoint.y
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
distort([mx, my]) {
|
|
35
|
+
const [k1, k2, k3] = this.parameters.radial;
|
|
36
|
+
const [p1, p2] = this.parameters.tangential;
|
|
37
|
+
const r2 = mx * mx + my * my;
|
|
38
|
+
const radial = 1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2;
|
|
39
|
+
const tangentialX = 2 * p1 * mx * my + p2 * (r2 + 2 * mx * mx);
|
|
40
|
+
const tangentialY = p1 * (r2 + 2 * my * my) + 2 * p2 * mx * my;
|
|
41
|
+
return [radial * mx + tangentialX, radial * my + tangentialY];
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
export { MeiModel };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { pixelAtRadius, toPolar } from "./lensGeometry.js";
|
|
2
|
+
import { HALF_FIELD_OF_VIEW } from "./opticsConstants.js";
|
|
3
|
+
//#region ../../packages/core/src/domain/optics/PolynomialModel.ts
|
|
4
|
+
/**
|
|
5
|
+
* Polynomial fisheye model of `offset_v2`. Insta360 does not document the normalisation; on the
|
|
6
|
+
* X5 lenses, evaluating the polynomial in radians and scaling it so that the field edge maps to
|
|
7
|
+
* `edgeRadius` reproduces the MEI model within 9 px, so that is the working interpretation until
|
|
8
|
+
* a recording that carries only `offset_v2` can confirm it.
|
|
9
|
+
*/
|
|
10
|
+
var PolynomialModel = class {
|
|
11
|
+
parameters;
|
|
12
|
+
kind = "polynomial";
|
|
13
|
+
halfFieldOfView = HALF_FIELD_OF_VIEW;
|
|
14
|
+
principalPoint;
|
|
15
|
+
scale;
|
|
16
|
+
constructor(parameters) {
|
|
17
|
+
this.parameters = parameters;
|
|
18
|
+
this.principalPoint = parameters.principalPoint;
|
|
19
|
+
this.scale = parameters.edgeRadius / this.polynomial(this.halfFieldOfView);
|
|
20
|
+
}
|
|
21
|
+
get projection() {
|
|
22
|
+
const [c1, c2, c3, c4] = this.parameters.coefficients;
|
|
23
|
+
return {
|
|
24
|
+
kind: "radial-polynomial",
|
|
25
|
+
principalPoint: this.principalPoint,
|
|
26
|
+
coefficients: [
|
|
27
|
+
c1 * this.scale,
|
|
28
|
+
c2 * this.scale,
|
|
29
|
+
c3 * this.scale,
|
|
30
|
+
c4 * this.scale
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
project(direction) {
|
|
35
|
+
const { theta, radialUnit } = toPolar(direction);
|
|
36
|
+
return theta > this.halfFieldOfView ? void 0 : pixelAtRadius(this.principalPoint, this.scale * this.polynomial(theta), radialUnit);
|
|
37
|
+
}
|
|
38
|
+
polynomial(theta) {
|
|
39
|
+
const [c1, c2, c3, c4] = this.parameters.coefficients;
|
|
40
|
+
return theta * (c1 + theta * (c2 + theta * (c3 + theta * c4)));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { PolynomialModel };
|