From f2e0e5be3691f18f275482e083a6366c118bd41c Mon Sep 17 00:00:00 2001 From: Yuto Jumonji Date: Wed, 14 Aug 2019 17:12:33 +0900 Subject: [PATCH 1/3] feature: react based labeling_tool --- .../labeling_tool/labeling_tool.html | 138 ++- front/app/labeling_tool/annotation.js | 233 +++-- ...base_label_tool.js => base_label_tool.jsx} | 674 ++++++------ front/app/labeling_tool/controls.js | 346 ------- front/app/labeling_tool/controls.jsx | 964 ++++++++++++++++++ ...age_label_tool.js => image_label_tool.jsx} | 114 ++- front/app/labeling_tool/klass_set.js | 211 ++-- front/app/labeling_tool/label_tool.jsx | 64 +- .../{pcd_label_tool.js => pcd_label_tool.jsx} | 117 ++- 9 files changed, 1901 insertions(+), 960 deletions(-) rename front/app/labeling_tool/{base_label_tool.js => base_label_tool.jsx} (53%) delete mode 100644 front/app/labeling_tool/controls.js create mode 100644 front/app/labeling_tool/controls.jsx rename front/app/labeling_tool/{image_label_tool.js => image_label_tool.jsx} (84%) rename front/app/labeling_tool/{pcd_label_tool.js => pcd_label_tool.jsx} (90%) diff --git a/automan/templates/labeling_tool/labeling_tool.html b/automan/templates/labeling_tool/labeling_tool.html index 3e96f4bc..f3fdc8b9 100644 --- a/automan/templates/labeling_tool/labeling_tool.html +++ b/automan/templates/labeling_tool/labeling_tool.html @@ -31,90 +31,88 @@
+ - + +
-
-
-

Classes

-
-
    -
-
-
-
-

Bounding Boxes

-
-
-
    -
+
+
+

Classes

+
+
    +
+
+
+
+

Bounding Boxes

+
+
+
    +
- +
-
-
+
+
+
+
-
-
+ --> - - - - - - -{% load render_bundle from webpack_loader %} -{% render_bundle 'label_tool' %} + + + + + + +{% load render_bundle from webpack_loader %} +{% render_bundle 'label_tool' %} diff --git a/front/app/labeling_tool/annotation.js b/front/app/labeling_tool/annotation.js index 2f0b1c08..65df2519 100644 --- a/front/app/labeling_tool/annotation.js +++ b/front/app/labeling_tool/annotation.js @@ -1,23 +1,39 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListSubheader from '@material-ui/core/ListSubheader'; + +import classNames from 'classnames'; + import RequestClient from 'automan/services/request-client'; -export default class Annotation { - _labels = null; +class Annotation extends React.Component { + _labelTool = null; + _controls = null; + _klassSet = null; + // data _deleted = null; _targetLabel = null; - // DOM - _bboxTable = null; // status _loaded = true; _nextId = -1; - _labelTool = null; - constructor(labelTool) { - this._labelTool = labelTool; + constructor(props) { + super(props); + this._labelTool = props.labelTool; + this._controls = props.controls; + this.state = { + labels: null + }; + props.getRef(this); } - init() { + init(klassSet) { + this._klassSet = klassSet; return new Promise((resolve, reject) => { - this._bboxTable = $('#bbox-table'); - resolve(); }); } @@ -28,24 +44,26 @@ export default class Annotation { this._removeAll(); this._nextId = -1; return new Promise((resolve, reject) => { - this._labels = new Map(); this._deleted = []; RequestClient.get( - this._labelTool.getURL('frame_labels'), + this._labelTool.getURL('frame_labels', frameNumber), null, res => { + const labels = new Map(); res.records.forEach(obj => { - let klass = this._labelTool.getKlass(obj.name); + let klass = this._klassSet.getByName(obj.name); let bboxes = {}; - this._labelTool.getTools().forEach(tool => { + 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.object_id, klass, bboxes); + labels.set(label.id, label); }); + this.setState({ labels }); }, err => { reject(err); @@ -57,14 +75,14 @@ export default class Annotation { }); } isChanged() { - if (this._labels == null) { + if (this.state.labels == null) { return false; } if (this._deleted.length > 0) { return true; } let changedFlag = false; - this._labels.forEach(label => { + this.state.labels.forEach(label => { changedFlag = changedFlag || label.isChanged; }); return changedFlag; @@ -77,7 +95,7 @@ export default class Annotation { const edited = []; const deleted = this._deleted; this._deleted = []; - this._labels.forEach(label => { + this.state.labels.forEach(label => { if (label.id < 0) { created.push(label.toObject()); } else if (label.isChanged) { @@ -91,7 +109,7 @@ export default class Annotation { deleted: deleted }; RequestClient.post( - this._labelTool.getURL('frame_labels'), + this._labelTool.getURL('frame_labels', this._controls.getFrameNumber()), data, () => { resolve(); @@ -111,9 +129,15 @@ export default class Annotation { if (prev != null && next != null && next.id === prev.id) { return prev; } + if (prev != null) { + prev.setTarget(false); + } + if (next != null) { + next.setTarget(true); + } this._targetLabel = next; // table dom events - this._labelTool.getTools().forEach(tool => { + this._controls.getTools().forEach(tool => { tool.updateTarget(prev, next); }); return next; @@ -121,26 +145,29 @@ export default class Annotation { create(klass, bbox) { if (klass == null) { let txt = 'Label create error: Error Class "' + klass + '"'; - this._labelTool.controls.error(txt); + this._controls.error(txt); return null; } - return new Label(this, this._nextId--, klass, bbox); + const label = new Label(this, this._nextId--, klass, bbox); + const labels = new Map(this.state.labels); + labels.set(label.id, label); + this.setState({ labels }); + return label; } changeKlass(id, klass) { let label = this._getLabel(id); if (label == null) { let txt = 'Label change Class error: Error selector "' + id + '"'; - this._labelTool.controls.error(txt); + this._controls.error(txt); return; } if (klass == null) { let txt = 'Label change Class error: Error Class "' + klass + '"'; - this._labelTool.controls.error(txt); + this._controls.error(txt); return; } - label.klass = klass; - label.updateKlass(); - this._labelTool.getTools().forEach(tool => { + label.setKlass(klass); + this._controls.getTools().forEach(tool => { tool.updateBBox(label); }); } @@ -148,12 +175,12 @@ export default class Annotation { let label = this._getLabel(id); if (label == null) { let txt = 'Label add BBox error: Error selector "' + id + '"'; - this._labelTool.controls.error(txt); + this._controls.error(txt); return; } if (label.has(candidateId)) { let txt = `Label add BBox error: this BBox is already attached in "${id}"`; - this._labelTool.controls.error(txt); + this._controls.error(txt); return; } label.bbox[candidateId] = bbox; @@ -163,11 +190,11 @@ export default class Annotation { let label = this._getLabel(id); if (label == null) { let txt = 'Label remove BBox error: Error selector "' + id + '"'; - this._labelTool.controls.error(txt); + this._controls.error(txt); return; } if (label.has(candidateId)) { - const tool = this._labelTool.getToolFromCandidateId(candidateId); + const tool = this._controls.getToolFromCandidateId(candidateId); tool.disposeBBox(label.bbox[candidateId]); label.bbox[candidateId] = null; //label.tableItem.addClass('has-image-bbox'); @@ -177,10 +204,10 @@ export default class Annotation { let label = this._getLabel(id); if (label == null) { let txt = 'Label remove error: Error selector "' + id + '"'; - this._labelTool.controls.error(txt); + this._controls.error(txt); return; } - this._labelTool.getTools().forEach(tool => { + this._controls.getTools().forEach(tool => { if (label.bbox[tool.candidateId] != null) { tool.disposeBBox(label.bbox[tool.candidateId]); } @@ -188,14 +215,18 @@ export default class Annotation { const tgt = this._targetLabel; if (tgt != null && label.id === tgt.id) { this._targetLabel = null; - this._labelTool.getTools().forEach(tool => { + tgt.setTarget(false); + this._controls.getTools().forEach(tool => { tool.updateTarget(tgt, null); }); } if (label.id >= 0) { this._deleted.push(label.id); } - this._labels.delete(label.id); + + const labels = new Map(this.state.labels); + labels.delete(label.id); + this.setState({ labels }); label.dispose(); } @@ -204,19 +235,19 @@ export default class Annotation { if (label instanceof Label) { return label; } else if (typeof label === 'number') { - return this._labels.get(label) || null; + return this.state.labels.get(label) || null; } return null; } _removeAll() { - this._labelTool.selectLabel(null); - if (this._labels == null) { + this._controls.selectLabel(null); + if (this.state.labels == null) { return; } this._loaded = false; - this._labels.forEach(label => { - this._labelTool.getTools().forEach(tool => { + this.state.labels.forEach(label => { + this._controls.getTools().forEach(tool => { const id = tool.candidateId; if (label.bbox[id] != null) { tool.disposeBBox(label.bbox[id]); @@ -224,36 +255,106 @@ export default class Annotation { }); label.dispose(); }); - this._labels.clear(); - this._labels = null; this._targetLabel = null; + this.setState({labels: null}); + } + renderList(classes) { + if (this.state.labels === null) { + return []; + } + let list = []; + for (let [_, label] of this.state.labels) { + list.push( + + ); + } + return list; + } + render() { + console.log('annotation render()'); + const classes = this.props.classes; + return ( + + Bounding Box + + } + > + {this.renderList(classes)} + + ); } } +export default Annotation; +class LabelItem extends React.Component { + constructor(props) { + super(props); + this.classes = props.classes; + this.controls = props.controls; + const label = props.label; + label.setLabelItem(this); + this.state = { + color: label.getColor(), + isTarget: label.isTarget + }; + this.label = label; + } + updateKlass() { + this.setState({ color: this.label.getColor() }); + } + updateTarget() { + this.setState({ isTarget: this.label.isTarget }); + } + render() { + console.log('labelItem render()'); + const classes = this.props.classes; + const label = this.label; + return ( + this.controls.selectLabel(label)} + button + > +
+ + + ); + } +} class Label { _annotationTool = null; + _listItem = null; + id = 0; + isChanged = false; + isTarget = false; + klass = null; + minSize = null; + bbox = null; constructor(annotationTool, id, klass, bbox) { this._annotationTool = annotationTool; this.id = id; this.isChanged = this.id < 0; + this.isTarget = false; this.klass = klass; this.minSize = klass.getMinSize(); this.bbox = {}; - this.tableItem = $('
  • '); - this.tableItem.click(() => { - this._annotationTool._labelTool.selectLabel(this); - }); - this.tableItem.css({ background: this.klass.color }); - this.tableItem.append( - $('').text(this.toIDString()) - ); - - this._annotationTool._bboxTable.append(this.tableItem); - this._annotationTool._labels.set(this.id, this); - - this._annotationTool._labelTool.getTools().forEach(tool => { + this._annotationTool._controls.getTools().forEach(tool => { const id = tool.candidateId; if (bbox[id] == null) { this.bbox[id] = null; @@ -264,16 +365,16 @@ class Label { }); } addBBox(name) { - const ret = $('').text(name); - this.tableItem.append(ret); - return ret; + // how to change state? } dispose() { - this.tableItem.remove(); } - updateKlass() { + setLabelItem(labelItem) { + this.labelItem = labelItem; + } + setKlass(klass) { + this.klass = klass; this.isChanged = true; - this.tableItem.css({ background: this.klass.color }); this.minSize = this.klass.getMinSize(); Object.keys(this.bbox).forEach(id => { const bbox = this.bbox[id]; @@ -282,6 +383,15 @@ class Label { } bbox.updateKlass(); }); + if (this.labelItem != null) { + this.labelItem.updateKlass(); + } + } + setTarget(val) { + this.isTarget = val; + if (this.labelItem != null) { + this.labelItem.updateTarget(); + } } toIDString() { if (this.id < 0) { @@ -312,7 +422,7 @@ class Label { if (this.id >= 0) { ret.object_id = this.id; } - this._annotationTool._labelTool.getTools().forEach(tool => { + this._annotationTool._controls.getTools().forEach(tool => { const id = tool.candidateId; if (!this.has(id)) { return; @@ -323,4 +433,5 @@ class Label { }); return ret; } + } diff --git a/front/app/labeling_tool/base_label_tool.js b/front/app/labeling_tool/base_label_tool.jsx similarity index 53% rename from front/app/labeling_tool/base_label_tool.js rename to front/app/labeling_tool/base_label_tool.jsx index 4dc05aad..202d01bc 100644 --- a/front/app/labeling_tool/base_label_tool.js +++ b/front/app/labeling_tool/base_label_tool.jsx @@ -1,36 +1,13 @@ -import Annotation from 'automan/labeling_tool/annotation'; -import ImageLabelTool from 'automan/labeling_tool/image_label_tool'; -import PCDLabelTool from 'automan/labeling_tool/pcd_label_tool'; +import React from 'react'; +import ReactDOM from 'react-dom'; + import Controls from 'automan/labeling_tool/controls'; -import KlassSet from 'automan/labeling_tool/klass_set'; import RequestClient from 'automan/services/request-client'; let annotation, imageLabelTool, pcdLabelTool, controls, klassSet; const toolStatus = { - // informations - projectId: null, // from location - projectInfo: null, // from 'project' - annotationId: null, // from location - annotationName: null, // from 'annotation' - datasetId: null, // from 'annotation' - originalId: null, // from 'dataset' - // navigation - pageBox: null, - nextFrameButton: null, - prevFrameButton: null, - frameSkipText: null, - // file status - filenames: {}, - // progress - frameNumber: 0, - frameLength: 0, - skipFrameCount: 1, - // tool status - tools: [], - activeTool: 0, - labelType: null }; const innerStatus = { loaded: true @@ -38,44 +15,10 @@ const innerStatus = { // base-labeltool public methods const LabelTool = { - controls: null, - isChanged() { - return annotation.isChanged(); - }, - isLoaded() { - return innerStatus.loaded && - annotation.isLoaded() && - toolStatus.tools.every(tool=>tool.isLoaded()); - }, isEditable() { return LabelTool.isLoaded(); }, loadFrame(num) { - // promise function!! - if (!LabelTool.isLoaded()) { - return Promise.reject('Duplicate loading'); - } - - // TODO: check 'num' - // TODO: decide move or not - let savePromise; - if (LabelTool.isChanged()) { - const TEXT_SAVE = 'Do you want to save?'; - const TEXT_MOVE = 'Do you want to move frame WITHOUT saving?'; - if ( window.confirm(TEXT_SAVE) ) { - savePromise = annotation.save(); - } else if ( window.confirm(TEXT_MOVE) ) { - savePromise = Promise.resolve(); - } else { - return Promise.resolve(); - } - } else { - savePromise = Promise.resolve(); - } - - LabelTool.selectLabel(null); - - innerStatus.loaded = false; let promise = new Promise((resolve, reject) => { savePromise .then(() => { @@ -220,166 +163,6 @@ const LabelTool = { } return true; }, - selectKlass(kls) { - if (!LabelTool.isLoaded()) { - return false; - } - let newKls = klassSet.setTarget(kls); - if (newKls !== null) { - const label = annotation.getTarget(); - if (label !== null) { - annotation.changeKlass(label, newKls); - controls.SideBar.update(); - } - } else { - return false; - } - return true; - }, - getTargetKlass() { - return klassSet.getTarget(); - }, - getKlass(name) { - return klassSet.getByName(name); - }, - selectLabel(label) { - if (!LabelTool.isLoaded()) { - return false; - } - let newLabel; - newLabel = annotation.setTarget(label); - if (newLabel !== null) { - klassSet.setTarget(newLabel.klass); - } - controls.update(); - return true; - }, - getTargetLabel() { - return annotation.getTarget(); - }, - createLabel(klass, param) { - if (!LabelTool.isLoaded()) { - return null; - } - let newLabel = null; - try { - newLabel = annotation.create(klass, param); - } catch (e) { - controls.error(e); - return null; - } - annotation.setTarget(newLabel); - klassSet.setTarget(newLabel.klass); - controls.update(); - return newLabel; - }, - removeLabel(label) { - if (!LabelTool.isLoaded()) { - return false; - } - try { - annotation.remove(label); - } catch (e) { - controls.error(e); - return false; - } - controls.update(); - return true; - }, - toggleDataType() { - if (toolStatus.tools.length === 1) { - return; - } - const prevTool = LabelTool.getTool(); - toolStatus.activeTool = - (toolStatus.activeTool + 1) % toolStatus.tools.length; - const nextTool = LabelTool.getTool(); - prevTool.setActive(false); - nextTool.setActive(true); - controls.update(); - }, - getStatus() { - return toolStatus; - }, - getLabelType() { - return toolStatus.labelType; - }, - getProjectInfo() { - return toolStatus.projectInfo; - }, - getTool() { - return toolStatus.tools[toolStatus.activeTool]; - }, - getTools() { - return toolStatus.tools; - }, - getToolFromCandidateId(id) { - const filtered = toolStatus.tools.filter(tool => - tool.isTargetCandidate(id) - ); - if (filtered.length != 1) { - controls.error('candidate error'); - return null; - } - return filtered[0]; - }, - getFrameNumber() { - return toolStatus.frameNumber; - }, - getURL(type, ...args) { - type = type.toString().toLowerCase(); - const projectId = toolStatus.projectId; - const annotationId = toolStatus.annotationId; - const PROJECT_ROOT = '/projects/' + projectId + '/'; - const ANNOTATION_ROOT = PROJECT_ROOT + 'annotations/' + annotationId + '/'; - const DATASET_ROOT = - PROJECT_ROOT + 'datasets/' + toolStatus.datasetId + '/'; - let ret = null; - switch (type) { - case 'project': - ret = PROJECT_ROOT; - break; - case 'annotation': - ret = ANNOTATION_ROOT; - break; - case 'dataset': - ret = DATASET_ROOT; - break; - case 'candidate_info': { - const dataType = args[0]; - ret = - PROJECT_ROOT + 'originals/' + toolStatus.originalId + '/candidates/'; - if (dataType != null) { - ret += '?data_type=' + dataType; - } - break; - } - case 'frame_labels': - ret = - `${ANNOTATION_ROOT}frames/${toolStatus.frameNumber + 1}` + - '/objects/'; - break; - case 'image_url': { - const candidateId = args[0]; - ret = - `${DATASET_ROOT}candidates/${candidateId}` + - `/frames/${toolStatus.frameNumber + 1}/`; - break; - } - case 'frame_blob': { - const candidateId = args[0]; - ret = toolStatus.filenames[candidateId][toolStatus.frameNumber]; - break; - } - /* - Add more request urls - */ - default: - const text = 'Url request error: type="' + type + '", args=' + args; - controls.error(text); - } - return ret; - } }; // base-labeltool internal functions @@ -435,7 +218,7 @@ const initializeBase = function() { // load labeling tools const LABEL_TYPES = { BB2D: { - tools: [imageLabelTool] + tools: [imageLabelTool, pcdLabelTool] }, BB2D3D: { tools: [imageLabelTool, pcdLabelTool] @@ -456,132 +239,347 @@ const initializeBase = function() { } ); }) - .then( - () => - new Promise((resolve, reject) => { - RequestClient.get( - LabelTool.getURL('annotation'), - null, - res => { - toolStatus.annotationName = res.name; - toolStatus.datasetId = res.dataset_id; - resolve(); - }, - err => { - reject(err); - } - ); - }) - ) - .then( - () => - new Promise((resolve, reject) => { - RequestClient.get( - LabelTool.getURL('dataset'), - null, - res => { - toolStatus.originalId = res.original_id; - toolStatus.frameLength = res.frame_count; - toolStatus.pageBox[0].placeholder = - 1 + '/' + toolStatus.frameLength; - toolStatus.pageBox.val(''); - resolve(); - }, - err => { - reject(err); - } - ); - }) - ) - .then( - () => - new Promise((resolve, reject) => { - RequestClient.get( - LabelTool.getURL('candidate_info'), - null, - res => { - const tools = toolStatus.tools; - res.records.forEach(info => { - tools.forEach(tool => { - if (tool.dataType === info.data_type) { - if (tool.candidateId >= 0) { - return; - } - tool.candidateId = info.candidate_id; // TODO: multi candidate_id - toolStatus.filenames[tool.candidateId] = []; - } - }); - }); - resolve(); - }, - err => { - reject(err); - } - ); - }) - ); + }; -const initializeEvent = function() { - $(window) - .keydown(function(e) { - if (e.keyCode == 8 || e.keyCode == 46) { - // Backspace or Delete - const label = LabelTool.getTargetLabel(); - if (label != null) { - LabelTool.removeLabel(label); - } - } else if (e.keyCode == 39) { - LabelTool.nextFrame(); - } else if (e.keyCode == 37) { - LabelTool.previousFrame(); + +// init all when dom loaded +//$(initializeAll); + +export default class LabelTool_ extends React.Component { + // components + controls = null; + // informations + projectId = null; // from location + projectInfo = null; // from 'project' + annotationId = null; // from location + annotationName = null; // from 'annotation' + datasetId = null; // from 'annotation' + originalId = null; // from 'dataset' + labelType = null; + // progress + loaded = false; + // file status + filenames = {}; + frameLength = -1; + // error + errorMessage = null; + + isLoaded() { + return this.loaded; + } + loadFrame(num) { + if (!this.isLoaded()) { + return Promise.reject('Duplicate loading'); + } + + // TODO: check 'num' + // TODO: decide move or not + let savePromise; + if (LabelTool.isChanged()) { + const TEXT_SAVE = 'Do you want to save?'; + const TEXT_MOVE = 'Do you want to leave from this frame WITHOUT SAVING?'; + if ( window.confirm(TEXT_SAVE) ) { + savePromise = annotation.save(); + } else if ( window.confirm(TEXT_MOVE) ) { + savePromise = Promise.resolve(); } else { - LabelTool.getTool().handles.keydown(e); + return Promise.resolve(); } - }) - .keyup(function(e) { - LabelTool.getTool().handles.keyup(e); - }); + } else { + savePromise = Promise.resolve(); + } - window.addEventListener('resize', function() { - // TODO: resize all - toolStatus.tools.forEach(function(tool) { - tool.handles.resize(); + this.controls.selectLabel(null); + + this.loaded = false; + return savePromise.then(() => { + return this.controls.setFrameNumber(num); + }).then(() => { + return Promise.all(/* load all */); + }).then(() => { + this.loaded = true; + return Promise.resolve(); + }).catch(e => { + // error toast + this.loaded = true; + return Promise.reject(); }); - }); + // ********* + } + reloadFrame() { + this.loaded = false; - // header setup - toolStatus.nextFrameButton.keyup(function(e) { - if (e.which === 32) { - return false; + + return this.controls.loadFrame().then( + () => { + this.loaded = true; + }, + e => { + this.loaded = true; + return Promise.reject(e); + } + ); + // ********* + } + saveFrame() { + if (!this.isLoaded()) { + return Promise.reject('Duplicate save'); } - }); - toolStatus.nextFrameButton.click(() => { - LabelTool.nextFrame(); - }); - toolStatus.prevFrameButton.click(() => { - LabelTool.previousFrame(); - }); - toolStatus.frameSkipText.change(function() { - let value = $(this).val(); - if (value == '') { - value = 1; - } else { - value = parseInt(value); + return this.saveStatus() + .then(() => this.controls.save()) + .then(() => this.reloadFrame()); + // ********* + } + saveStateus() { + return Promise.resolve(); + // ********* + } + + + getProjectInfo() { + return this.projectInfo; + } + loadBlobURL(num) { + // load something (image, pcd) by URL + return Promise.all( + this.controls.getTools().map( + tool => { + const candidateId = tool.candidateId; + const fname = this.filenames[candidateId][num]; + if (typeof fname === 'string') { + return Promise.resolve(); + } + return (new Promise((resolve, reject) => { + RequestClient.get( + this.getURL('image_url', candidateId, num), + null, + res => { + resolve(res); + }, + e => { + reject(e); + } + ); + })).then(res => { + return new Promise((resolve, reject) => { + RequestClient.getBinaryAsURL( + res, + blobUrl => { + this.filenames[candidateId][num] = blobUrl; + resolve(); + }, + e => { + reject(e); + } + ); + }); + }) + } + ) + ); + } + getURL(type, ...args) { + type = type.toString().toLowerCase(); + const projectId = this.projectId; + const annotationId = this.annotationId; + const PROJECT_ROOT = '/projects/' + projectId + '/'; + const ANNOTATION_ROOT = PROJECT_ROOT + 'annotations/' + annotationId + '/'; + const DATASET_ROOT = + PROJECT_ROOT + 'datasets/' + this.datasetId + '/'; + let ret = null; + switch (type) { + case 'project': + ret = PROJECT_ROOT; + break; + case 'annotation': + ret = ANNOTATION_ROOT; + break; + case 'dataset': + ret = DATASET_ROOT; + break; + case 'candidate_info': { + const dataType = args[0]; + ret = + PROJECT_ROOT + 'originals/' + this.originalId + '/candidates/'; + if (dataType != null) { + ret += '?data_type=' + dataType; + } + break; + } + case 'frame_labels': + const frameNumber = args[0] + 1; + ret = `${ANNOTATION_ROOT}frames/${frameNumber}/objects/`; + break; + case 'image_url': { + const candidateId = args[0]; + const frameNumber = args[1] + 1; + ret = + `${DATASET_ROOT}candidates/${candidateId}` + + `/frames/${frameNumber}/`; + break; + } + case 'frame_blob': { + const candidateId = args[0]; + const frameNumber = args[1] + 1; + ret = this.filenames[candidateId][frameNumber-1]; + break; + } + /* + Add more request urls + */ + default: + const text = 'Url request error: type="' + type + '", args=' + args; + this.controls.error(text); } - value = Math.max(value, 1); - toolStatus.skipFrameCount = value; - $(this).val(value); - }); - toolStatus.pageBox.keyup(e => { - if (e.keyCode === 13) { - LabelTool.setFrame(toolStatus.pageBox.val() - 1); - toolStatus.pageBox.blur(); + return ret; + } + + labelUpdate(label) { + // ********* + } + constructor(props) { + super(props); + this.state = { + isLoaded: false, + isInitialized: false + }; + //this.klassSet = new KlassSet(this); + //this.annotation = new Annotation(this, this.klassSet); + //this.controls = React.createRef(); + + this.initializeBase().then(() => { + console.log('initialized (mountHandle is setted)'); + return new Promise((resolve, reject) => { + this.mountHandle = () => { + resolve(); + }; + this.setState({isInitialized: true}); + }); + }).then(() => { + this.initializeEvent(); + + return this.controls.loadFrame(0); + }).catch(e => { + this.errorMessage = e; + console.error(e); + // ****** + }); + } + + // get project information + initProject() { + console.log('initProject'); + return new Promise((resolve, reject) => { + RequestClient.get( + this.getURL('project'), + null, + res => { + this.projectInfo = res; + + this.labelType = res.label_type; + + resolve(); + }, + err => { + reject(err); + } + ); + }) + } + initAnnotation() { + console.log('initAnnotation'); + return new Promise((resolve, reject) => { + RequestClient.get( + this.getURL('annotation'), + null, + res => { + this.annotationName = res.name; + this.datasetId = res.dataset_id; + resolve(); + }, + err => { + reject(err); + } + ); + }); + } + initDataset() { + console.log('initDataset'); + return new Promise((resolve, reject) => { + RequestClient.get( + this.getURL('dataset'), + null, + res => { + this.originalId = res.original_id; + this.frameLength = res.frame_count; + resolve(); + }, + err => { + reject(err); + } + ); + }); + } + initCandidateInfo() { + console.log('initCandidateInfo'); + return new Promise((resolve, reject) => { + RequestClient.get( + this.getURL('candidate_info'), + null, + res => { + this.candidateInfo = res.records; + + resolve(); + }, + err => { + reject(err); + } + ); + }); + } + initializeBase() { + console.log('inittializeBase'); + const pathItems = window.location.pathname.split('/'); + this.projectId = parseInt(pathItems[2]); + this.annotationId = parseInt(pathItems[4]); + return this.initProject() + .then(() => this.initAnnotation()) + .then(() => this.initDataset()) + .then(() => this.initCandidateInfo()) + } + + initializeEvent() { + this.controls.initEvent(); + } + isInitialized() { + return this.state.isInitialized; + } + isLoaded() { + return this.state.isLoaded; + } + controlsDidMount = (controls) => { + this.controls = controls; + this.mountHandle(); + this.setState({isLoaded: true}); + } + render() { + if (this.errorMessage !== null) { + return ( +
    + {this.errorMessage} +
    + ); + } + if (!this.isInitialized()) { + return
    Loading
    ; } - }); + return ( + + ); + } }; -// init all when dom loaded -$(initializeAll); - -export default LabelTool; diff --git a/front/app/labeling_tool/controls.js b/front/app/labeling_tool/controls.js deleted file mode 100644 index 554d5d72..00000000 --- a/front/app/labeling_tool/controls.js +++ /dev/null @@ -1,346 +0,0 @@ -import RequestClient from 'automan/services/request-client' - -let ImageLabelTool, PCDLabelTool, LabelTool; - - -// dat.GUI menu tool -let gui = null; -const getLabel = function() { - return LabelTool.getTargetLabel(); -}; -const guiRef = { - label: { - get klass() { - let label = getLabel(); - if (label == null) { return null; } - return label.getKlassName(); - }, - set klass(name) { - let label = getLabel(); - if (label == null) { return null; } - let klass = KlassSet.getByName(name); - if (klass == null) { return null; } - Annotation.changeKlass(label, klass); - }, - }, - image: { - getBBox() { - let label = getLabel(); - if (label == null) { return null; } - return label.bbox[ImageLabelTool.candidateId]; - }, - // getters - get posX() { - let bbox = guiRef.image.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.min.x; - }, - get posY() { - let bbox = guiRef.image.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.min.y; - }, - get sizeX() { - let bbox = guiRef.image.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.getSize().x; - }, - get sizeY() { - let bbox = guiRef.image.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.getSize().y; - }, - // setters - set posX(v) { - let bbox = guiRef.image.getBBox(); - if (bbox == null) { return; } - bbox.dragStart(); - console.log(JSON.stringify({x: v, prev: bbox.box.min.x})); - bbox.dragMove(v - bbox.box.min.x, 0); - bbox.dragEnd(); - }, - set posY(v) { - let bbox = guiRef.image.getBBox(); - if (bbox == null) { return; } - bbox.dragStart(); - bbox.dragMove(0, v - bbox.box.min.y); - bbox.dragEnd(); - }, - set sizeX(v) { - let bbox = guiRef.image.getBBox(); - if (bbox == null) { return; } - let sx = bbox.box.getSize().x; - bbox.dragStart(); - bbox.setMaxX(v - sx); - bbox.dragEnd(); - }, - set sizeY(v) { - let bbox = guiRef.image.getBBox(); - if (bbox == null) { return; } - let sy = bbox.box.getSize().y; - bbox.dragStart(); - bbox.setMaxY(v - sy); - bbox.dragEnd(); - }, - }, - pcd: { - getBBox() { - let label = getLabel(); - if (label == null) { return null; } - return label.bbox[PCDLabelTool.candidateId]; - }, - // getters - get posX() { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.pos.x; - }, - get posY() { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.pos.y; - }, - get posZ() { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.pos.z; - }, - get sizeX() { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.size.x; - }, - get sizeY() { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.size.y; - }, - get sizeZ() { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - return bbox.box.size.z; - }, - get yaw() { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - let v = (bbox.box.yaw/Math.PI*180) % 360; - if (v < -180) { v += 360; } - else if (v > 180) { v -= 360; } - return v; - }, - // setters - set posX(v) { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - bbox.box.pos.x = v; - bbox.updateCube(); - PCDLabelTool.redrawRequest(); - }, - set posY(v) { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - bbox.box.pos.y = v; - bbox.updateCube(); - PCDLabelTool.redrawRequest(); - }, - set posZ(v) { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - bbox.box.pos.z = v; - bbox.updateCube(); - PCDLabelTool.redrawRequest(); - }, - set sizeX(v) { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - bbox.box.size.x = v; - bbox.updateCube(); - PCDLabelTool.redrawRequest(); - }, - set sizeY(v) { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - bbox.box.size.y = v; - bbox.updateCube(); - PCDLabelTool.redrawRequest(); - }, - set sizeZ(v) { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - bbox.box.size.z = v; - bbox.updateCube(); - PCDLabelTool.redrawRequest(); - }, - set yaw(v) { - let bbox = guiRef.pcd.getBBox(); - if (bbox == null) { return 0; } - bbox.box.yaw = v*Math.PI/180; - bbox.updateCube(); - PCDLabelTool.redrawRequest(); - }, - }, -}; -// Add image controller -let imageFolder = null; -const initGUIImage = function(gui, targetLabel) { - if (imageFolder != null) { - gui.removeFolder(imageFolder); - imageFolder = null; - } - if (targetLabel==null || !targetLabel.has(ImageLabelTool.candidateId) ) { - return; - } - const folder = gui.addFolder('Image'); - imageFolder = folder; - folder.open(); - const fpos = folder.addFolder('Position'); - fpos.open(); - fpos.add(guiRef.image, 'posX').name('x').min(0).max(1000).step(1).listen(); // TODO: get max pos - fpos.add(guiRef.image, 'posY').name('y').min(0).max(1000).step(1).listen(); - const fsize = folder.addFolder('Size'); - fsize.open(); - fsize.add(guiRef.image, 'sizeX').name('x').max(1000).min(10).step(1).listen(); // TODO: use minSize - fsize.add(guiRef.image, 'sizeY').name('y').max(1000).min(10).step(1).listen(); -}; -// Add pcd controller -let pcdFolder = null; -const initGUIPCD = function(gui, targetLabel) { - if (pcdFolder != null) { - gui.removeFolder(pcdFolder); - pcdFolder = null; - } - if (targetLabel==null || !targetLabel.has(PCDLabelTool.candidateId) ) { - return; - } - const folder = gui.addFolder('PCD'); - pcdFolder = folder; - folder.open(); - const fpos = folder.addFolder('Position'); - fpos.open(); - fpos.add(guiRef.pcd, 'posX').name('x').max(30).min(-30).step(0.01).listen(); - fpos.add(guiRef.pcd, 'posY').name('y').max(30).min(-30).step(0.01).listen(); - fpos.add(guiRef.pcd, 'posZ').name('z').max(30).min(-30).step(0.01).listen(); - const fsize = folder.addFolder('Size'); - fsize.open(); - fsize.add(guiRef.pcd, 'sizeX').name('x').max(10).min(0.5).step(0.1).listen(); - fsize.add(guiRef.pcd, 'sizeY').name('y').max(10).min(0.5).step(0.1).listen(); - fsize.add(guiRef.pcd, 'sizeZ').name('z').max(10).min(0.5).step(0.1).listen(); - const frotate = folder.addFolder('Rotation'); - frotate.open(); - frotate.add(guiRef.pcd, 'yaw').name('yaw').max(180).min(-180).step(1).listen(); -}; -// Add tool -let toolFolder = null; -const toolRef = { - toggleType: () => { - LabelTool.toggleDataType(); - const tool = LabelTool.getTool(); - toolRef.toggleTypeItem.name('Tool[' + tool.name + ']'); - }, - toggleTypeItem: null, - save: () => { - LabelTool.saveFrame().then(()=>{ - console.log('saved'); - }, (err)=>{ - console.log('save error', err); - }); - }, - pcdMode: null, - changeMode: () => { - let name = PCDLabelTool.changeMode(); - toolRef.pcdMode.name('Mode['+name+']'); - }, - load: () => { - LabelTool.reloadFrame().then(()=>{ - console.log('reloaded'); - }, (err)=>{ - console.log('reload error', err); - }); - }, - deleteLabel: () => { - const targetLabel = LabelTool.getTargetLabel(); - if (targetLabel != null) { - LabelTool.removeLabel(targetLabel); - } - } -}; -const initGUITool = function(gui, targetLabel) { - if (toolFolder != null) { - gui.removeFolder(toolFolder); - toolFolder = null; - } - const folder = gui.addFolder('Tool'); - toolFolder = folder; - folder.open(); - const tool = LabelTool.getTool(); - toolRef.toggleTypeItem = folder.add(toolRef, 'toggleType').name('Tool[' + tool.name + ']'); - folder.add(toolRef, 'save').name('Save'); - folder.add(toolRef, 'load').name('Load'); - if (LabelTool.getTool() == PCDLabelTool) { - const mode = PCDLabelTool.getMode(); - toolRef.pcdMode = folder.add(toolRef, 'changeMode').name('Mode[' + mode + ']'); - } - // delete - if (targetLabel != null) { - folder.add(toolRef, 'deleteLabel').name('Delete'); - } -}; -const initGUI = function() { - gui = new dat.GUI(/*{autoPlace: false}*/); - //Controls.update(); - //$('').append(gui.domElement); -}; - - -const initToolBar = function() { -}; -const initSideBar = function() { -}; - -// export object -export default class Controls { - constructor(labelTool, imageLabelTool, pcdLabelTool) { - this.labelTool = labelTool; - this.imageLabelTool = imageLabelTool; - this.pcdLabelTool = pcdLabelTool; - LabelTool = labelTool; - ImageLabelTool = imageLabelTool; - PCDLabelTool = pcdLabelTool; - } - init() { - initGUI(); - initToolBar(); - initSideBar(); - return Promise.resolve(); - } - update() { - this.GUI.update(); - this.SideBar.update(); - } - error(e) { - if (e instanceof Error) { - console.error(e); - } else { - console.error(arguments); - } - } - GUI = { - update() { - const targetLabel = LabelTool.getTargetLabel(); - initGUITool(gui, targetLabel); - initGUIImage(gui, targetLabel); - initGUIPCD(gui, targetLabel); - } - } - ToolBar = { - update() { - } - } - SideBar = { - update() { - } - } -}; - - - diff --git a/front/app/labeling_tool/controls.jsx b/front/app/labeling_tool/controls.jsx new file mode 100644 index 00000000..0291145e --- /dev/null +++ b/front/app/labeling_tool/controls.jsx @@ -0,0 +1,964 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { withStyles } from '@material-ui/core/styles'; + +import AppBar from '@material-ui/core/AppBar'; +import TextField from '@material-ui/core/TextField'; +import Drawer from '@material-ui/core/Drawer'; +import Divider from '@material-ui/core/Divider'; +import Grid from '@material-ui/core/Grid'; +import Button from '@material-ui/core/Button'; +import IconButton from '@material-ui/core/IconButton'; +import {NavigateNext, NavigateBefore} from '@material-ui/icons'; + +import KlassSet from 'automan/labeling_tool/klass_set'; +import Annotation from 'automan/labeling_tool/annotation'; + +import ImageLabelTool from 'automan/labeling_tool/image_label_tool'; +import PCDLabelTool from 'automan/labeling_tool/pcd_label_tool'; + +import RequestClient from 'automan/services/request-client' + + +// toolbar status +const appBarHeight = 54; +// sidebar status +const drawerWidth = 160; +const toolHeight = 300; +const listHead = 20; +const controlsStyle = { + drawer: { + width: drawerWidth, + marginTop: appBarHeight, + overflow: 'auto' + }, + list: { + overflow: 'auto', + height: '100%', + position: 'relative' + }, + listHead: { + backgroundColor: '#eee', + color: '#000', + height: listHead, + lineHeight: listHead+'px' + }, + listItem: { + height: listHead + }, + selectedListItem: { + }, + appBar: { + width: '100%', + height: appBarHeight + }, + gridContainer: { + height: appBarHeight, + }, + gridItem: { + textAlign: 'center', + }, + frameNumberParts: { + color: '#000', + backgroundColor: '#fff', + borderRadius: 5, + width: 200 + }, + frameNumber: { + width: 100 + }, + toolControls: { + height: toolHeight, + textAlign: 'center' + }, + activeTool: { + border: 'solid 1px #000' + }, + labelList: { + height: `calc(40%)` + }, + klassSetList: { + textAlign: 'center', + margin: 'auto', + //height: `calc(25%)` + }, + colorPane: { + width: 18, + height: 18, + borderRadius: 2 + }, + content: { + marginLeft: drawerWidth, + width: `calc(100% - ${drawerWidth*2}px)`, + height: `calc(100% - ${appBarHeight}px)`, + overflow: 'hidden', + backgroundColor: '#000' + } +}; +/* +let ImageLabelTool, PCDLabelTool, LabelTool; + + +// dat.GUI menu tool +let gui = null; +const getLabel = function() { + return LabelTool.getTargetLabel(); +}; +const guiRef = { + label: { + get klass() { + let label = getLabel(); + if (label == null) { return null; } + return label.getKlassName(); + }, + set klass(name) { + let label = getLabel(); + if (label == null) { return null; } + let klass = KlassSet.getByName(name); + if (klass == null) { return null; } + Annotation.changeKlass(label, klass); + }, + }, + image: { + getBBox() { + let label = getLabel(); + if (label == null) { return null; } + return label.bbox[ImageLabelTool.candidateId]; + }, + // getters + get posX() { + let bbox = guiRef.image.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.min.x; + }, + get posY() { + let bbox = guiRef.image.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.min.y; + }, + get sizeX() { + let bbox = guiRef.image.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.getSize().x; + }, + get sizeY() { + let bbox = guiRef.image.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.getSize().y; + }, + // setters + set posX(v) { + let bbox = guiRef.image.getBBox(); + if (bbox == null) { return; } + bbox.dragStart(); + console.log(JSON.stringify({x: v, prev: bbox.box.min.x})); + bbox.dragMove(v - bbox.box.min.x, 0); + bbox.dragEnd(); + }, + set posY(v) { + let bbox = guiRef.image.getBBox(); + if (bbox == null) { return; } + bbox.dragStart(); + bbox.dragMove(0, v - bbox.box.min.y); + bbox.dragEnd(); + }, + set sizeX(v) { + let bbox = guiRef.image.getBBox(); + if (bbox == null) { return; } + let sx = bbox.box.getSize().x; + bbox.dragStart(); + bbox.setMaxX(v - sx); + bbox.dragEnd(); + }, + set sizeY(v) { + let bbox = guiRef.image.getBBox(); + if (bbox == null) { return; } + let sy = bbox.box.getSize().y; + bbox.dragStart(); + bbox.setMaxY(v - sy); + bbox.dragEnd(); + }, + }, + pcd: { + getBBox() { + let label = getLabel(); + if (label == null) { return null; } + return label.bbox[PCDLabelTool.candidateId]; + }, + // getters + get posX() { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.pos.x; + }, + get posY() { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.pos.y; + }, + get posZ() { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.pos.z; + }, + get sizeX() { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.size.x; + }, + get sizeY() { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.size.y; + }, + get sizeZ() { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + return bbox.box.size.z; + }, + get yaw() { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + let v = (bbox.box.yaw/Math.PI*180) % 360; + if (v < -180) { v += 360; } + else if (v > 180) { v -= 360; } + return v; + }, + // setters + set posX(v) { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + bbox.box.pos.x = v; + bbox.updateCube(); + PCDLabelTool.redrawRequest(); + }, + set posY(v) { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + bbox.box.pos.y = v; + bbox.updateCube(); + PCDLabelTool.redrawRequest(); + }, + set posZ(v) { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + bbox.box.pos.z = v; + bbox.updateCube(); + PCDLabelTool.redrawRequest(); + }, + set sizeX(v) { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + bbox.box.size.x = v; + bbox.updateCube(); + PCDLabelTool.redrawRequest(); + }, + set sizeY(v) { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + bbox.box.size.y = v; + bbox.updateCube(); + PCDLabelTool.redrawRequest(); + }, + set sizeZ(v) { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + bbox.box.size.z = v; + bbox.updateCube(); + PCDLabelTool.redrawRequest(); + }, + set yaw(v) { + let bbox = guiRef.pcd.getBBox(); + if (bbox == null) { return 0; } + bbox.box.yaw = v*Math.PI/180; + bbox.updateCube(); + PCDLabelTool.redrawRequest(); + }, + }, +}; +// Add image controller +let imageFolder = null; +const initGUIImage = function(gui, targetLabel) { + if (imageFolder != null) { + gui.removeFolder(imageFolder); + imageFolder = null; + } + if (targetLabel==null || !targetLabel.has(ImageLabelTool.candidateId) ) { + return; + } + const folder = gui.addFolder('Image'); + imageFolder = folder; + folder.open(); + const fpos = folder.addFolder('Position'); + fpos.open(); + fpos.add(guiRef.image, 'posX').name('x').min(0).max(1000).step(1).listen(); // TODO: get max pos + fpos.add(guiRef.image, 'posY').name('y').min(0).max(1000).step(1).listen(); + const fsize = folder.addFolder('Size'); + fsize.open(); + fsize.add(guiRef.image, 'sizeX').name('x').max(1000).min(10).step(1).listen(); // TODO: use minSize + fsize.add(guiRef.image, 'sizeY').name('y').max(1000).min(10).step(1).listen(); +}; +// Add pcd controller +let pcdFolder = null; +const initGUIPCD = function(gui, targetLabel) { + if (pcdFolder != null) { + gui.removeFolder(pcdFolder); + pcdFolder = null; + } + if (targetLabel==null || !targetLabel.has(PCDLabelTool.candidateId) ) { + return; + } + const folder = gui.addFolder('PCD'); + pcdFolder = folder; + folder.open(); + const fpos = folder.addFolder('Position'); + fpos.open(); + fpos.add(guiRef.pcd, 'posX').name('x').max(30).min(-30).step(0.01).listen(); + fpos.add(guiRef.pcd, 'posY').name('y').max(30).min(-30).step(0.01).listen(); + fpos.add(guiRef.pcd, 'posZ').name('z').max(30).min(-30).step(0.01).listen(); + const fsize = folder.addFolder('Size'); + fsize.open(); + fsize.add(guiRef.pcd, 'sizeX').name('x').max(10).min(0.5).step(0.1).listen(); + fsize.add(guiRef.pcd, 'sizeY').name('y').max(10).min(0.5).step(0.1).listen(); + fsize.add(guiRef.pcd, 'sizeZ').name('z').max(10).min(0.5).step(0.1).listen(); + const frotate = folder.addFolder('Rotation'); + frotate.open(); + frotate.add(guiRef.pcd, 'yaw').name('yaw').max(180).min(-180).step(1).listen(); +}; +// Add tool +let toolFolder = null; +const toolRef = { + toggleType: () => { + LabelTool.toggleDataType(); + const tool = LabelTool.getTool(); + toolRef.toggleTypeItem.name('Tool[' + tool.name + ']'); + }, + toggleTypeItem: null, + save: () => { + LabelTool.saveFrame().then(()=>{ + console.log('saved'); + }, (err)=>{ + console.log('save error', err); + }); + }, + pcdMode: null, + changeMode: () => { + let name = PCDLabelTool.changeMode(); + toolRef.pcdMode.name('Mode['+name+']'); + }, + load: () => { + LabelTool.reloadFrame().then(()=>{ + console.log('reloaded'); + }, (err)=>{ + console.log('reload error', err); + }); + }, + deleteLabel: () => { + const targetLabel = LabelTool.getTargetLabel(); + if (targetLabel != null) { + LabelTool.removeLabel(targetLabel); + } + } +}; +const initGUITool = function(gui, targetLabel) { + if (toolFolder != null) { + gui.removeFolder(toolFolder); + toolFolder = null; + } + const folder = gui.addFolder('Tool'); + toolFolder = folder; + folder.open(); + const tool = LabelTool.getTool(); + toolRef.toggleTypeItem = folder.add(toolRef, 'toggleType').name('Tool[' + tool.name + ']'); + folder.add(toolRef, 'save').name('Save'); + folder.add(toolRef, 'load').name('Load'); + if (LabelTool.getTool() == PCDLabelTool) { + const mode = PCDLabelTool.getMode(); + toolRef.pcdMode = folder.add(toolRef, 'changeMode').name('Mode[' + mode + ']'); + } + // delete + if (targetLabel != null) { + folder.add(toolRef, 'deleteLabel').name('Delete'); + } +}; +const initGUI = function() { + gui = new dat.GUI({autoPlace: false}); + //Controls.update(); + //$('').append(gui.domElement); +}; + + +const initToolBar = function() { +}; +const initSideBar = function() { +}; + +// export object +class Controls { + constructor(labelTool, imageLabelTool, pcdLabelTool) { + this.labelTool = labelTool; + this.imageLabelTool = imageLabelTool; + this.pcdLabelTool = pcdLabelTool; + LabelTool = labelTool; + ImageLabelTool = imageLabelTool; + PCDLabelTool = pcdLabelTool; + } + init() { + initGUI(); + initToolBar(); + initSideBar(); + return Promise.resolve(); + } + update() { + this.GUI.update(); + this.SideBar.update(); + } + error(e) { + if (e instanceof Error) { + console.error(e); + } else { + console.error(arguments); + } + } + GUI = { + update() { + const targetLabel = LabelTool.getTargetLabel(); + initGUITool(gui, targetLabel); + initGUIImage(gui, targetLabel); + initGUIPCD(gui, targetLabel); + } + } + ToolBar = { + update() { + } + } + SideBar = { + update() { + } + } +}; +*/ +class Controls extends React.Component { + labelTool = null; + annotation = null; + getAnnotation = (tgt) => { this.annotation = tgt; } + klassSet = null; + getKlassSet = (tgt) => { this.klassSet= tgt; } + // progress + frameLength = 0; + // navigation + //pageBox = null; + //nextFrameButton = null; + //prevFrameButton = null; + //frameSkipText = null; + content = null; + // tool status + tools = []; + toolNames = []; + toolComponents = []; + + constructor(props) { + super(props); + this.state = { + frameNumber: 0, + skipFrameCount: 1, + activeTool: 0 + }; + this.labelTool = props.labelTool; + + this.frameLength = this.labelTool.frameLength; + this.initTools(); + } + initTools() { + // load labeling tools + const LABEL_TYPES = { + BB2D: { + tools: [ImageLabelTool, PCDLabelTool], + names: ['2D', '3D'] + }, + BB2D3D: { + tools: [ImageLabelTool, PCDLabelTool], + names: ['2D', '3D'] + } + }; + const type = LABEL_TYPES[this.labelTool.labelType]; + if (type == null) { + console.error('Tool type error [' + this.labelTool.labelType + ']'); + return; + } + this.toolNames = type.names; + this.toolComponents = []; + this.tools = type.tools.map((tool, idx) => { + const ref = React.createRef(); + const Component = tool; + const component = ( + + ); + this.toolComponents.push(component); + return ref; + }); + console.log(this.tools, this.toolComponents); + } + init() { + return Promise.all([ + this.annotation.init(this.klassSet), + this.klassSet.init() + ]); + } + resize() { + // TODO: resize all + const w = $(window); + const size = { + width: w.width() - drawerWidth * 2, + height: w.height() - appBarHeight + }; + this.tools.forEach((tool) => { + tool.current.handles.resize(size); + }); + } + initEvent() { + $(window) + .keydown(e => { + console.log(`window keydown(code = ${e.keyCode})`); + if (e.keyCode == 8 || e.keyCode == 46) { + // Backspace or Delete + const label = this.getTargetLabel(); + if (label != null) { + this.removeLabel(label); + } + } else if (e.keyCode == 39) { + this.nextFrame(); + } else if (e.keyCode == 37) { + this.previousFrame(); + } else { + this.getTool().handles.keydown(e); + } + }) + .keyup(e => { + this.getTool().handles.keyup(e); + }); + + window.addEventListener('resize', () => { + this.resize(); + }); + + // header setup + /* + toolStatus.nextFrameButton.keyup(function(e) { + if (e.which === 32) { + return false; + } + }); + toolStatus.nextFrameButton.click(() => { + LabelTool.nextFrame(); + }); + toolStatus.prevFrameButton.click(() => { + LabelTool.previousFrame(); + }); + toolStatus.frameSkipText.change(function() { + let value = $(this).val(); + if (value == '') { + value = 1; + } else { + value = parseInt(value); + } + value = Math.max(value, 1); + toolStatus.skipFrameCount = value; + $(this).val(value); + }); + toolStatus.pageBox.keyup(e => { + if (e.keyCode === 13) { + LabelTool.setFrame(toolStatus.pageBox.val() - 1); + toolStatus.pageBox.blur(); + } + }); + */ + } + isLoaded() { + // ********* + } + + selectKlass(kls) { + if (!this.labelTool.isLoaded()) { + return false; + } + let newKls = this.klassSet.setTarget(kls); + if (newKls !== null) { + const label = this.annotation.getTarget(); + if (label !== null) { + this.annotation.changeKlass(label, newKls); + // update ?? + } + } else { + return false; + } + return true; + // ********* + } + getTargetKlass() { + return this.klassSet.getTarget(); + } + getKlass(name) { + return this.klassSet.getByName(name); + } + selectLabel(label) { + if (!this.labelTool.isLoaded()) { + return false; + } + let newLabel; + newLabel = this.annotation.setTarget(label); + if (newLabel !== null) { + this.klassSet.setTarget(newLabel.klass); + } + //update + return true; + // ********* + } + getTargetLabel() { + return this.annotation.getTarget(); + } + createLabel(klass, param) { + if (!this.labelTool.isLoaded()) { + return null; + } + let newLabel = null; + try { + newLabel = this.annotation.create(klass, param); + } catch (e) { + // error + console.log(e); + return null; + } + this.annotation.setTarget(newLabel); + this.klassSet.setTarget(newLabel.klass); + // update ?? + return newLabel; + // ********* + } + removeLabel(label) { + if (!this.labelTool.isLoaded()) { + return false; + } + try { + this.annotation.remove(label); + } catch (e) { + // error + return false; + } + // update + return true; + // ********* + } + + getTools() { + return this.tools.map(ref => ref.current); + } + setTool(idx) { + const activeTool = this.state.activeTool + if (activeTool === idx) { + return; + } + const prevTool = this.tools[activeTool].current; + const nextTool = this.tools[idx].current; + this.setState({activeTool: idx}); + prevTool.setActive(false); + nextTool.setActive(true); + // update ?? + // ********* + } + getTool() { + return this.tools[this.state.activeTool].current; + } + getToolFromCandidateId(id) { + const filtered = this.getTools().filter(tool => + tool.isTargetCandidate(id) + ); + if (filtered.length != 1) { + //controls.error('candidate id error'); + return null; + } + return filtered[0]; + // ********* + } + + nextFrame(count) { + if (count == undefined) { + count = this.state.skipFrameCount; + } + this.moveFrame(count); + } + previousFrame(count) { + if (count == undefined) { + count = this.state.skipFrameCount; + } + this.moveFrame(-count); + } + moveFrame(cnt) { + // TODO: check type of'cnt' + let newFrame = this.state.frameNumber + cnt; + newFrame = Math.max(newFrame, 0); + newFrame = Math.min(this.frameLength - 1, newFrame); + if (window.isFinite(newFrame)) { + return this.setFrameNumber(newFrame); + } + return false; + // ********* + } + isLoading = false; + setFrameNumber(num) { + num = parseInt(num); + if (isNaN(num) || num < 0 || this.frameLength <= num) { + return false; + } + if (this.state.frameNumber === num) { + return true; + } + + this.loadFrame(num).then( + () => { + }, + () => { + } + ); + return true; + } + + saveFrame() { + return this.annotation.save(); + } + loadFrame(num) { + if (this.isLoading) { + return Promise.reject('duplicate loading'); + } + this.selectLabel(null); + + this.isLoading = true; + if (num == null) { + num = this.state.frameNumber; + } + + this.isLoading = true; + console.log('start loadFrame('+num+')'); + return this.labelTool.loadBlobURL(num) + .then(() => { + console.log(' start annotation load'); + return this.annotation.load(num); + }) + .then(() => { + console.log(' start tool load'); + return Promise.all( + this.getTools().map( + tool => tool.load(num) + ) + ); + }) + .then(() => { + console.log('load end'); + this.isLoading = false; + this.setState({frameNumber: num}); + }); + } + getFrameNumber() { + return this.state.frameNumber; + // ********* + } + + + componentDidMount() { + this.labelTool.candidateInfo.forEach(info => { + this.getTools().forEach(tool => { + if (tool.dataType === info.data_type) { + if (tool.candidateId >= 0) { + return; + } + tool.candidateId = info.candidate_id; // TODO: multi candidate_id + this.labelTool.filenames[tool.candidateId] = []; + } + }); + }); + + this.tools[this.state.activeTool].current.setActive(true); + + this.resize(); + + this.init().then(() => { + this.props.onload(this); + }); + } + + // events + onClickNextFrame = (e) => { + this.nextFrame(); + }; + onClickPrevFrame = (e) => { + this.previousFrame(); + }; + onFrameBlurOrFocus = (e) => { + e.target.value = ''; + }; + onFrameKeyPress = (e) => { + // only when enter + if (e.charCode == 13) { + let value = +(e.target.value); + this.moveFrame(value); + e.target.value = ''; + e.preventDefault(); + return; + } + }; + + renderKlassSet(classes) { + return ( + + ); + } + renderLabels(classes) { + return ( + + ); + } + renderLeftBar(classes) { + const toolButtons = []; + this.toolNames.forEach((name, idx) => { + const cls = this.state.activeTool === idx ? classes.activeTool : ''; + const button = ( + + ); + toolButtons.push(button); + }); + return ( + +
    + Tools + + + + {toolButtons} + + + + + + + + + + + + + + + +
    + +
    + {this.renderLabels(classes)} +
    +
    + ); + } + render() { + console.log('rerender'); + const classes = this.props.classes; + let frameNumberForm = ( +
    + + + + + + + +
    + ); + let appBar = ( + + + + {frameNumberForm} + + + {this.renderKlassSet(classes)} + + + + ); + + let editBar = ( + + test + + ); + return ( +
    + {appBar} + {this.renderLeftBar(classes)} +
    + {this.toolComponents} +
    + {editBar} +
    + ); + } +} +export default withStyles(controlsStyle)(Controls); + diff --git a/front/app/labeling_tool/image_label_tool.js b/front/app/labeling_tool/image_label_tool.jsx similarity index 84% rename from front/app/labeling_tool/image_label_tool.js rename to front/app/labeling_tool/image_label_tool.jsx index d9c032ee..59831764 100644 --- a/front/app/labeling_tool/image_label_tool.js +++ b/front/app/labeling_tool/image_label_tool.jsx @@ -1,14 +1,52 @@ -export default class ImageLabelTool { +import React from 'react'; +import ReactDOM from 'react-dom'; + +export default class ImageLabelTool_ extends React.Component { + constructor(props) { + super(props); + this.state = { + scale: 1.0 + }; + this._labelTool = props.labelTool; + this._controls = props.controls; + this._element = React.createRef(); + } + componentDidMount() { + console.log('image componentDidMount()'); + this.init(); + } + render() { + const wrapperStyle = { + transform: `scale(${this.state.scale})`, + transformOrigin: 'center top' + }; + return ( +
    + ); + } + // private + _controls = null; _labelTool = null; _decoration = null; // DOM + _element = null; _container = null; _paper = null; _loaded = true; _image = null; - _scale = 1.0; // TODO: use scale + _imageSize = { + width: 0, + height: 0 + }; + _wrapperSize = { + width: 0, + height: 0 + }; // public name = 'Image'; @@ -21,21 +59,19 @@ export default class ImageLabelTool { isTargetCandidate(id) { return this.candidateId == id; } - constructor(labelTool) { - this._labelTool = labelTool; - } init() { - const container = $('#jpeg-label-canvas'); - this._paper = Raphael(container.get(0)); - this._container = container; + //const container = $('#jpeg-label-canvas'); + const container = this._element.current; + this._paper = Raphael(container); + this._container = $(container); this._decoration = new Decorations(this._paper); - container.hide(); + this._container.hide(); } - load() { + load(frame) { this._loaded = false; // TODO: use getURL - const imgURL = this._labelTool.getURL('frame_blob', this.candidateId); + const imgURL = this._labelTool.getURL('frame_blob', this.candidateId, frame); this._initImage(imgURL); this._decoration.hide(); @@ -45,11 +81,13 @@ export default class ImageLabelTool { }); } handles = { - resize() { + resize: size => { + this._wrapperSize = size; + this._resize(); }, - keydown(e) { + keydown: e =>{ }, - keyup(e) { + keyup: e =>{ } }; setActive(isActive) { @@ -88,6 +126,19 @@ export default class ImageLabelTool { + _resize() { + const paper = this._paper; + + let scale = Math.min( + this._wrapperSize.width / this._imageSize.width, + this._wrapperSize.height / this._imageSize.height + ); + paper.setSize( + this._imageSize.width, + this._imageSize.height + ); + this.setState({ scale: scale }); + } _initImage(url) { if (this._image != null) { this._image.remove(); @@ -98,7 +149,11 @@ export default class ImageLabelTool { img.src = url; img.addEventListener('load', () => { // TODO: resize method - paper.setSize(img.width, img.height); + this._imageSize = { + width: img.width, + height: img.height + }; + this._resize(); img = null; // dispose image }); const image = paper.image(url, 0, 0, "100%", "100%"); @@ -118,7 +173,9 @@ export default class ImageLabelTool { _creatingRect = null; _creatingBox = null; _imageDragMove = (dx, dy) => { - const klass = this._labelTool.getTargetKlass(); + const klass = this._controls.getTargetKlass(); + dx = dx / this.state.scale | 0; + dy = dy / this.state.scale | 0; if (this._creatingRect == null) { const posDiff = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); if (posDiff >= 10) { @@ -148,11 +205,11 @@ export default class ImageLabelTool { } }; _imageDragStart = (x, y) => { - this._labelTool.selectLabel(null); + this._controls.selectLabel(null); const offset = this._container.offset(); this._creatingBox = { - sx: x-offset.left, - sy: y-offset.top, + sx: (x - offset.left) / this.state.scale | 0, + sy: (y - offset.top) / this.state.scale | 0, ex: 0, ey: 0 }; }; @@ -167,8 +224,8 @@ export default class ImageLabelTool { 'max_y_2d': Math.max(box.sy, box.ey) }); // TODO: add branch use selecting label - const label = this._labelTool.createLabel( - this._labelTool.getTargetKlass(), + const label = this._controls.createLabel( + this._controls.getTargetKlass(), {[this.candidateId]: imageBBox} ); if (label == null) { @@ -304,13 +361,16 @@ class ImageBBox { this.initRect(); this.initResizer(); } + getScale() { + return this.imageTool.state.scale; + } setLabel(label) { if (this.label != null) { // TODO: control error throw "Label already set"; } this.label = label; - this.labelItem = label.addBBox('Image'); + //this.labelItem = label.addBBox('Image'); this.rect.attr({ 'stroke': label.getColor() }); } updateKlass() { @@ -318,7 +378,7 @@ class ImageBBox { this.deco.show(this); } remove() { - this.labelItem.remove(); + //this.labelItem.remove(); this.rect.remove(); this.edgeResizers.forEach(it => it.remove()); this.cornerResizers.forEach(it => it.remove()); @@ -376,18 +436,21 @@ class ImageBBox { resizerDragBL(dx, dy) { this.setMinX(dx); this.setMaxY(dy); this.setVisiblePos(); } resizerDragBR(dx, dy) { this.setMaxX(dx); this.setMaxY(dy); this.setVisiblePos(); } setMinX(dx) { + dx = dx / this.getScale() | 0; const prevBox = this.prev.box; const x = Math.min(Math.max(prevBox.min.x+dx, 0), prevBox.max.x - this.label.getMinSize().x); this.box.min.x = x; } setMinY(dy) { + dy = dy / this.getScale() | 0; const prevBox = this.prev.box; const y = Math.min(Math.max(prevBox.min.y+dy, 0), prevBox.max.y - this.label.getMinSize().y); this.box.min.y = y; } setMaxX(dx) { + dx = dx / this.getScale() | 0; const prevBox = this.prev.box; const width = this.paper.width; const x = Math.max(Math.min(prevBox.max.x+dx, width), @@ -395,6 +458,7 @@ class ImageBBox { this.box.max.x = x; } setMaxY(dy) { + dy = dy / this.getScale() | 0; const prevBox = this.prev.box; const height = this.paper.height; const y = Math.max(Math.min(prevBox.max.y+dy, height), @@ -407,6 +471,8 @@ class ImageBBox { this.cornerResizers.forEach(it => it.toFront()); } dragMove(dx, dy) { + dx = dx / this.getScale() | 0; + dy = dy / this.getScale() | 0; const prev = this.prev; const width = this.paper.width, height = this.paper.height; @@ -418,7 +484,7 @@ class ImageBBox { this.setVisiblePos(); } dragStart() { - this.imageTool._labelTool.selectLabel(this.label); + this.imageTool._controls.selectLabel(this.label); this.prev = { box: this.box.clone() }; diff --git a/front/app/labeling_tool/klass_set.js b/front/app/labeling_tool/klass_set.js index 08f3424c..dd4dbea5 100644 --- a/front/app/labeling_tool/klass_set.js +++ b/front/app/labeling_tool/klass_set.js @@ -1,45 +1,74 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { withStyles } from '@material-ui/core/styles'; +//import List from '@material-ui/core/List'; +//import ListItem from '@material-ui/core/ListItem'; +//import ListItemIcon from '@material-ui/core/ListItemIcon'; +//import ListItemText from '@material-ui/core/ListItemText'; +//import ListSubheader from '@material-ui/core/ListSubheader'; -export default class KlassSet { +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; + +import classNames from 'classnames'; + +const KlassTab = withStyles(theme => ({ + root: { + '&:hover': { + backgroundColor: 'rgba(0, 0, 0, 0.1)', + }, + '&$selected': { + backgroundColor: 'rgba(0, 0, 0, 0.3)', + } + } +}))(props => ); + +class KlassSet extends React.Component { _labelTool = null; - _klasses = null; - _targetKlass = null; + _controls = null; + // data + _klasses = new Map(); + _klassList = []; // DOM - _klassSetList = null; _nextId = 0; - constructor(labelTool) { - this._labelTool = labelTool; + constructor(props) { + super(props); + this._labelTool = props.labelTool; + this._controls = props.controls; + this.state = { + targetKlass: null, + targetIndex: -1 + }; + props.getRef(this); } init() { - const klassSetList = $('#class-list') - .css({ - 'cursor': 'pointer' - }); - this._klassSetList = klassSetList; - this._klasses = new Map(); return new Promise((resolve, reject) => { let klassset = this._labelTool.getProjectInfo().klassset; klassset.records.forEach((klass) => { let config = JSON.parse(klass.config); - this._klasses.set( + let klassObj = new Klass( + this, + this._nextId++, klass.name, - new Klass( - this, - this._nextId++, - klass.name, - config.color, - new THREE.Vector2(config.minSize.x, config.minSize.y), - this._klassSetList - ) + config.color, + new THREE.Vector2(config.minSize.x, config.minSize.y) ); + this._klasses.set(klass.name, klassObj); + this._klassList[klassObj.id] = klassObj; }); // select default target class - this._targetKlass = this._klasses.get( - klassset.records[0].name - ); - this._targetKlass.setActive(true); + + this.setState({ + /* + targetKlass: this._klasses.get( + klassset.records[0].name + ) + */ + targetIndex: 0 + }); resolve(); }); } @@ -53,18 +82,18 @@ export default class KlassSet { return this._klasses.get(name); } getTarget() { - return this._targetKlass; + //return this.state.targetKlass; + return this._klassList[this.state.targetIndex]; } setTarget(tgt) { let next = this._getKlass(tgt), - prev = this._targetKlass; + prev = this.getTarget(); if (next.id === prev.id) { return prev; } - this._targetKlass = next; + //this.setState({targetKlass: next}); + this.setState({targetIndex: next.id}); // DOM change - prev.setActive(false); - next.setActive(true); return next; } _getKlass(kls) { @@ -75,59 +104,91 @@ export default class KlassSet { } return null; } + /* + renderList(classes) { + let list = []; + for (let [_, klass] of this._klasses) { + const isSelected = klass === this.state.targetKlass; + list.push( + this._controls.selectKlass(klass)} + button + > +
    + + + ); + } + return list; + } + */ + renderTabs(classes) { + let list = []; + for (let klass of this._klassList) { + const isSelected = klass.id === this.state.targetIndex; + list.push( + +
    + {klass.name} +
    + )} + /> + ); + } + return list; + } + handleTabChange = (e, newVal) => { + this._controls.selectKlass(this._klassList[newVal]); + this.setState({ targetIndex: newVal }); + }; + render() { + const classes = this.props.classes; + return ( + + {this.renderTabs(classes)} + + ); + /* + return ( + + Class Set + + } + > + {this.renderList(classes)} + + ); + */ + } }; +export default KlassSet; class Klass { - constructor(klassSet, id, name, color, size, klassSetList) { + constructor(klassSet, id, name, color, size) { this.klassSet = klassSet; this.id = id; this.name = name; this.color = color; this.minSize = size; - - // TODO: redesign - const dom = $('
    '); - dom.append( - $('') - .text(' ') - .css({ - background: color, - width: '10px', - height: '10px', - display: 'inline-block' - }), - $('') - .text(' ' + name) - ).css({ - 'color': 'white', - }).click(() => { - this.klassSet._labelTool.selectKlass(this); - }).hover(() => { - if (this.klassSet.getTarget() !== this) { - dom.css({ - 'background': '#aaa' - }); - } - }, () => { - if (this.klassSet.getTarget() !== this) { - dom.css({ - 'background': '' - }); - } - }); - this.dom = dom; - this.klassSet._klassSetList.append(dom); - } - setActive(flag) { - this.dom.css( - flag ? { - 'background': '#fff', - 'color': '#333' - } : { - 'background': '', - 'color': '#fff' - } - ); } getName() { return this.name; diff --git a/front/app/labeling_tool/label_tool.jsx b/front/app/labeling_tool/label_tool.jsx index 43166381..51c9e82d 100644 --- a/front/app/labeling_tool/label_tool.jsx +++ b/front/app/labeling_tool/label_tool.jsx @@ -1,15 +1,61 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; -/* -import RequestClient from 'automan/services/request-client' - -import Annotation from 'automan/labeling_tool/annotation' -import ImageLabelTool from 'automan/labeling_tool/image_label_tool' -import PCDLabelTool from 'automan/labeling_tool/pcd_label_tool' -import Controls from 'automan/labeling_tool/controls' -import KlassSet from 'automan/labeling_tool/klass_set' -*/ import LabelTool from 'automan/labeling_tool/base_label_tool' +ReactDOM.render( +
    + +
    , + document.getElementById('wrapper') +); +/* +class TestParent extends React.Component { + constructor(props) { + super(props); + this.elem = ; + } + componentDidMount() { + console.log('parent.componentDidMount()'); + } + render() { + return ( +
    + {this.elem} +
    + ); + } +} +class TestChild extends React.Component { + constructor(props) { + super(props); + this.state = { + a: false + }; + this.test = React.createRef(); + } + componentDidMount() { + console.log('child.componentDidMount()'); + $(this.test.current).append($('
    aaa
    ')); + } + handle = () => { + this.setState({a: !this.state.a}); + console.log('handle'); + } + render() { + return ( +
    + test {this.state.a.toString()} +
    + ); + } +} + +ReactDOM.render( + , + document.getElementById('wrapper') +); +*/ diff --git a/front/app/labeling_tool/pcd_label_tool.js b/front/app/labeling_tool/pcd_label_tool.jsx similarity index 90% rename from front/app/labeling_tool/pcd_label_tool.js rename to front/app/labeling_tool/pcd_label_tool.jsx index 549c6028..664a5d9f 100644 --- a/front/app/labeling_tool/pcd_label_tool.js +++ b/front/app/labeling_tool/pcd_label_tool.jsx @@ -1,21 +1,41 @@ -const toolStatus = { -}; +import React from 'react'; +import ReactDOM from 'react-dom'; + // 3d eidt arrow const arrowColors = [0xff0000, 0x00ff00, 0x0000ff], hoverColors = [0xffaaaa, 0xaaffaa, 0xaaaaff], AXES = [new THREE.Vector3(1,0,0), new THREE.Vector3(0,1,0), new THREE.Vector3(0,0,1)]; -export default class PCDLabelTool{ +export default class PCDLabelTool_ extends React.Component { + constructor(props) { + super(props); + this.state = { + }; + this._labelTool = props.labelTool; + this._controls = props.controls; + this._element = React.createRef(); + } + componentDidMount() { + console.log('pcd componentDidMount()'); + this.init(); + } + render() { + return ( +
    + ); + } + // private + _canvasSize = { width: 2, height: 1 }; _labelTool = null; _wrapper = null; _loaded = true; _scene = null; _renderer = null; _camera = null; - _controls = null; + _cameraControls = null; //cameraExMat = new THREE.Matrix4(); // PCD objects _pcdLoader = null; @@ -55,9 +75,6 @@ export default class PCDLabelTool{ isTargetCandidate(id) { return this.candidateId == id; } - constructor(labelTool) { - this._labelTool = labelTool; - } init() { if ( !Detector.webgl ) { Detector.addGetWebGLMessage(); @@ -73,14 +90,14 @@ export default class PCDLabelTool{ this._animate(); } - load() { + load(frame) { this._loaded = false; - const frame = this._labelTool.getFrameNumber(); - const url = this._labelTool.getURL('frame_blob', this.candidateId); + const url = this._labelTool.getURL('frame_blob', this.candidateId, frame); this._pointMeshes.forEach(mesh => { mesh.visible = false; }); // use preloaded pcd mesh if (this._pointMeshes[frame] != null) { this._pointMeshes[frame].visible =true; + this._redrawFlag = true; this._loaded = true; return Promise.resolve(); } @@ -100,12 +117,21 @@ export default class PCDLabelTool{ }); } handles = { - resize: () => { - /* - camera.aspect = window.innerWidth / window.innerHeight; + resize: size => { + console.log('3D resize'); + this._canvasSize = size; + const camera = this._camera; + if (camera instanceof THREE.OrthographicCamera) { + const y = camera.right * size.height / size.width; + camera.top = y; + camera.bottom = -y; + console.log(camera.right, camera.top); + } else { + camera.aspect = size.width / size.height; + } camera.updateProjectionMatrix(); - renderer.setSize( window.innerWidth, window.innerHeight ); - */ + this._renderer.setSize(size.width, size.height); + this._redrawFlag = true; }, keydown: (e) => { if (e.keyCode === 16) { // shift @@ -141,6 +167,7 @@ export default class PCDLabelTool{ setActive(isActive) { if ( isActive ) { this._wrapper.show(); + this._redrawFlag = true; } else { this._wrapper.hide(); } @@ -192,25 +219,40 @@ export default class PCDLabelTool{ const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setClearColor(0x000000); renderer.setPixelRatio(window.devicePixelRatio); - renderer.setSize(window.innerWidth, window.innerHeight); + renderer.setSize(this._canvasSize.width, this._canvasSize.height); this._renderer = renderer; this._scene = scene; + const GRID_SIZE = 100; + const gridPlane = new THREE.GridHelper(GRID_SIZE, GRID_SIZE/5); + gridPlane.rotation.x = Math.PI/2; + gridPlane.position.x = 0; + gridPlane.position.y = 0; + gridPlane.position.z = -1; + this._gridPlane = gridPlane; + this._scene.add(gridPlane); + + + const pcdLoader = new THREE.PCDLoader(); this._pcdLoader = pcdLoader; } _initCamera() { // TODO: read YAML and set camera? let camera; + const NEAR = 1, FAR = 2000; + const aspect = this._canvasSize.width / this._canvasSize.height; if(this._isBirdView){ - camera = new THREE.OrthographicCamera (-40,40,20,-20, 10, 2000); + const x = 40, y = x / aspect; + camera = new THREE.OrthographicCamera(-x, x, y, -y, NEAR, FAR); camera.position.set (0,0,450); camera.lookAt (new THREE.Vector3(0,0,0)); }else{ - camera = new THREE.PerspectiveCamera( 90, window.innerWidth / window.innerHeight, 0.01, 10000 ); + camera = new THREE.PerspectiveCamera( 90, aspect, NEAR, FAR); camera.position.set(0,0,0.5); } - camera.up.set (0,0,1); + camera.up.set(0,0,1); + window.camera = camera; this._scene.add( camera ); const controls = new THREE.OrbitControls(camera, this._renderer.domElement); @@ -230,10 +272,11 @@ export default class PCDLabelTool{ controls.update(); this._camera = camera; - this._controls = controls; + this._cameraControls = controls; } _initDom() { - const wrapper = $('#canvas3d'); // change dom id + //const wrapper = $('#canvas3d'); // change dom id + const wrapper = $(this._element.current); // change dom id wrapper.append(this._renderer.domElement); this._wrapper = wrapper; wrapper.hide(); @@ -456,13 +499,13 @@ class PCDBBox { throw "Label already set"; } this.label = label; - this.labelItem = label.addBBox('PCD'); + //this.labelItem = label.addBBox('PCD'); } updateKlass() { } remove() { // TODO: remove meshes - this.labelItem.remove(); + //this.labelItem.remove(); const mesh = this.cube.mesh; this.pcdTool._scene.remove(mesh); this.pcdTool._redrawFlag = true; @@ -528,9 +571,9 @@ function createModeMethods(pcdTool) { this.mouse = pcdTool.getMousePos(e); pcdTool._modeStatus.busy = true; } else if (this.prevHover != null) { - pcdTool._labelTool.selectLabel(this.prevHover.label); + pcdTool._controls.selectLabel(this.prevHover.label); } else { - pcdTool._labelTool.selectLabel(null); + pcdTool._controls.selectLabel(null); } }, resetHover: function() { @@ -580,7 +623,7 @@ function createModeMethods(pcdTool) { }, mouseMove: function(e) { const ray = pcdTool.getRay(e); - const label = pcdTool._labelTool.getTargetLabel(); + const label = pcdTool._controls.getTargetLabel(); if (this.selectFace != null) { if (label == null) { return; } // TODO: this is error // TODO: 3d controlable @@ -653,7 +696,7 @@ function createModeMethods(pcdTool) { animate: function() { }, mouseDown: function(e) { - const label = pcdTool._labelTool.getTargetLabel(); + const label = pcdTool._controls.getTargetLabel(); if (label != null) { this.mouse = pcdTool.getMousePos(e); pcdTool._modeStatus.busy = true; @@ -661,7 +704,7 @@ function createModeMethods(pcdTool) { }, mouseMove: function(e) { if (this.mouse != null) { - const label = pcdTool._labelTool.getTargetLabel(); + const label = pcdTool._controls.getTargetLabel(); if (label == null) { return; } // TODO: this is error // TODO: 3d controlable const mouse = pcdTool.getMousePos(e); @@ -702,9 +745,9 @@ function createModeMethods(pcdTool) { }; pcdTool._modeStatus.busy = true; } else if (this.prevHover != null) { - pcdTool._labelTool.selectLabel(this.prevHover.label); + pcdTool._controls.selectLabel(this.prevHover.label); } else { - pcdTool._labelTool.selectLabel(null); + pcdTool._controls.selectLabel(null); } }, resetHover: function() { @@ -720,7 +763,7 @@ function createModeMethods(pcdTool) { }, mouseMove: function(e) { if (this.arrowMoving != null) { - const label = pcdTool._labelTool.getTargetLabel(); + const label = pcdTool._controls.getTargetLabel(); if (label == null) { return; } // TODO: this is error // TODO: support Z axis const pos = pcdTool.getMousePos(e); // TODO: need 3d mouse pos @@ -733,7 +776,7 @@ function createModeMethods(pcdTool) { pcdTool._redrawFlag = true; /* const move = pos.clone().sub(this.arrowMoving.mouse).multiply(AXES[this.arrowMoving.arrow]); - const label = pcdTool._labelTool.getTargetLabel(); + const label = pcdTool._controls.getTargetLabel(); if (label == null) { return; } // TODO: this is error const bbox = label.bbox[pcdTool.candidateId]; bbox.box.pos.add(move); @@ -859,8 +902,8 @@ function createModeMethods(pcdTool) { 'rotation_y': bbox.box.rotation.z, }); // TODO: add branch use selecting label - const label = pcdTool._labelTool.createLabel( - pcdTool._labelTool.getTargetKlass(), + const label = pcdTool._controls.createLabel( + pcdTool._controls.getTargetKlass(), {[pcdTool.candidateId]: pcdBBox} ); pcdTool._scene.remove(bbox.box); @@ -878,7 +921,7 @@ function createModeMethods(pcdTool) { 'view': { animate: function() { pcdTool._redrawFlag = true; - pcdTool._controls.update(); + pcdTool._cameraControls.update(); }, mouseDown: function(e) { pcdTool._modeStatus.busy = true; @@ -888,10 +931,10 @@ function createModeMethods(pcdTool) { mouseUp: function(e) { }, changeFrom: function() { - pcdTool._controls.enabled = false; + pcdTool._cameraControls.enabled = false; }, changeTo: function() { - pcdTool._controls.enabled = true; + pcdTool._cameraControls.enabled = true; pcdTool._wrapper.css('cursor', 'all-scroll'); }, }, From 71712102b1312fe4be4f8a333d7cb8b3c697f60d Mon Sep 17 00:00:00 2001 From: Yuto Jumonji Date: Wed, 18 Sep 2019 13:42:46 +0900 Subject: [PATCH 2/3] feature: Labeling tool BIG DESIGN CHANGE --- .../labeling_tool/labeling_tool.html | 4 +- front/app/labeling_tool/annotation.js | 2 - front/app/labeling_tool/base_label_tool.jsx | 6 - front/app/labeling_tool/controls.jsx | 605 +++----------- front/app/labeling_tool/image_label_tool.jsx | 13 +- front/app/labeling_tool/klass_set.js | 3 - front/app/labeling_tool/label_tool.jsx | 50 +- front/app/labeling_tool/pcd_label_tool.jsx | 760 ++++++++++-------- front/app/labeling_tool/tool-style.js | 88 ++ front/static/css/labeltool.css | 312 +------ 10 files changed, 627 insertions(+), 1216 deletions(-) create mode 100644 front/app/labeling_tool/tool-style.js diff --git a/automan/templates/labeling_tool/labeling_tool.html b/automan/templates/labeling_tool/labeling_tool.html index f3fdc8b9..4b855334 100644 --- a/automan/templates/labeling_tool/labeling_tool.html +++ b/automan/templates/labeling_tool/labeling_tool.html @@ -1,5 +1,5 @@ - + @@ -29,7 +29,7 @@ - +
    - - - - - -
    +