Skip to content

Commit

Permalink
Merge pull request #153 from pierotofy/win32
Browse files Browse the repository at this point in the history
Native NodeODM on Windows
  • Loading branch information
pierotofy authored May 25, 2021
2 parents f986888 + 81896b5 commit 4eb5e48
Show file tree
Hide file tree
Showing 19 changed files with 530 additions and 191 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules
tests
tmp
nodeodm.exe
dist
41 changes: 41 additions & 0 deletions .github/workflows/publish-windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Publish Windows Bundle

on:
push:
branches:
- master
tags:
- v*

jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup NodeJS
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Setup Env
run: |
npm i
npm i -g nexe
- name: Build bundle
run: |
npm run winbundle
- name: Upload Bundle File
uses: actions/upload-artifact@v2
with:
name: Bundle
path: dist\*.zip
- name: Upload Bundle to Release
uses: svenstaro/upload-release-action@v2
if: startsWith(github.ref, 'refs/tags/')
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: dist\*.zip
file_glob: true
tag: ${{ github.ref }}
overwrite: true

24 changes: 24 additions & 0 deletions .github/workflows/test-build-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Build PRs

on:
pull_request:

jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build
uses: docker/build-push-action@v2
with:
platforms: linux/amd64
push: false
tags: opendronemap/nodeodm:test
- name: Test Powercycle
run: |
docker run -ti --rm opendronemap/nodeodm:test --powercycle
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ jspm_packages
.vscode

package-lock.json
apps/
nodeodm.exe
dist/
9 changes: 0 additions & 9 deletions .travis.yml

This file was deleted.

8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ You're in good shape!

See https://github.com/NVIDIA/nvidia-docker and https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker for information on docker/NVIDIA setup.

### Windows Bundle

NodeODM can run as a self-contained executable on Windows without the need for additional dependencies (except for [ODM](https://github.com/OpenDroneMap/ODM) which needs to be installed separately). You can download the latest `nodeodm-windows-x64.zip` bundle from the [releases](https://github.com/OpenDroneMap/NodeODM/releases) page. Extract the contents in a folder and run:

```bash
nodeodm.exe --odm_path c:\path\to\ODM
```

### Run it Natively

If you are already running [ODM](https://github.com/OpenDroneMap/ODM) on Ubuntu natively you can follow these steps:
Expand Down
1 change: 1 addition & 0 deletions SOURCE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NodeODM is free software. You can download the source code from https://github.com/OpenDroneMap/NodeODM/
5 changes: 3 additions & 2 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
let fs = require('fs');
let argv = require('minimist')(process.argv.slice(2));
let utils = require('./libs/utils');
let apps = require('./libs/apps');
const spawnSync = require('child_process').spawnSync;

if (argv.help){
Expand Down Expand Up @@ -141,8 +142,8 @@ config.maxConcurrency = parseInt(argv.max_concurrency || fromConfigFile("maxConc
config.maxRuntime = parseInt(argv.max_runtime || fromConfigFile("maxRuntime", -1));

// Detect 7z availability
config.has7z = spawnSync("7z", ['--help']).status === 0;
config.hasUnzip = spawnSync("unzip", ['--help']).status === 0;
config.has7z = spawnSync(apps.sevenZ, ['--help']).status === 0;
config.hasUnzip = spawnSync(apps.unzip, ['--help']).status === 0;


module.exports = config;
8 changes: 8 additions & 0 deletions helpers/odm_python.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@echo off

setlocal

call %ODM_PATH%\win32env.bat
python %*

endlocal
108 changes: 80 additions & 28 deletions libs/Task.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.

const config = require('../config');
const async = require('async');
const os = require('os');
const assert = require('assert');
const logger = require('./logger');
const fs = require('fs');
const path = require('path');
const rmdir = require('rimraf');
const odmRunner = require('./odmRunner');
const odmInfo = require('./odmInfo');
const processRunner = require('./processRunner');
const Directories = require('./Directories');
const kill = require('tree-kill');
Expand All @@ -36,16 +38,15 @@ const archiver = require('archiver');
const statusCodes = require('./statusCodes');

module.exports = class Task{
constructor(uuid, name, options = [], webhook = null, skipPostProcessing = false, outputs = [], dateCreated = new Date().getTime(), done = () => {}){
constructor(uuid, name, options = [], webhook = null, skipPostProcessing = false, outputs = [], dateCreated = new Date().getTime(), imagesCountEstimate = -1){
assert(uuid !== undefined, "uuid must be set");
assert(done !== undefined, "ready must be set");

this.uuid = uuid;
this.name = name !== "" ? name : "Task of " + (new Date()).toISOString();
this.dateCreated = isNaN(parseInt(dateCreated)) ? new Date().getTime() : parseInt(dateCreated);
this.dateStarted = 0;
this.processingTime = -1;
this.setStatus(statusCodes.QUEUED);
this.setStatus(statusCodes.RUNNING);
this.options = options;
this.gcpFiles = [];
this.geoFiles = [];
Expand All @@ -56,8 +57,33 @@ module.exports = class Task{
this.skipPostProcessing = skipPostProcessing;
this.outputs = utils.parseUnsafePathsList(outputs);
this.progress = 0;

async.series([
this.imagesCountEstimate = imagesCountEstimate;
this.initialized = false;
this.onInitialize = []; // Events to trigger on initialization
}

initialize(done, additionalSteps = []){
async.series(additionalSteps.concat([
// Handle post-processing options logic
cb => {
// If we need to post process results
// if pc-ept is supported (build entwine point cloud)
// we automatically add the pc-ept option to the task options by default
if (this.skipPostProcessing) cb();
else{
odmInfo.supportsOption("pc-ept", (err, supported) => {
if (err){
console.warn(`Cannot check for supported option pc-ept: ${err}`);
}else if (supported){
if (!this.options.find(opt => opt.name === "pc-ept")){
this.options.push({ name: 'pc-ept', value: true });
}
}
cb();
});
}
},

// Read images info
cb => {
fs.readdir(this.getImagesFolderPath(), (err, files) => {
Expand Down Expand Up @@ -91,35 +117,45 @@ module.exports = class Task{
}
});
}
], err => {
]), err => {
// Status might have changed due to user action
// in which case we leave it unchanged
if (this.getStatus() === statusCodes.RUNNING){
if (err) this.setStatus(statusCodes.FAILED, { errorMessage: err.message });
else this.setStatus(statusCodes.QUEUED);
}
this.initialized = true;
this.onInitialize.forEach(evt => evt(this));
this.onInitialize = [];
done(err, this);
});
}

static CreateFromSerialized(taskJson, done){
new Task(taskJson.uuid,
const task = new Task(taskJson.uuid,
taskJson.name,
taskJson.options,
taskJson.options,
taskJson.webhook,
taskJson.skipPostProcessing,
taskJson.outputs,
taskJson.dateCreated,
(err, task) => {
if (err) done(err);
else{
// Override default values with those
// provided in the taskJson
for (let k in taskJson){
task[k] = taskJson[k];
}

// Tasks that were running should be put back to QUEUED state
if (task.status.code === statusCodes.RUNNING){
task.status.code = statusCodes.QUEUED;
}
done(null, task);
taskJson.dateCreated);

task.initialize((err, task) => {
if (err) done(err);
else{
// Override default values with those
// provided in the taskJson
for (let k in taskJson){
task[k] = taskJson[k];
}
});

// Tasks that were running should be put back to QUEUED state
if (task.status.code === statusCodes.RUNNING){
task.status.code = statusCodes.QUEUED;
}
done(null, task);
}
});
}

// Get path where images are stored for this task
Expand Down Expand Up @@ -154,7 +190,10 @@ module.exports = class Task{

// Deletes files and folders related to this task
cleanup(cb){
rmdir(this.getProjectFolderPath(), cb);
if (this.initialized) rmdir(this.getProjectFolderPath(), cb);
else this.onInitialize.push(() => {
rmdir(this.getProjectFolderPath(), cb);
});
}

setStatus(code, extra){
Expand Down Expand Up @@ -432,7 +471,15 @@ module.exports = class Task{

}

if (!this.skipPostProcessing) tasks.push(runPostProcessingScript());
// postprocess.sh is still here for legacy/backward compatibility
// purposes, but we might remove it in the future. The new logic
// instructs the processing engine to do the necessary processing
// of outputs without post processing steps (build EPT).
// We're leaving it here only for Linux/docker setups, but will not
// be triggered on Windows.
if (os.platform() !== "win32" && !this.skipPostProcessing){
tasks.push(runPostProcessingScript());
}

const taskOutputFile = path.join(this.getProjectFolderPath(), 'task_output.txt');
tasks.push(saveTaskOutput(taskOutputFile));
Expand Down Expand Up @@ -547,8 +594,13 @@ module.exports = class Task{

// Re-executes the task (by setting it's state back to QUEUED)
// Only tasks that have been canceled, completed or have failed can be restarted.
// unless they are being initialized, in which case we switch them back to running
restart(options, cb){
if ([statusCodes.CANCELED, statusCodes.FAILED, statusCodes.COMPLETED].indexOf(this.status.code) !== -1){
if (!this.initialized && this.status.code === statusCodes.CANCELED){
this.setStatus(statusCodes.RUNNING);
if (options !== undefined) this.options = options;
cb(null);
}else if ([statusCodes.CANCELED, statusCodes.FAILED, statusCodes.COMPLETED].indexOf(this.status.code) !== -1){
this.setStatus(statusCodes.QUEUED);
this.dateCreated = new Date().getTime();
this.dateStarted = 0;
Expand All @@ -571,7 +623,7 @@ module.exports = class Task{
processingTime: this.processingTime,
status: this.status,
options: this.options,
imagesCount: this.images.length,
imagesCount: this.images !== undefined ? this.images.length : this.imagesCountEstimate,
progress: this.progress
};
}
Expand Down
2 changes: 1 addition & 1 deletion libs/TaskManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class TaskManager{
// Finds the first QUEUED task.
findNextTaskToProcess(){
for (let uuid in this.tasks){
if (this.tasks[uuid].getStatus() === statusCodes.QUEUED){
if (this.tasks[uuid].getStatus() === statusCodes.QUEUED && this.tasks[uuid].initialized){
return this.tasks[uuid];
}
}
Expand Down
34 changes: 34 additions & 0 deletions libs/apps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
const fs = require('fs');
const path = require('path');

let sevenZ = "7z";
let unzip = "unzip";

if (fs.existsSync(path.join("apps", "7z", "7z.exe"))){
sevenZ = path.resolve(path.join("apps", "7z", "7z.exe"));
}

if (fs.existsSync(path.join("apps", "unzip", "unzip.exe"))){
unzip = path.resolve(path.join("apps", "unzip", "unzip.exe"));
}

module.exports = {
sevenZ, unzip
};
9 changes: 9 additions & 0 deletions libs/odmInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ module.exports = {
});
},

supportsOption: function(optName, cb){
this.getOptions((err, json) => {
if (err) cb(err);
else{
cb(null, !!json.find(opt => opt.name === optName));
}
});
},

getOptions: function(done){
if (odmOptions){
done(null, odmOptions);
Expand Down
Loading

0 comments on commit 4eb5e48

Please sign in to comment.