mapillary-tools 0.12.1__py3-none-any.whl → 0.13.1__py3-none-any.whl

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.
Files changed (59) hide show
  1. mapillary_tools/__init__.py +1 -1
  2. mapillary_tools/api_v4.py +94 -4
  3. mapillary_tools/{geotag → camm}/camm_builder.py +73 -61
  4. mapillary_tools/camm/camm_parser.py +561 -0
  5. mapillary_tools/commands/__init__.py +0 -1
  6. mapillary_tools/commands/__main__.py +0 -6
  7. mapillary_tools/commands/process.py +0 -50
  8. mapillary_tools/commands/upload.py +1 -26
  9. mapillary_tools/constants.py +2 -2
  10. mapillary_tools/exiftool_read_video.py +13 -11
  11. mapillary_tools/ffmpeg.py +2 -2
  12. mapillary_tools/geo.py +0 -54
  13. mapillary_tools/geotag/blackvue_parser.py +4 -4
  14. mapillary_tools/geotag/geotag_images_from_exif.py +2 -1
  15. mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -1
  16. mapillary_tools/geotag/geotag_images_from_gpx_file.py +7 -1
  17. mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +5 -3
  18. mapillary_tools/geotag/geotag_videos_from_video.py +13 -14
  19. mapillary_tools/geotag/gpmf_gps_filter.py +9 -10
  20. mapillary_tools/geotag/gpmf_parser.py +346 -83
  21. mapillary_tools/mp4/__init__.py +0 -0
  22. mapillary_tools/{geotag → mp4}/construct_mp4_parser.py +32 -16
  23. mapillary_tools/mp4/mp4_sample_parser.py +322 -0
  24. mapillary_tools/{geotag → mp4}/simple_mp4_builder.py +64 -38
  25. mapillary_tools/process_geotag_properties.py +25 -19
  26. mapillary_tools/process_sequence_properties.py +6 -6
  27. mapillary_tools/sample_video.py +17 -16
  28. mapillary_tools/telemetry.py +71 -0
  29. mapillary_tools/types.py +18 -0
  30. mapillary_tools/upload.py +74 -233
  31. mapillary_tools/upload_api_v4.py +8 -9
  32. mapillary_tools/utils.py +9 -16
  33. mapillary_tools/video_data_extraction/cli_options.py +0 -1
  34. mapillary_tools/video_data_extraction/extract_video_data.py +13 -31
  35. mapillary_tools/video_data_extraction/extractors/base_parser.py +13 -11
  36. mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +5 -4
  37. mapillary_tools/video_data_extraction/extractors/camm_parser.py +13 -16
  38. mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +4 -9
  39. mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +9 -11
  40. mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +6 -11
  41. mapillary_tools/video_data_extraction/extractors/gopro_parser.py +11 -4
  42. mapillary_tools/video_data_extraction/extractors/gpx_parser.py +90 -11
  43. mapillary_tools/video_data_extraction/extractors/nmea_parser.py +3 -3
  44. mapillary_tools/video_data_extraction/video_data_parser_factory.py +13 -20
  45. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/METADATA +10 -3
  46. mapillary_tools-0.13.1.dist-info/RECORD +75 -0
  47. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/WHEEL +1 -1
  48. mapillary_tools/commands/upload_blackvue.py +0 -33
  49. mapillary_tools/commands/upload_camm.py +0 -33
  50. mapillary_tools/commands/upload_zip.py +0 -33
  51. mapillary_tools/geotag/camm_parser.py +0 -306
  52. mapillary_tools/geotag/mp4_sample_parser.py +0 -426
  53. mapillary_tools/process_import_meta_properties.py +0 -76
  54. mapillary_tools-0.12.1.dist-info/RECORD +0 -77
  55. /mapillary_tools/{geotag → mp4}/io_utils.py +0 -0
  56. /mapillary_tools/{geotag → mp4}/simple_mp4_parser.py +0 -0
  57. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/LICENSE +0 -0
  58. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/entry_points.txt +0 -0
  59. {mapillary_tools-0.12.1.dist-info → mapillary_tools-0.13.1.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ import typing as T
5
5
  import xml.etree.ElementTree as ET
6
6
 
7
7
  from . import exif_read, exiftool_read, geo
8
+ from .telemetry import GPSFix, GPSPoint
8
9
 
9
10
 
10
11
  MAX_TRACK_ID = 10
@@ -87,7 +88,7 @@ def _aggregate_gps_track(
87
88
  alt_tag: T.Optional[str] = None,
88
89
  direction_tag: T.Optional[str] = None,
89
90
  ground_speed_tag: T.Optional[str] = None,
90
- ) -> T.List[geo.PointWithFix]:
91
+ ) -> T.List[GPSPoint]:
91
92
  """
92
93
  Aggregate all GPS data by the tags.
93
94
  It requires lat, lon to be present, and their lengths must match.
@@ -173,15 +174,16 @@ def _aggregate_gps_track(
173
174
  if timestamp is None or lon is None or lat is None:
174
175
  continue
175
176
  track.append(
176
- geo.PointWithFix(
177
+ GPSPoint(
177
178
  time=timestamp,
178
179
  lon=lon,
179
180
  lat=lat,
180
181
  alt=alt,
181
182
  angle=direction,
182
- gps_fix=None,
183
- gps_precision=None,
184
- gps_ground_speed=ground_speed,
183
+ epoch_time=None,
184
+ fix=None,
185
+ precision=None,
186
+ ground_speed=ground_speed,
185
187
  )
186
188
  )
187
189
 
@@ -230,8 +232,8 @@ def _aggregate_gps_track_by_sample_time(
230
232
  ground_speed_tag: T.Optional[str] = None,
231
233
  gps_fix_tag: T.Optional[str] = None,
232
234
  gps_precision_tag: T.Optional[str] = None,
233
- ) -> T.List[geo.PointWithFix]:
234
- track: T.List[geo.PointWithFix] = []
235
+ ) -> T.List[GPSPoint]:
236
+ track: T.List[GPSPoint] = []
235
237
 
236
238
  expanded_gps_fix_tag = None
237
239
  if gps_fix_tag is not None:
@@ -249,7 +251,7 @@ def _aggregate_gps_track_by_sample_time(
249
251
  gps_fix_texts = texts_by_tag.get(expanded_gps_fix_tag)
250
252
  if gps_fix_texts:
251
253
  try:
252
- gps_fix = geo.GPSFix(int(gps_fix_texts[0]))
254
+ gps_fix = GPSFix(int(gps_fix_texts[0]))
253
255
  except ValueError:
254
256
  gps_fix = None
255
257
 
@@ -280,7 +282,7 @@ def _aggregate_gps_track_by_sample_time(
280
282
  for idx, point in enumerate(points):
281
283
  point.time = sample_time + idx * avg_timedelta
282
284
  track.extend(
283
- dataclasses.replace(point, gps_fix=gps_fix, gps_precision=gps_precision)
285
+ dataclasses.replace(point, fix=gps_fix, precision=gps_precision)
284
286
  for point in points
285
287
  )
286
288
 
@@ -355,7 +357,7 @@ class ExifToolReadVideo:
355
357
  _, model = self._extract_make_and_model()
356
358
  return model
357
359
 
358
- def _extract_gps_track_from_track(self) -> T.List[geo.PointWithFix]:
360
+ def _extract_gps_track_from_track(self) -> T.List[GPSPoint]:
359
361
  for track_id in range(1, MAX_TRACK_ID + 1):
360
362
  track_ns = f"Track{track_id}"
361
363
  if self._all_tags_exists(
@@ -397,7 +399,7 @@ class ExifToolReadVideo:
397
399
 
398
400
  def _extract_gps_track_from_quicktime(
399
401
  self, namespace: str = "QuickTime"
400
- ) -> T.List[geo.PointWithFix]:
402
+ ) -> T.List[GPSPoint]:
401
403
  if not self._all_tags_exists(
402
404
  {
403
405
  expand_tag(f"{namespace}:GPSDateTime"),
mapillary_tools/ffmpeg.py CHANGED
@@ -202,10 +202,10 @@ class FFMPEG:
202
202
  return "0"
203
203
 
204
204
  if length == 1:
205
- return f"eq(n\\,{ sorted_frame_indices[0] })"
205
+ return f"eq(n\\,{sorted_frame_indices[0]})"
206
206
 
207
207
  middle = length // 2
208
- return f"if(lt(n\\,{ sorted_frame_indices[middle] })\\,{ self.generate_binary_search(sorted_frame_indices[:middle]) }\\,{ self.generate_binary_search(sorted_frame_indices[middle:]) })"
208
+ return f"if(lt(n\\,{sorted_frame_indices[middle]})\\,{self.generate_binary_search(sorted_frame_indices[:middle])}\\,{self.generate_binary_search(sorted_frame_indices[middle:])})"
209
209
 
210
210
  def extract_specified_frames(
211
211
  self,
mapillary_tools/geo.py CHANGED
@@ -6,7 +6,6 @@ import datetime
6
6
  import itertools
7
7
  import math
8
8
  import typing as T
9
- from enum import Enum, unique
10
9
 
11
10
  WGS84_a = 6378137.0
12
11
  WGS84_a_SQ = WGS84_a**2
@@ -32,45 +31,6 @@ class Point:
32
31
  angle: T.Optional[float]
33
32
 
34
33
 
35
- @unique
36
- class GPSFix(Enum):
37
- NO_FIX = 0
38
- FIX_2D = 2
39
- FIX_3D = 3
40
-
41
-
42
- @dataclasses.dataclass
43
- class PointWithFix(Point):
44
- gps_fix: T.Optional[GPSFix]
45
- gps_precision: T.Optional[float]
46
- gps_ground_speed: T.Optional[float]
47
-
48
-
49
- def _ecef_from_lla_DEPRECATED(
50
- lat: float, lon: float, alt: float
51
- ) -> T.Tuple[float, float, float]:
52
- """
53
- Deprecated because it is slow. Keep here for reference and comparison.
54
- Use _ecef_from_lla2 instead.
55
-
56
- Compute ECEF XYZ from latitude, longitude and altitude.
57
-
58
- All using the WGS94 model.
59
- Altitude is the distance to the WGS94 ellipsoid.
60
- Check results here http://www.oc.nps.edu/oc2902w/coord/llhxyz.htm
61
-
62
- """
63
- a2 = WGS84_a**2
64
- b2 = WGS84_b**2
65
- lat = math.radians(lat)
66
- lon = math.radians(lon)
67
- L = 1.0 / math.sqrt(a2 * math.cos(lat) ** 2 + b2 * math.sin(lat) ** 2)
68
- x = (a2 * L + alt) * math.cos(lat) * math.cos(lon)
69
- y = (a2 * L + alt) * math.cos(lat) * math.sin(lon)
70
- z = (b2 * L + alt) * math.sin(lat)
71
- return x, y, z
72
-
73
-
74
34
  def _ecef_from_lla2(lat: float, lon: float) -> T.Tuple[float, float, float]:
75
35
  """
76
36
  Compute ECEF XYZ from latitude, longitude and altitude.
@@ -172,20 +132,6 @@ def pairwise(iterable: T.Iterable[_IT]) -> T.Iterable[T.Tuple[_IT, _IT]]:
172
132
  return zip(a, b)
173
133
 
174
134
 
175
- def group_every(
176
- iterable: T.Iterable[_IT], n: int
177
- ) -> T.Generator[T.Generator[_IT, None, None], None, None]:
178
- """
179
- Return a generator that divides the iterable into groups by N.
180
- """
181
-
182
- if not (0 < n):
183
- raise ValueError("expect 0 < n but got {0}".format(n))
184
-
185
- for _, group in itertools.groupby(enumerate(iterable), key=lambda t: t[0] // n):
186
- yield (item for _, item in group)
187
-
188
-
189
135
  def as_unix_time(dt: T.Union[datetime.datetime, int, float]) -> float:
190
136
  if isinstance(dt, (int, float)):
191
137
  return dt
@@ -7,7 +7,7 @@ import typing as T
7
7
  import pynmea2
8
8
 
9
9
  from .. import geo
10
- from . import simple_mp4_parser
10
+ from ..mp4 import simple_mp4_parser as sparser
11
11
 
12
12
 
13
13
  LOG = logging.getLogger(__name__)
@@ -55,8 +55,8 @@ def _parse_gps_box(gps_data: bytes) -> T.Generator[geo.Point, None, None]:
55
55
 
56
56
  def extract_camera_model(fp: T.BinaryIO) -> str:
57
57
  try:
58
- cprt_bytes = simple_mp4_parser.parse_mp4_data_first(fp, [b"free", b"cprt"])
59
- except simple_mp4_parser.ParsingError:
58
+ cprt_bytes = sparser.parse_mp4_data_first(fp, [b"free", b"cprt"])
59
+ except sparser.ParsingError:
60
60
  return ""
61
61
 
62
62
  if cprt_bytes is None:
@@ -91,7 +91,7 @@ def extract_camera_model(fp: T.BinaryIO) -> str:
91
91
 
92
92
 
93
93
  def extract_points(fp: T.BinaryIO) -> T.Optional[T.List[geo.Point]]:
94
- gps_data = simple_mp4_parser.parse_mp4_data_first(fp, [b"free", b"gps "])
94
+ gps_data = sparser.parse_mp4_data_first(fp, [b"free", b"gps "])
95
95
  if gps_data is None:
96
96
  return None
97
97
 
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
 
7
7
  from tqdm import tqdm
8
8
 
9
- from .. import exceptions, exif_write, geo, types
9
+ from .. import exceptions, exif_write, geo, types, utils
10
10
  from ..exif_read import ExifRead, ExifReadABC
11
11
  from .geotag_from_generic import GeotagImagesFromGeneric
12
12
 
@@ -64,6 +64,7 @@ class GeotagImagesFromEXIF(GeotagImagesFromGeneric):
64
64
  image_metadata = types.ImageMetadata(
65
65
  filename=image_path,
66
66
  md5sum=None,
67
+ filesize=utils.get_file_size(image_path),
67
68
  time=geo.as_unix_time(capture_time),
68
69
  lat=lat,
69
70
  lon=lon,
@@ -48,7 +48,6 @@ class GeotagImagesFromExifToolBothImageAndVideo(GeotagImagesFromGeneric):
48
48
  video_paths = utils.find_videos(
49
49
  [Path(pathstr) for pathstr in rdf_description_by_path.keys()],
50
50
  skip_subfolders=True,
51
- check_file_suffix=True,
52
51
  )
53
52
 
54
53
  # will try to geotag these error metadatas from video later
@@ -25,7 +25,13 @@ class GeotagImagesFromGPXFile(GeotagImagesFromGeneric):
25
25
  num_processes: T.Optional[int] = None,
26
26
  ):
27
27
  super().__init__()
28
- tracks = parse_gpx(source_path)
28
+ try:
29
+ tracks = parse_gpx(source_path)
30
+ except Exception as ex:
31
+ raise RuntimeError(
32
+ f"Error parsing GPX {source_path}: {ex.__class__.__name__}: {ex}"
33
+ )
34
+
29
35
  if 1 < len(tracks):
30
36
  LOG.warning(
31
37
  "Found %s tracks in the GPX file %s. Will merge points in all the tracks as a single track for interpolation",
@@ -6,8 +6,9 @@ from pathlib import Path
6
6
 
7
7
  from tqdm import tqdm
8
8
 
9
- from .. import exceptions, exiftool_read, geo, types
9
+ from .. import exceptions, exiftool_read, geo, types, utils
10
10
  from ..exiftool_read_video import ExifToolReadVideo
11
+ from ..telemetry import GPSPoint
11
12
  from . import gpmf_gps_filter, utils as video_utils
12
13
  from .geotag_from_generic import GeotagVideosFromGeneric
13
14
 
@@ -45,11 +46,11 @@ class GeotagVideosFromExifToolVideo(GeotagVideosFromGeneric):
45
46
  points = geo.extend_deduplicate_points(points)
46
47
  assert points, "must have at least one point"
47
48
 
48
- if all(isinstance(p, geo.PointWithFix) for p in points):
49
+ if all(isinstance(p, GPSPoint) for p in points):
49
50
  points = T.cast(
50
51
  T.List[geo.Point],
51
52
  gpmf_gps_filter.remove_noisy_points(
52
- T.cast(T.List[geo.PointWithFix], points)
53
+ T.cast(T.List[GPSPoint], points)
53
54
  ),
54
55
  )
55
56
  if not points:
@@ -65,6 +66,7 @@ class GeotagVideosFromExifToolVideo(GeotagVideosFromGeneric):
65
66
  video_metadata = types.VideoMetadata(
66
67
  video_path,
67
68
  md5sum=None,
69
+ filesize=utils.get_file_size(video_path),
68
70
  filetype=types.FileType.VIDEO,
69
71
  points=points,
70
72
  make=exif.extract_make(),
@@ -6,15 +6,11 @@ from pathlib import Path
6
6
 
7
7
  from tqdm import tqdm
8
8
 
9
- from .. import exceptions, geo, types
10
- from . import (
11
- blackvue_parser,
12
- camm_parser,
13
- gpmf_gps_filter,
14
- gpmf_parser,
15
- simple_mp4_parser as parser,
16
- utils as video_utils,
17
- )
9
+ from .. import exceptions, geo, types, utils
10
+ from ..camm import camm_parser
11
+ from ..mp4 import simple_mp4_parser as sparser
12
+ from ..telemetry import GPSPoint
13
+ from . import blackvue_parser, gpmf_gps_filter, gpmf_parser, utils as video_utils
18
14
  from .geotag_from_generic import GeotagVideosFromGeneric
19
15
 
20
16
  LOG = logging.getLogger(__name__)
@@ -77,7 +73,7 @@ class GeotagVideosFromVideo(GeotagVideosFromGeneric):
77
73
  with video_path.open("rb") as fp:
78
74
  try:
79
75
  points = camm_parser.extract_points(fp)
80
- except parser.ParsingError:
76
+ except sparser.ParsingError:
81
77
  points = None
82
78
 
83
79
  if points is not None:
@@ -86,6 +82,7 @@ class GeotagVideosFromVideo(GeotagVideosFromGeneric):
86
82
  return types.VideoMetadata(
87
83
  filename=video_path,
88
84
  md5sum=None,
85
+ filesize=utils.get_file_size(video_path),
89
86
  filetype=types.FileType.CAMM,
90
87
  points=points,
91
88
  make=make,
@@ -100,7 +97,7 @@ class GeotagVideosFromVideo(GeotagVideosFromGeneric):
100
97
  with video_path.open("rb") as fp:
101
98
  try:
102
99
  points_with_fix = gpmf_parser.extract_points(fp)
103
- except parser.ParsingError:
100
+ except sparser.ParsingError:
104
101
  points_with_fix = None
105
102
 
106
103
  if points_with_fix is not None:
@@ -109,6 +106,7 @@ class GeotagVideosFromVideo(GeotagVideosFromGeneric):
109
106
  return types.VideoMetadata(
110
107
  filename=video_path,
111
108
  md5sum=None,
109
+ filesize=utils.get_file_size(video_path),
112
110
  filetype=types.FileType.GOPRO,
113
111
  points=T.cast(T.List[geo.Point], points_with_fix),
114
112
  make=make,
@@ -123,7 +121,7 @@ class GeotagVideosFromVideo(GeotagVideosFromGeneric):
123
121
  with video_path.open("rb") as fp:
124
122
  try:
125
123
  points = blackvue_parser.extract_points(fp)
126
- except parser.ParsingError:
124
+ except sparser.ParsingError:
127
125
  points = None
128
126
 
129
127
  if points is not None:
@@ -132,6 +130,7 @@ class GeotagVideosFromVideo(GeotagVideosFromGeneric):
132
130
  return types.VideoMetadata(
133
131
  filename=video_path,
134
132
  md5sum=None,
133
+ filesize=utils.get_file_size(video_path),
135
134
  filetype=types.FileType.BLACKVUE,
136
135
  points=points,
137
136
  make=make,
@@ -160,11 +159,11 @@ class GeotagVideosFromVideo(GeotagVideosFromGeneric):
160
159
  video_metadata.points = geo.extend_deduplicate_points(video_metadata.points)
161
160
  assert video_metadata.points, "must have at least one point"
162
161
 
163
- if all(isinstance(p, geo.PointWithFix) for p in video_metadata.points):
162
+ if all(isinstance(p, GPSPoint) for p in video_metadata.points):
164
163
  video_metadata.points = T.cast(
165
164
  T.List[geo.Point],
166
165
  gpmf_gps_filter.remove_noisy_points(
167
- T.cast(T.List[geo.PointWithFix], video_metadata.points)
166
+ T.cast(T.List[GPSPoint], video_metadata.points)
168
167
  ),
169
168
  )
170
169
  if not video_metadata.points:
@@ -2,6 +2,7 @@ import logging
2
2
  import typing as T
3
3
 
4
4
  from .. import constants, geo
5
+ from ..telemetry import GPSPoint
5
6
  from . import gps_filter
6
7
 
7
8
  """
@@ -13,8 +14,8 @@ LOG = logging.getLogger(__name__)
13
14
 
14
15
 
15
16
  def remove_outliers(
16
- sequence: T.Sequence[geo.PointWithFix],
17
- ) -> T.Sequence[geo.PointWithFix]:
17
+ sequence: T.Sequence[GPSPoint],
18
+ ) -> T.Sequence[GPSPoint]:
18
19
  distances = [
19
20
  geo.gps_distance((left.lat, left.lon), (right.lat, right.lon))
20
21
  for left, right in geo.pairwise(sequence)
@@ -37,9 +38,7 @@ def remove_outliers(
37
38
  "Split to %d sequences with max distance %f", len(sequences), max_distance
38
39
  )
39
40
 
40
- ground_speeds = [
41
- p.gps_ground_speed for p in sequence if p.gps_ground_speed is not None
42
- ]
41
+ ground_speeds = [p.ground_speed for p in sequence if p.ground_speed is not None]
43
42
  if len(ground_speeds) < 2:
44
43
  return sequence
45
44
 
@@ -50,20 +49,20 @@ def remove_outliers(
50
49
  )
51
50
 
52
51
  return T.cast(
53
- T.List[geo.PointWithFix],
52
+ T.List[GPSPoint],
54
53
  gps_filter.find_majority(merged.values()),
55
54
  )
56
55
 
57
56
 
58
57
  def remove_noisy_points(
59
- sequence: T.Sequence[geo.PointWithFix],
60
- ) -> T.Sequence[geo.PointWithFix]:
58
+ sequence: T.Sequence[GPSPoint],
59
+ ) -> T.Sequence[GPSPoint]:
61
60
  num_points = len(sequence)
62
61
  sequence = [
63
62
  p
64
63
  for p in sequence
65
64
  # include points **without** GPS fix
66
- if p.gps_fix is None or p.gps_fix.value in constants.GOPRO_GPS_FIXES
65
+ if p.fix is None or p.fix.value in constants.GOPRO_GPS_FIXES
67
66
  ]
68
67
  if len(sequence) < num_points:
69
68
  LOG.debug(
@@ -77,7 +76,7 @@ def remove_noisy_points(
77
76
  p
78
77
  for p in sequence
79
78
  # include points **without** precision
80
- if p.gps_precision is None or p.gps_precision <= constants.GOPRO_MAX_DOP100
79
+ if p.precision is None or p.precision <= constants.GOPRO_MAX_DOP100
81
80
  ]
82
81
  if len(sequence) < num_points:
83
82
  LOG.debug(