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);