| import yaml |
| import base64 |
| import random |
| import string |
| import math |
| import shutil |
| import io |
| import os |
| import json |
|
|
| from PIL import Image, ImageDraw, ImageFont |
| from sahi.slicing import slice_image, slice_coco |
| from sahi.utils.coco import Coco, CocoAnnotation, CocoImage, CocoCategory |
|
|
| from pathlib import Path |
|
|
| from ultralytics.data.utils import compress_one_image |
| from ultralytics.utils.downloads import zip_directory |
|
|
| from processor import * |
|
|
|
|
| def generate_yolo_dataset_coco(num_images, output_directory="Data/yolo_dataset", size=(640, 640), |
| allow_intersections=False, allow_grid=True, split_ratio=0.8, |
| use_sahi_slicing=False, slice_size=640, overlap_ratio=0.2): |
|
|
| if os.path.exists(output_directory): shutil.rmtree(output_directory) |
| for split in ['train', 'val']: |
| print(str(os.path.join(output_directory, split, "temp/images"))) |
| |
| |
| os.makedirs(os.path.join(output_directory, split, "temp/images"), exist_ok=True) |
| os.makedirs(os.path.join(output_directory, split, "temp/outputs"), exist_ok=True) |
|
|
| width, height = size |
| |
| shape_radius = (min(width, height) // 2) * 0.07 |
|
|
| for n in range(num_images): |
| img = Image.new("RGB", size, (255, 255, 255)) |
| draw = ImageDraw.Draw(img) |
| exclusion_zones = [] |
| elements = [] |
| |
| current_split = 'train' if random.random() < split_ratio else 'val' |
| |
| |
| to_place = [] |
| num_targets = random.randint(3, 6) |
| num_grids = random.randint(3, 5) if allow_grid else 0 |
| num_empty_shapes = random.randint(4, 8) |
| num_floating_texts = random.randint(5, 10) |
|
|
| for _ in range(num_targets): to_place.append({"type": "target", "is_shape": True}) |
| for _ in range(num_grids): to_place.append({"type": "grid", "is_shape": True}) |
| for _ in range(num_empty_shapes): to_place.append({"type": "empty", "is_shape": True}) |
| for _ in range(num_floating_texts): to_place.append({"type": "text", "is_shape": False}) |
| |
| for item in to_place: |
| placed = False |
| attempts = 0 |
| while not placed and attempts < 300: |
| tx, ty = random.randint(50, width-50), random.randint(50, height-50) |
| |
| e_r = (shape_radius + 15) if item["is_shape"] else 15 |
| |
| if not any(math.sqrt((tx-z[0][0])**2 + (ty-z[0][1])**2) < (e_r + z[1]) for z in exclusion_zones): |
| exclusion_zones.append(((tx, ty), e_r)) |
| item["pos"] = (tx, ty) |
| elements.append(item) |
| placed = True |
| attempts += 1 |
|
|
| temp_img_name = f"img_{n}.png" |
| temp_img_path = os.path.join(output_directory, current_split, "temp", "images", temp_img_name) |
|
|
| coco = Coco() |
| target_category = CocoCategory(id=0, name="target") |
| coco.add_category(target_category) |
| coco_image = CocoImage(file_name=temp_img_name, height=height, width=width) |
| |
| dynamic_font_size = int(shape_radius * 1.2) |
| try: font = ImageFont.load_default(size=dynamic_font_size) |
| except: font = ImageFont.load_default() |
|
|
| yolo_labels = [] |
|
|
| for el in elements: |
| ex, ey = el["pos"] |
| |
| if el["type"] == "grid": |
| grid_cell_size = shape_radius * 0.7 |
| for row in range(2): |
| for col in range(2): |
| x1, y1 = ex + (col - 1) * grid_cell_size, ey + (row - 1) * grid_cell_size |
| x2, y2 = x1 + grid_cell_size, y1 + grid_cell_size |
| draw.rectangle([x1, y1, x2, y2], outline=(0, 0, 0), width=1) |
| char = random.choice(string.ascii_uppercase + string.digits) |
| draw.text(((x1 + x2)/2, (y1 + y2)/2), char, fill=(0, 0, 0), font=font, anchor="mm") |
|
|
| elif el["is_shape"]: |
| shape_type = random.choice(["diamond", "hexagon"]) |
| if shape_type == "diamond": |
| pts = [(ex, ey-shape_radius), (ex+shape_radius, ey), (ex, ey+shape_radius), (ex-shape_radius, ey)] |
| else: |
| pts = [(ex + shape_radius * math.cos(math.radians(i*60-30)), ey + shape_radius * math.sin(math.radians(i*60-30))) for i in range(6)] |
| |
| draw.polygon(pts, fill=(255, 255, 255), outline=(0, 0, 0), width=2) |
| |
| if el["type"] == "target": |
| txt = f"{random.choice(string.ascii_uppercase)}{random.randint(0, 9)}" |
| draw.text((ex, ey), txt, fill=(0, 0, 0), font=font, anchor="mm") |
| |
| all_x, all_y = [p[0] for p in pts], [p[1] for p in pts] |
| xmin, ymin, xmax, ymax = min(all_x), min(all_y), max(all_x), max(all_y) |
| |
| x_center, y_center = ((xmin + xmax) / 2) / width, ((ymin + ymax) / 2) / height |
| w_norm, h_norm = (xmax - xmin) / width, (ymax - ymin) / height |
| yolo_labels.append(f"0 {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}") |
| |
| bbox = [ex - shape_radius, ey - shape_radius, shape_radius * 2, shape_radius * 2] |
| coco_image.add_annotation( |
| CocoAnnotation( |
| bbox=bbox, |
| category_id=0, |
| category_name="target" |
| ) |
| ) |
| else: |
| txt = f"{random.choice(string.ascii_uppercase)}{random.randint(0, 9)}" |
| draw.text((ex, ey), txt, fill=(0, 0, 0), font=font, anchor="mm") |
|
|
| |
| lines_drawn = 0 |
| while lines_drawn < 30: |
| p1, p2 = (random.randint(0, width), random.randint(0, height)), (random.randint(0, width), random.randint(0, height)) |
| random_width = random.randint(1, 3) |
| can_draw = True |
| if not allow_intersections: |
| for z_pos, z_r in exclusion_zones: |
| dx, dy = p2[0]-p1[0], p2[1]-p1[1] |
| mag_sq = dx**2 + dy**2 + 1e-9 |
| u = max(0, min(1, ((z_pos[0]-p1[0])*dx + (z_pos[1]-p1[1])*dy) / mag_sq)) |
| if ((p1[0] + u*dx - z_pos[0])**2 + (p1[1] + u*dy - z_pos[1])**2) < z_r**2: |
| can_draw = False; break |
| if can_draw: |
| draw.line([p1, p2], fill=(0, 0, 0), width=random_width) |
| lines_drawn += 1 |
|
|
| |
| img.save(temp_img_path) |
| coco.add_image(coco_image) |
| source_json_path = os.path.join(output_directory, current_split, "source_coco.json") |
| with open(source_json_path, 'w') as f: |
| json.dump(coco.json, f) |
|
|
| if use_sahi_slicing: |
| |
| _, sliced_path_result = slice_coco( |
| coco_annotation_file_path=source_json_path, |
| image_dir=os.path.join(output_directory, current_split, "temp", "images"), |
| output_dir=os.path.join(output_directory, current_split, "temp", "images"), |
| output_coco_annotation_file_name="temp_sliced_meta.json", |
| slice_height=slice_size, |
| slice_width=slice_size, |
| overlap_height_ratio=overlap_ratio, |
| overlap_width_ratio=overlap_ratio, |
| min_area_ratio=0.1, |
| verbose=False |
| ) |
| |
| |
| |
| final_path = str(sliced_path_result) |
| final_coco = Coco.from_coco_dict_or_path( |
| final_path, |
| image_dir=os.path.join(output_directory, current_split, "temp", "images") |
| ) |
| |
| |
| |
| final_coco.export_as_yolo( |
| output_dir=os.path.join(output_directory, current_split, "temp", "outputs"), |
| disable_symlink=True |
| ) |
| |
| else: |
| |
| img.save(os.path.join(output_directory, current_split, "images", f"img_{n:04d}.png")) |
| |
|
|
| if os.path.exists(temp_img_path): os.remove(temp_img_path) |
| |
| if use_sahi_slicing: |
| |
| for current_split in ['train', 'val']: |
| |
| print(current_split) |
|
|
| sahi_export_dir = os.path.join(output_directory, current_split, "temp") |
|
|
| |
| |
| sahi_outputs = os.path.join(sahi_export_dir, "outputs", "train") |
| |
| os.makedirs(os.path.join(output_directory, current_split, "images"), exist_ok=True) |
| os.makedirs(os.path.join(output_directory, current_split, "labels"), exist_ok=True) |
|
|
| target_images_dir = os.path.join(output_directory, current_split, "images") |
| target_labels_dir = os.path.join(output_directory, current_split, "labels") |
|
|
| |
| if os.path.exists(sahi_outputs): |
| for f in os.listdir(sahi_outputs): |
| if ".png" in f: |
| shutil.move(os.path.join(sahi_outputs, f), os.path.join(target_images_dir, f)) |
|
|
| |
| if os.path.exists(sahi_outputs): |
| for f in os.listdir(sahi_outputs): |
| if ".txt" in f: |
| shutil.move(os.path.join(sahi_outputs, f), os.path.join(target_labels_dir, f)) |
| |
| if os.path.exists(os.path.join(sahi_export_dir, "outputs")): |
| for f in os.listdir(os.path.join(sahi_export_dir, "outputs")): |
| if not os.path.exists(os.path.join(output_directory, "data.yml")): |
| if ".yml" in f: |
| shutil.move(os.path.join(os.path.join(sahi_export_dir, "outputs"), f), output_directory) |
|
|
| |
| |
| data = {} |
| data['path'] = os.path.abspath(output_directory) |
| data['train'] = "train/images" |
| data['val'] = "val/images" |
|
|
| data['names'] = {0: "target_shape"} |
|
|
| |
| with open(os.path.join(output_directory, 'data.yaml'), 'w') as file: |
| |
| yaml.dump(data, file, default_flow_style=False, sort_keys=False) |
|
|
| |
| shutil.rmtree(sahi_export_dir) |
| |
| |
| |
| if os.path.exists(temp_img_path): os.remove(temp_img_path) |
| |
| if os.path.exists(source_json_path): os.remove(source_json_path) |
| |
| if os.path.exists(sliced_path_result): os.remove(sliced_path_result) |
| |
| if os.path.exists(sahi_export_dir): os.remove(sahi_export_dir) |
| |
| if os.path.exists(os.path.join(output_directory, current_split, "source_coco.json")): |
| os.remove(os.path.join(output_directory, current_split, "source_coco.json")) |
| |
| if os.path.exists(os.path.join(output_directory, "data.yml")): |
| os.remove(os.path.join(output_directory, "data.yml")) |
|
|
| print(f"Dataset generated at {output_directory}") |
|
|
| |
| def generate_yolo_dataset_original(num_images, output_dir="Data/yolo_dataset", size=(640, 640), allow_intersections=False, allow_grid=True, split_ratio=0.8): |
| |
| for split in ['train', 'val']: |
| os.makedirs(os.path.join(output_dir, split, "images"), exist_ok=True) |
| os.makedirs(os.path.join(output_dir, split, "labels"), exist_ok=True) |
|
|
| width, height = size |
| shape_radius = (min(width, height) // 2) * 0.15 |
|
|
| for n in range(num_images): |
| |
| img = Image.new("RGB", size, (255, 255, 255)) |
| draw = ImageDraw.Draw(img) |
| exclusion_zones = [] |
| elements = [] |
| |
| |
| current_split = 'train' if random.random() < split_ratio else 'val' |
| |
| |
| num_floating_texts = random.randint(1, 2) |
| |
| if allow_grid: |
| to_place = [{"type": "target", "is_shape": True}, {"type": "grid", "is_shape": True}] |
| else: |
| to_place = [{"type": "target", "is_shape": True}] |
| |
| for _ in range(2): to_place.append({"type": "empty", "is_shape": True}) |
| for _ in range(num_floating_texts): to_place.append({"type": "text", "is_shape": False}) |
| |
| |
| for item in to_place: |
| placed = False |
| attempts = 0 |
| while not placed and attempts < 200: |
| tx, ty = random.randint(100, width-100), random.randint(100, height-100) |
| e_r = (shape_radius + 50) if item["is_shape"] else 50 |
| if not any(math.sqrt((tx-z[0][0])**2 + (ty-z[0][1])**2) < (e_r + z[1]) for z in exclusion_zones): |
| exclusion_zones.append(((tx, ty), e_r)) |
| item["pos"] = (tx, ty) |
| elements.append(item) |
| placed = True |
| attempts += 1 |
|
|
| |
| yolo_label_data = "" |
| |
| dynamic_font_size = int(shape_radius) |
| |
| try: font = ImageFont.load_default(size=dynamic_font_size) |
| except: font = ImageFont.load_default() |
|
|
| for el in elements: |
| ex, ey = el["pos"] |
| |
| |
| if el["type"] == "grid": |
| grid_cell_size = shape_radius * 0.8 |
| for row in range(2): |
| for col in range(2): |
| |
| x1 = ex + (col - 1) * grid_cell_size |
| y1 = ey + (row - 1) * grid_cell_size |
| x2 = x1 + grid_cell_size |
| y2 = y1 + grid_cell_size |
| |
| |
| draw.rectangle([x1, y1, x2, y2], outline=(0, 0, 0), width=2) |
| |
| |
| char = random.choice(string.ascii_uppercase + string.digits) |
| draw.text(((x1 + x2)/2, (y1 + y2)/2), char, fill=(0, 0, 0), font=font, anchor="mm") |
|
|
| |
| elif el["is_shape"]: |
| shape_type = random.choice(["diamond", "hexagon"]) |
| if shape_type == "diamond": |
| pts = [(ex, ey-shape_radius), (ex+shape_radius, ey), (ex, ey+shape_radius), (ex-shape_radius, ey)] |
| else: |
| pts = [(ex + shape_radius * math.cos(math.radians(i*60-30)), ey + shape_radius * math.sin(math.radians(i*60-30))) for i in range(6)] |
| |
| draw.polygon(pts, fill=(255, 255, 255), outline=(0, 0, 0), width=3) |
| |
| if el["type"] == "target": |
| txt = f"{random.choice(string.ascii_uppercase)}{random.randint(0, 9)}" |
| draw.text((ex, ey), txt, fill=(0, 0, 0), font=font, anchor="mm") |
| |
| all_x, all_y = [p[0] for p in pts], [p[1] for p in pts] |
| xmin, ymin, xmax, ymax = min(all_x)-5, min(all_y)-5, max(all_x)+5, max(all_y)+5 |
| |
| x_center, y_center = ((xmin + xmax) / 2) / width, ((ymin + ymax) / 2) / height |
| w_norm, h_norm = (xmax - xmin) / width, (ymax - ymin) / height |
| yolo_label_data = f"0 {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}" |
| draw.rectangle([xmin, ymin, xmax, ymax], outline=(255, 0, 0), width=2) |
| else: |
| |
| txt = f"{random.choice(string.ascii_uppercase)}{random.randint(0, 9)}" |
| draw.text((ex, ey), txt, fill=(0, 0, 0), font=font, anchor="mm") |
|
|
| |
| lines_drawn = 0 |
| while lines_drawn < 12: |
| p1, p2 = (random.randint(0, width), random.randint(0, height)), (random.randint(0, width), random.randint(0, height)) |
| random_width = random.randint(1, 5) |
| can_draw = True |
| if not allow_intersections: |
| for z_pos, z_r in exclusion_zones: |
| dx, dy = p2[0]-p1[0], p2[1]-p1[1] |
| mag_sq = dx**2 + dy**2 + 1e-9 |
| u = max(0, min(1, ((z_pos[0]-p1[0])*dx + (z_pos[1]-p1[1])*dy) / mag_sq)) |
| if ((p1[0] + u*dx - z_pos[0])**2 + (p1[1] + u*dy - z_pos[1])**2) < z_r**2: |
| can_draw = False; break |
| if can_draw: |
| draw.line([p1, p2], fill=(0, 0, 0), width=random_width) |
| lines_drawn += 1 |
|
|
|
|
| |
| file_base = f"img_{n:04d}" |
| |
| |
| img_path = os.path.join(output_dir, current_split, "images", f"{file_base}.png") |
| img.save(img_path) |
| |
| |
| if yolo_label_data: |
| lbl_path = os.path.join(output_dir, current_split, "labels", f"{file_base}.txt") |
| with open(lbl_path, "w") as f: |
| f.write(yolo_label_data) |
|
|
| |
| yaml_content = f"path: {os.path.abspath(output_dir)}\ntrain: images\nval: images\n\nnames:\n 0: target_shape" |
| with open(os.path.join(output_dir, "data.yaml"), "w") as f: |
| f.write(yaml_content) |
|
|
| print(f"Dataset created at {output_dir}. Ready for YOLO training.") |
| |