Browse Source

added setting and removing of labels in the croppedView component

Dimitri Korsch 3 years ago
parent
commit
2415af2263

+ 60 - 0
webui/src/assets/icons/untag.svg

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   viewBox="0 0 16 16"
+   width="16"
+   height="16"
+   version="1.1"
+   id="svg4"
+   sodipodi:docname="untag.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)">
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs8" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1135"
+     id="namedview6"
+     showgrid="false"
+     inkscape:zoom="14.75"
+     inkscape:cx="8"
+     inkscape:cy="8"
+     inkscape:window-x="1200"
+     inkscape:window-y="536"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg4" />
+  <path
+     fill-rule="evenodd"
+     d="M2.5 7.775V2.75a.25.25 0 01.25-.25h5.025a.25.25 0 01.177.073l6.25 6.25a.25.25 0 010 .354l-5.025 5.025a.25.25 0 01-.354 0l-6.25-6.25a.25.25 0 01-.073-.177zm-1.5 0V2.75C1 1.784 1.784 1 2.75 1h5.025c.464 0 .91.184 1.238.513l6.25 6.25a1.75 1.75 0 010 2.474l-5.026 5.026a1.75 1.75 0 01-2.474 0l-6.25-6.25A1.75 1.75 0 011 7.775zM6 5a1 1 0 100 2 1 1 0 000-2z"
+     id="path2" />
+  <path
+     inkscape:connector-curvature="0"
+     d="m 13.675781,1.0898446 c -0.188805,-0.00885 -0.387816,0.061085 -0.564453,0.2441406 L 7.7519531,6.691407 6.6914062,7.7519539 1.3320312,13.111328 c -0.73155592,0.707236 0.3540009,1.792769 1.0605469,1.060547 L 7.7519531,8.8125008 8.8125,7.7519539 14.171875,2.394532 c 0.539682,-0.5395655 0.07032,-1.2781454 -0.496094,-1.3046874 z"
+     id="path2-3"
+     sodipodi:nodetypes="cccccccccc" />
+</svg>

+ 8 - 0
webui/src/components/media/annotated-image.vue

@@ -76,6 +76,9 @@
                      :labels="labels"
                      :labels="labels"
                      :file="current"
                      :file="current"
                      :box="infoBox"
                      :box="infoBox"
+                     @predictBox="predictBox"
+                     @removeLabel="labelBox($event, null)"
+                     @setLabel="openLabelSelector($event)"
                      @close="
                      @close="
                       interaction=defaultInteraction;
                       interaction=defaultInteraction;
                       infoBox=false"
                       infoBox=false"
@@ -250,6 +253,7 @@ export default {
     },
     },
 
 
     labelBox: function(box_id, label_id) {
     labelBox: function(box_id, label_id) {
+      console.log(box_id, label_id);
       this.$root.socket.post(`/results/${box_id}/label`,
       this.$root.socket.post(`/results/${box_id}/label`,
         {label: label_id});
         {label: label_id});
     },
     },
@@ -273,6 +277,10 @@ export default {
       this.$root.socket.post(`/data/${file_id}/estimate`, coordinates);
       this.$root.socket.post(`/data/${file_id}/estimate`, coordinates);
     },
     },
 
 
+    predictBox: function(file_id, box_id) {
+      this.$root.socket.post(`/data/${file_id}/${box_id}/predict_bounding_box`, {predict: true});
+    },
+
     // confirm either a bbox or image result
     // confirm either a bbox or image result
     confirm: function(res_id) {
     confirm: function(res_id) {
       this.$root.socket.post(`/results/${res_id}/confirm`,
       this.$root.socket.post(`/results/${res_id}/confirm`,

+ 87 - 42
webui/src/components/media/cropped-image.vue

@@ -6,28 +6,42 @@
       <img alt="close button" src="@/assets/icons/cross.svg">
       <img alt="close button" src="@/assets/icons/cross.svg">
     </div>
     </div>
 
 
-    <div class="label-container">
-      <h3>{{ label }}</h3>
-
-      <div ref="create_predictions"
-           class="create-predictions-icon"
-           title="create prediction for this image"
-           :class="{active: isPredictionRunning}"
-           @click="predict_cropped_image">
-        <img alt="create prediction" src="@/assets/icons/rocket.svg">
-      </div>
-    </div>
 
 
+    <h3 v-html="label"></h3>
     <div v-if="src">
     <div v-if="src">
       <div class="image-container">
       <div class="image-container">
         <img alt="crop" :src="src" />
         <img alt="crop" :src="src" />
       </div>
       </div>
 
 
-      <div v-if="getAnnotator() !== ''" class="annotation-info">
+      <div class="label-container">
+        <div ref="create_prediction"
+             class="icon"
+             title="predict label for this image patch"
+             :class="{active: isPredictionRunning}"
+             @click="predictCroppedImage">
+          <img alt="predict label" src="@/assets/icons/rocket.svg">
+        </div>
+        <div v-if="hasLabel"
+             ref="remove_prediction"
+             class="icon"
+             title="remove this label"
+             @click="removeLabel">
+          <img alt="remove label" src="@/assets/icons/untag.svg">
+        </div>
+        <div v-if="!hasLabel"
+             ref="add_label"
+             class="icon"
+             title="set label"
+             @click="setLabel">
+          <img alt="set label" src="@/assets/icons/tag.svg">
+        </div>
+      </div>
+
+      <div v-if="hasAnnotator" class="annotation-info">
         <table border="0">
         <table border="0">
           <tr>
           <tr>
             <td>Annotated by:</td>
             <td>Annotated by:</td>
-            <td align="left"><b>{{getAnnotator()}}</b></td>
+            <td align="left"><b v-html="getAnnotator"></b></td>
           </tr>
           </tr>
           <tr v-if="box.confirmations.length !== 0">
           <tr v-if="box.confirmations.length !== 0">
             <td colspan="2" align="left">Confirmed by:</td>
             <td colspan="2" align="left">Confirmed by:</td>
@@ -55,7 +69,7 @@
     </div>
     </div>
 
 
     <div v-else>
     <div v-else>
-      click a bounding box
+      Click a bounding box
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
@@ -101,21 +115,42 @@ export default {
 
 
       return this.$root.socket.url(`/data/${file}/1024/${x}x${y}x${w}x${h}?uuid=${uuid}`)
       return this.$root.socket.url(`/data/${file}/1024/${x}x${y}x${w}x${h}?uuid=${uuid}`)
     },
     },
+
+    hasLabel: function() {
+      return this.box && this.box.label_id !== null;
+    },
+
     label: function () {
     label: function () {
-      if (!this.box)
-        return false;
-      if (this.box.label_id == null)
-        return 'Unknown'
+      if (!this.hasLabel)
+        return '<em>Unknown</em>'
 
 
       for (let label of this.labels)
       for (let label of this.labels)
         if (label.identifier === this.box.label_id)
         if (label.identifier === this.box.label_id)
           return label.name;
           return label.name;
 
 
-      return 'Not found';
+      return '<em>Not found</em>';
     },
     },
     isPredictionRunning: function () {
     isPredictionRunning: function () {
       return this.jobs.filter(j => !j.finished && j.type === 'Model Interaction').length > 0;
       return this.jobs.filter(j => !j.finished && j.type === 'Model Interaction').length > 0;
-    }
+    },
+
+    hasAnnotator: function() {
+      return this.box && this.box.origin_user !== null;
+    },
+    getAnnotator: function () {
+      let current_user = this.$root.socket.username;
+      if (this.box.origin === 'user' && this.box.origin_user !== null) {
+        if (this.box.origin_user === current_user)
+          return "<em>You</em>"
+        else
+          return this.box.origin_user;
+      }
+      if (this.box.origin === 'pipeline') {
+        return 'Pipeline';
+      }
+      return '<em>Unknown</em>';
+
+    },
   },
   },
   methods: {
   methods: {
     getJobs: function () {
     getJobs: function () {
@@ -149,27 +184,29 @@ export default {
         }
         }
       }
       }
     },
     },
-    getAnnotator: function () {
-      if (this.box.origin === 'user' && this.box.origin_user !== null) {
-        return this.box.origin_user;
-      }
-      if (this.box.origin === 'pipeline') {
-        return 'pipeline';
-      }
-      return '';
 
 
-    }, predict_cropped_image: function () {
+    removeLabel: function () {
+      if (!this.box)
+        return;
+      this.$emit("removeLabel", this.box.identifier);
+    },
+
+    setLabel: function () {
+      if (!this.box)
+        return;
+      console.log(this.box);
+      this.$emit("setLabel", this.box);
+    },
+
+    predictCroppedImage: function () {
       // This shouldn't happen, since the icon is only shown if a bounding box
       // This shouldn't happen, since the icon is only shown if a bounding box
       // was selected.
       // was selected.
       if (!this.box)
       if (!this.box)
         return;
         return;
 
 
-      if (!this.isPredictionRunning) {
-        // TODO then / error
-        this.$root.socket.post(`/data/${this.file.identifier}/${this.box.identifier}/predict_bounding_box`, {
-          predict: true
-        });
-      }
+      if (this.isPredictionRunning)
+        return
+      this.$emit("predictBox", this.file.identifier, this.box.identifier)
     }
     }
   }
   }
 }
 }
@@ -183,6 +220,14 @@ export default {
   position: relative;
   position: relative;
   padding: 2rem 1rem;
   padding: 2rem 1rem;
   text-align: center;
   text-align: center;
+  border-left: 2px solid;
+  margin-left: 0.5rem;
+}
+
+h3 {
+  max-width: 100%;
+  max-height: 5%;
+  margin: 0.5em;
 }
 }
 
 
 .close-button {
 .close-button {
@@ -195,7 +240,8 @@ export default {
 }
 }
 
 
 .close-button img {
 .close-button img {
-  width: 1.5rem;
+  width: 24px;
+  height: 24px;
 }
 }
 
 
 .image-container img {
 .image-container img {
@@ -211,7 +257,7 @@ export default {
   justify-content: center;
   justify-content: center;
 }
 }
 
 
-.create-predictions-icon {
+.icon {
   display: flex;
   display: flex;
   justify-content: center;
   justify-content: center;
   align-items: center;
   align-items: center;
@@ -222,16 +268,15 @@ export default {
   border: 1px solid whitesmoke;
   border: 1px solid whitesmoke;
   border-radius: 0.5rem;
   border-radius: 0.5rem;
 
 
-  width: 1.6rem;
-  height: 1.6rem;
+  width: 32px;
+  height: 32px;
 }
 }
 
 
-.create-predictions-icon.active {
+.icon:hover, .icon.active {
   background-color: rgba(0, 0, 0, 0.2);
   background-color: rgba(0, 0, 0, 0.2);
-  box-shadow: inset 0 0 5px 0 rgba(0, 0, 0, 0.2);
 }
 }
 
 
-.create-predictions-icon > img {
+.icon > img {
   max-width: 1.1rem;
   max-width: 1.1rem;
   max-height: 1.1rem;
   max-height: 1.1rem;
   filter: invert(1);
   filter: invert(1);