Combined-Sensor-Uncertainty/examples/PDF_Generator.py

248 lines
9.8 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)