|
@@ -6,9 +6,9 @@
|
|
|
@interaction="interaction = $event"
|
|
|
:filter="filter"
|
|
|
@filter="filter = $event"
|
|
|
- :label="label"
|
|
|
- @label="label = $event"
|
|
|
:labels="labels"
|
|
|
+ :labelsEnabled="labelsEnabled"
|
|
|
+ @labelSelector="openLabelSelector()"
|
|
|
:zoomBox="zoomBox"
|
|
|
:infoBox="infoBox !== false"
|
|
|
@infoBox="infoBox = $event; interaction=false"
|
|
@@ -16,6 +16,12 @@
|
|
|
@prevzoom="$refs.overlay.prevZoom()"
|
|
|
@nextzoom="$refs.overlay.nextZoom()"/>
|
|
|
|
|
|
+ <label-selector v-if="labelSelector"
|
|
|
+ :labels="labels"
|
|
|
+ @close="closeLabelSelector()"
|
|
|
+ @label="labelSelected($event);"
|
|
|
+ />
|
|
|
+
|
|
|
<div class="media">
|
|
|
<h3>{{current.path}}</h3>
|
|
|
<div class="mode-tooltip">{{modeTooltip()}}</div>
|
|
@@ -54,6 +60,11 @@
|
|
|
:results="results"
|
|
|
:labels="labels"
|
|
|
:crop="infoBox"
|
|
|
+ @labelSelector="openLabelSelector($event)"
|
|
|
+ @labelBox="labelBox"
|
|
|
+ @removeBox="removeBox"
|
|
|
+ @confirmBox="confirmBox"
|
|
|
+ @updateBox="updateBox"
|
|
|
@crop="infoBox = $event"
|
|
|
:zoom="zoomBox"
|
|
|
@zoom="zoomBox = $event"/>
|
|
@@ -63,7 +74,10 @@
|
|
|
:labels="labels"
|
|
|
:file="current"
|
|
|
:box="infoBox"
|
|
|
- @close="interaction=defaultInteraction; infoBox=false"/>
|
|
|
+ @close="
|
|
|
+ interaction=defaultInteraction;
|
|
|
+ infoBox=false"
|
|
|
+ />
|
|
|
</template>
|
|
|
</div>
|
|
|
</template>
|
|
@@ -73,11 +87,59 @@ import AnnotationOverlay from "@/components/media/annotation-overlay";
|
|
|
import VideoControl from "@/components/media/video-control";
|
|
|
import OptionsBar from "@/components/media/options-bar";
|
|
|
import CroppedImage from "@/components/media/cropped-image";
|
|
|
+import LabelSelector from "@/components/media/label-selector";
|
|
|
|
|
|
export default {
|
|
|
name: "annotated-image",
|
|
|
- components: {OptionsBar, VideoControl, AnnotationOverlay, CroppedImage},
|
|
|
- props: ['current'],
|
|
|
+ components: {
|
|
|
+ OptionsBar,
|
|
|
+ VideoControl,
|
|
|
+ AnnotationOverlay,
|
|
|
+ CroppedImage,
|
|
|
+ LabelSelector
|
|
|
+ },
|
|
|
+ props: [
|
|
|
+ 'current'
|
|
|
+ ],
|
|
|
+
|
|
|
+ data: function () {
|
|
|
+
|
|
|
+ return {
|
|
|
+ interval: false,
|
|
|
+ container: {
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ width: 0,
|
|
|
+ height: 0,
|
|
|
+ },
|
|
|
+ image: {
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ width: 0,
|
|
|
+ height: 0
|
|
|
+ },
|
|
|
+ video: {
|
|
|
+ frame: 0,
|
|
|
+ play: false
|
|
|
+ },
|
|
|
+ infoBox: false,
|
|
|
+ zoomBox: false,
|
|
|
+ supported: {
|
|
|
+ labeledImages: false,
|
|
|
+ boundingBoxes: false,
|
|
|
+ labeledBoundingBoxes: false,
|
|
|
+ },
|
|
|
+ interaction: 'draw-box',
|
|
|
+ defaultInteraction: 'draw-box',
|
|
|
+ filter: ['user', 'pipeline'],
|
|
|
+ label: false,
|
|
|
+ labelSelector: false,
|
|
|
+ boxTolabel: null,
|
|
|
+ model: null,
|
|
|
+ results: [],
|
|
|
+ labels: []
|
|
|
+ }
|
|
|
+ },
|
|
|
mounted: function () {
|
|
|
// add resize listener
|
|
|
window.addEventListener('resize', this.resize);
|
|
@@ -100,6 +162,7 @@ export default {
|
|
|
this.$root.socket.get(`/projects/${this.$root.project.identifier}/model`)
|
|
|
.then(response => response.json())
|
|
|
.then(model => {
|
|
|
+ this.model = model;
|
|
|
this.supported.labeledImages = model.supports.includes('labeled-images');
|
|
|
this.supported.labeledBoundingBoxes = model.supports.includes('labeled-bounding-boxes');
|
|
|
this.supported.boundingBoxes = this.supported.labeledBoundingBoxes
|
|
@@ -119,40 +182,6 @@ export default {
|
|
|
this.$root.socket.off('remove-label', this.removeLabelFromList);
|
|
|
this.$root.socket.off('edit-label', this.editLabelInList);
|
|
|
},
|
|
|
- data: function () {
|
|
|
- return {
|
|
|
- interval: false,
|
|
|
- container: {
|
|
|
- top: 0,
|
|
|
- left: 0,
|
|
|
- width: 0,
|
|
|
- height: 0,
|
|
|
- },
|
|
|
- image: {
|
|
|
- top: 0,
|
|
|
- left: 0,
|
|
|
- width: 0,
|
|
|
- height: 0
|
|
|
- },
|
|
|
- video: {
|
|
|
- frame: 0,
|
|
|
- play: false
|
|
|
- },
|
|
|
- infoBox: false,
|
|
|
- zoomBox: false,
|
|
|
- supported: {
|
|
|
- labeledImages: false,
|
|
|
- boundingBoxes: false,
|
|
|
- labeledBoundingBoxes: false,
|
|
|
- },
|
|
|
- interaction: 'draw-box',
|
|
|
- defaultInteraction: 'draw-box',
|
|
|
- filter: ['user', 'pipeline'],
|
|
|
- label: false,
|
|
|
- results: [],
|
|
|
- labels: []
|
|
|
- }
|
|
|
- },
|
|
|
computed: {
|
|
|
src: function () {
|
|
|
if (!this.container.width || !this.container.height)
|
|
@@ -187,10 +216,54 @@ export default {
|
|
|
return {
|
|
|
transform: `scale(${factor}) translateX(${posX * 100}%) translateY(${posY * 100}%)`
|
|
|
};
|
|
|
- }
|
|
|
+ },
|
|
|
+ labelsEnabled: function() {
|
|
|
+
|
|
|
+ return this.model
|
|
|
+ && (this.supported.labeledImages || this.supported.labeledBoundingBoxes);
|
|
|
+ },
|
|
|
},
|
|
|
methods: {
|
|
|
|
|
|
+ openLabelSelector: function(box) {
|
|
|
+ if (box === null || box === undefined)
|
|
|
+ this.boxTolabel = null
|
|
|
+ else
|
|
|
+ this.boxTolabel = box;
|
|
|
+
|
|
|
+ this.labelSelector = true;
|
|
|
+ },
|
|
|
+
|
|
|
+ closeLabelSelector: function() {
|
|
|
+ this.boxTolabel = null;
|
|
|
+ this.labelSelector = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ labelSelected: function(label) {
|
|
|
+ this.interaction = 'label-box';
|
|
|
+ this.label = label;
|
|
|
+
|
|
|
+ if (this.boxTolabel !== null)
|
|
|
+ this.labelBox(this.boxTolabel.identifier, label.identifier);
|
|
|
+ },
|
|
|
+
|
|
|
+ labelBox: function(box_id, label_id) {
|
|
|
+ this.$root.socket.post(`/results/${box_id}/label`,
|
|
|
+ {label: label_id});
|
|
|
+ },
|
|
|
+ removeBox: function(box_id,) {
|
|
|
+ this.$root.socket.post(`/results/${box_id}/remove`,
|
|
|
+ {remove: true});
|
|
|
+ },
|
|
|
+ confirmBox: function(box_id) {
|
|
|
+ this.$root.socket.post(`/results/${box_id}/confirm`,
|
|
|
+ {confirm: true});
|
|
|
+ },
|
|
|
+ updateBox: function(box_id, data) {
|
|
|
+ this.$root.socket.post(`/results/${box_id}/data`,
|
|
|
+ {data: data});
|
|
|
+ },
|
|
|
+
|
|
|
modeTooltip: function(){
|
|
|
let tip = "Current mode";
|
|
|
switch (this.interaction) {
|
|
@@ -369,6 +442,7 @@ export default {
|
|
|
align-items: center;
|
|
|
|
|
|
overflow: hidden;
|
|
|
+
|
|
|
}
|
|
|
|
|
|
.options-bar {
|
|
@@ -417,4 +491,28 @@ h3 {
|
|
|
margin: 0.5em;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+.label-selector {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 50%;
|
|
|
+ z-index: 101;
|
|
|
+
|
|
|
+ width: 30rem;
|
|
|
+ max-width: 90vw;
|
|
|
+ max-height: 90%;
|
|
|
+ padding-bottom: 0;
|
|
|
+
|
|
|
+ animation: label-selector-animation 0.3s ease-out forwards;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes label-selector-animation {
|
|
|
+ from {
|
|
|
+ transform: translateX(-50%) translateY(-100%);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ transform: translateX(-50%) translateY(0%);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
</style>
|