@openhps/openvslam 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +115 -2
- package/cc/config.cc +49 -0
- package/cc/config.h +24 -0
- package/cc/frame_publisher.cc +62 -0
- package/cc/frame_publisher.h +27 -0
- package/cc/graph_node.cc +78 -0
- package/cc/graph_node.h +26 -0
- package/cc/keyframe.cc +81 -0
- package/cc/keyframe.h +27 -0
- package/cc/landmark.cc +85 -0
- package/cc/landmark.h +26 -0
- package/cc/map_publisher.cc +119 -0
- package/cc/map_publisher.h +33 -0
- package/cc/openvslam.cc +26 -0
- package/cc/pangolin_viewer/viewer.h +26 -0
- package/cc/socket_publisher/publisher.cc +84 -0
- package/cc/socket_publisher/publisher.h +26 -0
- package/cc/system.cc +190 -0
- package/cc/system.h +47 -0
- package/dist/cjs/data/VSLAMFrame.js +44 -0
- package/dist/cjs/data/VSLAMFrame.js.map +1 -0
- package/dist/cjs/data/VSLAMGraphNode.js +7 -0
- package/dist/cjs/data/VSLAMGraphNode.js.map +1 -0
- package/dist/cjs/data/VSLAMKeyFrame.js +56 -0
- package/dist/cjs/data/VSLAMKeyFrame.js.map +1 -0
- package/dist/cjs/data/VSLAMLandmark.js +53 -0
- package/dist/cjs/data/VSLAMLandmark.js.map +1 -0
- package/dist/cjs/data/VSLAMMap.js +36 -0
- package/dist/cjs/data/VSLAMMap.js.map +1 -0
- package/dist/cjs/data/index.js +8 -0
- package/dist/cjs/data/index.js.map +1 -0
- package/dist/cjs/index.js +9 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/nodes/VSLAMConfiguration.js +43 -0
- package/dist/cjs/nodes/VSLAMConfiguration.js.map +1 -0
- package/dist/cjs/nodes/index.js +7 -0
- package/dist/cjs/nodes/index.js.map +1 -0
- package/dist/cjs/nodes/processing/VSLAMProcessingNode.js +124 -0
- package/dist/cjs/nodes/processing/VSLAMProcessingNode.js.map +1 -0
- package/dist/cjs/nodes/processing/index.js +5 -0
- package/dist/cjs/nodes/processing/index.js.map +1 -0
- package/dist/cjs/nodes/sink/VSLAMSocketPublisher.js +68 -0
- package/dist/cjs/nodes/sink/VSLAMSocketPublisher.js.map +1 -0
- package/dist/cjs/nodes/sink/index.js +5 -0
- package/dist/cjs/nodes/sink/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/service/VSLAMService.js +121 -0
- package/dist/cjs/service/VSLAMService.js.map +1 -0
- package/dist/cjs/service/index.js +5 -0
- package/dist/cjs/service/index.js.map +1 -0
- package/dist/cjs/tsconfig.bundle.cjs.tsbuildinfo +1 -0
- package/dist/esm/data/VSLAMFrame.js +41 -0
- package/dist/esm/data/VSLAMFrame.js.map +1 -0
- package/dist/esm/data/VSLAMGraphNode.js +3 -0
- package/dist/esm/data/VSLAMGraphNode.js.map +1 -0
- package/dist/esm/data/VSLAMKeyFrame.js +53 -0
- package/dist/esm/data/VSLAMKeyFrame.js.map +1 -0
- package/dist/esm/data/VSLAMLandmark.js +50 -0
- package/dist/esm/data/VSLAMLandmark.js.map +1 -0
- package/dist/esm/data/VSLAMMap.js +33 -0
- package/dist/esm/data/VSLAMMap.js.map +1 -0
- package/dist/esm/data/index.js +5 -0
- package/dist/esm/data/index.js.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/nodes/VSLAMConfiguration.js +39 -0
- package/dist/esm/nodes/VSLAMConfiguration.js.map +1 -0
- package/dist/esm/nodes/index.js +4 -0
- package/dist/esm/nodes/index.js.map +1 -0
- package/dist/esm/nodes/processing/VSLAMProcessingNode.js +120 -0
- package/dist/esm/nodes/processing/VSLAMProcessingNode.js.map +1 -0
- package/dist/esm/nodes/processing/index.js +2 -0
- package/dist/esm/nodes/processing/index.js.map +1 -0
- package/dist/esm/nodes/sink/VSLAMSocketPublisher.js +64 -0
- package/dist/esm/nodes/sink/VSLAMSocketPublisher.js.map +1 -0
- package/dist/esm/nodes/sink/index.js +2 -0
- package/dist/esm/nodes/sink/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/service/VSLAMService.js +117 -0
- package/dist/esm/service/VSLAMService.js.map +1 -0
- package/dist/esm/service/index.js +2 -0
- package/dist/esm/service/index.js.map +1 -0
- package/dist/esm/tsconfig.bundle.esm.tsbuildinfo +1 -0
- package/dist/esm/tsconfig.bundle.types.tsbuildinfo +1 -0
- package/dist/types/data/VSLAMFrame.d.ts +16 -0
- package/dist/types/data/VSLAMFrame.d.ts.map +1 -0
- package/dist/types/data/VSLAMGraphNode.d.ts +3 -0
- package/dist/types/data/VSLAMGraphNode.d.ts.map +1 -0
- package/dist/types/data/VSLAMKeyFrame.d.ts +28 -0
- package/dist/types/data/VSLAMKeyFrame.d.ts.map +1 -0
- package/dist/types/data/VSLAMLandmark.d.ts +20 -0
- package/dist/types/data/VSLAMLandmark.d.ts.map +1 -0
- package/dist/types/data/VSLAMMap.d.ts +13 -0
- package/dist/types/data/VSLAMMap.d.ts.map +1 -0
- package/dist/types/data/index.d.ts +5 -0
- package/dist/types/data/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/nodes/VSLAMConfiguration.d.ts +36 -0
- package/dist/types/nodes/VSLAMConfiguration.d.ts.map +1 -0
- package/dist/types/nodes/index.d.ts +4 -0
- package/dist/types/nodes/index.d.ts.map +1 -0
- package/dist/types/nodes/processing/VSLAMProcessingNode.d.ts +36 -0
- package/dist/types/nodes/processing/VSLAMProcessingNode.d.ts.map +1 -0
- package/dist/types/nodes/processing/index.d.ts +2 -0
- package/dist/types/nodes/processing/index.d.ts.map +1 -0
- package/dist/types/nodes/sink/VSLAMSocketPublisher.d.ts +17 -0
- package/dist/types/nodes/sink/VSLAMSocketPublisher.d.ts.map +1 -0
- package/dist/types/nodes/sink/index.d.ts +2 -0
- package/dist/types/nodes/sink/index.d.ts.map +1 -0
- package/dist/types/service/VSLAMService.d.ts +57 -0
- package/dist/types/service/VSLAMService.d.ts.map +1 -0
- package/dist/types/service/index.d.ts +2 -0
- package/dist/types/service/index.d.ts.map +1 -0
- package/install/install.js +85 -0
- package/install/parseEnv.js +7 -0
- package/package.json +125 -5
- package/src/data/VSLAMFrame.ts +48 -0
- package/src/data/VSLAMGraphNode.ts +1 -0
- package/src/data/VSLAMKeyFrame.ts +50 -0
- package/src/data/VSLAMLandmark.ts +42 -0
- package/src/data/VSLAMMap.ts +20 -0
- package/src/data/index.ts +4 -0
- package/src/index.ts +4 -0
- package/src/nodes/VSLAMConfiguration.ts +72 -0
- package/src/nodes/index.ts +3 -0
- package/src/nodes/processing/VSLAMProcessingNode.ts +152 -0
- package/src/nodes/processing/index.ts +1 -0
- package/src/nodes/sink/VSLAMSocketPublisher.ts +75 -0
- package/src/nodes/sink/index.ts +1 -0
- package/src/openvslam/index.d.ts +9 -0
- package/src/openvslam/index.js +8 -0
- package/src/openvslam/typings/Config.d.ts +3 -0
- package/src/openvslam/typings/FramePublisher.d.ts +12 -0
- package/src/openvslam/typings/GraphNode.d.ts +5 -0
- package/src/openvslam/typings/KeyFrame.d.ts +24 -0
- package/src/openvslam/typings/Landmark.d.ts +19 -0
- package/src/openvslam/typings/MapPublisher.d.ts +21 -0
- package/src/openvslam/typings/PangolinViewer.d.ts +9 -0
- package/src/openvslam/typings/SocketPublisher.d.ts +9 -0
- package/src/openvslam/typings/System.d.ts +45 -0
- package/src/service/VSLAMService.ts +130 -0
- package/src/service/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,126 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@openhps/openvslam",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Open Hybrid Positioning System - OpenVSLAM bindings",
|
|
5
|
+
"author": "Maxim Van de Wynckel",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/OpenHPS/openhps-openvslam.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/OpenHPS/openhps-openvslam/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://openhps.org",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"type": "commonjs",
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"main": "./dist/cjs/index.js",
|
|
21
|
+
"types": "./dist/types/index.d.ts",
|
|
22
|
+
"module": "./dist/esm/index.js",
|
|
23
|
+
"es2015": "./dist/esm/index.js",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/types/index.d.ts",
|
|
27
|
+
"import": "./dist/esm/index.js",
|
|
28
|
+
"require": "./dist/cjs/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/**/*",
|
|
34
|
+
"src/**/*",
|
|
35
|
+
"build/**/*",
|
|
36
|
+
"cc/**/*",
|
|
37
|
+
"install/**/*",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"install": "node ./install/install.js",
|
|
42
|
+
"test": "mocha --config test/.mocharc.json",
|
|
43
|
+
"test:docker": "docker-compose run openvslam-cpu /opt/app/docker/docker-entrypoint.sh",
|
|
44
|
+
"test:docker:quick": "docker-compose run openvslam-cpu /opt/app/docker/docker-entrypoint-quick.sh",
|
|
45
|
+
"cover": "c8 mocha --config test/.mocharc.json",
|
|
46
|
+
"cover:ci": "c8 mocha --config test/.mocharc.ci.json && c8 report --reporter=cobertura",
|
|
47
|
+
"lint": "eslint . && node scripts/check-peers.mjs",
|
|
48
|
+
"clean": "shx rm -rf ./dist && shx rm -rf ./docs/out",
|
|
49
|
+
"build": "npm run clean && npm-run-all --parallel build:ts:*",
|
|
50
|
+
"build:components": "componentsjs-generator",
|
|
51
|
+
"build:quick": "npm-run-all --parallel build:ts:*",
|
|
52
|
+
"build:typedoc": "typedoc --options docs/typedoc.json",
|
|
53
|
+
"build:ts:cjs": "tsc --build ./tsconfig/tsconfig.bundle.cjs.json",
|
|
54
|
+
"build:ts:esm": "tsc --build ./tsconfig/tsconfig.bundle.esm.json && node ./scripts/finalize-esm.mjs",
|
|
55
|
+
"build:ts:types": "tsc --build ./tsconfig/tsconfig.bundle.types.json",
|
|
56
|
+
"deploy:typedoc": "gh-pages -d ./docs/out",
|
|
57
|
+
"bump:development": "commit-and-tag-version --prerelease alpha -m 'chore(alpha): %s' --skip.changelog",
|
|
58
|
+
"bump:beta": "commit-and-tag-version --prerelease beta -m 'chore(beta): %s' --skip.changelog",
|
|
59
|
+
"bump:release": "commit-and-tag-version -m 'chore(release): %s' --skip.changelog",
|
|
60
|
+
"prepare": "husky"
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"openhps",
|
|
64
|
+
"openvslam"
|
|
65
|
+
],
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"@openhps/opencv": ">=0.3.6 <0.4",
|
|
68
|
+
"nan": "^2.30.0",
|
|
69
|
+
"native-node-utils": "^0.2.7",
|
|
70
|
+
"tslib": "^2.9.1",
|
|
71
|
+
"yaml": "^2.9.1"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"@openhps/core": ">=1.0.16 <2",
|
|
75
|
+
"@openhps/opencv": ">=0.3.6 <0.4",
|
|
76
|
+
"reflect-metadata": ">=0.2.2 <1"
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@commitlint/cli": "^21.2.4",
|
|
80
|
+
"@commitlint/config-conventional": "^21.2.4",
|
|
81
|
+
"@eslint/js": "^9.39.5",
|
|
82
|
+
"@openhps/core": "^1.0.16",
|
|
83
|
+
"@openhps/opencv": "^0.3.6",
|
|
84
|
+
"@openhps/video": "^1.0.3",
|
|
85
|
+
"@types/chai": "^5.2.2",
|
|
86
|
+
"@types/mocha": "^10.0.10",
|
|
87
|
+
"@types/node": "^26.6.6",
|
|
88
|
+
"c8": "^12.0.0",
|
|
89
|
+
"chai": "^6.3.0",
|
|
90
|
+
"commit-and-tag-version": "^13.3.0",
|
|
91
|
+
"eslint": "^9.39.5",
|
|
92
|
+
"eslint-config-prettier": "^10.1.8",
|
|
93
|
+
"eslint-plugin-import": "^2.32.0",
|
|
94
|
+
"eslint-plugin-jsdoc": "^65.4.5",
|
|
95
|
+
"eslint-plugin-prettier": "^5.5.7",
|
|
96
|
+
"globals": "^17.14.0",
|
|
97
|
+
"husky": "^9.1.7",
|
|
98
|
+
"jenkins-mocha": "^11.0.0",
|
|
99
|
+
"lint-staged": "^17.6.0",
|
|
100
|
+
"mocha": "^11.8.0",
|
|
101
|
+
"mocha-junit-reporter": "^2.2.1",
|
|
102
|
+
"mocha-multi-reporters": "^1.5.2",
|
|
103
|
+
"npm-run-all": "^4.1.5",
|
|
104
|
+
"prettier": "^3.10.0",
|
|
105
|
+
"reflect-metadata": ">=0.1.13",
|
|
106
|
+
"segfault-handler": "^1.3.0",
|
|
107
|
+
"shx": "^0.4.0",
|
|
108
|
+
"ts-node": "^10.9.2",
|
|
109
|
+
"typedoc": "^0.28.20",
|
|
110
|
+
"typescript": "^5.9.3",
|
|
111
|
+
"typescript-eslint": "^8.72.2"
|
|
112
|
+
},
|
|
113
|
+
"engines": {
|
|
114
|
+
"node": ">=22.0.0"
|
|
115
|
+
},
|
|
116
|
+
"packageManager": "npm@10.9.2",
|
|
117
|
+
"lsd:module": true,
|
|
118
|
+
"gypfile": true,
|
|
119
|
+
"husky": {
|
|
120
|
+
"hooks": {
|
|
121
|
+
"pre-commit": "npm run lint",
|
|
122
|
+
"pre-push": "npm run test",
|
|
123
|
+
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SerializableObject,
|
|
3
|
+
Matrix4,
|
|
4
|
+
LengthUnit,
|
|
5
|
+
Absolute3DPosition,
|
|
6
|
+
Orientation,
|
|
7
|
+
SerializableMember,
|
|
8
|
+
} from '@openhps/core';
|
|
9
|
+
import { VideoFrame } from '@openhps/opencv';
|
|
10
|
+
|
|
11
|
+
@SerializableObject()
|
|
12
|
+
export class VSLAMFrame extends VideoFrame {
|
|
13
|
+
@SerializableMember()
|
|
14
|
+
systemUID: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the camera pose at the time of the frame
|
|
18
|
+
* @returns {Matrix4} Camera pose as a matrix with translation and rotation
|
|
19
|
+
*/
|
|
20
|
+
get cameraPose(): Matrix4 {
|
|
21
|
+
if (this.source && this.source.getPosition()) {
|
|
22
|
+
const vector = this.source.getPosition().toVector3(LengthUnit.METER);
|
|
23
|
+
const matrix = new Matrix4().identity();
|
|
24
|
+
matrix.makeRotationFromQuaternion(this.source.getPosition().orientation);
|
|
25
|
+
matrix.setPosition(vector.x, vector.z, vector.y);
|
|
26
|
+
return matrix;
|
|
27
|
+
} else {
|
|
28
|
+
return new Matrix4().identity();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Set the camera at the time of the frame
|
|
34
|
+
* @param {Matrix4} pose Camera pose
|
|
35
|
+
*/
|
|
36
|
+
set cameraPose(pose: Matrix4) {
|
|
37
|
+
if (this.source) {
|
|
38
|
+
const position = new Absolute3DPosition(
|
|
39
|
+
pose.elements[12],
|
|
40
|
+
pose.elements[14],
|
|
41
|
+
pose.elements[13],
|
|
42
|
+
LengthUnit.METER,
|
|
43
|
+
);
|
|
44
|
+
position.orientation = Orientation.fromRotationMatrix(pose);
|
|
45
|
+
this.source.setPosition(position);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class VSLAMGraphNode {}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Matrix4, Quaternion, SerializableObject, TimeService, TimeUnit } from '@openhps/core';
|
|
2
|
+
import { KeyFrame } from '../openvslam';
|
|
3
|
+
import { VSLAMFrame } from './VSLAMFrame';
|
|
4
|
+
import { VSLAMLandmark } from './VSLAMLandmark';
|
|
5
|
+
|
|
6
|
+
@SerializableObject()
|
|
7
|
+
export class VSLAMKeyFrame extends VSLAMFrame {
|
|
8
|
+
willBeErased = false;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a VSLAM keyframe from native object
|
|
12
|
+
* @param {KeyFrame} keyframe Native object
|
|
13
|
+
* @returns {VSLAMKeyFrame} VSLAM keyframe instance
|
|
14
|
+
*/
|
|
15
|
+
static fromNative(keyframe: KeyFrame): VSLAMKeyFrame {
|
|
16
|
+
const mapKeyframe = new VSLAMKeyFrame();
|
|
17
|
+
const json = keyframe.toJSON();
|
|
18
|
+
mapKeyframe.uid = json.src_frm_id.toString();
|
|
19
|
+
mapKeyframe.createdTimestamp = TimeUnit.MILLISECOND.convert(json['ts'], TimeService.getUnit());
|
|
20
|
+
const pose = new Matrix4().identity();
|
|
21
|
+
pose.makeRotationFromQuaternion(Quaternion.fromRotationMatrix(new Matrix4().fromArray(json.rot_cw)));
|
|
22
|
+
pose.setPosition(json.trans_cw[0], json.trans_cw[1], json.trans_cw[2]);
|
|
23
|
+
mapKeyframe.cameraPose = pose;
|
|
24
|
+
return mapKeyframe;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get frame landmarks
|
|
29
|
+
* @returns {VSLAMLandmark[]} Array of landmarks found in this frame
|
|
30
|
+
*/
|
|
31
|
+
get landmarks(): VSLAMLandmark[] {
|
|
32
|
+
return this.getObjects(VSLAMLandmark);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Set frame landmarks
|
|
37
|
+
* @param {VSLAMLandmark[]} values Array of landmarks
|
|
38
|
+
*/
|
|
39
|
+
set landmarks(values: VSLAMLandmark[]) {
|
|
40
|
+
values.forEach(this.addObject.bind(this));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Add a landmark to the frame
|
|
45
|
+
* @param {VSLAMLandmark} landmark Landmark to add
|
|
46
|
+
*/
|
|
47
|
+
addLandmark(landmark: VSLAMLandmark): void {
|
|
48
|
+
this.addObject(landmark);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Absolute3DPosition, DataObject, LengthUnit, SerializableMember, SerializableObject } from '@openhps/core';
|
|
2
|
+
import { Landmark } from '../openvslam';
|
|
3
|
+
|
|
4
|
+
@SerializableObject()
|
|
5
|
+
export class VSLAMLandmark extends DataObject {
|
|
6
|
+
@SerializableMember()
|
|
7
|
+
keyframeUID: string;
|
|
8
|
+
@SerializableMember()
|
|
9
|
+
referenceKeyframeUID: string;
|
|
10
|
+
@SerializableMember()
|
|
11
|
+
observed: number;
|
|
12
|
+
@SerializableMember()
|
|
13
|
+
observable: number;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a VSLAM landmark from native object
|
|
17
|
+
* @param {Landmark} landmark Native object
|
|
18
|
+
* @returns {VSLAMLandmark} VSLAM landmark instance
|
|
19
|
+
*/
|
|
20
|
+
static fromNative(landmark: Landmark): VSLAMLandmark {
|
|
21
|
+
const mapLandmark = new VSLAMLandmark(landmark.id.toString());
|
|
22
|
+
const json = landmark.toJSON();
|
|
23
|
+
mapLandmark.keyframeUID = json['1st_keyfrm'].toString();
|
|
24
|
+
mapLandmark.referenceKeyframeUID = json['ref_keyfrm'].toString();
|
|
25
|
+
mapLandmark.observable = json['n_vis'];
|
|
26
|
+
mapLandmark.observed = json['n_fnd'];
|
|
27
|
+
if (json.pos_w) {
|
|
28
|
+
mapLandmark.setPosition(
|
|
29
|
+
new Absolute3DPosition(json.pos_w[0], json.pos_w[1], json.pos_w[2], LengthUnit.METER),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return mapLandmark;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convert the VSLAM landmark to native JSON serialization
|
|
37
|
+
* @returns {any} Native JSON serialization
|
|
38
|
+
*/
|
|
39
|
+
toNativeJSON(): any {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DataObject, SerializableObject, SerializableSetMember } from '@openhps/core';
|
|
2
|
+
import { VSLAMKeyFrame } from './VSLAMKeyFrame';
|
|
3
|
+
import { VSLAMLandmark } from './VSLAMLandmark';
|
|
4
|
+
|
|
5
|
+
@SerializableObject()
|
|
6
|
+
export class VSLAMMap extends DataObject {
|
|
7
|
+
@SerializableSetMember(VSLAMLandmark)
|
|
8
|
+
landmarks: Set<VSLAMLandmark> = new Set<VSLAMLandmark>();
|
|
9
|
+
@SerializableSetMember(VSLAMKeyFrame)
|
|
10
|
+
keyFrames: Set<VSLAMKeyFrame> = new Set<VSLAMKeyFrame>();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a VSLAM map from native object
|
|
14
|
+
* @returns {VSLAMMap} VSLAM map instance
|
|
15
|
+
*/
|
|
16
|
+
static fromNative(): VSLAMMap {
|
|
17
|
+
const map = new VSLAMMap();
|
|
18
|
+
return map;
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { CameraObject } from '@openhps/video';
|
|
2
|
+
import * as YAML from 'yaml';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
|
|
5
|
+
export class VSLAMConfiguration {
|
|
6
|
+
/**
|
|
7
|
+
* Camera object
|
|
8
|
+
*/
|
|
9
|
+
camera: CameraObject;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Convert the configuration to YAML string
|
|
13
|
+
* @returns {string} YAML output string
|
|
14
|
+
*/
|
|
15
|
+
toYAML(): string {
|
|
16
|
+
return YAML.stringify(this);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static fromYAML(yamlString: string): VSLAMConfiguration {
|
|
20
|
+
return VSLAMConfiguration.fromParsedYAML(YAML.parse(yamlString));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static fromYAMLFile(filePath: string): VSLAMConfiguration {
|
|
24
|
+
const file = fs.readFileSync(filePath, 'utf8');
|
|
25
|
+
return VSLAMConfiguration.fromParsedYAML(YAML.parse(file));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static fromParsedYAML(yaml: any): VSLAMConfiguration {
|
|
29
|
+
const instance = new VSLAMConfiguration();
|
|
30
|
+
if (yaml === null || typeof yaml !== 'object') {
|
|
31
|
+
return instance;
|
|
32
|
+
}
|
|
33
|
+
// Copy the properties without letting a malicious document alter the
|
|
34
|
+
// prototype chain (a `__proto__` key) of the instance
|
|
35
|
+
for (const key of Object.keys(yaml)) {
|
|
36
|
+
if (key === '__proto__') {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
Object.defineProperty(instance, key, {
|
|
40
|
+
value: yaml[key],
|
|
41
|
+
writable: true,
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return instance;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type CameraOptions = RadialDivisionCameraOptions | PerspectiveCameraOptions | FisheyeCameraOptions;
|
|
51
|
+
|
|
52
|
+
export interface RadialDivisionCameraOptions {
|
|
53
|
+
model: 'radial_division';
|
|
54
|
+
distortion: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface PerspectiveCameraOptions {
|
|
58
|
+
model: 'perspective';
|
|
59
|
+
k1: number;
|
|
60
|
+
k2: number;
|
|
61
|
+
p1: number;
|
|
62
|
+
p2: number;
|
|
63
|
+
k3: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface FisheyeCameraOptions {
|
|
67
|
+
model: 'fisheye';
|
|
68
|
+
k1: number;
|
|
69
|
+
k2: number;
|
|
70
|
+
k3: number;
|
|
71
|
+
k4: number;
|
|
72
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Matrix4, ProcessingNode, ProcessingNodeOptions, TimeService, TimeUnit } from '@openhps/core';
|
|
2
|
+
import { DepthImageFrame, StereoVideoFrame, VideoFrame } from '@openhps/opencv';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { VSLAMFrame } from '../../data';
|
|
5
|
+
import { VSLAMService } from '../../service/VSLAMService';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* VSLAM processing node is a node that takes in a video frame and uses
|
|
9
|
+
* OpenVSLAM to process the video frame.
|
|
10
|
+
*/
|
|
11
|
+
export class VSLAMProcessingNode<
|
|
12
|
+
In extends VideoFrame | StereoVideoFrame | DepthImageFrame,
|
|
13
|
+
Out extends VSLAMFrame,
|
|
14
|
+
> extends ProcessingNode<In, Out> {
|
|
15
|
+
protected options: VSLAMProcessingNodeOptions;
|
|
16
|
+
protected service: VSLAMService;
|
|
17
|
+
protected systems: string[] = [];
|
|
18
|
+
|
|
19
|
+
constructor(options?: VSLAMProcessingNodeOptions) {
|
|
20
|
+
super(options);
|
|
21
|
+
this.options.mapping = this.options.mapping === undefined ? true : this.options.mapping;
|
|
22
|
+
|
|
23
|
+
this.once('build', this._onBuild.bind(this));
|
|
24
|
+
this.once('destroy', this._onDestroy.bind(this));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private _onBuild(): Promise<void> {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
if (!this.model) {
|
|
30
|
+
return reject(new Error(`Unable to start VSLAM processing without VSLAMService!`));
|
|
31
|
+
}
|
|
32
|
+
this.service = this.model.findService(VSLAMService);
|
|
33
|
+
this.service
|
|
34
|
+
.createSystem(this.options.config)
|
|
35
|
+
.then((uid) => {
|
|
36
|
+
this.systems.push(uid);
|
|
37
|
+
return this.service.findSystemByUID(uid);
|
|
38
|
+
})
|
|
39
|
+
.then((system) => {
|
|
40
|
+
const mapDatabaseExists =
|
|
41
|
+
this.options.mapDatabaseFile !== undefined && fs.existsSync(this.options.mapDatabaseFile);
|
|
42
|
+
|
|
43
|
+
if (mapDatabaseExists) {
|
|
44
|
+
this.logger('debug', {
|
|
45
|
+
msg: `Loading OpenVSLAM map database ...`,
|
|
46
|
+
file: this.options.mapDatabaseFile,
|
|
47
|
+
});
|
|
48
|
+
system.loadMap(this.options.mapDatabaseFile);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
system.startup(!mapDatabaseExists);
|
|
52
|
+
|
|
53
|
+
// Global mapper
|
|
54
|
+
if (this.options.mapping) {
|
|
55
|
+
this.logger('debug', {
|
|
56
|
+
msg: `Enable mapping for OpenVSLAM`,
|
|
57
|
+
});
|
|
58
|
+
system.enableMapping();
|
|
59
|
+
} else {
|
|
60
|
+
this.logger('debug', {
|
|
61
|
+
msg: `Disabling mapping for OpenVSLAM`,
|
|
62
|
+
});
|
|
63
|
+
system.disableMapping();
|
|
64
|
+
}
|
|
65
|
+
resolve();
|
|
66
|
+
})
|
|
67
|
+
.catch(reject);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private _onDestroy(): Promise<void> {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const promises = this.systems.map((systemUID) => this.service.findSystemByUID(systemUID));
|
|
74
|
+
Promise.all(promises)
|
|
75
|
+
.then((systemInstances) => {
|
|
76
|
+
systemInstances.forEach((system) => {
|
|
77
|
+
if (this.options.mapDatabaseFile && this.options.mapping && this.options.persistMapping) {
|
|
78
|
+
// Save mapping
|
|
79
|
+
this.logger('debug', {
|
|
80
|
+
msg: `Saving OpenVSLAM map database ...`,
|
|
81
|
+
file: this.options.mapDatabaseFile,
|
|
82
|
+
});
|
|
83
|
+
system.saveMap(this.options.mapDatabaseFile);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// Delete and shutdown systems
|
|
87
|
+
return Promise.all(this.systems.map((systemUID) => this.service.deleteSystem(systemUID)));
|
|
88
|
+
})
|
|
89
|
+
.then(() => {
|
|
90
|
+
resolve();
|
|
91
|
+
})
|
|
92
|
+
.catch(reject);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
process(data: In): Promise<Out> {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
let uid = undefined;
|
|
99
|
+
this.service
|
|
100
|
+
.findSystemUIDByObject(data.source)
|
|
101
|
+
.then((systemUID) => {
|
|
102
|
+
uid = systemUID;
|
|
103
|
+
return this.service.findSystemByUID(uid);
|
|
104
|
+
})
|
|
105
|
+
.then((system) => {
|
|
106
|
+
if (!system) {
|
|
107
|
+
return resolve(undefined);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const out = new VSLAMFrame(data);
|
|
111
|
+
const timestamp = TimeService.getUnit().convert(data.phenomenonTimestamp, TimeUnit.SECOND);
|
|
112
|
+
// Process the frame differently depending on the type of video frame
|
|
113
|
+
if (data instanceof VideoFrame) {
|
|
114
|
+
out.image = data.image;
|
|
115
|
+
system.feedMonocularFrame(data.image, timestamp);
|
|
116
|
+
} else if (data instanceof StereoVideoFrame) {
|
|
117
|
+
out.image = data.left.image;
|
|
118
|
+
system.feedStereoFrame(data.left.image, data.right.image, timestamp);
|
|
119
|
+
} else if (data instanceof DepthImageFrame) {
|
|
120
|
+
out.image = data.image;
|
|
121
|
+
system.feedRGBDFrame(data.image, data.depth, timestamp);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Fetch the current camera pose
|
|
125
|
+
const pose = new Matrix4().fromArray(system.getMapPublisher().getCurrentCamPose());
|
|
126
|
+
out.cameraPose = pose;
|
|
127
|
+
out.systemUID = uid;
|
|
128
|
+
resolve(out as Out);
|
|
129
|
+
})
|
|
130
|
+
.catch(reject);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface VSLAMProcessingNodeOptions extends ProcessingNodeOptions {
|
|
136
|
+
/**
|
|
137
|
+
* Enable mapping and localization
|
|
138
|
+
*/
|
|
139
|
+
mapping?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Configuration file
|
|
142
|
+
*/
|
|
143
|
+
config: string;
|
|
144
|
+
/**
|
|
145
|
+
* Map database file (MSG) for loading and storage
|
|
146
|
+
*/
|
|
147
|
+
mapDatabaseFile?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Persist the mapping to the map database file
|
|
150
|
+
*/
|
|
151
|
+
persistMapping?: boolean;
|
|
152
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './VSLAMProcessingNode';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PushOptions, SinkNode, SinkNodeOptions } from '@openhps/core';
|
|
2
|
+
import { VSLAMFrame } from '../../data';
|
|
3
|
+
import { SocketPublisher } from '../../openvslam';
|
|
4
|
+
import { VSLAMService } from '../../service/VSLAMService';
|
|
5
|
+
|
|
6
|
+
export class VSLAMSocketPublisher<In extends VSLAMFrame> extends SinkNode<In> {
|
|
7
|
+
protected options: VSLAMSocketViewerOptions;
|
|
8
|
+
protected service: VSLAMService;
|
|
9
|
+
protected publishers: Map<string, SocketPublisher> = new Map();
|
|
10
|
+
|
|
11
|
+
constructor(options?: VSLAMSocketViewerOptions) {
|
|
12
|
+
super(options);
|
|
13
|
+
this.options = options ?? {};
|
|
14
|
+
this.options.url = this.options.url ?? 'http://localhost:3000';
|
|
15
|
+
|
|
16
|
+
this.once('build', this._onBuild.bind(this));
|
|
17
|
+
this.once('destroy', this._onDestroy.bind(this));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private _onBuild(): Promise<void> {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
if (!this.model) {
|
|
23
|
+
return reject(new Error(`Unable to start VSLAM processing without VSLAMService!`));
|
|
24
|
+
}
|
|
25
|
+
this.service = this.model.findService(VSLAMService);
|
|
26
|
+
if (!this.service) {
|
|
27
|
+
return reject(new Error(`Unable to start VSLAM socket publisher without VSLAMService!`));
|
|
28
|
+
}
|
|
29
|
+
resolve();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private _onDestroy(): Promise<void> {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
this.publishers.forEach((publisher) => {
|
|
36
|
+
publisher.terminate();
|
|
37
|
+
});
|
|
38
|
+
resolve();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
onPush(frame: In, options?: PushOptions): Promise<void> {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
if (!frame.systemUID) {
|
|
45
|
+
return resolve();
|
|
46
|
+
}
|
|
47
|
+
const existingPublisher = this.publishers.get(frame.systemUID);
|
|
48
|
+
if (existingPublisher) {
|
|
49
|
+
return resolve();
|
|
50
|
+
}
|
|
51
|
+
this.service
|
|
52
|
+
.findSystemByUID(frame.systemUID)
|
|
53
|
+
.then((system) => {
|
|
54
|
+
if (!system) {
|
|
55
|
+
return reject(new Error(`VSLAM system ${frame.systemUID} not found!`));
|
|
56
|
+
}
|
|
57
|
+
// A publisher may have been created by a concurrent push
|
|
58
|
+
if (this.publishers.has(frame.systemUID)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const publisher = new SocketPublisher(system, this.options.url);
|
|
62
|
+
publisher.run();
|
|
63
|
+
this.publishers.set(frame.systemUID, publisher);
|
|
64
|
+
})
|
|
65
|
+
.then(() => {
|
|
66
|
+
resolve();
|
|
67
|
+
})
|
|
68
|
+
.catch(reject);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface VSLAMSocketViewerOptions extends SinkNodeOptions {
|
|
74
|
+
url?: string;
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './VSLAMSocketPublisher';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './typings/Config';
|
|
2
|
+
export * from './typings/FramePublisher';
|
|
3
|
+
export * from './typings/MapPublisher';
|
|
4
|
+
export * from './typings/System';
|
|
5
|
+
export * from './typings/Landmark';
|
|
6
|
+
export * from './typings/KeyFrame';
|
|
7
|
+
export * from './typings/GraphNode';
|
|
8
|
+
export * from './typings/SocketPublisher';
|
|
9
|
+
export * from './typings/PangolinViewer';
|