瀏覽代碼

Resolve "keyboard shortcut for labeling"

Eric Tröbs 4 年之前
父節點
當前提交
9dbe3dffeb

+ 28 - 5
webui/src/components/media/annotated-image.vue

@@ -39,9 +39,10 @@
                     :box-url="mediaUrl + '/' + result.id"
                     :labels="project.labels"
                     :supports="project.model.supports"
+                    :selected="selectedBoundingBox !== false && index === selectedBoundingBox"
                     @move="move"
                     @resize="resize"
-                    @update="$emit('update', true)"/>
+                    @update="update"/>
   </div>
 </template>
 
@@ -54,7 +55,7 @@ export default {
   components: {VideoControl, AnnotationBox},
   props: ['data', 'project', 'socket', 'filter', 'extremeClicking'],
   mounted: function () {
-    window.addEventListener("resize", this.resizeEvent);
+    window.addEventListener('resize', this.resizeEvent);
     this.resizeEvent();
 
     // TODO dirty workaround
@@ -62,8 +63,12 @@ export default {
     // videos and their received dimensions
     this.watcher = setInterval(this.resizeEvent, 400);
   },
+  created: function () {
+    window.addEventListener('keypress', this.keypressEvent);
+  },
   destroyed() {
-    window.removeEventListener("resize", this.resizeEvent);
+    window.removeEventListener('resize', this.resizeEvent);
+    window.removeEventListener('keypress', this.keypressEvent);
     clearInterval(this.watcher);
   },
   watch: {
@@ -99,7 +104,8 @@ export default {
       start: false,
       fixed: false,
       current: false,
-      callback: false
+      callback: false,
+      selectedBoundingBox: false
     }
   },
   computed: {
@@ -153,6 +159,23 @@ export default {
       this.image.width = element.width;
       this.image.height = element.height;
     },
+    keypressEvent: function (event) {
+      if (event.key === 'w') {
+        if (this.selectedBoundingBox === false || this.selectedBoundingBox === this.predictions.length - 1)
+          this.selectedBoundingBox = -1;
+
+        this.selectedBoundingBox += 1;
+      } else if (event.key === 's') {
+        if (this.selectedBoundingBox === false || this.selectedBoundingBox === 0)
+          this.selectedBoundingBox = this.predictions.length;
+
+        this.selectedBoundingBox -= 1;
+      }
+    },
+    update: function () {
+      this.selectedBoundingBox = false;
+      this.$emit('update', true);
+    },
     videoPlay: function (value) {
       this.video.play = value;
       if (value)
@@ -254,7 +277,7 @@ export default {
         this.current = false;
         this.callback = false;
 
-        this.$emit('update', true);
+        this.update();
       }
     },
     move: function (event, position, callback) {

+ 6 - 2
webui/src/components/media/annotated-media-view.vue

@@ -6,7 +6,7 @@
                        :socket="socket"
                        :filter="filterValue"
                        :extremeClicking="extremeClicking"
-                       @update="update"/>
+                       @update="updateEvent"/>
     </div>
 
     <div class="control">
@@ -22,7 +22,7 @@
                      @filter="filter"
                      :extremeClicking="extremeClicking"
                      @extremeClicking="extremeClicking = $event"
-                     @update="update"/>
+                     @update="updateEvent"/>
     </div>
 
     <div class="selector" v-if="showMediaSelector && !currentProject.unmanaged">
@@ -75,6 +75,10 @@ export default {
       if (this.hasNext)
         this.current += 1;
     },
+    updateEvent: function () {
+      if (this.currentProject.unmanaged)
+        this.update();
+    },
     update: async function (newVal) {
       if (this.currentProject.unmanaged) {
         const response = await this.socket.get('/projects/' + this.currentProject.id + '/unmanaged/' + this.current);

+ 48 - 13
webui/src/components/media/annotation-box.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="annotation-box"
-       :class="{draggable: type}"
+       :class="{draggable: type, selected: selected}"
        :style="style"
        @mousedown.stop.prevent="moveSelf" @touchstart.stop.prevent="moveSelf">
     <div class="nw" @mousedown.stop.prevent="resizeSelf('nw')" @touchstart.stop.prevent="resizeSelf('nw')"></div>
@@ -53,26 +53,30 @@
 <script>
 export default {
   name: "annotation-box",
-  props: ['type', 'image', 'position', 'socket', 'boxUrl', 'labels', 'supports'],
+  props: ['type', 'image', 'position', 'socket', 'boxUrl', 'labels', 'supports', 'selected'],
   data: function () {
     return {
       showLabelSelection: false
     }
   },
+  watch: {
+    selected: {
+      immediate: true,
+      handler: function (newVal) {
+        if (newVal)
+          window.addEventListener('keypress', this.keypressEvent);
+        else
+          window.removeEventListener('keypress', this.keypressEvent);
+      }
+    }
+  },
+  destroyed: function () {
+    window.removeEventListener('keypress', this.keypressEvent);
+  },
   computed: {
     immutable: function () {
       return !this.type;
     },
-    backgroundColor: function () {
-      switch (this.type) {
-        case 'user':
-          return 'rgba(255, 0, 0, 0.3)';
-        case 'pipeline':
-          return 'rgba(0, 0, 255, 0.3)';
-        default:
-          return 'rgba(255, 255, 255, 0.3)';
-      }
-    },
     confirmationButton: function () {
       return this.type === 'pipeline';
     },
@@ -88,6 +92,26 @@ export default {
       else
         return false;
     },
+    backgroundColor: function () {
+      switch (this.type) {
+        case 'user':
+          return 'rgba(255, 0, 0, 0.3)';
+        case 'pipeline':
+          return 'rgba(0, 0, 255, 0.3)';
+        default:
+          return 'rgba(255, 255, 255, 0.3)';
+      }
+    },
+    borderColor: function () {
+      switch (this.type) {
+        case 'user':
+          return 'rgba(255, 0, 0, 0.6)';
+        case 'pipeline':
+          return 'rgba(0, 0, 255, 0.6)';
+        default:
+          return 'rgba(255, 255, 255, 0.6)';
+      }
+    },
     style: function () {
       const left = this.image.left;
       const top = this.image.top;
@@ -99,11 +123,18 @@ export default {
         top: (top + this.position.y * height) + 'px',
         width: (this.position.w * width) + 'px',
         height: (this.position.h * height) + 'px',
-        backgroundColor: this.backgroundColor
+        backgroundColor: this.backgroundColor,
+        borderColor: this.borderColor
       }
     }
   },
   methods: {
+    keypressEvent: function (event) {
+      if (event.key === 'q')
+        this.confirmSelf();
+      else if (event.key === 'e')
+        this.deleteSelf();
+    },
     confirmSelf: function () {
       this.updateSelf(this.position);
     },
@@ -152,6 +183,10 @@ export default {
   grid-template-columns: 10px auto 10px;
 }
 
+.annotation-box.selected {
+  border: 2px solid;
+}
+
 .buttons {
   display: flex;
   justify-content: center;

+ 21 - 2
webui/src/components/media/media-control.vue

@@ -74,13 +74,13 @@
     <button-input type="transparent"
                   v-if="!extremeClicking && (project.model.supports.includes('bounding-boxes') || project.model.supports.includes('labeled-bounding-boxes'))"
                   @touchstart.stop @mousedown.stop
-                  @click.stop="$emit('extremeClicking', true)">
+                  @click.stop="switchExtremeClicking">
       <img alt="label" src="@/assets/icons/flame.svg">
     </button-input>
 
     <button-input type="transparent" v-if="extremeClicking"
                   @touchstart.stop @mousedown.stop
-                  @click.stop="$emit('extremeClicking', false)">
+                  @click.stop="switchExtremeClicking">
       <img alt="label" src="@/assets/icons/check.svg">
     </button-input>
   </div>
@@ -101,6 +101,12 @@ export default {
       showFilterSelection: false
     }
   },
+  created: function () {
+    window.addEventListener('keypress', this.keypressEvent);
+  },
+  destroyed: function () {
+    window.removeEventListener('keypress', this.keypressEvent);
+  },
   computed: {
     mediaUrl: function () {
       return this.socket.media(this.project.id, this.data.id);
@@ -120,6 +126,16 @@ export default {
     }
   },
   methods: {
+    keypressEvent: function (event) {
+      if (event.key === 'x')
+        this.switchExtremeClicking();
+      else if (event.key === 't')
+        this.reset();
+      else if (event.key === 'a')
+        this.previous();
+      else if (event.key === 'd')
+        this.next();
+    },
     previous: function () {
       if (this.hasPrevious)
         this.$emit('previous', null);
@@ -149,6 +165,9 @@ export default {
       });
 
       this.$emit('update', true);
+    },
+    switchExtremeClicking: function () {
+      this.$emit('extremeClicking', !this.extremeClicking);
     }
   }
 }