Generate DICOM PDF
This commit is contained in:
parent
1ac99bdcfa
commit
2110aac355
@ -95,7 +95,14 @@ def apply(retina_net, dicom,
|
|||||||
instance_number = 1
|
instance_number = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sr_object.StudyDate = dicom.StudyDate
|
||||||
|
sr_object.StudyTime = dicom.StudyTime
|
||||||
sr_object.SeriesDate = datetime.now().strftime("%Y%m%d")
|
sr_object.SeriesDate = datetime.now().strftime("%Y%m%d")
|
||||||
sr_object.SeriesTime = datetime.now().strftime("%H%M%S")
|
sr_object.SeriesTime = datetime.now().strftime("%H%M%S")
|
||||||
|
sr_object.PatientID = dicom.PatientID
|
||||||
|
sr_object.PatientName = dicom.PatientName
|
||||||
|
sr_object.PatientSex = dicom.PatientSex
|
||||||
|
sr_object.PatientBirthDate = dicom.PatientBirthDate
|
||||||
|
sr_object.ReferringPhysicianName = sr_object.ReferringPhysicianName
|
||||||
|
|
||||||
return sr_object
|
return sr_object
|
||||||
|
|||||||
163
dicom_sr_to_pdf.py
Normal file
163
dicom_sr_to_pdf.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
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 # Now import pyplot
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
from datetime import datetime, date
|
||||||
|
|
||||||
|
|
||||||
|
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(image, measurements, probabilities):
|
||||||
|
"""Overlays extracted measurements onto the mammography image."""
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.imshow(image, 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("temp.png", bbox_inches='tight', pad_inches=0)
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def create_pdf(temp_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 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(temp_image_path, 70, 300)
|
||||||
|
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
# Convert DICOM date
|
||||||
|
def formateted_datetime(dicom_date, dicom_time = None):
|
||||||
|
|
||||||
|
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):
|
||||||
|
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 = generate_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(image, sr):
|
||||||
|
measurements, probabilities = extract_measurements(sr)
|
||||||
|
overlay_measurements(image, measurements, probabilities)
|
||||||
|
create_pdf("temp.png", measurements, sr, "temp.pdf")
|
||||||
|
return create_dcm_pdf(sr, "temp.pdf",)
|
||||||
@ -100,6 +100,7 @@ import pydicom
|
|||||||
sys.path.append(os.path.join(SCRIPT_DIR, '..'))
|
sys.path.append(os.path.join(SCRIPT_DIR, '..'))
|
||||||
import model
|
import model
|
||||||
import dicom_sr
|
import dicom_sr
|
||||||
|
import dicom_sr_to_pdf
|
||||||
|
|
||||||
orthanc.LogWarning('Loading the RetinaNet model for mammography')
|
orthanc.LogWarning('Loading the RetinaNet model for mammography')
|
||||||
my_retina_net = model.load_retina_net()
|
my_retina_net = model.load_retina_net()
|
||||||
@ -126,6 +127,7 @@ def execute_inference(output, uri, **request):
|
|||||||
output.SendHttpStatusCode(400)
|
output.SendHttpStatusCode(400)
|
||||||
else:
|
else:
|
||||||
result = dicom_sr.apply(my_retina_net, dicom, minimum_score=0.2)
|
result = dicom_sr.apply(my_retina_net, dicom, minimum_score=0.2)
|
||||||
|
pdf = dicom_sr_to_pdf.create(dicom.pixel_array, result)
|
||||||
|
|
||||||
with io.BytesIO() as f:
|
with io.BytesIO() as f:
|
||||||
pydicom.dcmwrite(f, result)
|
pydicom.dcmwrite(f, result)
|
||||||
@ -134,4 +136,11 @@ def execute_inference(output, uri, **request):
|
|||||||
|
|
||||||
output.AnswerBuffer(orthanc.RestApiPost('/instances', content), 'application/json')
|
output.AnswerBuffer(orthanc.RestApiPost('/instances', content), 'application/json')
|
||||||
|
|
||||||
|
with io.BytesIO() as f:
|
||||||
|
pydicom.dcmwrite(f, pdf)
|
||||||
|
f.seek(0)
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
output.AnswerBuffer(orthanc.RestApiPost('/instances', content), 'application/json')
|
||||||
|
|
||||||
orthanc.RegisterRestCallback('/mammography-apply', execute_inference)
|
orthanc.RegisterRestCallback('/mammography-apply', execute_inference)
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
highdicom==0.22.0
|
highdicom==0.22.0
|
||||||
numpy==2.1.0
|
numpy==1.24.0
|
||||||
opencv-python==4.10.0.84
|
opencv-python==4.10.0.84
|
||||||
pydicom==2.4.4
|
pydicom==2.4.4
|
||||||
torch==2.3.0
|
torch==2.4.0
|
||||||
torchaudio==2.3.0
|
torchaudio==2.4.0
|
||||||
torchvision==0.18.0
|
torchvision==0.19.0
|
||||||
|
reportlab==4.3.1
|
||||||
|
matplotlib==3.10.0
|
||||||
Loading…
x
Reference in New Issue
Block a user