Skip to content

Sync a GPX to the GPS clock of a timelapse, even one too noisy to publish - #849

Merged
caglarpir merged 1 commit into
mapillary:mainfrom
caglarpir:sync-gpx-timelapse-clock
Oct 20, 2026
Merged

caglarpir merged 1 commit into
mapillary:mainfrom
caglarpir:sync-gpx-timelapse-clock

Conversation

@caglarpir

@caglarpir caglarpir commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A timelapse compresses time: each second of video spans several seconds of GPS time. A GPX is synced to the video at 1x, so on a timelapse it lands at the wrong video times, most of it past the end of the video.

The GoPro MAX2 clip reported in #831 is 1.24 s of 12.5x timelapse covering 15.5 s of GPS time. Since #831 its GPX is accepted, but lands at 0 to 12 s. Its GPS is rejected as noise, so there is not even a clock to read the rate from: the GPX is synced from the start of the video.

Changes

  • The GPS clock of the video is read as (video time, Unix time) pairs, anchored at the first pair at its own video time, as in Sync a GPX track to video time 0, not to the first video GPS point #847. Its rate is the least-squares slope of the pairs. It is used when both it and the median step between neighbouring pairs are over 1.5x, so that one timestamp far off cannot make a normal video a timelapse, and is 1x otherwise. A GPS5 block, which stamps ~1 s of points with one timestamp, counts once.
  • When the video's GPS is rejected as noise, the clock is read from the unfiltered points. They are never published, and this also keeps the make, model and filetype of the video. A GoPro stamps a placeholder date (2015-10-22 and similar) until it acquires GPS time, so a noisy clock that puts the GPX more than a day away from the video is ignored with a warning, and the GPX is synced from the start of the video, as on main.

Verification

  • The reported clip: its GPX lands at 0.16 to 1.13 s, where its GPS clock places it (0.16 to 1.12 s), with make and model GoPro / MAX2.

  • The tracks of 17 sample videos (GoPro MAX, MAX2, HERO7, HERO8, BlackVue, Labpano, Insta360) exported as GPX and synced back to their videos. Median error:

    Video main this PR
    GoPro MAX timelapse, 1 fps 74.5 s 0.10 s
    GoPro MAX timelapse, 2 fps 89.5 s 0.20 s
    GoPro MAX timelapse 612.4 s 0.002 s
    GoPro MAX timelapse 59.8 s 0.14 s
    GoPro MAX2 timelapse, 10x 35.5 s 0.02 s
    GoPro MAX2, 2x 20.3 s 0.007 s
    GoPro MAX2, 2x 6.7 s 0.16 s
    the 10 others unchanged unchanged
  • Across 519 sample files with a GPS clock (GoPro, BlackVue, CAMM cameras), only those seven are detected as timelapses (two of them stored twice).

  • Across 168 runs over sample files from over 20 cameras, this PR changes no output. The one timelapse with a GPX among them, from an Insta360 ONE X, has no GPS clock to read a rate from.

  • Unit: 750 passed, 18 skipped, 1 xfailed. Integration: 57 passed. Doctests pass. mypy, ruff check and ruff format --check are clean.

Interactions with other open PRs

Whichever lands second needs these by hand:

…lish

A timelapse compresses time: each second of video spans several seconds
of GPS time. A GPX was synced to it at 1x, so it landed at the wrong
video times, most of it past the end of the video. The reported GoPro
MAX2 clip is 1.24s of video covering 15.5s of GPS time, and its GPX
landed at 0-12s.

Read the GPS clock of the video as (video time, Unix time) pairs,
anchored at the first pair at its own video time. Its rate is the
least-squares slope of the pairs. It is used when both it and the median
step between neighbouring pairs are over 1.5x, so that one timestamp far
off cannot turn a normal video into a timelapse, and is 1x otherwise. A
GPS5 block, which stamps ~1s of points with one timestamp, counts once.

The reported clip's GPS is rejected as noise, so the GPX was synced from
the start of the video, without a clock to read the rate from. In that
case read the clock from the unfiltered points: they are never
published, and their timestamps usually still keep time (the reported
clip's do). This also keeps the make, model and filetype of the video.
A GoPro stamps a placeholder date (2015-10-22 and similar) until it
acquires GPS time, so a noisy clock that puts the GPX more than a day
away from the video is ignored with a warning, and the GPX is synced
from the start of the video as before.

To verify, the tracks of 17 sample videos (GoPro MAX, MAX2, HERO7,
HERO8, BlackVue, Labpano, Insta360) were exported as GPX and synced back
to their videos. The median error went from 6.7-612s to under 0.2s on the
seven timelapses among them, and did not change on the others. Across
519 sample files with a GPS clock, only the seven timelapses (two of
them stored twice) are detected as such, at 2x to 30x.
@caglarpir
caglarpir force-pushed the sync-gpx-timelapse-clock branch from a592d3e to 978649d Compare October 20, 2026 08:04
@caglarpir
caglarpir merged commit 27835ed into mapillary:main Oct 20, 2026
16 checks passed
caglarpir added a commit to caglarpir/mapillary_tools that referenced this pull request Oct 20, 2026
main reworked GPXVideoExtractor (mapillary#831, mapillary#847, mapillary#849): it syncs a GPX
through a VideoClock, with a rate for timelapses, falls back to the start
of the video when the video's GPS has no usable clock, and reports a GPX
it cannot parse. This branch changed the same method, so take main's and
re-apply this branch's changes to it:

- Drop GPX points timestamped before 1980, and sort the rest.
- Place the GPX by --video_start_time wherever the video has no clock to
  sync it with: no usable GPS, as before, and now also GPS without a
  clock, such as noise with a placeholder date, which main places at the
  start of the video.
- Apply --interpolation_offset_time after the sync. The offset is in GPS
  seconds, so with a timelapse clock the track moves by offset / rate
  seconds of video; by the whole offset, frames would be picked
  offset * (rate - 1) seconds of track away from where they are geotagged.

In factory.py, keep both main's EXTERNAL_GPS_SOURCES and this branch's
_ALL_VIDEO_TYPES. Tests cover the clockless placement and the timelapse
offset.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant