Skip to content

Commit 8377497

Browse files
authored
Merge pull request #2 from superannotateai/benchmark_dev
Benchmark dev
2 parents e473858 + 41d3834 commit 8377497

File tree

8 files changed

+222
-1
lines changed

8 files changed

+222
-1
lines changed
40.6 KB
Loading
25.3 KB
Loading

docs/source/benchmark_scatter.png

60.3 KB
Loading

docs/source/superannotate.sdk.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,5 @@ Utility functions
322322
--------------------------------
323323

324324
.. autofunction:: superannotate.dicom_to_rgb_sequence
325+
.. autofunction:: superannotate.consensus
326+
.. autofunction:: superannotate.benchmark

docs/source/tutorial.sdk.rst

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,4 +544,45 @@ Analogically the box plots of consensus scores for each project are colored acco
544544
Scatter plot of consensus score vs instance area is separated by projects. Hovering on a point reveals its annotator and image name.
545545
The points are colored according to class name. Each annotator is represented with separate symbol.
546546

547-
.. image:: consensus_scatter.png
547+
.. image:: consensus_scatter.png
548+
549+
----------
550+
551+
552+
Computing benchmark scores for instances between ground truth project and given project list
553+
____________________________________________________________________________________________
554+
555+
556+
Benchmark is a tool to compare the quallity of the annotations of the same image that is present in several projects with
557+
the ground truth annotation of the same image that is in a separate project.
558+
559+
To compute the benchmark scores:
560+
561+
.. code-block:: python
562+
563+
res_df = sa.benchmark("<ground_truth_project_name>",[project_names], "<path_to_export_folder>", [image_list], "<annotation_type>")
564+
565+
Here pandas DataFrame with exactly same structure as in case of consensus computation is returned.
566+
567+
Besides the pandas DataFrame there is an option to get the following plots by setting the show_plots flag to True:
568+
569+
* Box plot of benchmark scores for each annotators
570+
* Box plot of benchmark scores for each project
571+
* Scatter plots of benchmark score vs instance area for each project
572+
573+
.. code-block:: python
574+
575+
sa.benchmark("<ground_truth_project_name>", [project_names], "<path_to_export_folder>", [image_list], "<annotation_type>", show_plots=True)
576+
577+
To the left of each box plot the original score points of that annotator is depicted, the box plots are colored by annotator.
578+
579+
.. image:: benchmark_annotators_box.png
580+
581+
Analogically the box plots of benchmark scores for each project are colored according to project name.
582+
583+
.. image:: benchmark_projects_box.png
584+
585+
Scatter plot of benchmark score vs instance area is separated by projects. Hovering on a point reveals its annotator and image name.
586+
The points are colored according to class name. Each annotator is represented with separate symbol.
587+
588+
.. image:: benchmark_scatter.png

superannotate/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
project_type_str_to_int, user_role_str_to_int
2323
)
2424
from .consensus_benchmark.consensus import consensus
25+
from .consensus_benchmark.benchmark import benchmark
2526
from .dataframe_filtering import (
2627
filter_annotation_instances, filter_images_by_comments,
2728
filter_images_by_tags
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
Main module for benchmark computation
3+
"""
4+
import logging
5+
import tempfile
6+
import pandas as pd
7+
from pathlib import Path
8+
9+
from .helpers import image_consensus, consensus_plot
10+
from ..db.exports import prepare_export, download_export
11+
from ..analytics.common import aggregate_annotations_as_df
12+
13+
logger = logging.getLogger("superannotate-python-sdk")
14+
15+
16+
def benchmark(
17+
gt_project_name,
18+
project_names,
19+
export_root=None,
20+
image_list=None,
21+
annot_type='bbox',
22+
show_plots=False
23+
):
24+
"""Computes benchmark score for each instance of given images that are present both gt_project_name project and projects in project_names list:
25+
26+
:param gt_project_name: Project name that contains the ground truth annotations
27+
:type gt_project_name: str
28+
:param project_names: list of project names to aggregate through
29+
:type project_names: list of str
30+
:param export_root: root export path of the projects
31+
:type export_root: Pathlike (str or Path)
32+
:param image_list: List of image names from the projects list that must be used. If None, then all images from the projects list will be used. Default: None
33+
:type image_list: list
34+
:param annot_type: Type of annotation instances to consider. Available candidates are: ["bbox", "polygon", "point"]
35+
:type annot_type: str
36+
:param show_plots: If True, show plots based on results of consensus computation. Default: False
37+
:type show_plots: bool
38+
39+
"""
40+
def aggregate_attributes(instance_df):
41+
def attribute_to_list(attribute_df):
42+
attribute_names = list(attribute_df["attributeName"])
43+
attribute_df["attributeNames"] = len(attribute_df) * [
44+
attribute_names
45+
]
46+
return attribute_df
47+
48+
attributes = None
49+
if not instance_df["attributeGroupName"].isna().all():
50+
attrib_group_name = instance_df.groupby("attributeGroupName")[[
51+
"attributeGroupName", "attributeName"
52+
]].apply(attribute_to_list)
53+
attributes = dict(
54+
zip(
55+
attrib_group_name["attributeGroupName"],
56+
attrib_group_name["attributeNames"]
57+
)
58+
)
59+
60+
instance_df.drop(
61+
["attributeGroupName", "attributeName"], axis=1, inplace=True
62+
)
63+
instance_df.drop_duplicates(
64+
subset=["imageName", "instanceId", "project"], inplace=True
65+
)
66+
instance_df["attributes"] = [attributes]
67+
return instance_df
68+
69+
supported_types = ['polygon', 'bbox', 'point']
70+
if annot_type not in supported_types:
71+
raise NotImplementedError
72+
73+
if export_root is None:
74+
with tempfile.TemporaryDirectory() as export_dir:
75+
gt_project_meta = prepare_export(gt_project_name)
76+
download_export(gt_project_name, gt_project_meta, export_dir)
77+
gt_project_df = aggregate_annotations_as_df(export_dir)
78+
else:
79+
export_dir = Path(export_root) / gt_project_name
80+
gt_project_df = aggregate_annotations_as_df(export_dir)
81+
gt_project_df["project"] = gt_project_name
82+
83+
benchmark_dfs = []
84+
for project_name in project_names:
85+
if export_root is None:
86+
with tempfile.TemporaryDirectory() as export_dir:
87+
proj_export_meta = prepare_export(project_name)
88+
download_export(project_name, proj_export_meta, export_dir)
89+
project_df = aggregate_annotations_as_df(export_dir)
90+
else:
91+
export_dir = Path(export_root) / project_name
92+
project_df = aggregate_annotations_as_df(export_dir)
93+
94+
project_df["project"] = project_name
95+
project_gt_df = pd.concat([project_df, gt_project_df])
96+
project_gt_df = project_gt_df[project_gt_df["instanceId"].notna()]
97+
98+
if image_list is not None:
99+
project_gt_df = project_gt_df.loc[
100+
project_gt_df["imageName"].isin(image_list)]
101+
102+
project_gt_df.query("type == '" + annot_type + "'", inplace=True)
103+
104+
project_gt_df = project_gt_df.groupby(
105+
["imageName", "instanceId", "project"]
106+
)
107+
project_gt_df = project_gt_df.apply(aggregate_attributes).reset_index(
108+
drop=True
109+
)
110+
unique_images = set(project_gt_df["imageName"])
111+
all_benchmark_data = []
112+
for image_name in unique_images:
113+
image_data = image_consensus(project_gt_df, image_name, annot_type)
114+
all_benchmark_data.append(pd.DataFrame(image_data))
115+
116+
benchmark_project_df = pd.concat(all_benchmark_data, ignore_index=True)
117+
benchmark_project_df = benchmark_project_df[
118+
benchmark_project_df["projectName"] == project_name]
119+
120+
benchmark_dfs.append(benchmark_project_df)
121+
122+
benchmark_df = pd.concat(benchmark_dfs, ignore_index=True)
123+
124+
if show_plots:
125+
consensus_plot(benchmark_df, project_names)
126+
127+
return benchmark_df
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from pathlib import Path
2+
import superannotate as sa
3+
4+
sa.init(Path.home() / ".superannotate" / "config.json")
5+
6+
test_root = Path().resolve() / 'tests'
7+
8+
9+
def test_benchmark():
10+
annot_types = ['polygon', 'bbox', 'point']
11+
gt_project_name = 'consensus_1'
12+
project_names = ['consensus_2', 'consensus_3']
13+
df_column_names = [
14+
'creatorEmail', 'imageName', 'instanceId', 'area', 'className',
15+
'attributes', 'projectName', 'score'
16+
]
17+
export_path = test_root / 'consensus_benchmark'
18+
for annot_type in annot_types:
19+
res_df = sa.benchmark(
20+
gt_project_name,
21+
project_names,
22+
export_root=export_path,
23+
annot_type=annot_type
24+
)
25+
#test content of projectName column
26+
assert sorted(res_df['projectName'].unique()) == project_names
27+
28+
#test structure of resulting DataFrame
29+
assert sorted(res_df.columns) == sorted(df_column_names)
30+
31+
#test lower bound of the score
32+
assert (res_df['score'] >= 0).all()
33+
34+
#test upper bound of the score
35+
assert (res_df['score'] <= 1).all()
36+
37+
image_names = [
38+
'bonn_000000_000019_leftImg8bit.png',
39+
'bielefeld_000000_000321_leftImg8bit.png'
40+
]
41+
42+
#test filtering images with given image names list
43+
res_images = sa.benchmark(
44+
gt_project_name,
45+
project_names,
46+
export_root=export_path,
47+
image_list=image_names
48+
)
49+
50+
assert sorted(res_images['imageName'].unique()) == sorted(image_names)

0 commit comments

Comments
 (0)