6
0
Эх сурвалжийг харах

"Annotated by" and minor additions

Added user identification to results.
Credentials aren't needed to access media any more. This caused problems with static images and was unnecessary.
Patched uploading images (missing credentials and content type was overwritten).
blunk 3 жил өмнө
parent
commit
b083e28899

+ 2 - 1
migrations/versions/b03df3e31b8d_.py

@@ -1,7 +1,7 @@
 """empty message
 """empty message
 
 
 Revision ID: b03df3e31b8d
 Revision ID: b03df3e31b8d
-Revises: 
+Revises:
 Create Date: 2021-08-11 12:46:17.757283
 Create Date: 2021-08-11 12:46:17.757283
 
 
 """
 """
@@ -102,6 +102,7 @@ def upgrade():
     sa.Column('file_id', sa.Integer(), nullable=False),
     sa.Column('file_id', sa.Integer(), nullable=False),
     sa.Column('origin', sa.String(), nullable=False),
     sa.Column('origin', sa.String(), nullable=False),
     sa.Column('type', sa.String(), nullable=False),
     sa.Column('type', sa.String(), nullable=False),
+    sa.Column('origin_user', sa.String(), nullable=True),
     sa.Column('label_id', sa.Integer(), nullable=True),
     sa.Column('label_id', sa.Integer(), nullable=True),
     sa.Column('data_encoded', sa.String(), nullable=True),
     sa.Column('data_encoded', sa.String(), nullable=True),
     sa.ForeignKeyConstraint(['file_id'], ['file.id'], ondelete='CASCADE'),
     sa.ForeignKeyConstraint(['file_id'], ['file.id'], ondelete='CASCADE'),

+ 6 - 1
pycs/database/File.py

@@ -196,6 +196,7 @@ class File(NamedBaseModel):
     def create_result(self,
     def create_result(self,
                       origin: str,
                       origin: str,
                       result_type: str,
                       result_type: str,
+                      origin_user: str = None,
                       label: T.Optional[T.Union[Label, int]] = None,
                       label: T.Optional[T.Union[Label, int]] = None,
                       data: T.Optional[dict] = None) -> Result:
                       data: T.Optional[dict] = None) -> Result:
         """
         """
@@ -203,11 +204,15 @@ class File(NamedBaseModel):
 
 
         :return: result object
         :return: result object
         """
         """
+        if origin == "pipeline" and not origin_user is None:
+            raise ValueError("If an annotation was made by the pipeline no username"\
+                "can be specified!")
 
 
         result = Result.new(commit=False,
         result = Result.new(commit=False,
                             file_id=self.id,
                             file_id=self.id,
                             origin=origin,
                             origin=origin,
-                            type=result_type)
+                            type=result_type,
+                            origin_user=origin_user)
 
 
         result.data = data
         result.data = data
 
 

+ 9 - 2
pycs/database/Result.py

@@ -14,6 +14,7 @@ class Result(BaseModel):
         nullable=False)
         nullable=False)
 
 
     origin = db.Column(db.String, nullable=False)
     origin = db.Column(db.String, nullable=False)
+    origin_user = db.Column(db.String, nullable=True)
     type = db.Column(db.String, nullable=False)
     type = db.Column(db.String, nullable=False)
 
 
     label_id = db.Column(
     label_id = db.Column(
@@ -26,6 +27,7 @@ class Result(BaseModel):
     serialize_only = BaseModel.serialize_only + (
     serialize_only = BaseModel.serialize_only + (
         "file_id",
         "file_id",
         "origin",
         "origin",
+        "origin_user",
         "type",
         "type",
         "label_id",
         "label_id",
         "data",
         "data",
@@ -58,15 +60,20 @@ class Result(BaseModel):
             raise ValueError(f"Not supported type: {type(value)}")
             raise ValueError(f"Not supported type: {type(value)}")
 
 
     @commit_on_return
     @commit_on_return
-    def set_origin(self, origin: str):
+    def set_origin(self, origin: str, origin_user: str = None):
         """
         """
         set this results origin
         set this results origin
 
 
         :param origin: either 'user' or 'pipeline'
         :param origin: either 'user' or 'pipeline'
+        :param origin_user: None if origin is 'pipeline' else name of the user
         :return:
         :return:
         """
         """
-        self.origin = origin
+        if origin == "pipeline" and not origin_user is None:
+            raise ValueError("If an annotation was made by the pipeline no user"\
+                "can be specified!")
 
 
+        self.origin = origin
+        self.origin_user = origin_user
 
 
     @commit_on_return
     @commit_on_return
     def set_label(self, label: int):
     def set_label(self, label: int):

+ 5 - 6
pycs/frontend/WebServer.py

@@ -93,7 +93,7 @@ class WebServer:
                 response.headers['Access-Control-Allow-Origin'] = 'http://localhost:8080'
                 response.headers['Access-Control-Allow-Origin'] = 'http://localhost:8080'
                 response.headers['Access-Control-Allow-Credentials'] = 'true'
                 response.headers['Access-Control-Allow-Credentials'] = 'true'
                 response.headers['Access-Control-Allow-Methods'] = 'POST, GET'
                 response.headers['Access-Control-Allow-Methods'] = 'POST, GET'
-                response.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type'
+                response.headers['Access-Control-Allow-Headers'] = 'Authorization'
                 return response
                 return response
 
 
         # create service objects
         # create service objects
@@ -261,20 +261,19 @@ class WebServer:
         )
         )
         self.app.add_url_rule(
         self.app.add_url_rule(
             '/data/<int:file_id>',
             '/data/<int:file_id>',
-            view_func=self.htpasswd.required( GetFile.as_view('get_file') )
+            view_func=GetFile.as_view('get_file')
         )
         )
         self.app.add_url_rule(
         self.app.add_url_rule(
             '/data/<int:file_id>/<resolution>',
             '/data/<int:file_id>/<resolution>',
-            view_func=self.htpasswd.required( GetResizedFile.as_view('get_resized_file') )
+            view_func=GetResizedFile.as_view('get_resized_file')
         )
         )
         self.app.add_url_rule(
         self.app.add_url_rule(
             '/data/<int:file_id>/<resolution>/<crop_box>',
             '/data/<int:file_id>/<resolution>/<crop_box>',
-            view_func=self.htpasswd.required( GetCroppedFile.as_view('get_cropped_file') )
+            view_func=GetCroppedFile.as_view('get_cropped_file')
         )
         )
         self.app.add_url_rule(
         self.app.add_url_rule(
             '/data/<int:file_id>/previous_next',
             '/data/<int:file_id>/previous_next',
-            view_func=self.htpasswd.required(
-                GetPreviousAndNextFile.as_view('get_previous_and_next_file') )
+            view_func=GetPreviousAndNextFile.as_view('get_previous_and_next_file')
         )
         )
 
 
         # results
         # results

+ 1 - 1
pycs/frontend/endpoints/data/GetCroppedFile.py

@@ -19,7 +19,7 @@ class GetCroppedFile(View):
     methods = ['GET']
     methods = ['GET']
 
 
 
 
-    def dispatch_request(self, user: str, file_id: int, resolution: str, crop_box: str):
+    def dispatch_request(self, file_id: int, resolution: str, crop_box: str):
         # get file from database
         # get file from database
         file = File.get_or_404(file_id)
         file = File.get_or_404(file_id)
 
 

+ 1 - 1
pycs/frontend/endpoints/data/GetFile.py

@@ -13,7 +13,7 @@ class GetFile(View):
     # pylint: disable=arguments-differ
     # pylint: disable=arguments-differ
     methods = ['GET']
     methods = ['GET']
 
 
-    def dispatch_request(self, user: str, file_id: int):
+    def dispatch_request(self, file_id: int):
         file = File.get_or_404(file_id)
         file = File.get_or_404(file_id)
 
 
         abs_file_path = file.absolute_path
         abs_file_path = file.absolute_path

+ 1 - 1
pycs/frontend/endpoints/data/GetPreviousAndNextFile.py

@@ -12,7 +12,7 @@ class GetPreviousAndNextFile(View):
     methods = ['GET']
     methods = ['GET']
 
 
 
 
-    def dispatch_request(self, user: str, file_id: int):
+    def dispatch_request(self, file_id: int):
         # get file from database
         # get file from database
         file = File.get_or_404(file_id)
         file = File.get_or_404(file_id)
 
 

+ 1 - 1
pycs/frontend/endpoints/data/GetResizedFile.py

@@ -18,7 +18,7 @@ class GetResizedFile(View):
     methods = ['GET']
     methods = ['GET']
 
 
 
 
-    def dispatch_request(self, user: str, file_id: int, resolution: str):
+    def dispatch_request(self, file_id: int, resolution: str):
         # get file from database
         # get file from database
         file = File.get_or_404(file_id)
         file = File.get_or_404(file_id)
 
 

+ 1 - 1
pycs/frontend/endpoints/pipelines/PredictModel.py

@@ -171,7 +171,7 @@ class PredictModel(View):
                 # Add the labels determined in the inference process.
                 # Add the labels determined in the inference process.
                 for i, result in enumerate(result_filter[file_id]):
                 for i, result in enumerate(result_filter[file_id]):
                     result.label_id = bbox_labels[i].identifier
                     result.label_id = bbox_labels[i].identifier
-                    result.set_origin('user', commit=True)
+                    result.set_origin('user', origin_user=None, commit=True)
                     notifications.add(notification_manager.edit_result, result)
                     notifications.add(notification_manager.edit_result, result)
 
 
                 # yield progress
                 # yield progress

+ 1 - 1
pycs/frontend/endpoints/results/ConfirmResult.py

@@ -28,7 +28,7 @@ class ConfirmResult(View):
             return abort(400, "confirm flag is missing")
             return abort(400, "confirm flag is missing")
 
 
 
 
-        result.set_origin('user')
+        result.set_origin('user', origin_user=user)
 
 
         self.nm.edit_result(result)
         self.nm.edit_result(result)
         return make_response()
         return make_response()

+ 1 - 0
pycs/frontend/endpoints/results/CreateResult.py

@@ -68,6 +68,7 @@ class CreateResult(View):
             # insert into database
             # insert into database
             result = file.create_result(
             result = file.create_result(
                 origin='user',
                 origin='user',
+                origin_user=user,
                 result_type=result_type,
                 result_type=result_type,
                 label=label,
                 label=label,
                 data=data)
                 data=data)

+ 1 - 1
pycs/frontend/endpoints/results/EditResultData.py

@@ -30,7 +30,7 @@ class EditResultData(View):
             abort(400, "Could not find data argument!")
             abort(400, "Could not find data argument!")
 
 
         result.data = data
         result.data = data
-        result.set_origin('user', commit=True)
+        result.set_origin('user', origin_user=user, commit=True)
 
 
         self.nm.edit_result(result)
         self.nm.edit_result(result)
         return make_response()
         return make_response()

+ 1 - 1
pycs/frontend/endpoints/results/EditResultLabel.py

@@ -35,7 +35,7 @@ class EditResultLabel(View):
             abort(400, "Label is required for 'labeled-images' results")
             abort(400, "Label is required for 'labeled-images' results")
 
 
         result.label_id = label
         result.label_id = label
-        result.set_origin('user', commit=True)
+        result.set_origin('user', origin_user=user, commit=True)
 
 
         self.nm.edit_result(result)
         self.nm.edit_result(result)
         return make_response()
         return make_response()

+ 4 - 2
pycs/interfaces/MediaFile.py

@@ -50,7 +50,8 @@ class MediaFile:
         else:
         else:
             data = None
             data = None
 
 
-        created = self.__file.create_result('pipeline', 'labeled-image', label, data)
+        created = self.__file.create_result(origin='pipeline',
+            result_type='labeled-image', label=label, data=data)
         self.__notifications.add(self.__notifications.notifications.create_result, created)
         self.__notifications.add(self.__notifications.notifications.create_result, created)
 
 
     def add_bounding_box(self, x: float, y: float, w: float, h: float,
     def add_bounding_box(self, x: float, y: float, w: float, h: float,
@@ -77,7 +78,8 @@ class MediaFile:
         if label is not None and isinstance(label, MediaLabel):
         if label is not None and isinstance(label, MediaLabel):
             label = label.identifier
             label = label.identifier
 
 
-        created = self.__file.create_result('pipeline', 'bounding-box', label, result)
+        created = self.__file.create_result(origin='pipeline',
+            result_type='bounding-box', label=label, data=result)
         self.__notifications.add(self.__notifications.notifications.create_result, created)
         self.__notifications.add(self.__notifications.notifications.create_result, created)
 
 
     def remove_predictions(self):
     def remove_predictions(self):

+ 1 - 1
tests/client/project_tests.py

@@ -426,6 +426,7 @@ class ProjectListTests(_BaseProjectTests):
 
 
             file.create_result(
             file.create_result(
                 origin="user",
                 origin="user",
+                origin_user="dummy_username",
                 result_type="bounding-box",
                 result_type="bounding-box",
                 label=None,
                 label=None,
                 data=dict(x=0, y=0, w=1, h=1)
                 data=dict(x=0, y=0, w=1, h=1)
@@ -516,4 +517,3 @@ class ProjectEditTests(_BaseProjectTests):
         self.post(url, json=dict(), status_code=400)
         self.post(url, json=dict(), status_code=400)
 
 
         self.assertEqual("Project for a test case", self.project.description)
         self.assertEqual("Project for a test case", self.project.description)
-

+ 1 - 1
tests/client/result_tests.py

@@ -121,7 +121,7 @@ class ResultGettingTests(_BaseResultTests):
 
 
         for i in range(n):
         for i in range(n):
             box = dict(x=0, y=0, w=0.9, h=1.0)
             box = dict(x=0, y=0, w=0.9, h=1.0)
-            another_file.create_result("user", "bounding-box", data=box)
+            another_file.create_result("user", "bounding-box", origin_user="dummy_username", data=box)
 
 
         self.assertEqual(10, Result.query.count())
         self.assertEqual(10, Result.query.count())
 
 

+ 6 - 1
webui/src/components/media/cropped-image.vue

@@ -7,7 +7,7 @@
     </div>
     </div>
 
 
     <div class="label-container">
     <div class="label-container">
-      <h3> {{ label }} </h3>
+      <h3>{{ label }}</h3>
 
 
       <div  v-if="this.box.origin === 'user'"
       <div  v-if="this.box.origin === 'user'"
             ref="create_predictions"
             ref="create_predictions"
@@ -22,6 +22,11 @@
     <div v-if="src" class="image-container">
     <div v-if="src" class="image-container">
       <img alt="crop" :src="src"/>
       <img alt="crop" :src="src"/>
     </div>
     </div>
+
+    <div v-if="this.box.origin === 'user'">
+      Annotated by: {{this.box.origin_user}}
+    </div>
+
     <div v-else>
     <div v-else>
       click a bounding box
       click a bounding box
     </div>
     </div>

+ 0 - 1
webui/src/main.js

@@ -45,7 +45,6 @@ new Vue({
                 headers: function () {
                 headers: function () {
                     const authHeaders = new Headers();
                     const authHeaders = new Headers();
                     authHeaders.set('Authorization', 'Basic ' + window.btoa(this.username + ":" + this.password));
                     authHeaders.set('Authorization', 'Basic ' + window.btoa(this.username + ":" + this.password));
-                    authHeaders.set('Content-Type', 'application/json');
                     return authHeaders;
                     return authHeaders;
                 },
                 },
                 url: function (name) {
                 url: function (name) {