Spaces:
Runtime error
Runtime error
Commit ·
0a216c0
1
Parent(s): 989ec3c
Add test_combined_models.py and compare/ folder (excluding cvat_project_7_export and Annika 2 folders)
Browse files- .gitignore +12 -0
- compare/data/README.md +63 -0
- compare/data/batch_model_comparison_all_datasets.py +273 -0
- compare/data/batch_model_comparison_cvat_export.py +291 -0
- compare/data/batch_model_comparison_test_2025.py +273 -0
- compare/data/batch_process_all_datasets.py +216 -0
- compare/data/compare.py +642 -0
- compare/data/expert_datasets_model_comparison_summary.json +134 -0
- compare/data/ground_truth_coco.json +1217 -0
- compare/data/new_models.py +137 -0
- compare/data/old_models.py +371 -0
- compare/data/original_annotations.py +359 -0
- compare/data/sample_batches_model_comparison_summary.json +68 -0
- compare/data/sample_batches_summary.json +31 -0
- compare/data/test_models_on_sample_batches.py +263 -0
- compare/data/test_old_models.py +335 -0
- compare/data/test_old_models_output.json +0 -0
- compare/data/visualize_ground_truth.py +330 -0
- compare/data/visualize_sample_batches.py +198 -0
- test_combined_models.py +322 -0
.gitignore
CHANGED
|
@@ -33,3 +33,15 @@ __pycache__/
|
|
| 33 |
# OS files
|
| 34 |
.DS_Store
|
| 35 |
._*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
# OS files
|
| 34 |
.DS_Store
|
| 35 |
._*
|
| 36 |
+
|
| 37 |
+
# Image files (binary files not allowed in HF Spaces)
|
| 38 |
+
*.png
|
| 39 |
+
*.jpg
|
| 40 |
+
*.jpeg
|
| 41 |
+
*.gif
|
| 42 |
+
*.bmp
|
| 43 |
+
*.tiff
|
| 44 |
+
|
| 45 |
+
# Excel files (binary)
|
| 46 |
+
*.xlsx
|
| 47 |
+
*.xls
|
compare/data/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Comparison Scripts
|
| 2 |
+
|
| 3 |
+
This directory contains scripts to compare old models vs new models vs ground truth annotations.
|
| 4 |
+
|
| 5 |
+
## Files
|
| 6 |
+
|
| 7 |
+
- `original_annotations.py`: Parses CVAT XML annotations and converts to COCO format
|
| 8 |
+
- `old_models.py`: Runs old models (Line, Border, Zones) and converts predictions to COCO
|
| 9 |
+
- `new_models.py`: Runs new models (emanuskript, catmus, zone) and converts predictions to COCO
|
| 10 |
+
- `compare.py`: Main script that orchestrates the comparison and calculates metrics
|
| 11 |
+
|
| 12 |
+
## Setup
|
| 13 |
+
|
| 14 |
+
1. Install required dependencies:
|
| 15 |
+
```bash
|
| 16 |
+
pip install pycocotools numpy pillow matplotlib ultralytics
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
2. Ensure model files are in the project root:
|
| 20 |
+
- Old models: `best_line_detection_yoloe (1).pt`, `border_model_weights.pt`, `zones_model_weights.pt`
|
| 21 |
+
- New models: `best_emanuskript_segmentation.pt`, `best_catmus.pt`, `best_zone_detection.pt`
|
| 22 |
+
|
| 23 |
+
## Usage
|
| 24 |
+
|
| 25 |
+
Run the main comparison script:
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
cd /home/hasan/layout/compare/data
|
| 29 |
+
python compare.py
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
The script will:
|
| 33 |
+
1. Load ground truth annotations from `Aleyna 1 (2024)/Annotations/annotations.xml`
|
| 34 |
+
2. Run old models on all images in `Aleyna 1 (2024)/Images`
|
| 35 |
+
3. Run new models on all images
|
| 36 |
+
4. Calculate metrics (mAP@50, mAP@[.50:.95], Precision, Recall)
|
| 37 |
+
5. Create side-by-side visualizations for each image
|
| 38 |
+
|
| 39 |
+
## Output
|
| 40 |
+
|
| 41 |
+
Results are saved to `results/` directory:
|
| 42 |
+
- `ground_truth.json`: Ground truth in COCO format
|
| 43 |
+
- `old_models_merged.json`: Old models predictions
|
| 44 |
+
- `new_models_merged.json`: New models predictions
|
| 45 |
+
- `metrics.json`: Calculated metrics for both model sets
|
| 46 |
+
- `visualizations/`: Side-by-side comparison images
|
| 47 |
+
|
| 48 |
+
## Metrics
|
| 49 |
+
|
| 50 |
+
The comparison calculates:
|
| 51 |
+
- **mAP@50**: Mean Average Precision at IoU=0.50
|
| 52 |
+
- **mAP@[.50:.95]**: Mean Average Precision averaged over IoU thresholds from 0.50 to 0.95
|
| 53 |
+
- **Precision**: Approximated from mAP@50
|
| 54 |
+
- **Recall**: Maximum recall with 100 detections
|
| 55 |
+
- **F1 Score**: Harmonic mean of Precision and Recall
|
| 56 |
+
|
| 57 |
+
## Notes
|
| 58 |
+
|
| 59 |
+
- The CVAT XML parser handles RLE (Run-Length Encoding) format masks
|
| 60 |
+
- Category alignment is performed automatically to match ground truth categories
|
| 61 |
+
- Images are processed sequentially - batch processing may take time
|
| 62 |
+
- Visualizations show: Original+GT | Old Models | New Models side-by-side
|
| 63 |
+
|
compare/data/batch_model_comparison_all_datasets.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Batch model comparison for all expert-annotated datasets.
|
| 3 |
+
|
| 4 |
+
Datasets:
|
| 5 |
+
- Aleyna 1 (2024)
|
| 6 |
+
- Annika 2 (2024)
|
| 7 |
+
- Luise 1 (2024)
|
| 8 |
+
- Luise 2 (2024)
|
| 9 |
+
- Nuray 1 (2024)
|
| 10 |
+
- Nuray 2 (2024)
|
| 11 |
+
|
| 12 |
+
For each folder (e.g. "Aleyna 1 (2024)"):
|
| 13 |
+
- Uses the existing `ground_truth_coco.json`
|
| 14 |
+
- Runs OLD models and NEW models on all images in `Images/`
|
| 15 |
+
- Calculates detection/segmentation metrics vs ground truth
|
| 16 |
+
- Creates side‑by‑side visualizations:
|
| 17 |
+
Ground Truth | Old Models | New Models
|
| 18 |
+
- Saves everything under `<folder>/model_comparison/`
|
| 19 |
+
"""
|
| 20 |
+
import os
|
| 21 |
+
import sys
|
| 22 |
+
import json
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
|
| 25 |
+
import matplotlib.pyplot as plt
|
| 26 |
+
|
| 27 |
+
# Paths
|
| 28 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 29 |
+
PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR))
|
| 30 |
+
sys.path.insert(0, SCRIPT_DIR)
|
| 31 |
+
sys.path.insert(0, PROJECT_ROOT)
|
| 32 |
+
|
| 33 |
+
from old_models import process_dataset as process_old_models
|
| 34 |
+
from new_models import process_dataset as process_new_models
|
| 35 |
+
from compare import calculate_metrics, align_categories, draw_coco_annotations_simple
|
| 36 |
+
|
| 37 |
+
# Re‑use the same dataset list as in batch_process_all_datasets.py
|
| 38 |
+
DATASET_FOLDERS = [
|
| 39 |
+
"Aleyna 1 (2024)",
|
| 40 |
+
"Annika 2 (2024)",
|
| 41 |
+
"Luise 1 (2024)",
|
| 42 |
+
"Luise 2 (2024)",
|
| 43 |
+
"Nuray 1 (2024)",
|
| 44 |
+
"Nuray 2 (2024)",
|
| 45 |
+
]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def create_side_by_side_visualization(image_path, gt_coco, old_coco, new_coco, output_path):
|
| 49 |
+
"""
|
| 50 |
+
Create side‑by‑side visualization: GT | Old Models | New Models
|
| 51 |
+
"""
|
| 52 |
+
fig, axes = plt.subplots(1, 3, figsize=(30, 10))
|
| 53 |
+
|
| 54 |
+
# Left: Ground Truth
|
| 55 |
+
draw_coco_annotations_simple(image_path, gt_coco, "Ground Truth", axes[0])
|
| 56 |
+
|
| 57 |
+
# Middle: Old Models
|
| 58 |
+
draw_coco_annotations_simple(image_path, old_coco, "Old Models", axes[1])
|
| 59 |
+
|
| 60 |
+
# Right: New Models
|
| 61 |
+
draw_coco_annotations_simple(image_path, new_coco, "New Models", axes[2])
|
| 62 |
+
|
| 63 |
+
plt.tight_layout()
|
| 64 |
+
plt.savefig(output_path, dpi=150, bbox_inches="tight")
|
| 65 |
+
plt.close()
|
| 66 |
+
print(f" ✓ Saved comparison to: {output_path}")
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def process_expert_dataset(folder_name, base_dir=None):
|
| 70 |
+
"""
|
| 71 |
+
Process a single expert dataset:
|
| 72 |
+
- Load ground_truth_coco.json
|
| 73 |
+
- Run old & new models
|
| 74 |
+
- Compute metrics
|
| 75 |
+
- Create GT | Old | New visualizations
|
| 76 |
+
"""
|
| 77 |
+
if base_dir is None:
|
| 78 |
+
base_dir = SCRIPT_DIR
|
| 79 |
+
|
| 80 |
+
folder_path = Path(base_dir) / folder_name
|
| 81 |
+
|
| 82 |
+
if not folder_path.exists():
|
| 83 |
+
print(f"⚠️ Warning: Folder not found: {folder_path}")
|
| 84 |
+
return None
|
| 85 |
+
|
| 86 |
+
print("\n" + "=" * 70)
|
| 87 |
+
print(f"Processing expert dataset: {folder_name}")
|
| 88 |
+
print("=" * 70)
|
| 89 |
+
|
| 90 |
+
# Paths
|
| 91 |
+
gt_json_path = folder_path / "ground_truth_coco.json"
|
| 92 |
+
images_dir = folder_path / "Images"
|
| 93 |
+
output_dir = folder_path / "model_comparison"
|
| 94 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 95 |
+
|
| 96 |
+
if not gt_json_path.exists():
|
| 97 |
+
print(f"❌ Error: ground_truth_coco.json not found at {gt_json_path}")
|
| 98 |
+
return None
|
| 99 |
+
|
| 100 |
+
if not images_dir.exists():
|
| 101 |
+
print(f"❌ Error: Images directory not found at {images_dir}")
|
| 102 |
+
return None
|
| 103 |
+
|
| 104 |
+
# Load ground truth
|
| 105 |
+
print(f"\n[1/5] Loading ground truth...")
|
| 106 |
+
with open(gt_json_path, "r") as f:
|
| 107 |
+
gt_coco = json.load(f)
|
| 108 |
+
|
| 109 |
+
print(f" ✓ Loaded {len(gt_coco['images'])} images")
|
| 110 |
+
print(f" ✓ Loaded {len(gt_coco['annotations'])} annotations")
|
| 111 |
+
|
| 112 |
+
# Run old models
|
| 113 |
+
print(f"\n[2/5] Running old models...")
|
| 114 |
+
old_output_dir = output_dir / "old_models"
|
| 115 |
+
os.makedirs(old_output_dir, exist_ok=True)
|
| 116 |
+
|
| 117 |
+
try:
|
| 118 |
+
old_coco = process_old_models(str(images_dir), str(old_output_dir))
|
| 119 |
+
print(f" ✓ Generated {len(old_coco['annotations'])} annotations")
|
| 120 |
+
print(f" ✓ Categories: {[c['name'] for c in old_coco['categories']]}")
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print(f" ❌ Error running old models: {e}")
|
| 123 |
+
import traceback
|
| 124 |
+
|
| 125 |
+
traceback.print_exc()
|
| 126 |
+
return None
|
| 127 |
+
|
| 128 |
+
# Run new models
|
| 129 |
+
print(f"\n[3/5] Running new models...")
|
| 130 |
+
new_output_dir = output_dir / "new_models"
|
| 131 |
+
os.makedirs(new_output_dir, exist_ok=True)
|
| 132 |
+
|
| 133 |
+
try:
|
| 134 |
+
new_coco = process_new_models(str(images_dir), str(new_output_dir))
|
| 135 |
+
print(f" ✓ Generated {len(new_coco['annotations'])} annotations")
|
| 136 |
+
except Exception as e:
|
| 137 |
+
print(f" ❌ Error running new models: {e}")
|
| 138 |
+
import traceback
|
| 139 |
+
|
| 140 |
+
traceback.print_exc()
|
| 141 |
+
return None
|
| 142 |
+
|
| 143 |
+
# Calculate metrics
|
| 144 |
+
print(f"\n[4/5] Calculating metrics...")
|
| 145 |
+
|
| 146 |
+
# Align categories with ground truth (by name matching)
|
| 147 |
+
old_coco_aligned = align_categories(gt_coco.copy(), old_coco.copy())
|
| 148 |
+
new_coco_aligned = align_categories(gt_coco.copy(), new_coco.copy())
|
| 149 |
+
|
| 150 |
+
# Metrics for old models
|
| 151 |
+
print(f"\n Old Models Metrics:")
|
| 152 |
+
old_metrics = calculate_metrics(gt_coco, old_coco_aligned, str(output_dir))
|
| 153 |
+
print(f" mAP@50: {old_metrics.get('mAP@50', 0):.4f}")
|
| 154 |
+
print(f" mAP@[.50:.95]: {old_metrics.get('mAP@[.50:.95]', 0):.4f}")
|
| 155 |
+
print(f" Precision: {old_metrics.get('Precision', 0):.4f}")
|
| 156 |
+
print(f" Recall: {old_metrics.get('Recall', 0):.4f}")
|
| 157 |
+
|
| 158 |
+
# Metrics for new models
|
| 159 |
+
print(f"\n New Models Metrics:")
|
| 160 |
+
new_metrics = calculate_metrics(gt_coco, new_coco_aligned, str(output_dir))
|
| 161 |
+
print(f" mAP@50: {new_metrics.get('mAP@50', 0):.4f}")
|
| 162 |
+
print(f" mAP@[.50:.95]: {new_metrics.get('mAP@[.50:.95]', 0):.4f}")
|
| 163 |
+
print(f" Precision: {new_metrics.get('Precision', 0):.4f}")
|
| 164 |
+
print(f" Recall: {new_metrics.get('Recall', 0):.4f}")
|
| 165 |
+
|
| 166 |
+
# Save metrics JSON
|
| 167 |
+
metrics_path = output_dir / "metrics.json"
|
| 168 |
+
with open(metrics_path, "w") as f:
|
| 169 |
+
json.dump({"old_models": old_metrics, "new_models": new_metrics}, f, indent=4)
|
| 170 |
+
print(f" ✓ Saved metrics to: {metrics_path}")
|
| 171 |
+
|
| 172 |
+
# Create visualizations
|
| 173 |
+
print(f"\n[5/5] Creating side-by-side visualizations...")
|
| 174 |
+
vis_dir = output_dir / "visualizations"
|
| 175 |
+
os.makedirs(vis_dir, exist_ok=True)
|
| 176 |
+
|
| 177 |
+
for img_info in gt_coco["images"]:
|
| 178 |
+
image_name = img_info["file_name"]
|
| 179 |
+
image_path = images_dir / image_name
|
| 180 |
+
|
| 181 |
+
if not image_path.exists():
|
| 182 |
+
continue
|
| 183 |
+
|
| 184 |
+
img_id = img_info["id"]
|
| 185 |
+
|
| 186 |
+
# Filter annotations for this image
|
| 187 |
+
gt_img_coco = {
|
| 188 |
+
"images": [img_info],
|
| 189 |
+
"annotations": [a for a in gt_coco["annotations"] if a["image_id"] == img_id],
|
| 190 |
+
"categories": gt_coco["categories"],
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
old_img_coco = {
|
| 194 |
+
"images": [img_info],
|
| 195 |
+
"annotations": [a for a in old_coco["annotations"] if a["image_id"] == img_id],
|
| 196 |
+
"categories": old_coco["categories"],
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
new_img_coco = {
|
| 200 |
+
"images": [img_info],
|
| 201 |
+
"annotations": [a for a in new_coco["annotations"] if a["image_id"] == img_id],
|
| 202 |
+
"categories": new_coco["categories"],
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
out_path = vis_dir / f"{Path(image_name).stem}_comparison.png"
|
| 206 |
+
create_side_by_side_visualization(
|
| 207 |
+
str(image_path),
|
| 208 |
+
gt_img_coco,
|
| 209 |
+
old_img_coco,
|
| 210 |
+
new_img_coco,
|
| 211 |
+
str(out_path),
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
print(f" ✓ Visualizations saved to: {vis_dir}")
|
| 215 |
+
|
| 216 |
+
return {
|
| 217 |
+
"folder": folder_name,
|
| 218 |
+
"old_metrics": old_metrics,
|
| 219 |
+
"new_metrics": new_metrics,
|
| 220 |
+
"old_annotations": len(old_coco["annotations"]),
|
| 221 |
+
"new_annotations": len(new_coco["annotations"]),
|
| 222 |
+
"gt_annotations": len(gt_coco["annotations"]),
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def main():
|
| 227 |
+
"""Run model comparison for all expert datasets."""
|
| 228 |
+
print("=" * 70)
|
| 229 |
+
print("MODEL COMPARISON ON EXPERT DATASETS")
|
| 230 |
+
print("=" * 70)
|
| 231 |
+
print(f"\nProcessing {len(DATASET_FOLDERS)} folders:")
|
| 232 |
+
for folder in DATASET_FOLDERS:
|
| 233 |
+
print(f" - {folder}")
|
| 234 |
+
|
| 235 |
+
results = []
|
| 236 |
+
|
| 237 |
+
for folder_name in DATASET_FOLDERS:
|
| 238 |
+
result = process_expert_dataset(folder_name)
|
| 239 |
+
if result:
|
| 240 |
+
results.append(result)
|
| 241 |
+
|
| 242 |
+
# Print summary
|
| 243 |
+
print("\n" + "=" * 70)
|
| 244 |
+
print("SUMMARY")
|
| 245 |
+
print("=" * 70)
|
| 246 |
+
|
| 247 |
+
for r in results:
|
| 248 |
+
print(f"\n{r['folder']}:")
|
| 249 |
+
print(f" Ground Truth: {r['gt_annotations']} annotations")
|
| 250 |
+
print(f" Old Models: {r['old_annotations']} annotations")
|
| 251 |
+
print(f" mAP@50: {r['old_metrics'].get('mAP@50', 0):.4f}")
|
| 252 |
+
print(f" Precision: {r['old_metrics'].get('Precision', 0):.4f}")
|
| 253 |
+
print(f" Recall: {r['old_metrics'].get('Recall', 0):.4f}")
|
| 254 |
+
print(f" New Models: {r['new_annotations']} annotations")
|
| 255 |
+
print(f" mAP@50: {r['new_metrics'].get('mAP@50', 0):.4f}")
|
| 256 |
+
print(f" Precision: {r['new_metrics'].get('Precision', 0):.4f}")
|
| 257 |
+
print(f" Recall: {r['new_metrics'].get('Recall', 0):.4f}")
|
| 258 |
+
|
| 259 |
+
# Save summary
|
| 260 |
+
summary_path = Path(SCRIPT_DIR) / "expert_datasets_model_comparison_summary.json"
|
| 261 |
+
with open(summary_path, "w") as f:
|
| 262 |
+
json.dump(results, f, indent=4)
|
| 263 |
+
|
| 264 |
+
print(f"\n✓ Summary saved to: {summary_path}")
|
| 265 |
+
print("\n" + "=" * 70)
|
| 266 |
+
print("COMPLETE!")
|
| 267 |
+
print("=" * 70)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
if __name__ == "__main__":
|
| 271 |
+
main()
|
| 272 |
+
|
| 273 |
+
|
compare/data/batch_model_comparison_cvat_export.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Batch model comparison for CVAT export datasets.
|
| 3 |
+
|
| 4 |
+
For each task folder (e.g. "task_74_EMS_T1"):
|
| 5 |
+
- Uses `annotations/instances_default.json` as ground truth
|
| 6 |
+
- Runs OLD models and NEW models on all images in `images/`
|
| 7 |
+
- Calculates detection/segmentation metrics vs ground truth
|
| 8 |
+
- Creates side‑by‑side visualizations:
|
| 9 |
+
Ground Truth | Old Models | New Models
|
| 10 |
+
- Saves everything under `<task_folder>/model_comparison/`
|
| 11 |
+
"""
|
| 12 |
+
import os
|
| 13 |
+
import sys
|
| 14 |
+
import json
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
import matplotlib.pyplot as plt
|
| 18 |
+
|
| 19 |
+
# Paths
|
| 20 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 21 |
+
PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR))
|
| 22 |
+
sys.path.insert(0, SCRIPT_DIR)
|
| 23 |
+
sys.path.insert(0, PROJECT_ROOT)
|
| 24 |
+
|
| 25 |
+
from old_models import process_dataset as process_old_models
|
| 26 |
+
from new_models import process_dataset as process_new_models
|
| 27 |
+
from compare import calculate_metrics, align_categories, draw_coco_annotations_simple
|
| 28 |
+
|
| 29 |
+
# CVAT export directory
|
| 30 |
+
CVAT_EXPORT_DIR = Path("/home/hasan/layout/compare/data/cvat_project_7_export")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def discover_task_folders(base_dir):
|
| 34 |
+
"""
|
| 35 |
+
Discover all task folders in the CVAT export directory.
|
| 36 |
+
A task folder is identified by having:
|
| 37 |
+
- annotations/instances_default.json
|
| 38 |
+
- images/ directory
|
| 39 |
+
"""
|
| 40 |
+
task_folders = []
|
| 41 |
+
base_path = Path(base_dir)
|
| 42 |
+
|
| 43 |
+
if not base_path.exists():
|
| 44 |
+
print(f"❌ Error: CVAT export directory not found: {base_path}")
|
| 45 |
+
return []
|
| 46 |
+
|
| 47 |
+
for item in base_path.iterdir():
|
| 48 |
+
if item.is_dir():
|
| 49 |
+
annotations_path = item / "annotations" / "instances_default.json"
|
| 50 |
+
images_path = item / "images"
|
| 51 |
+
|
| 52 |
+
if annotations_path.exists() and images_path.exists():
|
| 53 |
+
task_folders.append(item.name)
|
| 54 |
+
|
| 55 |
+
return sorted(task_folders)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def create_side_by_side_visualization(image_path, gt_coco, old_coco, new_coco, output_path):
|
| 59 |
+
"""
|
| 60 |
+
Create side‑by‑side visualization: GT | Old Models | New Models
|
| 61 |
+
"""
|
| 62 |
+
fig, axes = plt.subplots(1, 3, figsize=(30, 10))
|
| 63 |
+
|
| 64 |
+
# Left: Ground Truth
|
| 65 |
+
draw_coco_annotations_simple(image_path, gt_coco, "Ground Truth", axes[0])
|
| 66 |
+
|
| 67 |
+
# Middle: Old Models
|
| 68 |
+
draw_coco_annotations_simple(image_path, old_coco, "Old Models", axes[1])
|
| 69 |
+
|
| 70 |
+
# Right: New Models
|
| 71 |
+
draw_coco_annotations_simple(image_path, new_coco, "New Models", axes[2])
|
| 72 |
+
|
| 73 |
+
plt.tight_layout()
|
| 74 |
+
plt.savefig(output_path, dpi=150, bbox_inches="tight")
|
| 75 |
+
plt.close()
|
| 76 |
+
print(f" ✓ Saved comparison to: {output_path}")
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def process_cvat_task(task_folder_name, base_dir=None):
|
| 80 |
+
"""
|
| 81 |
+
Process a single CVAT task folder:
|
| 82 |
+
- Load annotations/instances_default.json
|
| 83 |
+
- Run old & new models
|
| 84 |
+
- Compute metrics
|
| 85 |
+
- Create GT | Old | New visualizations
|
| 86 |
+
"""
|
| 87 |
+
if base_dir is None:
|
| 88 |
+
base_dir = CVAT_EXPORT_DIR
|
| 89 |
+
|
| 90 |
+
task_path = Path(base_dir) / task_folder_name
|
| 91 |
+
|
| 92 |
+
if not task_path.exists():
|
| 93 |
+
print(f"⚠️ Warning: Task folder not found: {task_path}")
|
| 94 |
+
return None
|
| 95 |
+
|
| 96 |
+
print("\n" + "=" * 70)
|
| 97 |
+
print(f"Processing CVAT task: {task_folder_name}")
|
| 98 |
+
print("=" * 70)
|
| 99 |
+
|
| 100 |
+
# Paths
|
| 101 |
+
gt_json_path = task_path / "annotations" / "instances_default.json"
|
| 102 |
+
images_dir = task_path / "images"
|
| 103 |
+
output_dir = task_path / "model_comparison"
|
| 104 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 105 |
+
|
| 106 |
+
if not gt_json_path.exists():
|
| 107 |
+
print(f"❌ Error: instances_default.json not found at {gt_json_path}")
|
| 108 |
+
return None
|
| 109 |
+
|
| 110 |
+
if not images_dir.exists():
|
| 111 |
+
print(f"❌ Error: images directory not found at {images_dir}")
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
# Load ground truth
|
| 115 |
+
print(f"\n[1/5] Loading ground truth...")
|
| 116 |
+
with open(gt_json_path, "r") as f:
|
| 117 |
+
gt_coco = json.load(f)
|
| 118 |
+
|
| 119 |
+
print(f" ✓ Loaded {len(gt_coco['images'])} images")
|
| 120 |
+
print(f" ✓ Loaded {len(gt_coco['annotations'])} annotations")
|
| 121 |
+
|
| 122 |
+
# Run old models
|
| 123 |
+
print(f"\n[2/5] Running old models...")
|
| 124 |
+
old_output_dir = output_dir / "old_models"
|
| 125 |
+
os.makedirs(old_output_dir, exist_ok=True)
|
| 126 |
+
|
| 127 |
+
try:
|
| 128 |
+
old_coco = process_old_models(str(images_dir), str(old_output_dir))
|
| 129 |
+
print(f" ✓ Generated {len(old_coco['annotations'])} annotations")
|
| 130 |
+
print(f" ✓ Categories: {[c['name'] for c in old_coco['categories']]}")
|
| 131 |
+
except Exception as e:
|
| 132 |
+
print(f" ❌ Error running old models: {e}")
|
| 133 |
+
import traceback
|
| 134 |
+
|
| 135 |
+
traceback.print_exc()
|
| 136 |
+
return None
|
| 137 |
+
|
| 138 |
+
# Run new models
|
| 139 |
+
print(f"\n[3/5] Running new models...")
|
| 140 |
+
new_output_dir = output_dir / "new_models"
|
| 141 |
+
os.makedirs(new_output_dir, exist_ok=True)
|
| 142 |
+
|
| 143 |
+
try:
|
| 144 |
+
new_coco = process_new_models(str(images_dir), str(new_output_dir))
|
| 145 |
+
print(f" ✓ Generated {len(new_coco['annotations'])} annotations")
|
| 146 |
+
except Exception as e:
|
| 147 |
+
print(f" ❌ Error running new models: {e}")
|
| 148 |
+
import traceback
|
| 149 |
+
|
| 150 |
+
traceback.print_exc()
|
| 151 |
+
return None
|
| 152 |
+
|
| 153 |
+
# Calculate metrics
|
| 154 |
+
print(f"\n[4/5] Calculating metrics...")
|
| 155 |
+
|
| 156 |
+
# Align categories with ground truth (by name matching)
|
| 157 |
+
old_coco_aligned = align_categories(gt_coco.copy(), old_coco.copy())
|
| 158 |
+
new_coco_aligned = align_categories(gt_coco.copy(), new_coco.copy())
|
| 159 |
+
|
| 160 |
+
# Metrics for old models
|
| 161 |
+
print(f"\n Old Models Metrics:")
|
| 162 |
+
old_metrics = calculate_metrics(gt_coco, old_coco_aligned, str(output_dir))
|
| 163 |
+
print(f" mAP@50: {old_metrics.get('mAP@50', 0):.4f}")
|
| 164 |
+
print(f" mAP@[.50:.95]: {old_metrics.get('mAP@[.50:.95]', 0):.4f}")
|
| 165 |
+
print(f" Precision: {old_metrics.get('Precision', 0):.4f}")
|
| 166 |
+
print(f" Recall: {old_metrics.get('Recall', 0):.4f}")
|
| 167 |
+
|
| 168 |
+
# Metrics for new models
|
| 169 |
+
print(f"\n New Models Metrics:")
|
| 170 |
+
new_metrics = calculate_metrics(gt_coco, new_coco_aligned, str(output_dir))
|
| 171 |
+
print(f" mAP@50: {new_metrics.get('mAP@50', 0):.4f}")
|
| 172 |
+
print(f" mAP@[.50:.95]: {new_metrics.get('mAP@[.50:.95]', 0):.4f}")
|
| 173 |
+
print(f" Precision: {new_metrics.get('Precision', 0):.4f}")
|
| 174 |
+
print(f" Recall: {new_metrics.get('Recall', 0):.4f}")
|
| 175 |
+
|
| 176 |
+
# Save metrics JSON
|
| 177 |
+
metrics_path = output_dir / "metrics.json"
|
| 178 |
+
with open(metrics_path, "w") as f:
|
| 179 |
+
json.dump({"old_models": old_metrics, "new_models": new_metrics}, f, indent=4)
|
| 180 |
+
print(f" ✓ Saved metrics to: {metrics_path}")
|
| 181 |
+
|
| 182 |
+
# Create visualizations
|
| 183 |
+
print(f"\n[5/5] Creating side-by-side visualizations...")
|
| 184 |
+
vis_dir = output_dir / "visualizations"
|
| 185 |
+
os.makedirs(vis_dir, exist_ok=True)
|
| 186 |
+
|
| 187 |
+
for img_info in gt_coco["images"]:
|
| 188 |
+
image_name = img_info["file_name"]
|
| 189 |
+
image_path = images_dir / image_name
|
| 190 |
+
|
| 191 |
+
if not image_path.exists():
|
| 192 |
+
continue
|
| 193 |
+
|
| 194 |
+
img_id = img_info["id"]
|
| 195 |
+
|
| 196 |
+
# Filter annotations for this image
|
| 197 |
+
gt_img_coco = {
|
| 198 |
+
"images": [img_info],
|
| 199 |
+
"annotations": [a for a in gt_coco["annotations"] if a["image_id"] == img_id],
|
| 200 |
+
"categories": gt_coco["categories"],
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
old_img_coco = {
|
| 204 |
+
"images": [img_info],
|
| 205 |
+
"annotations": [a for a in old_coco["annotations"] if a["image_id"] == img_id],
|
| 206 |
+
"categories": old_coco["categories"],
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
new_img_coco = {
|
| 210 |
+
"images": [img_info],
|
| 211 |
+
"annotations": [a for a in new_coco["annotations"] if a["image_id"] == img_id],
|
| 212 |
+
"categories": new_coco["categories"],
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
out_path = vis_dir / f"{Path(image_name).stem}_comparison.png"
|
| 216 |
+
create_side_by_side_visualization(
|
| 217 |
+
str(image_path),
|
| 218 |
+
gt_img_coco,
|
| 219 |
+
old_img_coco,
|
| 220 |
+
new_img_coco,
|
| 221 |
+
str(out_path),
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
print(f" ✓ Visualizations saved to: {vis_dir}")
|
| 225 |
+
|
| 226 |
+
return {
|
| 227 |
+
"task": task_folder_name,
|
| 228 |
+
"old_metrics": old_metrics,
|
| 229 |
+
"new_metrics": new_metrics,
|
| 230 |
+
"old_annotations": len(old_coco["annotations"]),
|
| 231 |
+
"new_annotations": len(new_coco["annotations"]),
|
| 232 |
+
"gt_annotations": len(gt_coco["annotations"]),
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def main():
|
| 237 |
+
"""Run model comparison for all CVAT export tasks."""
|
| 238 |
+
print("=" * 70)
|
| 239 |
+
print("MODEL COMPARISON ON CVAT EXPORT DATASETS")
|
| 240 |
+
print("=" * 70)
|
| 241 |
+
|
| 242 |
+
# Discover all task folders
|
| 243 |
+
print(f"\nDiscovering task folders in: {CVAT_EXPORT_DIR}")
|
| 244 |
+
task_folders = discover_task_folders(CVAT_EXPORT_DIR)
|
| 245 |
+
|
| 246 |
+
if not task_folders:
|
| 247 |
+
print("❌ No task folders found!")
|
| 248 |
+
return
|
| 249 |
+
|
| 250 |
+
print(f"\nFound {len(task_folders)} task folders:")
|
| 251 |
+
for folder in task_folders:
|
| 252 |
+
print(f" - {folder}")
|
| 253 |
+
|
| 254 |
+
results = []
|
| 255 |
+
|
| 256 |
+
for task_folder_name in task_folders:
|
| 257 |
+
result = process_cvat_task(task_folder_name)
|
| 258 |
+
if result:
|
| 259 |
+
results.append(result)
|
| 260 |
+
|
| 261 |
+
# Print summary
|
| 262 |
+
print("\n" + "=" * 70)
|
| 263 |
+
print("SUMMARY")
|
| 264 |
+
print("=" * 70)
|
| 265 |
+
|
| 266 |
+
for r in results:
|
| 267 |
+
print(f"\n{r['task']}:")
|
| 268 |
+
print(f" Ground Truth: {r['gt_annotations']} annotations")
|
| 269 |
+
print(f" Old Models: {r['old_annotations']} annotations")
|
| 270 |
+
print(f" mAP@50: {r['old_metrics'].get('mAP@50', 0):.4f}")
|
| 271 |
+
print(f" Precision: {r['old_metrics'].get('Precision', 0):.4f}")
|
| 272 |
+
print(f" Recall: {r['old_metrics'].get('Recall', 0):.4f}")
|
| 273 |
+
print(f" New Models: {r['new_annotations']} annotations")
|
| 274 |
+
print(f" mAP@50: {r['new_metrics'].get('mAP@50', 0):.4f}")
|
| 275 |
+
print(f" Precision: {r['new_metrics'].get('Precision', 0):.4f}")
|
| 276 |
+
print(f" Recall: {r['new_metrics'].get('Recall', 0):.4f}")
|
| 277 |
+
|
| 278 |
+
# Save summary
|
| 279 |
+
summary_path = Path(SCRIPT_DIR) / "cvat_export_model_comparison_summary.json"
|
| 280 |
+
with open(summary_path, "w") as f:
|
| 281 |
+
json.dump(results, f, indent=4)
|
| 282 |
+
|
| 283 |
+
print(f"\n✓ Summary saved to: {summary_path}")
|
| 284 |
+
print("\n" + "=" * 70)
|
| 285 |
+
print("COMPLETE!")
|
| 286 |
+
print("=" * 70)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
if __name__ == "__main__":
|
| 290 |
+
main()
|
| 291 |
+
|
compare/data/batch_model_comparison_test_2025.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Batch model comparison for all expert-annotated datasets.
|
| 3 |
+
|
| 4 |
+
Datasets:
|
| 5 |
+
- Aleyna 1 (2024)
|
| 6 |
+
- Annika 2 (2024)
|
| 7 |
+
- Luise 1 (2024)
|
| 8 |
+
- Luise 2 (2024)
|
| 9 |
+
- Nuray 1 (2024)
|
| 10 |
+
- Nuray 2 (2024)
|
| 11 |
+
|
| 12 |
+
For each folder (e.g. "Aleyna 1 (2024)"):
|
| 13 |
+
- Uses the existing `ground_truth_coco.json`
|
| 14 |
+
- Runs OLD models and NEW models on all images in `Images/`
|
| 15 |
+
- Calculates detection/segmentation metrics vs ground truth
|
| 16 |
+
- Creates side‑by‑side visualizations:
|
| 17 |
+
Ground Truth | Old Models | New Models
|
| 18 |
+
- Saves everything under `<folder>/model_comparison/`
|
| 19 |
+
"""
|
| 20 |
+
import os
|
| 21 |
+
import sys
|
| 22 |
+
import json
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
|
| 25 |
+
import matplotlib.pyplot as plt
|
| 26 |
+
|
| 27 |
+
# Paths
|
| 28 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 29 |
+
PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR))
|
| 30 |
+
sys.path.insert(0, SCRIPT_DIR)
|
| 31 |
+
sys.path.insert(0, PROJECT_ROOT)
|
| 32 |
+
|
| 33 |
+
from old_models import process_dataset as process_old_models
|
| 34 |
+
from new_models import process_dataset as process_new_models
|
| 35 |
+
from compare import calculate_metrics, align_categories, draw_coco_annotations_simple
|
| 36 |
+
|
| 37 |
+
# Re‑use the same dataset list as in batch_process_all_datasets.py
|
| 38 |
+
DATASET_FOLDERS = [
|
| 39 |
+
"Aleyna 1 (2024)",
|
| 40 |
+
"Annika 2 (2024)",
|
| 41 |
+
"Luise 1 (2024)",
|
| 42 |
+
"Luise 2 (2024)",
|
| 43 |
+
"Nuray 1 (2024)",
|
| 44 |
+
"Nuray 2 (2024)",
|
| 45 |
+
]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def create_side_by_side_visualization(image_path, gt_coco, old_coco, new_coco, output_path):
|
| 49 |
+
"""
|
| 50 |
+
Create side‑by‑side visualization: GT | Old Models | New Models
|
| 51 |
+
"""
|
| 52 |
+
fig, axes = plt.subplots(1, 3, figsize=(30, 10))
|
| 53 |
+
|
| 54 |
+
# Left: Ground Truth
|
| 55 |
+
draw_coco_annotations_simple(image_path, gt_coco, "Ground Truth", axes[0])
|
| 56 |
+
|
| 57 |
+
# Middle: Old Models
|
| 58 |
+
draw_coco_annotations_simple(image_path, old_coco, "Old Models", axes[1])
|
| 59 |
+
|
| 60 |
+
# Right: New Models
|
| 61 |
+
draw_coco_annotations_simple(image_path, new_coco, "New Models", axes[2])
|
| 62 |
+
|
| 63 |
+
plt.tight_layout()
|
| 64 |
+
plt.savefig(output_path, dpi=150, bbox_inches="tight")
|
| 65 |
+
plt.close()
|
| 66 |
+
print(f" ✓ Saved comparison to: {output_path}")
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def process_expert_dataset(folder_name, base_dir=None):
|
| 70 |
+
"""
|
| 71 |
+
Process a single expert dataset:
|
| 72 |
+
- Load ground_truth_coco.json
|
| 73 |
+
- Run old & new models
|
| 74 |
+
- Compute metrics
|
| 75 |
+
- Create GT | Old | New visualizations
|
| 76 |
+
"""
|
| 77 |
+
if base_dir is None:
|
| 78 |
+
base_dir = SCRIPT_DIR
|
| 79 |
+
|
| 80 |
+
folder_path = Path(base_dir) / folder_name
|
| 81 |
+
|
| 82 |
+
if not folder_path.exists():
|
| 83 |
+
print(f"⚠️ Warning: Folder not found: {folder_path}")
|
| 84 |
+
return None
|
| 85 |
+
|
| 86 |
+
print("\n" + "=" * 70)
|
| 87 |
+
print(f"Processing expert dataset: {folder_name}")
|
| 88 |
+
print("=" * 70)
|
| 89 |
+
|
| 90 |
+
# Paths
|
| 91 |
+
gt_json_path = folder_path / "ground_truth_coco.json"
|
| 92 |
+
images_dir = folder_path / "Images"
|
| 93 |
+
output_dir = folder_path / "model_comparison"
|
| 94 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 95 |
+
|
| 96 |
+
if not gt_json_path.exists():
|
| 97 |
+
print(f"❌ Error: ground_truth_coco.json not found at {gt_json_path}")
|
| 98 |
+
return None
|
| 99 |
+
|
| 100 |
+
if not images_dir.exists():
|
| 101 |
+
print(f"❌ Error: Images directory not found at {images_dir}")
|
| 102 |
+
return None
|
| 103 |
+
|
| 104 |
+
# Load ground truth
|
| 105 |
+
print(f"\n[1/5] Loading ground truth...")
|
| 106 |
+
with open(gt_json_path, "r") as f:
|
| 107 |
+
gt_coco = json.load(f)
|
| 108 |
+
|
| 109 |
+
print(f" ✓ Loaded {len(gt_coco['images'])} images")
|
| 110 |
+
print(f" ✓ Loaded {len(gt_coco['annotations'])} annotations")
|
| 111 |
+
|
| 112 |
+
# Run old models
|
| 113 |
+
print(f"\n[2/5] Running old models...")
|
| 114 |
+
old_output_dir = output_dir / "old_models"
|
| 115 |
+
os.makedirs(old_output_dir, exist_ok=True)
|
| 116 |
+
|
| 117 |
+
try:
|
| 118 |
+
old_coco = process_old_models(str(images_dir), str(old_output_dir))
|
| 119 |
+
print(f" ✓ Generated {len(old_coco['annotations'])} annotations")
|
| 120 |
+
print(f" ✓ Categories: {[c['name'] for c in old_coco['categories']]}")
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print(f" ❌ Error running old models: {e}")
|
| 123 |
+
import traceback
|
| 124 |
+
|
| 125 |
+
traceback.print_exc()
|
| 126 |
+
return None
|
| 127 |
+
|
| 128 |
+
# Run new models
|
| 129 |
+
print(f"\n[3/5] Running new models...")
|
| 130 |
+
new_output_dir = output_dir / "new_models"
|
| 131 |
+
os.makedirs(new_output_dir, exist_ok=True)
|
| 132 |
+
|
| 133 |
+
try:
|
| 134 |
+
new_coco = process_new_models(str(images_dir), str(new_output_dir))
|
| 135 |
+
print(f" ✓ Generated {len(new_coco['annotations'])} annotations")
|
| 136 |
+
except Exception as e:
|
| 137 |
+
print(f" ❌ Error running new models: {e}")
|
| 138 |
+
import traceback
|
| 139 |
+
|
| 140 |
+
traceback.print_exc()
|
| 141 |
+
return None
|
| 142 |
+
|
| 143 |
+
# Calculate metrics
|
| 144 |
+
print(f"\n[4/5] Calculating metrics...")
|
| 145 |
+
|
| 146 |
+
# Align categories with ground truth (by name matching)
|
| 147 |
+
old_coco_aligned = align_categories(gt_coco.copy(), old_coco.copy())
|
| 148 |
+
new_coco_aligned = align_categories(gt_coco.copy(), new_coco.copy())
|
| 149 |
+
|
| 150 |
+
# Metrics for old models
|
| 151 |
+
print(f"\n Old Models Metrics:")
|
| 152 |
+
old_metrics = calculate_metrics(gt_coco, old_coco_aligned, str(output_dir))
|
| 153 |
+
print(f" mAP@50: {old_metrics.get('mAP@50', 0):.4f}")
|
| 154 |
+
print(f" mAP@[.50:.95]: {old_metrics.get('mAP@[.50:.95]', 0):.4f}")
|
| 155 |
+
print(f" Precision: {old_metrics.get('Precision', 0):.4f}")
|
| 156 |
+
print(f" Recall: {old_metrics.get('Recall', 0):.4f}")
|
| 157 |
+
|
| 158 |
+
# Metrics for new models
|
| 159 |
+
print(f"\n New Models Metrics:")
|
| 160 |
+
new_metrics = calculate_metrics(gt_coco, new_coco_aligned, str(output_dir))
|
| 161 |
+
print(f" mAP@50: {new_metrics.get('mAP@50', 0):.4f}")
|
| 162 |
+
print(f" mAP@[.50:.95]: {new_metrics.get('mAP@[.50:.95]', 0):.4f}")
|
| 163 |
+
print(f" Precision: {new_metrics.get('Precision', 0):.4f}")
|
| 164 |
+
print(f" Recall: {new_metrics.get('Recall', 0):.4f}")
|
| 165 |
+
|
| 166 |
+
# Save metrics JSON
|
| 167 |
+
metrics_path = output_dir / "metrics.json"
|
| 168 |
+
with open(metrics_path, "w") as f:
|
| 169 |
+
json.dump({"old_models": old_metrics, "new_models": new_metrics}, f, indent=4)
|
| 170 |
+
print(f" ✓ Saved metrics to: {metrics_path}")
|
| 171 |
+
|
| 172 |
+
# Create visualizations
|
| 173 |
+
print(f"\n[5/5] Creating side-by-side visualizations...")
|
| 174 |
+
vis_dir = output_dir / "visualizations"
|
| 175 |
+
os.makedirs(vis_dir, exist_ok=True)
|
| 176 |
+
|
| 177 |
+
for img_info in gt_coco["images"]:
|
| 178 |
+
image_name = img_info["file_name"]
|
| 179 |
+
image_path = images_dir / image_name
|
| 180 |
+
|
| 181 |
+
if not image_path.exists():
|
| 182 |
+
continue
|
| 183 |
+
|
| 184 |
+
img_id = img_info["id"]
|
| 185 |
+
|
| 186 |
+
# Filter annotations for this image
|
| 187 |
+
gt_img_coco = {
|
| 188 |
+
"images": [img_info],
|
| 189 |
+
"annotations": [a for a in gt_coco["annotations"] if a["image_id"] == img_id],
|
| 190 |
+
"categories": gt_coco["categories"],
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
old_img_coco = {
|
| 194 |
+
"images": [img_info],
|
| 195 |
+
"annotations": [a for a in old_coco["annotations"] if a["image_id"] == img_id],
|
| 196 |
+
"categories": old_coco["categories"],
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
new_img_coco = {
|
| 200 |
+
"images": [img_info],
|
| 201 |
+
"annotations": [a for a in new_coco["annotations"] if a["image_id"] == img_id],
|
| 202 |
+
"categories": new_coco["categories"],
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
out_path = vis_dir / f"{Path(image_name).stem}_comparison.png"
|
| 206 |
+
create_side_by_side_visualization(
|
| 207 |
+
str(image_path),
|
| 208 |
+
gt_img_coco,
|
| 209 |
+
old_img_coco,
|
| 210 |
+
new_img_coco,
|
| 211 |
+
str(out_path),
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
print(f" ✓ Visualizations saved to: {vis_dir}")
|
| 215 |
+
|
| 216 |
+
return {
|
| 217 |
+
"folder": folder_name,
|
| 218 |
+
"old_metrics": old_metrics,
|
| 219 |
+
"new_metrics": new_metrics,
|
| 220 |
+
"old_annotations": len(old_coco["annotations"]),
|
| 221 |
+
"new_annotations": len(new_coco["annotations"]),
|
| 222 |
+
"gt_annotations": len(gt_coco["annotations"]),
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def main():
|
| 227 |
+
"""Run model comparison for all expert datasets."""
|
| 228 |
+
print("=" * 70)
|
| 229 |
+
print("MODEL COMPARISON ON EXPERT DATASETS")
|
| 230 |
+
print("=" * 70)
|
| 231 |
+
print(f"\nProcessing {len(DATASET_FOLDERS)} folders:")
|
| 232 |
+
for folder in DATASET_FOLDERS:
|
| 233 |
+
print(f" - {folder}")
|
| 234 |
+
|
| 235 |
+
results = []
|
| 236 |
+
|
| 237 |
+
for folder_name in DATASET_FOLDERS:
|
| 238 |
+
result = process_expert_dataset(folder_name)
|
| 239 |
+
if result:
|
| 240 |
+
results.append(result)
|
| 241 |
+
|
| 242 |
+
# Print summary
|
| 243 |
+
print("\n" + "=" * 70)
|
| 244 |
+
print("SUMMARY")
|
| 245 |
+
print("=" * 70)
|
| 246 |
+
|
| 247 |
+
for r in results:
|
| 248 |
+
print(f"\n{r['folder']}:")
|
| 249 |
+
print(f" Ground Truth: {r['gt_annotations']} annotations")
|
| 250 |
+
print(f" Old Models: {r['old_annotations']} annotations")
|
| 251 |
+
print(f" mAP@50: {r['old_metrics'].get('mAP@50', 0):.4f}")
|
| 252 |
+
print(f" Precision: {r['old_metrics'].get('Precision', 0):.4f}")
|
| 253 |
+
print(f" Recall: {r['old_metrics'].get('Recall', 0):.4f}")
|
| 254 |
+
print(f" New Models: {r['new_annotations']} annotations")
|
| 255 |
+
print(f" mAP@50: {r['new_metrics'].get('mAP@50', 0):.4f}")
|
| 256 |
+
print(f" Precision: {r['new_metrics'].get('Precision', 0):.4f}")
|
| 257 |
+
print(f" Recall: {r['new_metrics'].get('Recall', 0):.4f}")
|
| 258 |
+
|
| 259 |
+
# Save summary
|
| 260 |
+
summary_path = Path(SCRIPT_DIR) / "expert_datasets_model_comparison_summary.json"
|
| 261 |
+
with open(summary_path, "w") as f:
|
| 262 |
+
json.dump(results, f, indent=4)
|
| 263 |
+
|
| 264 |
+
print(f"\n✓ Summary saved to: {summary_path}")
|
| 265 |
+
print("\n" + "=" * 70)
|
| 266 |
+
print("COMPLETE!")
|
| 267 |
+
print("=" * 70)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
if __name__ == "__main__":
|
| 271 |
+
main()
|
| 272 |
+
|
| 273 |
+
|
compare/data/batch_process_all_datasets.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Batch process all datasets: Convert XML to COCO and create visualizations.
|
| 3 |
+
|
| 4 |
+
Processes all folders:
|
| 5 |
+
- Aleyna 1 (2024)
|
| 6 |
+
- Annika 2 (2024)
|
| 7 |
+
- Luise 1 (2024)
|
| 8 |
+
- Luise 2 (2024)
|
| 9 |
+
- Nuray 1 (2024)
|
| 10 |
+
- Nuray 2 (2024)
|
| 11 |
+
|
| 12 |
+
For each folder:
|
| 13 |
+
1. Converts XML annotations to COCO format
|
| 14 |
+
2. Creates visualizations of annotations on images
|
| 15 |
+
3. Saves outputs inside each folder
|
| 16 |
+
"""
|
| 17 |
+
import os
|
| 18 |
+
import sys
|
| 19 |
+
import json
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
# Add current directory to path
|
| 23 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 24 |
+
sys.path.insert(0, SCRIPT_DIR)
|
| 25 |
+
|
| 26 |
+
from original_annotations import load_ground_truth
|
| 27 |
+
from visualize_ground_truth import visualize_all_images
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# List of all dataset folders to process
|
| 31 |
+
DATASET_FOLDERS = [
|
| 32 |
+
"Aleyna 1 (2024)",
|
| 33 |
+
"Annika 2 (2024)",
|
| 34 |
+
"Luise 1 (2024)",
|
| 35 |
+
"Luise 2 (2024)",
|
| 36 |
+
"Nuray 1 (2024)",
|
| 37 |
+
"Nuray 2 (2024)",
|
| 38 |
+
]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def process_dataset(folder_name, base_dir=None):
|
| 42 |
+
"""
|
| 43 |
+
Process a single dataset folder.
|
| 44 |
+
|
| 45 |
+
Args:
|
| 46 |
+
folder_name: Name of the dataset folder
|
| 47 |
+
base_dir: Base directory containing the dataset folders (default: SCRIPT_DIR)
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
dict with processing results
|
| 51 |
+
"""
|
| 52 |
+
if base_dir is None:
|
| 53 |
+
base_dir = SCRIPT_DIR
|
| 54 |
+
|
| 55 |
+
folder_path = Path(base_dir) / folder_name
|
| 56 |
+
|
| 57 |
+
if not folder_path.exists():
|
| 58 |
+
print(f"⚠️ Warning: Folder not found: {folder_path}")
|
| 59 |
+
return {
|
| 60 |
+
"folder": folder_name,
|
| 61 |
+
"status": "not_found",
|
| 62 |
+
"images": 0,
|
| 63 |
+
"annotations": 0
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
print("\n" + "=" * 70)
|
| 67 |
+
print(f"Processing: {folder_name}")
|
| 68 |
+
print("=" * 70)
|
| 69 |
+
|
| 70 |
+
# Paths
|
| 71 |
+
xml_path = folder_path / "Annotations" / "annotations.xml"
|
| 72 |
+
images_dir = folder_path / "Images"
|
| 73 |
+
|
| 74 |
+
# Check if required files/directories exist
|
| 75 |
+
if not xml_path.exists():
|
| 76 |
+
print(f"⚠️ Warning: XML file not found: {xml_path}")
|
| 77 |
+
return {
|
| 78 |
+
"folder": folder_name,
|
| 79 |
+
"status": "no_xml",
|
| 80 |
+
"images": 0,
|
| 81 |
+
"annotations": 0
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
if not images_dir.exists():
|
| 85 |
+
print(f"⚠️ Warning: Images directory not found: {images_dir}")
|
| 86 |
+
return {
|
| 87 |
+
"folder": folder_name,
|
| 88 |
+
"status": "no_images",
|
| 89 |
+
"images": 0,
|
| 90 |
+
"annotations": 0
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
# Step 1: Convert XML to COCO
|
| 94 |
+
print(f"\n[Step 1/2] Converting XML to COCO format...")
|
| 95 |
+
print(f" XML: {xml_path}")
|
| 96 |
+
print(f" Images: {images_dir}")
|
| 97 |
+
|
| 98 |
+
try:
|
| 99 |
+
coco_json = load_ground_truth(str(xml_path), str(images_dir))
|
| 100 |
+
|
| 101 |
+
if not coco_json:
|
| 102 |
+
print(f"❌ Error: Failed to parse XML")
|
| 103 |
+
return {
|
| 104 |
+
"folder": folder_name,
|
| 105 |
+
"status": "parse_error",
|
| 106 |
+
"images": 0,
|
| 107 |
+
"annotations": 0
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
num_images = len(coco_json["images"])
|
| 111 |
+
num_annotations = len(coco_json["annotations"])
|
| 112 |
+
|
| 113 |
+
print(f" ✓ Loaded {num_images} images")
|
| 114 |
+
print(f" ✓ Loaded {num_annotations} annotations")
|
| 115 |
+
print(f" ✓ Categories: {len(coco_json['categories'])}")
|
| 116 |
+
|
| 117 |
+
# Save COCO JSON inside the dataset folder
|
| 118 |
+
coco_output_path = folder_path / "ground_truth_coco.json"
|
| 119 |
+
with open(coco_output_path, 'w') as f:
|
| 120 |
+
json.dump(coco_json, f, indent=4)
|
| 121 |
+
print(f" ✓ Saved COCO JSON to: {coco_output_path}")
|
| 122 |
+
|
| 123 |
+
except Exception as e:
|
| 124 |
+
print(f"❌ Error converting XML to COCO: {e}")
|
| 125 |
+
import traceback
|
| 126 |
+
traceback.print_exc()
|
| 127 |
+
return {
|
| 128 |
+
"folder": folder_name,
|
| 129 |
+
"status": "conversion_error",
|
| 130 |
+
"error": str(e),
|
| 131 |
+
"images": 0,
|
| 132 |
+
"annotations": 0
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
# Step 2: Create visualizations
|
| 136 |
+
print(f"\n[Step 2/2] Creating visualizations...")
|
| 137 |
+
|
| 138 |
+
try:
|
| 139 |
+
# Create visualizations directory inside the dataset folder
|
| 140 |
+
vis_output_dir = folder_path / "visualizations"
|
| 141 |
+
|
| 142 |
+
visualize_all_images(coco_json, str(images_dir), str(vis_output_dir))
|
| 143 |
+
|
| 144 |
+
print(f" ✓ Visualizations saved to: {vis_output_dir}")
|
| 145 |
+
|
| 146 |
+
except Exception as e:
|
| 147 |
+
print(f"⚠️ Warning: Error creating visualizations: {e}")
|
| 148 |
+
import traceback
|
| 149 |
+
traceback.print_exc()
|
| 150 |
+
# Don't fail the whole process if visualization fails
|
| 151 |
+
|
| 152 |
+
return {
|
| 153 |
+
"folder": folder_name,
|
| 154 |
+
"status": "success",
|
| 155 |
+
"images": num_images,
|
| 156 |
+
"annotations": num_annotations,
|
| 157 |
+
"categories": len(coco_json["categories"]),
|
| 158 |
+
"coco_json_path": str(coco_output_path),
|
| 159 |
+
"visualizations_path": str(vis_output_dir)
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def main():
|
| 164 |
+
"""Main function to process all datasets."""
|
| 165 |
+
print("=" * 70)
|
| 166 |
+
print("BATCH PROCESSING: XML to COCO Conversion & Visualization")
|
| 167 |
+
print("=" * 70)
|
| 168 |
+
print(f"\nProcessing {len(DATASET_FOLDERS)} datasets:")
|
| 169 |
+
for folder in DATASET_FOLDERS:
|
| 170 |
+
print(f" - {folder}")
|
| 171 |
+
|
| 172 |
+
results = []
|
| 173 |
+
|
| 174 |
+
for folder_name in DATASET_FOLDERS:
|
| 175 |
+
result = process_dataset(folder_name)
|
| 176 |
+
results.append(result)
|
| 177 |
+
|
| 178 |
+
# Print summary
|
| 179 |
+
print("\n" + "=" * 70)
|
| 180 |
+
print("PROCESSING SUMMARY")
|
| 181 |
+
print("=" * 70)
|
| 182 |
+
|
| 183 |
+
successful = [r for r in results if r["status"] == "success"]
|
| 184 |
+
failed = [r for r in results if r["status"] != "success"]
|
| 185 |
+
|
| 186 |
+
print(f"\n✓ Successfully processed: {len(successful)}/{len(results)}")
|
| 187 |
+
for r in successful:
|
| 188 |
+
print(f" - {r['folder']}: {r['images']} images, {r['annotations']} annotations")
|
| 189 |
+
|
| 190 |
+
if failed:
|
| 191 |
+
print(f"\n⚠️ Failed/Skipped: {len(failed)}/{len(results)}")
|
| 192 |
+
for r in failed:
|
| 193 |
+
print(f" - {r['folder']}: {r['status']}")
|
| 194 |
+
|
| 195 |
+
# Save summary to JSON
|
| 196 |
+
summary_path = Path(SCRIPT_DIR) / "processing_summary.json"
|
| 197 |
+
with open(summary_path, 'w') as f:
|
| 198 |
+
json.dump({
|
| 199 |
+
"total_datasets": len(DATASET_FOLDERS),
|
| 200 |
+
"successful": len(successful),
|
| 201 |
+
"failed": len(failed),
|
| 202 |
+
"results": results
|
| 203 |
+
}, f, indent=4)
|
| 204 |
+
|
| 205 |
+
print(f"\n✓ Summary saved to: {summary_path}")
|
| 206 |
+
print("\n" + "=" * 70)
|
| 207 |
+
print("BATCH PROCESSING COMPLETE!")
|
| 208 |
+
print("=" * 70)
|
| 209 |
+
print("\nEach dataset folder now contains:")
|
| 210 |
+
print(" - ground_truth_coco.json (COCO format annotations)")
|
| 211 |
+
print(" - visualizations/ (annotated images)")
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
if __name__ == "__main__":
|
| 215 |
+
main()
|
| 216 |
+
|
compare/data/compare.py
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Main comparison script: Compare old models vs new models vs ground truth.
|
| 3 |
+
Calculates mAP@50, mAP@[.50:.95], Precision, Recall.
|
| 4 |
+
Creates side-by-side visualization.
|
| 5 |
+
"""
|
| 6 |
+
import os
|
| 7 |
+
import json
|
| 8 |
+
import sys
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
import numpy as np
|
| 11 |
+
from PIL import Image
|
| 12 |
+
import matplotlib.pyplot as plt
|
| 13 |
+
import matplotlib.patches as patches
|
| 14 |
+
import matplotlib.colors as mcolors
|
| 15 |
+
try:
|
| 16 |
+
from pycocotools.coco import COCO
|
| 17 |
+
from pycocotools.cocoeval import COCOeval
|
| 18 |
+
HAS_PYCOCOTOOLS = True
|
| 19 |
+
except ImportError:
|
| 20 |
+
HAS_PYCOCOTOOLS = False
|
| 21 |
+
print("Warning: pycocotools not available. Metrics calculation will be limited.")
|
| 22 |
+
COCO = None
|
| 23 |
+
COCOeval = None
|
| 24 |
+
|
| 25 |
+
import tempfile
|
| 26 |
+
|
| 27 |
+
# Add project root to path
|
| 28 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 29 |
+
PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR))
|
| 30 |
+
sys.path.insert(0, PROJECT_ROOT)
|
| 31 |
+
sys.path.insert(0, SCRIPT_DIR)
|
| 32 |
+
|
| 33 |
+
from original_annotations import load_ground_truth
|
| 34 |
+
from old_models import process_dataset as process_old_models
|
| 35 |
+
from new_models import process_dataset as process_new_models
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def draw_coco_annotations_simple(image_path, coco_json, title="", ax=None):
|
| 39 |
+
"""
|
| 40 |
+
Draw COCO annotations on image (simpler version for comparison).
|
| 41 |
+
"""
|
| 42 |
+
if ax is None:
|
| 43 |
+
fig, ax = plt.subplots(1, 1, figsize=(10, 14))
|
| 44 |
+
|
| 45 |
+
img = Image.open(image_path).convert("RGB")
|
| 46 |
+
ax.imshow(img)
|
| 47 |
+
ax.set_title(title, fontsize=14, fontweight='bold')
|
| 48 |
+
ax.axis("off")
|
| 49 |
+
|
| 50 |
+
if not coco_json.get("images"):
|
| 51 |
+
return ax
|
| 52 |
+
|
| 53 |
+
img_info = coco_json["images"][0]
|
| 54 |
+
img_id = img_info["id"]
|
| 55 |
+
anns = [a for a in coco_json["annotations"] if a["image_id"] == img_id]
|
| 56 |
+
|
| 57 |
+
id_to_name = {c["id"]: c["name"] for c in coco_json["categories"]}
|
| 58 |
+
|
| 59 |
+
# Color map
|
| 60 |
+
colors = plt.cm.tab20(np.linspace(0, 1, 20))
|
| 61 |
+
color_map = {}
|
| 62 |
+
|
| 63 |
+
# Track label positions to avoid overlap
|
| 64 |
+
placed_labels = []
|
| 65 |
+
|
| 66 |
+
def find_label_position(bbox, text_width, text_height, image_width, image_height):
|
| 67 |
+
"""Find a good position for label to avoid overlap."""
|
| 68 |
+
x, y, w, h = bbox
|
| 69 |
+
candidates = [
|
| 70 |
+
(x, y - text_height - 5), # Above top-left
|
| 71 |
+
(x, y), # Top-left corner
|
| 72 |
+
(x + w - text_width, y), # Top-right corner
|
| 73 |
+
(x, y + h + 5), # Below bottom-left
|
| 74 |
+
]
|
| 75 |
+
|
| 76 |
+
for pos_x, pos_y in candidates:
|
| 77 |
+
# Check if position is within image bounds
|
| 78 |
+
if pos_x < 0 or pos_y < 0 or pos_x + text_width > image_width or pos_y + text_height > image_height:
|
| 79 |
+
continue
|
| 80 |
+
|
| 81 |
+
# Check overlap with existing labels
|
| 82 |
+
overlap = False
|
| 83 |
+
for placed_x, placed_y, placed_w, placed_h in placed_labels:
|
| 84 |
+
if not (pos_x + text_width < placed_x or pos_x > placed_x + placed_w or
|
| 85 |
+
pos_y + text_height < placed_y or pos_y > placed_y + placed_h):
|
| 86 |
+
overlap = True
|
| 87 |
+
break
|
| 88 |
+
|
| 89 |
+
if not overlap:
|
| 90 |
+
return pos_x, pos_y
|
| 91 |
+
|
| 92 |
+
# If all positions overlap, use top-left anyway
|
| 93 |
+
return x, y
|
| 94 |
+
|
| 95 |
+
img_width, img_height = img.size
|
| 96 |
+
|
| 97 |
+
for ann in anns:
|
| 98 |
+
name = id_to_name.get(ann["category_id"], f"cls_{ann['category_id']}")
|
| 99 |
+
|
| 100 |
+
# Get or assign color
|
| 101 |
+
if name not in color_map:
|
| 102 |
+
color_idx = len(color_map) % len(colors)
|
| 103 |
+
color_map[name] = colors[color_idx]
|
| 104 |
+
|
| 105 |
+
color = color_map[name]
|
| 106 |
+
|
| 107 |
+
# Get bbox for label positioning
|
| 108 |
+
bbox = ann.get("bbox", [0, 0, 0, 0])
|
| 109 |
+
if not bbox or len(bbox) < 4:
|
| 110 |
+
# Try to get bbox from segmentation
|
| 111 |
+
segs = ann.get("segmentation", [])
|
| 112 |
+
if segs and isinstance(segs, list) and len(segs) > 0:
|
| 113 |
+
if isinstance(segs[0], list) and len(segs[0]) >= 6:
|
| 114 |
+
coords = segs[0]
|
| 115 |
+
xs = coords[0::2]
|
| 116 |
+
ys = coords[1::2]
|
| 117 |
+
bbox = [min(xs), min(ys), max(xs) - min(xs), max(ys) - min(ys)]
|
| 118 |
+
else:
|
| 119 |
+
continue
|
| 120 |
+
else:
|
| 121 |
+
continue
|
| 122 |
+
|
| 123 |
+
x, y, w, h = bbox
|
| 124 |
+
|
| 125 |
+
# Draw segmentation or bbox
|
| 126 |
+
segs = ann.get("segmentation", [])
|
| 127 |
+
if segs and isinstance(segs, list) and len(segs) > 0:
|
| 128 |
+
if isinstance(segs[0], list) and len(segs[0]) >= 6:
|
| 129 |
+
# Polygon
|
| 130 |
+
coords = segs[0]
|
| 131 |
+
xs = coords[0::2]
|
| 132 |
+
ys = coords[1::2]
|
| 133 |
+
poly = patches.Polygon(
|
| 134 |
+
list(zip(xs, ys)),
|
| 135 |
+
closed=True,
|
| 136 |
+
edgecolor=color,
|
| 137 |
+
facecolor=color,
|
| 138 |
+
linewidth=2,
|
| 139 |
+
alpha=0.3,
|
| 140 |
+
)
|
| 141 |
+
ax.add_patch(poly)
|
| 142 |
+
# Edge
|
| 143 |
+
poly_edge = patches.Polygon(
|
| 144 |
+
list(zip(xs, ys)),
|
| 145 |
+
closed=True,
|
| 146 |
+
edgecolor=color,
|
| 147 |
+
facecolor="none",
|
| 148 |
+
linewidth=2,
|
| 149 |
+
alpha=0.8,
|
| 150 |
+
)
|
| 151 |
+
ax.add_patch(poly_edge)
|
| 152 |
+
else:
|
| 153 |
+
# Bbox
|
| 154 |
+
rect = patches.Rectangle(
|
| 155 |
+
(x, y),
|
| 156 |
+
w,
|
| 157 |
+
h,
|
| 158 |
+
edgecolor=color,
|
| 159 |
+
facecolor=color,
|
| 160 |
+
linewidth=2,
|
| 161 |
+
alpha=0.3,
|
| 162 |
+
)
|
| 163 |
+
ax.add_patch(rect)
|
| 164 |
+
# Edge
|
| 165 |
+
rect_edge = patches.Rectangle(
|
| 166 |
+
(x, y),
|
| 167 |
+
w,
|
| 168 |
+
h,
|
| 169 |
+
edgecolor=color,
|
| 170 |
+
facecolor="none",
|
| 171 |
+
linewidth=2,
|
| 172 |
+
alpha=0.8,
|
| 173 |
+
)
|
| 174 |
+
ax.add_patch(rect_edge)
|
| 175 |
+
|
| 176 |
+
# Add label
|
| 177 |
+
# Estimate text size (approximate)
|
| 178 |
+
text_width = len(name) * 7 # Approximate character width
|
| 179 |
+
text_height = 12 # Approximate text height
|
| 180 |
+
|
| 181 |
+
label_x, label_y = find_label_position(bbox, text_width, text_height, img_width, img_height)
|
| 182 |
+
placed_labels.append((label_x, label_y, text_width, text_height))
|
| 183 |
+
|
| 184 |
+
# Draw label with background
|
| 185 |
+
# Convert color to RGB tuple if it's an array
|
| 186 |
+
if isinstance(color, np.ndarray):
|
| 187 |
+
edge_color = tuple(color[:3])
|
| 188 |
+
elif isinstance(color, (list, tuple)) and len(color) >= 3:
|
| 189 |
+
edge_color = tuple(color[:3])
|
| 190 |
+
else:
|
| 191 |
+
edge_color = color
|
| 192 |
+
|
| 193 |
+
ax.text(
|
| 194 |
+
label_x,
|
| 195 |
+
label_y,
|
| 196 |
+
name,
|
| 197 |
+
color='black',
|
| 198 |
+
fontsize=9,
|
| 199 |
+
fontweight='bold',
|
| 200 |
+
bbox=dict(
|
| 201 |
+
boxstyle="round,pad=0.3",
|
| 202 |
+
facecolor="white",
|
| 203 |
+
edgecolor=edge_color,
|
| 204 |
+
linewidth=2,
|
| 205 |
+
alpha=0.9,
|
| 206 |
+
),
|
| 207 |
+
zorder=10, # Ensure labels are on top
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
return ax
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def validate_and_fix_annotation(ann, img_width, img_height):
|
| 214 |
+
"""
|
| 215 |
+
Validate and fix annotation segmentation/bbox.
|
| 216 |
+
Converts bbox to polygon if segmentation is missing or invalid.
|
| 217 |
+
"""
|
| 218 |
+
segs = ann.get("segmentation", [])
|
| 219 |
+
bbox = ann.get("bbox", [0, 0, 0, 0])
|
| 220 |
+
|
| 221 |
+
# Check if segmentation is valid
|
| 222 |
+
has_valid_seg = False
|
| 223 |
+
if segs and isinstance(segs, list) and len(segs) > 0:
|
| 224 |
+
# Check if it's a polygon (list of coordinates)
|
| 225 |
+
if isinstance(segs[0], list) and len(segs[0]) >= 6:
|
| 226 |
+
# Valid polygon
|
| 227 |
+
has_valid_seg = True
|
| 228 |
+
# Check if it's RLE (dict)
|
| 229 |
+
elif isinstance(segs, dict) or (isinstance(segs, list) and len(segs) > 0 and isinstance(segs[0], dict)):
|
| 230 |
+
# RLE format - assume valid
|
| 231 |
+
has_valid_seg = True
|
| 232 |
+
|
| 233 |
+
# If no valid segmentation, create polygon from bbox
|
| 234 |
+
if not has_valid_seg and len(bbox) == 4 and bbox[2] > 0 and bbox[3] > 0:
|
| 235 |
+
x, y, w, h = bbox
|
| 236 |
+
# Create polygon from bbox: [x, y, x+w, y, x+w, y+h, x, y+h]
|
| 237 |
+
polygon = [x, y, x + w, y, x + w, y + h, x, y + h]
|
| 238 |
+
ann["segmentation"] = [polygon]
|
| 239 |
+
# Update area if needed
|
| 240 |
+
if ann.get("area", 0) == 0:
|
| 241 |
+
ann["area"] = w * h
|
| 242 |
+
has_valid_seg = True
|
| 243 |
+
|
| 244 |
+
return has_valid_seg
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def filter_valid_annotations(coco_dict):
|
| 248 |
+
"""
|
| 249 |
+
Filter out annotations with invalid segmentation/bbox.
|
| 250 |
+
Convert bbox-only annotations to polygon format.
|
| 251 |
+
"""
|
| 252 |
+
# Get image dimensions
|
| 253 |
+
img_id_to_size = {}
|
| 254 |
+
for img in coco_dict["images"]:
|
| 255 |
+
img_id_to_size[img["id"]] = (img["width"], img["height"])
|
| 256 |
+
|
| 257 |
+
valid_annotations = []
|
| 258 |
+
for ann in coco_dict["annotations"]:
|
| 259 |
+
img_id = ann["image_id"]
|
| 260 |
+
if img_id in img_id_to_size:
|
| 261 |
+
img_width, img_height = img_id_to_size[img_id]
|
| 262 |
+
if validate_and_fix_annotation(ann, img_width, img_height):
|
| 263 |
+
valid_annotations.append(ann)
|
| 264 |
+
|
| 265 |
+
coco_dict["annotations"] = valid_annotations
|
| 266 |
+
return coco_dict
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def calculate_metrics(gt_coco, pred_coco, output_dir):
|
| 270 |
+
"""
|
| 271 |
+
Calculate mAP@50, mAP@[.50:.95], Precision, Recall using pycocotools.
|
| 272 |
+
|
| 273 |
+
Args:
|
| 274 |
+
gt_coco: Ground truth COCO format dict
|
| 275 |
+
pred_coco: Predictions COCO format dict
|
| 276 |
+
output_dir: Directory to save results
|
| 277 |
+
|
| 278 |
+
Returns:
|
| 279 |
+
Dictionary with metrics
|
| 280 |
+
"""
|
| 281 |
+
if not HAS_PYCOCOTOOLS:
|
| 282 |
+
return {
|
| 283 |
+
'mAP@50': 0.0,
|
| 284 |
+
'mAP@[.50:.95]': 0.0,
|
| 285 |
+
'Precision': 0.0,
|
| 286 |
+
'Recall': 0.0,
|
| 287 |
+
'F1': 0.0,
|
| 288 |
+
'error': 'pycocotools not available'
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
# Filter and fix invalid annotations
|
| 292 |
+
gt_coco_clean = filter_valid_annotations(gt_coco.copy())
|
| 293 |
+
pred_coco_clean = filter_valid_annotations(pred_coco.copy())
|
| 294 |
+
|
| 295 |
+
if len(gt_coco_clean["annotations"]) == 0:
|
| 296 |
+
print("Warning: No valid ground truth annotations after filtering")
|
| 297 |
+
return {
|
| 298 |
+
'mAP@50': 0.0,
|
| 299 |
+
'mAP@[.50:.95]': 0.0,
|
| 300 |
+
'Precision': 0.0,
|
| 301 |
+
'Recall': 0.0,
|
| 302 |
+
'F1': 0.0,
|
| 303 |
+
'error': 'No valid GT annotations'
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
if len(pred_coco_clean["annotations"]) == 0:
|
| 307 |
+
print("Warning: No valid prediction annotations after filtering")
|
| 308 |
+
return {
|
| 309 |
+
'mAP@50': 0.0,
|
| 310 |
+
'mAP@[.50:.95]': 0.0,
|
| 311 |
+
'Precision': 0.0,
|
| 312 |
+
'Recall': 0.0,
|
| 313 |
+
'F1': 0.0,
|
| 314 |
+
'error': 'No valid prediction annotations'
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
# Save to temporary JSON files for pycocotools
|
| 318 |
+
gt_file = os.path.join(output_dir, "gt_temp.json")
|
| 319 |
+
pred_file = os.path.join(output_dir, "pred_temp.json")
|
| 320 |
+
|
| 321 |
+
with open(gt_file, 'w') as f:
|
| 322 |
+
json.dump(gt_coco_clean, f)
|
| 323 |
+
|
| 324 |
+
with open(pred_file, 'w') as f:
|
| 325 |
+
json.dump(pred_coco_clean, f)
|
| 326 |
+
|
| 327 |
+
# Load with pycocotools
|
| 328 |
+
try:
|
| 329 |
+
gt_coco_obj = COCO(gt_file)
|
| 330 |
+
pred_coco_obj = COCO(pred_file)
|
| 331 |
+
except Exception as e:
|
| 332 |
+
print(f"Error loading COCO files: {e}")
|
| 333 |
+
return {
|
| 334 |
+
'mAP@50': 0.0,
|
| 335 |
+
'mAP@[.50:.95]': 0.0,
|
| 336 |
+
'Precision': 0.0,
|
| 337 |
+
'Recall': 0.0,
|
| 338 |
+
'F1': 0.0,
|
| 339 |
+
'error': f'COCO load error: {str(e)}'
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
# Get all image IDs
|
| 343 |
+
img_ids = sorted(gt_coco_obj.getImgIds())
|
| 344 |
+
|
| 345 |
+
if len(img_ids) == 0:
|
| 346 |
+
return {
|
| 347 |
+
'mAP@50': 0.0,
|
| 348 |
+
'mAP@[.50:.95]': 0.0,
|
| 349 |
+
'Precision': 0.0,
|
| 350 |
+
'Recall': 0.0,
|
| 351 |
+
'F1': 0.0,
|
| 352 |
+
'error': 'No images in GT'
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
# Get all category IDs from ground truth
|
| 356 |
+
cat_ids = sorted(gt_coco_obj.getCatIds())
|
| 357 |
+
|
| 358 |
+
# Try segmentation evaluation first, fall back to bbox if it fails
|
| 359 |
+
eval_type = 'segm'
|
| 360 |
+
try:
|
| 361 |
+
coco_eval = COCOeval(gt_coco_obj, pred_coco_obj, eval_type)
|
| 362 |
+
coco_eval.params.imgIds = img_ids
|
| 363 |
+
coco_eval.params.catIds = cat_ids
|
| 364 |
+
coco_eval.evaluate()
|
| 365 |
+
coco_eval.accumulate()
|
| 366 |
+
coco_eval.summarize()
|
| 367 |
+
|
| 368 |
+
# Extract metrics
|
| 369 |
+
metrics = {
|
| 370 |
+
'mAP@50': float(coco_eval.stats[1]), # mAP@0.50
|
| 371 |
+
'mAP@[.50:.95]': float(coco_eval.stats[0]), # mAP@[.50:.95]
|
| 372 |
+
'mAP@75': float(coco_eval.stats[2]), # mAP@0.75
|
| 373 |
+
'mAP_small': float(coco_eval.stats[3]),
|
| 374 |
+
'mAP_medium': float(coco_eval.stats[4]),
|
| 375 |
+
'mAP_large': float(coco_eval.stats[5]),
|
| 376 |
+
'mAR_1': float(coco_eval.stats[6]),
|
| 377 |
+
'mAR_10': float(coco_eval.stats[7]),
|
| 378 |
+
'mAR_100': float(coco_eval.stats[8]),
|
| 379 |
+
'mAR_small': float(coco_eval.stats[9]),
|
| 380 |
+
'mAR_medium': float(coco_eval.stats[10]),
|
| 381 |
+
'mAR_large': float(coco_eval.stats[11]),
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
# Calculate Precision and Recall
|
| 385 |
+
precision = metrics['mAP@50'] # Approximate
|
| 386 |
+
recall = metrics['mAR_100'] # Maximum recall with 100 detections
|
| 387 |
+
|
| 388 |
+
metrics['Precision'] = precision
|
| 389 |
+
metrics['Recall'] = recall
|
| 390 |
+
metrics['F1'] = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
|
| 391 |
+
|
| 392 |
+
except Exception as e:
|
| 393 |
+
print(f"Error during {eval_type} evaluation: {e}")
|
| 394 |
+
# Try bbox evaluation as fallback
|
| 395 |
+
try:
|
| 396 |
+
print("Trying bbox evaluation as fallback...")
|
| 397 |
+
coco_eval = COCOeval(gt_coco_obj, pred_coco_obj, 'bbox')
|
| 398 |
+
coco_eval.params.imgIds = img_ids
|
| 399 |
+
coco_eval.params.catIds = cat_ids
|
| 400 |
+
coco_eval.evaluate()
|
| 401 |
+
coco_eval.accumulate()
|
| 402 |
+
coco_eval.summarize()
|
| 403 |
+
|
| 404 |
+
metrics = {
|
| 405 |
+
'mAP@50': float(coco_eval.stats[1]),
|
| 406 |
+
'mAP@[.50:.95]': float(coco_eval.stats[0]),
|
| 407 |
+
'mAP@75': float(coco_eval.stats[2]),
|
| 408 |
+
'mAP_small': float(coco_eval.stats[3]),
|
| 409 |
+
'mAP_medium': float(coco_eval.stats[4]),
|
| 410 |
+
'mAP_large': float(coco_eval.stats[5]),
|
| 411 |
+
'mAR_1': float(coco_eval.stats[6]),
|
| 412 |
+
'mAR_10': float(coco_eval.stats[7]),
|
| 413 |
+
'mAR_100': float(coco_eval.stats[8]),
|
| 414 |
+
'mAR_small': float(coco_eval.stats[9]),
|
| 415 |
+
'mAR_medium': float(coco_eval.stats[10]),
|
| 416 |
+
'mAR_large': float(coco_eval.stats[11]),
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
precision = metrics['mAP@50']
|
| 420 |
+
recall = metrics['mAR_100']
|
| 421 |
+
|
| 422 |
+
metrics['Precision'] = precision
|
| 423 |
+
metrics['Recall'] = recall
|
| 424 |
+
metrics['F1'] = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
|
| 425 |
+
metrics['eval_type'] = 'bbox' # Note that we used bbox evaluation
|
| 426 |
+
|
| 427 |
+
except Exception as e2:
|
| 428 |
+
print(f"Error during bbox evaluation: {e2}")
|
| 429 |
+
import traceback
|
| 430 |
+
traceback.print_exc()
|
| 431 |
+
metrics = {
|
| 432 |
+
'mAP@50': 0.0,
|
| 433 |
+
'mAP@[.50:.95]': 0.0,
|
| 434 |
+
'Precision': 0.0,
|
| 435 |
+
'Recall': 0.0,
|
| 436 |
+
'F1': 0.0,
|
| 437 |
+
'error': f'{eval_type} error: {str(e)}, bbox error: {str(e2)}'
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
return metrics
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
def create_comparison_visualization(image_path, gt_coco, old_coco, new_coco, output_path):
|
| 444 |
+
"""
|
| 445 |
+
Create side-by-side comparison: Original + GT | Old Models | New Models
|
| 446 |
+
"""
|
| 447 |
+
fig, axes = plt.subplots(1, 3, figsize=(30, 10))
|
| 448 |
+
|
| 449 |
+
# Left: Original image with ground truth
|
| 450 |
+
draw_coco_annotations_simple(image_path, gt_coco, "Ground Truth", axes[0])
|
| 451 |
+
|
| 452 |
+
# Middle: Old models
|
| 453 |
+
draw_coco_annotations_simple(image_path, old_coco, "Old Models", axes[1])
|
| 454 |
+
|
| 455 |
+
# Right: New models
|
| 456 |
+
draw_coco_annotations_simple(image_path, new_coco, "New Models", axes[2])
|
| 457 |
+
|
| 458 |
+
plt.tight_layout()
|
| 459 |
+
plt.savefig(output_path, dpi=150, bbox_inches='tight')
|
| 460 |
+
plt.close()
|
| 461 |
+
print(f"Saved comparison visualization to {output_path}")
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
def align_categories(gt_coco, pred_coco):
|
| 465 |
+
"""
|
| 466 |
+
Align category IDs between GT and predictions.
|
| 467 |
+
Maps prediction categories to GT categories by name.
|
| 468 |
+
"""
|
| 469 |
+
# Create name to ID maps
|
| 470 |
+
gt_name_to_id = {c["name"]: c["id"] for c in gt_coco["categories"]}
|
| 471 |
+
pred_name_to_id = {c["name"]: c["id"] for c in pred_coco["categories"]}
|
| 472 |
+
|
| 473 |
+
# Create mapping from pred category ID to GT category ID
|
| 474 |
+
pred_to_gt_map = {}
|
| 475 |
+
for pred_name, pred_id in pred_name_to_id.items():
|
| 476 |
+
if pred_name in gt_name_to_id:
|
| 477 |
+
pred_to_gt_map[pred_id] = gt_name_to_id[pred_name]
|
| 478 |
+
else:
|
| 479 |
+
# If category doesn't exist in GT, skip it
|
| 480 |
+
print(f"Warning: Category '{pred_name}' not in ground truth, skipping...")
|
| 481 |
+
|
| 482 |
+
# Update prediction annotations
|
| 483 |
+
new_anns = []
|
| 484 |
+
for ann in pred_coco["annotations"]:
|
| 485 |
+
old_cat_id = ann["category_id"]
|
| 486 |
+
if old_cat_id in pred_to_gt_map:
|
| 487 |
+
new_ann = ann.copy()
|
| 488 |
+
new_ann["category_id"] = pred_to_gt_map[old_cat_id]
|
| 489 |
+
new_anns.append(new_ann)
|
| 490 |
+
|
| 491 |
+
pred_coco["annotations"] = new_anns
|
| 492 |
+
|
| 493 |
+
# Update categories to match GT
|
| 494 |
+
pred_coco["categories"] = [
|
| 495 |
+
c for c in gt_coco["categories"]
|
| 496 |
+
if c["name"] in pred_name_to_id
|
| 497 |
+
]
|
| 498 |
+
|
| 499 |
+
return pred_coco
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def main():
|
| 503 |
+
"""
|
| 504 |
+
Main comparison function.
|
| 505 |
+
"""
|
| 506 |
+
# Paths
|
| 507 |
+
data_dir = os.path.join(SCRIPT_DIR, "Aleyna 1 (2024)")
|
| 508 |
+
xml_path = os.path.join(data_dir, "Annotations", "annotations.xml")
|
| 509 |
+
images_dir = os.path.join(data_dir, "Images")
|
| 510 |
+
output_dir = os.path.join(SCRIPT_DIR, "results")
|
| 511 |
+
|
| 512 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 513 |
+
|
| 514 |
+
print("=" * 60)
|
| 515 |
+
print("COMPARISON: Old Models vs New Models vs Ground Truth")
|
| 516 |
+
print("=" * 60)
|
| 517 |
+
|
| 518 |
+
# 1. Load ground truth
|
| 519 |
+
print("\n[1/4] Loading ground truth annotations...")
|
| 520 |
+
gt_coco = load_ground_truth(xml_path, images_dir)
|
| 521 |
+
print(f" ✓ Loaded {len(gt_coco['images'])} images")
|
| 522 |
+
print(f" ✓ Loaded {len(gt_coco['annotations'])} annotations")
|
| 523 |
+
print(f" ✓ Categories: {[c['name'] for c in gt_coco['categories']]}")
|
| 524 |
+
|
| 525 |
+
# Save GT
|
| 526 |
+
gt_output = os.path.join(output_dir, "ground_truth.json")
|
| 527 |
+
with open(gt_output, 'w') as f:
|
| 528 |
+
json.dump(gt_coco, f, indent=2)
|
| 529 |
+
print(f" ✓ Saved to {gt_output}")
|
| 530 |
+
|
| 531 |
+
# 2. Run old models
|
| 532 |
+
print("\n[2/4] Running old models...")
|
| 533 |
+
old_output_dir = os.path.join(output_dir, "old_models")
|
| 534 |
+
os.makedirs(old_output_dir, exist_ok=True)
|
| 535 |
+
old_coco = process_old_models(images_dir, old_output_dir)
|
| 536 |
+
print(f" ✓ Processed {len(old_coco['images'])} images")
|
| 537 |
+
print(f" ✓ Generated {len(old_coco['annotations'])} annotations")
|
| 538 |
+
|
| 539 |
+
old_output = os.path.join(output_dir, "old_models_merged.json")
|
| 540 |
+
with open(old_output, 'w') as f:
|
| 541 |
+
json.dump(old_coco, f, indent=2)
|
| 542 |
+
print(f" ✓ Saved to {old_output}")
|
| 543 |
+
|
| 544 |
+
# 3. Run new models
|
| 545 |
+
print("\n[3/4] Running new models...")
|
| 546 |
+
new_output_dir = os.path.join(output_dir, "new_models")
|
| 547 |
+
os.makedirs(new_output_dir, exist_ok=True)
|
| 548 |
+
new_coco = process_new_models(images_dir, new_output_dir)
|
| 549 |
+
print(f" ✓ Processed {len(new_coco['images'])} images")
|
| 550 |
+
print(f" ✓ Generated {len(new_coco['annotations'])} annotations")
|
| 551 |
+
|
| 552 |
+
new_output = os.path.join(output_dir, "new_models_merged.json")
|
| 553 |
+
with open(new_output, 'w') as f:
|
| 554 |
+
json.dump(new_coco, f, indent=2)
|
| 555 |
+
print(f" ✓ Saved to {new_output}")
|
| 556 |
+
|
| 557 |
+
# 4. Calculate metrics
|
| 558 |
+
print("\n[4/4] Calculating metrics...")
|
| 559 |
+
|
| 560 |
+
# Align categories
|
| 561 |
+
old_coco_aligned = align_categories(gt_coco.copy(), old_coco.copy())
|
| 562 |
+
new_coco_aligned = align_categories(gt_coco.copy(), new_coco.copy())
|
| 563 |
+
|
| 564 |
+
# Calculate metrics for old models
|
| 565 |
+
print("\n Calculating metrics for OLD MODELS...")
|
| 566 |
+
old_metrics = calculate_metrics(gt_coco, old_coco_aligned, output_dir)
|
| 567 |
+
print(f" mAP@50: {old_metrics['mAP@50']:.4f}")
|
| 568 |
+
print(f" mAP@[.50:.95]: {old_metrics['mAP@[.50:.95]']:.4f}")
|
| 569 |
+
print(f" Precision: {old_metrics['Precision']:.4f}")
|
| 570 |
+
print(f" Recall: {old_metrics['Recall']:.4f}")
|
| 571 |
+
|
| 572 |
+
# Calculate metrics for new models
|
| 573 |
+
print("\n Calculating metrics for NEW MODELS...")
|
| 574 |
+
new_metrics = calculate_metrics(gt_coco, new_coco_aligned, output_dir)
|
| 575 |
+
print(f" mAP@50: {new_metrics['mAP@50']:.4f}")
|
| 576 |
+
print(f" mAP@[.50:.95]: {new_metrics['mAP@[.50:.95]']:.4f}")
|
| 577 |
+
print(f" Precision: {new_metrics['Precision']:.4f}")
|
| 578 |
+
print(f" Recall: {new_metrics['Recall']:.4f}")
|
| 579 |
+
|
| 580 |
+
# Save metrics
|
| 581 |
+
metrics_output = os.path.join(output_dir, "metrics.json")
|
| 582 |
+
with open(metrics_output, 'w') as f:
|
| 583 |
+
json.dump({
|
| 584 |
+
'old_models': old_metrics,
|
| 585 |
+
'new_models': new_metrics
|
| 586 |
+
}, f, indent=2)
|
| 587 |
+
print(f"\n ✓ Saved metrics to {metrics_output}")
|
| 588 |
+
|
| 589 |
+
# 5. Create visualizations for each image
|
| 590 |
+
print("\n[5/5] Creating comparison visualizations...")
|
| 591 |
+
vis_dir = os.path.join(output_dir, "visualizations")
|
| 592 |
+
os.makedirs(vis_dir, exist_ok=True)
|
| 593 |
+
|
| 594 |
+
for img_info in gt_coco["images"]:
|
| 595 |
+
image_name = img_info["file_name"]
|
| 596 |
+
image_path = os.path.join(images_dir, image_name)
|
| 597 |
+
|
| 598 |
+
if not os.path.exists(image_path):
|
| 599 |
+
continue
|
| 600 |
+
|
| 601 |
+
# Get COCO for this image
|
| 602 |
+
img_id = img_info["id"]
|
| 603 |
+
|
| 604 |
+
# Filter annotations for this image
|
| 605 |
+
gt_img_coco = {
|
| 606 |
+
"images": [img_info],
|
| 607 |
+
"annotations": [a for a in gt_coco["annotations"] if a["image_id"] == img_id],
|
| 608 |
+
"categories": gt_coco["categories"]
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
old_img_coco = {
|
| 612 |
+
"images": [img_info],
|
| 613 |
+
"annotations": [a for a in old_coco["annotations"] if a["image_id"] == img_id],
|
| 614 |
+
"categories": old_coco["categories"]
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
new_img_coco = {
|
| 618 |
+
"images": [img_info],
|
| 619 |
+
"annotations": [a for a in new_coco["annotations"] if a["image_id"] == img_id],
|
| 620 |
+
"categories": new_coco["categories"]
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
# Create visualization
|
| 624 |
+
output_path = os.path.join(vis_dir, f"{Path(image_name).stem}_comparison.png")
|
| 625 |
+
create_comparison_visualization(
|
| 626 |
+
image_path,
|
| 627 |
+
gt_img_coco,
|
| 628 |
+
old_img_coco,
|
| 629 |
+
new_img_coco,
|
| 630 |
+
output_path
|
| 631 |
+
)
|
| 632 |
+
|
| 633 |
+
print(f"\n ✓ Saved visualizations to {vis_dir}")
|
| 634 |
+
print("\n" + "=" * 60)
|
| 635 |
+
print("COMPARISON COMPLETE!")
|
| 636 |
+
print("=" * 60)
|
| 637 |
+
print(f"\nResults saved to: {output_dir}")
|
| 638 |
+
|
| 639 |
+
|
| 640 |
+
if __name__ == "__main__":
|
| 641 |
+
main()
|
| 642 |
+
|
compare/data/expert_datasets_model_comparison_summary.json
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"folder": "Aleyna 1 (2024)",
|
| 4 |
+
"old_metrics": {
|
| 5 |
+
"mAP@50": 0.0,
|
| 6 |
+
"mAP@[.50:.95]": 0.0,
|
| 7 |
+
"Precision": 0.0,
|
| 8 |
+
"Recall": 0.0,
|
| 9 |
+
"F1": 0.0,
|
| 10 |
+
"error": "No valid prediction annotations"
|
| 11 |
+
},
|
| 12 |
+
"new_metrics": {
|
| 13 |
+
"mAP@50": 0.0,
|
| 14 |
+
"mAP@[.50:.95]": 0.0,
|
| 15 |
+
"Precision": 0.0,
|
| 16 |
+
"Recall": 0.0,
|
| 17 |
+
"F1": 0.0,
|
| 18 |
+
"error": "segm error: 'score', bbox error: 'score'"
|
| 19 |
+
},
|
| 20 |
+
"old_annotations": 829,
|
| 21 |
+
"new_annotations": 1127,
|
| 22 |
+
"gt_annotations": 44
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"folder": "Annika 2 (2024)",
|
| 26 |
+
"old_metrics": {
|
| 27 |
+
"mAP@50": 0.0,
|
| 28 |
+
"mAP@[.50:.95]": 0.0,
|
| 29 |
+
"Precision": 0.0,
|
| 30 |
+
"Recall": 0.0,
|
| 31 |
+
"F1": 0.0,
|
| 32 |
+
"error": "No valid prediction annotations"
|
| 33 |
+
},
|
| 34 |
+
"new_metrics": {
|
| 35 |
+
"mAP@50": 0.0,
|
| 36 |
+
"mAP@[.50:.95]": 0.0,
|
| 37 |
+
"Precision": 0.0,
|
| 38 |
+
"Recall": 0.0,
|
| 39 |
+
"F1": 0.0,
|
| 40 |
+
"error": "segm error: 'score', bbox error: 'score'"
|
| 41 |
+
},
|
| 42 |
+
"old_annotations": 769,
|
| 43 |
+
"new_annotations": 898,
|
| 44 |
+
"gt_annotations": 136
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"folder": "Luise 1 (2024)",
|
| 48 |
+
"old_metrics": {
|
| 49 |
+
"mAP@50": 0.0,
|
| 50 |
+
"mAP@[.50:.95]": 0.0,
|
| 51 |
+
"Precision": 0.0,
|
| 52 |
+
"Recall": 0.0,
|
| 53 |
+
"F1": 0.0,
|
| 54 |
+
"error": "No valid GT annotations"
|
| 55 |
+
},
|
| 56 |
+
"new_metrics": {
|
| 57 |
+
"mAP@50": 0.0,
|
| 58 |
+
"mAP@[.50:.95]": 0.0,
|
| 59 |
+
"Precision": 0.0,
|
| 60 |
+
"Recall": 0.0,
|
| 61 |
+
"F1": 0.0,
|
| 62 |
+
"error": "No valid GT annotations"
|
| 63 |
+
},
|
| 64 |
+
"old_annotations": 415,
|
| 65 |
+
"new_annotations": 712,
|
| 66 |
+
"gt_annotations": 0
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"folder": "Luise 2 (2024)",
|
| 70 |
+
"old_metrics": {
|
| 71 |
+
"mAP@50": 0.0,
|
| 72 |
+
"mAP@[.50:.95]": 0.0,
|
| 73 |
+
"Precision": 0.0,
|
| 74 |
+
"Recall": 0.0,
|
| 75 |
+
"F1": 0.0,
|
| 76 |
+
"error": "No valid prediction annotations"
|
| 77 |
+
},
|
| 78 |
+
"new_metrics": {
|
| 79 |
+
"mAP@50": 0.0,
|
| 80 |
+
"mAP@[.50:.95]": 0.0,
|
| 81 |
+
"Precision": 0.0,
|
| 82 |
+
"Recall": 0.0,
|
| 83 |
+
"F1": 0.0,
|
| 84 |
+
"error": "segm error: 'score', bbox error: 'score'"
|
| 85 |
+
},
|
| 86 |
+
"old_annotations": 715,
|
| 87 |
+
"new_annotations": 759,
|
| 88 |
+
"gt_annotations": 99
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"folder": "Nuray 1 (2024)",
|
| 92 |
+
"old_metrics": {
|
| 93 |
+
"mAP@50": 0.0,
|
| 94 |
+
"mAP@[.50:.95]": 0.0,
|
| 95 |
+
"Precision": 0.0,
|
| 96 |
+
"Recall": 0.0,
|
| 97 |
+
"F1": 0.0,
|
| 98 |
+
"error": "No valid prediction annotations"
|
| 99 |
+
},
|
| 100 |
+
"new_metrics": {
|
| 101 |
+
"mAP@50": 0.0,
|
| 102 |
+
"mAP@[.50:.95]": 0.0,
|
| 103 |
+
"Precision": 0.0,
|
| 104 |
+
"Recall": 0.0,
|
| 105 |
+
"F1": 0.0,
|
| 106 |
+
"error": "segm error: 'score', bbox error: 'score'"
|
| 107 |
+
},
|
| 108 |
+
"old_annotations": 301,
|
| 109 |
+
"new_annotations": 417,
|
| 110 |
+
"gt_annotations": 64
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"folder": "Nuray 2 (2024)",
|
| 114 |
+
"old_metrics": {
|
| 115 |
+
"mAP@50": 0.0,
|
| 116 |
+
"mAP@[.50:.95]": 0.0,
|
| 117 |
+
"Precision": 0.0,
|
| 118 |
+
"Recall": 0.0,
|
| 119 |
+
"F1": 0.0,
|
| 120 |
+
"error": "No valid prediction annotations"
|
| 121 |
+
},
|
| 122 |
+
"new_metrics": {
|
| 123 |
+
"mAP@50": 0.0,
|
| 124 |
+
"mAP@[.50:.95]": 0.0,
|
| 125 |
+
"Precision": 0.0,
|
| 126 |
+
"Recall": 0.0,
|
| 127 |
+
"F1": 0.0,
|
| 128 |
+
"error": "segm error: 'score', bbox error: 'score'"
|
| 129 |
+
},
|
| 130 |
+
"old_annotations": 419,
|
| 131 |
+
"new_annotations": 467,
|
| 132 |
+
"gt_annotations": 25
|
| 133 |
+
}
|
| 134 |
+
]
|
compare/data/ground_truth_coco.json
ADDED
|
@@ -0,0 +1,1217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"info": {
|
| 3 |
+
"description": "Converted from CVAT XML",
|
| 4 |
+
"year": 2024,
|
| 5 |
+
"version": "1.0"
|
| 6 |
+
},
|
| 7 |
+
"licenses": [],
|
| 8 |
+
"images": [
|
| 9 |
+
{
|
| 10 |
+
"id": 1,
|
| 11 |
+
"width": 6132,
|
| 12 |
+
"height": 8176,
|
| 13 |
+
"file_name": "e-codices_bbb-0219_001v_max.jpg"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"id": 2,
|
| 17 |
+
"width": 6132,
|
| 18 |
+
"height": 8176,
|
| 19 |
+
"file_name": "e-codices_bbb-0219_003v_max.jpg"
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"id": 3,
|
| 23 |
+
"width": 6132,
|
| 24 |
+
"height": 8176,
|
| 25 |
+
"file_name": "e-codices_bbb-0219_007v_max.jpg"
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"id": 4,
|
| 29 |
+
"width": 6132,
|
| 30 |
+
"height": 8176,
|
| 31 |
+
"file_name": "e-codices_bbb-0219_023v_max.jpg"
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"id": 5,
|
| 35 |
+
"width": 6132,
|
| 36 |
+
"height": 8176,
|
| 37 |
+
"file_name": "e-codices_bbb-0219_041v_max.jpg"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"id": 6,
|
| 41 |
+
"width": 6132,
|
| 42 |
+
"height": 8176,
|
| 43 |
+
"file_name": "e-codices_bbb-0219_044r_maxr.jpg"
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"id": 7,
|
| 47 |
+
"width": 6132,
|
| 48 |
+
"height": 8176,
|
| 49 |
+
"file_name": "e-codices_bbb-0219_047v_max.jpg"
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"id": 8,
|
| 53 |
+
"width": 6132,
|
| 54 |
+
"height": 8176,
|
| 55 |
+
"file_name": "e-codices_bbb-0219_050v_max.jpg"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"id": 9,
|
| 59 |
+
"width": 6132,
|
| 60 |
+
"height": 8176,
|
| 61 |
+
"file_name": "e-codices_bbb-0219_055r_max.jpg"
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"id": 10,
|
| 65 |
+
"width": 6132,
|
| 66 |
+
"height": 8176,
|
| 67 |
+
"file_name": "e-codices_bbb-0219_070v_max.jpg"
|
| 68 |
+
}
|
| 69 |
+
],
|
| 70 |
+
"annotations": [
|
| 71 |
+
{
|
| 72 |
+
"id": 1,
|
| 73 |
+
"image_id": 1,
|
| 74 |
+
"category_id": 10,
|
| 75 |
+
"bbox": [
|
| 76 |
+
1348,
|
| 77 |
+
5552,
|
| 78 |
+
2039,
|
| 79 |
+
295
|
| 80 |
+
],
|
| 81 |
+
"area": 601505,
|
| 82 |
+
"iscrowd": 0,
|
| 83 |
+
"segmentation": []
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"id": 2,
|
| 87 |
+
"image_id": 1,
|
| 88 |
+
"category_id": 10,
|
| 89 |
+
"bbox": [
|
| 90 |
+
3825,
|
| 91 |
+
6664,
|
| 92 |
+
2186,
|
| 93 |
+
231
|
| 94 |
+
],
|
| 95 |
+
"area": 504966,
|
| 96 |
+
"iscrowd": 0,
|
| 97 |
+
"segmentation": []
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"id": 3,
|
| 101 |
+
"image_id": 1,
|
| 102 |
+
"category_id": 10,
|
| 103 |
+
"bbox": [
|
| 104 |
+
3838,
|
| 105 |
+
3853,
|
| 106 |
+
2066,
|
| 107 |
+
216
|
| 108 |
+
],
|
| 109 |
+
"area": 446256,
|
| 110 |
+
"iscrowd": 0,
|
| 111 |
+
"segmentation": []
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"id": 4,
|
| 115 |
+
"image_id": 1,
|
| 116 |
+
"category_id": 10,
|
| 117 |
+
"bbox": [
|
| 118 |
+
1113,
|
| 119 |
+
5968,
|
| 120 |
+
2270,
|
| 121 |
+
194
|
| 122 |
+
],
|
| 123 |
+
"area": 440380,
|
| 124 |
+
"iscrowd": 0,
|
| 125 |
+
"segmentation": []
|
| 126 |
+
},
|
| 127 |
+
{
|
| 128 |
+
"id": 5,
|
| 129 |
+
"image_id": 1,
|
| 130 |
+
"category_id": 10,
|
| 131 |
+
"bbox": [
|
| 132 |
+
3721,
|
| 133 |
+
5186,
|
| 134 |
+
2300,
|
| 135 |
+
260
|
| 136 |
+
],
|
| 137 |
+
"area": 598000,
|
| 138 |
+
"iscrowd": 0,
|
| 139 |
+
"segmentation": []
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"id": 6,
|
| 143 |
+
"image_id": 1,
|
| 144 |
+
"category_id": 10,
|
| 145 |
+
"bbox": [
|
| 146 |
+
3809,
|
| 147 |
+
5978,
|
| 148 |
+
2058,
|
| 149 |
+
183
|
| 150 |
+
],
|
| 151 |
+
"area": 376614,
|
| 152 |
+
"iscrowd": 0,
|
| 153 |
+
"segmentation": []
|
| 154 |
+
},
|
| 155 |
+
{
|
| 156 |
+
"id": 7,
|
| 157 |
+
"image_id": 1,
|
| 158 |
+
"category_id": 10,
|
| 159 |
+
"bbox": [
|
| 160 |
+
3809,
|
| 161 |
+
6891,
|
| 162 |
+
1949,
|
| 163 |
+
231
|
| 164 |
+
],
|
| 165 |
+
"area": 450219,
|
| 166 |
+
"iscrowd": 0,
|
| 167 |
+
"segmentation": []
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
"id": 8,
|
| 171 |
+
"image_id": 1,
|
| 172 |
+
"category_id": 10,
|
| 173 |
+
"bbox": [
|
| 174 |
+
1098,
|
| 175 |
+
6337,
|
| 176 |
+
1659,
|
| 177 |
+
269
|
| 178 |
+
],
|
| 179 |
+
"area": 446271,
|
| 180 |
+
"iscrowd": 0,
|
| 181 |
+
"segmentation": []
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"id": 9,
|
| 185 |
+
"image_id": 1,
|
| 186 |
+
"category_id": 10,
|
| 187 |
+
"bbox": [
|
| 188 |
+
3838,
|
| 189 |
+
5373,
|
| 190 |
+
2095,
|
| 191 |
+
277
|
| 192 |
+
],
|
| 193 |
+
"area": 580315,
|
| 194 |
+
"iscrowd": 0,
|
| 195 |
+
"segmentation": []
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"id": 10,
|
| 199 |
+
"image_id": 1,
|
| 200 |
+
"category_id": 11,
|
| 201 |
+
"bbox": [
|
| 202 |
+
3828,
|
| 203 |
+
1769,
|
| 204 |
+
1998,
|
| 205 |
+
379
|
| 206 |
+
],
|
| 207 |
+
"area": 757242,
|
| 208 |
+
"iscrowd": 0,
|
| 209 |
+
"segmentation": []
|
| 210 |
+
},
|
| 211 |
+
{
|
| 212 |
+
"id": 11,
|
| 213 |
+
"image_id": 1,
|
| 214 |
+
"category_id": 21,
|
| 215 |
+
"bbox": [
|
| 216 |
+
595,
|
| 217 |
+
511,
|
| 218 |
+
5442,
|
| 219 |
+
4998
|
| 220 |
+
],
|
| 221 |
+
"area": 27199116,
|
| 222 |
+
"iscrowd": 0,
|
| 223 |
+
"segmentation": []
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"id": 12,
|
| 227 |
+
"image_id": 1,
|
| 228 |
+
"category_id": 10,
|
| 229 |
+
"bbox": [
|
| 230 |
+
3845,
|
| 231 |
+
4989,
|
| 232 |
+
2187,
|
| 233 |
+
248
|
| 234 |
+
],
|
| 235 |
+
"area": 542376,
|
| 236 |
+
"iscrowd": 0,
|
| 237 |
+
"segmentation": []
|
| 238 |
+
},
|
| 239 |
+
{
|
| 240 |
+
"id": 13,
|
| 241 |
+
"image_id": 1,
|
| 242 |
+
"category_id": 10,
|
| 243 |
+
"bbox": [
|
| 244 |
+
3834,
|
| 245 |
+
3292,
|
| 246 |
+
2187,
|
| 247 |
+
246
|
| 248 |
+
],
|
| 249 |
+
"area": 538002,
|
| 250 |
+
"iscrowd": 0,
|
| 251 |
+
"segmentation": []
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"id": 14,
|
| 255 |
+
"image_id": 1,
|
| 256 |
+
"category_id": 10,
|
| 257 |
+
"bbox": [
|
| 258 |
+
1097,
|
| 259 |
+
6172,
|
| 260 |
+
2116,
|
| 261 |
+
204
|
| 262 |
+
],
|
| 263 |
+
"area": 431664,
|
| 264 |
+
"iscrowd": 0,
|
| 265 |
+
"segmentation": []
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"id": 15,
|
| 269 |
+
"image_id": 1,
|
| 270 |
+
"category_id": 10,
|
| 271 |
+
"bbox": [
|
| 272 |
+
1082,
|
| 273 |
+
6516,
|
| 274 |
+
2311,
|
| 275 |
+
244
|
| 276 |
+
],
|
| 277 |
+
"area": 563884,
|
| 278 |
+
"iscrowd": 0,
|
| 279 |
+
"segmentation": []
|
| 280 |
+
},
|
| 281 |
+
{
|
| 282 |
+
"id": 16,
|
| 283 |
+
"image_id": 1,
|
| 284 |
+
"category_id": 10,
|
| 285 |
+
"bbox": [
|
| 286 |
+
3853,
|
| 287 |
+
4227,
|
| 288 |
+
1941,
|
| 289 |
+
263
|
| 290 |
+
],
|
| 291 |
+
"area": 510483,
|
| 292 |
+
"iscrowd": 0,
|
| 293 |
+
"segmentation": []
|
| 294 |
+
},
|
| 295 |
+
{
|
| 296 |
+
"id": 17,
|
| 297 |
+
"image_id": 1,
|
| 298 |
+
"category_id": 4,
|
| 299 |
+
"bbox": [
|
| 300 |
+
848,
|
| 301 |
+
5400,
|
| 302 |
+
636,
|
| 303 |
+
594
|
| 304 |
+
],
|
| 305 |
+
"area": 377784,
|
| 306 |
+
"iscrowd": 0,
|
| 307 |
+
"segmentation": []
|
| 308 |
+
},
|
| 309 |
+
{
|
| 310 |
+
"id": 18,
|
| 311 |
+
"image_id": 1,
|
| 312 |
+
"category_id": 10,
|
| 313 |
+
"bbox": [
|
| 314 |
+
1085,
|
| 315 |
+
6739,
|
| 316 |
+
2174,
|
| 317 |
+
235
|
| 318 |
+
],
|
| 319 |
+
"area": 510890,
|
| 320 |
+
"iscrowd": 0,
|
| 321 |
+
"segmentation": []
|
| 322 |
+
},
|
| 323 |
+
{
|
| 324 |
+
"id": 19,
|
| 325 |
+
"image_id": 1,
|
| 326 |
+
"category_id": 10,
|
| 327 |
+
"bbox": [
|
| 328 |
+
3809,
|
| 329 |
+
6336,
|
| 330 |
+
2104,
|
| 331 |
+
163
|
| 332 |
+
],
|
| 333 |
+
"area": 342952,
|
| 334 |
+
"iscrowd": 0,
|
| 335 |
+
"segmentation": []
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"id": 20,
|
| 339 |
+
"image_id": 1,
|
| 340 |
+
"category_id": 10,
|
| 341 |
+
"bbox": [
|
| 342 |
+
3778,
|
| 343 |
+
6465,
|
| 344 |
+
2198,
|
| 345 |
+
304
|
| 346 |
+
],
|
| 347 |
+
"area": 668192,
|
| 348 |
+
"iscrowd": 0,
|
| 349 |
+
"segmentation": []
|
| 350 |
+
},
|
| 351 |
+
{
|
| 352 |
+
"id": 21,
|
| 353 |
+
"image_id": 1,
|
| 354 |
+
"category_id": 10,
|
| 355 |
+
"bbox": [
|
| 356 |
+
1470,
|
| 357 |
+
5751,
|
| 358 |
+
1944,
|
| 359 |
+
237
|
| 360 |
+
],
|
| 361 |
+
"area": 460728,
|
| 362 |
+
"iscrowd": 0,
|
| 363 |
+
"segmentation": []
|
| 364 |
+
},
|
| 365 |
+
{
|
| 366 |
+
"id": 22,
|
| 367 |
+
"image_id": 1,
|
| 368 |
+
"category_id": 10,
|
| 369 |
+
"bbox": [
|
| 370 |
+
3838,
|
| 371 |
+
4036,
|
| 372 |
+
2132,
|
| 373 |
+
249
|
| 374 |
+
],
|
| 375 |
+
"area": 530868,
|
| 376 |
+
"iscrowd": 0,
|
| 377 |
+
"segmentation": []
|
| 378 |
+
},
|
| 379 |
+
{
|
| 380 |
+
"id": 23,
|
| 381 |
+
"image_id": 1,
|
| 382 |
+
"category_id": 10,
|
| 383 |
+
"bbox": [
|
| 384 |
+
3820,
|
| 385 |
+
3640,
|
| 386 |
+
1985,
|
| 387 |
+
224
|
| 388 |
+
],
|
| 389 |
+
"area": 444640,
|
| 390 |
+
"iscrowd": 0,
|
| 391 |
+
"segmentation": []
|
| 392 |
+
},
|
| 393 |
+
{
|
| 394 |
+
"id": 24,
|
| 395 |
+
"image_id": 1,
|
| 396 |
+
"category_id": 10,
|
| 397 |
+
"bbox": [
|
| 398 |
+
3816,
|
| 399 |
+
5743,
|
| 400 |
+
2183,
|
| 401 |
+
271
|
| 402 |
+
],
|
| 403 |
+
"area": 591593,
|
| 404 |
+
"iscrowd": 0,
|
| 405 |
+
"segmentation": []
|
| 406 |
+
},
|
| 407 |
+
{
|
| 408 |
+
"id": 25,
|
| 409 |
+
"image_id": 1,
|
| 410 |
+
"category_id": 10,
|
| 411 |
+
"bbox": [
|
| 412 |
+
3853,
|
| 413 |
+
5567,
|
| 414 |
+
2080,
|
| 415 |
+
202
|
| 416 |
+
],
|
| 417 |
+
"area": 420160,
|
| 418 |
+
"iscrowd": 0,
|
| 419 |
+
"segmentation": []
|
| 420 |
+
},
|
| 421 |
+
{
|
| 422 |
+
"id": 26,
|
| 423 |
+
"image_id": 1,
|
| 424 |
+
"category_id": 10,
|
| 425 |
+
"bbox": [
|
| 426 |
+
3831,
|
| 427 |
+
6139,
|
| 428 |
+
2165,
|
| 429 |
+
186
|
| 430 |
+
],
|
| 431 |
+
"area": 402690,
|
| 432 |
+
"iscrowd": 0,
|
| 433 |
+
"segmentation": []
|
| 434 |
+
},
|
| 435 |
+
{
|
| 436 |
+
"id": 27,
|
| 437 |
+
"image_id": 1,
|
| 438 |
+
"category_id": 10,
|
| 439 |
+
"bbox": [
|
| 440 |
+
3772,
|
| 441 |
+
4824,
|
| 442 |
+
2205,
|
| 443 |
+
267
|
| 444 |
+
],
|
| 445 |
+
"area": 588735,
|
| 446 |
+
"iscrowd": 0,
|
| 447 |
+
"segmentation": []
|
| 448 |
+
},
|
| 449 |
+
{
|
| 450 |
+
"id": 28,
|
| 451 |
+
"image_id": 1,
|
| 452 |
+
"category_id": 10,
|
| 453 |
+
"bbox": [
|
| 454 |
+
1092,
|
| 455 |
+
7062,
|
| 456 |
+
1461,
|
| 457 |
+
237
|
| 458 |
+
],
|
| 459 |
+
"area": 346257,
|
| 460 |
+
"iscrowd": 0,
|
| 461 |
+
"segmentation": []
|
| 462 |
+
},
|
| 463 |
+
{
|
| 464 |
+
"id": 29,
|
| 465 |
+
"image_id": 1,
|
| 466 |
+
"category_id": 10,
|
| 467 |
+
"bbox": [
|
| 468 |
+
3831,
|
| 469 |
+
3472,
|
| 470 |
+
2183,
|
| 471 |
+
234
|
| 472 |
+
],
|
| 473 |
+
"area": 510822,
|
| 474 |
+
"iscrowd": 0,
|
| 475 |
+
"segmentation": []
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"id": 30,
|
| 479 |
+
"image_id": 1,
|
| 480 |
+
"category_id": 10,
|
| 481 |
+
"bbox": [
|
| 482 |
+
1026,
|
| 483 |
+
6935,
|
| 484 |
+
2314,
|
| 485 |
+
215
|
| 486 |
+
],
|
| 487 |
+
"area": 497510,
|
| 488 |
+
"iscrowd": 0,
|
| 489 |
+
"segmentation": []
|
| 490 |
+
},
|
| 491 |
+
{
|
| 492 |
+
"id": 31,
|
| 493 |
+
"image_id": 1,
|
| 494 |
+
"category_id": 10,
|
| 495 |
+
"bbox": [
|
| 496 |
+
3853,
|
| 497 |
+
4461,
|
| 498 |
+
2000,
|
| 499 |
+
245
|
| 500 |
+
],
|
| 501 |
+
"area": 490000,
|
| 502 |
+
"iscrowd": 0,
|
| 503 |
+
"segmentation": []
|
| 504 |
+
},
|
| 505 |
+
{
|
| 506 |
+
"id": 32,
|
| 507 |
+
"image_id": 1,
|
| 508 |
+
"category_id": 10,
|
| 509 |
+
"bbox": [
|
| 510 |
+
3800,
|
| 511 |
+
7049,
|
| 512 |
+
2157,
|
| 513 |
+
269
|
| 514 |
+
],
|
| 515 |
+
"area": 580233,
|
| 516 |
+
"iscrowd": 0,
|
| 517 |
+
"segmentation": []
|
| 518 |
+
},
|
| 519 |
+
{
|
| 520 |
+
"id": 33,
|
| 521 |
+
"image_id": 1,
|
| 522 |
+
"category_id": 10,
|
| 523 |
+
"bbox": [
|
| 524 |
+
3853,
|
| 525 |
+
4659,
|
| 526 |
+
2143,
|
| 527 |
+
197
|
| 528 |
+
],
|
| 529 |
+
"area": 422171,
|
| 530 |
+
"iscrowd": 0,
|
| 531 |
+
"segmentation": []
|
| 532 |
+
},
|
| 533 |
+
{
|
| 534 |
+
"id": 34,
|
| 535 |
+
"image_id": 2,
|
| 536 |
+
"category_id": 10,
|
| 537 |
+
"bbox": [
|
| 538 |
+
869.8,
|
| 539 |
+
816.34,
|
| 540 |
+
2091.09,
|
| 541 |
+
243.56000000000006
|
| 542 |
+
],
|
| 543 |
+
"area": 250644.53920000035,
|
| 544 |
+
"iscrowd": 0,
|
| 545 |
+
"segmentation": [
|
| 546 |
+
[
|
| 547 |
+
869.8,
|
| 548 |
+
911.39,
|
| 549 |
+
961.88,
|
| 550 |
+
863.86,
|
| 551 |
+
985.64,
|
| 552 |
+
893.56,
|
| 553 |
+
1039.11,
|
| 554 |
+
828.22,
|
| 555 |
+
1095.54,
|
| 556 |
+
863.86,
|
| 557 |
+
1131.19,
|
| 558 |
+
881.68,
|
| 559 |
+
1172.77,
|
| 560 |
+
869.8,
|
| 561 |
+
1226.24,
|
| 562 |
+
857.92,
|
| 563 |
+
1267.82,
|
| 564 |
+
863.86,
|
| 565 |
+
1645.05,
|
| 566 |
+
863.86,
|
| 567 |
+
2170.79,
|
| 568 |
+
846.04,
|
| 569 |
+
2937.13,
|
| 570 |
+
816.34,
|
| 571 |
+
2960.89,
|
| 572 |
+
917.33,
|
| 573 |
+
2747.03,
|
| 574 |
+
941.09,
|
| 575 |
+
2741.09,
|
| 576 |
+
941.09,
|
| 577 |
+
2402.48,
|
| 578 |
+
964.85,
|
| 579 |
+
1936.14,
|
| 580 |
+
964.85,
|
| 581 |
+
1852.97,
|
| 582 |
+
1006.44,
|
| 583 |
+
1769.8,
|
| 584 |
+
982.67,
|
| 585 |
+
1677.72,
|
| 586 |
+
967.82,
|
| 587 |
+
1505.45,
|
| 588 |
+
979.7,
|
| 589 |
+
1327.23,
|
| 590 |
+
976.73,
|
| 591 |
+
1214.36,
|
| 592 |
+
988.61,
|
| 593 |
+
1119.31,
|
| 594 |
+
982.67,
|
| 595 |
+
994.55,
|
| 596 |
+
997.52,
|
| 597 |
+
881.68,
|
| 598 |
+
1059.9
|
| 599 |
+
]
|
| 600 |
+
]
|
| 601 |
+
},
|
| 602 |
+
{
|
| 603 |
+
"id": 35,
|
| 604 |
+
"image_id": 2,
|
| 605 |
+
"category_id": 21,
|
| 606 |
+
"bbox": [
|
| 607 |
+
556.0,
|
| 608 |
+
5498.0,
|
| 609 |
+
2620.0,
|
| 610 |
+
1290.0
|
| 611 |
+
],
|
| 612 |
+
"area": 2305750.0,
|
| 613 |
+
"iscrowd": 0,
|
| 614 |
+
"segmentation": [
|
| 615 |
+
[
|
| 616 |
+
726.0,
|
| 617 |
+
5648.0,
|
| 618 |
+
626.0,
|
| 619 |
+
5718.0,
|
| 620 |
+
576.0,
|
| 621 |
+
5828.0,
|
| 622 |
+
566.0,
|
| 623 |
+
5918.0,
|
| 624 |
+
556.0,
|
| 625 |
+
6018.0,
|
| 626 |
+
636.0,
|
| 627 |
+
6028.0,
|
| 628 |
+
756.0,
|
| 629 |
+
5968.0,
|
| 630 |
+
796.0,
|
| 631 |
+
5908.0,
|
| 632 |
+
856.0,
|
| 633 |
+
5838.0,
|
| 634 |
+
886.0,
|
| 635 |
+
5728.0,
|
| 636 |
+
866.0,
|
| 637 |
+
5918.0,
|
| 638 |
+
876.0,
|
| 639 |
+
6028.0,
|
| 640 |
+
896.0,
|
| 641 |
+
6128.0,
|
| 642 |
+
896.0,
|
| 643 |
+
6228.0,
|
| 644 |
+
856.0,
|
| 645 |
+
6328.0,
|
| 646 |
+
856.0,
|
| 647 |
+
6448.0,
|
| 648 |
+
866.0,
|
| 649 |
+
6508.0,
|
| 650 |
+
876.0,
|
| 651 |
+
6568.0,
|
| 652 |
+
876.0,
|
| 653 |
+
6658.0,
|
| 654 |
+
876.0,
|
| 655 |
+
6728.0,
|
| 656 |
+
876.0,
|
| 657 |
+
6788.0,
|
| 658 |
+
926.0,
|
| 659 |
+
6778.0,
|
| 660 |
+
976.0,
|
| 661 |
+
6748.0,
|
| 662 |
+
1026.0,
|
| 663 |
+
6708.0,
|
| 664 |
+
1116.0,
|
| 665 |
+
6648.0,
|
| 666 |
+
1156.0,
|
| 667 |
+
6608.0,
|
| 668 |
+
1196.0,
|
| 669 |
+
6588.0,
|
| 670 |
+
1236.0,
|
| 671 |
+
6568.0,
|
| 672 |
+
1296.0,
|
| 673 |
+
6558.0,
|
| 674 |
+
1326.0,
|
| 675 |
+
6548.0,
|
| 676 |
+
1326.0,
|
| 677 |
+
6548.0,
|
| 678 |
+
1376.0,
|
| 679 |
+
6568.0,
|
| 680 |
+
1416.0,
|
| 681 |
+
6568.0,
|
| 682 |
+
1486.0,
|
| 683 |
+
6558.0,
|
| 684 |
+
1536.0,
|
| 685 |
+
6558.0,
|
| 686 |
+
1586.0,
|
| 687 |
+
6568.0,
|
| 688 |
+
1676.0,
|
| 689 |
+
6568.0,
|
| 690 |
+
1836.0,
|
| 691 |
+
6558.0,
|
| 692 |
+
1916.0,
|
| 693 |
+
6568.0,
|
| 694 |
+
1976.0,
|
| 695 |
+
6568.0,
|
| 696 |
+
2056.0,
|
| 697 |
+
6568.0,
|
| 698 |
+
2136.0,
|
| 699 |
+
6568.0,
|
| 700 |
+
2206.0,
|
| 701 |
+
6568.0,
|
| 702 |
+
2326.0,
|
| 703 |
+
6558.0,
|
| 704 |
+
2396.0,
|
| 705 |
+
6558.0,
|
| 706 |
+
2476.0,
|
| 707 |
+
6548.0,
|
| 708 |
+
2536.0,
|
| 709 |
+
6548.0,
|
| 710 |
+
2606.0,
|
| 711 |
+
6548.0,
|
| 712 |
+
2676.0,
|
| 713 |
+
6538.0,
|
| 714 |
+
2756.0,
|
| 715 |
+
6538.0,
|
| 716 |
+
2856.0,
|
| 717 |
+
6528.0,
|
| 718 |
+
2936.0,
|
| 719 |
+
6528.0,
|
| 720 |
+
2996.0,
|
| 721 |
+
6518.0,
|
| 722 |
+
3066.0,
|
| 723 |
+
6508.0,
|
| 724 |
+
3066.0,
|
| 725 |
+
6508.0,
|
| 726 |
+
3066.0,
|
| 727 |
+
6498.0,
|
| 728 |
+
3066.0,
|
| 729 |
+
6498.0,
|
| 730 |
+
3076.0,
|
| 731 |
+
6438.0,
|
| 732 |
+
3046.0,
|
| 733 |
+
6388.0,
|
| 734 |
+
3026.0,
|
| 735 |
+
6358.0,
|
| 736 |
+
3016.0,
|
| 737 |
+
6308.0,
|
| 738 |
+
2996.0,
|
| 739 |
+
6248.0,
|
| 740 |
+
2956.0,
|
| 741 |
+
6208.0,
|
| 742 |
+
2936.0,
|
| 743 |
+
6158.0,
|
| 744 |
+
2926.0,
|
| 745 |
+
6108.0,
|
| 746 |
+
2936.0,
|
| 747 |
+
6068.0,
|
| 748 |
+
2956.0,
|
| 749 |
+
6028.0,
|
| 750 |
+
2996.0,
|
| 751 |
+
6028.0,
|
| 752 |
+
3036.0,
|
| 753 |
+
6008.0,
|
| 754 |
+
3086.0,
|
| 755 |
+
6008.0,
|
| 756 |
+
3146.0,
|
| 757 |
+
5998.0,
|
| 758 |
+
3176.0,
|
| 759 |
+
5958.0,
|
| 760 |
+
3156.0,
|
| 761 |
+
5908.0,
|
| 762 |
+
3126.0,
|
| 763 |
+
5868.0,
|
| 764 |
+
3076.0,
|
| 765 |
+
5828.0,
|
| 766 |
+
3076.0,
|
| 767 |
+
5788.0,
|
| 768 |
+
3056.0,
|
| 769 |
+
5708.0,
|
| 770 |
+
3056.0,
|
| 771 |
+
5628.0,
|
| 772 |
+
3026.0,
|
| 773 |
+
5588.0,
|
| 774 |
+
2986.0,
|
| 775 |
+
5518.0,
|
| 776 |
+
2936.0,
|
| 777 |
+
5498.0,
|
| 778 |
+
2856.0,
|
| 779 |
+
5498.0,
|
| 780 |
+
2796.0,
|
| 781 |
+
5498.0,
|
| 782 |
+
2756.0,
|
| 783 |
+
5508.0,
|
| 784 |
+
2676.0,
|
| 785 |
+
5508.0,
|
| 786 |
+
2636.0,
|
| 787 |
+
5528.0,
|
| 788 |
+
2556.0,
|
| 789 |
+
5538.0,
|
| 790 |
+
2486.0,
|
| 791 |
+
5538.0,
|
| 792 |
+
2386.0,
|
| 793 |
+
5548.0,
|
| 794 |
+
2386.0,
|
| 795 |
+
5548.0,
|
| 796 |
+
2256.0,
|
| 797 |
+
5548.0,
|
| 798 |
+
2196.0,
|
| 799 |
+
5558.0,
|
| 800 |
+
2136.0,
|
| 801 |
+
5568.0,
|
| 802 |
+
2136.0,
|
| 803 |
+
5568.0,
|
| 804 |
+
2066.0,
|
| 805 |
+
5578.0,
|
| 806 |
+
1956.0,
|
| 807 |
+
5568.0,
|
| 808 |
+
1906.0,
|
| 809 |
+
5588.0,
|
| 810 |
+
1866.0,
|
| 811 |
+
5588.0,
|
| 812 |
+
1796.0,
|
| 813 |
+
5588.0,
|
| 814 |
+
1746.0,
|
| 815 |
+
5588.0,
|
| 816 |
+
1676.0,
|
| 817 |
+
5598.0,
|
| 818 |
+
1636.0,
|
| 819 |
+
5598.0,
|
| 820 |
+
1556.0,
|
| 821 |
+
5598.0,
|
| 822 |
+
1396.0,
|
| 823 |
+
5578.0,
|
| 824 |
+
1366.0,
|
| 825 |
+
5578.0,
|
| 826 |
+
1326.0,
|
| 827 |
+
5568.0,
|
| 828 |
+
1296.0,
|
| 829 |
+
5548.0,
|
| 830 |
+
1266.0,
|
| 831 |
+
5548.0,
|
| 832 |
+
1216.0,
|
| 833 |
+
5558.0,
|
| 834 |
+
1196.0,
|
| 835 |
+
5548.0,
|
| 836 |
+
1136.0,
|
| 837 |
+
5548.0,
|
| 838 |
+
1096.0,
|
| 839 |
+
5548.0,
|
| 840 |
+
1036.0,
|
| 841 |
+
5568.0,
|
| 842 |
+
956.0,
|
| 843 |
+
5568.0,
|
| 844 |
+
926.0,
|
| 845 |
+
5568.0,
|
| 846 |
+
926.0,
|
| 847 |
+
5568.0,
|
| 848 |
+
866.0,
|
| 849 |
+
5558.0,
|
| 850 |
+
796.0,
|
| 851 |
+
5568.0,
|
| 852 |
+
796.0,
|
| 853 |
+
5568.0,
|
| 854 |
+
716.0,
|
| 855 |
+
5628.0
|
| 856 |
+
]
|
| 857 |
+
]
|
| 858 |
+
},
|
| 859 |
+
{
|
| 860 |
+
"id": 36,
|
| 861 |
+
"image_id": 2,
|
| 862 |
+
"category_id": 10,
|
| 863 |
+
"bbox": [
|
| 864 |
+
893.56,
|
| 865 |
+
964.85,
|
| 866 |
+
2206.93,
|
| 867 |
+
264.36
|
| 868 |
+
],
|
| 869 |
+
"area": 262811.36715000035,
|
| 870 |
+
"iscrowd": 0,
|
| 871 |
+
"segmentation": [
|
| 872 |
+
[
|
| 873 |
+
917.33,
|
| 874 |
+
1113.37,
|
| 875 |
+
893.56,
|
| 876 |
+
1143.07,
|
| 877 |
+
911.39,
|
| 878 |
+
1202.48,
|
| 879 |
+
964.85,
|
| 880 |
+
1190.59,
|
| 881 |
+
994.55,
|
| 882 |
+
1229.21,
|
| 883 |
+
1027.23,
|
| 884 |
+
1223.27,
|
| 885 |
+
1053.96,
|
| 886 |
+
1190.59,
|
| 887 |
+
1104.46,
|
| 888 |
+
1190.59,
|
| 889 |
+
1146.04,
|
| 890 |
+
1199.5,
|
| 891 |
+
1244.06,
|
| 892 |
+
1190.59,
|
| 893 |
+
1327.23,
|
| 894 |
+
1190.59,
|
| 895 |
+
1451.98,
|
| 896 |
+
1184.65,
|
| 897 |
+
1520.3,
|
| 898 |
+
1172.77,
|
| 899 |
+
1650.99,
|
| 900 |
+
1187.62,
|
| 901 |
+
1743.07,
|
| 902 |
+
1190.59,
|
| 903 |
+
1850.0,
|
| 904 |
+
1181.68,
|
| 905 |
+
1962.87,
|
| 906 |
+
1166.83,
|
| 907 |
+
2069.8,
|
| 908 |
+
1160.89,
|
| 909 |
+
2188.61,
|
| 910 |
+
1160.89,
|
| 911 |
+
2262.87,
|
| 912 |
+
1163.86,
|
| 913 |
+
2349.01,
|
| 914 |
+
1154.95,
|
| 915 |
+
2470.79,
|
| 916 |
+
1143.07,
|
| 917 |
+
2562.87,
|
| 918 |
+
1157.92,
|
| 919 |
+
2684.65,
|
| 920 |
+
1149.01,
|
| 921 |
+
2794.55,
|
| 922 |
+
1131.19,
|
| 923 |
+
2874.75,
|
| 924 |
+
1131.19,
|
| 925 |
+
2978.71,
|
| 926 |
+
1128.22,
|
| 927 |
+
3076.73,
|
| 928 |
+
1116.34,
|
| 929 |
+
3097.52,
|
| 930 |
+
1056.93,
|
| 931 |
+
3100.49,
|
| 932 |
+
1009.41,
|
| 933 |
+
3032.18,
|
| 934 |
+
991.58,
|
| 935 |
+
2907.43,
|
| 936 |
+
1012.38,
|
| 937 |
+
2812.38,
|
| 938 |
+
1030.2,
|
| 939 |
+
2806.44,
|
| 940 |
+
964.85,
|
| 941 |
+
2744.06,
|
| 942 |
+
970.79,
|
| 943 |
+
2711.39,
|
| 944 |
+
988.61,
|
| 945 |
+
2723.27,
|
| 946 |
+
1030.2,
|
| 947 |
+
2687.62,
|
| 948 |
+
1042.08,
|
| 949 |
+
2580.69,
|
| 950 |
+
1036.14,
|
| 951 |
+
2494.55,
|
| 952 |
+
1036.14,
|
| 953 |
+
2357.92,
|
| 954 |
+
1039.11,
|
| 955 |
+
2224.26,
|
| 956 |
+
1036.14,
|
| 957 |
+
2099.5,
|
| 958 |
+
1045.05,
|
| 959 |
+
1808.42,
|
| 960 |
+
1059.9,
|
| 961 |
+
1749.01,
|
| 962 |
+
1065.84,
|
| 963 |
+
1680.69,
|
| 964 |
+
1056.93,
|
| 965 |
+
1627.23,
|
| 966 |
+
1050.99,
|
| 967 |
+
1564.85,
|
| 968 |
+
1059.9,
|
| 969 |
+
1541.09,
|
| 970 |
+
1083.66,
|
| 971 |
+
1490.59,
|
| 972 |
+
1083.66,
|
| 973 |
+
1350.99,
|
| 974 |
+
1083.66,
|
| 975 |
+
1288.61,
|
| 976 |
+
1042.08,
|
| 977 |
+
1214.36,
|
| 978 |
+
1042.08,
|
| 979 |
+
1184.65,
|
| 980 |
+
1068.81,
|
| 981 |
+
1160.89,
|
| 982 |
+
1089.6,
|
| 983 |
+
1089.6,
|
| 984 |
+
1077.72,
|
| 985 |
+
1012.38,
|
| 986 |
+
1083.66,
|
| 987 |
+
961.88,
|
| 988 |
+
1089.6
|
| 989 |
+
]
|
| 990 |
+
]
|
| 991 |
+
},
|
| 992 |
+
{
|
| 993 |
+
"id": 37,
|
| 994 |
+
"image_id": 2,
|
| 995 |
+
"category_id": 10,
|
| 996 |
+
"bbox": [
|
| 997 |
+
891,
|
| 998 |
+
2489,
|
| 999 |
+
2218,
|
| 1000 |
+
248
|
| 1001 |
+
],
|
| 1002 |
+
"area": 550064,
|
| 1003 |
+
"iscrowd": 0,
|
| 1004 |
+
"segmentation": []
|
| 1005 |
+
},
|
| 1006 |
+
{
|
| 1007 |
+
"id": 38,
|
| 1008 |
+
"image_id": 2,
|
| 1009 |
+
"category_id": 10,
|
| 1010 |
+
"bbox": [
|
| 1011 |
+
891,
|
| 1012 |
+
2271,
|
| 1013 |
+
2218,
|
| 1014 |
+
316
|
| 1015 |
+
],
|
| 1016 |
+
"area": 700888,
|
| 1017 |
+
"iscrowd": 0,
|
| 1018 |
+
"segmentation": []
|
| 1019 |
+
},
|
| 1020 |
+
{
|
| 1021 |
+
"id": 39,
|
| 1022 |
+
"image_id": 2,
|
| 1023 |
+
"category_id": 10,
|
| 1024 |
+
"bbox": [
|
| 1025 |
+
853,
|
| 1026 |
+
1201,
|
| 1027 |
+
2012,
|
| 1028 |
+
254
|
| 1029 |
+
],
|
| 1030 |
+
"area": 511048,
|
| 1031 |
+
"iscrowd": 0,
|
| 1032 |
+
"segmentation": []
|
| 1033 |
+
},
|
| 1034 |
+
{
|
| 1035 |
+
"id": 40,
|
| 1036 |
+
"image_id": 2,
|
| 1037 |
+
"category_id": 10,
|
| 1038 |
+
"bbox": [
|
| 1039 |
+
798,
|
| 1040 |
+
1578,
|
| 1041 |
+
2355,
|
| 1042 |
+
401
|
| 1043 |
+
],
|
| 1044 |
+
"area": 944355,
|
| 1045 |
+
"iscrowd": 0,
|
| 1046 |
+
"segmentation": []
|
| 1047 |
+
},
|
| 1048 |
+
{
|
| 1049 |
+
"id": 41,
|
| 1050 |
+
"image_id": 2,
|
| 1051 |
+
"category_id": 10,
|
| 1052 |
+
"bbox": [
|
| 1053 |
+
912,
|
| 1054 |
+
2636,
|
| 1055 |
+
2097,
|
| 1056 |
+
348
|
| 1057 |
+
],
|
| 1058 |
+
"area": 729756,
|
| 1059 |
+
"iscrowd": 0,
|
| 1060 |
+
"segmentation": []
|
| 1061 |
+
},
|
| 1062 |
+
{
|
| 1063 |
+
"id": 42,
|
| 1064 |
+
"image_id": 2,
|
| 1065 |
+
"category_id": 10,
|
| 1066 |
+
"bbox": [
|
| 1067 |
+
884,
|
| 1068 |
+
1931,
|
| 1069 |
+
2168,
|
| 1070 |
+
216
|
| 1071 |
+
],
|
| 1072 |
+
"area": 468288,
|
| 1073 |
+
"iscrowd": 0,
|
| 1074 |
+
"segmentation": []
|
| 1075 |
+
},
|
| 1076 |
+
{
|
| 1077 |
+
"id": 43,
|
| 1078 |
+
"image_id": 2,
|
| 1079 |
+
"category_id": 10,
|
| 1080 |
+
"bbox": [
|
| 1081 |
+
876,
|
| 1082 |
+
2135,
|
| 1083 |
+
2286,
|
| 1084 |
+
198
|
| 1085 |
+
],
|
| 1086 |
+
"area": 452628,
|
| 1087 |
+
"iscrowd": 0,
|
| 1088 |
+
"segmentation": []
|
| 1089 |
+
},
|
| 1090 |
+
{
|
| 1091 |
+
"id": 44,
|
| 1092 |
+
"image_id": 2,
|
| 1093 |
+
"category_id": 10,
|
| 1094 |
+
"bbox": [
|
| 1095 |
+
844,
|
| 1096 |
+
1370,
|
| 1097 |
+
2308,
|
| 1098 |
+
248
|
| 1099 |
+
],
|
| 1100 |
+
"area": 572384,
|
| 1101 |
+
"iscrowd": 0,
|
| 1102 |
+
"segmentation": []
|
| 1103 |
+
}
|
| 1104 |
+
],
|
| 1105 |
+
"categories": [
|
| 1106 |
+
{
|
| 1107 |
+
"id": 1,
|
| 1108 |
+
"name": "Border",
|
| 1109 |
+
"supercategory": "object"
|
| 1110 |
+
},
|
| 1111 |
+
{
|
| 1112 |
+
"id": 2,
|
| 1113 |
+
"name": "Catchword",
|
| 1114 |
+
"supercategory": "object"
|
| 1115 |
+
},
|
| 1116 |
+
{
|
| 1117 |
+
"id": 3,
|
| 1118 |
+
"name": "Diagram",
|
| 1119 |
+
"supercategory": "object"
|
| 1120 |
+
},
|
| 1121 |
+
{
|
| 1122 |
+
"id": 4,
|
| 1123 |
+
"name": "Embellished",
|
| 1124 |
+
"supercategory": "object"
|
| 1125 |
+
},
|
| 1126 |
+
{
|
| 1127 |
+
"id": 5,
|
| 1128 |
+
"name": "Gloss",
|
| 1129 |
+
"supercategory": "object"
|
| 1130 |
+
},
|
| 1131 |
+
{
|
| 1132 |
+
"id": 6,
|
| 1133 |
+
"name": "Historiated",
|
| 1134 |
+
"supercategory": "object"
|
| 1135 |
+
},
|
| 1136 |
+
{
|
| 1137 |
+
"id": 7,
|
| 1138 |
+
"name": "Ignore",
|
| 1139 |
+
"supercategory": "object"
|
| 1140 |
+
},
|
| 1141 |
+
{
|
| 1142 |
+
"id": 8,
|
| 1143 |
+
"name": "Illustrations",
|
| 1144 |
+
"supercategory": "object"
|
| 1145 |
+
},
|
| 1146 |
+
{
|
| 1147 |
+
"id": 9,
|
| 1148 |
+
"name": "Inhabited",
|
| 1149 |
+
"supercategory": "object"
|
| 1150 |
+
},
|
| 1151 |
+
{
|
| 1152 |
+
"id": 10,
|
| 1153 |
+
"name": "Main script black",
|
| 1154 |
+
"supercategory": "object"
|
| 1155 |
+
},
|
| 1156 |
+
{
|
| 1157 |
+
"id": 11,
|
| 1158 |
+
"name": "Main script coloured",
|
| 1159 |
+
"supercategory": "object"
|
| 1160 |
+
},
|
| 1161 |
+
{
|
| 1162 |
+
"id": 12,
|
| 1163 |
+
"name": "Music",
|
| 1164 |
+
"supercategory": "object"
|
| 1165 |
+
},
|
| 1166 |
+
{
|
| 1167 |
+
"id": 13,
|
| 1168 |
+
"name": "Page Number",
|
| 1169 |
+
"supercategory": "object"
|
| 1170 |
+
},
|
| 1171 |
+
{
|
| 1172 |
+
"id": 14,
|
| 1173 |
+
"name": "Plain initial - Black",
|
| 1174 |
+
"supercategory": "object"
|
| 1175 |
+
},
|
| 1176 |
+
{
|
| 1177 |
+
"id": 15,
|
| 1178 |
+
"name": "Plain initial - Highlighted",
|
| 1179 |
+
"supercategory": "object"
|
| 1180 |
+
},
|
| 1181 |
+
{
|
| 1182 |
+
"id": 16,
|
| 1183 |
+
"name": "Plain initial- coloured",
|
| 1184 |
+
"supercategory": "object"
|
| 1185 |
+
},
|
| 1186 |
+
{
|
| 1187 |
+
"id": 17,
|
| 1188 |
+
"name": "Quire Mark",
|
| 1189 |
+
"supercategory": "object"
|
| 1190 |
+
},
|
| 1191 |
+
{
|
| 1192 |
+
"id": 18,
|
| 1193 |
+
"name": "Running header",
|
| 1194 |
+
"supercategory": "object"
|
| 1195 |
+
},
|
| 1196 |
+
{
|
| 1197 |
+
"id": 19,
|
| 1198 |
+
"name": "Table",
|
| 1199 |
+
"supercategory": "object"
|
| 1200 |
+
},
|
| 1201 |
+
{
|
| 1202 |
+
"id": 20,
|
| 1203 |
+
"name": "Variant script black",
|
| 1204 |
+
"supercategory": "object"
|
| 1205 |
+
},
|
| 1206 |
+
{
|
| 1207 |
+
"id": 21,
|
| 1208 |
+
"name": "Variant script coloured",
|
| 1209 |
+
"supercategory": "object"
|
| 1210 |
+
},
|
| 1211 |
+
{
|
| 1212 |
+
"id": 22,
|
| 1213 |
+
"name": "Zoo - Anthropomorphic",
|
| 1214 |
+
"supercategory": "object"
|
| 1215 |
+
}
|
| 1216 |
+
]
|
| 1217 |
+
}
|
compare/data/new_models.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Run new models (emanuskript, catmus, zone) and convert predictions to COCO format.
|
| 3 |
+
Uses the same logic as app.py and test_combined_models.py.
|
| 4 |
+
"""
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
import tempfile
|
| 10 |
+
|
| 11 |
+
# Add project root to path
|
| 12 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 13 |
+
PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR))
|
| 14 |
+
sys.path.insert(0, PROJECT_ROOT)
|
| 15 |
+
|
| 16 |
+
# Import from project root
|
| 17 |
+
from test_combined_models import run_model_predictions, combine_and_filter_predictions
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def run_new_models_on_image(image_path, conf_threshold=0.25, iou_threshold=0.45):
|
| 21 |
+
"""
|
| 22 |
+
Run new models on a single image and return COCO format predictions.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
image_path: Path to image file
|
| 26 |
+
conf_threshold: Confidence threshold (not directly used, but kept for consistency)
|
| 27 |
+
iou_threshold: IoU threshold (not directly used, but kept for consistency)
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
COCO format dictionary with predictions
|
| 31 |
+
"""
|
| 32 |
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
| 33 |
+
# Run 3 models and save predictions JSON
|
| 34 |
+
labels_folders = run_model_predictions(image_path, tmp_dir)
|
| 35 |
+
|
| 36 |
+
# Combine & filter to coco_class_mapping
|
| 37 |
+
coco_json = combine_and_filter_predictions(
|
| 38 |
+
image_path, labels_folders, output_json_path=None
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
return coco_json
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def process_dataset(images_dir, output_dir, conf_threshold=0.25, iou_threshold=0.45):
|
| 45 |
+
"""
|
| 46 |
+
Process all images in a directory with new models.
|
| 47 |
+
|
| 48 |
+
Args:
|
| 49 |
+
images_dir: Directory containing images
|
| 50 |
+
output_dir: Directory to save COCO JSON files
|
| 51 |
+
conf_threshold: Confidence threshold
|
| 52 |
+
iou_threshold: IoU threshold
|
| 53 |
+
|
| 54 |
+
Returns:
|
| 55 |
+
Merged COCO format dictionary for all images
|
| 56 |
+
"""
|
| 57 |
+
# Get all image files
|
| 58 |
+
image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.tif', '.tiff'}
|
| 59 |
+
image_files = [
|
| 60 |
+
f for f in os.listdir(images_dir)
|
| 61 |
+
if os.path.splitext(f)[1].lower() in image_extensions
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
all_coco_dicts = []
|
| 65 |
+
image_id = 1
|
| 66 |
+
|
| 67 |
+
for image_file in sorted(image_files):
|
| 68 |
+
image_path = os.path.join(images_dir, image_file)
|
| 69 |
+
print(f"Processing {image_file}...")
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
coco = run_new_models_on_image(
|
| 73 |
+
image_path,
|
| 74 |
+
conf_threshold=conf_threshold,
|
| 75 |
+
iou_threshold=iou_threshold
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
# Update image ID if needed
|
| 79 |
+
if len(coco["images"]) > 0:
|
| 80 |
+
coco["images"][0]["id"] = image_id
|
| 81 |
+
|
| 82 |
+
# Update annotation image_ids
|
| 83 |
+
for ann in coco["annotations"]:
|
| 84 |
+
ann["image_id"] = image_id
|
| 85 |
+
|
| 86 |
+
all_coco_dicts.append(coco)
|
| 87 |
+
image_id += 1
|
| 88 |
+
|
| 89 |
+
# Save individual file
|
| 90 |
+
output_path = os.path.join(output_dir, f"{Path(image_file).stem}_new.json")
|
| 91 |
+
with open(output_path, 'w') as f:
|
| 92 |
+
json.dump(coco, f, indent=2)
|
| 93 |
+
|
| 94 |
+
except Exception as e:
|
| 95 |
+
print(f"Error processing {image_file}: {e}")
|
| 96 |
+
import traceback
|
| 97 |
+
traceback.print_exc()
|
| 98 |
+
continue
|
| 99 |
+
|
| 100 |
+
# Merge all COCO dicts
|
| 101 |
+
merged = {
|
| 102 |
+
"info": {"description": "New models predictions - merged"},
|
| 103 |
+
"licenses": [],
|
| 104 |
+
"images": [],
|
| 105 |
+
"annotations": [],
|
| 106 |
+
"categories": []
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
# Use categories from first COCO (they should all be the same)
|
| 110 |
+
if len(all_coco_dicts) > 0:
|
| 111 |
+
merged["categories"] = all_coco_dicts[0]["categories"]
|
| 112 |
+
|
| 113 |
+
# Merge images and annotations
|
| 114 |
+
ann_id = 1
|
| 115 |
+
for coco in all_coco_dicts:
|
| 116 |
+
merged["images"].extend(coco["images"])
|
| 117 |
+
|
| 118 |
+
for ann in coco["annotations"]:
|
| 119 |
+
new_ann = ann.copy()
|
| 120 |
+
new_ann["id"] = ann_id
|
| 121 |
+
merged["annotations"].append(new_ann)
|
| 122 |
+
ann_id += 1
|
| 123 |
+
|
| 124 |
+
return merged
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
if __name__ == "__main__":
|
| 128 |
+
# Test on single image
|
| 129 |
+
test_image = "../../e-codices_bbb-0219_044r_max.jpg"
|
| 130 |
+
coco = run_new_models_on_image(test_image)
|
| 131 |
+
|
| 132 |
+
print(f"Predictions: {len(coco['annotations'])} annotations")
|
| 133 |
+
print(f"Categories: {[c['name'] for c in coco['categories']]}")
|
| 134 |
+
|
| 135 |
+
with open("test_new_models.json", "w") as f:
|
| 136 |
+
json.dump(coco, f, indent=2)
|
| 137 |
+
|
compare/data/old_models.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Run old models (Line, Border, Zones) and convert predictions to COCO format.
|
| 3 |
+
"""
|
| 4 |
+
import os
|
| 5 |
+
import json
|
| 6 |
+
import numpy as np
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from ultralytics import YOLO, YOLOE
|
| 9 |
+
import tempfile
|
| 10 |
+
from typing import Dict, List
|
| 11 |
+
import pycocotools.mask as mask_util
|
| 12 |
+
import cv2
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# Model files (same as app_original_app_with_three_models.py)
|
| 16 |
+
MODEL_FILES = {
|
| 17 |
+
"Line Detection": "best_line_detection_yoloe (1).pt",
|
| 18 |
+
"Border Detection": "border_model_weights.pt",
|
| 19 |
+
"Zones Detection": "zones_model_weights.pt"
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 23 |
+
PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR))
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def load_old_models():
|
| 27 |
+
"""Load the three old models."""
|
| 28 |
+
models = {}
|
| 29 |
+
for name, model_file in MODEL_FILES.items():
|
| 30 |
+
model_path = os.path.join(PROJECT_ROOT, model_file)
|
| 31 |
+
if os.path.exists(model_path):
|
| 32 |
+
try:
|
| 33 |
+
if name == "Line Detection":
|
| 34 |
+
models[name] = YOLOE(model_path)
|
| 35 |
+
else:
|
| 36 |
+
models[name] = YOLO(model_path)
|
| 37 |
+
print(f"✓ Loaded {name} model")
|
| 38 |
+
except Exception as e:
|
| 39 |
+
print(f"✗ Error loading {name} model: {e}")
|
| 40 |
+
models[name] = None
|
| 41 |
+
else:
|
| 42 |
+
print(f"✗ Model file not found: {model_path}")
|
| 43 |
+
models[name] = None
|
| 44 |
+
return models
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def results_to_coco(result, model_name, image_id, image_width, image_height, category_map):
|
| 48 |
+
"""
|
| 49 |
+
Convert YOLO result to COCO format annotations.
|
| 50 |
+
Handles masks properly for YOLOE Line Detection model (like app.py).
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
result: YOLO Results object (single result, not list)
|
| 54 |
+
model_name: Name of the model (for special handling)
|
| 55 |
+
image_id: COCO image ID
|
| 56 |
+
image_width: Image width
|
| 57 |
+
image_height: Image height
|
| 58 |
+
category_map: Dict mapping class names to COCO category IDs
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
List of COCO annotation dictionaries
|
| 62 |
+
"""
|
| 63 |
+
annotations = []
|
| 64 |
+
ann_id = 1
|
| 65 |
+
|
| 66 |
+
if result is None:
|
| 67 |
+
return annotations
|
| 68 |
+
|
| 69 |
+
# Get boxes and masks
|
| 70 |
+
boxes = result.boxes
|
| 71 |
+
if boxes is None:
|
| 72 |
+
return annotations
|
| 73 |
+
|
| 74 |
+
# Get masks if available
|
| 75 |
+
masks = result.masks
|
| 76 |
+
has_masks = masks is not None and len(masks) > 0
|
| 77 |
+
|
| 78 |
+
num_detections = len(boxes)
|
| 79 |
+
|
| 80 |
+
for i in range(num_detections):
|
| 81 |
+
# Get box coordinates
|
| 82 |
+
box = boxes.xyxy[i].cpu().numpy() # [x1, y1, x2, y2]
|
| 83 |
+
x1, y1, x2, y2 = box
|
| 84 |
+
|
| 85 |
+
# Get class
|
| 86 |
+
cls_id = int(boxes.cls[i].cpu().numpy())
|
| 87 |
+
cls_name = result.names[cls_id]
|
| 88 |
+
|
| 89 |
+
# Map "object" to "line" for Line Detection model (like app.py)
|
| 90 |
+
if model_name == "Line Detection" and cls_name == "object":
|
| 91 |
+
cls_name = "line"
|
| 92 |
+
|
| 93 |
+
# Skip if class not in category map
|
| 94 |
+
if cls_name not in category_map:
|
| 95 |
+
continue
|
| 96 |
+
|
| 97 |
+
# Get confidence
|
| 98 |
+
conf = float(boxes.conf[i].cpu().numpy())
|
| 99 |
+
|
| 100 |
+
# Convert bbox to COCO format [x, y, width, height]
|
| 101 |
+
bbox = [float(x1), float(y1), float(x2 - x1), float(y2 - y1)]
|
| 102 |
+
|
| 103 |
+
# Get segmentation
|
| 104 |
+
segmentation = None
|
| 105 |
+
area = bbox[2] * bbox[3] # Default to bbox area
|
| 106 |
+
|
| 107 |
+
if has_masks and i < len(masks.data):
|
| 108 |
+
try:
|
| 109 |
+
# Get mask (like app.py handles YOLOE masks)
|
| 110 |
+
mask = masks.data[i].cpu().numpy()
|
| 111 |
+
|
| 112 |
+
# Handle mask resizing similar to app.py
|
| 113 |
+
if mask.shape != (image_height, image_width):
|
| 114 |
+
# Resize mask to image size using cv2 (like app.py)
|
| 115 |
+
mask_np = (mask > 0).astype(np.uint8)
|
| 116 |
+
resized_mask = cv2.resize(
|
| 117 |
+
mask_np,
|
| 118 |
+
(image_width, image_height),
|
| 119 |
+
interpolation=cv2.INTER_NEAREST
|
| 120 |
+
)
|
| 121 |
+
mask = resized_mask.astype(np.uint8)
|
| 122 |
+
else:
|
| 123 |
+
mask = (mask > 0.5).astype(np.uint8)
|
| 124 |
+
|
| 125 |
+
# Convert to COCO RLE format
|
| 126 |
+
rle = mask_util.encode(np.asfortranarray(mask))
|
| 127 |
+
if isinstance(rle['counts'], bytes):
|
| 128 |
+
rle['counts'] = rle['counts'].decode('utf-8')
|
| 129 |
+
segmentation = rle
|
| 130 |
+
area = float(mask_util.area(rle))
|
| 131 |
+
except Exception as e:
|
| 132 |
+
print(f"Warning: Failed to convert mask to RLE for detection {i}: {e}")
|
| 133 |
+
# Fall back to bbox
|
| 134 |
+
pass
|
| 135 |
+
|
| 136 |
+
# Create COCO annotation
|
| 137 |
+
ann = {
|
| 138 |
+
"id": ann_id,
|
| 139 |
+
"image_id": image_id,
|
| 140 |
+
"category_id": category_map[cls_name],
|
| 141 |
+
"bbox": bbox,
|
| 142 |
+
"area": area,
|
| 143 |
+
"iscrowd": 0,
|
| 144 |
+
"score": conf
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
if segmentation is not None:
|
| 148 |
+
ann["segmentation"] = segmentation
|
| 149 |
+
|
| 150 |
+
annotations.append(ann)
|
| 151 |
+
ann_id += 1
|
| 152 |
+
|
| 153 |
+
return annotations
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def run_old_models_on_image(image_path, models, conf_threshold=0.25, iou_threshold=0.45):
|
| 157 |
+
"""
|
| 158 |
+
Run old models on a single image and return COCO format predictions.
|
| 159 |
+
Matches the behavior of app.py for consistent results.
|
| 160 |
+
|
| 161 |
+
Args:
|
| 162 |
+
image_path: Path to image file
|
| 163 |
+
models: Dict of loaded models
|
| 164 |
+
conf_threshold: Confidence threshold
|
| 165 |
+
iou_threshold: IoU threshold
|
| 166 |
+
|
| 167 |
+
Returns:
|
| 168 |
+
COCO format dictionary with predictions
|
| 169 |
+
"""
|
| 170 |
+
# Load image as numpy array (like app.py does)
|
| 171 |
+
image = cv2.imread(image_path)
|
| 172 |
+
if image is None:
|
| 173 |
+
raise ValueError(f"Failed to load image: {image_path}")
|
| 174 |
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
| 175 |
+
image_height, image_width = image.shape[:2]
|
| 176 |
+
|
| 177 |
+
# Create category map (map all detected classes to sequential IDs)
|
| 178 |
+
all_classes = set()
|
| 179 |
+
results_dict = {}
|
| 180 |
+
|
| 181 |
+
# Run each model
|
| 182 |
+
for model_name, model in models.items():
|
| 183 |
+
if model is None:
|
| 184 |
+
continue
|
| 185 |
+
|
| 186 |
+
try:
|
| 187 |
+
# Use numpy array for prediction (like app.py)
|
| 188 |
+
# Access result as [0] immediately (like app.py)
|
| 189 |
+
result = model.predict(
|
| 190 |
+
image,
|
| 191 |
+
conf=conf_threshold,
|
| 192 |
+
iou=iou_threshold
|
| 193 |
+
)[0]
|
| 194 |
+
|
| 195 |
+
# Collect class names and map "object" to "line" for Line Detection
|
| 196 |
+
if result.names:
|
| 197 |
+
for cls_id, cls_name in result.names.items():
|
| 198 |
+
# Map "object" to "line" for Line Detection model (like app.py)
|
| 199 |
+
if model_name == "Line Detection" and cls_name == "object":
|
| 200 |
+
all_classes.add("line")
|
| 201 |
+
else:
|
| 202 |
+
all_classes.add(cls_name)
|
| 203 |
+
|
| 204 |
+
results_dict[model_name] = result
|
| 205 |
+
except Exception as e:
|
| 206 |
+
print(f"Error running {model_name}: {e}")
|
| 207 |
+
import traceback
|
| 208 |
+
traceback.print_exc()
|
| 209 |
+
results_dict[model_name] = None
|
| 210 |
+
|
| 211 |
+
# Create category mapping
|
| 212 |
+
category_map = {cls_name: idx + 1 for idx, cls_name in enumerate(sorted(all_classes))}
|
| 213 |
+
|
| 214 |
+
# Convert all results to COCO format
|
| 215 |
+
all_annotations = []
|
| 216 |
+
ann_id = 1
|
| 217 |
+
|
| 218 |
+
for model_name, result in results_dict.items():
|
| 219 |
+
if result is None:
|
| 220 |
+
continue
|
| 221 |
+
|
| 222 |
+
annotations = results_to_coco(
|
| 223 |
+
result,
|
| 224 |
+
model_name,
|
| 225 |
+
image_id=1, # Will be set later
|
| 226 |
+
image_width=image_width,
|
| 227 |
+
image_height=image_height,
|
| 228 |
+
category_map=category_map
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
# Update annotation IDs
|
| 232 |
+
for ann in annotations:
|
| 233 |
+
ann["id"] = ann_id
|
| 234 |
+
ann_id += 1
|
| 235 |
+
|
| 236 |
+
all_annotations.extend(annotations)
|
| 237 |
+
|
| 238 |
+
# Create COCO format
|
| 239 |
+
coco = {
|
| 240 |
+
"info": {"description": "Old models predictions"},
|
| 241 |
+
"licenses": [],
|
| 242 |
+
"images": [{
|
| 243 |
+
"id": 1,
|
| 244 |
+
"width": image_width,
|
| 245 |
+
"height": image_height,
|
| 246 |
+
"file_name": os.path.basename(image_path)
|
| 247 |
+
}],
|
| 248 |
+
"annotations": all_annotations,
|
| 249 |
+
"categories": [
|
| 250 |
+
{"id": cid, "name": name, "supercategory": ""}
|
| 251 |
+
for name, cid in sorted(category_map.items(), key=lambda x: x[1])
|
| 252 |
+
]
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
return coco
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def process_dataset(images_dir, output_dir, conf_threshold=0.25, iou_threshold=0.45):
|
| 259 |
+
"""
|
| 260 |
+
Process all images in a directory with old models.
|
| 261 |
+
|
| 262 |
+
Args:
|
| 263 |
+
images_dir: Directory containing images
|
| 264 |
+
output_dir: Directory to save COCO JSON files
|
| 265 |
+
conf_threshold: Confidence threshold
|
| 266 |
+
iou_threshold: IoU threshold
|
| 267 |
+
|
| 268 |
+
Returns:
|
| 269 |
+
Merged COCO format dictionary for all images
|
| 270 |
+
"""
|
| 271 |
+
# Load models
|
| 272 |
+
models = load_old_models()
|
| 273 |
+
|
| 274 |
+
# Get all image files
|
| 275 |
+
image_extensions = {'.jpg', '.jpeg', '.png', '.bmp', '.tif', '.tiff'}
|
| 276 |
+
image_files = [
|
| 277 |
+
f for f in os.listdir(images_dir)
|
| 278 |
+
if os.path.splitext(f)[1].lower() in image_extensions
|
| 279 |
+
]
|
| 280 |
+
|
| 281 |
+
all_coco_dicts = []
|
| 282 |
+
image_id = 1
|
| 283 |
+
|
| 284 |
+
for image_file in sorted(image_files):
|
| 285 |
+
image_path = os.path.join(images_dir, image_file)
|
| 286 |
+
print(f"Processing {image_file}...")
|
| 287 |
+
|
| 288 |
+
try:
|
| 289 |
+
coco = run_old_models_on_image(
|
| 290 |
+
image_path,
|
| 291 |
+
models,
|
| 292 |
+
conf_threshold=conf_threshold,
|
| 293 |
+
iou_threshold=iou_threshold
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
# Update image ID
|
| 297 |
+
coco["images"][0]["id"] = image_id
|
| 298 |
+
|
| 299 |
+
# Update annotation image_ids
|
| 300 |
+
for ann in coco["annotations"]:
|
| 301 |
+
ann["image_id"] = image_id
|
| 302 |
+
|
| 303 |
+
all_coco_dicts.append(coco)
|
| 304 |
+
image_id += 1
|
| 305 |
+
|
| 306 |
+
# Save individual file
|
| 307 |
+
output_path = os.path.join(output_dir, f"{Path(image_file).stem}_old.json")
|
| 308 |
+
with open(output_path, 'w') as f:
|
| 309 |
+
json.dump(coco, f, indent=2)
|
| 310 |
+
|
| 311 |
+
except Exception as e:
|
| 312 |
+
print(f"Error processing {image_file}: {e}")
|
| 313 |
+
continue
|
| 314 |
+
|
| 315 |
+
# Merge all COCO dicts
|
| 316 |
+
merged = {
|
| 317 |
+
"info": {"description": "Old models predictions - merged"},
|
| 318 |
+
"licenses": [],
|
| 319 |
+
"images": [],
|
| 320 |
+
"annotations": [],
|
| 321 |
+
"categories": []
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
# Collect all categories
|
| 325 |
+
all_categories = {}
|
| 326 |
+
for coco in all_coco_dicts:
|
| 327 |
+
for cat in coco["categories"]:
|
| 328 |
+
if cat["name"] not in all_categories:
|
| 329 |
+
all_categories[cat["name"]] = cat["id"]
|
| 330 |
+
|
| 331 |
+
# Update category IDs to be sequential
|
| 332 |
+
category_map = {name: idx + 1 for idx, name in enumerate(sorted(all_categories.keys()))}
|
| 333 |
+
reverse_map = {old_id: category_map[name] for name, old_id in all_categories.items()}
|
| 334 |
+
|
| 335 |
+
merged["categories"] = [
|
| 336 |
+
{"id": cid, "name": name, "supercategory": ""}
|
| 337 |
+
for name, cid in sorted(category_map.items(), key=lambda x: x[1])
|
| 338 |
+
]
|
| 339 |
+
|
| 340 |
+
# Merge images and annotations
|
| 341 |
+
ann_id = 1
|
| 342 |
+
for coco in all_coco_dicts:
|
| 343 |
+
merged["images"].extend(coco["images"])
|
| 344 |
+
|
| 345 |
+
for ann in coco["annotations"]:
|
| 346 |
+
new_ann = ann.copy()
|
| 347 |
+
new_ann["id"] = ann_id
|
| 348 |
+
# Update category_id using reverse_map
|
| 349 |
+
old_cat_id = ann["category_id"]
|
| 350 |
+
# Find category name
|
| 351 |
+
cat_name = next((c["name"] for c in coco["categories"] if c["id"] == old_cat_id), None)
|
| 352 |
+
if cat_name and cat_name in category_map:
|
| 353 |
+
new_ann["category_id"] = category_map[cat_name]
|
| 354 |
+
merged["annotations"].append(new_ann)
|
| 355 |
+
ann_id += 1
|
| 356 |
+
|
| 357 |
+
return merged
|
| 358 |
+
|
| 359 |
+
|
| 360 |
+
if __name__ == "__main__":
|
| 361 |
+
# Test on single image
|
| 362 |
+
test_image = "../../e-codices_bbb-0219_044r_max.jpg"
|
| 363 |
+
models = load_old_models()
|
| 364 |
+
coco = run_old_models_on_image(test_image, models)
|
| 365 |
+
|
| 366 |
+
print(f"Predictions: {len(coco['annotations'])} annotations")
|
| 367 |
+
print(f"Categories: {[c['name'] for c in coco['categories']]}")
|
| 368 |
+
|
| 369 |
+
with open("test_old_models.json", "w") as f:
|
| 370 |
+
json.dump(coco, f, indent=2)
|
| 371 |
+
|
compare/data/original_annotations.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Parse CVAT XML annotations and convert to COCO format for evaluation.
|
| 3 |
+
"""
|
| 4 |
+
import xml.etree.ElementTree as ET
|
| 5 |
+
import json
|
| 6 |
+
import numpy as np
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from PIL import Image
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
import pycocotools.mask as mask_util
|
| 12 |
+
HAS_PYCOCOTOOLS = True
|
| 13 |
+
except ImportError:
|
| 14 |
+
HAS_PYCOCOTOOLS = False
|
| 15 |
+
print("Warning: pycocotools not available. Install with: pip install pycocotools")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def parse_rle(rle_string, width, height):
|
| 19 |
+
"""
|
| 20 |
+
Parse RLE (Run-Length Encoding) string from CVAT format.
|
| 21 |
+
CVAT RLE format is a simple list of counts: "count1, count2, count3, ..."
|
| 22 |
+
This represents a flattened binary mask where counts alternate between
|
| 23 |
+
runs of 0s and 1s.
|
| 24 |
+
"""
|
| 25 |
+
if not rle_string or not rle_string.strip():
|
| 26 |
+
return None
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
# Split by comma and convert to integers
|
| 30 |
+
counts = [int(x.strip()) for x in rle_string.split(',') if x.strip()]
|
| 31 |
+
|
| 32 |
+
if len(counts) == 0:
|
| 33 |
+
return None
|
| 34 |
+
|
| 35 |
+
# Create binary mask
|
| 36 |
+
mask = np.zeros((height, width), dtype=np.uint8)
|
| 37 |
+
|
| 38 |
+
# Parse RLE: counts alternate between 0s and 1s
|
| 39 |
+
# First count is typically 0s, then 1s, then 0s, etc.
|
| 40 |
+
pos = 0
|
| 41 |
+
is_foreground = False # Start with background (0s)
|
| 42 |
+
|
| 43 |
+
for count in counts:
|
| 44 |
+
if is_foreground:
|
| 45 |
+
# Fill foreground pixels
|
| 46 |
+
for _ in range(count):
|
| 47 |
+
y = pos // width
|
| 48 |
+
x = pos % width
|
| 49 |
+
if y < height and x < width:
|
| 50 |
+
mask[y, x] = 1
|
| 51 |
+
pos += 1
|
| 52 |
+
else:
|
| 53 |
+
# Skip background pixels
|
| 54 |
+
pos += count
|
| 55 |
+
|
| 56 |
+
is_foreground = not is_foreground
|
| 57 |
+
|
| 58 |
+
# Convert to COCO RLE format
|
| 59 |
+
try:
|
| 60 |
+
rle = mask_util.encode(np.asfortranarray(mask))
|
| 61 |
+
rle['counts'] = rle['counts'].decode('utf-8')
|
| 62 |
+
return rle
|
| 63 |
+
except ImportError:
|
| 64 |
+
# Fallback if pycocotools not available
|
| 65 |
+
print("Warning: pycocotools not available, using bbox only")
|
| 66 |
+
return None
|
| 67 |
+
except Exception as e:
|
| 68 |
+
print(f"Warning: Failed to parse RLE: {e}")
|
| 69 |
+
return None
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def bbox_from_mask(rle, width, height):
|
| 73 |
+
"""Extract bounding box from RLE mask."""
|
| 74 |
+
if rle is None or not HAS_PYCOCOTOOLS:
|
| 75 |
+
return None
|
| 76 |
+
|
| 77 |
+
try:
|
| 78 |
+
# Decode RLE to get mask
|
| 79 |
+
rle_decoded = rle.copy()
|
| 80 |
+
rle_decoded['counts'] = rle_decoded['counts'].encode('utf-8')
|
| 81 |
+
mask = mask_util.decode(rle_decoded)
|
| 82 |
+
|
| 83 |
+
# Find bounding box
|
| 84 |
+
rows = np.any(mask, axis=1)
|
| 85 |
+
cols = np.any(mask, axis=0)
|
| 86 |
+
|
| 87 |
+
if not np.any(rows) or not np.any(cols):
|
| 88 |
+
return None
|
| 89 |
+
|
| 90 |
+
y_min, y_max = np.where(rows)[0][[0, -1]]
|
| 91 |
+
x_min, x_max = np.where(cols)[0][[0, -1]]
|
| 92 |
+
|
| 93 |
+
# COCO format: [x, y, width, height]
|
| 94 |
+
return [int(x_min), int(y_min), int(x_max - x_min + 1), int(y_max - y_min + 1)]
|
| 95 |
+
except Exception as e:
|
| 96 |
+
print(f"Warning: Failed to extract bbox from mask: {e}")
|
| 97 |
+
return None
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def parse_cvat_xml(xml_path, images_dir):
|
| 101 |
+
"""
|
| 102 |
+
Parse CVAT XML file and convert to COCO format.
|
| 103 |
+
Handles <box>, <polygon>, and <mask> annotations.
|
| 104 |
+
|
| 105 |
+
Args:
|
| 106 |
+
xml_path: Path to CVAT annotations.xml file
|
| 107 |
+
images_dir: Directory containing the images
|
| 108 |
+
|
| 109 |
+
Returns:
|
| 110 |
+
COCO format dictionary
|
| 111 |
+
"""
|
| 112 |
+
# 1. Load the XML
|
| 113 |
+
try:
|
| 114 |
+
tree = ET.parse(xml_path)
|
| 115 |
+
root = tree.getroot()
|
| 116 |
+
except FileNotFoundError:
|
| 117 |
+
print(f"Error: Could not find XML file: {xml_path}")
|
| 118 |
+
return None
|
| 119 |
+
|
| 120 |
+
# 2. Initialize COCO structure
|
| 121 |
+
coco = {
|
| 122 |
+
"info": {
|
| 123 |
+
"description": "Converted from CVAT XML",
|
| 124 |
+
"year": 2024,
|
| 125 |
+
"version": "1.0"
|
| 126 |
+
},
|
| 127 |
+
"licenses": [],
|
| 128 |
+
"images": [],
|
| 129 |
+
"annotations": [],
|
| 130 |
+
"categories": []
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
# 3. Create Category (Label) Map
|
| 134 |
+
# First, try to get labels from <label> tags in meta section
|
| 135 |
+
labels = set()
|
| 136 |
+
for label in root.findall('.//label'):
|
| 137 |
+
label_name = label.find('name')
|
| 138 |
+
if label_name is not None and label_name.text:
|
| 139 |
+
labels.add(label_name.text)
|
| 140 |
+
|
| 141 |
+
# Also scan images for any labels used in annotations
|
| 142 |
+
for image in root.findall('image'):
|
| 143 |
+
for child in image:
|
| 144 |
+
if child.tag in ['box', 'polygon', 'mask']:
|
| 145 |
+
label = child.get('label')
|
| 146 |
+
if label:
|
| 147 |
+
labels.add(label)
|
| 148 |
+
|
| 149 |
+
# Sort labels to ensure consistent IDs
|
| 150 |
+
label_map = {}
|
| 151 |
+
for i, label_name in enumerate(sorted(list(labels))):
|
| 152 |
+
category_id = i + 1 # COCO IDs start at 1
|
| 153 |
+
label_map[label_name] = category_id
|
| 154 |
+
coco["categories"].append({
|
| 155 |
+
"id": category_id,
|
| 156 |
+
"name": label_name,
|
| 157 |
+
"supercategory": "object"
|
| 158 |
+
})
|
| 159 |
+
|
| 160 |
+
print(f"Found Categories: {label_map}")
|
| 161 |
+
|
| 162 |
+
# 4. Parse Images and Annotations
|
| 163 |
+
annotation_id = 1
|
| 164 |
+
image_id = 1
|
| 165 |
+
|
| 166 |
+
# CVAT images are stored in <image> tags
|
| 167 |
+
for img_tag in root.findall('image'):
|
| 168 |
+
file_name = img_tag.get('name')
|
| 169 |
+
|
| 170 |
+
# Check if file exists
|
| 171 |
+
full_image_path = Path(images_dir) / file_name
|
| 172 |
+
if not full_image_path.exists():
|
| 173 |
+
print(f"Warning: Image {file_name} mentioned in XML not found in folder. Processing anyway.")
|
| 174 |
+
|
| 175 |
+
width = int(img_tag.get('width'))
|
| 176 |
+
height = int(img_tag.get('height'))
|
| 177 |
+
|
| 178 |
+
# Add Image to COCO
|
| 179 |
+
coco_image = {
|
| 180 |
+
"id": image_id,
|
| 181 |
+
"width": width,
|
| 182 |
+
"height": height,
|
| 183 |
+
"file_name": file_name
|
| 184 |
+
}
|
| 185 |
+
coco["images"].append(coco_image)
|
| 186 |
+
|
| 187 |
+
# Process Bounding Boxes (<box>)
|
| 188 |
+
for box in img_tag.findall('box'):
|
| 189 |
+
label = box.get('label')
|
| 190 |
+
if label not in label_map:
|
| 191 |
+
continue
|
| 192 |
+
|
| 193 |
+
# CVAT uses Top-Left (xtl, ytl) and Bottom-Right (xbr, ybr)
|
| 194 |
+
xtl = float(box.get('xtl'))
|
| 195 |
+
ytl = float(box.get('ytl'))
|
| 196 |
+
xbr = float(box.get('xbr'))
|
| 197 |
+
ybr = float(box.get('ybr'))
|
| 198 |
+
|
| 199 |
+
# Convert to COCO format: [x_min, y_min, width, height]
|
| 200 |
+
w = xbr - xtl
|
| 201 |
+
h = ybr - ytl
|
| 202 |
+
bbox = [xtl, ytl, w, h]
|
| 203 |
+
area = w * h
|
| 204 |
+
|
| 205 |
+
ann = {
|
| 206 |
+
"id": annotation_id,
|
| 207 |
+
"image_id": image_id,
|
| 208 |
+
"category_id": label_map[label],
|
| 209 |
+
"bbox": bbox,
|
| 210 |
+
"area": area,
|
| 211 |
+
"iscrowd": 0,
|
| 212 |
+
"segmentation": [] # Empty for simple boxes
|
| 213 |
+
}
|
| 214 |
+
coco["annotations"].append(ann)
|
| 215 |
+
annotation_id += 1
|
| 216 |
+
|
| 217 |
+
# Process Polygons (<polygon>)
|
| 218 |
+
for poly in img_tag.findall('polygon'):
|
| 219 |
+
label = poly.get('label')
|
| 220 |
+
if label not in label_map:
|
| 221 |
+
continue
|
| 222 |
+
|
| 223 |
+
points_str = poly.get('points') # "x1,y1;x2,y2;..."
|
| 224 |
+
|
| 225 |
+
# Parse points into flat list [x1, y1, x2, y2, ...]
|
| 226 |
+
points = []
|
| 227 |
+
for pair in points_str.split(';'):
|
| 228 |
+
if not pair.strip():
|
| 229 |
+
continue
|
| 230 |
+
x, y = map(float, pair.split(','))
|
| 231 |
+
points.extend([x, y])
|
| 232 |
+
|
| 233 |
+
if len(points) < 6: # Need at least 3 points (6 coordinates)
|
| 234 |
+
continue
|
| 235 |
+
|
| 236 |
+
# Calculate bounding box from polygon
|
| 237 |
+
x_coords = points[0::2]
|
| 238 |
+
y_coords = points[1::2]
|
| 239 |
+
x_min = min(x_coords)
|
| 240 |
+
y_min = min(y_coords)
|
| 241 |
+
w = max(x_coords) - x_min
|
| 242 |
+
h = max(y_coords) - y_min
|
| 243 |
+
|
| 244 |
+
# Calculate polygon area using shoelace formula
|
| 245 |
+
area = 0.5 * abs(sum(x_coords[i] * y_coords[(i + 1) % len(x_coords)] -
|
| 246 |
+
x_coords[(i + 1) % len(x_coords)] * y_coords[i]
|
| 247 |
+
for i in range(len(x_coords))))
|
| 248 |
+
|
| 249 |
+
ann = {
|
| 250 |
+
"id": annotation_id,
|
| 251 |
+
"image_id": image_id,
|
| 252 |
+
"category_id": label_map[label],
|
| 253 |
+
"bbox": [x_min, y_min, w, h],
|
| 254 |
+
"area": area,
|
| 255 |
+
"iscrowd": 0,
|
| 256 |
+
"segmentation": [points]
|
| 257 |
+
}
|
| 258 |
+
coco["annotations"].append(ann)
|
| 259 |
+
annotation_id += 1
|
| 260 |
+
|
| 261 |
+
# Process Masks (<mask>)
|
| 262 |
+
for mask_elem in img_tag.findall('mask'):
|
| 263 |
+
label_name = mask_elem.get('label')
|
| 264 |
+
if label_name not in label_map:
|
| 265 |
+
continue
|
| 266 |
+
|
| 267 |
+
# Get RLE data
|
| 268 |
+
rle_string = mask_elem.text
|
| 269 |
+
left = int(mask_elem.get('left', 0))
|
| 270 |
+
top = int(mask_elem.get('top', 0))
|
| 271 |
+
mask_width = int(mask_elem.get('width', width))
|
| 272 |
+
mask_height = int(mask_elem.get('height', height))
|
| 273 |
+
|
| 274 |
+
# Parse RLE
|
| 275 |
+
rle = parse_rle(rle_string, mask_width, mask_height)
|
| 276 |
+
if rle is None:
|
| 277 |
+
# Fallback: try to create bbox from mask attributes
|
| 278 |
+
bbox = [left, top, mask_width, mask_height]
|
| 279 |
+
area = mask_width * mask_height
|
| 280 |
+
ann = {
|
| 281 |
+
"id": annotation_id,
|
| 282 |
+
"image_id": image_id,
|
| 283 |
+
"category_id": label_map[label_name],
|
| 284 |
+
"bbox": bbox,
|
| 285 |
+
"area": area,
|
| 286 |
+
"iscrowd": 0,
|
| 287 |
+
"segmentation": []
|
| 288 |
+
}
|
| 289 |
+
coco["annotations"].append(ann)
|
| 290 |
+
annotation_id += 1
|
| 291 |
+
continue
|
| 292 |
+
|
| 293 |
+
# Get bounding box from mask
|
| 294 |
+
bbox = bbox_from_mask(rle, mask_width, mask_height)
|
| 295 |
+
if bbox is None:
|
| 296 |
+
continue
|
| 297 |
+
|
| 298 |
+
# Adjust bbox coordinates if mask has offset
|
| 299 |
+
bbox[0] += left
|
| 300 |
+
bbox[1] += top
|
| 301 |
+
|
| 302 |
+
# Calculate area
|
| 303 |
+
if HAS_PYCOCOTOOLS:
|
| 304 |
+
area = mask_util.area(rle)
|
| 305 |
+
else:
|
| 306 |
+
# Approximate area from bbox
|
| 307 |
+
area = bbox[2] * bbox[3]
|
| 308 |
+
|
| 309 |
+
# Create COCO annotation
|
| 310 |
+
ann = {
|
| 311 |
+
"id": annotation_id,
|
| 312 |
+
"image_id": image_id,
|
| 313 |
+
"category_id": label_map[label_name],
|
| 314 |
+
"segmentation": rle,
|
| 315 |
+
"area": float(area),
|
| 316 |
+
"bbox": bbox,
|
| 317 |
+
"iscrowd": 0
|
| 318 |
+
}
|
| 319 |
+
coco["annotations"].append(ann)
|
| 320 |
+
annotation_id += 1
|
| 321 |
+
|
| 322 |
+
image_id += 1
|
| 323 |
+
|
| 324 |
+
return coco
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def load_ground_truth(xml_path, images_dir):
|
| 328 |
+
"""
|
| 329 |
+
Load ground truth annotations from CVAT XML.
|
| 330 |
+
|
| 331 |
+
Args:
|
| 332 |
+
xml_path: Path to annotations.xml
|
| 333 |
+
images_dir: Directory containing images
|
| 334 |
+
|
| 335 |
+
Returns:
|
| 336 |
+
COCO format dictionary
|
| 337 |
+
"""
|
| 338 |
+
return parse_cvat_xml(xml_path, images_dir)
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
if __name__ == "__main__":
|
| 342 |
+
# Test parsing
|
| 343 |
+
xml_path = "Aleyna 1 (2024)/Annotations/annotations.xml"
|
| 344 |
+
images_dir = "Aleyna 1 (2024)/Images"
|
| 345 |
+
output_json = "ground_truth_coco.json"
|
| 346 |
+
|
| 347 |
+
coco = load_ground_truth(xml_path, images_dir)
|
| 348 |
+
|
| 349 |
+
if coco:
|
| 350 |
+
print(f"\nSuccess! Converted {len(coco['images'])} images and {len(coco['annotations'])} annotations.")
|
| 351 |
+
print(f"Categories: {[c['name'] for c in coco['categories']]}")
|
| 352 |
+
|
| 353 |
+
# Save to JSON for inspection
|
| 354 |
+
with open(output_json, "w") as f:
|
| 355 |
+
json.dump(coco, f, indent=4)
|
| 356 |
+
print(f"Saved to: {output_json}")
|
| 357 |
+
else:
|
| 358 |
+
print("Error: Failed to parse XML file")
|
| 359 |
+
|
compare/data/sample_batches_model_comparison_summary.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"folder": "SampleBatch2",
|
| 4 |
+
"old_metrics": {
|
| 5 |
+
"mAP@50": 0.0,
|
| 6 |
+
"mAP@[.50:.95]": 0.0,
|
| 7 |
+
"Precision": 0.0,
|
| 8 |
+
"Recall": 0.0,
|
| 9 |
+
"F1": 0.0,
|
| 10 |
+
"error": "list index out of range"
|
| 11 |
+
},
|
| 12 |
+
"new_metrics": {
|
| 13 |
+
"mAP@50": 0.0,
|
| 14 |
+
"mAP@[.50:.95]": 0.0,
|
| 15 |
+
"Precision": 0.0,
|
| 16 |
+
"Recall": 0.0,
|
| 17 |
+
"F1": 0.0,
|
| 18 |
+
"error": "list index out of range"
|
| 19 |
+
},
|
| 20 |
+
"old_annotations": 50,
|
| 21 |
+
"new_annotations": 1465,
|
| 22 |
+
"gt_annotations": 1437
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"folder": "SampleBatch3",
|
| 26 |
+
"old_metrics": {
|
| 27 |
+
"mAP@50": 0.0,
|
| 28 |
+
"mAP@[.50:.95]": 0.0,
|
| 29 |
+
"Precision": 0.0,
|
| 30 |
+
"Recall": 0.0,
|
| 31 |
+
"F1": 0.0,
|
| 32 |
+
"error": "list index out of range"
|
| 33 |
+
},
|
| 34 |
+
"new_metrics": {
|
| 35 |
+
"mAP@50": 0.0,
|
| 36 |
+
"mAP@[.50:.95]": 0.0,
|
| 37 |
+
"Precision": 0.0,
|
| 38 |
+
"Recall": 0.0,
|
| 39 |
+
"F1": 0.0,
|
| 40 |
+
"error": "list index out of range"
|
| 41 |
+
},
|
| 42 |
+
"old_annotations": 50,
|
| 43 |
+
"new_annotations": 923,
|
| 44 |
+
"gt_annotations": 909
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"folder": "SampleBatch4",
|
| 48 |
+
"old_metrics": {
|
| 49 |
+
"mAP@50": 0.0,
|
| 50 |
+
"mAP@[.50:.95]": 0.0,
|
| 51 |
+
"Precision": 0.0,
|
| 52 |
+
"Recall": 0.0,
|
| 53 |
+
"F1": 0.0,
|
| 54 |
+
"error": "list index out of range"
|
| 55 |
+
},
|
| 56 |
+
"new_metrics": {
|
| 57 |
+
"mAP@50": 0.0,
|
| 58 |
+
"mAP@[.50:.95]": 0.0,
|
| 59 |
+
"Precision": 0.0,
|
| 60 |
+
"Recall": 0.0,
|
| 61 |
+
"F1": 0.0,
|
| 62 |
+
"error": "list index out of range"
|
| 63 |
+
},
|
| 64 |
+
"old_annotations": 50,
|
| 65 |
+
"new_annotations": 1167,
|
| 66 |
+
"gt_annotations": 1160
|
| 67 |
+
}
|
| 68 |
+
]
|
compare/data/sample_batches_summary.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"total_batches": 3,
|
| 3 |
+
"successful": 3,
|
| 4 |
+
"failed": 0,
|
| 5 |
+
"results": [
|
| 6 |
+
{
|
| 7 |
+
"folder": "SampleBatch2",
|
| 8 |
+
"status": "success",
|
| 9 |
+
"images": 25,
|
| 10 |
+
"annotations": 1437,
|
| 11 |
+
"categories": 25,
|
| 12 |
+
"visualizations_path": "/home/hasan/layout/compare/data/SampleBatch2/visualizations"
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"folder": "SampleBatch3",
|
| 16 |
+
"status": "success",
|
| 17 |
+
"images": 25,
|
| 18 |
+
"annotations": 909,
|
| 19 |
+
"categories": 25,
|
| 20 |
+
"visualizations_path": "/home/hasan/layout/compare/data/SampleBatch3/visualizations"
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"folder": "SampleBatch4",
|
| 24 |
+
"status": "success",
|
| 25 |
+
"images": 25,
|
| 26 |
+
"annotations": 1160,
|
| 27 |
+
"categories": 25,
|
| 28 |
+
"visualizations_path": "/home/hasan/layout/compare/data/SampleBatch4/visualizations"
|
| 29 |
+
}
|
| 30 |
+
]
|
| 31 |
+
}
|
compare/data/test_models_on_sample_batches.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test old models and new models on SampleBatch data.
|
| 3 |
+
Compare both against ground truth and calculate metrics.
|
| 4 |
+
Create side-by-side visualizations: GT | Old Models | New Models
|
| 5 |
+
"""
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import json
|
| 9 |
+
import tempfile
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
import numpy as np
|
| 12 |
+
from PIL import Image
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
import matplotlib.patches as patches
|
| 15 |
+
|
| 16 |
+
# Add paths
|
| 17 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 18 |
+
PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR))
|
| 19 |
+
sys.path.insert(0, SCRIPT_DIR)
|
| 20 |
+
sys.path.insert(0, PROJECT_ROOT)
|
| 21 |
+
|
| 22 |
+
from old_models import process_dataset as process_old_models, run_old_models_on_image
|
| 23 |
+
from new_models import process_dataset as process_new_models, run_new_models_on_image
|
| 24 |
+
from compare import calculate_metrics, align_categories, draw_coco_annotations_simple
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
from pycocotools.coco import COCO
|
| 28 |
+
from pycocotools.cocoeval import COCOeval
|
| 29 |
+
HAS_PYCOCOTOOLS = True
|
| 30 |
+
except ImportError:
|
| 31 |
+
HAS_PYCOCOTOOLS = False
|
| 32 |
+
print("Warning: pycocotools not available. Metrics calculation will be limited.")
|
| 33 |
+
|
| 34 |
+
# No mapping - use old models' actual output directly
|
| 35 |
+
|
| 36 |
+
# Sample batch folders
|
| 37 |
+
SAMPLE_BATCH_FOLDERS = [
|
| 38 |
+
"SampleBatch2",
|
| 39 |
+
"SampleBatch3",
|
| 40 |
+
"SampleBatch4",
|
| 41 |
+
]
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# Removed mapping function - use old models' output directly
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def create_side_by_side_visualization(image_path, gt_coco, old_coco, new_coco, output_path):
|
| 48 |
+
"""
|
| 49 |
+
Create side-by-side visualization: GT | Old Models | New Models
|
| 50 |
+
"""
|
| 51 |
+
fig, axes = plt.subplots(1, 3, figsize=(30, 10))
|
| 52 |
+
|
| 53 |
+
# Left: Ground Truth
|
| 54 |
+
draw_coco_annotations_simple(image_path, gt_coco, "Ground Truth", axes[0])
|
| 55 |
+
|
| 56 |
+
# Middle: Old Models
|
| 57 |
+
draw_coco_annotations_simple(image_path, old_coco, "Old Models", axes[1])
|
| 58 |
+
|
| 59 |
+
# Right: New Models
|
| 60 |
+
draw_coco_annotations_simple(image_path, new_coco, "New Models", axes[2])
|
| 61 |
+
|
| 62 |
+
plt.tight_layout()
|
| 63 |
+
plt.savefig(output_path, dpi=150, bbox_inches='tight')
|
| 64 |
+
plt.close()
|
| 65 |
+
print(f" ✓ Saved comparison to: {output_path}")
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def process_sample_batch(folder_name, base_dir=None):
|
| 69 |
+
"""
|
| 70 |
+
Process a single sample batch: test old and new models, calculate metrics, create visualizations.
|
| 71 |
+
"""
|
| 72 |
+
if base_dir is None:
|
| 73 |
+
base_dir = SCRIPT_DIR
|
| 74 |
+
|
| 75 |
+
folder_path = Path(base_dir) / folder_name
|
| 76 |
+
|
| 77 |
+
if not folder_path.exists():
|
| 78 |
+
print(f"⚠️ Warning: Folder not found: {folder_path}")
|
| 79 |
+
return None
|
| 80 |
+
|
| 81 |
+
print("\n" + "=" * 70)
|
| 82 |
+
print(f"Processing: {folder_name}")
|
| 83 |
+
print("=" * 70)
|
| 84 |
+
|
| 85 |
+
# Paths
|
| 86 |
+
gt_json_path = folder_path / "Annotations" / "instances_default.json"
|
| 87 |
+
images_dir = folder_path / "Images"
|
| 88 |
+
output_dir = folder_path / "model_comparison"
|
| 89 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 90 |
+
|
| 91 |
+
# Load ground truth
|
| 92 |
+
print(f"\n[1/5] Loading ground truth...")
|
| 93 |
+
with open(gt_json_path, 'r') as f:
|
| 94 |
+
gt_coco = json.load(f)
|
| 95 |
+
|
| 96 |
+
print(f" ✓ Loaded {len(gt_coco['images'])} images")
|
| 97 |
+
print(f" ✓ Loaded {len(gt_coco['annotations'])} annotations")
|
| 98 |
+
|
| 99 |
+
# Run old models
|
| 100 |
+
print(f"\n[2/5] Running old models...")
|
| 101 |
+
old_output_dir = output_dir / "old_models"
|
| 102 |
+
os.makedirs(old_output_dir, exist_ok=True)
|
| 103 |
+
|
| 104 |
+
try:
|
| 105 |
+
old_coco = process_old_models(str(images_dir), str(old_output_dir))
|
| 106 |
+
print(f" ✓ Generated {len(old_coco['annotations'])} annotations")
|
| 107 |
+
print(f" ✓ Categories: {[c['name'] for c in old_coco['categories']]}")
|
| 108 |
+
|
| 109 |
+
except Exception as e:
|
| 110 |
+
print(f" ❌ Error running old models: {e}")
|
| 111 |
+
import traceback
|
| 112 |
+
traceback.print_exc()
|
| 113 |
+
return None
|
| 114 |
+
|
| 115 |
+
# Run new models
|
| 116 |
+
print(f"\n[3/5] Running new models...")
|
| 117 |
+
new_output_dir = output_dir / "new_models"
|
| 118 |
+
os.makedirs(new_output_dir, exist_ok=True)
|
| 119 |
+
|
| 120 |
+
try:
|
| 121 |
+
new_coco = process_new_models(str(images_dir), str(new_output_dir))
|
| 122 |
+
print(f" ✓ Generated {len(new_coco['annotations'])} annotations")
|
| 123 |
+
|
| 124 |
+
except Exception as e:
|
| 125 |
+
print(f" ❌ Error running new models: {e}")
|
| 126 |
+
import traceback
|
| 127 |
+
traceback.print_exc()
|
| 128 |
+
return None
|
| 129 |
+
|
| 130 |
+
# Calculate metrics
|
| 131 |
+
print(f"\n[4/5] Calculating metrics...")
|
| 132 |
+
|
| 133 |
+
# Align categories with ground truth (by name matching)
|
| 134 |
+
old_coco_aligned = align_categories(gt_coco.copy(), old_coco.copy())
|
| 135 |
+
new_coco_aligned = align_categories(gt_coco.copy(), new_coco.copy())
|
| 136 |
+
|
| 137 |
+
# Calculate metrics for old models
|
| 138 |
+
print(f"\n Old Models Metrics:")
|
| 139 |
+
old_metrics = calculate_metrics(gt_coco, old_coco_aligned, str(output_dir))
|
| 140 |
+
print(f" mAP@50: {old_metrics.get('mAP@50', 0):.4f}")
|
| 141 |
+
print(f" mAP@[.50:.95]: {old_metrics.get('mAP@[.50:.95]', 0):.4f}")
|
| 142 |
+
print(f" Precision: {old_metrics.get('Precision', 0):.4f}")
|
| 143 |
+
print(f" Recall: {old_metrics.get('Recall', 0):.4f}")
|
| 144 |
+
|
| 145 |
+
# Calculate metrics for new models
|
| 146 |
+
print(f"\n New Models Metrics:")
|
| 147 |
+
new_metrics = calculate_metrics(gt_coco, new_coco_aligned, str(output_dir))
|
| 148 |
+
print(f" mAP@50: {new_metrics.get('mAP@50', 0):.4f}")
|
| 149 |
+
print(f" mAP@[.50:.95]: {new_metrics.get('mAP@[.50:.95]', 0):.4f}")
|
| 150 |
+
print(f" Precision: {new_metrics.get('Precision', 0):.4f}")
|
| 151 |
+
print(f" Recall: {new_metrics.get('Recall', 0):.4f}")
|
| 152 |
+
|
| 153 |
+
# Save metrics
|
| 154 |
+
metrics_path = output_dir / "metrics.json"
|
| 155 |
+
with open(metrics_path, 'w') as f:
|
| 156 |
+
json.dump({
|
| 157 |
+
"old_models": old_metrics,
|
| 158 |
+
"new_models": new_metrics
|
| 159 |
+
}, f, indent=4)
|
| 160 |
+
print(f" ✓ Saved metrics to: {metrics_path}")
|
| 161 |
+
|
| 162 |
+
# Create visualizations
|
| 163 |
+
print(f"\n[5/5] Creating side-by-side visualizations...")
|
| 164 |
+
vis_dir = output_dir / "visualizations"
|
| 165 |
+
os.makedirs(vis_dir, exist_ok=True)
|
| 166 |
+
|
| 167 |
+
for img_info in gt_coco["images"]:
|
| 168 |
+
image_name = img_info["file_name"]
|
| 169 |
+
image_path = images_dir / image_name
|
| 170 |
+
|
| 171 |
+
if not image_path.exists():
|
| 172 |
+
continue
|
| 173 |
+
|
| 174 |
+
img_id = img_info["id"]
|
| 175 |
+
|
| 176 |
+
# Filter annotations for this image
|
| 177 |
+
gt_img_coco = {
|
| 178 |
+
"images": [img_info],
|
| 179 |
+
"annotations": [a for a in gt_coco["annotations"] if a["image_id"] == img_id],
|
| 180 |
+
"categories": gt_coco["categories"]
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
old_img_coco = {
|
| 184 |
+
"images": [img_info],
|
| 185 |
+
"annotations": [a for a in old_coco["annotations"] if a["image_id"] == img_id],
|
| 186 |
+
"categories": old_coco["categories"]
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
new_img_coco = {
|
| 190 |
+
"images": [img_info],
|
| 191 |
+
"annotations": [a for a in new_coco["annotations"] if a["image_id"] == img_id],
|
| 192 |
+
"categories": new_coco["categories"]
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
# Create visualization
|
| 196 |
+
output_path = vis_dir / f"{Path(image_name).stem}_comparison.png"
|
| 197 |
+
create_side_by_side_visualization(
|
| 198 |
+
str(image_path),
|
| 199 |
+
gt_img_coco,
|
| 200 |
+
old_img_coco,
|
| 201 |
+
new_img_coco,
|
| 202 |
+
str(output_path)
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
print(f" ✓ Visualizations saved to: {vis_dir}")
|
| 206 |
+
|
| 207 |
+
return {
|
| 208 |
+
"folder": folder_name,
|
| 209 |
+
"old_metrics": old_metrics,
|
| 210 |
+
"new_metrics": new_metrics,
|
| 211 |
+
"old_annotations": len(old_coco["annotations"]),
|
| 212 |
+
"new_annotations": len(new_coco["annotations"]),
|
| 213 |
+
"gt_annotations": len(gt_coco["annotations"])
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def main():
|
| 218 |
+
"""Main function to process all sample batches."""
|
| 219 |
+
print("=" * 70)
|
| 220 |
+
print("TESTING MODELS ON SAMPLE BATCHES")
|
| 221 |
+
print("=" * 70)
|
| 222 |
+
print(f"\nProcessing {len(SAMPLE_BATCH_FOLDERS)} sample batch folders:")
|
| 223 |
+
for folder in SAMPLE_BATCH_FOLDERS:
|
| 224 |
+
print(f" - {folder}")
|
| 225 |
+
|
| 226 |
+
results = []
|
| 227 |
+
|
| 228 |
+
for folder_name in SAMPLE_BATCH_FOLDERS:
|
| 229 |
+
result = process_sample_batch(folder_name)
|
| 230 |
+
if result:
|
| 231 |
+
results.append(result)
|
| 232 |
+
|
| 233 |
+
# Print summary
|
| 234 |
+
print("\n" + "=" * 70)
|
| 235 |
+
print("SUMMARY")
|
| 236 |
+
print("=" * 70)
|
| 237 |
+
|
| 238 |
+
for r in results:
|
| 239 |
+
print(f"\n{r['folder']}:")
|
| 240 |
+
print(f" Ground Truth: {r['gt_annotations']} annotations")
|
| 241 |
+
print(f" Old Models: {r['old_annotations']} annotations")
|
| 242 |
+
print(f" mAP@50: {r['old_metrics'].get('mAP@50', 0):.4f}")
|
| 243 |
+
print(f" Precision: {r['old_metrics'].get('Precision', 0):.4f}")
|
| 244 |
+
print(f" Recall: {r['old_metrics'].get('Recall', 0):.4f}")
|
| 245 |
+
print(f" New Models: {r['new_annotations']} annotations")
|
| 246 |
+
print(f" mAP@50: {r['new_metrics'].get('mAP@50', 0):.4f}")
|
| 247 |
+
print(f" Precision: {r['new_metrics'].get('Precision', 0):.4f}")
|
| 248 |
+
print(f" Recall: {r['new_metrics'].get('Recall', 0):.4f}")
|
| 249 |
+
|
| 250 |
+
# Save summary
|
| 251 |
+
summary_path = Path(SCRIPT_DIR) / "sample_batches_model_comparison_summary.json"
|
| 252 |
+
with open(summary_path, 'w') as f:
|
| 253 |
+
json.dump(results, f, indent=4)
|
| 254 |
+
|
| 255 |
+
print(f"\n✓ Summary saved to: {summary_path}")
|
| 256 |
+
print("\n" + "=" * 70)
|
| 257 |
+
print("COMPLETE!")
|
| 258 |
+
print("=" * 70)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
if __name__ == "__main__":
|
| 262 |
+
main()
|
| 263 |
+
|
compare/data/test_old_models.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test script for old_models.py to verify it works like app.py
|
| 3 |
+
"""
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
import json
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import numpy as np
|
| 9 |
+
from PIL import Image
|
| 10 |
+
import matplotlib.pyplot as plt
|
| 11 |
+
import matplotlib.patches as patches
|
| 12 |
+
import pycocotools.mask as mask_util
|
| 13 |
+
import cv2
|
| 14 |
+
|
| 15 |
+
# Add paths
|
| 16 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 17 |
+
PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR))
|
| 18 |
+
sys.path.insert(0, SCRIPT_DIR)
|
| 19 |
+
sys.path.insert(0, PROJECT_ROOT)
|
| 20 |
+
|
| 21 |
+
from old_models import load_old_models, run_old_models_on_image
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def visualize_annotations(image_path, coco_json, output_path):
|
| 25 |
+
"""
|
| 26 |
+
Visualize COCO annotations on the image.
|
| 27 |
+
"""
|
| 28 |
+
img = Image.open(image_path).convert("RGB")
|
| 29 |
+
fig, ax = plt.subplots(1, 1, figsize=(12, 16))
|
| 30 |
+
ax.imshow(img)
|
| 31 |
+
ax.set_title("Old Models Predictions", fontsize=16, fontweight='bold')
|
| 32 |
+
ax.axis("off")
|
| 33 |
+
|
| 34 |
+
if not coco_json.get("images") or not coco_json.get("annotations"):
|
| 35 |
+
plt.savefig(output_path, dpi=150, bbox_inches='tight')
|
| 36 |
+
plt.close()
|
| 37 |
+
return
|
| 38 |
+
|
| 39 |
+
img_info = coco_json["images"][0]
|
| 40 |
+
img_id = img_info["id"]
|
| 41 |
+
anns = [a for a in coco_json["annotations"] if a["image_id"] == img_id]
|
| 42 |
+
|
| 43 |
+
id_to_name = {c["id"]: c["name"] for c in coco_json["categories"]}
|
| 44 |
+
|
| 45 |
+
# Color map
|
| 46 |
+
colors = plt.cm.tab20(np.linspace(0, 1, 20))
|
| 47 |
+
color_map = {}
|
| 48 |
+
|
| 49 |
+
# Track label positions to avoid overlap
|
| 50 |
+
placed_labels = []
|
| 51 |
+
|
| 52 |
+
def find_label_position(bbox, text_width, text_height, image_width, image_height):
|
| 53 |
+
"""Find a good position for label to avoid overlap."""
|
| 54 |
+
x, y, w, h = bbox
|
| 55 |
+
candidates = [
|
| 56 |
+
(x, y - text_height - 5), # Above top-left
|
| 57 |
+
(x, y), # Top-left corner
|
| 58 |
+
(x + w - text_width, y), # Top-right corner
|
| 59 |
+
(x, y + h + 5), # Below bottom-left
|
| 60 |
+
]
|
| 61 |
+
|
| 62 |
+
for pos_x, pos_y in candidates:
|
| 63 |
+
# Check if position is within image bounds
|
| 64 |
+
if pos_x < 0 or pos_y < 0 or pos_x + text_width > image_width or pos_y + text_height > image_height:
|
| 65 |
+
continue
|
| 66 |
+
|
| 67 |
+
# Check overlap with existing labels
|
| 68 |
+
overlap = False
|
| 69 |
+
for placed_x, placed_y, placed_w, placed_h in placed_labels:
|
| 70 |
+
if not (pos_x + text_width < placed_x or pos_x > placed_x + placed_w or
|
| 71 |
+
pos_y + text_height < placed_y or pos_y > placed_y + placed_h):
|
| 72 |
+
overlap = True
|
| 73 |
+
break
|
| 74 |
+
|
| 75 |
+
if not overlap:
|
| 76 |
+
return pos_x, pos_y
|
| 77 |
+
|
| 78 |
+
# If all positions overlap, use top-left anyway
|
| 79 |
+
return x, y
|
| 80 |
+
|
| 81 |
+
img_width, img_height = img.size
|
| 82 |
+
|
| 83 |
+
for ann in anns:
|
| 84 |
+
name = id_to_name.get(ann["category_id"], f"cls_{ann['category_id']}")
|
| 85 |
+
|
| 86 |
+
# Get or assign color
|
| 87 |
+
if name not in color_map:
|
| 88 |
+
color_idx = len(color_map) % len(colors)
|
| 89 |
+
color_map[name] = colors[color_idx]
|
| 90 |
+
|
| 91 |
+
color = color_map[name]
|
| 92 |
+
|
| 93 |
+
# Get bbox
|
| 94 |
+
bbox = ann.get("bbox", [0, 0, 0, 0])
|
| 95 |
+
if not bbox or len(bbox) < 4:
|
| 96 |
+
# Try to get bbox from segmentation
|
| 97 |
+
segs = ann.get("segmentation", {})
|
| 98 |
+
if isinstance(segs, dict) and 'counts' in segs:
|
| 99 |
+
# RLE mask
|
| 100 |
+
try:
|
| 101 |
+
rle = segs
|
| 102 |
+
if isinstance(rle['counts'], str):
|
| 103 |
+
rle['counts'] = rle['counts'].encode('utf-8')
|
| 104 |
+
mask = mask_util.decode(rle)
|
| 105 |
+
ys, xs = np.where(mask > 0)
|
| 106 |
+
if len(xs) > 0 and len(ys) > 0:
|
| 107 |
+
bbox = [float(min(xs)), float(min(ys)), float(max(xs) - min(xs)), float(max(ys) - min(ys))]
|
| 108 |
+
else:
|
| 109 |
+
continue
|
| 110 |
+
except Exception as e:
|
| 111 |
+
continue
|
| 112 |
+
elif isinstance(segs, list) and len(segs) > 0:
|
| 113 |
+
if isinstance(segs[0], list) and len(segs[0]) >= 6:
|
| 114 |
+
coords = segs[0]
|
| 115 |
+
xs = coords[0::2]
|
| 116 |
+
ys = coords[1::2]
|
| 117 |
+
bbox = [min(xs), min(ys), max(xs) - min(xs), max(ys) - min(ys)]
|
| 118 |
+
else:
|
| 119 |
+
continue
|
| 120 |
+
else:
|
| 121 |
+
continue
|
| 122 |
+
|
| 123 |
+
x, y, w, h = bbox
|
| 124 |
+
|
| 125 |
+
# Draw segmentation or bbox
|
| 126 |
+
segs = ann.get("segmentation", {})
|
| 127 |
+
if isinstance(segs, dict) and 'counts' in segs:
|
| 128 |
+
# RLE mask - draw as filled polygon using contour
|
| 129 |
+
try:
|
| 130 |
+
rle = segs
|
| 131 |
+
if isinstance(rle['counts'], str):
|
| 132 |
+
rle['counts'] = rle['counts'].encode('utf-8')
|
| 133 |
+
mask = mask_util.decode(rle)
|
| 134 |
+
|
| 135 |
+
# Use cv2 to find contours (memory efficient)
|
| 136 |
+
mask_uint8 = (mask * 255).astype(np.uint8)
|
| 137 |
+
contours, _ = cv2.findContours(mask_uint8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 138 |
+
|
| 139 |
+
for contour in contours:
|
| 140 |
+
if len(contour) > 2:
|
| 141 |
+
# Convert contour to list of (x, y) tuples
|
| 142 |
+
poly_coords = [(pt[0][0], pt[0][1]) for pt in contour]
|
| 143 |
+
poly = patches.Polygon(
|
| 144 |
+
poly_coords, closed=True,
|
| 145 |
+
edgecolor=color, facecolor=color,
|
| 146 |
+
linewidth=2, alpha=0.3
|
| 147 |
+
)
|
| 148 |
+
ax.add_patch(poly)
|
| 149 |
+
poly_edge = patches.Polygon(
|
| 150 |
+
poly_coords, closed=True,
|
| 151 |
+
edgecolor=color, facecolor="none",
|
| 152 |
+
linewidth=2, alpha=0.8
|
| 153 |
+
)
|
| 154 |
+
ax.add_patch(poly_edge)
|
| 155 |
+
except Exception as e:
|
| 156 |
+
# Fall back to bbox
|
| 157 |
+
rect = patches.Rectangle(
|
| 158 |
+
(x, y), w, h,
|
| 159 |
+
edgecolor=color, facecolor=color,
|
| 160 |
+
linewidth=2, alpha=0.3
|
| 161 |
+
)
|
| 162 |
+
ax.add_patch(rect)
|
| 163 |
+
rect_edge = patches.Rectangle(
|
| 164 |
+
(x, y), w, h,
|
| 165 |
+
edgecolor=color, facecolor="none",
|
| 166 |
+
linewidth=2, alpha=0.8
|
| 167 |
+
)
|
| 168 |
+
ax.add_patch(rect_edge)
|
| 169 |
+
elif isinstance(segs, list) and len(segs) > 0:
|
| 170 |
+
if isinstance(segs[0], list) and len(segs[0]) >= 6:
|
| 171 |
+
# Polygon
|
| 172 |
+
coords = segs[0]
|
| 173 |
+
xs = coords[0::2]
|
| 174 |
+
ys = coords[1::2]
|
| 175 |
+
poly = patches.Polygon(
|
| 176 |
+
list(zip(xs, ys)), closed=True,
|
| 177 |
+
edgecolor=color, facecolor=color,
|
| 178 |
+
linewidth=2, alpha=0.3
|
| 179 |
+
)
|
| 180 |
+
ax.add_patch(poly)
|
| 181 |
+
poly_edge = patches.Polygon(
|
| 182 |
+
list(zip(xs, ys)), closed=True,
|
| 183 |
+
edgecolor=color, facecolor="none",
|
| 184 |
+
linewidth=2, alpha=0.8
|
| 185 |
+
)
|
| 186 |
+
ax.add_patch(poly_edge)
|
| 187 |
+
else:
|
| 188 |
+
# Fall back to bbox
|
| 189 |
+
rect = patches.Rectangle(
|
| 190 |
+
(x, y), w, h,
|
| 191 |
+
edgecolor=color, facecolor=color,
|
| 192 |
+
linewidth=2, alpha=0.3
|
| 193 |
+
)
|
| 194 |
+
ax.add_patch(rect)
|
| 195 |
+
rect_edge = patches.Rectangle(
|
| 196 |
+
(x, y), w, h,
|
| 197 |
+
edgecolor=color, facecolor="none",
|
| 198 |
+
linewidth=2, alpha=0.8
|
| 199 |
+
)
|
| 200 |
+
ax.add_patch(rect_edge)
|
| 201 |
+
else:
|
| 202 |
+
# Bbox only
|
| 203 |
+
rect = patches.Rectangle(
|
| 204 |
+
(x, y), w, h,
|
| 205 |
+
edgecolor=color, facecolor=color,
|
| 206 |
+
linewidth=2, alpha=0.3
|
| 207 |
+
)
|
| 208 |
+
ax.add_patch(rect)
|
| 209 |
+
rect_edge = patches.Rectangle(
|
| 210 |
+
(x, y), w, h,
|
| 211 |
+
edgecolor=color, facecolor="none",
|
| 212 |
+
linewidth=2, alpha=0.8
|
| 213 |
+
)
|
| 214 |
+
ax.add_patch(rect_edge)
|
| 215 |
+
|
| 216 |
+
# Add label
|
| 217 |
+
text_width = len(name) * 7
|
| 218 |
+
text_height = 12
|
| 219 |
+
label_x, label_y = find_label_position(bbox, text_width, text_height, img_width, img_height)
|
| 220 |
+
placed_labels.append((label_x, label_y, text_width, text_height))
|
| 221 |
+
|
| 222 |
+
edge_color = tuple(color[:3]) if isinstance(color, np.ndarray) else color
|
| 223 |
+
ax.text(
|
| 224 |
+
label_x, label_y, name,
|
| 225 |
+
color='black', fontsize=9, fontweight='bold',
|
| 226 |
+
bbox=dict(
|
| 227 |
+
boxstyle="round,pad=0.3",
|
| 228 |
+
facecolor="white",
|
| 229 |
+
edgecolor=edge_color,
|
| 230 |
+
linewidth=2,
|
| 231 |
+
alpha=0.9,
|
| 232 |
+
),
|
| 233 |
+
zorder=10,
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
plt.tight_layout()
|
| 237 |
+
plt.savefig(output_path, dpi=150, bbox_inches='tight')
|
| 238 |
+
plt.close()
|
| 239 |
+
print(f" ✓ Saved visualization to: {output_path}")
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def test_single_image():
|
| 243 |
+
"""Test old models on a single image."""
|
| 244 |
+
print("=" * 70)
|
| 245 |
+
print("TESTING OLD MODELS ON SINGLE IMAGE")
|
| 246 |
+
print("=" * 70)
|
| 247 |
+
|
| 248 |
+
# Find a test image (use first available image from SampleBatch2)
|
| 249 |
+
test_image_dir = Path(SCRIPT_DIR) / "SampleBatch2" / "Images"
|
| 250 |
+
if not test_image_dir.exists():
|
| 251 |
+
print(f"⚠️ Test image directory not found: {test_image_dir}")
|
| 252 |
+
print("Please provide a test image path.")
|
| 253 |
+
return
|
| 254 |
+
|
| 255 |
+
# Get first image
|
| 256 |
+
image_files = list(test_image_dir.glob("*.jpg")) + list(test_image_dir.glob("*.png"))
|
| 257 |
+
if not image_files:
|
| 258 |
+
print(f"⚠️ No images found in {test_image_dir}")
|
| 259 |
+
return
|
| 260 |
+
|
| 261 |
+
test_image_path = image_files[0]
|
| 262 |
+
print(f"\n📸 Testing with image: {test_image_path.name}")
|
| 263 |
+
|
| 264 |
+
# Load models
|
| 265 |
+
print("\n[1/3] Loading models...")
|
| 266 |
+
models = load_old_models()
|
| 267 |
+
|
| 268 |
+
# Check if all models loaded
|
| 269 |
+
failed_models = [name for name, model in models.items() if model is None]
|
| 270 |
+
if failed_models:
|
| 271 |
+
print(f"⚠️ Warning: Some models failed to load: {failed_models}")
|
| 272 |
+
|
| 273 |
+
# Run predictions
|
| 274 |
+
print(f"\n[2/3] Running predictions...")
|
| 275 |
+
try:
|
| 276 |
+
coco = run_old_models_on_image(
|
| 277 |
+
str(test_image_path),
|
| 278 |
+
models,
|
| 279 |
+
conf_threshold=0.25,
|
| 280 |
+
iou_threshold=0.45
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
print(f" ✓ Generated {len(coco['annotations'])} annotations")
|
| 284 |
+
print(f" ✓ Categories: {[c['name'] for c in coco['categories']]}")
|
| 285 |
+
|
| 286 |
+
# Count annotations per model/category
|
| 287 |
+
category_counts = {}
|
| 288 |
+
for ann in coco['annotations']:
|
| 289 |
+
cat_id = ann['category_id']
|
| 290 |
+
cat_name = next((c['name'] for c in coco['categories'] if c['id'] == cat_id), f"cat_{cat_id}")
|
| 291 |
+
category_counts[cat_name] = category_counts.get(cat_name, 0) + 1
|
| 292 |
+
|
| 293 |
+
print(f"\n Annotation counts by category:")
|
| 294 |
+
for cat_name, count in sorted(category_counts.items()):
|
| 295 |
+
print(f" - {cat_name}: {count}")
|
| 296 |
+
|
| 297 |
+
# Check for masks (Line Detection should have masks)
|
| 298 |
+
masks_count = sum(1 for ann in coco['annotations'] if 'segmentation' in ann)
|
| 299 |
+
print(f"\n ✓ Annotations with masks: {masks_count}")
|
| 300 |
+
|
| 301 |
+
except Exception as e:
|
| 302 |
+
print(f" ❌ Error running predictions: {e}")
|
| 303 |
+
import traceback
|
| 304 |
+
traceback.print_exc()
|
| 305 |
+
return
|
| 306 |
+
|
| 307 |
+
# Save results
|
| 308 |
+
print(f"\n[3/4] Saving results...")
|
| 309 |
+
output_path = Path(SCRIPT_DIR) / "test_old_models_output.json"
|
| 310 |
+
with open(output_path, 'w') as f:
|
| 311 |
+
json.dump(coco, f, indent=2)
|
| 312 |
+
|
| 313 |
+
print(f" ✓ Saved to: {output_path}")
|
| 314 |
+
|
| 315 |
+
# Visualize annotations
|
| 316 |
+
print(f"\n[4/4] Creating visualization...")
|
| 317 |
+
vis_output_path = Path(SCRIPT_DIR) / "test_old_models_visualization.png"
|
| 318 |
+
try:
|
| 319 |
+
visualize_annotations(str(test_image_path), coco, str(vis_output_path))
|
| 320 |
+
except Exception as e:
|
| 321 |
+
print(f" ⚠️ Warning: Failed to create visualization: {e}")
|
| 322 |
+
import traceback
|
| 323 |
+
traceback.print_exc()
|
| 324 |
+
|
| 325 |
+
print("\n" + "=" * 70)
|
| 326 |
+
print("TEST COMPLETE!")
|
| 327 |
+
print("=" * 70)
|
| 328 |
+
print(f"\n📊 Results saved:")
|
| 329 |
+
print(f" - JSON: {output_path}")
|
| 330 |
+
print(f" - Visualization: {vis_output_path}")
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
if __name__ == "__main__":
|
| 334 |
+
test_single_image()
|
| 335 |
+
|
compare/data/test_old_models_output.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
compare/data/visualize_ground_truth.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Visualize ground truth annotations from COCO format on images.
|
| 3 |
+
This helps verify the accuracy of XML to COCO conversion.
|
| 4 |
+
"""
|
| 5 |
+
import os
|
| 6 |
+
import json
|
| 7 |
+
import sys
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
import numpy as np
|
| 10 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 11 |
+
import matplotlib.pyplot as plt
|
| 12 |
+
import matplotlib.patches as patches
|
| 13 |
+
import matplotlib.colors as mcolors
|
| 14 |
+
|
| 15 |
+
# Add current directory to path
|
| 16 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 17 |
+
sys.path.insert(0, SCRIPT_DIR)
|
| 18 |
+
|
| 19 |
+
from original_annotations import load_ground_truth
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
import pycocotools.mask as mask_util
|
| 23 |
+
HAS_PYCOCOTOOLS = True
|
| 24 |
+
except ImportError:
|
| 25 |
+
HAS_PYCOCOTOOLS = False
|
| 26 |
+
print("Warning: pycocotools not available. Mask visualization may be limited.")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def decode_rle(rle, width, height):
|
| 30 |
+
"""Decode COCO RLE to binary mask."""
|
| 31 |
+
if not HAS_PYCOCOTOOLS:
|
| 32 |
+
return None
|
| 33 |
+
try:
|
| 34 |
+
rle_decoded = rle.copy()
|
| 35 |
+
rle_decoded['counts'] = rle_decoded['counts'].encode('utf-8')
|
| 36 |
+
mask = mask_util.decode(rle_decoded)
|
| 37 |
+
return mask
|
| 38 |
+
except Exception as e:
|
| 39 |
+
print(f"Warning: Failed to decode RLE: {e}")
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def draw_coco_annotations(image_path, coco_json, output_path=None, show_labels=True):
|
| 44 |
+
"""
|
| 45 |
+
Draw COCO annotations on an image.
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
image_path: Path to image file
|
| 49 |
+
coco_json: COCO format dictionary
|
| 50 |
+
output_path: Path to save visualized image (if None, returns numpy array)
|
| 51 |
+
show_labels: Whether to show class labels
|
| 52 |
+
|
| 53 |
+
Returns:
|
| 54 |
+
numpy array of visualized image (if output_path is None)
|
| 55 |
+
"""
|
| 56 |
+
# Load image
|
| 57 |
+
img = Image.open(image_path).convert("RGB")
|
| 58 |
+
img_array = np.array(img)
|
| 59 |
+
|
| 60 |
+
# Get image info from COCO
|
| 61 |
+
image_name = os.path.basename(image_path)
|
| 62 |
+
img_info = None
|
| 63 |
+
for img_data in coco_json["images"]:
|
| 64 |
+
if img_data["file_name"] == image_name:
|
| 65 |
+
img_info = img_data
|
| 66 |
+
break
|
| 67 |
+
|
| 68 |
+
if img_info is None:
|
| 69 |
+
print(f"Warning: Image {image_name} not found in COCO data")
|
| 70 |
+
return img_array
|
| 71 |
+
|
| 72 |
+
img_id = img_info["id"]
|
| 73 |
+
|
| 74 |
+
# Get annotations for this image
|
| 75 |
+
annotations = [a for a in coco_json["annotations"] if a["image_id"] == img_id]
|
| 76 |
+
|
| 77 |
+
if len(annotations) == 0:
|
| 78 |
+
print(f"No annotations found for {image_name}")
|
| 79 |
+
return img_array
|
| 80 |
+
|
| 81 |
+
# Create category name map
|
| 82 |
+
id_to_name = {c["id"]: c["name"] for c in coco_json["categories"]}
|
| 83 |
+
|
| 84 |
+
# Create figure
|
| 85 |
+
fig, ax = plt.subplots(1, 1, figsize=(15, 20))
|
| 86 |
+
ax.imshow(img_array)
|
| 87 |
+
ax.axis("off")
|
| 88 |
+
ax.set_title(f"{image_name}\n({len(annotations)} annotations)",
|
| 89 |
+
fontsize=14, fontweight='bold', pad=20)
|
| 90 |
+
|
| 91 |
+
# Generate distinct colors for each category
|
| 92 |
+
num_categories = len(coco_json["categories"])
|
| 93 |
+
colors = plt.cm.tab20(np.linspace(0, 1, min(20, num_categories)))
|
| 94 |
+
if num_categories > 20:
|
| 95 |
+
# Use additional colormap for more categories
|
| 96 |
+
colors2 = plt.cm.Set3(np.linspace(0, 1, num_categories - 20))
|
| 97 |
+
colors = np.vstack([colors, colors2])
|
| 98 |
+
|
| 99 |
+
category_colors = {}
|
| 100 |
+
for idx, cat in enumerate(coco_json["categories"]):
|
| 101 |
+
category_colors[cat["id"]] = colors[idx % len(colors)]
|
| 102 |
+
|
| 103 |
+
# Draw each annotation
|
| 104 |
+
for ann in annotations:
|
| 105 |
+
cat_id = ann["category_id"]
|
| 106 |
+
cat_name = id_to_name.get(cat_id, f"category_{cat_id}")
|
| 107 |
+
color = category_colors.get(cat_id, [1, 0, 0, 0.5]) # Red fallback
|
| 108 |
+
|
| 109 |
+
# Get segmentation
|
| 110 |
+
segs = ann.get("segmentation", [])
|
| 111 |
+
bbox = ann.get("bbox", [0, 0, 0, 0])
|
| 112 |
+
|
| 113 |
+
# Draw segmentation (polygon or mask)
|
| 114 |
+
if segs:
|
| 115 |
+
if isinstance(segs, list) and len(segs) > 0:
|
| 116 |
+
# Check if it's RLE (dict) or polygon (list of coordinates)
|
| 117 |
+
if isinstance(segs, dict) or (isinstance(segs, list) and len(segs) > 0 and isinstance(segs[0], dict)):
|
| 118 |
+
# RLE mask
|
| 119 |
+
if isinstance(segs, list):
|
| 120 |
+
rle = segs[0]
|
| 121 |
+
else:
|
| 122 |
+
rle = segs
|
| 123 |
+
|
| 124 |
+
if HAS_PYCOCOTOOLS:
|
| 125 |
+
mask = decode_rle(rle, img_info["width"], img_info["height"])
|
| 126 |
+
if mask is not None:
|
| 127 |
+
# Draw mask with transparency
|
| 128 |
+
mask_colored = np.zeros((*mask.shape, 4))
|
| 129 |
+
mask_colored[mask > 0] = [*color[:3], 0.3] # Semi-transparent fill
|
| 130 |
+
ax.imshow(mask_colored, alpha=0.5)
|
| 131 |
+
|
| 132 |
+
# Draw mask outline
|
| 133 |
+
try:
|
| 134 |
+
from scipy import ndimage
|
| 135 |
+
contours = ndimage.binary_erosion(mask) ^ mask
|
| 136 |
+
ax.contour(contours, colors=[color[:3]], linewidths=2, alpha=0.8)
|
| 137 |
+
except ImportError:
|
| 138 |
+
# Fallback: just draw the mask without contour
|
| 139 |
+
pass
|
| 140 |
+
|
| 141 |
+
elif isinstance(segs[0], list) and len(segs[0]) >= 6:
|
| 142 |
+
# Polygon: flat list [x1, y1, x2, y2, ...]
|
| 143 |
+
coords = segs[0]
|
| 144 |
+
xs = coords[0::2]
|
| 145 |
+
ys = coords[1::2]
|
| 146 |
+
|
| 147 |
+
# Draw polygon with fill
|
| 148 |
+
poly = patches.Polygon(
|
| 149 |
+
list(zip(xs, ys)),
|
| 150 |
+
closed=True,
|
| 151 |
+
edgecolor=color[:3],
|
| 152 |
+
facecolor=color[:3],
|
| 153 |
+
linewidth=2.5,
|
| 154 |
+
alpha=0.3, # Semi-transparent fill
|
| 155 |
+
)
|
| 156 |
+
ax.add_patch(poly)
|
| 157 |
+
|
| 158 |
+
# Draw polygon outline
|
| 159 |
+
poly_edge = patches.Polygon(
|
| 160 |
+
list(zip(xs, ys)),
|
| 161 |
+
closed=True,
|
| 162 |
+
edgecolor=color[:3],
|
| 163 |
+
facecolor="none",
|
| 164 |
+
linewidth=2.5,
|
| 165 |
+
alpha=0.8, # More opaque edge
|
| 166 |
+
)
|
| 167 |
+
ax.add_patch(poly_edge)
|
| 168 |
+
|
| 169 |
+
# Draw bounding box if no segmentation or as fallback
|
| 170 |
+
if not segs or (isinstance(segs, list) and len(segs) == 0):
|
| 171 |
+
x, y, w, h = bbox
|
| 172 |
+
if w > 0 and h > 0:
|
| 173 |
+
rect = patches.Rectangle(
|
| 174 |
+
(x, y),
|
| 175 |
+
w,
|
| 176 |
+
h,
|
| 177 |
+
edgecolor=color[:3],
|
| 178 |
+
facecolor=color[:3],
|
| 179 |
+
linewidth=2.5,
|
| 180 |
+
alpha=0.3,
|
| 181 |
+
)
|
| 182 |
+
ax.add_patch(rect)
|
| 183 |
+
|
| 184 |
+
# Draw bbox outline
|
| 185 |
+
rect_edge = patches.Rectangle(
|
| 186 |
+
(x, y),
|
| 187 |
+
w,
|
| 188 |
+
h,
|
| 189 |
+
edgecolor=color[:3],
|
| 190 |
+
facecolor="none",
|
| 191 |
+
linewidth=2.5,
|
| 192 |
+
alpha=0.8,
|
| 193 |
+
)
|
| 194 |
+
ax.add_patch(rect_edge)
|
| 195 |
+
|
| 196 |
+
# Add label
|
| 197 |
+
if show_labels:
|
| 198 |
+
# Get position for label (use bbox or polygon center)
|
| 199 |
+
if segs and isinstance(segs, list) and len(segs) > 0:
|
| 200 |
+
if isinstance(segs[0], list) and len(segs[0]) >= 6:
|
| 201 |
+
# Polygon
|
| 202 |
+
coords = segs[0]
|
| 203 |
+
xs = coords[0::2]
|
| 204 |
+
ys = coords[1::2]
|
| 205 |
+
label_x = min(xs)
|
| 206 |
+
label_y = min(ys) - 10
|
| 207 |
+
else:
|
| 208 |
+
# Use bbox
|
| 209 |
+
x, y, w, h = bbox
|
| 210 |
+
label_x = x
|
| 211 |
+
label_y = y - 10
|
| 212 |
+
else:
|
| 213 |
+
x, y, w, h = bbox
|
| 214 |
+
label_x = x
|
| 215 |
+
label_y = y - 10
|
| 216 |
+
|
| 217 |
+
# Draw label with background
|
| 218 |
+
ax.text(
|
| 219 |
+
label_x,
|
| 220 |
+
label_y,
|
| 221 |
+
cat_name,
|
| 222 |
+
color='black',
|
| 223 |
+
fontsize=10,
|
| 224 |
+
fontweight='bold',
|
| 225 |
+
bbox=dict(
|
| 226 |
+
boxstyle="round,pad=0.5",
|
| 227 |
+
facecolor="white",
|
| 228 |
+
edgecolor=color[:3],
|
| 229 |
+
linewidth=2,
|
| 230 |
+
alpha=0.9,
|
| 231 |
+
),
|
| 232 |
+
zorder=10,
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
plt.tight_layout()
|
| 236 |
+
|
| 237 |
+
if output_path:
|
| 238 |
+
plt.savefig(output_path, dpi=150, bbox_inches='tight', facecolor='white')
|
| 239 |
+
plt.close()
|
| 240 |
+
print(f"Saved visualization to: {output_path}")
|
| 241 |
+
return None
|
| 242 |
+
else:
|
| 243 |
+
# Return as numpy array
|
| 244 |
+
fig.canvas.draw()
|
| 245 |
+
buf = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
|
| 246 |
+
buf = buf.reshape(fig.canvas.get_width_height()[::-1] + (3,))
|
| 247 |
+
plt.close()
|
| 248 |
+
return buf
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def visualize_all_images(coco_json, images_dir, output_dir):
|
| 252 |
+
"""
|
| 253 |
+
Visualize annotations for all images in the COCO dataset.
|
| 254 |
+
|
| 255 |
+
Args:
|
| 256 |
+
coco_json: COCO format dictionary
|
| 257 |
+
images_dir: Directory containing images
|
| 258 |
+
output_dir: Directory to save visualized images
|
| 259 |
+
"""
|
| 260 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 261 |
+
|
| 262 |
+
print(f"Visualizing {len(coco_json['images'])} images...")
|
| 263 |
+
|
| 264 |
+
for img_info in coco_json["images"]:
|
| 265 |
+
image_name = img_info["file_name"]
|
| 266 |
+
image_path = Path(images_dir) / image_name
|
| 267 |
+
|
| 268 |
+
if not image_path.exists():
|
| 269 |
+
print(f"Warning: Image {image_name} not found, skipping...")
|
| 270 |
+
continue
|
| 271 |
+
|
| 272 |
+
output_path = Path(output_dir) / f"{Path(image_name).stem}_annotated.png"
|
| 273 |
+
|
| 274 |
+
print(f"Processing {image_name}...")
|
| 275 |
+
draw_coco_annotations(
|
| 276 |
+
str(image_path),
|
| 277 |
+
coco_json,
|
| 278 |
+
output_path=str(output_path),
|
| 279 |
+
show_labels=True
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
print(f"\nAll visualizations saved to: {output_dir}")
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def main():
|
| 286 |
+
"""Main function to visualize ground truth annotations."""
|
| 287 |
+
# Paths
|
| 288 |
+
data_dir = os.path.join(SCRIPT_DIR, "Aleyna 1 (2024)")
|
| 289 |
+
xml_path = os.path.join(data_dir, "Annotations", "annotations.xml")
|
| 290 |
+
images_dir = os.path.join(data_dir, "Images")
|
| 291 |
+
output_dir = os.path.join(SCRIPT_DIR, "visualizations_gt")
|
| 292 |
+
|
| 293 |
+
# Option 1: Load from existing COCO JSON
|
| 294 |
+
coco_json_path = os.path.join(SCRIPT_DIR, "ground_truth_coco.json")
|
| 295 |
+
if os.path.exists(coco_json_path):
|
| 296 |
+
print(f"Loading COCO JSON from: {coco_json_path}")
|
| 297 |
+
with open(coco_json_path, 'r') as f:
|
| 298 |
+
coco_json = json.load(f)
|
| 299 |
+
else:
|
| 300 |
+
# Option 2: Generate from XML
|
| 301 |
+
print(f"Loading from XML: {xml_path}")
|
| 302 |
+
coco_json = load_ground_truth(xml_path, images_dir)
|
| 303 |
+
|
| 304 |
+
if coco_json:
|
| 305 |
+
# Save for future use
|
| 306 |
+
with open(coco_json_path, 'w') as f:
|
| 307 |
+
json.dump(coco_json, f, indent=4)
|
| 308 |
+
print(f"Saved COCO JSON to: {coco_json_path}")
|
| 309 |
+
|
| 310 |
+
if not coco_json:
|
| 311 |
+
print("Error: Failed to load annotations")
|
| 312 |
+
return
|
| 313 |
+
|
| 314 |
+
print(f"\nLoaded {len(coco_json['images'])} images")
|
| 315 |
+
print(f"Loaded {len(coco_json['annotations'])} annotations")
|
| 316 |
+
print(f"Categories: {[c['name'] for c in coco_json['categories']]}")
|
| 317 |
+
|
| 318 |
+
# Visualize all images
|
| 319 |
+
visualize_all_images(coco_json, images_dir, output_dir)
|
| 320 |
+
|
| 321 |
+
print("\n" + "=" * 60)
|
| 322 |
+
print("Visualization complete!")
|
| 323 |
+
print("=" * 60)
|
| 324 |
+
print(f"\nCheck the visualizations in: {output_dir}")
|
| 325 |
+
print("Compare them with the original images to verify conversion accuracy.")
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
if __name__ == "__main__":
|
| 329 |
+
main()
|
| 330 |
+
|
compare/data/visualize_sample_batches.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Visualize annotations for SampleBatch2, SampleBatch3, and SampleBatch4.
|
| 3 |
+
These folders already have COCO format JSON files, so we just need to visualize them.
|
| 4 |
+
"""
|
| 5 |
+
import os
|
| 6 |
+
import json
|
| 7 |
+
import sys
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
# Add current directory to path
|
| 11 |
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 12 |
+
sys.path.insert(0, SCRIPT_DIR)
|
| 13 |
+
|
| 14 |
+
from visualize_ground_truth import visualize_all_images, draw_coco_annotations
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
# List of sample batch folders
|
| 18 |
+
SAMPLE_BATCH_FOLDERS = [
|
| 19 |
+
"SampleBatch2",
|
| 20 |
+
"SampleBatch3",
|
| 21 |
+
"SampleBatch4",
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def visualize_sample_batch(folder_name, base_dir=None):
|
| 26 |
+
"""
|
| 27 |
+
Visualize annotations for a sample batch folder.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
folder_name: Name of the sample batch folder
|
| 31 |
+
base_dir: Base directory containing the folders (default: SCRIPT_DIR)
|
| 32 |
+
|
| 33 |
+
Returns:
|
| 34 |
+
dict with processing results
|
| 35 |
+
"""
|
| 36 |
+
if base_dir is None:
|
| 37 |
+
base_dir = SCRIPT_DIR
|
| 38 |
+
|
| 39 |
+
folder_path = Path(base_dir) / folder_name
|
| 40 |
+
|
| 41 |
+
if not folder_path.exists():
|
| 42 |
+
print(f"⚠️ Warning: Folder not found: {folder_path}")
|
| 43 |
+
return {
|
| 44 |
+
"folder": folder_name,
|
| 45 |
+
"status": "not_found",
|
| 46 |
+
"images": 0,
|
| 47 |
+
"annotations": 0
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
print("\n" + "=" * 70)
|
| 51 |
+
print(f"Processing: {folder_name}")
|
| 52 |
+
print("=" * 70)
|
| 53 |
+
|
| 54 |
+
# Paths
|
| 55 |
+
json_path = folder_path / "Annotations" / "instances_default.json"
|
| 56 |
+
images_dir = folder_path / "Images"
|
| 57 |
+
|
| 58 |
+
# Check if required files/directories exist
|
| 59 |
+
if not json_path.exists():
|
| 60 |
+
print(f"⚠️ Warning: JSON file not found: {json_path}")
|
| 61 |
+
return {
|
| 62 |
+
"folder": folder_name,
|
| 63 |
+
"status": "no_json",
|
| 64 |
+
"images": 0,
|
| 65 |
+
"annotations": 0
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
if not images_dir.exists():
|
| 69 |
+
print(f"⚠️ Warning: Images directory not found: {images_dir}")
|
| 70 |
+
return {
|
| 71 |
+
"folder": folder_name,
|
| 72 |
+
"status": "no_images",
|
| 73 |
+
"images": 0,
|
| 74 |
+
"annotations": 0
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
# Load COCO JSON
|
| 78 |
+
print(f"\n[Loading COCO JSON]")
|
| 79 |
+
print(f" JSON: {json_path}")
|
| 80 |
+
print(f" Images: {images_dir}")
|
| 81 |
+
|
| 82 |
+
try:
|
| 83 |
+
with open(json_path, 'r') as f:
|
| 84 |
+
coco_json = json.load(f)
|
| 85 |
+
|
| 86 |
+
# Verify it's COCO format
|
| 87 |
+
if not all(key in coco_json for key in ['images', 'annotations', 'categories']):
|
| 88 |
+
print(f"⚠️ Warning: JSON file doesn't appear to be in COCO format")
|
| 89 |
+
print(f" Keys found: {list(coco_json.keys())}")
|
| 90 |
+
return {
|
| 91 |
+
"folder": folder_name,
|
| 92 |
+
"status": "invalid_format",
|
| 93 |
+
"images": 0,
|
| 94 |
+
"annotations": 0
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
num_images = len(coco_json["images"])
|
| 98 |
+
num_annotations = len(coco_json["annotations"])
|
| 99 |
+
num_categories = len(coco_json["categories"])
|
| 100 |
+
|
| 101 |
+
print(f" ✓ Loaded {num_images} images")
|
| 102 |
+
print(f" ✓ Loaded {num_annotations} annotations")
|
| 103 |
+
print(f" ✓ Loaded {num_categories} categories")
|
| 104 |
+
|
| 105 |
+
# Create visualizations directory inside the folder
|
| 106 |
+
vis_output_dir = folder_path / "visualizations"
|
| 107 |
+
|
| 108 |
+
print(f"\n[Creating visualizations]")
|
| 109 |
+
visualize_all_images(coco_json, str(images_dir), str(vis_output_dir))
|
| 110 |
+
|
| 111 |
+
print(f" ✓ Visualizations saved to: {vis_output_dir}")
|
| 112 |
+
|
| 113 |
+
return {
|
| 114 |
+
"folder": folder_name,
|
| 115 |
+
"status": "success",
|
| 116 |
+
"images": num_images,
|
| 117 |
+
"annotations": num_annotations,
|
| 118 |
+
"categories": num_categories,
|
| 119 |
+
"visualizations_path": str(vis_output_dir)
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
except json.JSONDecodeError as e:
|
| 123 |
+
print(f"❌ Error: Invalid JSON file: {e}")
|
| 124 |
+
return {
|
| 125 |
+
"folder": folder_name,
|
| 126 |
+
"status": "json_error",
|
| 127 |
+
"error": str(e),
|
| 128 |
+
"images": 0,
|
| 129 |
+
"annotations": 0
|
| 130 |
+
}
|
| 131 |
+
except Exception as e:
|
| 132 |
+
print(f"❌ Error processing {folder_name}: {e}")
|
| 133 |
+
import traceback
|
| 134 |
+
traceback.print_exc()
|
| 135 |
+
return {
|
| 136 |
+
"folder": folder_name,
|
| 137 |
+
"status": "error",
|
| 138 |
+
"error": str(e),
|
| 139 |
+
"images": 0,
|
| 140 |
+
"annotations": 0
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def main():
|
| 145 |
+
"""Main function to visualize all sample batches."""
|
| 146 |
+
print("=" * 70)
|
| 147 |
+
print("VISUALIZING SAMPLE BATCHES")
|
| 148 |
+
print("=" * 70)
|
| 149 |
+
print(f"\nProcessing {len(SAMPLE_BATCH_FOLDERS)} sample batch folders:")
|
| 150 |
+
for folder in SAMPLE_BATCH_FOLDERS:
|
| 151 |
+
print(f" - {folder}")
|
| 152 |
+
|
| 153 |
+
results = []
|
| 154 |
+
|
| 155 |
+
for folder_name in SAMPLE_BATCH_FOLDERS:
|
| 156 |
+
result = visualize_sample_batch(folder_name)
|
| 157 |
+
results.append(result)
|
| 158 |
+
|
| 159 |
+
# Print summary
|
| 160 |
+
print("\n" + "=" * 70)
|
| 161 |
+
print("PROCESSING SUMMARY")
|
| 162 |
+
print("=" * 70)
|
| 163 |
+
|
| 164 |
+
successful = [r for r in results if r["status"] == "success"]
|
| 165 |
+
failed = [r for r in results if r["status"] != "success"]
|
| 166 |
+
|
| 167 |
+
print(f"\n✓ Successfully processed: {len(successful)}/{len(results)}")
|
| 168 |
+
for r in successful:
|
| 169 |
+
print(f" - {r['folder']}: {r['images']} images, {r['annotations']} annotations, {r['categories']} categories")
|
| 170 |
+
|
| 171 |
+
if failed:
|
| 172 |
+
print(f"\n⚠️ Failed/Skipped: {len(failed)}/{len(results)}")
|
| 173 |
+
for r in failed:
|
| 174 |
+
print(f" - {r['folder']}: {r['status']}")
|
| 175 |
+
if 'error' in r:
|
| 176 |
+
print(f" Error: {r['error']}")
|
| 177 |
+
|
| 178 |
+
# Save summary to JSON
|
| 179 |
+
summary_path = Path(SCRIPT_DIR) / "sample_batches_summary.json"
|
| 180 |
+
with open(summary_path, 'w') as f:
|
| 181 |
+
json.dump({
|
| 182 |
+
"total_batches": len(SAMPLE_BATCH_FOLDERS),
|
| 183 |
+
"successful": len(successful),
|
| 184 |
+
"failed": len(failed),
|
| 185 |
+
"results": results
|
| 186 |
+
}, f, indent=4)
|
| 187 |
+
|
| 188 |
+
print(f"\n✓ Summary saved to: {summary_path}")
|
| 189 |
+
print("\n" + "=" * 70)
|
| 190 |
+
print("VISUALIZATION COMPLETE!")
|
| 191 |
+
print("=" * 70)
|
| 192 |
+
print("\nEach sample batch folder now contains:")
|
| 193 |
+
print(" - visualizations/ (annotated images)")
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
if __name__ == "__main__":
|
| 197 |
+
main()
|
| 198 |
+
|
test_combined_models.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script to combine predictions from three YOLO models:
|
| 4 |
+
- best_emanuskript_segmentation.pt (segmentation model for manuscript elements)
|
| 5 |
+
- best_catmus.pt (segmentation model for lines and zones)
|
| 6 |
+
- best_zone_detection.pt (detection model for zones)
|
| 7 |
+
|
| 8 |
+
WORKFLOW SUMMARY:
|
| 9 |
+
================
|
| 10 |
+
|
| 11 |
+
1. MODEL PREDICTIONS (run_model_predictions):
|
| 12 |
+
- Runs each of the 3 models on the input image
|
| 13 |
+
- Saves predictions to JSON files in separate folders
|
| 14 |
+
- Emanuskript: detects manuscript elements (Main script, Plain initial, etc.)
|
| 15 |
+
- Catmus: detects lines (DefaultLine, InterlinearLine)
|
| 16 |
+
- Zone: detects zones (MainZone, DropCapitalZone, etc.)
|
| 17 |
+
|
| 18 |
+
2. COMBINING & FILTERING (combine_and_filter_predictions):
|
| 19 |
+
- Uses ImageBatch class to:
|
| 20 |
+
a) Load all predictions from the 3 JSON files
|
| 21 |
+
b) Unify class names (maps catmus/zone names to coco_class_mapping)
|
| 22 |
+
c) Filter overlapping/conflicting annotations using spatial indexing
|
| 23 |
+
d) Convert to COCO format
|
| 24 |
+
- Only keeps classes defined in coco_class_mapping (25 classes total)
|
| 25 |
+
|
| 26 |
+
3. OUTPUT:
|
| 27 |
+
- COCO format JSON file with filtered annotations
|
| 28 |
+
- Only contains classes from coco_class_mapping
|
| 29 |
+
|
| 30 |
+
KEY CLASSES IN coco_class_mapping:
|
| 31 |
+
- Main script black/coloured
|
| 32 |
+
- Variant script black/coloured
|
| 33 |
+
- Plain initial (coloured/highlighted/black)
|
| 34 |
+
- Historiated, Inhabited, Embellished
|
| 35 |
+
- Page Number, Quire Mark, Running header
|
| 36 |
+
- Gloss, Illustrations, Column
|
| 37 |
+
- Music, MusicZone, MusicLine
|
| 38 |
+
- Border, Table, Diagram
|
| 39 |
+
- GraphicZone
|
| 40 |
+
|
| 41 |
+
The ImageBatch class handles:
|
| 42 |
+
- Spatial overlap detection (removes duplicates)
|
| 43 |
+
- Class name unification (catmus_zones_mapping)
|
| 44 |
+
- Annotation filtering based on overlap thresholds
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
import os
|
| 48 |
+
import json
|
| 49 |
+
import tempfile
|
| 50 |
+
import shutil
|
| 51 |
+
from pathlib import Path
|
| 52 |
+
from ultralytics import YOLO
|
| 53 |
+
import sys
|
| 54 |
+
|
| 55 |
+
# Add current directory to path to import ImageBatch
|
| 56 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
| 57 |
+
PROJECT_ROOT = current_dir # This file is in the project root
|
| 58 |
+
sys.path.insert(0, current_dir)
|
| 59 |
+
|
| 60 |
+
try:
|
| 61 |
+
from utils.image_batch_classes import ImageBatch, coco_class_mapping
|
| 62 |
+
except ImportError as e:
|
| 63 |
+
print(f"Warning: Could not import ImageBatch: {e}")
|
| 64 |
+
print("Make sure all dependencies are installed (rtree, shapely, etc.)")
|
| 65 |
+
ImageBatch = None
|
| 66 |
+
|
| 67 |
+
def run_model_predictions(image_path, output_dir):
|
| 68 |
+
"""Run all three models on the image and save predictions."""
|
| 69 |
+
|
| 70 |
+
# Create output directories
|
| 71 |
+
catmus_dir = os.path.join(output_dir, 'catmus')
|
| 72 |
+
emanuskript_dir = os.path.join(output_dir, 'emanuskript')
|
| 73 |
+
zone_dir = os.path.join(output_dir, 'zone')
|
| 74 |
+
|
| 75 |
+
for dir_path in [catmus_dir, emanuskript_dir, zone_dir]:
|
| 76 |
+
os.makedirs(dir_path, exist_ok=True)
|
| 77 |
+
|
| 78 |
+
image_id = Path(image_path).stem
|
| 79 |
+
|
| 80 |
+
print("=" * 60)
|
| 81 |
+
print("Running Model Predictions")
|
| 82 |
+
print("=" * 60)
|
| 83 |
+
|
| 84 |
+
# 1. Emanuskript model
|
| 85 |
+
print("\n[1/3] Running emanuskript segmentation model...")
|
| 86 |
+
emanuskript_model_path = os.path.join(PROJECT_ROOT, "best_emanuskript_segmentation.pt")
|
| 87 |
+
emanuskript_model = YOLO(emanuskript_model_path)
|
| 88 |
+
emanuskript_classes = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20]
|
| 89 |
+
emanuskript_results = emanuskript_model.predict(
|
| 90 |
+
image_path,
|
| 91 |
+
classes=emanuskript_classes,
|
| 92 |
+
iou=0.3,
|
| 93 |
+
device='cpu',
|
| 94 |
+
augment=False,
|
| 95 |
+
stream=False
|
| 96 |
+
)
|
| 97 |
+
emanuskript_path = f'{emanuskript_dir}/{image_id}.json'
|
| 98 |
+
with open(emanuskript_path, 'w') as f:
|
| 99 |
+
f.write(emanuskript_results[0].to_json())
|
| 100 |
+
print(f" ✓ Saved to: {emanuskript_path}")
|
| 101 |
+
del emanuskript_model
|
| 102 |
+
del emanuskript_results
|
| 103 |
+
|
| 104 |
+
# 2. Catmus model
|
| 105 |
+
print("\n[2/3] Running catmus segmentation model...")
|
| 106 |
+
catmus_model_path = os.path.join(PROJECT_ROOT, "best_catmus.pt")
|
| 107 |
+
catmus_model = YOLO(catmus_model_path)
|
| 108 |
+
catmus_classes = [1, 7] # DefaultLine and InterlinearLine
|
| 109 |
+
catmus_results = catmus_model.predict(
|
| 110 |
+
image_path,
|
| 111 |
+
classes=catmus_classes,
|
| 112 |
+
iou=0.3,
|
| 113 |
+
device='cpu',
|
| 114 |
+
augment=False,
|
| 115 |
+
stream=False
|
| 116 |
+
)
|
| 117 |
+
catmus_path = f'{catmus_dir}/{image_id}.json'
|
| 118 |
+
with open(catmus_path, 'w') as f:
|
| 119 |
+
f.write(catmus_results[0].to_json())
|
| 120 |
+
print(f" ✓ Saved to: {catmus_path}")
|
| 121 |
+
del catmus_model
|
| 122 |
+
del catmus_results
|
| 123 |
+
|
| 124 |
+
# 3. Zone detection model
|
| 125 |
+
print("\n[3/3] Running zone detection model...")
|
| 126 |
+
zone_model_path = os.path.join(PROJECT_ROOT, "best_zone_detection.pt")
|
| 127 |
+
zone_model = YOLO(zone_model_path)
|
| 128 |
+
zone_results = zone_model.predict(
|
| 129 |
+
image_path,
|
| 130 |
+
device='cpu',
|
| 131 |
+
iou=0.3,
|
| 132 |
+
augment=False,
|
| 133 |
+
stream=False
|
| 134 |
+
)
|
| 135 |
+
zone_path = f'{zone_dir}/{image_id}.json'
|
| 136 |
+
with open(zone_path, 'w') as f:
|
| 137 |
+
f.write(zone_results[0].to_json())
|
| 138 |
+
print(f" ✓ Saved to: {zone_path}")
|
| 139 |
+
del zone_model
|
| 140 |
+
del zone_results
|
| 141 |
+
|
| 142 |
+
return {
|
| 143 |
+
'catmus': catmus_dir,
|
| 144 |
+
'emanuskript': emanuskript_dir,
|
| 145 |
+
'zone': zone_dir
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def combine_and_filter_predictions(image_path, labels_folders, output_json_path=None):
|
| 150 |
+
"""Combine predictions from all models and filter to coco_class_mapping classes."""
|
| 151 |
+
|
| 152 |
+
print("\n" + "=" * 60)
|
| 153 |
+
print("Combining and Filtering Predictions")
|
| 154 |
+
print("=" * 60)
|
| 155 |
+
|
| 156 |
+
if ImageBatch is None:
|
| 157 |
+
print("\nERROR: ImageBatch class not available.")
|
| 158 |
+
print("Please install missing dependencies:")
|
| 159 |
+
print(" pip install rtree shapely")
|
| 160 |
+
return None
|
| 161 |
+
|
| 162 |
+
# Create a temporary folder with just the image file
|
| 163 |
+
# ImageBatch.load_images() loads all files in the folder, so we need only images
|
| 164 |
+
temp_image_dir = tempfile.mkdtemp()
|
| 165 |
+
image_filename = os.path.basename(image_path)
|
| 166 |
+
temp_image_path = os.path.join(temp_image_dir, image_filename)
|
| 167 |
+
shutil.copy2(image_path, temp_image_path)
|
| 168 |
+
|
| 169 |
+
# Create ImageBatch instance
|
| 170 |
+
image_folder = temp_image_dir
|
| 171 |
+
|
| 172 |
+
image_batch = ImageBatch(
|
| 173 |
+
image_folder=image_folder,
|
| 174 |
+
catmus_labels_folder=labels_folders['catmus'],
|
| 175 |
+
emanuskript_labels_folder=labels_folders['emanuskript'],
|
| 176 |
+
zone_labels_folder=labels_folders['zone']
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# Load images
|
| 180 |
+
print("\n[Step 1] Loading images...")
|
| 181 |
+
image_batch.load_images()
|
| 182 |
+
print(f" ✓ Loaded {len(image_batch.images)} image(s)")
|
| 183 |
+
|
| 184 |
+
# Load annotations from all three models
|
| 185 |
+
print("\n[Step 2] Loading annotations from all models...")
|
| 186 |
+
image_batch.load_annotations()
|
| 187 |
+
|
| 188 |
+
total_annotations = sum(len(img.annotations) for img in image_batch.images)
|
| 189 |
+
print(f" ✓ Loaded {total_annotations} total annotations")
|
| 190 |
+
|
| 191 |
+
# Unify names (maps catmus/zone names to coco_class_mapping names)
|
| 192 |
+
print("\n[Step 3] Unifying class names...")
|
| 193 |
+
image_batch.unify_names()
|
| 194 |
+
|
| 195 |
+
# Filter annotations (removes overlapping/conflicting annotations)
|
| 196 |
+
print("\n[Step 4] Filtering annotations...")
|
| 197 |
+
for img in image_batch.images:
|
| 198 |
+
filtered = img.filter_annotations()
|
| 199 |
+
print(f" Image {img.filename}: {len(img.annotations)} -> {len(filtered)} annotations")
|
| 200 |
+
|
| 201 |
+
# Get COCO format JSON
|
| 202 |
+
print("\n[Step 5] Generating COCO format...")
|
| 203 |
+
coco_json = image_batch.return_coco_file()
|
| 204 |
+
|
| 205 |
+
# Filter to only classes in coco_class_mapping
|
| 206 |
+
valid_category_ids = set(coco_class_mapping.values())
|
| 207 |
+
|
| 208 |
+
filtered_annotations = [
|
| 209 |
+
ann for ann in coco_json['annotations']
|
| 210 |
+
if ann['category_id'] in valid_category_ids
|
| 211 |
+
]
|
| 212 |
+
|
| 213 |
+
coco_json['annotations'] = filtered_annotations
|
| 214 |
+
|
| 215 |
+
# Update categories to only include valid ones
|
| 216 |
+
coco_json['categories'] = [
|
| 217 |
+
cat for cat in coco_json['categories']
|
| 218 |
+
if cat['id'] in valid_category_ids
|
| 219 |
+
]
|
| 220 |
+
|
| 221 |
+
print(f" ✓ Final annotations: {len(filtered_annotations)}")
|
| 222 |
+
print(f" ✓ Final categories: {len(coco_json['categories'])}")
|
| 223 |
+
|
| 224 |
+
# Save to file if path provided
|
| 225 |
+
if output_json_path:
|
| 226 |
+
with open(output_json_path, 'w') as f:
|
| 227 |
+
json.dump(coco_json, f, indent=2)
|
| 228 |
+
print(f"\n ✓ Saved COCO JSON to: {output_json_path}")
|
| 229 |
+
|
| 230 |
+
# Cleanup temporary image directory
|
| 231 |
+
shutil.rmtree(temp_image_dir, ignore_errors=True)
|
| 232 |
+
|
| 233 |
+
return coco_json
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def print_summary(coco_json):
|
| 237 |
+
"""Print summary of results."""
|
| 238 |
+
print("\n" + "=" * 60)
|
| 239 |
+
print("Results Summary")
|
| 240 |
+
print("=" * 60)
|
| 241 |
+
|
| 242 |
+
# Category counts
|
| 243 |
+
category_counts = {}
|
| 244 |
+
for ann in coco_json['annotations']:
|
| 245 |
+
cat_id = ann['category_id']
|
| 246 |
+
category_counts[cat_id] = category_counts.get(cat_id, 0) + 1
|
| 247 |
+
|
| 248 |
+
# Map category IDs to names
|
| 249 |
+
id_to_name = {cat['id']: cat['name'] for cat in coco_json['categories']}
|
| 250 |
+
|
| 251 |
+
print(f"\nTotal Annotations: {len(coco_json['annotations'])}")
|
| 252 |
+
print(f"Total Categories: {len(coco_json['categories'])}")
|
| 253 |
+
print(f"\nAnnotations per Category:")
|
| 254 |
+
for cat_id in sorted(category_counts.keys()):
|
| 255 |
+
name = id_to_name.get(cat_id, f"Unknown({cat_id})")
|
| 256 |
+
count = category_counts[cat_id]
|
| 257 |
+
print(f" {name:30s}: {count:4d}")
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def visualize_results(image_path, coco_json):
|
| 261 |
+
"""Visualize the combined results on the image."""
|
| 262 |
+
print("\n" + "=" * 60)
|
| 263 |
+
print("Visualizing Results")
|
| 264 |
+
print("=" * 60)
|
| 265 |
+
|
| 266 |
+
try:
|
| 267 |
+
from utils.image_batch_classes import ImageBatch
|
| 268 |
+
import tempfile
|
| 269 |
+
|
| 270 |
+
# Create temporary labels folders for visualization
|
| 271 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
| 272 |
+
# We need to recreate the ImageBatch with the combined results
|
| 273 |
+
# For now, just show the COCO JSON structure
|
| 274 |
+
print("\nTo visualize, you can:")
|
| 275 |
+
print("1. Use the COCO JSON file with any COCO visualization tool")
|
| 276 |
+
print("2. Load the JSON in your annotation tool")
|
| 277 |
+
print("3. Use the ImageBatch.plot_annotations() method")
|
| 278 |
+
|
| 279 |
+
except Exception as e:
|
| 280 |
+
print(f"Visualization not available: {e}")
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def main():
|
| 284 |
+
"""Main function to run the complete pipeline."""
|
| 285 |
+
|
| 286 |
+
# Configuration
|
| 287 |
+
image_path = "bnf-naf-10039__page-001-of-004.jpg"
|
| 288 |
+
output_json = "combined_predictions.json"
|
| 289 |
+
|
| 290 |
+
if not os.path.exists(image_path):
|
| 291 |
+
print(f"Error: Image file not found: {image_path}")
|
| 292 |
+
return
|
| 293 |
+
|
| 294 |
+
# Create temporary directory for predictions
|
| 295 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
| 296 |
+
print(f"Using temporary directory: {temp_dir}")
|
| 297 |
+
|
| 298 |
+
# Step 1: Run all three models
|
| 299 |
+
labels_folders = run_model_predictions(image_path, temp_dir)
|
| 300 |
+
|
| 301 |
+
# Step 2: Combine and filter predictions
|
| 302 |
+
coco_json = combine_and_filter_predictions(
|
| 303 |
+
image_path,
|
| 304 |
+
labels_folders,
|
| 305 |
+
output_json_path=output_json
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
# Step 3: Print summary
|
| 309 |
+
print_summary(coco_json)
|
| 310 |
+
|
| 311 |
+
# Step 4: Visualize (optional)
|
| 312 |
+
visualize_results(image_path, coco_json)
|
| 313 |
+
|
| 314 |
+
print("\n" + "=" * 60)
|
| 315 |
+
print("Pipeline Complete!")
|
| 316 |
+
print("=" * 60)
|
| 317 |
+
print(f"\nOutput saved to: {output_json}")
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
if __name__ == "__main__":
|
| 321 |
+
main()
|
| 322 |
+
|