Sfoglia il codice sorgente

Resolve "bounding box options"

Eric Tröbs 4 anni fa
parent
commit
f2e4f678a1

+ 23 - 0
pycs/frontend/WebServer.py

@@ -200,6 +200,29 @@ class WebServer:
             # return default success response
             return response()
 
+        @self.__flask.route('/projects/<project_identifier>/data/<file_identifier>/<result_identifier>', methods=['POST'])
+        def edit_result(project_identifier, file_identifier, result_identifier):
+            # abort if project id is not valid
+            if project_identifier not in app_status['projects'].keys():
+                return make_response('project does not exist', 500)
+
+            project = app_status['projects'][project_identifier]
+
+            # abort if file id is not valid
+            if file_identifier not in project['data'].keys():
+                return make_response('file does not exist', 500)
+
+            target_object = project['data'][file_identifier]
+
+            # parse post data
+            result = request.get_json(force=True)
+
+            # remove result
+            if 'delete' in result.keys():
+                target_object.remove_result(result_identifier)
+
+            return response()
+
         # finally start web server
         host = app_status['settings']['frontend']['host']
         port = app_status['settings']['frontend']['port']

+ 3 - 4
pycs/pipeline/PipelineManager.py

@@ -20,15 +20,14 @@ class PipelineManager:
         self.pipeline.close()
 
     def run(self, media_file):
-        print('>>>', media_file)
-
         # create job list
         # TODO update job progress
         job = Job('detect-faces', self.project['id'], media_file)
         result = tpool.execute(lambda p, j: p.execute(j), self.pipeline, job)
-        media_file['predictionResults'] = result.predictions
 
-        print('<<<', media_file)
+        # TODO filter predictions
+        for prediction in result.predictions:
+            media_file.add_result(prediction)
 
     def __load_pipeline(self, pipeline_identifier):
         model_distribution = self.project.parent.parent['models'][pipeline_identifier]

+ 6 - 0
pycs/projects/MediaFile.py

@@ -8,6 +8,9 @@ from pycs.observable import ObservableDict
 
 class MediaFile(ObservableDict):
     def __init__(self, obj, parent):
+        if 'predictionResults' not in obj.keys():
+            obj['predictionResults'] = {}
+
         super().__init__(obj, parent)
 
     def __get_file(self, id):
@@ -23,6 +26,9 @@ class MediaFile(ObservableDict):
         result['id'] = str(uuid1())
         self['predictionResults'][result['id']] = result
 
+    def remove_result(self, identifier):
+        del self['predictionResults'][identifier]
+
     def resize(self, maximum_width):
         # check if resized file already exists
         resized = MediaFile(self, self.parent)

+ 4 - 0
webui/src/assets/icons/trash.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
+    <path fill-rule="evenodd"
+          d="M6.5 1.75a.25.25 0 01.25-.25h2.5a.25.25 0 01.25.25V3h-3V1.75zm4.5 0V3h2.25a.75.75 0 010 1.5H2.75a.75.75 0 010-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75zM4.496 6.675a.75.75 0 10-1.492.15l.66 6.6A1.75 1.75 0 005.405 15h5.19c.9 0 1.652-.681 1.741-1.576l.66-6.6a.75.75 0 00-1.492-.149l-.66 6.6a.25.25 0 01-.249.225h-5.19a.25.25 0 01-.249-.225l-.66-6.6z"></path>
+</svg>

+ 20 - 15
webui/src/components/media/annotated-image.vue

@@ -7,13 +7,17 @@
 
     <annotation-box v-if="current"
                     :image="image"
-                    :position="current"/>
+                    :position="current"
+                    :immutable="true"/>
 
     <annotation-box v-for="(result, index) in predictions"
                     type="server"
                     :key="index"
                     :image="image"
-                    :position="result"/>
+                    :position="result"
+                    :socket="socket"
+                    :immutable="false"
+                    :box-url="mediaUrl + '/' + result.id"/>
   </div>
 </template>
 
@@ -24,7 +28,7 @@ export default {
   name: "annotated-image",
   components: {AnnotationBox},
   props: ['project', 'data', 'socket'],
-  mounted: function() {
+  mounted: function () {
     window.addEventListener("resize", this.resizeEvent);
 
     if (this.$refs.image.complete)
@@ -36,12 +40,12 @@ export default {
     window.removeEventListener("resize", this.resizeEvent);
   },
   watch: {
-    data: function() {
+    data: function () {
       this.current = false;
       this.resizeEvent();
     }
   },
-  data: function() {
+  data: function () {
     return {
       sizes: [600, 800, 1200, 1600, 2000, 3000],
       image: {
@@ -80,7 +84,7 @@ export default {
     }
   },
   methods: {
-    resizeEvent: function() {
+    resizeEvent: function () {
       const element = this.$refs.image.getBoundingClientRect();
       const parent = this.$refs.image.parentElement.getBoundingClientRect();
 
@@ -89,7 +93,7 @@ export default {
       this.image.width = element.width;
       this.image.height = element.height;
     },
-    get: function(event) {
+    get: function (event) {
       if ('clientX' in event)
         return event;
       if ('touches' in event && event.touches.length > 0)
@@ -97,15 +101,15 @@ export default {
       if ('changedTouches' in event && event.changedTouches.length > 0)
         return event.changedTouches[0];
     },
-    getX: function(event) {
+    getX: function (event) {
       const bcr = this.$refs.image.getBoundingClientRect();
       return (this.get(event).clientX - bcr.left) / bcr.width;
     },
-    getY: function(event) {
+    getY: function (event) {
       const bcr = this.$refs.image.getBoundingClientRect();
       return (this.get(event).clientY - bcr.top) / bcr.height;
     },
-    buildRectangle: function(x1, y1, x2, y2) {
+    buildRectangle: function (x1, y1, x2, y2) {
       const lx = Math.max(Math.min(x1, x2), 0);
       const hx = Math.min(Math.max(x1, x2), 1);
       const ly = Math.max(Math.min(y1, y2), 0);
@@ -118,7 +122,7 @@ export default {
         h: hy - ly
       }
     },
-    press: function(event) {
+    press: function (event) {
       this.start = {
         x: this.getX(event),
         y: this.getY(event)
@@ -131,12 +135,13 @@ export default {
 
         this.current = this.buildRectangle(this.start.x, this.start.y, x, y);
       }
-
     },
     release: function () {
-      this.socket.post(this.mediaUrl, this.current);
-      this.start = false;
-      this.current = false;
+      if (this.start) {
+        this.socket.post(this.mediaUrl, this.current);
+        this.start = false;
+        this.current = false;
+      }
     }
   }
 }

+ 1 - 1
webui/src/components/media/annotated-media-view.vue

@@ -66,7 +66,7 @@ export default {
     predict: function() {
       this.socket.post('/projects/' + this.currentProject.id, {
         'predict': [this.currentMedia.id]
-      })
+      });
     }
   }
 }

+ 21 - 3
webui/src/components/media/annotation-box.vue

@@ -1,13 +1,15 @@
 <template>
-  <div class="annotation-box" :style="style">
-    .annotation-box
+  <div class="annotation-box" :style="style" @mousedown.stop.prevent @touchstart.stop.prevent>
+    <template v-if="!immutable">
+      <img alt="delete" src="@/assets/icons/trash.svg" @click="deleteSelf">
+    </template>
   </div>
 </template>
 
 <script>
 export default {
   name: "annotation-box",
-  props: ['type', 'image', 'position'],
+  props: ['type', 'image', 'position', 'socket', 'immutable', 'boxUrl'],
   computed: {
     backgroundColor: function () {
       switch (this.type) {
@@ -32,6 +34,11 @@ export default {
         backgroundColor: this.backgroundColor
       }
     }
+  },
+  methods: {
+    deleteSelf: function () {
+      this.socket.post(this.boxUrl, {delete: true});
+    }
   }
 }
 </script>
@@ -39,5 +46,16 @@ export default {
 <style scoped>
 .annotation-box {
   position: absolute;
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+img {
+  width: 2rem;
+  max-width: 50%;
+  filter: invert(1);
+  opacity: 0.9;
 }
 </style>