Initial commit
Filter images with dry run and bounding box
This commit is contained in:
commit
eb243c5aa7
7 files changed
+208
No files matched your search
@@ -0,0 +1,4 @@
|
||||
venv/
|
||||
build/
|
||||
dist/
|
||||
filtered/
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Filter Images",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "filter_gps_images.py",
|
||||
"args": [
|
||||
"--input_folder", "./test",
|
||||
"--output_folder", "./filtered",
|
||||
"--gps_box", "0.0", "-180.0", "90.0", "180.0",
|
||||
"--dry_run"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "Package single executable",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "PyInstaller",
|
||||
"args": [
|
||||
"--onefile",
|
||||
"--name", "filter_gps_images",
|
||||
"filter_gps_images.py"
|
||||
],
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
Filter Images
|
||||
=============
|
||||
|
||||
This project provides tools to filter images based on gps coordinates criteria.
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# Iterate over a folder with images
|
||||
# print the image gps coordinates
|
||||
# filter for images within a certain gps coordinates box
|
||||
# Use pillow to read image metadata
|
||||
import os
|
||||
import argparse
|
||||
import shutil
|
||||
from PIL import Image
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(description="Filter images within a GPS coordinates box")
|
||||
parser.add_argument("--input_folder", type=str, required=True, help="Path to the input folder containing images")
|
||||
parser.add_argument("--output_folder", type=str, required=True, help="Path to the output folder to save filtered images")
|
||||
gps_box_help = "Bounding box defined by min and max latitude and longitude" \
|
||||
" Swiss Alps region: --gps_box 46.0 8.0 48.0 9.0" \
|
||||
" Zurich area: --gps_box 47.2 8.3 47.6 8.7" \
|
||||
" Bernese Alps: --gps_box 46.4 7.8 46.8 8.3"
|
||||
parser.add_argument("--gps_box", type=float, nargs=4, required=True, metavar=('LAT_MIN', 'LON_MIN', 'LAT_MAX', 'LON_MAX'), help=gps_box_help)
|
||||
parser.add_argument("--dry_run", action="store_true", help="Enable dry run mode")
|
||||
# Add help text for gps_box
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
def get_gps_coordinates(image):
|
||||
"""Extract GPS coordinates from image EXIF data"""
|
||||
exifdata = image.getexif()
|
||||
|
||||
# Check if GPS info is available
|
||||
if 34853 not in exifdata:
|
||||
return None, None
|
||||
|
||||
# Use get_ifd() to access the actual GPS data dictionary
|
||||
try:
|
||||
gps_info = exifdata.get_ifd(34853)
|
||||
except (KeyError, TypeError):
|
||||
return None, None
|
||||
|
||||
# Check if gps_info is actually a dictionary with GPS data
|
||||
if not isinstance(gps_info, dict) or len(gps_info) == 0:
|
||||
return None, None
|
||||
|
||||
def convert_to_degrees(value):
|
||||
"""Convert GPS coordinates to decimal degrees"""
|
||||
try:
|
||||
if isinstance(value, (list, tuple)) and len(value) >= 3:
|
||||
d, m, s = value[0], value[1], value[2]
|
||||
# Handle rational numbers (tuples like (40, 1) meaning 40/1)
|
||||
if isinstance(d, tuple) and len(d) == 2:
|
||||
d = d[0] / d[1]
|
||||
if isinstance(m, tuple) and len(m) == 2:
|
||||
m = m[0] / m[1]
|
||||
if isinstance(s, tuple) and len(s) == 2:
|
||||
s = s[0] / s[1]
|
||||
return float(d) + (float(m) / 60.0) + (float(s) / 3600.0)
|
||||
return None
|
||||
except (ValueError, TypeError, IndexError, ZeroDivisionError):
|
||||
return None
|
||||
|
||||
lat = None
|
||||
lon = None
|
||||
|
||||
try:
|
||||
# Get latitude
|
||||
if 2 in gps_info and 1 in gps_info: # GPSLatitude and GPSLatitudeRef
|
||||
lat = convert_to_degrees(gps_info[2])
|
||||
if lat is not None and gps_info[1] == 'S':
|
||||
lat = -lat
|
||||
|
||||
# Get longitude
|
||||
if 4 in gps_info and 3 in gps_info: # GPSLongitude and GPSLongitudeRef
|
||||
lon = convert_to_degrees(gps_info[4])
|
||||
if lon is not None and gps_info[3] == 'W':
|
||||
lon = -lon
|
||||
except (TypeError, KeyError):
|
||||
# Handle cases where gps_info doesn't support 'in' operator or key access
|
||||
return None, None
|
||||
|
||||
return lat, lon
|
||||
|
||||
def is_within_gps_box(image, gps_box):
|
||||
# Get the image gps coordinates from the image metadata
|
||||
latitude, longitude = get_gps_coordinates(image)
|
||||
|
||||
if latitude is None or longitude is None:
|
||||
print("No GPS data found in image")
|
||||
return False
|
||||
|
||||
print(f"Image GPS coordinates: {latitude}, {longitude}")
|
||||
lat_min, lon_min, lat_max, lon_max = gps_box
|
||||
return lat_min <= latitude <= lat_max and lon_min <= longitude <= lon_max
|
||||
|
||||
def main():
|
||||
args = parse_arguments()
|
||||
|
||||
if not os.path.exists(args.output_folder):
|
||||
os.makedirs(args.output_folder)
|
||||
|
||||
for filename in os.listdir(args.input_folder):
|
||||
if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
|
||||
image_path = os.path.join(args.input_folder, filename)
|
||||
# Load the image metadata
|
||||
image = Image.open(image_path)
|
||||
|
||||
if image is None:
|
||||
print(f"Failed to load image: {image_path}")
|
||||
continue
|
||||
|
||||
if is_within_gps_box(image, args.gps_box):
|
||||
if args.dry_run:
|
||||
print(f"Dry run: Image {filename} is within the GPS box")
|
||||
lat, lon = get_gps_coordinates(image)
|
||||
print(f"coordinates: {lat}, {lon}")
|
||||
continue
|
||||
output_path = os.path.join(args.output_folder, filename)
|
||||
shutil.copy2(image_path, output_path)
|
||||
print(f"Saved filtered image: {output_path}")
|
||||
else:
|
||||
print(f"Image {filename} is outside the bounding box")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['filter_gps_images.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='filter_gps_images',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
argparse
|
||||
Pillow
|
||||
piexif
|
||||
PyInstaller
|
||||
shutil
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.9 MiB |
Reference in new issue
Block a user