From f9d2d416e905365dca03fd3fdd995596363b34c2 Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Thu, 9 Feb 2017 12:55:45 -0500 Subject: [PATCH 01/13] Add settings.yaml file for default options Former-commit-id: c193a296569c6729b73312ec4f4a40c9625daff1 --- opendm/config.py | 14 ++++++++++---- opendm/context.py | 6 ++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/opendm/config.py b/opendm/config.py index 3a82169f..b1c1bb4d 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -1,11 +1,16 @@ import argparse -import context +from opendm import context # 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'] + class RerunFrom(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): @@ -22,7 +27,8 @@ def config(): parser.add_argument('--project-path', metavar='', - help='Path to the project to process') + help='Path to the project to process', + default=defaultSettings[0]['project_path']) parser.add_argument('--resize-to', # currently doesn't support 'orig' metavar='', @@ -124,14 +130,14 @@ def config(): parser.add_argument('--opensfm-processes', metavar='', - default=context.num_cores, + default=defaultSettings[2]['opensfm_processes'], type=int, help=('The maximum number of processes to use in dense ' 'reconstruction. Default: %(default)s')) parser.add_argument('--use-opensfm-pointcloud', action='store_true', - default=False, + default=defaultSettings[1]['opensfm_pointcloud'], help='Use OpenSfM to compute the point cloud instead ' 'of PMVS') diff --git a/opendm/context.py b/opendm/context.py index 2ae84e71..5b7782cf 100644 --- a/opendm/context.py +++ b/opendm/context.py @@ -1,5 +1,6 @@ import os import sys +from opendm import io import multiprocessing # Define some needed locations @@ -37,6 +38,11 @@ pdal_path = os.path.join(superbuild_path, 'build/pdal/bin') 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'} From 0622690688510df50110c46e05ec2c03fa249882 Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Thu, 9 Feb 2017 18:57:24 -0500 Subject: [PATCH 02/13] Add string validation for projectname Former-commit-id: ad443beb5b65171ee5dbc780d6f70773b99220b3 --- opendm/config.py | 11 +++++++++++ run.py | 11 ++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/opendm/config.py b/opendm/config.py index b1c1bb4d..3edb5b95 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -11,6 +11,12 @@ with open(context.settings_path) as stream: datamap = yaml.safe_load(stream) defaultSettings = datamap['settings'] +def alphanumeric_string(string): + import re + if re.match('^[a-zA-Z0-9]+$', str) is None: + msg = '{0} is not a valid name. Must use alphanumeric characters.'.format(string) + raise argparse.ArgumentTypeError(msg) + return string class RerunFrom(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): @@ -30,6 +36,11 @@ def config(): help='Path to the project to process', default=defaultSettings[0]['project_path']) + parser.add_argument('name', + metavar='', + type=alphanumeric_string, + help='Name of Project (i.e subdirectory of projects folder)') + parser.add_argument('--resize-to', # currently doesn't support 'orig' metavar='', default=2400, diff --git a/run.py b/run.py index d118d5da..af9c1d40 100644 --- a/run.py +++ b/run.py @@ -13,7 +13,8 @@ from scripts.odm_app import ODMApp def usage(): - log.ODM_ERROR('USAGE: %s --project-path [project_path]' % sys.argv[0]) + 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) @@ -24,11 +25,15 @@ if __name__ == '__main__': args = config.config() + # Check that args.name is a valid string + + proj_dir = io.join_paths(args.project_path, args.name) + # Force to provide the images path if args.project_path is None: usage() - elif not io.dir_exists(args.project_path): - log.ODM_WARNING('Directory %s does not exist. Creating it now.' % args.project_path) + if not io.dir_exists(args.name): + 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. From 3b393cb2ebd7fbd8d790d6279386dcce5883c516 Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Wed, 22 Feb 2017 10:32:02 -0500 Subject: [PATCH 03/13] Add default settings file Former-commit-id: 000abefcf440e61074f62513d0c834249fcd9128 --- default.settings.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 default.settings.yaml diff --git a/default.settings.yaml b/default.settings.yaml new file mode 100644 index 00000000..d8cb0277 --- /dev/null +++ b/default.settings.yaml @@ -0,0 +1,6 @@ +--- +# A list of global configuration variables +settings: + - project_path: '/code/' + - opensfm_pointcloud: True + - opensfm_processes: 4 \ No newline at end of file From aafb83e02e135861f60c4b14b599847774cc78c5 Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Tue, 7 Mar 2017 14:58:40 -0500 Subject: [PATCH 04/13] Make functional global configuration Former-commit-id: d22f05b3cc2069edf99b8f9554523e01b6d174de --- .gitignore | 1 + README.md | 32 +++++++++++++++++++++----------- default.settings.yaml | 6 ++++-- opendm/config.py | 13 ++++++++----- run.py | 6 ++---- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index a8f6a2e9..853970b2 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ ceres-solver.tar.gz *.pyc pcl.tar.gz opencv.zip +settings.yaml \ No newline at end of file diff --git a/README.md b/README.md index 2eef971d..faa9f520 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,9 @@ Current version: 0.2 (this software is in beta) 1. Extract and enter the OpenDroneMap directory 2. Run `bash configure.sh` -3. Download and extract a sample dataset [here](https://github.com/OpenDroneMap/odm_data_aukerman/archive/master.zip) -4. Run `./run.sh --project-path `, replacing `` with the dataset path. +4. Copy the default settings file and edit it: `cp default.settings.yaml settings.yaml`. Set the `project-path` value to an empty directory (you will place sub-directories containing individual projects inside). You can add many options to this file, [see here](https://github.com/OpenDroneMap/OpenDroneMap/wiki/Run-Time-Parameters) +3. Download a sample dataset from [here](https://github.com/OpenDroneMap/odm_data_aukerman/archive/master.zip) (about 550MB) and extract it as a subdirectory in your project directory. +4. Run `./run.sh odm_data_aukerman` 5. Enter dataset directory to view results: - orthophoto: odm_orthophoto/odm_orthophoto.tif - textured mesh model: odm_texturing/odm_textured_model_geo.obj @@ -65,13 +66,24 @@ Note that using `run.sh` sets these temporarily in the shell. ### Run OpenDroneMap -First you need a set of images, taken from a drone or otherwise. Example data can be cloned from https://github.com/OpenDroneMap/odm_data +First you need a set of images, taken from a drone or otherwise. Example data can be obtained from https://github.com/OpenDroneMap/odm_data + +Next, you need to copy over the settings file `default.settings.yaml` and edit it. The only setting you must edit is the `project-path` key. Set this to an empty directory within projects will be saved. There are many options for tuning your project. See the [wiki](https://github.com/OpenDroneMap/OpenDroneMap/wiki/Run-Time-Parameters) or run `python run.py -h` + Then run: - python run.py --project-path /path/to/project -i /path/to/images + python run.py -i /path/to/images project-name -The images will be copied over to the project path so you only need to specify the `-i /path/` once. There are many options for tuning your project. See the [wiki](https://github.com/OpenDroneMap/OpenDroneMap/wiki/Run-Time-Parameters) or run `python run.py -h` +The images will be copied over to the project path so you only need to specify the `-i /path/` once. You can also override any variable from settings.yaml here using the command line arguments. If you want to rerun the whole thing, run + + python run.py --rerun-all project-name + +or + + python run.py --rerun-from odm_meshing project-name + +The options for rerunning are: 'resize', 'opensfm', 'slam', 'cmvs', 'pmvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto' ### View Results @@ -85,10 +97,8 @@ When the process finishes, the results will be organized as follows: |-- ... |-- opensfm/ |-- see mapillary/opensfm repository for more info - |-- pmvs/ - |-- recon0/ - |-- models/ - |-- option-0000.ply # Dense point cloud (not georeferenced) + |-- depthmaps/ + |-- merged.ply # Dense Point cloud (not georeferenced) |-- odm_meshing/ |-- odm_mesh.ply # A 3D mesh |-- odm_meshing_log.txt # Output of the meshing task. May point out errors. @@ -102,13 +112,13 @@ When the process finishes, the results will be organized as follows: |-- odm_georeferenced_model.csv # XYZ format point cloud |-- odm_georeferencing_log.txt # Georeferencing log |-- odm_georeferencing_utm_log.txt # Log for the extract_utm portion - |-- odm_georeferencing/ + |-- odm_orthophoto/ |-- odm_orthophoto.png # Orthophoto image (no coordinates) |-- odm_orthophoto.tif # Orthophoto GeoTiff |-- odm_orthophoto_log.txt # Log file |-- gdal_translate_log.txt # Log for georeferencing the png file -Any file ending in .obj or .ply can be opened and viewed in [MeshLab](http://meshlab.sourceforge.net/) or similar software. That includes `pmvs/recon0/models/option-000.ply`, `odm_meshing/odm_mesh.ply`, `odm_texturing/odm_textured_model[_geo].obj`, or `odm_georeferencing/odm_georeferenced_model.ply`. Below is an example textured mesh: +Any file ending in .obj or .ply can be opened and viewed in [MeshLab](http://meshlab.sourceforge.net/) or similar software. That includes `opensfm/depthmaps/merged.ply`, `odm_meshing/odm_mesh.ply`, `odm_texturing/odm_textured_model[_geo].obj`, or `odm_georeferencing/odm_georeferenced_model.ply`. Below is an example textured mesh: ![](https://raw.githubusercontent.com/alexhagiopol/OpenDroneMap/feature-better-docker/toledo_dataset_example_mesh.jpg) diff --git a/default.settings.yaml b/default.settings.yaml index d8cb0277..7e94028e 100644 --- a/default.settings.yaml +++ b/default.settings.yaml @@ -1,6 +1,8 @@ --- # A list of global configuration variables +# 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. settings: - - project_path: '/code/' + - project_path: '/home/dmb2/ODMProjects' - opensfm_pointcloud: True - - opensfm_processes: 4 \ No newline at end of file + - opensfm_processes: 4 # Set this to nproc-2 for large datasets \ No newline at end of file diff --git a/opendm/config.py b/opendm/config.py index 3edb5b95..7516fe6b 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -1,5 +1,6 @@ import argparse from opendm import context +import yaml # parse arguments processopts = ['resize', 'opensfm', 'slam', 'cmvs', 'pmvs', @@ -11,13 +12,15 @@ with open(context.settings_path) as stream: datamap = yaml.safe_load(stream) defaultSettings = datamap['settings'] + def alphanumeric_string(string): import re - if re.match('^[a-zA-Z0-9]+$', str) is None: + if re.match('^[a-zA-Z0-9]+$', string) is None: msg = '{0} is not a valid name. Must use alphanumeric characters.'.format(string) raise argparse.ArgumentTypeError(msg) return string + class RerunFrom(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, processopts[processopts.index(values):]) @@ -28,16 +31,16 @@ parser = argparse.ArgumentParser(description='OpenDroneMap') def config(): parser.add_argument('--images', '-i', - metavar='', + metavar='', help='Path to input images'), parser.add_argument('--project-path', - metavar='', - help='Path to the project to process', + metavar='', + help='Path to the project folder', default=defaultSettings[0]['project_path']) parser.add_argument('name', - metavar='', + metavar='', type=alphanumeric_string, help='Name of Project (i.e subdirectory of projects folder)') diff --git a/run.py b/run.py index af9c1d40..368de568 100644 --- a/run.py +++ b/run.py @@ -25,14 +25,12 @@ if __name__ == '__main__': args = config.config() - # Check that args.name is a valid string - - proj_dir = io.join_paths(args.project_path, args.name) # Force to provide the images path if args.project_path is None: usage() - if not io.dir_exists(args.name): + 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)) From 6138f6c95f039384493501712ee186ea98536f69 Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Tue, 7 Mar 2017 14:59:29 -0500 Subject: [PATCH 05/13] Add settings file for docker Former-commit-id: 4a88e6f090b9e8cb1b94ec75f297fc52d09edb0d --- Dockerfile | 3 ++- docker.settings.yaml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 docker.settings.yaml diff --git a/Dockerfile b/Dockerfile index 23fa6f8b..a440fd24 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM packages # Prepare directories -RUN mkdir /code +RUN mkdir /code && mkdir /project WORKDIR /code # Copy repository files @@ -19,6 +19,7 @@ COPY run.py /code/run.py COPY /scripts/ /code/scripts/ COPY /SuperBuild/cmake/ /code/SuperBuild/cmake/ COPY /SuperBuild/CMakeLists.txt /code/SuperBuild/CMakeLists.txt +COPY docker.settings.yaml /code/settings.yaml COPY /tests/ /code/tests/ # Update submodules diff --git a/docker.settings.yaml b/docker.settings.yaml new file mode 100644 index 00000000..14d1ec36 --- /dev/null +++ b/docker.settings.yaml @@ -0,0 +1,7 @@ +--- +# A list of global configuration variables +# 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. +settings: + - project_path: '/project' + - opensfm_processes: 4 # Set this to nproc-2 for large datasets \ No newline at end of file From f685096d3b366013df9d4c758d66961102f38dfe Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Wed, 8 Mar 2017 15:19:00 -0500 Subject: [PATCH 06/13] Simplify format for settings.yaml Former-commit-id: 7f33fe9b255d920b156e6f147b147214d26c4e8b --- default.settings.yaml | 6 +++--- docker.settings.yaml | 5 +++-- opendm/config.py | 11 +++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/default.settings.yaml b/default.settings.yaml index 7e94028e..2751b6cf 100644 --- a/default.settings.yaml +++ b/default.settings.yaml @@ -3,6 +3,6 @@ # 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. settings: - - project_path: '/home/dmb2/ODMProjects' - - opensfm_pointcloud: True - - opensfm_processes: 4 # Set this to nproc-2 for large datasets \ No newline at end of file + project_path: '/home/dmb2/ODMProjects' + use_pmvs: False + opensfm_processes: 4 # Set this to nproc-2 for large datasets \ No newline at end of file diff --git a/docker.settings.yaml b/docker.settings.yaml index 14d1ec36..42ea2454 100644 --- a/docker.settings.yaml +++ b/docker.settings.yaml @@ -3,5 +3,6 @@ # 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. settings: - - project_path: '/project' - - opensfm_processes: 4 # Set this to nproc-2 for large datasets \ No newline at end of file + project_path: '/' + use_pmvs: False + opensfm_processes: 4 # Set this to nproc-2 for large datasets \ No newline at end of file diff --git a/opendm/config.py b/opendm/config.py index 64c26db3..5b392568 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -15,7 +15,7 @@ with open(context.settings_path) as stream: def alphanumeric_string(string): import re - if re.match('^[a-zA-Z0-9]+$', string) is None: + if re.match('^[a-zA-Z0-9_-]+$', string) is None: msg = '{0} is not a valid name. Must use alphanumeric characters.'.format(string) raise argparse.ArgumentTypeError(msg) return string @@ -37,7 +37,7 @@ def config(): parser.add_argument('--project-path', metavar='', help='Path to the project folder', - default=defaultSettings[0]['project_path']) + default=defaultSettings['project_path']) parser.add_argument('name', metavar='', @@ -144,16 +144,15 @@ def config(): parser.add_argument('--opensfm-processes', metavar='', - default=defaultSettings[2]['opensfm_processes'], + default=defaultSettings['opensfm_processes'], 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[1]['opensfm_pointcloud'], - help='Use OpenSfM to compute the point cloud instead ' - 'of PMVS') + default=defaultSettings['use_pmvs'], + help='Use pmvs to compute point cloud alternatively') parser.add_argument('--cmvs-maxImages', metavar='', From 951a733afb7fa2e5d323adce9a18151ce6f04b8c Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Wed, 8 Mar 2017 15:23:19 -0500 Subject: [PATCH 07/13] Update docker and README Former-commit-id: e37e10405b330d9a0085b76129e2b69dcda0de27 --- Dockerfile | 4 ++-- README.md | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index a440fd24..11dae284 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM packages # Prepare directories -RUN mkdir /code && mkdir /project +RUN mkdir /code WORKDIR /code # Copy repository files @@ -30,4 +30,4 @@ RUN cd SuperBuild && mkdir build && cd build && cmake .. && make -j$(nproc) \ && cd ../.. && mkdir build && cd build && cmake .. && make -j$(nproc) # Entry point -ENTRYPOINT ["python", "/code/run.py", "--project-path", "/code/"] +ENTRYPOINT ["python", "/code/run.py", "code"] diff --git a/README.md b/README.md index abd1873d..023cd244 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,6 @@ instructions through "Create a Docker group". Once Docker is installed, the fast If you want to build your own Docker image from sources, type: docker build -t packages -f packages.Dockerfile . - docker build -t my_odm_image . docker run -it --rm -v $(pwd)/images:/code/images -v $(pwd)/odm_orthophoto:/code/odm_orthophoto -v $(pwd)/odm_texturing:/code/odm_texturing my_odm_image @@ -151,7 +150,7 @@ If you want to view other results outside the Docker image simply add which dire established above. For example, if you're interested in the dense cloud results generated by PMVS and in the orthophoto, simply use the following `docker run` command after building the image: - docker run -it --rm -v $(pwd)/images:/code/images -v $(pwd)/pmvs:/code/pmvs -v $(pwd)/odm_orthophoto:/code/odm_orthophoto my_odm_image + docker run -it --rm -v $(pwd)/images:/code/images -v $(pwd)/odm_georeferencing:/code/odm_georeferencing -v $(pwd)/odm_orthophoto:/code/odm_orthophoto my_odm_image To pass in custom parameters to the run.py script, simply pass it as arguments to the `docker run` command. For example: From 99ff082deb1964b159659ae39bc77b6c7ddabf17 Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Fri, 10 Mar 2017 11:43:26 -0500 Subject: [PATCH 08/13] Populate settings.yaml file with all params Former-commit-id: bf2069a16909b9f7e86b2cfd4a5e11551f7c46c8 --- README.md | 5 +++ default.settings.yaml | 49 ++++++++++++++++++++++++++-- docker.settings.yaml | 51 +++++++++++++++++++++++++++-- opendm/config.py | 76 ++++++++++++++++++++++--------------------- 4 files changed, 139 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 023cd244..6ee2e101 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,11 @@ To pass in custom parameters to the run.py script, simply pass it as arguments t docker run -it --rm -v $(pwd)/images:/code/images v $(pwd)/odm_orthophoto:/code/odm_orthophoto -v $(pwd)/odm_texturing:/code/odm_texturing opendronemap/opendronemap --resize-to 1800 --force-ccd 6.16 +If you want to pass in custom parameters using the settings.yaml file, you can pass it as a -v volume binding: + + docker run -it --rm -v $(pwd)/images:/code/images v $(pwd)/odm_orthophoto:/code/odm_orthophoto -v $(pwd)/odm_texturing:/code/odm_texturing -v $(pwd)/settings.yaml:/code/settings.yaml opendronemap/opendronemap + + ## User Interface A web interface and API to OpenDroneMap is currently under active development in the [WebODM](https://github.com/OpenDroneMap/WebODM) repository. diff --git a/default.settings.yaml b/default.settings.yaml index 2751b6cf..3f3cc2ae 100644 --- a/default.settings.yaml +++ b/default.settings.yaml @@ -1,8 +1,53 @@ --- # 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' - use_pmvs: False - opensfm_processes: 4 # Set this to nproc-2 for large datasets \ No newline at end of file + 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 42ea2454..1655ee4f 100644 --- a/docker.settings.yaml +++ b/docker.settings.yaml @@ -1,8 +1,53 @@ --- # 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: - project_path: '/' - use_pmvs: False - opensfm_processes: 4 # Set this to nproc-2 for large datasets \ No newline at end of file + # 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 diff --git a/opendm/config.py b/opendm/config.py index 5b392568..c205b881 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -46,19 +46,19 @@ def config(): parser.add_argument('--resize-to', # currently doesn't support 'orig' metavar='', - default=2400, + default=defaultSettings['resize_to'], type=int, help='resizes images by the largest side') parser.add_argument('--start-with', '-s', metavar='', - default='resize', + default=defaultSettings['start_with'], choices=processopts, help=('Can be one of: ' + ' | '.join(processopts))) parser.add_argument('--end-with', '-e', metavar='', - default='odm_orthophoto', + default=defaultSettings['end_with'], choices=processopts, help=('Can be one of:' + ' | '.join(processopts))) @@ -71,7 +71,7 @@ def config(): rerun.add_argument('--rerun-all', action='store_true', - default=False, + default=defaultSettings['rerun_all'], help='force rerun of all tasks') rerun.add_argument('--rerun-from', @@ -101,7 +101,7 @@ def config(): parser.add_argument('--min-num-features', metavar='', - default=4000, + default=defaultSettings['opensfm']['min_num_features'], type=int, help=('Minimum number of features to extract per image. ' 'More features leads to better results but slower ' @@ -109,7 +109,7 @@ def config(): parser.add_argument('--matcher-threshold', metavar='', - default=2.0, + default=defaultSettings['opensfm']['matcher_threshold'], type=float, help=('Ignore matched keypoints if the two images share ' 'less than percent of keypoints. Default:' @@ -117,7 +117,7 @@ def config(): parser.add_argument('--matcher-ratio', metavar='', - default=0.6, + default=defaultSettings['opensfm']['matcher_ratio'], type=float, help=('Ratio of the distance to the next best matched ' 'keypoint. Default: %(default)s')) @@ -125,7 +125,7 @@ def config(): parser.add_argument('--matcher-neighbors', type=int, metavar='', - default=8, + default=defaultSettings['opensfm']['matcher_neighbors'], 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, ' @@ -136,7 +136,7 @@ def config(): parser.add_argument('--matcher-distance', metavar='', - default=0, + default=defaultSettings['opensfm']['matcher_distance'], type=int, help='Distance threshold in meters to find pre-matching ' 'images based on GPS exif data. Set to 0 to skip ' @@ -144,26 +144,26 @@ def config(): parser.add_argument('--opensfm-processes', metavar='', - default=defaultSettings['opensfm_processes'], + default=defaultSettings['opensfm']['processes'], 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['use_pmvs'], + default=defaultSettings['pmvs']['enabled'], help='Use pmvs to compute point cloud alternatively') parser.add_argument('--cmvs-maxImages', metavar='', - default=500, + default=defaultSettings['pmvs']['cmvs_max_images'], type=int, help='The maximum number of images per cluster. ' 'Default: %(default)s') parser.add_argument('--pmvs-level', metavar='', - default=1, + default=defaultSettings['pmvs']['level'], type=int, help=('The level in the image pyramid that is used ' 'for the computation. see ' @@ -171,15 +171,15 @@ def config(): 'more pmvs documentation. Default: %(default)s')) parser.add_argument('--pmvs-csize', - metavar='< positive integer>', - default=2, + metavar='', + default=defaultSettings['pmvs']['cell_size'], type=int, help='Cell size controls the density of reconstructions' 'Default: %(default)s') parser.add_argument('--pmvs-threshold', metavar='', - default=0.7, + default=defaultSettings['pmvs']['threshold'], type=float, help=('A patch reconstruction is accepted as a success ' 'and kept if its associated photometric consistency ' @@ -187,7 +187,7 @@ def config(): parser.add_argument('--pmvs-wsize', metavar='', - default=7, + default=defaultSettings['pmvs']['wsize'], type=int, help='pmvs samples wsize x wsize pixel colors from ' 'each image to compute photometric consistency ' @@ -198,7 +198,7 @@ def config(): parser.add_argument('--pmvs-min-images', metavar='', - default=3, + default=defaultSettings['pmvs']['min_images'], type=int, help=('Each 3D point must be visible in at least ' 'minImageNum images for being reconstructed. 3 is ' @@ -206,21 +206,21 @@ def config(): parser.add_argument('--pmvs-num-cores', metavar='', - default=context.num_cores, + default=defaultSettings['pmvs']['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=100000, + default=defaultSettings['mesh']['size'], type=int, help=('The maximum vertex count of the output mesh ' 'Default: %(default)s')) parser.add_argument('--mesh-octree-depth', metavar='', - default=9, + default=defaultSettings['mesh']['octree_depth'], type=int, help=('Oct-tree depth used in the mesh reconstruction, ' 'increase to get more vertices, recommended ' @@ -228,14 +228,14 @@ def config(): parser.add_argument('--mesh-samples', metavar='= 1.0>', - default=1.0, + default=defaultSettings['mesh']['samples'], type=float, help=('Number of points per octree node, recommended ' 'and default value: %(default)s')) parser.add_argument('--mesh-solver-divide', metavar='', - default=9, + default=defaultSettings['mesh']['solver_divide'], type=int, help=('Oct-tree depth at which the Laplacian equation ' 'is solved in the surface reconstruction step. ' @@ -245,57 +245,57 @@ def config(): parser.add_argument('--texturing-data-term', metavar='', - default='gmi', + default=defaultSettings['texturing']['data_term'], help=('Data term: [area, gmi]. Default: ' '%(default)s')) parser.add_argument('--texturing-outlier-removal-type', metavar='', - default='gauss_clamping', + default=defaultSettings['texturing']['outlier_removal_type'], 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=False, + default=defaultSettings['texturing']['skip_visibility_test'], help=('Skip geometric visibility test. Default: ' ' %(default)s')) parser.add_argument('--texturing-skip-global-seam-leveling', action='store_true', - default=False, + default=defaultSettings['texturing']['skip_global_seam_leveling'], help=('Skip global seam leveling. Useful for IR data.' 'Default: %(default)s')) parser.add_argument('--texturing-skip-local-seam-leveling', action='store_true', - default=False, + default=defaultSettings['texturing']['skip_local_seam_leveling'], help='Skip local seam blending. Default: %(default)s') parser.add_argument('--texturing-skip-hole-filling', action='store_true', - default=False, + default=defaultSettings['texturing']['skip_hole_filling'], help=('Skip filling of holes in the mesh. Default: ' ' %(default)s')) parser.add_argument('--texturing-keep-unseen-faces', action='store_true', - default=False, + default=defaultSettings['texturing']['keep_unseen_faces'], 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='none', + default=defaultSettings['texturing']['tone_mapping'], 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=None, + default=defaultSettings['georeferencing']['gcp'], help=('path to the file containing the ground control ' 'points used for georeferencing. Default: ' '%(default)s. The file needs to ' @@ -304,32 +304,34 @@ def config(): parser.add_argument('--use-exif', action='store_true', - default=False, + default=defaultSettings['georeferencing']['use_exif'], 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=20.0, + default=defaultSettings['orthophoto']['resolution'], type=float, help=('Orthophoto ground resolution in pixels/meter' 'Default: %(default)s')) parser.add_argument('--zip-results', action='store_true', - default=False, + default=defaultSettings['zip_results'], help='compress the results using gunzip') parser.add_argument('--verbose', '-v', action='store_true', - default=False, + default=defaultSettings['verbose'], help='Print additional messages to the console\n' 'Default: %(default)s') parser.add_argument('--time', action='store_true', - default=False, + default=defaultSettings['time'], help='Generates a benchmark file with runtime info\n' 'Default: %(default)s') + #TODO: Add version, gdal options + return parser.parse_args() From ec6ed4b625855b9357085622d2b1c83bea1dcc3c Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Fri, 10 Mar 2017 14:36:16 -0500 Subject: [PATCH 09/13] Add --version parameter and version tracking Former-commit-id: cee7b633123e114aa1f5635680cb7d9df592d924 --- VERSION | 1 + opendm/config.py | 6 +++++- run.py | 11 +++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 VERSION diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..f55f8fd9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.3-development \ No newline at end of file diff --git a/opendm/config.py b/opendm/config.py index c205b881..ae36803d 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -332,6 +332,10 @@ def config(): help='Generates a benchmark file with runtime info\n' 'Default: %(default)s') - #TODO: Add version, gdal options + #TODO: Add gdal options + parser.add_argument('--version', + action='store_true', + default=False, + help='Displays version number and exits. ') return parser.parse_args() diff --git a/run.py b/run.py index 368de568..3d017144 100644 --- a/run.py +++ b/run.py @@ -4,6 +4,7 @@ from opendm import log from opendm import config from opendm import system from opendm import io +from opendm import context import sys import ecto @@ -11,6 +12,9 @@ import os from scripts.odm_app import ODMApp +with open(io.join_paths(context.root_path, 'VERSION')) as version_file: + __version__ = version_file.read().strip() + def usage(): log.ODM_ERROR('You must specify a project name:') @@ -21,10 +25,13 @@ def usage(): if __name__ == '__main__': - log.ODM_INFO('Initializing OpenDroneMap app - %s' % system.now()) - 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: From e46cac88bb09dff41da89c1eabcdd6c4eb4595d0 Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Fri, 10 Mar 2017 16:09:55 -0500 Subject: [PATCH 10/13] Add more gdal creation options Former-commit-id: d4d09cbc3fb86c51d9637ab9cd794f53a3bc8169 --- opendm/config.py | 23 +++++++++++++++++++++++ scripts/odm_app.py | 3 +++ scripts/odm_orthophoto.py | 23 ++++++++++++++++------- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/opendm/config.py b/opendm/config.py index ae36803d..0d85f952 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -315,6 +315,29 @@ def config(): help=('Orthophoto ground resolution in pixels/meter' 'Default: %(default)s')) + parser.add_argument('--orthophoto-target-srs', + metavar="", + type=str, + default=None, + help='Target spatial reference for orthophoto creation. ' + 'Not implemented yet.\n' + 'Default: %(default)s') + + parser.add_argument('--orthophoto-no-tiled', + action='store_true', + default=False, + help='Set this parameter if you want a stripped geoTIFF.\n' + 'Default: %(default)s') + + parser.add_argument('--orthophoto-compression', + metavar='', + type=str, + 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 ' + 'are doing. Options: %(choices)s.\nDefault: %(default)s') + parser.add_argument('--zip-results', action='store_true', default=defaultSettings['zip_results'], diff --git a/scripts/odm_app.py b/scripts/odm_app.py index 626c7f4a..cc3729a1 100644 --- a/scripts/odm_app.py +++ b/scripts/odm_app.py @@ -73,6 +73,9 @@ class ODMApp(ecto.BlackBox): use_exif=p.args.use_exif, verbose=p.args.verbose), 'orthophoto': ODMOrthoPhotoCell(resolution=p.args.orthophoto_resolution, + t_srs=p.args.orthophoto_target_srs, + no_tiled=p.args.orthophoto_no_tiled, + compress=p.args.orthophoto_compression, verbose=p.args.verbose) } diff --git a/scripts/odm_orthophoto.py b/scripts/odm_orthophoto.py index 97dcad2c..52e688db 100644 --- a/scripts/odm_orthophoto.py +++ b/scripts/odm_orthophoto.py @@ -10,6 +10,9 @@ from opendm import types class ODMOrthoPhotoCell(ecto.Cell): def declare_params(self, params): params.declare("resolution", 'Orthophoto ground resolution in pixels/meter', 20) + params.declare("t_srs", 'Target SRS', None) + params.declare("no_tiled", 'Do not tile tiff', False) + params.declare("compress", 'Compression type', 'DEFLATE') params.declare("verbose", 'print additional messages to console', False) def declare_io(self, params, inputs, outputs): @@ -93,20 +96,26 @@ class ODMOrthoPhotoCell(ecto.Cell): 'uly': uly, 'lrx': lrx, 'lry': lry, + 'tiled': '' if self.params.no_tiled else '-co TILED=yes ', + 'compress': self.params.compress, + 'predictor': '-co PREDICTOR=2 ' if self.params.compress in + ['LZW', 'DEFLATE'] else '', 'epsg': georef.epsg, + 't_srs': self.params.t_srs or "EPSG:{0}".format(georef.epsg), 'png': tree.odm_orthophoto_file, 'tiff': tree.odm_orthophoto_tif, 'log': tree.odm_orthophoto_tif_log } system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} ' - '-co TILED=yes ' - '-co COMPRESS=DEFLATE ' - '-co PREDICTOR=2 ' - '-co BLOCKXSIZE=512 ' - '-co BLOCKYSIZE=512 ' - '-co NUM_THREADS=ALL_CPUS ' - '-a_srs \"EPSG:{epsg}\" {png} {tiff} > {log}'.format(**kwargs)) + '{tiled} ' + '-co COMPRESS={compress} ' + '{predictor} ' + '-co BLOCKXSIZE=512 ' + '-co BLOCKYSIZE=512 ' + '-co NUM_THREADS=ALL_CPUS ' + '-a_srs \"EPSG:{epsg}\" ' + '{png} {tiff} > {log}'.format(**kwargs)) geotiffcreated = True if not geotiffcreated: log.ODM_WARNING('No geo-referenced orthophoto created due ' From 58ef8bfce5e2c8353a4f03949783c71cc8ef428d Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Thu, 23 Mar 2017 14:55:22 -0400 Subject: [PATCH 11/13] Make verbose work without needing to select a project Former-commit-id: 166405f7cc5d78250ec67ab5b92fbeef445694d1 --- opendm/config.py | 8 +++++--- run.py | 9 +++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/opendm/config.py b/opendm/config.py index 0d85f952..05b98eee 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -1,5 +1,6 @@ import argparse from opendm import context +from opendm import io import yaml # parse arguments @@ -12,6 +13,8 @@ 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 @@ -355,10 +358,9 @@ def config(): help='Generates a benchmark file with runtime info\n' 'Default: %(default)s') - #TODO: Add gdal options parser.add_argument('--version', - action='store_true', - default=False, + action='version', + version='OpenDroneMap {0}'.format(__version__), help='Displays version number and exits. ') return parser.parse_args() diff --git a/run.py b/run.py index 3d017144..8941f1bb 100644 --- a/run.py +++ b/run.py @@ -12,9 +12,6 @@ import os from scripts.odm_app import ODMApp -with open(io.join_paths(context.root_path, 'VERSION')) as version_file: - __version__ = version_file.read().strip() - def usage(): log.ODM_ERROR('You must specify a project name:') @@ -27,9 +24,9 @@ if __name__ == '__main__': args = config.config() - if args.version: - log.ODM_INFO(__version__) - sys.exit(0) + #if args.version: + # log.ODM_INFO(__version__) + # sys.exit(0) log.ODM_INFO('Initializing OpenDroneMap app - %s' % system.now()) From 2039f4479a946de69d80b15b987708192f510ad1 Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Mon, 27 Mar 2017 15:41:51 -0400 Subject: [PATCH 12/13] Clean up argument parsing Former-commit-id: 43b116842eee6a2001d22684076de67f9d62ab75 --- .gitignore | 4 +- default.settings.yaml | 53 -------------------- docker.settings.yaml | 92 ++++++++++++++++------------------ opendm/config.py | 113 +++++++++++++++++++++++------------------- opendm/context.py | 3 -- run.py | 20 +------- settings.yaml | 49 ++++++++++++++++++ 7 files changed, 160 insertions(+), 174 deletions(-) delete mode 100644 default.settings.yaml create mode 100644 settings.yaml 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 From ebd99c6fa4aa350de8ea39de7634905c86aaab8a Mon Sep 17 00:00:00 2001 From: Dakota Benjamin Date: Wed, 29 Mar 2017 13:57:35 -0400 Subject: [PATCH 13/13] Add appsettings installation Former-commit-id: c8a1053e9c4ab40c6004abb5ab966c76fde2d2dd --- configure.sh | 3 ++- packages.Dockerfile | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/configure.sh b/configure.sh index d34b6d03..67aba83a 100755 --- a/configure.sh +++ b/configure.sh @@ -67,7 +67,8 @@ sudo apt-get install -y -qq python-networkx \ sudo pip install -U PyYAML \ exifread \ gpxpy \ - xmltodict + xmltodict \ + appsettings echo "Installing Ecto Dependencies" sudo pip install -U catkin-pkg diff --git a/packages.Dockerfile b/packages.Dockerfile index 72dda399..5048d19a 100644 --- a/packages.Dockerfile +++ b/packages.Dockerfile @@ -69,7 +69,8 @@ RUN pip install -U PyYAML \ exifread \ gpxpy \ xmltodict \ - catkin-pkg + catkin-pkg \ + appsettings #Installing Ecto Dependencies RUN apt-get install -y -qq python-empy \