Command line options --duplicate_distance and --duplicate_angle

I’m trying to figure out how to use the command line options correctly when uploading sequences captured with a GoPro. I’ve experimented with the Mapillary_tools process command to see what happens. The command used…

mapillary_tools-0.9.3-win-64bit process “E:\GoPro Capture\100722\104GOPRO” --interpolate_directions --duplicate_distance 0.5 --duplicate_angle 20

First, I find no documentation (why?) for this command option in the README.md file. Looking at the comments in the process.py file I see this…

help=‘The maximum distance that can be considered “too close” between two images. If both images also point in the same direction (see --duplicate_angle), the later image will be marked as duplicate and will not be upload. [default: %(default)s]’,

I’m presuming the “distance” is in meters. No? Why isn’t this documented?

First question… it says the “image will be marked as duplicate”. Where will it be marked? In the mapillary_image_description.json file? Can I find this marking so that I can tell this command option worked? I’ve tried this on a capture sequence that I know has multiple images captured while I was not moving. I can’t see anything that changed after processing to indicate that any images were marked as duplicates.

Second question… What’s the use case for the –duplicate_angle option? I don’t believe GoPros provide compass info. Reading the comments in the process.py…

help=“The maximum camera angle difference between two images to be considered as heading in the same direction. If both images are also close to each other (see --duplicate_distance), the later image will be marked as duplicate and will not be upload. [default: %(default)s]”,

So… If I read this correctly, if the perceived angle between two images is equal to or less than the angle set by the --duplicate_angle options, the two images will be considered heading in the same direction. If the images are heading in the same direction AND the distance between the two images is below (or equal to) the threshold set by the --duplicate_distance option, then “the later image will be marked as duplicate and will not be upload(ed)”.

My understanding is that GPS compass headings can vary widely while standing still.
I’ve got a sequence where I was not moving for the first six images. The “MAPCompassHeading” - in the json file after processing - varied by up to ~15 degrees for these images. Max distance between the image coordinates was ~0.2 m. So if I’ve understood this correctly, then the -duplicate_distance 0.5 and -duplicate_angle 20 command options should have resulted in the later images being marked as duplicates somewhere. No? If they were marked, where can I see this?

Next question… I find in the constants.py file…

DUPLICATE_DISTANCE = float(os.getenv(_ENV_PREFIX + “DUPLICATE_DISTANCE”, 0.1))
DUPLICATE_ANGLE = float(os.getenv(_ENV_PREFIX + “DUPLICATE_ANGLE”, 5))

So if the –duplicate_distance and –duplicate_angle options are not used in your command, do “duplicate images” still get identified (based on the defaults) and prevented from being uploaded?

How about the Mapillary Uploader application? Does it mark images as duplicates based on the defaults?

For reference… here is the info for the first six images in the json file produced by the process command…

{
“MAPLatitude”: 39.5872201,
“MAPLongitude”: -76.86480359999999,
“MAPCaptureTime”: “2022_10_07_13_13_13_000”,
“filename”: “E:\GoPro Capture\100722\104GOPRO\G0017329.JPG”,
“MAPAltitude”: 240.253,
“MAPOrientation”: 1,
“MAPDeviceMake”: “GoPro”,
“MAPDeviceModel”: “HERO5 Black”,
“MAPMetaTags”: {
“strings”: [
{
“key”: “mapillary_tools_version”,
“value”: “0.9.3”
}
]
},
“MAPCompassHeading”: {
“TrueHeading”: 170.62278154453497,
“MagneticHeading”: 170.62278154453497
},
“MAPSequenceUUID”: “82885958-2c89-42b6-a686-42c60964325a”
},
{
“MAPLatitude”: 39.5872187,
“MAPLongitude”: -76.86480329999999,
“MAPCaptureTime”: “2022_10_07_13_13_13_000”,
“filename”: “E:\GoPro Capture\100722\104GOPRO\G0017330.JPG”,
“MAPAltitude”: 239.972,
“MAPOrientation”: 1,
“MAPDeviceMake”: “GoPro”,
“MAPDeviceModel”: “HERO5 Black”,
“MAPMetaTags”: {
“strings”: [
{
“key”: “mapillary_tools_version”,
“value”: “0.9.3”
}
]
},
“MAPCompassHeading”: {
“TrueHeading”: 156.0295583552704,
“MagneticHeading”: 156.0295583552704
},
“MAPSequenceUUID”: “82885958-2c89-42b6-a686-42c60964325a”
},
{
“MAPLatitude”: 39.58721349997222,
“MAPLongitude”: -76.86480029997222,
“MAPCaptureTime”: “2022_10_07_13_13_14_000”,
“filename”: “E:\GoPro Capture\100722\104GOPRO\G0017331.JPG”,
“MAPAltitude”: 238.647,
“MAPOrientation”: 1,
“MAPDeviceMake”: “GoPro”,
“MAPDeviceModel”: “HERO5 Black”,
“MAPMetaTags”: {
“strings”: [
{
“key”: “mapillary_tools_version”,
“value”: “0.9.3”
}
]
},
“MAPCompassHeading”: {
“TrueHeading”: 163.2784159355898,
“MagneticHeading”: 163.2784159355898
},
“MAPSequenceUUID”: “82885958-2c89-42b6-a686-42c60964325a”
},
{
“MAPLatitude”: 39.587207599972224,
“MAPLongitude”: -76.86479799997221,
“MAPCaptureTime”: “2022_10_07_13_13_15_000”,
“filename”: “E:\GoPro Capture\100722\104GOPRO\G0017332.JPG”,
“MAPAltitude”: 237.476,
“MAPOrientation”: 1,
“MAPDeviceMake”: “GoPro”,
“MAPDeviceModel”: “HERO5 Black”,
“MAPMetaTags”: {
“strings”: [
{
“key”: “mapillary_tools_version”,
“value”: “0.9.3”
}
]
},
“MAPCompassHeading”: {
“TrueHeading”: 155.79381295178064,
“MagneticHeading”: 155.79381295178064
},
“MAPSequenceUUID”: “82885958-2c89-42b6-a686-42c60964325a”
},
{
“MAPLatitude”: 39.5872016,
“MAPLongitude”: -76.86479449999999,
“MAPCaptureTime”: “2022_10_07_13_13_15_000”,
“filename”: “E:\GoPro Capture\100722\104GOPRO\G0017333.JPG”,
“MAPAltitude”: 236.332,
“MAPOrientation”: 1,
“MAPDeviceMake”: “GoPro”,
“MAPDeviceModel”: “HERO5 Black”,
“MAPMetaTags”: {
“strings”: [
{
“key”: “mapillary_tools_version”,
“value”: “0.9.3”
}
]
},
“MAPCompassHeading”: {
“TrueHeading”: 155.4543505595425,
“MagneticHeading”: 155.4543505595425
},
“MAPSequenceUUID”: “82885958-2c89-42b6-a686-42c60964325a”
},
{
“MAPLatitude”: 39.5871962,
“MAPLongitude”: -76.86479129997221,
“MAPCaptureTime”: “2022_10_07_13_13_16_000”,
“filename”: “E:\GoPro Capture\100722\104GOPRO\G0017334.JPG”,
“MAPAltitude”: 235.068,
“MAPOrientation”: 1,
“MAPDeviceMake”: “GoPro”,
“MAPDeviceModel”: “HERO5 Black”,
“MAPMetaTags”: {
“strings”: [
{
“key”: “mapillary_tools_version”,
“value”: “0.9.3”
}
]
},
“MAPCompassHeading”: {
“TrueHeading”: 176.32543633256842,
“MagneticHeading”: 176.32543633256842
},
“MAPSequenceUUID”: “82885958-2c89-42b6-a686-42c60964325a”
},

Personally, I dont think the getting rid of duplicates is actually working in the tools. The changelog back in 2019 says

  • Duplicate flagging is now done automatically with the default duplicate thresholds. To keep duplicates, argument --keep_duplicates must be passed.

I have plenty of sequences where duplicates remain despite using the duplicate distance parameter in the tools.

That entry in the change log is interesting…

  • Duplicate flagging is now done automatically with the default duplicate thresholds. To keep duplicates, argument --keep_duplicates must be passed.

So that answers one of my questions. However, the defaults - from what I can tell - is 0.1 meters for –duplicate_distance and 5 degrees for –duplicate_angle. Those defaults seem so tight that one would never be within the threshold to ID a duplicate. So, even though duplicate flagging may occur automatically, that fact may be worthless essentially.

I can’t find any documentation on --keep_duplicates or what “must be passed” means. My guess “must be passed” means you have to include the command option to keep the duplicates and upload them. Since I found no documentation on the option - to include in –help, I’m guessing maybe it doesn’t exist anymore? Maybe so - trying to use it to see what it does causes an error. Doesn’t matter, it’s contrary to what I want to do.

So if Mapillary_tools command line automatically flags duplicates (even with what appears to be option defeating defaults), does the Mapillary Uploader desktop app also attempt to automatically flag duplicates (with essentially the same option defeating defaults)?

With further experimentation I found that if I first run this…

mapillary_tools-0.9.3-win-64bit process “E:\GoPro Capture\100722\104GOPRO” --overwrite_EXIF_direction_tag

… followed by this…

mapillary_tools-0.9.3-win-64bit process “E:\GoPro Capture\100722\104GOPRO” --duplicate_distance 2.0 --duplicate_angle 360 --interpolate_directions

… it works.

Ok maybe that makes sense. The EXIF tags may first require something to be in the EXIF GPSImgDirection and GPSImgDirectionRef tags before the --duplicate_angle option will work. However, I thought I saw - in other posted workflows - all options included in one command. I.e. I figured that running this command would work…

mapillary_tools-0.9.3-win-64bit process “E:\GoPro Capture\100722\104GOPRO” --overwrite_EXIF_direction_tag --duplicate_distance 2.0 --duplicate_angle 360 --interpolate_directions

But apparently not. It appears to first process the folder first (with non existent EXIF direction tags) in which case the duplicate flagging does nothing. Then it appears to write the new EXIF direction tags to the image files. At least this is what it appears to me to be doing. If I’m missing something, someone please let me know.

I’m presuming the “distance” is in meters. No? Why isn’t this documented?

Yes, it’s meters. Will update the help docs.

First question… it says the “image will be marked as duplicate”. Where will it be marked?
Yes, you will find it in mapillary_image_description.json. Duplicated images will be marked as "error": "MapillaryDuplicationError"

So… If I read this correctly, if the perceived angle between two images is equal to or less than the angle set by the --duplicate_angle options, the two images will be considered heading in the same direction. If the images are heading in the same direction AND the distance between the two images is below (or equal to) the threshold set by the --duplicate_distance option, then “the later image will be marked as duplicate and will not be upload(ed)”.

Yes, exactly. Both checks must be true to be considered as duplicated.

I’ve got a sequence where I was not moving for the first six images. The “MAPCompassHeading” - in the json file after processing - varied by up to ~15 degrees for these images. Max distance between the image coordinates was ~0.2 m. So if I’ve understood this correctly, then the -duplicate_distance 0.5 and -duplicate_angle 20 command options should have resulted in the later images being marked as duplicates somewhere. No? If they were marked, where can I see this?

These images should be marked as duplicated in mapillary_image_description.json. If not, let me know.

So if the –duplicate_distance and –duplicate_angle options are not used in your command, do “duplicate images” still get identified (based on the defaults) and prevented from being uploaded?

These are the defaults. So yes, if two images with angle <=5 AND distance <=0.1, the later will be marked as duplicated.

How about the Mapillary Uploader application? Does it mark images as duplicates based on the defaults?

Yes, I think so.

  • Duplicate flagging is now done automatically with the default duplicate thresholds. To keep duplicates, argument --keep_duplicates must be passed.

--keep_duplicates was removed in Release v0.8.0 · mapillary/mapillary_tools · GitHub.

To keep duplications (--keep_duplicates) in the latest versions, specify --duplicate_angle=360 --duplication_distance=1000000 should work.

So that answers one of my questions. However, the defaults - from what I can tell - is 0.1 meters for –duplicate_distance and 5 degrees for –duplicate_angle. Those defaults seem so tight that one would never be within the threshold to ID a duplicate. So, even though duplicate flagging may occur automatically, that fact may be worthless essentially.

I guess the original idea of setting these tight thresholds is to mark images at the same points. I’m happy to change it to a better defaults.

With further experimentation I found that if I first run this…

mapillary_tools-0.9.3-win-64bit process “E:\GoPro Capture\100722\104GOPRO” --overwrite_EXIF_direction_tag

… followed by this…

mapillary_tools-0.9.3-win-64bit process “E:\GoPro Capture\100722\104GOPRO” --duplicate_distance 2.0 --duplicate_angle 360 --interpolate_directions

This is because mapillary_tools check duplicates first and then interpolate image angles.

In the first run, there were no angle information, therefore no duplicates found. And then angles were interpolated and written into the EXIFs.

In the second run, it found image angles, but since --duplicate_angle 360 specified so it interpolated based on the distance only.

If you wonder why duplicate check before interpolation, check Please run --interpolate_directions AFTER detecting duplicates · Issue #383 · mapillary/mapillary_tools · GitHub

Thanks Tao for getting back to me.

So basically, the conclusion from my experimentation is correct. You essentially have to run the process command twice. First run the process command to interpolate and write the compass angles to the EXIFs and second, run it again to do the duplications check.

After reading your reply, I realized that the --interpolate_directions option in the second process command appears to be unnecessary (already done in the first command with the --overwrite_EXIF_direction_tag). So the two commands should be…

mapillary_tools-0.9.4-win-64bit process “E:\GoPro Capture\100722 - test\104GOPRO” --overwrite_EXIF_direction_tag

mapillary_tools-0.9.4-win-64bit process “E:\GoPro Capture\100722 - test\104GOPRO” --duplicate_distance 2.0 --duplicate_angle 360

This will interpolate directions, write the compass direction into the EXIF tags, and remove duplicates (any images closer than 2 meters).

The --duplicate_angle 360 option was intentional. I didn’t want it to consider compass angles when detecting duplicates. Why? The GPS tags for the images taken while sitting still (at an intersection for example) might produce large differences in interpolated compass angles between images which will cause the duplicate detection to fail.