def process(self, args, outputs): tree = outputs['tree'] reconstruction = outputs['reconstruction'] if not os.path.exists(tree.odm_filterpoints): system.mkdir_p(tree.odm_filterpoints) # check if reconstruction was done before if not io.file_exists(tree.filtered_point_cloud) or self.rerun(): if args.fast_orthophoto: inputPointCloud = os.path.join(tree.opensfm, 'reconstruction.ply') elif args.use_opensfm_dense: inputPointCloud = tree.opensfm_model else: inputPointCloud = tree.mve_model point_cloud.filter(inputPointCloud, tree.filtered_point_cloud, standard_deviation=args.pc_filter, confidence=None, sample_radius=args.pc_sample, verbose=args.verbose) else: log.ODM_WARNING('Found a valid point cloud file in: %s' % tree.filtered_point_cloud) if args.optimize_disk_space: os.remove(inputPointCloud)
def process(self, inputs, outputs): # Benchmarking start_time = system.now_raw() log.ODM_INFO('Running ODM FilterPoints Cell') # get inputs tree = inputs.tree args = inputs.args reconstruction = inputs.reconstruction # check if we rerun cell or not rerun_cell = (args.rerun is not None and args.rerun == 'odm_filterpoints') or \ (args.rerun_all) or \ (args.rerun_from is not None and 'odm_filterpoints' in args.rerun_from) if not os.path.exists(tree.odm_filterpoints): system.mkdir_p(tree.odm_filterpoints) # check if reconstruction was done before if not io.file_exists(tree.filtered_point_cloud) or rerun_cell: if args.fast_orthophoto: inputPointCloud = os.path.join(tree.opensfm, 'reconstruction.ply') elif args.use_opensfm_dense: inputPointCloud = tree.opensfm_model else: inputPointCloud = tree.mve_model confidence = None if not args.use_opensfm_dense and not args.fast_orthophoto: confidence = args.mve_confidence point_cloud.filter(inputPointCloud, tree.filtered_point_cloud, standard_deviation=args.pc_filter, confidence=confidence, verbose=args.verbose) else: log.ODM_WARNING('Found a valid point cloud file in: %s' % tree.filtered_point_cloud) outputs.reconstruction = reconstruction if args.time: system.benchmark(start_time, tree.benchmarking, 'MVE') log.ODM_INFO('Running ODM FilterPoints Cell - Finished') return ecto.OK if args.end_with != 'odm_filterpoints' else ecto.QUIT
def filter_points(odm_filterpoints_folder, mve_model, filter_points_ply_file, max_concurrency): if not os.path.exists(odm_filterpoints_folder): system.mkdir_p(odm_filterpoints_folder) pc_filter = 2.5 pc_sample = 0 verbose = False # check if reconstruction was done before if not io.file_exists(filter_points_ply_file): inputPointCloud = mve_model point_cloud.filter(inputPointCloud, filter_points_ply_file, standard_deviation=pc_filter, sample_radius=pc_sample, verbose=verbose, max_concurrency=max_concurrency) else: log.ODM_WARNING('Found a valid point cloud file in: %s' % filter_points_ply_file)
def process(self, inputs, outputs): # Benchmarking start_time = system.now_raw() log.ODM_INFO('Running SMVS Cell') # get inputs tree = inputs.tree args = inputs.args reconstruction = inputs.reconstruction photos = reconstruction.photos if not photos: log.ODM_ERROR('Not enough photos in photos array to start SMVS') return ecto.QUIT # check if we rerun cell or not rerun_cell = (args.rerun is not None and args.rerun == 'smvs') or \ (args.rerun_all) or \ (args.rerun_from is not None and 'smvs' in args.rerun_from) # check if reconstruction was done before if not io.file_exists(tree.smvs_model) or rerun_cell: # cleanup if a rerun if io.dir_exists(tree.mve_path) and rerun_cell: shutil.rmtree(tree.mve_path) # make bundle directory if not io.file_exists(tree.mve_bundle): system.mkdir_p(tree.mve_path) system.mkdir_p(io.join_paths(tree.mve_path, 'bundle')) io.copy(tree.opensfm_image_list, tree.mve_image_list) io.copy(tree.opensfm_bundle, tree.mve_bundle) # mve makescene wants the output directory # to not exists before executing it (otherwise it # will prompt the user for confirmation) if io.dir_exists(tree.smvs): shutil.rmtree(tree.smvs) # run mve makescene if not io.dir_exists(tree.mve_views): system.run('%s %s %s' % (context.makescene_path, tree.mve_path, tree.smvs)) # config config = [ "-t%s" % self.params.threads, "-a%s" % self.params.alpha, "--max-pixels=%s" % int(self.params.max_pixels), "-o%s" % self.params.output_scale, "--debug-lvl=%s" % ('1' if self.params.verbose else '0'), "%s" % '-S' if self.params.shading else '', "%s" % '-g' if self.params.gamma_srgb and self.params.shading else '', "--force" if rerun_cell else '' ] # run smvs system.run('%s %s %s' % (context.smvs_path, ' '.join(config), tree.smvs)) # find and rename the output file for simplicity smvs_files = glob.glob(os.path.join(tree.smvs, 'smvs-*')) smvs_files.sort(key=os.path.getmtime) # sort by last modified date if len(smvs_files) > 0: old_file = smvs_files[-1] if not (io.rename_file(old_file, tree.smvs_model)): log.ODM_WARNING("File %s does not exist, cannot be renamed. " % old_file) # Filter point_cloud.filter(tree.smvs_model, standard_deviation=args.pc_filter, verbose=args.verbose) else: log.ODM_WARNING("Cannot find a valid point cloud (smvs-XX.ply) in %s. Check the console output for errors." % tree.smvs) else: log.ODM_WARNING('Found a valid SMVS reconstruction file in: %s' % tree.smvs_model) outputs.reconstruction = reconstruction if args.time: system.benchmark(start_time, tree.benchmarking, 'SMVS') log.ODM_INFO('Running ODM SMVS Cell - Finished') return ecto.OK if args.end_with != 'smvs' else ecto.QUIT
def process(self, inputs, outputs): # Benchmarking start_time = system.now_raw() log.ODM_INFO('Running ODM OpenSfM Cell') # get inputs tree = inputs.tree args = inputs.args reconstruction = inputs.reconstruction photos = reconstruction.photos if not photos: log.ODM_ERROR('Not enough photos in photos array to start OpenSfM') return ecto.QUIT # create working directories system.mkdir_p(tree.opensfm) # check if we rerun cell or not rerun_cell = (args.rerun is not None and args.rerun == 'opensfm') or \ (args.rerun_all) or \ (args.rerun_from is not None and 'opensfm' in args.rerun_from) if args.fast_orthophoto: output_file = io.join_paths(tree.opensfm, 'reconstruction.ply') elif args.use_opensfm_dense: output_file = tree.opensfm_model else: output_file = tree.opensfm_reconstruction # check if reconstruction was done before if not io.file_exists(output_file) or rerun_cell: # create file list list_path = io.join_paths(tree.opensfm, 'image_list.txt') has_alt = True with open(list_path, 'w') as fout: for photo in photos: if not photo.altitude: has_alt = False fout.write('%s\n' % io.join_paths(tree.dataset_raw, photo.filename)) # create config file for OpenSfM config = [ "use_exif_size: %s" % ('no' if not self.params.use_exif_size else 'yes'), "feature_process_size: %s" % self.params.feature_process_size, "feature_min_frames: %s" % self.params.feature_min_frames, "processes: %s" % self.params.processes, "matching_gps_neighbors: %s" % self.params.matching_gps_neighbors, "depthmap_method: %s" % args.opensfm_depthmap_method, "depthmap_resolution: %s" % args.depthmap_resolution, "depthmap_min_patch_sd: %s" % args.opensfm_depthmap_min_patch_sd, "depthmap_min_consistent_views: %s" % args.opensfm_depthmap_min_consistent_views, "optimize_camera_parameters: %s" % ('no' if self.params.fixed_camera_params else 'yes') ] if has_alt: log.ODM_DEBUG( "Altitude data detected, enabling it for GPS alignment") config.append("use_altitude_tag: yes") config.append("align_method: naive") else: config.append("align_method: orientation_prior") config.append("align_orientation_prior: vertical") if args.use_hybrid_bundle_adjustment: log.ODM_DEBUG("Enabling hybrid bundle adjustment") config.append( "bundle_interval: 100" ) # Bundle after adding 'bundle_interval' cameras config.append( "bundle_new_points_ratio: 1.2" ) # Bundle when (new points) / (bundled points) > bundle_new_points_ratio config.append( "local_bundle_radius: 1" ) # Max image graph distance for images to be included in local bundle adjustment if args.matcher_distance > 0: config.append("matching_gps_distance: %s" % self.params.matching_gps_distance) if tree.odm_georeferencing_gcp: config.append("bundle_use_gcp: yes") io.copy(tree.odm_georeferencing_gcp, tree.opensfm) # write config file log.ODM_DEBUG(config) config_filename = io.join_paths(tree.opensfm, 'config.yaml') with open(config_filename, 'w') as fout: fout.write("\n".join(config)) # run OpenSfM reconstruction matched_done_file = io.join_paths(tree.opensfm, 'matching_done.txt') if not io.file_exists(matched_done_file) or rerun_cell: system.run('PYTHONPATH=%s %s/bin/opensfm extract_metadata %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) system.run('PYTHONPATH=%s %s/bin/opensfm detect_features %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) system.run('PYTHONPATH=%s %s/bin/opensfm match_features %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) with open(matched_done_file, 'w') as fout: fout.write("Matching done!\n") else: log.ODM_WARNING( 'Found a feature matching done progress file in: %s' % matched_done_file) if not io.file_exists(tree.opensfm_tracks) or rerun_cell: system.run('PYTHONPATH=%s %s/bin/opensfm create_tracks %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) else: log.ODM_WARNING('Found a valid OpenSfM tracks file in: %s' % tree.opensfm_tracks) if not io.file_exists(tree.opensfm_reconstruction) or rerun_cell: system.run('PYTHONPATH=%s %s/bin/opensfm reconstruct %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) else: log.ODM_WARNING( 'Found a valid OpenSfM reconstruction file in: %s' % tree.opensfm_reconstruction) # Check that a reconstruction file has been created if not io.file_exists(tree.opensfm_reconstruction): log.ODM_ERROR( "The program could not process this dataset using the current settings. " "Check that the images have enough overlap, " "that there are enough recognizable features " "and that the images are in focus. " "You could also try to increase the --min-num-features parameter." "The program will now exit.") sys.exit(1) # Always export VisualSFM's reconstruction and undistort images # as we'll use these for texturing (after GSD estimation and resizing) if not args.ignore_gsd: image_scale = gsd.image_scale_factor( args.orthophoto_resolution, tree.opensfm_reconstruction) else: image_scale = 1.0 if not io.file_exists( tree.opensfm_reconstruction_nvm) or rerun_cell: system.run( 'PYTHONPATH=%s %s/bin/opensfm export_visualsfm --image_extension png --scale_focal %s %s' % (context.pyopencv_path, context.opensfm_path, image_scale, tree.opensfm)) else: log.ODM_WARNING( 'Found a valid OpenSfM NVM reconstruction file in: %s' % tree.opensfm_reconstruction_nvm) # These will be used for texturing system.run( 'PYTHONPATH=%s %s/bin/opensfm undistort --image_format png --image_scale %s %s' % (context.pyopencv_path, context.opensfm_path, image_scale, tree.opensfm)) # Skip dense reconstruction if necessary and export # sparse reconstruction instead if args.fast_orthophoto: system.run( 'PYTHONPATH=%s %s/bin/opensfm export_ply --no-cameras %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) # Filter point_cloud.filter(os.path.join(tree.opensfm, 'reconstruction.ply'), standard_deviation=args.pc_filter, verbose=args.verbose) elif args.use_opensfm_dense: # Undistort images at full scale in JPG # (TODO: we could compare the size of the PNGs if they are < than depthmap_resolution # and use those instead of re-exporting full resolution JPGs) system.run('PYTHONPATH=%s %s/bin/opensfm undistort %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) system.run( 'PYTHONPATH=%s %s/bin/opensfm compute_depthmaps %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) # Filter point_cloud.filter(tree.opensfm_model, standard_deviation=args.pc_filter, verbose=args.verbose) else: log.ODM_WARNING( 'Found a valid OpenSfM reconstruction file in: %s' % tree.opensfm_reconstruction) # check if reconstruction was exported to bundler before if not io.file_exists(tree.opensfm_bundle_list) or rerun_cell: # convert back to bundler's format system.run( 'PYTHONPATH=%s %s/bin/export_bundler %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) else: log.ODM_WARNING('Found a valid Bundler file in: %s' % tree.opensfm_reconstruction) if reconstruction.georef: system.run( 'PYTHONPATH=%s %s/bin/opensfm export_geocoords %s --transformation --proj \'%s\'' % (context.pyopencv_path, context.opensfm_path, tree.opensfm, reconstruction.georef.projection.srs)) outputs.reconstruction = reconstruction if args.time: system.benchmark(start_time, tree.benchmarking, 'OpenSfM') log.ODM_INFO('Running ODM OpenSfM Cell - Finished') return ecto.OK if args.end_with != 'opensfm' else ecto.QUIT
def process(self, args, outputs): tree = outputs['tree'] reconstruction = outputs['reconstruction'] if not os.path.exists(tree.odm_filterpoints): system.mkdir_p(tree.odm_filterpoints) inputPointCloud = "" # check if reconstruction was done before if not io.file_exists(tree.filtered_point_cloud) or self.rerun(): if args.fast_orthophoto: inputPointCloud = os.path.join(tree.opensfm, 'reconstruction.ply') else: inputPointCloud = tree.openmvs_model # Check if we need to compute boundary if args.auto_boundary: if reconstruction.is_georeferenced(): if not 'boundary' in outputs: avg_gsd = gsd.opensfm_reconstruction_average_gsd( tree.opensfm_reconstruction) if avg_gsd is not None: outputs['boundary'] = compute_boundary_from_shots( tree.opensfm_reconstruction, avg_gsd * 20, reconstruction.get_proj_offset() ) # 20 is arbitrary if outputs['boundary'] is None: log.ODM_WANING( "Cannot compute boundary from camera shots" ) else: log.ODM_WARNING( "Cannot compute boundary (GSD cannot be estimated)" ) else: log.ODM_WARNING( "--auto-boundary set but so is --boundary, will use --boundary" ) else: log.ODM_WARNING( "Not a georeferenced reconstruction, will ignore --auto-boundary" ) point_cloud.filter(inputPointCloud, tree.filtered_point_cloud, standard_deviation=args.pc_filter, sample_radius=args.pc_sample, boundary=boundary_offset( outputs.get('boundary'), reconstruction.get_proj_offset()), verbose=args.verbose, max_concurrency=args.max_concurrency) # Quick check info = point_cloud.ply_info(tree.filtered_point_cloud) if info["vertex_count"] == 0: extra_msg = '' if 'boundary' in outputs: extra_msg = '. Also, since you used a boundary setting, make sure that the boundary polygon you specified covers the reconstruction area correctly.' raise system.ExitException( "Uh oh! We ended up with an empty point cloud. This means that the reconstruction did not succeed. Have you followed best practices for data acquisition? See https://docs.opendronemap.org/flying/%s" % extra_msg) else: log.ODM_WARNING('Found a valid point cloud file in: %s' % tree.filtered_point_cloud) if args.optimize_disk_space and inputPointCloud: if os.path.isfile(inputPointCloud): os.remove(inputPointCloud)