import pydicom from pydicom.dataset import Dataset from pydicom.dataset import FileMetaDataset from pydicom.uid import MediaStorageDirectoryStorage, EncapsulatedPDFStorage, generate_uid import matplotlib matplotlib.use("Agg") # Use non-GUI backend to avoid Tkinter issues import matplotlib.pyplot as plt from reportlab.pdfgen import canvas from datetime import datetime, date import os def extract_measurements(sr): """Extracts measurement annotations from an SR.""" measurements = [] probabilities = [] if "ContentSequence" in sr: for itemLevel1 in sr.ContentSequence: if len(itemLevel1.ConceptNameCodeSequence) == 1: if itemLevel1.ConceptNameCodeSequence[0].CodeMeaning == "Imaging Measurements": for itemLevel2 in itemLevel1.ContentSequence: for itemLevel3 in itemLevel2.ContentSequence: if itemLevel3.ValueType == "SCOORD": measurements.append(itemLevel3.GraphicData) elif itemLevel3.ValueType == "NUM": if len(itemLevel3.MeasuredValueSequence) == 1: probabilities.append(itemLevel3.MeasuredValueSequence[0].NumericValue) return measurements, probabilities def overlay_measurements(dcm_image_pixels, measurements, probabilities, image_path): """Overlays extracted measurements onto the mammography image.""" fig, ax = plt.subplots() ax.imshow(dcm_image_pixels, cmap='gray') # Draw each polyline for i in range(0, len(measurements), 1): measurement = measurements[i] x = measurement[0::2] # Extract x-coordinates (every other value) y = measurement[1::2] # Extract y-coordinates (every other value) ax.plot(x, y, 'lime', linewidth=1) # Plot the entire polyline at once ax.text(x[-3] + 100, y[-3], f"{probabilities[i]:.2f} %", color='lime', fontsize=8) ax.axis("off") # Save the overlay as an image plt.savefig(image_path, bbox_inches='tight', pad_inches=0) plt.close(fig) def create_pdf(image_path, measurements, sr, pdf_path): """Creates a PDF with the mammography image and extracted measurements.""" c = canvas.Canvas(pdf_path) # Set font for the title c.setFont("Helvetica-Bold", 16) # Get page width to center the title page_width = 595 # Default A4 width in points title = "Mammography Report" c.drawCentredString(page_width / 2, 820, title) # Adjust Y-position as needed # Reset font for other text c.setFont("Helvetica", 12) # Add patient and study info to the PDF c.drawString(70, 800, f"Patient ID: {sr.PatientID}") c.drawString(70, 785, f"Patient name: {sr.PatientName}") c.drawString(70, 770, f"Patient birth date: {formateted_datetime(sr.PatientBirthDate)}") c.drawString(70, 755, f"Patient sex: {sr.PatientSex}") c.drawString(70, 730, f"Study date: {formateted_datetime(sr.StudyDate, sr.StudyTime)}") c.drawString(70, 715, f"Report date: {formateted_datetime(sr.SeriesDate, sr.SeriesTime)}") c.drawString(70, 700, f"Referring physician: {sr.ReferringPhysicianName}") # Add the image to the PDF c.drawImage(image_path, 70, 300) c.save() def formateted_datetime(dicom_date, dicom_time = None): """Convert DICOM date and time format.""" if dicom_date is None or dicom_date == '': return '' # Convert DICOM date formatted_date = datetime.strptime(dicom_date, "%Y%m%d").strftime("%Y-%m-%d") if dicom_time is None or dicom_time == '': return formatted_date # Convert DICOM time (handling optional fractions of a second) if "." in dicom_time: formatted_time = datetime.strptime(dicom_time, "%H%M%S.%f").strftime("%H:%M:%S.%f")[:-3] # Keep milliseconds else: formatted_time = datetime.strptime(dicom_time, "%H%M%S").strftime("%H:%M:%S") # Combined datetime return f"{formatted_date} {formatted_time}" def create_dcm_pdf(sr, pdf_path, instance_uid): ds = Dataset() # Add general DICOM metadata ds.PatientName = sr.PatientName ds.PatientID = sr.PatientID ds.PatientBirthDate = sr.PatientBirthDate ds.PatientSex = sr.PatientSex ds.StudyInstanceUID = sr.StudyInstanceUID ds.StudyDate = sr.StudyDate ds.StudyTime = sr.StudyTime ds.AccessionNumber = sr.AccessionNumber ds.ReferringPhysicianName = sr.ReferringPhysicianName ds.StudyID = sr.StudyID ds.SeriesInstanceUID = generate_uid() ds.SeriesDate = sr.SeriesDate ds.SeriesTime = sr.SeriesTime ds.SeriesNumber = 1 ds.Modality = "DOC" ds.Manufacturer = "MammographyAI" ds.ConversionType = "DI" ds.SOPInstanceUID = instance_uid ds.SOPClassUID = EncapsulatedPDFStorage # Open the PDF file and read it as binary data with open(pdf_path, 'rb') as f: pdf_data = f.read() # Add the EncapsulatedDocument (PDF content) to the DICOM dataset ds.ContentDate = ds.SeriesDate ds.ContentTime = ds.SeriesTime ds.AcquisitionDateTime = "" ds.InstanceNumber = 1 ds.BurnedInAnnotation = "YES" ds.DocumentTitle = "" ds.EncapsulatedDocument = pdf_data ds.MIMETypeOfEncapsulatedDocument = "application/pdf" # Create a FileMetaDataset for DICOM file meta information file_meta = FileMetaDataset() file_meta.MediaStorageSOPClassUID = EncapsulatedPDFStorage file_meta.MediaStorageSOPInstanceUID = ds.SOPInstanceUID file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian file_meta.FileMetaInformationGroupLength = 0 # Assign the file meta information to the dataset ds.file_meta = file_meta # Ensure preamble and "DICM" prefix is included ds.is_implicit_VR = True # Set to explicit VR ds.is_little_endian = True # Set to little endian return ds def create(dcm_image_pixels, sr): instance_uid = generate_uid() temp_image_path = f"{instance_uid}.png" temp_pdf_path = f"{instance_uid}.pdf" measurements, probabilities = extract_measurements(sr) overlay_measurements(dcm_image_pixels, measurements, probabilities, temp_image_path) create_pdf(temp_image_path, measurements, sr, temp_pdf_path) dcm_pdf = create_dcm_pdf(sr, temp_pdf_path, instance_uid) print("Current Working Directory:", os.getcwd()) if os.path.exists(temp_image_path): os.remove(temp_image_path) if os.path.exists(temp_pdf_path): os.remove(temp_pdf_path) return dcm_pdf