Skip to content

Commit

Permalink
Merge pull request #23 from Monjisan/feature/labeling_tool_copy
Browse files Browse the repository at this point in the history
Labeling Tool Features: Copy / Paste
  • Loading branch information
eratostennis authored Oct 30, 2019
2 parents 9f5090d + cc6d454 commit 0748638
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 39 deletions.
101 changes: 83 additions & 18 deletions front/app/labeling_tool/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -203,17 +207,16 @@ 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 + '"';
this._controls.error(txt);
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]);
Expand Down Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions front/app/labeling_tool/clipboard.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Grid item xs={12}>
<Button onClick={() => this.copy(false)}>Copy</Button>
<Button onClick={() => this.copy(true)}>Copy ALL</Button>
<Button onClick={() => this.paste()} disabled={!this.hasCopy()}>Paste</Button>
</Grid>
);
}
}
export default Clipboard;

25 changes: 22 additions & 3 deletions front/app/labeling_tool/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -457,9 +473,12 @@ class Controls extends React.Component {
<Button onClick={() => this.reloadFrame()}>Reload</Button>
</Grid>
<Grid item xs={12}>
<Button disabled>Copy</Button>
<Button disabled>Paste</Button>
</Grid>
<Clipboard
controls={this}
classes={classes}
getRef={this.getClipboard}
/>
<Grid item xs={12}>
<History
controls={this}
Expand Down
40 changes: 22 additions & 18 deletions front/app/labeling_tool/history.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,35 @@ class History extends React.Component {
'delete': 'create',
};
createAntiHist(hist) {
const label = this.annotation.getLabel(hist.id);
const ret = {
type: this.ANTI_TYPES[hist.type],
id: hist.id
};
if (hist.type == 'change') {
ret.obj = label.toHistory();
ret.objects = hist.objects.map(obj => {
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
}
return ret;
}
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
}
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 0748638

Please sign in to comment.