Project.py 7.1 KB

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