123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- from contextlib import closing
- from os.path import join
- from time import time
- from typing import List, Optional, Tuple, Iterator, Union
- from pycs.database.Collection import Collection
- from pycs.database.File import File
- from pycs.database.Label import Label
- from pycs.database.LabelProvider import LabelProvider
- from pycs.database.Model import Model
- class Project:
- """
- database class for projects
- """
- def __init__(self, database, row):
- self.database = database
- self.identifier = row[0]
- self.name = row[1]
- self.description = row[2]
- self.created = row[3]
- self.model_id = row[4]
- self.label_provider_id = row[5]
- self.root_folder = row[6]
- self.external_data = bool(row[7])
- self.data_folder = row[8]
- def model(self) -> Model:
- """
- get the model this project is associated with
- :return: model
- """
- return self.database.model(self.model_id)
- def label_provider(self) -> Optional[LabelProvider]:
- """
- get the label provider this project is associated with
- :return: label provider
- """
- if self.label_provider_id is not None:
- return self.database.label_provider(self.label_provider_id)
- return None
- def labels(self) -> List[Label]:
- """
- get a list of labels associated with this project
- :return: list of labels
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT * FROM labels WHERE project = ?', [self.identifier])
- return list(map(
- lambda row: Label(self.database, row),
- cursor.fetchall()
- ))
- def label(self, identifier: int) -> Optional[Label]:
- """
- get a label using its unique identifier
- :param identifier: unique identifier
- :return: label
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT * FROM labels WHERE id = ? AND project = ?',
- (identifier, self.identifier))
- row = cursor.fetchone()
- if row is not None:
- return Label(self.database, row)
- return None
- def label_by_reference(self, reference: str) -> Optional[Label]:
- """
- get a label using its reference string
- :param reference: reference string
- :return: label
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT * FROM labels WHERE reference = ? AND project = ?',
- (reference, self.identifier))
- row = cursor.fetchone()
- if row is not None:
- return Label(self.database, row)
- return None
- def create_label(self, name: str, reference: str = None,
- parent: Union[Label, int, str] = None) -> Tuple[Optional[Label], bool]:
- """
- create a label for this project. If there is already a label with the same reference
- in the database its name is updated.
- :param name: label name
- :param reference: label reference
- :param parent: either parent identifier, parent reference string or `Label` object
- :return: created or edited label, insert
- """
- created = int(time())
- if isinstance(parent, str):
- parent = self.label_by_reference(parent)
- if isinstance(parent, Label):
- parent = parent.identifier
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('''
- INSERT INTO labels (project, parent, created, reference, name)
- VALUES (?, ?, ?, ?, ?)
- ON CONFLICT (project, reference) DO
- UPDATE SET parent = ?, name = ?
- ''', (self.identifier, parent, created, reference, name, parent, name))
- # lastrowid is 0 if on conflict clause applies.
- # If this is the case we do an extra query to receive the row id.
- if cursor.lastrowid > 0:
- row_id = cursor.lastrowid
- insert = True
- else:
- cursor.execute('SELECT id FROM labels WHERE project = ? AND reference = ?',
- (self.identifier, reference))
- row_id = cursor.fetchone()[0]
- insert = False
- return self.label(row_id), insert
- def collections(self) -> List[Collection]:
- """
- get a list of collections associated with this project
- :return: list of collections
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT * FROM collections WHERE project = ? ORDER BY position ASC',
- [self.identifier])
- return list(map(
- lambda row: Collection(self.database, row),
- cursor.fetchall()
- ))
- def collection(self, identifier: int) -> Optional[Collection]:
- """
- get a collection using its unique identifier
- :param identifier: unique identifier
- :return: collection
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT * FROM collections WHERE id = ? AND project = ?',
- (identifier, self.identifier))
- row = cursor.fetchone()
- if row is not None:
- return Collection(self.database, row)
- return None
- def collection_by_reference(self, reference: str):
- """
- get a collection using its reference string
- :param reference: reference string
- :return: collection
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT * FROM collections WHERE reference = ? AND project = ?',
- (reference, self.identifier))
- row = cursor.fetchone()
- if row is not None:
- return Collection(self.database, row)
- return None
- def create_collection(self,
- reference: str,
- name: str,
- description: str,
- position: int,
- autoselect: bool):
- autoselect = 1 if autoselect else 0
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('''
- INSERT INTO collections
- (project, reference, name, description, position, autoselect)
- VALUES (?, ?, ?, ?, ?, ?)
- ON CONFLICT (project, reference) DO
- UPDATE SET name = ?, description = ?, position = ?, autoselect = ?
- ''', (self.identifier, reference, name, description, position, autoselect,
- name, description, position, autoselect))
- # lastrowid is 0 if on conflict clause applies.
- # If this is the case we do an extra query to receive the row id.
- if cursor.lastrowid > 0:
- row_id = cursor.lastrowid
- insert = True
- else:
- cursor.execute('SELECT id FROM collections WHERE project = ? AND reference = ?',
- (self.identifier, reference))
- row_id = cursor.fetchone()[0]
- insert = False
- return self.collection(row_id), insert
- def remove(self) -> None:
- """
- remove this project from the database
- :return:
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('DELETE FROM projects WHERE id = ?', [self.identifier])
- def set_name(self, name: str) -> None:
- """
- set this projects name
- :param name: new name
- :return:
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('UPDATE projects SET name = ? WHERE id = ?', (name, self.identifier))
- self.name = name
- def set_description(self, description: str) -> None:
- """
- set this projects description
- :param description: new description
- :return:
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('UPDATE projects SET description = ? WHERE id = ?',
- (description, self.identifier))
- self.description = description
- def count_files(self) -> int:
- """
- count files associated with this project
- :return: count
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT COUNT(*) FROM files WHERE project = ?', [self.identifier])
- return cursor.fetchone()[0]
- def files(self, offset: int = 0, limit: int = -1) -> Iterator[File]:
- """
- get an iterator of files associated with this project
- :param offset: file offset
- :param limit: file limit
- :return: iterator of files
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT * FROM files WHERE project = ? ORDER BY id ASC LIMIT ? OFFSET ?',
- (self.identifier, limit, offset))
- return map(
- lambda row: File(self.database, row),
- cursor.fetchall()
- )
- def count_files_without_results(self) -> int:
- """
- count files without associated results
- :return: count
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('''
- SELECT COUNT(*)
- FROM files
- LEFT JOIN results ON files.id = results.file
- WHERE files.project = ? AND results.id IS NULL
- ''', [self.identifier])
- return cursor.fetchone()[0]
- def files_without_results(self) -> Iterator[File]:
- """
- get an iterator of files without associated results
- :return: list of files
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('''
- SELECT files.*
- FROM files
- LEFT JOIN results ON files.id = results.file
- WHERE files.project = ? AND results.id IS NULL
- ORDER BY id ASC
- ''', [self.identifier])
- for row in cursor:
- yield File(self.database, row)
- def count_files_without_collection(self) -> int:
- """
- count files associated with this project but with no collection
- :return: count
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT COUNT(*) FROM files WHERE project = ? AND collection IS NULL',
- [self.identifier])
- return cursor.fetchone()[0]
- def files_without_collection(self, offset=0, limit=-1) -> Iterator[File]:
- """
- get an iterator of files without not associated with any collection
- :return: list of files
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('''
- SELECT * FROM files
- WHERE files.project = ? AND files.collection IS NULL
- ORDER BY id ASC
- LIMIT ? OFFSET ?
- ''', (self.identifier, limit, offset))
- for row in cursor:
- yield File(self.database, row)
- def file(self, identifier) -> Optional[File]:
- """
- get a file using its unique identifier
- :param identifier: unique identifier
- :return: file
- """
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('SELECT * FROM files WHERE id = ? AND project = ?',
- (identifier, self.identifier))
- row = cursor.fetchone()
- if row is not None:
- return File(self.database, row)
- return None
- def add_file(self, uuid: str, file_type: str, name: str, extension: str, size: int,
- filename: str, frames: int = None, fps: float = None) -> Tuple[File, bool]:
- """
- add a file to this project
- :param uuid: unique identifier which is used for temporary files
- :param file_type: file type (either image or video)
- :param name: file name
- :param extension: file extension
- :param size: file size
- :param filename: actual name in filesystem
- :param frames: frame count
- :param fps: frames per second
- :return: file
- """
- created = int(time())
- path = join(self.data_folder, filename + extension)
- with closing(self.database.con.cursor()) as cursor:
- cursor.execute('''
- INSERT INTO files (
- uuid, project, type, name, extension, size, created, path, frames, fps
- )
- VALUES (
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
- )
- ON CONFLICT (project, path) DO
- UPDATE SET type = ?, name = ?, extension = ?, size = ?, frames = ?, fps = ?
- ''', (uuid, self.identifier, file_type, name, extension, size, created, path, frames,
- fps, file_type, name, extension, size, frames, fps))
- # lastrowid is 0 if on conflict clause applies.
- # If this is the case we do an extra query to receive the row id.
- if cursor.lastrowid > 0:
- row_id = cursor.lastrowid
- insert = True
- else:
- cursor.execute('SELECT id FROM files WHERE project = ? AND path = ?',
- (self.identifier, path))
- row_id = cursor.fetchone()[0]
- insert = False
- return self.file(row_id), insert
|