Project.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. from contextlib import closing
  2. from os.path import join
  3. from time import time
  4. from typing import List, Optional, Tuple
  5. from pycs.database.Collection import Collection
  6. from pycs.database.File import File
  7. from pycs.database.Label import Label
  8. from pycs.database.LabelProvider import LabelProvider
  9. from pycs.database.Model import Model
  10. class Project:
  11. """
  12. database class for projects
  13. """
  14. def __init__(self, database, row):
  15. self.database = database
  16. self.identifier = row[0]
  17. self.name = row[1]
  18. self.description = row[2]
  19. self.created = row[3]
  20. self.model_id = row[4]
  21. self.label_provider_id = row[5]
  22. self.root_folder = row[6]
  23. self.external_data = bool(row[7])
  24. self.data_folder = row[8]
  25. def model(self) -> Model:
  26. """
  27. get the model this project is associated with
  28. :return: model
  29. """
  30. return self.database.model(self.model_id)
  31. def label_provider(self) -> Optional[LabelProvider]:
  32. """
  33. get the label provider this project is associated with
  34. :return: label provider
  35. """
  36. if self.label_provider_id is not None:
  37. return self.database.label_provider(self.label_provider_id)
  38. return None
  39. def labels(self) -> List[Label]:
  40. """
  41. get a list of labels associated with this project
  42. :return: list of labels
  43. """
  44. with closing(self.database.con.cursor()) as cursor:
  45. cursor.execute('SELECT * FROM labels WHERE project = ?', [self.identifier])
  46. return list(map(
  47. lambda row: Label(self.database, row),
  48. cursor.fetchall()
  49. ))
  50. def label(self, identifier: int) -> Optional[Label]:
  51. """
  52. get a label using its unique identifier
  53. :param identifier: unique identifier
  54. :return: label
  55. """
  56. with closing(self.database.con.cursor()) as cursor:
  57. cursor.execute('SELECT * FROM labels WHERE id = ? AND project = ?',
  58. (identifier, self.identifier))
  59. row = cursor.fetchone()
  60. if row is not None:
  61. return Label(self.database, row)
  62. return 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. created = int(time())
  74. with closing(self.database.con.cursor()) as cursor:
  75. cursor.execute('''
  76. INSERT INTO labels (project, parent, created, reference, name)
  77. VALUES (?, ?, ?, ?, ?)
  78. ON CONFLICT (project, reference) DO
  79. UPDATE SET parent = ?, name = ?
  80. ''', (self.identifier, parent_id, created, reference, name, parent_id, name))
  81. # lastrowid is 0 if on conflict clause applies.
  82. # If this is the case we do an extra query to receive the row id.
  83. if cursor.lastrowid > 0:
  84. row_id = cursor.lastrowid
  85. insert = True
  86. else:
  87. cursor.execute('SELECT id FROM labels WHERE project = ? AND reference = ?',
  88. (self.identifier, reference))
  89. row_id = cursor.fetchone()[0]
  90. insert = False
  91. return self.label(row_id), insert
  92. def collections(self) -> List[Collection]:
  93. """
  94. get a list of collections associated with this project
  95. :return: list of collections
  96. """
  97. with closing(self.database.con.cursor()) as cursor:
  98. cursor.execute('SELECT * FROM collections WHERE project = ? ORDER BY position ASC',
  99. [self.identifier])
  100. return list(map(
  101. lambda row: Collection(self.database, row),
  102. cursor.fetchall()
  103. ))
  104. def collection(self, identifier: int) -> Optional[Collection]:
  105. """
  106. get a collection using its unique identifier
  107. :param identifier: unique identifier
  108. :return: collection
  109. """
  110. with closing(self.database.con.cursor()) as cursor:
  111. cursor.execute('SELECT * FROM collections WHERE id = ? AND project = ?',
  112. (identifier, self.identifier))
  113. row = cursor.fetchone()
  114. if row is not None:
  115. return Collection(self.database, row)
  116. return None
  117. def create_collection(self,
  118. reference: str,
  119. name: str,
  120. description: str,
  121. position: int,
  122. autoselect: bool):
  123. autoselect = 1 if autoselect else 0
  124. with closing(self.database.con.cursor()) as cursor:
  125. cursor.execute('''
  126. INSERT INTO collections
  127. (project, reference, name, description, position, autoselect)
  128. VALUES (?, ?, ?, ?, ?, ?)
  129. ON CONFLICT (project, reference) DO
  130. UPDATE SET name = ?, description = ?, position = ?, autoselect = ?
  131. ''', (self.identifier, reference, name, description, position, autoselect,
  132. name, description, position, autoselect))
  133. # lastrowid is 0 if on conflict clause applies.
  134. # If this is the case we do an extra query to receive the row id.
  135. if cursor.lastrowid > 0:
  136. row_id = cursor.lastrowid
  137. insert = True
  138. else:
  139. cursor.execute('SELECT id FROM collections WHERE project = ? AND reference = ?',
  140. (self.identifier, reference))
  141. row_id = cursor.fetchone()[0]
  142. insert = False
  143. return self.collection(row_id), insert
  144. def remove(self) -> None:
  145. """
  146. remove this project from the database
  147. :return:
  148. """
  149. with closing(self.database.con.cursor()) as cursor:
  150. cursor.execute('DELETE FROM projects WHERE id = ?', [self.identifier])
  151. def set_name(self, name: str) -> None:
  152. """
  153. set this projects name
  154. :param name: new name
  155. :return:
  156. """
  157. with closing(self.database.con.cursor()) as cursor:
  158. cursor.execute('UPDATE projects SET name = ? WHERE id = ?', (name, self.identifier))
  159. self.name = name
  160. def set_description(self, description: str) -> None:
  161. """
  162. set this projects description
  163. :param description: new description
  164. :return:
  165. """
  166. with closing(self.database.con.cursor()) as cursor:
  167. cursor.execute('UPDATE projects SET description = ? WHERE id = ?',
  168. (description, self.identifier))
  169. self.description = description
  170. def count_files(self) -> int:
  171. """
  172. count files associated with this project
  173. :return: count
  174. """
  175. with closing(self.database.con.cursor()) as cursor:
  176. cursor.execute('SELECT COUNT(*) FROM files WHERE project = ?', [self.identifier])
  177. return cursor.fetchone()[0]
  178. def files(self, offset=0, limit=-1) -> List[File]:
  179. """
  180. get a list of files associated with this project
  181. :param offset: file offset
  182. :param limit: file limit
  183. :return: list of files
  184. """
  185. with closing(self.database.con.cursor()) as cursor:
  186. cursor.execute('SELECT * FROM files WHERE project = ? ORDER BY id ASC LIMIT ? OFFSET ?',
  187. (self.identifier, limit, offset))
  188. return list(map(
  189. lambda row: File(self.database, row),
  190. cursor.fetchall()
  191. ))
  192. def files_without_results(self) -> List[File]:
  193. """
  194. get a list of files without associated results
  195. :return: list of files
  196. """
  197. with closing(self.database.con.cursor()) as cursor:
  198. cursor.execute('''
  199. SELECT files.*
  200. FROM files
  201. LEFT JOIN results ON files.id = results.file
  202. WHERE files.project = ? AND results.id IS NULL
  203. ORDER BY id ASC
  204. ''', [self.identifier])
  205. return list(map(
  206. lambda row: File(self.database, row),
  207. cursor.fetchall()
  208. ))
  209. def count_files_without_collection(self) -> int:
  210. """
  211. count files associated with this project but with no collection
  212. :return: count
  213. """
  214. with closing(self.database.con.cursor()) as cursor:
  215. cursor.execute('SELECT COUNT(*) FROM files WHERE project = ? AND collection IS NULL',
  216. [self.identifier])
  217. return cursor.fetchone()[0]
  218. def files_without_collection(self, offset=0, limit=-1) -> List[File]:
  219. """
  220. get a list of files without not associated with any collection
  221. :return: list of files
  222. """
  223. with closing(self.database.con.cursor()) as cursor:
  224. cursor.execute('''
  225. SELECT * FROM files
  226. WHERE files.project = ? AND files.collection IS NULL
  227. ORDER BY id ASC
  228. LIMIT ? OFFSET ?
  229. ''', (self.identifier, limit, offset))
  230. return list(map(
  231. lambda row: File(self.database, row),
  232. cursor.fetchall()
  233. ))
  234. def file(self, identifier) -> Optional[File]:
  235. """
  236. get a file using its unique identifier
  237. :param identifier: unique identifier
  238. :return: file
  239. """
  240. with closing(self.database.con.cursor()) as cursor:
  241. cursor.execute('SELECT * FROM files WHERE id = ? AND project = ?',
  242. (identifier, self.identifier))
  243. row = cursor.fetchone()
  244. if row is not None:
  245. return File(self.database, row)
  246. return None
  247. def add_file(self, uuid: str, file_type: str, name: str, extension: str, size: int,
  248. filename: str, frames: int = None, fps: float = None) -> Tuple[File, bool]:
  249. """
  250. add a file to this project
  251. :param uuid: unique identifier which is used for temporary files
  252. :param file_type: file type (either image or video)
  253. :param name: file name
  254. :param extension: file extension
  255. :param size: file size
  256. :param filename: actual name in filesystem
  257. :param frames: frame count
  258. :param fps: frames per second
  259. :return: file
  260. """
  261. created = int(time())
  262. path = join(self.data_folder, filename + extension)
  263. with closing(self.database.con.cursor()) as cursor:
  264. cursor.execute('''
  265. INSERT INTO files (
  266. uuid, project, type, name, extension, size, created, path, frames, fps
  267. )
  268. VALUES (
  269. ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
  270. )
  271. ON CONFLICT (project, path) DO
  272. UPDATE SET type = ?, name = ?, extension = ?, size = ?, frames = ?, fps = ?
  273. ''', (uuid, self.identifier, file_type, name, extension, size, created, path, frames,
  274. fps, file_type, name, extension, size, frames, fps))
  275. # lastrowid is 0 if on conflict clause applies.
  276. # If this is the case we do an extra query to receive the row id.
  277. if cursor.lastrowid > 0:
  278. row_id = cursor.lastrowid
  279. insert = True
  280. else:
  281. cursor.execute('SELECT id FROM files WHERE project = ? AND path = ?',
  282. (self.identifier, path))
  283. row_id = cursor.fetchone()[0]
  284. insert = False
  285. return self.file(row_id), insert