hassanshka commited on
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 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
+