diff --git a/front/app/labeling_tool/annotation.js b/front/app/labeling_tool/annotation.js index 026445ae..eb44420d 100644 --- a/front/app/labeling_tool/annotation.js +++ b/front/app/labeling_tool/annotation.js @@ -83,6 +83,10 @@ class Annotation extends React.Component { if (this._deleted.length > 0) { return true; } + if (!this._history.hasUndo()) { + // check by history + return false; + } let changedFlag = false; this.state.labels.forEach(label => { changedFlag = changedFlag || label.isChanged; @@ -153,7 +157,7 @@ class Annotation extends React.Component { const label = new Label(this, this._nextId--, klass, bbox); const labels = new Map(this.state.labels); labels.set(label.id, label); - this._history.addHistory(label, 'create'); + this._history.addHistory([label], 'create'); this.setState({ labels }); return label; } @@ -203,7 +207,7 @@ class Annotation extends React.Component { //label.tableItem.addClass('has-image-bbox'); } } - remove(id, setHistory=true) { + remove(id) { let label = this.getLabel(id); if (label == null) { let txt = 'Label remove error: Error selector "' + id + '"'; @@ -211,9 +215,8 @@ class Annotation extends React.Component { return; } - if (setHistory) { - this._history.addHistory(label, 'delete'); - } + this._history.addHistory([label], 'delete'); + this._controls.getTools().forEach(tool => { if (label.bbox[tool.candidateId] != null) { tool.disposeBBox(label.bbox[tool.candidateId]); @@ -244,28 +247,90 @@ class Annotation extends React.Component { } return null; } + // methods to history createHistory(label) { - this._history.createHistory(label, 'change'); + this._history.createHistory([label], 'change'); } addHistory() { this._history.addHistory(null); } - createFromHistory(id, obj) { - let bboxes = {}; - this._controls.getTools().forEach(tool => { - const id = tool.candidateId; - if (obj.content[id] != null) { - bboxes[id] = tool.createBBox(obj.content[id]); + createFromHistory(objects) { + const labels = new Map(this.state.labels); + let labelList = []; + for (let obj of objects) { + let bboxes = {}; + this._controls.getTools().forEach(tool => { + const id = tool.candidateId; + if (obj.content[id] != null) { + bboxes[id] = tool.createBBox(obj.content[id]); + } + }); + let label = new Label(this, obj.id, obj.klass, bboxes); + labels.set(label.id, label); + if (label.id >= 0) { + this._deleted = this._deleted.filter(id => id != label.id); } - }); - let label = new Label(this, id, obj.klass, bboxes); + labelList.push(label); + } + this.setState({ labels }); + return labelList; + } + removeFromHistory(objects) { + const tools = this._controls.getTools(); + const tgt = this._targetLabel; const labels = new Map(this.state.labels); - labels.set(label.id, label); + for (let obj of objects) { + let label = this.getLabel(obj.id); + + tools.forEach(tool => { + if (label.bbox[tool.candidateId] != null) { + tool.disposeBBox(label.bbox[tool.candidateId]); + } + }); + if (tgt != null && label.id === tgt.id) { + this._targetLabel = null; + tgt.setTarget(false); + this._controls.getTools().forEach(tool => { + tool.updateTarget(tgt, null); + }); + } + if (label.id >= 0) { + this._deleted.push(label.id); + } + + labels.delete(label.id); + label.dispose(); + } this.setState({ labels }); - return label; } - removeFromHistory(id) { - this.remove(id, false); + // methods to clipboard + copyLabels(isAll) { + let target = []; + if (isAll) { + target = Array.from(this.state.labels.values()); + } else if (this._targetLabel != null) { + target = [this._targetLabel]; + } + return target.map(label => label.toObject()); + } + pasteLabels(data) { + const labels = new Map(this.state.labels); + let pastedLabels = []; + data.forEach(obj => { + let klass = this._klassSet.getByName(obj.name); + let bboxes = {}; + this._controls.getTools().forEach(tool => { + const id = tool.candidateId; + if (obj.content[id] != null) { + bboxes[id] = tool.createBBox(obj.content[id]); + } + }); + let label = new Label(this, this._nextId--, klass, bboxes); + labels.set(label.id, label); + pastedLabels.push(label); + }); + this._history.addHistory(pastedLabels, 'create'); + this.setState({ labels }); } // private diff --git a/front/app/labeling_tool/clipboard.jsx b/front/app/labeling_tool/clipboard.jsx new file mode 100644 index 00000000..cf19482e --- /dev/null +++ b/front/app/labeling_tool/clipboard.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import Grid from '@material-ui/core/Grid'; +import Button from '@material-ui/core/Button'; + +class Clipboard extends React.Component { + + constructor(props) { + super(props); + this.annotation = null; + this._controls = props.controls; + this.state = { + copy: null + }; + props.getRef(this); + } + init(annotation) { + this.annotation = annotation; + } + hasCopy() { + return this.state.copy != null; + } + copy(isAll) { + if (isAll === null) { + isAll = this.annotation.getTarget() == null; + } + const copy = this.annotation.copyLabels(isAll); + if (copy.length === 0) { + return; + } + this.setState({ copy: copy }); + } + paste() { + const copy = this.state.copy; + this.annotation.pasteLabels(copy); + } + + render() { + return ( + + + + + + ); + } +} +export default Clipboard; + diff --git a/front/app/labeling_tool/controls.jsx b/front/app/labeling_tool/controls.jsx index 036d6c1b..90bb9275 100644 --- a/front/app/labeling_tool/controls.jsx +++ b/front/app/labeling_tool/controls.jsx @@ -14,6 +14,7 @@ import {NavigateNext, NavigateBefore} from '@material-ui/icons'; import KlassSet from 'automan/labeling_tool/klass_set'; import Annotation from 'automan/labeling_tool/annotation'; import History from 'automan/labeling_tool/history'; +import Clipboard from 'automan/labeling_tool/clipboard'; import ImageLabelTool from 'automan/labeling_tool/image_label_tool'; import PCDLabelTool from 'automan/labeling_tool/pcd_label_tool'; @@ -31,6 +32,9 @@ class Controls extends React.Component { getKlassSet = (tgt) => { this.klassSet = tgt; } history = null; getHistory = (tgt) => { this.history = tgt; } + clipboard = null; + getClipboard = (tgt) => { this.clipboard = tgt; } + // progress frameLength = 0; // tool status @@ -89,7 +93,9 @@ class Controls extends React.Component { return Promise.all([ this.annotation.init(this.klassSet, this.history), this.klassSet.init(), - this.history.init(this.annotation) + this.history.init(this.annotation), + this.clipboard.init(this.annotation) + ]); } resize() { @@ -129,6 +135,16 @@ class Controls extends React.Component { this.history.undo(); } } + } else if (e.keyCode == 67) { + // C key + if (e.ctrlKey) { + this.clipboard.copy(null); + } + } else if (e.keyCode == 86) { + // V key + if (e.ctrlKey) { + this.clipboard.paste(); + } } else { this.getTool().handles.keydown(e); } @@ -457,9 +473,12 @@ class Controls extends React.Component { - - + { + const label = this.annotation.getLabel(obj.id); + return label.toHistory(); + }); } else if (hist.type === 'create') { - ret.obj = hist.obj + ret.objects = hist.objects; } else if (hist.type === 'delete') { - ret.obj = hist.obj + ret.objects = hist.objects; } else { // error } @@ -51,14 +52,16 @@ class History extends React.Component { } undoHist(hist) { if (hist.type === 'change') { - const label = this.annotation.getLabel(hist.id); - label.fromHistory(hist.obj); - this._controls.selectLabel(label); + for (let obj of hist.objects) { + const label = this.annotation.getLabel(obj.id); + label.fromHistory(obj); + } + this._controls.selectLabel(hist.objects[0].id); } else if (hist.type === 'create') { - this.annotation.removeFromHistory(hist.id); + this.annotation.removeFromHistory(hist.objects); } else if (hist.type === 'delete') { - const label = this.annotation.createFromHistory(hist.id, hist.obj); - this._controls.selectLabel(label); + const labels = this.annotation.createFromHistory(hist.objects); + this._controls.selectLabel(labels[0]); } else { // error } @@ -99,24 +102,25 @@ class History extends React.Component { redoHistory: [] }); } - createHistory(label, type) { + createHistory(labels, type) { this.tmpHist = { type: type, - obj: label.toHistory(), - id: label.id + objects: labels.map(label => label.toHistory()) }; } - addHistory(label, type) { + addHistory(labels, type) { const undoHist = this.state.undoHistory.slice(); const redoHist = []; let hist; - if (label == null) { + if (labels == null) { hist = this.tmpHist; } else { + if (labels.length == 0) { + return; + } hist = { type: type, - obj: label.toHistory(), - id: label.id + objects: labels.map(label => label.toHistory()) }; } undoHist.push(hist);