draw_2D_annotations

Overlay 2D annotations from a CSV file onto an image, drawing circles, ellipses, or bounding boxes to visualize annotation locations.

draw_2D_annotations(
    annotation_path: str,
    image_path: str,
    output_path: str,
    radius: int = 5,
    debug: bool = False
) -> None

Overview

This function visualizes 2D annotations by drawing them directly on images. It reads annotation coordinates from a CSV file and overlays them as yellow markers (circles, ellipses, or rectangles) on the input image. The function supports three annotation formats commonly used in medical imaging and object detection tasks.

Supported annotation types:

  1. Center points: Small circles marking point locations
  2. Bounding boxes: Rectangles defining object boundaries
  3. Radius-based: Ellipses with custom radii for each annotation

This is useful for:

  • Visualizing detection or localization results
  • Quality control of annotation datasets
  • Verifying coordinate accuracy
  • Creating figures for presentations or papers
  • Debugging annotation extraction pipelines



Parameters

Name Type Default Description
annotation_path str required Path to the CSV annotation file containing coordinates.
image_path str required Path to the input image file (PNG, JPG, TIFF, etc.).
output_path str required Directory where the annotated image will be saved.
radius int 5 Radius of circles drawn for center-point annotations (pixels).
debug bool False If True, prints detailed information about the drawing process.

Returns

None – The function saves the annotated image to disk.

Output File

The annotated image is saved as:

<IMAGE_NAME>_annotated.<EXTENSION>

Example: Input slice_042.tif → Output slice_042_annotated.tif

Visual Properties

  • Annotation color: Yellow (BGR: 0, 255, 255)
  • Line thickness: 2 pixels
  • Center points: 1-pixel yellow dots
  • Image format: Same as input (preserved)

Annotation Formats

The function automatically detects and handles three CSV formats:

Format 1: Center Points

CSV Columns: CENTER_X, CENTER_Y

Drawing: Circle with specified radius + center dot

Example CSV:

CENTER_X,CENTER_Y
120,150
340,280

Use case: Point annotations, landmarks, detection centers

Format 2: Bounding Boxes

CSV Columns: X_MIN, Y_MIN, X_MAX, Y_MAX

Drawing: Rectangle from min to max coordinates

Example CSV:

X_MIN,Y_MIN,X_MAX,Y_MAX
100,120,180,200
300,250,380,330

Use case: Object detection, region proposals, lesion boundaries

Format 3: Radius-Based (Ellipses)

CSV Columns: CENTER_X, CENTER_Y, RADIUS_X, RADIUS_Y

Drawing: Ellipse with custom radii + center dot

Example CSV:

CENTER_X,CENTER_Y,RADIUS_X,RADIUS_Y
120,150,30,20
340,280,25,25

Use case: Size-aware annotations, variable-sized objects, anisotropic structures

Image Format Support

Supported input formats (via OpenCV):

  • PNG (.png)
  • JPEG (.jpg, .jpeg)
  • TIFF (.tif, .tiff)
  • BMP (.bmp)
  • Other OpenCV-supported formats

Automatic conversions:

  • Grayscale → 3-channel BGR
  • RGBA → 3-channel BGR

Exceptions

Exception Condition
FileNotFoundError Annotation file or image file does not exist
ValueError CSV contains NaN values
ValueError CSV is in volume format (contains Z coordinates)
ValueError CSV columns don’t match any supported format
ValueError Image failed to load

Usage Notes

  • Slice Format Only: Function only supports 2D slice annotations, not 3D volume annotations
  • Coordinate System: Coordinates must be in pixel indices (0-based)
  • Color: All annotations drawn in yellow for high visibility
  • Image Modification: Creates new image, does not modify original
  • Output Directory: Automatically created if it doesn’t exist
  • Multiple Annotations: All annotations from CSV drawn on same image

Examples

Basic Usage - Center Points

Draw center point annotations:

from nidataset.draw import draw_2D_annotations

draw_2D_annotations(
    annotation_path="labels/slice_042_centers.csv",
    image_path="images/slice_042.tif",
    output_path="annotated/",
    radius=8
)
# Output: annotated/slice_042_annotated.tif

Bounding Box Annotations

Visualize detection boxes:

draw_2D_annotations(
    annotation_path="detections/slice_042_boxes.csv",
    image_path="images/slice_042.tif",
    output_path="visualizations/",
    debug=True
)
# Prints: Drew 15 bounding boxes.
# Annotated image saved at: 'visualizations/slice_042_annotated.tif'

Radius-Based Annotations

Draw ellipses with custom sizes:

draw_2D_annotations(
    annotation_path="lesions/slice_042_radii.csv",
    image_path="images/slice_042.tif",
    output_path="lesion_overlays/",
    debug=True
)
# Prints: Drew 8 radius-based annotations (ellipses).

Custom Radius for Center Points

Adjust circle size for visibility:

draw_2D_annotations(
    annotation_path="landmarks/centers.csv",
    image_path="scans/brain_slice.tif",
    output_path="marked/",
    radius=12,  # Larger circles for better visibility
    debug=True
)

Batch Processing

Annotate multiple images:

import os
from nidataset.draw import draw_2D_annotations

annotation_folder = "annotations/"
image_folder = "images/"
output_folder = "annotated_images/"

# Match annotations to images
for csv_file in os.listdir(annotation_folder):
    if csv_file.endswith('.csv'):
        # Extract base name (assumes matching naming)
        base_name = csv_file.replace('.csv', '')
        
        # Find corresponding image
        for ext in ['.tif', '.png', '.jpg']:
            image_file = base_name + ext
            image_path = os.path.join(image_folder, image_file)
            
            if os.path.exists(image_path):
                print(f"Processing: {base_name}")
                
                draw_2D_annotations(
                    annotation_path=os.path.join(annotation_folder, csv_file),
                    image_path=image_path,
                    output_path=output_folder,
                    radius=6,
                    debug=True
                )
                break

Quality Control Workflow

Verify annotation accuracy:

import pandas as pd
from nidataset.draw import draw_2D_annotations

# Load annotations to check
annotations = pd.read_csv("qa/annotations.csv")

print("Annotation Quality Check:")
print(f"  Total annotations: {len(annotations)}")
print(f"  Columns: {annotations.columns.tolist()}")

# Check for issues
if annotations.isnull().any().any():
    print("  Warning: Contains NaN values")
else:
    print("  ✓ No NaN values")

# Draw annotations
draw_2D_annotations(
    annotation_path="qa/annotations.csv",
    image_path="qa/test_image.tif",
    output_path="qa/visualized/",
    radius=8,
    debug=True
)

print("\nReview 'qa/visualized/test_image_annotated.tif' for accuracy")

Comparing Detection Results

Visualize model predictions:

from nidataset.draw import draw_2D_annotations
import shutil

# Ground truth annotations
draw_2D_annotations(
    annotation_path="ground_truth/slice_042.csv",
    image_path="images/slice_042.tif",
    output_path="comparison/ground_truth/",
    radius=6,
    debug=True
)

# Model predictions
draw_2D_annotations(
    annotation_path="predictions/slice_042.csv",
    image_path="images/slice_042.tif",
    output_path="comparison/predictions/",
    radius=6,
    debug=True
)

print("Compare ground_truth and predictions folders visually")

Creating Presentation Figures

Generate annotated images for reports:

from nidataset.draw import draw_2D_annotations

# Representative slices with annotations
representative_slices = [
    ('slice_020', 'lesion'),
    ('slice_045', 'lesion'),
    ('slice_067', 'lesion')
]

for slice_name, annotation_type in representative_slices:
    draw_2D_annotations(
        annotation_path=f"annotations/{slice_name}_{annotation_type}.csv",
        image_path=f"images/{slice_name}.tif",
        output_path="figures/",
        radius=10,  # Larger for presentation
        debug=True
    )

print("Presentation figures ready in figures/ folder")

Handling Different Annotation Formats

Process mixed annotation types:

import pandas as pd
from nidataset.draw import draw_2D_annotations

def identify_annotation_format(csv_path):
    """Identify which format the CSV uses."""
    df = pd.read_csv(csv_path)
    
    if 'RADIUS_X' in df.columns and 'RADIUS_Y' in df.columns:
        return 'radius'
    elif 'X_MIN' in df.columns and 'X_MAX' in df.columns:
        return 'bbox'
    elif 'CENTER_X' in df.columns and 'CENTER_Y' in df.columns:
        return 'center'
    else:
        return 'unknown'

# Process based on format
annotation_file = "mixed_annotations/slice_042.csv"
format_type = identify_annotation_format(annotation_file)

print(f"Detected format: {format_type}")

draw_2D_annotations(
    annotation_path=annotation_file,
    image_path="images/slice_042.tif",
    output_path="annotated/",
    radius=7,
    debug=True
)

Creating Training Data Visualizations

Visualize training dataset:

import os
from nidataset.draw import draw_2D_annotations

# Visualize training samples
train_annotations = "training/labels/"
train_images = "training/images/"
train_visualized = "training/visualized/"

annotation_files = [f for f in os.listdir(train_annotations) if f.endswith('.csv')]

print(f"Visualizing {len(annotation_files)} training samples...")

for i, csv_file in enumerate(annotation_files[:20], 1):  # First 20 samples
    base_name = csv_file.replace('.csv', '')
    
    # Assume corresponding .tif image
    image_file = base_name + '.tif'
    
    if os.path.exists(os.path.join(train_images, image_file)):
        draw_2D_annotations(
            annotation_path=os.path.join(train_annotations, csv_file),
            image_path=os.path.join(train_images, image_file),
            output_path=train_visualized,
            radius=5,
            debug=False
        )
        
        if i % 5 == 0:
            print(f"  Processed {i} samples...")

print(f"\nVisualized samples saved in {train_visualized}")

Error Handling

Robust processing with validation:

from nidataset.draw import draw_2D_annotations
import os

def safe_draw_annotations(annotation_path, image_path, output_path):
    """Draw annotations with comprehensive error handling."""
    
    # Check files exist
    if not os.path.exists(annotation_path):
        print(f"✗ Annotation file not found: {annotation_path}")
        return False
    
    if not os.path.exists(image_path):
        print(f"✗ Image file not found: {image_path}")
        return False
    
    try:
        draw_2D_annotations(
            annotation_path=annotation_path,
            image_path=image_path,
            output_path=output_path,
            radius=6,
            debug=True
        )
        print(f"✓ Successfully annotated")
        return True
        
    except ValueError as e:
        print(f"✗ Invalid data: {e}")
        return False
        
    except Exception as e:
        print(f"✗ Unexpected error: {e}")
        return False

# Use with error handling
safe_draw_annotations(
    "annotations/slice_042.csv",
    "images/slice_042.tif",
    "output/"
)

Analyzing Annotation Distribution

Visualize and analyze:

import pandas as pd
import matplotlib.pyplot as plt
from nidataset.draw import draw_2D_annotations

# Load annotations
annotations = pd.read_csv("analysis/annotations.csv")

# Analyze distribution
print("Annotation Analysis:")
print(f"  Total annotations: {len(annotations)}")

if 'CENTER_X' in annotations.columns:
    print(f"  X range: [{annotations['CENTER_X'].min():.0f}, {annotations['CENTER_X'].max():.0f}]")
    print(f"  Y range: [{annotations['CENTER_Y'].min():.0f}, {annotations['CENTER_Y'].max():.0f}]")
    
    # Plot distribution
    plt.figure(figsize=(10, 5))
    
    plt.subplot(1, 2, 1)
    plt.hist(annotations['CENTER_X'], bins=20, alpha=0.7)
    plt.xlabel('X Coordinate')
    plt.ylabel('Count')
    plt.title('X Distribution')
    
    plt.subplot(1, 2, 2)
    plt.hist(annotations['CENTER_Y'], bins=20, alpha=0.7)
    plt.xlabel('Y Coordinate')
    plt.ylabel('Count')
    plt.title('Y Distribution')
    
    plt.tight_layout()
    plt.savefig('annotation_distribution.png')
    print("  Distribution plot saved")

# Visualize on image
draw_2D_annotations(
    annotation_path="analysis/annotations.csv",
    image_path="analysis/image.tif",
    output_path="analysis/visualized/",
    radius=7,
    debug=True
)

Creating Dataset Documentation

Generate annotated examples for documentation:

from nidataset.draw import draw_2D_annotations

# Example annotations for each format
examples = {
    'center_points': {
        'annotation': 'docs/examples/centers.csv',
        'image': 'docs/examples/sample.tif',
        'description': 'Center point annotations'
    },
    'bounding_boxes': {
        'annotation': 'docs/examples/boxes.csv',
        'image': 'docs/examples/sample.tif',
        'description': 'Bounding box annotations'
    },
    'radius_based': {
        'annotation': 'docs/examples/radii.csv',
        'image': 'docs/examples/sample.tif',
        'description': 'Radius-based annotations'
    }
}

print("Creating documentation examples...")

for format_name, config in examples.items():
    print(f"\n{config['description']}:")
    
    draw_2D_annotations(
        annotation_path=config['annotation'],
        image_path=config['image'],
        output_path=f"docs/figures/{format_name}/",
        radius=8,
        debug=True
    )

print("\nDocumentation figures created")

Typical Workflow

from nidataset.draw import draw_2D_annotations
import pandas as pd

# 1. Check annotation format
annotations = pd.read_csv("annotations/slice_042.csv")
print(f"Annotation format: {annotations.columns.tolist()}")
print(f"Total annotations: {len(annotations)}")

# 2. Draw annotations on image
draw_2D_annotations(
    annotation_path="annotations/slice_042.csv",
    image_path="images/slice_042.tif",
    output_path="visualized/",
    radius=6,
    debug=True
)

# 3. Review annotated image
# Open 'visualized/slice_042_annotated.tif' in image viewer

# 4. Use annotated images for:
# - Quality control of annotations
# - Presentation and documentation
# - Verification of coordinate accuracy
# - Debugging annotation pipelines

This site uses Just the Docs, a documentation theme for Jekyll.