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.