6
0

Project.py 7.1 KB

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