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.sceneId; 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 ${selectedEditors.length} 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", "当前已是最后一帧"); } } 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( "信息", "当前已是第一帧" ); } } 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) [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 };