mapillary-tools 0.13.1a1__tar.gz → 0.13.3a1__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.
Files changed (85) hide show
  1. {mapillary_tools-0.13.1a1/mapillary_tools.egg-info → mapillary_tools-0.13.3a1}/PKG-INFO +1 -1
  2. mapillary_tools-0.13.3a1/mapillary_tools/__init__.py +1 -0
  3. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/constants.py +1 -1
  4. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_images_from_gpx_file.py +7 -1
  5. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/upload_api_v4.py +68 -61
  6. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/uploader.py +0 -2
  7. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/extractors/camm_parser.py +8 -12
  8. mapillary_tools-0.13.3a1/mapillary_tools/video_data_extraction/extractors/gpx_parser.py +108 -0
  9. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1/mapillary_tools.egg-info}/PKG-INFO +1 -1
  10. mapillary_tools-0.13.1a1/mapillary_tools/__init__.py +0 -1
  11. mapillary_tools-0.13.1a1/mapillary_tools/video_data_extraction/extractors/gpx_parser.py +0 -71
  12. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/LICENSE +0 -0
  13. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/MANIFEST.in +0 -0
  14. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/README.md +0 -0
  15. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/api_v4.py +0 -0
  16. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/authenticate.py +0 -0
  17. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/camm/camm_builder.py +0 -0
  18. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/camm/camm_parser.py +0 -0
  19. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/__init__.py +0 -0
  20. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/__main__.py +0 -0
  21. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/authenticate.py +0 -0
  22. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/process.py +0 -0
  23. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/process_and_upload.py +0 -0
  24. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/sample_video.py +0 -0
  25. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/upload.py +0 -0
  26. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/video_process.py +0 -0
  27. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/video_process_and_upload.py +0 -0
  28. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/commands/zip.py +0 -0
  29. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/config.py +0 -0
  30. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/exceptions.py +0 -0
  31. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/exif_read.py +0 -0
  32. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/exif_write.py +0 -0
  33. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/exiftool_read.py +0 -0
  34. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/exiftool_read_video.py +0 -0
  35. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/ffmpeg.py +0 -0
  36. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geo.py +0 -0
  37. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/__init__.py +0 -0
  38. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/blackvue_parser.py +0 -0
  39. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_from_generic.py +0 -0
  40. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_images_from_exif.py +0 -0
  41. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_images_from_exiftool.py +0 -0
  42. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_images_from_exiftool_both_image_and_video.py +0 -0
  43. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_images_from_gpx.py +0 -0
  44. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_images_from_nmea_file.py +0 -0
  45. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_images_from_video.py +0 -0
  46. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_videos_from_exiftool_video.py +0 -0
  47. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/geotag_videos_from_video.py +0 -0
  48. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/gpmf_gps_filter.py +0 -0
  49. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/gpmf_parser.py +0 -0
  50. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/gps_filter.py +0 -0
  51. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/geotag/utils.py +0 -0
  52. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/history.py +0 -0
  53. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/ipc.py +0 -0
  54. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/mp4/__init__.py +0 -0
  55. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/mp4/construct_mp4_parser.py +0 -0
  56. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/mp4/io_utils.py +0 -0
  57. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/mp4/mp4_sample_parser.py +0 -0
  58. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/mp4/simple_mp4_builder.py +0 -0
  59. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/mp4/simple_mp4_parser.py +0 -0
  60. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/process_geotag_properties.py +0 -0
  61. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/process_sequence_properties.py +0 -0
  62. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/sample_video.py +0 -0
  63. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/telemetry.py +0 -0
  64. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/types.py +0 -0
  65. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/upload.py +0 -0
  66. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/utils.py +0 -0
  67. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/cli_options.py +0 -0
  68. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/extract_video_data.py +0 -0
  69. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/extractors/base_parser.py +0 -0
  70. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/extractors/blackvue_parser.py +0 -0
  71. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/extractors/exiftool_runtime_parser.py +0 -0
  72. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/extractors/exiftool_xml_parser.py +0 -0
  73. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/extractors/generic_video_parser.py +0 -0
  74. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/extractors/gopro_parser.py +0 -0
  75. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/extractors/nmea_parser.py +0 -0
  76. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools/video_data_extraction/video_data_parser_factory.py +0 -0
  77. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools.egg-info/SOURCES.txt +0 -0
  78. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools.egg-info/dependency_links.txt +0 -0
  79. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools.egg-info/entry_points.txt +0 -0
  80. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools.egg-info/requires.txt +0 -0
  81. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/mapillary_tools.egg-info/top_level.txt +0 -0
  82. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/requirements.txt +0 -0
  83. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/schema/image_description_schema.json +0 -0
  84. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/setup.cfg +0 -0
  85. {mapillary_tools-0.13.1a1 → mapillary_tools-0.13.3a1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mapillary_tools
3
- Version: 0.13.1a1
3
+ Version: 0.13.3a1
4
4
  Summary: Mapillary Image/Video Import Pipeline
5
5
  Home-page: https://github.com/mapillary/mapillary_tools
6
6
  Author: Mapillary
@@ -0,0 +1 @@
1
+ VERSION = "0.13.3a1"
@@ -45,6 +45,6 @@ GOPRO_GPS_PRECISION = float(os.getenv(_ENV_PREFIX + "GOPRO_GPS_PRECISION", 15))
45
45
  # Max number of images per sequence
46
46
  MAX_SEQUENCE_LENGTH = int(os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_LENGTH", 1000))
47
47
  # Max file size per sequence (sum of image filesizes in the sequence)
48
- MAX_SEQUENCE_FILESIZE: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_FILESIZE", "10G")
48
+ MAX_SEQUENCE_FILESIZE: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_FILESIZE", "110G")
49
49
  # Max number of pixels per sequence (sum of image pixels in the sequence)
50
50
  MAX_SEQUENCE_PIXELS: str = os.getenv(_ENV_PREFIX + "MAX_SEQUENCE_PIXELS", "6G")
@@ -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",
@@ -5,6 +5,7 @@ import logging
5
5
  import os
6
6
  import random
7
7
  import typing as T
8
+ import uuid
8
9
 
9
10
  import requests
10
11
 
@@ -55,31 +56,31 @@ def _truncate_end(s: _S) -> _S:
55
56
 
56
57
  class UploadService:
57
58
  user_access_token: str
58
- entity_size: int
59
59
  session_key: str
60
60
  callbacks: T.List[T.Callable[[bytes, T.Optional[requests.Response]], None]]
61
61
  cluster_filetype: ClusterFileType
62
62
  organization_id: T.Optional[T.Union[str, int]]
63
63
  chunk_size: int
64
64
 
65
+ MIME_BY_CLUSTER_TYPE: T.Dict[ClusterFileType, str] = {
66
+ ClusterFileType.ZIP: "application/zip",
67
+ ClusterFileType.BLACKVUE: "video/mp4",
68
+ ClusterFileType.CAMM: "video/mp4",
69
+ }
70
+
65
71
  def __init__(
66
72
  self,
67
73
  user_access_token: str,
68
74
  session_key: str,
69
- entity_size: int,
70
75
  organization_id: T.Optional[T.Union[str, int]] = None,
71
76
  cluster_filetype: ClusterFileType = ClusterFileType.ZIP,
72
77
  chunk_size: int = DEFAULT_CHUNK_SIZE,
73
78
  ):
74
- if entity_size <= 0:
75
- raise ValueError(f"Expect positive entity size but got {entity_size}")
76
-
77
79
  if chunk_size <= 0:
78
80
  raise ValueError("Expect positive chunk size")
79
81
 
80
82
  self.user_access_token = user_access_token
81
83
  self.session_key = session_key
82
- self.entity_size = entity_size
83
84
  self.organization_id = organization_id
84
85
  # validate the input
85
86
  self.cluster_filetype = ClusterFileType(cluster_filetype)
@@ -107,55 +108,66 @@ class UploadService:
107
108
  data: T.IO[bytes],
108
109
  offset: T.Optional[int] = None,
109
110
  ) -> str:
110
- if offset is None:
111
- offset = self.fetch_offset()
112
-
113
- entity_type_map: T.Dict[ClusterFileType, str] = {
114
- ClusterFileType.ZIP: "application/zip",
115
- ClusterFileType.BLACKVUE: "video/mp4",
116
- ClusterFileType.CAMM: "video/mp4",
117
- }
118
-
119
- entity_type = entity_type_map[self.cluster_filetype]
120
-
121
- data.seek(offset, io.SEEK_CUR)
111
+ chunks = self._chunkize_byte_stream(data)
112
+ return self.upload_chunks(chunks, offset=offset)
122
113
 
114
+ def _chunkize_byte_stream(
115
+ self, stream: T.IO[bytes]
116
+ ) -> T.Generator[bytes, None, None]:
123
117
  while True:
124
- chunk = data.read(self.chunk_size)
125
- # it is possible to upload an empty chunk here
126
- # in order to return the handle
127
- headers = {
128
- "Authorization": f"OAuth {self.user_access_token}",
129
- "Offset": f"{offset}",
130
- "X-Entity-Length": str(self.entity_size),
131
- "X-Entity-Name": self.session_key,
132
- "X-Entity-Type": entity_type,
133
- }
134
- url = f"{MAPILLARY_UPLOAD_ENDPOINT}/{self.session_key}"
135
- LOG.debug("POST %s HEADERS %s", url, json.dumps(_sanitize_headers(headers)))
136
- resp = request_post(
137
- url,
138
- headers=headers,
139
- data=chunk,
140
- timeout=UPLOAD_REQUESTS_TIMEOUT,
141
- )
142
- LOG.debug(
143
- "HTTP response %s: %s", resp.status_code, _truncate_end(resp.content)
144
- )
145
- resp.raise_for_status()
146
- offset += len(chunk)
147
- LOG.debug("The next offset will be: %s", offset)
118
+ data = stream.read(self.chunk_size)
119
+ if not data:
120
+ break
121
+ yield data
122
+
123
+ def _offset_chunks(
124
+ self, chunks: T.Iterable[bytes], offset: int
125
+ ) -> T.Generator[bytes, None, None]:
126
+ assert offset >= 0, f"Expect non-negative offset but got {offset}"
127
+
128
+ for chunk in chunks:
129
+ if offset:
130
+ if offset < len(chunk):
131
+ yield chunk[offset:]
132
+ offset = 0
133
+ else:
134
+ offset -= len(chunk)
135
+ else:
136
+ yield chunk
137
+
138
+ def _attach_callbacks(
139
+ self, chunks: T.Iterable[bytes]
140
+ ) -> T.Generator[bytes, None, None]:
141
+ for chunk in chunks:
142
+ yield chunk
148
143
  for callback in self.callbacks:
149
- callback(chunk, resp)
150
- # we can assert that offset == self.fetch_offset(session_key)
151
- # otherwise, server will throw
144
+ callback(chunk, None)
152
145
 
153
- if not chunk:
154
- break
146
+ def upload_chunks(
147
+ self,
148
+ chunks: T.Iterable[bytes],
149
+ offset: T.Optional[int] = None,
150
+ ) -> str:
151
+ if offset is None:
152
+ offset = self.fetch_offset()
155
153
 
156
- assert offset == self.entity_size, (
157
- f"Offset ends at {offset} but the entity size is {self.entity_size}"
154
+ chunks = self._attach_callbacks(self._offset_chunks(chunks, offset))
155
+
156
+ headers = {
157
+ "Authorization": f"OAuth {self.user_access_token}",
158
+ "Offset": f"{offset}",
159
+ "X-Entity-Name": self.session_key,
160
+ "X-Entity-Type": self.MIME_BY_CLUSTER_TYPE[self.cluster_filetype],
161
+ }
162
+ url = f"{MAPILLARY_UPLOAD_ENDPOINT}/{self.session_key}"
163
+ LOG.debug("POST %s HEADERS %s", url, json.dumps(_sanitize_headers(headers)))
164
+ resp = request_post(
165
+ url,
166
+ headers=headers,
167
+ data=chunks,
168
+ timeout=UPLOAD_REQUESTS_TIMEOUT,
158
169
  )
170
+ LOG.debug("HTTP response %s: %s", resp.status_code, _truncate_end(resp.content))
159
171
 
160
172
  payload = resp.json()
161
173
  try:
@@ -209,35 +221,30 @@ class FakeUploadService(UploadService):
209
221
  )
210
222
  self._error_ratio = 0.1
211
223
 
212
- def upload(
224
+ def upload_chunks(
213
225
  self,
214
- data: T.IO[bytes],
226
+ chunks: T.Iterable[bytes],
215
227
  offset: T.Optional[int] = None,
216
228
  ) -> str:
217
229
  if offset is None:
218
230
  offset = self.fetch_offset()
231
+
232
+ chunks = self._attach_callbacks(self._offset_chunks(chunks, offset))
233
+
219
234
  os.makedirs(self._upload_path, exist_ok=True)
220
235
  filename = os.path.join(self._upload_path, self.session_key)
221
236
  with open(filename, "ab") as fp:
222
- data.seek(offset, io.SEEK_CUR)
223
- while True:
224
- chunk = data.read(self.chunk_size)
225
- if not chunk:
226
- break
227
- # fail here means nothing uploaded
237
+ for chunk in chunks:
228
238
  if random.random() <= self._error_ratio:
229
239
  raise requests.ConnectionError(
230
240
  f"TEST ONLY: Failed to upload with error ratio {self._error_ratio}"
231
241
  )
232
242
  fp.write(chunk)
233
- # fail here means patially uploaded
234
243
  if random.random() <= self._error_ratio:
235
244
  raise requests.ConnectionError(
236
245
  f"TEST ONLY: Partially uploaded with error ratio {self._error_ratio}"
237
246
  )
238
- for callback in self.callbacks:
239
- callback(chunk, None)
240
- return self.session_key
247
+ return uuid.uuid4().hex
241
248
 
242
249
  def finish(self, _: str) -> str:
243
250
  return "0"
@@ -195,7 +195,6 @@ class Uploader:
195
195
  upload_api_v4.FakeUploadService(
196
196
  user_access_token=self.user_items["user_upload_token"],
197
197
  session_key=session_key,
198
- entity_size=entity_size,
199
198
  organization_id=self.user_items.get("MAPOrganizationKey"),
200
199
  cluster_filetype=cluster_filetype,
201
200
  chunk_size=self.chunk_size,
@@ -205,7 +204,6 @@ class Uploader:
205
204
  upload_service = upload_api_v4.UploadService(
206
205
  user_access_token=self.user_items["user_upload_token"],
207
206
  session_key=session_key,
208
- entity_size=entity_size,
209
207
  organization_id=self.user_items.get("MAPOrganizationKey"),
210
208
  cluster_filetype=cluster_filetype,
211
209
  chunk_size=self.chunk_size,
@@ -13,8 +13,12 @@ class CammParser(BaseParser):
13
13
  parser_label = "camm"
14
14
 
15
15
  @functools.cached_property
16
- def __camera_info(self) -> T.Tuple[str, str]:
17
- with self.videoPath.open("rb") as fp:
16
+ def _camera_info(self) -> T.Tuple[str, str]:
17
+ source_path = self.geotag_source_path
18
+ if not source_path:
19
+ return "", ""
20
+
21
+ with source_path.open("rb") as fp:
18
22
  return camm_parser.extract_camera_make_and_model(fp)
19
23
 
20
24
  def extract_points(self) -> T.Sequence[geo.Point]:
@@ -28,15 +32,7 @@ class CammParser(BaseParser):
28
32
  return []
29
33
 
30
34
  def extract_make(self) -> T.Optional[str]:
31
- source_path = self.geotag_source_path
32
- if not source_path:
33
- return None
34
- with source_path.open("rb") as _fp:
35
- return self.__camera_info[0] or None
35
+ return self._camera_info[0] or None
36
36
 
37
37
  def extract_model(self) -> T.Optional[str]:
38
- source_path = self.geotag_source_path
39
- if not source_path:
40
- return None
41
- with source_path.open("rb") as _fp:
42
- return self.__camera_info[1] or None
38
+ return self._camera_info[1] or None
@@ -0,0 +1,108 @@
1
+ import datetime
2
+ import logging
3
+ import typing as T
4
+
5
+ from ... import geo, telemetry
6
+ from ...geotag import geotag_images_from_gpx_file
7
+ from .base_parser import BaseParser
8
+ from .generic_video_parser import GenericVideoParser
9
+
10
+
11
+ LOG = logging.getLogger(__name__)
12
+
13
+
14
+ class GpxParser(BaseParser):
15
+ default_source_pattern = "%g.gpx"
16
+ parser_label = "gpx"
17
+
18
+ def extract_points(self) -> T.Sequence[geo.Point]:
19
+ path = self.geotag_source_path
20
+ if not path:
21
+ return []
22
+
23
+ try:
24
+ gpx_tracks = geotag_images_from_gpx_file.parse_gpx(path)
25
+ except Exception as ex:
26
+ raise RuntimeError(
27
+ f"Error parsing GPX {path}: {ex.__class__.__name__}: {ex}"
28
+ )
29
+
30
+ if 1 < len(gpx_tracks):
31
+ LOG.warning(
32
+ "Found %s tracks in the GPX file %s. Will merge points in all the tracks as a single track for interpolation",
33
+ len(gpx_tracks),
34
+ self.videoPath,
35
+ )
36
+
37
+ gpx_points: T.Sequence[geo.Point] = sum(gpx_tracks, [])
38
+ if not gpx_points:
39
+ return gpx_points
40
+
41
+ offset = self._synx_gpx_by_first_gps_timestamp(gpx_points)
42
+
43
+ self._rebase_times(gpx_points, offset=offset)
44
+
45
+ return gpx_points
46
+
47
+ def _synx_gpx_by_first_gps_timestamp(
48
+ self, gpx_points: T.Sequence[geo.Point]
49
+ ) -> float:
50
+ offset: float = 0.0
51
+
52
+ if not gpx_points:
53
+ return offset
54
+
55
+ first_gpx_dt = datetime.datetime.fromtimestamp(
56
+ gpx_points[0].time, tz=datetime.timezone.utc
57
+ )
58
+ LOG.info("First GPX timestamp: %s", first_gpx_dt)
59
+
60
+ # Extract first GPS timestamp (if found) for synchronization
61
+ # Use an empty dictionary to force video parsers to extract make/model from the video metadata itself
62
+ parser = GenericVideoParser(self.videoPath, self.options, {})
63
+ gps_points = parser.extract_points()
64
+
65
+ if not gps_points:
66
+ LOG.warning(
67
+ "Skip GPX synchronization because no GPS found in video %s",
68
+ self.videoPath,
69
+ )
70
+ return offset
71
+
72
+ first_gps_point = gps_points[0]
73
+ if isinstance(first_gps_point, telemetry.GPSPoint):
74
+ if first_gps_point.epoch_time is not None:
75
+ first_gps_dt = datetime.datetime.fromtimestamp(
76
+ first_gps_point.epoch_time, tz=datetime.timezone.utc
77
+ )
78
+ LOG.info("First GPS timestamp: %s", first_gps_dt)
79
+ offset = gpx_points[0].time - first_gps_point.epoch_time
80
+ if offset:
81
+ LOG.warning(
82
+ "Found offset between GPX %s and video GPS timestamps %s: %s seconds",
83
+ first_gpx_dt,
84
+ first_gps_dt,
85
+ offset,
86
+ )
87
+ else:
88
+ LOG.info(
89
+ "GPX and GPS are perfectly synchronized (all starts from %s)",
90
+ first_gpx_dt,
91
+ )
92
+ else:
93
+ LOG.warning(
94
+ "Skip GPX synchronization because no GPS epoch time found in video %s",
95
+ self.videoPath,
96
+ )
97
+
98
+ return offset
99
+
100
+ def extract_make(self) -> T.Optional[str]:
101
+ # Use an empty dictionary to force video parsers to extract make/model from the video metadata itself
102
+ parser = GenericVideoParser(self.videoPath, self.options, {})
103
+ return parser.extract_make()
104
+
105
+ def extract_model(self) -> T.Optional[str]:
106
+ # Use an empty dictionary to force video parsers to extract make/model from the video metadata itself
107
+ parser = GenericVideoParser(self.videoPath, self.options, {})
108
+ return parser.extract_model()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mapillary_tools
3
- Version: 0.13.1a1
3
+ Version: 0.13.3a1
4
4
  Summary: Mapillary Image/Video Import Pipeline
5
5
  Home-page: https://github.com/mapillary/mapillary_tools
6
6
  Author: Mapillary
@@ -1 +0,0 @@
1
- VERSION = "0.13.1a1"
@@ -1,71 +0,0 @@
1
- import datetime
2
- import logging
3
- import typing as T
4
-
5
- from ... import geo, telemetry
6
- from ...geotag import geotag_images_from_gpx_file
7
- from .base_parser import BaseParser
8
- from .generic_video_parser import GenericVideoParser
9
-
10
-
11
- LOG = logging.getLogger(__name__)
12
-
13
-
14
- class GpxParser(BaseParser):
15
- default_source_pattern = "%g.gpx"
16
- parser_label = "gpx"
17
-
18
- def extract_points(self) -> T.Sequence[geo.Point]:
19
- path = self.geotag_source_path
20
- if not path:
21
- return []
22
-
23
- gpx_tracks = geotag_images_from_gpx_file.parse_gpx(path)
24
- if 1 < len(gpx_tracks):
25
- LOG.warning(
26
- "Found %s tracks in the GPX file %s. Will merge points in all the tracks as a single track for interpolation",
27
- len(gpx_tracks),
28
- self.videoPath,
29
- )
30
-
31
- gpx_points: T.Sequence[geo.Point] = sum(gpx_tracks, [])
32
- if not gpx_points:
33
- return gpx_points
34
-
35
- first_gpx_dt = datetime.datetime.fromtimestamp(
36
- gpx_points[0].time, tz=datetime.timezone.utc
37
- )
38
- LOG.info("First GPX timestamp: %s", first_gpx_dt)
39
-
40
- # Extract first GPS timestamp (if found) for synchronization
41
- offset: float = 0.0
42
- parser = GenericVideoParser(self.videoPath, self.options, self.parserOptions)
43
- gps_points = parser.extract_points()
44
- if gps_points:
45
- first_gps_point = gps_points[0]
46
- if isinstance(first_gps_point, telemetry.GPSPoint):
47
- if first_gps_point.epoch_time is not None:
48
- first_gps_dt = datetime.datetime.fromtimestamp(
49
- first_gps_point.epoch_time, tz=datetime.timezone.utc
50
- )
51
- LOG.info("First GPS timestamp: %s", first_gps_dt)
52
- offset = gpx_points[0].time - first_gps_point.epoch_time
53
- if offset:
54
- LOG.warning(
55
- "Found offset between GPX %s and video GPS timestamps %s: %s seconds",
56
- first_gpx_dt,
57
- first_gps_dt,
58
- offset,
59
- )
60
-
61
- self._rebase_times(gpx_points, offset=offset)
62
-
63
- return gpx_points
64
-
65
- def extract_make(self) -> T.Optional[str]:
66
- parser = GenericVideoParser(self.videoPath, self.options, self.parserOptions)
67
- return parser.extract_make()
68
-
69
- def extract_model(self) -> T.Optional[str]:
70
- parser = GenericVideoParser(self.videoPath, self.options, self.parserOptions)
71
- return parser.extract_model()