-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathimage_processing.py
176 lines (139 loc) · 7.97 KB
/
image_processing.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import argparse
import tempfile
import os
import sys
from pathlib import Path
from drone_deploy_upload import upload_to_dd
from experiments.inverted import process_inverted
from experiments.color_bands import process_color_bands
from experiments.monochrome import process_monochrome
from experiments.no_pose import process_no_pose
from experiments.noise import process_noise
from experiments.perspective import process_perspective
from experiments.set_gps import process_set_gps
from experiments.tilt import process_tilt
from experiments.timestamp import process_timestamp
from util import collect_images
def validate_directory(path):
if not os.path.isdir(path):
print(f"Error: {path} is not a valid directory.")
sys.exit(1)
return path
def validate_percentage(value):
ivalue = int(value)
if ivalue < 0 or ivalue > 100:
raise argparse.ArgumentTypeError(f"{value} is an invalid percentage value. Must be between 0 and 100.")
return ivalue
def validate_degrees(value):
ivalue = int(value)
if ivalue < 0 or ivalue > 180:
raise argparse.ArgumentTypeError(f"{value} is an invalid angle value. Must be between 0 and 180.")
return ivalue
def validate_iso_date(date_string):
try:
from datetime import datetime
datetime.fromisoformat(date_string)
except ValueError:
raise argparse.ArgumentTypeError(f"{date_string} is not a valid ISO date.")
return date_string
def main():
parser = argparse.ArgumentParser(description="Experiment with photogrammetry images.")
# Input and output directories
parser.add_argument("-i", "--input", required=True, type=validate_directory,
help="Path to the input directory containing *.jpg, *.jpeg images.")
parser.add_argument("-o", "--output", type=str,
help="Path to the output directory where processed images will be saved.")
# Experiment selection
parser.add_argument("-e", "--experiment", required=True,
choices=["color-bands", "monochrome", "inverted", "set-gps",
"timestamp", "noise", "perspective", "tilt", "no-pose"],
help="Select the experiment to perform on the images.")
# Color bands options
parser.add_argument("-b", "--bands-to-keep", type=str, choices=["r", "g", "b", "rg", "rb", "gb", "rgb"], default="rgb",
help="Specify which color bands to keep for the color bands experiment.")
# Inverted options
parser.add_argument("-f", "--flip", type=validate_percentage, default=0,
help="Percentage of images to flip vertically.")
parser.add_argument("-m", "--mirror", type=validate_percentage, default=0,
help="Percentage of images to mirror horizontally.")
# Experiments with percentage options
parser.add_argument("-p", "--percentage", type=validate_percentage, default=0,
help="Percentage of images to change. Applies to monochrome, set-gps, perspective, tilt, and pose-removal.")
# GPS replacement option
parser.add_argument("--lat", type=float, help="Latitude for GPS data replacement.")
parser.add_argument("--lng", type=float, help="Longitude for GPS data replacement.")
parser.add_argument('--altitude', type=float, help="Altitude for GPS data replacement.")
parser.add_argument("--max-wiggle", type=float, default=0.0, help="Maximum distance in meters to randomly vary the GPS coordinates.")
# Timestamp options
parser.add_argument("--start-date", type=validate_iso_date,
help="Start date (ISO format) for random timestamp assignment.")
parser.add_argument("--end-date", type=validate_iso_date,
help="End date (ISO format) for random timestamp assignment.")
# Noise options
parser.add_argument("--noise-level", type=validate_percentage, default=0,
help="Percentage of images to add noise to.")
# Perspective options
parser.add_argument("-w", "--warp-by", type=validate_percentage, default=0,
help="Percentage of images to warp perspective for. 20% is significant.")
# Tilt options
parser.add_argument("-t", "--max-tilt", type=validate_degrees, default=0,
help="Max tilt angle in degrees for the tilt experiment.")
# DroneDeploy upload options
parser.add_argument("--dd-project-id", help="Upload images to the listed DroneDeploy Project ID, using the DD_API_KEY environment variable.")
parser.add_argument("--dd-plan-name", type=str, help="Name of the plan to create and upload images to.")
args = parser.parse_args()
# Additional validation
if args.experiment == "color-bands" and args.bands_to_keep not in ["r", "g", "b", "rg", "rb", "gb", "rgb"]:
print("Error: Invalid value for --bands-to-keep. Must be one of 'r', 'g', 'b', 'rg', 'rb', 'gb', or 'rgb'.")
sys.exit(1)
if args.experiment == "timestamp" and (not args.start_date or not args.end_date):
print("Error: --start-date and --end-date are required for the 'timestamp' experiment.")
sys.exit(1)
if args.flip + args.mirror > 100:
print("Error: The sum of --flip and --mirror percentages cannot exceed 100.")
sys.exit(1)
if args.experiment == "set-gps" and (args.lat is None or args.lng is None):
print(f"--lat and/or --lng not set, removing GPS metadata from {args.percentage}% of images.")
if args.experiment == "set-gps" and args.lat and args.lng:
print(f"Setting GPS coordinates to ({args.lat}, {args.lng}) for {args.percentage}% of images, with {args.max_wiggle} meters of variation max.")
if args.altitude:
print(f"Setting altitude to {args.altitude} meters.")
else:
print("Passing through altitude from original images.")
if args.dd_project_id and not os.getenv("DD_API_KEY"):
print("Error: DD_API_KEY environment variable not set.")
sys.exit(1)
if not args.output and not args.dd_project_id:
print("Error: --output or --dd-project-id must be specified.")
sys.exit(1)
input_images = collect_images(args.input)
# Create output directory if it doesn't exist. If uploading to DD, and no output directory is specified, make a temporary directory.
if args.dd_project_id and not args.output:
temp_dir = tempfile.TemporaryDirectory()
args.output = temp_dir.name
print(f"Output directory not specified. Using temporary directory {args.output}.")
Path(args.output).mkdir(parents=True, exist_ok=True)
default_name = args.experiment
# Execute the selected experiment
if args.experiment == "color-bands":
default_name = process_color_bands(input_images, args.output, args.bands_to_keep)
if args.experiment == "monochrome":
default_name = process_monochrome(input_images, args.output, args.percentage)
elif args.experiment == "inverted":
default_name = process_inverted(input_images, args.output, args.flip, args.mirror)
elif args.experiment == "set-gps":
default_name = process_set_gps(input_images, args.output, args.percentage, args.lat, args.lng, args.altitude, args.max_wiggle)
elif args.experiment == "no-pose":
default_name = process_no_pose(input_images, args.output, args.percentage)
elif args.experiment == "timestamp":
default_name = process_timestamp(input_images, args.output, args.start_date, args.end_date)
elif args.experiment == "noise":
default_name = process_noise(input_images, args.output, args.noise_level)
elif args.experiment == "perspective":
default_name = process_perspective(input_images, args.output, args.percentage, args.warp_by)
elif args.experiment == "tilt":
default_name = process_tilt(input_images, args.output, args.percentage, args.max_tilt)
if args.dd_project_id:
upload_to_dd(args.dd_project_id, args.output, args.dd_plan_name or default_name)
if __name__ == "__main__":
main()