You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
591 lines
16 KiB
JavaScript
591 lines
16 KiB
JavaScript
|
|
|
|
import * as THREE from './lib/three.module.js';
|
|
import {globalObjectCategory} from './obj_cfg.js';
|
|
import {saveWorldList} from "./save.js"
|
|
import { intersect } from './util.js';
|
|
|
|
|
|
function Annotation(sceneMeta, world, frameInfo){
|
|
this.world = world;
|
|
this.data = this.world.data;
|
|
//this.coordinatesOffset = this.world.coordinatesOffset;
|
|
this.boxes_load_time = 0;
|
|
this.frameInfo = frameInfo;
|
|
|
|
|
|
this.modified = false;
|
|
this.setModified = function(){
|
|
this.modified=true;
|
|
|
|
if (pointsGlobalConfig.autoSave)
|
|
{
|
|
saveWorldList([this.world]);
|
|
}
|
|
};
|
|
this.resetModified = function(){this.modified=false;};
|
|
|
|
|
|
this.sort_boxes = function(){
|
|
this.boxes = this.boxes.sort(function(x,y){
|
|
return x.position.y - y.position.y;
|
|
});
|
|
};
|
|
this.findBoxByTrackId = function(id){
|
|
if (this.boxes){
|
|
let box = this.boxes.find(function(x){
|
|
return x.obj_track_id == id;
|
|
});
|
|
return box;
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
this.findIntersectedBoxes = function(box){
|
|
return this.boxes.filter(b=>b!=box).filter(b=>intersect(box, b));
|
|
};
|
|
|
|
this.preload = function(on_preload_finished){
|
|
this.on_preload_finished = on_preload_finished;
|
|
this.load_annotation((boxes)=>this.proc_annotation(boxes));
|
|
};
|
|
|
|
|
|
|
|
this.go_cmd_received = false;
|
|
this.webglScene = null;
|
|
this.on_go_finished = null;
|
|
this.go = function(webglScene, on_go_finished){
|
|
this.webglScene = webglScene;
|
|
|
|
if (this.preloaded){
|
|
|
|
//this.boxes.forEach(b=>this.webglScene.add(b));
|
|
if (this.data.cfg.color_obj != "no"){
|
|
this.color_boxes();
|
|
}
|
|
|
|
if (on_go_finished)
|
|
on_go_finished();
|
|
} else {
|
|
this.go_cmd_received = true;
|
|
this.on_go_finished = on_go_finished;
|
|
}
|
|
};
|
|
|
|
|
|
// internal funcs below
|
|
this._afterPreload = function(){
|
|
this.preloaded = true;
|
|
console.log("annotation preloaded");
|
|
|
|
if (this.on_preload_finished){
|
|
this.on_preload_finished();
|
|
}
|
|
if (this.go_cmd_received){
|
|
this.go(this.webglScene, this.on_go_finished);
|
|
}
|
|
};
|
|
|
|
|
|
this.unload = function(){
|
|
if (this.boxes){
|
|
this.boxes.forEach((b)=>{
|
|
//this.webglGroup.remove(b);
|
|
|
|
if (b.boxEditor)
|
|
b.boxEditor.detach();
|
|
});
|
|
}
|
|
};
|
|
|
|
this.deleteAll = function(){
|
|
this.remove_all_boxes();
|
|
};
|
|
this.boxToAnn = function(box){
|
|
let ann = {
|
|
psr: {
|
|
position:{
|
|
x: box.position.x,
|
|
y: box.position.y,
|
|
z: box.position.z,
|
|
},
|
|
scale:{
|
|
x: box.scale.x,
|
|
y: box.scale.y,
|
|
z: box.scale.z,
|
|
},
|
|
rotation:{
|
|
x:box.rotation.x,
|
|
y:box.rotation.y,
|
|
z:box.rotation.z,
|
|
},
|
|
},
|
|
obj_type: box.obj_type,
|
|
obj_id: String(box.obj_track_id),
|
|
obj_attr: box.obj_attr,
|
|
//vertices: vertices,
|
|
};
|
|
return ann;
|
|
};
|
|
|
|
this.toBoxAnnotations = function(){
|
|
let anns = this.boxes.map((b)=>{
|
|
//var vertices = psr_to_xyz(b.position, b.scale, b.rotation);
|
|
let ann = this.boxToAnn(b);
|
|
|
|
if (b.annotator)
|
|
ann.annotator = b.annotator;
|
|
|
|
if (b.follows)
|
|
ann.follows = b.follows;
|
|
return ann;
|
|
});
|
|
|
|
anns.sort((a,b)=>a.obj_id- b.obj_id);
|
|
|
|
return anns;
|
|
};
|
|
|
|
|
|
|
|
// to real-world position (no offset)
|
|
this.ann_to_vector_global = function(box) {
|
|
let posG = this.world.lidarPosToScene(box.position);
|
|
let rotG = this.world.lidarRotToScene(box.rotation);
|
|
|
|
return [
|
|
posG.x - this.world.coordinatesOffset[0], posG.y-this.world.coordinatesOffset[1], posG.z-this.world.coordinatesOffset[2],
|
|
rotG.x, rotG.y, rotG.z,
|
|
box.scale.x, box.scale.y, box.scale.z,
|
|
];
|
|
|
|
};
|
|
|
|
// real-world position to ann
|
|
this.vector_global_to_ann = function(v)
|
|
{
|
|
let posG = new THREE.Vector3(v[0]+this.world.coordinatesOffset[0],
|
|
v[1]+this.world.coordinatesOffset[1],
|
|
v[2]+this.world.coordinatesOffset[2]);
|
|
let rotG = new THREE.Euler(v[3],v[4],v[5]);
|
|
|
|
let rotL = this.world.sceneRotToLidar(rotG);
|
|
let posL = this.world.scenePosToLidar(posG);
|
|
|
|
return {
|
|
position: {x: posL.x, y: posL.y, z: posL.z},
|
|
rotation: {x: rotL.x, y: rotL.y, z: rotL.z},
|
|
scale: {x: v[6], y: v[7], z: v[8]}
|
|
};
|
|
|
|
};
|
|
|
|
|
|
// this.vector_to_ann = function(v){
|
|
// return {
|
|
// position:{
|
|
// x:v[0],// + this.coordinatesOffset[0],
|
|
// y:v[1],// + this.coordinatesOffset[1],
|
|
// z:v[2],// + this.coordinatesOffset[2],
|
|
// },
|
|
|
|
|
|
// rotation:{
|
|
// x:v[3],
|
|
// y:v[4],
|
|
// z:v[5],
|
|
// },
|
|
|
|
// scale:{
|
|
// x:v[6],
|
|
// y:v[7],
|
|
// z:v[8],
|
|
// },
|
|
|
|
// };
|
|
// };
|
|
|
|
this.remove_all_boxes = function(){
|
|
if (this.boxes){
|
|
this.boxes.forEach((b)=>{
|
|
this.webglGroup.remove(b);
|
|
this.world.data.dbg.free();
|
|
b.geometry.dispose();
|
|
b.material.dispose();
|
|
b.world = null;
|
|
b.boxEditor = null;
|
|
});
|
|
|
|
this.boxes = [];
|
|
}
|
|
else{
|
|
console.error("destroy empty world!")
|
|
}
|
|
};
|
|
|
|
this.new_bbox_cube=function(color){
|
|
|
|
var h = 0.5;
|
|
|
|
var body = [
|
|
//top
|
|
-h,h,h, h,h,h,
|
|
h,h,h, h,-h,h,
|
|
h,-h,h, -h,-h,h,
|
|
-h,-h,h, -h, h, h,
|
|
|
|
//botom
|
|
-h,h,-h, h,h,-h,
|
|
h,h,-h, h,-h,-h,
|
|
h,-h,-h, -h,-h,-h,
|
|
-h,-h,-h, -h, h, -h,
|
|
|
|
// vertical lines
|
|
-h,h,h, -h,h,-h,
|
|
h,h,h, h,h,-h,
|
|
h,-h,h, h,-h,-h,
|
|
-h,-h,h, -h,-h,-h,
|
|
|
|
//direction
|
|
h, 0, h, 1.5*h, 0, h,
|
|
//h/2, -h, h+0.1, h, 0, h+0.1,
|
|
//h/2, h, h+0.1, h, 0, h+0.1,
|
|
|
|
//side direction
|
|
// h, h/2, h, h, h, 0,
|
|
// h, h/2, -h, h, h, 0,
|
|
// h, 0, 0, h, h, 0,
|
|
|
|
];
|
|
|
|
|
|
this.world.data.dbg.alloc();
|
|
|
|
var bbox = new THREE.BufferGeometry();
|
|
bbox.setAttribute( 'position', new THREE.Float32BufferAttribute(body, 3 ) );
|
|
|
|
if (!color){
|
|
color = 0x00ff00;
|
|
}
|
|
|
|
/*
|
|
https://threejs.org/docs/index.html#api/en/materials/LineBasicMaterial
|
|
linewidth is 1, regardless of set value.
|
|
*/
|
|
|
|
|
|
var material = new THREE.LineBasicMaterial( { color: color, linewidth: 1, opacity: this.data.cfg.box_opacity, transparent: true } );
|
|
var box = new THREE.LineSegments( bbox, material );
|
|
|
|
box.scale.x=1.8;
|
|
box.scale.y=4.5;
|
|
box.scale.z=1.5;
|
|
box.name="bbox";
|
|
box.obj_type="car";
|
|
|
|
//box.computeLineDistances();
|
|
|
|
|
|
|
|
return box;
|
|
};
|
|
|
|
this.createCuboid = function(pos, scale, rotation, obj_type, track_id, obj_attr){
|
|
let mesh = this.new_bbox_cube(parseInt("0x"+globalObjectCategory.get_obj_cfg_by_type(obj_type).color.slice(1)));
|
|
mesh.position.x = pos.x;
|
|
mesh.position.y = pos.y;
|
|
mesh.position.z = pos.z;
|
|
|
|
mesh.scale.x = scale.x;
|
|
mesh.scale.y = scale.y;
|
|
mesh.scale.z = scale.z;
|
|
|
|
mesh.rotation.x = rotation.x;
|
|
mesh.rotation.y = rotation.y;
|
|
mesh.rotation.z = rotation.z;
|
|
|
|
mesh.obj_track_id = track_id; //tracking id
|
|
mesh.obj_type = obj_type;
|
|
mesh.obj_attr = obj_attr;
|
|
mesh.obj_local_id = this.get_new_box_local_id();
|
|
|
|
mesh.world = this.world;
|
|
|
|
return mesh;
|
|
};
|
|
/*
|
|
pos: offset position, after transformed
|
|
*/
|
|
|
|
this.add_box=function(pos, scale, rotation, obj_type, track_id, obj_attr){
|
|
|
|
let mesh = this.createCuboid(pos, scale, rotation, obj_type, track_id, obj_attr)
|
|
|
|
this.boxes.push(mesh);
|
|
this.sort_boxes();
|
|
|
|
this.webglGroup.add(mesh);
|
|
|
|
return mesh;
|
|
};
|
|
|
|
this.load_box = function(box){
|
|
|
|
this.webglGroup.add(box);
|
|
};
|
|
|
|
this.unload_box = function(box){
|
|
|
|
this.webglGroup.remove(box);
|
|
};
|
|
|
|
this.remove_box=function(box){
|
|
this.world.data.dbg.free();
|
|
box.geometry.dispose();
|
|
box.material.dispose();
|
|
//selected_box.dispose();
|
|
this.boxes = this.boxes.filter(function(x){return x !=box;});
|
|
};
|
|
|
|
this.set_box_opacity=function(box_opacity){
|
|
this.boxes.forEach(function(x){
|
|
x.material.opacity = box_opacity;
|
|
});
|
|
};
|
|
|
|
this.translate_box_position=function(pos, theta, axis, delta){
|
|
switch (axis){
|
|
case 'x':
|
|
pos.x += delta*Math.cos(theta);
|
|
pos.y += delta*Math.sin(theta);
|
|
break;
|
|
case 'y':
|
|
pos.x += delta*Math.cos(Math.PI/2 + theta);
|
|
pos.y += delta*Math.sin(Math.PI/2 + theta);
|
|
break;
|
|
case 'z':
|
|
pos.z += delta;
|
|
break;
|
|
|
|
}
|
|
};
|
|
|
|
this.find_boxes_inside_rect = function(x,y,w,h, camera){
|
|
|
|
let selected_boxes_by_rect = [];
|
|
|
|
if (!this.boxes)
|
|
return selected_boxes_by_rect;
|
|
|
|
|
|
var p = new THREE.Vector3();
|
|
|
|
for (var i=0; i< this.boxes.length; i++){
|
|
let box_center = this.boxes[i].position;
|
|
|
|
let pw = this.world.lidarPosToScene(box_center);
|
|
p.set(pw.x, pw.y, pw.z);
|
|
p.project(camera);
|
|
p.x = p.x/p.z;
|
|
p.y = p.y/p.z;
|
|
//console.log(p);
|
|
if ((p.x > x) && (p.x < x+w) && (p.y>y) && (p.y<y+h)){
|
|
selected_boxes_by_rect.push(this.boxes[i]);
|
|
}
|
|
}
|
|
|
|
console.log("select boxes", selected_boxes_by_rect.length);
|
|
return selected_boxes_by_rect;
|
|
},
|
|
|
|
|
|
this.proc_annotation = function(boxes){
|
|
|
|
// boxes = this.transformBoxesByEgoPose(boxes);
|
|
// boxes = this.transformBoxesByOffset(boxes);
|
|
|
|
// //var boxes = JSON.parse(this.responseText);
|
|
//console.log(ret);
|
|
this.boxes = this.createBoxes(boxes); //create in future world
|
|
|
|
this.webglGroup = new THREE.Group();
|
|
this.webglGroup.name = "annotations";
|
|
this.boxes.forEach(b=>this.webglGroup.add(b));
|
|
|
|
this.world.webglGroup.add(this.webglGroup);
|
|
|
|
this.boxes_load_time = new Date().getTime();
|
|
console.log(this.boxes_load_time, this.frameInfo.scene, this.frameInfo.frame, "loaded boxes ", this.boxes_load_time - this.create_time, "ms");
|
|
|
|
this.sort_boxes();
|
|
|
|
this._afterPreload();
|
|
};
|
|
|
|
this.load_annotation=function(on_load){
|
|
if (this.data.cfg.disableLabels){
|
|
on_load([]);
|
|
}else {
|
|
var xhr = new XMLHttpRequest();
|
|
// we defined the xhr
|
|
var _self = this;
|
|
xhr.onreadystatechange = function () {
|
|
if (this.readyState != 4) return;
|
|
|
|
if (this.status == 200) {
|
|
let ann = _self.frameInfo.anno_to_boxes(this.responseText);
|
|
on_load(ann);
|
|
}
|
|
|
|
// end of state change: it can be after some time (async)
|
|
};
|
|
|
|
xhr.open('GET', "/load_annotation"+"?scene="+this.frameInfo.scene+"&frame="+this.frameInfo.frame, true);
|
|
xhr.send();
|
|
}
|
|
};
|
|
|
|
this.reloadAnnotation=function(done){
|
|
this.load_annotation(ann=>{
|
|
this.reapplyAnnotation(ann, done);
|
|
});
|
|
};
|
|
|
|
|
|
this.reapplyAnnotation = function(boxes, done){
|
|
// these boxes haven't attached a world
|
|
//boxes = this.transformBoxesByOffset(boxes);
|
|
|
|
// mark all old boxes
|
|
this.boxes.forEach(b=>{b.delete=true;});
|
|
|
|
let pendingBoxList=[];
|
|
|
|
boxes.forEach(nb=>{ // nb is annotation format, not a true box
|
|
let old_box = this.boxes.find(function(x){
|
|
return x.obj_track_id == nb.obj_id && x.obj_track_id != "" && nb.obj_id != "" && x.obj_type == nb.obj_type;;
|
|
});
|
|
|
|
if (old_box){
|
|
// found
|
|
// update psr
|
|
delete old_box.delete; // unmark delete flag
|
|
old_box.position.set(nb.psr.position.x, nb.psr.position.y, nb.psr.position.z);
|
|
old_box.scale.set(nb.psr.scale.x, nb.psr.scale.y, nb.psr.scale.z);
|
|
old_box.rotation.set(nb.psr.rotation.x, nb.psr.rotation.y, nb.psr.rotation.z);
|
|
old_box.obj_attr = nb.obj_attr;
|
|
old_box.annotator = nb.annotator;
|
|
old_box.changed=false; // clear changed flag.
|
|
|
|
}else{
|
|
// not found
|
|
let box=this.createOneBoxByAnn(nb);
|
|
pendingBoxList.push(box);
|
|
}
|
|
});
|
|
|
|
// delete removed
|
|
let toBeDelBoxes = this.boxes.filter(b=>b.delete);
|
|
toBeDelBoxes.forEach(b=>{
|
|
if (b.boxEditor){
|
|
|
|
b.boxEditor.detach("donthide");
|
|
}
|
|
|
|
this.webglGroup.remove(b);
|
|
|
|
this.remove_box(b);
|
|
})
|
|
|
|
pendingBoxList.forEach(b=>{
|
|
this.boxes.push(b);
|
|
})
|
|
|
|
|
|
//todo, restore point color
|
|
//todo, update imagecontext, selected box, ...
|
|
//refer to normal delete operation
|
|
// re-color again
|
|
this.world.lidar.recolor_all_points();
|
|
|
|
this.color_boxes();
|
|
|
|
// add new boxes
|
|
pendingBoxList.forEach(b=>{
|
|
this.webglGroup.add(b);
|
|
})
|
|
|
|
|
|
this.resetModified();
|
|
|
|
if (done)
|
|
done();
|
|
|
|
}
|
|
|
|
this.createOneBoxByAnn = function(annotation){
|
|
let b = annotation;
|
|
|
|
let mesh = this.createCuboid(b.psr.position,
|
|
b.psr.scale,
|
|
b.psr.rotation,
|
|
b.obj_type,
|
|
b.obj_id,
|
|
b.obj_attr);
|
|
|
|
if (b.annotator){
|
|
mesh.annotator = b.annotator;
|
|
}
|
|
|
|
if (b.follows)
|
|
mesh.follows = b.follows;
|
|
|
|
return mesh;
|
|
};
|
|
|
|
this.createBoxes = function(annotations){
|
|
return annotations.map((b)=>{
|
|
return this.createOneBoxByAnn(b);
|
|
});
|
|
};
|
|
|
|
|
|
this.box_local_id = 0;
|
|
this.get_new_box_local_id=function(){
|
|
var ret = this.box_local_id;
|
|
this.box_local_id+=1;
|
|
return ret;
|
|
};
|
|
|
|
|
|
this.color_box = function(box)
|
|
{
|
|
if (this.data.cfg.color_obj == "category" || this.data.cfg.color_obj == "no")
|
|
{
|
|
let color = globalObjectCategory.get_color_by_category(box.obj_type);
|
|
box.material.color.r=color.x;
|
|
box.material.color.g=color.y;
|
|
box.material.color.b=color.z;
|
|
}
|
|
else
|
|
{
|
|
|
|
let color = globalObjectCategory.get_color_by_id(box.obj_track_id);
|
|
box.material.color.r=color.x;
|
|
box.material.color.g=color.y;
|
|
box.material.color.b=color.z;
|
|
}
|
|
}
|
|
|
|
this.color_boxes = function()
|
|
{
|
|
this.boxes.forEach(box=>{
|
|
this.color_box(box);
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
export{Annotation} |