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
15 changes: 15 additions & 0 deletions docs/source/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ To initialize CLI (and SDK) with team token:

----------


.. _ref_create_server:

Creating a server
~~~~~~~~~~~~~~~~~~

This will create a directory by the given name in your current or provided directory:

.. code-block:: bash

superannotatecli create-server --name <directory_name> --path <directory_path>

----------


.. _ref_create_project:

Creating a project
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

tutorial.sdk.rst
superannotate.sdk.rst
server.rst
cli.rst
LICENSE.rst

Expand Down
122 changes: 122 additions & 0 deletions docs/source/server.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
.. _ref_server:

SAServer Reference
======================================

.. contents::

The SAServer provides interface to create web API and run in development or production servers.

This will create a directory by the given name in your current or provided directory:

.. code-block:: bash

superannotatecli create-server --name <directory_name> --path <directory_path>

----------

Usage
----------------

SuperAnnotate Python SDK allows access to the platform without web browser:

.. code-block:: python

import random
from superannotate import SAClient
from superannotate import SAServer


app = SAServer()
sa_client = SAClient()
QA_EMAILS = [
'qa1@superannotate.com', 'qa2@superannotate.com',
'qa3@superannotate.com', 'qa4@superannotate.com'
]


@app.route("item_completed", methods=["POST"])
def index(request):
"""
Listening webhooks on items completed events form Superannotate automation
and is randomly assigned to qa
"""
project_id, folder_id = request.data['project_id'], request.data['folder_id']
project = sa_client.get_project_by_id(project_id)
folder = sa_client.get_folder_by_id(project_id=project_id, folder_id=folder_id)
sa_client.assign_items(
f"{project['name']}/{folder['name']}",
items=[request.data['name']],
user=random.choice(QA_EMAILS)
)


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5002)

Interface
----------------

.. automethod:: superannotate.SAServer.route
.. automethod:: superannotate.SAServer.add_url_rule
.. automethod:: superannotate.SAServer.run


uWSGI
----------

`uWSGI`_ is a fast, compiled server suite with extensive configuration
and capabilities beyond a basic server.

* It can be very performant due to being a compiled program.
* It is complex to configure beyond the basic application, and has so
many options that it can be difficult for beginners to understand.
* It does not support Windows (but does run on WSL).
* It requires a compiler to install in some cases.

This page outlines the basics of running uWSGI. Be sure to read its
documentation to understand what features are available.

.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/

uWSGI has multiple ways to install it. The most straightforward is to
install the ``pyuwsgi`` package, which provides precompiled wheels for
common platforms. However, it does not provide SSL support, which can be
provided with a reverse proxy instead.

Install ``pyuwsgi``.

.. code-block:: text

$ pip install pyuwsgi

If you have a compiler available, you can install the ``uwsgi`` package
instead. Or install the ``pyuwsgi`` package from sdist instead of wheel.
Either method will include SSL support.

.. code-block:: text

$ pip install uwsgi

# or
$ pip install --no-binary pyuwsgi pyuwsgi


Running
-------

The most basic way to run uWSGI is to tell it to start an HTTP server
and import your application.

.. code-block:: text

$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app

*** Starting uWSGI 2.0.20 (64bit) on [x] ***
*** Operational MODE: preforking ***
spawned uWSGI master process (pid: x)
spawned uWSGI worker 1 (pid: x, cores: 1)
spawned uWSGI worker 2 (pid: x, cores: 1)
spawned uWSGI worker 3 (pid: x, cores: 1)
spawned uWSGI worker 4 (pid: x, cores: 1)
spawned uWSGI http 1 (pid: x)
2 changes: 2 additions & 0 deletions docs/source/superannotate.sdk.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ ______
.. automethod:: superannotate.SAClient.unassign_items
.. automethod:: superannotate.SAClient.get_item_metadata
.. automethod:: superannotate.SAClient.set_annotation_statuses
.. automethod:: superannotate.SAClient.set_approval_statuses
.. automethod:: superannotate.SAClient.set_approval

----------

Expand Down
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
minversion = 3.7
log_cli=true
python_files = test_*.py
;pytest_plugins = ['pytest_profiling']
pytest_plugins = ['pytest_profiling']
;addopts = -n auto --dist=loadscope
9 changes: 5 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ pydicom>=2.0.0
boto3>=1.14.53
requests==2.26.0
requests-toolbelt>=0.9.1
aiohttp>=3.8.1
tqdm==4.64.0
pillow>=7.2.0
matplotlib>=3.3.1
xmltodict==0.12.0
opencv-python>=4.4.0.42
wheel==0.35.1
packaging>=20.4
plotly==4.1.0
plotly>=4.1.0
ffmpeg-python>=0.2.0
fire==0.4.0
mixpanel==4.8.3
pydantic>=1.10.2
setuptools~=57.4.0
aiohttp==3.8.1
setuptools>=57.4.0
email-validator>=1.0.3
nest-asyncio==1.5.4
jsonschema==3.2.0
pandas>=1.1.4
aiofiles==0.8.0

Werkzeug==2.2.2
Jinja2==3.0.3
18 changes: 14 additions & 4 deletions src/superannotate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import os
import sys
import typing


__version__ = "4.4.8"

__version__ = "4.4.9dev5"

sys.path.append(os.path.split(os.path.realpath(__file__))[0])

Expand All @@ -19,6 +18,8 @@
from superannotate.lib.app.input_converters import export_annotation # noqa
from superannotate.lib.app.input_converters import import_annotation # noqa
from superannotate.lib.app.interface.sdk_interface import SAClient # noqa
from superannotate.lib.app.server import SAServer # noqa
from superannotate.lib.app.server.utils import setup_app # noqa
from superannotate.lib.core import PACKAGE_VERSION_INFO_MESSAGE # noqa
from superannotate.lib.core import PACKAGE_VERSION_MAJOR_UPGRADE # noqa
from superannotate.lib.core import PACKAGE_VERSION_UPGRADE # noqa
Expand All @@ -27,9 +28,18 @@

SESSIONS = {}


def create_app(apps: typing.List[str] = None) -> SAServer:
setup_app(apps)
server = SAServer()
return server


__all__ = [
"__version__",
"SAClient",
"SAServer",
"create_app",
# Utils
"enums",
"AppException",
Expand All @@ -52,7 +62,7 @@ def log_version_info():
local_version = parse(__version__)
if local_version.is_prerelease:
logger.info(PACKAGE_VERSION_INFO_MESSAGE.format(__version__))
req = requests.get("https://pypi.python.org/pypi/superannotate/json")
req = requests.get("https://pypi.org/pypi/superannotate/json")
if req.ok:
releases = req.json().get("releases", [])
pip_version = parse("0")
Expand Down
63 changes: 31 additions & 32 deletions src/superannotate/lib/app/input_converters/sa_conversion.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import itertools
import json
import shutil

Expand All @@ -20,65 +21,63 @@ def copy_file(src_path, dst_path):

def from_pixel_to_vector(json_paths, output_dir):
img_names = []

for json_path in json_paths:
file_name = str(json_path.name).replace("___pixel.json", "___objects.json")

mask_name = str(json_path).replace("___pixel.json", "___save.png")
img = cv2.imread(mask_name)
H, W, _ = img.shape

sa_json = json.load(open(json_path))
instances = sa_json["instances"]
idx = 0
new_instances = []
global_idx = itertools.count()
sa_instances = []

for instance in instances:
if "parts" not in instance.keys():
if "type" in instance.keys() and instance["type"] == "meta":
sa_instances.append(instance)
continue

parts = instance["parts"]
if len(parts) > 1:
group_id = next(global_idx)
else:
group_id = 0
from collections import defaultdict

polygons = []
for part in parts:
color = list(hex_to_rgb(part["color"]))
mask = np.zeros((H, W), dtype=np.uint8)
mask[np.all((img == color[::-1]), axis=2)] = 255
contours, _ = cv2.findContours(
mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
part_polygons = []
for contour in contours:
segment = contour.flatten().tolist()
if len(segment) > 6:
part_polygons.append(segment)
polygons.append(part_polygons)

for part_polygons in polygons:
if len(part_polygons) > 1:
idx += 1
group_id = idx
else:
group_id = 0

for polygon in part_polygons:
# child contour index hierarchy[0][[i][3]
contours, hierarchy = cv2.findContours(
mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE
)
parent_child_map = defaultdict(list)
for idx, _hierarchy in enumerate(hierarchy[0]):

if len(contours[idx].flatten().tolist()) <= 6:
continue
if _hierarchy[3] < 0:
parent_child_map[idx] = []
else:
parent_child_map[_hierarchy[3]].append(idx)

for outer, inners in parent_child_map.items():
outer_points = contours[outer].flatten().tolist()
exclude_points = [contours[i].flatten().tolist() for i in inners]
temp = instance.copy()
del temp["parts"]
temp["pointLabels"] = {}
temp["groupId"] = group_id
temp["type"] = "polygon"
temp["points"] = polygon
sa_instances.append(temp.copy())
temp["type"] = "bbox"
temp["points"] = {
"x1": min(polygon[::2]),
"x2": max(polygon[::2]),
"y1": min(polygon[1::2]),
"y2": max(polygon[1::2]),
}
sa_instances.append(temp.copy())
temp["points"] = outer_points
temp["exclude"] = exclude_points
new_instances.append(temp)

sa_json["instances"] = sa_instances
sa_json["instances"] = new_instances
write_to_json(output_dir / file_name, sa_json)
img_names.append(file_name.replace("___objects.json", ""))
return img_names
Expand Down
20 changes: 20 additions & 0 deletions src/superannotate/lib/app/interface/cli_interface.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import os
import shutil
import sys
import tempfile
from pathlib import Path
from typing import Any
from typing import Optional

import lib as sa_lib
import lib.core as constances
from lib import __file__ as lib_path
from lib.app.input_converters.conversion import import_annotation
Expand Down Expand Up @@ -255,3 +258,20 @@ def upload_videos(
image_quality_in_editor=None,
)
sys.exit(0)

def create_server(self, name: str, path: str = None):
"""
This will create a directory by the given name in your current or provided directory.
"""
path = Path(os.path.expanduser(path if path else ".")) / name
if path.exists():
raise Exception(f"Directory already exists {str(path.absolute())}")
path.mkdir(parents=True)
default_files_path = Path(sa_lib.__file__).parent / "app" / "server"
shutil.copy(default_files_path / "__app.py", path / "app.py")
shutil.copy(default_files_path / "__wsgi.py", path / "wsgi.py")
shutil.copy(default_files_path / "Dockerfile", path / "Dockerfile")
shutil.copy(default_files_path / "requirements.txt", path / "requirements.txt")
shutil.copy(default_files_path / "README.rst", path / "README.rst")
shutil.copy(default_files_path / "run.sh", path / "run.sh")
shutil.copytree(default_files_path / "deployment", path / "deployment")
Loading