Spaces:
Paused
Paused
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| from reportlab.lib.pagesizes import letter | |
| from reportlab.pdfgen import canvas | |
| from reportlab.lib.utils import ImageReader | |
| import os | |
| from datetime import datetime | |
| from reportlab.platypus import Table, TableStyle | |
| def create_percentage_circle(percentage, title): | |
| """ | |
| Create a circular progress bar for percentage visualization. | |
| """ | |
| fig, ax = plt.subplots(figsize=(4, 4)) | |
| ax.set_aspect("equal") | |
| circle = plt.Circle((0.5, 0.5), 0.4, color="lightgray") | |
| ax.add_patch(circle) | |
| theta = np.linspace(0, 360 * (percentage / 100), 100) | |
| x = 0.5 + 0.4 * np.cos(np.radians(theta)) | |
| y = 0.5 + 0.4 * np.sin(np.radians(theta)) | |
| ax.plot(x, y, color="blue", linewidth=20) | |
| ax.text(0.5, 0.5, f"{percentage:.1f}%", ha="center", va="center", fontsize=20) | |
| ax.set_title(title, fontsize=12) | |
| ax.axis("off") | |
| output_path = f"screenshots/circle_{int(percentage)}_{int(np.random.randint(1000))}.png" | |
| plt.savefig(output_path, bbox_inches="tight") | |
| plt.close() | |
| return output_path | |
| def generate_pdf_report(summary, screenshots, output_path, zip_path, circle_path): | |
| """ | |
| Generate a PDF report with summary, object counts, charts, screenshots, and video ZIP link. | |
| """ | |
| c = canvas.Canvas(output_path, pagesize=letter) | |
| width, height = letter | |
| # Title page | |
| c.setFont("Helvetica-Bold", 22) | |
| c.drawCentredString(width / 2, height - 80, "Road Safety Analysis Report") | |
| c.setFont("Helvetica", 14) | |
| c.drawCentredString(width / 2, height - 110, f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| c.showPage() | |
| # Summary section | |
| c.setFont("Helvetica-Bold", 16) | |
| c.drawString(50, height - 60, "Analysis Summary") | |
| c.setFont("Helvetica", 12) | |
| y = height - 90 | |
| for line in summary.split('\n'): | |
| if line.strip(): | |
| c.drawString(70, y, line.strip()) | |
| y -= 20 | |
| if y < 50: | |
| c.showPage() | |
| y = height - 50 | |
| c.showPage() | |
| # Object Counts section | |
| c.setFont("Helvetica-Bold", 16) | |
| c.drawString(50, height - 60, "Object Counts") | |
| c.setFont("Helvetica", 12) | |
| y = height - 90 | |
| data = [["Object", "Count"]] | |
| lines = [line for line in summary.split('\n') if line.strip().startswith("- ") and "Object Counts" not in line] | |
| counts = {} | |
| for line in lines: | |
| if ": " in line: | |
| key, value = line.split(": ", 1) | |
| key = key.replace("-", "").strip() | |
| try: | |
| counts[key] = int(value) | |
| except ValueError: | |
| counts[key] = 0 | |
| for obj in ["Motorcycles", "Persons", "Cars", "Traffic Lights", "Signs", "Trucks"]: | |
| data.append([obj, counts.get(obj, 0)]) | |
| table = Table(data, colWidths=[150, 50], rowHeights=25) | |
| table.setStyle(TableStyle([ | |
| ('BACKGROUND', (0, 0), (-1, 0), '#ECF0F1'), | |
| ('TEXTCOLOR', (0, 0), (-1, 0), '#2C3E50'), | |
| ('ALIGN', (0, 0), (-1, -1), 'CENTER'), | |
| ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), | |
| ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'), | |
| ('FONTSIZE', (0, 0), (-1, -1), 12), | |
| ('BOTTOMPADDING', (0, 0), (-1, 0), 12), | |
| ('BACKGROUND', (0, 1), (-1, -1), '#FFFFFF'), | |
| ('GRID', (0, 0), (-1, -1), 1, '#BDC3C7') | |
| ])) | |
| table.wrapOn(c, width, height) | |
| table.drawOn(c, 50, y - 180) | |
| y -= 230 | |
| c.showPage() | |
| # Charts section | |
| if os.path.exists(circle_path): | |
| c.setFont("Helvetica-Bold", 16) | |
| c.drawString(50, height - 60, "Visualizations") | |
| c.setFont("Helvetica", 12) | |
| y = height - 90 | |
| # Pedestrian Confidence Metric | |
| if os.path.exists(circle_path): | |
| img = ImageReader(circle_path) | |
| img_width = 200 | |
| img_height = img_width * (img.getSize()[1] / img.getSize()[0]) | |
| c.drawImage(circle_path, 50, y - img_height, width=img_width, height=img_height) | |
| c.drawString(50, y - img_height - 20, "Pedestrian Confidence Metric") | |
| y -= img_height + 40 | |
| # Object Distribution Pie Chart | |
| pie_chart_path = [s for s in screenshots if "pie_chart" in s.lower()] | |
| if pie_chart_path: | |
| pie_chart_path = pie_chart_path[0] | |
| if os.path.exists(pie_chart_path): | |
| img = ImageReader(pie_chart_path) | |
| img_width = 500 | |
| img_height = img_width * (img.getSize()[1] / img.getSize()[0]) | |
| if y - img_height < 50: | |
| c.showPage() | |
| y = height - 50 | |
| c.drawImage(pie_chart_path, 50, y - img_height, width=img_width, height=img_height) | |
| c.drawString(50, y - img_height - 20, "Object Distribution Pie Chart") | |
| y -= img_height + 40 | |
| c.showPage() | |
| # Incident Screenshots section (top 15 HD) | |
| if screenshots: | |
| c.setFont("Helvetica-Bold", 16) | |
| c.drawString(50, height - 60, "Top 15 HD Incident Screenshots") | |
| c.setFont("Helvetica", 12) | |
| y = height - 90 | |
| for i, screenshot in enumerate(screenshots[:15]): | |
| if os.path.exists(screenshot) and "pie_chart" not in screenshot.lower() and "circle" not in screenshot.lower(): | |
| img = ImageReader(screenshot) | |
| img_width = 350 | |
| img_height = img_width * (img.getSize()[1] / img.getSize()[0]) | |
| if y - img_height < 50: | |
| c.showPage() | |
| y = height - 50 | |
| c.drawImage(screenshot, 50, y - img_height, width=img_width, height=img_height) | |
| c.drawString(50, y - img_height - 20, f"Incident {i+1}") | |
| y -= img_height + 40 | |
| # Video ZIP download section | |
| if os.path.exists(zip_path): | |
| c.showPage() | |
| c.setFont("Helvetica-Bold", 16) | |
| c.drawString(50, height - 60, "Download Annotated HD Video") | |
| c.setFont("Helvetica", 12) | |
| c.drawString(70, height - 90, f"Download the annotated video: {os.path.basename(zip_path)}") | |
| c.save() |