248 lines
10 KiB
Python
248 lines
10 KiB
Python
import os
|
|
from fpdf import FPDF
|
|
|
|
from PIL import Image
|
|
|
|
class PDFGenerator(FPDF):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
# Lists for storing items to memory
|
|
self.stored_memory: list = []
|
|
self.toc_entries: list = [] # List to store TOC entries
|
|
|
|
def add_header(self, text, level: int = 1):
|
|
""" Adds a header to the PDF. Level determines the size of the header. """
|
|
size = {1: 18, 2: 16, 3: 14}.get(level, 18)
|
|
self.set_font('DejaVu', 'B', size)
|
|
self.multi_cell(0, 5, text)
|
|
self.ln(2) # Add a small line break after the header
|
|
|
|
def add_text(self, text, bold: bool = False, indent: int = 0, newline=True):
|
|
""" Adds normal text to the PDF """
|
|
if bold == True:
|
|
self.set_font('DejaVu', 'B', 8)
|
|
else:
|
|
self.set_font('DejaVu', '', 8)
|
|
indents = " " * 4 * indent
|
|
|
|
self.multi_cell(0, 5, indents + text)
|
|
self.ln(1)
|
|
|
|
def add_centered_text(self, text, bold: bool = False):
|
|
""" Adds centered text to the PDF """
|
|
if bold == True:
|
|
self.set_font('DejaVu', 'B', 8)
|
|
else:
|
|
self.set_font('DejaVu', '', 8)
|
|
self.multi_cell(0, 5, text, align='C')
|
|
self.ln(1)
|
|
|
|
def add_centered_image(self, image_path: str, width_ratio: float = 0.5, caption=None):
|
|
""" Adds an image centered on the page, optionally with a caption below it. """
|
|
with Image.open(image_path) as img:
|
|
image_width, image_height = img.size
|
|
|
|
pdf_image_width = self.w * width_ratio
|
|
aspect_ratio = image_height / image_width
|
|
pdf_image_height = pdf_image_width * aspect_ratio
|
|
x_position = (self.w - pdf_image_width) / 2
|
|
|
|
# Center the image
|
|
self.ln(1)
|
|
self.image(image_path, x=x_position, w=pdf_image_width, h=pdf_image_height)
|
|
|
|
# If a caption is provided, add it below the image
|
|
if caption:
|
|
self.set_font('DejaVu', 'I', 6)
|
|
self.multi_cell(0, 10, caption, 0, 1, 'C')
|
|
|
|
def format_value(self, value):
|
|
"""Format a number to 5 significant digits."""
|
|
try:
|
|
return f"{value:.5g}"
|
|
except (ValueError, TypeError):
|
|
return str(value)
|
|
|
|
def create_metrics_table(self, title, metrics_list, column_labels):
|
|
# Make title
|
|
self.add_centered_text(title, bold=True)
|
|
|
|
# Define row labels based on the keys of the first dictionary in the metrics list
|
|
row_labels = list(metrics_list[0].keys())
|
|
|
|
# Define column widths (you can adjust these as needed)
|
|
col_widths = [40] + [13] * len(column_labels)
|
|
cell_height = 10 # Adjust cell height as needed
|
|
|
|
# Calculate the total width of the table
|
|
table_width = sum(col_widths)
|
|
|
|
# Define font settings
|
|
self.set_font('DejaVu', 'B', 6)
|
|
|
|
# Calculate the starting x position to center the table
|
|
start_x = (210 - table_width) / 2 # Assuming A4 size paper (210mm width)
|
|
|
|
# Set the starting x position
|
|
self.set_x(start_x)
|
|
|
|
# Add header row
|
|
self.cell(col_widths[0], 10, 'Metrics/Sensor Noise Gain', 1, 0, 'C')
|
|
for col_label in column_labels:
|
|
self.cell(col_widths[1], cell_height, self.format_value(col_label), 1, 0, 'C')
|
|
self.ln()
|
|
|
|
# Add data rows
|
|
self.set_font('DejaVu', '', 6)
|
|
for row_label in row_labels:
|
|
self.set_x(start_x) # Reset the x position for each row
|
|
x, y = self.get_x(), self.get_y() # Save the current position
|
|
self.set_font('DejaVu', 'B', 6)
|
|
self.multi_cell(col_widths[0], cell_height, row_label, 1, 'C')
|
|
self.set_font('DejaVu', '', 6)
|
|
self.set_xy(x + col_widths[0], y) # Adjust position for the next cells
|
|
for metrics in metrics_list:
|
|
self.cell(col_widths[1], cell_height, self.format_value(metrics[row_label]), 1, 0, 'C')
|
|
self.ln()
|
|
|
|
def make_pdf_from_memory(self, filepath: str) -> None:
|
|
'''Makes the pdf from the stored list'''
|
|
self.set_margins(5, 5, 5) # left, top, and right margins in mm
|
|
|
|
# Load a Unicode font
|
|
current_directory = os.path.dirname(__file__) # Get the directory where the script is located
|
|
self.add_font('DejaVu', '', f"{current_directory}\\fonts\\dejavu-sans-condensed.ttf", uni=True)
|
|
self.add_font('DejaVu', 'B', f"{current_directory}\\fonts\\dejavu-sans-condensedbold.ttf", uni=True)
|
|
self.add_font('DejaVu', 'I', f"{current_directory}\\fonts\\dejavu-sans-condensedoblique.ttf", uni=True)
|
|
self.set_font('DejaVu', '', 8) # Set DejaVu as the default font
|
|
|
|
|
|
self.add_header("Table of Contents")
|
|
self.add_newline_memory()
|
|
self.set_font('DejaVu', '', 8)
|
|
for entry in self.toc_entries:
|
|
self.cell(0, 10, f"{entry['text']}", 0, 0, 'L')
|
|
self.cell(-20, 10, f"{entry['page']}", 0, 1, 'R')
|
|
|
|
self.add_page()
|
|
|
|
for item in self.stored_memory:
|
|
match item["type"]:
|
|
case "header":
|
|
self.add_header(item["text"], item["level"])
|
|
case "text":
|
|
self.add_text(item["text"], item["bold"], item["indent"])
|
|
case "centered_text":
|
|
self.add_centered_text(item["text"], item["bold"])
|
|
case "centered_image":
|
|
self.add_centered_image(item["image_path"], item["width_ratio"], item["caption"])
|
|
case "page":
|
|
self.add_page()
|
|
case "newline":
|
|
self.ln(1)
|
|
case "metrics_table":
|
|
self.create_metrics_table(item["title"], item["metrics_list"], item["column_labels"])
|
|
|
|
self.output(filepath)
|
|
|
|
def add_header_memory(self, text, level: int = 1, toc=True):
|
|
dict_to_add = {
|
|
"type": "header",
|
|
"text": text,
|
|
"level": level
|
|
}
|
|
self.stored_memory.append(dict_to_add)
|
|
|
|
# Track header for TOC
|
|
if toc:
|
|
self.toc_entries.append({
|
|
"text": text,
|
|
"level": level,
|
|
"page": sum(1 for item in self.stored_memory if item.get("type") == "page") + 2
|
|
})
|
|
|
|
|
|
def add_text_memory(self, text, bold: bool = False, indent: int = 0, newline: bool = True):
|
|
dict_to_add = {
|
|
"type": "text",
|
|
"text": text,
|
|
"bold": bold,
|
|
"indent": indent,
|
|
"newline": newline
|
|
}
|
|
self.stored_memory.append(dict_to_add)
|
|
|
|
|
|
def add_centered_text_memory(self, text, bold: bool = False):
|
|
dict_to_add = {
|
|
"type": "centered_text",
|
|
"text": text,
|
|
"bold": bold
|
|
}
|
|
self.stored_memory.append(dict_to_add)
|
|
|
|
|
|
def add_centered_image_memory(self, image_path: str, width_ratio: float = 0.5, caption=None):
|
|
dict_to_add = {
|
|
"type": "centered_image",
|
|
"image_path": image_path,
|
|
"width_ratio": width_ratio,
|
|
"caption": caption
|
|
}
|
|
self.stored_memory.append(dict_to_add)
|
|
|
|
def add_page_memory(self) -> None:
|
|
dict_to_add = {
|
|
"type": "page",
|
|
}
|
|
self.stored_memory.append(dict_to_add)
|
|
|
|
def add_newline_memory(self) -> None:
|
|
dict_to_add = {
|
|
"type": "newline",
|
|
}
|
|
self.stored_memory.append(dict_to_add)
|
|
|
|
def add_metrics_list_memory(self, metrics, indent=0) -> None:
|
|
if "true_value" in metrics:
|
|
self.add_text_memory(f"True System Output Value: {metrics['true_value']}", indent=indent, bold=True)
|
|
if "median" in metrics:
|
|
self.add_text_memory(f"Median: {metrics['median']}", indent=indent, bold=True)
|
|
if "average" in metrics:
|
|
self.add_text_memory(f"Average of Monte-Carlo: {metrics['average']}", indent=indent, bold=True)
|
|
if "average_percent_difference" in metrics:
|
|
self.add_text_memory(f"Average Percent Difference: {metrics['average_percent_difference']} %", indent=indent, bold=True)
|
|
if "min_val" in metrics:
|
|
self.add_text_memory(f"Minimum Value: {metrics['min_val']}", indent=indent)
|
|
if "max_val" in metrics:
|
|
self.add_text_memory(f"Maximum Value: {metrics['max_val']}", indent=indent)
|
|
if "std_dev" in metrics:
|
|
self.add_text_memory(f"Standard Deviation: ±{metrics['std_dev']}", indent=indent)
|
|
if "std_dev_percent" in metrics:
|
|
self.add_text_memory(f"Standard Deviation Percent: {metrics['std_dev_percent']} %", indent=indent)
|
|
if "mean_absolute_error" in metrics:
|
|
self.add_text_memory(f"Mean Absolute Error: {metrics['mean_absolute_error']}", indent=indent)
|
|
if "mean_absolute_percent_error" in metrics:
|
|
self.add_text_memory(f"Mean Absolute Percent Error: {metrics['mean_absolute_percent_error']} %", indent=indent)
|
|
if "max_2std_error" in metrics:
|
|
self.add_text_memory(f"Max Error 95% of the Time: {metrics['max_2std_error']}", indent=indent)
|
|
if "max_2std_percent_error" in metrics:
|
|
self.add_text_memory(f"Max Percent Error 95% of the Time: {metrics['max_2std_percent_error']} %", indent=indent, bold=True)
|
|
if "max_3std_error" in metrics:
|
|
self.add_text_memory(f"Max Error 99.73% of the Time: {metrics['max_3std_error']}", indent=indent)
|
|
if "max_3std_percent_error" in metrics:
|
|
self.add_text_memory(f"Max Percent Error 99.73% of the Time: {metrics['max_3std_percent_error']} %", indent=indent)
|
|
|
|
|
|
|
|
def add_metrics_table_memory(self, title, metrics_list, column_labels) -> None:
|
|
dict_to_add = {
|
|
"type": "metrics_table",
|
|
"title": title,
|
|
"metrics_list": metrics_list,
|
|
"column_labels": column_labels
|
|
}
|
|
self.stored_memory.append(dict_to_add)
|
|
|