diff --git a/Dockerfile b/Dockerfile index b9f61ad..d94c05e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -82,6 +82,7 @@ RUN cd micmac && \ ENV PATH "$PATH:/code/micmac/bin" RUN ln -s "$(which python3)" /usr/bin/python +ENV python "$(which python3)" RUN figlet -f slant NodeMICMAC RUN mkdir /code/opendm diff --git a/README.md b/README.md index 5312693..92ce8ce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # NodeMICMAC +

NodeMICMAC is a Node.js App and REST API to access [MicMac](https://github.com/micmacIGN/micmac). It exposes an API which is used by [WebODM](https://github.com/OpenDroneMap/WebODM) or other projects. This project is sponsored and developed by [DroneMapper](https://dronemapper.com). This repository was originally forked from [NodeODM](https://github.com/OpenDroneMap/NodeODM), which is part of the [OpenDroneMap](https://www.opendronemap.org/) Project. @@ -27,7 +28,7 @@ This list is not by order of importance. - [ ] Oblique Imagery and/or 3D Model - [x] Conform / Rename Outputs to ODM Conventions - [x] Wire Up 2D and Potree Tile Creation -- [x] Multi-Scale Tie-Point Generation (Speed up processing by switching to image resolution) +- [x] Multi-Scale Tie-Point Generation (Similar to Photoscan) - [ ] Export Undistorted Images - [ ] Utilize RPY Omega Phi Kappa - [x] Sparse Point Cloud w/ Camera Position @@ -60,7 +61,7 @@ Note: This project currently creates a geo-referenced DEM and Ortho from our 4th ![Greg12 Ortho](docs/readme_greg12_ortho_gcp.png) ![Greg12 GCP1](docs/readme_greg12_gcp1.png) -* Results clipped to an AOI +* Results clipped to an AOI and displayed using Global Mapper [GlobalMapper](https://bluemarblegeo.com) ## Mission Planning / Execution @@ -76,12 +77,12 @@ We recommend that you setup NodeMICMAC using [Docker](https://www.docker.com/). * Docker image build: ``` -docker build -t odm/node-micmac . +docker build -t dronemapper/node-micmac . ``` * From the Docker Quickstart Terminal (Windows / OSX) or from the command line (Linux) type: ``` -docker run -p 3000:3000 odm/node-micmac +docker run -p 3000:3000 dronemapper/node-micmac ``` * If you're on Windows/OSX, find the IP of your Docker machine by running this command from your Docker Quickstart Terminal: @@ -104,7 +105,7 @@ If the computer running NodeMICMAC is using an old or 32bit CPU, you need to com For GCP processing, you will need to include two files in txt format. Examples of the files are shown below. -`GCP_3D.txt` +`DroneMapperGCP_3D.txt` `GCPNAME UTMX UTMY Z PRECISIONXY PRECISIONZ` ```$xslt @@ -121,7 +122,7 @@ hg2 250117.42 4319009.086 2565.418 0.005 0.005 sw2 250159.165 4319019.774 2567.198 0.005 0.005 ``` -`GCP_2D.txt` +`DroneMapperGCP_2D.txt` `GCPNAME IMAGENAME PIXELX PIXELY` ```$xslt @@ -218,7 +219,7 @@ optional arguments: Distance threshold in meters to find pre-matching images based on GPS exif data. Default: 0 (use auto-distance) --multi-scale Uses an image file pair based multi-scale tie-point - generation routine. + generation routine similar to Photoscan. --remove-ortho-tiles Remove every other ortho tile. Speeds up ortho creation and radiometric equalization. --camera-cloud Creates a sparse point cloud with camera positions --image-footprint Creates a point cloud and geojson with image footprints @@ -282,7 +283,7 @@ make && sudo make install ```bash sudo curl --silent --location https://deb.nodesource.com/setup_6.x | sudo bash - sudo apt-get install -y nodejs python-gdal -git clone hhttps://github.com/OpenDroneMap/NodeMICMAC.git +git clone hhttps://github.com/dronemapper-io/NodeMICMAC.git cd NodeMICMAC npm install ``` @@ -366,6 +367,3 @@ Stay current with upstream MicMac development providing an easy to use interface ## MicMac Version Cloned: 04-26-2019 Commit: [fec03b2](https://github.com/micmacIGN/micmac/commit/fec03b2b9596886f9b929f5b663bbded3ae591c0) -MicMac is under CeCill-B License -Original running project could be find following this link : http://logiciels.ign.fr/?Micmac - diff --git a/helpers/odmOptionsToJson.py b/helpers/odmOptionsToJson.py index cf607d1..e20b722 100644 --- a/helpers/odmOptionsToJson.py +++ b/helpers/odmOptionsToJson.py @@ -21,10 +21,20 @@ import imp import argparse import json +import os + +dest_file = os.environ.get("ODM_OPTIONS_TMP_FILE") sys.path.append(sys.argv[2]) -imp.load_source('context', sys.argv[2] + '/opendm/context.py') +try: + imp.load_source('opendm', sys.argv[2] + '/opendm/__init__.py') +except: + pass +try: + imp.load_source('context', sys.argv[2] + '/opendm/context.py') +except: + pass odm = imp.load_source('config', sys.argv[2] + '/opendm/config.py') options = {} @@ -38,6 +48,16 @@ def add_argument(self, *args, **kwargs): def add_mutually_exclusive_group(self): return ArgumentParserStub() -odm.parser = ArgumentParserStub() -odm.config() -print json.dumps(options) +if not hasattr(odm, 'parser'): + # ODM >= 2.0 + odm.config(parser=ArgumentParserStub()) +else: + # ODM 1.0 + odm.parser = ArgumentParserStub() + odm.config() + +out = json.dumps(options) +print(out) +if dest_file is not None: + with open(dest_file, "w") as f: + f.write(out) diff --git a/libs/odmRunner.js.bak b/libs/odmRunner.js.bak new file mode 100644 index 0000000..6b315d5 --- /dev/null +++ b/libs/odmRunner.js.bak @@ -0,0 +1,136 @@ +/* +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 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, projectName, done, outputReceived){ + assert(projectName !== undefined, "projectName must be specified"); + assert(options["project-path"] !== undefined, "project-path must be defined"); + + const command = path.join(config.odm_path, "run.sh"), + params = []; + + for (var name in options){ + let value = options[name]; + + // Skip false booleans + if (value === false) continue; + + params.push("--" + name); + + // We don't specify "--time true" (just "--time") + if (typeof value !== 'boolean'){ + params.push(value); + } + } + + params.push(projectName); + + logger.info(`About to run: ${command} ${params.join(" ")}`); + + if (config.test){ + logger.info("Test mode is on, command will not execute"); + + 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 + } + + // Launch + let childProcess = spawn(command, params, {cwd: config.odm_path}); + + childProcess + .on('exit', (code, signal) => done(null, code, signal)) + .on('error', done); + + childProcess.stdout.on('data', chunk => outputReceived(chunk.toString())); + childProcess.stderr.on('data', chunk => outputReceived(chunk.toString())); + + return childProcess; + }, + + getVersion: function(done){ + fs.readFile(path.join(config.odm_path, 'VERSION'), {encoding: 'utf8'}, (err, content) => { + if (err) done(null, "?"); + else done(null, content.split("\n").map(l => l.trim())[0]); + }); + }, + + getJsonOptions: function(done){ + // In test mode, we don't call ODM, + // instead we return a mock + if (config.test){ + 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); + done(null, options); + }catch(e){ + logger.warn(`Invalid test options ${optionsTestFile}: ${err.message}`); + done(e); + } + }else{ + logger.warn(`Error: ${err.message}`); + done(err); + } + }); + + return; // Skip rest + } + + // Launch + let childProcess = spawn("python", [path.join(__dirname, "..", "helpers", "odmOptionsToJson.py"), + "--project-path", config.odm_path, "bogusname"]); + let output = []; + + childProcess + .on('exit', (code, signal) => { + try{ + let json = JSON.parse(output.join("")); + done(null, json); + }catch(err){ + done(new Error(`Could not load list of options from OpenDroneMap. Is OpenDroneMap installed in ${config.odm_path}? Make sure that OpenDroneMap is installed and that --odm_path is set properly: ${err.message}`)); + } + }) + .on('error', done); + + let processOutput = chunk => output.push(chunk.toString()); + + childProcess.stdout.on('data', processOutput); + childProcess.stderr.on('data', processOutput); + } +}; diff --git a/libs/utils.js.bak b/libs/utils.js.bak new file mode 100644 index 0000000..9b6a303 --- /dev/null +++ b/libs/utils.js.bak @@ -0,0 +1,48 @@ +"use strict"; + +const path = require('path'); + +module.exports = { + get: function(scope, prop, defaultValue){ + let parts = prop.split("."); + let current = scope; + for (let i = 0; i < parts.length; i++){ + if (current[parts[i]] !== undefined && i < parts.length - 1){ + current = current[parts[i]]; + }else if (current[parts[i]] !== undefined && i < parts.length){ + return current[parts[i]]; + }else{ + return defaultValue; + } + } + return defaultValue; + }, + + sanitize: function(filePath){ + filePath = filePath.replace(/[^\w.-]/g, "_"); + return filePath; + }, + + parseUnsafePathsList: function(paths){ + // Parse a list (or a JSON encoded string representing a list) + // of paths and remove all traversals (., ..) and guarantee + // that the paths are relative + + if (typeof paths === "string"){ + try{ + paths = JSON.parse(paths); + }catch(e){ + return []; + } + } + + if (!Array.isArray(paths)){ + return []; + } + + return paths.map(p => { + const safeSuffix = path.normalize(p).replace(/^(\.\.(\/|\\|$))+/, ''); + return path.join('./', safeSuffix); + }); + } +}; \ No newline at end of file