1
1

Project.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. from contextlib import closing
  2. from os.path import join
  3. from time import time
  4. from typing import List, Optional, Tuple, Iterator
  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 collection_by_reference(self, reference: str):
  118. """
  119. get a collection using its reference string
  120. :param reference: reference string
  121. :return: collection
  122. """
  123. with closing(self.database.con.cursor()) as cursor:
  124. cursor.execute('SELECT * FROM collections WHERE reference = ? AND project = ?',
  125. (reference, self.identifier))
  126. row = cursor.fetchone()
  127. if row is not None:
  128. return Collection(self.database, row)
  129. return None
  130. def create_collection(self,
  131. reference: str,
  132. name: str,
  133. description: str,
  134. position: int,
  135. autoselect: bool):
  136. autoselect = 1 if autoselect else 0
  137. with closing(self.database.con.cursor()) as cursor:
  138. cursor.execute('''
  139. INSERT INTO collections
  140. (project, reference, name, description, position, autoselect)
  141. VALUES (?, ?, ?, ?, ?, ?)
  142. ON CONFLICT (project, reference) DO
  143. UPDATE SET name = ?, description = ?, position = ?, autoselect = ?
  144. ''', (self.identifier, reference, name, description, position, autoselect,
  145. name, description, position, autoselect))
  146. # lastrowid is 0 if on conflict clause applies.
  147. # If this is the case we do an extra query to receive the row id.
  148. if cursor.lastrowid > 0:
  149. row_id = cursor.lastrowid
  150. insert = True
  151. else:
  152. cursor.execute('SELECT id FROM collections WHERE project = ? AND reference = ?',
  153. (self.identifier, reference))
  154. row_id = cursor.fetchone()[0]
  155. insert = False
  156. return self.collection(row_id), insert
  157. def remove(self) -> None:
  158. """
  159. remove this project from the database
  160. :return:
  161. """
  162. with closing(self.database.con.cursor()) as cursor:
  163. cursor.execute('DELETE FROM projects WHERE id = ?', [self.identifier])
  164. def set_name(self, name: str) -> None:
  165. """
  166. set this projects name
  167. :param name: new name
  168. :return:
  169. """
  170. with closing(self.database.con.cursor()) as cursor:
  171. cursor.execute('UPDATE projects SET name = ? WHERE id = ?', (name, self.identifier))
  172. self.name = name
  173. def set_description(self, description: str) -> None:
  174. """
  175. set this projects description
  176. :param description: new description
  177. :return:
  178. """
  179. with closing(self.database.con.cursor()) as cursor:
  180. cursor.execute('UPDATE projects SET description = ? WHERE id = ?',
  181. (description, self.identifier))
  182. self.description = description
  183. def count_files(self) -> int:
  184. """
  185. count files associated with this project
  186. :return: count
  187. """
  188. with closing(self.database.con.cursor()) as cursor:
  189. cursor.execute('SELECT COUNT(*) FROM files WHERE project = ?', [self.identifier])
  190. return cursor.fetchone()[0]
  191. def files(self, offset: int = 0, limit: int = -1) -> List[File]:
  192. """
  193. get a list of files associated with this project
  194. :param offset: file offset
  195. :param limit: file limit
  196. :return: list of files
  197. """
  198. return list(self.files_iter(offset, limit))
  199. def files_iter(self, offset: int = 0, limit: int = -1) -> Iterator[File]:
  200. """
  201. get an iterator of files associated with this project
  202. :param offset: file offset
  203. :param limit: file limit
  204. :return: iterator of files
  205. """
  206. with closing(self.database.con.cursor()) as cursor:
  207. cursor.execute('SELECT * FROM files WHERE project = ? ORDER BY id ASC LIMIT ? OFFSET ?',
  208. (self.identifier, limit, offset))
  209. return map(
  210. lambda row: File(self.database, row),
  211. cursor.fetchall()
  212. )
  213. def count_files_without_results(self) -> int:
  214. """
  215. count files without associated results
  216. :return: count
  217. """
  218. with closing(self.database.con.cursor()) as cursor:
  219. cursor.execute('''
  220. SELECT COUNT(*)
  221. FROM files
  222. LEFT JOIN results ON files.id = results.file
  223. WHERE files.project = ? AND results.id IS NULL
  224. ''', [self.identifier])
  225. return cursor.fetchone()[0]
  226. def files_without_results(self) -> List[File]:
  227. """
  228. get a list of files without associated results
  229. :return: list of files
  230. """
  231. with closing(self.database.con.cursor()) as cursor:
  232. cursor.execute('''
  233. SELECT files.*
  234. FROM files
  235. LEFT JOIN results ON files.id = results.file
  236. WHERE files.project = ? AND results.id IS NULL
  237. ORDER BY id ASC
  238. ''', [self.identifier])
  239. return list(map(
  240. lambda row: File(self.database, row),
  241. cursor.fetchall()
  242. ))
  243. def count_files_without_collection(self) -> int:
  244. """
  245. count files associated with this project but with no collection
  246. :return: count
  247. """
  248. with closing(self.database.con.cursor()) as cursor:
  249. cursor.execute('SELECT COUNT(*) FROM files WHERE project = ? AND collection IS NULL',
  250. [self.identifier])
  251. return cursor.fetchone()[0]
  252. def files_without_collection(self, offset=0, limit=-1) -> List[File]:
  253. """
  254. get a list of files without not associated with any collection
  255. :return: list of files
  256. """
  257. with closing(self.database.con.cursor()) as cursor:
  258. cursor.execute('''
  259. SELECT * FROM files
  260. WHERE files.project = ? AND files.collection IS NULL
  261. ORDER BY id ASC
  262. LIMIT ? OFFSET ?
  263. ''', (self.identifier, limit, offset))
  264. return list(map(
  265. lambda row: File(self.database, row),
  266. cursor.fetchall()
  267. ))
  268. def file(self, identifier) -> Optional[File]:
  269. """
  270. get a file using its unique identifier
  271. :param identifier: unique identifier
  272. :return: file
  273. """
  274. with closing(self.database.con.cursor()) as cursor:
  275. cursor.execute('SELECT * FROM files WHERE id = ? AND project = ?',
  276. (identifier, self.identifier))
  277. row = cursor.fetchone()
  278. if row is not None:
  279. return File(self.database, row)
  280. return None
  281. def add_file(self, uuid: str, file_type: str, name: str, extension: str, size: int,
  282. filename: str, frames: int = None, fps: float = None) -> Tuple[File, bool]:
  283. """
  284. add a file to this project
  285. :param uuid: unique identifier which is used for temporary files
  286. :param file_type: file type (either image or video)
  287. :param name: file name
  288. :param extension: file extension
  289. :param size: file size
  290. :param filename: actual name in filesystem
  291. :param frames: frame count
  292. :param fps: frames per second
  293. :return: file
  294. """
  295. created = int(time())
  296. path = join(self.data_folder, filename + extension)
  297. with closing(self.database.con.cursor()) as cursor:
  298. cursor.execute('''
  299. INSERT INTO files (
  300. uuid, project, type, name, extension, size, created, path, frames, fps
  301. )
  302. VALUES (
  303. ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
  304. )
  305. ON CONFLICT (project, path) DO
  306. UPDATE SET type = ?, name = ?, extension = ?, size = ?, frames = ?, fps = ?
  307. ''', (uuid, self.identifier, file_type, name, extension, size, created, path, frames,
  308. fps, file_type, name, extension, size, frames, fps))
  309. # lastrowid is 0 if on conflict clause applies.
  310. # If this is the case we do an extra query to receive the row id.
  311. if cursor.lastrowid > 0:
  312. row_id = cursor.lastrowid
  313. insert = True
  314. else:
  315. cursor.execute('SELECT id FROM files WHERE project = ? AND path = ?',
  316. (self.identifier, path))
  317. row_id = cursor.fetchone()[0]
  318. insert = False
  319. return self.file(row_id), insert