ClassBalance is a widget in Supervisely that allows for displaying input data classes balance on the UI. For example, you can display the distribution of tags to different classes in the project, or set your data according to the required format. It also provides functionality for data streaming and dynamic updates, allowing the class balance to display real-time data. Additionally, users can control the widget through Python code by detecting events such as clicking on a class name or segment data.
Display the collapse button. The case collapsable=True has been shown above to show examples for changing the max_height parameter. So now an example will be shown for case collapsable=False.
Create card widget with first ClassBalance widget content
card_1 =Card( title="Simple example how to use ClassBalance widget", content=Container([class_balance_1]), collapsable=True,)card_1.collapse()
Example 2. Advanced using ClassBalance widget.
👍 In this example, we will iterate through sample dataset images from the Supervisely platform and crop them based on object classes to set in the image slider. Finally, we will calculate and collect statistics for each class and display the class balance information.
Create static dir
Create a static directory to store the cropped images in local directory. Or you can upload them to the Team Files and use the full_storage_url to display them in the image slider.
Get all image infos, annotations and download images from server
image_infos = api.image.get_list(dataset.id)image_ids = [image_info.id for image_info in image_infos]imame_nps = api.image.download_nps(dataset.id, image_ids)anns_json = api.annotation.download_json_batch(dataset.id, image_ids)anns = [sly.Annotation.from_json(json, project_meta)for json in anns_json]
Calculate and collect series for the ClassBalance widget
PADDINGS ={"top":"20px","left":"20px","bottom":"20px","right":"20px"}progress =tqdm(desc=f"Processing datasets...", total=dataset.items_count)# prepare data for ClassBalance widgetcrop_id =0slider_data =defaultdict(list)new_slider_data =defaultdict(list)objclass_stats =defaultdict(lambda: defaultdict(lambda: 0))# collect cropped images for image slidersfor image_info, img, ann inzip(image_infos, imame_nps, anns):for idx, objclass inenumerate(project_meta.obj_classes):# crop current image to separated images which contain current class instance crops = sly.aug.instance_crop(img, ann, objclass.name, False, PADDINGS) objclass_stats[objclass.name]["total"] +=len(crops)for crop_img, crop_ann in crops:# draw annotations on image and upload result crop image to Team Files crop_ann.draw_pretty(crop_img) path = os.path.join(static_dir, f"{crop_id}-{image_info.name}") static_path = os.path.join("static", os.path.basename(path)) crop_id +=1 sly.image.write(path, crop_img)# collect cropped image paths for ClassBalance widgetif idx ==0: slider_data[objclass.name].append({"preview": static_path})else: new_slider_data[objclass.name].append({"preview": static_path})# count number of objects with each tagfor label in ann.labels:for tag in label.tags: objclass_stats[label.obj_class.name][tag.name] +=1 progress.update(1)# prepare rows datarows_data = []new_rows_data = []tag_metas = project_meta.tag_metasfor idx, obj_class inenumerate(project_meta.obj_classes): data ={} data["name"]= obj_class.name data["nameHtml"]=f"<strong>{obj_class.name}</strong>" data["total"]= objclass_stats[obj_class.name]["total"] data["disabled"]=False data["segments"]={}for tag_meta in tag_metas: data["segments"][tag_meta.name] = objclass_stats[obj_class.name][tag_meta.name]if idx ==0: rows_data.append(data)else: new_rows_data.append(data)segments = [{"name": tm.name,"key": tm.name,"color":rgb2hex(tm.color)}for tm in tag_metas]
Initialize ClassBalance
class_balance_2 =ClassBalance( max_value=max([stat["total"] for stat in objclass_stats.values()]), segments=segments[:-1], rows_data=rows_data, slider_data=slider_data, max_height=700, collapsable=True, clickable_name=True, clickable_segment=True,)
Create additional widgets
add_segment_btn =Button("Add segment")add_row_btn =Button("Add row data")add_slider_data_btn =Button("Add slider data")buttons =Flexbox([add_segment_btn, add_row_btn, add_slider_data_btn], "horizontal")text =Text()card_2 =Card( title="Advanced example how to use ClassBalance widget", content=Container([class_balance_2, buttons, text]), collapsable=True,)card_2.collapse()
Create app layout
Prepare a layout for the app using Card widget with the content parameter.
layout =Container(widgets=[card_1, card_2])
Create app using layout
Create an app object with the layout and static_dir parameters.
@class_balance_2.clickdefshow_item(res):if res.get("segmentValue")isnotNoneand res.get("segmentName")isnotNone: info = (f"Class {res['name']} contain {res['segmentValue']} tags with name {res['segmentName']}" )if res["segmentName"]=="val": status ="success"elif res["segmentName"]=="test": status ="warning"elif res["segmentName"]=="trash": status ="error"else: status ="info"else: info =f"Class {res['name']}" status ="text" text.set(text=info, status=status)@add_segment_btn.clickdefadd_segment(): class_balance_2.add_segments(segments[-1:])@add_row_btn.clickdefadd_row(): class_balance_2.add_rows_data(new_rows_data)@add_slider_data_btn.clickdefadd_slider_data(): class_balance_2.add_slider_data(new_slider_data)