| |
| from .yolo_manager import YOLOManager |
| from .utils import get_abs_path, backup_file |
| import os |
| from .config import Config, load_config |
| import yaml |
| import os |
| from pathlib import Path |
| import shutil |
|
|
| config = load_config() |
|
|
| def convert_box_to_polygon(label_file: Path): |
| """ |
| Converts YOLO box-format labels (class xc yc w h) to YOLO polygon-format labels |
| for segmentation. Creates a 4-point polygon representing the bounding box. |
| Overwrites the label file in place if conversion is needed. |
| """ |
| if not label_file.exists(): |
| return |
|
|
| new_lines = [] |
| changed = False |
|
|
| with open(label_file, "r") as f: |
| for line in f: |
| line = line.strip() |
| if not line: |
| continue |
| |
| parts = line.split() |
| |
| if len(parts) == 5: |
| |
| try: |
| cls = int(float(parts[0])) |
| xc, yc, bw, bh = map(float, parts[1:]) |
| |
| |
| x1 = max(0.0, min(1.0, xc - bw / 2)) |
| y1 = max(0.0, min(1.0, yc - bh / 2)) |
| x2 = max(0.0, min(1.0, xc + bw / 2)) |
| y2 = max(0.0, min(1.0, yc - bh / 2)) |
| x3 = max(0.0, min(1.0, xc + bw / 2)) |
| y3 = max(0.0, min(1.0, yc + bh / 2)) |
| x4 = max(0.0, min(1.0, xc - bw / 2)) |
| y4 = max(0.0, min(1.0, yc + bh / 2)) |
| |
| |
| polygon_line = f"{cls} {x1:.6f} {y1:.6f} {x2:.6f} {y2:.6f} {x3:.6f} {y3:.6f} {x4:.6f} {y4:.6f}" |
| new_lines.append(polygon_line) |
| changed = True |
| |
| except (ValueError, IndexError): |
| |
| new_lines.append(line) |
| |
| elif len(parts) > 5 and len(parts) % 2 == 1: |
| |
| try: |
| cls = int(float(parts[0])) |
| coords = [float(x) for x in parts[1:]] |
| |
| coords = [max(0.0, min(1.0, coord)) for coord in coords] |
| coord_str = " ".join(f"{coord:.6f}" for coord in coords) |
| new_lines.append(f"{cls} {coord_str}") |
| except (ValueError, IndexError): |
| |
| new_lines.append(line) |
| else: |
| |
| new_lines.append(line) |
|
|
| if changed: |
| with open(label_file, "w") as f: |
| f.write("\n".join(new_lines) + "\n") |
|
|
| def create_filtered_dataset(original_dataset_path, output_filtered_dataset_path): |
| """ |
| Create a filtered dataset with only images that have non-empty labels |
| """ |
| shutil.rmtree(output_filtered_dataset_path, ignore_errors=True) |
| original_path = Path(original_dataset_path) |
| output_path = Path(output_filtered_dataset_path) |
| |
| |
| output_images = output_path / "images" |
| output_labels = output_path / "labels" |
| |
| for split in ['train', 'val', 'test']: |
| (output_images / split).mkdir(parents=True, exist_ok=True) |
| (output_labels / split).mkdir(parents=True, exist_ok=True) |
| |
| filtered_counts = {} |
| |
| for split in ['train', 'val', 'test']: |
| original_images_dir = original_path / 'images' / split |
| original_labels_dir = original_path / 'labels' / split |
| |
| output_images_dir = output_images / split |
| output_labels_dir = output_labels / split |
| |
| if not original_images_dir.exists() or not original_labels_dir.exists(): |
| print(f"Skipping {split} - source directory not found") |
| filtered_counts[split] = 0 |
| continue |
| |
| total_count = 0 |
| copied_count = 0 |
| |
| |
| for img_file in original_images_dir.glob('*'): |
| if img_file.suffix.lower() in ['.jpg', '.jpeg', '.png', '.bmp']: |
| total_count += 1 |
| label_file = original_labels_dir / f"{img_file.stem}.txt" |
| |
| |
| if label_file.exists(): |
| with open(label_file, 'r') as f: |
| content = f.read().strip() |
| if content: |
| |
| shutil.copy2(img_file, output_images_dir / img_file.name) |
| |
| shutil.copy2(label_file, output_labels_dir / label_file.name) |
| convert_box_to_polygon(output_labels_dir / label_file.name) |
| copied_count += 1 |
| else: |
| print(f"Skipping {img_file.name} - empty label file") |
| else: |
| print(f"Skipping {img_file.name} - no label file") |
| |
| filtered_counts[split] = copied_count |
| print(f"{split.upper()} split: {copied_count}/{total_count} images copied") |
| |
| return filtered_counts |
|
|
| def create_filtered_yaml(output_filtered_dataset_path, filtered_counts): |
| """ |
| Create the YAML file for the filtered dataset |
| """ |
| output_path = Path(output_filtered_dataset_path) |
| yaml_path = f'{config.current_path}/filtered_comic.yaml' |
| |
| |
| yaml_data = { |
| 'names': ['panel'], |
| 'nc': 1, |
| 'path': str(output_path), |
| 'train': str(output_path / 'images' / 'train'), |
| 'val': str(output_path / 'images' / 'val') |
| } |
| |
| |
| if filtered_counts.get('test', 0) > 0: |
| yaml_data['test'] = str(output_path / 'images' / 'test') |
| |
| |
| with open(yaml_path, 'w') as f: |
| yaml.dump(yaml_data, f, default_flow_style=False, sort_keys=False) |
| |
| print(f"\nβ
Created filtered dataset YAML: {yaml_path}") |
| return yaml_path |
|
|
| def main(): |
| """Main training function.""" |
| try: |
| |
| yolo_manager = YOLOManager() |
| |
| |
| data_yaml_path = f'{config.current_path}/filtered_comic.yaml' |
| |
| if not os.path.isfile(data_yaml_path): |
| raise FileNotFoundError(f"β Dataset YAML not found: {data_yaml_path}") |
| |
| print(f"π― Training model: {config.YOLO_MODEL_NAME}") |
| |
| |
| yolo_manager.train( |
| data_yaml_path=data_yaml_path |
| ) |
| |
| |
| yolo_manager.validate() |
| |
| print("π Training completed successfully!") |
| |
| except Exception as e: |
| print(f"β Training failed: {str(e)}") |
| raise |
|
|
| if __name__ == "__main__": |
| |
| original_dataset_path = "/home/jebin/git/comic-panel-extractor/comic_panel_extractor/dataset" |
| output_filtered_dataset_path = "/home/jebin/git/comic-panel-extractor/comic_panel_extractor/filtered_dataset" |
| |
| print("π Starting dataset filtering...") |
| print(f"π Source: {original_dataset_path}") |
| print(f"π Output: {output_filtered_dataset_path}") |
| |
| |
| filtered_counts = create_filtered_dataset(original_dataset_path, output_filtered_dataset_path) |
| |
| |
| yaml_path = create_filtered_yaml(output_filtered_dataset_path, filtered_counts) |
| |
| |
| total_filtered = sum(filtered_counts.values()) |
| print(f"\nπ Filtering Summary:") |
| for split, count in filtered_counts.items(): |
| if count > 0: |
| print(f" {split.upper()}: {count} images") |
| print(f" TOTAL: {total_filtered} images with labels") |
| |
| print(f"\nπ― Use this YAML for training: {yaml_path}") |
| |
| |
| with open(yaml_path, 'r') as f: |
| yaml_content = f.read() |
| print(f"\nπ Generated YAML content:") |
| print("β" * 50) |
| print(yaml_content) |
| print("β" * 50) |
| main() |