Skip to content

Commit db50dca

Browse files
authored
Merge pull request #549 from superannotateai/friday
Friday
2 parents 97f46dc + e012dcb commit db50dca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1467
-279
lines changed

docs/source/cli.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ To initialize CLI (and SDK) with team token:
3636
3737
----------
3838

39+
40+
.. _ref_create_server:
41+
42+
Creating a server
43+
~~~~~~~~~~~~~~~~~~
44+
45+
This will create a directory by the given name in your current or provided directory:
46+
47+
.. code-block:: bash
48+
49+
superannotatecli create-server --name <directory_name> --path <directory_path>
50+
51+
----------
52+
53+
3954
.. _ref_create_project:
4055

4156
Creating a project

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
tutorial.sdk.rst
1919
superannotate.sdk.rst
20+
server.rst
2021
cli.rst
2122
LICENSE.rst
2223

docs/source/server.rst

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
.. _ref_server:
2+
3+
SAServer Reference
4+
======================================
5+
6+
.. contents::
7+
8+
The SAServer provides interface to create web API and run in development or production servers.
9+
10+
This will create a directory by the given name in your current or provided directory:
11+
12+
.. code-block:: bash
13+
14+
superannotatecli create-server --name <directory_name> --path <directory_path>
15+
16+
----------
17+
18+
Usage
19+
----------------
20+
21+
SuperAnnotate Python SDK allows access to the platform without web browser:
22+
23+
.. code-block:: python
24+
25+
import random
26+
from superannotate import SAClient
27+
from superannotate import SAServer
28+
29+
30+
app = SAServer()
31+
sa_client = SAClient()
32+
QA_EMAILS = [
33+
'qa1@superannotate.com', 'qa2@superannotate.com',
34+
'qa3@superannotate.com', 'qa4@superannotate.com'
35+
]
36+
37+
38+
@app.route("item_completed", methods=["POST"])
39+
def index(request):
40+
"""
41+
Listening webhooks on items completed events form Superannotate automation
42+
and is randomly assigned to qa
43+
"""
44+
project_id, folder_id = request.data['project_id'], request.data['folder_id']
45+
project = sa_client.get_project_by_id(project_id)
46+
folder = sa_client.get_folder_by_id(project_id=project_id, folder_id=folder_id)
47+
sa_client.assign_items(
48+
f"{project['name']}/{folder['name']}",
49+
items=[request.data['name']],
50+
user=random.choice(QA_EMAILS)
51+
)
52+
53+
54+
if __name__ == '__main__':
55+
app.run(host='0.0.0.0', port=5002)
56+
57+
Interface
58+
----------------
59+
60+
.. automethod:: superannotate.SAServer.route
61+
.. automethod:: superannotate.SAServer.add_url_rule
62+
.. automethod:: superannotate.SAServer.run
63+
64+
65+
uWSGI
66+
----------
67+
68+
`uWSGI`_ is a fast, compiled server suite with extensive configuration
69+
and capabilities beyond a basic server.
70+
71+
* It can be very performant due to being a compiled program.
72+
* It is complex to configure beyond the basic application, and has so
73+
many options that it can be difficult for beginners to understand.
74+
* It does not support Windows (but does run on WSL).
75+
* It requires a compiler to install in some cases.
76+
77+
This page outlines the basics of running uWSGI. Be sure to read its
78+
documentation to understand what features are available.
79+
80+
.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/
81+
82+
uWSGI has multiple ways to install it. The most straightforward is to
83+
install the ``pyuwsgi`` package, which provides precompiled wheels for
84+
common platforms. However, it does not provide SSL support, which can be
85+
provided with a reverse proxy instead.
86+
87+
Install ``pyuwsgi``.
88+
89+
.. code-block:: text
90+
91+
$ pip install pyuwsgi
92+
93+
If you have a compiler available, you can install the ``uwsgi`` package
94+
instead. Or install the ``pyuwsgi`` package from sdist instead of wheel.
95+
Either method will include SSL support.
96+
97+
.. code-block:: text
98+
99+
$ pip install uwsgi
100+
101+
# or
102+
$ pip install --no-binary pyuwsgi pyuwsgi
103+
104+
105+
Running
106+
-------
107+
108+
The most basic way to run uWSGI is to tell it to start an HTTP server
109+
and import your application.
110+
111+
.. code-block:: text
112+
113+
$ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app
114+
115+
*** Starting uWSGI 2.0.20 (64bit) on [x] ***
116+
*** Operational MODE: preforking ***
117+
spawned uWSGI master process (pid: x)
118+
spawned uWSGI worker 1 (pid: x, cores: 1)
119+
spawned uWSGI worker 2 (pid: x, cores: 1)
120+
spawned uWSGI worker 3 (pid: x, cores: 1)
121+
spawned uWSGI worker 4 (pid: x, cores: 1)
122+
spawned uWSGI http 1 (pid: x)

docs/source/superannotate.sdk.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ ______
8181
.. automethod:: superannotate.SAClient.unassign_items
8282
.. automethod:: superannotate.SAClient.get_item_metadata
8383
.. automethod:: superannotate.SAClient.set_annotation_statuses
84+
.. automethod:: superannotate.SAClient.set_approval_statuses
85+
.. automethod:: superannotate.SAClient.set_approval
8486

8587
----------
8688

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
minversion = 3.7
33
log_cli=true
44
python_files = test_*.py
5-
;pytest_plugins = ['pytest_profiling']
5+
pytest_plugins = ['pytest_profiling']
66
;addopts = -n auto --dist=loadscope

requirements.txt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,24 @@ pydicom>=2.0.0
22
boto3>=1.14.53
33
requests==2.26.0
44
requests-toolbelt>=0.9.1
5+
aiohttp>=3.8.1
56
tqdm==4.64.0
67
pillow>=7.2.0
78
matplotlib>=3.3.1
89
xmltodict==0.12.0
910
opencv-python>=4.4.0.42
1011
wheel==0.35.1
1112
packaging>=20.4
12-
plotly==4.1.0
13+
plotly>=4.1.0
1314
ffmpeg-python>=0.2.0
1415
fire==0.4.0
1516
mixpanel==4.8.3
1617
pydantic>=1.10.2
17-
setuptools~=57.4.0
18-
aiohttp==3.8.1
18+
setuptools>=57.4.0
1919
email-validator>=1.0.3
2020
nest-asyncio==1.5.4
2121
jsonschema==3.2.0
2222
pandas>=1.1.4
2323
aiofiles==0.8.0
24-
24+
Werkzeug==2.2.2
25+
Jinja2==3.0.3

src/superannotate/__init__.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import os
22
import sys
3+
import typing
34

4-
5-
__version__ = "4.4.8"
6-
5+
__version__ = "4.4.9dev5"
76

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

@@ -19,6 +18,8 @@
1918
from superannotate.lib.app.input_converters import export_annotation # noqa
2019
from superannotate.lib.app.input_converters import import_annotation # noqa
2120
from superannotate.lib.app.interface.sdk_interface import SAClient # noqa
21+
from superannotate.lib.app.server import SAServer # noqa
22+
from superannotate.lib.app.server.utils import setup_app # noqa
2223
from superannotate.lib.core import PACKAGE_VERSION_INFO_MESSAGE # noqa
2324
from superannotate.lib.core import PACKAGE_VERSION_MAJOR_UPGRADE # noqa
2425
from superannotate.lib.core import PACKAGE_VERSION_UPGRADE # noqa
@@ -27,9 +28,18 @@
2728

2829
SESSIONS = {}
2930

31+
32+
def create_app(apps: typing.List[str] = None) -> SAServer:
33+
setup_app(apps)
34+
server = SAServer()
35+
return server
36+
37+
3038
__all__ = [
3139
"__version__",
3240
"SAClient",
41+
"SAServer",
42+
"create_app",
3343
# Utils
3444
"enums",
3545
"AppException",
@@ -52,7 +62,7 @@ def log_version_info():
5262
local_version = parse(__version__)
5363
if local_version.is_prerelease:
5464
logger.info(PACKAGE_VERSION_INFO_MESSAGE.format(__version__))
55-
req = requests.get("https://pypi.python.org/pypi/superannotate/json")
65+
req = requests.get("https://pypi.org/pypi/superannotate/json")
5666
if req.ok:
5767
releases = req.json().get("releases", [])
5868
pip_version = parse("0")

src/superannotate/lib/app/input_converters/sa_conversion.py

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import itertools
12
import json
23
import shutil
34

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

2122
def from_pixel_to_vector(json_paths, output_dir):
2223
img_names = []
24+
2325
for json_path in json_paths:
2426
file_name = str(json_path.name).replace("___pixel.json", "___objects.json")
2527

2628
mask_name = str(json_path).replace("___pixel.json", "___save.png")
2729
img = cv2.imread(mask_name)
2830
H, W, _ = img.shape
29-
3031
sa_json = json.load(open(json_path))
3132
instances = sa_json["instances"]
32-
idx = 0
33+
new_instances = []
34+
global_idx = itertools.count()
3335
sa_instances = []
36+
3437
for instance in instances:
3538
if "parts" not in instance.keys():
3639
if "type" in instance.keys() and instance["type"] == "meta":
3740
sa_instances.append(instance)
3841
continue
39-
4042
parts = instance["parts"]
43+
if len(parts) > 1:
44+
group_id = next(global_idx)
45+
else:
46+
group_id = 0
47+
from collections import defaultdict
4148

42-
polygons = []
4349
for part in parts:
4450
color = list(hex_to_rgb(part["color"]))
4551
mask = np.zeros((H, W), dtype=np.uint8)
4652
mask[np.all((img == color[::-1]), axis=2)] = 255
47-
contours, _ = cv2.findContours(
48-
mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
49-
)
50-
part_polygons = []
51-
for contour in contours:
52-
segment = contour.flatten().tolist()
53-
if len(segment) > 6:
54-
part_polygons.append(segment)
55-
polygons.append(part_polygons)
56-
57-
for part_polygons in polygons:
58-
if len(part_polygons) > 1:
59-
idx += 1
60-
group_id = idx
61-
else:
62-
group_id = 0
6353

64-
for polygon in part_polygons:
54+
# child contour index hierarchy[0][[i][3]
55+
contours, hierarchy = cv2.findContours(
56+
mask, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE
57+
)
58+
parent_child_map = defaultdict(list)
59+
for idx, _hierarchy in enumerate(hierarchy[0]):
60+
61+
if len(contours[idx].flatten().tolist()) <= 6:
62+
continue
63+
if _hierarchy[3] < 0:
64+
parent_child_map[idx] = []
65+
else:
66+
parent_child_map[_hierarchy[3]].append(idx)
67+
68+
for outer, inners in parent_child_map.items():
69+
outer_points = contours[outer].flatten().tolist()
70+
exclude_points = [contours[i].flatten().tolist() for i in inners]
6571
temp = instance.copy()
6672
del temp["parts"]
6773
temp["pointLabels"] = {}
6874
temp["groupId"] = group_id
6975
temp["type"] = "polygon"
70-
temp["points"] = polygon
71-
sa_instances.append(temp.copy())
72-
temp["type"] = "bbox"
73-
temp["points"] = {
74-
"x1": min(polygon[::2]),
75-
"x2": max(polygon[::2]),
76-
"y1": min(polygon[1::2]),
77-
"y2": max(polygon[1::2]),
78-
}
79-
sa_instances.append(temp.copy())
76+
temp["points"] = outer_points
77+
temp["exclude"] = exclude_points
78+
new_instances.append(temp)
8079

81-
sa_json["instances"] = sa_instances
80+
sa_json["instances"] = new_instances
8281
write_to_json(output_dir / file_name, sa_json)
8382
img_names.append(file_name.replace("___objects.json", ""))
8483
return img_names

src/superannotate/lib/app/interface/cli_interface.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import os
2+
import shutil
23
import sys
34
import tempfile
5+
from pathlib import Path
46
from typing import Any
57
from typing import Optional
68

9+
import lib as sa_lib
710
import lib.core as constances
811
from lib import __file__ as lib_path
912
from lib.app.input_converters.conversion import import_annotation
@@ -255,3 +258,20 @@ def upload_videos(
255258
image_quality_in_editor=None,
256259
)
257260
sys.exit(0)
261+
262+
def create_server(self, name: str, path: str = None):
263+
"""
264+
This will create a directory by the given name in your current or provided directory.
265+
"""
266+
path = Path(os.path.expanduser(path if path else ".")) / name
267+
if path.exists():
268+
raise Exception(f"Directory already exists {str(path.absolute())}")
269+
path.mkdir(parents=True)
270+
default_files_path = Path(sa_lib.__file__).parent / "app" / "server"
271+
shutil.copy(default_files_path / "__app.py", path / "app.py")
272+
shutil.copy(default_files_path / "__wsgi.py", path / "wsgi.py")
273+
shutil.copy(default_files_path / "Dockerfile", path / "Dockerfile")
274+
shutil.copy(default_files_path / "requirements.txt", path / "requirements.txt")
275+
shutil.copy(default_files_path / "README.rst", path / "README.rst")
276+
shutil.copy(default_files_path / "run.sh", path / "run.sh")
277+
shutil.copytree(default_files_path / "deployment", path / "deployment")

0 commit comments

Comments
 (0)