Skip to content

Commit f27e26e

Browse files
committed
Updated convert_project_type
1 parent 5e046dc commit f27e26e

31 files changed

+368
-149
lines changed

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ ffmpeg-python>=0.2.0
1414
fire==0.4.0
1515
mixpanel==4.8.3
1616
pydantic>=1.10.2
17-
setuptools~=57.4.0
17+
setuptools>=57.4.0
1818
aiohttp==3.8.1
1919
email-validator>=1.0.3
2020
nest-asyncio==1.5.4

src/superannotate/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
import sys
3-
3+
import typing
44

55
__version__ = "4.4.8"
66

@@ -19,6 +19,7 @@
1919
from superannotate.lib.app.input_converters import import_annotation # noqa
2020
from superannotate.lib.app.interface.sdk_interface import SAClient # noqa
2121
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,10 +28,18 @@
2728

2829
SESSIONS = {}
2930

31+
32+
def create_app(apps: typing.List[str]) -> SAServer:
33+
setup_app(apps)
34+
server = SAServer()
35+
return server
36+
37+
3038
__all__ = [
3139
"__version__",
3240
"SAClient",
3341
"SAServer",
42+
"create_app",
3443
# Utils
3544
"enums",
3645
"AppException",

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

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import numpy as np
66
from lib.app.exceptions import AppException
77
from lib.core import DEPRICATED_DOCUMENT_VIDEO_MESSAGE
8+
from shapely.geometry import Polygon
89
from superannotate.logger import get_default_logger
910

1011
from ..common import blue_color_generator
@@ -26,59 +27,57 @@ def from_pixel_to_vector(json_paths, output_dir):
2627
mask_name = str(json_path).replace("___pixel.json", "___save.png")
2728
img = cv2.imread(mask_name)
2829
H, W, _ = img.shape
29-
3030
sa_json = json.load(open(json_path))
3131
instances = sa_json["instances"]
32+
new_instances = []
3233
idx = 0
3334
sa_instances = []
35+
3436
for instance in instances:
3537
if "parts" not in instance.keys():
3638
if "type" in instance.keys() and instance["type"] == "meta":
3739
sa_instances.append(instance)
3840
continue
39-
4041
parts = instance["parts"]
42+
if len(parts) > 1:
43+
idx += 1
44+
group_id = 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()
66-
del temp["parts"]
72+
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: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ def create_server(self, name: str, path: str = None):
264264
This will create a directory by the given name in your current or provided directory.
265265
"""
266266
path = Path(os.path.expanduser(path if path else ".")) / name
267-
268267
if path.exists():
269268
raise Exception(f"Directory already exists {str(path.absolute())}")
270269
path.mkdir(parents=True)
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1+
from lib.app.server.core import Response
12
from lib.app.server.core import SAServer
23

34

4-
__all__ = ["SAServer"]
5+
__all__ = ["SAServer", "Response"]
Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
1-
from importlib import import_module
2-
3-
from superannotate import SAServer
4-
1+
from superannotate import create_app
52

63
APPS = ["app"]
74

8-
9-
def create_app():
10-
server = SAServer()
11-
for path in APPS:
12-
import_module(path)
13-
return server
14-
15-
16-
app = create_app()
5+
app = create_app(APPS)

src/superannotate/lib/app/server/core.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
import json
2+
import pathlib
23
import typing
4+
from datetime import datetime
35

6+
from jinja2 import Environment
7+
from jinja2 import FileSystemLoader
8+
from superannotate.logger import get_server_logger
49
from werkzeug.exceptions import HTTPException
510
from werkzeug.routing import Map
611
from werkzeug.routing import Rule
712
from werkzeug.serving import run_simple
813
from werkzeug.wrappers import Request
9-
from werkzeug.wrappers import Response
14+
from werkzeug.wrappers import Response as BaseResponse
15+
16+
logger = get_server_logger()
17+
18+
19+
class Response(BaseResponse):
20+
...
1021

1122

1223
class SingletonMeta(type):
@@ -23,8 +34,14 @@ class SAServer(metaclass=SingletonMeta):
2334
def __init__(self):
2435
self._url_map: Map = Map([])
2536
self._view_function_map: typing.Dict[str, typing.Callable] = {}
37+
self.jinja_env = Environment(
38+
loader=FileSystemLoader(str(pathlib.Path(__file__).parent / "templates")),
39+
autoescape=True,
40+
)
2641

27-
def route(self, rule: str, methods: typing.List[str] = None, **options: typing.Any):
42+
def route(
43+
self, rule: str, methods: typing.List[str] = None, **options: typing.Any
44+
) -> typing.Any:
2845
"""Decorate a view function to register it with the given URL
2946
rule and options. Calls :meth:`add_url_rule`, which has more
3047
details about the implementation.
@@ -135,17 +152,47 @@ def index():
135152
def _dispatch_request(self, request):
136153
"""Dispatches the request."""
137154
adapter = self._url_map.bind_to_environ(request.environ)
155+
response = None
156+
content = None
138157
try:
139158
endpoint, values = adapter.match()
140159
view_func = self._view_function_map.get(endpoint)
141160
if not view_func:
142161
return Response(status=404)
143-
response = view_func(request, **values)
144-
if isinstance(response, dict):
145-
return Response(json.dumps(response), content_type="application/json")
146-
return Response(response)
162+
content = view_func(request, **values)
163+
if isinstance(content, Response):
164+
response = content
165+
elif isinstance(content, (list, dict)):
166+
response = Response(
167+
json.dumps(content), content_type="application/json"
168+
)
169+
else:
170+
response = Response(content)
171+
return response
147172
except HTTPException as e:
148173
return e
174+
finally:
175+
if "monitor" not in request.full_path and "log" not in request.full_path:
176+
data = {
177+
"date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
178+
"request": {
179+
"method": request.full_path,
180+
"path": request.url,
181+
"headers": dict(request.headers.items()),
182+
"data": request.data.decode("utf-8"),
183+
},
184+
"response": {
185+
"headers": dict(request.headers.items()) if response else None,
186+
# 'data': response.data.decode('utf-8') if response else None,
187+
"data": content.data.decode("utf-8")
188+
if isinstance(content, Response)
189+
else content,
190+
"status_code": response.status_code if response else None,
191+
},
192+
}
193+
# import ndjson
194+
print(11111, request.full_path)
195+
logger.info(json.dumps(data))
149196

150197
def wsgi_app(self, environ, start_response):
151198
"""WSGI application that processes requests and returns responses."""
@@ -197,3 +244,7 @@ def run(
197244
ssl_context=ssl_context,
198245
**kwargs
199246
)
247+
248+
def render_template(self, template_name, **context):
249+
t = self.jinja_env.get_template(template_name)
250+
return Response(t.render(context), mimetype="text/html")
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import json
2+
3+
from lib.app.server import SAServer
4+
from lib.core import LOG_FILE_LOCATION
5+
6+
app = SAServer()
7+
8+
LOG_FILE = "/var/log/orchestra/consumer.log"
9+
10+
11+
@app.route("/monitor", methods=["GET"])
12+
def monitor_view(request):
13+
return app.render_template("monitor.html", **{})
14+
15+
16+
@app.route("/logs", methods=["GET"])
17+
def logs(request):
18+
data = []
19+
limit = 20
20+
items = []
21+
cursor = None
22+
get_cursor = lambda x: max(x - 2048, 0)
23+
24+
with open(
25+
f"{LOG_FILE_LOCATION}/sa_server.log",
26+
) as log_file:
27+
log_file.seek(0, 2)
28+
file_size = log_file.tell()
29+
cursor = get_cursor(file_size)
30+
while True:
31+
log_file.seek(cursor, 0)
32+
lines = log_file.read().splitlines()[-limit:]
33+
# if cursor == 0 and len(lines) >= limit:
34+
# continue
35+
for line in lines:
36+
try:
37+
items.append(json.loads(line))
38+
except Exception:
39+
...
40+
if len(lines) >= limit or cursor == 0:
41+
return items
42+
cursor = get_cursor(cursor)
43+
items = []
44+
45+
46+
#
47+
# @app.route("/_log_stream", methods=["GET"])
48+
# def log_stream(request):
49+
# def generate():
50+
# for line in Pygtail(LOG_FILE, every_n=1):
51+
# yield "data:" + str(line) + "\n\n"
52+
# time.sleep(0.5)
53+
#
54+
# return Response(generate(), mimetype="text/event-stream")
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<!DOCTYPE html>
2+
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" dir="ltr" class="uk-height-1-1">
3+
<head>
4+
<meta charset="utf-8"/>
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>flasktest</title>
7+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css"/>
8+
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
9+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
10+
</head>
11+
<body>
12+
<section class="section">
13+
<div class="container">
14+
<div class="columns is-centered is-mobile">
15+
<div class="column is-muted notification is-four-fifths">
16+
<h1 class="title">SuperServer</h1>
17+
<h2 class="subtitle">Progress example</h2>
18+
<div id="progress"></div>
19+
</div>
20+
</div>
21+
</div>
22+
<div class="container">
23+
<div class="columns is-centered is-mobile">
24+
<div class="column is-dark notification is-four-fifths">
25+
<div class="is-size-7 has-text-warning" id="display">
26+
<ul id="display_list">
27+
</ul>
28+
</div>
29+
</div>
30+
</div>
31+
</div>
32+
</section>
33+
34+
<script type="text/javascript">
35+
$.get("http://127.0.0.1:5002/logs", function (data) {
36+
$("body").append("<p>" + JSON.stringify(data) + "</p>");
37+
});
38+
39+
40+
</script>
41+
</body>
42+
</html>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import typing
2+
from importlib import import_module
3+
4+
5+
def setup_app(apps: typing.List[str]):
6+
apps.extend(["superannotate.lib.app.server.default_app"])
7+
for path in apps:
8+
import_module(path)

0 commit comments

Comments
 (0)