File size: 11,571 Bytes
0a216c0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
"""
Visualize ground truth annotations from COCO format on images.
This helps verify the accuracy of XML to COCO conversion.
"""
import os
import json
import sys
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.colors as mcolors

# Add current directory to path
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, SCRIPT_DIR)

from original_annotations import load_ground_truth

try:
    import pycocotools.mask as mask_util
    HAS_PYCOCOTOOLS = True
except ImportError:
    HAS_PYCOCOTOOLS = False
    print("Warning: pycocotools not available. Mask visualization may be limited.")


def decode_rle(rle, width, height):
    """Decode COCO RLE to binary mask."""
    if not HAS_PYCOCOTOOLS:
        return None
    try:
        rle_decoded = rle.copy()
        rle_decoded['counts'] = rle_decoded['counts'].encode('utf-8')
        mask = mask_util.decode(rle_decoded)
        return mask
    except Exception as e:
        print(f"Warning: Failed to decode RLE: {e}")
        return None


def draw_coco_annotations(image_path, coco_json, output_path=None, show_labels=True):
    """
    Draw COCO annotations on an image.
    
    Args:
        image_path: Path to image file
        coco_json: COCO format dictionary
        output_path: Path to save visualized image (if None, returns numpy array)
        show_labels: Whether to show class labels
    
    Returns:
        numpy array of visualized image (if output_path is None)
    """
    # Load image
    img = Image.open(image_path).convert("RGB")
    img_array = np.array(img)
    
    # Get image info from COCO
    image_name = os.path.basename(image_path)
    img_info = None
    for img_data in coco_json["images"]:
        if img_data["file_name"] == image_name:
            img_info = img_data
            break
    
    if img_info is None:
        print(f"Warning: Image {image_name} not found in COCO data")
        return img_array
    
    img_id = img_info["id"]
    
    # Get annotations for this image
    annotations = [a for a in coco_json["annotations"] if a["image_id"] == img_id]
    
    if len(annotations) == 0:
        print(f"No annotations found for {image_name}")
        return img_array
    
    # Create category name map
    id_to_name = {c["id"]: c["name"] for c in coco_json["categories"]}
    
    # Create figure
    fig, ax = plt.subplots(1, 1, figsize=(15, 20))
    ax.imshow(img_array)
    ax.axis("off")
    ax.set_title(f"{image_name}\n({len(annotations)} annotations)", 
                 fontsize=14, fontweight='bold', pad=20)
    
    # Generate distinct colors for each category
    num_categories = len(coco_json["categories"])
    colors = plt.cm.tab20(np.linspace(0, 1, min(20, num_categories)))
    if num_categories > 20:
        # Use additional colormap for more categories
        colors2 = plt.cm.Set3(np.linspace(0, 1, num_categories - 20))
        colors = np.vstack([colors, colors2])
    
    category_colors = {}
    for idx, cat in enumerate(coco_json["categories"]):
        category_colors[cat["id"]] = colors[idx % len(colors)]
    
    # Draw each annotation
    for ann in annotations:
        cat_id = ann["category_id"]
        cat_name = id_to_name.get(cat_id, f"category_{cat_id}")
        color = category_colors.get(cat_id, [1, 0, 0, 0.5])  # Red fallback
        
        # Get segmentation
        segs = ann.get("segmentation", [])
        bbox = ann.get("bbox", [0, 0, 0, 0])
        
        # Draw segmentation (polygon or mask)
        if segs:
            if isinstance(segs, list) and len(segs) > 0:
                # Check if it's RLE (dict) or polygon (list of coordinates)
                if isinstance(segs, dict) or (isinstance(segs, list) and len(segs) > 0 and isinstance(segs[0], dict)):
                    # RLE mask
                    if isinstance(segs, list):
                        rle = segs[0]
                    else:
                        rle = segs
                    
                    if HAS_PYCOCOTOOLS:
                        mask = decode_rle(rle, img_info["width"], img_info["height"])
                        if mask is not None:
                            # Draw mask with transparency
                            mask_colored = np.zeros((*mask.shape, 4))
                            mask_colored[mask > 0] = [*color[:3], 0.3]  # Semi-transparent fill
                            ax.imshow(mask_colored, alpha=0.5)
                            
                            # Draw mask outline
                            try:
                                from scipy import ndimage
                                contours = ndimage.binary_erosion(mask) ^ mask
                                ax.contour(contours, colors=[color[:3]], linewidths=2, alpha=0.8)
                            except ImportError:
                                # Fallback: just draw the mask without contour
                                pass
                
                elif isinstance(segs[0], list) and len(segs[0]) >= 6:
                    # Polygon: flat list [x1, y1, x2, y2, ...]
                    coords = segs[0]
                    xs = coords[0::2]
                    ys = coords[1::2]
                    
                    # Draw polygon with fill
                    poly = patches.Polygon(
                        list(zip(xs, ys)),
                        closed=True,
                        edgecolor=color[:3],
                        facecolor=color[:3],
                        linewidth=2.5,
                        alpha=0.3,  # Semi-transparent fill
                    )
                    ax.add_patch(poly)
                    
                    # Draw polygon outline
                    poly_edge = patches.Polygon(
                        list(zip(xs, ys)),
                        closed=True,
                        edgecolor=color[:3],
                        facecolor="none",
                        linewidth=2.5,
                        alpha=0.8,  # More opaque edge
                    )
                    ax.add_patch(poly_edge)
        
        # Draw bounding box if no segmentation or as fallback
        if not segs or (isinstance(segs, list) and len(segs) == 0):
            x, y, w, h = bbox
            if w > 0 and h > 0:
                rect = patches.Rectangle(
                    (x, y),
                    w,
                    h,
                    edgecolor=color[:3],
                    facecolor=color[:3],
                    linewidth=2.5,
                    alpha=0.3,
                )
                ax.add_patch(rect)
                
                # Draw bbox outline
                rect_edge = patches.Rectangle(
                    (x, y),
                    w,
                    h,
                    edgecolor=color[:3],
                    facecolor="none",
                    linewidth=2.5,
                    alpha=0.8,
                )
                ax.add_patch(rect_edge)
        
        # Add label
        if show_labels:
            # Get position for label (use bbox or polygon center)
            if segs and isinstance(segs, list) and len(segs) > 0:
                if isinstance(segs[0], list) and len(segs[0]) >= 6:
                    # Polygon
                    coords = segs[0]
                    xs = coords[0::2]
                    ys = coords[1::2]
                    label_x = min(xs)
                    label_y = min(ys) - 10
                else:
                    # Use bbox
                    x, y, w, h = bbox
                    label_x = x
                    label_y = y - 10
            else:
                x, y, w, h = bbox
                label_x = x
                label_y = y - 10
            
            # Draw label with background
            ax.text(
                label_x,
                label_y,
                cat_name,
                color='black',
                fontsize=10,
                fontweight='bold',
                bbox=dict(
                    boxstyle="round,pad=0.5",
                    facecolor="white",
                    edgecolor=color[:3],
                    linewidth=2,
                    alpha=0.9,
                ),
                zorder=10,
            )
    
    plt.tight_layout()
    
    if output_path:
        plt.savefig(output_path, dpi=150, bbox_inches='tight', facecolor='white')
        plt.close()
        print(f"Saved visualization to: {output_path}")
        return None
    else:
        # Return as numpy array
        fig.canvas.draw()
        buf = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
        buf = buf.reshape(fig.canvas.get_width_height()[::-1] + (3,))
        plt.close()
        return buf


def visualize_all_images(coco_json, images_dir, output_dir):
    """
    Visualize annotations for all images in the COCO dataset.
    
    Args:
        coco_json: COCO format dictionary
        images_dir: Directory containing images
        output_dir: Directory to save visualized images
    """
    os.makedirs(output_dir, exist_ok=True)
    
    print(f"Visualizing {len(coco_json['images'])} images...")
    
    for img_info in coco_json["images"]:
        image_name = img_info["file_name"]
        image_path = Path(images_dir) / image_name
        
        if not image_path.exists():
            print(f"Warning: Image {image_name} not found, skipping...")
            continue
        
        output_path = Path(output_dir) / f"{Path(image_name).stem}_annotated.png"
        
        print(f"Processing {image_name}...")
        draw_coco_annotations(
            str(image_path),
            coco_json,
            output_path=str(output_path),
            show_labels=True
        )
    
    print(f"\nAll visualizations saved to: {output_dir}")


def main():
    """Main function to visualize ground truth annotations."""
    # Paths
    data_dir = os.path.join(SCRIPT_DIR, "Aleyna 1 (2024)")
    xml_path = os.path.join(data_dir, "Annotations", "annotations.xml")
    images_dir = os.path.join(data_dir, "Images")
    output_dir = os.path.join(SCRIPT_DIR, "visualizations_gt")
    
    # Option 1: Load from existing COCO JSON
    coco_json_path = os.path.join(SCRIPT_DIR, "ground_truth_coco.json")
    if os.path.exists(coco_json_path):
        print(f"Loading COCO JSON from: {coco_json_path}")
        with open(coco_json_path, 'r') as f:
            coco_json = json.load(f)
    else:
        # Option 2: Generate from XML
        print(f"Loading from XML: {xml_path}")
        coco_json = load_ground_truth(xml_path, images_dir)
        
        if coco_json:
            # Save for future use
            with open(coco_json_path, 'w') as f:
                json.dump(coco_json, f, indent=4)
            print(f"Saved COCO JSON to: {coco_json_path}")
    
    if not coco_json:
        print("Error: Failed to load annotations")
        return
    
    print(f"\nLoaded {len(coco_json['images'])} images")
    print(f"Loaded {len(coco_json['annotations'])} annotations")
    print(f"Categories: {[c['name'] for c in coco_json['categories']]}")
    
    # Visualize all images
    visualize_all_images(coco_json, images_dir, output_dir)
    
    print("\n" + "=" * 60)
    print("Visualization complete!")
    print("=" * 60)
    print(f"\nCheck the visualizations in: {output_dir}")
    print("Compare them with the original images to verify conversion accuracy.")


if __name__ == "__main__":
    main()