|
@@ -1,12 +1,8 @@
|
|
|
+import os
|
|
|
import typing as T
|
|
|
+import warnings
|
|
|
|
|
|
-from contextlib import closing
|
|
|
from datetime import datetime
|
|
|
-from os.path import join
|
|
|
-from typing import Iterator
|
|
|
-from typing import List
|
|
|
-from typing import Optional
|
|
|
-from typing import Tuple
|
|
|
|
|
|
from pycs import db
|
|
|
from pycs.database.base import NamedBaseModel
|
|
@@ -15,7 +11,7 @@ from pycs.database.Collection import Collection
|
|
|
from pycs.database.File import File
|
|
|
from pycs.database.Label import Label
|
|
|
from pycs.database.util import commit_on_return
|
|
|
-from pycs.database.util.TreeNodeLabel import TreeNodeLabel
|
|
|
+
|
|
|
|
|
|
class Project(NamedBaseModel):
|
|
|
description = db.Column(db.String)
|
|
@@ -41,76 +37,74 @@ class Project(NamedBaseModel):
|
|
|
__table_args__ = ()
|
|
|
|
|
|
# relationships to other models
|
|
|
- files = db.relationship("File", backref="project", lazy=True)
|
|
|
- labels = db.relationship("Label", backref="project", lazy=True)
|
|
|
- collections = db.relationship("Collection", backref="project", lazy=True)
|
|
|
-
|
|
|
-
|
|
|
- def label(self, id: int) -> T.Optional[Label]:
|
|
|
+ files = db.relationship(
|
|
|
+ "File",
|
|
|
+ backref="project",
|
|
|
+ lazy="dynamic")
|
|
|
+
|
|
|
+ labels = db.relationship(
|
|
|
+ "Label",
|
|
|
+ backref="project",
|
|
|
+ lazy="dynamic")
|
|
|
+
|
|
|
+ collections = db.relationship(
|
|
|
+ "Collection",
|
|
|
+ backref="project",
|
|
|
+ lazy="dynamic")
|
|
|
+
|
|
|
+
|
|
|
+ serialize_only = NamedBaseModel.serialize_only + (
|
|
|
+ "created",
|
|
|
+ "description",
|
|
|
+ "model_id",
|
|
|
+ "label_provider_id",
|
|
|
+ "root_folder",
|
|
|
+ "external_data",
|
|
|
+ "data_folder",
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+ def label(self, identifier: int) -> T.Optional[Label]:
|
|
|
"""
|
|
|
get a label using its unique identifier
|
|
|
|
|
|
:param identifier: unique identifier
|
|
|
:return: label
|
|
|
"""
|
|
|
- return self.labels.get(id)
|
|
|
+ return self.labels.filter(Label.id == identifier).one_or_none()
|
|
|
|
|
|
- def file(self, id: int) -> T.Optional[Label]:
|
|
|
- """
|
|
|
- get a file using its unique identifier
|
|
|
|
|
|
- :param identifier: unique identifier
|
|
|
- :return: file
|
|
|
+ def label_by_reference(self, reference: str) -> T.Optional[Label]:
|
|
|
"""
|
|
|
- return self.files.get(id)
|
|
|
+ get a label using its reference string
|
|
|
|
|
|
- def collection(self, id: int) -> T.Optional[Collection]:
|
|
|
+ :param reference: reference string
|
|
|
+ :return: label
|
|
|
"""
|
|
|
- get a collection using its unique identifier
|
|
|
+ return self.labels.filter(Label.reference == reference).one_or_none()
|
|
|
|
|
|
- :param identifier: unique identifier
|
|
|
- :return: collection
|
|
|
- """
|
|
|
- return self.collections.get(id)
|
|
|
|
|
|
- def collection_by_reference(self, reference: str) -> T.Optional[Collection]:
|
|
|
+ def file(self, identifier: int) -> T.Optional[Label]:
|
|
|
"""
|
|
|
- get a collection using its unique identifier
|
|
|
+ get a file using its unique identifier
|
|
|
|
|
|
:param identifier: unique identifier
|
|
|
- :return: collection
|
|
|
+ :return: file
|
|
|
"""
|
|
|
- return self.collections.filter_by(reference=reference).one()
|
|
|
+ return self.files.filter(File.id == identifier).one_or_none()
|
|
|
|
|
|
- @commit_on_return
|
|
|
- def create_label(self, name: str, reference: str = None,
|
|
|
- parent_id: int = None,
|
|
|
- hierarchy_level: str = None) -> Tuple[Optional[Label], bool]:
|
|
|
- """
|
|
|
- create a label for this project. If there is already a label with the same reference
|
|
|
- in the database its name is updated.
|
|
|
|
|
|
- :param name: label name
|
|
|
- :param reference: label reference
|
|
|
- :param parent_id: parent's identifier
|
|
|
- :param hierarchy_level: hierarchy level name
|
|
|
- :return: created or edited label, insert
|
|
|
+ def label_tree(self) -> T.List[Label]:
|
|
|
"""
|
|
|
+ get a list of root labels associated with this project
|
|
|
|
|
|
- label = Label.query.get(project=self, reference=reference)
|
|
|
- is_new = False
|
|
|
-
|
|
|
- if label is None:
|
|
|
- label = Label.new(project=self, reference=reference)
|
|
|
- is_new = True
|
|
|
-
|
|
|
- label.set_name(name, commit=False)
|
|
|
- label.set_parent(parent_id, commit=False)
|
|
|
- label.hierarchy_level = hierarchy_level
|
|
|
+ :return: list of labels
|
|
|
+ """
|
|
|
+ warnings.warn("Check performance of this method!")
|
|
|
+ return self.labels.filter(Label.parent_id == None).all()
|
|
|
|
|
|
- return label, is_new
|
|
|
|
|
|
- def label_tree(self) -> List[TreeNodeLabel]:
|
|
|
+ def label_tree_original(self):
|
|
|
"""
|
|
|
get a list of root labels associated with this project
|
|
|
|
|
@@ -144,13 +138,64 @@ class Project(NamedBaseModel):
|
|
|
|
|
|
return result
|
|
|
|
|
|
+
|
|
|
+ def collection(self, identifier: int) -> T.Optional[Collection]:
|
|
|
+ """
|
|
|
+ get a collection using its unique identifier
|
|
|
+
|
|
|
+ :param identifier: unique identifier
|
|
|
+ :return: collection
|
|
|
+ """
|
|
|
+ return self.collections.filter(Collection.id == identifier).one_or_none()
|
|
|
+
|
|
|
+
|
|
|
+ def collection_by_reference(self, reference: str) -> T.Optional[Collection]:
|
|
|
+ """
|
|
|
+ get a collection using its unique identifier
|
|
|
+
|
|
|
+ :param identifier: unique identifier
|
|
|
+ :return: collection
|
|
|
+ """
|
|
|
+ return self.collections.filter(Collection.reference == reference).one_or_none()
|
|
|
+
|
|
|
+
|
|
|
+ @commit_on_return
|
|
|
+ def create_label(self, name: str,
|
|
|
+ reference: str = None,
|
|
|
+ parent_id: int = None,
|
|
|
+ hierarchy_level: str = None) -> T.Tuple[T.Optional[Label], bool]:
|
|
|
+ """
|
|
|
+ create a label for this project. If there is already a label with the same reference
|
|
|
+ in the database its name is updated.
|
|
|
+
|
|
|
+ :param name: label name
|
|
|
+ :param reference: label reference
|
|
|
+ :param parent_id: parent's identifier
|
|
|
+ :param hierarchy_level: hierarchy level name
|
|
|
+ :return: created or edited label, insert
|
|
|
+ """
|
|
|
+
|
|
|
+ label = Label.query.get(project=self, reference=reference)
|
|
|
+ is_new = False
|
|
|
+
|
|
|
+ if label is None:
|
|
|
+ label = Label.new(project=self, reference=reference)
|
|
|
+ is_new = True
|
|
|
+
|
|
|
+ label.set_name(name, commit=False)
|
|
|
+ label.set_parent(parent_id, commit=False)
|
|
|
+ label.hierarchy_level = hierarchy_level
|
|
|
+
|
|
|
+ return label, is_new
|
|
|
+
|
|
|
+
|
|
|
@commit_on_return
|
|
|
def create_collection(self,
|
|
|
reference: str,
|
|
|
name: str,
|
|
|
description: str,
|
|
|
position: int,
|
|
|
- autoselect: bool) -> Tuple[Collection, bool]:
|
|
|
+ autoselect: bool) -> T.Tuple[Collection, bool]:
|
|
|
"""
|
|
|
create a new collection associated with this project
|
|
|
|
|
@@ -163,13 +208,9 @@ class Project(NamedBaseModel):
|
|
|
:return: collection object, insert
|
|
|
"""
|
|
|
|
|
|
- collection = Collection.query.get(project=self, reference=reference)
|
|
|
- is_new = False
|
|
|
|
|
|
- if collection is None:
|
|
|
- collection = Collection.new(project=self,
|
|
|
- reference=reference)
|
|
|
- is_new = True
|
|
|
+ collection, is_new = Collection.get_or_create(
|
|
|
+ project_id=self.id, reference=reference)
|
|
|
|
|
|
collection.name = name
|
|
|
collection.description = description
|
|
@@ -178,9 +219,17 @@ class Project(NamedBaseModel):
|
|
|
|
|
|
return collection, is_new
|
|
|
|
|
|
+
|
|
|
@commit_on_return
|
|
|
- def add_file(self, uuid: str, file_type: str, name: str, extension: str, size: int,
|
|
|
- filename: str, frames: int = None, fps: float = None) -> T.Tuple[File, bool]:
|
|
|
+ def add_file(self,
|
|
|
+ uuid: str,
|
|
|
+ file_type: str,
|
|
|
+ name: str,
|
|
|
+ extension: str,
|
|
|
+ size: int,
|
|
|
+ filename: str,
|
|
|
+ frames: int = None,
|
|
|
+ fps: float = None) -> T.Tuple[File, bool]:
|
|
|
"""
|
|
|
add a file to this project
|
|
|
|
|
@@ -194,14 +243,10 @@ class Project(NamedBaseModel):
|
|
|
:param fps: frames per second
|
|
|
:return: file
|
|
|
"""
|
|
|
- path = join(self.data_folder, filename + extension)
|
|
|
-
|
|
|
- file = File.objects.get(project=self, path=path)
|
|
|
- is_new = False
|
|
|
+ path = os.path.join(self.data_folder, f"{filename}{extension}")
|
|
|
|
|
|
- if file is None:
|
|
|
- file = File.new(uuid=uuid, project=self, path=path)
|
|
|
- is_new = True
|
|
|
+ file, is_new = File.get_or_create(
|
|
|
+ project_id=self.id, path=path)
|
|
|
|
|
|
file.type = file_type
|
|
|
file.name = name
|
|
@@ -213,15 +258,6 @@ class Project(NamedBaseModel):
|
|
|
return file, is_new
|
|
|
|
|
|
|
|
|
- def set_description(self, description: str):
|
|
|
- """
|
|
|
- set this projects description
|
|
|
-
|
|
|
- :param description: new description
|
|
|
- :return:
|
|
|
- """
|
|
|
- self.description = description
|
|
|
- self
|
|
|
def count_files(self) -> int:
|
|
|
"""
|
|
|
count files associated with this project
|
|
@@ -230,7 +266,8 @@ class Project(NamedBaseModel):
|
|
|
"""
|
|
|
return self.files.count()
|
|
|
|
|
|
- def get_files(self, offset: int = 0, limit: int = -1) -> T.Iterator[File]:
|
|
|
+
|
|
|
+ def get_files(self, offset: int = 0, limit: int = -1) -> T.List[File]:
|
|
|
"""
|
|
|
get an iterator of files associated with this project
|
|
|
|
|
@@ -238,7 +275,17 @@ class Project(NamedBaseModel):
|
|
|
:param limit: file limit
|
|
|
:return: iterator of files
|
|
|
"""
|
|
|
- return self.files.order_by(File.id.acs()).offset(offset).limit(limit)
|
|
|
+ return self.files.order_by(File.id).offset(offset).limit(limit).all()
|
|
|
+
|
|
|
+
|
|
|
+ def _files_without_results(self):
|
|
|
+ """
|
|
|
+ get files without any results
|
|
|
+
|
|
|
+ :return: a query object
|
|
|
+ """
|
|
|
+ return self.files.filter(~File.results.any())
|
|
|
+
|
|
|
|
|
|
def count_files_without_results(self) -> int:
|
|
|
"""
|
|
@@ -246,50 +293,40 @@ class Project(NamedBaseModel):
|
|
|
|
|
|
:return: count
|
|
|
"""
|
|
|
- raise NotImplementedError
|
|
|
|
|
|
- with closing(self.database.con.cursor()) as cursor:
|
|
|
- cursor.execute('''
|
|
|
- SELECT COUNT(*)
|
|
|
- FROM files
|
|
|
- LEFT JOIN results ON files.id = results.file
|
|
|
- WHERE files.project = ? AND results.id IS NULL
|
|
|
- ''', [self.identifier])
|
|
|
- return cursor.fetchone()[0]
|
|
|
+ return self._files_without_results().count()
|
|
|
|
|
|
- def files_without_results(self) -> Iterator[File]:
|
|
|
+
|
|
|
+ def files_without_results(self) -> T.List[File]:
|
|
|
"""
|
|
|
- get an iterator of files without associated results
|
|
|
+ get a list of files without associated results
|
|
|
|
|
|
:return: list of files
|
|
|
"""
|
|
|
- raise NotImplementedError
|
|
|
+ return self._files_without_results().all()
|
|
|
|
|
|
- with closing(self.database.con.cursor()) as cursor:
|
|
|
- cursor.execute('''
|
|
|
- SELECT files.*
|
|
|
- FROM files
|
|
|
- LEFT JOIN results ON files.id = results.file
|
|
|
- WHERE files.project = ? AND results.id IS NULL
|
|
|
- ORDER BY id ASC
|
|
|
- ''', [self.identifier])
|
|
|
|
|
|
- for row in cursor:
|
|
|
- yield File(self.database, row)
|
|
|
+ def _files_without_collection(self, offset: int = 0, limit: int = -1):
|
|
|
+ """
|
|
|
+ get files without a collection
|
|
|
|
|
|
- def files_without_collection(self, offset: int = 0, limit: int = -1) -> Iterator[File]:
|
|
|
+ :return: a query object
|
|
|
"""
|
|
|
- get an iterator of files without not associated with any collection
|
|
|
+ return self.get_files(offset, limit).filter(File.collection_id == None)
|
|
|
+
|
|
|
+ def files_without_collection(self, offset: int = 0, limit: int = -1) -> T.List[File]:
|
|
|
+ """
|
|
|
+ get a list of files without a collection
|
|
|
|
|
|
:return: list of files
|
|
|
"""
|
|
|
- return self.get_files(offset, limit).filter(File.collection_id == None)
|
|
|
+ return self._files_without_collection(offset=offset, limit=limit).all()
|
|
|
|
|
|
|
|
|
def count_files_without_collection(self) -> int:
|
|
|
"""
|
|
|
- count files associated with this project but with no collection
|
|
|
+ count files associated with this project but without a collection
|
|
|
|
|
|
:return: count
|
|
|
"""
|
|
|
- return self.files_without_collection().count()
|
|
|
+ return self._files_without_collection().count()
|