Skip to content

Commit 982e1fa

Browse files
authored
fix: add back MAPFilename (#739)
* add image basename to MAPFilename * refactor: use types.separate_errors * move combine_filetype_filters to types.py * tests * not deprecated yet
1 parent 7a45e5e commit 982e1fa

File tree

4 files changed

+92
-81
lines changed

4 files changed

+92
-81
lines changed

mapillary_tools/process_geotag_properties.py

Lines changed: 43 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def _parse_source_options(
4545

4646
for s in video_geotag_source:
4747
for video_option in parse_source_option(s):
48-
video_option.filetypes = _combine_filetypes(
48+
video_option.filetypes = types.combine_filetype_filters(
4949
video_option.filetypes, {types.FileType.VIDEO}
5050
)
5151
parsed_options.append(video_option)
@@ -69,35 +69,6 @@ def _parse_source_options(
6969
return parsed_options
7070

7171

72-
# Assume {GOPRO, VIDEO} are the NATIVE_VIDEO_FILETYPES:
73-
# a | b = result
74-
# {CAMM} | {GOPRO} = {}
75-
# {CAMM} | {GOPRO, VIDEO} = {CAMM}
76-
# {GOPRO} | {GOPRO, VIDEO} = {GOPRO}
77-
# {GOPRO} | {VIDEO} = {GOPRO}
78-
# {CAMM, GOPRO} | {VIDEO} = {CAMM, GOPRO}
79-
# {VIDEO} | {VIDEO} = {CAMM, GOPRO, VIDEO}
80-
def _combine_filetypes(
81-
a: set[types.FileType] | None, b: set[types.FileType] | None
82-
) -> set[types.FileType] | None:
83-
if a is None:
84-
return b
85-
86-
if b is None:
87-
return a
88-
89-
# VIDEO is a superset of NATIVE_VIDEO_FILETYPES,
90-
# so we add NATIVE_VIDEO_FILETYPES to each set for intersection later
91-
92-
if types.FileType.VIDEO in a:
93-
a = a | types.NATIVE_VIDEO_FILETYPES
94-
95-
if types.FileType.VIDEO in b:
96-
b = b | types.NATIVE_VIDEO_FILETYPES
97-
98-
return a.intersection(b)
99-
100-
10172
def process_geotag_properties(
10273
import_path: Path | T.Sequence[Path],
10374
filetypes: set[types.FileType] | None,
@@ -135,7 +106,7 @@ def process_geotag_properties(
135106
)
136107

137108
for option in options:
138-
option.filetypes = _combine_filetypes(option.filetypes, filetypes)
109+
option.filetypes = types.combine_filetype_filters(option.filetypes, filetypes)
139110
option.num_processes = num_processes
140111
if option.interpolation is None:
141112
option.interpolation = InterpolationOption(
@@ -236,9 +207,7 @@ def _write_metadatas(
236207
LOG.info("Check the description file for details: %s", desc_path)
237208

238209

239-
def _is_error_skipped(
240-
error_type: str, skipped_process_errors: T.Set[T.Type[Exception]]
241-
):
210+
def _is_error_skipped(error_type: str, skipped_process_errors: set[T.Type[Exception]]):
242211
skipped_process_error_names = set(err.__name__ for err in skipped_process_errors)
243212
skip_all = Exception in skipped_process_errors
244213
return skip_all or error_type in skipped_process_error_names
@@ -248,17 +217,13 @@ def _show_stats(
248217
metadatas: T.Sequence[types.MetadataOrError],
249218
skipped_process_errors: T.Set[T.Type[Exception]],
250219
) -> None:
251-
metadatas_by_filetype: T.Dict[types.FileType, list[types.MetadataOrError]] = {}
220+
metadatas_by_filetype: dict[types.FileType, list[types.MetadataOrError]] = {}
252221
for metadata in metadatas:
253-
filetype: types.FileType | None
254222
if isinstance(metadata, types.ImageMetadata):
255223
filetype = types.FileType.IMAGE
256224
else:
257225
filetype = metadata.filetype
258-
if filetype:
259-
metadatas_by_filetype.setdefault(types.FileType(filetype), []).append(
260-
metadata
261-
)
226+
metadatas_by_filetype.setdefault(filetype, []).append(metadata)
262227

263228
for filetype, group in metadatas_by_filetype.items():
264229
_show_stats_per_filetype(group, filetype, skipped_process_errors)
@@ -278,19 +243,16 @@ def _show_stats(
278243

279244

280245
def _show_stats_per_filetype(
281-
metadatas: T.Sequence[types.MetadataOrError],
246+
metadatas: T.Collection[types.MetadataOrError],
282247
filetype: types.FileType,
283248
skipped_process_errors: T.Set[T.Type[Exception]],
284249
):
285-
good_metadatas: list[T.Union[types.VideoMetadata, types.ImageMetadata]] = []
286-
filesize_to_upload = 0
287-
error_metadatas: list[types.ErrorMetadata] = []
288-
for metadata in metadatas:
289-
if isinstance(metadata, types.ErrorMetadata):
290-
error_metadatas.append(metadata)
291-
else:
292-
good_metadatas.append(metadata)
293-
filesize_to_upload += metadata.filesize or 0
250+
good_metadatas: list[types.Metadata]
251+
good_metadatas, error_metadatas = types.separate_errors(metadatas)
252+
253+
filesize_to_upload = sum(
254+
[0 if m.filesize is None else m.filesize for m in good_metadatas]
255+
)
294256

295257
LOG.info("%8d %s(s) read in total", len(metadatas), filetype.value)
296258
if good_metadatas:
@@ -317,8 +279,10 @@ def _show_stats_per_filetype(
317279

318280

319281
def _validate_metadatas(
320-
metadatas: T.Sequence[types.MetadataOrError], num_processes: int | None
282+
metadatas: T.Collection[types.MetadataOrError], num_processes: int | None
321283
) -> list[types.MetadataOrError]:
284+
LOG.debug("Validating %d metadatas", len(metadatas))
285+
322286
# validating metadatas is slow, hence multiprocessing
323287

324288
# Do not pass error metadatas where the error object can not be pickled for multiprocessing to work
@@ -361,34 +325,45 @@ def process_finalize(
361325
desc_path: str | None = None,
362326
num_processes: int | None = None,
363327
) -> list[types.MetadataOrError]:
328+
image_metadatas: list[types.ImageMetadata] = []
329+
video_metadatas: list[types.VideoMetadata] = []
330+
364331
for metadata in metadatas:
365332
if isinstance(metadata, types.VideoMetadata):
366-
if device_make is not None:
367-
metadata.make = device_make
368-
if device_model is not None:
369-
metadata.model = device_model
333+
video_metadatas.append(metadata)
370334
elif isinstance(metadata, types.ImageMetadata):
371-
if device_make is not None:
372-
metadata.MAPDeviceMake = device_make
373-
if device_model is not None:
374-
metadata.MAPDeviceModel = device_model
335+
image_metadatas.append(metadata)
336+
337+
for metadata in video_metadatas:
338+
if device_make is not None:
339+
metadata.make = device_make
340+
if device_model is not None:
341+
metadata.model = device_model
342+
343+
for metadata in image_metadatas:
344+
if device_make is not None:
345+
metadata.MAPDeviceMake = device_make
346+
if device_model is not None:
347+
metadata.MAPDeviceModel = device_model
348+
# Add the basename
349+
metadata.MAPFilename = metadata.filename.name
375350

376351
# modified in place
377352
_apply_offsets(
378-
[
379-
metadata
380-
for metadata in metadatas
381-
if isinstance(metadata, types.ImageMetadata)
382-
],
353+
image_metadatas,
383354
offset_time=offset_time,
384355
offset_angle=offset_angle,
385356
)
386357

387-
LOG.debug("Validating %d metadatas", len(metadatas))
388358
metadatas = _validate_metadatas(metadatas, num_processes=num_processes)
389359

360+
# image_metadatas and video_metadatas get stale after the validation,
361+
# hence delete them to avoid confusion
362+
del image_metadatas
363+
del video_metadatas
364+
390365
_overwrite_exif_tags(
391-
# search image metadatas again because some of them might have been failed
366+
# Search image metadatas again because some of them might have been failed
392367
[
393368
metadata
394369
for metadata in metadatas
@@ -426,10 +401,10 @@ def process_finalize(
426401
# write descs first because _show_stats() may raise an exception
427402
_write_metadatas(metadatas, desc_path)
428403

429-
# show stats
404+
# Show stats
430405
skipped_process_errors: T.Set[T.Type[Exception]]
431406
if skip_process_errors:
432-
# skip all exceptions
407+
# Skip all exceptions
433408
skipped_process_errors = {Exception}
434409
else:
435410
skipped_process_errors = {exceptions.MapillaryDuplicationError}

mapillary_tools/types.py

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ class ImageMetadata(geo.Point):
6666
MAPOrientation: T.Optional[int] = None
6767
# deprecated since v0.10.0; keep here for compatibility
6868
MAPMetaTags: T.Optional[T.Dict] = None
69-
# deprecated since v0.10.0; keep here for compatibility
7069
MAPFilename: T.Optional[str] = None
7170
filesize: T.Optional[int] = None
7271

@@ -105,7 +104,7 @@ def update_md5sum(self) -> None:
105104
@dataclasses.dataclass
106105
class ErrorMetadata:
107106
filename: Path
108-
filetype: T.Optional[FileType]
107+
filetype: FileType
109108
error: Exception
110109

111110

@@ -115,6 +114,35 @@ class ErrorMetadata:
115114
MetadataOrError = T.Union[Metadata, ErrorMetadata]
116115

117116

117+
# Assume {GOPRO, VIDEO} are the NATIVE_VIDEO_FILETYPES:
118+
# a | b = result
119+
# {CAMM} | {GOPRO} = {}
120+
# {CAMM} | {GOPRO, VIDEO} = {CAMM}
121+
# {GOPRO} | {GOPRO, VIDEO} = {GOPRO}
122+
# {GOPRO} | {VIDEO} = {GOPRO}
123+
# {CAMM, GOPRO} | {VIDEO} = {CAMM, GOPRO}
124+
# {VIDEO} | {VIDEO} = {CAMM, GOPRO, VIDEO}
125+
def combine_filetype_filters(
126+
a: set[FileType] | None, b: set[FileType] | None
127+
) -> set[FileType] | None:
128+
if a is None:
129+
return b
130+
131+
if b is None:
132+
return a
133+
134+
# VIDEO is a superset of NATIVE_VIDEO_FILETYPES,
135+
# so we add NATIVE_VIDEO_FILETYPES to each set for intersection later
136+
137+
if FileType.VIDEO in a:
138+
a = a | NATIVE_VIDEO_FILETYPES
139+
140+
if FileType.VIDEO in b:
141+
b = b | NATIVE_VIDEO_FILETYPES
142+
143+
return a.intersection(b)
144+
145+
118146
class UserItem(TypedDict, total=False):
119147
MAPOrganizationKey: T.Union[int, str]
120148
# Not in use. Keep here for back-compatibility
@@ -239,7 +267,7 @@ def _describe_error_desc(
239267

240268

241269
def describe_error_metadata(
242-
exc: Exception, filename: Path, filetype: T.Optional[FileType]
270+
exc: Exception, filename: Path, filetype: FileType
243271
) -> ErrorMetadata:
244272
return ErrorMetadata(filename=filename, filetype=filetype, error=exc)
245273

@@ -307,7 +335,6 @@ def describe_error_metadata(
307335
"MAPDeviceModel": {"type": "string"},
308336
"MAPGPSAccuracyMeters": {"type": "number"},
309337
"MAPCameraUUID": {"type": "string"},
310-
# deprecated since v0.10.0; keep here for compatibility
311338
"MAPFilename": {
312339
"type": "string",
313340
"description": "The base filename of the image",
@@ -673,15 +700,16 @@ def validate_and_fail_metadata(metadata: MetadataOrError) -> MetadataOrError:
673700
if isinstance(metadata, ErrorMetadata):
674701
return metadata
675702

676-
filetype: T.Optional[FileType] = None
703+
if isinstance(metadata, ImageMetadata):
704+
filetype = FileType.IMAGE
705+
validate = validate_image_desc
706+
else:
707+
assert isinstance(metadata, VideoMetadata)
708+
filetype = metadata.filetype
709+
validate = validate_video_desc
710+
677711
try:
678-
if isinstance(metadata, ImageMetadata):
679-
filetype = FileType.IMAGE
680-
validate_image_desc(as_desc(metadata))
681-
else:
682-
assert isinstance(metadata, VideoMetadata)
683-
filetype = metadata.filetype
684-
validate_video_desc(as_desc(metadata))
712+
validate(as_desc(metadata))
685713
except exceptions.MapillaryMetadataValidationError as ex:
686714
# rethrow because the original error is too verbose
687715
return describe_error_metadata(

tests/integration/test_process_and_upload.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,14 @@ def _validate_uploads(upload_dir: py.path.local, expected):
139139
else:
140140
raise Exception(f"invalid file {file}")
141141

142-
excludes = ["filename", "filesize", "md5sum", "MAPMetaTags", "MAPSequenceUUID"]
142+
excludes = [
143+
"filename",
144+
"filesize",
145+
"md5sum",
146+
"MAPMetaTags",
147+
"MAPSequenceUUID",
148+
"MAPFilename",
149+
]
143150

144151
actual = {}
145152
for desc in descs:

tests/unit/test_sequence_processing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ def test_process_finalize(setup_data):
449449
"filename": str(test_exif),
450450
"filetype": "image",
451451
"filesize": None,
452+
"MAPFilename": "test_exif.jpg",
452453
"MAPLatitude": 1,
453454
"MAPLongitude": 1,
454455
"MAPCaptureTime": "1970_01_01_00_00_02_000",

0 commit comments

Comments
 (0)