diff --git a/app/api/annotator.py b/app/api/annotator.py index e95278ad..53071495 100644 --- a/app/api/annotator.py +++ b/app/api/annotator.py @@ -46,7 +46,11 @@ def post(self): if db_category is None: continue - db_category.update(set__color=category.get('color')) + db_category.update( + set__color=category.get('color'), + set__keypoint_edges=category.get('keypoint_edges', []), + set__keypoint_labels=category.get('keypoint_labels', []) + ) # Iterate every annotation from the data annotations for annotation in category.get('annotations', []): @@ -63,6 +67,7 @@ def post(self): # Update annotation in database db_annotation.update( + set__keypoints=annotation.get('keypoints', []), set__metadata=annotation.get('metadata'), set__color=annotation.get('color') ) diff --git a/app/models.py b/app/models.py index 07dab566..31566c72 100644 --- a/app/models.py +++ b/app/models.py @@ -221,6 +221,8 @@ class AnnotationModel(db.DynamicDocument): height = db.IntField() color = db.StringField() + + keypoints = db.ListField(default=[]) metadata = db.DictField(default={}) paper_object = db.ListField(default=[]) @@ -311,6 +313,10 @@ class CategoryModel(db.DynamicDocument): deleted = db.BooleanField(default=False) deleted_date = db.DateTimeField() + keypoint_edges = db.ListField(default=[]) + keypoint_labels = db.ListField(default=[]) + + @classmethod def bulk_create(cls, categories): @@ -462,6 +468,7 @@ def api_json(self): "name": self.name } + class CocoImportModel(db.DynamicDocument): id = db.SequenceField(primary_key=True) creator = db.StringField(required=True) diff --git a/app/util/coco_util.py b/app/util/coco_util.py index 1ceec93c..f71ca6e4 100644 --- a/app/util/coco_util.py +++ b/app/util/coco_util.py @@ -114,24 +114,40 @@ def get_image_coco(image): annotations = [] for category in bulk_categories: - + category = category[1] category_annotations = AnnotationModel.objects( - deleted=False, category_id=category[1].id, image_id=image.get('id') + deleted=False, category_id=category.id, image_id=image.get('id') ).exclude('paper_object', 'deleted_date').all() - + if len(category_annotations) == 0: continue + + has_keypoints = len(category.keypoint_labels) > 0 for annotation in category_annotations: annotation = fix_ids(annotation) - if len(annotation.get('segmentation')) != 0: + if len(annotation.get('segmentation', [])) != 0 or \ + len(annotation.get('keypoints', [])) != 0: del annotation['deleted'] - del annotation['paper_object'] + + if not has_keypoints: + del annotation['keypoints'] + else: + arr = np.array(annotation.get('keypoints', [])) + arr = arr[2::3] + annotation['num_keypoints'] = len(arr[arr > 0]) + annotations.append(annotation) - category = fix_ids(category[1]) + category = fix_ids(category) del category['deleted'] + if has_keypoints: + category['keypoints'] = category.pop('keypoint_labels') + category['skeleton'] = category.pop('keypoint_edges') + else: + del category['keypoint_edges'] + del category['keypoint_labels'] categories.append(category) del image['deleted'] @@ -169,7 +185,15 @@ def get_dataset_coco(dataset): for category in categories: category = fix_ids(category[1]) + del category['deleted'] + if len(category.keypoint_labels) > 0: + category['keypoints'] = category.pop('keypoint_labels') + category['skeleton'] = category.pop('keypoint_edges') + else: + del category['keypoint_edges'] + del category['keypoint_labels'] + coco.get('categories').append(category) for image in images: @@ -180,8 +204,19 @@ def get_dataset_coco(dataset): annotations = fix_ids(annotations.all()) for annotation in annotations: - if len(annotation.get('segmentation', [])) != 0: + + has_keypoints = len(annotation.get('keypoints', [])) > 0 + has_segmentation = len(annotation.get('segmentation', [])) > 0 + + if has_keypoints or has_keypoints: del annotation['deleted'] + + if not has_keypoints: + del annotation['keypoints'] + else: + arr = np.array(annotation.get('keypoints', [])) + arr = arr[2::3] + annotation['num_keypoints'] = len(arr[arr > 0]) coco.get('annotations').append(annotation) image = fix_ids(image) @@ -192,11 +227,4 @@ def get_dataset_coco(dataset): def _fit(value, max_value, min_value): - - if value > max_value: - return max_value - - if value < min_value: - return min_value - - return value + return max(min(value, max_value), min_value) diff --git a/client/src/components/PanelInputDropdown.vue b/client/src/components/PanelInputDropdown.vue new file mode 100755 index 00000000..92927db2 --- /dev/null +++ b/client/src/components/PanelInputDropdown.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/client/src/components/PanelText.vue b/client/src/components/PanelText.vue new file mode 100755 index 00000000..8b8a25d3 --- /dev/null +++ b/client/src/components/PanelText.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/client/src/components/annotator/Annotation.vue b/client/src/components/annotator/Annotation.vue index 1393671d..009efb0b 100755 --- a/client/src/components/annotator/Annotation.vue +++ b/client/src/components/annotator/Annotation.vue @@ -1,10 +1,10 @@ @@ -124,10 +139,11 @@ import paper from "paper"; import axios from "axios"; import Annotation from "@/components/annotator/Annotation"; +import TagsInput from "@/components/TagsInput"; export default { name: "Category", - components: { Annotation }, + components: { Annotation, TagsInput }, props: { category: { type: Object, @@ -160,12 +176,20 @@ export default { simplify: { type: Number, default: 1 + }, + activeTool: { + type: String, + required: true } }, data: function() { return { group: null, color: this.category.color, + keypoint: { + labels: this.category.keypoint_labels, + edges: this.category.keypoint_edges + }, selectedAnnotation: -1, showAnnotations: false, isVisible: false, @@ -237,7 +261,9 @@ export default { visualize: this.isVisible, color: this.color, metadata: [], - annotations: [] + annotations: [], + keypoint_labels: this.keypoint.labels, + keypoint_edges: this.keypoint.edges }; if (refs.hasOwnProperty("annotation")) { @@ -248,6 +274,27 @@ export default { return categoryData; }, + + addKeypointEdge(edge) { + this.keypoint.edges.push(edge); + }, + removeKeypointEdge(edge) { + let index = this.keypoint.edges.findIndex(e => { + let i1 = Math.min(edge[0], edge[1]) == Math.min(e[0], e[1]); + let i2 = Math.max(edge[0], edge[1]) == Math.max(e[0], e[1]); + + return i1 && i2; + }); + + if (index !== -1) { + let edge = this.keypoint.edges[index]; + this.keypoint.edges.splice(index, 1); + let annotations = this.$refs.annotation; + if (annotations) { + annotations.forEach(a => a.keypoints.removeLine(edge)); + } + } + }, /** * Event handler for visibility button */ @@ -325,20 +372,24 @@ export default { setColor() { if (!this.isVisible) return; + let annotations = this.$refs.annotation; if (this.showAnnotations) { - let annotations = this.$refs.annotation; - if (annotations) { - annotations.forEach(annotation => { - annotation.setColor(); - }); - } + if (annotations) annotations.forEach(a => a.setColor()); } else { if (this.group != null) { this.group.fillColor = this.color; let h = Math.round(this.group.fillColor.hue); - let l = Math.round((this.group.fillColor.lightness - 0.2) * 100); + let l = Math.round(this.group.fillColor.lightness * 50); let s = Math.round(this.group.fillColor.saturation * 100); - this.group.strokeColor = "hsl(" + h + "," + s + "%," + l + "%)"; + let hsl = "hsl(" + h + "," + s + "%," + l + "%)"; + this.group.strokeColor = hsl; + + if (annotations) { + annotations.forEach(a => { + a.keypoints.color = hsl; + a.keypoints.bringToFront(); + }); + } } } }, @@ -392,6 +443,9 @@ export default { isVisible(newVisible) { if (this.group == null) return; this.group.visible = newVisible; + let annotations = this.$refs.annotation; + if (annotations) + annotations.forEach(a => (a.keypoints.visible = newVisible)); this.setColor(); }, showAnnotations(showing) { diff --git a/client/src/components/annotator/panels/KeypointPanel.vue b/client/src/components/annotator/panels/KeypointPanel.vue new file mode 100755 index 00000000..516488c1 --- /dev/null +++ b/client/src/components/annotator/panels/KeypointPanel.vue @@ -0,0 +1,60 @@ + + + diff --git a/client/src/components/annotator/tools/BrushTool.vue b/client/src/components/annotator/tools/BrushTool.vue index 0a53d320..28a054fe 100755 --- a/client/src/components/annotator/tools/BrushTool.vue +++ b/client/src/components/annotator/tools/BrushTool.vue @@ -118,7 +118,7 @@ export default { if (active) { this.tool.activate(); - localStorage.setItem("editorTool", this.name) + localStorage.setItem("editorTool", this.name); } }, /** diff --git a/client/src/components/annotator/tools/EraserTool.vue b/client/src/components/annotator/tools/EraserTool.vue index c4d1168a..8be6591b 100755 --- a/client/src/components/annotator/tools/EraserTool.vue +++ b/client/src/components/annotator/tools/EraserTool.vue @@ -116,7 +116,7 @@ export default { if (active) { this.tool.activate(); - localStorage.setItem("editorTool", this.name) + localStorage.setItem("editorTool", this.name); } }, /** diff --git a/client/src/components/annotator/tools/KeypointTool.vue b/client/src/components/annotator/tools/KeypointTool.vue new file mode 100755 index 00000000..f85a896f --- /dev/null +++ b/client/src/components/annotator/tools/KeypointTool.vue @@ -0,0 +1,41 @@ + diff --git a/client/src/components/annotator/tools/MagicWandTool.vue b/client/src/components/annotator/tools/MagicWandTool.vue index 94ab31e4..eb30b3d2 100755 --- a/client/src/components/annotator/tools/MagicWandTool.vue +++ b/client/src/components/annotator/tools/MagicWandTool.vue @@ -36,7 +36,7 @@ export default { isActive(active) { if (active) { this.tool.activate(); - localStorage.setItem("editorTool", this.name) + localStorage.setItem("editorTool", this.name); } } }, diff --git a/client/src/components/annotator/tools/PolygonTool.vue b/client/src/components/annotator/tools/PolygonTool.vue index 5f7f701e..e9afa985 100755 --- a/client/src/components/annotator/tools/PolygonTool.vue +++ b/client/src/components/annotator/tools/PolygonTool.vue @@ -224,7 +224,7 @@ export default { isActive(active) { if (active) { this.tool.activate(); - localStorage.setItem("editorTool", this.name) + localStorage.setItem("editorTool", this.name); } }, /** diff --git a/client/src/components/annotator/tools/SelectTool.vue b/client/src/components/annotator/tools/SelectTool.vue index d426eacb..5f82d69f 100755 --- a/client/src/components/annotator/tools/SelectTool.vue +++ b/client/src/components/annotator/tools/SelectTool.vue @@ -46,14 +46,7 @@ export default { fill: false, tolerance: 5, match: hit => { - if (this.point == null) return true; - if ( - hit.item instanceof paper.Path || - hit.item instanceof paper.CompoundPath - ) { - return !hit.item.hasOwnProperty("indicator"); - } - return true; + return !hit.item.hasOwnProperty("indicator"); } } }; @@ -177,6 +170,8 @@ export default { if (this.point != null) { this.edit.canMove = this.point.contains(event.point); + } else { + this.edit.canMove = false; } }, createPoint(point) { @@ -190,8 +185,7 @@ export default { this.point.indicator = true; }, onMouseDrag(event) { - if (this.segment) { - if (!this.edit.canMove) return; + if (this.segment && this.edit.canMove) { this.createPoint(event.point); this.segment.point = event.point; } diff --git a/client/src/components/cards/ImageCard.vue b/client/src/components/cards/ImageCard.vue index 3777b504..905017e6 100755 --- a/client/src/components/cards/ImageCard.vue +++ b/client/src/components/cards/ImageCard.vue @@ -96,7 +96,7 @@ export default { return { hover: false, showAnnotations: true, - loaderUrl: require("@/assets/loader.gif"), + loaderUrl: require("@/assets/loader.gif") }; }, methods: { diff --git a/client/src/components/tasks/Task.vue b/client/src/components/tasks/Task.vue index ff8a0244..73ea12ed 100644 --- a/client/src/components/tasks/Task.vue +++ b/client/src/components/tasks/Task.vue @@ -100,7 +100,7 @@ export default { showLogs: "getLogs", completed() { if (this.showLogs) { - this.getLogs() + this.getLogs(); } } }, diff --git a/client/src/libs/keypoints.js b/client/src/libs/keypoints.js new file mode 100644 index 00000000..c347c93f --- /dev/null +++ b/client/src/libs/keypoints.js @@ -0,0 +1,435 @@ +import paper from "paper"; + +export class Keypoints extends paper.Group { + constructor(edges, args) { + super(); + args = args || {}; + + this._edges = {}; + + this._lines = {}; + this._labelled = {}; + this._keypoints = []; + + this.strokeColor = args.strokeColor || "red"; + this.lineWidth = args.strokeWidth || 4; + + edges = edges || []; + edges.forEach(e => this.addEdge(e)); + } + + isEmpty() { + return this._keypoints.length === 0; + } + + setKeypointIndex(keypoint, newIndex) { + let oldIndex = keypoint.indexLabel; + if (newIndex == oldIndex) return; + + keypoint.indexLabel = parseInt(newIndex); + + if (oldIndex >= 0) { + delete this._labelled[oldIndex]; + + let otherIndices = this._edges[oldIndex]; + if (otherIndices) { + otherIndices.forEach(i => this.removeLine([i, oldIndex])); + } + // TODO: Remove assoicated lines + } + if (newIndex >= 0) { + this._labelled[newIndex] = keypoint; + this._drawLines(keypoint); + } + } + + bringToFront() { + super.bringToFront(); + this._keypoints.forEach(k => k.path.bringToFront()); + } + + addKeypoint(keypoint) { + keypoint.keypoints = this; + keypoint.path.keypoints = this; + keypoint.color = this.strokeColor; + keypoint.path.strokeWidth = this.strokeWidth; + + let indexLabel = keypoint.indexLabel; + if (this._labelled.hasOwnProperty(indexLabel)) { + keypoint.indexLabel = -1; + } else { + this._labelled[indexLabel] = keypoint; + } + + this._keypoints.push(keypoint); + this.addChild(keypoint.path); + this._drawLines(keypoint); + keypoint.path.bringToFront(); + } + + deleteKeypoint(keypoint) { + let indexLabel = keypoint.indexLabel; + if (this._labelled.hasOwnProperty(indexLabel)) { + delete this._labelled[indexLabel]; + } + if (this._edges.hasOwnProperty(indexLabel)) { + this._edges[indexLabel].forEach(e => this.removeLine([e, indexLabel])); + } + let index = this._keypoints.findIndex(k => k == keypoint); + if (index > -1) this._keypoints.splice(index, 1); + keypoint.path.remove(); + } + + moveKeypoint(point, keypoint) { + let indexLabel = keypoint.indexLabel; + let edges = this._edges[indexLabel]; + + if (edges) { + edges.forEach(i => { + let line = this.getLine([i, indexLabel]); + if (line) { + // We need to move the line aswell + for (let i = 0; i < line.segments.length; i++) { + let segment = line.segments[i]; + if (segment.point.isClose(keypoint, 0)) { + segment.point = point; + break; + } + } + } + }); + } + keypoint.move(point); + keypoint.path.bringToFront(); + } + + set visible(val) { + this._visible = val; + this._keypoints.forEach(k => (k.visible = val)); + Object.values(this._lines).forEach(l => (l.visible = val)); + } + + get visible() { + return this._visible; + } + + set color(val) { + this._color = val; + this.strokeColor = val; + this._keypoints.forEach(k => (k.color = val)); + } + + get color() { + return this._color; + } + + set lineWidth(val) { + this._lineWidth = val; + this.strokeWidth = val; + this._keypoints.forEach(k => (k.path.storkeWidth = val)); + } + + get lineWidth() { + return this._lineWidth; + } + + set radius(val) { + this._radius = val; + this._keypoints.forEach(k => (k.radius = val)); + } + + get radius() { + return this._radius; + } + + exportJSON(labels, width, height) { + let array = []; + for (let i = 0; i < labels.length; i++) { + let j = i * 3; + array[j] = 0; + array[j + 1] = 0; + array[j + 2] = 0; + } + + this._keypoints.forEach(k => { + let center = new paper.Point(width / 2, height / 2); + let point = k.clone().add(center); + let index = k.indexLabel; + + if (index == -1) { + array.push(...[Math.round(point.x), Math.round(point.y), k.visibility]); + } else { + index = (index - 1) * 3; + array[index] = Math.round(point.x); + array[index + 1] = Math.round(point.y); + array[index + 2] = Math.round(k.visibility); + } + }); + + return array; + } + + contains(point) { + return this._keypoints.findIndex(k => k.path.contains(point)) > -1; + } + + edges() { + let edges = []; + let keys = Object.keys(this._edges); + + for (let i = 0; i < keys.length; i++) { + let i1 = parseInt(keys[i]); + let otherIndices = Array.from(this._edges[i1]); + + for (let j = 0; j < otherIndices.length; j++) { + let i2 = parseInt(otherIndices[j]); + + if (i2 < i1) continue; + edges.push([i1, i2]); + } + } + + return edges; + } + + addEdge(edge) { + if (edge.length !== 2) return; + + let i1 = edge[0]; + let i2 = edge[1]; + + // If labels convert to indexs + if (typeof i1 == "string") i1 = this.getLabelIndex(i1); + if (typeof i2 == "string") i2 = this.getLabelIndex(i2); + if (i1 < 0 || i2 < 0) return; + + this._addEdgeIndex(i1, i2); + this._addEdgeIndex(i2, i1); + + // Draw line if points exist + let k1 = this._labelled[i1]; + let k2 = this._labelled[i2]; + if (k1 && k2) { + this._drawLine(edge, k1, k2); + } + } + + getLabelIndex(label) { + return this.labels.find(l => l == label); + } + + _addEdgeIndex(index1, index2) { + if (this._edges.hasOwnProperty(index1)) { + if (!this._edges[index1].has(index2)) this._edges[index1].add(index2); + } else { + this._edges[index1] = new Set([index2]); + } + } + + /** + * Draws lines to other keypoints if they exist + */ + _drawLines(keypoint) { + if (keypoint.indexLabel < 0) return; + if (!this._edges.hasOwnProperty(keypoint.indexLabel)) return; + + let otherIndices = this._edges[keypoint.indexLabel]; + otherIndices.forEach(i => { + let k2 = this._labelled[i]; + if (!k2) return; + + let edge = [keypoint.indexLabel, i]; + this._drawLine(edge, keypoint, k2); + }); + } + + /** + * Draws a line between two keypoints and hashes to a table for quick look up + * @param {list} edge array of two elementings contain the index edges + * @param {Keypoint} firstKeypoint first keypoint object + * @param {Keypoint} secondKeypoint second keypoint object + */ + _drawLine(edge, firstKeypoint, secondKeypoint) { + let h = this._hashEdge(edge); + if (this._lines[h]) return; + + let line = new paper.Path.Line(firstKeypoint, secondKeypoint); + line.strokeColor = this.strokeColor; + line.strokeWidth = this.strokeWidth; + line.indicator = true; + + if (firstKeypoint.path.isBelow(secondKeypoint.path)) { + line.insertBelow(firstKeypoint.path); + } else { + line.insertBelow(secondKeypoint.path); + } + + this._lines[h] = line; + } + + removeLine(edge) { + let h = this._hashEdge(edge); + let line = this._lines[h]; + if (line) { + line.remove(); + delete this._lines[h]; + } + } + + /** + * Returns paperjs path of line [O(1) lookup time] + * @param {list} edge array of two elementing contains the index edges + * @returns paperjs object path of the line or undefind if not found + */ + getLine(edge) { + let h = this._hashEdge(edge); + return this._lines[h]; + } + + /** + * Uses cantor pairing function to has two numbers + * @param {list} edge array of two elementing contains the index edges + */ + _hashEdge(edge) { + // Order doesn't matter so can sort first + let min = Math.min(edge[0], edge[1]); + let max = Math.max(edge[0], edge[1]); + // Cantor pairing function + let add = min + max; + return (1 / 2) * add * (add - 1) - max; + } +} + +/** + * Keypoint visibility types as defined by the COCO format + */ +export let VisibilityType = { + NOT_LABELED: 0, + LABELED_NOT_VISIBLE: 1, + LABELED_VISIBLE: 2, + UNKNOWN: 3 +}; + +export class Keypoint extends paper.Point { + constructor(x, y, args) { + super(x, y); + args = args || {}; + + this.path = null; + + this.label = args.label || ""; + this.radius = args.radius || 5; + this.indexLabel = args.indexLabel || -1; + this.visibility = args.visibility || VisibilityType.NOT_LABELED; + this.visible = args.visible || true; + + this.onClick = args.onClick; + this.onDoubleClick = args.onDoubleClick; + this.onMouseDrag = args.onMouseDrag; + + this._draw(); + this.color = args.color || "red"; + this.setFillColor(); + } + + setFillColor() { + if (this.path == null) return; + + switch (this.visibility) { + case VisibilityType.NOT_LABELED: + this.path.fillColor = "black"; + break; + case VisibilityType.LABELED_NOT_VISIBLE: + this.path.fillColor = "white"; + break; + default: + this.path.fillColor = this.color; + } + } + + move(point) { + this.x = point.x; + this.y = point.y; + this._draw(); + } + + _draw() { + let storkeWidth = 1; + if (this.path !== null) { + storkeWidth = this.path.strokeWidth; + this.path.remove(); + } + + this.path = new paper.Path.Circle(this, this.radius); + + this.path.onMouseDown = this.onMouseDown; + this.path.onMouseUp = this.onMouseUp; + this.path.onMouseDrag = this.onMouseDrag; + this.path.onDoubleClick = this.onDoubleClick; + this.path.onClick = this.onClick; + + this.path.indicator = true; + this.path.strokeColor = this.color; + this.path.strokeWidth = storkeWidth; + this.path.visible = this.visible; + this.path.keypoint = this; + this.path.keypoints = this.keypoints; + + this.setFillColor(); + } + + set visible(val) { + this._visible = val; + this.path.visible = val; + } + + get visible() { + return this._visible; + } + + set visibility(val) { + this._visibility = val; + this.setFillColor(); + } + + get visibility() { + return this._visibility; + } + + set radius(val) { + this._radius = val; + this._draw(); + } + + get radius() { + return this._radius; + } + + set color(val) { + this._color = val; + this.path.strokeColor = this.selected ? "white" : val; + this.setFillColor(); + } + + get color() { + return this._color; + } + + set strokeColor(val) { + this.color = val; + } + + get strokeColor() { + return this.color; + } + + set selected(val) { + this._selected = val; + this.path.strokeColor = val ? "white" : this.color; + this.path.bringToFront(); + } + + get selected() { + return this._selected; + } +} diff --git a/client/src/mixins/shortcuts.js b/client/src/mixins/shortcuts.js index 6d6b63aa..e5bb4091 100755 --- a/client/src/mixins/shortcuts.js +++ b/client/src/mixins/shortcuts.js @@ -44,7 +44,16 @@ export default { name: "Delete Current Annotation", function: () => { if (this.currentAnnotation) { - this.currentAnnotation.deleteAnnotation(); + let currentKeypoint = this.currentAnnotation.currentKeypoint; + if (currentKeypoint) { + this.currentAnnotation.keypoints.deleteKeypoint( + currentKeypoint + ); + this.currentAnnotation.tagRecomputeCounter++; + this.currentAnnotation.currentKeypoint = null; + } else { + this.currentAnnotation.deleteAnnotation(); + } } } }, @@ -75,6 +84,13 @@ export default { this.activeTool = "Magic Wand"; } }, + { + default: ["k"], + name: "Keypoints Tool", + function: () => { + if (!this.$refs.magicwand.isDisabled) this.activeTool = "Keypoints"; + } + }, { default: ["b"], name: "Brush Tool", diff --git a/client/src/views/Annotator.vue b/client/src/views/Annotator.vue index 3b2c892a..120f5555 100755 --- a/client/src/views/Annotator.vue +++ b/client/src/views/Annotator.vue @@ -39,6 +39,12 @@ @setcursor="setCursor" ref="eraser" /> + +
@@ -115,6 +121,7 @@ :index="index" @click="onCategoryClick" :current="current" + :active-tool="activeTool" ref="category" /> @@ -154,6 +161,13 @@
+ +
+ +
@@ -186,6 +200,7 @@ import SelectTool from "@/components/annotator/tools/SelectTool"; import MagicWandTool from "@/components/annotator/tools/MagicWandTool"; import EraserTool from "@/components/annotator/tools/EraserTool"; import BrushTool from "@/components/annotator/tools/BrushTool"; +import KeypointTool from "@/components/annotator/tools/KeypointTool"; import CopyAnnotationsButton from "@/components/annotator/tools/CopyAnnotationsButton"; import CenterButton from "@/components/annotator/tools/CenterButton"; @@ -203,6 +218,7 @@ import SelectPanel from "@/components/annotator/panels/SelectPanel"; import MagicWandPanel from "@/components/annotator/panels/MagicWandPanel"; import BrushPanel from "@/components/annotator/panels/BrushPanel"; import EraserPanel from "@/components/annotator/panels/EraserPanel"; +import KeypointPanel from "@/components/annotator/panels/KeypointPanel"; import { mapMutations } from "vuex"; @@ -219,6 +235,7 @@ export default { MagicWandTool, EraserTool, BrushTool, + KeypointTool, DownloadButton, SaveButton, SettingsButton, @@ -231,7 +248,8 @@ export default { ModeButton, UndoButton, HideAllButton, - ShowAllButton + ShowAllButton, + KeypointPanel }, mixins: [toastrs, shortcuts], props: { @@ -538,7 +556,7 @@ export default { }, selectLastEditorTool() { - this.activeTool = localStorage.getItem("editorTool") || "Select" + this.activeTool = localStorage.getItem("editorTool") || "Select"; }, setCursor(newCursor) { diff --git a/client/src/views/Dataset.vue b/client/src/views/Dataset.vue index 8e6711c0..0e49df9e 100755 --- a/client/src/views/Dataset.vue +++ b/client/src/views/Dataset.vue @@ -240,7 +240,7 @@ import PanelToggle from "@/components/PanelToggle"; import JQuery from "jquery"; import { mapMutations } from "vuex"; -let $ = JQuery +let $ = JQuery; export default { name: "Dataset", @@ -374,8 +374,8 @@ export default { this.$router.push({ path: "/tasks", query: { id: this.importing.id } }); return; } - - $('#cocoUpload').modal('show'); + + $("#cocoUpload").modal("show"); }, importCOCO() { let uploaded = document.getElementById("coco"); @@ -426,7 +426,6 @@ export default { }, sockets: { taskProgress(data) { - if (data.id === this.scan.id) { this.scan.progress = data.progress; }