diff --git a/ai/.gitignore b/ai/.gitignore
index 89bf445fc4a046cfffe2ac5ba2910f89be8c1d5d..65029931b77a1c341e376b5b7be6834ff8f360e5 100644
--- a/ai/.gitignore
+++ b/ai/.gitignore
@@ -1,2 +1,3 @@
 .idea
-src/output.pdf
\ No newline at end of file
+src/output.pdf
+generated_pdfs/*
\ No newline at end of file
diff --git a/ai/src/bubble_sheet_generator.py b/ai/src/bubble_sheet_generator.py
index a383a8fd9d6bf3de968b0c5fb964c649dca743f2..4eb852c8db970d36871fd93d270c3623c41c857b 100644
--- a/ai/src/bubble_sheet_generator.py
+++ b/ai/src/bubble_sheet_generator.py
@@ -1,3 +1,5 @@
+import time
+
 import numpy as np
 import matplotlib.pyplot as plt
 import matplotlib.patches as patches
@@ -7,79 +9,64 @@ from ai.src.utils import load_config
 question_number = 1
 
 
-def draw_rect(ax, config, rect_x, rect_y, rect_type="answer_rect", gray_columns=False, last_rect_q=None):
+def draw_header(ax, rect_x, rect_y, config):
     """
-    Draws a rectangle including circles to be filled in the final bubble sheet
+    Draw the header of the bubble sheet
+    :param ax: The axis to draw the rectangle on
+    :param rect_x: The x-coordinate of the rectangle top left corner
+    :param rect_y: The y-coordinate of the rectangle top left corner
+    :param config: Configuration dictionary
+    """
+    PIXELS_PER_INCH = 96
+    # Configuration
+    header_title = config["header"]["title"]
+    header_date = config["header"]["date"]
+    header_name = config["header"]["name"]
+    font_size = config["header"]["font_size"]
+    font_size_relative = font_size / (ax.figure.get_figheight() * PIXELS_PER_INCH)
+    font = config["header"]["font"]
+    text_color = config["colors"]["main_color"]
+
+    # Draw the header
+    rect_y -= font_size_relative
+    rect_x -= font_size_relative
+    ax.text(rect_x, rect_y, header_title, ha='left', va='bottom', fontsize=font_size+5, fontname=font, color=text_color, weight='bold')
+    font_size_offset = font_size_relative * 2
+    ax.text(rect_x, rect_y - font_size_offset, header_date, ha='left', va='bottom', fontsize=font_size, fontname=font,
+            color=text_color)
+    font_size_offset = font_size_relative * 4
+    ax.text(rect_x, rect_y - font_size_offset, header_name, ha='left', va='bottom', fontsize=font_size, fontname=font,
+            color=text_color)
+
+
+def gray_out(ax, config, rect_x, rect_y, rect_type="answer_rect", gray_columns=False):
+    """
+    Gray out every other column or row
     :param ax: The axis to draw the rectangle on
     :param config: Configuration dictionary
     :param rect_x: The x-coordinate of the rectangle top left corner
     :param rect_y: The y-coordinate of the rectangle top left corner
     :param rect_type: Type of the rectangle (Student ID or Answers)
     :param gray_columns: If true every other column will be grayed out, otherwise every other row
-    :param last_rect_q: Number of questions in the last rectangle
     """
-    # Set colors from the config
-    rect_color = config["colors"]["main_color"]
+    # Configuration
     off_color = config["colors"]["off_color"]
-    text_color = config["colors"]["text_color"]
 
-    # Set the width of line of the rectangle
     rect_width = config["rect_settings"]["rect_line_width"]
 
-    # Set the width and height of the rectangle and the grid inside
     width = config[rect_type]["width"]
     height = config[rect_type]["height"]
-    grid_x = config[rect_type]["grid"]["cols"]
-    grid_y = config[rect_type]["grid"]["rows"]
-
-    # Set the labels and offsets and font sizes from the config
-    label = config[rect_type]["label"]["main"]
-    q_label = config[rect_type]["label"]["rows"]
-    a_label = config[rect_type]["label"]["cols"]
-
-    label_offset = config[rect_type]["label_offset"]["main"]
-    q_offset = config[rect_type]["label_offset"]["rows"]
-    a_offset = config[rect_type]["label_offset"]["cols"]
-
-    label_font_size = config[rect_type]["label_font_size"]["main"]
-    q_label_fontsize = config[rect_type]["label_font_size"]["rows"]
-    a_label_fontsize = config[rect_type]["label_font_size"]["cols"]
-
-    # Set up the question label and answer label correctly
-    if rect_type == "answer_rect":
-        global question_number  # Global question number counter
-
-        # If this is the last rectangle, that could have less questions
-        if last_rect_q is not None:
-            q_label = [str(i + question_number) for i in range(last_rect_q)]
-            question_number += last_rect_q
-        # Else normal rectangle
-        else:
-            q_label = [str(i + question_number) for i in range(grid_y)]
-            question_number += grid_y
-
-        # If the answer labels are alphabetic use ABCD... else if numeric use 1234...
-        if a_label == "alphabetic":
-            a_label = [chr(65 + i) for i in range(grid_x)]
-        elif a_label == "numeric":
-            a_label = [str(i + 1) for i in range(grid_x)]
-
-    # Set up the student ID label correctly
-    if rect_type == "student_id_rect":
-        q_label = [str(i) for i in range(grid_y)]
 
-    # Rounded corners rectangle
-    round_rect = patches.FancyBboxPatch((rect_x, rect_y), width, height, edgecolor=rect_color, facecolor="none",
-                                        linewidth=rect_width, boxstyle="round,pad=0.01")
-    ax.add_patch(round_rect)
+    cols = config[rect_type]["grid"]["cols"]
+    rows = config[rect_type]["grid"]["rows"]
 
     # Width and Height of grid cell
-    grid_width = width / grid_x
-    grid_height = height / grid_y
+    grid_width = width / cols
+    grid_height = height / rows
 
     # Gray out every other column
     if gray_columns:
-        for i in range(grid_x):
+        for i in range(cols):
             x = rect_x + grid_width * i
             if i % 2 == 0:
                 rect = patches.Rectangle((x, rect_y), grid_width, height, edgecolor=off_color,
@@ -87,16 +74,42 @@ def draw_rect(ax, config, rect_x, rect_y, rect_type="answer_rect", gray_columns=
                 ax.add_patch(rect)
     # Gray out every other row
     else:
-        for i in range(grid_y):
+        for i in range(rows):
             y = rect_y + grid_height * i
             if i % 2 == 0:
                 rect = patches.Rectangle((rect_x, y), width, grid_height, edgecolor=off_color,
                                          facecolor=off_color, linewidth=rect_width)
                 ax.add_patch(rect)
 
-    # Draw the bubbles
-    for i in range(grid_x):
-        for j in range(grid_y):
+
+def draw_bubbles(ax, config, rect_x, rect_y, rect_type, last_rect_q, student_id):
+    """
+    Draw the bubbles in the rectangle
+    :param ax: The axis to draw the rectangle on
+    :param config: Configuration dictionary
+    :param rect_x: The x-coordinate of the rectangle top left corner
+    :param rect_y: The y-coordinate of the rectangle top left corner
+    :param rect_type: Type of the rectangle (Student ID or Answers)
+    :param last_rect_q: Number of questions in the last rectangle
+    :param student_id: Student ID
+    """
+    # Configuration
+    rect_color = config["colors"]["main_color"]
+
+    rect_width = config["rect_settings"]["rect_line_width"]
+
+    width = config[rect_type]["width"]
+    height = config[rect_type]["height"]
+
+    cols = config[rect_type]["grid"]["cols"]
+    rows = config[rect_type]["grid"]["rows"]
+
+    # Width and Height of grid cell
+    grid_width = width / cols
+    grid_height = height / rows
+
+    for i in range(cols):
+        for j in range(rows):
             # Skip the bubbles if this is the last rectangle and the question number is less than the last_rect_q
             if last_rect_q is not None and j < last_rect_q:
                 continue
@@ -104,12 +117,97 @@ def draw_rect(ax, config, rect_x, rect_y, rect_type="answer_rect", gray_columns=
             # Calculate the position of the bubble
             x = rect_x + grid_width * i
             y = rect_y + grid_height * j
+
+            # If this is the student ID rectangle, fill the bubbles according to the student ID
+            face_color = "none"
+            if rect_type == "student_id_rect":
+                y = rect_y + height - (grid_height * (j + 1))
+                if student_id[i] == str(j):
+                    face_color = rect_color
+
             # Draw the bubble
             circle = patches.Circle((x + grid_width / 2, y + grid_height / 2), grid_width / 3,
-                                    edgecolor=rect_color, facecolor="none", linewidth=rect_width)
+                                    edgecolor=rect_color, facecolor=face_color, linewidth=rect_width)
             ax.add_patch(circle)
 
-    # Draw the big label (if it is set in the config)
+
+def setup_labels(config, rect_type, last_rect_q=None):
+    """
+    Set up the question label and answer label correctly
+    :param config: Configuration dictionary
+    :param rect_type: Type of the rectangle (Student ID or Answers)
+    :param last_rect_q: Number of questions in the last rectangle
+    :return: Question label and Answer label
+    """
+    # Configuration
+    cols = config[rect_type]["grid"]["cols"]
+    rows = config[rect_type]["grid"]["rows"]
+
+    q_label = config[rect_type]["label"]["rows"]
+    a_label = config[rect_type]["label"]["cols"]
+
+    # Set up the question label and answer label correctly
+    if rect_type == "answer_rect":
+        global question_number  # Global question number counter
+
+        # If this is the last rectangle, that could have less questions
+        if last_rect_q is not None:
+            q_label = [str(i + question_number) for i in range(last_rect_q)]
+            question_number += last_rect_q
+        # Else normal rectangle
+        else:
+            q_label = [str(i + question_number) for i in range(rows)]
+            question_number += rows
+
+        # If the answer labels are alphabetic use ABCD... else if numeric use 1234...
+        if a_label == "alphabetic":
+            a_label = [chr(65 + i) for i in range(cols)]
+        elif a_label == "numeric":
+            a_label = [str(i + 1) for i in range(cols)]
+
+    # Set up the student ID label correctly
+    if rect_type == "student_id_rect":
+        q_label = [str(i) for i in range(rows)]
+
+    return q_label, a_label
+
+
+def draw_labels(ax, config, rect_x, rect_y, rect_type, last_rect_q=None):
+    """
+    Draw the labels of questions and answers
+    :param ax: The axis to draw the rectangle on
+    :param config: Configuration dictionary
+    :param rect_x: The x-coordinate of the rectangle top left corner
+    :param rect_y: The y-coordinate of the rectangle top left corner
+    :param rect_type: Type of the rectangle (Student ID or Answers)
+    :param last_rect_q: Number of questions in the last rectangle
+    """
+    # Configuration
+    text_color = config["colors"]["text_color"]
+
+    width = config[rect_type]["width"]
+    height = config[rect_type]["height"]
+
+    label = config[rect_type]["label"]["main"]
+
+    cols = config[rect_type]["grid"]["cols"]
+    rows = config[rect_type]["grid"]["rows"]
+
+    label_offset = config[rect_type]["label_offset"]["main"]
+    q_offset = config[rect_type]["label_offset"]["rows"]
+    a_offset = config[rect_type]["label_offset"]["cols"]
+
+    label_font_size = config[rect_type]["label_font_size"]["main"]
+    q_label_fontsize = config[rect_type]["label_font_size"]["rows"]
+    a_label_fontsize = config[rect_type]["label_font_size"]["cols"]
+
+    # Set up the question label and answer label correctly (or student ID label)
+    q_label, a_label = setup_labels(config, rect_type, last_rect_q=last_rect_q)
+
+    # Width and Height of grid cell
+    grid_width = width / cols
+    grid_height = height / rows
+
     if label != "":
         ax.text(rect_x + width / 2, rect_y + height + label_offset, label,
                 ha='center', va='center', fontsize=label_font_size)
@@ -125,9 +223,45 @@ def draw_rect(ax, config, rect_x, rect_y, rect_type="answer_rect", gray_columns=
                 ha='center', va='center', fontsize=q_label_fontsize, color=text_color)
 
 
+def draw_rect(ax, config, rect_x, rect_y, rect_type="answer_rect", gray_columns=False, last_rect_q=None, student_id=0):
+    """
+    Draws a rectangle including circles to be filled in the final bubble sheet
+    :param ax: The axis to draw the rectangle on
+    :param config: Configuration dictionary
+    :param rect_x: The x-coordinate of the rectangle top left corner
+    :param rect_y: The y-coordinate of the rectangle top left corner
+    :param rect_type: Type of the rectangle (Student ID or Answers)
+    :param gray_columns: If true every other column will be grayed out, otherwise every other row
+    :param last_rect_q: Number of questions in the last rectangle
+    :param student_id: Student ID
+    """
+    # Configuration
+    rect_color = config["colors"]["main_color"]
+
+    rect_width = config["rect_settings"]["rect_line_width"]
+
+    width = config[rect_type]["width"]
+    height = config[rect_type]["height"]
+
+    # Rounded corners rectangle
+    round_rect = patches.FancyBboxPatch((rect_x, rect_y), width, height, edgecolor=rect_color, facecolor="none",
+                                        linewidth=rect_width, boxstyle="round,pad=0.01")
+    ax.add_patch(round_rect)
+
+    # Gray out every other column or row
+    gray_out(ax, config, rect_x, rect_y, rect_type=rect_type, gray_columns=gray_columns)
+
+    # Draw the bubbles
+    draw_bubbles(ax, config, rect_x, rect_y, rect_type, last_rect_q, str(student_id).zfill(4))
+
+    # Draw labels
+    draw_labels(ax, config, rect_x, rect_y, rect_type, last_rect_q=last_rect_q)
+
+
 def generate_bubble_sheet(student_id):
     """
     Main function to generate the bubble sheet
+    :param student_id: Student ID (number from 0 to 9999)
     """
     # Load the configuration file
     config = load_config()
@@ -142,7 +276,10 @@ def generate_bubble_sheet(student_id):
     # Define the Student ID field
     x = config["student_id_rect"]["x"]
     y = config["student_id_rect"]["y"]
-    draw_rect(ax, config, x, y, rect_type="student_id_rect", gray_columns=True)
+    draw_rect(ax, config, x, y, rect_type="student_id_rect", gray_columns=True, student_id=student_id)
+
+    # Draw the header
+    draw_header(ax, x, 1 - y, config)
 
     # Offset between rectangles
     offset_between_rect = config["rect_settings"]["rect_space_between"]
diff --git a/ai/src/config.json b/ai/src/config.json
index cd185397fe89b57512a2f1205cdf749fe314f383..3669a340419329f7fb3d2daf77a96cd22acb3ccd 100644
--- a/ai/src/config.json
+++ b/ai/src/config.json
@@ -5,9 +5,11 @@
     "text_color": "gray"
   },
   "header": {
-    "title": "Multiple Choice Answer Sheet",
-    "date": "Date: ",
-    "signature": "Signature: "
+    "font_size": 15,
+    "font": "Arial",
+    "title": "První písemná práce z předmětu KIV/ADT",
+    "date": "Datum: ",
+    "name": "Jméno: "
   },
   "rect_settings": {
     "rect_line_width": 2,
@@ -23,17 +25,17 @@
       "cols": 4
     },
     "label": {
-      "main": "Student ID",
+      "main": "ID Studenta",
       "rows": "",
       "cols": ""
     },
     "label_offset": {
-      "main": 0.05,
+      "main": 0.04,
       "rows": 0.04,
       "cols": 0.05
     },
     "label_font_size": {
-      "main": 20,
+      "main": 15,
       "rows": 12,
       "cols": 12
     }
diff --git a/ai/src/preprocessor.py b/ai/src/preprocessor.py
index b683ca8ea4cdfc3608bafeb3b097d5b2bc9283f6..2644a9d7581b3ac93e219dc721bedf933249a018 100644
--- a/ai/src/preprocessor.py
+++ b/ai/src/preprocessor.py
@@ -6,7 +6,7 @@ import skimage.filters.thresholding as th
 import pythreshold.utils as putils
 import imutils.contours
 
-IMG_FOLDER = "tsp_zaznamove_archy"
+IMG_FOLDER = "../tsp_zaznamove_archy"
 INPUT_FILE = "naskenovany_vyplneny.pdf"
 
 
diff --git a/ai/src/tsp_zaznamove_archy/naskenovany_prazdny.pdf b/ai/tsp_zaznamove_archy/naskenovany_prazdny.pdf
similarity index 100%
rename from ai/src/tsp_zaznamove_archy/naskenovany_prazdny.pdf
rename to ai/tsp_zaznamove_archy/naskenovany_prazdny.pdf
diff --git a/ai/src/tsp_zaznamove_archy/naskenovany_vyplneny.pdf b/ai/tsp_zaznamove_archy/naskenovany_vyplneny.pdf
similarity index 100%
rename from ai/src/tsp_zaznamove_archy/naskenovany_vyplneny.pdf
rename to ai/tsp_zaznamove_archy/naskenovany_vyplneny.pdf
diff --git a/ai/src/tsp_zaznamove_archy/vygenerovany.pdf b/ai/tsp_zaznamove_archy/vygenerovany.pdf
similarity index 100%
rename from ai/src/tsp_zaznamove_archy/vygenerovany.pdf
rename to ai/tsp_zaznamove_archy/vygenerovany.pdf