1
1
Eric Tröbs 3 жил өмнө
parent
commit
d74e4ba978

+ 1 - 1
pycs/database/Collection.py

@@ -18,7 +18,7 @@ class Collection:
         self.name = row[3]
         self.description = row[4]
         self.position = row[5]
-        self.autoselect = False if row[6] == 0 else True
+        self.autoselect = row[6] > 0
 
     def set_name(self, name: str):
         """

+ 13 - 0
pycs/database/Database.py

@@ -140,12 +140,25 @@ class Database:
                 discover_label_providers(self.con)
 
     def close(self):
+        """
+        close database file
+        """
         self.con.close()
 
     def copy(self):
+        """
+        Create a copy of this database object. This can be used to access the database
+        from another thread. Table initialization and model and label provider discovery is
+        disabled to speedup this function.
+
+        :return: Database
+        """
         return Database(self.path, initialization=False, discovery=False)
 
     def commit(self):
+        """
+        commit changes
+        """
         self.con.commit()
 
     def __enter__(self):

+ 7 - 1
pycs/database/File.py

@@ -160,7 +160,7 @@ class File:
                     LIMIT 1
                 ''', (self.identifier, self.project_id))
             else:
-                cursor.execute(''' 
+                cursor.execute('''
                     SELECT * FROM files
                     WHERE id > ? AND project = ? AND collection = ?
                     ORDER BY id ASC
@@ -226,6 +226,12 @@ class File:
             return self.result(cursor.lastrowid)
 
     def remove_results(self, origin='pipeline') -> List[Result]:
+        """
+        remove all results with the specified origin
+
+        :param origin: either 'pipeline' or 'user'
+        :return: list of removed results
+        """
         with closing(self.database.con.cursor()) as cursor:
             cursor.execute('''
                 SELECT * FROM results WHERE file = ? AND origin = ?

+ 12 - 1
pycs/database/Project.py

@@ -187,7 +187,18 @@ class Project:
                           name: str,
                           description: str,
                           position: int,
-                          autoselect: bool):
+                          autoselect: bool) -> Tuple[Collection, bool]:
+        """
+        create a new collection associated with this project
+
+        :param reference: collection reference string
+        :param name: collection name
+        :param description: collection description
+        :param position: position in menus
+        :param autoselect: automatically select this collection on session load
+
+        :return: collection object, insert
+        """
         autoselect = 1 if autoselect else 0
 
         with closing(self.database.con.cursor()) as cursor:

+ 1 - 0
pycs/frontend/WebServer.py

@@ -55,6 +55,7 @@ class WebServer:
     """
 
     # pylint: disable=line-too-long
+    # pylint: disable=too-many-statements
     def __init__(self, settings: dict, database: Database, jobs: JobRunner, pipelines: PipelineCache):
         # initialize web server
         if exists('webui/index.html'):

+ 2 - 2
pycs/frontend/endpoints/data/UploadFile.py

@@ -56,8 +56,8 @@ class UploadFile(View):
         try:
             ftype, frames, fps = tpool.execute(file_info,
                                                self.data_folder, self.file_id, self.file_extension)
-        except ValueError as e:
-            return abort(400, str(e))
+        except ValueError as exception:
+            return abort(400, str(exception))
 
         # add to project files
         with self.db:

+ 13 - 6
pycs/frontend/endpoints/pipelines/FitModel.py

@@ -47,15 +47,22 @@ class FitModel(View):
 
     @staticmethod
     def load_and_fit(database: Database, pipelines: PipelineCache, project_id: int):
-        db = None
+        """
+        load the pipeline and call the fit function
+
+        :param database: database object
+        :param pipelines: pipeline cache
+        :param project_id: project id
+        """
+        database_copy = None
         pipeline = None
 
         # create new database instance
         try:
-            db = database.copy()
-            project = db.project(project_id)
+            database_copy = database.copy()
+            project = database_copy.project(project_id)
             model = project.model()
-            storage = MediaStorage(db, project_id)
+            storage = MediaStorage(database_copy, project_id)
 
             # load pipeline
             try:
@@ -67,5 +74,5 @@ class FitModel(View):
                 if pipeline is not None:
                     pipelines.free_instance(model.root_folder)
         finally:
-            if db is not None:
-                db.close()
+            if database_copy is not None:
+                database_copy.close()

+ 28 - 9
pycs/frontend/endpoints/pipelines/PredictModel.py

@@ -1,9 +1,10 @@
-from typing import Any
+from typing import Union, List
 
 from flask import make_response, request, abort
 from flask.views import View
 
 from pycs.database.Database import Database
+from pycs.database.File import File
 from pycs.frontend.notifications.NotificationList import NotificationList
 from pycs.frontend.notifications.NotificationManager import NotificationManager
 from pycs.interfaces.MediaFile import MediaFile
@@ -59,16 +60,27 @@ class PredictModel(View):
 
     @staticmethod
     def load_and_predict(database: Database, pipelines: PipelineCache,
-                         notifications: NotificationList, project_id: int, file_filter: Any):
-        db = None
+                         notifications: NotificationList,
+                         project_id: int, file_filter: Union[str, List[File]]):
+        """
+        load the pipeline and call the execute function
+
+        :param database: database object
+        :param pipelines: pipeline cache
+        :param notifications: notification object
+        :param project_id: project id
+        :param file_filter: list of files or 'new' / 'all'
+        :return:
+        """
+        database_copy = None
         pipeline = None
 
         # create new database instance
         try:
-            db = database.copy()
-            project = db.project(project_id)
+            database_copy = database.copy()
+            project = database_copy.project(project_id)
             model = project.model()
-            storage = MediaStorage(db, project_id, notifications)
+            storage = MediaStorage(database_copy, project_id, notifications)
 
             # create a list of MediaFile
             if isinstance(file_filter, str):
@@ -99,7 +111,7 @@ class PredictModel(View):
                     pipeline.execute(storage, file)
 
                     # commit changes and yield progress
-                    db.commit()
+                    database_copy.commit()
                     yield index / length, notifications
 
                     index += 1
@@ -107,10 +119,17 @@ class PredictModel(View):
                 if pipeline is not None:
                     pipelines.free_instance(model.root_folder)
         finally:
-            if db is not None:
-                db.close()
+            if database_copy is not None:
+                database_copy.close()
 
     @staticmethod
     def progress(progress: float, notifications: NotificationList):
+        """
+        fire notifications from the correct thread
+
+        :param progress: [0, 1]
+        :param notifications: Notificationlist
+        :return: progress
+        """
         notifications.fire()
         return progress

+ 2 - 2
pycs/frontend/endpoints/results/GetProjectResults.py

@@ -23,8 +23,8 @@ class GetProjectResults(View):
             return abort(404)
 
         # map media files to a dict
-        ms = MediaStorage(self.db, project.identifier, None)
-        files = list(map(lambda f: f.serialize(), ms.files().iter()))
+        storage = MediaStorage(self.db, project.identifier, None)
+        files = list(map(lambda f: f.serialize(), storage.files().iter()))
 
         # return result
         return jsonify(files)

+ 15 - 2
pycs/frontend/notifications/NotificationList.py

@@ -2,14 +2,27 @@ from pycs.frontend.notifications.NotificationManager import NotificationManager
 
 
 class NotificationList:
-    def __init__(self, nm: NotificationManager):
+    """
+    stores notifications to fire them later in the correct thread
+    """
+
+    def __init__(self, notifications: NotificationManager):
         self.__list = []
-        self.nm = nm
+        self.notifications = notifications
 
     def add(self, fun: callable, *params):
+        """
+        add a function and parameters to be executed later
+
+        :param fun: function
+        :param params: parameters
+        """
         self.__list.append((fun, *params))
 
     def fire(self):
+        """
+        fire all stored events and reset the list
+        """
         for fun, *params in self.__list:
             fun(*params)
 

+ 10 - 0
pycs/interfaces/MediaBoundingBox.py

@@ -2,6 +2,11 @@ from pycs.database.Result import Result
 
 
 class MediaBoundingBox:
+    """
+    A bounding box defined by it's upper left corner coordinates plus width and height. All those
+    values are normalized. A label and a frame (for videos) are optional.
+    """
+
     def __init__(self, result: Result):
         self.x = result.data['x']
         self.y = result.data['y']
@@ -11,4 +16,9 @@ class MediaBoundingBox:
         self.frame = result.data['frame'] if 'frame' in result.data else None
 
     def serialize(self) -> dict:
+        """
+        serialize all object properties to a dict
+
+        :return: dict
+        """
         return dict({'type': 'bounding-box'}, **self.__dict__)

+ 12 - 7
pycs/interfaces/MediaFile.py

@@ -35,7 +35,7 @@ class MediaFile:
         :param reference: use None to remove this file's collection
         """
         self.__file.set_collection_by_reference(reference)
-        self.__notifications.add(self.__notifications.nm.edit_file, self.__file)
+        self.__notifications.add(self.__notifications.notifications.edit_file, self.__file)
 
     def set_image_label(self, label: Union[int, MediaLabel], frame: int = None):
         """
@@ -53,7 +53,7 @@ class MediaFile:
             data = None
 
         created = self.__file.create_result('pipeline', 'labeled-image', label, data)
-        self.__notifications.add(self.__notifications.nm.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,
                          label: Union[int, MediaLabel] = None, frame: int = None):
@@ -80,22 +80,22 @@ class MediaFile:
             label = label.identifier
 
         created = self.__file.create_result('pipeline', 'bounding-box', label, result)
-        self.__notifications.add(self.__notifications.nm.create_result, created)
+        self.__notifications.add(self.__notifications.notifications.create_result, created)
 
     def remove_predictions(self):
         """
         remove and return all predictions added from pipelines
         """
         removed = self.__file.remove_results(origin='pipeline')
-        for r in removed:
-            self.__notifications.add(self.__notifications.nm.remove_result, r)
+        for result in removed:
+            self.__notifications.add(self.__notifications.notifications.remove_result, result)
 
     def __get_results(self, origin: str) -> List[Union[MediaImageLabel, MediaBoundingBox]]:
         def map_r(result: Result) -> Union[MediaImageLabel, MediaBoundingBox]:
             if result.type == 'labeled-image':
                 return MediaImageLabel(result)
-            else:
-                return MediaBoundingBox(result)
+
+            return MediaBoundingBox(result)
 
         return list(map(map_r,
                         filter(lambda r: r.origin == origin,
@@ -118,6 +118,11 @@ class MediaFile:
         return self.__get_results('pipeline')
 
     def serialize(self) -> dict:
+        """
+        serialize all object properties to a dict
+
+        :return: dict
+        """
         return {
             'type': self.type,
             'size': self.size,

+ 12 - 2
pycs/interfaces/MediaFileList.py

@@ -17,13 +17,23 @@ class MediaFileList:
         self.__collection = None
         self.__label = None
 
-    # TODO pydoc
     def filter_collection(self, collection_reference: str):
+        """
+        only include results matching the collection
+
+        :param collection_reference: reference string
+        :return: MediaFileList
+        """
         self.__collection = collection_reference
         return self
 
-    # TODO pydoc
     def filter_label(self, label: MediaLabel):
+        """
+        only include results matching the label
+
+        :param label: label
+        :return: MediaFileList
+        """
         self.__label = label
         return self
 

+ 9 - 0
pycs/interfaces/MediaImageLabel.py

@@ -2,9 +2,18 @@ from pycs.database.Result import Result
 
 
 class MediaImageLabel:
+    """
+    An image label with an optional frame index for videos.
+    """
+
     def __init__(self, result: Result):
         self.label = result.label
         self.frame = result.data['frame'] if 'frame' in result.data else None
 
     def serialize(self) -> dict:
+        """
+        serialize all object properties to a dict
+
+        :return: dict
+        """
         return dict({'type': 'image-label'}, **self.__dict__)

+ 4 - 0
pycs/interfaces/MediaLabel.py

@@ -2,6 +2,10 @@ from pycs.database.Label import Label
 
 
 class MediaLabel:
+    """
+    a label
+    """
+
     def __init__(self, label: Label):
         self.identifier = label.identifier
         self.parent = None

+ 4 - 4
pycs/interfaces/MediaStorage.py

@@ -30,13 +30,13 @@ class MediaStorage:
         result = []
 
         for label in label_list:
-            ml = label_dict[label.identifier]
+            medial_label = label_dict[label.identifier]
 
             if label.parent_id is not None:
-                ml.parent = label_dict[label.parent_id]
-                ml.parent.children.append(ml)
+                medial_label.parent = label_dict[label.parent_id]
+                medial_label.parent.children.append(medial_label)
 
-            result.append(ml)
+            result.append(medial_label)
 
         return result
 

+ 2 - 2
pycs/jobs/JobRunner.py

@@ -218,8 +218,8 @@ class JobRunner:
                     result_event.send(result)
 
             # save exceptions to show in ui
-            except Exception as e:
-                job.exception = f'{type(e).__name__} ({str(e)})'
+            except Exception as exception:
+                job.exception = f'{type(exception).__name__} ({str(exception)})'
 
             # remove from group dict
             if group is not None:

+ 4 - 0
pycs/util/PipelineCache.py

@@ -11,6 +11,10 @@ from pycs.util.PipelineUtil import load_from_root_folder
 
 
 class PipelineCache:
+    """
+    Store initialized pipelines and call `close` after `CLOSE_TIMER` if they are not requested
+    another time.
+    """
     CLOSE_TIMER = 120
 
     def __init__(self, jobs: JobRunner):