From 36ffae3389b83531d5327a8601123ba2610be06c Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Sun, 25 Sep 2016 18:35:44 -0400 Subject: [PATCH] Test mode, bug fixing, automatic linting --- Gruntfile.js | 16 +++ README.md | 10 ++ index.js | 19 +-- libs/Directories.js | 28 +++++ libs/Task.js | 27 ++-- libs/TaskManager.js | 8 +- libs/logger.js | 2 +- libs/odmOptions.js | 1 + libs/odmRunner.js | 28 +++-- package.json | 5 +- public/js/fileinput.js | 4 +- public/js/main.js | 3 +- tests/odm_options.json | 206 +----------------------------- tests/odm_output.txt | 279 +++++++++++++++++++++++++++++++++++++++++ 14 files changed, 391 insertions(+), 245 deletions(-) create mode 100644 Gruntfile.js create mode 100644 libs/Directories.js create mode 100644 tests/odm_output.txt diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..eaca935 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,16 @@ +module.exports = function(grunt) { + + require('time-grunt')(grunt); + + grunt.initConfig({ + jshint: { + options: { + jshintrc: ".jshintrc" + }, + all: ['Gruntfile.js', 'libs/**/*.js', 'docs/**/*.js', 'index.js', 'config.js'] + } + }); + + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.registerTask('default', ['jshint']); +}; \ No newline at end of file diff --git a/README.md b/README.md index 3fed387..a93f8e4 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,16 @@ http://www.buildsucceeded.com/2015/solved-pm2-startup-at-boot-time-centos-7-red- You can monitor the process using `pm2 status`. +### Test Mode + +If you want to make a contribution, but don't want to setup OpenDroneMap, or perhaps you are working on a Windows machine, or if you want to run automated tests, you can turn test mode on: + +``` +node index.js --test +``` + +While in test mode all calls to OpenDroneMap's code will be simulated (see the /tests directory for the mock data that is returned). + ### Test Images You can find some test drone images [here](https://github.com/dakotabenjamin/odm_data). diff --git a/index.js b/index.js index 106b37e..87136dd 100644 --- a/index.js +++ b/index.js @@ -36,6 +36,7 @@ let morgan = require('morgan'); let TaskManager = require('./libs/TaskManager'); let Task = require('./libs/Task'); let odmOptions = require('./libs/odmOptions'); +let Directories = require('./libs/Directories'); let winstonStream = { write: function(message, encoding){ @@ -51,14 +52,14 @@ app.use('/swagger.json', express.static('docs/swagger.json')); let upload = multer({ storage: multer.diskStorage({ destination: (req, file, cb) => { - let path = `tmp/${req.id}/`; - fs.exists(path, exists => { + let dstPath = path.join("tmp", req.id); + fs.exists(dstPath, exists => { if (!exists){ - fs.mkdir(path, undefined, () => { - cb(null, path); + fs.mkdir(dstPath, undefined, () => { + cb(null, dstPath); }); }else{ - cb(null, path); + cb(null, dstPath); } }); }, @@ -115,10 +116,10 @@ let server; app.post('/task/new', addRequestId, upload.array('images'), (req, res) => { if (req.files.length === 0) res.json({error: "Need at least 1 file."}); else{ - let srcPath = `tmp/${req.id}`; - let destPath = `data/${req.id}`; - let destImagesPath = `${destPath}/images`; - let destGpcPath = `${destPath}/gpc`; + let srcPath = path.join("tmp", req.id); + let destPath = path.join(Directories.data, req.id); + let destImagesPath = path.join(destPath, "images"); + let destGpcPath = path.join(destPath, "gpc"); async.series([ cb => { diff --git a/libs/Directories.js b/libs/Directories.js new file mode 100644 index 0000000..92e2af7 --- /dev/null +++ b/libs/Directories.js @@ -0,0 +1,28 @@ +/* +Node-OpenDroneMap Node.js App and REST API to access OpenDroneMap. +Copyright (C) 2016 Node-OpenDroneMap Contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +"use strict"; +let config = require('../config'); +let path = require('path'); + +class Directories{ + static get data(){ + return !config.test ? "data" : path.join("tests", "data"); + } +} + +module.exports = Directories; \ No newline at end of file diff --git a/libs/Task.js b/libs/Task.js index 27d08e7..d86e26a 100644 --- a/libs/Task.js +++ b/libs/Task.js @@ -17,6 +17,7 @@ along with this program. If not, see . */ "use strict"; +let config = require('../config'); let async = require('async'); let assert = require('assert'); let logger = require('./logger'); @@ -26,6 +27,7 @@ let rmdir = require('rimraf'); let odmRunner = require('./odmRunner'); let archiver = require('archiver'); let os = require('os'); +let Directories = require('./Directories'); let statusCodes = require('./statusCodes'); @@ -99,25 +101,25 @@ module.exports = class Task{ // Get path where images are stored for this task // (relative to nodejs process CWD) getImagesFolderPath(){ - return `${this.getProjectFolderPath()}/images`; + return path.join(this.getProjectFolderPath(), "images"); } // Get path where GPC file(s) are stored // (relative to nodejs process CWD) getGpcFolderPath(){ - return `${this.getProjectFolderPath()}/gpc`; + return path.join(this.getProjectFolderPath(), "gpc"); } // Get path of project (where all images and assets folder are contained) // (relative to nodejs process CWD) getProjectFolderPath(){ - return `data/${this.uuid}`; + return path.join(Directories.data, this.uuid); } // Get the path of the archive where all assets // outputted by this task are stored. getAssetsArchivePath(){ - return `${this.getProjectFolderPath()}/all.zip`; + return path.join(this.getProjectFolderPath(), "all.zip"); } // Deletes files and folders related to this task @@ -207,16 +209,21 @@ module.exports = class Task{ archive.on('error', err => { this.setStatus(statusCodes.FAILED); + logger.error(`Could not archive .zip file: ${err.message}`); finished(err); }); archive.pipe(output); - archive - .directory(`${this.getProjectFolderPath()}/odm_orthophoto`, 'odm_orthophoto') - .directory(`${this.getProjectFolderPath()}/odm_georeferencing`, 'odm_georeferencing') - .directory(`${this.getProjectFolderPath()}/odm_texturing`, 'odm_texturing') - .directory(`${this.getProjectFolderPath()}/odm_meshing`, 'odm_meshing') - .finalize(); + ['odm_orthophoto', 'odm_georeferencing', 'odm_texturing', 'odm_meshing'].forEach(folderToArchive => { + let sourcePath = !config.test ? + this.getProjectFolderPath() : + path.join("tests", "processing_results"); + + archive.directory( + path.join(sourcePath, folderToArchive), + folderToArchive); + }); + archive.finalize(); }; if (this.status.code === statusCodes.QUEUED){ diff --git a/libs/TaskManager.js b/libs/TaskManager.js index dfdd333..d55f1fb 100644 --- a/libs/TaskManager.js +++ b/libs/TaskManager.js @@ -26,9 +26,9 @@ let Task = require('./Task'); let statusCodes = require('./statusCodes'); let async = require('async'); let schedule = require('node-schedule'); +let Directories = require('./Directories'); -const DATA_DIR = "data"; -const TASKS_DUMP_FILE = `${DATA_DIR}/tasks.json`; +const TASKS_DUMP_FILE = path.join(Directories.data, "tasks.json"); const CLEANUP_TASKS_IF_OLDER_THAN = 1000 * 60 * 60 * 24 * config.cleanupTasksAfter; // days module.exports = class TaskManager{ @@ -85,11 +85,11 @@ module.exports = class TaskManager{ removeOrphanedDirectories(done){ logger.info("Checking for orphaned directories to be removed..."); - fs.readdir(DATA_DIR, (err, entries) => { + fs.readdir(Directories.data, (err, entries) => { if (err) done(err); else{ async.eachSeries(entries, (entry, cb) => { - let dirPath = path.join(DATA_DIR, entry); + let dirPath = path.join(Directories.data, entry); if (fs.statSync(dirPath).isDirectory() && entry.match(/^[\w\d]+\-[\w\d]+\-[\w\d]+\-[\w\d]+\-[\w\d]+$/) && !this.tasks[entry]){ diff --git a/libs/logger.js b/libs/logger.js index 5aba9bf..5f5aad8 100644 --- a/libs/logger.js +++ b/libs/logger.js @@ -26,7 +26,7 @@ let path = require('path'); // Configure custom File transport to write plain text messages let logPath = ( config.logger.logDirectory ? config.logger.logDirectory : - `${__dirname}/../` ); + path.join(__dirname, "..") ); // Check that log file directory can be written to try { diff --git a/libs/odmOptions.js b/libs/odmOptions.js index 3319283..af3a263 100644 --- a/libs/odmOptions.js +++ b/libs/odmOptions.js @@ -189,6 +189,7 @@ module.exports = { // Scan through all possible options for (let odmOption of odmOptions){ // Was this option selected by the user? + /*jshint loopfunc: true */ let opt = options.find(o => o.name === odmOption.name); if (opt){ try{ diff --git a/libs/odmRunner.js b/libs/odmRunner.js index e20a4f2..f17367f 100644 --- a/libs/odmRunner.js +++ b/libs/odmRunner.js @@ -28,7 +28,7 @@ module.exports = { run: function(options, done, outputReceived){ assert(options["project-path"] !== undefined, "project-path must be defined"); - let command = [`${config.odm_path}/run.py`]; + let command = [path.join(config.odm_path, "run.py")]; for (var name in options){ let value = options[name]; @@ -48,8 +48,18 @@ module.exports = { if (config.test){ logger.info("Test mode is on, command will not execute"); - // TODO: simulate test output - done(null, 0, null); + let outputTestFile = path.join("..", "tests", "odm_output.txt"); + fs.readFile(path.resolve(__dirname, outputTestFile), 'utf8', (err, text) => { + if (!err){ + let lines = text.split("\n"); + lines.forEach(line => outputReceived(line)); + + done(null, 0, null); + }else{ + logger.warn(`Error: ${err.message}`); + done(err); + } + }); return; // Skip rest } @@ -71,22 +81,18 @@ module.exports = { // In test mode, we don't call ODM, // instead we return a mock if (config.test){ - let optionsTestFile = "../tests/odm_options.json"; + let optionsTestFile = path.join("..", "tests", "odm_options.json"); fs.readFile(path.resolve(__dirname, optionsTestFile), 'utf8', (err, json) => { if (!err){ try{ let options = JSON.parse(json); - - // We also mark each description with "TEST" (to make sure we know this is not real data) - options.forEach(option => { option.help = "## TEST ##" + (option.help !== undefined ? ` ${option.help}` : ""); }); - done(null, options); }catch(e){ - console.log(`Invalid test options ${optionsTestFile}: ${err.message}`); + logger.warn(`Invalid test options ${optionsTestFile}: ${err.message}`); done(e); } }else{ - console.log(`Error: ${err.message}`); + logger.warn(`Error: ${err.message}`); done(err); } }); @@ -95,7 +101,7 @@ module.exports = { } // Launch - let childProcess = spawn("python", [`${__dirname}/../helpers/odmOptionsToJson.py`, + let childProcess = spawn("python", [path.join(__dirname, "..", "helpers", "odmOptionsToJson.py"), "--project-path", config.odm_path]); let output = []; diff --git a/package.json b/package.json index 0ef57db..dc0dda7 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,9 @@ "winston": "^2.2.0" }, "devDependencies": { - "nodemon": "^1.9.2" + "grunt": "^1.0.1", + "grunt-contrib-jshint": "^1.0.0", + "nodemon": "^1.9.2", + "time-grunt": "^1.4.0" } } diff --git a/public/js/fileinput.js b/public/js/fileinput.js index 7542b38..f8c2578 100644 --- a/public/js/fileinput.js +++ b/public/js/fileinput.js @@ -2216,9 +2216,9 @@ } if (!self.showPreview) { self.addToStack(file); - setTimeout(function () { + // setTimeout(function () { readFile(i + 1); - }, 100); + // }, 100); self._raise('fileloaded', [file, previewId, i, reader]); return; } diff --git a/public/js/main.js b/public/js/main.js index 1335f2a..87f5971 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -302,8 +302,7 @@ $(function(){ $("#btnUpload").removeAttr('disabled') .val(btnUploadLabel); }) - .on('filebatchuploaderror', function(e, data, msg){ - }); + .on('filebatchuploaderror', console.warn); // Load options function Option(properties){ diff --git a/tests/odm_options.json b/tests/odm_options.json index 19142a2..5fcae67 100644 --- a/tests/odm_options.json +++ b/tests/odm_options.json @@ -1,205 +1 @@ -[ - { - "domain": "integer", - "help": "The maximum number of images per cluster. Default: 500", - "name": "cmvs-maxImages", - "type": "int", - "value": "500" - }, - { - "domain": "positive integer", - "help": "Oct-tree depth used in the mesh reconstruction, increase to get more vertices, recommended values are 8-12. Default: 9", - "name": "odm_meshing-octreeDepth", - "type": "int", - "value": "9" - }, - { - "domain": "positive integer", - "help": "The maximum vertex count of the output mesh Default: 100000", - "name": "odm_meshing-maxVertexCount", - "type": "int", - "value": "100000" - }, - { - "domain": "percent", - "help": "Ignore matched keypoints if the two images share less than percent of keypoints. Default: 2", - "name": "matcher-threshold", - "type": "float", - "value": "2" - }, - { - "domain": "integer", - "help": "Minimum number of features to extract per image. More features leads to better results but slower execution. Default: 4000", - "name": "min-num-features", - "type": "int", - "value": "4000" - }, - { - "domain": "positive float", - "help": "Override the focal length information for the images", - "name": "force-focal", - "type": "float", - "value": "0" - }, - { - "domain": "integer", - "help": "resizes images by the largest side", - "name": "resize-to", - "type": "int", - "value": "2400" - }, - { - "domain": "positive integer", - "help": "The level in the image pyramid that is used for the computation. see http://www.di.ens.fr/pmvs/documentation.html for more pmvs documentation. Default: 1", - "name": "pmvs-level", - "type": "int", - "value": "1" - }, - { - "domain": "float: -1.0 <= x <= 1.0", - "help": "A patch reconstruction is accepted as a success and kept if its associated photometric consistency measure is above this threshold. Default: 0.7", - "name": "pmvs-threshold", - "type": "float", - "value": "0.7" - }, - { - "domain": "positive integer", - "help": "Each 3D point must be visible in at least minImageNum images for being reconstructed. 3 is suggested in general. Default: 3", - "name": "pmvs-minImageNum", - "type": "int", - "value": "3" - }, - { - "domain": "float >= 1.0", - "help": "Number of points per octree node, recommended and default value: 1", - "name": "odm_meshing-samplesPerNode", - "type": "float", - "value": "1" - }, - { - "domain": "string", - "help": "Skip filling of holes in the mesh. Default: false", - "name": "mvs_texturing-skipHoleFilling", - "type": "string", - "value": "false" - }, - { - "domain": "string", - "help": "Skip geometric visibility test. Default: false", - "name": "mvs_texturing-skipGlobalSeamLeveling", - "type": "string", - "value": "false" - }, - { - "domain": "positive float", - "help": "Override the ccd width information for the images", - "name": "force-ccd", - "type": "float", - "value": "0" - }, - { - "domain": "", - "help": "Generates a benchmark file with runtime info\nDefault: false", - "name": "time", - "type": "bool", - "value": "false" - }, - { - "domain": "positive integer", - "help": "The resolution of the output textures. Must be greater than textureWithSize. Default: 4096", - "name": "odm_texturing-textureResolution", - "type": "int", - "value": "4096" - }, - { - "domain": "integer", - "help": "Distance threshold in meters to find pre-matching images based on GPS exif data. Set to 0 to skip pre-matching. Default: 0", - "name": "matcher-distance", - "type": "int", - "value": "0" - }, - { - "domain": "positive integer", - "help": "Cell size controls the density of reconstructionsDefault: 2", - "name": "pmvs-csize", - "type": "int", - "value": "2" - }, - { - "domain": "float > 0.0", - "help": "Orthophoto ground resolution in pixels/meterDefault: 20", - "name": "odm_orthophoto-resolution", - "type": "float", - "value": "20" - }, - { - "domain": "positive integer", - "help": "Oct-tree depth at which the Laplacian equation is solved in the surface reconstruction step. Increasing this value increases computation times slightly but helps reduce memory usage. Default: 9", - "name": "odm_meshing-solverDivide", - "type": "int", - "value": "9" - }, - { - "domain": "positive integer", - "help": "pmvs samples wsize x wsize pixel colors from each image to compute photometric consistency score. For example, when wsize=7, 7x7=49 pixel colors are sampled in each image. Increasing the value leads to more stable reconstructions, but the program becomes slower. Default: 7", - "name": "pmvs-wsize", - "type": "int", - "value": "7" - }, - { - "domain": "string", - "help": "Keep faces in the mesh that are not seen in any camera. Default: false", - "name": "mvs_texturing-keepUnseenFaces", - "type": "string", - "value": "false" - }, - { - "domain": "positive integer", - "help": "The resolution to rescale the images performing the texturing. Default: 3600", - "name": "odm_texturing-textureWithSize", - "type": "int", - "value": "3600" - }, - { - "domain": "string", - "help": "Type of photometric outlier removal method: [none, gauss_damping, gauss_clamping]. Default: none", - "name": "mvs_texturing-outlierRemovalType", - "type": "string", - "value": "none" - }, - { - "domain": "integer", - "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, set both to 0 to not use pre-matching. OpenSFM uses both parameters at the same time, Bundler uses only one which has value, prefering the Neighbors parameter. Default: 8", - "name": "matcher-neighbors", - "type": "int", - "value": "8" - }, - { - "domain": "string", - "help": "Skip local seam blending. Default: false", - "name": "mvs_texturing-skipLocalSeamLeveling", - "type": "string", - "value": "false" - }, - { - "domain": "string", - "help": "Data term: [area, gmi]. Default: gmi", - "name": "mvs_texturing-dataTerm", - "type": "string", - "value": "gmi" - }, - { - "domain": "string", - "help": "Skip geometric visibility test. Default: false", - "name": "mvs_texturing-skipGeometricVisibilityTest", - "type": "string", - "value": "false" - }, - { - "domain": "float", - "help": "Ratio of the distance to the next best matched keypoint. Default: 0.6", - "name": "matcher-ratio", - "type": "float", - "value": "0.6" - } -] \ No newline at end of file +{"--pmvs-num-cores": {"default": "1", "type": "", "metavar": "", "help": "The maximum number of cores to use in dense reconstruction. Default: %(default)s"}, "--cmvs-maxImages": {"default": "500", "type": "", "metavar": "", "help": "The maximum number of images per cluster. Default: %(default)s"}, "--odm_meshing-octreeDepth": {"default": "9", "type": "", "metavar": "", "help": "Oct-tree depth used in the mesh reconstruction, increase to get more vertices, recommended values are 8-12. Default: %(default)s"}, "--odm_meshing-maxVertexCount": {"default": "100000", "type": "", "metavar": "", "help": "The maximum vertex count of the output mesh Default: %(default)s"}, "--matcher-threshold": {"default": "2.0", "type": "", "metavar": "", "help": "Ignore matched keypoints if the two images share less than percent of keypoints. Default: %(default)s"}, "--min-num-features": {"default": "4000", "type": "", "metavar": "", "help": "Minimum number of features to extract per image. More features leads to better results but slower execution. Default: %(default)s"}, "--force-focal": {"type": "", "metavar": "", "help": "Override the focal length information for the images"}, "--resize-to": {"default": "2400", "type": "", "metavar": "", "help": "resizes images by the largest side"}, "--odm_georeferencing-useGcp": {"action": "store_true", "default": "False", "help": "Enabling GCPs from the file above. The GCP file is not used by default."}, "--pmvs-level": {"default": "1", "type": "", "metavar": "", "help": "The level in the image pyramid that is used for the computation. see http://www.di.ens.fr/pmvs/documentation.html for more pmvs documentation. Default: %(default)s"}, "--pmvs-threshold": {"default": "0.7", "type": "", "metavar": "", "help": "A patch reconstruction is accepted as a success and kept if its associated photometric consistency measure is above this threshold. Default: %(default)s"}, "--pmvs-minImageNum": {"default": "3", "type": "", "metavar": "", "help": "Each 3D point must be visible in at least minImageNum images for being reconstructed. 3 is suggested in general. Default: %(default)s"}, "--odm_meshing-samplesPerNode": {"default": "1.0", "type": "", "metavar": "= 1.0>", "help": "Number of points per octree node, recommended and default value: %(default)s"}, "--mvs_texturing-skipHoleFilling": {"default": "false", "metavar": "", "help": "Skip filling of holes in the mesh. Default: %(default)s"}, "--project-path": {"metavar": "", "help": "Path to the project to process"}, "--start-with": {"default": "resize", "choices": "['resize', 'opensfm', 'cmvs', 'pmvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto']", "help": "Can be one of: resize | opensfm | cmvs | pmvs | odm_meshing | mvs_texturing | odm_georeferencing | odm_orthophoto", "metavar": ""}, "--mvs_texturing-skipGlobalSeamLeveling": {"default": "false", "metavar": "", "help": "Skip geometric visibility test. Default: %(default)s"}, "--force-ccd": {"type": "", "metavar": "", "help": "Override the ccd width information for the images"}, "-h": {"action": "help", "default": "==SUPPRESS==", "help": "show this help message and exit"}, "--time": {"action": "store_true", "default": "False", "help": "Generates a benchmark file with runtime info\nDefault: %(default)s"}, "--odm_texturing-textureResolution": {"default": "4096", "type": "", "metavar": "", "help": "The resolution of the output textures. Must be greater than textureWithSize. Default: %(default)s"}, "--matcher-distance": {"default": "0", "type": "", "metavar": "", "help": "Distance threshold in meters to find pre-matching images based on GPS exif data. Set to 0 to skip pre-matching. Default: %(default)s"}, "--zip-results": {"action": "store_true", "default": "False", "help": "compress the results using gunzip"}, "--pmvs-csize": {"default": "2", "type": "", "metavar": "< positive integer>", "help": "Cell size controls the density of reconstructionsDefault: %(default)s"}, "--odm_orthophoto-resolution": {"default": "20.0", "type": "", "metavar": " 0.0>", "help": "Orthophoto ground resolution in pixels/meterDefault: %(default)s"}, "--odm_meshing-solverDivide": {"default": "9", "type": "", "metavar": "", "help": "Oct-tree depth at which the Laplacian equation is solved in the surface reconstruction step. Increasing this value increases computation times slightly but helps reduce memory usage. Default: %(default)s"}, "--odm_georeferencing-gcpFile": {"default": "gcp_list.txt", "metavar": "", "help": "path to the file containing the ground control points used for georeferencing. Default: %(default)s. The file needs to be on the following line format: \neasting northing height pixelrow pixelcol imagename"}, "--pmvs-wsize": {"default": "7", "type": "", "metavar": "", "help": "pmvs samples wsize x wsize pixel colors from each image to compute photometric consistency score. For example, when wsize=7, 7x7=49 pixel colors are sampled in each image. Increasing the value leads to more stable reconstructions, but the program becomes slower. Default: %(default)s"}, "--mvs_texturing-keepUnseenFaces": {"default": "false", "metavar": "", "help": "Keep faces in the mesh that are not seen in any camera. Default: %(default)s"}, "--odm_texturing-textureWithSize": {"default": "3600", "type": "", "metavar": "", "help": "The resolution to rescale the images performing the texturing. Default: %(default)s"}, "--mvs_texturing-outlierRemovalType": {"default": "none", "metavar": "", "help": "Type of photometric outlier removal method: [none, gauss_damping, gauss_clamping]. Default: %(default)s"}, "--matcher-neighbors": {"default": "8", "type": "", "metavar": "", "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, set both to 0 to not use pre-matching. OpenSFM uses both parameters at the same time, Bundler uses only one which has value, prefering the Neighbors parameter. Default: %(default)s"}, "--mvs_texturing-skipLocalSeamLeveling": {"default": "false", "metavar": "", "help": "Skip local seam blending. Default: %(default)s"}, "--end-with": {"default": "odm_orthophoto", "choices": "['resize', 'opensfm', 'cmvs', 'pmvs', 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto']", "help": "Can be one of:resize | opensfm | cmvs | pmvs | odm_meshing | mvs_texturing | odm_georeferencing | odm_orthophoto", "metavar": ""}, "--mvs_texturing-dataTerm": {"default": "gmi", "metavar": "", "help": "Data term: [area, gmi]. Default: %(default)s"}, "--mvs_texturing-skipGeometricVisibilityTest": {"default": "false", "metavar": "", "help": "Skip geometric visibility test. Default: %(default)s"}, "--matcher-ratio": {"default": "0.6", "type": "", "metavar": "", "help": "Ratio of the distance to the next best matched keypoint. Default: %(default)s"}} diff --git a/tests/odm_output.txt b/tests/odm_output.txt new file mode 100644 index 0000000..de36114 --- /dev/null +++ b/tests/odm_output.txt @@ -0,0 +1,279 @@ +DJI_0131.JPG - DJI_0313.JPG has 1 candidate matches +DJI_0131.JPG - DJI_0177.JPG has 3 candidate matches +DJI_0131.JPG - DJI_0302.JPG has 0 candidate matches +DJI_0131.JPG - DJI_0210.JPG has 0 candidate matches +DJI_0131.JPG - DJI_0164.JPG has 1 candidate matches +DJI_0131.JPG - DJI_0222.JPG has 0 candidate matches +DJI_0131.JPG - DJI_0211.JPG has 1 candidate matches +Matching DJI_0290.JPG - 205 / 252 +DJI_0290.JPG - DJI_0325.JPG has 1 candidate matches +DJI_0290.JPG - DJI_0336.JPG has 0 candidate matches +Matching DJI_0153.JPG - 206 / 252 +DJI_0153.JPG - DJI_0188.JPG has 1 candidate matches +DJI_0153.JPG - DJI_0245.JPG has 3 candidate matches +DJI_0153.JPG - DJI_0199.JPG has 0 candidate matches +DJI_0153.JPG - DJI_0337.JPG has 0 candidate matches +DJI_0153.JPG - DJI_0291.JPG has 2 candidate matches +DJI_0153.JPG - DJI_0234.JPG has 0 candidate matches +Matching DJI_0321.JPG - 207 / 252 +DJI_0321.JPG - DJI_0340.JPG has 2 candidate matches +Matching DJI_0345.JPG - 208 / 252 +Matching DJI_0325.JPG - 209 / 252 +DJI_0325.JPG - DJI_0336.JPG has 5 candidate matches +Matching DJI_0215.JPG - 210 / 252 +DJI_0215.JPG - DJI_0261.JPG has 0 candidate matches +DJI_0215.JPG - DJI_0308.JPG has 1 candidate matches +DJI_0215.JPG - DJI_0353.JPG has 1 candidate matches +DJI_0215.JPG - DJI_0218.JPG has 3 candidate matches +Matching DJI_0284.JPG - 211 / 252 +DJI_0284.JPG - DJI_0329.JPG has 1 candidate matches +DJI_0284.JPG - DJI_0286.JPG has 0 candidate matches +DJI_0284.JPG - DJI_0332.JPG has 2 candidate matches +Matching DJI_0156.JPG - 212 / 252 +DJI_0156.JPG - DJI_0294.JPG has 1 candidate matches +DJI_0156.JPG - DJI_0231.JPG has 2 candidate matches +DJI_0156.JPG - DJI_0248.JPG has 0 candidate matches +DJI_0156.JPG - DJI_0185.JPG has 13 candidate matches +DJI_0156.JPG - DJI_0276.JPG has 0 candidate matches +DJI_0156.JPG - DJI_0202.JPG has 3 candidate matches +Matching DJI_0108.JPG - 213 / 252 +DJI_0108.JPG - DJI_0188.JPG has 0 candidate matches +DJI_0108.JPG - DJI_0279.JPG has 0 candidate matches +DJI_0108.JPG - DJI_0153.JPG has 0 candidate matches +DJI_0108.JPG - DJI_0200.JPG has 0 candidate matches +DJI_0108.JPG - DJI_0199.JPG has 0 candidate matches +DJI_0108.JPG - DJI_0234.JPG has 0 candidate matches +DJI_0108.JPG - DJI_0291.JPG has 0 candidate matches +DJI_0108.JPG - DJI_0142.JPG has 5 candidate matches +Matching DJI_0174.JPG - 214 / 252 +DJI_0174.JPG - DJI_0213.JPG has 0 candidate matches +DJI_0174.JPG - DJI_0220.JPG has 0 candidate matches +DJI_0174.JPG - DJI_0259.JPG has 0 candidate matches +DJI_0174.JPG - DJI_0266.JPG has 1 candidate matches +DJI_0174.JPG - DJI_0305.JPG has 0 candidate matches +Matching DJI_0324.JPG - 215 / 252 +DJI_0324.JPG - DJI_0337.JPG has 1 candidate matches +Matching DJI_0116.JPG - 216 / 252 +DJI_0116.JPG - DJI_0134.JPG has 0 candidate matches +DJI_0116.JPG - DJI_0299.JPG has 0 candidate matches +DJI_0116.JPG - DJI_0271.JPG has 0 candidate matches +DJI_0116.JPG - DJI_0161.JPG has 1 candidate matches +DJI_0116.JPG - DJI_0208.JPG has 0 candidate matches +DJI_0116.JPG - DJI_0225.JPG has 0 candidate matches +DJI_0116.JPG - DJI_0345.JPG has 0 candidate matches +DJI_0116.JPG - DJI_0254.JPG has 1 candidate matches +DJI_0116.JPG - DJI_0179.JPG has 1 candidate matches +Matching DJI_0247.JPG - 217 / 252 +DJI_0247.JPG - DJI_0278.JPG has 1 candidate matches +DJI_0247.JPG - DJI_0323.JPG has 0 candidate matches +DJI_0247.JPG - DJI_0339.JPG has 0 candidate matches +DJI_0247.JPG - DJI_0338.JPG has 0 candidate matches +Matching DJI_0220.JPG - 218 / 252 +DJI_0220.JPG - DJI_0266.JPG has 0 candidate matches +DJI_0220.JPG - DJI_0351.JPG has 1 candidate matches +DJI_0220.JPG - DJI_0305.JPG has 0 candidate matches +DJI_0220.JPG - DJI_0310.JPG has 0 candidate matches +Matching DJI_0128.JPG - 219 / 252 +DJI_0128.JPG - DJI_0174.JPG has 1 candidate matches +DJI_0128.JPG - DJI_0213.JPG has 0 candidate matches +DJI_0128.JPG - DJI_0310.JPG has 0 candidate matches +DJI_0128.JPG - DJI_0167.JPG has 1 candidate matches +DJI_0128.JPG - DJI_0351.JPG has 0 candidate matches +DJI_0128.JPG - DJI_0305.JPG has 0 candidate matches +DJI_0128.JPG - DJI_0260.JPG has 0 candidate matches +DJI_0128.JPG - DJI_0220.JPG has 0 candidate matches +Matching DJI_0183.JPG - 220 / 252 +DJI_0183.JPG - DJI_0250.JPG has 0 candidate matches +DJI_0183.JPG - DJI_0274.JPG has 1 candidate matches +DJI_0183.JPG - DJI_0229.JPG has 0 candidate matches +DJI_0183.JPG - DJI_0204.JPG has 7 candidate matches +DJI_0183.JPG - DJI_0319.JPG has 2 candidate matches +DJI_0183.JPG - DJI_0341.JPG has 0 candidate matches +DJI_0183.JPG - DJI_0296.JPG has 0 candidate matches +Matching DJI_0252.JPG - 221 / 252 +DJI_0252.JPG - DJI_0317.JPG has 0 candidate matches +DJI_0252.JPG - DJI_0343.JPG has 1 candidate matches +DJI_0252.JPG - DJI_0318.JPG has 0 candidate matches +DJI_0252.JPG - DJI_0298.JPG has 0 candidate matches +DJI_0252.JPG - DJI_0273.JPG has 7 candidate matches +Matching DJI_0308.JPG - 222 / 252 +DJI_0308.JPG - DJI_0353.JPG has 6 candidate matches +Matching DJI_0194.JPG - 223 / 252 +DJI_0194.JPG - DJI_0239.JPG has 7 candidate matches +DJI_0194.JPG - DJI_0285.JPG has 0 candidate matches +DJI_0194.JPG - DJI_0286.JPG has 0 candidate matches +DJI_0194.JPG - DJI_0331.JPG has 2 candidate matches +DJI_0194.JPG - DJI_0240.JPG has 1 candidate matches +DJI_0194.JPG - DJI_0329.JPG has 0 candidate matches +DJI_0194.JPG - DJI_0330.JPG has 0 candidate matches +Matching DJI_0175.JPG - 224 / 252 +DJI_0175.JPG - DJI_0212.JPG has 2 candidate matches +DJI_0175.JPG - DJI_0221.JPG has 1 candidate matches +DJI_0175.JPG - DJI_0267.JPG has 1 candidate matches +DJI_0175.JPG - DJI_0349.JPG has 1 candidate matches +DJI_0175.JPG - DJI_0304.JPG has 0 candidate matches +Matching DJI_0246.JPG - 225 / 252 +DJI_0246.JPG - DJI_0292.JPG has 1 candidate matches +DJI_0246.JPG - DJI_0324.JPG has 0 candidate matches +DJI_0246.JPG - DJI_0279.JPG has 1 candidate matches +Matching DJI_0208.JPG - 226 / 252 +DJI_0208.JPG - DJI_0271.JPG has 0 candidate matches +DJI_0208.JPG - DJI_0225.JPG has 0 candidate matches +DJI_0208.JPG - DJI_0254.JPG has 1 candidate matches +DJI_0208.JPG - DJI_0345.JPG has 2 candidate matches +DJI_0208.JPG - DJI_0316.JPG has 0 candidate matches +Matching DJI_0225.JPG - 227 / 252 +DJI_0225.JPG - DJI_0345.JPG has 0 candidate matches +DJI_0225.JPG - DJI_0299.JPG has 1 candidate matches +DJI_0225.JPG - DJI_0316.JPG has 0 candidate matches +DJI_0225.JPG - DJI_0254.JPG has 1 candidate matches +DJI_0225.JPG - DJI_0271.JPG has 0 candidate matches +Matching DJI_0210.JPG - 228 / 252 +DJI_0210.JPG - DJI_0347.JPG has 0 candidate matches +DJI_0210.JPG - DJI_0223.JPG has 1 candidate matches +DJI_0210.JPG - DJI_0256.JPG has 0 candidate matches +DJI_0210.JPG - DJI_0269.JPG has 0 candidate matches +Matching DJI_0185.JPG - 229 / 252 +DJI_0185.JPG - DJI_0248.JPG has 0 candidate matches +DJI_0185.JPG - DJI_0231.JPG has 1 candidate matches +DJI_0185.JPG - DJI_0276.JPG has 1 candidate matches +DJI_0185.JPG - DJI_0294.JPG has 1 candidate matches +DJI_0185.JPG - DJI_0321.JPG has 0 candidate matches +DJI_0185.JPG - DJI_0202.JPG has 26 candidate matches +Robust matching time : 0.00102090835571s +Full matching 23 / 26, time: 0.113751173019s +Matching DJI_0333.JPG - 230 / 252 +Matching DJI_0137.JPG - 231 / 252 +DJI_0137.JPG - DJI_0250.JPG has 0 candidate matches +DJI_0137.JPG - DJI_0158.JPG has 16 candidate matches +DJI_0137.JPG - DJI_0183.JPG has 3 candidate matches +DJI_0137.JPG - DJI_0296.JPG has 0 candidate matches +DJI_0137.JPG - DJI_0204.JPG has 2 candidate matches +DJI_0137.JPG - DJI_0319.JPG has 0 candidate matches +DJI_0137.JPG - DJI_0229.JPG has 0 candidate matches +DJI_0137.JPG - DJI_0274.JPG has 0 candidate matches +Matching DJI_0150.JPG - 232 / 252 +DJI_0150.JPG - DJI_0191.JPG has 1 candidate matches +DJI_0150.JPG - DJI_0288.JPG has 1 candidate matches +DJI_0150.JPG - DJI_0334.JPG has 0 candidate matches +DJI_0150.JPG - DJI_0237.JPG has 0 candidate matches +DJI_0150.JPG - DJI_0196.JPG has 0 candidate matches +DJI_0150.JPG - DJI_0242.JPG has 1 candidate matches +Matching DJI_0249.JPG - 233 / 252 +DJI_0249.JPG - DJI_0321.JPG has 1 candidate matches +DJI_0249.JPG - DJI_0340.JPG has 1 candidate matches +DJI_0249.JPG - DJI_0276.JPG has 3 candidate matches +Matching DJI_0283.JPG - 234 / 252 +DJI_0283.JPG - DJI_0328.JPG has 0 candidate matches +DJI_0283.JPG - DJI_0333.JPG has 0 candidate matches +DJI_0283.JPG - DJI_0287.JPG has 1 candidate matches +Matching DJI_0256.JPG - 235 / 252 +DJI_0256.JPG - DJI_0301.JPG has 0 candidate matches +DJI_0256.JPG - DJI_0346.JPG has 2 candidate matches +DJI_0256.JPG - DJI_0347.JPG has 0 candidate matches +DJI_0256.JPG - DJI_0314.JPG has 0 candidate matches +DJI_0256.JPG - DJI_0269.JPG has 4 candidate matches +Matching DJI_0235.JPG - 236 / 252 +DJI_0235.JPG - DJI_0336.JPG has 1 candidate matches +DJI_0235.JPG - DJI_0326.JPG has 1 candidate matches +DJI_0235.JPG - DJI_0281.JPG has 0 candidate matches +DJI_0235.JPG - DJI_0290.JPG has 0 candidate matches +DJI_0235.JPG - DJI_0244.JPG has 2 candidate matches +Matching DJI_0277.JPG - 237 / 252 +DJI_0277.JPG - DJI_0322.JPG has 0 candidate matches +DJI_0277.JPG - DJI_0339.JPG has 0 candidate matches +DJI_0277.JPG - DJI_0293.JPG has 3 candidate matches +Matching DJI_0296.JPG - 238 / 252 +DJI_0296.JPG - DJI_0319.JPG has 1 candidate matches +DJI_0296.JPG - DJI_0342.JPG has 0 candidate matches +Matching DJI_0157.JPG - 239 / 252 +DJI_0157.JPG - DJI_0295.JPG has 0 candidate matches +DJI_0157.JPG - DJI_0340.JPG has 0 candidate matches +DJI_0157.JPG - DJI_0184.JPG has 12 candidate matches +DJI_0157.JPG - DJI_0230.JPG has 0 candidate matches +DJI_0157.JPG - DJI_0203.JPG has 3 candidate matches +DJI_0157.JPG - DJI_0249.JPG has 0 candidate matches +DJI_0157.JPG - DJI_0275.JPG has 0 candidate matches +Matching DJI_0273.JPG - 240 / 252 +DJI_0273.JPG - DJI_0318.JPG has 1 candidate matches +DJI_0273.JPG - DJI_0343.JPG has 2 candidate matches +DJI_0273.JPG - DJI_0298.JPG has 0 candidate matches +Matching DJI_0148.JPG - 241 / 252 +DJI_0148.JPG - DJI_0331.JPG has 0 candidate matches +DJI_0148.JPG - DJI_0193.JPG has 7 candidate matches +DJI_0148.JPG - DJI_0285.JPG has 0 candidate matches +DJI_0148.JPG - DJI_0194.JPG has 1 candidate matches +DJI_0148.JPG - DJI_0330.JPG has 1 candidate matches +DJI_0148.JPG - DJI_0286.JPG has 0 candidate matches +DJI_0148.JPG - DJI_0240.JPG has 3 candidate matches +DJI_0148.JPG - DJI_0239.JPG has 0 candidate matches +DJI_0148.JPG - DJI_0329.JPG has 3 candidate matches +DJI_0148.JPG - DJI_0332.JPG has 1 candidate matches +Matching DJI_0162.JPG - 242 / 252 +DJI_0162.JPG - DJI_0179.JPG has 16 candidate matches +DJI_0162.JPG - DJI_0255.JPG has 1 candidate matches +DJI_0162.JPG - DJI_0208.JPG has 3 candidate matches +DJI_0162.JPG - DJI_0315.JPG has 0 candidate matches +DJI_0162.JPG - DJI_0224.JPG has 0 candidate matches +DJI_0162.JPG - DJI_0254.JPG has 2 candidate matches +DJI_0162.JPG - DJI_0300.JPG has 2 candidate matches +Matching DJI_0236.JPG - 243 / 252 +DJI_0236.JPG - DJI_0289.JPG has 2 candidate matches +DJI_0236.JPG - DJI_0243.JPG has 1 candidate matches +DJI_0236.JPG - DJI_0282.JPG has 2 candidate matches +DJI_0236.JPG - DJI_0327.JPG has 1 candidate matches +DJI_0236.JPG - DJI_0335.JPG has 0 candidate matches +Matching DJI_0298.JPG - 244 / 252 +DJI_0298.JPG - DJI_0343.JPG has 0 candidate matches +DJI_0298.JPG - DJI_0344.JPG has 0 candidate matches +DJI_0298.JPG - DJI_0317.JPG has 2 candidate matches +Matching DJI_0228.JPG - 245 / 252 +DJI_0228.JPG - DJI_0251.JPG has 6 candidate matches +DJI_0228.JPG - DJI_0274.JPG has 0 candidate matches +DJI_0228.JPG - DJI_0342.JPG has 0 candidate matches +DJI_0228.JPG - DJI_0319.JPG has 0 candidate matches +Matching DJI_0322.JPG - 246 / 252 +DJI_0322.JPG - DJI_0339.JPG has 3 candidate matches +Matching DJI_0176.JPG - 247 / 252 +DJI_0176.JPG - DJI_0222.JPG has 1 candidate matches +DJI_0176.JPG - DJI_0211.JPG has 2 candidate matches +DJI_0176.JPG - DJI_0312.JPG has 1 candidate matches +DJI_0176.JPG - DJI_0257.JPG has 0 candidate matches +DJI_0176.JPG - DJI_0258.JPG has 1 candidate matches +DJI_0176.JPG - DJI_0268.JPG has 0 candidate matches +DJI_0176.JPG - DJI_0303.JPG has 0 candidate matches +Matching DJI_0272.JPG - 248 / 252 +DJI_0272.JPG - DJI_0344.JPG has 0 candidate matches +DJI_0272.JPG - DJI_0317.JPG has 0 candidate matches +DJI_0272.JPG - DJI_0298.JPG has 3 candidate matches +DJI_0272.JPG - DJI_0299.JPG has 3 candidate matches +Matching DJI_0124.JPG - 249 / 252 +DJI_0124.JPG - DJI_0307.JPG has 0 candidate matches +DJI_0124.JPG - DJI_0263.JPG has 1 candidate matches +DJI_0124.JPG - DJI_0215.JPG has 0 candidate matches +DJI_0124.JPG - DJI_0169.JPG has 1 candidate matches +DJI_0124.JPG - DJI_0126.JPG has 1 candidate matches +DJI_0124.JPG - DJI_0170.JPG has 0 candidate matches +DJI_0124.JPG - DJI_0261.JPG has 1 candidate matches +DJI_0124.JPG - DJI_0125.JPG has 2 candidate matches +DJI_0124.JPG - DJI_0172.JPG has 1 candidate matches +DJI_0124.JPG - DJI_0216.JPG has 0 candidate matches +DJI_0124.JPG - DJI_0171.JPG has 0 candidate matches +DJI_0124.JPG - DJI_0218.JPG has 1 candidate matches +Matching DJI_0310.JPG - 250 / 252 +DJI_0310.JPG - DJI_0351.JPG has 1 candidate matches +Matching DJI_0241.JPG - 251 / 252 +DJI_0241.JPG - DJI_0333.JPG has 1 candidate matches +DJI_0241.JPG - DJI_0328.JPG has 0 candidate matches +DJI_0241.JPG - DJI_0284.JPG has 1 candidate matches +DJI_0241.JPG - DJI_0332.JPG has 2 candidate matches +DJI_0241.JPG - DJI_0287.JPG has 1 candidate matches +Matching DJI_0118.JPG - 252 / 252 +DJI_0118.JPG - DJI_0178.JPG has 0 candidate matches +DJI_0118.JPG - DJI_0301.JPG has 0 candidate matches +DJI_0118.JPG - DJI_0269.JPG has 1 candidate matches +DJI_0118.JPG - DJI_0256.JPG has 1 candidate matches +DJI_0118.JPG - DJI_0210.JPG has 0 candidate matches +DJI_0118.JPG - DJI_0132.JPG has 3 candidate matches +DJI_0118.JPG - DJI_0223.JPG has 1 candidate matches +DJI_0118.JPG - DJI_0163.JPG has 1 candidate matches \ No newline at end of file