1010#
1111
1212import os
13+ import subprocess
1314import logging
1415from argparse import ArgumentParser
1516import shutil
3738magick_command = '"{}"' .format (args .magick_executable ) if len (args .magick_executable ) > 0 else "magick"
3839use_gpu = 1 if not args .no_gpu else 0
3940
40- EXIT_FAIL = 1
41+ # configure logging
42+ logging .basicConfig (level = logging .INFO )
43+
44+ # execute a command after logging it and propagate failure correctly
45+ def exec (cmd ):
46+ logging .info (f"Executing: { cmd } " )
47+ try :
48+ subprocess .check_call (cmd , stdout = subprocess .DEVNULL , stderr = subprocess .STDOUT , shell = True )
49+ except subprocess .CalledProcessError as e :
50+ logging .error (f"Command failed with code { e .returncode } . Exiting." )
51+ exit (e .returncode )
4152
4253if not args .skip_matching :
4354 os .makedirs (args .source_path + "/distorted/sparse" , exist_ok = True )
4960 --ImageReader.single_camera 1 \
5061 --ImageReader.camera_model " + args .camera + " \
5162 --SiftExtraction.use_gpu " + str (use_gpu )
52- exit_code = os .system (feat_extracton_cmd )
53- if exit_code != 0 :
54- logging .error (f"Feature extraction failed with code { exit_code } . Exiting." )
55- exit (EXIT_FAIL )
63+ exec (feat_extracton_cmd )
5664
5765 ## Feature matching
5866 feat_matching_cmd = colmap_command + " exhaustive_matcher \
5967 --database_path " + args .source_path + "/distorted/database.db \
6068 --SiftMatching.use_gpu " + str (use_gpu )
61- exit_code = os .system (feat_matching_cmd )
62- if exit_code != 0 :
63- logging .error (f"Feature matching failed with code { exit_code } . Exiting." )
64- exit (EXIT_FAIL )
69+ exec (feat_matching_cmd )
6570
6671 ### Bundle adjustment
6772 # The default Mapper tolerance is unnecessarily large,
7176 --image_path " + args .source_path + "/input \
7277 --output_path " + args .source_path + "/distorted/sparse \
7378 --Mapper.ba_global_function_tolerance=0.000001" )
74- exit_code = os .system (mapper_cmd )
75- if exit_code != 0 :
76- logging .error (f"Mapper failed with code { exit_code } . Exiting." )
77- exit (EXIT_FAIL )
78-
79+ exec (mapper_cmd )
7980
8081# select the largest submodel
8182i = 0
107108 --input_path " + distorted_sparse_path + " \
108109 --output_path " + args .source_path + "\
109110 --output_type COLMAP" )
110-
111- exit_code = os .system (img_undist_cmd )
112- if exit_code != 0 :
113- logging .error (f"image_undistorter failed with code { exit_code } . Exiting." )
114- exit (EXIT_FAIL )
111+ exec (img_undist_cmd )
115112
116113
117- def remove_dir_if_exist (path ):
118- if Path (path ).exists ():
119- shutil .rmtree (path )
120-
114+ # Handle masks
121115
122116if args .masks_path is not None :
123- remove_dir_if_exist (args .source_path + "/alpha_distorted_sparse_txt/" )
124- Path (args .source_path + "/alpha_distorted_sparse_txt/" ).mkdir (exist_ok = True )
125- # We need to "hack" colmap to undistort segmentation maps modify paths
117+ # We need to modify the colmap database to reference the mask images
118+ # which are always in png format.
119+ mask_model_path = args .masks_path + "/model"
120+ Path (mask_model_path ).mkdir (exist_ok = True )
121+
126122 # First convert model to text format
127123 model_converter_cmd = (colmap_command + " model_converter \
128124 --input_path " + distorted_sparse_path + " \
129- --output_path " + args . source_path + "/alpha_distorted_sparse_txt/ \
125+ --output_path " + mask_model_path + " \
130126 --output_type TXT" )
131- exit_code = os .system (model_converter_cmd )
132- if exit_code != 0 :
133- logging .error (f"model_converter failed with code { exit_code } . Exiting." )
134- exit (EXIT_FAIL )
135-
136- # replace '.jpg' to '.png'
137- with open (args .source_path + "/alpha_distorted_sparse_txt/images.txt" , "r+" ) as f :
138- images_txt = f .read ()
139- images_txt = images_txt .replace ('.jpg' , '.png' )
140- f .seek (0 )
141- f .write (images_txt )
142- f .truncate ()
143-
144- # Undistort alpha masks
127+ exec (model_converter_cmd )
128+
129+ # read images.txt
130+ with open (mask_model_path + "/images.txt" , 'r' ) as file :
131+ lines = file .readlines ()
132+
133+ # replace image filenames with png extensions (and keep the list of renames for later)
134+ filenames = []
135+ l = 0
136+ for i in range (len (lines )):
137+ if lines [i ].startswith ("#" ):
138+ # skip comments
139+ continue
140+ if l % 2 == 0 :
141+ # handle every second line
142+ words = lines [i ].rstrip ().split (" " )
143+ filename = words [- 1 ].split ("." )
144+ filename [- 1 ] = "png"
145+ new_filename = "." .join (filename )
146+ filenames .append ([words [- 1 ], new_filename ])
147+ words [- 1 ] = new_filename
148+ lines [i ] = " " .join (words ) + "\n "
149+ l += 1
150+
151+ # write modified images.txt
152+ with open (mask_model_path + "/images.txt" , 'w' ) as file :
153+ file .writelines (lines )
154+
155+ # Undistort mask images
145156 seg_undist_cmd = (colmap_command + " image_undistorter \
146157 --image_path " + args .masks_path + " \
147- --input_path " + args . source_path + "/alpha_distorted_sparse_txt/ \
148- --output_path " + args .source_path + "/alpha_undistorted_sparse \
158+ --input_path " + mask_model_path + " \
159+ --output_path " + args .masks_path + "/undistorted \
149160 --output_type COLMAP" )
150- exit_code = os .system (seg_undist_cmd )
151- if exit_code != 0 :
152- logging .error (f"image_undistorter for segs failed with code { exit_code } . Exiting." )
153- exit (EXIT_FAIL )
154-
155- # switch images
156- remove_dir_if_exist (f'{ args .source_path } /alpha_undistorted_sparse/alphas' )
157- Path (f'{ args .source_path } /alpha_undistorted_sparse/images' ).replace (f'{ args .source_path } /alpha_undistorted_sparse/alphas' )
158- remove_dir_if_exist (f'{ args .source_path } /images_src/' )
159- Path (f'{ args .source_path } /images/' ).replace (f'{ args .source_path } /images_src/' )
160-
161- # concat undistorted images with undistorted alpha masks - TODO: make parallel
162- remove_dir_if_exist (f'{ args .source_path } /images/' )
163- Path (f'{ args .source_path } /images/' ).mkdir ()
164-
165- def concat_alpha (seg_path ):
166- seg = Image .open (seg_path ).convert ('L' )
167- img = Image .open (f'{ args .source_path } /images_src/{ Path (seg_path ).stem } .jpg' )
168- img .putalpha (seg )
169- img .save (f'{ args .source_path } /images/{ Path (seg_path ).stem } .png' )
170-
171- all_masks_paths = glob (args .source_path + "/alpha_undistorted_sparse/alphas/*.png" )
172- with mp .Pool () as pool :
173- list (tqdm (pool .imap_unordered (concat_alpha , all_masks_paths ), total = len (all_masks_paths )))
174-
175- # switch models
176- remove_dir_if_exist (f'{ args .source_path } /sparse_src/' )
177- Path (f'{ args .source_path } /sparse' ).replace (f'{ args .source_path } /sparse_src/' )
178- Path (f'{ args .source_path } /alpha_undistorted_sparse/sparse' ).replace (f'{ args .source_path } /sparse/' )
179-
180- if args .generate_text_model :
181- ### Convert model to text format so we can read cameras
182- convert_cmd = (colmap_command + " model_converter \
183- --input_path " + args .source_path + "/sparse" + " \
184- --output_path " + args .source_path + "/sparse" + " \
185- --output_type TXT" )
186- exit_code = os .system (convert_cmd )
187- if exit_code != 0 :
188- logging .error (f"Convert failed with code { exit_code } . Exiting." )
189- exit (exit_code )
190-
191- # move all files from sparse into sparse/0, as train.py expects it
192- files = os .listdir (args .source_path + "/sparse" )
193- os .makedirs (args .source_path + "/sparse/0" , exist_ok = True )
194- # Copy each file from the source directory to the destination directory
195- for file in files :
196- if file == "0" :
197- continue
198- source_file = os .path .join (args .source_path , "sparse" , file )
199- destination_file = os .path .join (args .source_path , "sparse" , "0" , file )
200- shutil .move (source_file , destination_file )
201-
202- if (args .resize ):
161+ exec (seg_undist_cmd )
162+
163+ # combine undistorted color and mask images
164+ def combine (color_path , alpha_path , output_path ):
165+ alpha = Image .open (alpha_path ).convert ('L' )
166+ clr = Image .open (color_path )
167+ clr .putalpha (alpha )
168+ clr .save (output_path )
169+
170+ for i in range (len (filenames )):
171+ color_image = args .source_path + "/images/" + filenames [i ][0 ]
172+ mask_image = args .masks_path + "/undistorted/images/" + filenames [i ][1 ]
173+ output_image = args .source_path + "/images/" + filenames [i ][1 ]
174+ combine (color_image , mask_image , output_image )
175+
176+ # copy the modified database to final location for use in training
177+ target_path = args .source_path + "/sparse/0"
178+ Path (target_path ).mkdir (exist_ok = True )
179+
180+ source_path = args .masks_path + "/undistorted/sparse"
181+ files = os .listdir (source_path )
182+ for file in files :
183+ source_file = os .path .join (source_path , file )
184+ destination_file = os .path .join (target_path , file )
185+ shutil .move (source_file , destination_file )
186+ else :
187+ # move all files from sparse into sparse/0, as train.py expects it
188+ files = os .listdir (args .source_path + "/sparse" )
189+ os .makedirs (args .source_path + "/sparse/0" , exist_ok = True )
190+ # Copy each file from the source directory to the destination directory
191+ for file in files :
192+ if file == "0" :
193+ continue
194+ source_file = os .path .join (args .source_path , "sparse" , file )
195+ destination_file = os .path .join (args .source_path , "sparse" , "0" , file )
196+ shutil .move (source_file , destination_file )
197+
198+ if (args .resize ):
203199 print ("Copying and resizing..." )
204200
205201 # Resize images.
@@ -211,26 +207,17 @@ def concat_alpha(seg_path):
211207 # Copy each file from the source directory to the destination directory
212208 for file in files :
213209 source_file = os .path .join (args .source_path , "images" , file )
214-
215- destination_file = os .path .join (args .source_path , "images_2" , file )
216- shutil .copy2 (source_file , destination_file )
217- exit_code = os .system ("mogrify -resize 50% " + destination_file )
218- if exit_code != 0 :
219- logging .error (f"50% resize failed with code { exit_code } . Exiting." )
220- exit (EXIT_FAIL )
221-
222- destination_file = os .path .join (args .source_path , "images_4" , file )
223- shutil .copy2 (source_file , destination_file )
224- exit_code = os .system ("mogrify -resize 25% " + destination_file )
225- if exit_code != 0 :
226- logging .error (f"25% resize failed with code { exit_code } . Exiting." )
227- exit (EXIT_FAIL )
228-
229- destination_file = os .path .join (args .source_path , "images_8" , file )
230- shutil .copy2 (source_file , destination_file )
231- exit_code = os .system ("mogrify -resize 12.5% " + destination_file )
232- if exit_code != 0 :
233- logging .error (f"12.5% resize failed with code { exit_code } . Exiting." )
234- exit (EXIT_FAIL )
210+ output_file2 = os .path .join (args .source_path , "images_2" , file )
211+ output_file4 = os .path .join (args .source_path , "images_4" , file )
212+ output_file8 = os .path .join (args .source_path , "images_8" , file )
213+
214+ # generate the resized images in a single call
215+ generate_thumbnails_cmd = ("convert "
216+ # resize input file, uses less memory
217+ f"{ source_file } [50%]"
218+ f" -write mpr:thumb -write { output_file2 } +delete"
219+ f" mpr:thumb -resize 50% -write mpr:thumb -write { output_file4 } +delete"
220+ f" mpr:thumb -resize 50% { output_file8 } " )
221+ exec (generate_thumbnails_cmd )
235222
236223print ("Done." )
0 commit comments