1+ from shapely .geometry import Polygon , box , Point
2+ import plotly .express as px
3+ from plotly .subplots import make_subplots
4+ import plotly .graph_objects as go
5+ import plotly .colors as col
6+ import logging
7+ import numpy as np
8+ logger = logging .getLogger ("superannotate-python-sdk" )
9+
10+
11+ def instance_consensus (inst_1 , inst_2 ):
12+ """Helper function that computes consensus score between two instances:
13+
14+ :param inst_1: First instance for consensus score.
15+ :type inst_1: shapely object
16+ :param inst_2: Second instance for consensus score.
17+ :type inst_2: shapely object
18+
19+ """
20+ if inst_1 .type == inst_2 .type == 'Polygon' :
21+ intersect = inst_1 .intersection (inst_2 )
22+ union = inst_1 .union (inst_2 )
23+ score = intersect .area / union .area
24+ elif inst_1 .type == inst_2 .type == 'Point' :
25+ score = - 1 * inst_1 .distance (inst_2 )
26+ else :
27+ raise NotImplementedError
28+
29+ return score
30+
31+
32+ def image_consensus (df , image_name , annot_type ):
33+ """Helper function that computes consensus score for instances of a single image:
34+
35+ :param df: Annotation data of all images
36+ :type df: pandas.DataFrame
37+ :param image_name: The image name for which the consensus score will be computed
38+ :type image_name: str
39+ :param annot_type: Type of annotation instances to consider. Available candidates are: ["bbox", "polygon", "point"]
40+ :type dataset_format: str
41+
42+ """
43+ image_df = df [df ["imageName" ] == image_name ]
44+ all_projects = list (set (df ["project" ]))
45+ column_names = [
46+ "creatorEmail" , "imageName" , "instanceId" , "area" , "className" ,
47+ "attributes" , "projectName" , "score"
48+ ]
49+ instance_id = 0
50+ image_data = {}
51+ for column_name in column_names :
52+ image_data [column_name ] = []
53+
54+ projects_shaply_objs = {}
55+ # generate shapely objects of instances
56+ for _ , row in image_df .iterrows ():
57+ if row ["project" ] not in projects_shaply_objs :
58+ projects_shaply_objs [row ["project" ]] = []
59+ inst_data = row ["meta" ]
60+ if annot_type == 'bbox' :
61+ inst_coords = inst_data ["points" ]
62+ x1 , x2 = inst_coords ["x1" ], inst_coords ["x2" ]
63+ y1 , y2 = inst_coords ["y1" ], inst_coords ["y2" ]
64+ inst = box (min (x1 , x2 ), min (y1 , y2 ), max (x1 , x2 ), max (y1 , y2 ))
65+ elif annot_type == 'polygon' :
66+ inst_coords = inst_data ["points" ]
67+ shapely_format = []
68+ for i in range (0 , len (inst_coords ) - 1 , 2 ):
69+ shapely_format .append ((inst_coords [i ], inst_coords [i + 1 ]))
70+ inst = Polygon (shapely_format )
71+ elif annot_type == 'point' :
72+ inst = Point (inst_data ["x" ], inst_data ["y" ])
73+ if inst .is_valid :
74+ projects_shaply_objs [row ["project" ]].append (
75+ (
76+ inst , row ["className" ], row ["creatorEmail" ],
77+ row ["attributes" ]
78+ )
79+ )
80+ else :
81+ logger .info (
82+ "Invalid %s instance occured, skipping to the next one." ,
83+ annot_type
84+ )
85+
86+ # match instances
87+ for curr_proj , curr_proj_instances in projects_shaply_objs .items ():
88+ for curr_inst_data in curr_proj_instances :
89+ curr_inst , curr_class , _ , _ = curr_inst_data
90+ max_instances = []
91+ for other_proj , other_proj_instances in projects_shaply_objs .items (
92+ ):
93+ if curr_proj == other_proj :
94+ max_instances .append ((curr_proj , * curr_inst_data ))
95+ projects_shaply_objs [curr_proj ].remove (curr_inst_data )
96+ else :
97+ if annot_type in ['polygon' , 'bbox' ]:
98+ max_score = 0
99+ else :
100+ max_score = float ('-inf' )
101+ max_inst_data = None
102+ for other_inst_data in other_proj_instances :
103+ other_inst , other_class , _ , _ = other_inst_data
104+ score = instance_consensus (curr_inst , other_inst )
105+ if score > max_score and other_class == curr_class :
106+ max_score = score
107+ max_inst_data = other_inst_data
108+ if max_inst_data is not None :
109+ max_instances .append ((other_proj , * max_inst_data ))
110+ projects_shaply_objs [other_proj ].remove (max_inst_data )
111+ if len (max_instances ) == 1 :
112+ image_data ["creatorEmail" ].append (max_instances [0 ][3 ])
113+ image_data ["attributes" ].append (max_instances [0 ][4 ])
114+ image_data ["area" ].append (max_instances [0 ][1 ].area )
115+ image_data ["imageName" ].append (image_name )
116+ image_data ["instanceId" ].append (instance_id )
117+ image_data ["className" ].append (max_instances [0 ][2 ])
118+ image_data ["projectName" ].append (max_instances [0 ][0 ])
119+ image_data ["score" ].append (0 )
120+ else :
121+ for curr_match_data in max_instances :
122+ proj_cons = 0
123+ for other_match_data in max_instances :
124+ if curr_match_data [0 ] != other_match_data [0 ]:
125+ score = instance_consensus (
126+ curr_match_data [1 ], other_match_data [1 ]
127+ )
128+ proj_cons += (1. if score <= 0 else score )
129+ image_data ["creatorEmail" ].append (curr_match_data [3 ])
130+ image_data ["attributes" ].append (curr_match_data [4 ])
131+ image_data ["area" ].append (curr_match_data [1 ].area )
132+ image_data ["imageName" ].append (image_name )
133+ image_data ["instanceId" ].append (instance_id )
134+ image_data ["className" ].append (curr_match_data [2 ])
135+ image_data ["projectName" ].append (curr_match_data [0 ])
136+ image_data ["score" ].append (
137+ proj_cons / (len (all_projects ) - 1 )
138+ )
139+ instance_id += 1
140+
141+ return image_data
142+
143+
144+ def consensus_plot (consensus_df , projects ):
145+ plot_data = consensus_df .copy ()
146+
147+ #annotator-wise boxplot
148+ annot_box_fig = px .box (plot_data , x = "creatorEmail" , y = "score" , points = "all" )
149+ annot_box_fig .show ()
150+
151+ #project-wise boxplot
152+ project_box_fig = px .box (
153+ plot_data , x = "projectName" , y = "score" , points = "all"
154+ )
155+ project_box_fig .show ()
156+
157+ #scatter plot of score vs area
158+ fig = px .scatter (
159+ plot_data ,
160+ x = "area" ,
161+ y = "score" ,
162+ color = "className" ,
163+ symbol = "creatorEmail" ,
164+ facet_col = "projectName" ,
165+ hover_data = {
166+ "className" : False ,
167+ "imageName" : True ,
168+ "projectName" : False ,
169+ "area" : False ,
170+ "score" : False
171+ }
172+ )
173+ fig .show ()
0 commit comments