Project.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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 label(self, id: int) -> T.Optional[Label]:
  48. """
  49. get a label using its unique identifier
  50. :param id: unique identifier
  51. :return: label
  52. """
  53. return self.labels.filter_by(id=id).one_or_none()
  54. def label_by_reference(self, reference: str) -> T.Optional[Label]:
  55. """
  56. get a label using its reference string
  57. :param reference: reference string
  58. :return: label
  59. """
  60. return self.labels.filter_by(reference=reference).one_or_none()
  61. def file(self, id: int) -> T.Optional[Label]:
  62. """
  63. get a file using its unique identifier
  64. :param id: unique identifier
  65. :return: file
  66. """
  67. return self.files.filter_by(id=id).one_or_none()
  68. def collection(self, id: int) -> T.Optional[Collection]:
  69. """
  70. get a collection using its unique identifier
  71. :param identifier: unique identifier
  72. :return: collection
  73. """
  74. return self.collections.filter_by(id=id).one_or_none()
  75. def collection_by_reference(self, reference: str) -> T.Optional[Collection]:
  76. """
  77. get a collection using its unique identifier
  78. :param identifier: unique identifier
  79. :return: collection
  80. """
  81. return self.collections.filter_by(reference=reference).one_or_none()
  82. def create_label(self, name: str, reference: str = None,
  83. parent_id: T.Union[Label, int, str] = None, commit: bool = True) -> T.Tuple[T.Optional[Label], bool]:
  84. """
  85. create a label for this project. If there is already a label with the same reference
  86. in the database its name is updated.
  87. :param name: label name
  88. :param reference: label reference
  89. :param parent_id: parent's identifier
  90. :return: created or edited label, insert
  91. """
  92. if isinstance(parent_id, str):
  93. parent_id = self.label_by_reference(parent_id)
  94. if isinstance(parent_id, Label):
  95. parent_id = parent_id.id
  96. label, is_new = Label.get_or_create(project=self, reference=reference)
  97. label.name = name
  98. label.set_parent(parent_id, commit=False)
  99. if commit:
  100. self.commit()
  101. return label, is_new
  102. def create_collection(self,
  103. reference: str,
  104. name: str,
  105. description: str,
  106. position: int,
  107. autoselect: bool,
  108. commit: bool = True) -> T.Tuple[Collection, bool]:
  109. """
  110. create a new collection associated with this project
  111. :param reference: collection reference string
  112. :param name: collection name
  113. :param description: collection description
  114. :param position: position in menus
  115. :param autoselect: automatically select this collection on session load
  116. :return: collection object, insert
  117. """
  118. collection, is_new = Collection.get_or_create(project_id=self.id, reference=reference)
  119. collection.name = name
  120. collection.description = description
  121. collection.position = position
  122. collection.autoselect = autoselect
  123. if commit:
  124. self.commit()
  125. return collection, is_new
  126. def add_file(self, uuid: str, file_type: str, name: str, extension: str, size: int,
  127. filename: str, frames: int = None, fps: float = None, commit: bool = True) -> T.Tuple[File, bool]:
  128. """
  129. add a file to this project
  130. :param uuid: unique identifier which is used for temporary files
  131. :param file_type: file type (either image or video)
  132. :param name: file name
  133. :param extension: file extension
  134. :param size: file size
  135. :param filename: actual name in filesystem
  136. :param frames: frame count
  137. :param fps: frames per second
  138. :return: file
  139. """
  140. path = os.path.join(self.data_folder, filename + extension)
  141. file, is_new = File.get_or_create(project=self, path=path)
  142. file.uuid = uuid
  143. file.type = file_type
  144. file.name = name
  145. file.extension = extension
  146. file.size = size
  147. file.frames = frames
  148. file.fps = fps
  149. if commit:
  150. self.commit()
  151. return file, is_new
  152. def set_description(self, description: str):
  153. """
  154. set this projects description
  155. :param description: new description
  156. :return:
  157. """
  158. self.description = description
  159. self.commit()
  160. def count_files(self) -> int:
  161. """
  162. count files associated with this project
  163. :return: count
  164. """
  165. return self.files.count()
  166. def get_files(self, offset: int = 0, limit: int = -1) -> T.Iterator[File]:
  167. """
  168. get an iterator of files associated with this project
  169. :param offset: file offset
  170. :param limit: file limit
  171. :return: iterator of files
  172. """
  173. return self.files.order_by(File.id).offset(offset).limit(limit)
  174. def _files_without_results(self):
  175. """
  176. get files without any results
  177. :return: a query object
  178. """
  179. return self.files.filter(~File.results.any())
  180. def count_files_without_results(self) -> int:
  181. """
  182. count files without associated results
  183. :return: count
  184. """
  185. return self._files_without_results().count()
  186. def files_without_results(self) -> T.Iterator[File]:
  187. """
  188. get an iterator of files without associated results
  189. :return: list of files
  190. """
  191. return self._files_without_results().all()
  192. def files_without_collection(self, offset: int = 0, limit: int = -1) -> T.Iterator[File]:
  193. """
  194. get an iterator of files without not associated with any collection
  195. :return: list of files
  196. """
  197. return self.get_files(offset, limit).filter(File.collection_id == None)
  198. def count_files_without_collection(self) -> int:
  199. """
  200. count files associated with this project but with no collection
  201. :return: count
  202. """
  203. return self.files_without_collection().count()