1
+ from __future__ import annotations
2
+ from pycocotools .coco import COCO
3
+ from pycocotools .cocoeval import COCOeval
4
+
5
+ from PIL import Image
6
+ from PIL import Image , ImageDraw , ImageFont
7
+
8
+ import os
9
+ import cv2
10
+
11
+ font_path = os .path .join (cv2 .__path__ [0 ], 'qt' , 'fonts' , 'DejaVuSans.ttf' )
12
+
13
+
14
+ def box_to_coco_format (box ):
15
+ """
16
+ Convert (top, left, bottom, right) to (left, top, width, height)
17
+ """
18
+ top , left , bottom , right = box
19
+ width = right - left
20
+ height = bottom - top
21
+ return [left , top , width , height ]
22
+
23
+
24
+ def prepare_coco_format (gt_data : list [list [tuple [tuple [float , float , float , float ], float , int ]]],
25
+ pred_data : list [list [tuple [tuple [float , float , float , float ], float , int ]]],
26
+ classes : list [str ]):
27
+ """
28
+ Input data format: ((left, top, width, height), certainty, class), coordinates are absolute.
29
+ """
30
+ coco_ground_truth = {
31
+ "images" : [{"id" : img_id } for img_id in range (len (gt_data ))], # Image IDs for all images
32
+ "annotations" : [],
33
+ "categories" : [{"id" : cls_id , "name" : cls_name } for cls_id , cls_name in enumerate (classes , 1 )],
34
+ }
35
+
36
+ coco_predictions = []
37
+ annotation_id = 0
38
+
39
+ # Add ground truth annotations for each image
40
+ for img_id , gt_boxes in enumerate (gt_data ):
41
+ for box , certainty , clss in gt_boxes :
42
+ coco_ground_truth ["annotations" ].append ({
43
+ "id" : annotation_id ,
44
+ "image_id" : img_id , # assign image ID
45
+ "category_id" : clss , # multiple classes
46
+ "bbox" : box ,
47
+ "area" : box [2 ] * box [3 ], # area of the bbox
48
+ "iscrowd" : 0
49
+ })
50
+ annotation_id += 1
51
+
52
+ # Add predictions for each image
53
+ for img_id , pred_boxes in enumerate (pred_data ):
54
+ for box , certainty , clss in pred_boxes :
55
+ coco_predictions .append ({
56
+ "image_id" : img_id , # Assign image ID
57
+ "category_id" : clss , # Multiple classes
58
+ "bbox" : box ,
59
+ "score" : certainty
60
+ })
61
+
62
+ return coco_ground_truth , coco_predictions
63
+
64
+
65
+ def evaluate_metrics (gt_data , pred_data , classes ):
66
+ coco_gt_data , coco_pred_data = prepare_coco_format (gt_data , pred_data , classes )
67
+
68
+ # Create COCO ground truth object
69
+ coco_gt = COCO ()
70
+ coco_gt .dataset = coco_gt_data
71
+ coco_gt .createIndex ()
72
+
73
+ # Create COCO predictions object (simulates the result file)
74
+ coco_dt = coco_gt .loadRes (coco_pred_data )
75
+
76
+ # Evaluate using COCOeval
77
+ coco_eval = COCOeval (coco_gt , coco_dt , 'bbox' )
78
+ coco_eval .params .imgIds = list (range (len (gt_data ))) # Evaluate all image IDs
79
+ coco_eval .evaluate ()
80
+ coco_eval .accumulate ()
81
+ print (f"=== Total Metrics (all images and classes combined) ===" )
82
+ coco_eval .summarize ()
83
+
84
+ # Per-class metrics
85
+ for class_id , class_name in enumerate (classes , 1 ):
86
+ coco_eval .params .catIds = [class_id ] # Filter by class
87
+ coco_eval .evaluate ()
88
+ coco_eval .accumulate ()
89
+ print (f"\n === Metrics for class: { class_name } ===" )
90
+ coco_eval .summarize ()
91
+
92
+
93
+ def draw_rectangles (image_path , coordinates : list [tuple [tuple [float , float , float , float ], float , int ]], classes : int ,
94
+ threshold : float = 0.5 ):
95
+ """
96
+ Draws rectangles on the image based on absolute coordinates. Expects COCO format (left, top, width, height).
97
+
98
+ Mainly used for debugging.
99
+ """
100
+ for current_cls in range (classes ):
101
+ img = Image .open (image_path )
102
+ draw = ImageDraw .Draw (img )
103
+ for dat in coordinates :
104
+ if dat [2 ] - 1 == current_cls and dat [1 ] > threshold :
105
+ (left , top , widht , height ) = dat [0 ]
106
+ # Scale the coordinates based on the image size
107
+ top_pixel = int (top )
108
+ left_pixel = int (left )
109
+ bottom_pixel = int (top + height )
110
+ right_pixel = int (left + widht )
111
+
112
+ # Draw a rectangle (outline only)
113
+ draw .rectangle ([left_pixel , top_pixel , right_pixel , bottom_pixel ], outline = "red" , width = 2 )
114
+
115
+ print (f"saving { current_cls } " )
116
+ img .save (f"eval_tests/{ current_cls } .png" )
117
+
118
+
119
+ def draw_rectangles_with_conf (image_path , coordinates : list [tuple [tuple [float , float , float , float ], float , int ]],
120
+ classes : int , threshold : float = 0.5 ):
121
+ """
122
+ Draws rectangles on the image based on absolute coordinates and displays confidence.
123
+ Expects coordinates in COCO format (left, top, width, height), with confidence and class info.
124
+ """
125
+
126
+ # Load a font (optional, you can specify a font path if desired)
127
+ # font = ImageFont.load_default()
128
+ font = ImageFont .truetype (font_path , size = 25 )
129
+
130
+ for current_cls in range (classes ):
131
+ img = Image .open (image_path )
132
+ draw = ImageDraw .Draw (img )
133
+
134
+ for dat in coordinates :
135
+ # dat[0]: (left, top, width, height)
136
+ # dat[1]: confidence score
137
+ # dat[2]: class label (subtract 1 as per the existing code)
138
+
139
+ if dat [2 ] - 1 == current_cls and dat [1 ] > threshold :
140
+ (left , top , width , height ) = dat [0 ]
141
+ confidence = dat [1 ]
142
+
143
+ # Scale the coordinates based on the image size
144
+ top_pixel = int (top )
145
+ left_pixel = int (left )
146
+ bottom_pixel = int (top + height )
147
+ right_pixel = int (left + width )
148
+
149
+ # Draw the main rectangle (outline only)
150
+ draw .rectangle ([left_pixel , top_pixel , right_pixel , bottom_pixel ], outline = "red" , width = 2 )
151
+
152
+ # Create a filled rectangle for the confidence label at the top-left of the main rectangle
153
+ label_height = 30 # Fixed height for the label box
154
+ label_width = 80 # Width of the label box for the confidence text
155
+ draw .rectangle ([left_pixel , top_pixel , left_pixel + label_width , top_pixel + label_height ], fill = "red" )
156
+
157
+ # Draw the confidence score inside the label box
158
+ draw .text ((left_pixel + 5 , top_pixel ), f"{ confidence :.3f} " , fill = "white" , font = font )
159
+
160
+ print (f"saving { current_cls } " )
161
+ img .save (f"{ image_path } _{ current_cls } .png" )
0 commit comments