Browse Source

Additional color for bounding boxes, fixed pure prediction for bounding boxes

Bounding boxes created by other users are now displayed in purple.
Executing the pipeline for single bounding boxes resulted in the created labels not being saved. That is now fixed.
The annotation box for cropped images is no longer misleading for user annotations for which no username was specified.
blunk 3 years ago
parent
commit
7e3ce6cd6a
34 changed files with 91 additions and 24 deletions
  1. 16 0
      pycs/database/File.py
  2. 1 0
      pycs/frontend/endpoints/ListJobs.py
  3. 1 0
      pycs/frontend/endpoints/ListLabelProviders.py
  4. 1 0
      pycs/frontend/endpoints/ListModels.py
  5. 1 0
      pycs/frontend/endpoints/ListProjects.py
  6. 1 0
      pycs/frontend/endpoints/data/RemoveFile.py
  7. 1 0
      pycs/frontend/endpoints/data/UploadFile.py
  8. 1 0
      pycs/frontend/endpoints/jobs/RemoveJob.py
  9. 1 0
      pycs/frontend/endpoints/labels/CreateLabel.py
  10. 1 0
      pycs/frontend/endpoints/labels/EditLabelName.py
  11. 1 0
      pycs/frontend/endpoints/labels/EditLabelParent.py
  12. 1 0
      pycs/frontend/endpoints/labels/ListLabelTree.py
  13. 1 0
      pycs/frontend/endpoints/labels/ListLabels.py
  14. 1 0
      pycs/frontend/endpoints/labels/RemoveLabel.py
  15. 1 0
      pycs/frontend/endpoints/pipelines/FitModel.py
  16. 5 4
      pycs/frontend/endpoints/pipelines/PredictBoundingBox.py
  17. 1 0
      pycs/frontend/endpoints/pipelines/PredictFile.py
  18. 13 12
      pycs/frontend/endpoints/pipelines/PredictModel.py
  19. 1 0
      pycs/frontend/endpoints/projects/CreateProject.py
  20. 1 0
      pycs/frontend/endpoints/projects/EditProjectDescription.py
  21. 1 0
      pycs/frontend/endpoints/projects/EditProjectName.py
  22. 1 0
      pycs/frontend/endpoints/projects/ExecuteExternalStorage.py
  23. 1 0
      pycs/frontend/endpoints/projects/ExecuteLabelProvider.py
  24. 1 0
      pycs/frontend/endpoints/projects/GetProjectModel.py
  25. 1 0
      pycs/frontend/endpoints/projects/ListProjectCollections.py
  26. 2 1
      pycs/frontend/endpoints/projects/ListProjectFiles.py
  27. 1 0
      pycs/frontend/endpoints/projects/RemoveProject.py
  28. 1 0
      pycs/frontend/endpoints/results/GetProjectResults.py
  29. 1 0
      pycs/frontend/endpoints/results/GetResults.py
  30. 1 0
      pycs/frontend/endpoints/results/RemoveResult.py
  31. 1 0
      pycs/frontend/endpoints/results/ResetResults.py
  32. 21 2
      pycs/interfaces/MediaFile.py
  33. 4 2
      webui/src/components/media/annotation-box.vue
  34. 3 3
      webui/src/components/media/cropped-image.vue

+ 16 - 0
pycs/database/File.py

@@ -226,6 +226,22 @@ class File(NamedBaseModel):
 
 
         return result
         return result
 
 
+    def remove_result(self, result_id: int) -> T.List[Result]:
+        """
+            Remove the result with the given id.
+
+            :param result_id: id of the result to delete
+            :return: list of result objects
+        """
+
+        results = Result.query.filter(
+            Result.file_id == self.id,
+            Result.id == result_id)
+
+        _results = [r.serialize() for r in results.all()]
+        results.delete()
+
+        return _results
 
 
     def remove_results(self, origin='pipeline') -> T.List[Result]:
     def remove_results(self, origin='pipeline') -> T.List[Result]:
         """
         """

+ 1 - 0
pycs/frontend/endpoints/ListJobs.py

@@ -16,4 +16,5 @@ class ListJobs(View):
         self.jobs = jobs
         self.jobs = jobs
 
 
     def dispatch_request(self, user: str):
     def dispatch_request(self, user: str):
+        # pylint: disable=unused-argument
         return jsonify(self.jobs.list())
         return jsonify(self.jobs.list())

+ 1 - 0
pycs/frontend/endpoints/ListLabelProviders.py

@@ -13,4 +13,5 @@ class ListLabelProviders(View):
 
 
 
 
     def dispatch_request(self, user: str):
     def dispatch_request(self, user: str):
+        # pylint: disable=unused-argument
         return jsonify(LabelProvider.query.all())
         return jsonify(LabelProvider.query.all())

+ 1 - 0
pycs/frontend/endpoints/ListModels.py

@@ -13,4 +13,5 @@ class ListModels(View):
 
 
 
 
     def dispatch_request(self, user: str):
     def dispatch_request(self, user: str):
+        # pylint: disable=unused-argument
         return jsonify(Model.query.all())
         return jsonify(Model.query.all())

+ 1 - 0
pycs/frontend/endpoints/ListProjects.py

@@ -13,4 +13,5 @@ class ListProjects(View):
 
 
 
 
     def dispatch_request(self, user: str):
     def dispatch_request(self, user: str):
+        # pylint: disable=unused-argument
         return jsonify(Project.query.all())
         return jsonify(Project.query.all())

+ 1 - 0
pycs/frontend/endpoints/data/RemoveFile.py

@@ -18,6 +18,7 @@ class RemoveFile(View):
         self.nm = nm
         self.nm = nm
 
 
     def dispatch_request(self, user: str, file_id: int):
     def dispatch_request(self, user: str, file_id: int):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
 
 

+ 1 - 0
pycs/frontend/endpoints/data/UploadFile.py

@@ -29,6 +29,7 @@ class UploadFile(View):
         self.size = None
         self.size = None
 
 
     def dispatch_request(self, user: str, project_id: int):
     def dispatch_request(self, user: str, project_id: int):
+        # pylint: disable=unused-argument
         # find project
         # find project
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)
 
 

+ 1 - 0
pycs/frontend/endpoints/jobs/RemoveJob.py

@@ -18,6 +18,7 @@ class RemoveJob(View):
         self.jobs = jobs
         self.jobs = jobs
 
 
     def dispatch_request(self, user: str, job_id):
     def dispatch_request(self, user: str, job_id):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
 
 

+ 1 - 0
pycs/frontend/endpoints/labels/CreateLabel.py

@@ -21,6 +21,7 @@ class CreateLabel(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id):
     def dispatch_request(self, user: str, project_id):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
         name = data.get('name')
         name = data.get('name')

+ 1 - 0
pycs/frontend/endpoints/labels/EditLabelName.py

@@ -20,6 +20,7 @@ class EditLabelName(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id: int, label_id: int):
     def dispatch_request(self, user: str, project_id: int, label_id: int):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
         name = data.get('name')
         name = data.get('name')

+ 1 - 0
pycs/frontend/endpoints/labels/EditLabelParent.py

@@ -20,6 +20,7 @@ class EditLabelParent(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id: int, label_id: int):
     def dispatch_request(self, user: str, project_id: int, label_id: int):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
         parent = data.get('parent')
         parent = data.get('parent')

+ 1 - 0
pycs/frontend/endpoints/labels/ListLabelTree.py

@@ -13,6 +13,7 @@ class ListLabelTree(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id):
     def dispatch_request(self, user: str, project_id):
+        # pylint: disable=unused-argument
         # find project
         # find project
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)
 
 

+ 1 - 0
pycs/frontend/endpoints/labels/ListLabels.py

@@ -13,6 +13,7 @@ class ListLabels(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id):
     def dispatch_request(self, user: str, project_id):
+        # pylint: disable=unused-argument
         # find project
         # find project
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)
 
 

+ 1 - 0
pycs/frontend/endpoints/labels/RemoveLabel.py

@@ -20,6 +20,7 @@ class RemoveLabel(View):
         self.nm = nm
         self.nm = nm
 
 
     def dispatch_request(self, user: str, project_id: int, label_id: int):
     def dispatch_request(self, user: str, project_id: int, label_id: int):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
 
 

+ 1 - 0
pycs/frontend/endpoints/pipelines/FitModel.py

@@ -23,6 +23,7 @@ class FitModel(View):
         self.pipelines = pipelines
         self.pipelines = pipelines
 
 
     def dispatch_request(self, user: str, project_id):
     def dispatch_request(self, user: str, project_id):
+        # pylint: disable=unused-argument
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)
 
 
         # extract request data
         # extract request data

+ 5 - 4
pycs/frontend/endpoints/pipelines/PredictBoundingBox.py

@@ -28,9 +28,10 @@ class PredictBoundingBox(View):
 
 
     def dispatch_request(self, user: str, file_id, bbox_id):
     def dispatch_request(self, user: str, file_id, bbox_id):
         # find file and result (=bounding box)
         # find file and result (=bounding box)
-        # We need the result to get (x,y,w,h)
+        # We will later need the result to get (x,y,w,h). Here, we just check
+        # whether the result is valid.
         file = File.get_or_404(file_id)
         file = File.get_or_404(file_id)
-        result = Result.get_or_404(bbox_id)
+        Result.get_or_404(bbox_id)
 
 
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
@@ -51,8 +52,8 @@ class PredictBoundingBox(View):
                           f'{project.id}/model-interaction',
                           f'{project.id}/model-interaction',
                           Predict.load_and_pure_inference,
                           Predict.load_and_pure_inference,
                           self.pipelines, notifications, self.nm,
                           self.pipelines, notifications, self.nm,
-                          project.id, [file.id], {file.id: [result]},
-                          progress=Predict.progress)
+                          project.id, [file.id], {file.id: [bbox_id]},
+                          user, progress=Predict.progress)
 
 
         except JobGroupBusyException:
         except JobGroupBusyException:
             abort(400, "File prediction is already running")
             abort(400, "File prediction is already running")

+ 1 - 0
pycs/frontend/endpoints/pipelines/PredictFile.py

@@ -26,6 +26,7 @@ class PredictFile(View):
         self.pipelines = pipelines
         self.pipelines = pipelines
 
 
     def dispatch_request(self, user: str, file_id):
     def dispatch_request(self, user: str, file_id):
+        # pylint: disable=unused-argument
         # find file
         # find file
         file = File.get_or_404(file_id)
         file = File.get_or_404(file_id)
 
 

+ 13 - 12
pycs/frontend/endpoints/pipelines/PredictModel.py

@@ -33,6 +33,7 @@ class PredictModel(View):
         self.pipelines = pipelines
         self.pipelines = pipelines
 
 
     def dispatch_request(self, user: str, project_id):
     def dispatch_request(self, user: str, project_id):
+        # pylint: disable=unused-argument
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)
 
 
         # extract request data
         # extract request data
@@ -129,7 +130,7 @@ class PredictModel(View):
                          notifications: NotificationList,
                          notifications: NotificationList,
                          notification_manager: NotificationManager,
                          notification_manager: NotificationManager,
                          project_id: int, file_filter: List[int],
                          project_id: int, file_filter: List[int],
-                         result_filter: dict[int, List[Result]]):
+                         bbox_id_filter: dict[int, List[int]], user: str):
         """
         """
         load the pipeline and call the execute function
         load the pipeline and call the execute function
 
 
@@ -139,7 +140,8 @@ class PredictModel(View):
         :param notification_manager: notification manager
         :param notification_manager: notification manager
         :param project_id: project id
         :param project_id: project id
         :param file_filter: list of file ids
         :param file_filter: list of file ids
-        :param result_filter: dict of file id and list of results to classify
+        :param bbox_id_filter: dict of file id and list of bbox_ids to classify
+        :param user: username of the user asking to predict the bounding box
         :return:
         :return:
         """
         """
         pipeline = None
         pipeline = None
@@ -149,32 +151,31 @@ class PredictModel(View):
         model_root = project.model.root_folder
         model_root = project.model.root_folder
         storage = MediaStorage(project_id, notifications)
         storage = MediaStorage(project_id, notifications)
 
 
-        # create a list of MediaFile
-        # Also convert dict to the same key type.
-        length = len(file_filter)
-
-
         # load pipeline
         # load pipeline
         try:
         try:
             pipeline = pipelines.load_from_root_folder(project_id, model_root)
             pipeline = pipelines.load_from_root_folder(project_id, model_root)
 
 
             # iterate over media files
             # iterate over media files
             index = 0
             index = 0
+            length = len(file_filter)
             for file_id in file_filter:
             for file_id in file_filter:
                 file = project.file(file_id)
                 file = project.file(file_id)
                 file = MediaFile(file, notifications)
                 file = MediaFile(file, notifications)
-                bounding_boxes = [MediaBoundingBox(result) for result in result_filter[file_id]]
+                bounding_boxes = [MediaBoundingBox(Result.get_or_404(bbox_id))
+                                for bbox_id in bbox_id_filter[file_id]]
 
 
                 # Perform inference.
                 # Perform inference.
                 bbox_labels = pipeline.pure_inference(storage, file, bounding_boxes)
                 bbox_labels = pipeline.pure_inference(storage, file, bounding_boxes)
 
 
                 # 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]):
-                    result.label_id = bbox_labels[i].identifier
-                    result.set_origin('user', origin_user=None, commit=True)
+                for i, bbox_id in enumerate(bbox_id_filter[file_id]):
+                    result = Result.get_or_404(bbox_id)
+                    result.set_label(bbox_labels[i].identifier, commit=True)
+                    result.set_origin('user', origin_user=user, commit=True)
                     notifications.add(notification_manager.edit_result, result)
                     notifications.add(notification_manager.edit_result, result)
 
 
-                # yield progress
+                # commit changes and yield progress
+                db.session.commit()
                 yield index / length, notifications
                 yield index / length, notifications
 
 
                 index += 1
                 index += 1

+ 1 - 0
pycs/frontend/endpoints/projects/CreateProject.py

@@ -34,6 +34,7 @@ class CreateProject(View):
         self.jobs = jobs
         self.jobs = jobs
 
 
     def dispatch_request(self, user: str):
     def dispatch_request(self, user: str):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
 
 

+ 1 - 0
pycs/frontend/endpoints/projects/EditProjectDescription.py

@@ -20,6 +20,7 @@ class EditProjectDescription(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id: int):
     def dispatch_request(self, user: str, project_id: int):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
         description = data.get('description')
         description = data.get('description')

+ 1 - 0
pycs/frontend/endpoints/projects/EditProjectName.py

@@ -20,6 +20,7 @@ class EditProjectName(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id: int):
     def dispatch_request(self, user: str, project_id: int):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
         name = data.get('name')
         name = data.get('name')

+ 1 - 0
pycs/frontend/endpoints/projects/ExecuteExternalStorage.py

@@ -28,6 +28,7 @@ class ExecuteExternalStorage(View):
         self.jobs = jobs
         self.jobs = jobs
 
 
     def dispatch_request(self, user: str, project_id: int):
     def dispatch_request(self, user: str, project_id: int):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
 
 

+ 1 - 0
pycs/frontend/endpoints/projects/ExecuteLabelProvider.py

@@ -26,6 +26,7 @@ class ExecuteLabelProvider(View):
         self.jobs = jobs
         self.jobs = jobs
 
 
     def dispatch_request(self, user: str, project_id: int):
     def dispatch_request(self, user: str, project_id: int):
+        # pylint: disable=unused-argument
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)
 
 
         # extract request data
         # extract request data

+ 1 - 0
pycs/frontend/endpoints/projects/GetProjectModel.py

@@ -13,6 +13,7 @@ class GetProjectModel(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id: int):
     def dispatch_request(self, user: str, project_id: int):
+        # pylint: disable=unused-argument
         # find project
         # find project
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)
 
 

+ 1 - 0
pycs/frontend/endpoints/projects/ListProjectCollections.py

@@ -14,6 +14,7 @@ class ListProjectCollections(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id: int):
     def dispatch_request(self, user: str, project_id: int):
+        # pylint: disable=unused-argument
         # find project
         # find project
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)
 
 

+ 2 - 1
pycs/frontend/endpoints/projects/ListProjectFiles.py

@@ -14,11 +14,12 @@ class ListProjectFiles(View):
 
 
 
 
     def dispatch_request(self,
     def dispatch_request(self,
-                         user: str, 
+                         user: str,
                          project_id: int,
                          project_id: int,
                          start: int = 0,
                          start: int = 0,
                          length: int = -1,
                          length: int = -1,
                          collection_id: int = None):
                          collection_id: int = None):
+        # pylint: disable=unused-argument
         # find project
         # find project
 
 
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)

+ 1 - 0
pycs/frontend/endpoints/projects/RemoveProject.py

@@ -19,6 +19,7 @@ class RemoveProject(View):
         self.nm = nm
         self.nm = nm
 
 
     def dispatch_request(self, user: str, project_id: int):
     def dispatch_request(self, user: str, project_id: int):
+        # pylint: disable=unused-argument
         # extract request data
         # extract request data
         data = request.get_json(force=True)
         data = request.get_json(force=True)
 
 

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

@@ -14,6 +14,7 @@ class GetProjectResults(View):
 
 
 
 
     def dispatch_request(self, user: str, project_id: int):
     def dispatch_request(self, user: str, project_id: int):
+        # pylint: disable=unused-argument
         # get project from database
         # get project from database
         project = Project.get_or_404(project_id)
         project = Project.get_or_404(project_id)
 
 

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

@@ -13,6 +13,7 @@ class GetResults(View):
 
 
 
 
     def dispatch_request(self, user: str, file_id: int):
     def dispatch_request(self, user: str, file_id: int):
+        # pylint: disable=unused-argument
         # get file from database
         # get file from database
         file = File.get_or_404(file_id)
         file = File.get_or_404(file_id)
 
 

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

@@ -19,6 +19,7 @@ class RemoveResult(View):
         self.nm = nm
         self.nm = nm
 
 
     def dispatch_request(self, user: str, result_id: int):
     def dispatch_request(self, user: str, result_id: int):
+        # pylint: disable=unused-argument
         result = Result.get_or_404(result_id)
         result = Result.get_or_404(result_id)
 
 
         # extract request data
         # extract request data

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

@@ -19,6 +19,7 @@ class ResetResults(View):
         self.nm = nm
         self.nm = nm
 
 
     def dispatch_request(self, user: str, file_id: int):
     def dispatch_request(self, user: str, file_id: int):
+        # pylint: disable=unused-argument
         file = File.get_or_404(file_id)
         file = File.get_or_404(file_id)
 
 
         # extract request data
         # extract request data

+ 21 - 2
pycs/interfaces/MediaFile.py

@@ -55,7 +55,8 @@ class MediaFile:
         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,
-                         label: Union[int, MediaLabel] = None, frame: int = None):
+                         label: Union[int, MediaLabel] = None, frame: int = None,
+                         origin:str = None, origin_user: str = None) -> Result:
         """
         """
         create a bounding-box result
         create a bounding-box result
 
 
@@ -65,6 +66,9 @@ class MediaFile:
         :param h: relative height [0, 1]
         :param h: relative height [0, 1]
         :param label: label
         :param label: label
         :param frame: frame index (only set for videos)
         :param frame: frame index (only set for videos)
+        :param origin: Either pipeline or user
+        :param origin_user: Username of the user that provided the bounding box
+        :return: Created Result
         """
         """
         result = {
         result = {
             'x': x,
             'x': x,
@@ -78,10 +82,14 @@ 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(origin='pipeline',
+        if origin is None:
+            origin = 'pipeline'
+        created = self.__file.create_result(origin=origin, origin_user=origin_user,
             result_type='bounding-box', label=label, data=result)
             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)
 
 
+        return created
+
     def remove_predictions(self):
     def remove_predictions(self):
         """
         """
         remove and return all predictions added from pipelines
         remove and return all predictions added from pipelines
@@ -90,6 +98,17 @@ class MediaFile:
         for result in removed:
         for result in removed:
             self.__notifications.add(self.__notifications.notifications.remove_result, result)
             self.__notifications.add(self.__notifications.notifications.remove_result, result)
 
 
+    def remove_result(self, result_id):
+        """
+        Removes the result with the given id.
+
+        :param result_id: id of the result to delete
+        """
+
+        removed = self.__file.remove_result(id=result_id)
+        for result in removed:
+            self.__notifications.add(self.__notifications.notifications.remove_result, result)
+
     def __get_results(self, origin: str) -> List[Union[MediaImageLabel, MediaBoundingBox]]:
     def __get_results(self, origin: str) -> List[Union[MediaImageLabel, MediaBoundingBox]]:
 
 
         def result_to_media(result: Result) -> Union[MediaImageLabel, MediaBoundingBox]:
         def result_to_media(result: Result) -> Union[MediaImageLabel, MediaBoundingBox]:

+ 4 - 2
webui/src/components/media/annotation-box.vue

@@ -37,7 +37,6 @@ export default {
   ],
   ],
   computed: {
   computed: {
     labelName: function () {
     labelName: function () {
-      console.log(this.box)
       if (!this.box || !this.box.label_id)
       if (!this.box || !this.box.label_id)
         return false;
         return false;
       for (let label of this.labels) {
       for (let label of this.labels) {
@@ -53,7 +52,10 @@ export default {
       if (this.box) {
       if (this.box) {
         switch (this.box.origin) {
         switch (this.box.origin) {
           case 'user':
           case 'user':
-            color = '255, 0, 0';
+            color = '136, 0, 136';
+            if (this.box.origin_user === this.$root.socket.username) {
+              color = '255, 0, 0';
+            }
             break;
             break;
           case 'pipeline':
           case 'pipeline':
             color = '0, 0, 255';
             color = '0, 0, 255';

+ 3 - 3
webui/src/components/media/cropped-image.vue

@@ -21,10 +21,10 @@
 
 
     <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 v-if="this.box.origin === 'user'">
-      Annotated by: {{this.box.origin_user}}
+      <div v-if="this.box.origin === 'user' && this.box.origin_user !== null">
+        Annotated by: <span style="color:red">{{this.box.origin_user}}</span>
+      </div>
     </div>
     </div>
 
 
     <div v-else>
     <div v-else>