Skip to content

Augmentation Guide

The following shows three ways to generate synthetic images:

  • Background Pattern Creator (Variant 1)
  • Variant 2 Python code

An example of the current effects used in the generation is shown below: Augmentations

Background Pattern Creator - User Interface Documentation

Overview Background Pattern Creator

The Background Pattern Creator is a GUI application built with Python Tkinter that allows users to create tiled background patterns from individual images. The tool is specifically designed to generate backgrounds with dimensions of 3076×1852 pixels, making it ideal for creating consistent background patterns for computer vision and image processing applications.

Background Pattern Creator Main Interface

Interface Components

1. Image Selection Section

The top section of the interface handles image loading and file management.

File Selection Frame

Components:

  • Select Image Button: Opens a file dialog to choose background images
  • File Label: Displays the currently selected image filename

Supported File Formats:

  • PNG files (*.png)
  • JPEG files (.jpg, .jpeg)
  • BMP files (*.bmp)
  • TIFF files (*.tiff)
  • GIF files (*.gif)

2. Controls & Comparison Section

This section provides the main controls for pattern manipulation and comparison functionality.

Controls and Comparison Section

Scale Controls
  • Scale Factor Slider: Adjusts the size of the base image from 0.1× to 5.0×
  • Scale Value Display: Shows the current scale factor numerically
  • Reset Scale Button: Instantly returns scale to 1.0×
Action Buttons
  • Save Pattern Button: Exports the current pattern as a high-resolution image
  • Load Comparison Image Button: Loads an image for overlay comparison
  • Comparison Label: Shows the filename of the loaded comparison image

3. Preview Section (Resizable)

The main preview area shows how the tiled pattern will look at the target resolution.

Preview Canvas with Tiled Pattern

Features:

  • Resizable Canvas: Hover near edges to see resize cursors
  • Mouse Resize: Click and drag edges/corners to resize the preview area
  • Scrollbars: Navigate through the full pattern when it's larger than the display area
  • Real-time Updates: Pattern updates automatically when scale changes

Resize Interactions:

  • Hover near edges (within 8 pixels) to see resize cursors
  • Click and drag to resize canvas dimensions

4. Comparison Section (Resizable)

The right panel provides a detailed view of a specific area of the pattern with comparison capabilities.

Comparison Panel with Controls

Position Controls
  • X Position Slider: Horizontally moves the comparison viewport
  • Y Position Slider: Vertically moves the comparison viewport
  • Info Label: Shows current comparison area dimensions and scale
Comparison Canvas
  • Proportional View: Maintains the correct aspect ratio of the target pattern
  • Resizable: Use mouse to resize the comparison view
  • Overlay Support: Displays comparison images centered in the view
  • Scale Range: 0.5× to 2.0× for detailed inspection

Comparison Canvas with Overlay

5. Status Bar

The bottom status bar provides real-time feedback about the application state.

Status Bar

Information Displayed:

  • Current image filename and dimensions
  • Scale factor and tile count
  • Operation status (loading, saving, errors)
  • File save confirmations

Usage Workflow

Basic Pattern Creation

1. Load Background Image

  • Click "Select Image" button
  • Navigate to your background images
  • Select desired background image

2. Adjust Scale

  • Use the scale slider to resize the base image
  • Preview updates automatically
  • Observe tile count in status bar

3. Preview and Refine

  • Resize preview canvas if needed for better visibility
  • Use scrollbars to examine different areas
  • Adjust scale until satisfied with pattern

4. Save Pattern

  • Click "Save Pattern" button
  • Choose location and filename
  • Select format (PNG recommended for quality, JPEG for smaller files)

Advanced Features: Comparison Mode

1. Load Comparison Image

  • Click "Load Comparison Image" button
  • Select an object or component image
  • Image appears centered in comparison view

2. Position Comparison Area

  • Use X and Y position sliders to move the comparison viewport
  • Find optimal background areas for your objects
  • Resize comparison canvas for detailed inspection

3. Evaluate Placement

  • Observe how objects appear against the background pattern
  • Assess visibility and contrast
  • Adjust background scale if needed for better object definition

Example Image

example0.png

Variant 2 Python code

Current Augementations

  • background objects (simple shapes and everyday objects)
  • rotation, translation
  • M6 and M4 screws, nuts and washers
  • other screws, nuts and washers

Example of generated images

example1.png example1.png

Usage

You can generate synthetic images using the synthetic_variants.variants_2.image_creation function showcased in the code/synthetic_variants/variants2/variants2_examples.py file.

Functions

create_distrubance_Canvas(mask_dir='disturbance_masks', number_generated_objects=30)

Create images with screws.

Parameters:

Name Type Description Default
mask_dir str

Directory containing the mask images

'disturbance_masks'
number_generated_objects int

Number of screws/nuts/washers (that are not a class) to be placed in the new image

30

Returns: np.ndarray: The generated screw canvas.

Source code in code/synthetic_variants/variants_2/image_creation.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def create_distrubance_Canvas(mask_dir='disturbance_masks', number_generated_objects=30):
    """Create images with screws.

    Args:
        mask_dir (str): Directory containing the mask images
        number_generated_objects (int): Number of screws/nuts/washers (that are not a class) to be placed in the new image
    Returns:
        np.ndarray: The generated screw canvas.
    """
    # 1) Get mask image paths and names
    mask_paths = sorted(glob.glob(os.path.join(mask_dir, '*.png')))
    mask_names = [os.path.basename(path) for path in mask_paths]

    # 2) Create an empty RGB image of size 3076 x 1852
    canvas_h, canvas_w = 1852, 3076
    canvas = np.zeros((canvas_h, canvas_w, 3), dtype=np.uint8)

    # 3) Choose random masks, randomly position and rotate them in the image
    num_masks = number_generated_objects
    chosen_indices = random.choices(range(len(mask_paths)), k=num_masks)
    placed_info = []

    for idx in chosen_indices:
        mask_path = mask_paths[idx]
        mask_name = mask_names[idx]
        mask_img = io.imread(mask_path)
        # Ensure mask is RGB
        if mask_img.ndim == 2:
            mask_img = np.stack([mask_img]*3, axis=-1)
        elif mask_img.shape[2] == 4:
            mask_img = mask_img[..., :3]
        mask_h, mask_w, _ = mask_img.shape

        # Random rotation
        angle = random.uniform(0, 360)
        rotated_mask = rotate(mask_img, angle, resize=True, preserve_range=True).astype(np.uint8)
        rot_h, rot_w, _ = rotated_mask.shape

        # Random position
        max_y = canvas_h - rot_h
        max_x = canvas_w - rot_w
        if max_y < 0 or max_x < 0:
            continue  # skip if mask doesn't fit

        rand_y = random.randint(0, max_y)
        rand_x = random.randint(0, max_x)

        # Place mask on canvas using the actual intensity values
        for c in range(3):
            canvas[rand_y:rand_y+rot_h, rand_x:rand_x+rot_w, c] = np.maximum(
                canvas[rand_y:rand_y+rot_h, rand_x:rand_x+rot_w, c],
                rotated_mask[..., c]
            )

    return canvas

create_screwImage(mask_dir='Masks', output_dir='generated_ImageLabel', image_name='generated_image', number_generated_objects=40, number_generated_otherScrews=45, number_generated_shapes=10, simpleBackground=False, ImageSave=False, Png=True)

Create images with screws.

Parameters:

Name Type Description Default
mask_dir str

Directory containing the mask images

'Masks'
output_dir str

Directory to save the generated images

'generated_ImageLabel'
image_name str

Name of the generated image and label file

'generated_image'
number_generated_objects int

Number of screws/nuts/washers to be placed in the new image

40
number_generated_otherScrews int

Number of other screws/nuts/washers to be placed in the new image

45
number_generated_shapes int

Number of random shapes to be placed in the new image

10
simpleBackground bool

Whether to add a simple white background (True) or with random non technical objectes (False)

False
ImageSave bool

Whether to save the generated image

False
Png bool

Whether to save the generated image as PNG (True) or JPG (False)

True

Returns: np.ndarray: The generated screw image.

Source code in code/synthetic_variants/variants_2/image_creation.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
def create_screwImage(mask_dir='Masks', output_dir='generated_ImageLabel', image_name = 'generated_image', number_generated_objects=40, number_generated_otherScrews=45, number_generated_shapes=10, simpleBackground=False, ImageSave=False, Png=True):
    """Create images with screws.

    Args:
        mask_dir (str): Directory containing the mask images
        output_dir (str): Directory to save the generated images
        image_name (str): Name of the generated image and label file
        number_generated_objects (int): Number of screws/nuts/washers to be placed in the new image
        number_generated_otherScrews (int): Number of other screws/nuts/washers to be placed in the new image
        number_generated_shapes (int): Number of random shapes to be placed in the new image
        simpleBackground (bool): Whether to add a simple white background (True) or with random non technical objectes (False)
        ImageSave (bool): Whether to save the generated image
        Png (bool): Whether to save the generated image as PNG (True) or JPG (False)
    Returns:
        np.ndarray: The generated screw image.
    """
    start_time = time.time()

    # setput image generation
    os.makedirs(output_dir, exist_ok=True)
    label_path = os.path.join(output_dir, image_name+'.txt') #  Label file path
    if Png:
        generatied_imagePath = os.path.join(output_dir, image_name+'.png')
    else:
        # If not PNG, save as JPG
        generatied_imagePath = os.path.join(output_dir, image_name+'.jpg')   

    #step1_start = time.time()
    # 1) Create the screw canvas and label
    screw_canvas = create_screws_CanvasAndLabel(mask_dir, label_path, number_generated_objects)
    #step1_end = time.time()
    #print(f"Step 1 (create_screws_CanvasAndLabel) took: {step1_end - step1_start:.2f} seconds")

    #step2_start = time.time()
    # 2) Create a background image with random shapes
    if simpleBackground:
            background_image = bg_creation.create_simpleBackground_image(num_shapes=number_generated_shapes)
    else:
        background_image = bg_creation.create_ObjectBackground_image(background_path=r'Backgrounds')
    #step2_end = time.time()
    #print(f"Step 2 (create background canvas) took: {step2_end - step2_start:.2f} seconds")


    # step2_5_start = time.time()
    # 2.5) Add other screws/nuts/washers to the background image
    disturbance_canvas = create_distrubance_Canvas(mask_dir='disturbance_masks', number_generated_objects=number_generated_otherScrews)
    # Both disturbance_canvas and background_image are RGB, so blend per channel
    mask = disturbance_canvas.sum(axis=-1, keepdims=True) > 0
    background_image = np.where(mask, disturbance_canvas, background_image)
    #step2_5_end = time.time()
    #print(f"Step 2.5 (create and add other screws to backgraound canvas) took: {step2_5_end - step2_5_start:.2f} seconds")

    # Step 3a: Integrate the background image with the canvas
    #step3a_start = time.time()
    # Both blending_mask and canvas are RGB images
    blending_mask = np.any(screw_canvas > 0, axis=-1, keepdims=True)
    blended_image = np.where(blending_mask, screw_canvas, background_image)
    #step3a_end = time.time()
    #print(f"Step 3a (add screw canvas to background canvas) took: {step3a_end - step3a_start:.2f} seconds")

    # # Step 3b: Apply Gaussian smoothing
    # step3b_start = time.time()
    # blur_intensity = np.random.uniform(0.5, 2)  # Random blur intensity
    # ksize = int(6 * blur_intensity) | 1         # Ensure kernel size is odd
    # blended_image = cv2.GaussianBlur(blended_image, (ksize, ksize), blur_intensity)
    # step3b_end = time.time()
    # print(f"Step 3b (smoothing) took: {step3b_end - step3b_start:.2f} seconds")

    # # Step 3c: Add random noise
    # step3c_start = time.time()
    # blended_image = blended_image.astype(np.float32)  # Convert to float for noise addition
    # noise = np.random.normal(0, 5, blended_image.shape)
    # blended_image += noise
    # np.clip(blended_image, 0, 255, out=blended_image)
    # blended_image = blended_image.astype(np.uint8)
    # step3c_end = time.time()
    # print(f"Step 3c (adding noise) took: {step3c_end - step3c_start:.2f} seconds")



    # 3) Save the image
    if ImageSave:
        io.imsave(generatied_imagePath, blended_image)
        print(f"Image saved to {generatied_imagePath}")

    end_time = time.time()
    print(f"Total time taken: {end_time - start_time:.2f} seconds")
    return blended_image

create_screws_CanvasAndLabel(mask_dir, label_path, number_generated_objects=30)

Create images with screws and YOLOv8 OBB labels.

Parameters:

Name Type Description Default
mask_dir str

Directory containing the mask images

required
label_path str

Path to save the label file

required
number_generated_objects int

Number of screws/nuts/washers to be placed in the new image

30

Returns: np.ndarray: The generated screw canvas.

Source code in code/synthetic_variants/variants_2/image_creation.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def create_screws_CanvasAndLabel(mask_dir, label_path, number_generated_objects=30):
    """Create images with screws and YOLOv8 OBB labels.

    Args:
        mask_dir (str): Directory containing the mask images
        label_path (str): Path to save the label file
        number_generated_objects (int): Number of screws/nuts/washers to be placed in the new image
    Returns:
        np.ndarray: The generated screw canvas.
    """
    # 1) Get mask image paths and names
    mask_paths = sorted(glob.glob(os.path.join(mask_dir, '*.png')))
    mask_names = [os.path.basename(path) for path in mask_paths]

    # 2) Create an empty RGB image of size 3076 x 1852
    canvas_h, canvas_w = 1852, 3076
    canvas = np.zeros((canvas_h, canvas_w, 3), dtype=np.uint8)

    # 3 & 4) Open label file once and write labels as we go
    with open(label_path, 'w') as f:
        num_masks = number_generated_objects
        chosen_indices = random.choices(range(len(mask_paths)), k=num_masks)

        for idx in chosen_indices:
            mask_path = mask_paths[idx]
            mask_name = mask_names[idx]
            mask = io.imread(mask_path)

            # Ensure mask is RGB
            if mask.ndim == 2:
                mask = np.stack([mask]*3, axis=-1)
            elif mask.shape[2] == 4:
                mask = mask[..., :3]

            # Random rotation
            angle = random.uniform(0, 360)
            rotated_mask = rotate(mask, angle, resize=True, preserve_range=True).astype(np.uint8)
            rot_h, rot_w, _ = rotated_mask.shape

            # Random position
            max_y = canvas_h - rot_h
            max_x = canvas_w - rot_w
            if max_y < 0 or max_x < 0:
                continue  # skip if mask doesn't fit

            rand_y = random.randint(0, max_y)
            rand_x = random.randint(0, max_x)

            # Place mask on canvas
            for c in range(3):
                canvas[rand_y:rand_y+rot_h, rand_x:rand_x+rot_w, c] = np.maximum(
                    canvas[rand_y:rand_y+rot_h, rand_x:rand_x+rot_w, c],
                    rotated_mask[..., c]
                )

            # ---- Label creation happens here ----
            mask_gray = color.rgb2gray(rotated_mask)
            mask_bin = mask_gray > 0.001
            mask_bin = np.pad(mask_bin, pad_width=3, mode='constant', constant_values=0)
            contours = find_contours(mask_bin, level=0.5)
            if not contours:
                continue

            contour = max(contours, key=lambda x: x.shape[0])
            poly = Polygon(contour[:, ::-1])  # (row, col) -> (x, y)
            if not poly.is_valid:
                poly = poly.buffer(0)
            min_rect = poly.minimum_rotated_rectangle
            x, y = min_rect.exterior.coords.xy
            obb_points = np.array(list(zip(x, y)))[:-1]  # drop duplicate last point

            # Shift to canvas position & normalize
            points_n = []
            for pt in obb_points:
                px = (pt[0] + rand_x) / canvas_w
                py = (pt[1] + rand_y) / canvas_h
                points_n.extend([px, py])

            class_name = mask_name.split('_')[0]
            line = f'{class_name} ' + ' '.join([str(x) for x in points_n])
            f.write(line + '\n')

    return canvas

display_obb_with_labels(image_path, label_path)

Reads an image and its corresponding YOLOv8 OBB label file, then displays the image with OBB bounding boxes and class names.

Parameters:

Name Type Description Default
image_path str

Path to the generated image.

required
label_path str

Path to the label file in YOLOv8 OBB 8 points format.

required
Source code in code/synthetic_variants/variants_2/image_creation.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
def display_obb_with_labels(image_path, label_path):
    """
    Reads an image and its corresponding YOLOv8 OBB label file,
    then displays the image with OBB bounding boxes and class names.

    Args:
        image_path (str): Path to the generated image.
        label_path (str): Path to the label file in YOLOv8 OBB 8 points format.
    """
    try:
        image = io.imread(image_path)
    except FileNotFoundError:
        print(f"Error: Image file not found at {image_path}")
        return

    canvas_h, canvas_w, _ = image.shape

    fig, ax = plt.subplots(figsize=(10, 6))
    ax.imshow(image)

    try:
        with open(label_path, 'r') as f:
            for line in f:
                parts = line.strip().split()
                class_name = parts[0]
                points_n = [float(x) for x in parts[1:]]

                # Ensure there are enough points for an OBB (8 points for 4 corners)
                if len(points_n) == 8:
                    points = np.array(points_n).reshape(4, 2)
                    points[:, 0] *= canvas_w
                    points[:, 1] *= canvas_h
                    poly = np.vstack([points, points[0]])  # Close the polygon

                    ax.plot(poly[:, 0], poly[:, 1], color='red', linewidth=2)
                    centroid = np.mean(points, axis=0)
                    ax.text(centroid[0], centroid[1], class_name, color='yellow', fontsize=12, ha='center', va='center')
                else:
                    print(f"Warning: Skipping malformed line in label file: {line.strip()}")
    except FileNotFoundError:
        print(f"Error: Label file not found at {label_path}")
        return
    except Exception as e:
        print(f"An error occurred while processing the label file: {e}")
        return

    plt.title('OBB Bounding Boxes with Class Names')
    plt.axis('off')
    plt.show()
    return

generate_screw_position_image(mask_dir='positionMasks', output_dir='positionImages', x_pos=10, y_pos=10, angle=0, Png=True)

Create images with screws and YOLOv8 OBB labels.

Parameters:

Name Type Description Default
mask_dir str

Directory containing the mask images

'positionMasks'
output_dir str

Directory to save the generated images and labels

'positionImages'
image_name str

Name of the generated image and label file

required
x_pos int

X position to place the object

10
y_pos int

Y position to place the object

10
angle int

Angle to rotate the object (0-360°)

0

Returns:

Source code in code/synthetic_variants/variants_2/image_creation.py
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
def generate_screw_position_image(mask_dir='positionMasks', output_dir='positionImages', x_pos=10, y_pos=10, angle=0, Png = True):
    """Create images with screws and YOLOv8 OBB labels.

    Args:
        mask_dir (str): Directory containing the mask images
        output_dir (str): Directory to save the generated images and labels
        image_name (str): Name of the generated image and label file
        x_pos (int): X position to place the object
        y_pos (int): Y position to place the object
        angle (int): Angle to rotate the object (0-360°)
    Returns:

    """
    # 1) Get mask image paths and names
    os.makedirs(output_dir, exist_ok=True)

    mask_paths = sorted(glob.glob(os.path.join(mask_dir, '*.png')))
    mask_names = [os.path.basename(path) for path in mask_paths]

    # 3 & 4) Open label file once and write labels as we go
    for idx in range(len(mask_paths)):
        name_ding = mask_names[idx].split('_')[0]
        image_name = f'{name_ding}_at_xpos{x_pos}_ypos{y_pos}_angle{angle}'
        label_path = os.path.join(output_dir, image_name+'.txt') #  Label file path
        if Png:
            generated_imagePath = os.path.join(output_dir, image_name+'.png')
        else:
            # If not PNG, save as JPG
            generated_imagePath = os.path.join(output_dir, image_name+'.jpg')   

        # 2) Create an empty RGB image of size 3076 x 1852
        canvas_h, canvas_w = 1852, 3076
        canvas = np.full((canvas_h, canvas_w, 3), 0, dtype=np.uint8)

        with open(label_path, 'w') as f:
            mask_path = mask_paths[idx]
            mask_name = mask_names[idx]
            mask = io.imread(mask_path)

            # Ensure mask is RGB
            if mask.ndim == 2:
                mask = np.stack([mask]*3, axis=-1)
            elif mask.shape[2] == 4:
                mask = mask[..., :3]

            # Random rotation
            rotated_mask = rotate(mask, angle, resize=True, preserve_range=True).astype(np.uint8)
            rot_h, rot_w, _ = rotated_mask.shape

            # Random position
            max_y = canvas_h - rot_h
            max_x = canvas_w - rot_w
            if max_y < 0 or max_x < 0:
                continue  # skip if mask doesn't fit

            rand_y = min(y_pos, max_y)
            rand_x = min(x_pos, max_x)

            # Place mask on canvas
            for c in range(3):
                canvas[rand_y:rand_y+rot_h, rand_x:rand_x+rot_w, c] = np.maximum(
                    canvas[rand_y:rand_y+rot_h, rand_x:rand_x+rot_w, c],
                    rotated_mask[..., c]
                )

            # ---- Label creation happens here ----
            mask_gray = color.rgb2gray(rotated_mask)
            mask_bin = mask_gray > 0.001
            mask_bin = np.pad(mask_bin, pad_width=3, mode='constant', constant_values=0)
            contours = find_contours(mask_bin, level=0.5)
            if not contours:
                continue

            contour = max(contours, key=lambda x: x.shape[0])
            poly = Polygon(contour[:, ::-1])  # (row, col) -> (x, y)
            if not poly.is_valid:
                poly = poly.buffer(0)
            min_rect = poly.minimum_rotated_rectangle
            x, y = min_rect.exterior.coords.xy
            obb_points = np.array(list(zip(x, y)))[:-1]  # drop duplicate last point

            # Shift to canvas position & normalize
            points_n = []
            for pt in obb_points:
                px = (pt[0] + rand_x) / canvas_w
                py = (pt[1] + rand_y) / canvas_h
                points_n.extend([px, py])

            class_name = mask_name.split('_')[0]
            line = f'{class_name} ' + ' '.join([str(x) for x in points_n])
            f.write(line + '\n')

        # Make all pixels that are not part of the screw white
        white_background = np.full((canvas_h, canvas_w, 3), 255, dtype=np.uint8)
        canvas = np.where(canvas == 0, white_background, canvas)
        io.imsave(generated_imagePath, canvas)
        print(f"Image saved to {generated_imagePath}")

    #checks and other stuff
    # convert_labels_to_indices(label_folder=f"./{output_dir}")
    # organize_generated_files(base_folder=f"./{output_dir}")
    # find_labels_without_images(base_folder=f"./{output_dir}")

    return