superannotate 4.5.4.dev1__py3-none-any.whl → 4.5.5.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- superannotate/__init__.py +1 -1
- superannotate/lib/app/analytics/aggregators.py +0 -8
- superannotate/lib/app/analytics/common.py +0 -48
- superannotate/lib/app/input_converters/conversion.py +0 -10
- superannotate/lib/app/input_converters/converters/supervisely_converters/supervisely_strategies.py +2 -2
- superannotate/lib/app/input_converters/converters/voc_converters/voc_helper.py +0 -11
- superannotate/lib/app/interface/responses.py +95 -0
- superannotate/lib/app/interface/sdk_interface.py +543 -326
- superannotate/lib/core/__init__.py +1 -6
- superannotate/lib/core/entities/base.py +0 -5
- superannotate/lib/core/entities/classes.py +0 -5
- superannotate/lib/core/entities/filters.py +2 -1
- superannotate/lib/core/entities/multimodal_form.py +0 -7
- superannotate/lib/core/entities/project.py +0 -1
- superannotate/lib/core/entities/work_managament.py +22 -8
- superannotate/lib/core/service_types.py +3 -23
- superannotate/lib/core/serviceproviders.py +18 -0
- superannotate/lib/core/usecases/annotations.py +0 -21
- superannotate/lib/core/usecases/items.py +38 -195
- superannotate/lib/core/usecases/projects.py +1 -1
- superannotate/lib/infrastructure/controller.py +123 -50
- superannotate/lib/infrastructure/serviceprovider.py +10 -1
- superannotate/lib/infrastructure/services/annotation.py +1 -2
- superannotate/lib/infrastructure/services/explore.py +6 -0
- superannotate/lib/infrastructure/services/item_service.py +0 -1
- superannotate/lib/infrastructure/services/work_management.py +60 -0
- superannotate/lib/infrastructure/stream_data_handler.py +0 -7
- superannotate/lib/infrastructure/utils.py +49 -0
- superannotate/lib/infrastructure/validators.py +0 -20
- {superannotate-4.5.4.dev1.dist-info → superannotate-4.5.5.dev3.dist-info}/METADATA +1 -1
- {superannotate-4.5.4.dev1.dist-info → superannotate-4.5.5.dev3.dist-info}/RECORD +35 -37
- superannotate/lib/app/input_converters/converters/coco_converters/coco_to_sa_pixel.py +0 -19
- superannotate/lib/app/input_converters/converters/coco_converters/sa_pixel_to_coco.py +0 -31
- superannotate/lib/app/input_converters/sa_conversion.py +0 -8
- {superannotate-4.5.4.dev1.dist-info → superannotate-4.5.5.dev3.dist-info}/LICENSE +0 -0
- {superannotate-4.5.4.dev1.dist-info → superannotate-4.5.5.dev3.dist-info}/WHEEL +0 -0
- {superannotate-4.5.4.dev1.dist-info → superannotate-4.5.5.dev3.dist-info}/entry_points.txt +0 -0
- {superannotate-4.5.4.dev1.dist-info → superannotate-4.5.5.dev3.dist-info}/top_level.txt +0 -0
superannotate/__init__.py
CHANGED
|
@@ -384,9 +384,7 @@ class DataAggregator:
|
|
|
384
384
|
annotation_json = None
|
|
385
385
|
with open(annotation_path) as fp:
|
|
386
386
|
annotation_json = json.load(fp)
|
|
387
|
-
parts = Path(annotation_path).name.split(self._annotation_suffix)
|
|
388
387
|
row_data = self.__fill_image_metadata(row_data, annotation_json["metadata"])
|
|
389
|
-
annotation_instance_id = 0
|
|
390
388
|
|
|
391
389
|
# include comments
|
|
392
390
|
for annotation in annotation_json["comments"]:
|
|
@@ -433,10 +431,8 @@ class DataAggregator:
|
|
|
433
431
|
if Path(annotation_path).parent != Path(self.project_root):
|
|
434
432
|
folder_name = Path(annotation_path).parent.name
|
|
435
433
|
instance_row.folderName = folder_name
|
|
436
|
-
num_added = 0
|
|
437
434
|
if not attributes:
|
|
438
435
|
rows.append(instance_row)
|
|
439
|
-
num_added = 1
|
|
440
436
|
else:
|
|
441
437
|
for attribute in attributes:
|
|
442
438
|
attribute_row = copy.copy(instance_row)
|
|
@@ -469,10 +465,6 @@ class DataAggregator:
|
|
|
469
465
|
attribute_row.attributeName = attribute_name
|
|
470
466
|
|
|
471
467
|
rows.append(attribute_row)
|
|
472
|
-
num_added += 1
|
|
473
|
-
|
|
474
|
-
if num_added > 0:
|
|
475
|
-
annotation_instance_id += 1
|
|
476
468
|
|
|
477
469
|
df = pd.DataFrame([row.__dict__ for row in rows], dtype=object)
|
|
478
470
|
df = df.astype({"probability": float})
|
|
@@ -3,7 +3,6 @@ import logging
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
import pandas as pd
|
|
6
|
-
import plotly.express as px
|
|
7
6
|
from lib.core.exceptions import AppException
|
|
8
7
|
|
|
9
8
|
logger = logging.getLogger("sa")
|
|
@@ -558,50 +557,3 @@ def consensus(df, item_name, annot_type):
|
|
|
558
557
|
instance_id += 1
|
|
559
558
|
|
|
560
559
|
return image_data
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
def consensus_plot(consensus_df, *_, **__):
|
|
564
|
-
plot_data = consensus_df.copy()
|
|
565
|
-
|
|
566
|
-
# annotator-wise boxplot
|
|
567
|
-
annot_box_fig = px.box(
|
|
568
|
-
plot_data,
|
|
569
|
-
x="creatorEmail",
|
|
570
|
-
y="score",
|
|
571
|
-
points="all",
|
|
572
|
-
color="creatorEmail",
|
|
573
|
-
color_discrete_sequence=px.colors.qualitative.Dark24,
|
|
574
|
-
)
|
|
575
|
-
annot_box_fig.show()
|
|
576
|
-
|
|
577
|
-
# project-wise boxplot
|
|
578
|
-
project_box_fig = px.box(
|
|
579
|
-
plot_data,
|
|
580
|
-
x="folderName",
|
|
581
|
-
y="score",
|
|
582
|
-
points="all",
|
|
583
|
-
color="folderName",
|
|
584
|
-
color_discrete_sequence=px.colors.qualitative.Dark24,
|
|
585
|
-
)
|
|
586
|
-
project_box_fig.show()
|
|
587
|
-
|
|
588
|
-
# scatter plot of score vs area
|
|
589
|
-
fig = px.scatter(
|
|
590
|
-
plot_data,
|
|
591
|
-
x="area",
|
|
592
|
-
y="score",
|
|
593
|
-
color="className",
|
|
594
|
-
symbol="creatorEmail",
|
|
595
|
-
facet_col="folderName",
|
|
596
|
-
color_discrete_sequence=px.colors.qualitative.Dark24,
|
|
597
|
-
hover_data={
|
|
598
|
-
"className": False,
|
|
599
|
-
"itemName": True,
|
|
600
|
-
"folderName": False,
|
|
601
|
-
"area": False,
|
|
602
|
-
"score": False,
|
|
603
|
-
},
|
|
604
|
-
)
|
|
605
|
-
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
|
|
606
|
-
fig.for_each_trace(lambda t: t.update(name=t.name.split("=")[-1]))
|
|
607
|
-
fig.show()
|
|
@@ -94,16 +94,6 @@ def _passes_type_sanity(params_info):
|
|
|
94
94
|
)
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
def _passes_list_members_type_sanity(lists_info):
|
|
98
|
-
for _list in lists_info:
|
|
99
|
-
for _list_member in _list[0]:
|
|
100
|
-
if not isinstance(_list_member, _list[2]):
|
|
101
|
-
raise AppException(
|
|
102
|
-
"'%s' should be list of '%s', but contains '%s'"
|
|
103
|
-
% (_list[1], _list[2], type(_list_member))
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
|
|
107
97
|
def _passes_value_sanity(values_info):
|
|
108
98
|
for value in values_info:
|
|
109
99
|
if value[0] not in value[2]:
|
superannotate/lib/app/input_converters/converters/supervisely_converters/supervisely_strategies.py
CHANGED
|
@@ -24,11 +24,11 @@ class SuperviselyStrategy(baseStrategy):
|
|
|
24
24
|
== "supervisely_keypoint_detection_to_sa_vector"
|
|
25
25
|
):
|
|
26
26
|
meta_json = json.load(open(self.export_root / "meta.json"))
|
|
27
|
-
|
|
27
|
+
self.conversion_algorithm(
|
|
28
28
|
json_files, classes_id_map, meta_json, self.output_dir
|
|
29
29
|
)
|
|
30
30
|
else:
|
|
31
|
-
|
|
31
|
+
self.conversion_algorithm(
|
|
32
32
|
json_files, classes_id_map, self.task, self.output_dir
|
|
33
33
|
)
|
|
34
34
|
(self.output_dir / "classes").mkdir(exist_ok=True)
|
|
@@ -38,17 +38,6 @@ def _iou(bbox1, bbox2):
|
|
|
38
38
|
)
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
def _get_image_shape_from_xml(file_path):
|
|
42
|
-
with open(os.path.splitext(file_path)[0] + ".xml") as f:
|
|
43
|
-
tree = ET.parse(f)
|
|
44
|
-
|
|
45
|
-
size = tree.find("size")
|
|
46
|
-
width = int(size.find("width").text)
|
|
47
|
-
height = int(size.find("height").text)
|
|
48
|
-
|
|
49
|
-
return height, width
|
|
50
|
-
|
|
51
|
-
|
|
52
41
|
def _get_image_metadata(file_path):
|
|
53
42
|
with open(os.path.splitext(file_path)[0] + ".xml") as f:
|
|
54
43
|
tree = ET.parse(f)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
from typing import Generic
|
|
6
|
+
from typing import overload
|
|
7
|
+
from typing import TypeVar
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseResult(list, Generic[T]):
|
|
13
|
+
"""A generic list-like wrapper for results with lazy loading support.
|
|
14
|
+
|
|
15
|
+
Inherits from ``list`` for full backward compatibility with code that
|
|
16
|
+
expects a real list (``isinstance(x, list)``, JSON serializers, etc.).
|
|
17
|
+
Data is fetched lazily on first access.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, data_fetcher: Callable[[], list[T]]) -> None:
|
|
21
|
+
super().__init__()
|
|
22
|
+
self._data_fetcher = data_fetcher
|
|
23
|
+
self._loaded = False
|
|
24
|
+
|
|
25
|
+
def _ensure_data(self) -> None:
|
|
26
|
+
"""Lazily fetch data if not already loaded."""
|
|
27
|
+
if not self._loaded:
|
|
28
|
+
list.extend(self, self._data_fetcher())
|
|
29
|
+
self._loaded = True
|
|
30
|
+
|
|
31
|
+
def data(self) -> list[T]:
|
|
32
|
+
self._ensure_data()
|
|
33
|
+
return list(self)
|
|
34
|
+
|
|
35
|
+
def __iter__(self) -> Iterator[T]:
|
|
36
|
+
self._ensure_data()
|
|
37
|
+
return list.__iter__(self)
|
|
38
|
+
|
|
39
|
+
def __len__(self) -> int:
|
|
40
|
+
self._ensure_data()
|
|
41
|
+
return list.__len__(self)
|
|
42
|
+
|
|
43
|
+
@overload
|
|
44
|
+
def __getitem__(self, index: int) -> T: ...
|
|
45
|
+
|
|
46
|
+
@overload
|
|
47
|
+
def __getitem__(self, index: slice) -> list[T]: ...
|
|
48
|
+
|
|
49
|
+
def __getitem__(self, index: int | slice) -> T | list[T]:
|
|
50
|
+
self._ensure_data()
|
|
51
|
+
return list.__getitem__(self, index)
|
|
52
|
+
|
|
53
|
+
def __repr__(self) -> str:
|
|
54
|
+
self._ensure_data()
|
|
55
|
+
return list.__repr__(self)
|
|
56
|
+
|
|
57
|
+
def __bool__(self) -> bool:
|
|
58
|
+
self._ensure_data()
|
|
59
|
+
return list.__len__(self) > 0
|
|
60
|
+
|
|
61
|
+
def __contains__(self, item: object) -> bool:
|
|
62
|
+
self._ensure_data()
|
|
63
|
+
return list.__contains__(self, item)
|
|
64
|
+
|
|
65
|
+
def __eq__(self, other: object) -> bool:
|
|
66
|
+
self._ensure_data()
|
|
67
|
+
return list.__eq__(self, other)
|
|
68
|
+
|
|
69
|
+
__hash__ = None # type: ignore[assignment]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class QueryResult(BaseResult[dict]):
|
|
73
|
+
"""A list-like wrapper for query results that supports .count() method.
|
|
74
|
+
|
|
75
|
+
This class wraps a list of query results while maintaining full backward
|
|
76
|
+
compatibility with list-like operations (iteration, indexing, len()).
|
|
77
|
+
Data is fetched lazily - only when accessed. Calling .count() does not
|
|
78
|
+
trigger data fetching.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
data_fetcher: Callable[[], list[dict]],
|
|
84
|
+
count_fetcher: Callable[[], int],
|
|
85
|
+
) -> None:
|
|
86
|
+
super().__init__(data_fetcher)
|
|
87
|
+
self._count_fetcher = count_fetcher
|
|
88
|
+
|
|
89
|
+
def count(self) -> int:
|
|
90
|
+
"""Return the count of items matching the query from the server.
|
|
91
|
+
|
|
92
|
+
This method does not trigger data fetching - it makes a separate
|
|
93
|
+
lightweight API call to get only the count.
|
|
94
|
+
"""
|
|
95
|
+
return self._count_fetcher()
|