supervisely 6.73.213__py3-none-any.whl → 6.73.215__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.
Potentially problematic release.
This version of supervisely might be problematic. Click here for more details.
- supervisely/app/widgets/report_thumbnail/report_thumbnail.py +17 -5
- supervisely/app/widgets/team_files_selector/team_files_selector.py +3 -0
- supervisely/io/network_exceptions.py +89 -32
- supervisely/nn/benchmark/comparison/__init__.py +0 -0
- supervisely/nn/benchmark/comparison/detection_visualization/__init__.py +0 -0
- supervisely/nn/benchmark/comparison/detection_visualization/text_templates.py +437 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/__init__.py +27 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/avg_precision_by_class.py +125 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/calibration_score.py +224 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/explore_predicttions.py +112 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/localization_accuracy.py +161 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/outcome_counts.py +336 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/overview.py +249 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/pr_curve.py +142 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/precision_recal_f1.py +300 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/speedtest.py +308 -0
- supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/vis_metric.py +19 -0
- supervisely/nn/benchmark/comparison/detection_visualization/visualizer.py +298 -0
- supervisely/nn/benchmark/comparison/model_comparison.py +84 -0
- supervisely/nn/benchmark/evaluation/coco/metric_provider.py +9 -7
- supervisely/nn/benchmark/visualization/evaluation_result.py +266 -0
- supervisely/nn/benchmark/visualization/renderer.py +100 -0
- supervisely/nn/benchmark/visualization/report_template.html +46 -0
- supervisely/nn/benchmark/visualization/visualizer.py +1 -1
- supervisely/nn/benchmark/visualization/widgets/__init__.py +17 -0
- supervisely/nn/benchmark/visualization/widgets/chart/__init__.py +0 -0
- supervisely/nn/benchmark/visualization/widgets/chart/chart.py +72 -0
- supervisely/nn/benchmark/visualization/widgets/chart/template.html +16 -0
- supervisely/nn/benchmark/visualization/widgets/collapse/__init__.py +0 -0
- supervisely/nn/benchmark/visualization/widgets/collapse/collapse.py +33 -0
- supervisely/nn/benchmark/visualization/widgets/container/__init__.py +0 -0
- supervisely/nn/benchmark/visualization/widgets/container/container.py +54 -0
- supervisely/nn/benchmark/visualization/widgets/gallery/__init__.py +0 -0
- supervisely/nn/benchmark/visualization/widgets/gallery/gallery.py +125 -0
- supervisely/nn/benchmark/visualization/widgets/gallery/template.html +49 -0
- supervisely/nn/benchmark/visualization/widgets/markdown/__init__.py +0 -0
- supervisely/nn/benchmark/visualization/widgets/markdown/markdown.py +53 -0
- supervisely/nn/benchmark/visualization/widgets/notification/__init__.py +0 -0
- supervisely/nn/benchmark/visualization/widgets/notification/notification.py +38 -0
- supervisely/nn/benchmark/visualization/widgets/sidebar/__init__.py +0 -0
- supervisely/nn/benchmark/visualization/widgets/sidebar/sidebar.py +67 -0
- supervisely/nn/benchmark/visualization/widgets/table/__init__.py +0 -0
- supervisely/nn/benchmark/visualization/widgets/table/table.py +116 -0
- supervisely/nn/benchmark/visualization/widgets/widget.py +22 -0
- supervisely/nn/inference/cache.py +8 -5
- {supervisely-6.73.213.dist-info → supervisely-6.73.215.dist-info}/METADATA +5 -5
- {supervisely-6.73.213.dist-info → supervisely-6.73.215.dist-info}/RECORD +51 -12
- {supervisely-6.73.213.dist-info → supervisely-6.73.215.dist-info}/LICENSE +0 -0
- {supervisely-6.73.213.dist-info → supervisely-6.73.215.dist-info}/WHEEL +0 -0
- {supervisely-6.73.213.dist-info → supervisely-6.73.215.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.213.dist-info → supervisely-6.73.215.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
from supervisely.nn.benchmark.visualization.widgets.widget import BaseWidget
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CollapseWidget(BaseWidget):
|
|
7
|
+
def __init__(self, widgets: List[BaseWidget]) -> None:
|
|
8
|
+
super().__init__()
|
|
9
|
+
self.widgets = widgets
|
|
10
|
+
|
|
11
|
+
def save_data(self, basepath: str) -> None:
|
|
12
|
+
for widget in self.widgets:
|
|
13
|
+
widget.save_data(basepath)
|
|
14
|
+
|
|
15
|
+
def get_state(self) -> Dict:
|
|
16
|
+
return {}
|
|
17
|
+
|
|
18
|
+
def to_html(self) -> str:
|
|
19
|
+
items = "\n".join(
|
|
20
|
+
[
|
|
21
|
+
f"""
|
|
22
|
+
<el-collapse-item title="{widget.title}">
|
|
23
|
+
{widget.to_html()}
|
|
24
|
+
</el-collapse-item>
|
|
25
|
+
"""
|
|
26
|
+
for widget in self.widgets
|
|
27
|
+
]
|
|
28
|
+
)
|
|
29
|
+
return f"""
|
|
30
|
+
<el-collapse class='mb-6'>
|
|
31
|
+
{items}
|
|
32
|
+
</el-collapse>
|
|
33
|
+
"""
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from typing import Dict, List, Literal, Optional, Union
|
|
2
|
+
|
|
3
|
+
from supervisely.nn.benchmark.visualization.widgets.widget import BaseWidget
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContainerWidget(BaseWidget):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
widgets: List[BaseWidget],
|
|
10
|
+
name: str = "container",
|
|
11
|
+
title: str = None,
|
|
12
|
+
grid: Optional[bool] = False,
|
|
13
|
+
grid_cols: Union[int, Literal["auto"]] = 2,
|
|
14
|
+
grid_rows: Union[int, Literal["auto"]] = "auto",
|
|
15
|
+
):
|
|
16
|
+
super().__init__(name, title)
|
|
17
|
+
self.widgets = widgets
|
|
18
|
+
self.grid = grid
|
|
19
|
+
self.grid_cols = grid_cols
|
|
20
|
+
self.grid_rows = grid_rows
|
|
21
|
+
|
|
22
|
+
def to_html(self) -> str:
|
|
23
|
+
if self.grid:
|
|
24
|
+
if any([v != "auto" and type(v) != int for v in [self.grid_cols, self.grid_rows]]):
|
|
25
|
+
raise ValueError("grid_cols and grid_rows must be either 'auto' or an integer")
|
|
26
|
+
s = f"""
|
|
27
|
+
<div
|
|
28
|
+
id="{ self.id }"
|
|
29
|
+
style="
|
|
30
|
+
display: grid;
|
|
31
|
+
grid-template-columns: repeat({self.grid_cols}, 1fr);
|
|
32
|
+
grid-template-rows: repeat({self.grid_rows}, 1fr);
|
|
33
|
+
grid-column-gap: 16px;
|
|
34
|
+
grid-row-gap: 16px;
|
|
35
|
+
margin-bottom: 25px;
|
|
36
|
+
"
|
|
37
|
+
>
|
|
38
|
+
"""
|
|
39
|
+
else:
|
|
40
|
+
s = "<div>"
|
|
41
|
+
for widget in self.widgets:
|
|
42
|
+
s += "<div>" + widget.to_html() + "</div>"
|
|
43
|
+
s += "</div>"
|
|
44
|
+
return s
|
|
45
|
+
|
|
46
|
+
def save_data(self, basepath: str) -> None:
|
|
47
|
+
for widget in self.widgets:
|
|
48
|
+
widget.save_data(basepath)
|
|
49
|
+
|
|
50
|
+
def get_state(self) -> Dict:
|
|
51
|
+
state = {}
|
|
52
|
+
for widget in self.widgets:
|
|
53
|
+
state.update(widget.get_state())
|
|
54
|
+
return state
|
|
File without changes
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from jinja2 import Template
|
|
6
|
+
|
|
7
|
+
from supervisely.api.annotation_api import AnnotationInfo
|
|
8
|
+
from supervisely.api.image_api import ImageInfo
|
|
9
|
+
from supervisely.app.widgets import GridGalleryV2
|
|
10
|
+
from supervisely.io.fs import ensure_base_path
|
|
11
|
+
from supervisely.nn.benchmark.visualization.widgets.widget import BaseWidget
|
|
12
|
+
from supervisely.project.project_meta import ProjectMeta
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GalleryWidget(BaseWidget):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
name: str,
|
|
19
|
+
filters: Optional[List] = None,
|
|
20
|
+
is_modal: Optional[bool] = False,
|
|
21
|
+
columns_number: int = 3,
|
|
22
|
+
):
|
|
23
|
+
super().__init__(name)
|
|
24
|
+
self.reference = self.id
|
|
25
|
+
self.is_modal = is_modal
|
|
26
|
+
self.click_handled = False
|
|
27
|
+
self.click_gallery_id = None
|
|
28
|
+
self.click_data = None
|
|
29
|
+
self.click_gallery_items_limit = None
|
|
30
|
+
self.image_left_header = False
|
|
31
|
+
self._project_meta = None
|
|
32
|
+
self.show_all_button = False
|
|
33
|
+
self.columns_number = columns_number
|
|
34
|
+
|
|
35
|
+
filters = filters # or [{"confidence": [0.6, 1]}]
|
|
36
|
+
self._gallery = GridGalleryV2(
|
|
37
|
+
columns_number=columns_number,
|
|
38
|
+
annotations_opacity=0.4,
|
|
39
|
+
border_width=4,
|
|
40
|
+
enable_zoom=False,
|
|
41
|
+
default_tag_filters=filters,
|
|
42
|
+
show_zoom_slider=False,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def set_project_meta(self, project_meta: ProjectMeta):
|
|
46
|
+
self._project_meta = project_meta
|
|
47
|
+
|
|
48
|
+
def set_images(
|
|
49
|
+
self,
|
|
50
|
+
image_infos: List[ImageInfo],
|
|
51
|
+
ann_infos: List[AnnotationInfo],
|
|
52
|
+
project_metas: List[ProjectMeta] = None,
|
|
53
|
+
skip_tags_filtering: Optional[List[Union[bool, List[str]]]] = None,
|
|
54
|
+
):
|
|
55
|
+
"""One time operation"""
|
|
56
|
+
if project_metas is None:
|
|
57
|
+
project_metas = [self._project_meta] * self.columns_number
|
|
58
|
+
|
|
59
|
+
if skip_tags_filtering is None:
|
|
60
|
+
skip_tags_filtering = [False] * self.columns_number
|
|
61
|
+
|
|
62
|
+
for idx, (image, ann) in enumerate(zip(image_infos, ann_infos)):
|
|
63
|
+
image_name = image.name
|
|
64
|
+
image_url = image.full_storage_url
|
|
65
|
+
self._gallery.append(
|
|
66
|
+
title=image_name,
|
|
67
|
+
image_url=image_url,
|
|
68
|
+
annotation_info=ann,
|
|
69
|
+
column_index=idx % self.columns_number,
|
|
70
|
+
project_meta=project_metas[idx % self.columns_number],
|
|
71
|
+
ignore_tags_filtering=skip_tags_filtering[idx % self.columns_number],
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def _get_init_data(self):
|
|
75
|
+
res = {}
|
|
76
|
+
self._gallery._update_filters()
|
|
77
|
+
res.update(self._gallery.get_json_state())
|
|
78
|
+
res.update(self._gallery.get_json_data()["content"])
|
|
79
|
+
res["layoutData"] = res.pop("annotations")
|
|
80
|
+
res["projectMeta"] = self._project_meta.to_json()
|
|
81
|
+
return res
|
|
82
|
+
|
|
83
|
+
def save_data(self, basepath: str) -> None:
|
|
84
|
+
# init data
|
|
85
|
+
basepath = basepath.rstrip("/")
|
|
86
|
+
ensure_base_path(basepath + self.data_source)
|
|
87
|
+
|
|
88
|
+
with open(basepath + self.data_source, "w") as f:
|
|
89
|
+
json.dump(self._get_init_data(), f)
|
|
90
|
+
|
|
91
|
+
# click data
|
|
92
|
+
if self.click_data is not None:
|
|
93
|
+
ensure_base_path(basepath + self.click_data_source)
|
|
94
|
+
with open(basepath + self.click_data_source, "w") as f:
|
|
95
|
+
json.dump(self.click_data, f)
|
|
96
|
+
|
|
97
|
+
def get_state(self) -> None:
|
|
98
|
+
return {}
|
|
99
|
+
|
|
100
|
+
def to_html(self) -> str:
|
|
101
|
+
template_str = Path(__file__).parent / "template.html"
|
|
102
|
+
return Template(template_str.read_text()).render(self._get_template_data())
|
|
103
|
+
|
|
104
|
+
def add_on_click(self, gallery_id, click_data, gallery_items_limit):
|
|
105
|
+
self.click_handled = True
|
|
106
|
+
self.click_gallery_id = gallery_id
|
|
107
|
+
self.click_data = click_data
|
|
108
|
+
self.click_gallery_items_limit = gallery_items_limit
|
|
109
|
+
|
|
110
|
+
def add_image_left_header(self, html: str):
|
|
111
|
+
self.image_left_header = html
|
|
112
|
+
|
|
113
|
+
def _get_template_data(self):
|
|
114
|
+
return {
|
|
115
|
+
"widget_id": self.id,
|
|
116
|
+
"reference": self.reference, # TODO: same as id?
|
|
117
|
+
"init_data_source": self.data_source,
|
|
118
|
+
"is_modal": str(self.is_modal).lower(),
|
|
119
|
+
"click_handled": self.click_handled,
|
|
120
|
+
"show_all_button": self.show_all_button,
|
|
121
|
+
"click_data_source": self.click_data_source,
|
|
122
|
+
"click_gallery_id": self.click_gallery_id,
|
|
123
|
+
"click_gallery_items_limit": self.click_gallery_items_limit,
|
|
124
|
+
"image_left_header": self.image_left_header,
|
|
125
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<div style="position: relative; max-height: 600px; overflow: hidden;">
|
|
2
|
+
<sly-iw-gallery ref="{{ widget_id }}" iw-widget-id="{{ widget_id }}" :options="{'isModalWindow': {{ is_modal }}}"
|
|
3
|
+
:actions="{
|
|
4
|
+
'init': {
|
|
5
|
+
'dataSource': '{{ init_data_source }}',
|
|
6
|
+
},
|
|
7
|
+
{% if click_handled %}
|
|
8
|
+
'chart-click': {
|
|
9
|
+
'key': 'explore',
|
|
10
|
+
'dataSource': '{{ click_data_source }}',
|
|
11
|
+
'galleryId': '{{ click_gallery_id }}',
|
|
12
|
+
'limit': {{ click_gallery_items_limit }},
|
|
13
|
+
},
|
|
14
|
+
{% endif %}
|
|
15
|
+
}" :command="command" :data="data">
|
|
16
|
+
{% if image_left_header %}
|
|
17
|
+
<span slot="image-left-header">
|
|
18
|
+
{{ image_left_header }}
|
|
19
|
+
</span>
|
|
20
|
+
{% endif %}
|
|
21
|
+
</sly-iw-gallery>
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
{% if show_all_button %}
|
|
25
|
+
<!-- Gradient overlay -->
|
|
26
|
+
<div
|
|
27
|
+
style="position: absolute; z-index: 100; bottom: 20px; width: 100%; height: 100px; background: linear-gradient(to top, rgba(255, 255, 255, 1), transparent);">
|
|
28
|
+
</div>
|
|
29
|
+
<div
|
|
30
|
+
style="position: absolute; z-index: 100; bottom: 0; width: 100%; height: 20px; background: rgba(255, 255, 255, 1);">
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- 'Explore more' button -->
|
|
34
|
+
<div style="position: absolute; bottom: 20px; z-index: 101; display: flex; justify-content: center; width: 100%">
|
|
35
|
+
<el-button iw-widget-id="btn-1" type="primary" @click="command({
|
|
36
|
+
method: 'update-gallery',
|
|
37
|
+
payload: {
|
|
38
|
+
data: {
|
|
39
|
+
'key': 'explore',
|
|
40
|
+
'limit': {{ click_gallery_items_limit}},
|
|
41
|
+
'dataSource': '{{ click_data_source }}',
|
|
42
|
+
},
|
|
43
|
+
'galleryId': '{{ click_gallery_id }}',
|
|
44
|
+
},
|
|
45
|
+
internalCommand: true
|
|
46
|
+
})">Explore more</el-button>
|
|
47
|
+
</div>
|
|
48
|
+
{% endif %}
|
|
49
|
+
</div>
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from supervisely.io.fs import ensure_base_path
|
|
4
|
+
from supervisely.nn.benchmark.visualization.widgets.widget import BaseWidget
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MarkdownWidget(BaseWidget):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
name: str,
|
|
11
|
+
title: str,
|
|
12
|
+
text: str = None,
|
|
13
|
+
) -> None:
|
|
14
|
+
super().__init__(name, title)
|
|
15
|
+
self.text = text
|
|
16
|
+
self.data_source = f"/data/{self.name}_{self.id}.md"
|
|
17
|
+
self.is_info_block = False
|
|
18
|
+
self.width_fit_content = False
|
|
19
|
+
|
|
20
|
+
def save_data(self, basepath: str) -> None:
|
|
21
|
+
# init data
|
|
22
|
+
basepath = basepath.rstrip("/")
|
|
23
|
+
ensure_base_path(basepath + self.data_source)
|
|
24
|
+
|
|
25
|
+
with open(basepath + self.data_source, "w") as f:
|
|
26
|
+
f.write(self.text)
|
|
27
|
+
|
|
28
|
+
def get_state(self) -> Dict:
|
|
29
|
+
return {}
|
|
30
|
+
|
|
31
|
+
def to_html(self) -> str:
|
|
32
|
+
style_class = "markdown-no-border"
|
|
33
|
+
if self.is_info_block:
|
|
34
|
+
style_class += " overview-info-block"
|
|
35
|
+
if self.width_fit_content:
|
|
36
|
+
style_class += " markdown-fit-content"
|
|
37
|
+
|
|
38
|
+
return f"""
|
|
39
|
+
<div style="margin-top: 10px;">
|
|
40
|
+
<sly-iw-markdown
|
|
41
|
+
id="{ self.id }"
|
|
42
|
+
class="{ style_class }"
|
|
43
|
+
iw-widget-id="{ self.id }"
|
|
44
|
+
:actions="{{
|
|
45
|
+
'init': {{
|
|
46
|
+
'dataSource': '{ self.data_source }',
|
|
47
|
+
}},
|
|
48
|
+
}}"
|
|
49
|
+
:command="command"
|
|
50
|
+
:data="data"
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
"""
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from supervisely.nn.benchmark.visualization.widgets.widget import BaseWidget
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NotificationWidget(BaseWidget):
|
|
7
|
+
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
name: str,
|
|
11
|
+
title: str,
|
|
12
|
+
desc: str = None,
|
|
13
|
+
) -> None:
|
|
14
|
+
super().__init__(name, title=title)
|
|
15
|
+
self.desc = desc
|
|
16
|
+
|
|
17
|
+
def save_data(self, basepath: str) -> None:
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
def get_state(self) -> Dict:
|
|
21
|
+
return {}
|
|
22
|
+
|
|
23
|
+
def to_html(self) -> str:
|
|
24
|
+
return f"""
|
|
25
|
+
<div style="margin-top: 20px; margin-bottom: 20px;">
|
|
26
|
+
<sly-iw-notification
|
|
27
|
+
iw-widget-id="{ self.id }"
|
|
28
|
+
>
|
|
29
|
+
<span slot="title">
|
|
30
|
+
{ self.title }
|
|
31
|
+
</span>
|
|
32
|
+
|
|
33
|
+
<span slot="description">
|
|
34
|
+
{ self.desc }
|
|
35
|
+
</span>
|
|
36
|
+
</sly-iw-notification>
|
|
37
|
+
</div>
|
|
38
|
+
"""
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
from jinja2 import Template
|
|
4
|
+
|
|
5
|
+
from supervisely.nn.benchmark.visualization.widgets.widget import BaseWidget
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SidebarWidget(BaseWidget):
|
|
9
|
+
|
|
10
|
+
def __init__(self, widgets: List[BaseWidget], anchors: List[str]) -> None:
|
|
11
|
+
self.widgets = widgets
|
|
12
|
+
self.anchors = anchors
|
|
13
|
+
|
|
14
|
+
def sidebar_template_str(self, widget: BaseWidget):
|
|
15
|
+
button_style = f"{{fontWeight: data.scrollIntoView === '{widget.id}' ? 'bold' : 'normal'}}"
|
|
16
|
+
|
|
17
|
+
return f"""
|
|
18
|
+
<div>
|
|
19
|
+
<el-button type="text" @click="data.scrollIntoView='{widget.id}'" :style="{button_style}">
|
|
20
|
+
{widget.title}
|
|
21
|
+
</el-button>
|
|
22
|
+
</div>
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def html_str(self):
|
|
27
|
+
anchored_widgets = sorted(
|
|
28
|
+
[widget for widget in self.widgets if widget.id in self.anchors],
|
|
29
|
+
key=lambda x: self.anchors.index(x.id),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
sidebar_content = "".join(self.sidebar_template_str(widget) for widget in anchored_widgets)
|
|
33
|
+
main_content = "".join(
|
|
34
|
+
f"""
|
|
35
|
+
<div style="margin-top: 20px;">
|
|
36
|
+
{widget.to_html()}
|
|
37
|
+
</div>
|
|
38
|
+
"""
|
|
39
|
+
for widget in self.widgets
|
|
40
|
+
)
|
|
41
|
+
template = f"""
|
|
42
|
+
<sly-iw-sidebar
|
|
43
|
+
:options="{{ height: 'calc(100vh - 130px)', clearMainPanelPaddings: true, leftSided: false, disableResize: true, sidebarWidth: 300 }}"
|
|
44
|
+
>
|
|
45
|
+
<div slot="sidebar">
|
|
46
|
+
{sidebar_content}
|
|
47
|
+
</div>
|
|
48
|
+
<div style="padding-right: 35px;">
|
|
49
|
+
{main_content}
|
|
50
|
+
</div>
|
|
51
|
+
</sly-iw-sidebar>
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
return template
|
|
55
|
+
|
|
56
|
+
def to_html(self) -> str:
|
|
57
|
+
return Template(self.html_str).render()
|
|
58
|
+
|
|
59
|
+
def save_data(self, basepath: str) -> None:
|
|
60
|
+
for widget in self.widgets:
|
|
61
|
+
widget.save_data(basepath)
|
|
62
|
+
|
|
63
|
+
def get_state(self) -> Dict:
|
|
64
|
+
state = {}
|
|
65
|
+
for widget in self.widgets:
|
|
66
|
+
state.update(widget.get_state())
|
|
67
|
+
return state
|
|
File without changes
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Dict, Optional, Union
|
|
3
|
+
|
|
4
|
+
from jinja2 import Template
|
|
5
|
+
|
|
6
|
+
from supervisely.io.fs import ensure_base_path
|
|
7
|
+
from supervisely.nn.benchmark.visualization.widgets.widget import BaseWidget
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TableWidget(BaseWidget):
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
name: str,
|
|
14
|
+
data: Dict = None,
|
|
15
|
+
click_data: Any = None,
|
|
16
|
+
click_gellery_id: str = "",
|
|
17
|
+
fix_columns: int = 0,
|
|
18
|
+
show_header_controls: bool = True,
|
|
19
|
+
main_column: Optional[str] = None,
|
|
20
|
+
width: Optional[Union[int, str]] = None,
|
|
21
|
+
page_size: int = 10,
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__(name=name)
|
|
24
|
+
self.data = data
|
|
25
|
+
self.fix_columns = fix_columns
|
|
26
|
+
self.show_header_controls = show_header_controls
|
|
27
|
+
self.main_column = main_column
|
|
28
|
+
self.click_data = click_data
|
|
29
|
+
self.click_gellery_id = click_gellery_id
|
|
30
|
+
|
|
31
|
+
if isinstance(width, int):
|
|
32
|
+
width = f"width: {width}px"
|
|
33
|
+
elif isinstance(width, str):
|
|
34
|
+
width = f"width: {width.rstrip('%')}%"
|
|
35
|
+
self.width = width
|
|
36
|
+
self.page_size = page_size
|
|
37
|
+
|
|
38
|
+
self.clickable = self.click_data is not None
|
|
39
|
+
|
|
40
|
+
def save_data(self, basepath: str) -> None:
|
|
41
|
+
# init data
|
|
42
|
+
basepath = basepath.rstrip("/")
|
|
43
|
+
ensure_base_path(basepath + self.data_source)
|
|
44
|
+
|
|
45
|
+
with open(basepath + self.data_source, "w") as f:
|
|
46
|
+
json.dump(self.data, f)
|
|
47
|
+
|
|
48
|
+
# click data
|
|
49
|
+
if self.click_data is not None:
|
|
50
|
+
ensure_base_path(basepath + self.click_data_source)
|
|
51
|
+
with open(basepath + self.click_data_source, "w") as f:
|
|
52
|
+
json.dump(self.click_data, f)
|
|
53
|
+
|
|
54
|
+
def get_state(self) -> Dict:
|
|
55
|
+
return {}
|
|
56
|
+
|
|
57
|
+
def get_render_data(self) -> Dict:
|
|
58
|
+
return {
|
|
59
|
+
"widget_id": self.id,
|
|
60
|
+
"fixColumns": self.fix_columns,
|
|
61
|
+
"showHeaderControls": self.show_header_controls,
|
|
62
|
+
"init_data_source": self.data_source,
|
|
63
|
+
"table_click_data": self.click_data_source,
|
|
64
|
+
"table_gallery_id": self.click_gellery_id,
|
|
65
|
+
"mainColumn": self.main_column,
|
|
66
|
+
"clickable": self.clickable,
|
|
67
|
+
"width": self.width or "",
|
|
68
|
+
"data": "data",
|
|
69
|
+
"command": "command",
|
|
70
|
+
"page_size": self.page_size,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def to_html(self) -> str:
|
|
74
|
+
template_str = """
|
|
75
|
+
<div style="margin-top: 20px; margin-bottom: 30px; {{ width }}">
|
|
76
|
+
<sly-iw-table
|
|
77
|
+
iw-widget-id="{{ widget_id }}"
|
|
78
|
+
{% if clickable %}
|
|
79
|
+
style="cursor: pointer;"
|
|
80
|
+
{% endif %}
|
|
81
|
+
:options="{
|
|
82
|
+
isRowClickable: '{{ clickable }}' === 'True',
|
|
83
|
+
fixColumns: {{ fixColumns }},
|
|
84
|
+
showHeaderControls: '{{ showHeaderControls }}' === 'True',
|
|
85
|
+
}"
|
|
86
|
+
:actions="{
|
|
87
|
+
'init': {
|
|
88
|
+
'dataSource': '{{ init_data_source }}',
|
|
89
|
+
'perPage': {{ page_size }},
|
|
90
|
+
},
|
|
91
|
+
{% if clickable %}
|
|
92
|
+
'chart-click': {
|
|
93
|
+
'dataSource': '{{ table_click_data }}',
|
|
94
|
+
'galleryId': '{{ table_gallery_id }}',
|
|
95
|
+
'getKey':(payload)=>payload.row[0],
|
|
96
|
+
},
|
|
97
|
+
{% endif %}
|
|
98
|
+
}"
|
|
99
|
+
:command="{{ command }}"
|
|
100
|
+
:data="{{ data }}"
|
|
101
|
+
>
|
|
102
|
+
<span
|
|
103
|
+
slot="custom-cell-content"
|
|
104
|
+
slot-scope='{ row, column, cellValue }'
|
|
105
|
+
>
|
|
106
|
+
<div
|
|
107
|
+
v-if="column === '{{ mainColumn }}'"
|
|
108
|
+
class="fflex"
|
|
109
|
+
>
|
|
110
|
+
<b>{{ '{{cellValue}}' }}</b>
|
|
111
|
+
</div>
|
|
112
|
+
</span>
|
|
113
|
+
</sly-iw-table>
|
|
114
|
+
</div>
|
|
115
|
+
"""
|
|
116
|
+
return Template(template_str).render(self.get_render_data())
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from supervisely._utils import camel_to_snake, rand_str
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseWidget:
|
|
7
|
+
def __init__(self, name: str = None, title: Optional[str] = None) -> None:
|
|
8
|
+
self.type = camel_to_snake(self.__class__.__name__)
|
|
9
|
+
self.id = f"{self.type}_{rand_str(5)}"
|
|
10
|
+
self.name = name
|
|
11
|
+
self.title = title
|
|
12
|
+
self.data_source = f"/data/{self.name}_{self.id}.json"
|
|
13
|
+
self.click_data_source = f"/data/{self.name}_{self.id}_click_data.json"
|
|
14
|
+
|
|
15
|
+
def save_data(self, basepath: str) -> None:
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
|
|
18
|
+
def get_state(self) -> None:
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
|
|
21
|
+
def to_html(self) -> str:
|
|
22
|
+
raise NotImplementedError
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
import shutil
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
5
|
from enum import Enum
|
|
5
6
|
from logging import Logger
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from threading import Lock, Thread
|
|
8
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
9
9
|
from time import sleep
|
|
10
10
|
from typing import Any, Callable, Generator, List, Optional, Tuple, Union
|
|
11
11
|
|
|
12
12
|
import cv2
|
|
13
13
|
import numpy as np
|
|
14
14
|
from cacheout import Cache as CacheOut
|
|
15
|
-
from cachetools import Cache, LRUCache, TTLCache
|
|
15
|
+
from cachetools import Cache, LRUCache, TTLCache
|
|
16
16
|
from fastapi import BackgroundTasks, FastAPI, Form, Request, UploadFile
|
|
17
17
|
|
|
18
18
|
import supervisely as sly
|
|
@@ -83,7 +83,10 @@ class PersistentImageTTLCache(TTLCache):
|
|
|
83
83
|
# pylint: disable=no-member
|
|
84
84
|
link = self._TTLCache__getlink(key)
|
|
85
85
|
# pylint: disable=no-member
|
|
86
|
-
link
|
|
86
|
+
if hasattr(link, "expire"):
|
|
87
|
+
link.expire = self.timer() + self._TTLCache__ttl
|
|
88
|
+
else:
|
|
89
|
+
link.expires = self.timer() + self._TTLCache__ttl
|
|
87
90
|
except KeyError:
|
|
88
91
|
return
|
|
89
92
|
|
|
@@ -626,11 +629,11 @@ class InferenceImageCache:
|
|
|
626
629
|
pos_by_name[name] = pos
|
|
627
630
|
elif return_images is True:
|
|
628
631
|
items.append((pos, name))
|
|
629
|
-
|
|
632
|
+
|
|
630
633
|
def get_one_image(item):
|
|
631
634
|
pos, name = item
|
|
632
635
|
return pos, self._cache.get_image(name)
|
|
633
|
-
|
|
636
|
+
|
|
634
637
|
if len(items) > 0:
|
|
635
638
|
with ThreadPoolExecutor(min(64, len(items))) as executor:
|
|
636
639
|
for pos, image in executor.map(get_one_image, items):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: supervisely
|
|
3
|
-
Version: 6.73.
|
|
3
|
+
Version: 6.73.215
|
|
4
4
|
Summary: Supervisely Python SDK.
|
|
5
5
|
Home-page: https://github.com/supervisely/supervisely
|
|
6
6
|
Author: Supervisely
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
20
20
|
Requires-Python: >=3.8
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: cachetools
|
|
23
|
+
Requires-Dist: cachetools<=5.5.0,>=4.2.3
|
|
24
24
|
Requires-Dist: numpy<2.0.0,>=1.19
|
|
25
25
|
Requires-Dist: opencv-python<5.0.0.0,>=4.5.5.62
|
|
26
26
|
Requires-Dist: PTable<1.0.0,>=0.9.2
|
|
@@ -38,12 +38,12 @@ Requires-Dist: SimpleITK<3.0.0.0,>=2.1.1.2
|
|
|
38
38
|
Requires-Dist: pydicom<3.0.0,>=2.3.0
|
|
39
39
|
Requires-Dist: stringcase<2.0.0,>=1.2.0
|
|
40
40
|
Requires-Dist: python-magic<1.0.0,>=0.4.25
|
|
41
|
-
Requires-Dist: trimesh
|
|
41
|
+
Requires-Dist: trimesh<=4.5.0,>=3.11.2
|
|
42
42
|
Requires-Dist: uvicorn[standard]<1.0.0,>=0.18.2
|
|
43
43
|
Requires-Dist: pydantic<=2.8.2,>=1.7.4
|
|
44
44
|
Requires-Dist: anyio<=4.2.0,>=3.7.1
|
|
45
45
|
Requires-Dist: fastapi<=0.109.0,>=0.79.0
|
|
46
|
-
Requires-Dist: websockets
|
|
46
|
+
Requires-Dist: websockets<=13.1,>=10.3
|
|
47
47
|
Requires-Dist: jinja2<4.0.0,>=3.0.3
|
|
48
48
|
Requires-Dist: psutil<6.0.0,>=5.9.0
|
|
49
49
|
Requires-Dist: jsonpatch<2.0,>=1.32
|
|
@@ -71,7 +71,7 @@ Requires-Dist: zstd
|
|
|
71
71
|
Provides-Extra: apps
|
|
72
72
|
Requires-Dist: uvicorn[standard]<1.0.0,>=0.18.2; extra == "apps"
|
|
73
73
|
Requires-Dist: fastapi<1.0.0,>=0.79.0; extra == "apps"
|
|
74
|
-
Requires-Dist: websockets
|
|
74
|
+
Requires-Dist: websockets<=13.1,>=10.3; extra == "apps"
|
|
75
75
|
Requires-Dist: jinja2<4.0.0,>=3.0.3; extra == "apps"
|
|
76
76
|
Requires-Dist: psutil<6.0.0,>=5.9.0; extra == "apps"
|
|
77
77
|
Requires-Dist: jsonpatch<2.0,>=1.32; extra == "apps"
|