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.

1839 lines
53 KiB
JavaScript

import {ProjectiveViewOps} from "./side_view_op.js"
import {BoxImageContext} from "./image.js";
import {saveWorldList, reloadWorldList} from "./save.js"
import {objIdManager} from "./obj_id_list.js"
import { globalKeyDownManager } from "./keydown_manager.js";
import{ml} from "./ml.js";
import { BooleanKeyframeTrack } from "./lib/three.module.js";
import { checkScene } from "./error_check.js";
import { logger } from "./log.js";
/*
2 ways to attach and edit a box
1) attach/detach
2) setTarget, tryAttach, resetTarget, this is only for batch-editor-manager
*/
function BoxEditor(parentUi, boxEditorManager, viewManager, cfg, boxOp,
func_on_box_changed, func_on_box_remove, name){
this.boxEditorManager = boxEditorManager;
this.parentUi = parentUi;
this.name=name;
let uiTmpl = document.getElementById("box-editor-ui-template");
let tmpui = uiTmpl.content.cloneNode(true); //sub-views
parentUi.appendChild(tmpui);
this.ui = parentUi.lastElementChild;
this.boxInfoUi = this.ui.querySelector("#box-info");
this.viewManager = viewManager;
this.boxOp = boxOp;
this.boxView = this.viewManager.addBoxView(this.ui); //this.editorUi.querySelector("#sub-views")
this.projectiveViewOps = new ProjectiveViewOps(
this.ui, //this.editorUi.querySelector("#sub-views"),
cfg,
this,
this.boxView.views,
this.boxOp,
func_on_box_changed,
func_on_box_remove);
this.focusImageContext = new BoxImageContext(this.ui.querySelector("#focuscanvas"));
this.pseudoBox = {
position: {x: 0, y: 0, z: 0},
rotation: {x: 0, y: 0, z: 0},
scale: {x: 1, y: 1, z: 1},
};
this.copyPseudoBox = function(b)
{
this.pseudoBox.position.x = b.position.x;
this.pseudoBox.position.y = b.position.y;
this.pseudoBox.position.z = b.position.z;
this.pseudoBox.rotation.x = b.rotation.x;
this.pseudoBox.rotation.y = b.rotation.y;
this.pseudoBox.rotation.z = b.rotation.z;
this.pseudoBox.scale.x = b.scale.x;
this.pseudoBox.scale.y = b.scale.y;
this.pseudoBox.scale.z = b.scale.z;
};
this.isInBatchMode = function(){
return !!this.boxEditorManager;
}
this.target = {};
this.setTarget = function(world, objTrackId, objType){
this.target = {
world: world,
objTrackId: objTrackId,
objType: objType,
}
if (this.isInBatchMode()){
this.pseudoBox.world = world;
this.boxView.attachBox(this.pseudoBox);
}
this.tryAttach();
this.ui.style.display="inline-block";
this.updateInfo();
};
this.setIndex = function(index){
this.index = index; // index as of in all editors.
};
this.setSelected = function(selected, eventId){
if (selected)
{
this.ui.className = "selected";
this.selected = true;
this.selectEventId = eventId;
}
else
{
if (!eventId || (this.selectEventId == eventId))
{
// cancel only you selected.
this.ui.className = "";
this.selected = false;
this.selectEventId = null;
}
}
};
// this.onContextMenu = (event)=>{
// if (this.boxEditorManager) // there is no manager for box editor in main ui
// this.boxEditorManager.onContextMenu(event, this);
// };
// this.ui.oncontextmenu = this.onContextMenu;
this.resetTarget = function(){
if (this.target.world){
//unload if it's not the main world
// if (this.target.world !== this.target.world.data.world)
// this.target.world.unload();
}
this.detach();
this.target = {};
//this.ui.style.display="none";
};
this.tryAttach = function(){
// find target box, attach to me
if (this.target && this.target.world){
let box = this.target.world.annotation.findBoxByTrackId(this.target.objTrackId);
if (box){
this.attachBox(box);
}
}
};
/*
the projectiveView tiggers zoomratio changing event.
editormanager broaccasts it to all editors
*/
this._setViewZoomRatio = function(viewIndex, ratio){
this.boxView.views[viewIndex].zoom_ratio = ratio;
};
this.updateViewZoomRatio = function(viewIndex, ratio){
//this.upate();
if (this.boxEditorManager)
this.boxEditorManager.updateViewZoomRatio(viewIndex, ratio);
else{
this._setViewZoomRatio(viewIndex, ratio);
this.update();
//this.viewManager.render();
}
};
this.onOpCmd = function(cmd){
if (this.boxEditorManager)
this.boxEditorManager.onOpCmd(cmd, this);
else{
this.executeOpCmd(cmd);
}
};
this.executeOpCmd = function(cmd){
if (!this.box)
{
return;
}
if (cmd.op == "translate")
{
for (let axis in cmd.params.delta)
{
this.boxOp.translate_box(this.box, axis, cmd.params.delta[axis]);
//this.boxOp.translate_box(this.box, "y", delta.y);
}
func_on_box_changed(this.box);
}
}
this.box = null;
this.attachBox = function(box){
if (this.box && this.box !== box){
this.box.boxEditor=null;
console.log("detach box editor");
//todo de-highlight box
}
this.box = null;
this.show();
if (box){
box.boxEditor = this;
this.box=box;
//this.boxOp.highlightBox(box);
this.boxView.attachBox(box);
this.projectiveViewOps.attachBox(box);
this.focusImageContext.updateFocusedImageContext(box);
//this.update();
this.updateInfo();
// this.boxView.render();
if (this.isInBatchMode()){
this.boxEditorManager.onBoxChanged(this);
}
}
};
this.detach = function(dontHide){
if (this.box){
if (this.box.boxEditor === this){
this.box.boxEditor = null;
}
//this.boxOp.unhighlightBox(this.box);
//todo de-highlight box
this.projectiveViewOps.detach();
this.boxView.detach();
if (this.isInBatchMode()){
this.copyPseudoBox(this.box);
this.boxView.attachBox(this.pseudoBox);
}
this.focusImageContext.clear_canvas();
this.box = null;
}
if (!dontHide)
this.hide();
};
this.hide = function(){
this.ui.style.display="none";
// this is a hack, if we don't have manager, this is the main editor
// hide parent ui
// todo, add a pseudo manager, hide itself when child hide
if (!this.boxEditorManager){
this.parentUi.style.display="none";
}
}
this.show = function(){
this.ui.style.display="";//"inline-block";
if (!this.boxEditorManager){
this.parentUi.style.display="";
}
}
this.onBoxChanged=function(){
this.projectiveViewOps.update_view_handle();
this.focusImageContext.updateFocusedImageContext(this.box);
this.boxView.onBoxChanged();
// mark
delete this.box.annotator; // human annotator doesn't need a name
delete this.box.follows;
this.box.changed = true;
// don't mark world's change flag, for it's hard to clear it.
// inform boxEditorMgr to transfer annotation to other frames.
if (this.boxEditorManager)
this.boxEditorManager.onBoxChanged(this);
this.updateInfo();
//this.boxView.render();
};
this.onDelBox = function(){
let box = this.box;
this.detach("donthide");
};
// windowresize...
this.update = function(dontRender=false){
if (this.boxView){
this.boxView.onBoxChanged(dontRender);
// this.boxView.updateCameraRange(this.box);
// this.boxView.updateCameraPose(this.box);
// if (!dontRender)
// this.boxView.render();
}
// boxview should be updated for pseudobox.
if (this.box === null)
return;
this.projectiveViewOps.update_view_handle();
// this is not needed somtime
this.focusImageContext.updateFocusedImageContext(this.box);
// should we update info?
this.updateInfo();
};
this.updateInfo = function(){
let info = ""
if (this.target.world){
info += String(this.target.world.frameInfo.frame);
if (this.box && this.box.annotator)
info += ","+this.box.annotator;
// if (this.box && this.box.changed)
// info += " *";
}
this.boxInfoUi.innerHTML = info;
};
this.updateBoxDimension = function(){
};
this.resize = function(width, height)
{
// if (height + "px" == this.ui.style.height && width + "px" == this.ui.style.width)
// {
// return;
// }
this.ui.style.width = width + "px";
this.ui.style.height = height + "px";
this.boxView.render();
};
this.setResize = function(option){
this.ui.style.resize=option;
this.ui.style["z-index"] = "0";
if (option == 'both')
{
this.lastSize= {
width: 0,
height: 0,
};
this.resizeObserver = new ResizeObserver(elements=>{
let rect = elements[0].contentRect;
console.log("sub-views resized.", rect);
if (rect.height == 0 || rect.width == 0)
{
return;
}
if (rect.height != this.lastSize.height || rect.width != this.lastSize.width)
{
// viewManager will clear backgound
// so this render is effectiveless.
//this.boxView.render();
// save
if (this.boxEditorManager) // there is no manager for box editor in main ui
{
pointsGlobalConfig.setItem("batchModeSubviewSize", {width: rect.width, height: rect.height});
this.boxEditorManager.onSubViewsResize(rect.width, rect.height);
}
else{
this.boxView.render();
}
//save
this.lastSize.width = rect.width;
this.lastSize.height = rect.height;
}
});
this.resizeObserver.observe(this.ui);
}
}
}
//parentUi #batch-box-editor
function BoxEditorManager(parentUi, viewManager, objectTrackView,
cfg, boxOp, globalHeader, contextMenu, configMenu,
func_on_box_changed, func_on_box_remove, func_on_annotation_reloaded){
this.viewManager = viewManager;
this.objectTrackView = objectTrackView;
this.boxOp = boxOp;
this.activeIndex = 0;
this.editorList = [];
this.cfg = cfg;
this.globalHeader = globalHeader;
this.contextMenu = contextMenu;
this.parentUi = parentUi; //#batch-box-editor
this.boxEditorGroupUi = parentUi.querySelector("#batch-box-editor-group");
this.boxEditorHeaderUi = parentUi.querySelector("#batch-box-editor-header");
this.batchSize = cfg.batchModeInstNumber;
//this.configMenu = configMenu;
this.activeEditorList = function(){
return this.editorList.slice(0, this.activeIndex);
};
this.editingTarget = {
data: null,
sceneMeta: "",
objTrackId: "",
frame:"",
frameIndex: NaN,
};
this.onExit = null;
// frame specifies the center frame to edit
// this.parentUi.addEventListener("contextmenu", event=>{
// this.contextMenu.show("boxEditorManager", event.clientX, event.clientY, this);
// event.stopPropagation();
// event.preventDefault();
// })
this.onSubViewsResize = function(width, height)
{
this.viewManager.mainView.clearView();
this.editorList.forEach(e=>{
e.resize(width, height);
});
//this.viewManager.render();
};
this.calculateBestSubviewSize=function(batchSize)
{
let parentRect = this.parentUi.getBoundingClientRect();
let headerRect = this.boxEditorHeaderUi.getBoundingClientRect();
let editorsGroupRect = this.boxEditorGroupUi.getBoundingClientRect();
let availableHeight = parentRect.height - headerRect.height;
let availableWidth = parentRect.width;
if (availableHeight ==0 || availableWidth ==0)
{
this.batchSizeUpdated=true;
return;
}
let defaultBoxWidth=130;
let defaultBoxHeight=450;
let rows = 1;
let w = availableWidth/Math.ceil(batchSize/rows);
let h = availableHeight/rows;
let cost = Math.abs((w/h) - (defaultBoxWidth/defaultBoxHeight));
let minCost = cost;
let bestRows = rows;
while(true)
{
rows +=1;
let w = Math.floor(availableWidth/Math.ceil(batchSize/rows));
let h = Math.floor(availableHeight/rows);
let cost = Math.abs((w/h) - (defaultBoxWidth/defaultBoxHeight));
if (cost < minCost)
{
minCost = cost;
bestRows = rows;
}
else{
break;
}
}
//bestRows
pointsGlobalConfig.batchModeSubviewSize = {
width: Math.floor(availableWidth/Math.ceil(batchSize/bestRows)),
height: Math.floor(availableHeight/bestRows),
}
}
this.setBatchSize = function(batchSize)
{
this.calculateBestSubviewSize(batchSize);
this.batchSize = batchSize;
if (this.parentUi.style.display != "none")
{
this.edit( this.editingTarget.data,
this.editingTarget.sceneMeta,
this.editingTarget.frame,
this.editingTarget.objTrackId,
this.editingTarget.objType
);
}
};
this.onWindowResize = function()
{
this.setBatchSize(this.batchSize);
};
this.edit = function(data, sceneMeta, frame, objTrackId, objType, onExit){
this.show();
this.reset();
if (this.batchSizeUpdated)
{
this.batchSizeUpdated=false;
this.calculateBestSubviewSize(this.batchSize);
}
if (onExit){
// next/prev call will not update onExit
this.onExit = onExit;
}
let sceneName = sceneMeta.scene;
this.editingTarget.data = data;
this.editingTarget.sceneMeta = sceneMeta;
this.editingTarget.objTrackId = objTrackId;
this.editingTarget.objType = objType;
this.editingTarget.frame = frame;
// this.parentUi.querySelector("#object-track-id-editor").value=objTrackId;
// this.parentUi.querySelector("#object-category-selector").value=objType;
let centerIndex = sceneMeta.frames.findIndex(f=>f==frame);
this.editingTarget.frameIndex = centerIndex;
if (centerIndex < 0){
centerIndex = 0;
}
let startIndex = Math.max(0, centerIndex - this.batchSize/2);
if(startIndex > 0)
{
if (startIndex + this.batchSize > sceneMeta.frames.length)
{
startIndex -= startIndex + this.batchSize - sceneMeta.frames.length;
if (startIndex < 0)
{
startIndex = 0;
}
}
}
let frames = sceneMeta.frames.slice(startIndex, startIndex+this.batchSize);
//this.viewManager.mainView.clearView();
frames.forEach(async (frame, editorIndex)=>{
let world = await data.getWorld(sceneName, frame);
let editor = this.addEditor();
//editor.setTarget(world, objTrackId, objType);
editor.setIndex(editorIndex);
editor.resize(pointsGlobalConfig.batchModeSubviewSize.width, pointsGlobalConfig.batchModeSubviewSize.height);
if (this.editingTarget.frame == frame){
editor.setSelected(true);
}
data.activate_world(world,
()=>{
//editor.tryAttach();
editor.setTarget(world, objTrackId, objType);
//
//this.viewManager.render();
},
true);
});
// set obj selector
this.globalHeader.setObject(objTrackId);
};
this.onContextMenu = function(event, boxEditor)
{
this.firingBoxEditor = boxEditor;
if (boxEditor.selected)
{
// ok
}
else
{
this.getSelectedEditors().forEach(e=>e.setSelected(false));
boxEditor.setSelected(true);
}
this.contextMenu.show("boxEditor", event.clientX, event.clientY, this);
event.stopPropagation();
event.preventDefault();
};
this.parentUi.oncontextmenu = (event)=>{
let ed = this.getEditorByMousePosition(event.clientX, event.clientY);
this.onContextMenu(event, ed);
};
this.handleContextMenuKeydownEvent = function(event, menuPos)
{
switch(event.key){
case 's':
this.activeEditorList().forEach(e=>e.setSelected(true));
return true;
break;
case 'a':
this.autoAnnotateSelectedFrames();
break;
case 'f':
this.finalizeSelectedBoxes();
break;
case 'd':
this.deleteSelectedBoxes(menuPos);
break;
case 'e':
this.interpolateSelectedFrames();
break;
case 'g':
this.gotoThisFrame();
break;
case 't':
this.showTrajectory();
break;
default:
return true;
}
return false;
};
this.delayUpdateAutoGeneratedBoxesTimer = null;
this.updateAutoGeneratedBoxes = function(){
if (this.delayUpdateAutoGeneratedBoxesTimer)
{
clearTimeout(this.delayUpdateAutoGeneratedBoxesTimer)
}
this.delayUpdateAutoGeneratedBoxesTimer = setTimeout(async ()=>{
if (this.cfg.autoUpdateInterpolatedBoxes){
await this.updateInterpolatedBoxes();
}
await this.updatePseudoBoxes();
},
500);
};
this.updateInterpolatedBoxes = async function(){
let editorList = this.activeEditorList();
let applyIndList = editorList.map(e=>e.box&&e.box.annotator=="i");
let boxList = editorList.map(e=>e.box);
let worldList = editorList.map(e=>e.target.world);
await this.boxOp.interpolateAsync(worldList, boxList, applyIndList);
//this.activeEditorList().forEach(e=>e.tryAttach());
this.globalHeader.updateModifiedStatus();
//this.viewManager.render();
editorList.forEach(e=>{
if (e.box&&e.box.annotator=="i"){
e.boxView.onBoxChanged();
}
});
};
this.updatePseudoBoxes = async function(){
let editorList = this.activeEditorList();
let boxList = editorList.map(e=>e.box);
let anns = boxList.map(b=> b?b.world.annotation.ann_to_vector_global(b):null);
let ret = await ml.interpolate_annotation(anns);
editorList.forEach((e,i)=>{
if (!e.box){
let ann = e.target.world.annotation.vector_global_to_ann(ret[i]);
e.copyPseudoBox(ann);
e.boxView.onBoxChanged();
}
});
};
// manager
this.onBoxChanged = function(e){
this.updateAutoGeneratedBoxes();
//
};
let onBoxChangedInBatchMode = function(box)
{
if (box.boxEditor) // if in batch mode with less than 40 windows, some box don't have editor attached.
box.boxEditor.update(); //render.
box.world.annotation.setModified();
};
this.finalizeSelectedBoxes = function()
{
this.getSelectedEditors().forEach(e=>{
if (e.box){
if (e.box.annotator)
{
delete e.box.annotator;
func_on_box_changed(e.box);
//e.box.world.annotation.setModified();
e.updateInfo();
}
}
});
this.globalHeader.updateModifiedStatus();;
};
this.interpolateSelectedFrames = function(){
let applyIndList = this.activeEditorList().map(e=>false); //all shoud be applied.
let selectedEditors = this.getSelectedEditors();
// if interpolate only one box, remove it if exist.
// no matter who is the annotator.
if (selectedEditors.length == 1)
{
if (selectedEditors[0].box)
{
func_on_box_remove(selectedEditors[0].box, true);
}
}
selectedEditors.forEach(e=>applyIndList[e.index] = true);
this.interpolate(applyIndList);
this.updateAutoGeneratedBoxes();
};
this.deleteEmptyBoxes = function()
{
let editors = this.activeEditorList();
editors.forEach(e=>{
if (e.box)
{
if (e.box.world.lidar.get_box_points_number(e.box) == 0)
{
func_on_box_remove(e.box, true);
}
}
});
this.updateAutoGeneratedBoxes();
};
this.deleteIntersectedBoxes = function(){
let editors = this.getSelectedEditors();
editors.forEach(e=>{
if (e.box)
{
let boxes = e.box.world.annotation.findIntersectedBoxes(e.box);
boxes.forEach(b=>{
func_on_box_remove(b, true);
});
onBoxChangedInBatchMode(e.box);
}
});
};
this.deleteSelectedBoxes = function(infoBoxPos)
{
let selectedEditors = this.getSelectedEditors();
if (selectedEditors.length >= 2)
{
window.editor.infoBox.show(
"Confirm",
`Delete <span class="red">${selectedEditors.length}</span> selected boxes?`,
["yes", "no"],
(btn)=>{
if (btn == "yes")
{
selectedEditors.forEach(e=>{
if (e.box)
func_on_box_remove(e.box, true);
});
this.updateAutoGeneratedBoxes();
}
},
infoBoxPos
);
}
else{
selectedEditors.forEach(e=>{
if (e.box)
func_on_box_remove(e.box, true)
});
this.updateAutoGeneratedBoxes();
}
};
this.autoAnnotateSelectedFrames = function()
{
let applyIndList = this.activeEditorList().map(e=>false); //all shoud be applied.
this.getSelectedEditors().forEach(e=>applyIndList[e.index] = true);
this.autoAnnotate(applyIndList);
};
this.onOpCmd = function(cmd, firingEditor){
firingEditor.executeOpCmd(cmd);
if (this.cfg.linkEditorsInBatchMode)
{
let editors = this.getSelectedEditors();
if (editors.includes(firingEditor))
{
editors.filter(x=>x!=firingEditor).forEach(e=>{
if (e.box && !e.box.annotator)
{
e.executeOpCmd(cmd);
}
});
}
}
};
this.handleContextMenuEvent = function(event)
{
console.log(event.currentTarget.id, event.type);
switch(event.currentTarget.id)
{
// manager
case 'cm-increase-box-editor':
this.batchSize += 1;
this.edit(
this.editingTarget.data,
this.editingTarget.sceneMeta,
this.editingTarget.sceneMeta.frame,
this.editingTarget.objTrackId,
this.editingTarget.objType
);
break;
case 'cm-decrease-box-editor':
this.batchSize -= 1;
this.edit(
this.editingTarget.data,
this.editingTarget.sceneMeta,
this.editingTarget.sceneMeta.frame,
this.editingTarget.objTrackId,
this.editingTarget.objType
);
break;
/////////////////////// obj instance //
case 'cm-select-all':
this.activeEditorList().forEach(e=>e.setSelected(true));
return false;//don't hide context menu
break;
case 'cm-select-all-previous':
this.activeEditorList().forEach(e=> e.setSelected(e.index <= this.firingBoxEditor.index));
return false;//don't hide context menu
break;
case 'cm-select-all-next':
this.activeEditorList().forEach(e=> e.setSelected(e.index >= this.firingBoxEditor.index));
return false;//don't hide context menu
break
case 'cm-delete':
this.deleteSelectedBoxes( {x: event.clientX, y: event.clientY});
break;
case 'cm-delete-empty-boxes':
this.deleteEmptyBoxes();
break;
case 'cm-delete-intersected-boxes':
this.deleteIntersectedBoxes();
break;
case 'cm-interpolate':
this.interpolateSelectedFrames();
break;
case 'cm-auto-annotate':
this.autoAnnotateSelectedFrames();
break;
case 'cm-auto-annotate-wo-rotation':
{
let applyIndList = this.activeEditorList().map(e=>false); //all shoud be applied.
this.getSelectedEditors().forEach(e=>applyIndList[e.index] = true);
this.autoAnnotate(applyIndList, "dontrotate");
}
break;
case 'cm-fit-moving-direction':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
let currentBox = e.box;
let estimatedRot = boxOp.estimate_rotation_by_moving_direciton(currentBox);
if (estimatedRot){
currentBox.rotation.z = estimatedRot.z;
func_on_box_changed(currentBox);
}
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-fit-size':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
boxOp.fit_size(e.box, ['x','y']);
func_on_box_changed(e.box);
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-fit-position':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
boxOp.auto_rotate_xyz(e.box, null,
null,//{x:false, y:false, z:true},
func_on_box_changed, //onBoxChangedInBatchMode,
"noscaling", "dontrotate");
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-fit-rotation':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
boxOp.auto_rotate_xyz(e.box, null,
null,
func_on_box_changed,//onBoxChangedInBatchMode, //
"noscaling");
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-fit-bottom':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
boxOp.fit_bottom(e.box);
func_on_box_changed(e.box);
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-fit-top':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
boxOp.fit_top(e.box);
func_on_box_changed(e.box);
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-fit-left':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
boxOp.fit_left(e.box);
func_on_box_changed(e.box);
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-fit-right':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
boxOp.fit_right(e.box);
func_on_box_changed(e.box);
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-fit-rear':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
boxOp.fit_rear(e.box);
func_on_box_changed(e.box);
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-fit-front':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
boxOp.fit_front(e.box);
func_on_box_changed(e.box);
});
this.updateAutoGeneratedBoxes();
break;
case 'cm-reverse-direction':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
if (e.box.rotation.z > 0){
e.box.rotation.z -= Math.PI;
}else{
e.box.rotation.z += Math.PI;
}
onBoxChangedInBatchMode(e.box);
});
//this.viewManager.render();
this.updateAutoGeneratedBoxes();
break;
case 'cm-reset-roll-pitch':
this.getSelectedEditors().forEach(e=>{
if (!e.box)
return;
e.box.rotation.x =0;
e.box.rotation.y =0;
e.update('dontrender');
e.box.world.annotation.setModified();
onBoxChangedInBatchMode(e.box);
});
//this.viewManager.render();
this.updateAutoGeneratedBoxes();
break;
case 'cm-show-trajectory':
this.showTrajectory();
break;
case 'cm-check':
{
let scene = this.editingTarget.sceneMeta.scene;
checkScene(scene);
logger.show();
logger.errorBtn.onclick();
}
break;
case 'cm-finalize':
this.finalizeSelectedBoxes();
break;
case 'cm-sync-size':
editor.data.worldList.forEach(w=>{
let box = w.annotation.boxes.find(b=>b.obj_track_id == this.firingBoxEditor.target.objTrackId);
if (box && box !== this.firingBoxEditor.box){
box.scale.x = this.firingBoxEditor.box.scale.x;
box.scale.y = this.firingBoxEditor.box.scale.y;
box.scale.z = this.firingBoxEditor.box.scale.z;
//saveList.push(w);
w.annotation.setModified();
onBoxChangedInBatchMode(box);
}
});
//this.activeEditorList().forEach(e=>e.update('dontrender'));
//this.viewManager.render();
this.updateAutoGeneratedBoxes();
break;
case 'cm-reload':
{
let selectedEditors = this.getSelectedEditors();
this.reloadAnnotation(selectedEditors);
this.updateAutoGeneratedBoxes();
}
break;
case 'cm-goto-this-frame':
{
this.gotoThisFrame();
}
break;
case 'cm-follow-static-objects':
{
let b = this.firingBoxEditor.box;
editor.autoAdjust.followStaticObjects(b);
this.globalHeader.updateModifiedStatus();
this.activeEditorList().forEach(e=>{
e.tryAttach();
});
//this.viewManager.render();
this.updateAutoGeneratedBoxes();
}
break;
};
return true;
};
this.reset = function(){
this.activeEditorList().forEach(e=>{
e.setSelected(false);
e.resetTarget();
});
this.viewManager.mainView.clearView();
this.activeIndex = 0;
};
this.keydownHandler = (event)=>{
switch(event.key){
case 'a':
if (event.ctrlKey){
this.activeEditorList().forEach(e=>e.setSelected(true));
}
break;
case 's':
if (event.ctrlKey){
this._save();
console.log("saved for batch editor");
}
break;
case '+':
case '=':
this.editingTarget.data.scale_point_size(1.2);
this.viewManager.render();
break;
case '-':
this.editingTarget.data.scale_point_size(0.8);
this.viewManager.render();
break;
case 'v':
case 'Escape':
{
// let selected = this.getSelectedEditors();
// if (selected.length >= 2){
// selected.forEach(e=>e.setSelected(false));
// }
// else
{
this.hide();
this.reset();
if (this.onExit)
this.onExit();
}
}
break;
case 'PageUp':
case '3':
this.prevBatch();
break;
case 'PageDown':
case '4':
this.nextBatch();
break;
case 't':
this.showTrajectory();
break;
default:
console.log(`key ${event.key} igonored`);
break;
}
return false;
};
let keydownHandler = (event)=>this.keydownHandler(event);
this.hide =function(){
if (this.parentUi.style.display != "none")
{
this.parentUi.style.display = "none";
this.toolbox.style.display = "none";
//document.removeEventListener("keydown", keydownHandler);
globalKeyDownManager.deregister('batch-editor');
}
};
this.show = function(){
if (this.parentUi.style.display == "none")
{
this.parentUi.style.display = "";
//document.addEventListener("keydown", keydownHandler);
globalKeyDownManager.register(keydownHandler, 'batch-editor');
this.toolbox.style.display = "";
}
};
this.render =function()
{
if (this.parentUi.style.display != "none")
{
this.viewManager.render();
}
};
this._addToolBox = function(){
let template = document.getElementById("batch-editor-tools-template");
let tool = template.content.cloneNode(true);
// this.boxEditorHeaderUi.appendChild(tool);
// return this.boxEditorHeaderUi.lastElementChild;
document.getElementById("dynamic-buttons-placeholder").appendChild(tool);
return document.getElementById("dynamic-buttons-placeholder").lastElementChild;
};
this.toolbox = this._addToolBox();
this.reloadAnnotation = function(editorList){
let done = (anns)=>{
// update editor
editorList.forEach(e=>{
e.tryAttach();
e.update("dontrender");
});
// reload main view
if (func_on_annotation_reloaded)
func_on_annotation_reloaded();
// render all, at last
this.viewManager.render();
this.globalHeader.updateModifiedStatus();
};
let worldList = editorList.map(e=>e.target.world);
let modifiedFrames =worldList.filter(w=>w && w.annotation.modified);
if (modifiedFrames.length > 0)
{
window.editor.infoBox.show(
"Confirm",
`Discard changes to ${modifiedFrames.length} frames, continue to reload?`,
["yes","no"],
(choice)=>{
if (choice=="yes")
{
reloadWorldList(worldList, done);
}
}
);
}
else{
reloadWorldList(worldList, done);
}
}
this.interpolate = async function(applyIndList){
let boxList = this.activeEditorList().map(e=>e.box);
let worldList = this.activeEditorList().map(e=>e.target.world);
await this.boxOp.interpolateAsync(worldList, boxList, applyIndList);
this.activeEditorList().forEach(e=>e.tryAttach());
this.globalHeader.updateModifiedStatus();
this.viewManager.render();
};
this.gotoThisFrame = function(){
let targetFrame = this.firingBoxEditor.target.world.frameInfo.frame;
let targetTrackId = this.firingBoxEditor.target.objTrackId;
this.hide();
this.reset();
if (this.onExit)
this.onExit(targetFrame, targetTrackId);
};
this.autoAnnotate = async function(applyIndList, dontRotate){
let editors = this.activeEditorList();
let boxList = editors.map(e=>e.box);
let worldList = editors.map(e=>e.target.world);
let onFinishOneBox = (i)=>{
editors[i].tryAttach();
editors[i].box.world.annotation.setModified();
this.viewManager.render();
this.updateAutoGeneratedBoxes();
}
await this.boxOp.interpolateAndAutoAdjustAsync(worldList, boxList, onFinishOneBox, applyIndList, dontRotate);
this.globalHeader.updateModifiedStatus();
}
// this.parentUi.querySelector("#object-track-id-editor").addEventListener("keydown", function(e){
// e.stopPropagation();});
// this.parentUi.querySelector("#object-track-id-editor").addEventListener("keyup", function(e){
// e.stopPropagation();
// });
// this.parentUi.querySelector("#object-track-id-editor").onchange = (ev)=>this.object_track_id_changed(ev);
// this.parentUi.querySelector("#object-category-selector").onchange = (ev)=>this.object_category_changed(ev);
// this should follow addToolBox
// this.parentUi.querySelector("#instance-number").value = this.batchSize;
// this.parentUi.querySelector("#instance-number").onchange = (ev)=>{
// this.batchSize = parseInt(ev.currentTarget.value);
// this.edit(
// this.editingTarget.data,
// this.editingTarget.sceneMeta,
// this.editingTarget.frame,
// this.editingTarget.objTrackId,
// this.editingTarget.objType
// );
// }
this.showTrajectory = () =>{
let tracks = this.editingTarget.data.worldList.map(w=>{
let box = w.annotation.findBoxByTrackId(this.editingTarget.objTrackId);
let ann = null;
if (box){
ann = w.annotation.boxToAnn(box);
ann.psr.position = w.lidarPosToUtm(ann.psr.position);
ann.psr.rotation = w.lidarRotToUtm(ann.psr.rotation);
}
return [w.frameInfo.frame, ann, false];
});
tracks.sort((a,b)=> (a[0] > b[0])? 1 : -1);
this.objectTrackView.setObject(
this.editingTarget.objType,
this.editingTarget.objTrackId,
tracks,
(targetFrame)=>{ //onExit
this.getSelectedEditors().forEach(e=>e.setSelected(false));
this.activeEditorList().find(e=>e.target.world.frameInfo.frame == targetFrame).setSelected(true);
}
);
};
this.toolbox.querySelector("#trajectory").onclick = (e)=>{
this.showTrajectory();
};
this.toolbox.querySelector("#reload").onclick = (e)=>{
let selectedEditors = this.activeEditorList();
this.reloadAnnotation(selectedEditors);
};
this.toolbox.querySelector("#interpolate").onclick = async ()=>{
//this.boxOp.interpolate_selected_object(this.editingTarget.scene, this.editingTarget.objTrackId, "");
let applyIndList = this.activeEditorList().map(e=>true); //all shoud be applied.
this.interpolate(applyIndList);
};
this.toolbox.querySelector("#auto-annotate").onclick = async ()=>{
let applyIndList = this.activeEditorList().map(e=>true); //all shoud be applied.
this.autoAnnotate(applyIndList);
};
this.toolbox.querySelector("#auto-annotate-translate-only").onclick = async ()=>{
let applyIndList = this.activeEditorList().map(e=>true); //all shoud be applied.
this.autoAnnotate(applyIndList, "dontrotate");
};
this.toolbox.querySelector("#exit").onclick = ()=>{
this.hide();
this.reset();
if (this.onExit)
this.onExit();
};
this.toolbox.querySelector("#next").onclick = ()=>{
this.nextBatch();
};
this.toolbox.querySelector("#prev").onclick = ()=>{
this.prevBatch();
};
this.nextBatch = function()
{
let maxFrameIndex = this.editingTarget.sceneMeta.frames.length-1;
let editors = this.activeEditorList()
let lastEditor = editors[editors.length-1];
if (lastEditor.target.world.frameInfo.frame_index == maxFrameIndex)
{
if (this.batchSize >= this.editingTarget.sceneMeta.frames.length)
{
this.nextObj();
}
else
{
window.editor.infoBox.show("Info", "This is the last batch of frames.");
}
}
else
{
this.edit(
this.editingTarget.data,
this.editingTarget.sceneMeta,
this.editingTarget.sceneMeta.frames[Math.min(this.editingTarget.frameIndex + this.batchSize/2, maxFrameIndex)],
this.editingTarget.objTrackId,
this.editingTarget.objType
);
}
};
this.prevBatch = function()
{
let firstEditor = this.activeEditorList()[0];
if (firstEditor.target.world.frameInfo.frame_index == 0)
{
if (this.batchSize >= this.editingTarget.sceneMeta.frames.length)
{
this.prevObj();
}
else
{
window.editor.infoBox.show("Info", "This is the first batch of frames");
}
}
else
{
this.edit(
this.editingTarget.data,
this.editingTarget.sceneMeta,
this.editingTarget.sceneMeta.frames[Math.max(this.editingTarget.frameIndex - this.batchSize/2, 0)],
this.editingTarget.objTrackId,
this.editingTarget.objType
);
}
};
this.prevObj = function(){
let idx = objIdManager.objectList.findIndex(x=>x.id==this.editingTarget.objTrackId);
let objNum = objIdManager.objectList.length;
idx = (idx + objNum - 1) % objNum;
let obj = objIdManager.objectList[idx];
this.edit(
this.editingTarget.data,
this.editingTarget.sceneMeta,
this.editingTarget.sceneMeta.frames[this.editingTarget.frameIndex],
obj.id,
obj.category,
);
};
this.gotoFrame = function(frameID){
this.getSelectedEditors().forEach(e=>e.setSelected(false));
this.activeEditorList().find(e=>e.target.world.frameInfo.frame == frameID).setSelected(true);
}
this.gotoObjectFrame = function(frameId, objId){
if (objId != this.editingTarget.objTrackId)
{
let obj = objIdManager.getObjById(objId);
this.edit(
this.editingTarget.data,
this.editingTarget.sceneMeta,
frameId,
objId,
obj.category,
);
}
this.getSelectedEditors().forEach(e=>e.setSelected(false));
this.activeEditorList().find(e=>e.target.world.frameInfo.frame == frameId).setSelected(true);
}
this.nextObj = function(){
let idx = objIdManager.objectList.findIndex(x=>x.id==this.editingTarget.objTrackId && x.category == this.editingTarget.objType);
let objNum = objIdManager.objectList.length;
idx = (idx + 1) % objNum;
let obj = objIdManager.objectList[idx];
this.edit(
this.editingTarget.data,
this.editingTarget.sceneMeta,
this.editingTarget.sceneMeta.frames[this.editingTarget.frameIndex],
obj.id,
obj.category,
);
};
// this.toolbox.querySelector("#save").onclick = ()=>{
// this._save();
// };
this.toolbox.querySelector("#finalize").onclick = ()=>{
this.finalize();
};
this.finalize = function(){
this.activeEditorList().forEach(e=>{
if (e.box){
if (e.box.annotator){
delete e.box.annotator;
func_on_box_changed(e.box);
}
e.box.world.annotation.setModified();
e.updateInfo();
}
});
this.globalHeader.updateModifiedStatus();
};
this.object_track_id_changed = function(event){
var id = event.currentTarget.value;
if (id == "new"){
id = objIdManager.generateNewUniqueId();
this.parentUi.querySelector("#object-track-id-editor").value=id;
}
this.activeEditorList().forEach(e=>{
if (e.box){
e.box.obj_track_id = id;
}
});
};
this.object_category_changed = function(event){
let obj_type = event.currentTarget.value;
this.activeEditorList().forEach(e=>{
if (e.box){
e.box.obj_type = obj_type;
}
});
};
this._save = function(){
let worldList = []
let editorList = []
this.activeEditorList().forEach(e=>{
worldList.push(e.target.world);
editorList.push(e);
});
saveWorldList(worldList);
}
this.updateViewZoomRatio = function(viewIndex, ratio){
const dontRender=true;
this.activeEditorList().forEach(e=>{
e._setViewZoomRatio(viewIndex, ratio);
e.update(dontRender);
})
// render all
this.viewManager.render();
}
this.addEditor = function(){
let editor = this.allocateEditor();
this.activeIndex += 1;
return editor;
};
this.allocateEditor = function(){
if (this.activeIndex >= this.editorList.length){
let editor = new BoxEditor(this.boxEditorGroupUi, this, this.viewManager, cfg, this.boxOp, func_on_box_changed, func_on_box_remove, String(this.activeIndex));
// resizable for the first editor
if (this.editorList.length == 0)
{
editor.setResize("both");
}
this.editorList.push(editor);
return editor;
}else{
return this.editorList[this.activeIndex];
}
};
this.getEditorByMousePosition = function(x,y){
return this.editorList.find(e=>{
let rect = e.ui.getBoundingClientRect();
return x > rect.left && x < rect.right && y > rect.top && y < rect.bottom;
})
};
this.parentUi.onmousedown= (event)=>{
if (event.which!=1)
return;
let eventId = Date.now();
let select_start_pos={
x: event.clientX,
y: event.clientY,
}
console.log("box editor manager, on mouse down.", select_start_pos);
let select_end_pos={
x: event.clientX,
y: event.clientY,
}
let leftMouseDown = true;
// a1<a2, b1<b2
function lineIntersect(a1, a2, b1, b2)
{
if (a1 > a2) [a1,a2]=[a2,a1];
if (b1 > b2) [b1,b2]=[b2,b1];
return (a1 > b1 && a1 < b2) || (a2 > b1 && a2 < b2) || (b1 > a1 && b1 < a2) || (b2 > a1 && b2 < a2)
}
// a,b: left, right, right, bottom
function intersect(domRect, mouseA, mouseB){
return (lineIntersect(select_end_pos.x, select_start_pos.x, domRect.left, domRect.right) &&
lineIntersect(select_end_pos.y, select_start_pos.y, domRect.top, domRect.bottom))
}
this.parentUi.onmousemove = (event)=>{
select_end_pos.x = event.clientX;
select_end_pos.y = event.clientY;
this.editorList.forEach(e=>{
let rect = e.ui.getBoundingClientRect();
let intersected = intersect(rect, select_start_pos, select_end_pos);
e.setSelected(intersected, event.ctrlKey?eventId:null);
})
}
this.parentUi.onmouseup = (event) =>{
if (event.which!=1)
return;
leftMouseDown = false;
this.parentUi.onmousemove = null;
this.parentUi.onmouseup = null;
if (event.clientX == select_start_pos.x && event.clientY == select_start_pos.y)
{ // click
let ed = this.getEditorByMousePosition(event.clientX, event.clientY);
if (event.shiftKey)
{
let selectedEditors = this.getSelectedEditors();
if (selectedEditors.length == 0)
{
}
else if (ed.index < selectedEditors[0].index)
{
this.activeEditorList().forEach(e=>{
if (e.index >= ed.index && e.index < selectedEditors[0].index){
e.setSelected(true);
}
});
}
else if (ed.index > selectedEditors[selectedEditors.length-1].index)
{
this.activeEditorList().forEach(e=>{
if (e.index <= ed.index && e.index > selectedEditors[selectedEditors.length-1].index){
e.setSelected(true);
}
});
}
}
else if (event.ctrlKey)
{
ed.setSelected(!ed.selected);
}
else
{
let selectedEditors = this.getSelectedEditors();
if (ed){
if (ed.selected && selectedEditors.length == 1)
{
ed.setSelected(false);
}
else
{
selectedEditors.forEach(e=>e.setSelected(false));
ed.setSelected(true);
}
}
else{
selectedEditors.forEach(e=>e.setSelected(false));
}
}
}
}
}
this.getSelectedEditors = function(){
return this.editorList.filter(e=>e.selected);
}
}
export {BoxEditorManager, BoxEditor};