Generate L-System training data with automated labelling

57 Views Asked by At

I am posting this because I am clueless on how to proceed and I am sure there is an easy solution to my problem. I want to generate training data for a YOLOv7 network. My data is based on L-system grammar. The basic idea is that I have 8 (or more) "templates" that generate different tree shapes, for example:

predefined_rules = { '1': 'F', '2': 'F[+F]F[-F]F', '3': 'F[+F][-F]F', '4': 'F[+F][-F]', '5': 'F[+F]', '6': 'F[-F]', '7': 'F[+F]F', '8': 'F[-F]F', }

Now I want to generate varieties of tree structures which combine those templates randomly. The generation process is not the problem but I am struggling with adding automatically generated bouding boxes around the individual inserted templates. The end goal is to train a network to idetnify the different templates in an unlabelled tree structure.

I thought I am close but whenever I make the bounding boxes work, the drawn tempaltes don't look like their orginal shape anymore.

I really hope someone could help me out or give advise on how to proceed. (I am probably just missing sth because I am working on it for too long...)

I posted the code that I have so far:

import random
import turtle
import json

# Create a global list to store bounding box coordinates for all examples
global_positions_list = []

# Define the eight predefined rules (L-System rules) together with their produced geometries
predefined_rules = {
    '1': 'F',                      # Rule 1 produces a single line segment (Template 1)
    '2': 'F[+F]F[-F]F',            # Rule 2 produces a branching pattern (Template 2)
    '3': 'F[+F][-F]F',             # ...
    '4': 'F[+F][-F]',              
    '5': 'F[+F]',                  
    '6': 'F[-F]',                  
    '7': 'F[+F]F',                 
    '8': 'F[-F]F',                 
}

def apply_rule(rule, axiom):
    result = ""
    for char in axiom:
        result += rule.get(char, char) if isinstance(rule, dict) else rule
    return result

def draw_l_system(l_system_string, angle, step_size, scaling_factors):
    turtle.speed(0)
    stack = []
    template_num = {}  # Dictionary, to follow tempalte names and their numbers
    positions = []  # List to collect positions of the drawn segments
    label = 1
    positions = []
        
    # Function to collect positions while drawing the segments
    def collect_positions(x, y):
        positions[-1].append([x, y])  # Change tuple to list for easier manipulation

    for char in l_system_string:
        if char == 'F':
            template = random.choice(list(predefined_rules.values()))
            template_name = next(name for name, t in predefined_rules.items() if t == template)

            position_before = turtle.position()  # Remember positon before drawing

            # Start collection of positons for bounding boxes
            positions.append([(position_before[0], position_before[1])])

            # Draw rule segment and collect positions
            for sub_char in template:
                if sub_char == 'F':
                    step_size_scaled = step_size * scaling_factors[template_name]
                    turtle.forward(step_size_scaled)

                    # Collect position during drawing
                    current_position = turtle.position()
                    positions[-1].append((current_position[0], current_position[1]))
                elif sub_char == '+':
                    turtle.right(angle)
                elif sub_char == '-':
                    turtle.left(angle)

            position_after = turtle.position()  # Remember position after drawing

            # Stop collecting positions of bounding boxes
            positions[-1].append((position_after[0], position_after[1]))

            # Add template number to the dictionary for labeling later
            template_num[tuple(positions[-1])] = template_name

        elif char == '+':
            turtle.right(angle)
        elif char == '-':
            turtle.left(angle)
        elif char == '[':
            stack.append((turtle.position(), turtle.heading()))
        elif char == ']':
            position, heading = stack.pop()
            turtle.penup()
            turtle.goto(position)
            turtle.setheading(heading)
            turtle.pendown()

    # Draw bounding boxes around each rule segment with template number as label
    for positions_list in positions:
        min_x = min(p[0] for p in positions_list)
        max_x = max(p[0] for p in positions_list)
        min_y = min(p[1] for p in positions_list)
        max_y = max(p[1] for p in positions_list)

        turtle.penup()
        turtle.goto(min_x, min_y)
        turtle.pendown()
        turtle.goto(min_x, max_y)
        turtle.goto(max_x, max_y)
        turtle.goto(max_x, min_y)
        turtle.goto(min_x, min_y)
        turtle.penup()

        # Label the bounding box with the template number
        x, y = (min_x + max_x) / 2, (min_y + max_y) / 2
        turtle.penup()
        label_y_offset = 20  # Vertical Offset for the Label
        turtle.goto(x, y + label_y_offset)  # Move up to label position
        turtle.pendown()

        # Get the template number for the current bounding box
        template_name = template_num[tuple(positions_list)]

        turtle.write(f'{template_name}', align="center", font=("Arial", 12, "normal"))
        turtle.penup()

def generate_l_system_string(axiom, depth, templates):
    l_system_string = axiom
    num_templates = len(templates)
    for _ in range(depth):
        new_string = ""
        for char in l_system_string:
            if char == 'F':
                template = templates[_ % num_templates]
                new_string += template
            else:
                new_string += char
        l_system_string = new_string
    return l_system_string

def generate_synthetic_dataset(num_examples, depth, angle, step_size):
    # List to store the dataset
    dataset = []
    # Use predefined templates for generation
    predefined_templates = list(predefined_rules.values())
    for _ in range(num_examples):
        # Generate L-System string using predefined templates
        axiom = 'F'
        l_system_string = generate_l_system_string(axiom, depth, predefined_templates)

        # Append the L-System string to the dataset
        dataset.append(l_system_string)

    return dataset

# Function to collect positions while drawing the segments
def collect_positions(x, y):
    positions[-1].append([x, y])
    
def main():
    depth = 2
    angle = 25
    step_size = 20
    scaling_factors = {}

    for template_name in predefined_rules.keys():
        scaling_factors[template_name] = random.uniform(0.5, 1.5)

    num_examples = 1
    dataset = generate_synthetic_dataset(num_examples, depth, angle, step_size)

    # Create a list to store the COCO annotations
    #coco_annotations = []
    #category_count = 0

    for i, l_system_string in enumerate(dataset):
        print(f"Example {i+1} - Rule: {l_system_string}")

        # Initialize the turtle for each example
        turtle.reset()
        turtle.penup()
        turtle.goto(-200, -200)  # Starting position of the turtle
        turtle.pendown()

        # Draw the L-System with the specified scaling factors and collect bounding box coordinates
        positions = []  # List to store bounding box coordinates for each segment
        label = 1

        turtle.onscreenclick(collect_positions)  # Register the position collector function
        draw_l_system(l_system_string, angle, step_size, scaling_factors)

        # Print positions for debugging
        print(f"Bounding Box Positions for Example {i+1}: {positions}")

        # Create the COCO annotations for the current example
     #   image_id = i + 1
     #   for j, bbox_coords in enumerate(positions):
     #       category_id = category_count + j + 1  # Each bounding box gets a unique category_id
     #       min_x = min(coords[0] for coords in bbox_coords)
     #       max_x = max(coords[0] for coords in bbox_coords)
     #       min_y = min(coords[1] for coords in bbox_coords)
    #        max_y = max(coords[1] for coords in bbox_coords)
#
    #        width = max_x - min_x
    #        height = max_y - min_y
    #        area = width * height
#
    #        x, y = min_x, min_y
    #        coco_annotation = {
    #            "image_id": image_id,
    #            "id": len(coco_annotations) + 1,
    #            "category_id": category_id,
    #            "bbox": [x, y, width, height],
    #            "area": area
    #        }
    #        coco_annotations.append(coco_annotation)
#
    #        # Restlicher Code bleibt unverändert ...
#
    #    print("-------------------")
#
    #    # Increment category_count to ensure unique category_id for each example
    #    category_count += len(positions)
#
    #    
    ## Create the COCO categories list
    #coco_categories = [{"id": j + 1, "name": f"template{j + 1}"} for j in range(category_count)]
#
    ## Convert the coco_annotations list and coco_categories list to a COCO JSON format
    #coco_data = {
    #    "images": [{"id": i + 1, "file_name": f"example_{i+1}.png"} for i in range(num_examples)],
    #    "annotations": coco_annotations,
    #    "categories": coco_categories
    #}
#
    ## Save the COCO JSON data to a file
    #with open("coco_dataset.json", "w") as f:
    #    json.dump(coco_data, f)

    # Keep the window open until it is closed manually
    turtle.done()

if __name__ == "__main__":
    main()

I can generate different shaped L-system trees with labelled bounding boxes but somehow the templates change although they should not. I also managed to generate a correct looking L-system but then the bounding boxes wouldn't be drawn correctly anymore.

0

There are 0 best solutions below