Project.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. import os
  2. import typing as T
  3. from contextlib import closing
  4. from datetime import datetime
  5. from pycs import db
  6. from pycs.database.base import NamedBaseModel
  7. from pycs.database.Collection import Collection
  8. from pycs.database.File import File
  9. from pycs.database.Label import Label
  10. class Project(NamedBaseModel):
  11. description = db.Column(db.String)
  12. created = db.Column(db.DateTime, default=datetime.utcnow,
  13. index=True, nullable=False)
  14. model_id = db.Column(
  15. db.Integer,
  16. db.ForeignKey("model.id", ondelete="SET NULL"))
  17. label_provider_id = db.Column(
  18. db.Integer,
  19. db.ForeignKey("label_provider.id", ondelete="SET NULL"))
  20. root_folder = db.Column(db.String, nullable=False, unique=True)
  21. external_data = db.Column(db.Boolean, nullable=False)
  22. data_folder = db.Column(db.String, nullable=False)
  23. # contraints
  24. __table_args__ = ()
  25. # relationships to other models
  26. files = db.relationship("File",
  27. backref="project",
  28. lazy="dynamic",
  29. passive_deletes=True)
  30. labels = db.relationship("Label",
  31. backref="project",
  32. lazy="dynamic",
  33. passive_deletes=True)
  34. collections = db.relationship("Collection",
  35. backref="project",
  36. lazy="dynamic",
  37. passive_deletes=True)
  38. serialize_only = NamedBaseModel.serialize_only + (
  39. "created",
  40. "description",
  41. "model_id",
  42. "label_provider_id",
  43. "root_folder",
  44. "external_data",
  45. "data_folder",
  46. )
  47. def file(self, id: int) -> T.Optional[File]:
  48. """
  49. get a file using its unique identifier
  50. :param id: unique identifier
  51. :return: file
  52. """
  53. return self.files.filter_by(id=id).one_or_none()
  54. def label_tree(self) -> T.List[Label]:
  55. """
  56. get a list of root labels associated with this project
  57. :return: list of labels
  58. """
  59. return self.labels.filter(Label.parent_id == None).all()
  60. def label(self, id: int) -> T.Optional[Label]:
  61. """
  62. get a label using its unique identifier
  63. :param id: unique identifier
  64. :return: label
  65. """
  66. return self.labels.filter_by(id=id).one_or_none()
  67. def label_by_reference(self, reference: str) -> T.Optional[Label]:
  68. """
  69. get a label using its reference string
  70. :param reference: reference string
  71. :return: label
  72. """
  73. return self.labels.filter_by(reference=reference).one_or_none()
  74. def create_label(self, name: str,
  75. reference: T.Optional[str] = None,
  76. parent_id: T.Union[Label, int, str] = None,
  77. hierarchy_level: T.Optional[str] = None,
  78. commit: bool = True) -> T.Tuple[T.Optional[Label], bool]:
  79. """
  80. create a label for this project. If there is already a label with the same reference
  81. in the database its name is updated.
  82. :param name: label name
  83. :param reference: label reference
  84. :param parent: either parent identifier, parent reference string or `Label` object
  85. :param hierarchy_level: hierarchy level name
  86. :return: created or edited label, insert
  87. """
  88. if isinstance(parent_id, str):
  89. parent_id = self.label_by_reference(parent_id)
  90. if isinstance(parent_id, Label):
  91. parent_id = parent_id.id
  92. label, is_new = Label.get_or_create(project=self, reference=reference)
  93. label.name = name
  94. label.hierarchy_level = hierarchy_level
  95. label.set_parent(parent_id, commit=False)
  96. if commit:
  97. self.commit()
  98. return label, is_new
  99. def collection(self, id: int) -> T.Optional[Collection]:
  100. """
  101. get a collection using its unique identifier
  102. :param identifier: unique identifier
  103. :return: collection
  104. """
  105. return self.collections.filter_by(id=id).one_or_none()
  106. def collection_by_reference(self, reference: str) -> T.Optional[Collection]:
  107. """
  108. get a collection using its unique identifier
  109. :param identifier: unique identifier
  110. :return: collection
  111. """
  112. return self.collections.filter_by(reference=reference).one_or_none()
  113. def create_collection(self,
  114. reference: str,
  115. name: str,
  116. description: str,
  117. position: int,
  118. autoselect: bool,
  119. commit: bool = True) -> T.Tuple[Collection, bool]:
  120. """
  121. create a new collection associated with this project
  122. :param reference: collection reference string
  123. :param name: collection name
  124. :param description: collection description
  125. :param position: position in menus
  126. :param autoselect: automatically select this collection on session load
  127. :return: collection object, insert
  128. """
  129. collection, is_new = Collection.get_or_create(project_id=self.id, reference=reference)
  130. collection.name = name
  131. collection.description = description
  132. collection.position = position
  133. collection.autoselect = autoselect
  134. if commit:
  135. self.commit()
  136. return collection, is_new
  137. def add_file(self, uuid: str, file_type: str, name: str, extension: str, size: int,
  138. filename: str, frames: int = None, fps: float = None, commit: bool = True) -> T.Tuple[File, bool]:
  139. """
  140. add a file to this project
  141. :param uuid: unique identifier which is used for temporary files
  142. :param file_type: file type (either image or video)
  143. :param name: file name
  144. :param extension: file extension
  145. :param size: file size
  146. :param filename: actual name in filesystem
  147. :param frames: frame count
  148. :param fps: frames per second
  149. :return: file
  150. """
  151. path = os.path.join(self.data_folder, filename + extension)
  152. file, is_new = File.get_or_create(project=self, path=path)
  153. file.uuid = uuid
  154. file.type = file_type
  155. file.name = name
  156. file.extension = extension
  157. file.size = size
  158. file.frames = frames
  159. file.fps = fps
  160. if commit:
  161. self.commit()
  162. return file, is_new
  163. def count_files(self) -> int:
  164. """
  165. count files associated with this project
  166. :return: count
  167. """
  168. return self.files.count()
  169. def get_files(self, offset: int = 0, limit: int = -1) -> T.Iterator[File]:
  170. """
  171. get an iterator of files associated with this project
  172. :param offset: file offset
  173. :param limit: file limit
  174. :return: iterator of files
  175. """
  176. return self.files.order_by(File.id).offset(offset).limit(limit)
  177. def _files_without_results(self):
  178. """
  179. get files without any results
  180. :return: a query object
  181. """
  182. return self.files.filter(~File.results.any())
  183. def count_files_without_results(self) -> int:
  184. """
  185. count files without associated results
  186. :return: count
  187. """
  188. return self._files_without_results().count()
  189. def files_without_results(self) -> T.Iterator[File]:
  190. """
  191. get an iterator of files without associated results
  192. :return: list of files
  193. """
  194. return self._files_without_results().all()