diff --git a/config-default.json b/config-default.json index 7f71512..5a46cbe 100644 --- a/config-default.json +++ b/config-default.json @@ -12,5 +12,6 @@ "port": 3000, "deamon": false, "parallelQueueProcessing": 2, - "cleanupTasksAfter": 3 + "cleanupTasksAfter": 3, + "test": false } \ No newline at end of file diff --git a/config.js b/config.js index 2422751..eed2c45 100644 --- a/config.js +++ b/config.js @@ -33,6 +33,7 @@ Options: -d, --deamonize Set process to run as a deamon --parallel_queue_processing Number of simultaneous processing tasks (default: 2) --cleanup_tasks_after Number of days that elapse before deleting finished and canceled tasks (default: 3) + --test Enable test mode. In test mode, no commands are sent to OpenDroneMap. This can be useful during development or testing (default: false) Log Levels: error | debug | info | verbose | debug | silly `); @@ -76,5 +77,6 @@ config.port = parseInt(argv.port || argv.p || fromConfigFile("port", process.env config.deamon = argv.deamonize || argv.d || fromConfigFile("daemon", false); config.parallelQueueProcessing = argv.parallel_queue_processing || fromConfigFile("parallelQueueProcessing", 2); config.cleanupTasksAfter = argv.cleanup_tasks_after || fromConfigFile("cleanupTasksAfter", 3); +config.test = argv.test || fromConfigFile("test", false); module.exports = config; diff --git a/index.js b/index.js index 372f7b6..106b37e 100644 --- a/index.js +++ b/index.js @@ -501,6 +501,8 @@ process.on ('SIGTERM', gracefulShutdown); process.on ('SIGINT', gracefulShutdown); // Startup +if (config.test) logger.info("Running in test mode"); + async.series([ cb => odmOptions.initialize(cb), cb => { taskManager = new TaskManager(cb); }, diff --git a/libs/odmRunner.js b/libs/odmRunner.js index 4a59094..e20a4f2 100644 --- a/libs/odmRunner.js +++ b/libs/odmRunner.js @@ -16,11 +16,14 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ "use strict"; +let fs = require('fs'); +let path = require('path'); let assert = require('assert'); let spawn = require('child_process').spawn; let config = require('../config.js'); let logger = require('./logger'); + module.exports = { run: function(options, done, outputReceived){ assert(options["project-path"] !== undefined, "project-path must be defined"); @@ -42,6 +45,15 @@ module.exports = { logger.info(`About to run: python ${command.join(" ")}`); + if (config.test){ + logger.info("Test mode is on, command will not execute"); + + // TODO: simulate test output + done(null, 0, null); + + return; // Skip rest + } + // Launch let childProcess = spawn("python", command, {cwd: config.odm_path}); @@ -56,6 +68,32 @@ module.exports = { }, getJsonOptions: function(done){ + // In test mode, we don't call ODM, + // instead we return a mock + if (config.test){ + let optionsTestFile = "../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}`); + done(e); + } + }else{ + console.log(`Error: ${err.message}`); + done(err); + } + }); + + return; // Skip rest + } + // Launch let childProcess = spawn("python", [`${__dirname}/../helpers/odmOptionsToJson.py`, "--project-path", config.odm_path]); diff --git a/tests/odm_options.json b/tests/odm_options.json new file mode 100644 index 0000000..19142a2 --- /dev/null +++ b/tests/odm_options.json @@ -0,0 +1,205 @@ +[ + { + "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