diff --git a/.gitignore b/.gitignore index 853970b2..460fe703 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,6 @@ LAStools.zip pcl.tar.gz ceres-solver.tar.gz *.pyc -pcl.tar.gz opencv.zip -settings.yaml \ No newline at end of file +settings.yaml +docker.settings.yaml \ No newline at end of file diff --git a/default.settings.yaml b/default.settings.yaml deleted file mode 100644 index 3f3cc2ae..00000000 --- a/default.settings.yaml +++ /dev/null @@ -1,53 +0,0 @@ ---- -# A list of global configuration variables -# WARNING: DO NOT EDIT THIS FILE!!! -# Copy this file and rename to settings.yaml to override. You can also place the a settings.yaml -# file with the same format inside your project to more specific parameter needs. -# Note this only works for settings with default values. Some commands like --rerun -# or --force-ccd n will have to be set in the command line (if you need to) - -settings: - # These are really important to set up properly - project_path: '/home/dmb2/ODMProjects' - resize_to: 2400 - start_with: 'resize' - end_with: 'odm_orthophoto' - rerun_all: False - zip_results: False - verbose: False - time: False - opensfm: - processes: 4 # IMPORTANT: Set this to nproc-1 or lower for larger datasets - min_num_features: 4000 - matcher_threshold: 2.0 - matcher_ratio: 0.6 - matcher_neighbors: 8 - matcher_distance: 0 - pmvs: # The following settings only matter if use_pmvs is set to True - enabled: False - cmvs_max_images: 500 - level: 1 - cell_size: 2 - threshold: 0.7 - wsize: 7 - min_images: 3 - num_cores: 4 # Set this to nproc - mesh: - size: 100000 - octree_depth: 9 - samples: 1.0 - solver_divide: 9 - texturing: - data_term: 'gmi' - outlier_removal_type: 'gauss_clamping' - skip_visibility_test: False - skip_global_seam_leveling: False - skip_local_seam_leveling: False - skip_hole_filling: False - keep_unseen_faces: False - tone_mapping: 'none' - georeferencing: - gcp: !!null # YAML tag for None - use_exif: False # Set to True if you have a GCP file (it auto-detects) and want to use EXIF - orthophoto: - resolution: 20.0 # Pixels/meter diff --git a/docker.settings.yaml b/docker.settings.yaml index 1655ee4f..dd9dfb4d 100644 --- a/docker.settings.yaml +++ b/docker.settings.yaml @@ -1,53 +1,49 @@ --- # A list of global configuration variables -# WARNING: DO NOT EDIT THIS FILE!!! -# Copy this file and rename to settings.yaml to override. You can also place the a settings.yaml -# file with the same format inside your project to more specific parameter needs. +# Uncomment lines as needed to edit default settings. # Note this only works for settings with default values. Some commands like --rerun # or --force-ccd n will have to be set in the command line (if you need to) -settings: - # These are really important to set up properly - project_path: '/' # DO NOT CHANGE THIS - resize_to: 2400 - start_with: 'resize' - end_with: 'odm_orthophoto' - rerun_all: False - zip_results: False - verbose: False - time: False - opensfm: - processes: 4 # IMPORTANT: Set this to nproc-1 or lower for larger datasets - min_num_features: 4000 - matcher_threshold: 2.0 - matcher_ratio: 0.6 - matcher_neighbors: 8 - matcher_distance: 0 - pmvs: # The following settings only matter if use_pmvs is set to True - enabled: False - cmvs_max_images: 500 - level: 1 - cell_size: 2 - threshold: 0.7 - wsize: 7 - min_images: 3 - num_cores: 4 # Set this to nproc - mesh: - size: 100000 - octree_depth: 9 - samples: 1.0 - solver_divide: 9 - texturing: - data_term: 'gmi' - outlier_removal_type: 'gauss_clamping' - skip_visibility_test: False - skip_global_seam_leveling: False - skip_local_seam_leveling: False - skip_hole_filling: False - keep_unseen_faces: False - tone_mapping: 'none' - georeferencing: - gcp: !!null # YAML tag for None - use_exif: False # Set to True if you have a GCP file (it auto-detects) and want to use EXIF - orthophoto: - resolution: 20.0 # Pixels/meter +# This line is really important to set up properly +project_path: '/' #DO NOT CHANGE THIS OR DOCKER WILL NOT WORK. It should be '/' + +# The rest of the settings will default to the values set unless you uncomment and change them +#resize_to: 2400 +#start_with: 'resize' +#end_with: 'odm_orthophoto' +#rerun_all: False +#zip_results: False +#verbose: False +#time: False +#opensfm_processes: 4 # by default this is set to $(nproc) +#min_num_features: 4000 +#matcher_threshold: 2.0 +#matcher_ratio: 0.6 +#matcher_neighbors: 8 +#matcher_distance: 0 +#use_pmvs: False # The cmvs/pmvs settings only matter if 'Enabled' is set to True +#cmvs_maximages: 500 +#pmvs_level: 1 +#pmvs_csize: 2 +#pmvs_threshold: 0.7 +#pmvs_wsize: 7 +#pmvs_min_images: 3 +#pmvs_num_cores: 4 # by default this is set to $(nproc) +#mesh_size: 100000 +#mesh_octree_depth: 9 +#mesh_samples: 1.0 +#mesh_solver_divide: 9 +#texturing_data_term: 'gmi' +#texturing_outlier_removal_type: 'gauss_clamping' +#texturing_skip_visibility_test: False +#texturing_skip_global_seam_leveling: False +#texturing_skip_local_seam_leveling: False +#texturing_skip_hole_filling: False +#texturing_keep_unseen_faces: False +#texturing_tone_mapping: 'none' +#gcp: !!null # YAML tag for None +#use_exif: False # Set to True if you have a GCP file (it auto-detects) and want to use EXIF +#orthophoto_resolution: 20.0 # Pixels/meter +#orthophoto_target_srs: !!null # Currently does nothing +#orthophoto_no_tiled: False +#orthophoto_compression: DEFLATE # Options are [JPEG, LZW, PACKBITS, DEFLATE, LZMA, NONE] Don't change unless you know what you are doing diff --git a/opendm/config.py b/opendm/config.py index 05b98eee..19321582 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -1,21 +1,21 @@ import argparse from opendm import context from opendm import io -import yaml +from opendm import log +from yaml import safe_load +from appsettings import SettingsParser + +import sys # parse arguments processopts = ['resize', 'opensfm', 'slam', 'cmvs', 'pmvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto'] -# Load global settings file -with open(context.settings_path) as stream: - datamap = yaml.safe_load(stream) - defaultSettings = datamap['settings'] - with open(io.join_paths(context.root_path, 'VERSION')) as version_file: __version__ = version_file.read().strip() + def alphanumeric_string(string): import re if re.match('^[a-zA-Z0-9_-]+$', string) is None: @@ -29,8 +29,9 @@ class RerunFrom(argparse.Action): setattr(namespace, self.dest, processopts[processopts.index(values):]) -parser = argparse.ArgumentParser(description='OpenDroneMap') - +parser = SettingsParser(description='OpenDroneMap', + usage='%(prog)s [options] ', + yaml_file=open(context.settings_path)) def config(): parser.add_argument('--images', '-i', @@ -39,8 +40,7 @@ def config(): parser.add_argument('--project-path', metavar='', - help='Path to the project folder', - default=defaultSettings['project_path']) + help='Path to the project folder') parser.add_argument('name', metavar='', @@ -49,19 +49,19 @@ def config(): parser.add_argument('--resize-to', # currently doesn't support 'orig' metavar='', - default=defaultSettings['resize_to'], + default=2400, type=int, help='resizes images by the largest side') parser.add_argument('--start-with', '-s', metavar='', - default=defaultSettings['start_with'], + default='resize', choices=processopts, help=('Can be one of: ' + ' | '.join(processopts))) parser.add_argument('--end-with', '-e', metavar='', - default=defaultSettings['end_with'], + default='odm_orthophoto', choices=processopts, help=('Can be one of:' + ' | '.join(processopts))) @@ -74,7 +74,7 @@ def config(): rerun.add_argument('--rerun-all', action='store_true', - default=defaultSettings['rerun_all'], + default=False, help='force rerun of all tasks') rerun.add_argument('--rerun-from', @@ -104,7 +104,7 @@ def config(): parser.add_argument('--min-num-features', metavar='', - default=defaultSettings['opensfm']['min_num_features'], + default=4000, type=int, help=('Minimum number of features to extract per image. ' 'More features leads to better results but slower ' @@ -112,7 +112,7 @@ def config(): parser.add_argument('--matcher-threshold', metavar='', - default=defaultSettings['opensfm']['matcher_threshold'], + default=2.0, type=float, help=('Ignore matched keypoints if the two images share ' 'less than percent of keypoints. Default:' @@ -120,7 +120,7 @@ def config(): parser.add_argument('--matcher-ratio', metavar='', - default=defaultSettings['opensfm']['matcher_ratio'], + default=0.6, type=float, help=('Ratio of the distance to the next best matched ' 'keypoint. Default: %(default)s')) @@ -128,7 +128,7 @@ def config(): parser.add_argument('--matcher-neighbors', type=int, metavar='', - default=defaultSettings['opensfm']['matcher_neighbors'], + default=8, help='Number of nearest images to pre-match based on GPS ' 'exif data. Set to 0 to skip pre-matching. ' 'Neighbors works together with Distance parameter, ' @@ -139,34 +139,35 @@ def config(): parser.add_argument('--matcher-distance', metavar='', - default=defaultSettings['opensfm']['matcher_distance'], + default=0, type=int, help='Distance threshold in meters to find pre-matching ' - 'images based on GPS exif data. Set to 0 to skip ' + 'images based on GPS exif data. Set both ' + 'matcher-neighbors and this to 0 to skip ' 'pre-matching. Default: %(default)s') parser.add_argument('--opensfm-processes', metavar='', - default=defaultSettings['opensfm']['processes'], + default=context.num_cores, type=int, help=('The maximum number of processes to use in dense ' 'reconstruction. Default: %(default)s')) parser.add_argument('--use-pmvs', action='store_true', - default=defaultSettings['pmvs']['enabled'], + default=False, help='Use pmvs to compute point cloud alternatively') parser.add_argument('--cmvs-maxImages', metavar='', - default=defaultSettings['pmvs']['cmvs_max_images'], + default=500, type=int, help='The maximum number of images per cluster. ' 'Default: %(default)s') parser.add_argument('--pmvs-level', metavar='', - default=defaultSettings['pmvs']['level'], + default=1, type=int, help=('The level in the image pyramid that is used ' 'for the computation. see ' @@ -175,14 +176,14 @@ def config(): parser.add_argument('--pmvs-csize', metavar='', - default=defaultSettings['pmvs']['cell_size'], + default=2, type=int, help='Cell size controls the density of reconstructions' 'Default: %(default)s') parser.add_argument('--pmvs-threshold', metavar='', - default=defaultSettings['pmvs']['threshold'], + default=0.7, type=float, help=('A patch reconstruction is accepted as a success ' 'and kept if its associated photometric consistency ' @@ -190,7 +191,7 @@ def config(): parser.add_argument('--pmvs-wsize', metavar='', - default=defaultSettings['pmvs']['wsize'], + default=7, type=int, help='pmvs samples wsize x wsize pixel colors from ' 'each image to compute photometric consistency ' @@ -201,7 +202,7 @@ def config(): parser.add_argument('--pmvs-min-images', metavar='', - default=defaultSettings['pmvs']['min_images'], + default=3, type=int, help=('Each 3D point must be visible in at least ' 'minImageNum images for being reconstructed. 3 is ' @@ -209,21 +210,21 @@ def config(): parser.add_argument('--pmvs-num-cores', metavar='', - default=defaultSettings['pmvs']['num_cores'], + default=context.num_cores, type=int, help=('The maximum number of cores to use in dense ' 'reconstruction. Default: %(default)s')) parser.add_argument('--mesh-size', metavar='', - default=defaultSettings['mesh']['size'], + default=100000, type=int, help=('The maximum vertex count of the output mesh ' 'Default: %(default)s')) parser.add_argument('--mesh-octree-depth', metavar='', - default=defaultSettings['mesh']['octree_depth'], + default=9, type=int, help=('Oct-tree depth used in the mesh reconstruction, ' 'increase to get more vertices, recommended ' @@ -231,14 +232,14 @@ def config(): parser.add_argument('--mesh-samples', metavar='= 1.0>', - default=defaultSettings['mesh']['samples'], + default=1.0, type=float, help=('Number of points per octree node, recommended ' 'and default value: %(default)s')) parser.add_argument('--mesh-solver-divide', metavar='', - default=defaultSettings['mesh']['solver_divide'], + default=9, type=int, help=('Oct-tree depth at which the Laplacian equation ' 'is solved in the surface reconstruction step. ' @@ -248,57 +249,59 @@ def config(): parser.add_argument('--texturing-data-term', metavar='', - default=defaultSettings['texturing']['data_term'], + default='gmi', + choices=['gmi', 'area'], help=('Data term: [area, gmi]. Default: ' '%(default)s')) parser.add_argument('--texturing-outlier-removal-type', metavar='', - default=defaultSettings['texturing']['outlier_removal_type'], + default='gauss_clamping', + choices=['none', 'gauss_clamping', 'gauss_damping'], help=('Type of photometric outlier removal method: ' '[none, gauss_damping, gauss_clamping]. Default: ' '%(default)s')) parser.add_argument('--texturing-skip-visibility-test', action='store_true', - default=defaultSettings['texturing']['skip_visibility_test'], + default=False, help=('Skip geometric visibility test. Default: ' ' %(default)s')) parser.add_argument('--texturing-skip-global-seam-leveling', action='store_true', - default=defaultSettings['texturing']['skip_global_seam_leveling'], + default=False, help=('Skip global seam leveling. Useful for IR data.' 'Default: %(default)s')) parser.add_argument('--texturing-skip-local-seam-leveling', action='store_true', - default=defaultSettings['texturing']['skip_local_seam_leveling'], + default=False, help='Skip local seam blending. Default: %(default)s') parser.add_argument('--texturing-skip-hole-filling', action='store_true', - default=defaultSettings['texturing']['skip_hole_filling'], + default=False, help=('Skip filling of holes in the mesh. Default: ' ' %(default)s')) parser.add_argument('--texturing-keep-unseen-faces', action='store_true', - default=defaultSettings['texturing']['keep_unseen_faces'], + default=False, help=('Keep faces in the mesh that are not seen in any camera. ' 'Default: %(default)s')) parser.add_argument('--texturing-tone-mapping', metavar='', choices=['none', 'gamma'], - default=defaultSettings['texturing']['tone_mapping'], + default='none', help='Turn on gamma tone mapping or none for no tone ' 'mapping. Choices are \'gamma\' or \'none\'. ' 'Default: %(default)s ') parser.add_argument('--gcp', metavar='', - default=defaultSettings['georeferencing']['gcp'], + default=None, help=('path to the file containing the ground control ' 'points used for georeferencing. Default: ' '%(default)s. The file needs to ' @@ -307,13 +310,13 @@ def config(): parser.add_argument('--use-exif', action='store_true', - default=defaultSettings['georeferencing']['use_exif'], + default=False, help=('Use this tag if you have a gcp_list.txt but ' 'want to use the exif geotags instead')) parser.add_argument('--orthophoto-resolution', metavar=' 0.0>', - default=defaultSettings['orthophoto']['resolution'], + default=20.0, type=float, help=('Orthophoto ground resolution in pixels/meter' 'Default: %(default)s')) @@ -333,9 +336,9 @@ def config(): 'Default: %(default)s') parser.add_argument('--orthophoto-compression', - metavar='', + metavar='', type=str, - choices=['JPEG','LZW','PACKBITS','DEFLATE','LZMA','NONE'], + choices=['JPEG', 'LZW', 'PACKBITS', 'DEFLATE', 'LZMA', 'NONE'], default='DEFLATE', help='Set the compression to use. Note that this could ' 'break gdal_translate if you don\'t know what you ' @@ -343,18 +346,18 @@ def config(): parser.add_argument('--zip-results', action='store_true', - default=defaultSettings['zip_results'], + default=False, help='compress the results using gunzip') parser.add_argument('--verbose', '-v', action='store_true', - default=defaultSettings['verbose'], + default=False, help='Print additional messages to the console\n' 'Default: %(default)s') parser.add_argument('--time', action='store_true', - default=defaultSettings['time'], + default=False, help='Generates a benchmark file with runtime info\n' 'Default: %(default)s') @@ -363,4 +366,14 @@ def config(): version='OpenDroneMap {0}'.format(__version__), help='Displays version number and exits. ') - return parser.parse_args() + args = parser.parse_args() + + # check that the project path setting has been set properly + if not args.project_path: + log.ODM_ERROR('You need to set the project path in the ' + 'settings.yaml file before you can run ODM, ' + 'or use `--project-path `. Run `python ' + 'run.py --help` for more information. ') + sys.exit(1) + + return args diff --git a/opendm/context.py b/opendm/context.py index 5b7782cf..9e1b7e12 100644 --- a/opendm/context.py +++ b/opendm/context.py @@ -39,9 +39,6 @@ odm_modules_path = os.path.join(root_path, "build/bin") odm_modules_src_path = os.path.join(root_path, "modules") settings_path = os.path.join(root_path, 'settings.yaml') -if not io.file_exists(settings_path): - settings_path = os.path.join(root_path, 'default.settings.yaml') - # Define supported image extensions supported_extensions = {'.jpg','.jpeg','.png'} diff --git a/run.py b/run.py index 8941f1bb..bb2e2f86 100644 --- a/run.py +++ b/run.py @@ -4,41 +4,25 @@ from opendm import log from opendm import config from opendm import system from opendm import io -from opendm import context -import sys import ecto import os from scripts.odm_app import ODMApp - -def usage(): - log.ODM_ERROR('You must specify a project name:') - log.ODM_ERROR('USAGE: %s [project name]' % sys.argv[0]) - log.ODM_ERROR('OpenDroneMap app finished - %s' % system.now()) - sys.exit(0) - - if __name__ == '__main__': args = config.config() - #if args.version: - # log.ODM_INFO(__version__) - # sys.exit(0) - log.ODM_INFO('Initializing OpenDroneMap app - %s' % system.now()) - # Force to provide the images path - if args.project_path is None: - usage() + # Add project dir if doesn't exist args.project_path = io.join_paths(args.project_path, args.name) if not io.dir_exists(args.project_path): log.ODM_WARNING('Directory %s does not exist. Creating it now.' % args.name) system.mkdir_p(os.path.abspath(args.project_path)) - #If user asks to rerun everything, delete all of the existing progress directories. + # If user asks to rerun everything, delete all of the existing progress directories. # TODO: Move this somewhere it's not hard-coded if args.rerun_all: os.system("rm -rf " diff --git a/settings.yaml b/settings.yaml new file mode 100644 index 00000000..7768477d --- /dev/null +++ b/settings.yaml @@ -0,0 +1,49 @@ +--- +# A list of global configuration variables +# Uncomment lines as needed to edit default settings. +# Note this only works for settings with default values. Some commands like --rerun +# or --force-ccd n will have to be set in the command line (if you need to) + +# This line is really important to set up properly +project_path: '' # Example: '/home/user/ODMProjects + +# The rest of the settings will default to the values set unless you uncomment and change them +#resize_to: 2400 +#start_with: 'resize' +#end_with: 'odm_orthophoto' +#rerun_all: False +#zip_results: False +#verbose: False +#time: False +#opensfm_processes: 4 # by default this is set to $(nproc) +#min_num_features: 4000 +#matcher_threshold: 2.0 +#matcher_ratio: 0.6 +#matcher_neighbors: 8 +#matcher_distance: 0 +#use_pmvs: False # The cmvs/pmvs settings only matter if 'Enabled' is set to True +#cmvs_maximages: 500 +#pmvs_level: 1 +#pmvs_csize: 2 +#pmvs_threshold: 0.7 +#pmvs_wsize: 7 +#pmvs_min_images: 3 +#pmvs_num_cores: 4 # by default this is set to $(nproc) +#mesh_size: 100000 +#mesh_octree_depth: 9 +#mesh_samples: 1.0 +#mesh_solver_divide: 9 +#texturing_data_term: 'gmi' +#texturing_outlier_removal_type: 'gauss_clamping' +#texturing_skip_visibility_test: False +#texturing_skip_global_seam_leveling: False +#texturing_skip_local_seam_leveling: False +#texturing_skip_hole_filling: False +#texturing_keep_unseen_faces: False +#texturing_tone_mapping: 'none' +#gcp: !!null # YAML tag for None +#use_exif: False # Set to True if you have a GCP file (it auto-detects) and want to use EXIF +#orthophoto_resolution: 20.0 # Pixels/meter +#orthophoto_target_srs: !!null # Currently does nothing +#orthophoto_no_tiled: False +#orthophoto_compression: DEFLATE # Options are [JPEG, LZW, PACKBITS, DEFLATE, LZMA, NONE] Don't change unless you know what you are doing