from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter from reportlab.lib.utils import ImageReader from io import BytesIO from models.schemas import MeasurementMetadata, MeasurementResult from PIL import Image, ImageDraw import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class PdfService: """ A service to generate PDF reports. """ def create_report( self, image_bytes: bytes, metadata: MeasurementMetadata, results: MeasurementResult, ml_results: dict ) -> bytes: """ Generates a PDF report with the measurement data. """ logger.info(f"Creating PDF with metadata: {metadata}") logger.info(f"Creating PDF with results: {results}") buffer = BytesIO() p = canvas.Canvas(buffer, pagesize=letter) width, height = letter # Title p.setFont("Helvetica-Bold", 16) p.drawString(72, height - 72, "Ship Draft Measurement Report") # Metadata p.setFont("Helvetica", 12) p.drawString(72, height - 108, f"Ship ID: {metadata.ship_id}") p.drawString(72, height - 126, f"Timestamp: {metadata.timestamp.strftime('%Y-%m-%d %H:%M:%S')}") p.drawString(72, height - 144, f"Latitude: {metadata.latitude}") p.drawString(72, height - 162, f"Longitude: {metadata.longitude}") # # Measurement Results # p.setFont("Helvetica-Bold", 14) # p.drawString(72, height - 198, "Measurement Results") # p.setFont("Helvetica", 12) # p.drawString(90, height - 218, f"Draft Measurement: {results.draft_measurement:.1f} meters") # p.drawString(90, height - 236, f"Confidence Score: {results.confidence_score:.2%}") # ML Results p.setFont("Helvetica-Bold", 14) y_position = height - 270 p.drawString(72, y_position, "ML Results") y_position -= 18 p.setFont("Helvetica", 12) # Highlight draft from ML results if 'draft' in ml_results: p.setFont("Helvetica-Bold", 12) # Highlight p.drawString(90, y_position, f"ML Draft: {ml_results['draft']:.2f} meters") p.setFont("Helvetica", 12) # Reset font y_position -= 18 # Add other ML results (excluding arrays and images) for key, value in ml_results.items(): if key in ['pose_results', 'segment_results', 'original_image', 'pose_image_result', 'segment_image_result', 'final_image_result']: continue p.drawString(90, y_position, f"{key.replace('_', ' ').title()}: {value}") y_position -= 18 # Images p.setFont("Helvetica-Bold", 14) y_position -= 18 p.drawString(72, y_position, "Images") y_position -= 18 p.setFont("Helvetica", 12) # Convert numpy arrays to PIL Image and then to bytes for ReportLab def get_image_bytes(np_array): if np_array is None: return None img = Image.fromarray(np_array.astype('uint8')) img_byte_arr = BytesIO() img.save(img_byte_arr, format='PNG') return img_byte_arr.getvalue() # Define starting positions and dimensions for horizontal layout x_start = 72 image_width = 150 image_height = 150 # Assuming square or adjust as needed y_image_row = y_position - image_height - 20 # Position for the bottom of the images current_x = x_start # Draw original image if 'original_image' in ml_results and ml_results['original_image'] is not None: original_img_bytes = get_image_bytes(ml_results['original_image']) if original_img_bytes: p.drawString(current_x, y_image_row + image_height + 5, "Original Image:") # Label above image p.drawImage(ImageReader(BytesIO(original_img_bytes)), current_x, y_image_row, width=image_width, height=image_height, preserveAspectRatio=True) current_x += image_width + 20 # Move x for next image # Draw pose image result if 'pose_image_result' in ml_results and ml_results['pose_image_result'] is not None: pose_img_bytes = get_image_bytes(ml_results['pose_image_result']) if pose_img_bytes: p.drawString(current_x, y_image_row + image_height + 5, "Pose Image Result:") p.drawImage(ImageReader(BytesIO(pose_img_bytes)), current_x, y_image_row, width=image_width, height=image_height, preserveAspectRatio=True) current_x += image_width + 20 # Draw segment image result if 'segment_image_result' in ml_results and ml_results['segment_image_result'] is not None: segment_img_bytes = get_image_bytes(ml_results['segment_image_result']) if segment_img_bytes: p.drawString(current_x, y_image_row + image_height + 5, "Segment Image Result:") p.drawImage(ImageReader(BytesIO(segment_img_bytes)), current_x, y_image_row, width=image_width, height=image_height, preserveAspectRatio=True) # No need to update current_x as it's the last image in the row # Update y_position for content after images y_position = y_image_row - 20 # Adjust y_position to be below the images p.showPage() p.save() pdf_bytes = buffer.getvalue() buffer.close() return pdf_bytes