Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
minversion = 3.0
log_cli=true
python_files = test_*.py
addopts = -n auto --dist=loadscope
;addopts = -n auto --dist=loadscope
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ mixpanel==4.8.3
pydantic>=1.8.2
pydantic[email]
setuptools~=57.4.0
superannotate_schemas
9 changes: 2 additions & 7 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
Sphinx==3.1.2
tox==3.24.2
pytest==6.2.4
pytest-xdist==2.3.0
pytest-parallel==0.1.0
pytest-rerunfailures==10.2
sphinx_rtd_theme==1.0.0
-r requirements.txt
superannotate_schemas>=1.0.38.b1

5 changes: 5 additions & 0 deletions requirements_extra.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
Sphinx==3.1.2
tox==3.24.2
pytest==6.2.4
pytest-xdist==2.3.0
pytest-parallel==0.1.0
pytest-rerunfailures==10.2
sphinx_rtd_theme==1.0.0
2 changes: 2 additions & 0 deletions requirements_prod.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-r requirements.txt
superannotate_schemas=1.0.39
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import sys

from packaging.version import parse
from setuptools import find_packages, setup


with open('src/superannotate/version.py') as f:
version = f.read().rstrip()[15:-1]

requirements_path = f"requirements_{'dev' if parse(version).is_prerelease else 'prod'}.txt"

with open('requirements.txt') as f:
with open(requirements_path) as f:
requirements = f.read()
requirements = requirements.splitlines()

Expand Down
6 changes: 4 additions & 2 deletions src/superannotate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import requests
import superannotate.lib.core as constances
from superannotate.lib import get_default_controller
from packaging.version import parse
from superannotate.lib import get_default_controller
from superannotate.lib.app.analytics.class_analytics import class_distribution
from superannotate.lib.app.exceptions import AppException
from superannotate.lib.app.input_converters.conversion import convert_json_version
Expand Down Expand Up @@ -50,6 +50,7 @@
from superannotate.lib.app.interface.sdk_interface import download_image
from superannotate.lib.app.interface.sdk_interface import download_image_annotations
from superannotate.lib.app.interface.sdk_interface import download_model
from superannotate.lib.app.interface.sdk_interface import get_annotations
from superannotate.lib.app.interface.sdk_interface import get_exports
from superannotate.lib.app.interface.sdk_interface import get_folder_metadata
from superannotate.lib.app.interface.sdk_interface import get_image_annotations
Expand Down Expand Up @@ -107,7 +108,6 @@
)
from superannotate.lib.app.interface.sdk_interface import validate_annotations
from superannotate.logger import get_default_logger
from superannotate.lib.infrastructure.controller import Controller
from superannotate.version import __version__


Expand All @@ -128,6 +128,8 @@
"class_distribution",
"aggregate_annotations_as_df",
"get_exports",
# annotations
"get_annotations",
# converters
"convert_json_version",
"import_annotation",
Expand Down
1 change: 0 additions & 1 deletion src/superannotate/lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@
def get_default_controller():
from lib.infrastructure.controller import Controller
return Controller.get_default()

2 changes: 1 addition & 1 deletion src/superannotate/lib/app/analytics/aggregators.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import copy
import json
from dataclasses import dataclass
from pathlib import Path
from typing import List
from typing import Optional
from typing import Union

import lib.core as constances
import pandas as pd
from dataclasses import dataclass
from lib.app.exceptions import AppException
from lib.core import ATTACHED_VIDEO_ANNOTATION_POSTFIX
from lib.core import PIXEL_ANNOTATION_POSTFIX
Expand Down
2 changes: 1 addition & 1 deletion src/superannotate/lib/app/interface/cli_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
from lib.app.interface.sdk_interface import upload_preannotations_from_folder_to_project
from lib.app.interface.sdk_interface import upload_videos_from_folder_to_project
from lib.core.entities import ConfigEntity
from lib.infrastructure.repositories import ConfigRepository
from lib.infrastructure.controller import Controller
from lib.infrastructure.repositories import ConfigRepository


controller = Controller.get_default()
Expand Down
47 changes: 47 additions & 0 deletions src/superannotate/lib/app/interface/sdk_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -2844,3 +2844,50 @@ def invite_contributors_to_team(
if response.errors:
raise AppException(response.errors)
return response.data


@Trackable
@validate_arguments
def get_annotations(project: NotEmptyStr, items: Optional[List[NotEmptyStr]]):
"""Returns annotations for the given list of items.

:param project: project name
:type project: str

:param items: item names. If None all items in the project will be exported
:type items: list of strs

:return: list of annotations
:rtype: list of strs
"""
project_name, folder_name = extract_project_folder(project)
response = Controller.get_default().get_annotations(project_name, folder_name, items)
if response.errors:
raise AppException(response.errors)
return response.data


@Trackable
@validate_arguments
def get_annotations_per_frame(project: NotEmptyStr, video: NotEmptyStr, fps: int = 1):
"""Returns per frame annotations for the given video.


:param project: project name
:type project: str

:param video: video name
:type video: str

:param fps: how many frames per second needs to be extracted from the video.
Will extract 1 frame per second by default.
:type fps: str

:return: list of annotation objects
:rtype: list of dicts
"""
project_name, folder_name = extract_project_folder(project)
response = Controller.get_default().get_annotations_per_frame(project_name, folder_name, video_name=video, fps=fps)
if response.errors:
raise AppException(response.errors)
return response.data
14 changes: 11 additions & 3 deletions src/superannotate/lib/app/mixp/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,17 @@ def track(self, *args, **kwargs):

def __call__(self, *args, **kwargs):
try:
self.__class__.TEAM_DATA = get_default_controller().get_team()
result = self.function(*args, **kwargs)
self._success = True
controller = get_default_controller()
if controller:
self.__class__.TEAM_DATA = controller.get_team()
result = self.function(*args, **kwargs)
self._success = True
else:
raise Exception(
"SuperAnnotate config file not found."
f" Please provide correct config file location to sa.init(<path>) or use "
f"CLI's superannotate init to generate default location config file."
)
except Exception as e:
self._success = False
logger.debug(str(e), exc_info=True)
Expand Down
1 change: 0 additions & 1 deletion src/superannotate/lib/app/mixp/utils/parsers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import lib.core as constances
from lib import get_default_controller
from lib.app.helpers import extract_project_folder
from lib.core.enums import ProjectType
from lib.infrastructure.controller import Controller
Expand Down
Empty file.
7 changes: 7 additions & 0 deletions src/superannotate/lib/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ class ClassTypeEnum(BaseTitledEnum):
OBJECT = "object", 1
TAG = "tag", 2

@classmethod
def get_value(cls, name):
for enum in list(cls):
if enum.name.lower() == name.lower():
return enum.value
return "object"


class TrainingStatus(BaseTitledEnum):
NOT_STARTED = "NotStarted", 1
Expand Down
4 changes: 4 additions & 0 deletions src/superannotate/lib/core/serviceproviders.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,7 @@ def get_limitations(
self, team_id: int, project_id: int, folder_id: int = None
) -> ServiceResponse:
raise NotImplementedError

@abstractmethod
def get_annotations(self, project_id: int, team_id: int, folder_id: int, items: List[str]) -> List[dict]:
raise NotImplementedError
74 changes: 74 additions & 0 deletions src/superannotate/lib/core/usecases/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
from collections import namedtuple
from typing import List
from typing import Optional
from typing import Tuple

import boto3
Expand All @@ -18,13 +19,15 @@
from lib.core.entities import ImageEntity
from lib.core.entities import ProjectEntity
from lib.core.entities import TeamEntity
from lib.core.exceptions import AppException
from lib.core.reporter import Reporter
from lib.core.repositories import BaseManageableRepository
from lib.core.service_types import UploadAnnotationAuthData
from lib.core.serviceproviders import SuerannotateServiceProvider
from lib.core.usecases.base import BaseReportableUseCae
from lib.core.usecases.images import GetBulkImages
from lib.core.usecases.images import ValidateAnnotationUseCase
from lib.core.video_convertor import VideoFrameGenerator
from superannotate.logger import get_default_logger
from superannotate_schemas.validators import AnnotationValidators

Expand Down Expand Up @@ -483,3 +486,74 @@ def execute(self):
f"Couldn't validate annotations. {constances.USE_VALIDATE_MESSAGE}"
)
return self._response


class GetAnnotations(BaseReportableUseCae):
def __init__(
self,
reporter: Reporter,
project: ProjectEntity,
folder: FolderEntity,
item_names: Optional[List[str]],
backend_service_provider: SuerannotateServiceProvider
):
super().__init__(reporter)
self._project = project
self._folder = folder
self._item_names = item_names
self._client = backend_service_provider

def validate_item_names(self):
if self._item_names:
item_names = list(dict.fromkeys(self._item_names))
len_unique_items, len_items = len(item_names), len(self._item_names)
if len_unique_items < len_items:
self.reporter.log_info(
f"Dropping duplicates. Found {len_unique_items}/{len_items} unique items."
)
self._item_names = item_names

def execute(self):
annotations = self._client.get_annotations(
team_id=self._project.team_id,
project_id=self._project.uuid,
folder_id=self._folder.uuid,
items=self._item_names
)
self._response.data = annotations
return self._response


class GetVideoAnnotationsPerFrame(BaseReportableUseCae):
def __init__(
self,
reporter: Reporter,
project: ProjectEntity,
folder: FolderEntity,
video_name: str,
fps: int,
backend_service_provider: SuerannotateServiceProvider
):
super().__init__(reporter)
self._project = project
self._folder = folder
self._video_name = video_name
self._fps = fps
self._client = backend_service_provider

def execute(self):
response = GetAnnotations(
reporter=self.reporter,
project=self._project,
folder=self._folder,
item_names=[self._video_name],
backend_service_provider=self._client
).execute()
if response.errors:
self._response.errors = response.errors
return self._response
if not response.data:
self._response.errors = AppException(f"Video {self._video_name} not found.")

self._response.data = VideoFrameGenerator(response.data[1], fps=self._fps)
return self._response
Loading