mapillary-tools 0.14.5__tar.gz → 0.14.6__tar.gz
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.
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/PKG-INFO +8 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/README.md +7 -0
- mapillary_tools-0.14.6/mapillary_tools/__init__.py +6 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/api_v4.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/authenticate.py +5 -1
- mapillary_tools-0.14.6/mapillary_tools/blackvue_parser.py +313 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/camm/camm_builder.py +5 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/camm/camm_parser.py +5 -0
- mapillary_tools-0.14.6/mapillary_tools/commands/__init__.py +15 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/__main__.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/authenticate.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/process.py +12 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/process_and_upload.py +5 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/sample_video.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/upload.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/video_process.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/video_process_and_upload.py +5 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/zip.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/config.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/constants.py +13 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/exceptions.py +9 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/exif_read.py +89 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/exif_write.py +17 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/exiftool_read.py +89 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/exiftool_read_video.py +56 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/exiftool_runner.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/ffmpeg.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geo.py +91 -31
- mapillary_tools-0.14.6/mapillary_tools/geotag/__init__.py +4 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/base.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/factory.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/geotag_images_from_exif.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/geotag_images_from_exiftool.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/geotag_images_from_gpx.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/geotag_images_from_gpx_file.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/geotag_images_from_video.py +6 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/geotag_videos_from_exiftool.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/geotag_videos_from_gpx.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/geotag_videos_from_video.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/image_extractors/base.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/image_extractors/exif.py +6 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/image_extractors/exiftool.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/options.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/utils.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/video_extractors/base.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/video_extractors/exiftool.py +7 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/video_extractors/gpx.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/geotag/video_extractors/native.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/gpmf/gpmf_gps_filter.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/gpmf/gpmf_parser.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/gpmf/gps_filter.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/history.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/http.py +5 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/ipc.py +5 -0
- mapillary_tools-0.14.6/mapillary_tools/mp4/__init__.py +4 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/mp4/construct_mp4_parser.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/mp4/io_utils.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/mp4/mp4_sample_parser.py +20 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/mp4/simple_mp4_builder.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/mp4/simple_mp4_parser.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/process_geotag_properties.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/process_sequence_properties.py +213 -31
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/sample_video.py +13 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/serializer/description.py +13 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/serializer/gpx.py +5 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/store.py +5 -0
- mapillary_tools-0.14.6/mapillary_tools/telemetry.py +180 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/types.py +6 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/upload.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/upload_api_v4.py +5 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/uploader.py +9 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/utils.py +16 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools.egg-info/PKG-INFO +8 -1
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/pyproject.toml +6 -0
- mapillary_tools-0.14.5/mapillary_tools/__init__.py +0 -1
- mapillary_tools-0.14.5/mapillary_tools/blackvue_parser.py +0 -195
- mapillary_tools-0.14.5/mapillary_tools/commands/__init__.py +0 -10
- mapillary_tools-0.14.5/mapillary_tools/geotag/__init__.py +0 -0
- mapillary_tools-0.14.5/mapillary_tools/mp4/__init__.py +0 -0
- mapillary_tools-0.14.5/mapillary_tools/telemetry.py +0 -72
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/LICENSE +0 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools.egg-info/SOURCES.txt +0 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools.egg-info/dependency_links.txt +0 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools.egg-info/entry_points.txt +0 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools.egg-info/requires.txt +0 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools.egg-info/top_level.txt +0 -0
- {mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mapillary_tools
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.6
|
|
4
4
|
Summary: Mapillary Image/Video Import Pipeline
|
|
5
5
|
Author-email: Mapillary <support@mapillary.com>
|
|
6
6
|
License: BSD
|
|
@@ -34,6 +34,13 @@ Requires-Dist: tqdm<5.0,>=4.0
|
|
|
34
34
|
Requires-Dist: typing-extensions>=4.12.2
|
|
35
35
|
Dynamic: license-file
|
|
36
36
|
|
|
37
|
+
<!--
|
|
38
|
+
Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
39
|
+
|
|
40
|
+
This source code is licensed under the BSD license found in the
|
|
41
|
+
LICENSE file in the root directory of this source tree.
|
|
42
|
+
-->
|
|
43
|
+
|
|
37
44
|
<p align="center">
|
|
38
45
|
<a href="https://github.com/mapillary/mapillary_tools/">
|
|
39
46
|
<img src="https://raw.githubusercontent.com/mapillary/mapillary_tools/main/docs/images/logo.png">
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
|
|
4
|
+
This source code is licensed under the BSD license found in the
|
|
5
|
+
LICENSE file in the root directory of this source tree.
|
|
6
|
+
-->
|
|
7
|
+
|
|
1
8
|
<p align="center">
|
|
2
9
|
<a href="https://github.com/mapillary/mapillary_tools/">
|
|
3
10
|
<img src="https://raw.githubusercontent.com/mapillary/mapillary_tools/main/docs/images/logo.png">
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
1
6
|
from __future__ import annotations
|
|
2
7
|
|
|
3
8
|
import getpass
|
|
@@ -7,7 +12,6 @@ import sys
|
|
|
7
12
|
import typing as T
|
|
8
13
|
|
|
9
14
|
import jsonschema
|
|
10
|
-
|
|
11
15
|
import requests
|
|
12
16
|
|
|
13
17
|
from . import api_v4, config, constants, exceptions, http
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import dataclasses
|
|
9
|
+
import datetime
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
import typing as T
|
|
14
|
+
|
|
15
|
+
import pynmea2
|
|
16
|
+
|
|
17
|
+
from . import telemetry
|
|
18
|
+
from .mp4 import simple_mp4_parser as sparser
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
LOG = logging.getLogger(__name__)
|
|
22
|
+
NMEA_LINE_REGEX = re.compile(
|
|
23
|
+
rb"""
|
|
24
|
+
^\s*
|
|
25
|
+
\[(\d+)\] # Timestamp
|
|
26
|
+
\s*
|
|
27
|
+
(\$\w{5}.*) # NMEA message
|
|
28
|
+
\s*
|
|
29
|
+
(\[\d+\])? # Strange timestamp
|
|
30
|
+
\s*$
|
|
31
|
+
""",
|
|
32
|
+
re.X,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclasses.dataclass
|
|
37
|
+
class BlackVueInfo:
|
|
38
|
+
# None and [] are equivalent here. Use None as default because:
|
|
39
|
+
# ValueError: mutable default <class 'list'> for field gps is not allowed: use default_factory
|
|
40
|
+
gps: list[telemetry.GPSPoint] | None = None
|
|
41
|
+
make: str = "BlackVue"
|
|
42
|
+
model: str = ""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def extract_blackvue_info(fp: T.BinaryIO) -> BlackVueInfo | None:
|
|
46
|
+
try:
|
|
47
|
+
gps_data = sparser.parse_mp4_data_first(fp, [b"free", b"gps "])
|
|
48
|
+
except sparser.ParsingError:
|
|
49
|
+
gps_data = None
|
|
50
|
+
|
|
51
|
+
if gps_data is None:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
points = _parse_gps_box(gps_data)
|
|
55
|
+
points.sort(key=lambda p: p.time)
|
|
56
|
+
|
|
57
|
+
if points:
|
|
58
|
+
# Convert the time field to relative time to the first point
|
|
59
|
+
# epoch_time stays as the original time in seconds
|
|
60
|
+
first_point_time = points[0].time
|
|
61
|
+
for p in points:
|
|
62
|
+
p.time = p.time - first_point_time
|
|
63
|
+
|
|
64
|
+
# Camera model
|
|
65
|
+
try:
|
|
66
|
+
cprt_bytes = sparser.parse_mp4_data_first(fp, [b"free", b"cprt"])
|
|
67
|
+
except sparser.ParsingError:
|
|
68
|
+
cprt_bytes = None
|
|
69
|
+
model = ""
|
|
70
|
+
|
|
71
|
+
if cprt_bytes is None:
|
|
72
|
+
model = ""
|
|
73
|
+
else:
|
|
74
|
+
model = _extract_camera_model_from_cprt(cprt_bytes)
|
|
75
|
+
|
|
76
|
+
return BlackVueInfo(model=model, gps=points)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def extract_camera_model(fp: T.BinaryIO) -> str:
|
|
80
|
+
try:
|
|
81
|
+
cprt_bytes = sparser.parse_mp4_data_first(fp, [b"free", b"cprt"])
|
|
82
|
+
except sparser.ParsingError:
|
|
83
|
+
return ""
|
|
84
|
+
|
|
85
|
+
if cprt_bytes is None:
|
|
86
|
+
return ""
|
|
87
|
+
|
|
88
|
+
return _extract_camera_model_from_cprt(cprt_bytes)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _extract_camera_model_from_cprt(cprt_bytes: bytes) -> str:
|
|
92
|
+
"""
|
|
93
|
+
>>> _extract_camera_model_from_cprt(b' {"model":"DR900X Plus","ver":0.918,"lang":"English","direct":1,"psn":"","temp":34,"GPS":1}')
|
|
94
|
+
'DR900X Plus'
|
|
95
|
+
>>> _extract_camera_model_from_cprt(b' Pittasoft Co., Ltd.;DR900S-1CH;1.008;English;1;D90SS1HAE00661;T69;')
|
|
96
|
+
'DR900S-1CH'
|
|
97
|
+
"""
|
|
98
|
+
cprt_bytes = cprt_bytes.strip().strip(b"\x00")
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
cprt_str = cprt_bytes.decode("utf8")
|
|
102
|
+
except UnicodeDecodeError:
|
|
103
|
+
return ""
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
cprt_json = json.loads(cprt_str)
|
|
107
|
+
except json.JSONDecodeError:
|
|
108
|
+
cprt_json = None
|
|
109
|
+
|
|
110
|
+
if cprt_json is not None:
|
|
111
|
+
return str(cprt_json.get("model", "")).strip()
|
|
112
|
+
|
|
113
|
+
fields = cprt_str.split(";")
|
|
114
|
+
if 2 <= len(fields):
|
|
115
|
+
model = fields[1]
|
|
116
|
+
if model:
|
|
117
|
+
return model.strip()
|
|
118
|
+
else:
|
|
119
|
+
return ""
|
|
120
|
+
else:
|
|
121
|
+
return ""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _compute_timezone_offset_from_rmc(
|
|
125
|
+
epoch_sec: float, message: pynmea2.NMEASentence
|
|
126
|
+
) -> float | None:
|
|
127
|
+
"""
|
|
128
|
+
Compute timezone offset from an RMC message which has full date+time.
|
|
129
|
+
|
|
130
|
+
Returns the offset to add to camera epoch to get correct UTC time,
|
|
131
|
+
or None if this message doesn't have the required datetime.
|
|
132
|
+
"""
|
|
133
|
+
if (
|
|
134
|
+
message.sentence_type != "RMC"
|
|
135
|
+
or not hasattr(message, "datetime")
|
|
136
|
+
or not message.datetime
|
|
137
|
+
):
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
correct_epoch = message.datetime.replace(tzinfo=datetime.timezone.utc).timestamp()
|
|
141
|
+
# Rounding needed to avoid floating point precision issues
|
|
142
|
+
return round(correct_epoch - epoch_sec, 3)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _compute_timezone_offset_from_time_only(
|
|
146
|
+
epoch_sec: float, message: pynmea2.NMEASentence
|
|
147
|
+
) -> float | None:
|
|
148
|
+
"""
|
|
149
|
+
Compute timezone offset from GGA/GLL which only have time (no date).
|
|
150
|
+
|
|
151
|
+
Uses the date from camera epoch and replaces the time with NMEA time assuming camera date is correct.
|
|
152
|
+
Handles day boundary when camera and GPS times differ by more than 12 hours.
|
|
153
|
+
"""
|
|
154
|
+
if not hasattr(message, "timestamp") or not message.timestamp:
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
camera_dt = datetime.datetime.fromtimestamp(epoch_sec, tz=datetime.timezone.utc)
|
|
158
|
+
|
|
159
|
+
nmea_time = message.timestamp
|
|
160
|
+
corrected_dt = camera_dt.replace(
|
|
161
|
+
hour=nmea_time.hour,
|
|
162
|
+
minute=nmea_time.minute,
|
|
163
|
+
second=nmea_time.second,
|
|
164
|
+
microsecond=getattr(nmea_time, "microsecond", 0),
|
|
165
|
+
)
|
|
166
|
+
# Handle day boundary e.g. camera time is 23:00, GPS time is 01:00 or vice versa
|
|
167
|
+
camera_secs = camera_dt.hour * 3600 + camera_dt.minute * 60 + camera_dt.second
|
|
168
|
+
nmea_secs = nmea_time.hour * 3600 + nmea_time.minute * 60 + nmea_time.second
|
|
169
|
+
if camera_secs - nmea_secs > 12 * 3600:
|
|
170
|
+
corrected_dt += datetime.timedelta(days=1)
|
|
171
|
+
elif nmea_secs - camera_secs > 12 * 3600:
|
|
172
|
+
corrected_dt -= datetime.timedelta(days=1)
|
|
173
|
+
|
|
174
|
+
# Rounding needed to avoid floating point precision issues
|
|
175
|
+
return round(corrected_dt.timestamp() - epoch_sec, 3)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _parse_nmea_lines(
|
|
179
|
+
gps_data: bytes,
|
|
180
|
+
) -> T.Iterator[tuple[int, pynmea2.NMEASentence]]:
|
|
181
|
+
"""Parse NMEA lines from GPS data, yielding (epoch_ms, message) tuples."""
|
|
182
|
+
for line_bytes in gps_data.splitlines():
|
|
183
|
+
match = NMEA_LINE_REGEX.match(line_bytes)
|
|
184
|
+
if match is None:
|
|
185
|
+
continue
|
|
186
|
+
nmea_line_bytes = match.group(2)
|
|
187
|
+
|
|
188
|
+
if not nmea_line_bytes:
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
nmea_line = nmea_line_bytes.decode("utf8")
|
|
193
|
+
except UnicodeDecodeError:
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
if not nmea_line:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
message = pynmea2.parse(nmea_line)
|
|
201
|
+
except pynmea2.nmea.ParseError:
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
epoch_ms = int(match.group(1))
|
|
205
|
+
yield epoch_ms, message
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _parse_gps_box(gps_data: bytes) -> list[telemetry.GPSPoint]:
|
|
209
|
+
"""
|
|
210
|
+
>>> list(_parse_gps_box(b"[1623057074211]$GPGGA,202530.00,5109.0262,N,11401.8407,W,5,40,0.5,1097.36,M,-17.00,M,18,TSTR*61"))
|
|
211
|
+
[GPSPoint(time=1623097530.0, lat=51.150436666666664, lon=-114.03067833333333, alt=1097.36, angle=None, epoch_time=1623097530.0, fix=<GPSFix.FIX_3D: 3>, precision=None, ground_speed=None)]
|
|
212
|
+
|
|
213
|
+
>>> list(_parse_gps_box(b"[1629874404069]$GNGGA,175322.00,3244.53126,N,11710.97811,W,1,12,0.84,17.4,M,-34.0,M,,*45"))
|
|
214
|
+
[GPSPoint(time=1629914002.0, lat=32.742187666666666, lon=-117.1829685, alt=17.4, angle=None, epoch_time=1629914002.0, fix=<GPSFix.FIX_3D: 3>, precision=None, ground_speed=None)]
|
|
215
|
+
|
|
216
|
+
>>> list(_parse_gps_box(b"[1629874404069]$GNGLL,4404.14012,N,12118.85993,W,001037.00,A,A*67"))
|
|
217
|
+
[GPSPoint(time=1629850237.0, lat=44.069002, lon=-121.31433216666667, alt=None, angle=None, epoch_time=1629850237.0, fix=None, precision=None, ground_speed=None)]
|
|
218
|
+
|
|
219
|
+
>>> list(_parse_gps_box(b"[1629874404069]$GNRMC,001031.00,A,4404.13993,N,12118.86023,W,0.146,,100117,,,A*7B"))
|
|
220
|
+
[GPSPoint(time=1484007031.0, lat=44.06899883333333, lon=-121.31433716666666, alt=None, angle=None, epoch_time=1484007031.0, fix=None, precision=None, ground_speed=None)]
|
|
221
|
+
|
|
222
|
+
>>> list(_parse_gps_box(b"[1623057074211]$GPVTG,,T,,M,0.078,N,0.144,K,D*28[1623057075215]"))
|
|
223
|
+
[]
|
|
224
|
+
"""
|
|
225
|
+
timezone_offset: float | None = None
|
|
226
|
+
parsed_lines: list[tuple[float, pynmea2.NMEASentence]] = []
|
|
227
|
+
first_valid_gga_gll: tuple[float, pynmea2.NMEASentence] | None = None
|
|
228
|
+
|
|
229
|
+
# First pass: collect parsed_lines and compute timezone offset from the first valid RMC message
|
|
230
|
+
for epoch_ms, message in _parse_nmea_lines(gps_data):
|
|
231
|
+
# Rounding needed to avoid floating point precision issues
|
|
232
|
+
epoch_sec = round(epoch_ms / 1000, 3)
|
|
233
|
+
parsed_lines.append((epoch_sec, message))
|
|
234
|
+
if timezone_offset is None and message.sentence_type == "RMC":
|
|
235
|
+
if hasattr(message, "is_valid") and message.is_valid:
|
|
236
|
+
timezone_offset = _compute_timezone_offset_from_rmc(epoch_sec, message)
|
|
237
|
+
if timezone_offset is not None:
|
|
238
|
+
LOG.debug(
|
|
239
|
+
"Computed timezone offset %.1fs from RMC (%s %s)",
|
|
240
|
+
timezone_offset,
|
|
241
|
+
message.datestamp,
|
|
242
|
+
message.timestamp,
|
|
243
|
+
)
|
|
244
|
+
# Track first valid GGA/GLL for fallback
|
|
245
|
+
if first_valid_gga_gll is None and message.sentence_type in ["GGA", "GLL"]:
|
|
246
|
+
if hasattr(message, "is_valid") and message.is_valid:
|
|
247
|
+
first_valid_gga_gll = (epoch_sec, message)
|
|
248
|
+
|
|
249
|
+
# Fallback: if no RMC found, try GGA/GLL (less reliable - no date info)
|
|
250
|
+
if timezone_offset is None and first_valid_gga_gll is not None:
|
|
251
|
+
epoch_sec, message = first_valid_gga_gll
|
|
252
|
+
timezone_offset = _compute_timezone_offset_from_time_only(epoch_sec, message)
|
|
253
|
+
if timezone_offset is not None:
|
|
254
|
+
LOG.debug(
|
|
255
|
+
"Computed timezone offset %.1fs from %s (fallback, no date info)",
|
|
256
|
+
timezone_offset,
|
|
257
|
+
message.sentence_type,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# If no offset could be determined, use 0 (camera clock assumed correct)
|
|
261
|
+
if timezone_offset is None:
|
|
262
|
+
timezone_offset = 0.0
|
|
263
|
+
|
|
264
|
+
points_by_sentence_type: dict[str, list[telemetry.GPSPoint]] = {}
|
|
265
|
+
|
|
266
|
+
# Second pass: apply offset to all GPS points
|
|
267
|
+
for epoch_sec, message in parsed_lines:
|
|
268
|
+
corrected_epoch = round(epoch_sec + timezone_offset, 3)
|
|
269
|
+
|
|
270
|
+
# https://tavotech.com/gps-nmea-sentence-structure/
|
|
271
|
+
if message.sentence_type in ["GGA"]:
|
|
272
|
+
if not message.is_valid:
|
|
273
|
+
continue
|
|
274
|
+
point = telemetry.GPSPoint(
|
|
275
|
+
time=corrected_epoch,
|
|
276
|
+
lat=message.latitude,
|
|
277
|
+
lon=message.longitude,
|
|
278
|
+
alt=message.altitude,
|
|
279
|
+
angle=None,
|
|
280
|
+
epoch_time=corrected_epoch,
|
|
281
|
+
fix=telemetry.GPSFix.FIX_3D if message.gps_qual >= 1 else None,
|
|
282
|
+
precision=None,
|
|
283
|
+
ground_speed=None,
|
|
284
|
+
)
|
|
285
|
+
points_by_sentence_type.setdefault(message.sentence_type, []).append(point)
|
|
286
|
+
|
|
287
|
+
elif message.sentence_type in ["RMC", "GLL"]:
|
|
288
|
+
if not message.is_valid:
|
|
289
|
+
continue
|
|
290
|
+
point = telemetry.GPSPoint(
|
|
291
|
+
time=corrected_epoch,
|
|
292
|
+
lat=message.latitude,
|
|
293
|
+
lon=message.longitude,
|
|
294
|
+
alt=None,
|
|
295
|
+
angle=None,
|
|
296
|
+
epoch_time=corrected_epoch,
|
|
297
|
+
fix=None,
|
|
298
|
+
precision=None,
|
|
299
|
+
ground_speed=None,
|
|
300
|
+
)
|
|
301
|
+
points_by_sentence_type.setdefault(message.sentence_type, []).append(point)
|
|
302
|
+
|
|
303
|
+
# This is the extraction order in exiftool
|
|
304
|
+
if "RMC" in points_by_sentence_type:
|
|
305
|
+
return points_by_sentence_type["RMC"]
|
|
306
|
+
|
|
307
|
+
if "GGA" in points_by_sentence_type:
|
|
308
|
+
return points_by_sentence_type["GGA"]
|
|
309
|
+
|
|
310
|
+
if "GLL" in points_by_sentence_type:
|
|
311
|
+
return points_by_sentence_type["GLL"]
|
|
312
|
+
|
|
313
|
+
return []
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
1
6
|
from __future__ import annotations
|
|
2
7
|
|
|
3
8
|
import io
|
|
@@ -9,7 +14,6 @@ from ..mp4 import (
|
|
|
9
14
|
mp4_sample_parser as sample_parser,
|
|
10
15
|
simple_mp4_builder as builder,
|
|
11
16
|
)
|
|
12
|
-
|
|
13
17
|
from . import camm_parser
|
|
14
18
|
|
|
15
19
|
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
1
6
|
# pyre-ignore-all-errors[5, 11, 16, 21, 24, 58]
|
|
2
7
|
from __future__ import annotations
|
|
3
8
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
6
|
+
# ruff: noqa: F401
|
|
7
|
+
from . import (
|
|
8
|
+
authenticate,
|
|
9
|
+
process,
|
|
10
|
+
process_and_upload,
|
|
11
|
+
sample_video,
|
|
12
|
+
upload,
|
|
13
|
+
video_process,
|
|
14
|
+
video_process_and_upload,
|
|
15
|
+
)
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
1
6
|
from __future__ import annotations
|
|
2
7
|
|
|
3
8
|
import argparse
|
|
@@ -203,6 +208,13 @@ class Command:
|
|
|
203
208
|
default=constants.DUPLICATE_ANGLE,
|
|
204
209
|
required=False,
|
|
205
210
|
)
|
|
211
|
+
group_sequence.add_argument(
|
|
212
|
+
"--skip_zigzag_check",
|
|
213
|
+
help="Skip the GPS zig-zag pattern detection check.",
|
|
214
|
+
action="store_true",
|
|
215
|
+
default=False,
|
|
216
|
+
required=False,
|
|
217
|
+
)
|
|
206
218
|
|
|
207
219
|
def run(self, vars_args: dict):
|
|
208
220
|
metadatas = process_geotag_properties(
|
{mapillary_tools-0.14.5 → mapillary_tools-0.14.6}/mapillary_tools/commands/process_and_upload.py
RENAMED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
1
6
|
import inspect
|
|
2
7
|
|
|
3
8
|
from ..authenticate import fetch_user_items
|
|
4
|
-
|
|
5
9
|
from .process import Command as ProcessCommand
|
|
6
10
|
from .upload import Command as UploadCommand
|
|
7
11
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
1
6
|
import inspect
|
|
2
7
|
|
|
3
8
|
from ..authenticate import fetch_user_items
|
|
4
|
-
|
|
5
9
|
from .upload import Command as UploadCommand
|
|
6
10
|
from .video_process import Command as VideoProcessCommand
|
|
7
11
|
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
1
6
|
from __future__ import annotations
|
|
2
7
|
|
|
3
8
|
import functools
|
|
@@ -115,7 +120,7 @@ MAPILLARY__EXPERIMENTAL_ENABLE_IMU: bool = _yes_or_no(
|
|
|
115
120
|
###### SEQUENCE PROCESSING ######
|
|
116
121
|
#################################
|
|
117
122
|
# In meters
|
|
118
|
-
CUTOFF_DISTANCE = float(os.getenv(_ENV_PREFIX + "CUTOFF_DISTANCE",
|
|
123
|
+
CUTOFF_DISTANCE = float(os.getenv(_ENV_PREFIX + "CUTOFF_DISTANCE", 100))
|
|
119
124
|
# In seconds
|
|
120
125
|
CUTOFF_TIME = float(os.getenv(_ENV_PREFIX + "CUTOFF_TIME", 60))
|
|
121
126
|
DUPLICATE_DISTANCE = float(os.getenv(_ENV_PREFIX + "DUPLICATE_DISTANCE", 0.1))
|
|
@@ -136,6 +141,13 @@ MAX_SEQUENCE_FILESIZE: int | None = _parse_filesize(
|
|
|
136
141
|
MAX_SEQUENCE_PIXELS: int | None = _parse_pixels(
|
|
137
142
|
os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_PIXELS", "6G")
|
|
138
143
|
)
|
|
144
|
+
# Zig-zag detection parameters
|
|
145
|
+
ZIGZAG_WINDOW_SIZE = int(os.getenv(_ENV_PREFIX + "ZIGZAG_WINDOW_SIZE", 5))
|
|
146
|
+
ZIGZAG_DEVIATION_THRESHOLD = float(
|
|
147
|
+
os.getenv(_ENV_PREFIX + "ZIGZAG_DEVIATION_THRESHOLD", 0.8)
|
|
148
|
+
)
|
|
149
|
+
ZIGZAG_MIN_DEVIATIONS = int(os.getenv(_ENV_PREFIX + "ZIGZAG_MIN_DEVIATIONS", 1))
|
|
150
|
+
ZIGZAG_MIN_DISTANCE = float(os.getenv(_ENV_PREFIX + "ZIGZAG_MIN_DISTANCE", 30))
|
|
139
151
|
|
|
140
152
|
|
|
141
153
|
##################
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the BSD license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
1
6
|
from __future__ import annotations
|
|
2
7
|
|
|
3
8
|
import typing as T
|
|
@@ -107,6 +112,10 @@ class MapillaryNullIslandError(MapillaryDescriptionError):
|
|
|
107
112
|
pass
|
|
108
113
|
|
|
109
114
|
|
|
115
|
+
class MapillaryZigZagError(MapillaryDescriptionError):
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
|
|
110
119
|
class MapillaryUploadConnectionError(MapillaryUserError):
|
|
111
120
|
exit_code = 12
|
|
112
121
|
|