Skip to content

Geotag interval samples by the video's own GPS, as distance samples are - #896

Merged
caglarpir merged 2 commits into
mapillary:mainfrom
caglarpir:geotag-interval-samples-by-default
Oct 23, 2026
Merged

caglarpir merged 2 commits into
mapillary:mainfrom
caglarpir:geotag-interval-samples-by-default

Conversation

@caglarpir

Copy link
Copy Markdown
Contributor

Stacked on #895; review the last commit.

video_process VIDEO OUT --video_sample_interval N --video_sample_distance -1 failed every sample with "Unable to extract
GPS Longitude or GPS Latitude from the image", and so did sample_video
followed by process. It did so on v0.13 and v0.14 too. Distance sampling
writes the position of each sample into its EXIF, where the default
geotag sources read it; interval sampling wrote only the capture time.
Only the legacy gopro, camm and blackvue sources geotagged interval
samples, by reading the video again in the geotagging stage.

Locate each interval sample along the video's own GPS track at its video
time, and write its position, altitude, direction and the make and model
of the camera into its EXIF, as distance sampling does, reading the
track by the same sources: the video's own GPS unless --geotag_source or
--video_geotag_source says otherwise. The capture times are stamped by
the video's GPS clock as before. A sample before the track starts or
after it ends gets no position. A video whose GPS cannot be read, as it
has none or it is rejected as noise, is named in a warning, and its
samples fail as before. With a GPX or NMEA source, nothing changes: the
geotagging stage places the samples along that track by their capture
times, which for a video without a GPS clock come from its creation
time, not from where GPX sync would place the track.

An altitude above 4293 m, which a GoPro with poor GPS can report, failed
the sample: written to the 1e-6 m, the numerator of its EXIF rational
did not fit in 32 bits. Such altitudes are written to the precision that
fits. That also fixes distance samples, which failed the same way.

On a GoPro HERO7, a GoPro MAX (a normal video and a timelapse), a PANOX
V2, a LabPano Pilot One, a BlackVue 900S, a Viofo A129 and a Ricoh Theta
X, the samples now lie within 1 cm of the video's track at their video
time, stamped at even steps of the clock (11.426 s for 10 s of the
LabPano). They get the positions, capture times, altitudes and cameras
that the legacy sources give them, within 1 cm on the LabPano, and
sample_video followed by process describes them as video_process does.
--geotag_source exif and native, which read the EXIF, now geotag them
too. With a legacy source the descriptions do not change, but for the
file sizes of the samples, which now hold their positions; nor do they
with a GPX (also with --interpolation_use_gpx_start_time, and with
native after it), with --video_geotag_source gpx, for distance sampling,
for a video without GPS and for a GoPro MAX 2 whose GPS is noise.

Unit and integration tests, doctests, mypy --python-version 3.9, ruff check, ruff format --check and usort diff are clean.

@meta-cla meta-cla Bot added the cla signed label Oct 23, 2026
One malformed RMC or GGA sentence failed the whole file, as not a valid
NMEA file, and since NMEA files are read from any talker, so did one
from a talker that v0.14.7 did not read. A log of 222 sentences with one
more, a $GLGGA sentence whose checksum does not match, failed all 113
images geotagged from it, where v0.14.7 geotagged 106 of them, the 7
others being duplicates of the previous image. A video geotagged from
such a log failed too. v0.14.7 crashed on a malformed $GPGGA sentence.

A sentence that does not parse, whose checksum does not match, or with a
field a fix is read from that does not parse, is now skipped, and one
warning per file says how many were skipped and quotes the first, with
its line number and what is wrong with it. pynmea2 leaves a field it
cannot parse a string: a malformed time failed the file, and a malformed
altitude crashed process with a traceback, for images and videos alike.
Void sentences and sentences without a fix are skipped as before,
without a warning. A file of which no fix can be read and that has
malformed sentences still fails as not a valid NMEA file.

On that log the 106 images get the same positions, times, directions and
altitudes as with v0.14.7 and as from the log without the bad sentence,
and the 7 others are duplicates as before.
`video_process VIDEO OUT --video_sample_interval N
--video_sample_distance -1` failed every sample with "Unable to extract
GPS Longitude or GPS Latitude from the image", and so did sample_video
followed by process. It did so on v0.13 and v0.14 too. Distance sampling
writes the position of each sample into its EXIF, where the default
geotag sources read it; interval sampling wrote only the capture time.
Only the legacy gopro, camm and blackvue sources geotagged interval
samples, by reading the video again in the geotagging stage.

Locate each interval sample along the video's own GPS track at its video
time, and write its position, altitude, direction and the make and model
of the camera into its EXIF, as distance sampling does, reading the
track by the same sources: the video's own GPS unless --geotag_source or
--video_geotag_source says otherwise. The capture times are stamped by
the video's GPS clock as before. A sample before the track starts or
after it ends gets no position. A video whose GPS cannot be read, as it
has none or it is rejected as noise, is named in a warning, and its
samples fail as before. With a GPX or NMEA source, nothing changes: the
geotagging stage places the samples along that track by their capture
times, which for a video without a GPS clock come from its creation
time, not from where GPX sync would place the track.

An altitude above 4293 m, which a GoPro with poor GPS can report, failed
the sample: written to the 1e-6 m, the numerator of its EXIF rational
did not fit in 32 bits. Such altitudes are written to the precision that
fits. That also fixes distance samples, which failed the same way.

On a GoPro HERO7, a GoPro MAX (a normal video and a timelapse), a PANOX
V2, a LabPano Pilot One, a BlackVue 900S, a Viofo A129 and a Ricoh Theta
X, the samples now lie within 1 cm of the video's track at their video
time, stamped at even steps of the clock (11.426 s for 10 s of the
LabPano). They get the positions, capture times, altitudes and cameras
that the legacy sources give them, within 1 cm on the LabPano, and
sample_video followed by process describes them as video_process does.
--geotag_source exif and native, which read the EXIF, now geotag them
too. With a legacy source the descriptions do not change, but for the
file sizes of the samples, which now hold their positions; nor do they
with a GPX (also with --interpolation_use_gpx_start_time, and with
native after it), with --video_geotag_source gpx, for distance sampling,
for a video without GPS and for a GoPro MAX 2 whose GPS is noise.
@caglarpir
caglarpir force-pushed the geotag-interval-samples-by-default branch from f7adc8e to cc717e3 Compare October 23, 2026 13:04
@caglarpir
caglarpir merged commit c4a1610 into mapillary:main Oct 23, 2026
16 checks passed
caglarpir added a commit that referenced this pull request Oct 24, 2026
…ource

Interval sampling with --video_geotag_source gpx or nmea geotagged none
of its samples: each failed with "Unable to extract GPS Longitude or GPS
Latitude from the image", and the command exited 6, on a GoPro MAX
timelapse with its GPX and on a Sony video with the GPX or NMEA log of
its GPS. Such a source geotags videos only, so the geotagging stage
reads the samples' own positions, and interval sampling wrote positions
only from the video's own GPS. Distance sampling with the same options
worked, and so did interval sampling with --geotag_source gpx, which
geotags the samples themselves. The message of #896, which located
interval samples along the video's own GPS, said that they were placed
along a GPX too; they were not.

Locate each interval sample along the GPX or NMEA track as GPX sync
placed it on the video, at the sample's video time, and write its
position into the sample, as distance sampling does. Stamp it with the
time of the track there, as distance sampling stamps the samples of a
track from a file: for a video with a GPS clock, and without
--interpolation_offset_time, that is the time it was stamped with
before. With --interpolation_use_gpx_start_time, the track starts at the
first sample, which ffmpeg cuts at time 0, as the samples of a video
whose GPS was rejected as noise are stamped from it, not at the first
frame, where GPX sync starts it for distance sampling. The samples
outside the track, or in one of its gaps of more than an hour, are left
out with a warning, as other samples outside their track are, unless a
legacy source or exiftool_xml, which geotag samples by the GPS of their
video, may geotag them instead.

Distance sampling with such a source interpolated the track across its
gaps: the frames in a gap of hours got positions between the fixes
either side of it, which say nothing of where the camera was, and the
geotagging stage read them from the frames. Since #874 a time in such a
gap is outside the track, and the samples of a file of --geotag_source
in it fail geotagging. Leave those frames out too, with one warning per
video that names the gaps, so that distance and interval samples agree.
The limit is in GPS seconds, as in the geotagging stage, so it spans
less video in a timelapse. Distance sampling with a file of
--geotag_source, which the geotagging stage geotags the frames by, or
with a legacy source or exiftool_xml, is unchanged.

On the GoPro MAX timelapse sampled every 0.5 s, the Sony video sampled
every 20 s with its GPX and with its NMEA log, and a noisy GoPro MAX 2
timelapse with its GPX started with the video, every sample is
geotagged, within 1.4 cm of the track at its capture time, and the
commands exit 0 (the last sample of the GoPro MAX 2 timelapse, past the
GPX, is left out). --geotag_source gpx and nmea, the video's own GPS and
the legacy sources give the same descriptions as before.

Checked distance sampling with tracks made to have gaps:

- A GoPro MAX video with a GPX missing 70 s of fixes inside it, with
  MAPILLARY_TOOLS_MAX_GPX_GAP_SECONDS at 60: the 2098 frames in the gap
  are left out, and at 5 m 55 samples are written instead of 70, none in
  the gap, the 24 before it as before. The same with
  --interpolation_offset_time 2 and with
  --interpolation_use_gpx_start_time. At the default limit, nothing
  changes.
- The same video with a GPX that resumes 99 s into it, 2 hours after its
  previous fix: the first 99 s are left out (46 samples instead of 59),
  where the track starts after the gap; with the GPX ending a minute
  later too, the 14 samples of that minute are written. Started with the
  video, the GPX covers only its first frame, which alone is written,
  instead of 45 samples placed along the gap.
- A Sony video with a GPX and with an NMEA log missing 2 minutes of
  fixes, at a limit of 60 s: 257 samples at 10 m instead of 316, none in
  the gap, the first 114 as before.
- A GoPro MAX timelapse with 41 s of GPS time missing, 1.4 s of video:
  at a limit of 30 s, its 40 frames are left out; at 60 s, nothing
  changes.
- With these tracks as --geotag_source gpx or nmea, with the legacy
  gopro source next to --video_geotag_source gpx, and with interval
  sampling, the descriptions are the same as before.

With the GPX and NMEA tracks of sample videos from a dozen cameras,
among them GPX logs of several trips with gaps of more than an hour
outside the videos (111 runs of video_process: --video_geotag_source gpx
and nmea, with --interpolation_use_gpx_start_time or an offset, interval
sampling, and --geotag_source gpx and nmea), and plain video_process by
distance and by interval on five cameras, the descriptions are the same
as before.
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