Skip to content

Commit 5603826

Browse files
authored
Merge pull request #798 from mapillary/feature/null-island-check
Move null island check for images to a dedicated method.
2 parents 1c90348 + 8647177 commit 5603826

2 files changed

Lines changed: 96 additions & 9 deletions

File tree

mapillary_tools/process_sequence_properties.py

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,55 @@ def duplication_check(
146146
return dedups, dups
147147

148148

149+
def _check_null_island(
150+
sequence: PointSequence,
151+
) -> tuple[PointSequence, list[types.ErrorMetadata]]:
152+
"""
153+
Filter out images with null island (0, 0) GPS coordinates.
154+
155+
Returns:
156+
Tuple of (valid images, error metadatas for null island images)
157+
"""
158+
valid: PointSequence = []
159+
errors: list[types.ErrorMetadata] = []
160+
161+
for image in sequence:
162+
if image.lat == 0 and image.lon == 0:
163+
ex = exceptions.MapillaryNullIslandError(
164+
"GPS coordinates in Null Island (0, 0)"
165+
)
166+
errors.append(
167+
types.describe_error_metadata(
168+
exc=ex, filename=image.filename, filetype=types.FileType.IMAGE
169+
)
170+
)
171+
else:
172+
valid.append(image)
173+
174+
return valid, errors
175+
176+
177+
def _check_sequences_null_island(
178+
input_sequences: T.Sequence[PointSequence],
179+
) -> tuple[list[PointSequence], list[types.ErrorMetadata]]:
180+
"""Apply null island check to all sequences."""
181+
output_sequences: list[PointSequence] = []
182+
output_errors: list[types.ErrorMetadata] = []
183+
184+
for sequence in input_sequences:
185+
output_sequence, errors = _check_null_island(sequence)
186+
if output_sequence:
187+
output_sequences.append(output_sequence)
188+
output_errors.extend(errors)
189+
190+
if output_errors:
191+
LOG.info(
192+
f"Null island check: {len(output_errors)} images removed with (0, 0) coordinates"
193+
)
194+
195+
return output_sequences, output_errors
196+
197+
149198
def _group_images_by(
150199
image_metadatas: T.Iterable[types.ImageMetadata],
151200
group_key_func: T.Callable[[types.ImageMetadata], T.Hashable],
@@ -310,14 +359,6 @@ def _check_sequences_by_limits(
310359
f"Sequence file size {humanize.naturalsize(sequence_filesize)} exceeds max allowed {humanize.naturalsize(max_sequence_filesize_in_bytes)}",
311360
)
312361

313-
contains_null_island = any(
314-
image.lat == 0 and image.lon == 0 for image in sequence
315-
)
316-
if contains_null_island:
317-
raise exceptions.MapillaryNullIslandError(
318-
"GPS coordinates in Null Island (0, 0)"
319-
)
320-
321362
avg_speed_kmh = geo.avg_speed(sequence) * 3.6 # Convert m/s to km/h
322363
too_fast = len(sequence) >= 2 and avg_speed_kmh > max_capture_speed_kmh
323364
if too_fast:
@@ -765,6 +806,10 @@ def process_sequence_properties(
765806
cutoff_time=cutoff_time,
766807
)
767808

809+
# Null island check
810+
sequences, errors = _check_sequences_null_island(sequences)
811+
error_metadatas.extend(errors)
812+
768813
# Duplication check
769814
sequences, errors = _check_sequences_duplication(
770815
sequences,
@@ -789,7 +834,7 @@ def process_sequence_properties(
789834
error_metadatas.extend(errors)
790835

791836
# Check for zig-zag GPS patterns
792-
# NOTE: This is done after _check_sequences_by_limits to filter missing or zero coordinates
837+
# NOTE: This is done after _check_sequences_null_island to filter zero coordinates
793838
if not skip_zigzag_check:
794839
sequences, errors = _check_sequences_zigzag(
795840
sequences,

tests/unit/test_sequence_processing.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,48 @@ def test_video_error(tmpdir: py.path.local):
713713
)
714714

715715

716+
def test_image_null_island(tmpdir: py.path.local):
717+
"""Test that only images with (0,0) coordinates get errors, not the whole sequence."""
718+
curdir = tmpdir.mkdir("null_island_test")
719+
sequence: T.List[types.MetadataOrError] = [
720+
# Image at null island - should get error
721+
_make_image_metadata(Path(curdir) / Path("null_island.jpg"), 0, 0, 1, angle=10),
722+
# Valid images in same folder - should pass through
723+
_make_image_metadata(
724+
Path(curdir) / Path("valid1.jpg"), 1.00001, 1.00001, 2, angle=20
725+
),
726+
_make_image_metadata(
727+
Path(curdir) / Path("valid2.jpg"), 1.00002, 1.00002, 3, angle=30
728+
),
729+
]
730+
metadatas = psp.process_sequence_properties(
731+
sequence,
732+
cutoff_distance=1000000,
733+
cutoff_time=100,
734+
interpolate_directions=False,
735+
duplicate_distance=0.1,
736+
duplicate_angle=5,
737+
)
738+
metadata_by_filename = {m.filename.name: m for m in metadatas}
739+
740+
# Null island image should have an error
741+
assert isinstance(metadata_by_filename["null_island.jpg"], types.ErrorMetadata), (
742+
"Null island image should be an ErrorMetadata"
743+
)
744+
assert isinstance(
745+
metadata_by_filename["null_island.jpg"].error,
746+
exceptions.MapillaryNullIslandError,
747+
), "Error should be MapillaryNullIslandError"
748+
749+
# Valid images should pass through as ImageMetadata
750+
assert isinstance(metadata_by_filename["valid1.jpg"], types.ImageMetadata), (
751+
"Valid image should pass through"
752+
)
753+
assert isinstance(metadata_by_filename["valid2.jpg"], types.ImageMetadata), (
754+
"Valid image should pass through"
755+
)
756+
757+
716758
def test_split_sequence_by_filesize(tmpdir):
717759
sequence: T.List[types.Metadata] = [
718760
# s1

0 commit comments

Comments
 (0)