Project.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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", backref="project", lazy="dynamic")
  28. labels = db.relationship("Label", backref="project", lazy="dynamic")
  29. collections = db.relationship("Collection", backref="project", lazy="dynamic")
  30. serialize_rules = (
  31. '-files',
  32. '-labels',
  33. '-collections',
  34. )
  35. def label(self, id: int) -> T.Optional[Label]:
  36. """
  37. get a label using its unique identifier
  38. :param identifier: unique identifier
  39. :return: label
  40. """
  41. return self.labels.filter_by(id=id).one_or_none()
  42. def file(self, id: int) -> T.Optional[Label]:
  43. """
  44. get a file using its unique identifier
  45. :param identifier: unique identifier
  46. :return: file
  47. """
  48. return self.files.filter_by(id=id).one_or_none()
  49. def collection(self, id: int) -> T.Optional[Collection]:
  50. """
  51. get a collection using its unique identifier
  52. :param identifier: unique identifier
  53. :return: collection
  54. """
  55. return self.collections.filter_by(id=id).one_or_none()
  56. def collection_by_reference(self, reference: str) -> T.Optional[Collection]:
  57. """
  58. get a collection using its unique identifier
  59. :param identifier: unique identifier
  60. :return: collection
  61. """
  62. return self.collections.filter_by(reference=reference).one_or_none()
  63. def create_label(self, name: str, reference: str = None,
  64. parent_id: int = None) -> Tuple[Optional[Label], bool]:
  65. """
  66. create a label for this project. If there is already a label with the same reference
  67. in the database its name is updated.
  68. :param name: label name
  69. :param reference: label reference
  70. :param parent_id: parent's identifier
  71. :return: created or edited label, insert
  72. """
  73. label, is_new = Label.get_or_create(project=self, reference=reference)
  74. label.set_name(name)
  75. label.set_parent(parent_id)
  76. self.commit()
  77. return label, is_new
  78. def create_collection(self,
  79. reference: str,
  80. name: str,
  81. description: str,
  82. position: int,
  83. autoselect: bool):
  84. collection, is_new = Collection.get_or_create(project=self, reference=reference)
  85. collection.name = name
  86. collection.description = description
  87. collection.position = position
  88. collection.autoselect = autoselect
  89. self.commit()
  90. return collection, is_new
  91. def add_file(self, uuid: str, file_type: str, name: str, extension: str, size: int,
  92. filename: str, frames: int = None, fps: float = None) -> T.Tuple[File, bool]:
  93. """
  94. add a file to this project
  95. :param uuid: unique identifier which is used for temporary files
  96. :param file_type: file type (either image or video)
  97. :param name: file name
  98. :param extension: file extension
  99. :param size: file size
  100. :param filename: actual name in filesystem
  101. :param frames: frame count
  102. :param fps: frames per second
  103. :return: file
  104. """
  105. path = join(self.data_folder, filename + extension)
  106. file, is_new = File.get_or_create(project=self, path=path)
  107. file.uuid = uuid
  108. file.type = file_type
  109. file.name = name
  110. file.extension = extension
  111. file.size = size
  112. file.frames = frames
  113. file.fps = fps
  114. self.commit()
  115. return file, is_new
  116. def set_description(self, description: str):
  117. """
  118. set this projects description
  119. :param description: new description
  120. :return:
  121. """
  122. self.description = description
  123. self.commit()
  124. def count_files(self) -> int:
  125. """
  126. count files associated with this project
  127. :return: count
  128. """
  129. return self.files.count()
  130. def get_files(self, offset: int = 0, limit: int = -1) -> T.Iterator[File]:
  131. """
  132. get an iterator of files associated with this project
  133. :param offset: file offset
  134. :param limit: file limit
  135. :return: iterator of files
  136. """
  137. return self.files.order_by(File.id).offset(offset).limit(limit)
  138. def count_files_without_results(self) -> int:
  139. """
  140. count files without associated results
  141. :return: count
  142. """
  143. raise NotImplementedError
  144. with closing(self.database.con.cursor()) as cursor:
  145. cursor.execute('''
  146. SELECT COUNT(*)
  147. FROM files
  148. LEFT JOIN results ON files.id = results.file
  149. WHERE files.project = ? AND results.id IS NULL
  150. ''', [self.identifier])
  151. return cursor.fetchone()[0]
  152. def files_without_results(self) -> Iterator[File]:
  153. """
  154. get an iterator of files without associated results
  155. :return: list of files
  156. """
  157. raise NotImplementedError
  158. with closing(self.database.con.cursor()) as cursor:
  159. cursor.execute('''
  160. SELECT files.*
  161. FROM files
  162. LEFT JOIN results ON files.id = results.file
  163. WHERE files.project = ? AND results.id IS NULL
  164. ORDER BY id ASC
  165. ''', [self.identifier])
  166. for row in cursor:
  167. yield File(self.database, row)
  168. def files_without_collection(self, offset: int = 0, limit: int = -1) -> Iterator[File]:
  169. """
  170. get an iterator of files without not associated with any collection
  171. :return: list of files
  172. """
  173. return self.get_files(offset, limit).filter(File.collection_id == None)
  174. def count_files_without_collection(self) -> int:
  175. """
  176. count files associated with this project but with no collection
  177. :return: count
  178. """
  179. return self.files_without_collection().count()